Skip to content

Commit

Permalink
Add Test With Precompile (#430)
Browse files Browse the repository at this point in the history
Fixes #317 

This PR adds a new `docker-compose` file for running a local Geth node
(based on the Optimism Geth node) with RIP-7212 precompile support in
order verify that the Safe WebAuthn verifier actually works with the
[ER]IP-7212 precompile.

In particular, we just use the `@bench` tests which are already run in
CI and would give us a rough idea on how much gas a Passkey signature
verification will take with the precompile (spoiler - its just 15k gas,
which is reasonable).

I did not create a dedicated E2E test, as I felt it didn't have much
value.
  • Loading branch information
nlordell authored Jun 5, 2024
1 parent 53e28a5 commit 4b7b71a
Show file tree
Hide file tree
Showing 13 changed files with 309 additions and 176 deletions.
2 changes: 1 addition & 1 deletion examples/4337-gas-metering/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
"viem": "2.12.5"
},
"devDependencies": {
"@types/node": "20.12.12",
"@types/node": "20.14.0",
"tsx": "4.11.0",
"typescript": "^5.4.5"
}
Expand Down
2 changes: 1 addition & 1 deletion modules/4337/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"@simplewebauthn/server": "10.0.0",
"@types/chai": "^4.3.16",
"@types/mocha": "^10.0.6",
"@types/node": "^20.12.11",
"@types/node": "^20.14.0",
"@types/yargs": "^17.0.32",
"cbor": "^9.0.2",
"debug": "^4.3.4",
Expand Down
7 changes: 4 additions & 3 deletions modules/allowances/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"@types/mocha": "^10.0.6",
"@types/node": "^20.12.11",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"@types/node": "^20.14.0",
"@typescript-eslint/eslint-plugin": "^7.12.0",
"@typescript-eslint/parser": "^7.12.0",
"dotenv": "^16.4.5",
"eslint": "^8.57.0",
"ethers": "^6.12.1",
"hardhat": "^2.22.3",
"hardhat-deploy": "^0.12.4",
Expand Down
66 changes: 66 additions & 0 deletions modules/passkey/bin/bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ethers } from 'ethers'
import childProcess, { SpawnOptions } from 'node:child_process'
import path from 'node:path'

const { DOCKER } = process.env

const root = path.join(__dirname, '..')

const docker = DOCKER || 'docker'

async function exec(command: string, args: string[], options: Omit<SpawnOptions, 'stdio'> = {}) {
const process = childProcess.spawn(command, args, { ...options, stdio: 'inherit' })
await new Promise((resolve, reject) => {
process.on('exit', (code) => {
if (code === 0) {
resolve(undefined)
} else {
reject(new Error(`'${command}' process exited with code ${code}`))
}
})
})
}

async function checkRpc(...urls: string[]) {
const statuses = await Promise.all(
urls.map(async (url) => {
try {
await new ethers.JsonRpcProvider(url).getNetwork()
return true
} catch {
return false
}
}),
)
return statuses.every((ok) => ok)
}

async function main() {
console.log('==> Starting docker containers...')
await exec(docker, ['compose', 'up', '-d'], { cwd: root })

console.log('==> Waiting for Ethereum JSON RPC endpoint')
const start = Date.now()
const timeout = 60 * 1000
while (!(await checkRpc('http://localhost:8545'))) {
if (Date.now() - start > timeout) {
throw new Error('timeout waiting for local node and bundler to start')
}
}

try {
console.log('==> Running tests')
await exec('hardhat', ['test', '--network', 'localhost', '--grep', '@bench'])
} finally {
console.log('==> Shutting down')
await exec(docker, ['compose', 'down'], { cwd: root })
}
}

main().catch((err) => {
console.error('ERROR: ', err)
if (err.stderr) {
console.log(err.stderr)
}
process.exitCode = 1
})
18 changes: 18 additions & 0 deletions modules/passkey/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
version: '3.8'

services:
geth:
build:
context: .
dockerfile: docker/geth/Dockerfile
restart: always
environment:
GETH_DEV: 'true'
GETH_HTTP: 'true'
GETH_HTTP_ADDR: '0.0.0.0'
GETH_HTTP_API: 'personal,eth,net,web3,debug'
GETH_HTTP_VHOSTS: '*'
GETH_OVERRIDE_FJORD: '0'
GETH_RPC_ALLOW_UNPROTECTED_TXS: 'true'
ports:
- 8545:8545
18 changes: 18 additions & 0 deletions modules/passkey/docker/geth/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM docker.io/library/golang:latest AS builder

# The Geth fork that was easiest to get running with the RIP-7212
# precompile was the Optimism Geth node. Note that we still need to apply a
# small patch, as the precompile is only enabled with Optimism consensus which
# is not enabled in Dev mode.
RUN git clone --depth 1 --branch v1.101315.1 https://github.com/ethereum-optimism/op-geth /src

WORKDIR /src
COPY docker/geth/geth.patch .
RUN git apply geth.patch && make geth

FROM docker.io/library/debian:bookworm-slim

COPY --from=builder /src/build/bin/geth /usr/local/bin/geth

ENTRYPOINT ["geth"]
CMD []
12 changes: 12 additions & 0 deletions modules/passkey/docker/geth/geth.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
diff --git a/params/config.go b/params/config.go
index b3a96a96a..166243692 100644
--- a/params/config.go
+++ b/params/config.go
@@ -1078,6 +1078,6 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
IsOptimismBedrock: isMerge && c.IsOptimismBedrock(num),
IsOptimismRegolith: isMerge && c.IsOptimismRegolith(timestamp),
IsOptimismCanyon: isMerge && c.IsOptimismCanyon(timestamp),
- IsOptimismFjord: isMerge && c.IsOptimismFjord(timestamp),
+ IsOptimismFjord: c.IsFjord(timestamp),
}
}
4 changes: 3 additions & 1 deletion modules/passkey/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
"build"
],
"scripts": {
"bench": "hardhat test --grep @bench",
"bench": "ts-node bin/bench.ts",
"build": "pnpm run build:sol && pnpm run build:ts",
"build:sol": "rimraf build typechain-types && hardhat compile",
"build:ts": "rimraf dist && tsc",
Expand Down Expand Up @@ -54,11 +54,13 @@
"@safe-global/safe-4337": "workspace:^0.3.0",
"@safe-global/safe-4337-local-bundler": "workspace:^0.0.0",
"@simplewebauthn/server": "^10.0.0",
"@types/node": "^20.14.0",
"dotenv": "^16.4.5",
"ethers": "^6.12.1",
"hardhat": "^2.22.3",
"hardhat-deploy": "^0.12.4",
"solhint": "^5.0.1",
"ts-node": "^10.9.2",
"typescript": "^5.4.5"
},
"dependencies": {
Expand Down
41 changes: 24 additions & 17 deletions modules/passkey/test/GasBenchmarking.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from 'chai'
import { deployments, ethers } from 'hardhat'
import { deployments, ethers, network } from 'hardhat'

import * as ERC1271 from './utils/erc1271'
import { WebAuthnCredentials } from '../test/utils/webauthnShim'
Expand Down Expand Up @@ -35,13 +35,14 @@ describe('Gas Benchmarking [@bench]', function () {
const factory = await ethers.getContractAt('SafeWebAuthnSignerFactory', SafeWebAuthnSignerFactory.address)

const DummyP256Verifier = await ethers.getContractFactory('DummyP256Verifier')
const verifiers = {
fcl: await ethers.getContractAt('IP256Verifier', FCLP256Verifier.address),
daimo: await ethers.getContractAt('IP256Verifier', DaimoP256Verifier.address),
dummy: await DummyP256Verifier.deploy(),
} as Record<string, IP256Verifier>

return { benchmarker, factory, verifiers }
const verifiersConfig = {
fcl: [0, await ethers.getContractAt('IP256Verifier', FCLP256Verifier.address)],
daimo: [0, await ethers.getContractAt('IP256Verifier', DaimoP256Verifier.address)],
dummy: [0, await DummyP256Verifier.deploy()],
precompile: [0x0100, null],
} as Record<string, [number, IP256Verifier | null]>

return { benchmarker, factory, verifiersConfig }
})

describe('SafeWebAuthnSignerProxy', () => {
Expand All @@ -56,13 +57,18 @@ describe('Gas Benchmarking [@bench]', function () {
console.log(` ⛽ deployment: ${gas}`)
})

for (const [name, key] of [
['FreshCryptoLib', 'fcl'],
['daimo-eth', 'daimo'],
['Dummy', 'dummy'],
]) {
for (const [name, key, networkName] of [
['FreshCryptoLib', 'fcl', null],
['daimo-eth', 'daimo', null],
['Dummy', 'dummy', null],
['Precompile', 'precompile', 'localhost'],
] as [string, string, string | null][]) {
it(`Benchmark signer verification cost with ${name} verifier`, async function () {
const { benchmarker, verifiers, factory } = await setupTests()
if (networkName && network.name !== networkName) {
this.skip()
}

const { benchmarker, verifiersConfig, factory } = await setupTests()

const challenge = ethers.id('hello world')
const assertion = navigator.credentials.get({
Expand All @@ -75,10 +81,11 @@ describe('Gas Benchmarking [@bench]', function () {
})

const { x, y } = decodePublicKey(credential.response)
const verifier = await verifiers[key].getAddress()
const [precompile, verifier] = verifiersConfig[key]
const verifiers = ethers.solidityPacked(['uint16', 'address'], [precompile, (await verifier?.getAddress()) ?? ethers.ZeroAddress])

await factory.createSigner(x, y, verifier)
const signer = await ethers.getContractAt('SafeWebAuthnSignerSingleton', await factory.getSigner(x, y, verifier))
await factory.createSigner(x, y, verifiers)
const signer = await ethers.getContractAt('SafeWebAuthnSignerSingleton', await factory.getSigner(x, y, verifiers))
const signature = encodeWebAuthnSignature(assertion.response)

const [gas, returnData] = await benchmarker.call.staticCall(
Expand Down
2 changes: 1 addition & 1 deletion modules/passkey/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
"skipLibCheck": true,
"resolveJsonModule": true
},
"include": ["src/**/*.ts", "hardhat.config.ts", "test", "typechain-types"]
"include": ["bin", "src/**/*.ts", "hardhat.config.ts", "test", "typechain-types"]
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@
"Safe"
],
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"@typescript-eslint/eslint-plugin": "^7.12.0",
"@typescript-eslint/parser": "^7.12.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"prettier": "^3.2.5",
"prettier": "^3.3.0",
"prettier-plugin-solidity": "^1.3.1",
"rimraf": "^5.0.7"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/4337-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"prepare": "pnpm run build"
},
"dependencies": {
"@types/node": "^20.12.11",
"@types/node": "^20.14.0",
"ethers": "^6.12.1",
"rimraf" :"^5.0.7",
"typescript": "^5.4.5"
Expand Down
Loading

0 comments on commit 4b7b71a

Please sign in to comment.