@1inch/solidity-utils 1.2.0 → 1.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ <p align="center">
2
+ <img src="https://app.1inch.io/assets/images/logo.svg" width="200" alt="1inch network" />
3
+ </p>
4
+
5
+ # Utils library for contracts and tests
6
+
7
+ [![Build Status](https://github.com/1inch/solidity-utils/workflows/CI/badge.svg)](https://github.com/1inch/solidity-utils/actions)
8
+ [![Coverage Status](https://coveralls.io/repos/github/1inch/solidity-utils/badge.svg?branch=master)](https://coveralls.io/github/1inch/solidity-utils?branch=master)
9
+ [![NPM Package](https://img.shields.io/npm/v/@1inch/solidity-utils.svg)](https://www.npmjs.org/package/@1inch/solidity-utils)
10
+
11
+ ### About
12
+
13
+ This repository contains frequently used smart contracts, libraries and interfaces. Also it contains utils which are used in tests.
14
+
15
+ ### Solidity
16
+
17
+ |directory|.sol|description|
18
+ |--|--|--|
19
+ |contracts|`EthReceiver`||
20
+ |contracts|`Permitable`||
21
+ |contracts|`GasChecker`||
22
+ |contracts/interfaces|`IDaiLikePermit`|Interface of token which has `permit` method like DAI token|
23
+ |contracts/interfaces|`IWETH`|WETH token interface|
24
+ |contracts/libraries|`AddressArray`|library for work with array of addresses|
25
+ |contracts/libraries|`AddressSet`|library for work with set of addresses|
26
+ |contracts/libraries|`RevertReasonParser`|library parse the message from reverted method to readble format|
27
+ |contracts/libraries|`StringUtil`|optimized methods to convert data to hex|
28
+ |contracts/libraries|`UniERC20`||
29
+
30
+ ### JS
31
+
32
+ |module|function|descrption|
33
+ |--|--|--|
34
+ |asserts|`assertThrowsAsync(action, msg)`|checks the async function `action()` thrown with message `msg`|
35
+ |asserts|`assertRoughlyEqualValues(expected, actual, relativeDiff)`|checks the `expected` value is equal to `actual` value with `relativeDiff` precision|
36
+ |utils|`timeIncreaseTo(seconds)`|increases blockchain time to `seconds` sec|
37
+ |utils|`trackReceivedToken(token, wallet, txPromise, ...args)`|returns amount of `token` which recieved the `wallet` in async method `txPromise` with arguments `args`|
38
+ |utils|`trackReceivedTokenAndTx(token, wallet, txPromise, ...args)`|returns transaction info and amount of `token` which recieved the `wallet` in async method `txPromise` with arguments `args`|
39
+ |utils|`fixSignature(signature)`|patchs ganache's signature to geth's version|
40
+ |utils|`signMessage(signer, messageHex) `|signs `messageHex` with `signer` and patchs ganache's signature to geth's version|
41
+ |utils|`countInstructions(txHash, instruction)`|counts amount of `instruction` in transaction with `txHash` hash|
42
+ |profileEVM|`profileEVM(txHash, instruction, optionalTraceFile)`|the same as the `countInstructions()` with option of writing all trace to `optionalTraceFile`|
43
+ |profileEVM|`gasspectEVM(txHash, optionalTraceFile)`|returns all used operations in `txHash` transaction and their costs with option of writing all trace to `optionalTraceFile`|
44
+
45
+ ### UTILS
46
+
47
+ ...
@@ -0,0 +1,11 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.0;
4
+ pragma abicoder v1;
5
+
6
+ abstract contract EthReceiver {
7
+ receive() external payable {
8
+ // solhint-disable-next-line avoid-tx-origin
9
+ require(msg.sender != tx.origin, "ETH deposit rejected");
10
+ }
11
+ }
@@ -0,0 +1,19 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.0;
4
+ pragma abicoder v1;
5
+
6
+ import "@openzeppelin/contracts/utils/Strings.sol";
7
+
8
+ contract GasChecker {
9
+ using Strings for uint256;
10
+
11
+ modifier checkGasCost(uint256 expectedGasCost) {
12
+ uint256 gas = gasleft();
13
+ _;
14
+ gas -= gasleft();
15
+ if (expectedGasCost > 0) {
16
+ require (gas == expectedGasCost, string(abi.encodePacked("Gas cost differs: expected ", expectedGasCost.toString(), ", actual: ", gas.toString())));
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,29 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.0;
4
+ pragma abicoder v1;
5
+
6
+ import "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
7
+ import "./libraries/RevertReasonParser.sol";
8
+ import "./interfaces/IDaiLikePermit.sol";
9
+
10
+ contract Permitable {
11
+ function _permit(address token, bytes calldata permit) internal {
12
+ if (permit.length > 0) {
13
+ bool success;
14
+ bytes memory result;
15
+ if (permit.length == 32 * 7) {
16
+ // solhint-disable-next-line avoid-low-level-calls
17
+ (success, result) = token.call(abi.encodePacked(IERC20Permit.permit.selector, permit));
18
+ } else if (permit.length == 32 * 8) {
19
+ // solhint-disable-next-line avoid-low-level-calls
20
+ (success, result) = token.call(abi.encodePacked(IDaiLikePermit.permit.selector, permit));
21
+ } else {
22
+ revert("Wrong permit length");
23
+ }
24
+ if (!success) {
25
+ revert(RevertReasonParser.parse(result, "Permit failed: "));
26
+ }
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,9 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.0;
4
+ pragma abicoder v1;
5
+
6
+
7
+ interface IDaiLikePermit {
8
+ function permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s) external;
9
+ }
@@ -0,0 +1,12 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.0;
4
+ pragma abicoder v1;
5
+
6
+ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7
+
8
+
9
+ interface IWETH is IERC20 {
10
+ function deposit() external payable;
11
+ function withdraw(uint256 amount) external;
12
+ }
@@ -0,0 +1,65 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.0;
4
+
5
+
6
+ library AddressArray {
7
+ struct Data {
8
+ mapping(uint256 => uint256) _raw;
9
+ }
10
+
11
+ function length(Data storage self) internal view returns(uint256) {
12
+ return self._raw[0] >> 160;
13
+ }
14
+
15
+ function at(Data storage self, uint i) internal view returns(address) {
16
+ return address(uint160(self._raw[i]));
17
+ }
18
+
19
+ function get(Data storage self) internal view returns(address[] memory arr) {
20
+ uint256 lengthAndFirst = self._raw[0];
21
+ uint256 len = lengthAndFirst >> 160;
22
+ arr = new address[](len);
23
+ if (len > 0) {
24
+ arr[0] = address(uint160(lengthAndFirst));
25
+ for (uint i = 1; i < len; i++) {
26
+ arr[i] = address(uint160(self._raw[i]));
27
+ }
28
+ }
29
+ }
30
+
31
+ function push(Data storage self, address account) internal returns(uint256) {
32
+ uint256 lengthAndFirst = self._raw[0];
33
+ uint256 len = lengthAndFirst >> 160;
34
+ if (len == 0) {
35
+ self._raw[0] = (1 << 160) + uint160(account);
36
+ }
37
+ else {
38
+ self._raw[0] = lengthAndFirst + (1 << 160);
39
+ self._raw[len] = uint160(account);
40
+ }
41
+ return len + 1;
42
+ }
43
+
44
+ function pop(Data storage self) internal {
45
+ uint256 lengthAndFirst = self._raw[0];
46
+ uint256 len = lengthAndFirst >> 160;
47
+ require(len > 0, "AddressArray: popping from empty");
48
+ self._raw[len - 1] = 0;
49
+ if (len > 1) {
50
+ self._raw[0] = lengthAndFirst - (1 << 160);
51
+ }
52
+ }
53
+
54
+ function set(Data storage self, uint256 index, address account) internal {
55
+ uint256 len = length(self);
56
+ require(index < len, "AddressArray: index out of range");
57
+
58
+ if (index == 0) {
59
+ self._raw[0] = (len << 160) | uint160(account);
60
+ }
61
+ else {
62
+ self._raw[index] = uint160(account);
63
+ }
64
+ }
65
+ }
@@ -0,0 +1,50 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.0;
4
+
5
+ import "./AddressArray.sol";
6
+
7
+
8
+ library AddressSet {
9
+ using AddressArray for AddressArray.Data;
10
+
11
+ struct Data {
12
+ AddressArray.Data items;
13
+ mapping(address => uint256) lookup;
14
+ }
15
+
16
+ function length(Data storage s) internal view returns(uint) {
17
+ return s.items.length();
18
+ }
19
+
20
+ function at(Data storage s, uint index) internal view returns(address) {
21
+ return s.items.at(index);
22
+ }
23
+
24
+ function contains(Data storage s, address item) internal view returns(bool) {
25
+ return s.lookup[item] != 0;
26
+ }
27
+
28
+ function add(Data storage s, address item) internal returns(bool) {
29
+ if (s.lookup[item] > 0) {
30
+ return false;
31
+ }
32
+ s.lookup[item] = s.items.push(item);
33
+ return true;
34
+ }
35
+
36
+ function remove(Data storage s, address item) internal returns(bool) {
37
+ uint index = s.lookup[item];
38
+ if (index == 0) {
39
+ return false;
40
+ }
41
+ if (index < s.items.length()) {
42
+ address lastItem = s.items.at(s.items.length() - 1);
43
+ s.items.set(index - 1, lastItem);
44
+ s.lookup[lastItem] = index;
45
+ }
46
+ s.items.pop();
47
+ delete s.lookup[item];
48
+ return true;
49
+ }
50
+ }
@@ -0,0 +1,60 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.0;
4
+ pragma abicoder v1;
5
+
6
+ import "./StringUtil.sol";
7
+
8
+ /** @title Library that allows to parse unsuccessful arbitrary calls revert reasons.
9
+ * See https://solidity.readthedocs.io/en/latest/control-structures.html#revert for details.
10
+ * Note that we assume revert reason being abi-encoded as Error(string) so it may fail to parse reason
11
+ * if structured reverts appear in the future.
12
+ *
13
+ * All unsuccessful parsings get encoded as Unknown(data) string
14
+ */
15
+ library RevertReasonParser {
16
+ using StringUtil for uint256;
17
+ using StringUtil for bytes;
18
+
19
+ bytes4 constant private _ERROR_SELECTOR = bytes4(keccak256("Error(string)"));
20
+ bytes4 constant private _PANIC_SELECTOR = bytes4(keccak256("Panic(uint256)"));
21
+
22
+ function parse(bytes memory data, string memory prefix) internal pure returns (string memory) {
23
+ // https://solidity.readthedocs.io/en/latest/control-structures.html#revert
24
+ // We assume that revert reason is abi-encoded as Error(string)
25
+ bytes4 selector;
26
+ assembly { // solhint-disable-line no-inline-assembly
27
+ selector := mload(add(data, 0x20))
28
+ }
29
+
30
+ // 68 = 4-byte selector + 32 bytes offset + 32 bytes length
31
+ if (selector == _ERROR_SELECTOR && data.length >= 68) {
32
+ string memory reason;
33
+ // solhint-disable no-inline-assembly
34
+ assembly {
35
+ // 68 = 32 bytes data length + 4-byte selector + 32 bytes offset
36
+ reason := add(data, 68)
37
+ }
38
+ /*
39
+ revert reason is padded up to 32 bytes with ABI encoder: Error(string)
40
+ also sometimes there is extra 32 bytes of zeros padded in the end:
41
+ https://github.com/ethereum/solidity/issues/10170
42
+ because of that we can't check for equality and instead check
43
+ that string length + extra 68 bytes is less than overall data length
44
+ */
45
+ require(data.length >= 68 + bytes(reason).length, "Invalid revert reason");
46
+ return string(abi.encodePacked(prefix, "Error(", reason, ")"));
47
+ }
48
+ // 36 = 4-byte selector + 32 bytes integer
49
+ else if (selector == _PANIC_SELECTOR && data.length == 36) {
50
+ uint256 code;
51
+ // solhint-disable no-inline-assembly
52
+ assembly {
53
+ // 36 = 32 bytes data length + 4-byte selector
54
+ code := mload(add(data, 36))
55
+ }
56
+ return string(abi.encodePacked(prefix, "Panic(", code.toHex(), ")"));
57
+ }
58
+ return string(abi.encodePacked(prefix, "Unknown(", data.toHex(), ")"));
59
+ }
60
+ }
@@ -0,0 +1,77 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.0;
4
+ pragma abicoder v1;
5
+
6
+ /// @title Library with gas-efficient string operations
7
+ library StringUtil {
8
+ function toHex(uint256 value) internal pure returns (string memory) {
9
+ return toHex(abi.encodePacked(value));
10
+ }
11
+
12
+ function toHex(address value) internal pure returns (string memory) {
13
+ return toHex(abi.encodePacked(value));
14
+ }
15
+
16
+ function toHex(bytes memory data) internal pure returns (string memory result) {
17
+ // solhint-disable no-inline-assembly
18
+ assembly {
19
+ function _toHex16(input) -> output {
20
+ output := or(
21
+ and(input, 0xFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000),
22
+ shr(64, and(input, 0x0000000000000000FFFFFFFFFFFFFFFF00000000000000000000000000000000))
23
+ )
24
+ output := or(
25
+ and(output, 0xFFFFFFFF000000000000000000000000FFFFFFFF000000000000000000000000),
26
+ shr(32, and(output, 0x00000000FFFFFFFF000000000000000000000000FFFFFFFF0000000000000000))
27
+ )
28
+ output := or(
29
+ and(output, 0xFFFF000000000000FFFF000000000000FFFF000000000000FFFF000000000000),
30
+ shr(16, and(output, 0x0000FFFF000000000000FFFF000000000000FFFF000000000000FFFF00000000))
31
+ )
32
+ output := or(
33
+ and(output, 0xFF000000FF000000FF000000FF000000FF000000FF000000FF000000FF000000),
34
+ shr(8, and(output, 0x00FF000000FF000000FF000000FF000000FF000000FF000000FF000000FF0000))
35
+ )
36
+ output := or(
37
+ shr(4, and(output, 0xF000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000)),
38
+ shr(8, and(output, 0x0F000F000F000F000F000F000F000F000F000F000F000F000F000F000F000F00))
39
+ )
40
+ output := add(
41
+ add(0x3030303030303030303030303030303030303030303030303030303030303030, output),
42
+ mul(
43
+ and(
44
+ shr(4, add(output, 0x0606060606060606060606060606060606060606060606060606060606060606)),
45
+ 0x0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F0F
46
+ ),
47
+ 7 // Change 7 to 39 for lower case output
48
+ )
49
+ )
50
+ }
51
+
52
+ result := mload(0x40)
53
+ let length := mload(data)
54
+ let resultLength := add(mul(length, 2), 2)
55
+ let toPtr := add(result, 0x20)
56
+ mstore(0x40, add(toPtr, resultLength)) // move free memory pointer
57
+ mstore(result, resultLength)
58
+ mstore(toPtr, 0x3078000000000000000000000000000000000000000000000000000000000000) // set 0x as first two bytes
59
+ toPtr := add(toPtr, 0x02)
60
+
61
+ for {
62
+ let fromPtr := add(data, 0x20)
63
+ let endPtr := add(fromPtr, length)
64
+ } lt(fromPtr, endPtr) {
65
+ fromPtr := add(fromPtr, 0x20)
66
+ } {
67
+ let rawData := mload(fromPtr)
68
+ let hexData := _toHex16(rawData)
69
+ mstore(toPtr, hexData)
70
+ toPtr := add(toPtr, 0x20)
71
+ hexData := _toHex16(shl(128, rawData))
72
+ mstore(toPtr, hexData)
73
+ toPtr := add(toPtr, 0x20)
74
+ }
75
+ }
76
+ }
77
+ }
@@ -0,0 +1,63 @@
1
+ // SPDX-License-Identifier: MIT
2
+
3
+ pragma solidity ^0.8.0;
4
+ pragma abicoder v1;
5
+
6
+ import "@openzeppelin/contracts/utils/math/SafeMath.sol";
7
+ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
8
+ import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
9
+ import "./RevertReasonParser.sol";
10
+
11
+ library UniERC20 {
12
+ using SafeMath for uint256;
13
+ using SafeERC20 for IERC20;
14
+
15
+ IERC20 private constant _ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);
16
+ IERC20 private constant _ZERO_ADDRESS = IERC20(address(0));
17
+
18
+ function isETH(IERC20 token) internal pure returns (bool) {
19
+ return (token == _ZERO_ADDRESS || token == _ETH_ADDRESS);
20
+ }
21
+
22
+ function uniBalanceOf(IERC20 token, address account) internal view returns (uint256) {
23
+ if (isETH(token)) {
24
+ return account.balance;
25
+ } else {
26
+ return token.balanceOf(account);
27
+ }
28
+ }
29
+
30
+ function uniTransfer(IERC20 token, address payable to, uint256 amount) internal {
31
+ if (amount > 0) {
32
+ if (isETH(token)) {
33
+ to.transfer(amount);
34
+ } else {
35
+ token.safeTransfer(to, amount);
36
+ }
37
+ }
38
+ }
39
+
40
+ function uniApprove(IERC20 token, address to, uint256 amount) internal {
41
+ require(!isETH(token), "Approve called on ETH");
42
+
43
+ // solhint-disable-next-line avoid-low-level-calls
44
+ (bool success, bytes memory returndata) = address(token).call(abi.encodeWithSelector(token.approve.selector, to, amount));
45
+
46
+ if (!success || (returndata.length > 0 && !abi.decode(returndata, (bool)))) {
47
+ _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, to, 0));
48
+ _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, to, amount));
49
+ }
50
+ }
51
+
52
+ function _callOptionalReturn(IERC20 token, bytes memory data) private {
53
+ // solhint-disable-next-line avoid-low-level-calls
54
+ (bool success, bytes memory result) = address(token).call(data);
55
+ if (!success) {
56
+ revert(RevertReasonParser.parse(result, "Low-level call failed: "));
57
+ }
58
+
59
+ if (result.length > 0) { // Return data is optional
60
+ require(abi.decode(result, (bool)), "ERC20 operation did not succeed");
61
+ }
62
+ }
63
+ }
package/js/asserts.js CHANGED
@@ -1,34 +1,34 @@
1
- const { expect } = require('chai');
2
- const { BN } = require('@openzeppelin/test-helpers');
3
-
4
- async function assertThrowsAsync (action, msg) {
5
- try {
6
- await action();
7
- } catch (e) {
8
- expect(e.message).to.contain(msg);
9
- return;
10
- }
11
- throw new Error('Should have thrown an error but didn\'t');
12
- }
13
-
14
- function assertRoughlyEqualValues (expected, actual, relativeDiff) {
15
- const expectedBN = new BN(expected);
16
- const actualBN = new BN(actual);
17
-
18
- let multiplerNumerator = relativeDiff;
19
- let multiplerDenominator = new BN('1');
20
- while (!Number.isInteger(multiplerNumerator)) {
21
- multiplerDenominator = multiplerDenominator.mul(new BN('10'));
22
- multiplerNumerator *= 10;
23
- }
24
- const diff = expectedBN.sub(actualBN).abs();
25
- const treshold = expectedBN.mul(new BN(multiplerNumerator.toString())).div(multiplerDenominator);
26
- if (!diff.lte(treshold)) {
27
- expect(actualBN).to.be.bignumber.equal(expectedBN, `${actualBN} != ${expectedBN} with ${relativeDiff} precision`);
28
- }
29
- }
30
-
31
- module.exports = {
32
- assertThrowsAsync,
33
- assertRoughlyEqualValues,
34
- };
1
+ const { expect } = require('chai');
2
+ const { BN } = require('@openzeppelin/test-helpers');
3
+
4
+ async function assertThrowsAsync (action, msg) {
5
+ try {
6
+ await action();
7
+ } catch (e) {
8
+ expect(e.message).to.contain(msg);
9
+ return;
10
+ }
11
+ throw new Error('Should have thrown an error but didn\'t');
12
+ }
13
+
14
+ function assertRoughlyEqualValues (expected, actual, relativeDiff) {
15
+ const expectedBN = new BN(expected);
16
+ const actualBN = new BN(actual);
17
+
18
+ let multiplerNumerator = relativeDiff;
19
+ let multiplerDenominator = new BN('1');
20
+ while (!Number.isInteger(multiplerNumerator)) {
21
+ multiplerDenominator = multiplerDenominator.mul(new BN('10'));
22
+ multiplerNumerator *= 10;
23
+ }
24
+ const diff = expectedBN.sub(actualBN).abs();
25
+ const treshold = expectedBN.mul(new BN(multiplerNumerator.toString())).div(multiplerDenominator);
26
+ if (!diff.lte(treshold)) {
27
+ expect(actualBN).to.be.bignumber.equal(expectedBN, `${actualBN} != ${expectedBN} with ${relativeDiff} precision`);
28
+ }
29
+ }
30
+
31
+ module.exports = {
32
+ assertThrowsAsync,
33
+ assertRoughlyEqualValues,
34
+ };