Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make settling swaps on SettlerIntent permissioned #293

Merged
merged 68 commits into from
Mar 11, 2025
Merged
Changes from 18 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
1d7f292
WIP: Make settling swaps on `SettlerIntent` permissioned
duncancmt Jan 28, 2025
9306e4b
Bug! Bad swap-and-pop in `SettlerIntent`
duncancmt Jan 28, 2025
c01e099
Pedantically, run the permission check before setting the metatx para…
duncancmt Jan 28, 2025
19576de
Merge branch 'master' into dcmt/intent-permissioned
duncancmt Jan 29, 2025
40a2cda
Golf
duncancmt Jan 29, 2025
70475ab
Golf
duncancmt Jan 29, 2025
988c283
Bug hunt
duncancmt Jan 29, 2025
19e91f4
Golf
duncancmt Jan 29, 2025
d18f6ff
Cleanup
duncancmt Jan 29, 2025
beeef86
Comments
duncancmt Jan 30, 2025
fc46136
Give an explicit revert reason in `setSolver`
duncancmt Jan 30, 2025
c0883d3
Comment
duncancmt Jan 30, 2025
1dd7eec
Form the `_SOLVER_LIST_BASE_SLOT` in a ERC1976-ish fashion
duncancmt Jan 30, 2025
bd8472c
Comment
duncancmt Jan 30, 2025
98ee81c
Typo
duncancmt Jan 30, 2025
9a18dd5
Remove event because we can use storage to enumerate the state change…
duncancmt Jan 30, 2025
23d5583
Comment
duncancmt Jan 30, 2025
90c5702
Simplify
duncancmt Jan 30, 2025
26f9245
DRY
duncancmt Jan 30, 2025
50db50c
Formatting
duncancmt Jan 30, 2025
41f2f00
Pedantry
duncancmt Jan 30, 2025
07d3527
Pedantry
duncancmt Jan 30, 2025
faa539e
Always clean dirty bits
duncancmt Feb 6, 2025
a33c023
Merge branch 'dcmt/multicall-2771' into dcmt/intent-permissioned
duncancmt Feb 10, 2025
11811a6
Use `MultiCallContext` in `SettlerIntent`
duncancmt Feb 10, 2025
1cd1c5d
`forge fmt`
duncancmt Feb 10, 2025
70404d8
Golf
duncancmt Feb 10, 2025
04ef79d
Bug! OOM on failure on `safeApprove` and `safeTransfer`
duncancmt Feb 10, 2025
9ba47f8
Golf `DodoV1` like my life depends on it
duncancmt Feb 10, 2025
e2a1192
Add checks for short `returndata`
duncancmt Feb 10, 2025
6eaca8e
Fix bugs
duncancmt Feb 10, 2025
3ba22b7
Make the list of authorized solvers enumerable through an accessor
duncancmt Feb 11, 2025
7328c01
Check for dirty `returndata`
duncancmt Feb 11, 2025
6901600
Bug! Use the correct ancestor contract implementation of `_msgSender(…
duncancmt Feb 11, 2025
5ac079e
Merge branch 'dcmt/multicall-2771' into dcmt/intent-permissioned
duncancmt Feb 11, 2025
41f7b71
Comment
duncancmt Feb 11, 2025
b1ae277
Comment
duncancmt Feb 12, 2025
9fe8813
Merge branch 'master' into dcmt/intent-permissioned
duncancmt Feb 12, 2025
81e62fb
WIP: `SettlerIntent` deployment ceremony
duncancmt Feb 14, 2025
4780051
Merge branch 'master' into dcmt/intent-permissioned
duncancmt Feb 14, 2025
bd5b661
Add `initial_description_intent.md`
duncancmt Feb 20, 2025
911ab41
Explain a bit more about *why* intent settlement is permissioned
duncancmt Mar 4, 2025
0ce9f8d
Merge branch 'master' into dcmt/intent-permissioned
duncancmt Mar 4, 2025
496b3bd
Add script for renewing the authority on an existing feature
duncancmt Mar 4, 2025
49bf6b4
Add script for submitting the authority renewal Safe{Wallet} transaction
duncancmt Mar 4, 2025
4da5c14
Add script for submitting the new feature Safe{Wallet} transaction
duncancmt Mar 4, 2025
5dbe84c
Golf
duncancmt Mar 4, 2025
2eef94c
Merge branch 'master' into dcmt/intent-permissioned
duncancmt Mar 4, 2025
96a60a2
Merge branch 'master' into dcmt/intent-permissioned
duncancmt Mar 5, 2025
6f6afbc
Add missing `source` to `new_feature.sh` and `renew_authority.sh`
duncancmt Mar 5, 2025
ae715ba
Fix typos/dumb mistakes
duncancmt Mar 9, 2025
02ad6ef
WIP: deployment scripts for SettlerIntent
duncancmt Mar 10, 2025
ba8f940
Add list of solvers for SettlerIntent
duncancmt Mar 10, 2025
109ec95
WIP: roll SettlerIntent solver configuration into deployment
duncancmt Mar 10, 2025
44d1777
Add SettlerIntent to verification script
duncancmt Mar 10, 2025
ec24ef0
REVERTME: comment-out the taker-submitted and metatransaction Settler…
duncancmt Mar 10, 2025
ba21e2c
Remove unused variable
duncancmt Mar 10, 2025
76acf5f
`cast call` doesn't like `--chain-id`
duncancmt Mar 10, 2025
f6d767c
Increase Sepolia minimum base gas price to 10 gwei
duncancmt Mar 11, 2025
bbfce8e
Typos
duncancmt Mar 11, 2025
1f8d7c9
Fix `verify_settler.sh`
duncancmt Mar 11, 2025
4396e37
Cleanup
duncancmt Mar 11, 2025
e95ceb2
Fix `renew_authority.sh`
duncancmt Mar 11, 2025
f6cd3c2
Homogenize
duncancmt Mar 11, 2025
f30df2c
Cleanup `replace_signer.sh`
duncancmt Mar 11, 2025
e4ee309
Merge branch 'dcmt/intent-permissioned' into dcmt/intent-deploy
duncancmt Mar 11, 2025
9ba195b
Merge branch 'dcmt/intent-deploy' into dcmt/intent-permissioned
duncancmt Mar 11, 2025
1a40f73
Cut release
duncancmt Mar 11, 2025
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
161 changes: 160 additions & 1 deletion src/SettlerIntent.sol
Original file line number Diff line number Diff line change
@@ -10,7 +10,166 @@ import {Permit2PaymentIntent, Permit2PaymentMetaTxn, Permit2Payment} from "./cor

import {ISignatureTransfer} from "@permit2/interfaces/ISignatureTransfer.sol";

import {DEPLOYER} from "./deployer/DeployerAddress.sol";
import {IDeployer} from "./deployer/IDeployer.sol";
import {Feature} from "./deployer/Feature.sol";
import {IOwnable} from "./deployer/IOwnable.sol";

abstract contract SettlerIntent is Permit2PaymentIntent, SettlerMetaTxn {
uint256 private constant _SOLVER_LIST_BASE_SLOT = 0xe4441b0608054751d605e5c08a2210bf; // uint128(uint256(keccak256("SettlerIntentSolverList")) - 1)

/// This mapping forms a circular singly-linked list that traverses all the authorized callers
/// of `executeMetaTxn`. The head and tail of the list is `address(1)`, which is the constant
/// `_SENTINEL_SOLVER`. No view function is provided for accessing this mapping. You'll have to
/// use an RPC to read storage directly and reconstruct the list that way. As a consequence of
/// the structure of this list, the check for whether an address is on the list is extremely
/// simple: `_$()[query] != address(0)`. This technique is cribbed from Safe{Wallet}
function _$() private pure returns (mapping(address => address) storage $) {
assembly ("memory-safe") {
$.slot := _SOLVER_LIST_BASE_SLOT
}
}

address private constant _SENTINEL_SOLVER = 0x0000000000000000000000000000000000000001;

constructor() {
_$()[_SENTINEL_SOLVER] = _SENTINEL_SOLVER;
}

modifier onlyOwner() {
// Solidity generates extremely bloated code for the following block, so it has been
// rewritten in assembly so as not to blow out the contract size limit
/*
(address owner, uint40 expiry) = IDeployer(DEPLOYER).authorized(Feature.wrap(uint128(_tokenId())));
*/
address deployer_ = DEPLOYER;
uint256 tokenId_ = _tokenId();
address owner;
uint40 expiry;
assembly ("memory-safe") {
// We lay out the calldata in memory in the first 2 slots. The first slot is the
// selector, but aligned incorrectly (this significantly saves on contract size). The
// second slot is the token ID. Therefore calldata starts at offset 0x1c (32 - 4) and is
// 0x24 bytes long (32 + 4)
mstore(0x00, 0x2bb83987) // selector for `authorized(uint128)`
mstore(0x20, tokenId_)

// Perform the call and bubble any revert. The expected returndata (2 arguments, each 1
// slot) is copied back into the first 2 slots of memory.
if iszero(staticcall(gas(), deployer_, 0x1c, 0x24, 0x00, 0x40)) {
let ptr := mload(0x40)
returndatacopy(ptr, 0x00, returndatasize())
revert(ptr, returndatasize())
}

// If calldata is short (we need at least 64 bytes), revert with an empty reason.
if iszero(gt(returndatasize(), 0x3f)) {
revert(0x00, 0x00)
}

// Load the return values that were automatically written into the first 2 slots of
// memory.
owner := mload(0x00)
expiry := mload(0x20)

// If there are any dirty bits in the return values, revert with an empty reason.
if or(shr(0xa0, owner), shr(0x28, expiry)) {
revert(0x00, 0x00)
}
}

// Check that the owner actually exists, that is that their authority hasn't expired.
require(expiry == type(uint40).max || block.timestamp <= expiry);

// Check that the caller (in this case `_operator()`, because we aren't using the special
// transient-storage taker logic) is the owner.
if (_operator() != owner) {
revert IOwnable.PermissionDenied();
}
_;
}

modifier onlySolver() {
if (_$()[_operator()] == address(0)) {
revert IOwnable.PermissionDenied();
}
_;
}

error InvalidSolver(address prev, address solver);

/// This pattern is cribbed from Safe{Wallet}. See `OwnerManager.sol` from
/// 0x3E5c63644E683549055b9Be8653de26E0B4CD36E.
function setSolver(address prev, address solver, bool addNotRemove) external onlyOwner {
// Solidity generates extremely bloated code for the following block, so it has been
// rewritten in assembly so as not to blow out the contract size limit
/*
require(solver != address(0));
mapping(address => address) storage $ = _$();
require(($[solver] == address(0)) == addNotRemove);
if (addNotRemove) {
require($[prev] == _SENTINEL_SOLVER);
$[prev] = solver;
$[solver] = _SENTINEL_SOLVER;
} else {
require($[prev] == solver);
$[prev] = $[solver];
$[solver] = address(0);
}
*/
assembly ("memory-safe") {
// Clean dirty bits.
prev := and(0xffffffffffffffffffffffffffffffffffffffff, prev)
solver := and(0xffffffffffffffffffffffffffffffffffffffff, solver)

// A solver of zero is special-cased. It is forbidden to set it because that would
// corrupt the list.
let fail := iszero(solver)

// Derive the slot for `solver` and load it.
mstore(0x00, solver)
mstore(0x20, _SOLVER_LIST_BASE_SLOT)
let solverSlot := keccak256(0x00, 0x40)
let solverSlotValue := sload(solverSlot)

// If the slot is zero, `addNotRemove` must be true (we are adding a new
// solver). Likewise if the slot is nonzero, `addNotRemove` must be false (we are
// removing one).
fail := or(fail, xor(iszero(solverSlotValue), addNotRemove))

// Derive the slot for `prev`.
mstore(0x00, and(0xffffffffffffffffffffffffffffffffffffffff, prev))
let prevSlot := keccak256(0x00, 0x40)

// This is a very fancy way of writing:
// expectedPrevSlotValue = addNotRemove ? _SENTINEL_SOLVER : solver
// newPrevSlotValue = addNotRemove ? solver : solverSlotValue
let expectedPrevSlotValue := xor(solver, mul(xor(_SENTINEL_SOLVER, solver), addNotRemove))
let newPrevSlotValue := xor(solverSlotValue, mul(xor(solverSlotValue, solver), addNotRemove))

// Check that the value for `prev` matches the value for `solver`. If we are adding a
// new solver, then `prev` must be the last element of the list (it points at
// `_SENTINEL_SOLVER`). If we are removing an existing solver, then `prev` must point at
// `solver.
fail := or(fail, xor(sload(prevSlot), expectedPrevSlotValue))

// Update the linked list. This either points `$[prev]` at `$[solver]` and zeroes
// `$[solver]` or it points `$[prev]` at `solver` and points `$[solver]` at
// `_SENTINEL_SOLVER`
sstore(prevSlot, newPrevSlotValue)
sstore(solverSlot, addNotRemove)

// If any of the checks failed, revert. This check is deferred because it makes the
// contract substantially smaller.
if fail {
mstore(0x00, 0xe2b339fd) // selector for `InvalidSolver(address,address)`
mstore(0x20, prev)
mstore(0x40, solver)
revert(0x1c, 0x44)
}
}
}

function _tokenId() internal pure virtual override(SettlerAbstract, SettlerMetaTxn) returns (uint256) {
return 4;
}
@@ -60,7 +219,7 @@ abstract contract SettlerIntent is Permit2PaymentIntent, SettlerMetaTxn {
bytes32, /* zid & affiliate */
address msgSender,
bytes calldata sig
) public virtual override metaTx(msgSender, _hashSlippage(slippage)) returns (bool) {
) public virtual override onlySolver metaTx(msgSender, _hashSlippage(slippage)) returns (bool) {
return _executeMetaTxn(slippage, actions, sig);
}

2 changes: 1 addition & 1 deletion src/chains/Blast/Common.sol
Original file line number Diff line number Diff line change
@@ -43,7 +43,7 @@ import {
import {BLAST_POOL_MANAGER} from "../../core/UniswapV4Addresses.sol";

import {DEPLOYER} from "../../deployer/DeployerAddress.sol";
import {IOwnable} from "../../deployer/TwoStepOwnable.sol";
import {IOwnable} from "../../deployer/IOwnable.sol";
import {BLAST, BLAST_USDB, BLAST_WETH, BlastYieldMode, BlastGasMode} from "./IBlast.sol";

// Solidity inheritance is stupid
2 changes: 1 addition & 1 deletion src/chains/Mainnet/Common.sol
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ import {
import {MAINNET_POOL_MANAGER} from "../../core/UniswapV4Addresses.sol";

import {DEPLOYER} from "../../deployer/DeployerAddress.sol";
import {IOwnable} from "../../deployer/TwoStepOwnable.sol";
import {IOwnable} from "../../deployer/IOwnable.sol";

// Solidity inheritance is stupid
import {SettlerAbstract} from "../../SettlerAbstract.sol";
3 changes: 2 additions & 1 deletion src/deployer/Deployer.sol
Original file line number Diff line number Diff line change
@@ -2,7 +2,8 @@
pragma solidity =0.8.25;

import {IERC165} from "@forge-std/interfaces/IERC165.sol";
import {IOwnable, AbstractOwnable} from "./TwoStepOwnable.sol";
import {IOwnable} from "./IOwnable.sol";
import {AbstractOwnable} from "./TwoStepOwnable.sol";
import {
ERC1967UUPSUpgradeable, ERC1967TwoStepOwnable, AbstractUUPSUpgradeable
} from "../proxy/ERC1967UUPSUpgradeable.sol";
2 changes: 1 addition & 1 deletion src/deployer/IDeployer.sol
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@
pragma solidity ^0.8.25;

import {IERC165} from "@forge-std/interfaces/IERC165.sol";
import {IOwnable} from "./TwoStepOwnable.sol";
import {IOwnable} from "./IOwnable.sol";
import {IERC1967Proxy} from "../proxy/ERC1967UUPSUpgradeable.sol";
import {IMultiCall} from "../utils/MultiCall.sol";
import {Feature} from "./Feature.sol";
15 changes: 15 additions & 0 deletions src/deployer/IOwnable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT
pragma solidity =0.8.25;

import {IERC165} from "@forge-std/interfaces/IERC165.sol";

interface IOwnable is IERC165 {
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

function owner() external view returns (address);

function transferOwnership(address) external returns (bool);

error PermissionDenied();
error ZeroAddress();
}
14 changes: 2 additions & 12 deletions src/deployer/TwoStepOwnable.sol
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import {AbstractContext} from "../Context.sol";
import {IERC165} from "@forge-std/interfaces/IERC165.sol";

interface IOwnable is IERC165 {
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

function owner() external view returns (address);

function transferOwnership(address) external returns (bool);

error PermissionDenied();
error ZeroAddress();
}
import {AbstractContext} from "../Context.sol";
import {IOwnable} from "./IOwnable.sol";

abstract contract AbstractOwnable is IOwnable {
// This looks stupid (and it is), but this is required due to the glaring
3 changes: 2 additions & 1 deletion test/ERC1967UUPS.t.sol
Original file line number Diff line number Diff line change
@@ -6,7 +6,8 @@ import "@forge-std/Test.sol";
import {ERC1967UUPSProxy} from "src/proxy/ERC1967UUPSProxy.sol";
import {ERC1967UUPSUpgradeable, IERC1967Proxy} from "src/proxy/ERC1967UUPSUpgradeable.sol";
import {IERC165} from "@forge-std/interfaces/IERC165.sol";
import {AbstractOwnable, IOwnable, Ownable} from "src/deployer/TwoStepOwnable.sol";
import {IOwnable} from "src/deployer/IOwnable.sol";
import {AbstractOwnable, Ownable} from "src/deployer/TwoStepOwnable.sol";
import {Context} from "src/Context.sol";

interface IMock is IOwnable, IERC1967Proxy {}