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

WIP: Intents Settler with conditional validity #282

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
7 changes: 7 additions & 0 deletions src/ISettlerActions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ interface ISettlerActions {
uint256 maxTakerAmount
) external;

function CHAINLINK(
string memory feedName,
int256 priceThreshold,
uint8 expectedDecimals,
uint256 staleThreshold
) external;

function UNISWAPV4(
address recipient,
address sellToken,
Expand Down
4 changes: 2 additions & 2 deletions src/Settler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -95,15 +95,15 @@ abstract contract Settler is Permit2PaymentTakerSubmitted, SettlerBase {
if (actions.length != 0) {
(uint256 action, bytes calldata data) = actions.decodeCall(0);
if (!_dispatchVIP(action, data)) {
if (!_dispatch(0, action, data)) {
if (!_dispatch(action, data)) {
revert ActionInvalid(0, bytes4(uint32(action)), data);
}
}
}

for (uint256 i = 1; i < actions.length; i = i.unsafeInc()) {
(uint256 action, bytes calldata data) = actions.decodeCall(i);
if (!_dispatch(i, action, data)) {
if (!_dispatch(action, data)) {
revert ActionInvalid(i, bytes4(uint32(action)), data);
}
}
Expand Down
16 changes: 1 addition & 15 deletions src/SettlerAbstract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,12 @@ import {IERC20} from "@forge-std/interfaces/IERC20.sol";
import {Permit2PaymentAbstract} from "./core/Permit2PaymentAbstract.sol";

abstract contract SettlerAbstract is Permit2PaymentAbstract {
// Permit2 Witness for meta transactions
string internal constant SLIPPAGE_AND_ACTIONS_TYPE =
"SlippageAndActions(address recipient,address buyToken,uint256 minAmountOut,bytes[] actions)";
bytes32 internal constant SLIPPAGE_AND_ACTIONS_TYPEHASH =
0x615e8d716cef7295e75dd3f1f10d679914ad6d7759e8e9459f0109ef75241701;
// Permit2 Witness for intents
string internal constant SLIPPAGE_TYPE = "Slippage(address recipient,address buyToken,uint256 minAmountOut)";
bytes32 internal constant SLIPPAGE_TYPEHASH = 0xdc83993a2ffc65b01b71ed08790b6e39c5c55d76937b62a3b5085b02071f1259;

uint256 internal constant BASIS = 10_000;
IERC20 internal constant ETH_ADDRESS = IERC20(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE);

constructor() {
assert(SLIPPAGE_AND_ACTIONS_TYPEHASH == keccak256(bytes(SLIPPAGE_AND_ACTIONS_TYPE)));
assert(SLIPPAGE_TYPEHASH == keccak256(bytes(SLIPPAGE_TYPE)));
}

function _hasMetaTxn() internal pure virtual returns (bool);

function _tokenId() internal pure virtual returns (uint256);

function _dispatch(uint256 i, uint256 action, bytes calldata data) internal virtual returns (bool);
function _dispatch(uint256 action, bytes calldata data) internal virtual returns (bool);
}
2 changes: 1 addition & 1 deletion src/SettlerBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ abstract contract SettlerBase is Basic, RfqOrderSettlement, UniswapV3Fork, Unisw
}
}

function _dispatch(uint256, uint256 action, bytes calldata data) internal virtual override returns (bool) {
function _dispatch(uint256 action, bytes calldata data) internal virtual override returns (bool) {
if (action == uint32(ISettlerActions.TRANSFER_FROM.selector)) {
(address recipient, ISignatureTransfer.PermitTransferFrom memory permit, bytes memory sig) =
abi.decode(data, (address, ISignatureTransfer.PermitTransferFrom, bytes));
Expand Down
96 changes: 44 additions & 52 deletions src/SettlerIntent.sol
Original file line number Diff line number Diff line change
@@ -1,72 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import {SettlerAbstract} from "./SettlerAbstract.sol";
import {SettlerBase} from "./SettlerBase.sol";
import {SettlerMetaTxn} from "./SettlerMetaTxn.sol";
import {SettlerMetaTxnBase} from "./SettlerMetaTxn.sol";

import {Permit2PaymentAbstract} from "./core/Permit2PaymentAbstract.sol";
import {Permit2PaymentIntent, Permit2PaymentMetaTxn, Permit2Payment} from "./core/Permit2Payment.sol";
import {Panic} from "./utils/Panic.sol";

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

abstract contract SettlerIntent is Permit2PaymentIntent, SettlerMetaTxn {
function _tokenId() internal pure virtual override(SettlerAbstract, SettlerMetaTxn) returns (uint256) {
return 4;
library ArraySliceBecauseSolidityIsDumb {
function slice(bytes[] calldata data, uint256 stop_) internal pure returns (bytes[] calldata rData) {
if (stop_ > data.length) {
Panic.panic(Panic.ARRAY_OUT_OF_BOUNDS);
}
assembly ("memory-safe") {
rData.offset := data.offset
rData.length := stop_
}
}
}

function _msgSender()
internal
view
virtual
// Solidity inheritance is so stupid
override(Permit2PaymentMetaTxn, SettlerMetaTxn)
returns (address)
{
return super._msgSender();
}
abstract contract SettlerIntent is SettlerMetaTxnBase {
using ArraySliceBecauseSolidityIsDumb for bytes[];

function _witnessTypeSuffix()
internal
pure
virtual
// Solidity inheritance is so stupid
override(Permit2PaymentMetaTxn, Permit2PaymentIntent)
returns (string memory)
{
return super._witnessTypeSuffix();
string internal constant SLIPPAGE_AND_CONDITION_TYPE =
"SlippageAndCondition(address recipient,address buyToken,uint256 minAmountOut,bytes[] condition)";
bytes32 internal constant SLIPPAGE_AND_CONDITION_TYPEHASH =
0x24a8d1e812d61f4d1c5a389ec4379906a57587add93708e221ed7965b9ec1c2c;
string internal constant INTENT_WITNESS_TYPE_SUFFIX =
"SlippageAndCondition slippageAndCondition)SlippageAndCondition(address recipient,address buyToken,uint256 minAmountOut,bytes[] condition)TokenPermissions(address token,uint256 amount)";

constructor() {
assert(SLIPPAGE_AND_CONDITION_TYPEHASH == keccak256(bytes(SLIPPAGE_AND_CONDITION_TYPE)));
assert(
keccak256(bytes(INTENT_WITNESS_TYPE_SUFFIX))
== keccak256(
abi.encodePacked(
"SlippageAndCondition slippageAndCondition)", SLIPPAGE_AND_CONDITION_TYPE, TOKEN_PERMISSIONS_TYPE
)
)
);
}

function _hashSlippage(AllowedSlippage calldata slippage) internal pure returns (bytes32 result) {
// This function does not check for or clean any dirty bits that might
// exist in `slippage`. We assume that `slippage` will be used elsewhere
// in this context and that if there are dirty bits it will result in a
// revert later.
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(ptr, SLIPPAGE_TYPEHASH)
calldatacopy(add(ptr, 0x20), slippage, 0x60)
result := keccak256(ptr, 0x80)
}
function _tokenId() internal pure override returns (uint256) {
return 4;
}

function executeMetaTxn(
function executeIntent(
AllowedSlippage calldata slippage,
bytes[] calldata actions,
bytes32, /* zid & affiliate */
address msgSender,
bytes calldata sig
) public virtual override metaTx(msgSender, _hashSlippage(slippage)) returns (bool) {
return _executeMetaTxn(slippage, actions, sig);
bytes calldata sig,
uint256 prefixLen
)
external
metaTx(msgSender, _hashSlippageAnd(SLIPPAGE_AND_CONDITION_TYPEHASH, actions.slice(prefixLen), slippage))
returns (bool)
{
return _executeMetaTxn(slippage, actions, sig, prefixLen);
}

function _permitToSellAmount(ISignatureTransfer.PermitTransferFrom memory permit)
internal
pure
virtual
override(Permit2PaymentAbstract, Permit2PaymentMetaTxn)
returns (uint256 sellAmount)
{
sellAmount = permit.permitted.amount;
function _witnessTypeSuffix() internal pure override returns (string memory) {
return INTENT_WITNESS_TYPE_SUFFIX;
}
}
175 changes: 29 additions & 146 deletions src/SettlerMetaTxn.sol
Original file line number Diff line number Diff line change
@@ -1,160 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

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

import {Permit2PaymentMetaTxn} from "./core/Permit2Payment.sol";

import {Context, AbstractContext} from "./Context.sol";
import {CalldataDecoder, SettlerBase} from "./SettlerBase.sol";
import {UnsafeMath} from "./utils/UnsafeMath.sol";

import {ISettlerActions} from "./ISettlerActions.sol";
import {ConfusedDeputy, ActionInvalid} from "./core/SettlerErrors.sol";

abstract contract SettlerMetaTxn is Permit2PaymentMetaTxn, SettlerBase {
using UnsafeMath for uint256;
using CalldataDecoder for bytes[];

function _tokenId() internal pure virtual override returns (uint256) {
import {SettlerMetaTxnBase} from "./SettlerMetaTxnBase.sol";

abstract contract SettlerMetaTxn is SettlerMetaTxnBase {
string internal constant SLIPPAGE_AND_ACTIONS_TYPE =
"SlippageAndActions(address recipient,address buyToken,uint256 minAmountOut,bytes[] actions)";
bytes32 internal constant SLIPPAGE_AND_ACTIONS_TYPEHASH =
0x615e8d716cef7295e75dd3f1f10d679914ad6d7759e8e9459f0109ef75241701;
string internal constant METATX_WITNESS_TYPE_SUFFIX =
"SlippageAndActions slippageAndActions)SlippageAndActions(address recipient,address buyToken,uint256 minAmountOut,bytes[] actions)TokenPermissions(address token,uint256 amount)";

constructor() {
assert(SLIPPAGE_AND_ACTIONS_TYPEHASH == keccak256(bytes(SLIPPAGE_AND_ACTIONS_TYPE)));
assert(
keccak256(bytes(METATX_WITNESS_TYPE_SUFFIX))
== keccak256(
abi.encodePacked(
"SlippageAndActions slippageAndActions)", SLIPPAGE_AND_ACTIONS_TYPE, TOKEN_PERMISSIONS_TYPE
)
)
);
}

function _tokenId() internal pure override returns (uint256) {
return 3;
}

function _hasMetaTxn() internal pure override returns (bool) {
return true;
}

function _msgSender()
internal
view
virtual
// Solidity inheritance is so stupid
override(Permit2PaymentMetaTxn, AbstractContext)
returns (address)
{
return super._msgSender();
}

function _hashArrayOfBytes(bytes[] calldata actions) internal pure returns (bytes32 result) {
// This function deliberately does no bounds checking on `actions` for
// gas efficiency. We assume that `actions` will get used elsewhere in
// this context and any OOB or other malformed calldata will result in a
// revert later.
assembly ("memory-safe") {
let ptr := mload(0x40)
let hashesLength := shl(5, actions.length)
for {
let i := actions.offset
let dst := ptr
let end := add(i, hashesLength)
} lt(i, end) {
i := add(i, 0x20)
dst := add(dst, 0x20)
} {
let src := add(actions.offset, calldataload(i))
let length := calldataload(src)
calldatacopy(dst, add(src, 0x20), length)
mstore(dst, keccak256(dst, length))
}
result := keccak256(ptr, hashesLength)
}
}

function _hashActionsAndSlippage(bytes[] calldata actions, AllowedSlippage calldata slippage)
internal
pure
returns (bytes32 result)
{
// This function does not check for or clean any dirty bits that might
// exist in `slippage`. We assume that `slippage` will be used elsewhere
// in this context and that if there are dirty bits it will result in a
// revert later.
bytes32 arrayOfBytesHash = _hashArrayOfBytes(actions);
assembly ("memory-safe") {
let ptr := mload(0x40)
mstore(ptr, SLIPPAGE_AND_ACTIONS_TYPEHASH)
calldatacopy(add(ptr, 0x20), slippage, 0x60)
mstore(add(ptr, 0x80), arrayOfBytesHash)
result := keccak256(ptr, 0xa0)
}
}

function _dispatchVIP(uint256 action, bytes calldata data, bytes calldata sig) internal virtual returns (bool) {
if (action == uint32(ISettlerActions.METATXN_RFQ_VIP.selector)) {
// An optimized path involving a maker/taker in a single trade
// The RFQ order is signed by both maker and taker, validation is
// performed inside the RfqOrderSettlement so there is no need to
// validate `sig` against `actions` here
(
address recipient,
ISignatureTransfer.PermitTransferFrom memory makerPermit,
address maker,
bytes memory makerSig,
ISignatureTransfer.PermitTransferFrom memory takerPermit
) = abi.decode(
data,
(address, ISignatureTransfer.PermitTransferFrom, address, bytes, ISignatureTransfer.PermitTransferFrom)
);

fillRfqOrderVIP(recipient, makerPermit, maker, makerSig, takerPermit, sig);
} else if (action == uint32(ISettlerActions.METATXN_TRANSFER_FROM.selector)) {
(address recipient, ISignatureTransfer.PermitTransferFrom memory permit) =
abi.decode(data, (address, ISignatureTransfer.PermitTransferFrom));
(ISignatureTransfer.SignatureTransferDetails memory transferDetails,) =
_permitToTransferDetails(permit, recipient);

// We simultaneously transfer-in the taker's tokens and authenticate the
// metatransaction.
_transferFrom(permit, transferDetails, sig);
} else if (action == uint32(ISettlerActions.METATXN_UNISWAPV3_VIP.selector)) {
(
address recipient,
bytes memory path,
ISignatureTransfer.PermitTransferFrom memory permit,
uint256 amountOutMin
) = abi.decode(data, (address, bytes, ISignatureTransfer.PermitTransferFrom, uint256));

sellToUniswapV3VIP(recipient, path, permit, sig, amountOutMin);
} else {
return false;
}
return true;
}

function _executeMetaTxn(AllowedSlippage calldata slippage, bytes[] calldata actions, bytes calldata sig)
internal
returns (bool)
{
require(actions.length != 0);
{
(uint256 action, bytes calldata data) = actions.decodeCall(0);

// By forcing the first action to be one of the witness-aware
// actions, we ensure that the entire sequence of actions is
// authorized. `msgSender` is the signer of the metatransaction.
if (!_dispatchVIP(action, data, sig)) {
revert ActionInvalid(0, bytes4(uint32(action)), data);
}
}

for (uint256 i = 1; i < actions.length; i = i.unsafeInc()) {
(uint256 action, bytes calldata data) = actions.decodeCall(i);
if (!_dispatch(i, action, data)) {
revert ActionInvalid(i, bytes4(uint32(action)), data);
}
}

_checkSlippageAndTransfer(slippage);
return true;
}

function executeMetaTxn(
AllowedSlippage calldata slippage,
bytes[] calldata actions,
bytes32, /* zid & affiliate */
address msgSender,
bytes calldata sig
) public virtual metaTx(msgSender, _hashActionsAndSlippage(actions, slippage)) returns (bool) {
return _executeMetaTxn(slippage, actions, sig);
) external metaTx(msgSender, _hashSlippageAnd(SLIPPAGE_AND_ACTIONS_TYPEHASH, actions, slippage)) returns (bool) {
return _executeMetaTxn(slippage, actions, sig, 0);
}

function _witnessTypeSuffix() internal pure override returns (string memory) {
return METATX_WITNESS_TYPE_SUFFIX;
}
}
Loading
Loading