Skip to content

Commit bc3b02f

Browse files
Amxxernestognwarr00
authored
Cherrypick 0a77e54 onto release-v5.3 (#5605)
Signed-off-by: Hadrien Croubois <[email protected]> Co-authored-by: ernestognw <[email protected]> Co-authored-by: Arr00 <[email protected]>
1 parent 39f5a02 commit bc3b02f

File tree

10 files changed

+131
-91
lines changed

10 files changed

+131
-91
lines changed

contracts/account/utils/draft-ERC4337Utils.sol

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import {Math} from "../../utils/math/Math.sol";
88
import {Calldata} from "../../utils/Calldata.sol";
99
import {Packing} from "../../utils/Packing.sol";
1010

11+
/// @dev This is available on all entrypoint since v0.4.0, but is not formally part of the ERC.
12+
interface IEntryPointExtra {
13+
function getUserOpHash(PackedUserOperation calldata userOp) external view returns (bytes32);
14+
}
15+
1116
/**
1217
* @dev Library with common ERC-4337 utility functions.
1318
*
@@ -19,6 +24,9 @@ library ERC4337Utils {
1924
/// @dev Address of the entrypoint v0.7.0
2025
IEntryPoint internal constant ENTRYPOINT_V07 = IEntryPoint(0x0000000071727De22E5E9d8BAf0edAc6f37da032);
2126

27+
/// @dev Address of the entrypoint v0.8.0
28+
IEntryPoint internal constant ENTRYPOINT_V08 = IEntryPoint(0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108);
29+
2230
/// @dev For simulation purposes, validateUserOp (and validatePaymasterUserOp) return this value on success.
2331
uint256 internal constant SIG_VALIDATION_SUCCESS = 0;
2432

@@ -77,31 +85,16 @@ library ERC4337Utils {
7785
return (aggregator_, block.timestamp < validAfter || validUntil < block.timestamp);
7886
}
7987

80-
/// @dev Computes the hash of a user operation for a given entrypoint and chainid.
81-
function hash(
82-
PackedUserOperation calldata self,
83-
address entrypoint,
84-
uint256 chainid
85-
) internal pure returns (bytes32) {
86-
bytes32 result = keccak256(
87-
abi.encode(
88-
keccak256(
89-
abi.encode(
90-
self.sender,
91-
self.nonce,
92-
keccak256(self.initCode),
93-
keccak256(self.callData),
94-
self.accountGasLimits,
95-
self.preVerificationGas,
96-
self.gasFees,
97-
keccak256(self.paymasterAndData)
98-
)
99-
),
100-
entrypoint,
101-
chainid
102-
)
103-
);
104-
return result;
88+
/// @dev Get the hash of a user operation for a given entrypoint
89+
function hash(PackedUserOperation calldata self, address entrypoint) internal view returns (bytes32) {
90+
// NOTE: getUserOpHash is available since v0.4.0
91+
//
92+
// Prior to v0.8.0, this was easy to replicate for any entrypoint and chainId. Since v0.8.0 of the
93+
// entrypoint, this depends on the Entrypoint's domain separator, which cannot be hardcoded and is complex
94+
// to recompute. Domain separator could be fetch using the `getDomainSeparatorV4` getter, or recomputed from
95+
// the ERC-5267 getter, but both operation would require doing a view call to the entrypoint. Overall it feels
96+
// simpler and less error prone to get that functionality from the entrypoint directly.
97+
return IEntryPointExtra(entrypoint).getUserOpHash(self);
10598
}
10699

107100
/// @dev Returns `factory` from the {PackedUserOperation}, or address(0) if the initCode is empty or not properly formatted.

hardhat/common-contracts.js

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,41 +6,58 @@ const fs = require('fs');
66
const path = require('path');
77

88
const INSTANCES = {
9-
// Entrypoint v0.7.0
9+
// ERC-4337 Entrypoints
1010
entrypoint: {
11-
address: '0x0000000071727De22E5E9d8BAf0edAc6f37da032',
12-
abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../test/bin/EntryPoint070.abi'), 'utf-8')),
13-
bytecode: fs.readFileSync(path.resolve(__dirname, '../test/bin/EntryPoint070.bytecode'), 'hex'),
11+
v07: {
12+
address: '0x0000000071727De22E5E9d8BAf0edAc6f37da032',
13+
abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../test/bin/EntryPoint070.abi'), 'utf-8')),
14+
bytecode: fs.readFileSync(path.resolve(__dirname, '../test/bin/EntryPoint070.bytecode'), 'hex'),
15+
},
16+
v08: {
17+
address: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108',
18+
abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../test/bin/EntryPoint080.abi'), 'utf-8')),
19+
bytecode: fs.readFileSync(path.resolve(__dirname, '../test/bin/EntryPoint080.bytecode'), 'hex'),
20+
},
1421
},
1522
senderCreator: {
16-
address: '0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C',
17-
abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../test/bin/SenderCreator070.abi'), 'utf-8')),
18-
bytecode: fs.readFileSync(path.resolve(__dirname, '../test/bin/SenderCreator070.bytecode'), 'hex'),
23+
v07: {
24+
address: '0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C',
25+
abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../test/bin/SenderCreator070.abi'), 'utf-8')),
26+
bytecode: fs.readFileSync(path.resolve(__dirname, '../test/bin/SenderCreator070.bytecode'), 'hex'),
27+
},
28+
v08: {
29+
address: '0x449ED7C3e6Fee6a97311d4b55475DF59C44AdD33',
30+
abi: JSON.parse(fs.readFileSync(path.resolve(__dirname, '../test/bin/SenderCreator080.abi'), 'utf-8')),
31+
bytecode: fs.readFileSync(path.resolve(__dirname, '../test/bin/SenderCreator080.bytecode'), 'hex'),
32+
},
1933
},
20-
// Arachnid's deterministic deployment proxy
21-
// See: https://github.com/Arachnid/deterministic-deployment-proxy/tree/master
22-
arachnidDeployer: {
23-
address: '0x4e59b44847b379578588920cA78FbF26c0B4956C',
24-
abi: [],
25-
bytecode:
26-
'0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3',
27-
},
28-
// Micah's deployer
29-
micahDeployer: {
30-
address: '0x7A0D94F55792C434d74a40883C6ed8545E406D12',
31-
abi: [],
32-
bytecode: '0x60003681823780368234f58015156014578182fd5b80825250506014600cf3',
34+
deployer: {
35+
// Arachnid's deterministic deployment proxy
36+
// See: https://github.com/Arachnid/deterministic-deployment-proxy/tree/master
37+
arachnid: {
38+
address: '0x4e59b44847b379578588920cA78FbF26c0B4956C',
39+
abi: [],
40+
bytecode:
41+
'0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe03601600081602082378035828234f58015156039578182fd5b8082525050506014600cf3',
42+
},
43+
// Micah's deployer
44+
micah: {
45+
address: '0x7A0D94F55792C434d74a40883C6ed8545E406D12',
46+
abi: [],
47+
bytecode: '0x60003681823780368234f58015156014578182fd5b80825250506014600cf3',
48+
},
3349
},
3450
};
3551

52+
const setup = (input, ethers) =>
53+
input.address && input.abi && input.bytecode
54+
? setCode(input.address, '0x' + input.bytecode.replace(/0x/, '')).then(() =>
55+
ethers.getContractAt(input.abi, input.address),
56+
)
57+
: Promise.all(
58+
Object.entries(input).map(([name, entry]) => setup(entry, ethers).then(result => [name, result])),
59+
).then(Object.fromEntries);
60+
3661
task(TASK_TEST_SETUP_TEST_ENVIRONMENT).setAction((_, env, runSuper) =>
37-
runSuper().then(() =>
38-
Promise.all(
39-
Object.entries(INSTANCES).map(([name, { address, abi, bytecode }]) =>
40-
setCode(address, '0x' + bytecode.replace(/0x/, '')).then(() =>
41-
env.ethers.getContractAt(abi, address).then(instance => (env[name] = instance)),
42-
),
43-
),
44-
),
45-
),
62+
runSuper().then(() => setup(INSTANCES, env.ethers).then(result => Object.assign(env, result))),
4663
);

scripts/fetch-common-contracts.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env node
2+
3+
// This script snapshots the bytecode and ABI for the `hardhat/common-contracts.js` script.
4+
// - Bytecode is fetched directly from the blockchain by querying the provided client endpoint. If no endpoint is
5+
// provided, ethers default provider is used instead.
6+
// - ABI is fetched from etherscan's API using the provided etherscan API key. If no API key is provided, ABI will not
7+
// be fetched and saved.
8+
//
9+
// The produced artifacts are stored in the `output` folder ('test/bin' by default). For each contract, two files are
10+
// produced:
11+
// - `<name>.bytecode` containing the contract bytecode (in binary encoding)
12+
// - `<name>.abi` containing the ABI (in utf-8 encoding)
13+
14+
const fs = require('fs');
15+
const path = require('path');
16+
const { ethers } = require('ethers');
17+
const { request } = require('undici');
18+
const { hideBin } = require('yargs/helpers');
19+
const { argv } = require('yargs/yargs')(hideBin(process.argv))
20+
.env('')
21+
.options({
22+
output: { type: 'string', default: 'test/bin/' },
23+
client: { type: 'string' },
24+
etherscan: { type: 'string' },
25+
});
26+
27+
// List of contract names and addresses to fetch
28+
const config = {
29+
EntryPoint070: '0x0000000071727De22E5E9d8BAf0edAc6f37da032',
30+
SenderCreator070: '0xEFC2c1444eBCC4Db75e7613d20C6a62fF67A167C',
31+
EntryPoint080: '0x4337084D9E255Ff0702461CF8895CE9E3b5Ff108',
32+
SenderCreator080: '0x449ED7C3e6Fee6a97311d4b55475DF59C44AdD33',
33+
};
34+
35+
Promise.all(
36+
Object.entries(config).flatMap(([name, addr]) =>
37+
Promise.all([
38+
argv.etherscan &&
39+
request(`https://api.etherscan.io/api?module=contract&action=getabi&address=${addr}&apikey=${argv.etherscan}`)
40+
.then(({ body }) => body.json())
41+
.then(({ result: abi }) => fs.writeFile(path.join(argv.output, `${name}.abi`), abi, 'utf-8', () => {})),
42+
ethers
43+
.getDefaultProvider(argv.client)
44+
.getCode(addr)
45+
.then(bytecode =>
46+
fs.writeFile(path.join(argv.output, `${name}.bytecode`), ethers.getBytes(bytecode), 'binary', () => {}),
47+
),
48+
]),
49+
),
50+
);

test/account/utils/draft-ERC4337Utils.test.js

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ describe('ERC4337Utils', function () {
2222

2323
describe('entrypoint', function () {
2424
it('v0.7.0', async function () {
25-
await expect(this.utils.$ENTRYPOINT_V07()).to.eventually.equal(entrypoint);
25+
await expect(this.utils.$ENTRYPOINT_V07()).to.eventually.equal(entrypoint.v07);
26+
});
27+
28+
it('v0.8.0', async function () {
29+
await expect(this.utils.$ENTRYPOINT_V08()).to.eventually.equal(entrypoint.v08);
2630
});
2731
});
2832

@@ -172,22 +176,14 @@ describe('ERC4337Utils', function () {
172176
});
173177

174178
describe('hash', function () {
175-
it('returns the operation hash with specified entrypoint and chainId', async function () {
176-
const userOp = new UserOperation({ sender: this.sender, nonce: 1 });
177-
const chainId = await ethers.provider.getNetwork().then(({ chainId }) => chainId);
178-
const otherChainId = 0xdeadbeef;
179+
for (const [version, instance] of Object.entries(entrypoint)) {
180+
it(`returns the operation hash for entrypoint ${version}`, async function () {
181+
const userOp = new UserOperation({ sender: this.sender, nonce: 1 });
182+
const expected = await userOp.hash(instance);
179183

180-
// check that helper matches entrypoint logic
181-
await expect(entrypoint.getUserOpHash(userOp.packed)).to.eventually.equal(userOp.hash(entrypoint, chainId));
182-
183-
// check library against helper
184-
await expect(this.utils.$hash(userOp.packed, entrypoint, chainId)).to.eventually.equal(
185-
userOp.hash(entrypoint, chainId),
186-
);
187-
await expect(this.utils.$hash(userOp.packed, entrypoint, otherChainId)).to.eventually.equal(
188-
userOp.hash(entrypoint, otherChainId),
189-
);
190-
});
184+
await expect(this.utils.$hash(userOp.packed, instance)).to.eventually.equal(expected);
185+
});
186+
}
191187
});
192188

193189
describe('userOp values', function () {

test/account/utils/draft-ERC7579Utils.t.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ contract ERC7579UtilsTest is Test {
375375
}
376376

377377
function hashUserOperation(PackedUserOperation calldata useroperation) public view returns (bytes32) {
378-
return useroperation.hash(address(ERC4337Utils.ENTRYPOINT_V07), block.chainid);
378+
return useroperation.hash(address(ERC4337Utils.ENTRYPOINT_V07));
379379
}
380380

381381
function _collectAndPrintLogs(bool includeTotalValue) internal {

0 commit comments

Comments
 (0)