Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate to new hardhat-ethers #89

Merged
merged 22 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import '@typechain/hardhat';
import '@nomiclabs/hardhat-ethers';
import '@nomicfoundation/hardhat-ethers';
import '@nomicfoundation/hardhat-chai-matchers';
import 'hardhat-gas-reporter';
import 'hardhat-tracer';
Expand Down Expand Up @@ -34,7 +34,7 @@ const config: HardhatUserConfig = {
enabled: true,
},
typechain: {
target: 'ethers-v5',
target: 'ethers-v6',
},
};

Expand Down
54 changes: 27 additions & 27 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@1inch/solidity-utils",
"version": "2.4.0",
"version": "3.0.0",
"main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"repository": {
Expand Down Expand Up @@ -28,52 +28,52 @@
"typechain": "hardhat typechain"
},
"dependencies": {
"@metamask/eth-sig-util": "5.0.2",
"@metamask/eth-sig-util": "6.0.0",
"@nomicfoundation/hardhat-ethers": "3.0.3",
"@nomicfoundation/hardhat-network-helpers": "1.0.8",
"@nomiclabs/hardhat-ethers": "2.2.2",
"@openzeppelin/contracts": "4.8.2",
"@uniswap/permit2-sdk": "^1.2.0",
"@openzeppelin/contracts": "4.9.2",
"@uniswap/permit2-sdk": "1.2.0",
"ethereumjs-util": "7.1.5",
"ethers": "5.7.2"
"ethers": "6.6.3",
"hardhat-deploy": "0.11.34"
},
"devDependencies": {
"@nomicfoundation/hardhat-chai-matchers": "1.0.6",
"@nomicfoundation/hardhat-verify": "1.0.1",
"@typechain/ethers-v5": "10.2.0",
"@typechain/hardhat": "6.1.5",
"@types/chai": "4.3.4",
"@nomicfoundation/hardhat-chai-matchers": "2.0.1",
"@nomicfoundation/hardhat-verify": "1.0.3",
"@typechain/ethers-v6": "0.4.0",
"@typechain/hardhat": "8.0.0",
"@types/chai": "4.3.5",
"@types/eth-sig-util": "2.1.1",
"@types/ethereumjs-util": "6.1.0",
"@types/mocha": "10.0.1",
"@types/node": "18.15.10",
"@typescript-eslint/eslint-plugin": "5.57.0",
"@typescript-eslint/parser": "5.57.0",
"@types/node": "20.4.1",
"@typescript-eslint/eslint-plugin": "6.0.0",
"@typescript-eslint/parser": "6.0.0",
"acquit": "1.3.0",
"chai": "4.3.7",
"commander": "10.0.0",
"commander": "11.0.0",
"create-ts-index": "1.14.0",
"cross-spawn": "7.0.3",
"dotenv": "16.0.3",
"eslint": "8.36.0",
"eslint-config-standard": "17.0.0",
"dotenv": "16.3.1",
"eslint": "8.44.0",
"eslint-config-standard": "17.1.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-n": "15.6.1",
"eslint-plugin-n": "16.0.1",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-standard": "5.0.0",
"ethereumjs-wallet": "1.0.2",
"hardhat": "2.13.0",
"hardhat-deploy": "0.11.30",
"hardhat": "2.16.1",
"hardhat-gas-reporter": "1.0.9",
"hardhat-tracer": "2.1.2",
"prettier": "2.8.7",
"hardhat-tracer": "2.5.1",
"prettier": "3.0.0",
"prettier-plugin-solidity": "1.1.3",
"rimraf": "4.4.1",
"rimraf": "5.0.1",
"shx": "0.3.4",
"solhint": "3.4.1",
"solidity-coverage": "0.8.2",
"solidity-coverage": "0.8.4",
"ts-node": "10.9.1",
"typechain": "8.1.1",
"typescript": "5.0.2"
"typechain": "8.2.0",
"typescript": "5.1.6"
},
"bin": {
"solidity-utils-docify": "utils/docify.utils.js",
Expand Down
2 changes: 1 addition & 1 deletion src/asserts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export function assertRoughlyEqualValues(
expected: string | number | bigint,
actual: string | number | bigint,
relativeDiff: number,
) {
): void {
let expectedBN = BigInt(expected);
let actualBN = BigInt(actual);
expect(expectedBN * actualBN).to.be.gte(0, 'Values are of different sign');
Expand Down
81 changes: 40 additions & 41 deletions src/permit.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import '@nomiclabs/hardhat-ethers';
import { SignTypedDataVersion, TypedDataUtils } from '@metamask/eth-sig-util';
import { constants } from './prelude';
import { Contract, Wallet } from 'ethers';
import { Signature, TypedDataDomain, Wallet } from 'ethers';
import { ethers } from 'hardhat';
import { splitSignature } from 'ethers/lib/utils';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers';
import { AllowanceTransfer, PERMIT2_ADDRESS } from '@uniswap/permit2-sdk';
import { bytecode as permit2Bytecode } from './permit2.json';
import { DaiLikePermitMock, ERC20Permit, IPermit2 } from '../typechain-types';

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

export function trim0x(bigNumber: bigint | string) {
export function trim0x(bigNumber: bigint | string): string {
const s = bigNumber.toString();
if (s.startsWith('0x')) {
return s.substring(2);
}
return s;
}

export function cutSelector(data: string) {
export function cutSelector(data: string): string {
const hexPrefix = '0x';
return hexPrefix + data.substr(hexPrefix.length + 8);
}

export function domainSeparator(name: string, version: string, chainId: string, verifyingContract: string) {
export function domainSeparator(name: string, version: string, chainId: string, verifyingContract: string): string {
return (
'0x' +
TypedDataUtils.hashStruct(
Expand Down Expand Up @@ -96,7 +95,7 @@ export function buildDataLikeDai(
} as const;
}

export async function permit2Contract() {
export async function permit2Contract(): Promise<IPermit2> {
if ((await ethers.provider.getCode(PERMIT2_ADDRESS)) === '0x') {
await ethers.provider.send('hardhat_setCode', [PERMIT2_ADDRESS, permit2Bytecode]);
}
Expand All @@ -108,29 +107,29 @@ export async function permit2Contract() {
*/
export async function getPermit(
owner: Wallet | SignerWithAddress,
permitContract: Contract,
permitContract: ERC20Permit,
tokenVersion: string,
chainId: number,
spender: string,
value: string,
deadline = defaultDeadline.toString(),
compact = false,
) {
const nonce = await permitContract.nonces(owner.address);
): Promise<string> {
const nonce = await permitContract.nonces(owner);
const name = await permitContract.name();
const data = buildData(
name,
tokenVersion,
chainId,
permitContract.address,
await permitContract.getAddress(),
owner.address,
spender,
value,
nonce.toString(),
deadline,
);
const signature = await owner._signTypedData(data.domain, data.types, data.message);
const { v, r, s } = splitSignature(signature);
const signature = await owner.signTypedData(data.domain, data.types, data.message);
const { v, r, s } = Signature.from(signature);
const permitCall = cutSelector(permitContract.interface.encodeFunctionData('permit', [owner.address, spender, value, deadline, v, r, s]));
return compact ? compressPermit(permitCall) : decompressPermit(compressPermit(permitCall), constants.ZERO_ADDRESS, owner.address, spender);
}
Expand All @@ -147,9 +146,9 @@ export async function getPermit2(
compact = false,
expiration = defaultDeadlinePermit2,
sigDeadline = defaultDeadlinePermit2,
) {
): Promise<string> {
const permitContract = await permit2Contract();
const nonce = (await permitContract.allowance(owner.address, token, spender)).nonce;
const nonce = (await permitContract.allowance(owner, token, spender)).nonce;
const details = {
token,
amount,
Expand All @@ -161,9 +160,9 @@ export async function getPermit2(
spender,
sigDeadline,
};
const data = AllowanceTransfer.getPermitData(permitSingle, permitContract.address, chainId);
const { r, _vs } = ethers.utils.splitSignature(await owner._signTypedData(data.domain, data.types, data.values));
const permitCall = cutSelector(permitContract.interface.encodeFunctionData('permit', [owner.address, permitSingle, r + trim0x(_vs)]));
const data = AllowanceTransfer.getPermitData(permitSingle, await permitContract.getAddress(), chainId);
const sig = Signature.from(await owner.signTypedData(data.domain as TypedDataDomain, data.types, data.values));
const permitCall = cutSelector(permitContract.interface.encodeFunctionData('permit', [owner.address, permitSingle, sig.r + trim0x(sig.yParityAndS)]));
return compact ? compressPermit(permitCall) : decompressPermit(compressPermit(permitCall), token, owner.address, spender);
}

Expand All @@ -172,73 +171,73 @@ export async function getPermit2(
*/
export async function getPermitLikeDai(
holder: Wallet | SignerWithAddress,
permitContract: Contract,
permitContract: DaiLikePermitMock,
tokenVersion: string,
chainId: number,
spender: string,
allowed: boolean,
expiry = defaultDeadline.toString(),
compact = false,
) {
const nonce = await permitContract.nonces(holder.address);
): Promise<string> {
const nonce = await permitContract.nonces(holder);
const name = await permitContract.name();
const data = buildDataLikeDai(
name,
tokenVersion,
chainId,
permitContract.address,
await permitContract.getAddress(),
holder.address,
spender,
nonce.toString(),
allowed,
expiry,
);
const signature = await holder._signTypedData(data.domain, data.types, data.message);
const { v, r, s } = splitSignature(signature);
const signature = await holder.signTypedData(data.domain, data.types, data.message);
const { v, r, s } = Signature.from(signature);
const permitCall = cutSelector(permitContract.interface.encodeFunctionData(
'permit(address,address,uint256,uint256,bool,uint8,bytes32,bytes32)',
[holder.address, spender, nonce, expiry, allowed, v, r, s],
));
return compact ? compressPermit(permitCall) : decompressPermit(compressPermit(permitCall), constants.ZERO_ADDRESS, holder.address, spender);
}

export function withTarget(target: bigint | string, data: bigint | string) {
export function withTarget(target: bigint | string, data: bigint | string): string {
return target.toString() + trim0x(data);
}

// Type | EIP-2612 | DAI | Permit2
// Uncompressed | 224 | 256 | 352
// Compressed | 100 | 72 | 96

export function compressPermit(permit: string) {
const abiCoder = ethers.utils.defaultAbiCoder;
export function compressPermit(permit: string): string {
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
switch (permit.length) {
case 450: {
// IERC20Permit.permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s)
const args = abiCoder.decode(['address owner', 'address spender', 'uint256 value', 'uint256 deadline', 'uint8 v', 'bytes32 r', 'bytes32 s'], permit);
// Compact IERC20Permit.permit(uint256 value, uint32 deadline, uint256 r, uint256 vs)
return '0x' + args.value.toBigInt().toString(16).padStart(64, '0') +
(args.deadline.toString() === constants.MAX_UINT256.toString() ? '00000000' : (args.expiry.toBigInt() + 1n).toString(16).padStart(8, '0')) +
return '0x' + args.value.toString(16).padStart(64, '0') +
(args.deadline.toString() === constants.MAX_UINT256.toString() ? '00000000' : (args.deadline + 1n).toString(16).padStart(8, '0')) +
BigInt(args.r).toString(16).padStart(64, '0') +
((BigInt(args.v - 27) << 255n) | BigInt(args.s)).toString(16).padStart(64, '0');
(((args.v - 27n) << 255n) | BigInt(args.s)).toString(16).padStart(64, '0');
}
case 514: {
// IDaiLikePermit.permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s)
const args = abiCoder.decode(['address holder', 'address spender', 'uint256 nonce', 'uint256 expiry', 'bool allowed', 'uint8 v', 'bytes32 r', 'bytes32 s'], permit);
// Compact IDaiLikePermit.permit(uint32 nonce, uint32 expiry, uint256 r, uint256 vs)
return '0x' + args.nonce.toBigInt().toString(16).padStart(8, '0') +
(args.expiry.toString() === constants.MAX_UINT256.toString() ? '00000000' : (args.expiry.toBigInt() + 1n).toString(16).padStart(8, '0')) +
return '0x' + args.nonce.toString(16).padStart(8, '0') +
(args.expiry.toString() === constants.MAX_UINT256.toString() ? '00000000' : (args.expiry + 1n).toString(16).padStart(8, '0')) +
BigInt(args.r).toString(16).padStart(64, '0') +
((BigInt(args.v - 27) << 255n) | BigInt(args.s)).toString(16).padStart(64, '0');
(((args.v - 27n) << 255n) | BigInt(args.s)).toString(16).padStart(64, '0');
}
case 706: {
// IPermit2.permit(address owner, PermitSingle calldata permitSingle, bytes calldata signature)
const args = abiCoder.decode(['address owner', 'address token', 'uint160 amount', 'uint48 expiration', 'uint48 nonce', 'address spender', 'uint256 sigDeadline', 'bytes signature'], permit);
// Compact IPermit2.permit(uint160 amount, uint32 expiration, uint32 nonce, uint32 sigDeadline, uint256 r, uint256 vs)
return '0x' + args.amount.toBigInt().toString(16).padStart(40, '0') +
(args.expiration.toString() === constants.MAX_UINT48.toString() ? '00000000' : (args.expiration.toBigInt() + 1n).toString(16).padStart(8, '0')) +
return '0x' + args.amount.toString(16).padStart(40, '0') +
(args.expiration.toString() === constants.MAX_UINT48.toString() ? '00000000' : (args.expiration + 1n).toString(16).padStart(8, '0')) +
args.nonce.toString(16).padStart(8, '0') +
(args.sigDeadline.toString() === constants.MAX_UINT48.toString() ? '00000000' : (args.sigDeadline.toBigInt() + 1n).toString(16).padStart(8, '0')) +
(args.sigDeadline.toString() === constants.MAX_UINT48.toString() ? '00000000' : (args.sigDeadline + 1n).toString(16).padStart(8, '0')) +
BigInt(args.signature).toString(16).padStart(128, '0');
}
case 202:
Expand All @@ -250,8 +249,8 @@ export function compressPermit(permit: string) {
}
}

export function decompressPermit(permit: string, token: string, owner: string, spender: string) {
const abiCoder = ethers.utils.defaultAbiCoder;
export function decompressPermit(permit: string, token: string, owner: string, spender: string): string {
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
switch (permit.length) {
case 202: {
// Compact IERC20Permit.permit(uint256 value, uint32 deadline, uint256 r, uint256 vs)
Expand All @@ -269,7 +268,7 @@ export function decompressPermit(permit: string, token: string, owner: string, s
spender,
args.value,
args.deadline === 0n ? constants.MAX_UINT256 : args.deadline - 1n,
Number(args.vs >> 255n) + 27,
(args.vs >> 255n) + 27n,
args.r,
'0x' + (args.vs & 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn).toString(16).padStart(64, '0'),
],
Expand All @@ -292,7 +291,7 @@ export function decompressPermit(permit: string, token: string, owner: string, s
args.nonce,
args.expiry === 0n ? constants.MAX_UINT256 : args.expiry - 1n,
true,
Number(args.vs >> 255n) + 27,
(args.vs >> 255n) + 27n,
args.r,
'0x' + (args.vs & 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffn).toString(16).padStart(64, '0'),
],
Expand Down
4 changes: 2 additions & 2 deletions src/prelude.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Assertion, AssertionError, assert, expect, config, should } from 'chai';
import { parseUnits } from 'ethers/lib/utils';
import { parseUnits } from 'ethers';
import { time } from '@nomicfoundation/hardhat-network-helpers';

export const constants = {
Expand All @@ -19,7 +19,7 @@ export const constants = {
export { time };

export function ether(n: string): bigint {
return parseUnits(n).toBigInt();
return parseUnits(n);
}

// chai
Expand Down
Loading
Loading