-
Notifications
You must be signed in to change notification settings - Fork 6
/
VeaInboxArbToEth.sol
218 lines (180 loc) · 8.04 KB
/
VeaInboxArbToEth.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
// SPDX-License-Identifier: MIT
/**
* @authors: [@jaybuidl, @shotaronowhere]
* @reviewers: []
* @auditors: []
* @bounties: []
* @deployments: []
*/
pragma solidity 0.8.18;
import "../canonical/arbitrum/IArbSys.sol";
import "../interfaces/IVeaInbox.sol";
import "./interfaces/IVeaOutboxArbToEth.sol";
/**
* Vea Bridge Inbox From Arbitrum to Ethereum.
*/
contract VeaInboxArbToEth is IVeaInbox {
/**
* @dev Relayers watch for these events to construct merkle proofs to execute transactions on Ethereum.
* @param nodeData The data to create leaves in the merkle tree. abi.encodePacked(msgId, to, data), outbox relays to.call(data)
*/
event MessageSent(bytes nodeData);
/**
* The bridgers can watch this event to claim the stateRoot on the veaOutbox.
* @param count The count of messages in the merkle tree
*/
event SnapshotSaved(uint256 count);
/**
* @dev The event is emitted when a snapshot through the canonical arbiturm bridge.
* @param epochSent The epoch of the snapshot.
* @param ticketId The ticketId of the L2->L1 message.
*/
event SnapshotSent(uint256 indexed epochSent, bytes32 ticketId);
IArbSys public constant ARB_SYS = IArbSys(address(100));
uint256 public immutable epochPeriod; // Epochs mark the period between stateroot snapshots
address public immutable veaOutbox; // The vea outbox on ethereum.
mapping(uint256 => bytes32) public snapshots; // epoch => state root snapshot
// inbox represents minimum data availability to maintain incremental merkle tree.
// supports a max of 2^64 - 1 messages and will *never* overflow, see parameter docs.
bytes32[64] public inbox; // stores minimal set of complete subtree roots of the merkle tree to increment.
uint256 public count; // count of messages in the merkle tree
/**
* @dev Constructor.
* @param _epochPeriod The duration in seconds between epochs.
* @param _veaOutbox The veaOutbox on ethereum.
*/
constructor(uint256 _epochPeriod, address _veaOutbox) {
epochPeriod = _epochPeriod;
veaOutbox = _veaOutbox;
}
/**
* @dev Sends an arbitrary message to a receiving chain.
* `O(log(count))` where count is the number of messages already sent.
* Note: Amortized cost is O(1).
* @param to The address of the contract on the receiving chain which receives the calldata.
* @param fnSelector The function selector of the receiving contract.
* @param data The message calldata, abi.encode(param1, param2, ...)
* @return msgId The zero based index of the message in the inbox.
*/
function sendMessage(address to, bytes4 fnSelector, bytes memory data) external override returns (uint64) {
uint256 oldCount = count;
bytes memory nodeData = abi.encodePacked(
uint64(oldCount),
to,
// data for outbox relay
abi.encodePacked( // abi.encodeWithSelector(fnSelector, msg.sender, data)
fnSelector,
bytes32(uint256(uint160(msg.sender))), // big endian padded encoding of msg.sender, simulating abi.encodeWithSelector
data
)
);
// single hashed leaf
bytes32 newInboxNode = keccak256(nodeData);
// double hashed leaf
// avoids second order preimage attacks
// https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack/
assembly {
// efficient hash using EVM scratch space
mstore(0x00, newInboxNode)
newInboxNode := keccak256(0x00, 0x20)
}
// increment merkle tree calculating minimal number of hashes
unchecked {
uint256 height;
// x = oldCount + 1; acts as a bit mask to determine if a hash is needed
// note: x is always non-zero, and x is bit shifted to the right each loop
// hence this loop will always terminate in a maximum of log_2(oldCount + 1) iterations
for (uint256 x = oldCount + 1; x & 1 == 0; x = x >> 1) {
bytes32 oldInboxNode = inbox[height];
// sort sibling hashes as a convention for efficient proof validation
newInboxNode = sortConcatAndHash(oldInboxNode, newInboxNode);
height++;
}
inbox[height] = newInboxNode;
// finally increment count
count = oldCount + 1;
}
emit MessageSent(nodeData);
// old count is the zero indexed leaf position in the tree, acts as a msgId
// gateways should index these msgIds to later relay proofs
return uint64(oldCount);
}
/**
* Saves snapshot of state root.
* `O(log(count))` where count number of messages in the inbox.
* @dev Snapshots can be saved a maximum of once per epoch.
*/
function saveSnapshot() external {
uint256 epoch;
bytes32 stateRoot;
unchecked {
epoch = block.timestamp / epochPeriod;
require(snapshots[epoch] == bytes32(0), "Snapshot already taken for this epoch.");
// calculate the current root of the incremental merkle tree encoded in the inbox
uint256 height;
// x acts as a bit mask to determine if the hash stored in the inbox contributes to the root
uint256 x;
// x is bit shifted to the right each loop, hence this loop will always terminate in a maximum of log_2(count) iterations
for (x = count; x > 0; x = x >> 1) {
if ((x & 1) == 1) {
// first hash is special case
// inbox stores the root of complete subtrees
// eg if count = 4 = 0b100, then the first complete subtree is inbox[2]
// inbox = [H(m_3), H(H(m_1),H(m_2)) H(H(H(m_1),H(m_2)),H(H(m_3),H(m_4)))], we read inbox[2] directly
stateRoot = inbox[height];
break;
}
height++;
}
for (x = x >> 1; x > 0; x = x >> 1) {
height++;
if ((x & 1) == 1) {
bytes32 inboxHash = inbox[height];
// sort sibling hashes as a convention for efficient proof validation
stateRoot = sortConcatAndHash(inboxHash, stateRoot);
}
}
}
snapshots[epoch] = stateRoot;
emit SnapshotSaved(count);
}
/**
* @dev Helper function to calculate merkle tree interior nodes by sorting and concatenating and hashing sibling hashes.
* note: EVM scratch space is used to efficiently calculate hashes.
* @param child_1 The first sibling hash.
* @param child_2 The second sibling hash.
* @return parent The parent hash.
*/
function sortConcatAndHash(bytes32 child_1, bytes32 child_2) internal pure returns (bytes32 parent) {
// sort sibling hashes as a convention for efficient proof validation
// efficient hash using EVM scratch space
if (child_1 > child_2) {
assembly {
mstore(0x00, child_2)
mstore(0x20, child_1)
parent := keccak256(0x00, 0x40)
}
} else {
assembly {
mstore(0x00, child_1)
mstore(0x20, child_2)
parent := keccak256(0x00, 0x40)
}
}
}
/**
* @dev Sends the state root snapshot using Arbitrum's canonical bridge.
* @param epochSend The epoch of the snapshot requested to send.
*/
function sendSnapshot(uint256 epochSend, IVeaOutboxArbToEth.Claim memory claim) external virtual {
unchecked {
require(epochSend < block.timestamp / epochPeriod, "Can only send past epoch snapshot.");
}
bytes memory data = abi.encodeCall(
IVeaOutboxArbToEth.resolveDisputedClaim,
(epochSend, snapshots[epochSend], claim)
);
bytes32 ticketID = bytes32(ARB_SYS.sendTxToL1(veaOutbox, data));
emit SnapshotSent(epochSend, ticketID);
}
}