@1inch/solidity-utils 1.2.3 → 1.2.8
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 +1 -1
- package/contracts/libraries/AddressArray.sol +1 -1
- package/contracts/libraries/AddressSet.sol +1 -1
- package/contracts/mocks/DaiLikePermitMock.sol +44 -0
- package/js/permit.js +110 -0
- package/js/profileEVM.js +73 -17
- package/package.json +14 -13
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ This repository contains frequently used smart contracts, libraries and interfac
|
|
|
40
40
|
|utils|`signMessage(signer, messageHex) `|signs `messageHex` with `signer` and patchs ganache's signature to geth's version|
|
|
41
41
|
|utils|`countInstructions(txHash, instruction)`|counts amount of `instruction` in transaction with `txHash` hash|
|
|
42
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`|
|
|
43
|
+
|profileEVM|`gasspectEVM(txHash, options, optionalTraceFile)`|returns all used operations in `txHash` transaction with `options` and their costs with option of writing all trace to `optionalTraceFile`|
|
|
44
44
|
|
|
45
45
|
### UTILS
|
|
46
46
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
|
|
3
|
+
pragma solidity ^0.8.0;
|
|
4
|
+
|
|
5
|
+
import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol";
|
|
6
|
+
|
|
7
|
+
contract DaiLikePermitMock is ERC20Permit {
|
|
8
|
+
// bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address holder,address spender,uint256 nonce,uint256 expiry,bool allowed)");
|
|
9
|
+
bytes32 public constant PERMIT_TYPEHASH = 0xea2aa0a1be11a07ed86d755c93467f4f82362b452371d1ba94d1715123511acb;
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
string memory name,
|
|
13
|
+
string memory symbol,
|
|
14
|
+
address initialAccount,
|
|
15
|
+
uint256 initialBalance
|
|
16
|
+
) payable ERC20(name, symbol) ERC20Permit(name) {
|
|
17
|
+
_mint(initialAccount, initialBalance);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s) external
|
|
21
|
+
{
|
|
22
|
+
bytes32 digest =
|
|
23
|
+
keccak256(abi.encodePacked(
|
|
24
|
+
"\x19\x01",
|
|
25
|
+
this.DOMAIN_SEPARATOR(),
|
|
26
|
+
keccak256(abi.encode(PERMIT_TYPEHASH,
|
|
27
|
+
holder,
|
|
28
|
+
spender,
|
|
29
|
+
nonce,
|
|
30
|
+
expiry,
|
|
31
|
+
allowed))
|
|
32
|
+
));
|
|
33
|
+
|
|
34
|
+
require(holder != address(0), "Dai/invalid-address-0");
|
|
35
|
+
require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit");
|
|
36
|
+
// solhint-disable-next-line not-rely-on-time
|
|
37
|
+
require(expiry == 0 || block.timestamp <= expiry, "Dai/permit-expired");
|
|
38
|
+
require(nonce == nonces(holder), "Dai/invalid-nonce");
|
|
39
|
+
_useNonce(holder);
|
|
40
|
+
uint wad = allowed ? type(uint128).max : 0;
|
|
41
|
+
_approve(holder, spender, wad);
|
|
42
|
+
emit Approval(holder, spender, wad);
|
|
43
|
+
}
|
|
44
|
+
}
|
package/js/permit.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const { constants } = require('@openzeppelin/test-helpers');
|
|
2
|
+
const ethSigUtil = require('eth-sig-util');
|
|
3
|
+
const { fromRpcSig } = require('ethereumjs-util');
|
|
4
|
+
const { artifacts } = require('hardhat');
|
|
5
|
+
const ERC20Permit = artifacts.require('@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol:ERC20Permit');
|
|
6
|
+
const ERC20PermitLikeDai = artifacts.require('DaiLikePermitMock');
|
|
7
|
+
|
|
8
|
+
const defaultDeadline = constants.MAX_UINT256;
|
|
9
|
+
|
|
10
|
+
const EIP712Domain = [
|
|
11
|
+
{ name: 'name', type: 'string' },
|
|
12
|
+
{ name: 'version', type: 'string' },
|
|
13
|
+
{ name: 'chainId', type: 'uint256' },
|
|
14
|
+
{ name: 'verifyingContract', type: 'address' },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const Permit = [
|
|
18
|
+
{ name: 'owner', type: 'address' },
|
|
19
|
+
{ name: 'spender', type: 'address' },
|
|
20
|
+
{ name: 'value', type: 'uint256' },
|
|
21
|
+
{ name: 'nonce', type: 'uint256' },
|
|
22
|
+
{ name: 'deadline', type: 'uint256' },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
const DaiLikePermit = [
|
|
26
|
+
{ name: 'holder', type: 'address' },
|
|
27
|
+
{ name: 'spender', type: 'address' },
|
|
28
|
+
{ name: 'nonce', type: 'uint256' },
|
|
29
|
+
{ name: 'expiry', type: 'uint256' },
|
|
30
|
+
{ name: 'allowed', type: 'bool' },
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function trim0x (bigNumber) {
|
|
34
|
+
const s = bigNumber.toString();
|
|
35
|
+
if (s.startsWith('0x')) {
|
|
36
|
+
return s.substring(2);
|
|
37
|
+
}
|
|
38
|
+
return s;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function cutSelector (data) {
|
|
42
|
+
const hexPrefix = '0x';
|
|
43
|
+
return hexPrefix + data.substr(hexPrefix.length + 8);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function domainSeparator (name, version, chainId, verifyingContract) {
|
|
47
|
+
return '0x' + ethSigUtil.TypedDataUtils.hashStruct(
|
|
48
|
+
'EIP712Domain',
|
|
49
|
+
{ name, version, chainId, verifyingContract },
|
|
50
|
+
{ EIP712Domain },
|
|
51
|
+
).toString('hex');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildData (name, version, chainId, verifyingContract, owner, spender, value, nonce, deadline = defaultDeadline) {
|
|
55
|
+
return {
|
|
56
|
+
primaryType: 'Permit',
|
|
57
|
+
types: { EIP712Domain, Permit },
|
|
58
|
+
domain: { name, version, chainId, verifyingContract },
|
|
59
|
+
message: { owner, spender, value, nonce, deadline },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildDataLikeDai (name, version, chainId, verifyingContract, holder, spender, nonce, allowed, expiry = defaultDeadline) {
|
|
64
|
+
return {
|
|
65
|
+
primaryType: 'Permit',
|
|
66
|
+
types: { EIP712Domain, Permit: DaiLikePermit },
|
|
67
|
+
domain: { name, version, chainId, verifyingContract },
|
|
68
|
+
message: { holder, spender, nonce, expiry, allowed },
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function getPermit (owner, ownerPrivateKey, token, tokenVersion, chainId, spender, value, deadline = defaultDeadline) {
|
|
73
|
+
const permitContract = await ERC20Permit.at(token.address);
|
|
74
|
+
const nonce = await permitContract.nonces(owner);
|
|
75
|
+
const name = await permitContract.name();
|
|
76
|
+
const data = buildData(name, tokenVersion, chainId, token.address, owner, spender, value, nonce, deadline);
|
|
77
|
+
const signature = ethSigUtil.signTypedMessage(Buffer.from(trim0x(ownerPrivateKey), 'hex'), { data });
|
|
78
|
+
const { v, r, s } = fromRpcSig(signature);
|
|
79
|
+
const permitCall = permitContract.contract.methods.permit(owner, spender, value, deadline, v, r, s).encodeABI();
|
|
80
|
+
return cutSelector(permitCall);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function getPermitLikeDai (holder, holderPrivateKey, token, tokenVersion, chainId, spender, allowed, expiry = defaultDeadline) {
|
|
84
|
+
const permitContract = await ERC20PermitLikeDai.at(token.address);
|
|
85
|
+
const nonce = await permitContract.nonces(holder);
|
|
86
|
+
const name = await permitContract.name();
|
|
87
|
+
const data = buildDataLikeDai(name, tokenVersion, chainId, token.address, holder, spender, nonce, allowed, expiry);
|
|
88
|
+
const signature = ethSigUtil.signTypedMessage(Buffer.from(trim0x(holderPrivateKey), 'hex'), { data });
|
|
89
|
+
const { v, r, s } = fromRpcSig(signature);
|
|
90
|
+
const permitCall = permitContract.contract.methods.permit(holder, spender, nonce, expiry, allowed, v, r, s).encodeABI();
|
|
91
|
+
return cutSelector(permitCall);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function withTarget (target, data) {
|
|
95
|
+
return target.toString() + trim0x(data);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
defaultDeadline,
|
|
100
|
+
EIP712Domain,
|
|
101
|
+
Permit,
|
|
102
|
+
DaiLikePermit,
|
|
103
|
+
trim0x,
|
|
104
|
+
domainSeparator,
|
|
105
|
+
buildData,
|
|
106
|
+
buildDataLikeDai,
|
|
107
|
+
getPermit,
|
|
108
|
+
getPermitLikeDai,
|
|
109
|
+
withTarget,
|
|
110
|
+
};
|
package/js/profileEVM.js
CHANGED
|
@@ -1,23 +1,74 @@
|
|
|
1
1
|
const { promisify } = require('util');
|
|
2
2
|
const fs = require('fs').promises;
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
4
|
+
const gasspectOptionsDefault = {
|
|
5
|
+
minOpGasCost: 300, // minimal gas cost of returned operations
|
|
6
|
+
args: false, // return operations arguments
|
|
7
|
+
res: false, // return operations results
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
function _normalizeOp (ops, i) {
|
|
11
|
+
if (ops[i].op === 'STATICCALL') {
|
|
12
|
+
ops[i].gasCost = ops[i].gasCost - ops[i + 1].gas;
|
|
13
|
+
|
|
14
|
+
if (ops[i].stack.length > 8 && ops[i].stack[ops[i].stack.length - 8] === '0000000000000000000000000000000000000000000000000000000000000001') {
|
|
15
|
+
ops[i].op = 'STATICCALL-ECRECOVER';
|
|
16
|
+
} else if (ops[i].stack.length > 8 && ops[i].stack[ops[i].stack.length - 8] <= '00000000000000000000000000000000000000000000000000000000000000FF') {
|
|
17
|
+
ops[i].op = 'STATICCALL-' + ops[i].stack[ops[i].stack.length - 8].substr(62, 2);
|
|
12
18
|
} else {
|
|
13
|
-
|
|
19
|
+
ops[i].args = [
|
|
20
|
+
'0x' + ops[i].stack[ops[i].stack.length - 2].substr(24),
|
|
21
|
+
'0x' + (ops[i].memory || []).join('').substr(
|
|
22
|
+
2 * web3.utils.toBN(ops[i].stack[ops[i].stack.length - 3]).toNumber(),
|
|
23
|
+
2 * web3.utils.toBN(ops[i].stack[ops[i].stack.length - 4]).toNumber(),
|
|
24
|
+
),
|
|
25
|
+
];
|
|
26
|
+
if (ops[i].gasCost === 100) {
|
|
27
|
+
ops[i].op += '_R';
|
|
28
|
+
}
|
|
14
29
|
}
|
|
15
30
|
}
|
|
16
|
-
if (['CALL', 'DELEGATECALL', 'CALLCODE'].indexOf(
|
|
17
|
-
|
|
31
|
+
if (['CALL', 'DELEGATECALL', 'CALLCODE'].indexOf(ops[i].op) !== -1) {
|
|
32
|
+
ops[i].args = [
|
|
33
|
+
'0x' + ops[i].stack[ops[i].stack.length - 2].substr(24),
|
|
34
|
+
'0x' + (ops[i].memory || []).join('').substr(
|
|
35
|
+
2 * web3.utils.toBN(ops[i].stack[ops[i].stack.length - 4]).toNumber(),
|
|
36
|
+
2 * web3.utils.toBN(ops[i].stack[ops[i].stack.length - 5]).toNumber(),
|
|
37
|
+
),
|
|
38
|
+
];
|
|
39
|
+
ops[i].gasCost = ops[i].gasCost - ops[i + 1].gas;
|
|
40
|
+
ops[i].res = ops[i + 1].stack[ops[i + 1].stack.length - 1];
|
|
41
|
+
|
|
42
|
+
if (ops[i].gasCost === 100) {
|
|
43
|
+
ops[i].op += '_R';
|
|
44
|
+
}
|
|
18
45
|
}
|
|
19
|
-
if (['RETURN', 'REVERT', 'INVALID'].indexOf(
|
|
20
|
-
|
|
46
|
+
if (['RETURN', 'REVERT', 'INVALID'].indexOf(ops[i].op) !== -1) {
|
|
47
|
+
ops[i].gasCost = 3;
|
|
48
|
+
}
|
|
49
|
+
if (['SSTORE', 'SLOAD'].indexOf(ops[i].op) !== -1) {
|
|
50
|
+
ops[i].args = [
|
|
51
|
+
'0x' + ops[i].stack[ops[i].stack.length - 1],
|
|
52
|
+
];
|
|
53
|
+
if (ops[i].op === 'SSTORE') {
|
|
54
|
+
ops[i].args.push('0x' + ops[i].stack[ops[i].stack.length - 2]);
|
|
55
|
+
}
|
|
56
|
+
if (ops[i].gasCost === 100) {
|
|
57
|
+
ops[i].op += '_R';
|
|
58
|
+
}
|
|
59
|
+
if (ops[i].gasCost === 20000) {
|
|
60
|
+
ops[i].op += '_I';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (ops[i].op.startsWith('SLOAD')) {
|
|
64
|
+
ops[i].res = ops[i + 1].stack[ops[i + 1].stack.length - 1];
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (ops[i].op === 'EXTCODESIZE') {
|
|
68
|
+
ops[i].args = [
|
|
69
|
+
'0x' + ops[i].stack[ops[i].stack.length - 1].substr(24),
|
|
70
|
+
];
|
|
71
|
+
ops[i].res = ops[i + 1].stack[ops[i + 1].stack.length - 1];
|
|
21
72
|
}
|
|
22
73
|
}
|
|
23
74
|
|
|
@@ -44,7 +95,9 @@ async function profileEVM (txHash, instruction, optionalTraceFile) {
|
|
|
44
95
|
return str.split('"' + instruction.toUpperCase() + '"').length - 1;
|
|
45
96
|
}
|
|
46
97
|
|
|
47
|
-
async function gasspectEVM (txHash, optionalTraceFile) {
|
|
98
|
+
async function gasspectEVM (txHash, options = gasspectOptionsDefault, optionalTraceFile) {
|
|
99
|
+
options = { ...gasspectOptionsDefault, ...options };
|
|
100
|
+
|
|
48
101
|
const trace = await promisify(web3.currentProvider.send.bind(web3.currentProvider))({
|
|
49
102
|
jsonrpc: '2.0',
|
|
50
103
|
method: 'debug_traceTransaction',
|
|
@@ -55,9 +108,9 @@ async function gasspectEVM (txHash, optionalTraceFile) {
|
|
|
55
108
|
const ops = trace.result.structLogs;
|
|
56
109
|
|
|
57
110
|
const traceAddress = [0, -1];
|
|
58
|
-
for (const op of ops) {
|
|
111
|
+
for (const [i, op] of ops.entries()) {
|
|
59
112
|
op.traceAddress = traceAddress.slice(0, traceAddress.length - 1);
|
|
60
|
-
_normalizeOp(
|
|
113
|
+
_normalizeOp(ops, i);
|
|
61
114
|
|
|
62
115
|
if (op.depth + 2 > traceAddress.length) {
|
|
63
116
|
traceAddress[traceAddress.length - 1] += 1;
|
|
@@ -69,7 +122,10 @@ async function gasspectEVM (txHash, optionalTraceFile) {
|
|
|
69
122
|
}
|
|
70
123
|
}
|
|
71
124
|
|
|
72
|
-
const result = ops.filter(op => op.gasCost >
|
|
125
|
+
const result = ops.filter(op => op.gasCost > options.minOpGasCost).map(op => op.traceAddress.join('-') + '-' + op.op +
|
|
126
|
+
(options.args ? '(' + (op.args || []).join(',') + ')' : '') +
|
|
127
|
+
(options.res ? (op.res ? ':0x' + op.res : '') : '') +
|
|
128
|
+
' = ' + op.gasCost);
|
|
73
129
|
|
|
74
130
|
if (optionalTraceFile) {
|
|
75
131
|
await fs.writeFile(optionalTraceFile, JSON.stringify(result));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1inch/solidity-utils",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.8",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -8,28 +8,29 @@
|
|
|
8
8
|
},
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@openzeppelin/contracts": "^4.
|
|
11
|
+
"@openzeppelin/contracts": "^4.4.0",
|
|
12
|
+
"ethereumjs-wallet": "^1.0.2"
|
|
12
13
|
},
|
|
13
14
|
"devDependencies": {
|
|
14
|
-
"@nomiclabs/hardhat-etherscan": "^2.1.
|
|
15
|
+
"@nomiclabs/hardhat-etherscan": "^2.1.8",
|
|
15
16
|
"@nomiclabs/hardhat-truffle5": "^2.0.0",
|
|
16
17
|
"@nomiclabs/hardhat-web3": "^2.0.0",
|
|
17
|
-
"@openzeppelin/test-helpers": "^0.5.
|
|
18
|
+
"@openzeppelin/test-helpers": "^0.5.15",
|
|
18
19
|
"chai": "^4.3.4",
|
|
19
20
|
"cross-spawn": "^7.0.3",
|
|
20
21
|
"dotenv": "^10.0.0",
|
|
21
|
-
"eslint": "^
|
|
22
|
+
"eslint": "^8.3.0",
|
|
22
23
|
"eslint-config-standard": "^16.0.3",
|
|
23
|
-
"eslint-plugin-import": "^2.
|
|
24
|
+
"eslint-plugin-import": "^2.25.3",
|
|
24
25
|
"eslint-plugin-node": "^11.1.0",
|
|
25
|
-
"eslint-plugin-promise": "^5.1.
|
|
26
|
+
"eslint-plugin-promise": "^5.1.1",
|
|
26
27
|
"eslint-plugin-standard": "^5.0.0",
|
|
27
|
-
"hardhat": "^2.
|
|
28
|
-
"hardhat-deploy": "^0.9.
|
|
29
|
-
"hardhat-gas-reporter": "^1.0.
|
|
28
|
+
"hardhat": "^2.7.0",
|
|
29
|
+
"hardhat-deploy": "^0.9.14",
|
|
30
|
+
"hardhat-gas-reporter": "^1.0.6",
|
|
30
31
|
"rimraf": "^3.0.2",
|
|
31
32
|
"shx": "^0.3.3",
|
|
32
|
-
"solc": "^0.8.
|
|
33
|
+
"solc": "^0.8.10",
|
|
33
34
|
"solhint": "^3.3.6",
|
|
34
35
|
"solidity-coverage": "^0.7.17"
|
|
35
36
|
},
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
"solidity-utils-docify": "utils/docify.utils.js"
|
|
38
39
|
},
|
|
39
40
|
"scripts": {
|
|
40
|
-
"build": "rimraf ./dist && mkdir dist && shx cp -R js ./dist/js && shx cp -R utils ./dist/utils && shx cp package.json ./dist && shx cp README.md ./dist && shx cp -R ./contracts ./dist/contracts && shx rm -rf dist/contracts/mocks dist/contracts/tests",
|
|
41
|
+
"build": "rimraf ./dist && mkdir dist && shx cp -R js ./dist/js && shx cp -R utils ./dist/utils && shx cp package.json ./dist && shx cp README.md ./dist && shx cp -R ./contracts ./dist/contracts && shx rm -rf dist/contracts/mocks dist/contracts/tests && shx mkdir -p dist/contracts/mocks && shx cp ./contracts/mocks/DaiLikePermitMock.sol dist/contracts/mocks/",
|
|
41
42
|
"coverage": "hardhat coverage",
|
|
42
43
|
"lint": "yarn run lint:js && yarn run lint:sol",
|
|
43
44
|
"lint:fix": "yarn run lint:js:fix && yarn run lint:sol:fix",
|
|
@@ -55,5 +56,5 @@
|
|
|
55
56
|
"test": "test"
|
|
56
57
|
},
|
|
57
58
|
"author": "1inch",
|
|
58
|
-
"description": ""
|
|
59
|
+
"description": "Solidity and JS utils"
|
|
59
60
|
}
|