Skip to content

Commit

Permalink
Add EntryPointSimulations contract
Browse files Browse the repository at this point in the history
  • Loading branch information
mmv08 committed Jun 20, 2024
1 parent 4305341 commit 732165b
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 4 deletions.
1 change: 1 addition & 0 deletions modules/4337/contracts/test/Imports.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import "@account-abstraction/contracts/core/EntryPointSimulations.sol";
19 changes: 19 additions & 0 deletions modules/4337/src/deploy/entrypointHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { DeployFunction } from 'hardhat-deploy/types'

const deploy: DeployFunction = async ({ deployments, getNamedAccounts, network }) => {
if (!network.tags.test) {
return
}

const { deployer } = await getNamedAccounts()
const { deploy } = deployments

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

export default deploy
2 changes: 2 additions & 0 deletions modules/4337/src/utils/userOp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export { PackedUserOperation, UserOperation }
type OptionalExceptFor<T, TRequired extends keyof T = keyof T> = Partial<Pick<T, Exclude<keyof T, TRequired>>> &
Required<Pick<T, TRequired>>

export const PLACEHOLDER_SIGNATURE = '0x9c8ecb7ad80d2dd4411c8827079cda17095236ee3cba1c9b81153d52af17bc9d0701228dc95a75136a3e3a0130988ba4053cc15d3805db49e2cc08d9c99562191b'

export type SafeUserOperation = {
safe: string
entryPoint: string
Expand Down
13 changes: 9 additions & 4 deletions modules/4337/test/gas/Gas.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { expect } from 'chai'
import { deployments, ethers } from 'hardhat'
import { getSafe4337Module, getEntryPoint, getFactory, getSafeModuleSetup, getSafeL2Singleton } from '../utils/setup'
import { getSafe4337Module, getEntryPoint, getFactory, getSafeModuleSetup, getSafeL2Singleton, getEntryPointSimulations } from '../utils/setup'
import { buildSignatureBytes, logUserOperationGas } from '../../src/utils/execution'
import { buildPackedUserOperationFromSafeUserOperation, buildSafeUserOpTransaction, signSafeOp } from '../../src/utils/userOp'
import { chainId } from '../utils/encoding'
import { Safe4337 } from '../../src/utils/safe'
import { estimateUserOperationGas } from '../utils/simulations'

describe('Gas Metering', () => {
const setupTests = deployments.createFixture(async ({ deployments }) => {
Expand All @@ -13,6 +14,7 @@ describe('Gas Metering', () => {

const [user] = await ethers.getSigners()
const entryPoint = await getEntryPoint()
const entryPointSimulations = await getEntryPointSimulations()
const module = await getSafe4337Module()
const proxyFactory = await getFactory()
const proxyCreationCode = await proxyFactory.proxyCreationCode()
Expand All @@ -33,6 +35,7 @@ describe('Gas Metering', () => {
return {
user,
entryPoint,
entryPointSimulations,
validator: module,
safe,
erc20Token,
Expand All @@ -41,8 +44,9 @@ describe('Gas Metering', () => {
})

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

// cover the prefund
await user.sendTransaction({ to: safe.address, value: ethers.parseEther('1.0') })
Expand All @@ -61,9 +65,10 @@ describe('Gas Metering', () => {
initCode: safe.getInitCode(),
},
)
const gasEstimation = await estimateUserOperationGas(ethers.provider, entryPointSimulations, safeOp, entryPointAddress)
console.log('Gas Estimation:', gasEstimation)

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

const userOp = buildPackedUserOperationFromSafeUserOperation({
safeOp,
signature,
Expand Down
5 changes: 5 additions & 0 deletions modules/4337/test/utils/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ export const getEntryPoint = async () => {
return await ethers.getContractAt('IEntryPoint', EntryPointDeployment.address)
}

export const getEntryPointSimulations = async () => {
const EntryPointDeployment = await deployments.get('EntryPointSimulations')
return await ethers.getContractAt('EntryPointSimulations', EntryPointDeployment.address)
}

export const getSafeAtAddress = async (address: string) => {
return await ethers.getContractAt('SafeMock', address)
}
Expand Down
91 changes: 91 additions & 0 deletions modules/4337/test/utils/simulations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { ethers } from 'ethers'
import { HardhatEthersProvider } from '@nomicfoundation/hardhat-ethers/internal/hardhat-ethers-provider'
import { buildPackedUserOperationFromSafeUserOperation, PLACEHOLDER_SIGNATURE, SafeUserOperation } from '../../src/utils/userOp'
import { EntryPointSimulations, IEntryPointSimulations } from '../../typechain-types'

export interface EstimateUserOpGasResult {
/**
* the preVerification gas used by this UserOperation.
*/
preVerificationGas: bigint
/**
* gas used for validation of this UserOperation, including account creation
*/
verificationGasLimit: bigint

/**
* (possibly future timestamp) after which this UserOperation is valid
*/
validAfter?: bigint

/**
* the deadline after which this UserOperation is invalid (not a gas estimation parameter, but returned by validation
*/
validUntil?: bigint
/**
* estimated cost of calling the account with the given callData
*/
callGasLimit: bigint
}

export const calcVerificationGasAndCallGasLimit = (
userOperation: SafeUserOperation,
executionResult: {
preOpGas: bigint
paid: bigint
},
) => {
const verificationGasLimit =
((executionResult.preOpGas - userOperation.preVerificationGas) * 3n) /
2n

let gasPrice: bigint
if (userOperation.maxPriorityFeePerGas === userOperation.maxFeePerGas) {
gasPrice = userOperation.maxFeePerGas
} else {
gasPrice = userOperation.maxFeePerGas
}

const calculatedCallGasLimit =
executionResult.paid / gasPrice - executionResult.preOpGas

const callGasLimit =
(calculatedCallGasLimit > 9000n ? calculatedCallGasLimit : 9000n) +
21000n +
50000n


return { verificationGasLimit, callGasLimit }
}

export const estimateUserOperationGas = async (
provider: HardhatEthersProvider,
entryPointSimulations: EntryPointSimulations,
safeOp: SafeUserOperation,
entryPointAddress: string,
// ): Promise<EstimateUserOpGasResult> => {
) => {
const packedUserOp = buildPackedUserOperationFromSafeUserOperation({ safeOp, signature: PLACEHOLDER_SIGNATURE })
const encodedSimulateHandleOp = entryPointSimulations.interface.encodeFunctionData('simulateHandleOp', [packedUserOp, ethers.ZeroAddress, '0x'])

const simulationData = await provider.send('eth_call', [
{
to: entryPointAddress,
data: encodedSimulateHandleOp,
},
'latest',
{
[entryPointAddress]: {
code: await entryPointSimulations.getDeployedCode(),
}
}
])
const decodedSimulationData = entryPointSimulations.interface.decodeFunctionResult('simulateHandleOp', simulationData) as unknown as IEntryPointSimulations.ExecutionResultStructOutput

console.log({ decodedSimulationData })
const callGasLimit = calcVerificationGasAndCallGasLimit(safeOp, decodedSimulationData, 1).callGasLimit

return {
callGasLimit,
}
}

0 comments on commit 732165b

Please sign in to comment.