Skip to content

Commit

Permalink
update typechain, migrate to ethers + new hardhat
Browse files Browse the repository at this point in the history
  • Loading branch information
ZumZoom committed Sep 27, 2022
1 parent f4e3eb8 commit dc79be3
Show file tree
Hide file tree
Showing 18 changed files with 1,175 additions and 982 deletions.
3 changes: 2 additions & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import '@typechain/hardhat';
import '@nomiclabs/hardhat-truffle5';
import '@nomicfoundation/hardhat-chai-matchers';
import 'hardhat-gas-reporter';
require('solidity-coverage'); // require because no TS typings available
import dotenv from 'dotenv';
Expand All @@ -24,7 +25,7 @@ const config: HardhatUserConfig = {
currency: 'USD',
},
typechain: {
target: 'truffle-v5',
target: 'ethers-v5',
},
};

Expand Down
11 changes: 7 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
"dependencies": {
"@metamask/eth-sig-util": "4.0.1",
"@openzeppelin/contracts": "4.7.3",
"@openzeppelin/test-helpers": "0.5.16",
"bn.js": "5.2.1",
"chai": "4.3.6",
"chai-as-promised": "7.1.1",
Expand All @@ -37,16 +36,19 @@
"web3-utils": "1.8.0"
},
"devDependencies": {
"@nomicfoundation/hardhat-chai-matchers": "1.0.3",
"@nomicfoundation/hardhat-network-helpers": "1.0.6",
"@nomiclabs/hardhat-truffle5": "2.0.7",
"@nomiclabs/hardhat-web3": "2.0.0",
"@nomiclabs/hardhat-ethers": "2.1.1",
"@typechain/hardhat": "6.1.3",
"@typechain/truffle-v5": "7.0.0",
"@typechain/ethers-v5": "10.1.0",
"@types/chai": "4.3.3",
"@types/chai-as-promised": "7.1.5",
"@types/eth-sig-util": "2.1.1",
"@types/ethereumjs-util": "6.1.0",
"@types/mocha": "9.1.1",
"@types/node": "18.7.18",
"@types/node": "18.7.21",
"@typescript-eslint/eslint-plugin": "5.38.0",
"@typescript-eslint/parser": "5.38.0",
"acquit": "1.2.1",
Expand All @@ -61,14 +63,15 @@
"eslint-plugin-promise": "6.0.1",
"eslint-plugin-standard": "5.0.0",
"ethereumjs-wallet": "1.0.2",
"ethers": "5.7.1",
"hardhat": "2.11.2",
"hardhat-gas-reporter": "1.0.9",
"rimraf": "3.0.2",
"shx": "0.3.4",
"solhint": "3.3.7",
"solidity-coverage": "0.8.2",
"ts-node": "10.9.1",
"typechain": "7.0.1",
"typechain": "8.1.0",
"typescript": "4.8.3"
},
"bin": {
Expand Down
36 changes: 13 additions & 23 deletions src/asserts.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,22 @@
import { expect, toBN } from './prelude';
import BN from 'bn.js';
import { expect } from './prelude';

export function toBNExtended (value: string | number | BN): BN {
if (typeof value === 'string' || typeof value === 'number') {
return toBN(value);
}
return value;
}

export function assertRoughlyEqualValues (expected: string | number | BN, actual: string | number | BN, relativeDiff: number) {
let expectedBN = toBNExtended(expected);
let actualBN = toBNExtended(actual);
if (expectedBN.isNeg() !== actualBN.isNeg()) {
expect(actualBN).to.be.bignumber.equal(expectedBN, 'Values are of different sign');
}
export function assertRoughlyEqualValues (expected: string | number | bigint, actual: string | number | bigint, relativeDiff: number) {
let expectedBN = BigInt(expected);
let actualBN = BigInt(actual);
expect(expectedBN * actualBN).to.be.gte(0, 'Values are of different sign');

expectedBN = expectedBN.abs();
actualBN = actualBN.abs();
if (expectedBN < 0) expectedBN = -expectedBN;
if (actualBN < 0) actualBN = -actualBN;

let multiplerNumerator = relativeDiff;
let multiplerDenominator = toBN('1');
let multiplerDenominator = 1n;
while (!Number.isInteger(multiplerNumerator)) {
multiplerDenominator = multiplerDenominator.mul(toBN('10'));
multiplerDenominator = multiplerDenominator * 10n;
multiplerNumerator *= 10;
}
const diff = expectedBN.sub(actualBN).abs();
const treshold = expectedBN.mul(toBN(multiplerNumerator.toString())).div(multiplerDenominator);
if (!diff.lte(treshold)) {
expect(actualBN).to.be.bignumber.equal(expectedBN, `${actual} != ${expected} with ${relativeDiff} precision`);
const diff = expectedBN > actualBN ? expectedBN - actualBN : actualBN - expectedBN;
const treshold = expectedBN * BigInt(multiplerNumerator) / multiplerDenominator;
if (diff > treshold) {
expect(actualBN).to.be.equal(expectedBN, `${actual} != ${expected} with ${relativeDiff} precision`);
}
}
54 changes: 23 additions & 31 deletions src/permit.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { MessageTypes, signTypedData, SignTypedDataVersion, TypedDataUtils, TypedMessage } from '@metamask/eth-sig-util';
import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util';
import { fromRpcSig } from 'ethereumjs-util';
import { Token } from './utils';
import { constants } from './prelude';

import { CallOverrides, BigNumber } from 'ethers';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';

export const TypedDataVersion = SignTypedDataVersion.V4;
export const defaultDeadline = constants.MAX_UINT256;
Expand Down Expand Up @@ -30,7 +31,7 @@ export const DaiLikePermit = [
{ name: 'allowed', type: 'bool' },
];

export function trim0x (bigNumber: BN | string) {
export function trim0x (bigNumber: bigint | string) {
const s = bigNumber.toString();
if (s.startsWith('0x')) {
return s.substring(2);
Expand Down Expand Up @@ -61,11 +62,10 @@ export function buildData (
spender: string,
value: string,
nonce: string,
deadline: string = defaultDeadline
deadline: string = defaultDeadline.toString()
) {
return {
primaryType: 'Permit',
types: { EIP712Domain, Permit },
types: { Permit },
domain: { name, version, chainId, verifyingContract },
message: { owner, spender, value, nonce, deadline },
} as const;
Expand All @@ -80,70 +80,62 @@ export function buildDataLikeDai (
spender: string,
nonce: string,
allowed: boolean,
expiry: string = defaultDeadline
expiry: string = defaultDeadline.toString()
) {
return {
primaryType: 'Permit',
types: { EIP712Domain, Permit: DaiLikePermit },
types: { Permit: DaiLikePermit },
domain: { name, version, chainId, verifyingContract },
message: { holder, spender, nonce, expiry, allowed },
} as const;
}

export interface PermittableToken extends Token {
nonces(owner: string, txDetails?: Truffle.TransactionDetails): Promise<BN>;
name(txDetails?: Truffle.TransactionDetails): Promise<string>;
}

export function signWithPk<T extends MessageTypes> (privateKey: Buffer | string, data: TypedMessage<T>) {
const buffer = Buffer.isBuffer(privateKey) ? privateKey : Buffer.from(trim0x(privateKey), 'hex');
return signTypedData({ privateKey: buffer, data, version: TypedDataVersion });
nonces(owner: string, overrides?: CallOverrides): Promise<BigNumber>;
name(overrides?: CallOverrides): Promise<string>;
}

/*
* @param permitContract The contract object with ERC20Permit type and token address for which the permit creating.
*/
export async function getPermit (
owner: string,
ownerPrivateKey: string,
owner: SignerWithAddress,
permitContract: PermittableToken,
tokenVersion: string,
chainId: number,
spender: string,
value: string,
deadline = defaultDeadline
deadline = defaultDeadline.toString()
) {
const nonce = await permitContract.nonces(owner);
const nonce = await permitContract.nonces(owner.address);
const name = await permitContract.name();
const data = buildData(name, tokenVersion, chainId, permitContract.address, owner, spender, value, nonce.toString(), deadline);
const signature = signWithPk(ownerPrivateKey, data);
const data = buildData(name, tokenVersion, chainId, permitContract.address, owner.address, spender, value, nonce.toString(), deadline);
const signature = await owner._signTypedData(data.domain, data.types, data.message);
const { v, r, s } = fromRpcSig(signature);
const permitCall = permitContract.contract.methods.permit(owner, spender, value, deadline, v, r, s).encodeABI();
const permitCall = permitContract.interface.encodeFunctionData('permit', [owner.address, spender, value, deadline, v, r, s]);
return cutSelector(permitCall);
}

/*
* @param permitContract The contract object with ERC20PermitLikeDai type and token address for which the permit creating.
*/
export async function getPermitLikeDai (
holder: string,
holderPrivateKey: string,
holder: SignerWithAddress,
permitContract: PermittableToken,
tokenVersion: string,
chainId: number,
spender: string,
allowed: boolean,
expiry = defaultDeadline
expiry = defaultDeadline.toString()
) {
const nonce = await permitContract.nonces(holder);
const nonce = await permitContract.nonces(holder.address);
const name = await permitContract.name();
const data = buildDataLikeDai(name, tokenVersion, chainId, permitContract.address, holder, spender, nonce.toString(), allowed, expiry);
const signature = signWithPk(holderPrivateKey, data);
const data = buildDataLikeDai(name, tokenVersion, chainId, permitContract.address, holder.address, spender, nonce.toString(), allowed, expiry);
const signature = await holder._signTypedData(data.domain, data.types, data.message);
const { v, r, s } = fromRpcSig(signature);
const permitCall = permitContract.contract.methods.permit(holder, spender, nonce, expiry, allowed, v, r, s).encodeABI();
const permitCall = permitContract.interface.encodeFunctionData('permit(address,address,uint256,uint256,bool,uint8,bytes32,bytes32)', [holder.address, spender, nonce, expiry, allowed, v, r, s]);
return cutSelector(permitCall);
}

export function withTarget (target: BN | string, data: BN | string) {
export function withTarget (target: bigint | string, data: bigint | string) {
return target.toString() + trim0x(data);
}
37 changes: 9 additions & 28 deletions src/prelude.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,24 @@
import chai, { Assertion, AssertionError, assert, expect, config, should } from 'chai';
import 'chai-bn';
import chaiAsPromised from 'chai-as-promised';
import { Assertion, AssertionError, assert, expect, config, should } from 'chai';
import { toWei } from 'web3-utils';
import BN from 'bn.js';
chai.use(chaiAsPromised);
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { time: timeImpl } = require('@openzeppelin/test-helpers');

export function toBN (num: string | number, base?: number | 'hex'): BN {
if (typeof(num) === 'string' && num.startsWith('0x')) {
return new BN(num.substring(2), 16);
}
return new BN(num, base);
}
import { time } from '@nomicfoundation/hardhat-network-helpers';

export const constants = {
ZERO_ADDRESS: '0x0000000000000000000000000000000000000000',
EEE_ADDRESS: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
ZERO_BYTES32: '0x0000000000000000000000000000000000000000000000000000000000000000',
MAX_UINT256: toBN('2').pow(toBN('256')).sub(toBN('1')).toString(),
MAX_INT256: toBN('2').pow(toBN('255')).sub(toBN('1')).toString(),
MIN_INT256: toBN('2').pow(toBN('255')).mul(toBN('-1')).toString(),
MAX_UINT256: (2n ** 256n) - 1n,
MAX_INT256: (2n ** 255n) - 1n,
MIN_INT256: -(2n ** 255n),
MAX_UINT128: (2n ** 128n) - 1n,
} as const;

// utils
export {
BN,
time,
};

//test-helpers
export type Time = {
increaseTo: (target: string | number | BN) => Promise<BN>,
latest: () => Promise<BN>
}

export const time: Time = timeImpl;

export function ether (n: string): BN {
return toBN(toWei(n, 'ether'));
export function ether (n: string): bigint {
return BigInt(toWei(n, 'ether'));
}

// chai
Expand Down
45 changes: 13 additions & 32 deletions src/profileEVM.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { promisify } from 'util';
import { PathLike, promises as fs } from 'fs';
import { toBN } from './prelude';
import { ethers } from 'hardhat';

export const gasspectOptionsDefault = {
minOpGasCost: 300, // minimal gas cost of returned operations
Expand Down Expand Up @@ -30,10 +29,10 @@ function _normalizeOp (ops: Op[], i: number) {
ops[i].op = 'STATICCALL-' + ops[i].stack[ops[i].stack.length - 8].substr(62, 2);
} else {
ops[i].args = [
'0x' + ops[i].stack[ops[i].stack.length - 2].substr(24),
'0x' + ops[i].stack[ops[i].stack.length - 2].substring(24),
'0x' + (ops[i].memory || []).join('').substr(
2 * toBN(ops[i].stack[ops[i].stack.length - 3]).toNumber(),
2 * toBN(ops[i].stack[ops[i].stack.length - 4]).toNumber(),
2 * Number(ops[i].stack[ops[i].stack.length - 3]),
2 * Number(ops[i].stack[ops[i].stack.length - 4]),
),
];
if (ops[i].gasCost === 100) {
Expand All @@ -43,10 +42,10 @@ function _normalizeOp (ops: Op[], i: number) {
}
if (['CALL', 'DELEGATECALL', 'CALLCODE'].indexOf(ops[i].op) !== -1) {
ops[i].args = [
'0x' + ops[i].stack[ops[i].stack.length - 2].substr(24),
'0x' + ops[i].stack[ops[i].stack.length - 2].substring(24),
'0x' + (ops[i].memory || []).join('').substr(
2 * toBN(ops[i].stack[ops[i].stack.length - 4]).toNumber(),
2 * toBN(ops[i].stack[ops[i].stack.length - 5]).toNumber(),
2 * Number(ops[i].stack[ops[i].stack.length - 4]),
2 * Number(ops[i].stack[ops[i].stack.length - 5]),
),
];
ops[i].gasCost = ops[i].gasCost - ops[i + 1].gas;
Expand All @@ -69,7 +68,7 @@ function _normalizeOp (ops: Op[], i: number) {
if (ops[i].gasCost === 100) {
ops[i].op += '_R';
}
if (ops[i].gasCost === 20000) {
if (ops[i].gasCost >= 20000) {
ops[i].op += '_I';
}

Expand All @@ -79,23 +78,14 @@ function _normalizeOp (ops: Op[], i: number) {
}
if (ops[i].op === 'EXTCODESIZE') {
ops[i].args = [
'0x' + ops[i].stack[ops[i].stack.length - 1].substr(24),
'0x' + ops[i].stack[ops[i].stack.length - 1].substring(24),
];
ops[i].res = ops[i + 1].stack[ops[i + 1].stack.length - 1];
}
}

export async function profileEVM (txHash: string, instruction: string[], optionalTraceFile?: PathLike | fs.FileHandle) {
if (!web3.currentProvider || typeof web3.currentProvider === 'string' || !web3.currentProvider.send) {
throw new Error('Unsupported provider');
}

const trace = await promisify(web3.currentProvider.send.bind(web3.currentProvider))({
jsonrpc: '2.0',
method: 'debug_traceTransaction',
params: [txHash, {}],
id: new Date().getTime(),
});
const trace = await ethers.provider.send('debug_traceTransaction', [ txHash ]);

const str = JSON.stringify(trace);

Expand All @@ -111,18 +101,9 @@ export async function profileEVM (txHash: string, instruction: string[], optiona
export async function gasspectEVM (txHash: string, gasspectOptions: Record<string, unknown> = {}, optionalTraceFile?: PathLike | fs.FileHandle) {
const options = { ...gasspectOptionsDefault, ...gasspectOptions };

if (!web3.currentProvider || typeof web3.currentProvider === 'string' || !web3.currentProvider.send) {
throw new Error('Unsupported provider');
}

const trace = await promisify(web3.currentProvider.send.bind(web3.currentProvider))({
jsonrpc: '2.0',
method: 'debug_traceTransaction',
params: [txHash, {}],
id: new Date().getTime(),
});
const trace = await ethers.provider.send('debug_traceTransaction', [ txHash ]);

const ops: Op[] = trace?.result.structLogs;
const ops: Op[] = trace.structLogs;

const traceAddress = [0, -1];
for (const [i, op] of ops.entries()) {
Expand All @@ -145,7 +126,7 @@ export async function gasspectEVM (txHash: string, gasspectOptions: Record<strin
' = ' + op.gasCost);

if (optionalTraceFile) {
await fs.writeFile(optionalTraceFile, JSON.stringify(result));
await fs.writeFile(optionalTraceFile, JSON.stringify(trace));
}

return result;
Expand Down
Loading

0 comments on commit dc79be3

Please sign in to comment.