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

Gas Metering for Deployment & Token Operation #175

Merged
merged 62 commits into from
Dec 21, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
c6fdba2
Function made exportable
remedcu Nov 29, 2023
1dd751f
Test ERC721 Token Added
remedcu Nov 29, 2023
653aaf9
ERC721 Deployment
remedcu Nov 29, 2023
ff677b9
Gas Metering Script Created
remedcu Nov 29, 2023
dbaa339
Linting issues rectified
remedcu Nov 29, 2023
148d5cb
Linting issues rectified
remedcu Nov 29, 2023
0e0dcc3
Refactored Gas Tests
remedcu Nov 30, 2023
4a53e34
Linting issues rectified
remedcu Nov 30, 2023
99a8599
XanderBlazeNFT introduced
remedcu Nov 30, 2023
0ee2d66
NPM init
remedcu Dec 4, 2023
07397b6
Package Lock Added
remedcu Dec 4, 2023
a2cc50b
Typescript Config Added
remedcu Dec 4, 2023
857265f
gitignore and env example
remedcu Dec 4, 2023
f8e58d7
Added utils files
remedcu Dec 4, 2023
628fdac
package.json refactored
remedcu Dec 4, 2023
2ff95b6
Pimlico Paymaster Analysis Added
remedcu Dec 4, 2023
80d7694
Alchemy Paymaster Analysis Added
remedcu Dec 4, 2023
ffb691d
Timeout module added to ERC721
remedcu Dec 4, 2023
e3ee3d8
README Added
remedcu Dec 4, 2023
fdfb267
prettier added and executed
remedcu Dec 4, 2023
bece65c
Faucet address added
remedcu Dec 4, 2023
3e10d7b
General cleanup
remedcu Dec 4, 2023
12f050a
Prettier changes
remedcu Dec 4, 2023
f1fd9e2
Update paymaster-analysis/README.md
remedcu Dec 14, 2023
77512f2
Typo removed
remedcu Dec 14, 2023
c88260e
Token Operation Values moved in .env
remedcu Dec 14, 2023
f3ea00b
Made package as private
remedcu Dec 14, 2023
d8604bf
Renamed to 4337-gas-metering
remedcu Dec 14, 2023
c356271
Unified ERC20.ts
remedcu Dec 14, 2023
26afc7e
Unified UserOp.ts
remedcu Dec 14, 2023
2883502
Unified safe.ts
remedcu Dec 14, 2023
54d975f
Cleaned up
remedcu Dec 14, 2023
7a80fd1
Code Refactoring
remedcu Dec 14, 2023
9347067
Cleaned up
remedcu Dec 14, 2023
e88bc21
Alchemy Code Redundancy Removed
remedcu Dec 15, 2023
224dce0
Pimlico Code Redundancy Removed
remedcu Dec 15, 2023
9034d2b
signUserOperation was updated
remedcu Dec 15, 2023
a9df612
env file updated
remedcu Dec 18, 2023
e0f87df
npm script updated
remedcu Dec 18, 2023
5657923
npm dependencies locked in a certain version
remedcu Dec 18, 2023
4fd5bfd
package-lock auto updated
remedcu Dec 18, 2023
0f9311f
Native Transfer util added
remedcu Dec 18, 2023
46c8093
utils updated
remedcu Dec 18, 2023
03421bc
Native Transfer, Paymaster, Mumbai Support, etc
remedcu Dec 18, 2023
086a6a4
Module v0.2 compatibility
remedcu Dec 19, 2023
e3d153a
Native Transfer Tests Added
remedcu Dec 19, 2023
97e95d3
Added check for ETH Transfer
remedcu Dec 19, 2023
4cae569
Added Pimlico & Alchemy Gas Usage README
remedcu Dec 19, 2023
83f26d6
Gas Logging in UserOps
remedcu Dec 19, 2023
d72c8f3
Paymaster and Test Gas Results Added
remedcu Dec 19, 2023
391064b
Formatting Changes
remedcu Dec 19, 2023
8a7323a
Lint issue rectified
remedcu Dec 19, 2023
33a4e05
Gelato Based Gas Results Added
remedcu Dec 20, 2023
de3fb3b
env.example updated with Gelato
remedcu Dec 20, 2023
fc338a8
Merge branch 'master' into gas-metering
remedcu Dec 20, 2023
e2f35ba
Updated Gas Spec based on master changes
remedcu Dec 20, 2023
23355ad
Updated Gas Spec based on master changes
remedcu Dec 20, 2023
cdffaa1
Tenderly Gas Profiler Link Added
remedcu Dec 21, 2023
2286ba0
README Updated with Detailed Analysis
remedcu Dec 21, 2023
ce6abc7
Github Workflow and Formatting Issues Rectified
remedcu Dec 21, 2023
783611c
Merge remote-tracking branch 'origin/master' into gas-metering
remedcu Dec 21, 2023
cb0a1fd
Github Workflow for Checks added
remedcu Dec 21, 2023
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
13 changes: 13 additions & 0 deletions 4337/contracts/test/TestERC721Token.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";

contract TestERC721Token is ERC721 {
constructor() ERC721("ERC 721 Token", "ERC721") {}

// @dev This can be called by anyone.
function safeMint(address to, uint256 tokenId) public {
_safeMint(to, tokenId, "");
}
}
7 changes: 7 additions & 0 deletions 4337/src/deploy/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ const deploy: DeployFunction = async ({ deployments, getNamedAccounts, network }
log: true,
deterministicDeployment: true,
})

await deploy('TestERC721Token', {
from: deployer,
args: [],
log: true,
deterministicDeployment: true,
})
}

export default deploy
236 changes: 236 additions & 0 deletions 4337/test/gas/Gas.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
import { expect } from 'chai'
import { deployments, ethers } from 'hardhat'
import { getSafe4337Module, getEntryPoint, getFactory, getAddModulesLib, getSafeL2Singleton } from '../utils/setup'
import { buildSignatureBytes, logGas } from '../../src/utils/execution'
import { buildUserOperationFromSafeUserOperation, buildSafeUserOpTransaction, signSafeOp } from '../../src/utils/userOp'
import { chainId } from '../utils/encoding'
import { Safe4337 } from '../../src/utils/safe'

describe('Gas Metering', () => {
const setupTests = deployments.createFixture(async ({ deployments }) => {
await deployments.fixture()
const { HariWillibaldToken, TestERC721Token } = await deployments.run()

const [user] = await ethers.getSigners()
const entryPoint = await getEntryPoint()
const module = await getSafe4337Module()
const proxyFactory = await getFactory()
const proxyCreationCode = await proxyFactory.proxyCreationCode()
const addModulesLib = await getAddModulesLib()
const singleton = await getSafeL2Singleton()
const safe = await Safe4337.withSigner(user.address, {
safeSingleton: await singleton.getAddress(),
entryPoint: await entryPoint.getAddress(),
erc4337module: await module.getAddress(),
proxyFactory: await proxyFactory.getAddress(),
addModulesLib: await addModulesLib.getAddress(),
proxyCreationCode,
chainId: Number(await chainId()),
})
const erc20Token = await ethers.getContractAt('HariWillibaldToken', HariWillibaldToken.address)
const erc721Token = await ethers.getContractAt('TestERC721Token', TestERC721Token.address)

return {
user,
entryPoint,
validator: module,
safe,
erc20Token,
erc721Token,
}
})

describe('Safe Deployment + Enabling 4337 Module', () => {
it('Safe with 4337 Module Deployment', async () => {
const { user, entryPoint, validator, safe } = await setupTests()

expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0)

const safeOp = buildSafeUserOpTransaction(
safe.address,
safe.address, // No functions are called.
0,
'0x',
await entryPoint.getNonce(safe.address, 0),
await entryPoint.getAddress(),
)

const signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())])

const userOp = buildUserOperationFromSafeUserOperation({
safeAddress: safe.address,
safeOp,
signature,
initCode: safe.getInitCode(),
})

await logGas('Safe with 4337 Module Deployment', entryPoint.executeUserOp(userOp, 0))

expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0)
})
})

describe('Safe Deployment + Enabling 4337 Module + Token Operations', () => {
it('Safe with 4337 Module Deployment + ERC20 Token Transfer', async () => {
const { user, entryPoint, validator, safe, erc20Token } = await setupTests()

expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0)

expect(await erc20Token.balanceOf(safe.address)).to.equal(0)
await erc20Token.transfer(safe.address, ethers.parseUnits('4.2', 18)).then((tx) => tx.wait())
expect(await erc20Token.balanceOf(safe.address)).to.equal(ethers.parseUnits('4.2', 18))

const safeOp = buildSafeUserOpTransaction(
safe.address,
await erc20Token.getAddress(),
0,
erc20Token.interface.encodeFunctionData('transfer', [user.address, await erc20Token.balanceOf(safe.address)]),
await entryPoint.getNonce(safe.address, 0),
await entryPoint.getAddress(),
)

const signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())])

const userOp = buildUserOperationFromSafeUserOperation({
safeAddress: safe.address,
safeOp,
signature,
initCode: safe.getInitCode(),
})

await logGas('Safe with 4337 Module Deployment + ERC20 Transfer', entryPoint.executeUserOp(userOp, 0))
expect(await erc20Token.balanceOf(safe.address)).to.equal(0)
expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0)
})

it('Safe with 4337 Module Deployment + ERC721 Token Minting', async () => {
const { user, entryPoint, validator, safe, erc721Token } = await setupTests()
const tokenID = 1

expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0)

const safeOp = buildSafeUserOpTransaction(
safe.address,
await erc721Token.getAddress(),
0,
erc721Token.interface.encodeFunctionData('safeMint', [safe.address, tokenID]),
await entryPoint.getNonce(safe.address, 0),
await entryPoint.getAddress(),
)
const signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())])
const userOp = buildUserOperationFromSafeUserOperation({
safeAddress: safe.address,
safeOp,
signature,
initCode: safe.getInitCode(),
})

expect(await erc721Token.balanceOf(safe.address)).to.equal(0)
await logGas('Safe with 4337 Module Deployment + ERC721 Transfer', entryPoint.executeUserOp(userOp, 0))
expect(await erc721Token.balanceOf(safe.address)).to.equal(1)
expect(await erc721Token.ownerOf(tokenID)).to.equal(safe.address)
expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0)
})
})

describe('Token Operations Only', () => {
it('Safe with 4337 Module ERC20 Token Transfer', async () => {
const { user, entryPoint, validator, safe, erc20Token } = await setupTests()

expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0)

let safeOp = buildSafeUserOpTransaction(
safe.address,
safe.address, // No functions are called.
0,
'0x',
await entryPoint.getNonce(safe.address, 0),
await entryPoint.getAddress(),
)
let signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())])
let userOp = buildUserOperationFromSafeUserOperation({
safeAddress: safe.address,
safeOp,
signature,
initCode: safe.getInitCode(),
})

await entryPoint.executeUserOp(userOp, 0)
expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0)

// Now Token Transfer
expect(await erc20Token.balanceOf(safe.address)).to.equal(0)
await erc20Token.transfer(safe.address, ethers.parseUnits('4.2', 18)).then((tx) => tx.wait())
expect(await erc20Token.balanceOf(safe.address)).to.equal(ethers.parseUnits('4.2', 18))

safeOp = buildSafeUserOpTransaction(
safe.address,
await erc20Token.getAddress(),
0,
erc20Token.interface.encodeFunctionData('transfer', [user.address, await erc20Token.balanceOf(safe.address)]),
await entryPoint.getNonce(safe.address, 0),
await entryPoint.getAddress(),
)
signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())])
userOp = buildUserOperationFromSafeUserOperation({
safeAddress: safe.address,
safeOp,
signature,
initCode: safe.getInitCode(),
})

await logGas('Safe with 4337 Module ERC20 Transfer', entryPoint.executeUserOp(userOp, 0))

expect(await erc20Token.balanceOf(safe.address)).to.equal(0)
})

it('Safe with 4337 Module ERC721 Token Minting', async () => {
const { user, entryPoint, validator, safe, erc721Token } = await setupTests()

expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.equal(0)

let safeOp = buildSafeUserOpTransaction(
safe.address,
safe.address, // No functions are called.
0,
'0x',
await entryPoint.getNonce(safe.address, 0),
await entryPoint.getAddress(),
)
let signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())])
let userOp = buildUserOperationFromSafeUserOperation({
safeAddress: safe.address,
safeOp,
signature,
initCode: safe.getInitCode(),
})

await entryPoint.executeUserOp(userOp, 0)
expect(ethers.dataLength(await ethers.provider.getCode(safe.address))).to.not.equal(0)

// Now ERC721 Token Transfer
const tokenID = 1

safeOp = buildSafeUserOpTransaction(
safe.address,
await erc721Token.getAddress(),
0,
erc721Token.interface.encodeFunctionData('safeMint', [safe.address, tokenID]),
await entryPoint.getNonce(safe.address, 0),
await entryPoint.getAddress(),
)
signature = buildSignatureBytes([await signSafeOp(user, await validator.getAddress(), safeOp, await chainId())])
userOp = buildUserOperationFromSafeUserOperation({
safeAddress: safe.address,
safeOp,
signature,
initCode: safe.getInitCode(),
})

expect(await erc721Token.balanceOf(safe.address)).to.equal(0)
await logGas('Safe with 4337 Module ERC721 Transfer', entryPoint.executeUserOp(userOp, 0))
expect(await erc721Token.balanceOf(safe.address)).to.equal(1)
expect(await erc721Token.ownerOf(tokenID)).to.equal(safe.address)
})
})
})