Skip to content
Open
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
32 changes: 25 additions & 7 deletions src/ComposableCoW.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,25 @@ contract ComposableCoW is ISafeSignatureVerifier {
* @param filledAmount raw `GPv2Settlement.filledAmount` for the discrete
* order (`type(uint256).max` when invalidated)
*/
/**
* @dev Registry-level restriction overlay. Like the fill overlay, this is
* orthogonal to the handler's verdict and never overwrites it: a
* restricted order keeps its true generator verdict (a guarded POST
* stays visible) but no signature is built while restricted. With
* restriction expressed here, `INVALID` is uniformly terminal - the
* swap guard is owner-reversible and its lifecycle is observable via
* `SwapGuardSet` (clear = address(0)).
*/
enum Restriction {
NONE, // No registry-level restriction
SWAP_GUARD // The owner's swap guard rejected the order
}

struct PollResult {
IConditionalOrderGenerator.GeneratorResult generator;
FillStatus fill;
uint256 filledAmount;
Restriction restriction;
}

// --- events
Expand Down Expand Up @@ -292,10 +307,9 @@ contract ComposableCoW is ISafeSignatureVerifier {
return (result, bytes(""));
}

// Check with the swap guard if the order is restricted or not
if (!_guardCheck(owner, ctx, params, offchainInput, result.generator.order)) {
result.generator.code = IConditionalOrderGenerator.GeneratorResultCode.INVALID;
result.generator.reasonCode = SwapGuardRestricted.selector;
// A restricted order is never signed; the generator verdict is
// preserved in the result (restriction is an overlay, not a verdict)
if (result.restriction != Restriction.NONE) {
return (result, bytes(""));
}

Expand All @@ -305,8 +319,9 @@ contract ComposableCoW is ISafeSignatureVerifier {
/**
* Check the current polling state of a conditional order
* @dev Returns the same composed result as `getTradeableOrderWithSignature`,
* without building the signature. Note the swap guard is not consulted
* here; it is enforced at signature build time and during settlement.
* without building the signature. The swap guard is consulted and
* reported via `restriction`; enforcement (signature withheld, revert
* at settlement) happens at signature build and during `verify`.
* @param owner of the order
* @param params `ConditionalOrderParams` for the order
* @param offchainInput any dynamic off-chain input for generating the discrete order
Expand Down Expand Up @@ -384,8 +399,11 @@ contract ComposableCoW is ISafeSignatureVerifier {
result.generator = IConditionalOrderGenerator(address(params.handler))
.poll(owner, msg.sender, ctx, params.staticInput, offchainInput);

// The fill overlay is only meaningful for a postable order
// The fill and restriction overlays are only meaningful for a postable order
if (result.generator.code == IConditionalOrderGenerator.GeneratorResultCode.POST) {
if (!_guardCheck(owner, ctx, params, offchainInput, result.generator.order)) {
result.restriction = Restriction.SWAP_GUARD;
}
uint256 filledAmount = _getFilledAmount(owner, result.generator.order);
result.filledAmount = filledAmount;
if (filledAmount == 0) {
Expand Down
14 changes: 10 additions & 4 deletions test/ComposableCoW.guards.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,11 @@ contract ComposableCoWGuardsTest is BaseComposableCoWTest {
// should not return a signature as the swap guard doesn't allow it
(ComposableCoW.PollResult memory guardedRes, bytes memory guardedSig) =
composableCow.getTradeableOrderWithSignature(address(safe1), params, bytes(""), proof);
assertEq(uint256(guardedRes.generator.code), uint256(IConditionalOrderGenerator.GeneratorResultCode.INVALID));
assertEq(guardedRes.generator.reasonCode, ComposableCoW.SwapGuardRestricted.selector);
// The generator verdict survives the restriction: POST stays visible,
// the overlay carries the guard, and no signature is built
assertEq(uint256(guardedRes.generator.code), uint256(IConditionalOrderGenerator.GeneratorResultCode.POST));
assertEq(guardedRes.generator.reasonCode, bytes4(0));
assertEq(uint256(guardedRes.restriction), uint256(ComposableCoW.Restriction.SWAP_GUARD));
assertEq(guardedSig.length, 0);

// should set the swap guard to the odd swap guard
Expand Down Expand Up @@ -135,8 +138,11 @@ contract ComposableCoWGuardsTest is BaseComposableCoWTest {
(ComposableCoW.PollResult memory guardedRes, bytes memory guardedSig) = composableCow.getTradeableOrderWithSignature(
address(safe1), params, abi.encode(orderOtherReceiver), new bytes32[](0)
);
assertEq(uint256(guardedRes.generator.code), uint256(IConditionalOrderGenerator.GeneratorResultCode.INVALID));
assertEq(guardedRes.generator.reasonCode, ComposableCoW.SwapGuardRestricted.selector);
// The generator verdict survives the restriction: POST stays visible,
// the overlay carries the guard, and no signature is built
assertEq(uint256(guardedRes.generator.code), uint256(IConditionalOrderGenerator.GeneratorResultCode.POST));
assertEq(guardedRes.generator.reasonCode, bytes4(0));
assertEq(uint256(guardedRes.restriction), uint256(ComposableCoW.Restriction.SWAP_GUARD));
assertEq(guardedSig.length, 0);

// should revert as the receiver is not the safe
Expand Down