From 9c786417accc4f8725f21506b7897506cea79a73 Mon Sep 17 00:00:00 2001 From: brunota20 Date: Wed, 24 Jun 2026 11:58:40 -0300 Subject: [PATCH] feat(ComposableCoW): getOrderInfo combined accessor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a single-call view function that returns the metadata a watch tower needs per watch: the params hash, the single-order authorisation flag, the cabinet value, and the owner's swap guard. ```solidity struct OrderInfo { bytes32 hash; bool authorized; bytes32 cabinetValue; ISwapGuard swapGuard; } function getOrderInfo(address owner, IConditionalOrder.ConditionalOrderParams calldata params) external view returns (OrderInfo memory info); ``` Combines what would otherwise be 3-4 separate `eth_call`s (`hash()`, `singleOrders`, `cabinet`, `swapGuards`) into one round trip. Returns inert defaults for fields that don't apply to the given owner / params combination — no revert path. Pure addition. No existing function/event/symbol changed. Addresses M2 grant deliverable "optimised getter functions". --- src/ComposableCoW.sol | 39 +++++++++++++++++++++++++++ test/ComposableCoW.t.sol | 58 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/ComposableCoW.sol b/src/ComposableCoW.sol index 6aaa8b6..b89d9ae 100644 --- a/src/ComposableCoW.sol +++ b/src/ComposableCoW.sol @@ -44,6 +44,18 @@ contract ComposableCoW is ISafeSignatureVerifier { bytes data; } + // Combined accessor for the metadata a watch tower needs per watch. + + /// @dev Single-call view of the per-watch metadata. Returns inert defaults for fields + /// that don't apply to the given owner / params combination (e.g. + /// `swapGuard == ISwapGuard(address(0))` when no guard is set). + struct OrderInfo { + bytes32 hash; // H(params) — the same value `_auth` would derive + bool authorized; // `singleOrders[owner][hash]` — meaningful only when not using a merkle proof + bytes32 cabinetValue; // `cabinet[owner][hash]` + ISwapGuard swapGuard; // `swapGuards[owner]` + } + // --- events // An event emitted when a user sets their merkle root @@ -278,6 +290,33 @@ contract ComposableCoW is ISafeSignatureVerifier { return keccak256(abi.encode(params)); } + /** + * @notice Single-call accessor returning the metadata a watch tower needs per watch: + * the params hash, the single-order authorisation flag, the cabinet value (if + * any), and the owner's swap guard. Combines what would otherwise be 3-4 + * separate `eth_call`s (`hash()`, `singleOrders`, `cabinet`, `swapGuards`) into + * one round trip. + * + * Returns inert defaults for fields that don't apply to the given owner / + * params combination — e.g. `authorized == false` when not authorised, + * `cabinetValue == bytes32(0)` when no cabinet slot is set, + * `swapGuard == ISwapGuard(address(0))` when no guard is set. + * + * @param owner The owner of the conditional order. + * @param params The `ConditionalOrderParams` identifying the order. + * @return info The combined per-watch metadata. + */ + function getOrderInfo(address owner, IConditionalOrder.ConditionalOrderParams calldata params) + external + view + returns (OrderInfo memory info) + { + info.hash = hash(params); + info.authorized = singleOrders[owner][info.hash]; + info.cabinetValue = cabinet[owner][info.hash]; + info.swapGuard = swapGuards[owner]; + } + // --- internal functions /** diff --git a/test/ComposableCoW.t.sol b/test/ComposableCoW.t.sol index f1c3ec4..bd45a54 100644 --- a/test/ComposableCoW.t.sol +++ b/test/ComposableCoW.t.sol @@ -11,7 +11,9 @@ import { INVALID_HASH, BaseComposableCoWTest, Safe, - TestNonSafeWallet + TestNonSafeWallet, + TestSwapGuard, + ISwapGuard } from "./ComposableCoW.base.t.sol"; contract ComposableCoWTest is BaseComposableCoWTest { @@ -478,4 +480,58 @@ contract ComposableCoWTest is BaseComposableCoWTest { ERC1271.isValidSignature.selector ); } + + // --- combined `getOrderInfo` accessor --- + + /// @dev After `create()`, `getOrderInfo` returns the params hash, `authorized = true`, + /// the (zero) cabinet value, and the (unset) swap guard. + function test_getOrderInfo_after_create_returns_all_fields() public { + IConditionalOrder.ConditionalOrderParams memory params = getPassthroughOrder(); + bytes32 expectedHash = composableCow.hash(params); + address owner = address(safe1); + + _create(owner, params, false); + + ComposableCoW.OrderInfo memory info = composableCow.getOrderInfo(owner, params); + + assertEq(info.hash, expectedHash, "hash mismatch"); + assertTrue(info.authorized, "expected authorized = true"); + assertEq(info.cabinetValue, bytes32(0), "expected zero cabinet for plain create"); + assertEq(address(info.swapGuard), address(0), "expected unset swap guard"); + } + + /// @dev For an owner that never authorised the params, all relevant fields are inert + /// defaults (no revert). + function test_getOrderInfo_for_unauthorized_owner_returns_inert_defaults() public { + IConditionalOrder.ConditionalOrderParams memory params = getPassthroughOrder(); + address owner = address(safe1); + + // Note: NOT authorising the order here. + ComposableCoW.OrderInfo memory info = composableCow.getOrderInfo(owner, params); + + // hash is deterministic over params alone; the rest must be zero / unset. + assertEq(info.hash, composableCow.hash(params), "hash should match"); + assertTrue(!info.authorized, "expected authorized = false"); + assertEq(info.cabinetValue, bytes32(0), "expected zero cabinet"); + assertEq(address(info.swapGuard), address(0), "expected unset swap guard"); + } + + /// @dev When the owner has set a swap guard, `getOrderInfo` surfaces its address. + function test_getOrderInfo_with_swap_guard_set_returns_guard_address() public { + IConditionalOrder.ConditionalOrderParams memory params = getPassthroughOrder(); + address owner = address(safe1); + + // Authorise the order so `authorized` is also non-default. + _create(owner, params, false); + + // Set a swap guard for the owner. + TestSwapGuard guard = new TestSwapGuard(2); + _setSwapGuard(owner, ISwapGuard(address(guard))); + + ComposableCoW.OrderInfo memory info = composableCow.getOrderInfo(owner, params); + + assertEq(info.hash, composableCow.hash(params), "hash should match"); + assertTrue(info.authorized, "expected authorized = true"); + assertEq(address(info.swapGuard), address(guard), "expected guard address"); + } }