forked from cosmos/solidity-ibc-eureka
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathICS26Router.sol
344 lines (293 loc) · 15 KB
/
ICS26Router.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import { ILightClientMsgs } from "./msgs/ILightClientMsgs.sol";
import { IICS26RouterMsgs } from "./msgs/IICS26RouterMsgs.sol";
import { IICS02ClientMsgs } from "./msgs/IICS02ClientMsgs.sol";
import { IIBCAppCallbacks } from "./msgs/IIBCAppCallbacks.sol";
import { IICS26RouterErrors } from "./errors/IICS26RouterErrors.sol";
import { IIBCApp } from "./interfaces/IIBCApp.sol";
import { IICS26Router } from "./interfaces/IICS26Router.sol";
import { ReentrancyGuardTransientUpgradeable } from
"@openzeppelin-upgradeable/utils/ReentrancyGuardTransientUpgradeable.sol";
import { IBCStoreUpgradeable } from "./utils/IBCStoreUpgradeable.sol";
import { Strings } from "@openzeppelin-contracts/utils/Strings.sol";
import { IBCIdentifiers } from "./utils/IBCIdentifiers.sol";
import { ICS24Host } from "./utils/ICS24Host.sol";
import { ICS02ClientUpgradeable } from "./utils/ICS02ClientUpgradeable.sol";
import { MulticallUpgradeable } from "@openzeppelin-upgradeable/utils/MulticallUpgradeable.sol";
import { IBCUUPSUpgradeable } from "./utils/IBCUUPSUpgradeable.sol";
/// @title IBC Eureka Router
/// @notice ICS26Router is the router for the IBC Eureka protocol
contract ICS26Router is
IICS26RouterErrors,
IICS26Router,
ICS02ClientUpgradeable,
IBCStoreUpgradeable,
ReentrancyGuardTransientUpgradeable,
MulticallUpgradeable,
IBCUUPSUpgradeable
{
/// @notice Storage of the ICS26Router contract
/// @dev It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions when using with
/// upgradeable contracts.
/// @param apps The mapping of port identifiers to IBC application contracts
/// @param ics02Client The ICS02Client contract
/// @custom:storage-location erc7201:ibc.storage.ICS26Router
struct ICS26RouterStorage {
mapping(string => IIBCApp) apps;
}
/// @notice ERC-7201 slot for the ICS26Router storage
/// @dev keccak256(abi.encode(uint256(keccak256("ibc.storage.ICS26Router")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ICS26ROUTER_STORAGE_SLOT =
0xc5779f3c2c21083eefa6d04f6a698bc0d8c10db124ad5e0df6ef394b6d7bf600;
/// @dev The maximum timeout duration for a packet
uint256 private constant MAX_TIMEOUT_DURATION = 1 days;
/// @inheritdoc IICS26Router
bytes32 public constant PORT_CUSTOMIZER_ROLE = keccak256("PORT_CUSTOMIZER_ROLE");
/// @dev This contract is meant to be deployed by a proxy, so the constructor is not used
constructor() {
_disableInitializers();
}
/// @notice Initializes the contract instead of a constructor
/// @dev Meant to be called only once from the proxy
/// @param timelockedAdmin The address of the timelocked admin for IBCUUPSUpgradeable
/// @param portCustomizer The address of the port customizer
function initialize(address timelockedAdmin, address portCustomizer) public initializer {
__AccessControl_init();
__ReentrancyGuardTransient_init();
__Multicall_init();
__ICS02Client_init();
__IBCStoreUpgradeable_init();
__IBCUUPSUpgradeable_init(timelockedAdmin);
_grantRole(PORT_CUSTOMIZER_ROLE, portCustomizer);
}
/// @notice Returns the address of the IBC application given the port identifier
/// @param portId The port identifier
/// @return The address of the IBC application contract
/// @inheritdoc IICS26Router
function getIBCApp(string calldata portId) public view returns (IIBCApp) {
IIBCApp app = _getICS26RouterStorage().apps[portId];
require(address(app) != address(0), IBCAppNotFound(portId));
return app;
}
/// @inheritdoc IICS26Router
function isPacketReceived(IICS26RouterMsgs.Packet calldata packet) public view returns (bool) {
bytes32 expReceipt = ICS24Host.packetReceiptCommitmentBytes32(packet);
return expReceipt == queryPacketReceipt(packet.destClient, packet.sequence);
}
/// @inheritdoc IICS26Router
function isPacketReceiveSuccessful(IICS26RouterMsgs.Packet calldata packet) external view returns (bool) {
if (!isPacketReceived(packet)) {
return false;
}
bytes[] memory errorAck = new bytes[](1);
errorAck[0] = ICS24Host.UNIVERSAL_ERROR_ACK;
bytes32 errorAckCommitment = ICS24Host.packetAcknowledgementCommitmentBytes32(errorAck);
bytes32 storedAckCommitment = queryAckCommitment(packet.destClient, packet.sequence);
return storedAckCommitment != 0 && storedAckCommitment != errorAckCommitment;
}
/// @notice Adds an IBC application to the router
/// @dev Only the admin can submit non-empty port identifiers
/// @param portId The port identifier
/// @param app The address of the IBC application contract
/// @inheritdoc IICS26Router
function addIBCApp(string calldata portId, address app) external {
string memory newPortId;
if (bytes(portId).length != 0) {
_checkRole(PORT_CUSTOMIZER_ROLE);
newPortId = portId;
} else {
newPortId = Strings.toHexString(app);
}
ICS26RouterStorage storage $ = _getICS26RouterStorage();
require(address($.apps[newPortId]) == address(0), IBCPortAlreadyExists(newPortId));
require(IBCIdentifiers.validatePortIdentifier(bytes(newPortId)), IBCInvalidPortIdentifier(newPortId));
$.apps[newPortId] = IIBCApp(app);
emit IBCAppAdded(newPortId, app);
}
/// @inheritdoc IICS26Router
function sendPacket(IICS26RouterMsgs.MsgSendPacket calldata msg_) external nonReentrant returns (uint64) {
address ibcApp = address(getIBCApp(msg_.payload.sourcePort));
require(ibcApp == _msgSender(), IBCUnauthorizedSender(_msgSender()));
string memory counterpartyId = getCounterparty(msg_.sourceClient).clientId;
// TODO: validate all identifiers
require(
msg_.timeoutTimestamp > block.timestamp, IBCInvalidTimeoutTimestamp(msg_.timeoutTimestamp, block.timestamp)
);
require(
msg_.timeoutTimestamp - block.timestamp <= MAX_TIMEOUT_DURATION,
IBCInvalidTimeoutDuration(MAX_TIMEOUT_DURATION, msg_.timeoutTimestamp - block.timestamp)
);
uint64 sequence = nextSequenceSend(msg_.sourceClient);
// TODO: Support multi-payload packets #93
IICS26RouterMsgs.Packet memory packet = IICS26RouterMsgs.Packet({
sequence: sequence,
sourceClient: msg_.sourceClient,
destClient: counterpartyId,
timeoutTimestamp: msg_.timeoutTimestamp,
payloads: new IICS26RouterMsgs.Payload[](1)
});
packet.payloads[0] = msg_.payload;
commitPacket(packet);
emit SendPacket(msg_.sourceClient, sequence, packet);
return sequence;
}
/// @notice Receives a packet
/// @param msg_ The message for receiving packets
/// @inheritdoc IICS26Router
function recvPacket(IICS26RouterMsgs.MsgRecvPacket calldata msg_) external nonReentrant {
// TODO: Support multi-payload packets (#93)
require(msg_.packet.payloads.length == 1, IBCMultiPayloadPacketNotSupported());
IICS26RouterMsgs.Payload calldata payload = msg_.packet.payloads[0];
IICS02ClientMsgs.CounterpartyInfo memory cInfo = getCounterparty(msg_.packet.destClient);
require(
keccak256(bytes(cInfo.clientId)) == keccak256(bytes(msg_.packet.sourceClient)),
IBCInvalidCounterparty(cInfo.clientId, msg_.packet.sourceClient)
);
require(
msg_.packet.timeoutTimestamp > block.timestamp,
IBCInvalidTimeoutTimestamp(msg_.packet.timeoutTimestamp, block.timestamp)
);
bytes memory commitmentPath =
ICS24Host.packetCommitmentPathCalldata(msg_.packet.sourceClient, msg_.packet.sequence);
bytes32 commitmentBz = ICS24Host.packetCommitmentBytes32(msg_.packet);
ILightClientMsgs.MsgVerifyMembership memory membershipMsg = ILightClientMsgs.MsgVerifyMembership({
proof: msg_.proofCommitment,
proofHeight: msg_.proofHeight,
path: ICS24Host.prefixedPath(cInfo.merklePrefix, commitmentPath),
value: abi.encodePacked(commitmentBz)
});
getClient(msg_.packet.destClient).verifyMembership(membershipMsg);
// recvPacket will no-op if the packet receipt already exists
// This no-op check must happen after the membership verification for proofs to be cached
bool receiptAlreadySet = !setPacketReceipt(msg_.packet);
if (receiptAlreadySet) {
emit Noop();
return;
}
bytes[] memory acks = new bytes[](1);
try getIBCApp(payload.destPort).onRecvPacket(
IIBCAppCallbacks.OnRecvPacketCallback({
sourceClient: msg_.packet.sourceClient,
destinationClient: msg_.packet.destClient,
sequence: msg_.packet.sequence,
payload: payload,
relayer: _msgSender()
})
) returns (bytes memory ack) {
require(ack.length != 0, IBCAsyncAcknowledgementNotSupported());
require(keccak256(ack) != ICS24Host.KECCAK256_UNIVERSAL_ERROR_ACK, IBCErrorUniversalAcknowledgement());
acks[0] = ack;
} catch (bytes memory reason) {
require(reason.length != 0, IBCFailedCallback()); // covers OOG
emit IBCAppRecvPacketCallbackError(reason);
acks[0] = ICS24Host.UNIVERSAL_ERROR_ACK;
}
commitPacketAcknowledgement(msg_.packet, acks);
emit WriteAcknowledgement(msg_.packet.destClient, msg_.packet.sequence, msg_.packet, acks);
}
/// @notice Acknowledges a packet
/// @param msg_ The message for acknowledging packets
/// @inheritdoc IICS26Router
function ackPacket(IICS26RouterMsgs.MsgAckPacket calldata msg_) external nonReentrant {
// TODO: Support multi-payload packets #93
require(msg_.packet.payloads.length == 1, IBCMultiPayloadPacketNotSupported());
IICS26RouterMsgs.Payload calldata payload = msg_.packet.payloads[0];
IICS02ClientMsgs.CounterpartyInfo memory cInfo = getCounterparty(msg_.packet.sourceClient);
require(
keccak256(bytes(cInfo.clientId)) == keccak256(bytes(msg_.packet.destClient)),
IBCInvalidCounterparty(cInfo.clientId, msg_.packet.destClient)
);
bytes memory commitmentPath =
ICS24Host.packetAcknowledgementCommitmentPathCalldata(msg_.packet.destClient, msg_.packet.sequence);
bytes[] memory acks = new bytes[](1);
acks[0] = msg_.acknowledgement;
bytes32 commitmentBz = ICS24Host.packetAcknowledgementCommitmentBytes32(acks);
// verify the packet acknowledgement
ILightClientMsgs.MsgVerifyMembership memory membershipMsg = ILightClientMsgs.MsgVerifyMembership({
proof: msg_.proofAcked,
proofHeight: msg_.proofHeight,
path: ICS24Host.prefixedPath(cInfo.merklePrefix, commitmentPath),
value: abi.encodePacked(commitmentBz)
});
getClient(msg_.packet.sourceClient).verifyMembership(membershipMsg);
// ackPacket will no-op if the packet commitment does not exist
// This no-op check must happen after the membership verification for proofs to be cached
bool commitmentFound = checkAndDeletePacketCommitment(msg_.packet);
if (!commitmentFound) {
emit Noop();
return;
}
getIBCApp(payload.sourcePort).onAcknowledgementPacket(
IIBCAppCallbacks.OnAcknowledgementPacketCallback({
sourceClient: msg_.packet.sourceClient,
destinationClient: msg_.packet.destClient,
sequence: msg_.packet.sequence,
payload: payload,
acknowledgement: msg_.acknowledgement,
relayer: _msgSender()
})
);
emit AckPacket(msg_.packet.sourceClient, msg_.packet.sequence, msg_.packet, msg_.acknowledgement);
}
/// @notice Timeouts a packet
/// @param msg_ The message for timing out packets
/// @inheritdoc IICS26Router
function timeoutPacket(IICS26RouterMsgs.MsgTimeoutPacket calldata msg_) external nonReentrant {
// TODO: Support multi-payload packets #93
require(msg_.packet.payloads.length == 1, IBCMultiPayloadPacketNotSupported());
IICS26RouterMsgs.Payload calldata payload = msg_.packet.payloads[0];
IICS02ClientMsgs.CounterpartyInfo memory cInfo = getCounterparty(msg_.packet.sourceClient);
require(
keccak256(bytes(cInfo.clientId)) == keccak256(bytes(msg_.packet.destClient)),
IBCInvalidCounterparty(cInfo.clientId, msg_.packet.destClient)
);
bytes memory receiptPath =
ICS24Host.packetReceiptCommitmentPathCalldata(msg_.packet.destClient, msg_.packet.sequence);
ILightClientMsgs.MsgVerifyNonMembership memory nonMembershipMsg = ILightClientMsgs.MsgVerifyNonMembership({
proof: msg_.proofTimeout,
proofHeight: msg_.proofHeight,
path: ICS24Host.prefixedPath(cInfo.merklePrefix, receiptPath)
});
uint256 counterpartyTimestamp = getClient(msg_.packet.sourceClient).verifyNonMembership(nonMembershipMsg);
require(
counterpartyTimestamp >= msg_.packet.timeoutTimestamp,
IBCInvalidTimeoutTimestamp(msg_.packet.timeoutTimestamp, counterpartyTimestamp)
);
// timeoutPacket will no-op if the packet commitment does not exist
// This no-op check must happen after the membership verification for proofs to be cached
bool commitmentFound = checkAndDeletePacketCommitment(msg_.packet);
if (!commitmentFound) {
emit Noop();
return;
}
getIBCApp(payload.sourcePort).onTimeoutPacket(
IIBCAppCallbacks.OnTimeoutPacketCallback({
sourceClient: msg_.packet.sourceClient,
destinationClient: msg_.packet.destClient,
sequence: msg_.packet.sequence,
payload: payload,
relayer: _msgSender()
})
);
emit TimeoutPacket(msg_.packet.sourceClient, msg_.packet.sequence, msg_.packet);
}
/// @inheritdoc IICS26Router
function grantPortCustomizerRole(address account) external onlyAdmin {
_grantRole(PORT_CUSTOMIZER_ROLE, account);
}
/// @inheritdoc IICS26Router
function revokePortCustomizerRole(address account) external onlyAdmin {
_revokeRole(PORT_CUSTOMIZER_ROLE, account);
}
/// @inheritdoc ICS02ClientUpgradeable
function _authorizeSetLightClientMigratorRole(string calldata, address) internal view override onlyAdmin { }
// solhint-disable-previous-line no-empty-blocks
/// @notice Returns the storage of the ICS26Router contract
function _getICS26RouterStorage() private pure returns (ICS26RouterStorage storage $) {
// solhint-disable-next-line no-inline-assembly
assembly {
$.slot := ICS26ROUTER_STORAGE_SLOT
}
}
}