Skip to content

Commit c013981

Browse files
Alpay AldemirAlpay Aldemir
authored andcommitted
feat(6900/v0.8) add address book module
- The existing 6900 v0.8 module `ColdStorageAddressBookModule.sol` requires that an account only executes calls to transfer asset. If a call does have selector that matches the checked token (ERC-20, ERC-721, ERC-1155) selectors the function `_getTargetOrRecipient()` returns `address(0)` and therefore execution reverts. - Motivation for this module is to allow an account to make calls that are not intended to transfer assets (native or token) while asserting calls that do transfer assets (based on the selectors this module will check) transfer to white listed recipients. - This hook module can be installed alongside another hook that enforces which targets and selectors can be called. - Implements `IValidationHookModule`, `IExecutionModule`. The module being added in this change will first check if any of the checked selectors is found in any of the execution's calls - if not then the hook allows execution to proceed (assumes no assets are being transferred by the calls in the execution). See `AddressBookModule.t.sol`
1 parent 3c47aa9 commit c013981

File tree

6 files changed

+1940
-0
lines changed

6 files changed

+1940
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ build
2424
/cache_forge
2525
/out
2626
.gas-snapshot
27+
foundry.lock
2728

2829
# Python environment file
2930
/venv

src/libs/RecipientAddressLib.sol

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,17 @@ library RecipientAddressLib {
7474
return address(0);
7575
}
7676

77+
function containsERC20Methods(bytes memory data) internal pure returns (bool) {
78+
bytes4 selector = bytes4(data);
79+
if (selector == IERC20.transfer.selector || selector == IERC20.approve.selector
80+
|| selector == ERC20_INCREASE_ALLOWANCE || selector == ERC20_DECREASE_ALLOWANCE) {
81+
return data.length >= TRANSFER_OR_APPROVE_MIN_LEN;
82+
} else if (selector == IERC20.transferFrom.selector) {
83+
return data.length >= TRANSFER_FROM_MIN_LEN;
84+
}
85+
return false;
86+
}
87+
7788
/// @notice Decode the recipient of a token.
7889
/// @dev This only supports the following **standard** ERC1155 functions:
7990
/// - setApprovalForAll(address,bool)
@@ -113,6 +124,18 @@ library RecipientAddressLib {
113124
return address(0);
114125
}
115126

127+
function containsERC1155Methods(bytes memory data) internal pure returns (bool) {
128+
bytes4 selector = bytes4(data);
129+
if (selector == IERC1155.setApprovalForAll.selector) {
130+
return data.length >= TRANSFER_OR_APPROVE_MIN_LEN;
131+
} else if (selector == IERC1155.safeTransferFrom.selector) {
132+
return data.length >= TRANSFER_FROM_WITH_BYTES_MIN_LEN;
133+
} else if (selector == IERC1155.safeBatchTransferFrom.selector) {
134+
return data.length >= BATCH_TRANSFER_FROM_WITH_BYTES_MIN_LEN;
135+
}
136+
return false;
137+
}
138+
116139
/// @notice Decode the recipient of a token.
117140
/// @dev This only supports the following **standard** ERC721 functions:
118141
/// - safeTransferFrom(address,address,uint256)
@@ -151,6 +174,18 @@ library RecipientAddressLib {
151174
return address(0);
152175
}
153176

177+
function containsERC721Methods(bytes memory data) internal pure returns (bool) {
178+
bytes4 selector = bytes4(data);
179+
if (selector == IERC721.setApprovalForAll.selector || selector == IERC721.approve.selector) {
180+
return data.length >= TRANSFER_OR_APPROVE_MIN_LEN;
181+
} else if (selector == ERC721_SAFE_TRANSFER_FROM || selector == IERC721.transferFrom.selector) {
182+
return data.length >= TRANSFER_FROM_MIN_LEN;
183+
} else if (selector == ERC721_SAFE_TRANSFER_FROM_WITH_BYTES) {
184+
return data.length >= TRANSFER_FROM_WITHOUT_AMOUNT_WITH_BYTES_MIN_LEN;
185+
}
186+
return false;
187+
}
188+
154189
/// @dev The caller must skip over the initial length prefix.
155190
function getRecipient(bytes memory data, uint256 recipientIndex) private pure returns (address) {
156191
address recipient;

src/msca/6900/shared/common/Errors.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,5 @@ error InvalidItem();
5252
error NotImplementedFunction(bytes4 selector, uint32 entityId);
5353

5454
error SignatureInflation();
55+
56+
error UnexpectedDataPassed();

src/msca/6900/v0.8/modules/BaseModule.sol

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,21 @@ pragma solidity 0.8.24;
2121
import {IModule} from "@erc6900/reference-implementation/interfaces/IModule.sol";
2222
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";
2323
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
24+
import {IAccountExecute} from "@eth-infinitism/account-abstraction/interfaces/IAccountExecute.sol";
25+
import {UnexpectedDataPassed} from "../../shared/common/Errors.sol";
2426

2527
/**
2628
* @dev Default implementation of https://eips.ethereum.org/EIPS/eip-6900. MSCAs must implement this interface to
2729
* support open-ended execution.
2830
*/
2931
abstract contract BaseModule is IModule, ERC165 {
32+
modifier assertNoData(bytes calldata data) {
33+
if (data.length > 0) {
34+
revert UnexpectedDataPassed();
35+
}
36+
_;
37+
}
38+
3039
/// @dev Returns true if this contract implements the interface defined by
3140
/// `interfaceId`. See the corresponding
3241
/// https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
@@ -41,4 +50,36 @@ abstract contract BaseModule is IModule, ERC165 {
4150
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
4251
return interfaceId == type(IModule).interfaceId || super.supportsInterface(interfaceId);
4352
}
53+
54+
/// @dev help method that returns extracted selector and calldata. If selector is executeUserOp, return the
55+
/// selector and calldata of the inner call. This is unique for both validation and execution phases.
56+
/// During validation phase, the `data` parameter is the uo's calldata.
57+
function _validationPhaseGetSelectorAndCalldata(bytes calldata data)
58+
internal
59+
pure
60+
returns (bytes4, bytes memory)
61+
{
62+
bytes4 selector = bytes4(data[:4]);
63+
if (selector == IAccountExecute.executeUserOp.selector) {
64+
// Copy the data to memory
65+
bytes memory finalCalldata = data;
66+
67+
// Bytes arr representation: [bytes32(len), bytes4(executeUserOp.selector), bytes4(actualSelector),
68+
// bytes(actualCallData)]
69+
assembly ("memory-safe") {
70+
// Copy actualSelector into a new var
71+
selector := shl(224, mload(add(finalCalldata, 8)))
72+
73+
let len := mload(finalCalldata)
74+
75+
// Move the finalCalldata pointer by 8
76+
finalCalldata := add(finalCalldata, 8)
77+
78+
// Shorten bytes array by 8 by: store length - 8 into the new pointer location
79+
mstore(finalCalldata, sub(len, 8))
80+
}
81+
return (selector, finalCalldata);
82+
}
83+
return (selector, data[4:]);
84+
}
4485
}

0 commit comments

Comments
 (0)