Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions src/ComposableCoW.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

/**
Expand Down
58 changes: 57 additions & 1 deletion test/ComposableCoW.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ import {
INVALID_HASH,
BaseComposableCoWTest,
Safe,
TestNonSafeWallet
TestNonSafeWallet,
TestSwapGuard,
ISwapGuard
} from "./ComposableCoW.base.t.sol";

contract ComposableCoWTest is BaseComposableCoWTest {
Expand Down Expand Up @@ -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");
}
}