Skip to content

Commit

Permalink
feat(BytesArrayUtils): Add BytesArrayUtils library (SetProtocol#248)
Browse files Browse the repository at this point in the history
* Add BytesLibUtils library

* Rename to BytesArrayUtils; Add mock contract

* Add unit tests

* Use BytesArrayUtils in UniswapV3ExchangeAdapterV2

* Add missing import

* Add BytesLib path to javadocs
  • Loading branch information
Sachin authored Apr 14, 2022
1 parent 2e5bad8 commit 7b35392
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 90 deletions.
49 changes: 49 additions & 0 deletions contracts/lib/BytesArrayUtils.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
31 changes: 31 additions & 0 deletions contracts/mocks/BytesArrayUtilsMock.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";

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

using BytesLib for bytes;
using BytesArrayUtils for bytes;

/* ============ State Variables ============ */

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
106 changes: 106 additions & 0 deletions test/lib/bytesArrayUtils.spec.ts
Original file line number Diff line number Diff line change
@@ -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<boolean> {
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");
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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<boolean> {
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");
});
});
});
});
1 change: 1 addition & 0 deletions utils/contracts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
6 changes: 6 additions & 0 deletions utils/deploys/deployMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
AaveLendingPoolMock,
AddressArrayUtilsMock,
AmmAdapterMock,
BytesArrayUtilsMock,
ChainlinkAggregatorMock,
ClaimAdapterMock,
ContractCallerMock,
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -463,6 +465,10 @@ export default class DeployMocks {
return await new StringArrayUtilsMock__factory(this._deployerSigner).deploy();
}

public async deployBytesArrayUtilsMock(): Promise<BytesArrayUtilsMock> {
return await new BytesArrayUtilsMock__factory(this._deployerSigner).deploy();
}

/** ***********************************
* Instance getters
************************************/
Expand Down

0 comments on commit 7b35392

Please sign in to comment.