Skip to content

Commit ebc0d2d

Browse files
feat: add polymer contracts for ERC-7683
1 parent 0e37a47 commit ebc0d2d

File tree

7 files changed

+1460
-30
lines changed

7 files changed

+1460
-30
lines changed

.gitmodules

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[submodule "solidity/lib/prover-contracts"]
2+
path = solidity/lib/prover-contracts
3+
url = https://github.com/polymerdao/prover-contracts
4+
[submodule "solidity/lib/optimism"]
5+
path = solidity/lib/optimism
6+
url = https://github.com/ethereum-optimism/optimism

solidity/lib/optimism

Submodule optimism added at 910c9ad

solidity/lib/prover-contracts

Submodule prover-contracts added at abb1faf

solidity/remappings.txt

+3
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,6 @@ forge-std/=node_modules/forge-std/src/
33
@hyperlane-xyz/=node_modules/@hyperlane-xyz/core/contracts/
44
contracts/=node_modules/@hyperlane-xyz/core/contracts/
55
@uniswap/=node_modules/@uniswap/
6+
@polymerdao/prover-contracts=lib/prover-contracts/contracts/
7+
optimism/=lib/optimism/packages/contracts-bedrock/src/
8+

solidity/src/Polymer7683.sol

+221
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity 0.8.25;
3+
4+
import { ICrossL2ProverV2 } from "@polymerdao/prover-contracts/interfaces/ICrossL2ProverV2.sol";
5+
import { BasicSwap7683 } from "./BasicSwap7683.sol";
6+
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
7+
8+
/**
9+
* @title Polymer7683
10+
* @author Assistant
11+
* @notice This contract builds on top of BasicSwap7683 as a messaging layer using Polymer.
12+
* @dev It integrates with the Polymer protocol for cross-chain event verification.
13+
*/
14+
contract Polymer7683 is BasicSwap7683, Ownable {
15+
// ============ Libraries ============
16+
17+
// ============ Constants ============
18+
string public constant CLIENT_TYPE = "polymer"; // Used for proof verification
19+
20+
// ============ Public Storage ============
21+
ICrossL2ProverV2 public immutable prover;
22+
uint256 public immutable localChainId;
23+
mapping(uint256 => address) public destinationContracts;
24+
25+
// Keep track of processed events to prevent replay
26+
mapping(bytes32 eventHash => bool processed) public processedEvents;
27+
28+
// ============ Events ============
29+
/**
30+
* @notice Event emitted when a batch of orders is filled on destination chain
31+
* @param chainId The chain ID where this event was emitted
32+
* @param orderIds The IDs of the orders being filled
33+
* @param ordersFillerData The filler data for each order
34+
*/
35+
event BatchOrdersFilled(
36+
uint256 indexed chainId,
37+
bytes32[] orderIds,
38+
bytes[] ordersFillerData
39+
);
40+
41+
/**
42+
* @notice Event emitted when a destination contract is updated
43+
* @param chainId The chain ID for the destination
44+
* @param contractAddress The new contract address
45+
*/
46+
event DestinationContractUpdated(uint256 indexed chainId, address contractAddress);
47+
48+
// ============ Errors ============
49+
error InvalidProof();
50+
error InvalidChainId();
51+
error InvalidEmitter();
52+
error EventAlreadyProcessed();
53+
error InvalidEventData();
54+
error InvalidDestinationContract();
55+
error UnregisteredDestinationChain();
56+
57+
// ============ Constructor ============
58+
/**
59+
* @notice Initializes the Polymer7683 contract with the specified Prover and PERMIT2 address.
60+
* @param _prover The address of the Polymer CrossL2Prover contract
61+
* @param _permit2 The address of the permit2 contract
62+
* @param _localChainId The chain ID of the chain this contract is deployed on
63+
*/
64+
constructor(
65+
ICrossL2ProverV2 _prover,
66+
address _permit2,
67+
uint256 _localChainId
68+
) BasicSwap7683(_permit2) {
69+
prover = _prover;
70+
localChainId = _localChainId;
71+
}
72+
73+
// ============ Admin Functions ============
74+
function setDestinationContract(uint256 chainId, address contractAddress) external onlyOwner {
75+
if (contractAddress == address(0)) revert InvalidDestinationContract();
76+
destinationContracts[chainId] = contractAddress;
77+
emit DestinationContractUpdated(chainId, contractAddress);
78+
}
79+
80+
// ============ External Functions ============
81+
/**
82+
* @notice Process a settlement proof from a destination chain
83+
* @param orderIds The order IDs being settled
84+
* @param eventProof The proof of the Fill event from the destination chain
85+
* @param logIndex The index of the log in the receipt
86+
*/
87+
function handleSettlementWithProof(
88+
bytes32[] calldata orderIds,
89+
bytes calldata eventProof,
90+
uint256 logIndex,
91+
uint256 destinationChainId
92+
) external {
93+
// 1. Verify the destination contract is registered
94+
address expectedEmitter = destinationContracts[destinationChainId];
95+
if (expectedEmitter == address(0)) revert UnregisteredDestinationChain();
96+
97+
// 2. Verify event using Polymer prover
98+
(
99+
uint32 chainId,
100+
address actualEmitter,
101+
bytes memory topics,
102+
bytes memory data
103+
) = prover.validateEvent(eventProof);
104+
105+
// 3. Validate the chain ID matches our expected destination chain
106+
if (chainId != destinationChainId) {
107+
revert InvalidChainId();
108+
}
109+
110+
// 4. Validate the emitter matches our registered destination contract
111+
if (actualEmitter != expectedEmitter) revert InvalidEmitter();
112+
113+
// 5. Verify this event hasn't been processed before (prevent replay)
114+
bytes32 eventHash = keccak256(abi.encodePacked(eventProof, logIndex, destinationChainId));
115+
if (processedEvents[eventHash]) revert EventAlreadyProcessed();
116+
processedEvents[eventHash] = true;
117+
118+
// 6. Parse the BatchOrdersFilled event data
119+
(bytes32[] memory eventOrderIds, bytes[] memory ordersFillerData) =
120+
abi.decode(data, (bytes32[], bytes[]));
121+
122+
// 7. Validate order IDs match what was requested
123+
if (orderIds.length != eventOrderIds.length) revert InvalidEventData();
124+
for (uint256 i = 0; i < orderIds.length; i++) {
125+
if (orderIds[i] != eventOrderIds[i]) revert InvalidEventData();
126+
127+
// 8. Process settlement for each order
128+
_handleSettleOrder(orderIds[i], abi.decode(ordersFillerData[i], (bytes32)));
129+
}
130+
}
131+
132+
/**
133+
* @notice Process a refund proof from a destination chain
134+
* @param orderIds The order IDs being refunded
135+
* @param eventProof The proof of the Refund event from the destination chain
136+
* @param logIndex The index of the log in the receipt
137+
*/
138+
function handleRefundWithProof(
139+
bytes32[] calldata orderIds,
140+
bytes calldata eventProof,
141+
uint256 logIndex,
142+
uint256 destinationChainId
143+
) external {
144+
// 1. Verify the destination contract is registered
145+
address expectedEmitter = destinationContracts[destinationChainId];
146+
if (expectedEmitter == address(0)) revert UnregisteredDestinationChain();
147+
148+
// 2. Verify event using Polymer prover
149+
(
150+
uint32 chainId,
151+
address actualEmitter,
152+
bytes memory topics,
153+
bytes memory data
154+
) = prover.validateEvent(eventProof);
155+
156+
// 3. Validate the chain ID matches our expected destination chain
157+
if (chainId != destinationChainId) {
158+
revert InvalidChainId();
159+
}
160+
161+
// 4. Validate the emitter matches our registered destination contract
162+
if (actualEmitter != expectedEmitter) revert InvalidEmitter();
163+
164+
// 4. Verify this event hasn't been processed before (prevent replay)
165+
bytes32 eventHash = keccak256(abi.encodePacked(eventProof, logIndex, destinationChainId));
166+
if (processedEvents[eventHash]) revert EventAlreadyProcessed();
167+
processedEvents[eventHash] = true;
168+
169+
// 5. Parse the BatchOrdersFilled event data (with empty fillerData for refunds)
170+
(bytes32[] memory eventOrderIds, bytes[] memory ordersFillerData) =
171+
abi.decode(data, (bytes32[], bytes[]));
172+
173+
// 6. Verify that this is actually a refund event which is indicated by the empty filler data.
174+
if (ordersFillerData.length != 0) revert InvalidEventData();
175+
176+
// 7. Validate order IDs match what was requested
177+
if (orderIds.length != eventOrderIds.length) revert InvalidEventData();
178+
for (uint256 i = 0; i < orderIds.length; i++) {
179+
if (orderIds[i] != eventOrderIds[i]) revert InvalidEventData();
180+
181+
// 8. Process refund for each order
182+
_handleRefundOrder(orderIds[i]);
183+
}
184+
}
185+
186+
// ============ Internal Functions ============
187+
188+
/**
189+
* @notice Dispatches a settlement instruction by emitting an event that will be proven on the origin chain
190+
* @param _originDomain The domain to which the settlement message is sent
191+
* @param _orderIds The IDs of the orders to settle
192+
* @param _ordersFillerData The filler data for the orders
193+
*/
194+
function _dispatchSettle(
195+
uint32 _originDomain,
196+
bytes32[] memory _orderIds,
197+
bytes[] memory _ordersFillerData
198+
) internal override {
199+
emit BatchOrdersFilled(localChainId, _orderIds, _ordersFillerData);
200+
}
201+
202+
/**
203+
* @notice Dispatches a refund instruction by emitting an event that will be proven on the origin chain
204+
* @param _originDomain The domain to which the refund message is sent
205+
* @param _orderIds The IDs of the orders to refund
206+
*/
207+
function _dispatchRefund(
208+
uint32 _originDomain,
209+
bytes32[] memory _orderIds
210+
) internal override {
211+
emit BatchOrdersFilled(localChainId, _orderIds, new bytes[](0));
212+
}
213+
214+
/**
215+
* @notice Retrieves the local domain identifier
216+
* @return The local domain ID (chain ID)
217+
*/
218+
function _localDomain() internal view override returns (uint32) {
219+
return uint32(localChainId);
220+
}
221+
}

0 commit comments

Comments
 (0)