Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ETH_MAINNET_RPC_URL=
GNOSIS_RPC_URL=
ARBITRUM_ONE_RPC_URL=
BASE_RPC_URL=
OPTIMISM_RPC_URL=
POLYGON_RPC_URL=
BNB_MAINNET_RPC_URL=
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
6 changes: 3 additions & 3 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ slither:

# Run tests
test:
{{FORGE}} test -vvv --show-progress --gas-snapshot-check true
{{FORGE}} test -vvv --show-progress
Comment thread
igorroncevic marked this conversation as resolved.
Outdated

# Print coverage summary
coverage-summary:
{{FORGE}} coverage --no-match-coverage "^(test|script)/" --report summary
{{FORGE}} coverage --no-match-coverage "^(test|script|lib)/" --report summary

# Generate lcov coverage report
coverage-lcov:
{{FORGE}} coverage --no-match-coverage "^(test|script)/" --report lcov
{{FORGE}} coverage --no-match-coverage "^(test|script|lib)/" --report lcov

# Fail if the minimum of all four coverage metrics (lines/statements/branches/funcs) on the `Total` row is below `COVERAGE_MIN` (default `100`)
coverage-check:
Expand Down
6 changes: 6 additions & 0 deletions foundry.lock
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
{
"lib/forge-std": {
"rev": "1801b0541f4fda118a10798fd3486bb7051c5dd6"
},
"lib/openzeppelin-contracts": {
"tag": {
"name": "v5.6.1",
"rev": "5fd1781b1454fd1ef8e722282f86f9293cacf256"
}
}
}
5 changes: 4 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
src = "src"
out = "out"
libs = ["lib"]
remappings = [
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/"
]
solc = "0.8.34" # See latest release at: https://github.com/argotorg/solidity/releases

[fmt]
Expand All @@ -13,4 +16,4 @@ wrap_comments = true
deny = "warnings" # Why not always: sometimes you just want to code and see what comes out
verbosity = 3 # Outputs stack traces for failed tests.
fuzz.seed = "0"
fuzz.runs = 10_000
fuzz.runs = 10_000
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added for mocks and possibly other utilities for testing. Isn't used in src/

Submodule openzeppelin-contracts added at 5fd178
26 changes: 26 additions & 0 deletions snapshots/Solver7702DelegateForkTest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"arbitrum - delegated call - simple WETH -> USDC order attempt": "15439",
"arbitrum - direct call - simple WETH -> USDC order attempt": "11450",
"base - delegated call - simple WETH -> USDC order attempt": "15435",
"base - direct call - simple WETH -> USDC order attempt": "11450",
"bnb - delegated call - simple WETH -> USDC order attempt": "15439",
"bnb - direct call - simple WETH -> USDC order attempt": "11450",
"ethereum - delegated call - simple WETH -> USDC order attempt": "15439",
"ethereum - direct call - simple WETH -> USDC order attempt": "11450",
"gnosis - delegated call - simple WETH -> USDC order attempt": "15439",
"gnosis - direct call - simple WETH -> USDC order attempt": "11450",
"historical order - delegated call - USDC -> EURA": "190342",
"historical order - delegated call - USDC permit -> MOG": "446987",
"historical order - delegated call - USDT -> AAVE": "783398",
"historical order - delegated call - WETH -> USDC": "253863",
"historical order - delegated call - YFI -> USDC": "814546",
"historical order - direct call - USDC -> EURA": "186425",
"historical order - direct call - USDC permit -> MOG": "443150",
"historical order - direct call - USDT -> AAVE": "779746",
"historical order - direct call - WETH -> USDC": "249951",
"historical order - direct call - YFI -> USDC": "811173",
"optimism - delegated call - simple WETH -> USDC order attempt": "15439",
"optimism - direct call - simple WETH -> USDC order attempt": "11450",
"polygon - delegated call - simple WETH -> USDC order attempt": "15439",
"polygon - direct call - simple WETH -> USDC order attempt": "11450"
}
9 changes: 9 additions & 0 deletions snapshots/Solver7702DelegateIntegrationTest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"delegated submission - reverts - UnauthorizedCaller": "460",
"delegated submission - success - interactions across phases": "44776",
"delegated submission - success - large settlement with interactions": "114722",
"delegated submission - success - small settlement": "24184",
"direct submission - success - interactions across phases": "157450",
"direct submission - success - large settlement with interactions": "224869",
"direct submission - success - small settlement": "117371"
}
21 changes: 21 additions & 0 deletions snapshots/Solver7702DelegateTest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"constructor - success - distinct callers": "1212494",
"constructor - success - duplicate callers": "1212494",
"constructor - success - zero address callers": "1212494",
"delegate fallback - reverts - custom error": "4398",
"delegate fallback - reverts - empty revert data": "3753",
"delegate fallback - reverts - non-empty revert data": "3828",
"delegate fallback - reverts - panic": "3405",
"delegate fallback - reverts - sending ETH to nonpayable target": "9875",
"delegate fallback - reverts - string error": "4057",
"delegate fallback - reverts - unauthorized caller": "460",
"delegate fallback - success - approved caller slot 0 forwards payload": "43084",
"delegate fallback - success - approved caller slot 1 forwards payload": "23429",
"delegate fallback - success - approved caller slot 2 forwards payload": "23490",
"delegate fallback - success - approved caller slot 3 forwards payload": "23549",
"delegate fallback - success - approved caller slot 4 forwards payload": "23577",
"delegate fallback - success - packed calldata bubbles return data": "3767",
"delegate fallback - success - packed calldata forwards zero ETH": "23749",
"delegate fallback - success - target has no code": "3131",
"delegate fallback - success - target is zero address": "631"
}
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
2 changes: 2 additions & 0 deletions test/.solhint.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{
"rules": {
"func-name-mixedcase": "off",
"gas-strict-inequalities": "off",
"no-empty-blocks": "off",
"use-natspec": "off"
}
}
97 changes: 97 additions & 0 deletions test/BaseTest.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// 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 {
/// @notice First approved caller slot.
address first;
/// @notice Second approved caller slot.
address second;
/// @notice Third approved caller slot.
address third;
/// @notice Fourth approved caller slot.
address fourth;
/// @notice Fifth approved caller slot.
address fifth;
}

/// @notice Shared setup and helpers for Solver7702Delegate tests.
abstract contract BaseTest is Test {
/// @notice Private key for the EOA that gets EIP-7702 delegation attached.
uint256 internal constant SOLVER_PRIVATE_KEY = uint256(keccak256("SOLVER_PRIVATE_KEY"));
/// @notice ETH value used by tests that forward native token value.
uint256 internal constant MSG_VALUE = 1 ether;
/// @notice Number of bytes used to encode the packed target address.
uint256 internal constant PACKED_TARGET_LENGTH = 20;
/// @notice Shared token and mock settlement amount.
uint256 internal constant TEST_AMOUNT = 100 ether;
/// @notice Shared order UID used by mock settlement tests.
bytes32 internal constant TEST_ORDER_UID = keccak256("TEST_ORDER_UID");
/// @notice Selector for RawRevert(uint256,string).
bytes4 internal constant RAW_REVERT_SELECTOR = bytes4(keccak256("RawRevert(uint256,string)"));

/// @notice Shared solver address.
address internal solver;
/// @notice Shared recipient address.
address internal recipient;
/// @notice Shared unauthorized caller address.
address internal unauthorizedCaller;
/// @notice Shared approved callers.
ApprovedCallers internal approvedCallers;
/// @notice Shared delegate contract.
Solver7702Delegate internal delegateContract;

/// @notice Creates the default solver, approved callers, and delegate contract.
function setUp() public virtual {
solver = vm.addr(SOLVER_PRIVATE_KEY);
recipient = makeAddr("RECIPIENT");
unauthorizedCaller = makeAddr("UNAUTHORIZED_CALLER");
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
]
);
}

/// @notice Encodes the delegate fallback calldata as a 20-byte target followed by payload.
function _packedCalldata(address target, bytes memory payload) internal pure returns (bytes memory) {
return abi.encodePacked(bytes20(target), payload);
}

/// @notice Attaches the delegate code to the solver EOA.
function _attachDelegation(uint256 solverPrivateKey) internal {
vm.signAndAttachDelegation(address(delegateContract), solverPrivateKey);
}

/// @notice Decodes and checks the return data from PayableFallbackTarget.
function _assertFallbackReturn(
bytes memory returnData,
address expectedSender,
uint256 expectedValue,
bytes memory expectedPayload,
uint256 expectedBalance
) internal pure {
(address sender, uint256 value, bytes memory payload, uint256 balance) =
abi.decode(returnData, (address, uint256, bytes, uint256));

assertEq(sender, expectedSender);
assertEq(value, expectedValue);
assertEq(payload, expectedPayload);
assertEq(balance, expectedBalance);
}
}
Loading
Loading