diff --git a/foundry.toml b/foundry.toml index 0a0d20ab..169844ad 100644 --- a/foundry.toml +++ b/foundry.toml @@ -7,7 +7,7 @@ evm_version = "cancun" via_ir = true optimizer = true optimizer_runs = 100_000_000 -fuzz.runs = 10_000 +fuzz.runs = 1000 # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/snapshots/inputSettler.json b/snapshots/inputSettler.json index 338e4b24..bad757ab 100644 --- a/snapshots/inputSettler.json +++ b/snapshots/inputSettler.json @@ -1,11 +1,11 @@ { "BasePurchaseOrder": "138330", "CompactFinaliseFor": "137653", - "CompactFinaliseSelf": "128815", - "CompactFinaliseTo": "128815", - "EscrowFinalise": "76395", + "CompactFinaliseSelf": "128820", + "CompactFinaliseTo": "128820", + "EscrowFinalise": "76400", "IntegrationCoinFill": "89913", - "IntegrationCompactFinaliseSelf": "125396", + "IntegrationCompactFinaliseSelf": "125401", "IntegrationWormholeReceiveMessage": "73509", "IntegrationWormholeSubmit": "42285", "escrowFinaliseWithSignature": "85128", diff --git a/snapshots/outputSettler.json b/snapshots/outputSettler.json index 8ee60e56..ba58b7ed 100644 --- a/snapshots/outputSettler.json +++ b/snapshots/outputSettler.json @@ -1,9 +1,9 @@ { - "outputSettlerCoinFill": "83343", - "outputSettlerCoinFillDutchAuction": "84573", - "outputSettlerCoinFillExclusive": "84739", - "outputSettlerCoinFillExclusiveDutchAuction": "85453", - "outputSettlerCoinFillNative": "89358", - "outputSettlerCoinFillOrderOutputsNative": "128860", - "outputSettlerCoinfillOrderOutputs": "119906" + "outputSettlerSimpleFill": "83343", + "outputSettlerSimpleFillDutchAuction": "84573", + "outputSettlerSimpleFillExclusive": "84739", + "outputSettlerSimpleFillExclusiveDutchAuction": "85453", + "outputSettlerSimpleFillNative": "89358", + "outputSettlerSimpleFillOrderOutputsNative": "128860", + "outputSettlerSimplefillOrderOutputs": "119906" } \ No newline at end of file diff --git a/src/input/InputSettlerBase.sol b/src/input/InputSettlerBase.sol index 29a11a95..b4a51716 100644 --- a/src/input/InputSettlerBase.sol +++ b/src/input/InputSettlerBase.sol @@ -57,6 +57,10 @@ abstract contract InputSettlerBase is EIP712 { * @dev Fill deadline is after expiry deadline. */ error FillDeadlineAfterExpiry(uint32 fillDeadline, uint32 expires); + /** + * @dev msg.sender was expected to be `expectedCaller`. + */ + error UnexpectedCaller(bytes32 expectedCaller); /** * @notice Emitted when an order is finalised. @@ -138,6 +142,17 @@ abstract contract InputSettlerBase is EIP712 { if (isZero) revert NoDestination(); } + /** + * @notice Enforces a specific caller + * @dev Only reads the rightmost 20 bytes to allow providing additional destination context + * @param expectedCaller Expected caller. The leftmost 12 bytes are not read. + */ + function _validateIsCaller( + bytes32 expectedCaller + ) internal view { + if (LibAddress.fromIdentifier(expectedCaller) != msg.sender) revert UnexpectedCaller(expectedCaller); + } + // --- Timestamp Helpers --- // /** diff --git a/src/input/InputSettlerPurchase.sol b/src/input/InputSettlerPurchase.sol index a802e242..c962d492 100644 --- a/src/input/InputSettlerPurchase.sol +++ b/src/input/InputSettlerPurchase.sol @@ -99,19 +99,6 @@ abstract contract InputSettlerPurchase is InputSettlerBase { // --- Order Purchase Helpers --- // - /** - * @notice Enforces that the caller is the order owner. - * @dev Only reads the rightmost 20 bytes to verify the owner/purchaser. This allows implementations to use the - * leftmost 12 bytes to encode further withdrawal logic. - * For TheCompact, 12 zero bytes indicates a withdrawals instead of a transfer. - * @param orderOwner The order owner. The leftmost 12 bytes are not read. - */ - function _orderOwnerIsCaller( - bytes32 orderOwner - ) internal view { - if (orderOwner.fromIdentifier() != msg.sender) revert NotOrderOwner(); - } - /** * @notice Helper function to get the owner of order incase it may have been bought. In case an order has been * bought, and bought in time, the owner will be set to the purchaser. Otherwise it will be set to the solver. diff --git a/src/input/compact/InputSettlerCompact.sol b/src/input/compact/InputSettlerCompact.sol index fb7796c2..7558e0a1 100644 --- a/src/input/compact/InputSettlerCompact.sol +++ b/src/input/compact/InputSettlerCompact.sol @@ -147,7 +147,7 @@ contract InputSettlerCompact is InputSettlerPurchase, IInputSettlerCompact { bytes32 orderId = _orderIdentifier(order); bytes32 orderOwner = _purchaseGetOrderOwner(orderId, solveParams); - _orderOwnerIsCaller(orderOwner); + _validateIsCaller(orderOwner); _validateFills(order.fillDeadline, order.inputOracle, order.outputs, orderId, solveParams); diff --git a/src/input/compact/InputSettlerMultichainCompact.sol b/src/input/compact/InputSettlerMultichainCompact.sol new file mode 100644 index 00000000..e9b509d4 --- /dev/null +++ b/src/input/compact/InputSettlerMultichainCompact.sol @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; +import { EIP712 } from "openzeppelin/utils/cryptography/EIP712.sol"; + +import { TheCompact } from "the-compact/src/TheCompact.sol"; +import { EfficiencyLib } from "the-compact/src/lib/EfficiencyLib.sol"; +import { IdLib } from "the-compact/src/lib/IdLib.sol"; +import { BatchMultichainClaim, ExogenousBatchMultichainClaim } from "the-compact/src/types/BatchMultichainClaims.sol"; +import { BatchClaimComponent, Component } from "the-compact/src/types/Components.sol"; + +import { IInputCallback } from "../../interfaces/IInputCallback.sol"; +import { IInputOracle } from "../../interfaces/IInputOracle.sol"; + +import { BytesLib } from "../../libs/BytesLib.sol"; +import { LibAddress } from "../../libs/LibAddress.sol"; +import { MandateOutputEncodingLib } from "../../libs/MandateOutputEncodingLib.sol"; + +import { InputSettlerBase } from "../InputSettlerBase.sol"; +import { MandateOutput } from "../types/MandateOutputType.sol"; + +import { MultichainCompactOrderType, MultichainOrderComponent } from "../types/MultichainCompactOrderType.sol"; +import { OrderPurchase } from "../types/OrderPurchaseType.sol"; + +/** + * @title Multichain Input Settler using `The Compact` as an escrow. + * @notice This Input Settler implementation uses The Compact as the deposit scheme. It is a Output first scheme that + * allows users with a deposit inside The Compact to execute transactions that will be paid **after** the outputs have + * been proven. This has the advantage that failed orders can be quickly retried. These orders are also entirely gasless + * since neither valid nor failed transactions does not require any transactions to redeem. + * + * Users are expected to have an existing deposit inside the Compact or purposefully deposit for the intent. Then either + * register or sign a supported claim with the intent outputs as the witness. + * + * A multichain intent is an intent that collects tokens on multiple chains in exchange for single set of outputs. Using + * TheCompact allows users to issue a multichain input intent using a single Multichain Compact signature. + * + * The contract is intended to be entirely ownerless, permissionlessly deployable, and unstoppable. + */ +contract InputSettlerMultichainCompact is InputSettlerBase { + using LibAddress for bytes32; + using LibAddress for uint256; + error UserCannotBeSettler(); + + TheCompact public immutable COMPACT; + + constructor( + address compact + ) EIP712("OIFMultichainEscrow", "1") { + COMPACT = TheCompact(compact); + } + + // --- Generic order identifier --- // + + function _orderIdentifier( + MultichainOrderComponent calldata order + ) internal view returns (bytes32) { + return MultichainCompactOrderType.orderIdentifier(order); + } + + function orderIdentifier( + MultichainOrderComponent calldata order + ) external view returns (bytes32) { + return _orderIdentifier(order); + } + + // --- Finalise Orders --- // + + /** + * @notice Finalise an order, paying the inputs to the solver. + * @param order that has been filled. + * @param signatures For the signed intent. Is packed: abi.encode(sponsorSignature, allocatorData). + * @param solver Solver of the outputs. + * @param destination Destination of the inputs funds signed for by the user. + * @return orderId Returns a unique global order identifier. + */ + function _finalise( + MultichainOrderComponent calldata order, + bytes calldata signatures, + bytes32 solver, + bytes32 destination + ) internal virtual returns (bytes32 orderId) { + bytes calldata sponsorSignature = BytesLib.toBytes(signatures, 0x00); + bytes calldata allocatorData = BytesLib.toBytes(signatures, 0x20); + orderId = _resolveLock(order, sponsorSignature, allocatorData, destination); + emit Finalised(orderId, solver, destination); + } + + /** + * @notice Finalises an order when called directly by the solver + * @dev The caller must be the address corresponding to the first solver in the solvers array. + * @param order MultichainOrderComponent signed in conjunction with a Compact to form an order + * @param signatures A signature for the sponsor and the allocator. abi.encode(bytes(sponsorSignature), + * bytes(allocatorData)) + * @param solveParams List of solve parameters for when the outputs were filled + * @param destination Where to send the inputs. If the solver wants to send the inputs to themselves, they should + * pass their address to this parameter. + * @param call Optional callback data. If non-empty, will call orderFinalised on the destination + */ + function finalise( + MultichainOrderComponent calldata order, + bytes calldata signatures, + SolveParams[] calldata solveParams, + bytes32 destination, + bytes calldata call + ) external virtual { + _validateDestination(destination); + + _validateIsCaller(solveParams[0].solver); + + bytes32 orderId = _finalise(order, signatures, solveParams[0].solver, destination); + + _validateFills(order.fillDeadline, order.inputOracle, order.outputs, orderId, solveParams); + + if (call.length > 0) { + IInputCallback(EfficiencyLib.asSanitizedAddress(uint256(destination))).orderFinalised(order.inputs, call); + } + } + + /** + * @notice Finalises a cross-chain order on behalf of someone else using their signature + * @dev This function serves to finalise intents on the origin chain with proper authorization from the order owner. + * @param order MultichainOrderComponent signed in conjunction with a Compact to form an order + * @param signatures A signature for the sponsor and the allocator. abi.encode(bytes(sponsorSignature), + * bytes(allocatorData)) + * @param solveParams List of solve parameters for when the outputs were filled + * @param destination Where to send the inputs + * @param call Optional callback data. If non-empty, will call orderFinalised on the destination + * @param orderOwnerSignature Signature from the order owner authorizing this external call + */ + function finaliseWithSignature( + MultichainOrderComponent calldata order, + bytes calldata signatures, + SolveParams[] calldata solveParams, + bytes32 destination, + bytes calldata call, + bytes calldata orderOwnerSignature + ) external virtual { + if (destination == bytes32(0)) revert NoDestination(); + + bytes32 orderId = _finalise(order, signatures, solveParams[0].solver, destination); + + // Validate the external claimant with signature + _allowExternalClaimant(orderId, solveParams[0].solver.fromIdentifier(), destination, call, orderOwnerSignature); + + _validateFills(order.fillDeadline, order.inputOracle, order.outputs, orderId, solveParams); + + if (call.length > 0) IInputCallback(destination.fromIdentifier()).orderFinalised(order.inputs, call); + } + + //--- The Compact & Resource Locks ---// + + /** + * @notice Resolves a Compact Claim for a Standard Order. + * @param order that should be converted into a Compact Claim. + * @param sponsorSignature The user's signature for the Compact Claim. + * @param allocatorData The allocator's signature for the Compact Claim. + * @param claimant Destination of the inputs funds signed for by the user. + * @return claimHash The compact claimhash is used as the order identifier, as it is identical for a specific order + * cross-chain. + */ + function _resolveLock( + MultichainOrderComponent calldata order, + bytes calldata sponsorSignature, + bytes calldata allocatorData, + bytes32 claimant + ) internal virtual returns (bytes32 claimHash) { + BatchClaimComponent[] memory batchClaimComponents; + { + uint256 numInputs = order.inputs.length; + batchClaimComponents = new BatchClaimComponent[](numInputs); + uint256[2][] calldata maxInputs = order.inputs; + for (uint256 i; i < numInputs; ++i) { + uint256[2] calldata input = maxInputs[i]; + uint256 tokenId = input[0]; + uint256 allocatedAmount = input[1]; + + Component[] memory components = new Component[](1); + components[0] = Component({ claimant: uint256(claimant), amount: allocatedAmount }); + batchClaimComponents[i] = BatchClaimComponent({ + id: tokenId, // The token ID of the ERC6909 token to allocate. + allocatedAmount: allocatedAmount, // The original allocated amount of ERC6909 tokens. + portions: components + }); + } + } + + address user = order.user; + // The Compact skips signature checks for msg.sender. Ensure no accidental intents are issued. + if (user == address(this)) revert UserCannotBeSettler(); + if (order.chainIdField == block.chainid) { + claimHash = COMPACT.batchMultichainClaim( + BatchMultichainClaim({ + allocatorData: allocatorData, + sponsorSignature: sponsorSignature, + sponsor: user, + nonce: order.nonce, + expires: order.expires, + witness: MultichainCompactOrderType.witnessHash(order), + witnessTypestring: string(MultichainCompactOrderType.BATCH_COMPACT_SUB_TYPES), + claims: batchClaimComponents, + additionalChains: order.additionalChains + }) + ); + } else { + claimHash = COMPACT.exogenousBatchClaim( + ExogenousBatchMultichainClaim({ + allocatorData: allocatorData, + sponsorSignature: sponsorSignature, + sponsor: user, + nonce: order.nonce, + expires: order.expires, + witness: MultichainCompactOrderType.witnessHash(order), + witnessTypestring: string(MultichainCompactOrderType.BATCH_COMPACT_SUB_TYPES), + claims: batchClaimComponents, + additionalChains: order.additionalChains, + chainIndex: order.chainIndex - 1, // We use chainIndex as the offset to elements array where compact + // uses it as offset to the notarized. + notarizedChainId: order.chainIdField + }) + ); + } + } +} diff --git a/src/input/escrow/InputSettlerEscrow.sol b/src/input/escrow/InputSettlerEscrow.sol index f8ef419a..f19b0247 100644 --- a/src/input/escrow/InputSettlerEscrow.sol +++ b/src/input/escrow/InputSettlerEscrow.sol @@ -22,6 +22,7 @@ import { OrderPurchase } from "../types/OrderPurchaseType.sol"; import { StandardOrder, StandardOrderType } from "../types/StandardOrderType.sol"; import { InputSettlerPurchase } from "../InputSettlerPurchase.sol"; + import { Permit2WitnessType } from "./Permit2WitnessType.sol"; /** @@ -396,7 +397,7 @@ contract InputSettlerEscrow is InputSettlerPurchase, IInputSettlerEscrow { bytes32 orderId = order.orderIdentifier(); bytes32 orderOwner = _purchaseGetOrderOwner(orderId, solveParams); - _orderOwnerIsCaller(orderOwner); + _validateIsCaller(orderOwner); _validateFills(order.fillDeadline, order.inputOracle, order.outputs, orderId, solveParams); diff --git a/src/input/escrow/InputSettlerMultichainEscrow.sol b/src/input/escrow/InputSettlerMultichainEscrow.sol new file mode 100644 index 00000000..036ff7db --- /dev/null +++ b/src/input/escrow/InputSettlerMultichainEscrow.sol @@ -0,0 +1,341 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import { IERC20 } from "openzeppelin/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "openzeppelin/token/ERC20/utils/SafeERC20.sol"; + +import { EIP712 } from "openzeppelin/utils/cryptography/EIP712.sol"; +import { ISignatureTransfer } from "permit2/src/interfaces/ISignatureTransfer.sol"; + +import { IInputCallback } from "../../interfaces/IInputCallback.sol"; +import { IInputOracle } from "../../interfaces/IInputOracle.sol"; +import { IInputSettlerEscrow } from "../../interfaces/IInputSettlerEscrow.sol"; +import { BytesLib } from "../../libs/BytesLib.sol"; +import { IsContractLib } from "../../libs/IsContractLib.sol"; + +import { LibAddress } from "../../libs/LibAddress.sol"; +import { MandateOutputEncodingLib } from "../../libs/MandateOutputEncodingLib.sol"; +import { MandateOutput } from "../types/MandateOutputType.sol"; +import { MultichainOrderComponent, MultichainOrderComponentType } from "../types/MultichainOrderComponentType.sol"; + +import { InputSettlerBase } from "../InputSettlerBase.sol"; +import { Permit2MultichainWitnessType } from "./Permit2MultichainWitnessType.sol"; + +/** + * @title OIF Input Settler using an explicit multichain escrows. + * @notice This Input Settler implementation uses an explicit escrow as a deposit scheme. + * This contract manages collecting input assets (through `open` and `openFor`) and releasing assets to solvers. + * It can collect tokens using Permit2. + * + * A multichain intent is an intent that collects tokens on multiple chains in exchange for single set of outputs. Using + * Permit2, a user has to sign once for each chain they wanna add to their intent. Partially signed / funded intents + * should be treated as only having committed funds. + * + * If a multichain intent contains 3 chains but is only opened on 2, then the intent is still valid for those 2 chains. + * The reduction in solver payment is equal to the missing component. + * + * This contract does not support fee on transfer tokens. + */ +contract InputSettlerMultichainEscrow is InputSettlerBase { + using LibAddress for bytes32; + using LibAddress for uint256; + + /** + * @dev The order status is invalid. + */ + error InvalidOrderStatus(); + /** + * @dev Mismatch between the provided and computed order IDs. + */ + error OrderIdMismatch(bytes32 provided, bytes32 computed); + /** + * @dev Mismatch between the number of inputs and signatures. + */ + error SignatureAndInputsNotEqual(); + /** + * @dev Reentrancy detected. + */ + error ReentrancyDetected(); + /** + * Signature type not supported. + */ + error SignatureNotSupported(bytes1); + + event Open(bytes32 indexed orderId, bytes order); + event Refunded(bytes32 indexed orderId); + + bytes1 internal constant SIGNATURE_TYPE_PERMIT2 = 0x00; + bytes1 internal constant SIGNATURE_TYPE_SELF = 0xff; + + enum OrderStatus { + None, + Deposited, + Claimed, + Refunded + } + + mapping(bytes32 orderId => OrderStatus) public orderStatus; + + // Address of the Permit2 contract. + ISignatureTransfer constant PERMIT2 = ISignatureTransfer(0x000000000022D473030F116dDEE9F6B43aC78BA3); + + constructor() EIP712("OIFMultichainEscrow", "1") { } + + // --- Generic order identifier --- // + function _orderIdentifier( + MultichainOrderComponent calldata order + ) internal view returns (bytes32) { + return MultichainOrderComponentType.orderIdentifier(order); + } + + function orderIdentifier( + MultichainOrderComponent calldata order + ) external view returns (bytes32) { + return _orderIdentifier(order); + } + + function open( + MultichainOrderComponent calldata order + ) external { + // Validate the order structure. + _validateTimestampHasNotPassed(order.fillDeadline); + _validateTimestampHasNotPassed(order.expires); + _validateInputChain(order.chainIdField); + + bytes32 orderId = _orderIdentifier(order); + + if (orderStatus[orderId] != OrderStatus.None) revert InvalidOrderStatus(); + // Mark order as deposited. If we can't make the deposit, we will + // revert and it will unmark it. This acts as a local-reentry check. + orderStatus[orderId] = OrderStatus.Deposited; + + _open(order); + + emit Open(orderId, abi.encode(order)); + } + + function _open( + MultichainOrderComponent calldata order + ) internal { + // Collect input tokens. + uint256[2][] calldata inputs = order.inputs; + uint256 numInputs = inputs.length; + for (uint256 i = 0; i < numInputs; ++i) { + uint256[2] calldata input = inputs[i]; + + address token = input[0].fromIdentifier(); + uint256 amount = input[1]; + SafeERC20.safeTransferFrom(IERC20(token), msg.sender, address(this), amount); + } + } + + function openFor( + MultichainOrderComponent calldata order, + address sponsor, + bytes calldata signature + ) external { + // Validate the order structure. + _validateTimestampHasNotPassed(order.fillDeadline); + _validateTimestampHasNotPassed(order.expires); + + bytes32 orderId = _orderIdentifier(order); + + if (orderStatus[orderId] != OrderStatus.None) revert InvalidOrderStatus(); + // Mark order as deposited. If we can't make the deposit, we will + // revert and it will unmark it. This acts as a reentry check. + orderStatus[orderId] = OrderStatus.Deposited; + + // Check the first byte of the signature for signature type then collect inputs. + bytes1 signatureType = signature.length > 0 ? signature[0] : SIGNATURE_TYPE_SELF; + if (signatureType == SIGNATURE_TYPE_PERMIT2) { + _openForWithPermit2(order, orderId, sponsor, signature[1:], address(this)); + } else if (msg.sender == sponsor && signatureType == SIGNATURE_TYPE_SELF) { + _open(order); + } else { + revert SignatureNotSupported(signatureType); + } + + // Validate that there has been no reentrancy. + if (orderStatus[orderId] != OrderStatus.Deposited) revert ReentrancyDetected(); + + emit Open(orderId, abi.encode(order)); + } + + /** + * @notice Helper function for using permit2 to collect assets represented by a StandardOrder. + * @param order StandardOrder representing the intent. + * @param signer Provider of the permit2 funds and signer of the intent. + * @param signature permit2 signature with Permit2Witness representing `order` signed by `order.user`. + * @param to recipient of the inputs tokens. In most cases, should be address(this). + */ + function _openForWithPermit2( + MultichainOrderComponent calldata order, + bytes32 orderId, + address signer, + bytes calldata signature, + address to + ) internal { + ISignatureTransfer.TokenPermissions[] memory permitted; + ISignatureTransfer.SignatureTransferDetails[] memory transferDetails; + + { + uint256[2][] calldata orderInputs = order.inputs; + // Load the number of inputs. We need them to set the array size & convert each + // input struct into a transferDetails struct. + uint256 numInputs = orderInputs.length; + permitted = new ISignatureTransfer.TokenPermissions[](numInputs); + transferDetails = new ISignatureTransfer.SignatureTransferDetails[](numInputs); + // Iterate through each input. + for (uint256 i; i < numInputs; ++i) { + uint256[2] calldata orderInput = orderInputs[i]; + uint256 inputToken = orderInput[0]; + uint256 amount = orderInput[1]; + // Validate that the input token's 12 leftmost bytes are 0. See non-multichain escrow. + address token = inputToken.validatedCleanAddress(); + // Check if input tokens are contracts. + IsContractLib.validateContainsCode(token); + // Set the allowance. This is the explicit max allowed amount approved by the user. + permitted[i] = ISignatureTransfer.TokenPermissions({ token: token, amount: amount }); + // Set our requested transfer. This has to be less than or equal to the allowance + transferDetails[i] = ISignatureTransfer.SignatureTransferDetails({ to: to, requestedAmount: amount }); + } + } + ISignatureTransfer.PermitBatchTransferFrom memory permitBatch = ISignatureTransfer.PermitBatchTransferFrom({ + permitted: permitted, nonce: order.nonce, deadline: order.fillDeadline + }); + PERMIT2.permitWitnessTransferFrom( + permitBatch, + transferDetails, + signer, + Permit2MultichainWitnessType.MultichainPermit2WitnessHash(orderId, order), + Permit2MultichainWitnessType.PERMIT2_MULTICHAIN_PERMIT2_TYPESTRING, + signature + ); + } + + // --- Refund --- // + + /** + * @notice Refunds an order that has not been finalised before it expired. This order may have been filled but + * finalise has not been called yet. + * @param order StandardOrder description of the intent. + */ + function refund( + MultichainOrderComponent calldata order + ) external { + _validateInputChain(order.chainIdField); + _validateTimestampHasPassed(order.expires); + + bytes32 orderId = _orderIdentifier(order); + _resolveLock(orderId, order.inputs, order.user, OrderStatus.Refunded); + emit Refunded(orderId); + } + + // --- Finalise Orders --- // + + /** + * @notice Finalise an order, paying the inputs to the solver. + * @param order that has been filled. + * @param orderId A unique identifier for the order. + * @param solver Solver of the outputs. + * @param destination Destination of the inputs funds signed for by the user. + */ + function _finalise( + MultichainOrderComponent calldata order, + bytes32 orderId, + bytes32 solver, + bytes32 destination + ) internal virtual { + _resolveLock(orderId, order.inputs, destination.fromIdentifier(), OrderStatus.Claimed); + emit Finalised(orderId, solver, destination); + } + + /** + * @notice Finalises an order when called directly by the solver + * @dev The caller must be the address corresponding to the first solver in the solvers array. + * @param order StandardOrder signed in conjunction with a Compact to form an order + * @param solveParams List of solve parameters for when the outputs were filled + * @param destination Where to send the inputs. If the solver wants to send the inputs to themselves, they should + * pass their address to this parameter. + * @param call Optional callback data. If non-empty, will call orderFinalised on the destination + */ + function finalise( + MultichainOrderComponent calldata order, + SolveParams[] calldata solveParams, + bytes32 destination, + bytes calldata call + ) external virtual { + _validateDestination(destination); + _validateInputChain(order.chainIdField); + + bytes32 orderId = _orderIdentifier(order); + _validateIsCaller(solveParams[0].solver); + + _validateFills(order.fillDeadline, order.inputOracle, order.outputs, orderId, solveParams); + + _finalise(order, orderId, solveParams[0].solver, destination); + + if (call.length > 0) IInputCallback(destination.fromIdentifier()).orderFinalised(order.inputs, call); + } + + /** + * @notice Finalises a cross-chain order on behalf of someone else using their signature + * @dev This function serves to finalise intents on the origin chain with proper authorization from the order owner. + * @param order StandardOrder signed in conjunction with a Compact to form an order + * @param solveParams List of solve parameters for when the outputs were filled + * @param destination Where to send the inputs + * @param call Optional callback data. If non-empty, will call orderFinalised on the destination + * @param orderOwnerSignature Signature from the order owner authorizing this external call + */ + function finaliseWithSignature( + MultichainOrderComponent calldata order, + SolveParams[] calldata solveParams, + bytes32 destination, + bytes calldata call, + bytes calldata orderOwnerSignature + ) external virtual { + _validateDestination(destination); + _validateInputChain(order.chainIdField); + + bytes32 orderId = _orderIdentifier(order); + + // Validate the external claimant with signature + _allowExternalClaimant(orderId, solveParams[0].solver.fromIdentifier(), destination, call, orderOwnerSignature); + + _validateFills(order.fillDeadline, order.inputOracle, order.outputs, orderId, solveParams); + + _finalise(order, orderId, solveParams[0].solver, destination); + + if (call.length > 0) IInputCallback(destination.fromIdentifier()).orderFinalised(order.inputs, call); + } + + //--- The Compact & Resource Locks ---// + + /** + * @dev This function employs a local reentry guard: we check the order status and then we update it afterwards. + * This is an important check as it is indeed to process external ERC20 transfers. + * @param newStatus specifies the new status to set the order to. Should never be OrderStatus.Deposited. + */ + function _resolveLock( + bytes32 orderId, + uint256[2][] calldata inputs, + address destination, + OrderStatus newStatus + ) internal virtual { + // Check the order status: + if (orderStatus[orderId] != OrderStatus.Deposited) revert InvalidOrderStatus(); + // Mark order as deposited. If we can't make the deposit, we will + // revert and it will unmark it. This acts as a reentry check. + orderStatus[orderId] = newStatus; + + // We have now ensured that this point can only be reached once. We can now process the asset delivery. + uint256 numInputs = inputs.length; + for (uint256 i; i < numInputs; ++i) { + uint256[2] calldata input = inputs[i]; + address token = input[0].fromIdentifier(); + uint256 amount = input[1]; + + SafeERC20.safeTransfer(IERC20(token), destination, amount); + } + } +} diff --git a/src/input/escrow/Permit2MultichainWitnessType.sol b/src/input/escrow/Permit2MultichainWitnessType.sol new file mode 100644 index 00000000..365fbfd1 --- /dev/null +++ b/src/input/escrow/Permit2MultichainWitnessType.sol @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import { MandateOutput, MandateOutputType } from "../types/MandateOutputType.sol"; +import { MultichainOrderComponent } from "../types/MultichainOrderComponentType.sol"; + +/** + * @notice The signed witness / mandate used for the permit2 transaction. + * @dev The filldeadline is part of the Permit2 struct as the openDeadline. + */ +struct MultichainPermit2Witness { + bytes32 orderId; + uint32 expires; + address inputOracle; + MandateOutput[] outputs; +} + +/** + * @notice Helper library for the Permit2 Witness type for StandardOrder. + * TYPE_PARTIAL: An incomplete type. Is missing a field. + * TYPE_STUB: Type has no sub-types. + * TYPE: Is complete including sub-types. + */ +library Permit2MultichainWitnessType { + bytes constant MULTICHAIN_PERMIT2_WITNESS_TYPE_STUB = abi.encodePacked( + "MultichainPermit2Witness(bytes32 orderId,uint32 expires,address inputOracle,uint256[2][] inputs,MandateOutput[] outputs)" + ); + + // M comes earlier than P. + bytes constant MULTICHAIN_PERMIT2_WITNESS_TYPE = abi.encodePacked( + "MultichainPermit2Witness(bytes32 orderId,uint32 expires,address inputOracle,MandateOutput[] outputs)MandateOutput(bytes32 oracle,bytes32 settler,uint256 chainId,bytes32 token,uint256 amount,bytes32 recipient,bytes callbackData,bytes context)" + ); + + bytes32 constant MULTICHAIN_PERMIT2_WITNESS_TYPE_HASH = keccak256(MULTICHAIN_PERMIT2_WITNESS_TYPE); + + /// @notice Typestring for handed to Permit2. + string constant PERMIT2_MULTICHAIN_PERMIT2_TYPESTRING = + "MultichainPermit2Witness witness)MandateOutput(bytes32 oracle,bytes32 settler,uint256 chainId,bytes32 token,uint256 amount,bytes32 recipient,bytes callbackData,bytes context)MultichainPermit2Witness(bytes32 orderId,uint32 expires,address inputOracle,MandateOutput[] outputs)TokenPermissions(address token,uint256 amount)"; + + /** + * @notice Computes the permit2 witness hash. + */ + function MultichainPermit2WitnessHash( + bytes32 orderId, + MultichainOrderComponent calldata order + ) internal pure returns (bytes32) { + return keccak256( + abi.encode( + MULTICHAIN_PERMIT2_WITNESS_TYPE_HASH, + orderId, + order.expires, + order.inputOracle, + MandateOutputType.hashOutputs(order.outputs) + ) + ); + } +} diff --git a/src/input/types/MandateOutputType.sol b/src/input/types/MandateOutputType.sol index fd16a790..bf17394c 100644 --- a/src/input/types/MandateOutputType.sol +++ b/src/input/types/MandateOutputType.sol @@ -64,12 +64,13 @@ library MandateOutputType { MandateOutput[] calldata outputs ) internal pure returns (bytes32) { unchecked { - bytes memory currentHash = new bytes(32 * outputs.length); + uint256 numOutputs = outputs.length; + bytes memory currentHash = new bytes(32 * numOutputs); uint256 p; assembly ("memory-safe") { p := add(currentHash, 0x20) } - for (uint256 i = 0; i < outputs.length; ++i) { + for (uint256 i = 0; i < numOutputs; ++i) { bytes32 outputHash = hashOutput(outputs[i]); assembly ("memory-safe") { mstore(add(p, mul(i, 0x20)), outputHash) @@ -78,4 +79,43 @@ library MandateOutputType { return keccak256(currentHash); } } + + // Memory copy of the above: + function hashOutputM( + MandateOutput memory output + ) internal pure returns (bytes32) { + return keccak256( + abi.encode( + MANDATE_OUTPUT_TYPE_HASH, + output.oracle, + output.settler, + output.chainId, + output.token, + output.amount, + output.recipient, + keccak256(output.callbackData), + keccak256(output.context) + ) + ); + } + + function hashOutputsM( + MandateOutput[] memory outputs + ) internal pure returns (bytes32) { + unchecked { + uint256 numOutputs = outputs.length; + bytes memory currentHash = new bytes(32 * numOutputs); + uint256 p; + assembly ("memory-safe") { + p := add(currentHash, 0x20) + } + for (uint256 i = 0; i < numOutputs; ++i) { + bytes32 outputHash = hashOutputM(outputs[i]); + assembly ("memory-safe") { + mstore(add(p, mul(i, 0x20)), outputHash) + } + } + return keccak256(currentHash); + } + } } diff --git a/src/input/types/MultichainCompactOrderType.sol b/src/input/types/MultichainCompactOrderType.sol new file mode 100644 index 00000000..ac229cac --- /dev/null +++ b/src/input/types/MultichainCompactOrderType.sol @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import { MandateOutput, MandateOutputType } from "./MandateOutputType.sol"; +import { MultichainOrderComponent } from "./MultichainOrderComponentType.sol"; + +import { LibAddress } from "../../libs/LibAddress.sol"; +import { StandardOrderType } from "./StandardOrderType.sol"; + +struct Mandate { + uint32 fillDeadline; + address inputOracle; + MandateOutput[] outputs; +} + +import { console } from "forge-std/console.sol"; + +/** + * @notice Helper library for the multichain order type. + * TYPE_PARTIAL: An incomplete type. Is missing a field. + * TYPE_STUB: Type has no subtypes. + * TYPE: Is complete including sub-types. + */ +library MultichainCompactOrderType { + using LibAddress for uint256; + + bytes32 constant MULTICHAIN_COMPACT_TYPEHASH_WITH_WITNESS = keccak256( + bytes( + "MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Element[] elements)Element(address arbiter,uint256 chainId,Lock[] commitments,Mandate mandate)Lock(bytes12 lockTag,address token,uint256 amount)Mandate(uint32 fillDeadline,address inputOracle,MandateOutput[] outputs)MandateOutput(bytes32 oracle,bytes32 settler,uint256 chainId,bytes32 token,uint256 amount,bytes32 recipient,bytes callbackData,bytes context)" + ) + ); + + bytes32 constant ELEMENTS_COMPACT_TYPEHASH_WITH_WITNESS = keccak256( + bytes( + "Element(address arbiter,uint256 chainId,Lock[] commitments,Mandate mandate)Lock(bytes12 lockTag,address token,uint256 amount)Mandate(uint32 fillDeadline,address inputOracle,MandateOutput[] outputs)MandateOutput(bytes32 oracle,bytes32 settler,uint256 chainId,bytes32 token,uint256 amount,bytes32 recipient,bytes callbackData,bytes context)" + ) + ); + + bytes32 constant LOCK_COMPACT_TYPEHASH = keccak256(bytes("Lock(bytes12 lockTag,address token,uint256 amount)")); + + bytes constant BATCH_COMPACT_SUB_TYPES = StandardOrderType.BATCH_COMPACT_SUB_TYPES; + + function inputsToLocksHash( + uint256[2][] calldata inputs + ) internal pure returns (bytes32) { + uint256 numInputs = inputs.length; + bytes memory lockHashes = new bytes(32 * numInputs); + uint256 p; + assembly ("memory-safe") { + p := add(lockHashes, 0x20) + } + for (uint256 i; i < numInputs; ++i) { + uint256[2] calldata input = inputs[i]; + bytes32 lockHash = keccak256( + abi.encode(LOCK_COMPACT_TYPEHASH, bytes12(bytes32(input[0])), input[0].fromIdentifier(), input[1]) + ); + assembly ("memory-safe") { + mstore(add(p, mul(i, 0x20)), lockHash) + } + } + return keccak256(lockHashes); + } + + function insertAndHash( + bytes32 elem, + uint256 index, + bytes32[] calldata arr + ) internal pure returns (bytes32) { + uint256 numElements = arr.length + 1; + bytes memory newArr = new bytes(32 * numElements); + uint256 p; + assembly ("memory-safe") { + p := add(newArr, 0x20) + } + for (uint256 i; i < numElements; ++i) { + if (index == i) { + assembly ("memory-safe") { + mstore(add(p, mul(i, 0x20)), elem) + } + continue; + } + // If we have already inserted elem, then i is ahead by 1 + uint256 selectFromIndexAt = index < i ? i - 1 : i; // TODO: sub(i, gt(i, index)) + bytes32 elementToInsert = arr[selectFromIndexAt]; + assembly ("memory-safe") { + mstore(add(p, mul(i, 0x20)), elementToInsert) + } + } + return keccak256(newArr); + } + + /** + * @notice Computes the Compact claim hash, as it is the one used as the order id. + */ + function orderIdentifier( + MultichainOrderComponent calldata order + ) internal view returns (bytes32) { + // Compute the element hash of this chain. + bytes32 elementHash = keccak256( + abi.encode( + ELEMENTS_COMPACT_TYPEHASH_WITH_WITNESS, + address(this), + block.chainid, + inputsToLocksHash(order.inputs), + witnessHash(order) + ) + ); + // Insert the element hash into the array of the other provided element. + bytes32 hashOfElements = insertAndHash(elementHash, order.chainIndex, order.additionalChains); + + return keccak256( + abi.encode(MULTICHAIN_COMPACT_TYPEHASH_WITH_WITNESS, order.user, order.nonce, order.expires, hashOfElements) + ); + } + + function witnessHash( + MultichainOrderComponent calldata order + ) internal pure returns (bytes32) { + return keccak256( + abi.encode( + // Same witness as StandardOrder + StandardOrderType.CATALYST_WITNESS_TYPE_HASH, + order.fillDeadline, + order.inputOracle, + MandateOutputType.hashOutputs(order.outputs) + ) + ); + } +} diff --git a/src/input/types/MultichainOrderComponentType.sol b/src/input/types/MultichainOrderComponentType.sol new file mode 100644 index 00000000..f6f47d5c --- /dev/null +++ b/src/input/types/MultichainOrderComponentType.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +import { MandateOutput, MandateOutputType } from "./MandateOutputType.sol"; + +struct MultichainOrderComponent { + address user; + uint256 nonce; + uint256 chainIdField; + uint256 chainIndex; + uint32 expires; + uint32 fillDeadline; + address inputOracle; + uint256[2][] inputs; + MandateOutput[] outputs; + bytes32[] additionalChains; +} + +/** + * @notice Helper library for the multichain order type. + * TYPE_PARTIAL: An incomplete type. Is missing a field. + * TYPE_STUB: Type has no subtypes. + * TYPE: Is complete including sub-types. + */ +library MultichainOrderComponentType { + error ChainIndexOutOfRange(uint256 chainIndex, uint256 numSegments); + + /** + * @dev If this function is used in a context where correctness of the identifier is important, the chainIdField + * needs to be validated against block.chainid + */ + function orderIdentifier( + MultichainOrderComponent calldata order + ) internal view returns (bytes32) { + return keccak256( + abi.encodePacked( + address(this), + order.user, + order.nonce, + order.expires, + order.fillDeadline, + order.inputOracle, + constructInputHash(order.chainIdField, order.chainIndex, order.inputs, order.additionalChains), + abi.encode(order.outputs) + ) + ); + } + + /** + * @notice Generates a shared identical input hash for a list of list of inputs. + * Assume that you have a list inputs: + * - a: [a, [1, 1], [1,2]] => ha = keccak256(abi.encodePacked("a", a)) + * - b: [b, [2, 1], [2,2]] => hb = keccak256(abi.encodePacked("b", b)) + * - c: [c, [3, 1], [3,2]] => hc = keccak256(abi.encodePacked("c", c)) + * And wants to compute the hash: h = keccak256(abi.encodePacked(a, b, c))) + * Given 1, b, and [ha, hc] the function will compute h. + */ + function constructInputHash( + uint256 inputsChainId, + uint256 chainIndex, + uint256[2][] calldata inputs, + bytes32[] calldata additionalChains + ) internal pure returns (bytes32) { + bytes32 inputHash = hashInputs(inputsChainId, inputs); + uint256 numSegments = additionalChains.length + 1; + if (numSegments <= chainIndex) revert ChainIndexOutOfRange(chainIndex, numSegments); + bytes memory claimStructure = new bytes(32 * numSegments); + uint256 p; + assembly ("memory-safe") { + p := add(claimStructure, 0x20) + } + for (uint256 i = 0; i < numSegments; ++i) { + uint256 additionalChainsIndex; + assembly ("memory-safe") { + // If we have already inserted "inputs" we need to remove 1 from the index. + additionalChainsIndex := sub(i, gt(i, chainIndex)) + } + bytes32 inputHashElement = chainIndex == i ? inputHash : additionalChains[additionalChainsIndex]; + assembly ("memory-safe") { + mstore(add(p, mul(i, 0x20)), inputHashElement) + } + } + // Length is implied by size. + return keccak256(claimStructure); + } + + /** + * @notice Internal pure function for deriving the hash of ids and amounts with a provided chainId salt. + * This function returns keccak256(abi.encodePacked(chainId, idsAndAmounts)) + * @param chainId Chain identifier used to salt the input hash. + * @param idsAndAmounts An array of ids and amounts. + * @return inputHash The hash of the ids and amounts salted with chainId. + * @dev This function expects that the calldata of idsAndAmounts will have bounds + * checked elsewhere; using it without this check occurring elsewhere can result in + * erroneous hash values. + */ + function hashInputs( + uint256 chainId, + uint256[2][] calldata idsAndAmounts + ) internal pure returns (bytes32 inputHash) { + assembly ("memory-safe") { + // Retrieve the free memory pointer; memory will be left dirtied. + let ptr := mload(0x40) + + // Get the total length of the calldata slice. + // Each element of the array consists of 2 words. + let len := mul(idsAndAmounts.length, 0x40) + + // Store the chainId at the pointer. + mstore(ptr, chainId) + + // Copy calldata into memory after the chainId. + calldatacopy(add(0x20, ptr), idsAndAmounts.offset, len) + + // Compute the hash of the calldata that has been copied into memory. + inputHash := keccak256(ptr, add(len, 0x20)) + } + } +} diff --git a/test/exploit/FillOutputsTwice.t.sol b/test/exploit/FillOutputsTwice.t.sol index 35aae6c1..aeb75e6b 100644 --- a/test/exploit/FillOutputsTwice.t.sol +++ b/test/exploit/FillOutputsTwice.t.sol @@ -16,13 +16,13 @@ import { MockERC20 } from "../mocks/MockERC20.sol"; contract FillOutputsTwiceTest is Test { using LibAddress for address; - OutputSettlerSimple outputSettlerCoin; + OutputSettlerSimple outputSettlerSimple; MockERC20 outputToken; MockCallbackExecutor mockCallbackExecutor; function setUp() public { - outputSettlerCoin = new OutputSettlerSimple(); + outputSettlerSimple = new OutputSettlerSimple(); outputToken = new MockERC20("TEST", "TEST", 18); } @@ -34,12 +34,12 @@ contract FillOutputsTwiceTest is Test { address sender = makeAddr("sender"); outputToken.mint(sender, amount * 2); vm.prank(sender); - outputToken.approve(address(outputSettlerCoin), amount * 2); + outputToken.approve(address(outputSettlerSimple), amount * 2); // This is the assumed "valid" output. MandateOutput memory output = MandateOutput({ oracle: bytes32(uint256(uint160(address(this)))), - settler: bytes32(uint256(uint160(address(outputSettlerCoin)))), + settler: bytes32(uint256(uint160(address(outputSettlerSimple)))), chainId: block.chainid, token: bytes32(abi.encode(address(outputToken))), amount: amount, @@ -51,7 +51,7 @@ contract FillOutputsTwiceTest is Test { { vm.prank(sender); - bytes32 fillRecord = outputSettlerCoin.fill(orderId, output, type(uint48).max, fillerData); + bytes32 fillRecord = outputSettlerSimple.fill(orderId, output, type(uint48).max, fillerData); // Fill should succeed and return a valid fill record hash assertNotEq(fillRecord, bytes32(0)); // Verify it matches the expected hash format @@ -72,7 +72,7 @@ contract FillOutputsTwiceTest is Test { ); bytes[] memory payloads = new bytes[](1); payloads[0] = fillDescription; - bool outputValid = outputSettlerCoin.hasAttested(payloads); + bool outputValid = outputSettlerSimple.hasAttested(payloads); assertEq(outputValid, true); // Fill the duplicate order with the intention to overwrite the solver. @@ -80,7 +80,7 @@ contract FillOutputsTwiceTest is Test { MandateOutput memory duplicateOutput = MandateOutput({ oracle: bytes32(uint256(uint160(address(this)) + 1)), - settler: bytes32(uint256(uint160(address(outputSettlerCoin)))), + settler: bytes32(uint256(uint160(address(outputSettlerSimple)))), chainId: block.chainid, token: bytes32(abi.encode(address(outputToken))), amount: amount, @@ -93,7 +93,7 @@ contract FillOutputsTwiceTest is Test { { vm.prank(sender); bytes32 duplicateFillRecord = - outputSettlerCoin.fill(orderId, duplicateOutput, type(uint48).max, duplicateFillerData); + outputSettlerSimple.fill(orderId, duplicateOutput, type(uint48).max, duplicateFillerData); // This is a different output (different oracle), so fill should succeed and return a different hash assertNotEq(duplicateFillRecord, bytes32(0)); bytes32 expectedDuplicateFillRecord = keccak256(abi.encodePacked(duplicateSolver, uint32(block.timestamp))); @@ -114,7 +114,7 @@ contract FillOutputsTwiceTest is Test { bytes[] memory duplicatePayloads = new bytes[](1); duplicatePayloads[0] = duplicateFillDescription; - outputValid = outputSettlerCoin.hasAttested(duplicatePayloads); + outputValid = outputSettlerSimple.hasAttested(duplicatePayloads); assertEq(outputValid, false); // Ensure that this is not claimed as valid. } diff --git a/test/input/compact/InputSettlerCompact.base.t.sol b/test/input/compact/InputSettlerCompact.base.t.sol index 4bc3748f..7ac7f377 100644 --- a/test/input/compact/InputSettlerCompact.base.t.sol +++ b/test/input/compact/InputSettlerCompact.base.t.sol @@ -55,7 +55,7 @@ contract ExportedMessages is Messages, Setters { contract InputSettlerCompactTestBase is Test { address inputSettlerCompact; - OutputSettlerSimple outputSettlerCoin; + OutputSettlerSimple outputSettlerSimple; // Oracles address alwaysYesOracle; @@ -96,7 +96,7 @@ contract InputSettlerCompactTestBase is Test { DOMAIN_SEPARATOR = theCompact.DOMAIN_SEPARATOR(); inputSettlerCompact = address(new InputSettlerCompact(address(theCompact))); - outputSettlerCoin = new OutputSettlerSimple(); + outputSettlerSimple = new OutputSettlerSimple(); alwaysYesOracle = address(new AlwaysYesOracle()); token = new MockERC20("Mock ERC20", "MOCK", 18); diff --git a/test/input/compact/InputSettlerCompact.t.sol b/test/input/compact/InputSettlerCompact.t.sol index 0fb9e199..c5aefbad 100644 --- a/test/input/compact/InputSettlerCompact.t.sol +++ b/test/input/compact/InputSettlerCompact.t.sol @@ -52,7 +52,7 @@ contract InputSettlerCompactTest is InputSettlerCompactTestBase { inputs[0] = [tokenId, amount]; MandateOutput[] memory outputs = new MandateOutput[](1); outputs[0] = MandateOutput({ - settler: address(outputSettlerCoin).toIdentifier(), + settler: address(outputSettlerSimple).toIdentifier(), oracle: address(alwaysYesOracle).toIdentifier(), chainId: block.chainid, token: address(anotherToken).toIdentifier(), @@ -86,10 +86,10 @@ contract InputSettlerCompactTest is InputSettlerCompactTestBase { // Other callers are disallowed: vm.prank(non_solver); - vm.expectRevert(abi.encodeWithSignature("NotOrderOwner()")); InputSettlerBase.SolveParams[] memory solveParams = new InputSettlerBase.SolveParams[](1); solveParams[0] = InputSettlerBase.SolveParams({ solver: solverIdentifier, timestamp: uint32(block.timestamp) }); + vm.expectRevert(abi.encodeWithSignature("UnexpectedCaller(bytes32)", solverIdentifier)); IInputSettlerCompact(inputSettlerCompact).finalise(order, signature, solveParams, solverIdentifier, hex""); assertEq(token.balanceOf(solver), 0); @@ -142,7 +142,7 @@ contract InputSettlerCompactTest is InputSettlerCompactTestBase { inputs[0] = [tokenId, amount]; MandateOutput[] memory outputs = new MandateOutput[](1); outputs[0] = MandateOutput({ - settler: address(outputSettlerCoin).toIdentifier(), + settler: address(outputSettlerSimple).toIdentifier(), oracle: inputOracle.toIdentifier(), chainId: block.chainid, token: address(anotherToken).toIdentifier(), @@ -211,7 +211,7 @@ contract InputSettlerCompactTest is InputSettlerCompactTestBase { inputs[0] = [tokenId, amount]; MandateOutput[] memory outputs = new MandateOutput[](1); outputs[0] = MandateOutput({ - settler: address(outputSettlerCoin).toIdentifier(), + settler: address(outputSettlerSimple).toIdentifier(), oracle: address(alwaysYesOracle).toIdentifier(), chainId: block.chainid, token: address(anotherToken).toIdentifier(), @@ -245,11 +245,11 @@ contract InputSettlerCompactTest is InputSettlerCompactTestBase { vm.prank(non_solver); - vm.expectRevert(abi.encodeWithSignature("NotOrderOwner()")); bytes32 solverIdentifier = solver.toIdentifier(); bytes32 destinationIdentifier = destination.toIdentifier(); InputSettlerBase.SolveParams[] memory solveParams = new InputSettlerBase.SolveParams[](1); solveParams[0] = InputSettlerBase.SolveParams({ solver: solverIdentifier, timestamp: uint32(block.timestamp) }); + vm.expectRevert(abi.encodeWithSignature("UnexpectedCaller(bytes32)", solverIdentifier)); IInputSettlerCompact(inputSettlerCompact).finalise(order, signature, solveParams, destinationIdentifier, hex""); assertEq(token.balanceOf(destination), 0); @@ -291,7 +291,7 @@ contract InputSettlerCompactTest is InputSettlerCompactTestBase { inputs[0] = [tokenId, amount]; MandateOutput[] memory outputs = new MandateOutput[](1); outputs[0] = MandateOutput({ - settler: address(outputSettlerCoin).toIdentifier(), + settler: address(outputSettlerSimple).toIdentifier(), oracle: address(alwaysYesOracle).toIdentifier(), chainId: block.chainid, token: address(anotherToken).toIdentifier(), diff --git a/test/input/compact/InputSettlerMultichainCompact.base.t.sol b/test/input/compact/InputSettlerMultichainCompact.base.t.sol new file mode 100644 index 00000000..75854c41 --- /dev/null +++ b/test/input/compact/InputSettlerMultichainCompact.base.t.sol @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import { Test, console } from "forge-std/Test.sol"; + +import { TheCompact } from "the-compact/src/TheCompact.sol"; +import { SimpleAllocator } from "the-compact/src/examples/allocator/SimpleAllocator.sol"; +import { EfficiencyLib } from "the-compact/src/lib/EfficiencyLib.sol"; +import { IdLib } from "the-compact/src/lib/IdLib.sol"; +import { AlwaysOKAllocator } from "the-compact/src/test/AlwaysOKAllocator.sol"; +import { ResetPeriod } from "the-compact/src/types/ResetPeriod.sol"; +import { Scope } from "the-compact/src/types/Scope.sol"; + +import { InputSettlerMultichainCompact } from "../../../src/input/compact/InputSettlerMultichainCompact.sol"; +import { AllowOpenType } from "../../../src/input/types/AllowOpenType.sol"; +import { MandateOutput } from "../../../src/input/types/MandateOutputType.sol"; +import { OrderPurchase, OrderPurchaseType } from "../../../src/input/types/OrderPurchaseType.sol"; +import { StandardOrder } from "../../../src/input/types/StandardOrderType.sol"; +import { WormholeOracle } from "../../../src/integrations/oracles/wormhole/WormholeOracle.sol"; +import { Messages } from "../../../src/integrations/oracles/wormhole/external/wormhole/Messages.sol"; +import { Setters } from "../../../src/integrations/oracles/wormhole/external/wormhole/Setters.sol"; +import { Structs } from "../../../src/integrations/oracles/wormhole/external/wormhole/Structs.sol"; +import { MessageEncodingLib } from "../../../src/libs/MessageEncodingLib.sol"; +import { OutputSettlerSimple } from "../../../src/output/simple/OutputSettlerSimple.sol"; + +import { AlwaysYesOracle } from "../../mocks/AlwaysYesOracle.sol"; +import { MockERC20 } from "../../mocks/MockERC20.sol"; + +interface EIP712 { + function DOMAIN_SEPARATOR() external view returns (bytes32); +} + +event PackagePublished(uint32 nonce, bytes payload, uint8 consistencyLevel); + +contract ExportedMessages is Messages, Setters { + function storeGuardianSetPub( + Structs.GuardianSet memory set, + uint32 index + ) public { + return super.storeGuardianSet(set, index); + } + + function publishMessage( + uint32 nonce, + bytes calldata payload, + uint8 consistencyLevel + ) external payable returns (uint64) { + emit PackagePublished(nonce, payload, consistencyLevel); + return 0; + } +} + +contract InputSettlerMultichainCompactTestBase is Test { + address inputSettlerMultichainCompact; + OutputSettlerSimple outputSettlerSimple; + + // Oracles + address alwaysYesOracle; + ExportedMessages messages; + WormholeOracle wormholeOracle; + + uint256 swapperPrivateKey; + address swapper; + uint256 solverPrivateKey; + address solver; + uint256 testGuardianPrivateKey; + address testGuardian; + + uint256 allocatorPrivateKey; + address allocator; + bytes12 signAllocatorLockTag; + + MockERC20 token; + MockERC20 anotherToken; + + TheCompact public theCompact; + address alwaysOKAllocator; + bytes12 alwaysOkAllocatorLockTag; + + function setUp() public virtual { + theCompact = new TheCompact(); + + alwaysOKAllocator = address(new AlwaysOKAllocator()); + uint96 alwaysOkAllocatorId = theCompact.__registerAllocator(alwaysOKAllocator, ""); + // use scope 0 and reset period 0. This is okay as long as we don't use anything time based. + alwaysOkAllocatorLockTag = bytes12(alwaysOkAllocatorId); + (allocator, allocatorPrivateKey) = makeAddrAndKey("allocator"); + SimpleAllocator simpleAllocator = new SimpleAllocator(allocator, address(theCompact)); + uint96 signAllocatorId = theCompact.__registerAllocator(address(simpleAllocator), ""); + signAllocatorLockTag = bytes12(signAllocatorId); + + inputSettlerMultichainCompact = address(new InputSettlerMultichainCompact(address(theCompact))); + outputSettlerSimple = new OutputSettlerSimple(); + alwaysYesOracle = address(new AlwaysYesOracle()); + + token = new MockERC20("Mock ERC20", "MOCK", 18); + anotherToken = new MockERC20("Mock2 ERC20", "MOCK2", 18); + + (swapper, swapperPrivateKey) = makeAddrAndKey("swapper"); + (solver, solverPrivateKey) = makeAddrAndKey("solver"); + + // Oracles + messages = new ExportedMessages(); + address wormholeDeployment = makeAddr("wormholeOracle"); + deployCodeTo("WormholeOracle.sol", abi.encode(address(this), address(messages)), wormholeDeployment); + wormholeOracle = WormholeOracle(wormholeDeployment); + wormholeOracle.setChainMap(1, 1); + wormholeOracle.setChainMap(3, 3); + (testGuardian, testGuardianPrivateKey) = makeAddrAndKey("testGuardian"); + // initialize guardian set with one guardian + address[] memory keys = new address[](1); + keys[0] = testGuardian; + Structs.GuardianSet memory guardianSet = Structs.GuardianSet(keys, 0); + require(messages.quorum(guardianSet.keys.length) == 1, "Quorum should be 1"); + + messages.storeGuardianSetPub(guardianSet, uint32(0)); + } + + struct Lock { + bytes12 lockTag; + address token; + uint256 amount; + } + + function lockToUint( + Lock memory lock + ) internal pure returns (uint256[2] memory arr) { + bytes12 lockTag = lock.lockTag; + address lockToken = lock.token; + uint256 elem0; + assembly { + elem0 := add(lockTag, lockToken) + } + arr[0] = elem0; + arr[1] = lock.amount; + } + + function locksToUints( + Lock[] memory locks + ) internal pure returns (uint256[2][] memory arr) { + arr = new uint256[2][](locks.length); + for (uint256 i; i < locks.length; ++i) { + arr[i] = lockToUint(locks[i]); + } + } + + function uintToLock( + uint256[2] memory idAndAmount + ) internal pure returns (Lock memory lock) { + lock = Lock(bytes12(bytes32(idAndAmount[0])), address(uint160(idAndAmount[0])), idAndAmount[1]); + } + + function uintsToLocks( + uint256[2][] memory idsAndAmounts + ) internal pure returns (Lock[] memory locks) { + locks = new Lock[](idsAndAmounts.length); + for (uint256 i; i < idsAndAmounts.length; ++i) { + locks[i] = uintToLock(idsAndAmounts[i]); + } + } + + struct Element { + address arbiter; + uint256 chainId; + Lock[] commitments; + } + + function getLocksHash( + Lock[] memory locks + ) internal pure returns (bytes32) { + uint256 numLocks = locks.length; + bytes32[] memory lockHashes = new bytes32[](numLocks); + for (uint256 i; i < numLocks; ++i) { + Lock memory lock = locks[i]; + lockHashes[i] = keccak256( + abi.encode( + keccak256(bytes("Lock(bytes12 lockTag,address token,uint256 amount)")), + lock.lockTag, + lock.token, + lock.amount + ) + ); + } + return keccak256(abi.encodePacked(lockHashes)); + } + + function getElementHash( + Element memory element, + bytes32 witness + ) internal pure returns (bytes32) { + return keccak256( + abi.encode( + keccak256( + bytes( + "Element(address arbiter,uint256 chainId,Lock[] commitments,Mandate mandate)Lock(bytes12 lockTag,address token,uint256 amount)Mandate(uint32 fillDeadline,address inputOracle,MandateOutput[] outputs)MandateOutput(bytes32 oracle,bytes32 settler,uint256 chainId,bytes32 token,uint256 amount,bytes32 recipient,bytes callbackData,bytes context)" + ) + ), + element.arbiter, + element.chainId, + getLocksHash(element.commitments), + witness + ) + ); + } + + function getElementsHashes( + Element[] memory elements, + bytes32 witness + ) internal pure returns (bytes32[] memory elementHashes) { + uint256 numElements = elements.length; + elementHashes = new bytes32[](numElements); + for (uint256 i; i < numElements; ++i) { + elementHashes[i] = getElementHash(elements[i], witness); + } + } + + function getCompactMultichainClaimHash( + address sponsor, + uint256 nonce, + uint256 expires, + Element[] memory elements, + bytes32 witness + ) internal pure returns (bytes32) { + return keccak256( + abi.encode( + keccak256( + bytes( + "MultichainCompact(address sponsor,uint256 nonce,uint256 expires,Element[] elements)Element(address arbiter,uint256 chainId,Lock[] commitments,Mandate mandate)Lock(bytes12 lockTag,address token,uint256 amount)Mandate(uint32 fillDeadline,address inputOracle,MandateOutput[] outputs)MandateOutput(bytes32 oracle,bytes32 settler,uint256 chainId,bytes32 token,uint256 amount,bytes32 recipient,bytes callbackData,bytes context)" + ) + ), + sponsor, + nonce, + expires, + keccak256(abi.encodePacked(getElementsHashes(elements, witness))) + ) + ); + } + + function getCompactMultichainWitnessSignature( + bytes32 domainSeparator, + uint256 privateKey, + address sponsor, + uint256 nonce, + uint256 expires, + Element[] memory elements, + bytes32 witness + ) internal pure returns (bytes memory sig) { + bytes32 msgHash = keccak256( + abi.encodePacked( + "\x19\x01", domainSeparator, getCompactMultichainClaimHash(sponsor, nonce, expires, elements, witness) + ) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); + return bytes.concat(r, s, bytes1(v)); + } + + function witnessHash( + uint256 fillDeadline, + address inputOracle, + MandateOutput[] memory outputs + ) internal pure returns (bytes32) { + return keccak256( + abi.encode( + keccak256( + bytes( + "Mandate(uint32 fillDeadline,address inputOracle,MandateOutput[] outputs)MandateOutput(bytes32 oracle,bytes32 settler,uint256 chainId,bytes32 token,uint256 amount,bytes32 recipient,bytes callbackData,bytes context)" + ) + ), + fillDeadline, + inputOracle, + outputsHash(outputs) + ) + ); + } + + function outputsHash( + MandateOutput[] memory outputs + ) internal pure returns (bytes32) { + bytes32[] memory hashes = new bytes32[](outputs.length); + for (uint256 i = 0; i < outputs.length; ++i) { + MandateOutput memory output = outputs[i]; + hashes[i] = keccak256( + abi.encode( + keccak256( + bytes( + "MandateOutput(bytes32 oracle,bytes32 settler,uint256 chainId,bytes32 token,uint256 amount,bytes32 recipient,bytes callbackData,bytes context)" + ) + ), + output.oracle, + output.settler, + output.chainId, + output.token, + output.amount, + output.recipient, + keccak256(output.callbackData), + keccak256(output.context) + ) + ); + } + return keccak256(abi.encodePacked(hashes)); + } + + function encodeMessage( + bytes32 remoteIdentifier, + bytes[] calldata payloads + ) external pure returns (bytes memory) { + return MessageEncodingLib.encodeMessage(remoteIdentifier, payloads); + } + + function _buildPreMessage( + uint16 emitterChainId, + bytes32 emitterAddress + ) internal pure returns (bytes memory preMessage) { + return + abi.encodePacked(hex"000003e8" hex"00000001", emitterChainId, emitterAddress, hex"0000000000000539" hex"0f"); + } + + function makeValidVAA( + uint16 emitterChainId, + bytes32 emitterAddress, + bytes memory message + ) internal view returns (bytes memory validVM) { + bytes memory postvalidVM = abi.encodePacked(_buildPreMessage(emitterChainId, emitterAddress), message); + bytes32 vmHash = keccak256(abi.encodePacked(keccak256(postvalidVM))); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(testGuardianPrivateKey, vmHash); + + validVM = abi.encodePacked(hex"01" hex"00000000" hex"01", uint8(0), r, s, v - 27, postvalidVM); + } + + function getOrderOpenSignature( + uint256 privateKey, + bytes32 orderId, + bytes32 destination, + bytes calldata call + ) external view returns (bytes memory sig) { + bytes32 domainSeparator = EIP712(inputSettlerMultichainCompact).DOMAIN_SEPARATOR(); + bytes32 msgHash = keccak256( + abi.encodePacked("\x19\x01", domainSeparator, AllowOpenType.hashAllowOpen(orderId, destination, call)) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); + return bytes.concat(r, s, bytes1(v)); + } + + function toTokenId( + address tkn, + Scope scope, + ResetPeriod resetPeriod, + address alloca + ) internal pure returns (uint256 id) { + // Derive the allocator ID for the provided allocator address. + uint96 allocatorId = IdLib.toAllocatorId(alloca); + + // Derive resource lock ID (pack scope, reset period, allocator ID, & token). + id = ((EfficiencyLib.asUint256(scope) << 255) | (EfficiencyLib.asUint256(resetPeriod) << 252) + | (EfficiencyLib.asUint256(allocatorId) << 160) | EfficiencyLib.asUint256(tkn)); + } + + function getOutputToFillFromMandateOutput( + uint48 fillDeadline, + MandateOutput memory output + ) internal pure returns (bytes memory) { + return abi.encodePacked( + fillDeadline, // fill deadline + output.oracle, // oracle + output.settler, // settler + uint256(output.chainId), // chainId + output.token, // token + output.amount, // amount + output.recipient, // recipient + uint16(output.callbackData.length), // call length + output.callbackData, // call + uint16(output.context.length), // context length + output.context // context + ); + } +} diff --git a/test/input/compact/InputSettlerMultichainCompact.t.sol b/test/input/compact/InputSettlerMultichainCompact.t.sol new file mode 100644 index 00000000..6c7465f9 --- /dev/null +++ b/test/input/compact/InputSettlerMultichainCompact.t.sol @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import { InputSettlerBase } from "../../../src/input/InputSettlerBase.sol"; +import { InputSettlerMultichainCompact } from "../../../src/input/compact/InputSettlerMultichainCompact.sol"; +import { MandateOutput, MandateOutputType } from "../../../src/input/types/MandateOutputType.sol"; +import { + MultichainOrderComponent, + MultichainOrderComponentType +} from "../../../src/input/types/MultichainOrderComponentType.sol"; +import { MandateOutputEncodingLib } from "../../../src/libs/MandateOutputEncodingLib.sol"; + +import { AlwaysYesOracle } from "../../mocks/AlwaysYesOracle.sol"; +import { MockERC20 } from "../../mocks/MockERC20.sol"; + +import { LibAddress } from "../../../src/libs/LibAddress.sol"; +import { EIP712, InputSettlerMultichainCompactTestBase } from "./InputSettlerMultichainCompact.base.t.sol"; + +contract InputSettlerMultichainCompactTest is InputSettlerMultichainCompactTestBase { + using LibAddress for address; + using LibAddress for bytes32; + + event Transfer(address from, address to, uint256 amount); + event Transfer(address by, address from, address to, uint256 id, uint256 amount); + + event Finalised(bytes32 indexed orderId, bytes32 solver, bytes32 destination); + + uint64 constant GOVERNANCE_FEE_CHANGE_DELAY = 7 days; + uint64 constant MAX_GOVERNANCE_FEE = 10 ** 18 * 0.05; // 10% + + address owner; + + // This test works slightly differently to other tests. We will be solving the entirety of the test, then opening + // and finalising the test, rolling back the chain, and doing it again. This is to showcase that the funds can be + // claimed on different chains. + /// forge-config: default.isolate = true + function test_finalise_self_2_inputs() public { + // -- Set Up --// + + token.mint(swapper, 1e18 / 10); + anotherToken.mint(swapper, 1e18 / 10); + vm.prank(swapper); + token.approve(address(theCompact), type(uint256).max); + vm.prank(swapper); + anotherToken.approve(address(theCompact), type(uint256).max); + + token.mint(solver, 1e18 / 10); + + vm.prank(solver); + token.approve(address(outputSettlerSimple), type(uint256).max); + + MultichainOrderComponent memory order1; + MultichainOrderComponent memory order3; + { + uint256[2][] memory inputs1 = new uint256[2][](1); + uint256[2][] memory inputs3 = new uint256[2][](1); + { + vm.prank(swapper); + uint256 tokenId0 = theCompact.depositERC20(address(token), alwaysOkAllocatorLockTag, 1e18 / 10, swapper); + inputs1[0] = [tokenId0, 1e18 / 10]; + } + { + vm.prank(swapper); + uint256 tokenId1 = + theCompact.depositERC20(address(anotherToken), alwaysOkAllocatorLockTag, 1e18 / 10, swapper); + inputs3[0] = [tokenId1, 1e18 / 10]; + } + MandateOutput[] memory outputs = new MandateOutput[](1); + outputs[0] = MandateOutput({ + settler: address(outputSettlerSimple).toIdentifier(), + oracle: address(wormholeOracle).toIdentifier(), + chainId: 3, + token: address(token).toIdentifier(), + amount: 1e18 / 10, + recipient: swapper.toIdentifier(), + callbackData: hex"", + context: hex"" + }); + + // Get the additional chain input hashes. Note that we need the other chain's input. + bytes32[] memory additionalChains1 = new bytes32[](1); + additionalChains1[0] = getElementHash( + InputSettlerMultichainCompactTestBase.Element(inputSettlerMultichainCompact, 3, uintsToLocks(inputs3)), + witnessHash(type(uint32).max, address(wormholeOracle), outputs) + ); + bytes32[] memory additionalChains3 = new bytes32[](1); + additionalChains3[0] = getElementHash( + InputSettlerMultichainCompactTestBase.Element(inputSettlerMultichainCompact, 1, uintsToLocks(inputs1)), + witnessHash(type(uint32).max, address(wormholeOracle), outputs) + ); + + order1 = MultichainOrderComponent({ + user: address(swapper), + nonce: 0, + chainIdField: 1, + chainIndex: 0, + fillDeadline: type(uint32).max, + expires: type(uint32).max, + inputOracle: address(wormholeOracle), + inputs: inputs1, + outputs: outputs, + additionalChains: additionalChains1 + }); + + order3 = MultichainOrderComponent({ + user: address(swapper), + nonce: 0, + chainIdField: 1, + chainIndex: 1, + fillDeadline: type(uint32).max, + expires: type(uint32).max, + inputOracle: address(wormholeOracle), + inputs: inputs3, + outputs: outputs, + additionalChains: additionalChains3 + }); + } + // Check that both orders have the same chainId + { + vm.chainId(1); + bytes32 orderId1 = InputSettlerMultichainCompact(inputSettlerMultichainCompact).orderIdentifier(order1); + vm.chainId(3); + bytes32 orderId3 = InputSettlerMultichainCompact(inputSettlerMultichainCompact).orderIdentifier(order3); + assertEq(orderId1, orderId3, "OrderId mismatch"); + } + + // -- Begin Swap -- // + // Fill swap + { + vm.chainId(3); + bytes32 orderId3 = InputSettlerMultichainCompact(inputSettlerMultichainCompact).orderIdentifier(order3); + vm.prank(solver); + outputSettlerSimple.fill(orderId3, order1.outputs[0], order1.fillDeadline, abi.encode(solver)); + } + + assertEq(token.balanceOf(solver), 0); + + { + bytes[] memory payloads = new bytes[](1); + bytes32 orderId3 = InputSettlerMultichainCompact(inputSettlerMultichainCompact).orderIdentifier(order3); + payloads[0] = MandateOutputEncodingLib.encodeFillDescriptionMemory( + solver.toIdentifier(), orderId3, uint32(block.timestamp), order1.outputs[0] + ); + + bytes memory expectedMessageEmitted = this.encodeMessage(order1.outputs[0].settler, payloads); + + // Submit fill to wormhole + wormholeOracle.submit(address(outputSettlerSimple), payloads); + bytes memory vaa = makeValidVAA(uint16(3), address(wormholeOracle).toIdentifier(), expectedMessageEmitted); + + wormholeOracle.receiveMessage(vaa); + } + // Open Order & finalise + uint256 snapshotId = vm.snapshot(); + + vm.chainId(1); + bytes memory signatures; + { + Element[] memory elements = new Element[](2); + elements[0] = Element({ + arbiter: inputSettlerMultichainCompact, chainId: 1, commitments: uintsToLocks(order1.inputs) + }); + elements[1] = Element({ + arbiter: inputSettlerMultichainCompact, chainId: 3, commitments: uintsToLocks(order3.inputs) + }); + // (Sponsor signature but stored in initiated variable for stack) + signatures = getCompactMultichainWitnessSignature( + EIP712(address(theCompact)).DOMAIN_SEPARATOR(), + swapperPrivateKey, + swapper, + 0, + type(uint32).max, + elements, + witnessHash(type(uint32).max, address(wormholeOracle), order1.outputs) + ); + bytes memory allocatorSignature = getCompactMultichainWitnessSignature( + EIP712(address(theCompact)).DOMAIN_SEPARATOR(), + allocatorPrivateKey, + swapper, + 0, + type(uint32).max, + elements, + witnessHash(type(uint32).max, address(wormholeOracle), order1.outputs) + ); + signatures = abi.encode(signatures, allocatorSignature); + } + + assertEq(token.balanceOf(address(theCompact)), order1.inputs[0][1]); + assertEq(token.balanceOf(solver), 0); + assertEq(anotherToken.balanceOf(address(theCompact)), order3.inputs[0][1]); + assertEq(anotherToken.balanceOf(solver), 0); + + { + vm.expectEmit(); + emit Finalised( + InputSettlerMultichainCompact(inputSettlerMultichainCompact).orderIdentifier(order1), + solver.toIdentifier(), + solver.toIdentifier() + ); + InputSettlerBase.SolveParams[] memory solveParams = new InputSettlerBase.SolveParams[](1); + solveParams[0] = + InputSettlerBase.SolveParams({ solver: solver.toIdentifier(), timestamp: uint32(block.timestamp) }); + vm.prank(solver); + InputSettlerMultichainCompact(inputSettlerMultichainCompact) + .finalise(order1, signatures, solveParams, solver.toIdentifier(), hex""); + } + + // Validate that we received input 0. + assertEq(token.balanceOf(address(theCompact)), 0); + assertEq(token.balanceOf(solver), order1.inputs[0][1]); + assertEq(anotherToken.balanceOf(address(theCompact)), order3.inputs[0][1]); + assertEq(anotherToken.balanceOf(solver), 0); + + vm.revertTo(snapshotId); + vm.chainId(3); + + assertEq(token.balanceOf(address(theCompact)), order1.inputs[0][1]); + assertEq(token.balanceOf(solver), 0); + assertEq(anotherToken.balanceOf(address(theCompact)), order3.inputs[0][1]); + assertEq(anotherToken.balanceOf(solver), 0); + + { + vm.expectEmit(); + emit Finalised( + InputSettlerMultichainCompact(inputSettlerMultichainCompact).orderIdentifier(order3), + solver.toIdentifier(), + solver.toIdentifier() + ); + InputSettlerBase.SolveParams[] memory solveParams = new InputSettlerBase.SolveParams[](1); + solveParams[0] = + InputSettlerBase.SolveParams({ solver: solver.toIdentifier(), timestamp: uint32(block.timestamp) }); + vm.prank(solver); + InputSettlerMultichainCompact(inputSettlerMultichainCompact) + .finalise(order3, signatures, solveParams, solver.toIdentifier(), hex""); + } + + // Validate that we received input 1. + assertEq(token.balanceOf(address(theCompact)), order1.inputs[0][1]); + assertEq(token.balanceOf(solver), 0); + assertEq(anotherToken.balanceOf(address(theCompact)), 0); + assertEq(anotherToken.balanceOf(solver), order3.inputs[0][1]); + + // Test opening with signature + vm.revertTo(snapshotId); + vm.chainId(3); + + bytes32 destination = keccak256(bytes("destination")).fromIdentifier().toIdentifier(); + { + bytes memory openSignature = this.getOrderOpenSignature( + solverPrivateKey, + InputSettlerMultichainCompact(inputSettlerMultichainCompact).orderIdentifier(order3), + destination, + hex"" + ); + InputSettlerBase.SolveParams[] memory solveParams = new InputSettlerBase.SolveParams[](1); + solveParams[0] = + InputSettlerBase.SolveParams({ solver: solver.toIdentifier(), timestamp: uint32(block.timestamp) }); + vm.prank(swapper); + InputSettlerMultichainCompact(inputSettlerMultichainCompact) + .finaliseWithSignature(order3, signatures, solveParams, destination, hex"", openSignature); + } + + assertEq(token.balanceOf(solver), 0); + assertEq(token.balanceOf(address(theCompact)), order1.inputs[0][1]); + assertEq(anotherToken.balanceOf(destination.fromIdentifier()), order3.inputs[0][1]); + assertEq(anotherToken.balanceOf(inputSettlerMultichainCompact), 0); + } +} diff --git a/test/input/escrow/InputSettlerEscrow.base.t.sol b/test/input/escrow/InputSettlerEscrow.base.t.sol index ae76ec2f..0f637205 100644 --- a/test/input/escrow/InputSettlerEscrow.base.t.sol +++ b/test/input/escrow/InputSettlerEscrow.base.t.sol @@ -33,7 +33,7 @@ contract InputSettlerEscrowTestBase is Permit2Test { uint64 constant MAX_GOVERNANCE_FEE = 10 ** 18 * 0.05; // 10% address inputSettlerEscrow; - OutputSettlerSimple outputSettlerCoin; + OutputSettlerSimple outputSettlerSimple; address alwaysYesOracle; @@ -69,7 +69,7 @@ contract InputSettlerEscrowTestBase is Permit2Test { DOMAIN_SEPARATOR = EIP712(inputSettlerEscrow).DOMAIN_SEPARATOR(); - outputSettlerCoin = new OutputSettlerSimple(); + outputSettlerSimple = new OutputSettlerSimple(); token = new MockERC20("Mock ERC20", "MOCK", 18); anotherToken = new MockERC20("Mock2 ERC20", "MOCK2", 18); @@ -86,7 +86,7 @@ contract InputSettlerEscrowTestBase is Permit2Test { vm.prank(swapper); token.approve(address(permit2), type(uint256).max); vm.prank(solver); - anotherToken.approve(address(outputSettlerCoin), type(uint256).max); + anotherToken.approve(address(outputSettlerSimple), type(uint256).max); } function witnessHash( @@ -153,15 +153,13 @@ contract InputSettlerEscrowTestBase is Permit2Test { StandardOrder memory order ) internal view returns (bytes memory sig) { uint256[2][] memory inputs = order.inputs; - bytes memory tokenPermissionsHashes = hex""; + bytes32[] memory tokenPermissionsHashes = new bytes32[](inputs.length); for (uint256 i; i < inputs.length; ++i) { uint256[2] memory input = inputs[i]; address inputToken = input[0].fromIdentifier(); uint256 amount = input[1]; - tokenPermissionsHashes = abi.encodePacked( - tokenPermissionsHashes, - keccak256(abi.encode(keccak256("TokenPermissions(address token,uint256 amount)"), inputToken, amount)) - ); + tokenPermissionsHashes[i] = + keccak256(abi.encode(keccak256("TokenPermissions(address token,uint256 amount)"), inputToken, amount)); } bytes32 domainSeparator = EIP712(permit2).DOMAIN_SEPARATOR(); bytes32 msgHash = keccak256( @@ -173,7 +171,7 @@ contract InputSettlerEscrowTestBase is Permit2Test { keccak256( "PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,Permit2Witness witness)MandateOutput(bytes32 oracle,bytes32 settler,uint256 chainId,bytes32 token,uint256 amount,bytes32 recipient,bytes callbackData,bytes context)Permit2Witness(uint32 expires,address inputOracle,MandateOutput[] outputs)TokenPermissions(address token,uint256 amount)" ), - keccak256(tokenPermissionsHashes), + keccak256(abi.encodePacked(tokenPermissionsHashes)), inputSettlerEscrow, order.nonce, order.fillDeadline, diff --git a/test/input/escrow/InputSettlerEscrow.t.sol b/test/input/escrow/InputSettlerEscrow.t.sol index ad9a7625..008c38a7 100644 --- a/test/input/escrow/InputSettlerEscrow.t.sol +++ b/test/input/escrow/InputSettlerEscrow.t.sol @@ -342,7 +342,7 @@ contract InputSettlerEscrowTest is InputSettlerEscrowTestBase { MandateOutput[] memory outputs = new MandateOutput[](1); outputs[0] = MandateOutput({ - settler: address(outputSettlerCoin).toIdentifier(), + settler: address(outputSettlerSimple).toIdentifier(), oracle: alwaysYesOracle.toIdentifier(), chainId: block.chainid, token: address(anotherToken).toIdentifier(), @@ -378,7 +378,7 @@ contract InputSettlerEscrowTest is InputSettlerEscrowTestBase { // Other callers are disallowed: vm.prank(non_solver); - vm.expectRevert(abi.encodeWithSignature("NotOrderOwner()")); + vm.expectRevert(abi.encodeWithSignature("UnexpectedCaller(bytes32)", solver.toIdentifier())); IInputSettlerEscrow(inputSettlerEscrow).finalise(order, solveParams, solver.toIdentifier(), hex""); assertEq(token.balanceOf(solver), 0); @@ -419,7 +419,7 @@ contract InputSettlerEscrowTest is InputSettlerEscrowTestBase { MandateOutput[] memory outputs = new MandateOutput[](1); outputs[0] = MandateOutput({ - settler: address(outputSettlerCoin).toIdentifier(), + settler: address(outputSettlerSimple).toIdentifier(), oracle: alwaysYesOracle.toIdentifier(), chainId: block.chainid, token: address(anotherToken).toIdentifier(), @@ -471,7 +471,7 @@ contract InputSettlerEscrowTest is InputSettlerEscrowTestBase { MandateOutput[] memory outputs = new MandateOutput[](1); outputs[0] = MandateOutput({ - settler: address(outputSettlerCoin).toIdentifier(), + settler: address(outputSettlerSimple).toIdentifier(), oracle: alwaysYesOracle.toIdentifier(), chainId: block.chainid, token: address(anotherToken).toIdentifier(), diff --git a/test/input/escrow/InputSettlerMultichainEscrow.base.t.sol b/test/input/escrow/InputSettlerMultichainEscrow.base.t.sol new file mode 100644 index 00000000..2a826140 --- /dev/null +++ b/test/input/escrow/InputSettlerMultichainEscrow.base.t.sol @@ -0,0 +1,247 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import { Test } from "forge-std/Test.sol"; + +import { OutputSettlerSimple } from "../../../src/output/simple/OutputSettlerSimple.sol"; + +import { InputSettlerMultichainEscrow } from "../../../src/input/escrow/InputSettlerMultichainEscrow.sol"; +import { AllowOpenType } from "../../../src/input/types/AllowOpenType.sol"; +import { MandateOutput, MandateOutputType } from "../../../src/input/types/MandateOutputType.sol"; + +import { + MultichainOrderComponent, + MultichainOrderComponentType +} from "../../../src/input/types/MultichainOrderComponentType.sol"; +import { OrderPurchase, OrderPurchaseType } from "../../../src/input/types/OrderPurchaseType.sol"; + +import { WormholeOracle } from "../../../src/integrations/oracles/wormhole/WormholeOracle.sol"; +import { Messages } from "../../../src/integrations/oracles/wormhole/external/wormhole/Messages.sol"; +import { Setters } from "../../../src/integrations/oracles/wormhole/external/wormhole/Setters.sol"; +import { Structs } from "../../../src/integrations/oracles/wormhole/external/wormhole/Structs.sol"; +import { LibAddress } from "../../../src/libs/LibAddress.sol"; +import { MandateOutputEncodingLib } from "../../../src/libs/MandateOutputEncodingLib.sol"; +import { MessageEncodingLib } from "../../../src/libs/MessageEncodingLib.sol"; + +import { AlwaysYesOracle } from "../../mocks/AlwaysYesOracle.sol"; +import { MockERC20 } from "../../mocks/MockERC20.sol"; +import { Permit2Test } from "./Permit2.t.sol"; + +interface EIP712 { + function DOMAIN_SEPARATOR() external view returns (bytes32); +} + +event PackagePublished(uint32 nonce, bytes payload, uint8 consistencyLevel); + +contract ExportedMessages is Messages, Setters { + function storeGuardianSetPub( + Structs.GuardianSet memory set, + uint32 index + ) public { + return super.storeGuardianSet(set, index); + } + + function publishMessage( + uint32 nonce, + bytes calldata payload, + uint8 consistencyLevel + ) external payable returns (uint64) { + emit PackagePublished(nonce, payload, consistencyLevel); + return 0; + } +} + +contract InputSettlerMultichainEscrowTestBase is Permit2Test { + using LibAddress for uint256; + + address inputSettlerMultichainEscrow; + OutputSettlerSimple outputSettlerSimple; + + // Oracles + address alwaysYesOracle; + ExportedMessages messages; + WormholeOracle wormholeOracle; + + uint256 swapperPrivateKey; + address swapper; + uint256 solverPrivateKey; + address solver; + uint256 testGuardianPrivateKey; + address testGuardian; + + MockERC20 token; + MockERC20 anotherToken; + + function setUp() public virtual override { + super.setUp(); + inputSettlerMultichainEscrow = address(new InputSettlerMultichainEscrow()); + outputSettlerSimple = new OutputSettlerSimple(); + alwaysYesOracle = address(new AlwaysYesOracle()); + + token = new MockERC20("Mock ERC20", "MOCK", 18); + anotherToken = new MockERC20("Mock2 ERC20", "MOCK2", 18); + + (swapper, swapperPrivateKey) = makeAddrAndKey("swapper"); + (solver, solverPrivateKey) = makeAddrAndKey("solver"); + + // Oracles + messages = new ExportedMessages(); + address wormholeDeployment = makeAddr("wormholeOracle"); + deployCodeTo("WormholeOracle.sol", abi.encode(address(this), address(messages)), wormholeDeployment); + wormholeOracle = WormholeOracle(wormholeDeployment); + wormholeOracle.setChainMap(3, 3); + (testGuardian, testGuardianPrivateKey) = makeAddrAndKey("testGuardian"); + // initialize guardian set with one guardian + address[] memory keys = new address[](1); + keys[0] = testGuardian; + Structs.GuardianSet memory guardianSet = Structs.GuardianSet(keys, 0); + require(messages.quorum(guardianSet.keys.length) == 1, "Quorum should be 1"); + + messages.storeGuardianSetPub(guardianSet, uint32(0)); + } + + function getOutputToFillFromMandateOutput( + uint48 fillDeadline, + MandateOutput memory output + ) internal pure returns (bytes memory) { + return abi.encodePacked( + fillDeadline, // fill deadline + output.oracle, // oracle + output.settler, // settler + uint256(output.chainId), // chainId + output.token, // token + output.amount, // amount + output.recipient, // recipient + uint16(output.callbackData.length), // call length + output.callbackData, // call + uint16(output.context.length), // context length + output.context // context + ); + } + + function encodeMessage( + bytes32 remoteIdentifier, + bytes[] calldata payloads + ) external pure returns (bytes memory) { + return MessageEncodingLib.encodeMessage(remoteIdentifier, payloads); + } + + function _buildPreMessage( + uint16 emitterChainId, + bytes32 emitterAddress + ) internal pure returns (bytes memory preMessage) { + return + abi.encodePacked(hex"000003e8" hex"00000001", emitterChainId, emitterAddress, hex"0000000000000539" hex"0f"); + } + + function makeValidVAA( + uint16 emitterChainId, + bytes32 emitterAddress, + bytes memory message + ) internal view returns (bytes memory validVM) { + bytes memory postvalidVM = abi.encodePacked(_buildPreMessage(emitterChainId, emitterAddress), message); + bytes32 vmHash = keccak256(abi.encodePacked(keccak256(postvalidVM))); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(testGuardianPrivateKey, vmHash); + + validVM = abi.encodePacked(hex"01" hex"00000000" hex"01", uint8(0), r, s, v - 27, postvalidVM); + } + + function getOrderOpenSignature( + uint256 privateKey, + bytes32 orderId, + bytes32 destination, + bytes calldata call + ) external view returns (bytes memory sig) { + bytes32 domainSeparator = EIP712(inputSettlerMultichainEscrow).DOMAIN_SEPARATOR(); + bytes32 msgHash = keccak256( + abi.encodePacked("\x19\x01", domainSeparator, AllowOpenType.hashAllowOpen(orderId, destination, call)) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); + return bytes.concat(r, s, bytes1(v)); + } + + function witnessHash( + MultichainOrderComponent memory order + ) internal view returns (bytes32) { + bytes32 orderId = InputSettlerMultichainEscrow(inputSettlerMultichainEscrow).orderIdentifier(order); + return keccak256( + abi.encode( + keccak256( + bytes( + "MultichainPermit2Witness(bytes32 orderId,uint32 expires,address inputOracle,MandateOutput[] outputs)MandateOutput(bytes32 oracle,bytes32 settler,uint256 chainId,bytes32 token,uint256 amount,bytes32 recipient,bytes callbackData,bytes context)" + ) + ), + orderId, + order.expires, + order.inputOracle, + outputsHash(order.outputs) + ) + ); + } + + function outputsHash( + MandateOutput[] memory outputs + ) internal pure returns (bytes32) { + bytes32[] memory hashes = new bytes32[](outputs.length); + for (uint256 i = 0; i < outputs.length; ++i) { + MandateOutput memory output = outputs[i]; + hashes[i] = keccak256( + abi.encode( + keccak256( + bytes( + "MandateOutput(bytes32 oracle,bytes32 settler,uint256 chainId,bytes32 token,uint256 amount,bytes32 recipient,bytes callbackData,bytes context)" + ) + ), + output.oracle, + output.settler, + output.chainId, + output.token, + output.amount, + output.recipient, + keccak256(output.callbackData), + keccak256(output.context) + ) + ); + } + return keccak256(abi.encodePacked(hashes)); + } + + function getPermit2Signature( + uint256 privateKey, + MultichainOrderComponent memory order + ) internal view returns (bytes memory sig) { + uint256[2][] memory inputs = order.inputs; + bytes32[] memory tokenPermissionsHashes = new bytes32[](inputs.length); + for (uint256 i; i < inputs.length; ++i) { + uint256[2] memory input = inputs[i]; + address inputToken = input[0].fromIdentifier(); + uint256 amount = input[1]; + tokenPermissionsHashes[i] = keccak256( + abi.encode(keccak256("TokenPermissions(address token,uint256 amount)"), inputToken, amount) + ); + } + bytes32 domainSeparator = EIP712(permit2).DOMAIN_SEPARATOR(); + bytes32 msgHash = keccak256( + abi.encodePacked( + "\x19\x01", + domainSeparator, + keccak256( + abi.encode( + keccak256( + "PermitBatchWitnessTransferFrom(TokenPermissions[] permitted,address spender,uint256 nonce,uint256 deadline,MultichainPermit2Witness witness)MandateOutput(bytes32 oracle,bytes32 settler,uint256 chainId,bytes32 token,uint256 amount,bytes32 recipient,bytes callbackData,bytes context)MultichainPermit2Witness(bytes32 orderId,uint32 expires,address inputOracle,MandateOutput[] outputs)TokenPermissions(address token,uint256 amount)" + ), + keccak256(abi.encodePacked(tokenPermissionsHashes)), + inputSettlerMultichainEscrow, + order.nonce, + order.fillDeadline, + witnessHash(order) + ) + ) + ) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, msgHash); + return bytes.concat(r, s, bytes1(v)); + } +} diff --git a/test/input/escrow/InputSettlerMultichainEscrow.t.sol b/test/input/escrow/InputSettlerMultichainEscrow.t.sol new file mode 100644 index 00000000..d690a57f --- /dev/null +++ b/test/input/escrow/InputSettlerMultichainEscrow.t.sol @@ -0,0 +1,368 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import { InputSettlerBase } from "../../../src/input/InputSettlerBase.sol"; +import { InputSettlerMultichainEscrow } from "../../../src/input/escrow/InputSettlerMultichainEscrow.sol"; +import { MandateOutput, MandateOutputType } from "../../../src/input/types/MandateOutputType.sol"; +import { + MultichainOrderComponent, + MultichainOrderComponentType +} from "../../../src/input/types/MultichainOrderComponentType.sol"; +import { MandateOutputEncodingLib } from "../../../src/libs/MandateOutputEncodingLib.sol"; + +import { AlwaysYesOracle } from "../../mocks/AlwaysYesOracle.sol"; +import { MockERC20 } from "../../mocks/MockERC20.sol"; + +import { LibAddress } from "../../../src/libs/LibAddress.sol"; +import { InputSettlerMultichainEscrowTestBase } from "./InputSettlerMultichainEscrow.base.t.sol"; + +contract InputSettlerMultichainEscrowTest is InputSettlerMultichainEscrowTestBase { + using LibAddress for address; + using LibAddress for bytes32; + + event Transfer(address from, address to, uint256 amount); + event Transfer(address by, address from, address to, uint256 id, uint256 amount); + event NextGovernanceFee(uint64 nextGovernanceFee, uint64 nextGovernanceFeeTime); + event GovernanceFeeChanged(uint64 oldGovernanceFee, uint64 newGovernanceFee); + + uint64 constant GOVERNANCE_FEE_CHANGE_DELAY = 7 days; + uint64 constant MAX_GOVERNANCE_FEE = 10 ** 18 * 0.05; // 10% + + address owner; + + // This test works slightly differently to other tests. We will be solving the entirety of the test, then opening + // and finalising the test, rolling back the chain, and doing it again. This is to showcase that the funds can be + // claimed on different chains. + /// forge-config: default.isolate = true + function test_finalise_self_2_inputs() public { + // -- Set Up --// + + uint256 amount = 1e18 / 10; + token.mint(swapper, amount); + anotherToken.mint(swapper, amount); + vm.prank(swapper); + token.approve(address(inputSettlerMultichainEscrow), type(uint256).max); + vm.prank(swapper); + anotherToken.approve(address(inputSettlerMultichainEscrow), type(uint256).max); + + token.mint(solver, amount); + vm.prank(solver); + token.approve(address(outputSettlerSimple), type(uint256).max); + + uint256[2][] memory inputs0 = new uint256[2][](1); + uint256[2][] memory inputs1 = new uint256[2][](1); + inputs0[0] = [uint256(address(token).toIdentifier()), amount]; + inputs1[0] = [uint256(address(anotherToken).toIdentifier()), amount]; + + // Get the additional chain input hashes. Note that we need the other chain's input. + bytes32[] memory additionalChains0 = new bytes32[](1); + additionalChains0[0] = keccak256(abi.encodePacked(uint256(3), inputs1)); + bytes32[] memory additionalChains1 = new bytes32[](1); + additionalChains1[0] = keccak256(abi.encodePacked(uint256(0), inputs0)); + + MandateOutput[] memory outputs = new MandateOutput[](1); + outputs[0] = MandateOutput({ + settler: address(outputSettlerSimple).toIdentifier(), + oracle: address(wormholeOracle).toIdentifier(), + chainId: 3, + token: address(token).toIdentifier(), + amount: amount, + recipient: swapper.toIdentifier(), + callbackData: hex"", + context: hex"" + }); + MultichainOrderComponent memory order = MultichainOrderComponent({ + user: address(swapper), + nonce: 0, + chainIdField: 0, // Selected index 0 chain. + chainIndex: 0, + fillDeadline: type(uint32).max, + expires: type(uint32).max, + inputOracle: address(wormholeOracle), + inputs: new uint256[2][](0), // Shall be replaced before execution + outputs: outputs, + additionalChains: new bytes32[](0) // Shall be replaced before execution + }); + + // Check that both orders have the same chainId + vm.chainId(0); + order.chainIdField = 0; + order.chainIndex = 0; + order.inputs = inputs0; + order.additionalChains = additionalChains0; + bytes32 orderId = InputSettlerMultichainEscrow(inputSettlerMultichainEscrow).orderIdentifier(order); + vm.chainId(3); + order.chainIdField = 3; + order.chainIndex = 1; + order.inputs = inputs1; + order.additionalChains = additionalChains1; + bytes32 orderId1 = InputSettlerMultichainEscrow(inputSettlerMultichainEscrow).orderIdentifier(order); + assertEq(orderId, orderId1, "OrderId mismatch"); + + // -- Begin Swap -- // + // Fill swap + vm.chainId(3); + order.chainIdField = 3; + order.chainIndex = 1; + order.inputs = inputs1; + order.additionalChains = additionalChains1; + + vm.prank(solver); + outputSettlerSimple.fill(orderId, order.outputs[0], order.fillDeadline, abi.encode(solver)); + + assertEq(token.balanceOf(solver), 0); + + bytes[] memory payloads = new bytes[](1); + payloads[0] = MandateOutputEncodingLib.encodeFillDescriptionMemory( + solver.toIdentifier(), orderId, uint32(block.timestamp), outputs[0] + ); + + bytes memory expectedMessageEmitted = this.encodeMessage(outputs[0].settler, payloads); + + // Submit fill to wormhole + wormholeOracle.submit(address(outputSettlerSimple), payloads); + bytes memory vaa = makeValidVAA(uint16(3), address(wormholeOracle).toIdentifier(), expectedMessageEmitted); + + wormholeOracle.receiveMessage(vaa); + + InputSettlerBase.SolveParams[] memory solveParams = new InputSettlerBase.SolveParams[](1); + solveParams[0] = + InputSettlerBase.SolveParams({ solver: solver.toIdentifier(), timestamp: uint32(block.timestamp) }); + // Open Order & finalise + uint256 snapshotId = vm.snapshot(); + + vm.chainId(0); + order.chainIdField = 0; + order.chainIndex = 0; + order.inputs = inputs0; + order.additionalChains = additionalChains0; + vm.prank(swapper); + InputSettlerMultichainEscrow(inputSettlerMultichainEscrow).open(order); + + assertEq(anotherToken.balanceOf(solver), 0); + assertEq(anotherToken.balanceOf(inputSettlerMultichainEscrow), 0); + assertEq(token.balanceOf(solver), 0); + assertEq(token.balanceOf(inputSettlerMultichainEscrow), inputs0[0][1]); + + { + vm.prank(solver); + InputSettlerMultichainEscrow(inputSettlerMultichainEscrow) + .finalise(order, solveParams, solver.toIdentifier(), hex""); + } + + // Validate that we received input 0. + assertEq(anotherToken.balanceOf(solver), 0); + assertEq(anotherToken.balanceOf(inputSettlerMultichainEscrow), 0); + assertEq(token.balanceOf(solver), inputs0[0][1]); + assertEq(token.balanceOf(inputSettlerMultichainEscrow), 0); + + vm.revertTo(snapshotId); + vm.chainId(3); + order.chainIdField = 3; + order.chainIndex = 1; + order.inputs = inputs1; + order.additionalChains = additionalChains1; + vm.prank(swapper); + InputSettlerMultichainEscrow(inputSettlerMultichainEscrow).open(order); + + assertEq(token.balanceOf(solver), 0); + assertEq(token.balanceOf(inputSettlerMultichainEscrow), 0); + assertEq(anotherToken.balanceOf(solver), 0); + assertEq(anotherToken.balanceOf(inputSettlerMultichainEscrow), inputs1[0][1]); + + { + vm.prank(solver); + InputSettlerMultichainEscrow(inputSettlerMultichainEscrow) + .finalise(order, solveParams, solver.toIdentifier(), hex""); + } + + // Validate that we received input 1. + assertEq(token.balanceOf(solver), 0); + assertEq(token.balanceOf(inputSettlerMultichainEscrow), 0); + assertEq(anotherToken.balanceOf(solver), inputs1[0][1]); + assertEq(anotherToken.balanceOf(inputSettlerMultichainEscrow), 0); + + // Test opening with signature + vm.revertTo(snapshotId); + vm.chainId(3); + order.chainIdField = 3; + order.chainIndex = 1; + order.inputs = inputs1; + order.additionalChains = additionalChains1; + + vm.prank(swapper); + InputSettlerMultichainEscrow(inputSettlerMultichainEscrow).open(order); + + bytes32 destination = keccak256(bytes("destination")).fromIdentifier().toIdentifier(); + { + bytes memory openSignature = this.getOrderOpenSignature( + solverPrivateKey, + InputSettlerMultichainEscrow(inputSettlerMultichainEscrow).orderIdentifier(order), + destination, + hex"" + ); + + vm.prank(swapper); + InputSettlerMultichainEscrow(inputSettlerMultichainEscrow) + .finaliseWithSignature(order, solveParams, destination, hex"", openSignature); + } + assertEq(token.balanceOf(solver), 0); + assertEq(token.balanceOf(inputSettlerMultichainEscrow), 0); + assertEq(anotherToken.balanceOf(destination.fromIdentifier()), inputs1[0][1]); + assertEq(anotherToken.balanceOf(inputSettlerMultichainEscrow), 0); + } + + function test_openFor_permit2_finalise_self_2_inputs() public { + // -- Set Up --// + + uint256 amount = 1e18 / 10; + token.mint(swapper, amount); + anotherToken.mint(swapper, amount); + vm.prank(swapper); + token.approve(permit2, type(uint256).max); + vm.prank(swapper); + anotherToken.approve(permit2, type(uint256).max); + + token.mint(solver, amount); + vm.prank(solver); + token.approve(address(outputSettlerSimple), type(uint256).max); + + uint256[2][] memory inputs0 = new uint256[2][](1); + uint256[2][] memory inputs1 = new uint256[2][](1); + inputs0[0] = [uint256(address(token).toIdentifier()), amount]; + inputs1[0] = [uint256(address(anotherToken).toIdentifier()), amount]; + + // Get the additional chain input hashes. Note that we need the other chain's input. + bytes32[] memory additionalChains0 = new bytes32[](1); + additionalChains0[0] = keccak256(abi.encodePacked(uint256(3), inputs1)); + bytes32[] memory additionalChains1 = new bytes32[](1); + additionalChains1[0] = keccak256(abi.encodePacked(uint256(0), inputs0)); + + MandateOutput[] memory outputs = new MandateOutput[](1); + outputs[0] = MandateOutput({ + settler: address(outputSettlerSimple).toIdentifier(), + oracle: address(wormholeOracle).toIdentifier(), + chainId: 3, + token: address(token).toIdentifier(), + amount: amount, + recipient: swapper.toIdentifier(), + callbackData: hex"", + context: hex"" + }); + MultichainOrderComponent memory order = MultichainOrderComponent({ + user: address(swapper), + nonce: 0, + chainIdField: 0, // Selected index 0 chain. + chainIndex: 0, + fillDeadline: type(uint32).max, + expires: type(uint32).max, + inputOracle: address(wormholeOracle), + inputs: new uint256[2][](0), // Shall be replaced before execution + outputs: outputs, + additionalChains: new bytes32[](0) // Shall be replaced before execution + }); + + // Check that both orders have the same chainId + vm.chainId(0); + order.chainIdField = 0; + order.chainIndex = 0; + order.inputs = inputs0; + order.additionalChains = additionalChains0; + bytes32 orderId = InputSettlerMultichainEscrow(inputSettlerMultichainEscrow).orderIdentifier(order); + vm.chainId(3); + order.chainIdField = 3; + order.chainIndex = 1; + order.inputs = inputs1; + order.additionalChains = additionalChains1; + bytes32 orderId1 = InputSettlerMultichainEscrow(inputSettlerMultichainEscrow).orderIdentifier(order); + assertEq(orderId, orderId1, "OrderId mismatch"); + + // -- Begin Swap -- // + // Fill swap + vm.chainId(3); + order.chainIdField = 3; + order.chainIndex = 1; + order.inputs = inputs1; + order.additionalChains = additionalChains1; + + vm.prank(solver); + outputSettlerSimple.fill(orderId, order.outputs[0], order.fillDeadline, abi.encode(solver)); + + assertEq(token.balanceOf(solver), 0); + + bytes[] memory payloads = new bytes[](1); + payloads[0] = MandateOutputEncodingLib.encodeFillDescriptionMemory( + solver.toIdentifier(), orderId, uint32(block.timestamp), outputs[0] + ); + + bytes memory expectedMessageEmitted = this.encodeMessage(outputs[0].settler, payloads); + + // Submit fill to wormhole + wormholeOracle.submit(address(outputSettlerSimple), payloads); + bytes memory vaa = makeValidVAA(uint16(3), address(wormholeOracle).toIdentifier(), expectedMessageEmitted); + + wormholeOracle.receiveMessage(vaa); + + InputSettlerBase.SolveParams[] memory solveParams = new InputSettlerBase.SolveParams[](1); + solveParams[0] = + InputSettlerBase.SolveParams({ solver: solver.toIdentifier(), timestamp: uint32(block.timestamp) }); + // Open Order & finalise + uint256 snapshotId = vm.snapshot(); + + vm.chainId(0); + order.chainIdField = 0; + order.chainIndex = 0; + order.inputs = inputs0; + order.additionalChains = additionalChains0; + { + bytes memory signature = abi.encodePacked(bytes1(0x00), getPermit2Signature(swapperPrivateKey, order)); + InputSettlerMultichainEscrow(inputSettlerMultichainEscrow).openFor(order, swapper, signature); + } + + assertEq(anotherToken.balanceOf(solver), 0); + assertEq(anotherToken.balanceOf(inputSettlerMultichainEscrow), 0); + assertEq(token.balanceOf(solver), 0); + assertEq(token.balanceOf(inputSettlerMultichainEscrow), inputs0[0][1]); + + { + vm.prank(solver); + InputSettlerMultichainEscrow(inputSettlerMultichainEscrow) + .finalise(order, solveParams, solver.toIdentifier(), hex""); + } + + // Validate that we received input 0. + assertEq(anotherToken.balanceOf(solver), 0); + assertEq(anotherToken.balanceOf(inputSettlerMultichainEscrow), 0); + assertEq(token.balanceOf(solver), inputs0[0][1]); + assertEq(token.balanceOf(inputSettlerMultichainEscrow), 0); + + vm.revertTo(snapshotId); + vm.chainId(3); + order.chainIdField = 3; + order.chainIndex = 1; + order.inputs = inputs1; + order.additionalChains = additionalChains1; + + { + bytes memory signature = abi.encodePacked(bytes1(0x00), getPermit2Signature(swapperPrivateKey, order)); + InputSettlerMultichainEscrow(inputSettlerMultichainEscrow).openFor(order, swapper, signature); + } + + assertEq(token.balanceOf(solver), 0); + assertEq(token.balanceOf(inputSettlerMultichainEscrow), 0); + assertEq(anotherToken.balanceOf(solver), 0); + assertEq(anotherToken.balanceOf(inputSettlerMultichainEscrow), inputs1[0][1]); + + { + vm.prank(solver); + InputSettlerMultichainEscrow(inputSettlerMultichainEscrow) + .finalise(order, solveParams, solver.toIdentifier(), hex""); + } + + // Validate that we received input 1. + assertEq(token.balanceOf(solver), 0); + assertEq(token.balanceOf(inputSettlerMultichainEscrow), 0); + assertEq(anotherToken.balanceOf(solver), inputs1[0][1]); + assertEq(anotherToken.balanceOf(inputSettlerMultichainEscrow), 0); + } +} diff --git a/test/integration/SettlerCompact.crosschain.t.sol b/test/integration/SettlerCompact.crosschain.t.sol index a57c960b..05eeb1bd 100644 --- a/test/integration/SettlerCompact.crosschain.t.sol +++ b/test/integration/SettlerCompact.crosschain.t.sol @@ -67,7 +67,7 @@ contract InputSettlerCompactTestCrossChain is Test { using LibAddress for address; address inputSettlerCompact; - OutputSettlerSimple outputSettlerCoin; + OutputSettlerSimple outputSettlerSimple; // Oracles address alwaysYesOracle; @@ -108,7 +108,7 @@ contract InputSettlerCompactTestCrossChain is Test { DOMAIN_SEPARATOR = theCompact.DOMAIN_SEPARATOR(); inputSettlerCompact = address(new InputSettlerCompact(address(theCompact))); - outputSettlerCoin = new OutputSettlerSimple(); + outputSettlerSimple = new OutputSettlerSimple(); alwaysYesOracle = address(new AlwaysYesOracle()); token = new MockERC20("Mock ERC20", "MOCK", 18); @@ -125,9 +125,9 @@ contract InputSettlerCompactTestCrossChain is Test { vm.prank(swapper); token.approve(address(theCompact), type(uint256).max); vm.prank(solver); - anotherToken.approve(address(outputSettlerCoin), type(uint256).max); + anotherToken.approve(address(outputSettlerSimple), type(uint256).max); vm.prank(solver); - token.approve(address(outputSettlerCoin), type(uint256).max); + token.approve(address(outputSettlerSimple), type(uint256).max); // Oracles @@ -378,7 +378,7 @@ contract InputSettlerCompactTestCrossChain is Test { inputs[0] = [tokenId, amount]; MandateOutput[] memory outputs = new MandateOutput[](1); outputs[0] = MandateOutput({ - settler: address(outputSettlerCoin).toIdentifier(), + settler: address(outputSettlerSimple).toIdentifier(), oracle: inputOracle.toIdentifier(), chainId: block.chainid, token: address(anotherToken).toIdentifier(), @@ -432,7 +432,7 @@ contract InputSettlerCompactTestCrossChain is Test { //bytes32 orderId = IInputSettlerCompact(inputSettlerCompact).orderIdentifier(order); vm.prank(solver); - outputSettlerCoin.fill(orderId, outputs[0], type(uint48).max, fillerData); + outputSettlerSimple.fill(orderId, outputs[0], type(uint48).max, fillerData); vm.snapshotGasLastCall("inputSettler", "IntegrationCoinFill"); } @@ -450,10 +450,10 @@ contract InputSettlerCompactTestCrossChain is Test { ); bytes memory expectedMessageEmitted = - this.encodeMessage(address(outputSettlerCoin).toIdentifier(), payloads); + this.encodeMessage(address(outputSettlerSimple).toIdentifier(), payloads); vm.expectEmit(); emit PackagePublished(0, expectedMessageEmitted, 15); - wormholeOracle.submit(address(outputSettlerCoin), payloads); + wormholeOracle.submit(address(outputSettlerSimple), payloads); vm.snapshotGasLastCall("inputSettler", "IntegrationWormholeSubmit"); bytes memory vaa = @@ -486,7 +486,7 @@ contract InputSettlerCompactTestCrossChain is Test { inputs[0] = [tokenId, amount]; MandateOutput[] memory outputs = new MandateOutput[](2); outputs[0] = MandateOutput({ - settler: address(outputSettlerCoin).toIdentifier(), + settler: address(outputSettlerSimple).toIdentifier(), oracle: address(wormholeOracle).toIdentifier(), chainId: block.chainid, token: address(anotherToken).toIdentifier(), @@ -496,7 +496,7 @@ contract InputSettlerCompactTestCrossChain is Test { context: hex"" }); outputs[1] = MandateOutput({ - settler: address(outputSettlerCoin).toIdentifier(), + settler: address(outputSettlerSimple).toIdentifier(), oracle: address(wormholeOracle).toIdentifier(), chainId: block.chainid, token: address(token).toIdentifier(), @@ -537,10 +537,10 @@ contract InputSettlerCompactTestCrossChain is Test { bytes32 orderId = IInputSettlerCompact(inputSettlerCompact).orderIdentifier(order); vm.prank(solver); - outputSettlerCoin.fill(orderId, outputs[0], type(uint48).max, fillerData1); + outputSettlerSimple.fill(orderId, outputs[0], type(uint48).max, fillerData1); vm.prank(solver); - outputSettlerCoin.fill(orderId, outputs[1], type(uint48).max, fillerData2); + outputSettlerSimple.fill(orderId, outputs[1], type(uint48).max, fillerData2); bytes[] memory payloads = new bytes[](2); payloads[0] = MandateOutputEncodingLib.encodeFillDescriptionMemory( @@ -568,7 +568,7 @@ contract InputSettlerCompactTestCrossChain is Test { vm.expectEmit(); emit PackagePublished(0, expectedMessageEmitted, 15); - wormholeOracle.submit(address(outputSettlerCoin), payloads); + wormholeOracle.submit(address(outputSettlerSimple), payloads); bytes memory vaa = makeValidVAA(uint16(block.chainid), address(wormholeOracle).toIdentifier(), expectedMessageEmitted); diff --git a/test/libs/IsContractLib.t.sol b/test/libs/IsContractLib.t.sol index e65d4802..d2bdb228 100644 --- a/test/libs/IsContractLib.t.sol +++ b/test/libs/IsContractLib.t.sol @@ -19,7 +19,7 @@ contract IsContractLibHarness { } contract IsContractLibTest is Test { - address outputSettlerCoin; + address outputSettlerSimple; address outputToken; address inputSettlerCompact; @@ -27,16 +27,16 @@ contract IsContractLibTest is Test { function setUp() public { isContractLib = new IsContractLibHarness(); - outputSettlerCoin = address(new OutputSettlerSimple()); + outputSettlerSimple = address(new OutputSettlerSimple()); outputToken = address(new MockERC20("TEST", "TEST", 18)); inputSettlerCompact = address(new InputSettlerCompact(address(0))); } function test_validateContainsCode_known_addresses() external { - isContractLib.validateContainsCode(outputSettlerCoin); + isContractLib.validateContainsCode(outputSettlerSimple); vm.expectRevert(abi.encodeWithSignature("CodeSize0()")); - isContractLib.validateContainsCode(makeAddr("outputSettlerCoin")); + isContractLib.validateContainsCode(makeAddr("outputSettlerSimple")); vm.expectRevert(abi.encodeWithSignature("CodeSize0()")); isContractLib.validateContainsCode(address(0)); diff --git a/test/libs/MultichainOrderComponentsType.sol b/test/libs/MultichainOrderComponentsType.sol new file mode 100644 index 00000000..fceb1cee --- /dev/null +++ b/test/libs/MultichainOrderComponentsType.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; + +import { MultichainOrderComponentType } from "../../src/input/types/MultichainOrderComponentType.sol"; + +contract MultichainOrderComponentTypeTest is Test { + function constructInputHash( + uint256 inputsChainId, + uint256 chainIndex, + uint256[2][] calldata inputs, + bytes32[] calldata additionalChains + ) external pure returns (bytes32) { + return MultichainOrderComponentType.constructInputHash(inputsChainId, chainIndex, inputs, additionalChains); + } + + struct SetOfInputs { + uint256 inputsChainId; + uint256[2][] inputs; + } + + function discardIndex( + uint256 index, + bytes32[] memory arr + ) internal pure returns (bytes32[] memory newArr) { + newArr = new bytes32[](arr.length - 1); + for (uint256 i; i < arr.length; ++i) { + if (i == index) continue; + newArr[index < i ? i - 1 : i] = arr[i]; + } + } + + /// @dev Test a documentation assertion that hashInputs is keccak256(abi.encodePacked(chainId, idsAndAmounts)) + function test_hashInputs_eq_keccak256_abi_encode( + uint256 chainId, + uint256[2][] calldata idsAndAmounts + ) external pure { + bytes32 functionHash = MultichainOrderComponentType.hashInputs(chainId, idsAndAmounts); + bytes32 assumedEqualTo = keccak256(abi.encodePacked(chainId, idsAndAmounts)); + assertEq(functionHash, assumedEqualTo); + } + + /// @dev Test when we iterate over a fixed set of inputs we get the same order id for all inputs. + /// This is a heavy test. + /// forge-config: default.fuzz.runs = 100 + function test_constructInputHash( + SetOfInputs[] calldata orderComponents + ) external view { + uint256 numComponentHashes = orderComponents.length; + vm.assume(numComponentHashes != 0); + bytes32[] memory inputComponentHashes = new bytes32[](numComponentHashes); + for (uint256 i; i < numComponentHashes; ++i) { + inputComponentHashes[i] = + MultichainOrderComponentType.hashInputs(orderComponents[i].inputsChainId, orderComponents[i].inputs); + } + SetOfInputs calldata firstInputSet = orderComponents[0]; + bytes32 firstIndexHash = this.constructInputHash( + firstInputSet.inputsChainId, 0, firstInputSet.inputs, discardIndex(0, inputComponentHashes) + ); + for (uint256 i = 1; i < numComponentHashes; ++i) { + SetOfInputs calldata inputSet = orderComponents[i]; + bytes32 computedComponentHash = this.constructInputHash( + inputSet.inputsChainId, i, inputSet.inputs, discardIndex(i, inputComponentHashes) + ); + assertEq(firstIndexHash, computedComponentHash); + } + } +} diff --git a/test/output/OutputSettlerSimple.fill.t.sol b/test/output/OutputSettlerSimple.fill.t.sol index 07671a54..924ce880 100644 --- a/test/output/OutputSettlerSimple.fill.t.sol +++ b/test/output/OutputSettlerSimple.fill.t.sol @@ -21,23 +21,23 @@ contract OutputSettlerSimpleTestFill is Test { bytes32 indexed orderId, bytes32 solver, uint32 timestamp, MandateOutput output, uint256 finalAmount ); - OutputSettlerSimple outputSettlerCoin; + OutputSettlerSimple outputSettlerSimple; MockERC20 outputToken; MockCallbackExecutor mockCallbackExecutor; address swapper; - address outputSettlerCoinAddress; + address outputSettlerSimpleAddress; address outputTokenAddress; address mockCallbackExecutorAddress; function setUp() public { - outputSettlerCoin = new OutputSettlerSimple(); + outputSettlerSimple = new OutputSettlerSimple(); outputToken = new MockERC20("TEST", "TEST", 18); mockCallbackExecutor = new MockCallbackExecutor(); swapper = makeAddr("swapper"); - outputSettlerCoinAddress = address(outputSettlerCoin); + outputSettlerSimpleAddress = address(outputSettlerSimple); outputTokenAddress = address(outputToken); mockCallbackExecutorAddress = address(mockCallbackExecutor); } @@ -59,13 +59,13 @@ contract OutputSettlerSimpleTestFill is Test { outputToken.mint(sender, amount); vm.prank(sender); - outputToken.approve(outputSettlerCoinAddress, amount); + outputToken.approve(outputSettlerSimpleAddress, amount); bytes memory fillerData = abi.encodePacked(filler); MandateOutput memory output = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(uint256(uint160(outputTokenAddress))), amount: amount, @@ -82,8 +82,8 @@ contract OutputSettlerSimpleTestFill is Test { outputTokenAddress, abi.encodeWithSignature("transferFrom(address,address,uint256)", sender, swapper, amount) ); - outputSettlerCoin.fill(orderId, output, type(uint48).max, fillerData); - vm.snapshotGasLastCall("outputSettler", "outputSettlerCoinFill"); + outputSettlerSimple.fill(orderId, output, type(uint48).max, fillerData); + vm.snapshotGasLastCall("outputSettler", "outputSettlerSimpleFill"); assertEq(outputToken.balanceOf(swapper), amount); assertEq(outputToken.balanceOf(sender), 0); @@ -116,12 +116,12 @@ contract OutputSettlerSimpleTestFill is Test { outputToken.mint(sender, amount); vm.prank(sender); - outputToken.approve(outputSettlerCoinAddress, amount); + outputToken.approve(outputSettlerSimpleAddress, amount); bytes memory context = abi.encodePacked(bytes1(0xe0), exclusiveFor, startTime); MandateOutput memory output = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(uint256(uint160(outputTokenAddress))), amount: amount, @@ -137,8 +137,8 @@ contract OutputSettlerSimpleTestFill is Test { if (exclusiveFor != solverIdentifier && currentTime < startTime) { vm.expectRevert(abi.encodeWithSignature("ExclusiveTo(bytes32)", exclusiveFor)); } - outputSettlerCoin.fill(orderId, output, type(uint48).max, fillerData); - vm.snapshotGasLastCall("outputSettler", "outputSettlerCoinFillExclusive"); + outputSettlerSimple.fill(orderId, output, type(uint48).max, fillerData); + vm.snapshotGasLastCall("outputSettler", "outputSettlerSimpleFillExclusive"); } function test_fill_mock_callback_executor( @@ -154,11 +154,11 @@ contract OutputSettlerSimpleTestFill is Test { outputToken.mint(sender, amount); vm.prank(sender); - outputToken.approve(outputSettlerCoinAddress, amount); + outputToken.approve(outputSettlerSimpleAddress, amount); MandateOutput memory output = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(uint256(uint160(outputTokenAddress))), amount: amount, @@ -187,7 +187,7 @@ contract OutputSettlerSimpleTestFill is Test { vm.expectEmit(); emit OutputFilled(orderId, filler, uint32(block.timestamp), output, amount); - outputSettlerCoin.fill(orderId, output, type(uint48).max, fillerData); + outputSettlerSimple.fill(orderId, output, type(uint48).max, fillerData); assertEq(outputToken.balanceOf(mockCallbackExecutorAddress), amount); assertEq(outputToken.balanceOf(sender), 0); @@ -233,7 +233,7 @@ contract OutputSettlerSimpleTestFill is Test { outputToken.mint(sender, finalAmount); vm.prank(sender); - outputToken.approve(outputSettlerCoinAddress, finalAmount); + outputToken.approve(outputSettlerSimpleAddress, finalAmount); context = abi.encodePacked( bytes1(0x01), bytes4(uint32(startTime)), bytes4(uint32(stopTime)), bytes32(uint256(slope)) @@ -242,7 +242,7 @@ contract OutputSettlerSimpleTestFill is Test { MandateOutput memory output = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(uint256(uint160(outputTokenAddress))), amount: amount, @@ -262,8 +262,8 @@ contract OutputSettlerSimpleTestFill is Test { outputTokenAddress, abi.encodeWithSignature("transferFrom(address,address,uint256)", sender, swapper, finalAmount) ); - outputSettlerCoin.fill(orderId, output, type(uint48).max, fillerData); - vm.snapshotGasLastCall("outputSettler", "outputSettlerCoinFillDutchAuction"); + outputSettlerSimple.fill(orderId, output, type(uint48).max, fillerData); + vm.snapshotGasLastCall("outputSettler", "outputSettlerSimpleFillDutchAuction"); assertEq(outputToken.balanceOf(swapper), finalAmount); assertEq(outputToken.balanceOf(sender), 0); @@ -309,7 +309,7 @@ contract OutputSettlerSimpleTestFill is Test { outputToken.mint(sender, finalAmount); vm.prank(sender); - outputToken.approve(outputSettlerCoinAddress, finalAmount); + outputToken.approve(outputSettlerSimpleAddress, finalAmount); context = abi.encodePacked( bytes1(0xe1), @@ -321,7 +321,7 @@ contract OutputSettlerSimpleTestFill is Test { } MandateOutput memory output = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(uint256(uint160(outputTokenAddress))), amount: amount, @@ -342,8 +342,8 @@ contract OutputSettlerSimpleTestFill is Test { abi.encodeWithSignature("transferFrom(address,address,uint256)", sender, swapper, finalAmount) ); - outputSettlerCoin.fill(orderId, output, type(uint48).max, fillerData); - vm.snapshotGasLastCall("outputSettler", "outputSettlerCoinFillExclusiveDutchAuction"); + outputSettlerSimple.fill(orderId, output, type(uint48).max, fillerData); + vm.snapshotGasLastCall("outputSettler", "outputSettlerSimpleFillExclusiveDutchAuction"); assertEq(outputToken.balanceOf(swapper), finalAmount); assertEq(outputToken.balanceOf(sender), 0); @@ -383,12 +383,12 @@ contract OutputSettlerSimpleTestFill is Test { outputToken.mint(sender, finalAmount); vm.prank(sender); - outputToken.approve(outputSettlerCoinAddress, finalAmount); + outputToken.approve(outputSettlerSimpleAddress, finalAmount); } MandateOutput memory output = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(uint256(uint160(outputTokenAddress))), amount: amount, @@ -399,7 +399,7 @@ contract OutputSettlerSimpleTestFill is Test { vm.prank(sender); if (startTime > currentTime) vm.expectRevert(abi.encodeWithSignature("ExclusiveTo(bytes32)", exclusiveFor)); - outputSettlerCoin.fill(orderId, output, type(uint48).max, abi.encodePacked(solverIdentifier)); + outputSettlerSimple.fill(orderId, output, type(uint48).max, abi.encodePacked(solverIdentifier)); } // --- FAILURE CASES --- // @@ -412,7 +412,7 @@ contract OutputSettlerSimpleTestFill is Test { MandateOutput memory output = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(uint256(uint160(outputTokenAddress))), amount: 0, @@ -425,7 +425,7 @@ contract OutputSettlerSimpleTestFill is Test { vm.expectRevert(ZeroValue.selector); vm.prank(sender); - outputSettlerCoin.fill(orderId, output, type(uint48).max, fillerData); + outputSettlerSimple.fill(orderId, output, type(uint48).max, fillerData); } function test_invalid_chain_id( @@ -450,7 +450,7 @@ contract OutputSettlerSimpleTestFill is Test { vm.expectRevert(abi.encodeWithSelector(WrongChain.selector, chainId, block.chainid)); vm.prank(sender); - outputSettlerCoin.fill(orderId, output, type(uint48).max, fillerData); + outputSettlerSimple.fill(orderId, output, type(uint48).max, fillerData); } function test_invalid_filler( @@ -459,9 +459,9 @@ contract OutputSettlerSimpleTestFill is Test { bytes32 orderId, bytes32 fillerOracleBytes ) public { - bytes32 outputSettlerCoinOracleBytes = bytes32(uint256(uint160(outputSettlerCoinAddress))); + bytes32 outputSettlerSimpleOracleBytes = bytes32(uint256(uint160(outputSettlerSimpleAddress))); - vm.assume(fillerOracleBytes != outputSettlerCoinOracleBytes); + vm.assume(fillerOracleBytes != outputSettlerSimpleOracleBytes); vm.assume(filler != bytes32(0)); MandateOutput memory output = MandateOutput({ oracle: bytes32(0), @@ -477,10 +477,10 @@ contract OutputSettlerSimpleTestFill is Test { bytes memory fillerData = abi.encodePacked(filler); vm.expectRevert( - abi.encodeWithSelector(WrongOutputSettler.selector, outputSettlerCoinOracleBytes, fillerOracleBytes) + abi.encodeWithSelector(WrongOutputSettler.selector, outputSettlerSimpleOracleBytes, fillerOracleBytes) ); vm.prank(sender); - outputSettlerCoin.fill(orderId, output, type(uint48).max, fillerData); + outputSettlerSimple.fill(orderId, output, type(uint48).max, fillerData); } function test_revert_fill_deadline_passed( @@ -496,7 +496,7 @@ contract OutputSettlerSimpleTestFill is Test { vm.warp(filledAt); MandateOutput memory output = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(0), amount: 0, @@ -509,7 +509,7 @@ contract OutputSettlerSimpleTestFill is Test { vm.expectRevert(abi.encodeWithSignature("FillDeadline()")); vm.prank(sender); - outputSettlerCoin.fill(orderId, output, fillDeadline, fillerData); + outputSettlerSimple.fill(orderId, output, fillDeadline, fillerData); } function test_fill_made_already( @@ -524,11 +524,11 @@ contract OutputSettlerSimpleTestFill is Test { outputToken.mint(sender, amount); vm.prank(sender); - outputToken.approve(outputSettlerCoinAddress, amount); + outputToken.approve(outputSettlerSimpleAddress, amount); MandateOutput memory output = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(uint256(uint160(outputTokenAddress))), amount: amount, @@ -540,11 +540,11 @@ contract OutputSettlerSimpleTestFill is Test { bytes memory fillerData = abi.encodePacked(filler); vm.prank(sender); - outputSettlerCoin.fill(orderId, output, type(uint48).max, fillerData); + outputSettlerSimple.fill(orderId, output, type(uint48).max, fillerData); bytes memory differentFillerData = abi.encodePacked(differentFiller); vm.prank(sender); - bytes32 alreadyFilledBy = outputSettlerCoin.fill(orderId, output, type(uint48).max, differentFillerData); + bytes32 alreadyFilledBy = outputSettlerSimple.fill(orderId, output, type(uint48).max, differentFillerData); assertNotEq(alreadyFilledBy, keccak256(abi.encodePacked(differentFiller, uint32(block.timestamp)))); assertEq(alreadyFilledBy, keccak256(abi.encodePacked(filler, uint32(block.timestamp)))); @@ -565,10 +565,10 @@ contract OutputSettlerSimpleTestFill is Test { outputToken.mint(sender, amount); vm.prank(sender); - outputToken.approve(outputSettlerCoinAddress, amount); + outputToken.approve(outputSettlerSimpleAddress, amount); MandateOutput memory output = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(uint256(uint160(outputTokenAddress))), amount: amount, @@ -581,7 +581,7 @@ contract OutputSettlerSimpleTestFill is Test { vm.prank(sender); vm.expectRevert(NotImplemented.selector); - outputSettlerCoin.fill(orderId, output, type(uint48).max, fillerData); + outputSettlerSimple.fill(orderId, output, type(uint48).max, fillerData); } // --- NATIVE TOKEN TESTS --- // @@ -608,7 +608,7 @@ contract OutputSettlerSimpleTestFill is Test { MandateOutput memory outputStruct = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(0), amount: amount, @@ -621,8 +621,8 @@ contract OutputSettlerSimpleTestFill is Test { vm.expectEmit(); emit OutputFilled(orderId, filler, uint32(block.timestamp), outputStruct, amount); - outputSettlerCoin.fill{ value: amount }(orderId, outputStruct, type(uint48).max, fillerData); - vm.snapshotGasLastCall("outputSettler", "outputSettlerCoinFillNative"); + outputSettlerSimple.fill{ value: amount }(orderId, outputStruct, type(uint48).max, fillerData); + vm.snapshotGasLastCall("outputSettler", "outputSettlerSimpleFillNative"); assertEq(swapper.balance, swapperBalanceBefore + amount); assertEq(sender.balance, 0); @@ -648,7 +648,7 @@ contract OutputSettlerSimpleTestFill is Test { MandateOutput memory outputStruct = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(0), amount: amount, @@ -661,7 +661,7 @@ contract OutputSettlerSimpleTestFill is Test { vm.expectEmit(); emit OutputFilled(orderId, filler, uint32(block.timestamp), outputStruct, amount); - outputSettlerCoin.fill{ value: totalValue }(orderId, outputStruct, type(uint48).max, fillerData); + outputSettlerSimple.fill{ value: totalValue }(orderId, outputStruct, type(uint48).max, fillerData); assertEq(swapper.balance, swapperBalanceBefore + amount); assertEq(sender.balance, senderBalanceBefore - amount); // Should get excess back @@ -683,7 +683,7 @@ contract OutputSettlerSimpleTestFill is Test { MandateOutput memory outputStruct = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(0), amount: amount, @@ -694,7 +694,7 @@ contract OutputSettlerSimpleTestFill is Test { vm.prank(sender); vm.expectRevert(abi.encodeWithSelector(InsufficientBalance.selector, sentValue, amount)); - outputSettlerCoin.fill{ value: sentValue }(orderId, outputStruct, type(uint48).max, fillerData); + outputSettlerSimple.fill{ value: sentValue }(orderId, outputStruct, type(uint48).max, fillerData); } function test_fill_native_token_zero_amount( @@ -713,7 +713,7 @@ contract OutputSettlerSimpleTestFill is Test { MandateOutput memory outputStruct = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(0), amount: 0, @@ -726,7 +726,7 @@ contract OutputSettlerSimpleTestFill is Test { vm.expectEmit(); emit OutputFilled(orderId, filler, uint32(block.timestamp), outputStruct, 0); - outputSettlerCoin.fill{ value: 1 ether }(orderId, outputStruct, type(uint48).max, fillerData); + outputSettlerSimple.fill{ value: 1 ether }(orderId, outputStruct, type(uint48).max, fillerData); assertEq(swapper.balance, swapperBalanceBefore); // No change for zero amount assertEq(sender.balance, senderBalanceBefore); // Should get full refund @@ -748,7 +748,7 @@ contract OutputSettlerSimpleTestFill is Test { MandateOutput memory outputStruct = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(0), amount: amount / 2, @@ -759,7 +759,7 @@ contract OutputSettlerSimpleTestFill is Test { // First fill vm.prank(sender); - outputSettlerCoin.fill{ value: amount / 2 }(orderId, outputStruct, type(uint48).max, fillerData); + outputSettlerSimple.fill{ value: amount / 2 }(orderId, outputStruct, type(uint48).max, fillerData); // Second fill attempt (should return existing fill record, no additional transfer) bytes memory differentFillerData = abi.encodePacked(differentFiller); @@ -767,7 +767,7 @@ contract OutputSettlerSimpleTestFill is Test { vm.prank(sender); bytes32 alreadyFilledBy = - outputSettlerCoin.fill{ value: amount / 2 }(orderId, outputStruct, type(uint48).max, differentFillerData); + outputSettlerSimple.fill{ value: amount / 2 }(orderId, outputStruct, type(uint48).max, differentFillerData); assertNotEq(alreadyFilledBy, keccak256(abi.encodePacked(differentFiller, uint32(block.timestamp)))); assertEq(alreadyFilledBy, keccak256(abi.encodePacked(filler, uint32(block.timestamp)))); @@ -792,7 +792,7 @@ contract OutputSettlerSimpleTestFill is Test { MandateOutput memory outputStruct = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(0), amount: amount, @@ -815,7 +815,7 @@ contract OutputSettlerSimpleTestFill is Test { vm.expectEmit(); emit OutputFilled(orderId, filler, uint32(block.timestamp), outputStruct, amount); - outputSettlerCoin.fill{ value: amount }(orderId, outputStruct, type(uint48).max, fillerData); + outputSettlerSimple.fill{ value: amount }(orderId, outputStruct, type(uint48).max, fillerData); assertEq(mockCallbackExecutorAddress.balance, amount); assertEq(sender.balance, 0); @@ -834,7 +834,7 @@ contract OutputSettlerSimpleTestFill is Test { outputToken.mint(sender, tokenAmount); vm.prank(sender); - outputToken.approve(outputSettlerCoinAddress, tokenAmount); + outputToken.approve(outputSettlerSimpleAddress, tokenAmount); vm.deal(sender, nativeValue); bytes memory fillerData = abi.encodePacked(filler); @@ -844,7 +844,7 @@ contract OutputSettlerSimpleTestFill is Test { MandateOutput memory outputStruct = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(uint256(uint160(outputTokenAddress))), amount: tokenAmount, @@ -862,7 +862,7 @@ contract OutputSettlerSimpleTestFill is Test { abi.encodeWithSignature("transferFrom(address,address,uint256)", sender, swapper, tokenAmount) ); - outputSettlerCoin.fill{ value: nativeValue }(orderId, outputStruct, type(uint48).max, fillerData); + outputSettlerSimple.fill{ value: nativeValue }(orderId, outputStruct, type(uint48).max, fillerData); // ERC20 transfer should work assertEq(outputToken.balanceOf(swapper), tokenAmount); diff --git a/test/output/OutputSettlerSimple.fillOrderOutputs.t.sol b/test/output/OutputSettlerSimple.fillOrderOutputs.t.sol index 44c051ed..0697442f 100644 --- a/test/output/OutputSettlerSimple.fillOrderOutputs.t.sol +++ b/test/output/OutputSettlerSimple.fillOrderOutputs.t.sol @@ -15,20 +15,20 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { bytes32 indexed orderId, bytes32 solver, uint32 timestamp, MandateOutput output, uint256 finalAmount ); - OutputSettlerSimple outputSettlerCoin; + OutputSettlerSimple outputSettlerSimple; MockERC20 outputToken; address swapper; - address outputSettlerCoinAddress; + address outputSettlerSimpleAddress; address outputTokenAddress; function setUp() public { - outputSettlerCoin = new OutputSettlerSimple(); + outputSettlerSimple = new OutputSettlerSimple(); outputToken = new MockERC20("TEST", "TEST", 18); swapper = makeAddr("swapper"); - outputSettlerCoinAddress = address(outputSettlerCoin); + outputSettlerSimpleAddress = address(outputSettlerSimple); outputTokenAddress = address(outputToken); } @@ -59,12 +59,12 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { outputToken.mint(sender, uint256(amount) + uint256(amount2)); vm.prank(sender); - outputToken.approve(outputSettlerCoinAddress, uint256(amount) + uint256(amount2)); + outputToken.approve(outputSettlerSimpleAddress, uint256(amount) + uint256(amount2)); MandateOutput[] memory outputs = new MandateOutput[](2); outputs[0] = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(uint256(uint160(outputTokenAddress))), amount: amount, @@ -75,7 +75,7 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { outputs[1] = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(uint256(uint160(outputTokenAddress))), amount: amount2, @@ -103,8 +103,8 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { uint256 prefillSnapshot = vm.snapshot(); vm.prank(sender); - outputSettlerCoin.fillOrderOutputs(orderId, outputs, type(uint48).max, fillerData); - vm.snapshotGasLastCall("outputSettler", "outputSettlerCoinfillOrderOutputs"); + outputSettlerSimple.fillOrderOutputs(orderId, outputs, type(uint48).max, fillerData); + vm.snapshotGasLastCall("outputSettler", "outputSettlerSimplefillOrderOutputs"); assertEq(outputToken.balanceOf(swapper), uint256(amount) + uint256(amount2)); assertEq(outputToken.balanceOf(sender), 0); @@ -112,20 +112,20 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { vm.revertTo(prefillSnapshot); // Fill the first output by someone else. The other outputs won't be filled. vm.prank(sender); - outputSettlerCoin.fill(orderId, outputs[0], type(uint48).max, nextFillerData); + outputSettlerSimple.fill(orderId, outputs[0], type(uint48).max, nextFillerData); vm.expectRevert(abi.encodeWithSignature("AlreadyFilled()")); vm.prank(sender); - outputSettlerCoin.fillOrderOutputs(orderId, outputs, type(uint48).max, fillerData); + outputSettlerSimple.fillOrderOutputs(orderId, outputs, type(uint48).max, fillerData); vm.revertTo(prefillSnapshot); // Fill the second output by someone else. The first output will be filled. vm.prank(sender); - outputSettlerCoin.fill(orderId, outputs[1], type(uint48).max, nextFillerData); + outputSettlerSimple.fill(orderId, outputs[1], type(uint48).max, nextFillerData); vm.prank(sender); - outputSettlerCoin.fillOrderOutputs(orderId, outputs, type(uint48).max, fillerData); + outputSettlerSimple.fillOrderOutputs(orderId, outputs, type(uint48).max, fillerData); } function test_revert_fill_batch_fillDeadline( @@ -139,13 +139,13 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { outputToken.mint(sender, uint256(amount)); vm.prank(sender); - outputToken.approve(outputSettlerCoinAddress, uint256(amount)); + outputToken.approve(outputSettlerSimpleAddress, uint256(amount)); MandateOutput[] memory outputs = new MandateOutput[](1); outputs[0] = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(uint256(uint160(outputTokenAddress))), amount: amount, @@ -161,7 +161,7 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { if (excess != 0) vm.expectRevert(abi.encodeWithSignature("FillDeadline()")); vm.prank(sender); - outputSettlerCoin.fillOrderOutputs(orderId, outputs, uint48(fillDeadline), fillerData); + outputSettlerSimple.fillOrderOutputs(orderId, outputs, uint48(fillDeadline), fillerData); } // --- NATIVE TOKEN BATCH TESTS --- // @@ -190,7 +190,7 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { outputs[0] = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(0), // native token amount: amount1, @@ -201,7 +201,7 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { outputs[1] = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(0), // native token amount: amount2, @@ -219,8 +219,8 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { emit OutputFilled(orderId, filler, uint32(block.timestamp), outputs[1], amount2); vm.prank(sender); - outputSettlerCoin.fillOrderOutputs{ value: totalAmount }(orderId, outputs, type(uint48).max, fillerData); - vm.snapshotGasLastCall("outputSettler", "outputSettlerCoinFillOrderOutputsNative"); + outputSettlerSimple.fillOrderOutputs{ value: totalAmount }(orderId, outputs, type(uint48).max, fillerData); + vm.snapshotGasLastCall("outputSettler", "outputSettlerSimpleFillOrderOutputsNative"); assertEq(swapper.balance, swapperBalanceBefore + totalAmount); assertEq(sender.balance, 0); @@ -239,14 +239,14 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { vm.deal(sender, nativeAmount); outputToken.mint(sender, tokenAmount); vm.prank(sender); - outputToken.approve(outputSettlerCoinAddress, tokenAmount); + outputToken.approve(outputSettlerSimpleAddress, tokenAmount); MandateOutput[] memory outputs = new MandateOutput[](2); // First output: native token outputs[0] = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(0), // native token amount: nativeAmount, @@ -258,7 +258,7 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { // Second output: ERC20 token outputs[1] = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(uint256(uint160(outputTokenAddress))), amount: tokenAmount, @@ -281,7 +281,7 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { ); vm.prank(sender); - outputSettlerCoin.fillOrderOutputs{ value: nativeAmount }(orderId, outputs, type(uint48).max, fillerData); + outputSettlerSimple.fillOrderOutputs{ value: nativeAmount }(orderId, outputs, type(uint48).max, fillerData); assertEq(swapper.balance, swapperBalanceBefore + nativeAmount); assertEq(outputToken.balanceOf(swapper), tokenAmount); @@ -310,7 +310,7 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { outputs[0] = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(0), // native token amount: amount1, @@ -321,7 +321,7 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { outputs[1] = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(0), // native token amount: amount2, @@ -336,7 +336,7 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { uint256 senderBalanceBefore = sender.balance; vm.prank(sender); - outputSettlerCoin.fillOrderOutputs{ value: totalSent }(orderId, outputs, type(uint48).max, fillerData); + outputSettlerSimple.fillOrderOutputs{ value: totalSent }(orderId, outputs, type(uint48).max, fillerData); assertEq(swapper.balance, swapperBalanceBefore + totalRequired); assertEq(sender.balance, senderBalanceBefore - totalRequired); // Should get excess back @@ -359,7 +359,7 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { outputs[0] = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(0), // native token amount: amount1, @@ -370,7 +370,7 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { outputs[1] = MandateOutput({ oracle: bytes32(0), - settler: bytes32(uint256(uint160(outputSettlerCoinAddress))), + settler: bytes32(uint256(uint160(outputSettlerSimpleAddress))), chainId: block.chainid, token: bytes32(0), // native token amount: amount2, @@ -383,6 +383,6 @@ contract OutputSettlerSimpleTestfillOrderOutputs is Test { vm.prank(sender); vm.expectRevert(abi.encodeWithSignature("InsufficientBalance(uint256,uint256)", 0, uint256(amount2))); - outputSettlerCoin.fillOrderOutputs{ value: sentValue }(orderId, outputs, type(uint48).max, fillerData); + outputSettlerSimple.fillOrderOutputs{ value: sentValue }(orderId, outputs, type(uint48).max, fillerData); } }