Skip to content

Commit dfa465d

Browse files
authored
Merge pull request #75 from base-org/jack/gas-improvements
Jack/gas improvements
2 parents a4feb90 + 6517014 commit dfa465d

File tree

4 files changed

+91
-44
lines changed

4 files changed

+91
-44
lines changed

Diff for: contracts/src/ERC7786Base.sol

+14-7
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ contract ERC7786Base {
5959
/// @param selector The selector of the attribute that was not found
6060
error AttributeNotFound(bytes4 selector);
6161

62+
/// @notice This error is thrown when the call attributes array contains more than one element
63+
error MaxOneAttributeExpected();
64+
6265
/// @notice Locates an attribute in the attributes array
6366
///
6467
/// @custom:reverts If the attribute is not found
@@ -100,15 +103,19 @@ contract ERC7786Base {
100103
/// @notice Locates an attribute value in the attributes array
101104
///
102105
/// @param attributes The attributes array to search
103-
/// @param selector The selector of the attribute to find
104106
///
105107
/// @return value The value of the attribute found
106-
function _locateAttributeValue(bytes[] calldata attributes, bytes4 selector) internal pure returns (uint256) {
107-
for (uint256 i; i < attributes.length; i++) {
108-
if (bytes4(attributes[i]) == selector) {
109-
return abi.decode(attributes[i][4:], (uint256));
110-
}
108+
function _locateAttributeValue(bytes[] calldata attributes) internal pure returns (uint256) {
109+
if (attributes.length > 1) {
110+
revert MaxOneAttributeExpected();
111111
}
112-
return 0;
112+
113+
uint256 value;
114+
115+
if (attributes.length == 1 && bytes4(attributes[0]) == _VALUE_ATTRIBUTE_SELECTOR) {
116+
value = abi.decode(attributes[0][4:], (uint256));
117+
}
118+
119+
return value;
113120
}
114121
}

Diff for: contracts/src/RIP7755Inbox.sol

+50-33
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
pragma solidity 0.8.24;
33

44
import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol";
5-
import {Address} from "openzeppelin-contracts/contracts/utils/Address.sol";
65
import {CAIP10} from "openzeppelin-contracts/contracts/utils/CAIP10.sol";
76
import {CAIP2} from "openzeppelin-contracts/contracts/utils/CAIP2.sol";
87
import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol";
@@ -18,7 +17,6 @@ import {Paymaster} from "./Paymaster.sol";
1817
/// @notice An inbox contract within RIP-7755. This contract's sole purpose is to route requested transactions on
1918
/// destination chains and store record of their fulfillment.
2019
contract RIP7755Inbox is ERC7786Base, Paymaster {
21-
using Address for address payable;
2220
using Strings for string;
2321

2422
struct MainStorage {
@@ -35,6 +33,16 @@ contract RIP7755Inbox is ERC7786Base, Paymaster {
3533
address fulfiller;
3634
}
3735

36+
/// @notice A message structure used for internal processing
37+
struct InternalMessage {
38+
/// @dev The fulfiller address allowed to claim on source chain
39+
address fulfiller;
40+
/// @dev Boolean value specifying if the request represents an ERC-4337 UserOp
41+
bool isUserOp;
42+
/// @dev Address of the specified precheck contract. This is optional.
43+
address precheckContract;
44+
}
45+
3846
/// @notice Main storage location used as the base for the fulfillmentInfo mapping following EIP-7201. Derived from
3947
/// the equation keccak256(abi.encode(uint256(keccak256(bytes("RIP-7755"))) - 1)) & ~bytes32(uint256(0xff))
4048
bytes32 private constant _MAIN_STORAGE_LOCATION = 0xfd1017d80ffe8da8a74488ee7408c9efa1877e094afa95857de95797c1228500;
@@ -81,21 +89,29 @@ contract RIP7755Inbox is ERC7786Base, Paymaster {
8189
Message[] calldata messages,
8290
bytes[] calldata globalAttributes
8391
) external payable returns (bytes4) {
84-
address fulfiller = _getFulfiller(globalAttributes);
85-
_revertIfUserOp(globalAttributes);
92+
InternalMessage memory m = _processAttributes(globalAttributes);
93+
94+
if (m.fulfiller == address(0)) {
95+
revert AttributeNotFound(_FULFILLER_ATTRIBUTE_SELECTOR);
96+
}
97+
98+
if (m.isUserOp) {
99+
revert UserOp();
100+
}
101+
86102
bytes32 messageId = getRequestId(sourceChain, sender, messages, globalAttributes);
87103

88-
_runPrecheck(sourceChain, sender, messages, globalAttributes);
104+
_runPrecheck(sourceChain, sender, messages, globalAttributes, m.precheckContract);
89105

90106
if (_getFulfillmentInfo(messageId).timestamp != 0) {
91107
revert CallAlreadyFulfilled();
92108
}
93109

94-
_setFulfillmentInfo(messageId, fulfiller);
110+
_setFulfillmentInfo(messageId, m.fulfiller);
95111

96112
_sendCallsAndValidateMsgValue(messages);
97113

98-
emit CallFulfilled({requestHash: messageId, fulfilledBy: fulfiller});
114+
emit CallFulfilled({requestHash: messageId, fulfilledBy: m.fulfiller});
99115

100116
return 0x675b049b; // this function's sig
101117
}
@@ -134,8 +150,8 @@ contract RIP7755Inbox is ERC7786Base, Paymaster {
134150
uint256 valueSent;
135151

136152
for (uint256 i; i < messages.length; i++) {
137-
address payable to = payable(messages[i].receiver.parseAddress());
138-
uint256 value = _locateAttributeValue(messages[i].attributes, _VALUE_ATTRIBUTE_SELECTOR);
153+
address to = messages[i].receiver.parseAddress();
154+
uint256 value = _locateAttributeValue(messages[i].attributes);
139155
_call(to, messages[i].payload, value);
140156

141157
unchecked {
@@ -148,11 +164,16 @@ contract RIP7755Inbox is ERC7786Base, Paymaster {
148164
}
149165
}
150166

151-
function _call(address payable to, bytes memory data, uint256 value) private {
152-
if (data.length == 0) {
153-
to.sendValue(value);
154-
} else {
155-
to.functionCallWithValue(data, value);
167+
function _call(address to, bytes memory data, uint256 value) private {
168+
bytes memory result;
169+
/// @solidity memory-safe-assembly
170+
assembly {
171+
result := mload(0x40)
172+
if iszero(call(gas(), to, value, add(data, 0x20), mload(data), codesize(), 0x00)) {
173+
// Bubble up the revert if the call reverts.
174+
returndatacopy(result, 0x00, returndatasize())
175+
revert(result, returndatasize())
176+
}
156177
}
157178
}
158179

@@ -167,39 +188,35 @@ contract RIP7755Inbox is ERC7786Base, Paymaster {
167188
string calldata sourceChain, // [CAIP-2] chain identifier
168189
string calldata sender, // [CAIP-10] account address
169190
Message[] calldata messages,
170-
bytes[] calldata attributes
191+
bytes[] calldata attributes,
192+
address precheck
171193
) private view {
172-
(bool found, bytes calldata precheckAttribute) =
173-
_locateAttributeUnchecked(attributes, _PRECHECK_ATTRIBUTE_SELECTOR);
174-
175-
if (!found) {
194+
if (precheck == address(0)) {
176195
return;
177196
}
178197

179-
address precheckContract = abi.decode(precheckAttribute[4:], (address));
180-
IPrecheckContract(precheckContract).precheckCall(sourceChain, sender, messages, attributes, msg.sender);
198+
IPrecheckContract(precheck).precheckCall(sourceChain, sender, messages, attributes, msg.sender);
181199
}
182200

183201
function _getFulfillmentInfo(bytes32 requestHash) private view returns (FulfillmentInfo memory) {
184202
MainStorage storage $ = _getMainStorage();
185203
return $.fulfillmentInfo[requestHash];
186204
}
187205

188-
function _getFulfiller(bytes[] calldata attributes) private pure returns (address) {
189-
bytes calldata fulfillerAttribute = _locateAttribute(attributes, _FULFILLER_ATTRIBUTE_SELECTOR);
190-
return abi.decode(fulfillerAttribute[4:], (address));
191-
}
192-
193-
function _revertIfUserOp(bytes[] calldata attributes) private pure {
194-
(bool found, bytes calldata userOpAttribute) =
195-
_locateAttributeUnchecked(attributes, _USER_OP_ATTRIBUTE_SELECTOR);
196-
if (found) {
197-
bool isUserOp = abi.decode(userOpAttribute[4:], (bool));
206+
function _processAttributes(bytes[] calldata attributes) private pure returns (InternalMessage memory) {
207+
InternalMessage memory message;
198208

199-
if (isUserOp) {
200-
revert UserOp();
209+
for (uint256 i; i < attributes.length; i++) {
210+
if (bytes4(attributes[i]) == _FULFILLER_ATTRIBUTE_SELECTOR) {
211+
message.fulfiller = abi.decode(attributes[i][4:], (address));
212+
} else if (bytes4(attributes[i]) == _USER_OP_ATTRIBUTE_SELECTOR) {
213+
message.isUserOp = abi.decode(attributes[i][4:], (bool));
214+
} else if (bytes4(attributes[i]) == _PRECHECK_ATTRIBUTE_SELECTOR) {
215+
message.precheckContract = abi.decode(attributes[i][4:], (address));
201216
}
202217
}
218+
219+
return message;
203220
}
204221

205222
function _filterOutFulfiller(bytes[] calldata attributes) private pure returns (bytes[] memory) {

Diff for: contracts/src/libraries/StateValidator.sol

+5-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
pragma solidity ^0.8.0;
33

44
import {RLPReader} from "optimism/packages/contracts-bedrock/src/libraries/rlp/RLPReader.sol";
5-
import {MerkleTrie} from "optimism/packages/contracts-bedrock/src/libraries/trie/MerkleTrie.sol";
65
import {SecureMerkleTrie} from "optimism/packages/contracts-bedrock/src/libraries/trie/SecureMerkleTrie.sol";
76

87
import {SSZ} from "./SSZ.sol";
@@ -113,10 +112,12 @@ library StateValidator {
113112
bytes32 stateRoot,
114113
AccountProofParameters memory accountProofParams
115114
) internal pure returns (bool) {
116-
// Derive the account key that shows up in the execution client's merkle trie
117-
bytes memory accountKey = abi.encodePacked(keccak256(abi.encodePacked(account)));
118115
// Use the account proof to derive the RLP-encoded account metadata
119-
bytes memory encodedAccount = MerkleTrie.get(accountKey, accountProofParams.accountProof, stateRoot);
116+
bytes memory encodedAccount = SecureMerkleTrie.get({
117+
_key: abi.encodePacked(account),
118+
_proof: accountProofParams.accountProof,
119+
_root: stateRoot
120+
});
120121

121122
// Extract storage root from account data
122123
bytes32 storageRoot = _extractStorageRoot(encodedAccount);

Diff for: contracts/test/RIP7755Inbox.t.sol

+22
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ contract RIP7755InboxTest is BaseTest {
4141
_setUp();
4242
}
4343

44+
function test_executeMessages_reverts_ifNoFulfiller() external {
45+
TestMessage memory m = _initMessage(false, true);
46+
47+
vm.expectRevert(abi.encodeWithSelector(AttributeNotFound.selector, _FULFILLER_ATTRIBUTE_SELECTOR));
48+
inbox.executeMessages(m.sourceChain, m.sender, m.messages, _filterOutFulfiller(m.attributes));
49+
}
50+
4451
function test_executeMessages_reverts_userOp() external {
4552
TestMessage memory m = _initMessage(false, true);
4653

@@ -126,6 +133,21 @@ contract RIP7755InboxTest is BaseTest {
126133
assertEq(ALICE.balance, 0);
127134
}
128135

136+
function test_executeMessages_reverts_tooManyAttributes(uint256 amount) external {
137+
TestMessage memory m = _initMessage(false, false);
138+
139+
bytes[] memory attributes = new bytes[](2);
140+
141+
_appendMessage(m, Message({receiver: ALICE.toChecksumHexString(), payload: "", attributes: attributes}));
142+
143+
vm.deal(FILLER, amount);
144+
vm.prank(FILLER);
145+
vm.expectRevert(MaxOneAttributeExpected.selector);
146+
inbox.executeMessages(m.sourceChain, m.sender, m.messages, m.attributes);
147+
148+
assertEq(ALICE.balance, 0);
149+
}
150+
129151
function test_executeMessages_reverts_ifTargetContractReverts() external {
130152
TestMessage memory m = _initMessage(false, false);
131153

0 commit comments

Comments
 (0)