mirror of
https://github.com/getwax/zk-account-abstraction.git
synced 2026-01-08 20:18:05 -05:00
* EntryPoint to manage Nonce - NonceManager to handle 2d nonces - _validateAndUpdateNonce() called after validateUserOp - getNonce(sender, key) - remove nonce checking from BaseAccount - BaseAccount implements getNonce() using ep.getNonce
124 lines
4.5 KiB
TypeScript
124 lines
4.5 KiB
TypeScript
import { Wallet } from 'ethers'
|
|
import { ethers } from 'hardhat'
|
|
import { expect } from 'chai'
|
|
import {
|
|
ERC1967Proxy__factory,
|
|
SimpleAccount,
|
|
SimpleAccountFactory__factory,
|
|
SimpleAccount__factory,
|
|
TestUtil,
|
|
TestUtil__factory
|
|
} from '../typechain'
|
|
import {
|
|
createAccount,
|
|
createAddress,
|
|
createAccountOwner,
|
|
deployEntryPoint,
|
|
getBalance,
|
|
isDeployed,
|
|
ONE_ETH,
|
|
HashZero
|
|
} from './testutils'
|
|
import { fillUserOpDefaults, getUserOpHash, packUserOp, signUserOp } from './UserOp'
|
|
import { parseEther } from 'ethers/lib/utils'
|
|
import { UserOperation } from './UserOperation'
|
|
|
|
describe('SimpleAccount', function () {
|
|
let entryPoint: string
|
|
let accounts: string[]
|
|
let testUtil: TestUtil
|
|
let accountOwner: Wallet
|
|
const ethersSigner = ethers.provider.getSigner()
|
|
|
|
before(async function () {
|
|
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()
|
|
testUtil = await new TestUtil__factory(ethersSigner).deploy()
|
|
accountOwner = createAccountOwner()
|
|
})
|
|
|
|
it('owner should be able to call transfer', async () => {
|
|
const { proxy: account } = await createAccount(ethers.provider.getSigner(), accounts[0], entryPoint)
|
|
await ethersSigner.sendTransaction({ from: accounts[0], to: account.address, value: parseEther('2') })
|
|
await account.execute(accounts[2], ONE_ETH, '0x')
|
|
})
|
|
it('other account should not be able to call transfer', async () => {
|
|
const { proxy: account } = await createAccount(ethers.provider.getSigner(), accounts[0], entryPoint)
|
|
await expect(account.connect(ethers.provider.getSigner(1)).execute(accounts[2], ONE_ETH, '0x'))
|
|
.to.be.revertedWith('account: not Owner or EntryPoint')
|
|
})
|
|
|
|
it('should pack in js the same as solidity', async () => {
|
|
const op = await fillUserOpDefaults({ sender: accounts[0] })
|
|
const packed = packUserOp(op)
|
|
expect(await testUtil.packUserOp(op)).to.equal(packed)
|
|
})
|
|
|
|
describe('#validateUserOp', () => {
|
|
let account: SimpleAccount
|
|
let userOp: UserOperation
|
|
let userOpHash: string
|
|
let preBalance: number
|
|
let expectedPay: number
|
|
|
|
const actualGasPrice = 1e9
|
|
// for testing directly validateUserOp, we initialize the account with EOA as entryPoint.
|
|
let entryPointEoa: string
|
|
|
|
before(async () => {
|
|
entryPointEoa = accounts[2]
|
|
const epAsSigner = await ethers.getSigner(entryPointEoa)
|
|
|
|
// cant use "SimpleAccountFactory", since it attempts to increment nonce first
|
|
const implementation = await new SimpleAccount__factory(ethersSigner).deploy(entryPointEoa)
|
|
const proxy = await new ERC1967Proxy__factory(ethersSigner).deploy(implementation.address, '0x')
|
|
account = SimpleAccount__factory.connect(proxy.address, epAsSigner)
|
|
|
|
await ethersSigner.sendTransaction({ from: accounts[0], to: account.address, value: parseEther('0.2') })
|
|
const callGasLimit = 200000
|
|
const verificationGasLimit = 100000
|
|
const maxFeePerGas = 3e9
|
|
const chainId = await ethers.provider.getNetwork().then(net => net.chainId)
|
|
|
|
userOp = signUserOp(fillUserOpDefaults({
|
|
sender: account.address,
|
|
callGasLimit,
|
|
verificationGasLimit,
|
|
maxFeePerGas
|
|
}), accountOwner, entryPointEoa, chainId)
|
|
|
|
userOpHash = await getUserOpHash(userOp, entryPointEoa, chainId)
|
|
|
|
expectedPay = actualGasPrice * (callGasLimit + verificationGasLimit)
|
|
|
|
preBalance = await getBalance(account.address)
|
|
const ret = await account.validateUserOp(userOp, userOpHash, expectedPay, { gasPrice: actualGasPrice })
|
|
await ret.wait()
|
|
})
|
|
|
|
it('should pay', async () => {
|
|
const postBalance = await getBalance(account.address)
|
|
expect(preBalance - postBalance).to.eql(expectedPay)
|
|
})
|
|
|
|
it('should return NO_SIG_VALIDATION on wrong signature', async () => {
|
|
const userOpHash = HashZero
|
|
const deadline = await account.callStatic.validateUserOp({ ...userOp, nonce: 1 }, userOpHash, 0)
|
|
expect(deadline).to.eq(1)
|
|
})
|
|
})
|
|
|
|
context('SimpleAccountFactory', () => {
|
|
it('sanity: check deployer', async () => {
|
|
const ownerAddr = createAddress()
|
|
const deployer = await new SimpleAccountFactory__factory(ethersSigner).deploy(entryPoint)
|
|
const target = await deployer.callStatic.createAccount(ownerAddr, 1234)
|
|
expect(await isDeployed(target)).to.eq(false)
|
|
await deployer.createAccount(ownerAddr, 1234)
|
|
expect(await isDeployed(target)).to.eq(true)
|
|
})
|
|
})
|
|
})
|