Skip to content

Commit d828cdb

Browse files
committed
fix comments from federico
* add minor edge case checks (found a bug in max length!) * better documentation * clean up usages of `abi.encodeWithSelector` as appropriate * fix licenses
1 parent 5e3e4ee commit d828cdb

File tree

4 files changed

+166
-47
lines changed

4 files changed

+166
-47
lines changed

src/CowWrapper.sol

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
// SPDX-License-Identifier: MIT OR Apache-2.0
2-
pragma solidity >=0.7.6 <0.9.0;
3-
pragma abicoder v2;
2+
pragma solidity >=0.8.0 <0.9.0;
43

54
/**
6-
* @title CoW Wrapper all-in-one integration file
7-
* @author CoW Protocol Developers
8-
* @notice This file is completely self-contained (ie no dependencies) and can be portably copied to whatever projects it is needed.
5+
* CoW Wrapper all-in-one integration file
6+
* CoW Protocol Developers
7+
* This file is completely self-contained (ie no dependencies) and can be portably copied to whatever projects it is needed.
98
* It contains:
109
* * CowWrapper -- an abstract base contract which should be inherited by all wrappers
1110
* * ICowWrapper -- the required interface for all wrappers
@@ -41,7 +40,7 @@ interface ICowSettlement {
4140
bytes signature;
4241
}
4342

44-
/// @notice Interaction data structure for pre/intra/post-settlement hooks
43+
/// @notice Interaction data structure for pre/intra/post-settlement actions which are supplied by the solver to complete the user request
4544
struct Interaction {
4645
address target;
4746
uint256 value;
@@ -89,7 +88,7 @@ interface ICowWrapper {
8988
/// before calling the next wrapper or settlement contract in the chain.
9089
/// @param settleData ABI-encoded call to ICowSettlement.settle() containing trade data
9190
/// @param wrapperData Encoded data for this wrapper and the chain of next wrappers/settlement.
92-
/// Format: [2-byte len][wrapper-specific-data][next-address]([2-byte len][wrapper-specific-data][next-address]...)
91+
/// Format: [2-byte len][user-supplied wrapper specific data][next address]([2-byte len][user supplied wrapper specific data]...)
9392
function wrappedSettle(bytes calldata settleData, bytes calldata wrapperData) external;
9493
}
9594

@@ -127,17 +126,17 @@ abstract contract CowWrapper is ICowWrapper {
127126
/// @notice Initiates a wrapped settlement call
128127
/// @dev Entry point for solvers to execute wrapped settlements. Verifies the caller is a solver,
129128
/// validates wrapper data, then delegates to _wrap() for custom logic.
130-
/// @param settleData ABI-encoded call to ICowSettlement.settle() containing trade data
129+
/// @param settleData ABI-encoded call to ICowSettlement.settle() containing trade data. NOTE: wrappers may read this data, but it should not be trusted for anything of great importance (ex. destination of funds) because a malicious solver can modify this data later in the chain.
131130
/// @param wrapperData Encoded data for this wrapper and the chain of next wrappers/settlement.
132-
/// Format: [2-byte len][wrapper-specific-data][next-address]([2-byte len][wrapper-specific-data][next-address]...)
131+
/// Format: [2-byte len][user-supplied wrapper specific data][next address]([2-byte len][user supplied wrapper specific data]...)
133132
function wrappedSettle(bytes calldata settleData, bytes calldata wrapperData) external {
134133
// Revert if not a valid solver
135134
require(AUTHENTICATOR.isSolver(msg.sender), NotASolver(msg.sender));
136135

137136
// Find out how long the next wrapper data is supposed to be
138137
// We use 2 bytes to decode the length of the wrapper data because it allows for up to 64KB of data for each wrapper.
139138
// This should be plenty of length for all identified use-cases of wrappers in the forseeable future.
140-
uint16 nextWrapperDataLen = uint16(bytes2(wrapperData[0:2]));
139+
uint256 nextWrapperDataLen = uint16(bytes2(wrapperData[0:2]));
141140

142141
// Delegate to the wrapper's custom logic
143142
uint256 remainingWrapperDataStart = 2 + nextWrapperDataLen;

test/EmptyWrapper.sol

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
// SPDX-License-Identifier: GPL-3.0-or-later
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
22
pragma solidity ^0.8;
3-
pragma abicoder v2;
43

54
import {ICowSettlement, CowWrapper} from "../src/CowWrapper.sol";
65

test/unit/CowWrapper.t.sol

Lines changed: 155 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
// SPDX-License-Identifier: GPL-3.0-or-later
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
22
pragma solidity ^0.8;
33

44
import {Test} from "forge-std/Test.sol";
5-
import {CowWrapper, ICowSettlement, ICowAuthentication} from "../../src/CowWrapper.sol";
5+
import {CowWrapper, ICowWrapper, ICowSettlement, ICowAuthentication} from "../../src/CowWrapper.sol";
66
import {EmptyWrapper} from "../EmptyWrapper.sol";
77

88
import {MockWrapper, MockCowSettlement, MockCowAuthentication} from "./mocks/MockCowProtocol.sol";
@@ -28,9 +28,11 @@ contract CowWrapperTest is Test {
2828
authenticator.setSolver(solver, true);
2929

3030
// Create test wrapper and three EmptyWrapper instances with the settlement contract
31-
wrapper1 = new MockWrapper(ICowSettlement(address(mockSettlement)), 65536);
32-
wrapper2 = new MockWrapper(ICowSettlement(address(mockSettlement)), 65536);
33-
wrapper3 = new MockWrapper(ICowSettlement(address(mockSettlement)), 65536);
31+
// (use type(uint16).max because it will force consuming all the wrapper data, which is
32+
// most useful for these tests)
33+
wrapper1 = new MockWrapper(ICowSettlement(address(mockSettlement)), type(uint16).max);
34+
wrapper2 = new MockWrapper(ICowSettlement(address(mockSettlement)), type(uint16).max);
35+
wrapper3 = new MockWrapper(ICowSettlement(address(mockSettlement)), type(uint16).max);
3436

3537
// Add all wrappers as solvers
3638
authenticator.setSolver(address(wrapper1), true);
@@ -51,27 +53,39 @@ contract CowWrapperTest is Test {
5153
address[] memory tokens = new address[](tokenCount);
5254
uint256[] memory clearingPrices = new uint256[](tokenCount);
5355
for (uint256 i = 0; i < tokenCount; i++) {
54-
tokens[i] = makeAddr(string(abi.encodePacked("Settle Token #", vm.toString(i + 1))));
56+
tokens[i] = makeAddr(string.concat("Settle Token #", vm.toString(i + 1)));
5557
clearingPrices[i] = 100 * (i + 1);
5658
}
57-
return abi.encodeWithSelector(
58-
ICowSettlement.settle.selector, tokens, clearingPrices, new ICowSettlement.Trade[](0), _emptyInteractions()
59+
return abi.encodeCall(
60+
ICowSettlement.settle, (tokens, clearingPrices, new ICowSettlement.Trade[](0), _emptyInteractions())
61+
);
62+
}
63+
64+
function test_verifyInitialState() public {
65+
assertEq(
66+
address(wrapper1.SETTLEMENT()),
67+
address(mockSettlement),
68+
"Settlement contract should be initialized correctly"
69+
);
70+
assertEq(
71+
address(wrapper1.AUTHENTICATOR()),
72+
address(authenticator),
73+
"Authenticator contract should be initialized from the settlement contract"
5974
);
6075
}
6176

6277
function test_next_CallsWrapperAndThenNextSettlement() public {
6378
bytes memory settleData = abi.encodePacked(_createSimpleSettleData(1), hex"123456");
64-
bytes memory secondCallWrapperData = hex"0003098765";
65-
bytes memory wrapperData = abi.encodePacked(hex"00021234", address(wrapper1), secondCallWrapperData);
79+
// here we encode [2-byte len] followed by the actual wrapper data (which is 3 bytes, 6 chars hex)
80+
bytes memory secondCallWrapperData = abi.encodePacked(uint16(3), hex"098765");
81+
// here we encode [2-byte len] followed by the actual wrapper data (which is 2 bytes, 4 chars hex), and build the chain
82+
bytes memory wrapperData = abi.encodePacked(uint16(2), hex"1234", address(wrapper1), secondCallWrapperData);
6683

67-
// the wrapper gets called exactly twice (once below and again inside the wrapper data calling self)
68-
vm.expectCall(address(wrapper1), 0, abi.encodeWithSelector(wrapper1.wrappedSettle.selector), 2);
84+
// verify the outside wrapper call data
85+
vm.expectCall(address(wrapper1), abi.encodeCall(ICowWrapper.wrappedSettle, (settleData, wrapperData)));
6986

7087
// verify the internal wrapper call data
71-
vm.expectCall(
72-
address(wrapper1),
73-
abi.encodeWithSelector(wrapper1.wrappedSettle.selector, settleData, secondCallWrapperData)
74-
);
88+
vm.expectCall(address(wrapper1), abi.encodeCall(ICowWrapper.wrappedSettle, (settleData, secondCallWrapperData)));
7589

7690
// the settlement contract gets called once after wrappers (including the surplus data at the end)
7791
vm.expectCall(address(mockSettlement), 0, settleData, 1);
@@ -108,20 +122,22 @@ contract CowWrapperTest is Test {
108122
settlement.clearingPrices[0] = 100;
109123
settlement.clearingPrices[1] = 200;
110124

111-
settlement.trades = new ICowSettlement.Trade[](1);
112-
settlement.trades[0] = ICowSettlement.Trade({
113-
sellTokenIndex: 0,
114-
buyTokenIndex: 1,
115-
receiver: address(0x123),
116-
sellAmount: 1000,
117-
buyAmount: 900,
118-
validTo: uint32(block.timestamp + 1000),
119-
appData: bytes32(uint256(1)),
120-
feeAmount: 10,
121-
flags: 0,
122-
executedAmount: 0,
123-
signature: hex"aabbccddee"
124-
});
125+
settlement.trades = new ICowSettlement.Trade[](10);
126+
for (uint256 i = 0; i < 10; i++) {
127+
settlement.trades[i] = ICowSettlement.Trade({
128+
sellTokenIndex: 0,
129+
buyTokenIndex: 1,
130+
receiver: address(0x123),
131+
sellAmount: 1000 * i,
132+
buyAmount: 900 * i,
133+
validTo: uint32(block.timestamp + 1000),
134+
appData: bytes32(uint256(1)),
135+
feeAmount: 10,
136+
flags: 0,
137+
executedAmount: 0,
138+
signature: hex"aabbccddee"
139+
});
140+
}
125141

126142
settlement.interactions = [
127143
new ICowSettlement.Interaction[](0),
@@ -144,10 +160,13 @@ contract CowWrapperTest is Test {
144160
(address target, bytes memory fullCalldata) =
145161
CowWrapperHelpers.encodeWrapperCall(wrappers, datas, address(mockSettlement), settlement);
146162

147-
// all the wrappers gets called, with wrapper 1 called twice
148-
vm.expectCall(address(wrapper1), 0, abi.encodeWithSelector(wrapper1.wrappedSettle.selector), 2);
149-
vm.expectCall(address(wrapper2), 0, abi.encodeWithSelector(wrapper1.wrappedSettle.selector), 1);
150-
vm.expectCall(address(wrapper3), 0, abi.encodeWithSelector(wrapper1.wrappedSettle.selector), 1);
163+
// all the wrappers get called, with wrapper 1 called twice
164+
165+
// we only want to verify that wrappedSettle was called. (not the specific data passed to wrappedSettle)
166+
bytes memory wrappedSettleSelector = abi.encodePacked(ICowWrapper.wrappedSettle.selector);
167+
vm.expectCall(address(wrapper1), 0, wrappedSettleSelector, 2);
168+
vm.expectCall(address(wrapper2), 0, wrappedSettleSelector, 1);
169+
vm.expectCall(address(wrapper3), 0, wrappedSettleSelector, 1);
151170

152171
// the settlement gets called with the full data
153172
vm.expectCall(address(mockSettlement), new bytes(0));
@@ -157,4 +176,106 @@ contract CowWrapperTest is Test {
157176
(bool success,) = target.call(fullCalldata);
158177
assertTrue(success, "Chained wrapper call should succeed");
159178
}
179+
180+
function test_wrappedSettle_RevertsOnZeroLengthWrapperData() public {
181+
bytes memory settleData = _createSimpleSettleData(0);
182+
bytes memory wrapperData = hex""; // Completely empty wrapper data
183+
184+
vm.prank(solver);
185+
vm.expectRevert(); // Should revert with out-of-bounds array access
186+
wrapper1.wrappedSettle(settleData, wrapperData);
187+
}
188+
189+
function test_wrappedSettle_RevertsOnOneByteWrapperData() public {
190+
bytes memory settleData = _createSimpleSettleData(0);
191+
bytes memory wrapperData = hex"01"; // Only 1 byte - not enough to read the 2-byte length
192+
193+
vm.prank(solver);
194+
vm.expectRevert(); // Should revert with out-of-bounds array access
195+
wrapper1.wrappedSettle(settleData, wrapperData);
196+
}
197+
198+
function test_wrappedSettle_SucceedsWithZeroLengthIndicator() public {
199+
bytes memory settleData = _createSimpleSettleData(0);
200+
bytes memory wrapperData = hex"0000"; // 2 bytes indicating 0-length wrapper data
201+
202+
// Should call settlement directly with no wrapper-specific data
203+
vm.expectCall(address(mockSettlement), 0, settleData, 1);
204+
205+
vm.prank(solver);
206+
wrapper1.wrappedSettle(settleData, wrapperData);
207+
}
208+
209+
function test_wrappedSettle_SucceedsWithMaximumLengthWrapperData() public {
210+
bytes memory settleData = _createSimpleSettleData(0);
211+
212+
// Create maximum length wrapper data (65535 bytes)
213+
// Format: [2-byte length = 0xFFFF][65535 bytes of data]
214+
bytes memory maxData = new bytes(65535);
215+
for (uint256 i = 0; i < 65535; i++) {
216+
maxData[i] = bytes1(uint8(i % 256));
217+
}
218+
219+
bytes memory wrapperData = abi.encodePacked(uint16(65535), maxData);
220+
221+
// Should successfully parse the maximum length data and call settlement
222+
vm.expectCall(address(mockSettlement), 0, settleData, 1);
223+
224+
vm.prank(solver);
225+
wrapper1.wrappedSettle(settleData, wrapperData);
226+
}
227+
228+
function test_wrappedSettle_RevertsWhenDataShorterThanIndicated() public {
229+
bytes memory settleData = _createSimpleSettleData(0);
230+
231+
// Wrapper data claims to be 100 bytes but only provides 50
232+
bytes memory shortData = new bytes(50);
233+
bytes memory wrapperData = abi.encodePacked(uint16(100), shortData);
234+
235+
vm.prank(solver);
236+
vm.expectRevert(); // Should revert with out-of-bounds array access
237+
wrapper1.wrappedSettle(settleData, wrapperData);
238+
}
239+
240+
function test_wrappedSettle_SucceedsWithMaxLengthAndNextWrapper() public {
241+
bytes memory settleData = _createSimpleSettleData(0);
242+
243+
// Create maximum length wrapper data followed by next wrapper address
244+
bytes memory maxData = new bytes(65535);
245+
for (uint256 i = 0; i < 65535; i++) {
246+
maxData[i] = bytes1(uint8(i % 256));
247+
}
248+
249+
// Format: [2-byte length = 0xFFFF][65535 bytes of data][20-byte next wrapper address][remaining data]
250+
bytes memory nextWrapperData = hex"00030000FF"; // 3 bytes of data for next wrapper
251+
bytes memory wrapperData = abi.encodePacked(type(uint16).max, maxData, address(wrapper2), nextWrapperData);
252+
253+
// Should call wrapper2 with the remaining data
254+
vm.expectCall(address(wrapper2), 0, abi.encodePacked(ICowWrapper.wrappedSettle.selector), 1);
255+
256+
vm.prank(solver);
257+
wrapper1.wrappedSettle(settleData, wrapperData);
258+
}
259+
260+
function test_wrappedSettle_RevertsWithInsufficientLengthData() public {
261+
bytes memory settleData = _createSimpleSettleData(0);
262+
263+
// Format: [1-byte length = 1 (insufficient)]
264+
bytes memory wrapperData = hex"01";
265+
266+
vm.expectRevert(new bytes(0));
267+
vm.prank(solver);
268+
wrapper1.wrappedSettle(settleData, wrapperData);
269+
}
270+
271+
function test_wrappedSettle_RevertsWithInsufficientCallData() public {
272+
bytes memory settleData = _createSimpleSettleData(0);
273+
274+
// Format: [2-byte length = 0xa][9 bytes of data (insufficient)]
275+
bytes memory wrapperData = hex"000A123412341234123412";
276+
277+
vm.expectRevert(new bytes(0));
278+
vm.prank(solver);
279+
wrapper1.wrappedSettle(settleData, wrapperData);
280+
}
160281
}

test/unit/mocks/MockCowProtocol.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-License-Identifier: GPL-2.0-or-later
1+
// SPDX-License-Identifier: MIT OR Apache-2.0
22
pragma solidity ^0.8;
33

44
import {ICowSettlement, ICowAuthentication, CowWrapper} from "../../../src/CowWrapper.sol";

0 commit comments

Comments
 (0)