diff --git a/contracts/core/EntryPointSimulations.sol b/contracts/core/EntryPointSimulations.sol index 9e5f69f35..9a5f50c9f 100644 --- a/contracts/core/EntryPointSimulations.sol +++ b/contracts/core/EntryPointSimulations.sol @@ -16,6 +16,14 @@ contract EntryPointSimulations is EntryPoint, IEntryPointSimulations { // solhint-disable-next-line var-name-mixedcase AggregatorStakeInfo private NOT_AGGREGATED = AggregatorStakeInfo(address(0), StakeInfo(0, 0)); + /** + * simulation contract should not be deployed, and specifically, accounts should not trust + * it as entrypoint, since the simulation functions don't check the signatures + */ + constructor() { + require(block.number < 100, "should not be deployed"); + } + /// @inheritdoc IEntryPointSimulations function simulateValidation( UserOperation calldata userOp diff --git a/gascalc/GasChecker.ts b/gascalc/GasChecker.ts index b0029d083..6c71440e5 100644 --- a/gascalc/GasChecker.ts +++ b/gascalc/GasChecker.ts @@ -5,8 +5,7 @@ import { AddressZero, checkForGeth, createAddress, - createAccountOwner, - deployActualEntryPoint + createAccountOwner } from '../test/testutils' import { EntryPoint, EntryPoint__factory, SimpleAccountFactory, diff --git a/test/entrypoint.test.ts b/test/entrypoint.test.ts index 7cc306a38..a6f25312e 100644 --- a/test/entrypoint.test.ts +++ b/test/entrypoint.test.ts @@ -49,7 +49,7 @@ import { getAggregatedAccountInitCode, decodeRevertReason } from './testutils' -import { DefaultsForUserOp, fillAndSign, getUserOpHash } from './UserOp' +import { DefaultsForUserOp, fillAndSign, getUserOpHash, simulateValidation } from './UserOp' import { UserOperation } from './UserOperation' import { PopulatedTransaction } from 'ethers/lib/ethers' import { ethers } from 'hardhat' @@ -236,7 +236,7 @@ describe('EntryPoint', function () { sender: await getAccountAddress(accountOwner1.address, simpleAccountFactory) }, accountOwner1, entryPoint) await fund(op1.sender) - await entryPoint.simulateValidation(op1, { gasLimit: 10e6 }).catch(e => e) + await simulateValidation(op1, entryPoint.address, { gasLimit: 10e6 }).catch(e => e) const block = await ethers.provider.getBlock('latest') const hash = block.transactions[0] await checkForBannedOps(hash, false) @@ -278,11 +278,11 @@ describe('EntryPoint', function () { paymasterAndData: '0x' } try { - await entryPoint.callStatic.simulateValidation(userOp, { gasLimit: 1e6 }) + await simulateValidation(userOp, entryPoint.address, { gasLimit: 1e6 }) console.log('after first simulation') await ethers.provider.send('evm_mine', []) - await expect(entryPoint.simulateValidation(userOp, { gasLimit: 1e6 })) + await expect(simulateValidation(userOp, entryPoint.address, { gasLimit: 1e6 })) .to.revertedWith('Revert after first validation') // if we get here, it means the userOp passed first sim and reverted second expect.fail(null, null, 'should fail on first simulation') @@ -306,7 +306,7 @@ describe('EntryPoint', function () { callData: badData.data! } const beneficiaryAddress = createAddress() - await entryPoint.callStatic.simulateValidation(badOp, { gasLimit: 3e5 }) + await simulateValidation(badOp, entryPoint.address, { gasLimit: 3e5 }) const tx = await entryPoint.handleOps([badOp], beneficiaryAddress) // { gasLimit: 3e5 }) const receipt = await tx.wait() @@ -328,13 +328,14 @@ describe('EntryPoint', function () { } const beneficiaryAddress = createAddress() try { - await entryPoint.simulateValidation(badOp, { gasLimit: 1e6 }) + await simulateValidation(badOp, entryPoint.address, { gasLimit: 1e6 }) + throw new Error('should revert') } catch (e: any) { if ((e as Error).message.includes('ValidationResult')) { const tx = await entryPoint.handleOps([badOp], beneficiaryAddress, { gasLimit: 1e6 }) await tx.wait() } else { - expect(e.message).to.include('FailedOp(0, "AA23 reverted (or OOG)")') + expect(e.message).to.include('AA23 reverted (or OOG)') } } }) @@ -352,13 +353,14 @@ describe('EntryPoint', function () { } const beneficiaryAddress = createAddress() try { - await entryPoint.simulateValidation(badOp, { gasLimit: 1e6 }) + await simulateValidation(badOp, entryPoint.address, { gasLimit: 1e6 }) + throw new Error('should revert') } catch (e: any) { if ((e as Error).message.includes('ValidationResult')) { const tx = await entryPoint.handleOps([badOp], beneficiaryAddress, { gasLimit: 1e6 }) await tx.wait() } else { - expect(e.message).to.include('FailedOp(0, "AA23 reverted (or OOG)")') + expect(e.message).to.include('AA23 reverted (or OOG)') } } }) @@ -660,13 +662,13 @@ describe('EntryPoint', function () { verificationGasLimit: 5e5 }, accountOwner, entryPoint) // must succeed with enough verification gas - await entryPoint.callStatic.simulateValidation(op0) + await simulateValidation(op0, entryPoint.address) const op1 = await fillAndSign({ sender: account.address, verificationGasLimit: 10000 }, accountOwner, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(op1)) + await expect(simulateValidation(op1, entryPoint.address)) .to.revertedWith('AA23 reverted (or OOG)') }) }) @@ -784,7 +786,7 @@ describe('EntryPoint', function () { verificationGasLimit: 76000 }, accountOwner2, entryPoint) - await entryPoint.callStatic.simulateValidation(op2, { gasPrice: 1e9 }) + await simulateValidation(op2, entryPoint.address) await fund(op1.sender) await fund(account2.address) @@ -967,7 +969,7 @@ describe('EntryPoint', function () { }) it('simulateValidation should return aggregator and its stake', async () => { await aggregator.addStake(entryPoint.address, 3, { value: TWO_ETH }) - const { aggregatorInfo } = await entryPoint.callStatic.simulateValidation(userOp) + const { aggregatorInfo } = await simulateValidation(userOp, entryPoint.address) expect(aggregatorInfo.aggregator).to.equal(aggregator.address) expect(aggregatorInfo.stakeInfo.stake).to.equal(TWO_ETH) expect(aggregatorInfo.stakeInfo.unstakeDelaySec).to.equal(3) @@ -1008,7 +1010,7 @@ describe('EntryPoint', function () { verificationGasLimit: 3e6, callGasLimit: 1e6 }, account2Owner, entryPoint) - await expect(entryPoint.simulateValidation(op)).to.revertedWith('"AA30 paymaster not deployed"') + await expect(simulateValidation(op, entryPoint.address)).to.revertedWith('"AA30 paymaster not deployed"') }) it('should fail if paymaster has no deposit', async function () { @@ -1067,7 +1069,7 @@ describe('EntryPoint', function () { initCode: getAccountInitCode(anOwner.address, simpleAccountFactory) }, anOwner, entryPoint) - const { paymasterInfo } = await entryPoint.callStatic.simulateValidation(op) + const { paymasterInfo } = await simulateValidation(op, entryPoint.address) const { stake: simRetStake, unstakeDelaySec: simRetDelay @@ -1098,7 +1100,7 @@ describe('EntryPoint', function () { const userOp = await fillAndSign({ sender: account.address }, sessionOwner, entryPoint) - const ret = await entryPoint.callStatic.simulateValidation(userOp) + const ret = await simulateValidation(userOp, entryPoint.address) expect(ret.returnInfo.validUntil).to.eql(now + 60) expect(ret.returnInfo.validAfter).to.eql(100) }) @@ -1109,7 +1111,7 @@ describe('EntryPoint', function () { const userOp = await fillAndSign({ sender: account.address }, expiredOwner, entryPoint) - const ret = await entryPoint.callStatic.simulateValidation(userOp) + const ret = await simulateValidation(userOp, entryPoint.address) expect(ret.returnInfo.validUntil).eql(now - 60) expect(ret.returnInfo.validAfter).to.eql(123) }) @@ -1132,7 +1134,7 @@ describe('EntryPoint', function () { sender: account.address, paymasterAndData: hexConcat([paymaster.address, timeRange]) }, ethersSigner, entryPoint) - const ret = await entryPoint.callStatic.simulateValidation(userOp) + const ret = await simulateValidation(userOp, entryPoint.address) expect(ret.returnInfo.validUntil).to.eql(now + 60) expect(ret.returnInfo.validAfter).to.eql(123) }) @@ -1143,7 +1145,7 @@ describe('EntryPoint', function () { sender: account.address, paymasterAndData: hexConcat([paymaster.address, timeRange]) }, ethersSigner, entryPoint) - const ret = await entryPoint.callStatic.simulateValidation(userOp) + const ret = await simulateValidation(userOp, entryPoint.address) expect(ret.returnInfo.validUntil).to.eql(now - 60) expect(ret.returnInfo.validAfter).to.eql(321) }) @@ -1166,7 +1168,7 @@ describe('EntryPoint', function () { async function simulateWithPaymasterParams (after: number, until: number): Promise { const userOp = await createOpWithPaymasterParams(owner, after, until) - const ret = await entryPoint.callStatic.simulateValidation(userOp) + const ret = await simulateValidation(userOp, entryPoint.address) return ret.returnInfo } diff --git a/test/entrypointsimulations.test.ts b/test/entrypointsimulations.test.ts index fd50194c4..8920057ab 100644 --- a/test/entrypointsimulations.test.ts +++ b/test/entrypointsimulations.test.ts @@ -12,11 +12,10 @@ import { createAccount, createAccountOwner, createAddress, - deployActualEntryPoint, fund, getAccountAddress, getAccountInitCode, - getBalance + getBalance, deployEntryPoint } from './testutils' import { fillAndSign, simulateHandleOp, simulateValidation } from './UserOp' @@ -35,7 +34,7 @@ describe('EntryPointSimulations', function () { let epSimulation: EntryPointSimulations before(async function () { - entryPoint = await deployActualEntryPoint() + entryPoint = await deployEntryPoint() epSimulation = await new EntryPointSimulations__factory(provider.getSigner()).deploy() accountOwner = createAccountOwner(); @@ -47,7 +46,7 @@ describe('EntryPointSimulations', function () { // await checkStateDiffSupported() }) - describe('Simulation Contract Sannity checks', () => { + describe('Simulation Contract Sanity checks', () => { const addr = createAddress() // coverage skews gas checks. diff --git a/test/paymaster.test.ts b/test/paymaster.test.ts index 49127127c..2e28ce782 100644 --- a/test/paymaster.test.ts +++ b/test/paymaster.test.ts @@ -26,7 +26,7 @@ import { createAccount, getAccountAddress } from './testutils' -import { fillAndSign } from './UserOp' +import { fillAndSign, simulateValidation } from './UserOp' import { hexConcat, parseEther } from 'ethers/lib/utils' import { UserOperation } from './UserOperation' import { hexValue } from '@ethersproject/bytes' @@ -140,7 +140,7 @@ describe('EntryPoint with paymaster', function () { // paymaster is the token, so no need for "approve" or any init function... const snapshot = await ethers.provider.send('evm_snapshot', []) - await entryPoint.simulateValidation(createOp, { gasLimit: 5e6 }).catch(e => e.message) + await simulateValidation(createOp, entryPoint.address, { gasLimit: 5e6 }).catch(e => e.message) const [tx] = await ethers.provider.getBlock('latest').then(block => block.transactions) await checkForBannedOps(tx, true) await ethers.provider.send('evm_revert', [snapshot]) diff --git a/test/samples/TokenPaymaster.test.ts b/test/samples/TokenPaymaster.test.ts index 2a33f9ee4..092793930 100644 --- a/test/samples/TokenPaymaster.test.ts +++ b/test/samples/TokenPaymaster.test.ts @@ -24,7 +24,7 @@ import { OracleHelper as OracleHelperNamespace, UniswapHelper as UniswapHelperNamespace } from '../../typechain/contracts/samples/TokenPaymaster' -import { checkForGeth, createAccount, createAccountOwner, deployActualEntryPoint, fund } from '../testutils' +import { checkForGeth, createAccount, createAccountOwner, deployEntryPoint, fund } from '../testutils' import { fillUserOp, signUserOp } from '../UserOp' @@ -72,7 +72,7 @@ describe('TokenPaymaster', function () { let weth: TestWrappedNativeToken before(async function () { - entryPoint = await deployActualEntryPoint() + entryPoint = await deployEntryPoint() weth = await new TestWrappedNativeToken__factory(ethersSigner).deploy() testUniswap = await new TestUniswap__factory(ethersSigner).deploy(weth.address) factory = await new SimpleAccountFactory__factory(ethersSigner).deploy(entryPoint.address) diff --git a/test/simple-wallet.test.ts b/test/simple-wallet.test.ts index ed420aa85..12d2ca70b 100644 --- a/test/simple-wallet.test.ts +++ b/test/simple-wallet.test.ts @@ -15,11 +15,10 @@ import { createAccount, createAddress, createAccountOwner, - deployActualEntryPoint, getBalance, isDeployed, ONE_ETH, - HashZero + HashZero, deployEntryPoint } from './testutils' import { fillUserOpDefaults, getUserOpHash, packUserOp, signUserOp } from './UserOp' import { parseEther } from 'ethers/lib/utils' @@ -33,7 +32,7 @@ describe('SimpleAccount', function () { const ethersSigner = ethers.provider.getSigner() before(async function () { - entryPoint = await deployActualEntryPoint().then(e => e.address) + entryPoint = await deployEntryPoint().then(e => e.address) accounts = await ethers.provider.listAccounts() // ignore in geth.. this is just a sanity test. should be refactored to use a single-account mode.. if (accounts.length < 2) this.skip() diff --git a/test/testutils.ts b/test/testutils.ts index fdde71a5d..a773f3647 100644 --- a/test/testutils.ts +++ b/test/testutils.ts @@ -15,9 +15,7 @@ import { SimpleAccountFactory__factory, SimpleAccount__factory, SimpleAccountFactory, - TestAggregatedAccountFactory, - EntryPointSimulations__factory, - EntryPointSimulations + TestAggregatedAccountFactory } from '../typechain' import { BytesLike } from '@ethersproject/bytes' import { expect } from 'chai' @@ -245,25 +243,12 @@ export async function checkForBannedOps (txHash: string, checkPaymaster: boolean } } -/** - * This deploys the EntryPoint that does not contain the simulation functions, as will be deployed to real networks. - */ -export async function deployActualEntryPoint (provider = ethers.provider): Promise { +export async function deployEntryPoint (provider = ethers.provider): Promise { const create2factory = new Create2Factory(provider) const addr = await create2factory.deploy(EntryPoint__factory.bytecode, 0, process.env.COVERAGE != null ? 20e6 : 8e6) return EntryPoint__factory.connect(addr, provider.getSigner()) } -/** - * Deploying the entry point with simulation code is required for testing as hardhat does not support state overrides. - * TODO: remove this once hardhat fixes the issue: https://github.com/NomicFoundation/hardhat/issues/2513 - */ -export async function deployEntryPoint (provider = ethers.provider): Promise { - const create2factory = new Create2Factory(provider) - const addr = await create2factory.deploy(EntryPointSimulations__factory.bytecode, 0, process.env.COVERAGE != null ? 20e6 : 8e6) - return EntryPointSimulations__factory.connect(addr, provider.getSigner()) -} - export async function isDeployed (addr: string): Promise { const code = await ethers.provider.getCode(addr) return code.length > 2 diff --git a/test/verifying_paymaster.test.ts b/test/verifying_paymaster.test.ts index 2ab7c4eee..584536c74 100644 --- a/test/verifying_paymaster.test.ts +++ b/test/verifying_paymaster.test.ts @@ -11,7 +11,7 @@ import { createAccountOwner, createAddress, deployEntryPoint } from './testutils' -import { fillAndSign } from './UserOp' +import { fillAndSign, simulateValidation } from './UserOp' import { arrayify, defaultAbiCoder, hexConcat, parseEther } from 'ethers/lib/utils' import { UserOperation } from './UserOperation' @@ -57,7 +57,7 @@ describe('EntryPoint with VerifyingPaymaster', function () { sender: account.address, paymasterAndData: hexConcat([paymaster.address, defaultAbiCoder.encode(['uint48', 'uint48'], [MOCK_VALID_UNTIL, MOCK_VALID_AFTER]), '0x1234']) }, accountOwner, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(userOp)).to.be.revertedWith('invalid signature length in paymasterAndData') + await expect(simulateValidation(userOp, entryPoint.address)).to.be.revertedWith('invalid signature length in paymasterAndData') }) it('should reject on invalid signature', async () => { @@ -65,7 +65,7 @@ describe('EntryPoint with VerifyingPaymaster', function () { sender: account.address, paymasterAndData: hexConcat([paymaster.address, defaultAbiCoder.encode(['uint48', 'uint48'], [MOCK_VALID_UNTIL, MOCK_VALID_AFTER]), '0x' + '00'.repeat(65)]) }, accountOwner, entryPoint) - await expect(entryPoint.callStatic.simulateValidation(userOp)).to.be.revertedWith('ECDSA: invalid signature') + await expect(simulateValidation(userOp, entryPoint.address)).to.be.revertedWith('ECDSA: invalid signature') }) describe('with wrong signature', () => { @@ -80,7 +80,7 @@ describe('EntryPoint with VerifyingPaymaster', function () { }) it('should return signature error (no revert) on wrong signer signature', async () => { - const ret = await entryPoint.callStatic.simulateValidation(wrongSigUserOp) + const ret = await simulateValidation(wrongSigUserOp, entryPoint.address) expect(ret.returnInfo.sigFailed).to.be.true }) @@ -100,7 +100,7 @@ describe('EntryPoint with VerifyingPaymaster', function () { ...userOp1, paymasterAndData: hexConcat([paymaster.address, defaultAbiCoder.encode(['uint48', 'uint48'], [MOCK_VALID_UNTIL, MOCK_VALID_AFTER]), sig]) }, accountOwner, entryPoint) - const res = await entryPoint.callStatic.simulateValidation(userOp) + const res = await simulateValidation(userOp, entryPoint.address) expect(res.returnInfo.sigFailed).to.be.false expect(res.returnInfo.validAfter).to.be.equal(ethers.BigNumber.from(MOCK_VALID_AFTER)) expect(res.returnInfo.validUntil).to.be.equal(ethers.BigNumber.from(MOCK_VALID_UNTIL)) diff --git a/test/y.bls.test.ts b/test/y.bls.test.ts index a3743d0ac..dd8fadc8d 100644 --- a/test/y.bls.test.ts +++ b/test/y.bls.test.ts @@ -13,7 +13,7 @@ import { } from '../typechain' import { ethers } from 'hardhat' import { createAddress, deployEntryPoint, fund, ONE_ETH } from './testutils' -import { DefaultsForUserOp, fillUserOp } from './UserOp' +import { DefaultsForUserOp, fillUserOp, simulateValidation } from './UserOp' import { expect } from 'chai' import { keccak256 } from 'ethereumjs-util' import { hashToPoint } from '@thehubbleproject/bls/dist/mcl' @@ -190,7 +190,7 @@ describe('bls account', function () { const sigParts = signer3.sign(requestHash) userOp.signature = hexConcat(sigParts) - const { aggregatorInfo } = await entrypoint.callStatic.simulateValidation(userOp) + const { aggregatorInfo } = await simulateValidation(userOp, entrypoint.address) expect(aggregatorInfo.aggregator).to.eq(blsAgg.address) expect(aggregatorInfo.stakeInfo.stake).to.eq(ONE_ETH) expect(aggregatorInfo.stakeInfo.unstakeDelaySec).to.eq(2)