From 7b353920e99400697f1708527541f0b52cdadbb2 Mon Sep 17 00:00:00 2001 From: Sachin Date: Thu, 14 Apr 2022 20:15:51 +0530 Subject: [PATCH] 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 --- contracts/lib/BytesArrayUtils.sol | 49 ++++++++ contracts/mocks/BytesArrayUtilsMock.sol | 31 +++++ .../exchange/UniswapV3ExchangeAdapterV2.sol | 22 +--- test/lib/bytesArrayUtils.spec.ts | 106 ++++++++++++++++++ .../uniswapV3ExchangeAdapterV2.spec.ts | 72 +----------- utils/contracts/index.ts | 1 + utils/deploys/deployMocks.ts | 6 + 7 files changed, 197 insertions(+), 90 deletions(-) create mode 100644 contracts/lib/BytesArrayUtils.sol create mode 100644 contracts/mocks/BytesArrayUtilsMock.sol create mode 100644 test/lib/bytesArrayUtils.spec.ts diff --git a/contracts/lib/BytesArrayUtils.sol b/contracts/lib/BytesArrayUtils.sol new file mode 100644 index 000000000..913446042 --- /dev/null +++ b/contracts/lib/BytesArrayUtils.sol @@ -0,0 +1,49 @@ +/* + Copyright 2022 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + SPDX-License-Identifier: Apache License, Version 2.0 +*/ + +pragma solidity 0.6.10; + +/** + * @title BytesArrayUtils + * @author Set Protocol + * + * Utility library to type cast bytes arrays. Extends BytesLib (external/contracts/uniswap/v3/lib/BytesLib.sol) + * library functionality. + */ +library BytesArrayUtils { + + /** + * Type cast byte to boolean. + * @param _bytes Bytes array + * @param _start Starting index + * @return bool Boolean value + */ + function toBool(bytes memory _bytes, uint256 _start) internal pure returns (bool) { + require(_start + 1 >= _start, "toBool_overflow"); + require(_bytes.length >= _start + 1, "toBool_outOfBounds"); + uint8 tempUint; + + assembly { + tempUint := mload(add(add(_bytes, 0x1), _start)) + } + + require(tempUint <= 1, "Invalid bool data"); // Should be either 0 or 1 + + return (tempUint == 0) ? false : true; + } +} \ No newline at end of file diff --git a/contracts/mocks/BytesArrayUtilsMock.sol b/contracts/mocks/BytesArrayUtilsMock.sol new file mode 100644 index 000000000..9f0558bb7 --- /dev/null +++ b/contracts/mocks/BytesArrayUtilsMock.sol @@ -0,0 +1,31 @@ +/* + Copyright 2022 Set Labs Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + SPDX-License-Identifier: Apache License, Version 2.0 +*/ + +pragma solidity 0.6.10; +pragma experimental "ABIEncoderV2"; + +import { BytesArrayUtils } from "../lib/BytesArrayUtils.sol"; + + +contract BytesArrayUtilsMock { + using BytesArrayUtils for bytes; + + function testToBool(bytes memory _bytes, uint256 _start) external pure returns (bool) { + return _bytes.toBool(_start); + } +} diff --git a/contracts/protocol/integration/exchange/UniswapV3ExchangeAdapterV2.sol b/contracts/protocol/integration/exchange/UniswapV3ExchangeAdapterV2.sol index 96b73c244..a0ba5ab1b 100644 --- a/contracts/protocol/integration/exchange/UniswapV3ExchangeAdapterV2.sol +++ b/contracts/protocol/integration/exchange/UniswapV3ExchangeAdapterV2.sol @@ -19,6 +19,7 @@ pragma solidity 0.6.10; pragma experimental "ABIEncoderV2"; +import { BytesArrayUtils } from "../../../lib/BytesArrayUtils.sol"; import { BytesLib } from "../../../../external/contracts/uniswap/v3/lib/BytesLib.sol"; import { ISwapRouter } from "../../../interfaces/external/ISwapRouter.sol"; @@ -35,6 +36,7 @@ import { ISwapRouter } from "../../../interfaces/external/ISwapRouter.sol"; contract UniswapV3ExchangeAdapterV2 { using BytesLib for bytes; + using BytesArrayUtils for bytes; /* ============ State Variables ============ */ @@ -87,7 +89,7 @@ contract UniswapV3ExchangeAdapterV2 { // For multi-hop trades, `_data.length` is greater than 44. require(_data.length >= 44, "Invalid data"); - bool fixInput = toBool(_data, _data.length - 1); // `fixInput` bool is stored at last byte + bool fixInput = _data.toBool(_data.length - 1); // `fixInput` bool is stored at last byte address sourceFromPath; address destinationFromPath; @@ -167,22 +169,4 @@ contract UniswapV3ExchangeAdapterV2 { // Encode fixIn return abi.encodePacked(data, _fixIn); } - - /** - * Helper function to decode bytes to boolean. Similar to functions found in BytesLib. - * Note: Access modifier is set to public to enable complete testing. - */ - function toBool(bytes memory _bytes, uint256 _start) public pure returns (bool) { - require(_start + 1 >= _start, "toBool_overflow"); - require(_bytes.length >= _start + 1, "toBool_outOfBounds"); - uint8 tempUint; - - assembly { - tempUint := mload(add(add(_bytes, 0x1), _start)) - } - - require(tempUint <= 1, "Invalid bool data"); // Should be either 0 or 1 - - return (tempUint == 0) ? false : true; - } } \ No newline at end of file diff --git a/test/lib/bytesArrayUtils.spec.ts b/test/lib/bytesArrayUtils.spec.ts new file mode 100644 index 000000000..a4117fcd5 --- /dev/null +++ b/test/lib/bytesArrayUtils.spec.ts @@ -0,0 +1,106 @@ +import "module-alias/register"; +import { BigNumber } from "ethers"; +import { solidityPack } from "ethers/lib/utils"; + +import { Address, Bytes } from "@utils/types"; +import { Account } from "@utils/test/types"; +import { MAX_UINT_256 } from "@utils/constants"; +import { BytesArrayUtilsMock } from "@utils/contracts"; +import DeployHelper from "@utils/deploys"; +import { + addSnapshotBeforeRestoreAfterEach, + getAccounts, + getWaffleExpect, + getRandomAddress +} from "@utils/test/index"; + +const expect = getWaffleExpect(); + +describe("BytesArrayUtils", () => { + let owner: Account; + let deployer: DeployHelper; + + let bytesArrayUtils: BytesArrayUtilsMock; + + + before(async () => { + [ + owner, + ] = await getAccounts(); + + deployer = new DeployHelper(owner.wallet); + bytesArrayUtils = await deployer.mocks.deployBytesArrayUtilsMock(); + }); + + addSnapshotBeforeRestoreAfterEach(); + + describe("#toBool", async () => { + let bool: boolean; + let randomAddress: Address; + + let subjectBytes: Bytes; + let subjectStart: BigNumber; + + before(async () => { + randomAddress = await getRandomAddress(); + }); + + beforeEach(async() => { + bool = true; + + subjectBytes = solidityPack( + ["address", "bool"], + [randomAddress, bool] + ); + subjectStart = BigNumber.from(20); // Address is 20 bytes long + }); + + async function subject(): Promise { + return await bytesArrayUtils.testToBool(subjectBytes, subjectStart); + } + + it("should return correct bool", async () => { + const actualBool = await subject(); + + expect(actualBool).to.eq(bool); + }); + + describe("when bool is false", async () => { + beforeEach(async() => { + bool = false; + + subjectBytes = solidityPack( + ["address", "bool"], + [randomAddress, bool] + ); + }); + + it("should return correct bool", async () => { + const actualBool = await subject(); + + expect(actualBool).to.eq(bool); + }); + }); + + describe("when start is max uint 256", async () => { + beforeEach(() => { + subjectStart = MAX_UINT_256; + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("toBool_overflow"); + }); + }); + + + describe("when start is out of bounds", async () => { + beforeEach(() => { + subjectStart = BigNumber.from(subjectBytes.length); + }); + + it("should revert", async () => { + await expect(subject()).to.be.revertedWith("toBool_outOfBounds"); + }); + }); + }); +}); diff --git a/test/protocol/integration/exchange/uniswapV3ExchangeAdapterV2.spec.ts b/test/protocol/integration/exchange/uniswapV3ExchangeAdapterV2.spec.ts index dbd1e897f..b0e0aff98 100644 --- a/test/protocol/integration/exchange/uniswapV3ExchangeAdapterV2.spec.ts +++ b/test/protocol/integration/exchange/uniswapV3ExchangeAdapterV2.spec.ts @@ -4,7 +4,7 @@ import { solidityPack } from "ethers/lib/utils"; import { Address, Bytes } from "@utils/types"; import { Account } from "@utils/test/types"; -import { MAX_UINT_256, ZERO } from "@utils/constants"; +import { ZERO } from "@utils/constants"; import { UniswapV3ExchangeAdapterV2 } from "@utils/contracts"; import DeployHelper from "@utils/deploys"; import { ether } from "@utils/index"; @@ -271,74 +271,4 @@ describe("UniswapV3ExchangeAdapterV2", () => { }); }); }); - - describe("#toBool", async () => { - let bool: boolean; - let randomAddress: Address; - - let subjectBytes: Bytes; - let subjectStart: BigNumber; - - before(async () => { - randomAddress = await getRandomAddress(); - }); - - beforeEach(async() => { - bool = true; - - subjectBytes = solidityPack( - ["address", "bool"], - [randomAddress, bool] - ); - subjectStart = BigNumber.from(20); // Address is 20 bytes long - }); - - async function subject(): Promise { - return await uniswapV3ExchangeAdapter.toBool(subjectBytes, subjectStart); - } - - it("should return correct bool", async () => { - const actualBool = await subject(); - - expect(actualBool).to.eq(bool); - }); - - describe("when bool is false", async () => { - beforeEach(async() => { - bool = false; - - subjectBytes = solidityPack( - ["address", "bool"], - [randomAddress, bool] - ); - }); - - it("should return correct bool", async () => { - const actualBool = await subject(); - - expect(actualBool).to.eq(bool); - }); - }); - - describe("when start is max uint 256", async () => { - beforeEach(() => { - subjectStart = MAX_UINT_256; - }); - - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("toBool_overflow"); - }); - }); - - - describe("when start is out of bounds", async () => { - beforeEach(() => { - subjectStart = BigNumber.from(subjectBytes.length); - }); - - it("should revert", async () => { - await expect(subject()).to.be.revertedWith("toBool_outOfBounds"); - }); - }); - }); }); \ No newline at end of file diff --git a/utils/contracts/index.ts b/utils/contracts/index.ts index 59e562cee..ea0f05978 100644 --- a/utils/contracts/index.ts +++ b/utils/contracts/index.ts @@ -12,6 +12,7 @@ export { AmmModule } from "../../typechain/AmmModule"; export { AssetLimitHook } from "../../typechain/AssetLimitHook"; export { BalancerV1IndexExchangeAdapter } from "../../typechain/BalancerV1IndexExchangeAdapter"; export { BasicIssuanceModule } from "../../typechain/BasicIssuanceModule"; +export { BytesArrayUtilsMock } from "../../typechain/BytesArrayUtilsMock"; export { ChainlinkAggregatorMock } from "../../typechain/ChainlinkAggregatorMock"; export { ClaimAdapterMock } from "../../typechain/ClaimAdapterMock"; export { ClaimModule } from "../../typechain/ClaimModule"; diff --git a/utils/deploys/deployMocks.ts b/utils/deploys/deployMocks.ts index 923c6f2fe..09356fcf2 100644 --- a/utils/deploys/deployMocks.ts +++ b/utils/deploys/deployMocks.ts @@ -6,6 +6,7 @@ import { AaveLendingPoolMock, AddressArrayUtilsMock, AmmAdapterMock, + BytesArrayUtilsMock, ChainlinkAggregatorMock, ClaimAdapterMock, ContractCallerMock, @@ -66,6 +67,7 @@ import { AaveLendingPoolCoreMock__factory } from "../../typechain/factories/Aave import { AaveLendingPoolMock__factory } from "../../typechain/factories/AaveLendingPoolMock__factory"; import { AddressArrayUtilsMock__factory } from "../../typechain/factories/AddressArrayUtilsMock__factory"; import { AmmAdapterMock__factory } from "../../typechain/factories/AmmAdapterMock__factory"; +import { BytesArrayUtilsMock__factory } from "../../typechain/factories/BytesArrayUtilsMock__factory"; import { ChainlinkAggregatorMock__factory } from "../../typechain/factories/ChainlinkAggregatorMock__factory"; import { ClaimAdapterMock__factory } from "../../typechain/factories/ClaimAdapterMock__factory"; import { CompoundMock__factory } from "../../typechain/factories/CompoundMock__factory"; @@ -463,6 +465,10 @@ export default class DeployMocks { return await new StringArrayUtilsMock__factory(this._deployerSigner).deploy(); } + public async deployBytesArrayUtilsMock(): Promise { + return await new BytesArrayUtilsMock__factory(this._deployerSigner).deploy(); + } + /** *********************************** * Instance getters ************************************/