Skip to content

Commit 7134927

Browse files
authored
Merge branch 'eth-infinitism:develop' into develop
2 parents 850628a + ed8a5c7 commit 7134927

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+2053
-570
lines changed

.depcheckrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ignores: ["@openzeppelin/contracts", "@uniswap/v3-periphery", " @typechain/ethers-v5" ]

.github/workflows/build.yml

+19-12
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ jobs:
1717
runs-on: ubuntu-latest
1818

1919
steps:
20-
- uses: actions/setup-node@v1
20+
- uses: actions/setup-node@v4
2121
with:
2222
node-version: '22'
23-
- uses: actions/checkout@v1
24-
- uses: actions/cache@v2
23+
- uses: actions/checkout@v4
24+
- uses: actions/cache@v4
2525
with:
2626
path: node_modules
2727
key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}
@@ -35,11 +35,13 @@ jobs:
3535
runs-on: ubuntu-latest
3636

3737
steps:
38-
- uses: actions/setup-node@v1
38+
- uses: actions/setup-node@v4
3939
with:
4040
node-version: '22'
41-
- uses: actions/checkout@v1
42-
- uses: actions/cache@v2
41+
- uses: actions/checkout@v4
42+
with:
43+
show-progress: false
44+
- uses: actions/cache@v4
4345
with:
4446
path: node_modules
4547
key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}
@@ -50,25 +52,30 @@ jobs:
5052
lint:
5153
runs-on: ubuntu-latest
5254
steps:
53-
- uses: actions/setup-node@v1
55+
- uses: actions/setup-node@v4
5456
with:
5557
node-version: '22'
56-
- uses: actions/checkout@v1
57-
- uses: actions/cache@v2
58+
- uses: actions/checkout@v4
59+
with:
60+
show-progress: false
61+
- uses: actions/cache@v4
5862
with:
5963
path: node_modules
6064
key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}
6165
- run: yarn install
66+
- run: yarn depcheck
6267
- run: yarn lint
6368

6469
coverage:
6570
runs-on: ubuntu-latest
6671
steps:
67-
- uses: actions/setup-node@v1
72+
- uses: actions/setup-node@v4
6873
with:
6974
node-version: '22'
70-
- uses: actions/checkout@v1
71-
- uses: actions/cache@v2
75+
- uses: actions/checkout@v4
76+
with:
77+
show-progress: false
78+
- uses: actions/cache@v4
7279
with:
7380
path: node_modules
7481
key: ${{ runner.os }}-${{ hashFiles('yarn.lock') }}

.solcover.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
module.exports = {
22
skipFiles: [
33
"test",
4-
"samples/bls/lib",
5-
"utils/Exec.sol"
4+
"utils/Exec.sol",
5+
"samples"
66
],
77
configureYulOptimizer: true,
88
};
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8;
3+
4+
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
5+
import "@openzeppelin/contracts/interfaces/IERC1271.sol";
6+
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
7+
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
8+
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
9+
import "../core/Helpers.sol";
10+
import "../core/BaseAccount.sol";
11+
12+
/**
13+
* Simple7702Account.sol
14+
* A minimal account to be used with EIP-7702 (for batching) and ERC-4337 (for gas sponsoring)
15+
*/
16+
contract Simple7702Account is BaseAccount, IERC165, IERC1271, ERC1155Holder, ERC721Holder {
17+
18+
// temporary address of entryPoint v0.8
19+
function entryPoint() public pure override returns (IEntryPoint) {
20+
return IEntryPoint(0xda853D8f693A836b2Ac48becC76d1d2e9BDCd1B3);
21+
}
22+
23+
/**
24+
* Make this account callable through ERC-4337 EntryPoint.
25+
* The UserOperation should be signed by this account's private key.
26+
*/
27+
function _validateSignature(
28+
PackedUserOperation calldata userOp,
29+
bytes32 userOpHash
30+
) internal virtual override returns (uint256 validationData) {
31+
32+
return _checkSignature(userOpHash, userOp.signature) ? 0 : SIG_VALIDATION_FAILED;
33+
}
34+
35+
function isValidSignature(bytes32 hash, bytes memory signature) public view returns (bytes4 magicValue) {
36+
return _checkSignature(hash, signature) ? this.isValidSignature.selector : bytes4(0);
37+
}
38+
39+
function _checkSignature(bytes32 hash, bytes memory signature) internal view returns (bool) {
40+
return ECDSA.recover(hash, signature) == address(this);
41+
}
42+
43+
function _requireForExecute() internal view virtual override {
44+
require(
45+
msg.sender == address(this) ||
46+
msg.sender == address(entryPoint()),
47+
"not from self or EntryPoint"
48+
);
49+
}
50+
51+
function supportsInterface(bytes4 id) public override(ERC1155Holder, IERC165) pure returns (bool) {
52+
return
53+
id == type(IERC165).interfaceId ||
54+
id == type(IAccount).interfaceId ||
55+
id == type(IERC1271).interfaceId ||
56+
id == type(IERC1155Receiver).interfaceId ||
57+
id == type(IERC721Receiver).interfaceId;
58+
}
59+
60+
// accept incoming calls (with or without value), to mimic an EOA.
61+
fallback() external payable {
62+
}
63+
64+
receive() external payable {
65+
}
66+
}

contracts/samples/SimpleAccount.sol renamed to contracts/accounts/SimpleAccount.sol

+2-43
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-License-Identifier: GPL-3.0
1+
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.23;
33

44
/* solhint-disable avoid-low-level-calls */
@@ -49,38 +49,6 @@ contract SimpleAccount is BaseAccount, TokenCallbackHandler, UUPSUpgradeable, In
4949
require(msg.sender == owner || msg.sender == address(this), "only owner");
5050
}
5151

52-
/**
53-
* execute a transaction (called directly from owner, or by entryPoint)
54-
* @param dest destination address to call
55-
* @param value the value to pass in this call
56-
* @param func the calldata to pass in this call
57-
*/
58-
function execute(address dest, uint256 value, bytes calldata func) external {
59-
_requireFromEntryPointOrOwner();
60-
_call(dest, value, func);
61-
}
62-
63-
/**
64-
* execute a sequence of transactions
65-
* @dev to reduce gas consumption for trivial case (no value), use a zero-length array to mean zero value
66-
* @param dest an array of destination addresses
67-
* @param value an array of values to pass to each call. can be zero-length for no-value calls
68-
* @param func an array of calldata to pass to each call
69-
*/
70-
function executeBatch(address[] calldata dest, uint256[] calldata value, bytes[] calldata func) external {
71-
_requireFromEntryPointOrOwner();
72-
require(dest.length == func.length && (value.length == 0 || value.length == func.length), "wrong array lengths");
73-
if (value.length == 0) {
74-
for (uint256 i = 0; i < dest.length; i++) {
75-
_call(dest[i], 0, func[i]);
76-
}
77-
} else {
78-
for (uint256 i = 0; i < dest.length; i++) {
79-
_call(dest[i], value[i], func[i]);
80-
}
81-
}
82-
}
83-
8452
/**
8553
* @dev The _entryPoint member is immutable, to reduce gas consumption. To upgrade EntryPoint,
8654
* a new implementation of SimpleAccount must be deployed with the new EntryPoint address, then upgrading
@@ -97,7 +65,7 @@ contract SimpleAccount is BaseAccount, TokenCallbackHandler, UUPSUpgradeable, In
9765
}
9866

9967
// Require the function call went through EntryPoint or owner
100-
function _requireFromEntryPointOrOwner() internal view {
68+
function _requireForExecute() internal view override virtual {
10169
require(msg.sender == address(entryPoint()) || msg.sender == owner, "account: not Owner or EntryPoint");
10270
}
10371

@@ -111,15 +79,6 @@ contract SimpleAccount is BaseAccount, TokenCallbackHandler, UUPSUpgradeable, In
11179
return SIG_VALIDATION_SUCCESS;
11280
}
11381

114-
function _call(address target, uint256 value, bytes memory data) internal {
115-
(bool success, bytes memory result) = target.call{value: value}(data);
116-
if (!success) {
117-
assembly {
118-
revert(add(result, 32), mload(result))
119-
}
120-
}
121-
}
122-
12382
/**
12483
* check current account deposit in the entryPoint
12584
*/

contracts/core/BaseAccount.sol

+52-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
// SPDX-License-Identifier: GPL-3.0
1+
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.23;
33

44
/* solhint-disable avoid-low-level-calls */
55
/* solhint-disable no-empty-blocks */
6+
/* solhint-disable no-inline-assembly */
67

78
import "../interfaces/IAccount.sol";
89
import "../interfaces/IEntryPoint.sol";
10+
import "../utils/Exec.sol";
911
import "./UserOperationLib.sol";
1012

1113
/**
@@ -16,6 +18,14 @@ import "./UserOperationLib.sol";
1618
abstract contract BaseAccount is IAccount {
1719
using UserOperationLib for PackedUserOperation;
1820

21+
struct Call {
22+
address target;
23+
uint256 value;
24+
bytes data;
25+
}
26+
27+
error ExecuteError(uint256 index, bytes error);
28+
1929
/**
2030
* Return the account nonce.
2131
* This method returns the next sequential nonce.
@@ -31,6 +41,40 @@ abstract contract BaseAccount is IAccount {
3141
*/
3242
function entryPoint() public view virtual returns (IEntryPoint);
3343

44+
/**
45+
* execute a single call from the account.
46+
*/
47+
function execute(address target, uint256 value, bytes calldata data) virtual external {
48+
_requireForExecute();
49+
50+
bool ok = Exec.call(target, value, data, gasleft());
51+
if (!ok) {
52+
Exec.revertWithReturnData();
53+
}
54+
}
55+
56+
/**
57+
* execute a batch of calls.
58+
* revert on the first call that fails.
59+
* If the batch reverts, and it contains more than a single call, then wrap the revert with ExecuteError,
60+
* to mark the failing call index.
61+
*/
62+
function executeBatch(Call[] calldata calls) external {
63+
_requireForExecute();
64+
65+
for (uint256 i = 0; i < calls.length; i++) {
66+
Call calldata call = calls[i];
67+
bool ok = Exec.call(call.target, call.value, call.data, gasleft());
68+
if (!ok) {
69+
if (calls.length == 1) {
70+
Exec.revertWithReturnData();
71+
} else {
72+
revert ExecuteError(i, Exec.getReturnData(0));
73+
}
74+
}
75+
}
76+
}
77+
3478
/// @inheritdoc IAccount
3579
function validateUserOp(
3680
PackedUserOperation calldata userOp,
@@ -53,6 +97,10 @@ abstract contract BaseAccount is IAccount {
5397
);
5498
}
5599

100+
function _requireForExecute() internal view virtual {
101+
_requireFromEntryPoint();
102+
}
103+
56104
/**
57105
* Validate the signature is valid for this message.
58106
* @param userOp - Validate the userOp.signature field.
@@ -102,9 +150,9 @@ abstract contract BaseAccount is IAccount {
102150
*/
103151
function _payPrefund(uint256 missingAccountFunds) internal virtual {
104152
if (missingAccountFunds != 0) {
105-
(bool success, ) = payable(msg.sender).call{
106-
value: missingAccountFunds
107-
}("");
153+
(bool success,) = payable(msg.sender).call{
154+
value: missingAccountFunds
155+
}("");
108156
(success);
109157
//ignore failure (its EntryPoint's job to verify, not account.)
110158
}

contracts/core/BasePaymaster.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-License-Identifier: GPL-3.0
1+
// SPDX-License-Identifier: MIT
22
pragma solidity ^0.8.23;
33

44
/* solhint-disable reason-string */

contracts/core/Eip7702Support.sol

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
pragma solidity ^0.8;
2+
// SPDX-License-Identifier: MIT
3+
// solhint-disable no-inline-assembly
4+
5+
import "../interfaces/PackedUserOperation.sol";
6+
import "../core/UserOperationLib.sol";
7+
8+
library Eip7702Support {
9+
10+
// EIP-7702 code prefix before delegate address.
11+
bytes3 internal constant EIP7702_PREFIX = 0xef0100;
12+
13+
// EIP-7702 initCode marker, to specify this account is EIP-7702.
14+
bytes2 internal constant INITCODE_EIP7702_MARKER = 0x7702;
15+
16+
using UserOperationLib for PackedUserOperation;
17+
18+
//get alternate InitCodeHash (just for UserOp hash) when using EIP-7702
19+
function _getEip7702InitCodeHashOverride(PackedUserOperation calldata userOp) internal view returns (bytes32) {
20+
bytes calldata initCode = userOp.initCode;
21+
if (!_isEip7702InitCode(initCode)) {
22+
return 0;
23+
}
24+
address delegate = _getEip7702Delegate(userOp.getSender());
25+
if (initCode.length <= 20)
26+
return keccak256(abi.encodePacked(delegate));
27+
else
28+
return keccak256(abi.encodePacked(delegate, initCode[20 :]));
29+
}
30+
31+
// check if this initCode is EIP-7702: starts with INITCODE_EIP7702_MARKER.
32+
function _isEip7702InitCode(bytes calldata initCode) internal pure returns (bool) {
33+
34+
if (initCode.length < 2) {
35+
return false;
36+
}
37+
bytes20 initCodeStart;
38+
// non-empty calldata bytes are always zero-padded to 32-bytes, so can be safely casted to "bytes20"
39+
assembly ("memory-safe") {
40+
initCodeStart := calldataload(initCode.offset)
41+
}
42+
// make sure first 20 bytes of initCode are "0x7702" (padded with zeros)
43+
return initCodeStart == bytes20(INITCODE_EIP7702_MARKER);
44+
}
45+
46+
/**
47+
* get the EIP-7702 delegate from contract code.
48+
* must only be used if _isEip7702InitCode(initCode) is true.
49+
*/
50+
function _getEip7702Delegate(address sender) internal view returns (address) {
51+
52+
bytes32 senderCode;
53+
54+
assembly ("memory-safe") {
55+
extcodecopy(sender, 0, 0, 23)
56+
senderCode := mload(0)
57+
}
58+
// To be a valid EIP-7702 delegate, the first 3 bytes are EIP7702_PREFIX
59+
// followed by the delegate address
60+
if (bytes3(senderCode) != EIP7702_PREFIX) {
61+
// instead of just "not an EIP-7702 delegate", if some info.
62+
require(sender.code.length > 0, "sender has no code");
63+
revert("not an EIP-7702 delegate");
64+
}
65+
return address(bytes20(senderCode << 24));
66+
}
67+
}

0 commit comments

Comments
 (0)