Skip to content

Commit a856b5a

Browse files
authored
Merge pull request #3 from EthGlobalBangkok/refacto/contract-verifications
refacto: remove a verification from the order
2 parents 8983be8 + 5f16c73 commit a856b5a

File tree

7 files changed

+105
-43
lines changed

7 files changed

+105
-43
lines changed

.env.sample

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
POLYSWAP_HANDLER=0x0D92aFDB0D334c221A946927877b12157Bed2F5d # with polymarket mock
1+
POLYSWAP_HANDLER=0x868c662d186689f773fAb37ea617E7cbeB293D86
22
COMPOSABLE_COW=0xfdaFc9d1902f4e0b84f65F49f244b32b31013b74
33
POLYMARKET_MOCK=0x62e36fBc7D6518A3DeEA0f863f50c8AC4c4a7695 # Polymarket mock contract
44
POLYMARKET=0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ The `Polyswap` contract uses the Polymarket `CTFExchange` contract to check, via
2121
* the remaining amount is `0`.
2222

2323
If both conditions are met, the swap is executed using the Composable CoW Swap protocol.
24+
Both conditions being met means that the limit order has been filled and that the price of the limit order has been achieved.
2425

2526
## 🧪 Stack
2627

@@ -32,8 +33,8 @@ If both conditions are met, the swap is executed using the Composable CoW Swap p
3233

3334
All contracts are deployed on **Polygon** for compatibility with Polymarket's on-chain infrastructure.
3435

35-
Contract Address: `0xC75f4070B794cE1EC7273767a7d67354F845c7ce`
36-
View on [Sourcify](https://repo.sourcify.dev/137/0xC75f4070B794cE1EC7273767a7d67354F845c7ce)
36+
Contract Address: `0xE930639F0EbE2A7309f214e48e9F5ae3C5306Ff7`
37+
View on [Sourcify](https://repo.sourcify.dev/137/0xE930639F0EbE2A7309f214e48e9F5ae3C5306Ff7)
3738

3839
## 🧑‍💻 Authors
3940

script/SubmitSingleOrder.s.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pragma solidity >=0.8.0 <0.9.0;
44
import {Script} from "forge-std/Script.sol";
55
import {console} from "forge-std/console.sol";
66

7+
import {GPv2Order} from "cowprotocol/contracts/libraries/GPv2Order.sol";
78
import {IERC20} from "cowprotocol/contracts/interfaces/IERC20.sol";
89

910
// Safe contracts
@@ -51,7 +52,7 @@ contract SubmitSingleOrder is Script {
5152
staticInput: abi.encode(polyswapOrder)
5253
});
5354

54-
console.logBytes32(composableCow.hash(params));
55+
console.logBytes32(GPv2Order.hash(PolyswapOrder.orderFor(polyswapOrder), composableCow.domainSeparator()));
5556

5657
vm.startBroadcast(deployerPrivateKey);
5758

src/Polyswap.sol

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ contract Polyswap is BaseConditionalOrder {
5050
*/
5151
PolyswapOrder.Data memory polyswapOrder = abi.decode(staticInput, (PolyswapOrder.Data));
5252

53-
order = PolyswapOrder.orderFor(polyswapOrder, polymarket);
53+
order = PolyswapOrder.orderFor(polyswapOrder);
5454

5555
// check if the polymarket order is fulfilled
5656
OrderStatus memory status = polymarket.getOrderStatus(polyswapOrder.polymarketOrderHash);
@@ -61,4 +61,13 @@ contract Polyswap is BaseConditionalOrder {
6161
revert IConditionalOrder.PollTryNextBlock(CONDITION_NOT_MET);
6262
}
6363
}
64+
65+
/**
66+
* @dev Get the hash of the Polyswap order.
67+
* @param polyswapOrder The Polyswap order to get the hash of.
68+
* @return The hash of the Polyswap order.
69+
*/
70+
function getOrderHash(PolyswapOrder.Data memory polyswapOrder) public view returns (bytes32) {
71+
return GPv2Order.hash(PolyswapOrder.orderFor(polyswapOrder), composableCow.domainSeparator());
72+
}
6473
}

src/PolyswapOrder.sol

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ string constant INVALID_MIN_BUY_AMOUNT = "invalid min buy amount";
1818
string constant INVALID_POLYMARKET_ORDER_HASH = "invalid order hash";
1919
string constant INVALID_START_DATE = "invalid start date";
2020
string constant INVALID_END_DATE = "invalid end date";
21+
string constant INVALID_RECEIVER = "invalid receiver";
2122

2223
/**
2324
* @title Polyswap Order Library
@@ -46,32 +47,29 @@ library PolyswapOrder {
4647
* @dev revert if the order is invalid
4748
* @param self The PolyswapOrder order to validate
4849
*/
49-
function validate(Data memory self, Trading polymarket) internal view {
50+
function validate(Data memory self) internal view {
5051
if (self.sellToken == self.buyToken) revert IConditionalOrder.OrderNotValid(INVALID_SAME_TOKEN);
5152
if (address(self.sellToken) == address(0) || address(self.buyToken) == address(0)) {
5253
revert IConditionalOrder.OrderNotValid(INVALID_TOKEN);
5354
}
55+
if (self.receiver == address(0)) revert IConditionalOrder.OrderNotValid(INVALID_RECEIVER);
5456
if (self.t0 > block.timestamp) revert IConditionalOrder.OrderNotValid(INVALID_START_DATE);
5557
if (self.t <= self.t0 || self.t < block.timestamp) revert IConditionalOrder.OrderNotValid(INVALID_END_DATE);
5658
if (self.sellAmount < 0) revert IConditionalOrder.OrderNotValid(INVALID_SELL_AMOUNT);
5759
if (self.minBuyAmount < 0) revert IConditionalOrder.OrderNotValid(INVALID_MIN_BUY_AMOUNT);
5860

5961
// Check if the Polymarket order is valid and not filled or cancelled.
6062
if (self.polymarketOrderHash == 0) revert IConditionalOrder.OrderNotValid(INVALID_POLYMARKET_ORDER_HASH);
61-
OrderStatus memory order = polymarket.getOrderStatus(self.polymarketOrderHash);
62-
if (order.remaining == 0 && order.isFilledOrCancelled == false) {
63-
revert IConditionalOrder.OrderNotValid(INVALID_POLYMARKET_ORDER_HASH);
64-
}
6563
}
6664

6765
/**
6866
* @dev Generate the `GPv2Order` of the Polyswap order.
6967
* @param self The Polyswap order to generate the order for.
7068
* @return order The `GPv2Order` of the Polyswap order.
7169
*/
72-
function orderFor(Data memory self, Trading polymarket) internal view returns (GPv2Order.Data memory order) {
70+
function orderFor(Data memory self) internal view returns (GPv2Order.Data memory order) {
7371
// First, validate and revert if the order is invalid.
74-
validate(self, polymarket);
72+
validate(self);
7573

7674
order = GPv2Order.Data({
7775
sellToken: self.sellToken,

test/Notes.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@
2424
#### create a polyswap order
2525
`forge script ./script/SubmitSingleOrder.s.sol:SubmitSingleOrder --rpc-url https://polygon-rpc.com --broadcast --private-key $PRIVATE_KEY`
2626

27-
### set the order status to filled in the mock contract
27+
### set the order status to filled in the mock contract (simulate polymarket limit order filled)
2828
`cast send $POLYMARKET_MOCK "setOrderStatus(bytes32,bool,uint256)" $POLYMARKET_ORDER_HASH true 0 --private-key $PRIVATE_KEY --rpc-url https://polygon-rpc.com`

test/PolyswapTest.t.sol

Lines changed: 83 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {Enum} from "safe/common/Enum.sol";
1515
// Composable CoW
1616
import {IConditionalOrder, ComposableCoW} from "composable-cow/src/ComposableCoW.sol";
1717
import {IValueFactory} from "composable-cow/src/interfaces/IValueFactory.sol";
18-
import {ExtensibleFallbackHandler} from "safe/handler/ExtensibleFallbackHandler.sol";
18+
import {ExtensibleFallbackHandler, ERC1271} from "safe/handler/ExtensibleFallbackHandler.sol";
1919
import {SignatureVerifierMuxer} from "safe/handler/extensible/SignatureVerifierMuxer.sol";
2020
import {ISafeSignatureVerifier} from "safe/handler/extensible/SignatureVerifierMuxer.sol";
2121

@@ -50,8 +50,8 @@ contract PolyswapTest is Test {
5050
address public constant POLYGON_TIMESTAMP_VALUE_FACTORY = 0x52eD56Da04309Aca4c3FECC595298d80C2f16BAc; // TimestampValueFactory on Polygon
5151

5252
// BLINDSPOT: Need real Polymarket contract address on Polygon
53-
// Polymarket CTF Exchange contract address - needs to be updated with real address
54-
address public constant POLYGON_POLYMARKET_EXCHANGE = address(0); // UPDATE REQUIRED
53+
// Polymarket CTF Exchange contract address
54+
address public constant POLYGON_POLYMARKET_EXCHANGE = address(0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E);
5555

5656
// Test tokens on Polygon
5757
address public constant USDC_POLYGON = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174;
@@ -145,10 +145,10 @@ contract PolyswapTest is Test {
145145
PolyswapOrder.Data memory order = _createTestOrder();
146146

147147
// Should not revert on validation
148-
PolyswapOrder.validate(order, Trading(address(mockPolymarket)));
148+
PolyswapOrder.validate(order);
149149

150150
// Should generate valid GPv2Order
151-
GPv2Order.Data memory gpv2Order = PolyswapOrder.orderFor(order, Trading(address(mockPolymarket)));
151+
GPv2Order.Data memory gpv2Order = PolyswapOrder.orderFor(order);
152152

153153
assertEq(address(gpv2Order.sellToken), address(order.sellToken));
154154
assertEq(address(gpv2Order.buyToken), address(order.buyToken));
@@ -206,28 +206,28 @@ contract PolyswapTest is Test {
206206
invalidOrder.buyToken = invalidOrder.sellToken;
207207

208208
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, "same token"));
209-
PolyswapOrder.validate(invalidOrder, Trading(address(mockPolymarket)));
209+
PolyswapOrder.validate(invalidOrder);
210210

211211
// Test zero sell amount
212212
invalidOrder = _createTestOrder();
213213
invalidOrder.sellAmount = 0;
214214

215215
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, "invalid sell amount"));
216-
PolyswapOrder.validate(invalidOrder, Trading(address(mockPolymarket)));
216+
PolyswapOrder.validate(invalidOrder);
217217

218218
// Test zero buy amount
219219
invalidOrder = _createTestOrder();
220220
invalidOrder.minBuyAmount = 0;
221221

222222
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, "invalid min buy amount"));
223-
PolyswapOrder.validate(invalidOrder, Trading(address(mockPolymarket)));
223+
PolyswapOrder.validate(invalidOrder);
224224

225225
// Test invalid time range
226226
invalidOrder = _createTestOrder();
227227
invalidOrder.t = invalidOrder.t0 - 1;
228228

229229
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, "invalid end date"));
230-
PolyswapOrder.validate(invalidOrder, Trading(address(mockPolymarket)));
230+
PolyswapOrder.validate(invalidOrder);
231231
}
232232

233233
// ===== INTEGRATION TESTS WITH REAL CONTRACTS =====
@@ -325,7 +325,7 @@ contract PolyswapTest is Test {
325325
order.t = uint256(block.timestamp - 1); // Expired
326326

327327
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, INVALID_END_DATE));
328-
PolyswapOrder.validate(order, Trading(address(mockPolymarket)));
328+
PolyswapOrder.validate(order);
329329
}
330330

331331
/**
@@ -337,7 +337,7 @@ contract PolyswapTest is Test {
337337
order.polymarketOrderHash = bytes32(0);
338338

339339
vm.expectRevert(abi.encodeWithSelector(IConditionalOrder.OrderNotValid.selector, INVALID_POLYMARKET_ORDER_HASH));
340-
PolyswapOrder.validate(order, Trading(address(mockPolymarket)));
340+
PolyswapOrder.validate(order);
341341
}
342342

343343
// TODO more negative tests
@@ -352,13 +352,13 @@ contract PolyswapTest is Test {
352352

353353
// Measure validation gas
354354
uint256 gasStart = gasleft();
355-
PolyswapOrder.validate(order, Trading(address(mockPolymarket)));
355+
PolyswapOrder.validate(order);
356356
uint256 validationGas = gasStart - gasleft();
357357
console.log("Validation gas:", validationGas);
358358

359359
// Measure order generation gas
360360
gasStart = gasleft();
361-
PolyswapOrder.orderFor(order, Trading(address(mockPolymarket)));
361+
PolyswapOrder.orderFor(order);
362362
uint256 orderGenGas = gasStart - gasleft();
363363
console.log("Order generation gas:", orderGenGas);
364364

@@ -367,6 +367,59 @@ contract PolyswapTest is Test {
367367
assertTrue(orderGenGas < 30000, "Order generation gas too high");
368368
}
369369

370+
/**
371+
* @dev Test isValidSafeSignature function from ComposableCoW in real context
372+
*/
373+
function test_integration_isValidSafeSignature() public {
374+
// Setup: Polymarket order fulfilled so condition is met
375+
mockPolymarket.setOrderStatus(TEST_ORDER_HASH, true, 0);
376+
377+
PolyswapOrder.Data memory order = _createTestOrder();
378+
379+
// Create conditional order params
380+
IConditionalOrder.ConditionalOrderParams memory params = IConditionalOrder.ConditionalOrderParams({
381+
handler: IConditionalOrder(address(polyswap)),
382+
salt: keccak256(abi.encodePacked("signature-test", block.timestamp)),
383+
staticInput: abi.encode(order)
384+
});
385+
386+
// Create the conditional order first
387+
vm.prank(owner);
388+
testSafe.executeSingleOwner(
389+
address(composableCow), 0, abi.encodeCall(composableCow.create, (params, false)), Enum.Operation.Call, owner
390+
);
391+
392+
// Get the tradeable order that would be generated
393+
GPv2Order.Data memory gpv2Order =
394+
polyswap.getTradeableOrder(address(testSafe), address(this), bytes32(0), abi.encode(order), bytes(""));
395+
396+
// Create the payload for isValidSafeSignature
397+
ComposableCoW.PayloadStruct memory payload = ComposableCoW.PayloadStruct({
398+
proof: new bytes32[](0), // Empty proof for single order
399+
params: params,
400+
offchainInput: bytes("")
401+
});
402+
403+
// Hash the order for signature validation
404+
bytes32 orderHash = GPv2Order.hash(gpv2Order, composableCow.domainSeparator());
405+
406+
bytes32 domainSeparator = composableCow.domainSeparator();
407+
408+
// Call isValidSafeSignature - this should succeed
409+
bytes4 result = composableCow.isValidSafeSignature(
410+
testSafe,
411+
address(this),
412+
orderHash,
413+
domainSeparator,
414+
GPv2Order.TYPE_HASH,
415+
abi.encode(gpv2Order),
416+
abi.encode(payload)
417+
);
418+
419+
// Should return the ERC1271 magic value
420+
assertEq(result, ERC1271.isValidSignature.selector);
421+
}
422+
370423
// /**
371424
// * @dev Test with maximum uint256 values to check for overflows
372425
// */
@@ -409,7 +462,7 @@ contract PolyswapTest is Test {
409462
PolyswapOrder.Data memory order = PolyswapOrder.Data({
410463
sellToken: IERC20(USDC_POLYGON),
411464
buyToken: IERC20(USDT_POLYGON),
412-
receiver: address(0),
465+
receiver: address(1),
413466
sellAmount: sellAmount,
414467
minBuyAmount: minBuyAmount,
415468
t0: block.timestamp,
@@ -419,8 +472,8 @@ contract PolyswapTest is Test {
419472
});
420473

421474
// Should not revert with valid parameters
422-
PolyswapOrder.validate(order, Trading(address(mockPolymarket)));
423-
GPv2Order.Data memory gpv2Order = PolyswapOrder.orderFor(order, Trading(address(mockPolymarket)));
475+
PolyswapOrder.validate(order);
476+
GPv2Order.Data memory gpv2Order = PolyswapOrder.orderFor(order);
424477

425478
assertEq(gpv2Order.sellAmount, sellAmount);
426479
assertEq(gpv2Order.buyAmount, minBuyAmount);
@@ -469,19 +522,19 @@ contract PolyswapTest is Test {
469522
safeOwner
470523
);
471524

472-
// // Set domain verifier for CoW Protocol through the fallback handler
473-
// bytes32 domainSeparator = composableCow.domainSeparator();
474-
// safe.executeSingleOwner(
475-
// address(safe),
476-
// 0,
477-
// abi.encodeWithSelector(
478-
// SignatureVerifierMuxer.setDomainVerifier.selector,
479-
// domainSeparator,
480-
// ISafeSignatureVerifier(composableCow)
481-
// ),
482-
// Enum.Operation.Call,
483-
// safeOwner
484-
// );
525+
// Set domain verifier for CoW Protocol through the fallback handler
526+
bytes32 domainSeparator = composableCow.domainSeparator();
527+
safe.executeSingleOwner(
528+
address(safe),
529+
0,
530+
abi.encodeWithSelector(
531+
SignatureVerifierMuxer.setDomainVerifier.selector,
532+
domainSeparator,
533+
ISafeSignatureVerifier(composableCow)
534+
),
535+
Enum.Operation.Call,
536+
safeOwner
537+
);
485538

486539
vm.stopPrank();
487540
}
@@ -507,7 +560,7 @@ contract PolyswapTest is Test {
507560
return PolyswapOrder.Data({
508561
sellToken: IERC20(USDC_POLYGON),
509562
buyToken: IERC20(USDT_POLYGON),
510-
receiver: address(0),
563+
receiver: address(1),
511564
sellAmount: 100000, // 0.1 USDC (6 decimals)
512565
minBuyAmount: 80000, // 0.08 USDT (6 decimals)
513566
t0: block.timestamp,

0 commit comments

Comments
 (0)