Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
6 changes: 6 additions & 0 deletions snapshots/Solver7702DelegateIntegrationTest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"7702 submission - reverts when - unauthorized error when revert": "460",
"7702 submission - success - forwards large settlement payload": "96271",
"7702 submission - success - forwards small settlement payload": "95120",
"7702 submission - success - settlement sees solver eoa": "121332"
}
16 changes: 16 additions & 0 deletions snapshots/Solver7702DelegateTest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"abi encoded envelope - success - does not reach intended target": "3143",
"empty calldata - success - receives eth from any caller": "47",
"packed calldata - reverts when - target empty data when revert": "3753",
"packed calldata - reverts when - target revert data when revert": "3828",
"packed calldata - reverts when - unauthorized error when revert": "460",
"packed calldata - success - calls target with empty payload": "25406",
"packed calldata approved caller 0 - success - first target call gas only": "47667",
"packed calldata approved caller 1 - success - repeat target call gas only": "16786",
"packed calldata approved caller 2 - success - repeat target call gas only": "16820",
"packed calldata approved caller 3 - success - repeat target call gas only": "16854",
"packed calldata approved caller 4 - success - repeat target call gas only": "16889",
"packed calldata no code target - success - returns empty data": "3131",
"packed calldata return data - success - bubbles exact return data": "3767",
"packed calldata zero msg value - success - forwards calldata": "26227"
}
1 change: 1 addition & 0 deletions src/Solver7702Delegate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ contract Solver7702Delegate {
}

/// @notice Fallback function to handle calls to the delegate
/// @dev Expected calldata format is `bytes20(target) || targetCalldata`.
fallback() external payable {
// Simply receive ETH
if (msg.data.length < 20) return;
Expand Down
49 changes: 49 additions & 0 deletions test/BaseTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.34;
Comment thread
igorroncevic marked this conversation as resolved.

import {Test} from "forge-std/Test.sol";

import {Solver7702Delegate} from "src/Solver7702Delegate.sol";

struct ApprovedCallers {
address first;
address second;
address third;
address fourth;
address fifth;
}

abstract contract BaseTest is Test {
uint256 internal constant SOLVER_PRIVATE_KEY = uint256(keccak256("SOLVER_PRIVATE_KEY"));
uint256 internal constant MSG_VALUE = 1 ether;

Solver7702Delegate internal delegateContract;

address internal solver;
ApprovedCallers internal approvedCallers;

function setUp() public virtual {
solver = vm.addr(SOLVER_PRIVATE_KEY);
approvedCallers = ApprovedCallers({
first: makeAddr("APPROVED_CALLER_0"),
second: makeAddr("APPROVED_CALLER_1"),
third: makeAddr("APPROVED_CALLER_2"),
fourth: makeAddr("APPROVED_CALLER_3"),
fifth: makeAddr("APPROVED_CALLER_4")
});

delegateContract = new Solver7702Delegate(
[
approvedCallers.first,
approvedCallers.second,
approvedCallers.third,
approvedCallers.fourth,
approvedCallers.fifth
]
);
}

function _packedCalldata(address target, bytes memory payload) internal pure returns (bytes memory) {
return abi.encodePacked(bytes20(target), payload);
}
}
34 changes: 34 additions & 0 deletions test/Solver7702Delegate.fork.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// solhint-disable avoid-low-level-calls, gas-small-strings
pragma solidity ^0.8.34;

import {BaseTest} from "test/BaseTest.t.sol";
import {MockSettlement} from "test/mocks/MockSettlement.sol";

contract Solver7702DelegateForkTest is BaseTest {
uint256 internal constant SETTLEMENT_AMOUNT = 12.34 ether;
bytes32 internal constant FORK_ORDER_UID = keccak256("fork order");

function test_fork_7702Submission_success_settlementSeesSolverEoa() public {
// ~~~~~~~~~~ Setup ~~~~~~~~~~
string memory mainnetRpcUrl = vm.envOr("MAINNET_RPC_URL", string(""));
if (bytes(mainnetRpcUrl).length == 0) {
vm.skip(true, "MAINNET_RPC_URL not set");
}

vm.createSelectFork(mainnetRpcUrl);
MockSettlement settlement = new MockSettlement();
bytes memory payload = abi.encodeCall(MockSettlement.settle, (FORK_ORDER_UID, SETTLEMENT_AMOUNT));
vm.signAndAttachDelegation(address(delegateContract), SOLVER_PRIVATE_KEY);

// ~~~~~~~~~~ Call ~~~~~~~~~~
vm.prank(approvedCallers.first);
(bool success, bytes memory returnData) = solver.call(_packedCalldata(address(settlement), payload));
vm.snapshotGasLastCall("7702 submission - success - settlement sees solver eoa on fork");

// ~~~~~~~~~~ Assertions ~~~~~~~~~~
assertTrue(success);
assertEq(abi.decode(returnData, (bytes32)), FORK_ORDER_UID);
assertEq(settlement.lastSender(), solver);
}
}
109 changes: 109 additions & 0 deletions test/Solver7702Delegate.integration.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
// solhint-disable avoid-low-level-calls, gas-small-strings
pragma solidity ^0.8.34;

import {Solver7702Delegate} from "src/Solver7702Delegate.sol";
import {BaseTest} from "test/BaseTest.t.sol";
import {MockSettlement} from "test/mocks/MockSettlement.sol";

contract Solver7702DelegateIntegrationTest is BaseTest {
uint256 internal constant SETTLEMENT_AMOUNT = 12.34 ether;
uint256 internal constant SMALL_SETTLEMENT_WORDS = 1;
uint256 internal constant LARGE_SETTLEMENT_WORDS = 64;
bytes32 internal constant ORDER_UID = keccak256("order");

MockSettlement internal settlement;
address internal unauthorizedCaller;

function setUp() public override {
super.setUp();

settlement = new MockSettlement();
unauthorizedCaller = makeAddr("UNAUTHORIZED_CALLER");
}

function test_integration_7702Submission_success_settlementSeesSolverEoa() public {
// ~~~~~~~~~~ Setup ~~~~~~~~~~
bytes memory payload = abi.encodeCall(MockSettlement.settle, (ORDER_UID, SETTLEMENT_AMOUNT));
vm.deal(approvedCallers.first, MSG_VALUE);
vm.signAndAttachDelegation(address(delegateContract), SOLVER_PRIVATE_KEY);

// ~~~~~~~~~~ Call ~~~~~~~~~~
vm.prank(approvedCallers.first);
(bool success, bytes memory returnData) =
solver.call{value: MSG_VALUE}(_packedCalldata(address(settlement), payload));
vm.snapshotGasLastCall("7702 submission - success - settlement sees solver eoa");

// ~~~~~~~~~~ Assertions ~~~~~~~~~~
assertTrue(success);
assertEq(abi.decode(returnData, (bytes32)), ORDER_UID);
assertEq(settlement.lastSender(), solver);
assertEq(settlement.lastValue(), MSG_VALUE);
assertEq(settlement.lastOrderUid(), ORDER_UID);
assertEq(settlement.lastAmount(), SETTLEMENT_AMOUNT);
assertEq(address(settlement).balance, MSG_VALUE);
}

function test_integration_7702Submission_success_forwardsSmallSettlementPayload() public {
// ~~~~~~~~~~ Setup ~~~~~~~~~~
bytes memory settlementPayload = _settlementPayload(SMALL_SETTLEMENT_WORDS);
bytes memory payload = abi.encodeCall(MockSettlement.settlePayload, (settlementPayload));
vm.signAndAttachDelegation(address(delegateContract), SOLVER_PRIVATE_KEY);

// ~~~~~~~~~~ Call ~~~~~~~~~~
vm.prank(approvedCallers.first);
(bool success, bytes memory returnData) = solver.call(_packedCalldata(address(settlement), payload));
vm.snapshotGasLastCall("7702 submission - success - forwards small settlement payload");

// ~~~~~~~~~~ Assertions ~~~~~~~~~~
assertTrue(success);
assertEq(abi.decode(returnData, (bytes32)), keccak256(settlementPayload));
assertEq(settlement.lastSender(), solver);
assertEq(settlement.lastPayloadHash(), keccak256(settlementPayload));
assertEq(settlement.lastPayloadLength(), settlementPayload.length);
}

function test_integration_7702Submission_success_forwardsLargeSettlementPayload() public {
// ~~~~~~~~~~ Setup ~~~~~~~~~~
bytes memory settlementPayload = _settlementPayload(LARGE_SETTLEMENT_WORDS);
bytes memory payload = abi.encodeCall(MockSettlement.settlePayload, (settlementPayload));
vm.signAndAttachDelegation(address(delegateContract), SOLVER_PRIVATE_KEY);

// ~~~~~~~~~~ Call ~~~~~~~~~~
vm.prank(approvedCallers.first);
(bool success, bytes memory returnData) = solver.call(_packedCalldata(address(settlement), payload));
vm.snapshotGasLastCall("7702 submission - success - forwards large settlement payload");

// ~~~~~~~~~~ Assertions ~~~~~~~~~~
assertTrue(success);
assertEq(abi.decode(returnData, (bytes32)), keccak256(settlementPayload));
assertEq(settlement.lastSender(), solver);
assertEq(settlement.lastPayloadHash(), keccak256(settlementPayload));
assertEq(settlement.lastPayloadLength(), settlementPayload.length);
}

function test_integration_7702Submission_revertsWhen_callerUnauthorized_UnauthorizedErrorWhenRevert() public {
// ~~~~~~~~~~ Setup ~~~~~~~~~~
bytes memory payload = abi.encodeCall(MockSettlement.settle, (ORDER_UID, SETTLEMENT_AMOUNT));
vm.signAndAttachDelegation(address(delegateContract), SOLVER_PRIVATE_KEY);

// ~~~~~~~~~~ Call ~~~~~~~~~~
vm.prank(unauthorizedCaller);
(bool success, bytes memory returnData) = solver.call(_packedCalldata(address(settlement), payload));
vm.snapshotGasLastCall("7702 submission - reverts when - unauthorized error when revert");

// ~~~~~~~~~~ Assertions ~~~~~~~~~~
assertFalse(success);
assertEq(returnData, abi.encodeWithSelector(Solver7702Delegate.Unauthorized.selector, unauthorizedCaller));
}

function _settlementPayload(uint256 words) internal pure returns (bytes memory payload) {
payload = new bytes(words * 32);
for (uint256 i; i < words; ++i) {
bytes32 word = keccak256(abi.encode(i));
assembly {
mstore(add(add(payload, 0x20), mul(i, 0x20)), word)
}
}
}
}
Loading
Loading