Skip to content
Merged
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
47 changes: 44 additions & 3 deletions src/ComposableCow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ interface ISafeSignaturePayload {
contract ComposableCow is ISafeSignatureVerifier {
// --- errors
error ProofNotAuthed();
/**
* @dev A zero root (explicit clear) must carry an empty proof
*/
error ProofDataMalformed();
/**
* @dev A listed blob versioned hash is not attached to this transaction
*/
error BlobNotAttached();
error SingleOrderNotAuthed();
error SwapGuardRestricted();
error InvalidHandler();
Expand All @@ -50,10 +58,20 @@ contract ComposableCow is ISafeSignatureVerifier {
bytes offchainInput;
}

// A struct representing where to find the proofs
/**
* @dev Where to find the merkle payload document (the complete leaf set,
* see `docs/discovery.md` §3). Two orthogonal publication channels,
* each optional:
* - `uris`: mirrors for the payload document; all URIs MUST reference
* the same bytes. Never interpreted on-chain.
* - `blobVersionedHashes`: EIP-4844 blobs carrying the payload. Every
* listed hash is verified attached to THIS transaction, binding
* publication to authorization.
* Both empty = private: no discovery is expected.
*/
struct Proof {
uint256 location;
bytes data;
string[] uris;
bytes32[] blobVersionedHashes;
}

/**
Expand Down Expand Up @@ -382,10 +400,33 @@ contract ComposableCow is ISafeSignatureVerifier {
* @dev Write the root and emit, carrying the resolved cabinet context
*/
function _setRoot(bytes32 root, Proof calldata proof, bytes memory context) internal {
if (root == bytes32(0)) {
// Explicit clear: a zero root authorizes no leaf; publishing a
// payload for it is malformed
require(proof.uris.length == 0 && proof.blobVersionedHashes.length == 0, ProofDataMalformed());
}
// Publication is atomic with authorization: every listed blob must be
// attached to the transaction setting the root
for (uint256 i = 0; i < proof.blobVersionedHashes.length; i++) {
require(_blobAttached(proof.blobVersionedHashes[i]), BlobNotAttached());
}
roots[msg.sender] = root;
emit MerkleRootSet(msg.sender, root, proof, context);
}

/**
* @dev True iff `versionedHash` is among this transaction's blob hashes.
* `blobhash(i)` returns zero past the last blob, and a real versioned
* hash can never be zero (it always begins with the 0x01 version byte).
*/
function _blobAttached(bytes32 versionedHash) internal view returns (bool) {
for (uint256 i;; i++) {
bytes32 h = blobhash(i);
if (h == bytes32(0)) return false;
if (h == versionedHash) return true;
}
}

/**
* @dev Reject a null handler, then hash. Obtaining the `orderHash` that
* `_create` requires therefore cannot skip the check, and the context
Expand Down
18 changes: 12 additions & 6 deletions test/ComposableCoW.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,16 @@ contract ComposableCowTest is BaseComposableCowTest {
* @dev Can set the Merkle root for `owner`
*/
function test_setRoot_FuzzSetAndEmit(address owner, bytes32 root) public {
_setRoot(owner, root, ComposableCow.Proof({location: 0, data: ""}));
_setRoot(owner, root, ComposableCow.Proof({uris: new string[](0), blobVersionedHashes: new bytes32[](0)}));
}

function test_setRootWithContext_FuzzSetAndEmit(address owner, bytes32 root, bytes32 data) public {
_setRootWithContext(
owner, root, ComposableCow.Proof({location: 0, data: ""}), testContextValue, abi.encode(data)
owner,
root,
ComposableCow.Proof({uris: new string[](0), blobVersionedHashes: new bytes32[](0)}),
testContextValue,
abi.encode(data)
);
}

Expand All @@ -54,7 +58,8 @@ contract ComposableCowTest is BaseComposableCowTest {
composableCow.getTradeableOrderWithSignature(address(safe1), params, bytes(""), proof);

// should set the root correctly
ComposableCow.Proof memory proofStruct = ComposableCow.Proof({location: 0, data: ""});
ComposableCow.Proof memory proofStruct =
ComposableCow.Proof({uris: new string[](0), blobVersionedHashes: new bytes32[](0)});
_setRoot(address(safe1), root, proofStruct);

// should pass with the root correctly set
Expand Down Expand Up @@ -97,7 +102,8 @@ contract ComposableCowTest is BaseComposableCowTest {
composableCow.getTradeableOrderWithSignature(address(safe1), params, bytes(""), proof);

// should set the root correctly
ComposableCow.Proof memory proofStruct = ComposableCow.Proof({location: 0, data: ""});
ComposableCow.Proof memory proofStruct =
ComposableCow.Proof({uris: new string[](0), blobVersionedHashes: new bytes32[](0)});
_setRootWithContext(address(safe1), root, proofStruct, testContextValue, abi.encode(bytes32("testValue")));

// should pass with the root correctly set
Expand Down Expand Up @@ -289,7 +295,7 @@ contract ComposableCowTest is BaseComposableCowTest {
});

// should set the root
_setRoot(owner, root, ComposableCow.Proof({location: 0, data: ""}));
_setRoot(owner, root, ComposableCow.Proof({uris: new string[](0), blobVersionedHashes: new bytes32[](0)}));

// should revert as the proof is invalid
vm.expectRevert(ComposableCow.ProofNotAuthed.selector);
Expand Down Expand Up @@ -423,7 +429,7 @@ contract ComposableCowTest is BaseComposableCowTest {
});

// should set the root
_setRoot(owner, root, ComposableCow.Proof({location: 0, data: ""}));
_setRoot(owner, root, ComposableCow.Proof({uris: new string[](0), blobVersionedHashes: new bytes32[](0)}));

// should revert as the proof is invalid
vm.expectRevert(ComposableCow.ProofNotAuthed.selector);
Expand Down
Loading
Loading