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

AA-525: Create Simple7702Account #536

Merged
merged 51 commits into from
Feb 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
db002af
userOpHash as ERC-712 signature
drortirosh Jan 1, 2025
edb2255
don't encode "domain" by default.
drortirosh Jan 2, 2025
dc43285
working
drortirosh Jan 13, 2025
fc0c4e7
removed initUserOpHashParams (internal helper)
drortirosh Jan 13, 2025
263a232
gas cals
drortirosh Jan 13, 2025
5b052ae
undo gas limit change.
drortirosh Jan 13, 2025
38d4714
pr review (typo hash1)
drortirosh Jan 15, 2025
5f0a6b7
remove unused comment.
drortirosh Jan 20, 2025
68894e4
initial implementation
drortirosh Jan 22, 2025
317d26b
memory-safe
drortirosh Jan 22, 2025
2617f03
optimize overrideInitCode
drortirosh Jan 22, 2025
5052317
addeds: zero-tails, fail if not eip-7702 account.
drortirosh Jan 23, 2025
b8a9d26
gas calcs
drortirosh Jan 23, 2025
d9b3f77
lints
drortirosh Jan 23, 2025
24b5473
Merge branch 'develop' into AA-521-ep-7702
drortirosh Jan 23, 2025
fafeca8
removed extracheck.
drortirosh Jan 23, 2025
f98cd95
gascalc
drortirosh Jan 23, 2025
fb06bc3
tests passes, including 7702-enabled external geth
drortirosh Jan 29, 2025
69499d0
gaschecks, geth docker.
drortirosh Jan 29, 2025
bac3113
geth docker.
drortirosh Jan 29, 2025
e54c2a7
fix geth script, coverage test
drortirosh Jan 29, 2025
146dbbe
separate entrypoint-7702 tests into a separate test file
drortirosh Jan 29, 2025
0860e41
Merge branch 'develop' into AA-521-ep-7702
drortirosh Jan 29, 2025
f25a26b
Merge branch 'develop' into AA-521-ep-7702
drortirosh Jan 30, 2025
ee54899
test timeout
drortirosh Jan 30, 2025
33b294a
lints
drortirosh Feb 4, 2025
ea09602
Merge branch 'develop' into AA-521-ep-7702
drortirosh Feb 6, 2025
8dafd94
account
drortirosh Feb 6, 2025
c026b2e
added wallet tests
drortirosh Feb 9, 2025
1442357
test eip-7702 account
drortirosh Feb 9, 2025
b088f05
test 7702 with paymaster
drortirosh Feb 9, 2025
6459a16
lnts
drortirosh Feb 9, 2025
192be39
coverage
drortirosh Feb 10, 2025
26d6a0b
rename to Simple7702Account
drortirosh Feb 10, 2025
a747be1
gascalc
drortirosh Feb 11, 2025
f7c1688
remove Simple7702Account, into a separate PR
drortirosh Feb 11, 2025
074844c
PR review: cleanup Eip7702Support assembly usage.
drortirosh Feb 12, 2025
fac94f7
update comments
drortirosh Feb 13, 2025
7ee78c4
AA-525 add Simple7702Account
drortirosh Feb 11, 2025
f177e32
Update Simple7702Account.sol
drortirosh Feb 12, 2025
6fdff83
AA-521-ep-7702
drortirosh Feb 12, 2025
aa0309c
added fallback
drortirosh Feb 13, 2025
de69b3b
refactor library. split constants.
drortirosh Feb 16, 2025
d4a2802
fix comment
drortirosh Feb 16, 2025
539f7b7
merge
drortirosh Feb 16, 2025
6fcc930
Merge branch 'develop' into AA-521-ep-7702
drortirosh Feb 16, 2025
ca77ba0
Merge branch 'AA-521-ep-7702' into AA-525-simple7702account
drortirosh Feb 16, 2025
7fe744c
merge fixes
drortirosh Feb 16, 2025
7df5213
Merge branch 'develop' into AA-525-simple7702account
drortirosh Feb 16, 2025
35204f7
refactor
drortirosh Feb 16, 2025
d131871
refactor SimpleAccount
drortirosh Feb 16, 2025
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
86 changes: 86 additions & 0 deletions contracts/samples/Simple7702Account.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "@openzeppelin/contracts/interfaces/IERC1271.sol";
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "../core/Helpers.sol";
import "../core/BaseAccount.sol";

/**
* Simple7702Account.sol
* A minimal account to be used with EIP-7702 (for batching) and ERC-4337 (for gas sponsoring)
*/
contract Simple7702Account is BaseAccount, IERC165, IERC1271, ERC1155Holder, ERC721Holder {

struct Call {
address target;
uint256 value;
bytes data;
}

// temporary address of entryPoint v0.8
function entryPoint() public pure override returns (IEntryPoint) {
return IEntryPoint(0xE2b1C20D236ECe93b91f0656A9428C072e3F88Ad);
}

/**
* Make this account callable through ERC-4337 EntryPoint.
* The UserOperation should be signed by this account's private key.
*/
function _validateSignature(
PackedUserOperation calldata userOp,
bytes32 userOpHash
) internal virtual override returns (uint256 validationData) {

return _checkSignature(userOpHash, userOp.signature) ? 0 : SIG_VALIDATION_FAILED;
}

function isValidSignature(bytes32 hash, bytes memory signature) public view returns (bytes4 magicValue) {
return _checkSignature(hash, signature) ? this.isValidSignature.selector : bytes4(0);
}

function _checkSignature(bytes32 hash, bytes memory signature) internal view returns (bool) {
return ECDSA.recover(hash, signature) == address(this);
}

function _requireFromSelfOrEntryPoint() internal view virtual {
require(
msg.sender == address(this) ||
msg.sender == address(entryPoint()),
"not from self or EntryPoint"
);
}


function execute(Call[] calldata calls) external {
_requireFromSelfOrEntryPoint();

for (uint256 i = 0; i < calls.length; i++) {
Call calldata call = calls[i];
(bool ok, bytes memory ret) = call.target.call{value: call.value}(call.data);
if (!ok) {
// solhint-disable-next-line no-inline-assembly
assembly {revert(add(ret, 32), mload(ret))}
}
}
}

function supportsInterface(bytes4 id) public override(ERC1155Holder, IERC165) pure returns (bool) {
return
id == type(IERC165).interfaceId ||
id == type(IAccount).interfaceId ||
id == type(IERC1271).interfaceId ||
id == type(IERC1155Receiver).interfaceId ||
id == type(IERC721Receiver).interfaceId;
}

// accept incoming calls (with or without value), to mimic an EOA.
fallback() external payable {
}

receive() external payable {
}
}
151 changes: 151 additions & 0 deletions test/eip7702-wallet.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { expect } from 'chai'

import { Simple7702Account, Simple7702Account__factory, EntryPoint, TestPaymasterAcceptAll__factory } from '../typechain'
import { createAccountOwner, createAddress, deployEntryPoint } from './testutils'
import { fillAndSign, INITCODE_EIP7702_MARKER, packUserOp } from './UserOp'
import { hexConcat, parseEther } from 'ethers/lib/utils'
import { signEip7702Authorization } from './eip7702helpers'
import { GethExecutable } from './GethExecutable'
import { Wallet } from 'ethers'

describe('Simple7702Account.sol', function () {
// can't deploy coverage "entrypoint" on geth (contract too large)
if (process.env.COVERAGE != null) {
return
}

let entryPoint: EntryPoint

let eip7702delegate: Simple7702Account
let geth: GethExecutable

before(async function () {
geth = new GethExecutable()
await geth.init()

entryPoint = await deployEntryPoint(geth.provider)

eip7702delegate = await new Simple7702Account__factory(geth.provider.getSigner()).deploy()
expect(await eip7702delegate.entryPoint()).to.equal(entryPoint.address, 'fix entryPoint in Simple7702Account.sol')
console.log('set eip7702delegate=', eip7702delegate.address)
})

after(() => {
geth.done()
})

describe('sanity: normal 7702 batching', () => {
let eoa: Wallet
before(async () => {
eoa = createAccountOwner(geth.provider)

const auth = await signEip7702Authorization(eoa, {
chainId: 0,
nonce: 0,
address: eip7702delegate.address
})
const sendVal = parseEther('10')
const tx = {
to: eoa.address,
value: sendVal.toHexString(),
gas: 1e6,
authorizationList: [auth]
}
await geth.sendTx(tx)
expect(await geth.provider.getBalance(eoa.address)).to.equal(sendVal)
expect(await geth.provider.getCode(eoa.address)).to.equal(hexConcat(['0xef0100', eip7702delegate.address]))
})

it('should fail call from another account', async () => {
const wallet1 = Simple7702Account__factory.connect(eoa.address, geth.provider.getSigner())
await expect(wallet1.execute([])).to.revertedWith('not from self or EntryPoint')
})

it('should succeed sending a batch', async () => {
// submit a batch
const wallet2 = Simple7702Account__factory.connect(eoa.address, eoa)
console.log('eoa balance=', await geth.provider.getBalance(eoa.address))

const addr1 = createAddress()
const addr2 = createAddress()

await wallet2.execute([{
target: addr1, value: 1, data: '0x'
}, {
target: addr2, value: 2, data: '0x'
}]).then(async tx => tx.wait())
expect(await geth.provider.getBalance(addr1)).to.equal(1)
expect(await geth.provider.getBalance(addr2)).to.equal(2)
})
})

it('should be able to use EntryPoint without paymaster', async () => {
const addr1 = createAddress()
const eoa = createAccountOwner(geth.provider)

const callData = eip7702delegate.interface.encodeFunctionData('execute', [[{
target: addr1,
value: 1,
data: '0x'
}]])
const userop = await fillAndSign({
sender: eoa.address,
initCode: INITCODE_EIP7702_MARKER,
nonce: 0,
callData
}, eoa, entryPoint, { eip7702delegate: eip7702delegate.address })

await geth.sendTx({ to: eoa.address, value: parseEther('1') })
const auth = await signEip7702Authorization(eoa, { chainId: 0, nonce: 0, address: eip7702delegate.address })
const beneficiary = createAddress()
// submit separate tx with tuple: geth's estimateGas doesn't work, and its easier to detect thrown errors..
await geth.sendTx({
to: entryPoint.address,
data: '0x',
gas: 1000000,
authorizationList: [auth]
})
const handleOps = entryPoint.interface.encodeFunctionData('handleOps', [[packUserOp(userop)], beneficiary])
const tx = {
to: entryPoint.address,
data: handleOps
}
await geth.sendTx(tx)
})

it('should use EntryPoint with paymaster', async () => {
const addr1 = createAddress()
const eoa = createAccountOwner(geth.provider)
const paymaster = await new TestPaymasterAcceptAll__factory(geth.provider.getSigner()).deploy(entryPoint.address)
await paymaster.deposit({ value: parseEther('1') })
const callData = eip7702delegate.interface.encodeFunctionData('execute', [[{
target: addr1,
value: 1,
data: '0x'
}]])
const userop = await fillAndSign({
sender: eoa.address,
paymaster: paymaster.address,
initCode: INITCODE_EIP7702_MARKER,
nonce: 0,
callData
}, eoa, entryPoint, { eip7702delegate: eip7702delegate.address })

const auth = await signEip7702Authorization(eoa, { chainId: 0, nonce: 0, address: eip7702delegate.address })
const beneficiary = createAddress()
console.log('delegate=', eip7702delegate.address)
// submit separate tx with tuple: geth's estimateGas doesn't work, and its easier to detect thrown errors..
await geth.sendTx({
to: entryPoint.address,
data: '0x',
gas: 1000000,
authorizationList: [auth]
})
const handleOps = entryPoint.interface.encodeFunctionData('handleOps', [[packUserOp(userop)], beneficiary])
const tx = {
to: entryPoint.address,
data: handleOps
}
await geth.sendTx(tx)
})
})