Skip to content

Commit

Permalink
Merge branch 'eth-infinitism:develop' into revert-436-AA-300-legacy-i…
Browse files Browse the repository at this point in the history
…nterfaces
  • Loading branch information
Joyce Jones authored Dec 25, 2024
2 parents 12fa215 + 720ce6d commit 946f581
Show file tree
Hide file tree
Showing 16 changed files with 259 additions and 169 deletions.
5 changes: 1 addition & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,6 @@ jobs:

gas-checks:
runs-on: ubuntu-latest
services:
localgeth:
image: dtr22/geth-dev

steps:
- uses: actions/setup-node@v1
Expand Down Expand Up @@ -80,7 +77,7 @@ jobs:
- run: yarn compile

- run: FORCE_COLOR=1 yarn coverage
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v4
with:
name: solidity-coverage
path: |
Expand Down
2 changes: 1 addition & 1 deletion contracts/core/EntryPoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ contract EntryPoint is IEntryPoint, StakeManager, NonceManager, ReentrancyGuard,

SenderCreator private immutable _senderCreator = new SenderCreator();

function senderCreator() internal view virtual returns (SenderCreator) {
function senderCreator() public view virtual returns (ISenderCreator) {
return _senderCreator;
}

Expand Down
4 changes: 2 additions & 2 deletions contracts/core/EntryPointSimulations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract EntryPointSimulations is EntryPoint, IEntryPointSimulations {
_senderCreator = SenderCreator(createdObj);
}

function senderCreator() internal view virtual override returns (SenderCreator) {
function senderCreator() public view virtual override(EntryPoint, IEntryPoint) returns (ISenderCreator) {
// return the same senderCreator as real EntryPoint.
// this call is slightly (100) more expensive than EntryPoint's access to immutable member
return _senderCreator;
Expand All @@ -35,7 +35,7 @@ contract EntryPointSimulations is EntryPoint, IEntryPointSimulations {
* it as entrypoint, since the simulation functions don't check the signatures
*/
constructor() {
require(block.number < 100, "should not be deployed");
require(block.number < 1000, "should not be deployed");
}

/// @inheritdoc IEntryPointSimulations
Expand Down
13 changes: 12 additions & 1 deletion contracts/core/SenderCreator.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.23;

import "../interfaces/ISenderCreator.sol";

/**
* Helper contract for EntryPoint, to call userOp.initCode from a "neutral" address,
* which is explicitly not the entryPoint itself.
*/
contract SenderCreator {
contract SenderCreator is ISenderCreator {
address public immutable entryPoint;

constructor(){
entryPoint = msg.sender;
}

/**
* Call the "initCode" factory to create and return the sender account address.
* @param initCode - The initCode value from a UserOp. contains 20 bytes of factory address,
Expand All @@ -15,6 +23,9 @@ contract SenderCreator {
function createSender(
bytes calldata initCode
) external returns (address sender) {
if (msg.sender != entryPoint) {
revert("AA97 should call from EntryPoint");
}
address factory = address(bytes20(initCode[0:20]));
bytes memory initCallData = initCode[20:];
bool success;
Expand Down
6 changes: 6 additions & 0 deletions contracts/interfaces/IEntryPoint.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import "./PackedUserOperation.sol";
import "./IStakeManager.sol";
import "./IAggregator.sol";
import "./INonceManager.sol";
import "./ISenderCreator.sol";

interface IEntryPoint is IStakeManager, INonceManager {
/***
Expand Down Expand Up @@ -220,4 +221,9 @@ interface IEntryPoint is IStakeManager, INonceManager {
* @param data data to pass to target in a delegatecall
*/
function delegateAndRevert(address target, bytes calldata data) external;

/**
* @notice Retrieves the immutable SenderCreator contract which is responsible for deployment of sender contracts.
*/
function senderCreator() external view returns (ISenderCreator);
}
11 changes: 11 additions & 0 deletions contracts/interfaces/ISenderCreator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ISenderCreator {
/**
* @dev Creates a new sender contract.
* @return sender Address of the newly created sender contract.
*/
function createSender(bytes calldata initCode) external returns (address sender);
}
4 changes: 4 additions & 0 deletions contracts/samples/SimpleAccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pragma solidity ^0.8.23;
import "@openzeppelin/contracts/utils/Create2.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

import "../interfaces/ISenderCreator.sol";
import "./SimpleAccount.sol";

/**
Expand All @@ -14,9 +15,11 @@ import "./SimpleAccount.sol";
*/
contract SimpleAccountFactory {
SimpleAccount public immutable accountImplementation;
ISenderCreator public immutable senderCreator;

constructor(IEntryPoint _entryPoint) {
accountImplementation = new SimpleAccount(_entryPoint);
senderCreator = _entryPoint.senderCreator();
}

/**
Expand All @@ -26,6 +29,7 @@ contract SimpleAccountFactory {
* This method returns an existing account address so that entryPoint.getSenderAddress() would work even after account creation
*/
function createAccount(address owner,uint256 salt) public returns (SimpleAccount ret) {
require(msg.sender == address(senderCreator), "only callable from SenderCreator");
address addr = getAddress(owner, salt);
uint256 codeSize = addr.code.length;
if (codeSize > 0) {
Expand Down
98 changes: 55 additions & 43 deletions gascalc/GasChecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import '../test/aa.init'
import { defaultAbiCoder, hexConcat, parseEther } from 'ethers/lib/utils'
import {
AddressZero,
checkForGeth,
createAddress,
createAccountOwner,
deployEntryPoint, decodeRevertReason
Expand All @@ -27,7 +26,8 @@ const gasCheckerLogFile = './reports/gas-checker.txt'

const ethers = hre.ethers
const provider = hre.ethers.provider
let ethersSigner = provider.getSigner()
const junkWallet = Wallet.fromMnemonic('test test test test test test test test test test test junk')
const globalSigner = new Wallet(junkWallet.privateKey, provider)
let lastGasUsed: number

const minDepositOrBalance = parseEther('0.1')
Expand Down Expand Up @@ -93,6 +93,7 @@ export class GasChecker {
accountOwner: Wallet

accountInterface: SimpleAccountInterface
private locked: boolean

constructor () {
this.accountOwner = createAccountOwner()
Expand Down Expand Up @@ -129,7 +130,7 @@ export class GasChecker {
defaultAbiCoder.encode(['address'], [this.entryPoint().address])
]), 0, 2885201)
console.log('factaddr', factoryAddress)
const fact = SimpleAccountFactory__factory.connect(factoryAddress, ethersSigner)
const fact = SimpleAccountFactory__factory.connect(factoryAddress, globalSigner)
// create accounts
const creationOps: PackedUserOperation[] = []
for (const n of range(count)) {
Expand All @@ -139,30 +140,32 @@ export class GasChecker {
const addr = await fact.getAddress(this.accountOwner.address, salt)

if (!this.createdAccounts.has(addr)) {
// explicit call to fillUseROp with no "entryPoint", to make sure we manually fill everything and
// not attempt to fill from blockchain.
const op = signUserOp(await fillUserOp({
sender: addr,
nonce: 0,
callGasLimit: 30000,
verificationGasLimit: 1000000,
// paymasterAndData: paymaster,
preVerificationGas: 1,
maxFeePerGas: 0
}), this.accountOwner, this.entryPoint().address, await provider.getNetwork().then(net => net.chainId))
creationOps.push(packUserOp(op))
const codeSize = await provider.getCode(addr).then(code => code.length)
if (codeSize === 2) {
// explicit call to fillUseROp with no "entryPoint", to make sure we manually fill everything and
// not attempt to fill from blockchain.
const op = signUserOp(await fillUserOp({
sender: addr,
initCode: this.accountInitCode(fact, salt),
nonce: 0,
callGasLimit: 30000,
verificationGasLimit: 1000000,
// paymasterAndData: paymaster,
preVerificationGas: 1,
maxFeePerGas: 0
}), this.accountOwner, this.entryPoint().address, await provider.getNetwork().then(net => net.chainId))
creationOps.push(packUserOp(op))
}
this.createdAccounts.add(addr)
}

this.accounts[addr] = this.accountOwner
// deploy if not already deployed.
await fact.createAccount(this.accountOwner.address, salt)
const accountBalance = await GasCheckCollector.inst.entryPoint.balanceOf(addr)
if (accountBalance.lte(minDepositOrBalance)) {
await GasCheckCollector.inst.entryPoint.depositTo(addr, { value: minDepositOrBalance.mul(5) })
}
}
await this.entryPoint().handleOps(creationOps, ethersSigner.getAddress())
await this.entryPoint().handleOps(creationOps, globalSigner.getAddress())
}

/**
Expand Down Expand Up @@ -203,7 +206,7 @@ export class GasChecker {
const destBalance = await getBalance(dest)
if (destBalance.eq(0)) {
console.log('dest replenish', dest)
await ethersSigner.sendTransaction({ to: dest, value: 1 })
await globalSigner.sendTransaction({ to: dest, value: 1 })
}
}
const accountExecFromEntryPoint = this.accountExec(dest, destValue, destCallData)
Expand All @@ -224,21 +227,30 @@ export class GasChecker {
}
// console.debug('== account est=', accountEst.toString())
accountEst = est.accountEst
const op = await fillSignAndPack({
sender: account,
callData: accountExecFromEntryPoint,
maxPriorityFeePerGas: info.gasPrice,
maxFeePerGas: info.gasPrice,
callGasLimit: accountEst,
verificationGasLimit: 1000000,
paymaster: paymaster,
paymasterVerificationGasLimit: 50000,
paymasterPostOpGasLimit: 50000,
preVerificationGas: 1
}, accountOwner, GasCheckCollector.inst.entryPoint)
// const packed = packUserOp(op, false)
// console.log('== packed cost=', callDataCost(packed), packed)
return op
while (this.locked) {
await new Promise(resolve => setTimeout(resolve, 1))
}
try {
this.locked = true

const op = await fillSignAndPack({
sender: account,
callData: accountExecFromEntryPoint,
maxPriorityFeePerGas: info.gasPrice,
maxFeePerGas: info.gasPrice,
callGasLimit: accountEst,
verificationGasLimit: 1000000,
paymaster: paymaster,
paymasterVerificationGasLimit: 50000,
paymasterPostOpGasLimit: 50000,
preVerificationGas: 1
}, accountOwner, GasCheckCollector.inst.entryPoint)
// const packed = packUserOp(op, false)
// console.log('== packed cost=', callDataCost(packed), packed)
return op
} finally {
this.locked = false
}
}))

const txdata = GasCheckCollector.inst.entryPoint.interface.encodeFunctionData('handleOps', [userOps, info.beneficiary])
Expand All @@ -254,6 +266,13 @@ export class GasChecker {
throw e
})
const ret = await GasCheckCollector.inst.entryPoint.handleOps(userOps, info.beneficiary, { gasLimit: gasEst.mul(3).div(2) })
// "ret.wait()" is dead slow without it...
for (let count = 0; count < 100; count++) {
if (await provider.getTransactionReceipt(ret.hash) != null) {
break
}
await new Promise(resolve => setTimeout(resolve, 10))
}
const rcpt = await ret.wait()
const gasUsed = rcpt.gasUsed.toNumber()
const countSuccessOps = rcpt.events?.filter(e => e.event === 'UserOperationEvent' && e.args?.success).length
Expand Down Expand Up @@ -316,20 +335,13 @@ export class GasCheckCollector {
}

async _init (entryPointAddressOrTest: string = 'test'): Promise<this> {
console.log('signer=', await ethersSigner.getAddress())
console.log('signer=', await globalSigner.getAddress())
DefaultGasTestInfo.beneficiary = createAddress()

const bal = await getBalance(ethersSigner.getAddress())
if (bal.gt(parseEther('100000000'))) {
console.log('DONT use geth miner.. use account 2 instead')
await checkForGeth()
ethersSigner = ethers.provider.getSigner(2)
}

if (entryPointAddressOrTest === 'test') {
this.entryPoint = await deployEntryPoint(provider)
} else {
this.entryPoint = EntryPoint__factory.connect(entryPointAddressOrTest, ethersSigner)
this.entryPoint = EntryPoint__factory.connect(entryPointAddressOrTest, globalSigner)
}

const tableHeaders = [
Expand Down
Loading

0 comments on commit 946f581

Please sign in to comment.