Skip to content

Commit

Permalink
upgrade to ethers v6
Browse files Browse the repository at this point in the history
  • Loading branch information
kevincharm committed Jun 30, 2024
1 parent 2385645 commit 074a074
Show file tree
Hide file tree
Showing 5 changed files with 610 additions and 830 deletions.
45 changes: 28 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,46 @@
"test": "yarn hardhat test",
"prepack": "yarn clean && yarn check:format && yarn test && yarn build"
},
"files": ["contracts/**/*.sol"],
"keywords": ["solidity", "stateless", "shuffle", "feistel", "cipher", "fpe"],
"files": [
"contracts/**/*.sol"
],
"keywords": [
"solidity",
"stateless",
"shuffle",
"feistel",
"cipher",
"fpe"
],
"author": "Kevin Charm <[email protected]>",
"license": "MIT",
"dependencies": {},
"devDependencies": {
"@ethersproject/abi": "^5.4.7",
"@ethersproject/providers": "^5.4.7",
"@kevincharm/gfc-fpe": "^1.0.0",
"@nomicfoundation/hardhat-chai-matchers": "^1.0.0",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "^2.0.0",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-etherscan": "^3.0.0",
"@kevincharm/gfc-fpe": "^1.1.0",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.7",
"@nomicfoundation/hardhat-ethers": "^3.0.6",
"@nomicfoundation/hardhat-ignition": "^0.15.5",
"@nomicfoundation/hardhat-ignition-ethers": "^0.15.5",
"@nomicfoundation/hardhat-network-helpers": "^1.0.11",
"@nomicfoundation/hardhat-toolbox": "^5.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.8",
"@nomicfoundation/ignition-core": "^0.15.5",
"@nomiclabs/hardhat-ethers": "^2.2.3",
"@openzeppelin/contracts": "^4.7.3",
"@typechain/ethers-v5": "^10.1.0",
"@typechain/hardhat": "^6.1.2",
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"@types/chai": "^4.2.0",
"@types/mocha": "^9.1.0",
"@types/node": ">=12.0.0",
"@types/node": "^16",
"chai": "^4.2.0",
"ethers": "^5.4.7",
"hardhat": "^2.11.2",
"ethers": "^6.13.1",
"hardhat": "^2.22.5",
"hardhat-gas-reporter": "^1.0.8",
"prettier": "^2.8.8",
"prettier-plugin-solidity": "^1.1.3",
"solidity-coverage": "^0.8.0",
"solidity-coverage": "^0.8.12",
"ts-node": ">=8.0.0",
"typechain": "^8.1.0",
"typechain": "^8.3.2",
"typescript": ">=4.5.0"
}
}
47 changes: 23 additions & 24 deletions scripts/benchmark.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { BigNumber } from 'ethers'
import { ethers } from 'hardhat'
import { FeistelShuffleConsumer__factory } from '../typechain-types'
import { randomBytes } from 'crypto'
Expand Down Expand Up @@ -37,62 +36,62 @@ async function benchmarkFeistel() {

// params
const rounds = 4
const seed = BigNumber.from('0x' + randomBytes(32).toString('hex'))
const seed = BigInt('0x' + randomBytes(32).toString('hex'))

let minGasUnopt = BigNumber.from(2n ** 255n - 1n)
let maxGasUnopt = BigNumber.from(-(2n ** 255n - 1n))
let sumGasUsedUnopt = BigNumber.from(0)
let minGasOpt = BigNumber.from(2n ** 255n - 1n)
let maxGasOpt = BigNumber.from(-(2n ** 255n - 1n))
let sumGasUsedOpt = BigNumber.from(0)
let minGasUnopt = BigInt(2n ** 255n - 1n)
let maxGasUnopt = BigInt(-(2n ** 255n - 1n))
let sumGasUsedUnopt = BigInt(0)
let minGasOpt = BigInt(2n ** 255n - 1n)
let maxGasOpt = BigInt(-(2n ** 255n - 1n))
let sumGasUsedOpt = BigInt(0)
for (let i = 0; i < indices.length; i++) {
process.stdout.write(`Sending tx \x1B[33K${i}/${indices.length}\r`)
// Optimised function
const gasUsedOpt = await deployer
.sendTransaction(
await feistelShuffle.populateTransaction.shuffle__OPT(
await feistelShuffle.shuffle__OPT.populateTransaction(
i,
indices.length,
seed,
rounds
)
)
.then((tx) => tx.wait())
.then((receipt) => receipt.gasUsed)
sumGasUsedOpt = sumGasUsedOpt.add(gasUsedOpt)
if (gasUsedOpt.gt(maxGasOpt)) {
.then((receipt) => receipt!.gasUsed)
sumGasUsedOpt = sumGasUsedOpt + gasUsedOpt
if (gasUsedOpt > maxGasOpt) {
maxGasOpt = gasUsedOpt
}
if (gasUsedOpt.lt(minGasOpt)) {
if (gasUsedOpt < minGasOpt) {
minGasOpt = gasUsedOpt
}
// Un-ptimised function
const gasUsedUnopt = await deployer
.sendTransaction(
await feistelShuffle.populateTransaction.shuffle(i, indices.length, seed, rounds)
await feistelShuffle.shuffle.populateTransaction(i, indices.length, seed, rounds)
)
.then((tx) => tx.wait())
.then((receipt) => receipt.gasUsed)
sumGasUsedUnopt = sumGasUsedUnopt.add(gasUsedUnopt)
if (gasUsedUnopt.gt(maxGasUnopt)) {
.then((receipt) => receipt!.gasUsed)
sumGasUsedUnopt = sumGasUsedUnopt + gasUsedUnopt
if (gasUsedUnopt > maxGasUnopt) {
maxGasUnopt = gasUsedUnopt
}
if (gasUsedUnopt.lt(minGasUnopt)) {
if (gasUsedUnopt < minGasUnopt) {
minGasUnopt = gasUsedUnopt
}
}
return {
FeistelShuffleOptimised: {
rounds,
min: minGasOpt.sub(21000).toNumber(),
max: maxGasOpt.sub(21000).toNumber(),
avg: sumGasUsedOpt.div(indices.length).sub(21000).toNumber(),
min: Number(minGasOpt - 21000n),
max: Number(maxGasOpt - 21000n),
avg: Number(sumGasUsedOpt / BigInt(indices.length) - 21000n),
},
FeistelShuffle: {
rounds,
min: minGasUnopt.sub(21000).toNumber(),
max: maxGasUnopt.sub(21000).toNumber(),
avg: sumGasUsedUnopt.div(indices.length).sub(21000).toNumber(),
min: Number(minGasUnopt - 21000n),
max: Number(maxGasUnopt - 21000n),
avg: Number(sumGasUsedUnopt / BigInt(indices.length) - 21000n),
},
}
}
66 changes: 30 additions & 36 deletions test/FeistelShuffle.spec.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { expect } from 'chai'
import { ethers } from 'hardhat'
import { FeistelShuffleConsumer, FeistelShuffleConsumer__factory } from '../typechain-types'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { BigNumber, BigNumberish } from 'ethers'
import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'
import { BigNumberish } from 'ethers'
import { randomBytes } from 'crypto'
import * as tsFeistel from '@kevincharm/gfc-fpe'
import { solidityKeccak256 } from 'ethers/lib/utils'
const solidityKeccak256 = ethers.solidityPackedKeccak256

const f = (R: bigint, i: bigint, seed: bigint, domain: bigint) =>
BigNumber.from(
solidityKeccak256(['uint256', 'uint256', 'uint256', 'uint256'], [R, i, seed, domain])
).toBigInt()
BigInt(solidityKeccak256(['uint256', 'uint256', 'uint256', 'uint256'], [R, i, seed, domain]))

describe('FeistelShuffle', () => {
let deployer: SignerWithAddress
Expand All @@ -24,7 +22,7 @@ describe('FeistelShuffle', () => {
indices = Array(100)
.fill(0)
.map((_, i) => i)
seed = ethers.utils.defaultAbiCoder.encode(
seed = ethers.AbiCoder.defaultAbiCoder().encode(
['bytes32'],
['0x' + randomBytes(32).toString('hex')]
)
Expand Down Expand Up @@ -61,10 +59,10 @@ describe('FeistelShuffle', () => {
) {
const contractRefAnswer = await feistelShuffle.shuffle(x, domain, seed, rounds)
const refAnswer = await tsFeistel.encrypt(
BigNumber.from(x).toBigInt(),
BigNumber.from(domain).toBigInt(),
BigNumber.from(seed).toBigInt(),
BigNumber.from(rounds).toBigInt(),
BigInt(x),
BigInt(domain),
BigInt(seed),
BigInt(rounds),
f
)
expect(contractRefAnswer).to.equal(refAnswer)
Expand All @@ -78,41 +76,41 @@ describe('FeistelShuffle', () => {

it('should create permutation with FeistelShuffle', async () => {
const rounds = 4
const shuffled: BigNumber[] = []
const shuffled: bigint[] = []
for (let i = 0; i < indices.length; i++) {
const s = await feistelShuffle.shuffle__OPT(i, indices.length, seed, rounds)
shuffled.push(s)
}
assertSetEquality(
indices,
shuffled.map((s) => s.toNumber())
shuffled.map((s) => Number(s))
)

// GASSSS
let sumGasUsed = BigNumber.from(0)
let maxGasUsed = BigNumber.from(0)
let sumGasUsed = 0n
let maxGasUsed = 0n
for (let i = 0; i < indices.length; i++) {
const _txSingle = await deployer.sendTransaction(
await feistelShuffle.populateTransaction.shuffle(i, indices.length, seed, rounds)
await feistelShuffle.shuffle.populateTransaction(i, indices.length, seed, rounds)
)
const txSingle = await _txSingle.wait()
const txSingle = (await _txSingle.wait())!
const _txSingleOpt = await deployer.sendTransaction(
await feistelShuffle.populateTransaction.shuffle__OPT(
await feistelShuffle.shuffle__OPT.populateTransaction(
i,
indices.length,
seed,
rounds
)
)
const txSingleOpt = await _txSingleOpt.wait()
const txSingleOpt = (await _txSingleOpt.wait())!
expect(txSingleOpt.gasUsed).to.be.lessThan(txSingle.gasUsed)
const actualGasUsed = txSingleOpt.gasUsed.sub(21_000)
if (actualGasUsed.gt(maxGasUsed)) {
const actualGasUsed = txSingleOpt.gasUsed - 21_000n
if (actualGasUsed > maxGasUsed) {
maxGasUsed = actualGasUsed
}
sumGasUsed = sumGasUsed.add(actualGasUsed)
sumGasUsed = sumGasUsed + actualGasUsed
}
const averageGasUsed = sumGasUsed.div(indices.length)
const averageGasUsed = sumGasUsed / BigInt(indices.length)
// console.log('Feistel avg gas:', averageGasUsed)
expect(averageGasUsed).to.be.lessThanOrEqual(3450) // <-- AVG gas
// console.log('Feistel max gas:', maxGasUsed)
Expand All @@ -128,16 +126,16 @@ describe('FeistelShuffle', () => {
// Test that optimised Yul version spits out the same output
const sOpt = await feistelShuffle.shuffle__OPT(i, indices.length, seed, rounds)
expect(s).to.equal(sOpt)
shuffled.push(sOpt.toNumber())
shuffled.push(Number(sOpt))
}

const specOutput: bigint[] = []
for (const index of indices) {
const xPrime = await tsFeistel.encrypt(
BigInt(index),
BigInt(indices.length),
BigNumber.from(seed).toBigInt(),
BigNumber.from(rounds).toBigInt(),
BigInt(seed),
BigInt(rounds),
f
)
specOutput.push(xPrime)
Expand Down Expand Up @@ -182,30 +180,28 @@ describe('FeistelShuffle', () => {
modulus = 2
const shuffledTwo = new Set<number>()
for (let i = 0; i < modulus; i++) {
shuffledTwo.add((await checkedShuffle(i, modulus, seed, rounds)).toNumber())
shuffledTwo.add(Number(await checkedShuffle(i, modulus, seed, rounds)))
}
// |shuffledSet| = modulus
expect(shuffledTwo.size).to.equal(modulus)
// set equality with optimised version
for (let i = 0; i < modulus; i++) {
shuffledTwo.delete(
(await feistelShuffle.shuffle__OPT(i, modulus, seed, rounds)).toNumber()
)
shuffledTwo.delete(Number(await feistelShuffle.shuffle__OPT(i, modulus, seed, rounds)))
}
expect(shuffledTwo.size).to.equal(0)

// list size of 3
modulus = 3
const shuffledThree = new Set<number>()
for (let i = 0; i < modulus; i++) {
shuffledThree.add((await checkedShuffle(i, modulus, seed, rounds)).toNumber())
shuffledThree.add(Number(await checkedShuffle(i, modulus, seed, rounds)))
}
// |shuffledSet| = modulus
expect(shuffledThree.size).to.equal(modulus)
// set equality with optimised version
for (let i = 0; i < modulus; i++) {
shuffledThree.delete(
(await feistelShuffle.shuffle__OPT(i, modulus, seed, rounds)).toNumber()
Number(await feistelShuffle.shuffle__OPT(i, modulus, seed, rounds))
)
}
expect(shuffledThree.size).to.equal(0)
Expand All @@ -214,15 +210,13 @@ describe('FeistelShuffle', () => {
modulus = 4
const shuffledFour = new Set<number>()
for (let i = 0; i < modulus; i++) {
shuffledFour.add((await checkedShuffle(i, modulus, seed, rounds)).toNumber())
shuffledFour.add(Number(await checkedShuffle(i, modulus, seed, rounds)))
}
// |shuffledSet| = modulus
expect(shuffledFour.size).to.equal(modulus)
// set equality with optimised version
for (let i = 0; i < modulus; i++) {
shuffledFour.delete(
(await feistelShuffle.shuffle__OPT(i, modulus, seed, rounds)).toNumber()
)
shuffledFour.delete(Number(await feistelShuffle.shuffle__OPT(i, modulus, seed, rounds)))
}
expect(shuffledFour.size).to.equal(0)
})
Expand Down
4 changes: 2 additions & 2 deletions test/examples/ERC721Shuffled.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from 'chai'
import { ethers } from 'hardhat'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'
import { randomBytes } from 'crypto'
import { ERC721Shuffled, ERC721Shuffled__factory } from '../../typechain-types'

Expand All @@ -18,7 +18,7 @@ describe('ERC721Shuffled', () => {
'YEET',
maxSupply
)
seed = ethers.utils.defaultAbiCoder.encode(
seed = ethers.AbiCoder.defaultAbiCoder().encode(
['bytes32'],
['0x' + randomBytes(32).toString('hex')]
)
Expand Down
Loading

0 comments on commit 074a074

Please sign in to comment.