@@ -8,72 +8,106 @@ import {IERC7802} from "@openzeppelin/contracts/interfaces/draft-IERC7802.sol";
8
8
import {IERC7786GatewaySource , IERC7786Receiver } from "../interfaces/IERC7786.sol " ;
9
9
10
10
// Utilities
11
+ import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol " ;
12
+ import {ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol " ;
11
13
import {Arrays} from "@openzeppelin/contracts/utils/Arrays.sol " ;
12
14
import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol " ;
13
15
import {Bytes} from "@openzeppelin/contracts/utils/Bytes.sol " ;
14
16
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol " ;
15
17
import {InteroperableAddress} from "@openzeppelin/contracts/utils/draft-InteroperableAddress.sol " ;
16
- import {IndirectCall} from "../utils/IndirectCall.sol " ;
17
18
18
- contract ERC7802Bridge is IERC7786Receiver {
19
+ contract ERC7802BridgeSatellite {
20
+ address private immutable _bridge = msg .sender ;
21
+
22
+ fallback (bytes calldata data ) external payable returns (bytes memory ) {
23
+ require (msg .sender == _bridge && data.length >= 20 );
24
+
25
+ (bool success , bytes memory returndata ) = address (bytes20 (data)).call {value: msg .value }(data[20 :]);
26
+
27
+ if (! success) {
28
+ assembly ("memory-safe" ) {
29
+ revert (add (returndata, 0x20 ), mload (returndata))
30
+ }
31
+ } else {
32
+ return returndata;
33
+ }
34
+ }
35
+ }
36
+
37
+ contract ERC7802Bridge is ERC721 ("ERC7802Bridge ", "ERC7802Bridge "), IERC7786Receiver {
19
38
using BitMaps for BitMaps.BitMap;
20
39
using InteroperableAddress for bytes ;
21
-
22
40
struct BridgeMetadata {
23
41
address token;
42
+ bool isPaused;
24
43
bool isCustodial;
25
44
mapping (bytes chain = > address ) gateway;
26
45
mapping (bytes chain = > bytes ) remote;
27
46
}
28
47
48
+ address private immutable _satellite = address (new ERC7802BridgeSatellite ());
29
49
mapping (bytes32 bridgeId = > BridgeMetadata) private _bridges;
30
50
BitMaps.BitMap private _processed;
31
51
32
52
event Sent (address token , address from , bytes to , uint256 amount );
33
53
event Received (address token , bytes from , address to , uint256 amount );
34
- event NewBridge (bytes32 indexed bridgeId , address indexed token );
35
- event NewBridgeLink (bytes32 indexed bridgeId , address gateway , bytes remote );
54
+ event BridgePaused (bytes32 indexed bridgeId , bool isPaused );
55
+ event BridgeLinkSet (bytes32 indexed bridgeId , address gateway , bytes remote );
36
56
57
+ error ERC7802BridgePaused (bytes32 bridgeId );
37
58
error ERC7802BridgeInvalidBidgeId (bytes32 bridgeId );
38
59
error ERC7802BridgeMissingGateway (bytes32 bridgeId , bytes chain );
39
60
error ERC7802BridgeMissingRemote (bytes32 bridgeId , bytes chain );
40
61
error ERC7802BridgeDuplicate ();
41
62
error ERC7802BridgeInvalidGateway ();
42
63
error ERC7802BridgeInvalidSender ();
43
64
44
- function getBridgeEndpoint (bytes32 bridgeId ) public returns (address ) {
45
- return IndirectCall.getRelayer (bridgeId);
65
+ modifier bridgeAdminRestricted (bytes32 bridgeId ) {
66
+ _checkAuthorized (ownerOf (uint256 (bridgeId)), msg .sender , uint256 (bridgeId));
67
+ _;
68
+ }
69
+
70
+ // ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
71
+ // │ Getters │
72
+ // └─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
73
+ function getBridgeEndpoint (bytes32 bridgeId ) public view returns (address ) {
74
+ return Clones.predictDeterministicAddress (_satellite, bridgeId);
46
75
}
47
76
48
77
function getBridgeToken (bytes32 bridgeId ) public view returns (address token , bool isCustodial ) {
49
- token = _bridges[bridgeId].token;
50
- isCustodial = _bridges[bridgeId].isCustodial;
51
- if (token == address (0 )) revert ERC7802BridgeInvalidBidgeId (bridgeId);
78
+ _requireOwned (uint256 (bridgeId));
79
+ return (_bridges[bridgeId].token, _bridges[bridgeId].isCustodial);
52
80
}
53
81
54
82
function getBridgeGateway (bytes32 bridgeId , bytes memory chain ) public view returns (address ) {
55
- address result = _bridges[bridgeId].gateway[chain];
56
- if (result == address (0 )) revert ERC7802BridgeMissingGateway (bridgeId, chain);
57
- return result;
83
+ _requireOwned (uint256 (bridgeId));
84
+ return _bridges[bridgeId].gateway[chain];
58
85
}
59
86
60
87
function getBridgeRemote (bytes32 bridgeId , bytes memory chain ) public view returns (bytes memory ) {
61
- bytes memory result = _bridges[bridgeId].remote[chain];
62
- if (result.length == 0 ) revert ERC7802BridgeMissingRemote (bridgeId, chain);
63
- return result;
88
+ _requireOwned (uint256 (bridgeId));
89
+ return _bridges[bridgeId].remote[chain];
64
90
}
65
91
92
+ // ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
93
+ // │ Bridge creation and administration │
94
+ // └─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
66
95
struct Foreign {
67
96
bytes32 id;
68
97
address gateway;
69
98
bytes remote;
70
99
}
71
- function createBridge (address token , bool isCustodial , Foreign[] calldata foreign ) public returns (bytes32 ) {
100
+
101
+ function createBridge (
102
+ address token ,
103
+ address admin ,
104
+ bool isCustodial ,
105
+ Foreign[] calldata foreign
106
+ ) public returns (bytes32 ) {
72
107
bytes32 [] memory ids = new bytes32 [](foreign.length + 1 );
73
108
bytes32 [] memory links = new bytes32 [](foreign.length );
74
109
for (uint256 i = 0 ; i < foreign.length ; ++ i) {
75
- require (foreign[i].gateway != address (0 ));
76
- require (foreign[i].remote.length > 0 );
110
+ require (foreign[i].gateway != address (0 ) && foreign[i].remote.length > 0 );
77
111
ids[i] = foreign[i].id;
78
112
links[i] = keccak256 (
79
113
abi.encode (InteroperableAddress.formatEvmV1 (block .chainid , foreign[i].gateway), foreign[i].remote)
@@ -82,7 +116,7 @@ contract ERC7802Bridge is IERC7786Receiver {
82
116
ids[foreign.length ] = keccak256 (
83
117
abi.encode (
84
118
InteroperableAddress.formatEvmV1 (block .chainid , token), // bytes token
85
- bytes32 (SafeCast.toUint (isCustodial)), // bytes32 tokenOptions
119
+ bytes32 (bytes20 (admin)) | bytes32 ( SafeCast.toUint (isCustodial)), // bytes32 tokenOptions
86
120
Arrays.sort (links)
87
121
)
88
122
);
@@ -94,7 +128,8 @@ contract ERC7802Bridge is IERC7786Receiver {
94
128
details.token = token;
95
129
details.isCustodial = isCustodial;
96
130
97
- emit NewBridge (bridgeId, token);
131
+ Clones.cloneDeterministic (_satellite, bridgeId);
132
+ _safeMint (admin == address (0 ) ? address (1 ) : admin, uint256 (bridgeId));
98
133
99
134
for (uint256 i = 0 ; i < foreign.length ; ++ i) {
100
135
(bytes2 chainType , bytes memory chainReference , ) = foreign[i].remote.parseV1 ();
@@ -103,27 +138,51 @@ contract ERC7802Bridge is IERC7786Receiver {
103
138
details.gateway[chain] = foreign[i].gateway;
104
139
details.remote[chain] = foreign[i].remote;
105
140
106
- emit NewBridgeLink (bridgeId, foreign[i].gateway, foreign[i].remote);
141
+ emit BridgeLinkSet (bridgeId, foreign[i].gateway, foreign[i].remote);
107
142
}
108
143
109
144
return bridgeId;
110
145
}
111
146
147
+ function setPaused (bytes32 bridgeId , bool isPaused ) public bridgeAdminRestricted (bridgeId) {
148
+ _bridges[bridgeId].isPaused = isPaused;
149
+ emit BridgePaused (bridgeId, isPaused);
150
+ }
151
+
152
+ function updateGateway (
153
+ bytes32 bridgeId ,
154
+ bytes calldata chain ,
155
+ address gateway ,
156
+ bytes calldata remote
157
+ ) public bridgeAdminRestricted (bridgeId) {
158
+ require (gateway != address (0 ) && remote.length > 0 );
159
+ _bridges[bridgeId].gateway[chain] = gateway;
160
+ _bridges[bridgeId].remote[chain] = remote;
161
+ emit BridgeLinkSet (bridgeId, gateway, remote);
162
+ }
163
+
164
+ // ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
165
+ // │ Send / Receive tokens │
166
+ // └─────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
112
167
function send (
113
168
bytes32 bridgeId ,
114
169
bytes memory to ,
115
170
uint256 amount ,
116
171
bytes [] memory attributes
117
172
) public payable virtual returns (bytes32 ) {
173
+ _requireOwned (uint256 (bridgeId));
174
+
175
+ require (! _bridges[bridgeId].isPaused, ERC7802BridgePaused (bridgeId));
176
+
118
177
address token = _fetchTokens (bridgeId, msg .sender , amount);
119
178
120
179
// identify destination chain
121
180
(bytes2 chainType , bytes memory chainReference , bytes memory recipient ) = to.parseV1 ();
122
181
bytes memory destChain = InteroperableAddress.formatV1 (chainType, chainReference, "" );
123
182
124
183
// get details for that bridge: gateway, remote bridge, remote token
125
- address gateway = getBridgeGateway ( bridgeId, destChain) ;
126
- bytes memory bridge = getBridgeRemote ( bridgeId, destChain) ;
184
+ address gateway = _bridges[ bridgeId].gateway[ destChain] ;
185
+ bytes memory bridge = _bridges[ bridgeId].remote[ destChain] ;
127
186
128
187
// prepare payload
129
188
bytes memory payload = abi.encode (
@@ -156,12 +215,14 @@ contract ERC7802Bridge is IERC7786Receiver {
156
215
(bytes32 , bytes , bytes , uint256 )
157
216
);
158
217
218
+ _requireOwned (uint256 (bridgeId));
219
+
159
220
// identify source chain and validate corresponding gateway
160
221
(bytes2 chainType , bytes memory chainReference , ) = from.parseV1 ();
161
222
bytes memory srcChain = InteroperableAddress.formatV1 (chainType, chainReference, "" );
162
223
163
- require (msg .sender == getBridgeGateway ( bridgeId, srcChain) , ERC7802BridgeInvalidGateway ());
164
- require (Bytes.equal (sender, getBridgeRemote ( bridgeId, srcChain) ), ERC7802BridgeInvalidSender ());
224
+ require (msg .sender == _bridges[ bridgeId].gateway[ srcChain] , ERC7802BridgeInvalidGateway ());
225
+ require (Bytes.equal (sender, _bridges[ bridgeId].remote[ srcChain] ), ERC7802BridgeInvalidSender ());
165
226
166
227
// get recipient
167
228
address to = address (bytes20 (recipient));
@@ -173,40 +234,35 @@ contract ERC7802Bridge is IERC7786Receiver {
173
234
return IERC7786Receiver .executeMessage.selector ;
174
235
}
175
236
176
- function _fetchTokens (bytes32 bridgeId , address from , uint256 amount ) internal virtual returns (address ) {
177
- (address token , bool isCustodial ) = getBridgeToken (bridgeId);
178
- if (isCustodial) {
179
- (bool success , bytes memory returndata ) = IndirectCall.indirectCall (
180
- token,
181
- abi.encodeCall (IERC20 .transferFrom, (from, getBridgeEndpoint (bridgeId), amount)),
182
- bridgeId
237
+ function _fetchTokens (bytes32 bridgeId , address from , uint256 amount ) private returns (address ) {
238
+ address token = _bridges[bridgeId].token;
239
+ if (_bridges[bridgeId].isCustodial) {
240
+ (bool success , bytes memory returndata ) = getBridgeEndpoint (bridgeId).call (
241
+ abi.encodePacked (
242
+ token,
243
+ abi.encodeCall (IERC20 .transferFrom, (from, getBridgeEndpoint (bridgeId), amount))
244
+ )
183
245
);
184
246
require (success && (returndata.length == 0 ? token.code.length == 0 : uint256 (bytes32 (returndata)) == 1 ));
185
247
} else {
186
- (bool success , ) = IndirectCall.indirectCall (
187
- token,
188
- abi.encodeCall (IERC7802 .crosschainBurn, (from, amount)),
189
- bridgeId
248
+ (bool success , ) = getBridgeEndpoint (bridgeId).call (
249
+ abi.encodePacked (token, abi.encodeCall (IERC7802 .crosschainBurn, (from, amount)))
190
250
);
191
251
require (success);
192
252
}
193
253
return token;
194
254
}
195
255
196
- function _distributeTokens (bytes32 bridgeId , address to , uint256 amount ) internal virtual returns (address ) {
197
- (address token , bool isCustodial ) = getBridgeToken (bridgeId);
198
- if (isCustodial) {
199
- (bool success , bytes memory returndata ) = IndirectCall.indirectCall (
200
- token,
201
- abi.encodeCall (IERC20 .transfer, (to, amount)),
202
- bridgeId
256
+ function _distributeTokens (bytes32 bridgeId , address to , uint256 amount ) private returns (address ) {
257
+ address token = _bridges[bridgeId].token;
258
+ if (_bridges[bridgeId].isCustodial) {
259
+ (bool success , bytes memory returndata ) = getBridgeEndpoint (bridgeId).call (
260
+ abi.encodePacked (token, abi.encodeCall (IERC20 .transfer, (to, amount)))
203
261
);
204
262
require (success && (returndata.length == 0 ? token.code.length == 0 : uint256 (bytes32 (returndata)) == 1 ));
205
263
} else {
206
- (bool success , ) = IndirectCall.indirectCall (
207
- token,
208
- abi.encodeCall (IERC7802 .crosschainMint, (to, amount)),
209
- bridgeId
264
+ (bool success , ) = getBridgeEndpoint (bridgeId).call (
265
+ abi.encodePacked (token, abi.encodeCall (IERC7802 .crosschainMint, (to, amount)))
210
266
);
211
267
require (success);
212
268
}
0 commit comments