Skip to content

Commit 7b35392

Browse files
author
Sachin
authored
feat(BytesArrayUtils): Add BytesArrayUtils library (#248)
* Add BytesLibUtils library * Rename to BytesArrayUtils; Add mock contract * Add unit tests * Use BytesArrayUtils in UniswapV3ExchangeAdapterV2 * Add missing import * Add BytesLib path to javadocs
1 parent 2e5bad8 commit 7b35392

File tree

7 files changed

+197
-90
lines changed

7 files changed

+197
-90
lines changed

contracts/lib/BytesArrayUtils.sol

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
Copyright 2022 Set Labs Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache License, Version 2.0
17+
*/
18+
19+
pragma solidity 0.6.10;
20+
21+
/**
22+
* @title BytesArrayUtils
23+
* @author Set Protocol
24+
*
25+
* Utility library to type cast bytes arrays. Extends BytesLib (external/contracts/uniswap/v3/lib/BytesLib.sol)
26+
* library functionality.
27+
*/
28+
library BytesArrayUtils {
29+
30+
/**
31+
* Type cast byte to boolean.
32+
* @param _bytes Bytes array
33+
* @param _start Starting index
34+
* @return bool Boolean value
35+
*/
36+
function toBool(bytes memory _bytes, uint256 _start) internal pure returns (bool) {
37+
require(_start + 1 >= _start, "toBool_overflow");
38+
require(_bytes.length >= _start + 1, "toBool_outOfBounds");
39+
uint8 tempUint;
40+
41+
assembly {
42+
tempUint := mload(add(add(_bytes, 0x1), _start))
43+
}
44+
45+
require(tempUint <= 1, "Invalid bool data"); // Should be either 0 or 1
46+
47+
return (tempUint == 0) ? false : true;
48+
}
49+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
Copyright 2022 Set Labs Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
16+
SPDX-License-Identifier: Apache License, Version 2.0
17+
*/
18+
19+
pragma solidity 0.6.10;
20+
pragma experimental "ABIEncoderV2";
21+
22+
import { BytesArrayUtils } from "../lib/BytesArrayUtils.sol";
23+
24+
25+
contract BytesArrayUtilsMock {
26+
using BytesArrayUtils for bytes;
27+
28+
function testToBool(bytes memory _bytes, uint256 _start) external pure returns (bool) {
29+
return _bytes.toBool(_start);
30+
}
31+
}

contracts/protocol/integration/exchange/UniswapV3ExchangeAdapterV2.sol

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
pragma solidity 0.6.10;
2020
pragma experimental "ABIEncoderV2";
2121

22+
import { BytesArrayUtils } from "../../../lib/BytesArrayUtils.sol";
2223
import { BytesLib } from "../../../../external/contracts/uniswap/v3/lib/BytesLib.sol";
2324
import { ISwapRouter } from "../../../interfaces/external/ISwapRouter.sol";
2425

@@ -35,6 +36,7 @@ import { ISwapRouter } from "../../../interfaces/external/ISwapRouter.sol";
3536
contract UniswapV3ExchangeAdapterV2 {
3637

3738
using BytesLib for bytes;
39+
using BytesArrayUtils for bytes;
3840

3941
/* ============ State Variables ============ */
4042

@@ -87,7 +89,7 @@ contract UniswapV3ExchangeAdapterV2 {
8789
// For multi-hop trades, `_data.length` is greater than 44.
8890
require(_data.length >= 44, "Invalid data");
8991

90-
bool fixInput = toBool(_data, _data.length - 1); // `fixInput` bool is stored at last byte
92+
bool fixInput = _data.toBool(_data.length - 1); // `fixInput` bool is stored at last byte
9193

9294
address sourceFromPath;
9395
address destinationFromPath;
@@ -167,22 +169,4 @@ contract UniswapV3ExchangeAdapterV2 {
167169
// Encode fixIn
168170
return abi.encodePacked(data, _fixIn);
169171
}
170-
171-
/**
172-
* Helper function to decode bytes to boolean. Similar to functions found in BytesLib.
173-
* Note: Access modifier is set to public to enable complete testing.
174-
*/
175-
function toBool(bytes memory _bytes, uint256 _start) public pure returns (bool) {
176-
require(_start + 1 >= _start, "toBool_overflow");
177-
require(_bytes.length >= _start + 1, "toBool_outOfBounds");
178-
uint8 tempUint;
179-
180-
assembly {
181-
tempUint := mload(add(add(_bytes, 0x1), _start))
182-
}
183-
184-
require(tempUint <= 1, "Invalid bool data"); // Should be either 0 or 1
185-
186-
return (tempUint == 0) ? false : true;
187-
}
188172
}

test/lib/bytesArrayUtils.spec.ts

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import "module-alias/register";
2+
import { BigNumber } from "ethers";
3+
import { solidityPack } from "ethers/lib/utils";
4+
5+
import { Address, Bytes } from "@utils/types";
6+
import { Account } from "@utils/test/types";
7+
import { MAX_UINT_256 } from "@utils/constants";
8+
import { BytesArrayUtilsMock } from "@utils/contracts";
9+
import DeployHelper from "@utils/deploys";
10+
import {
11+
addSnapshotBeforeRestoreAfterEach,
12+
getAccounts,
13+
getWaffleExpect,
14+
getRandomAddress
15+
} from "@utils/test/index";
16+
17+
const expect = getWaffleExpect();
18+
19+
describe("BytesArrayUtils", () => {
20+
let owner: Account;
21+
let deployer: DeployHelper;
22+
23+
let bytesArrayUtils: BytesArrayUtilsMock;
24+
25+
26+
before(async () => {
27+
[
28+
owner,
29+
] = await getAccounts();
30+
31+
deployer = new DeployHelper(owner.wallet);
32+
bytesArrayUtils = await deployer.mocks.deployBytesArrayUtilsMock();
33+
});
34+
35+
addSnapshotBeforeRestoreAfterEach();
36+
37+
describe("#toBool", async () => {
38+
let bool: boolean;
39+
let randomAddress: Address;
40+
41+
let subjectBytes: Bytes;
42+
let subjectStart: BigNumber;
43+
44+
before(async () => {
45+
randomAddress = await getRandomAddress();
46+
});
47+
48+
beforeEach(async() => {
49+
bool = true;
50+
51+
subjectBytes = solidityPack(
52+
["address", "bool"],
53+
[randomAddress, bool]
54+
);
55+
subjectStart = BigNumber.from(20); // Address is 20 bytes long
56+
});
57+
58+
async function subject(): Promise<boolean> {
59+
return await bytesArrayUtils.testToBool(subjectBytes, subjectStart);
60+
}
61+
62+
it("should return correct bool", async () => {
63+
const actualBool = await subject();
64+
65+
expect(actualBool).to.eq(bool);
66+
});
67+
68+
describe("when bool is false", async () => {
69+
beforeEach(async() => {
70+
bool = false;
71+
72+
subjectBytes = solidityPack(
73+
["address", "bool"],
74+
[randomAddress, bool]
75+
);
76+
});
77+
78+
it("should return correct bool", async () => {
79+
const actualBool = await subject();
80+
81+
expect(actualBool).to.eq(bool);
82+
});
83+
});
84+
85+
describe("when start is max uint 256", async () => {
86+
beforeEach(() => {
87+
subjectStart = MAX_UINT_256;
88+
});
89+
90+
it("should revert", async () => {
91+
await expect(subject()).to.be.revertedWith("toBool_overflow");
92+
});
93+
});
94+
95+
96+
describe("when start is out of bounds", async () => {
97+
beforeEach(() => {
98+
subjectStart = BigNumber.from(subjectBytes.length);
99+
});
100+
101+
it("should revert", async () => {
102+
await expect(subject()).to.be.revertedWith("toBool_outOfBounds");
103+
});
104+
});
105+
});
106+
});

test/protocol/integration/exchange/uniswapV3ExchangeAdapterV2.spec.ts

Lines changed: 1 addition & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { solidityPack } from "ethers/lib/utils";
44

55
import { Address, Bytes } from "@utils/types";
66
import { Account } from "@utils/test/types";
7-
import { MAX_UINT_256, ZERO } from "@utils/constants";
7+
import { ZERO } from "@utils/constants";
88
import { UniswapV3ExchangeAdapterV2 } from "@utils/contracts";
99
import DeployHelper from "@utils/deploys";
1010
import { ether } from "@utils/index";
@@ -271,74 +271,4 @@ describe("UniswapV3ExchangeAdapterV2", () => {
271271
});
272272
});
273273
});
274-
275-
describe("#toBool", async () => {
276-
let bool: boolean;
277-
let randomAddress: Address;
278-
279-
let subjectBytes: Bytes;
280-
let subjectStart: BigNumber;
281-
282-
before(async () => {
283-
randomAddress = await getRandomAddress();
284-
});
285-
286-
beforeEach(async() => {
287-
bool = true;
288-
289-
subjectBytes = solidityPack(
290-
["address", "bool"],
291-
[randomAddress, bool]
292-
);
293-
subjectStart = BigNumber.from(20); // Address is 20 bytes long
294-
});
295-
296-
async function subject(): Promise<boolean> {
297-
return await uniswapV3ExchangeAdapter.toBool(subjectBytes, subjectStart);
298-
}
299-
300-
it("should return correct bool", async () => {
301-
const actualBool = await subject();
302-
303-
expect(actualBool).to.eq(bool);
304-
});
305-
306-
describe("when bool is false", async () => {
307-
beforeEach(async() => {
308-
bool = false;
309-
310-
subjectBytes = solidityPack(
311-
["address", "bool"],
312-
[randomAddress, bool]
313-
);
314-
});
315-
316-
it("should return correct bool", async () => {
317-
const actualBool = await subject();
318-
319-
expect(actualBool).to.eq(bool);
320-
});
321-
});
322-
323-
describe("when start is max uint 256", async () => {
324-
beforeEach(() => {
325-
subjectStart = MAX_UINT_256;
326-
});
327-
328-
it("should revert", async () => {
329-
await expect(subject()).to.be.revertedWith("toBool_overflow");
330-
});
331-
});
332-
333-
334-
describe("when start is out of bounds", async () => {
335-
beforeEach(() => {
336-
subjectStart = BigNumber.from(subjectBytes.length);
337-
});
338-
339-
it("should revert", async () => {
340-
await expect(subject()).to.be.revertedWith("toBool_outOfBounds");
341-
});
342-
});
343-
});
344274
});

utils/contracts/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export { AmmModule } from "../../typechain/AmmModule";
1212
export { AssetLimitHook } from "../../typechain/AssetLimitHook";
1313
export { BalancerV1IndexExchangeAdapter } from "../../typechain/BalancerV1IndexExchangeAdapter";
1414
export { BasicIssuanceModule } from "../../typechain/BasicIssuanceModule";
15+
export { BytesArrayUtilsMock } from "../../typechain/BytesArrayUtilsMock";
1516
export { ChainlinkAggregatorMock } from "../../typechain/ChainlinkAggregatorMock";
1617
export { ClaimAdapterMock } from "../../typechain/ClaimAdapterMock";
1718
export { ClaimModule } from "../../typechain/ClaimModule";

utils/deploys/deployMocks.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
AaveLendingPoolMock,
77
AddressArrayUtilsMock,
88
AmmAdapterMock,
9+
BytesArrayUtilsMock,
910
ChainlinkAggregatorMock,
1011
ClaimAdapterMock,
1112
ContractCallerMock,
@@ -66,6 +67,7 @@ import { AaveLendingPoolCoreMock__factory } from "../../typechain/factories/Aave
6667
import { AaveLendingPoolMock__factory } from "../../typechain/factories/AaveLendingPoolMock__factory";
6768
import { AddressArrayUtilsMock__factory } from "../../typechain/factories/AddressArrayUtilsMock__factory";
6869
import { AmmAdapterMock__factory } from "../../typechain/factories/AmmAdapterMock__factory";
70+
import { BytesArrayUtilsMock__factory } from "../../typechain/factories/BytesArrayUtilsMock__factory";
6971
import { ChainlinkAggregatorMock__factory } from "../../typechain/factories/ChainlinkAggregatorMock__factory";
7072
import { ClaimAdapterMock__factory } from "../../typechain/factories/ClaimAdapterMock__factory";
7173
import { CompoundMock__factory } from "../../typechain/factories/CompoundMock__factory";
@@ -463,6 +465,10 @@ export default class DeployMocks {
463465
return await new StringArrayUtilsMock__factory(this._deployerSigner).deploy();
464466
}
465467

468+
public async deployBytesArrayUtilsMock(): Promise<BytesArrayUtilsMock> {
469+
return await new BytesArrayUtilsMock__factory(this._deployerSigner).deploy();
470+
}
471+
466472
/** ***********************************
467473
* Instance getters
468474
************************************/

0 commit comments

Comments
 (0)