-
Notifications
You must be signed in to change notification settings - Fork 33
/
State.sol
186 lines (161 loc) · 9.17 KB
/
State.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
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import {MemView, MemViewLib} from "./MemView.sol";
import {GAS_DATA_LENGTH, STATE_LENGTH, STATE_INVALID_SALT} from "../Constants.sol";
import {UnformattedState} from "../Errors.sol";
import {GasData, GasDataLib} from "../stack/GasData.sol";
/// State is a memory view over a formatted state payload.
type State is uint256;
using StateLib for State global;
/// # State
/// State structure represents the state of Origin contract at some point of time.
/// - State is structured in a way to track the updates of the Origin Merkle Tree.
/// - State includes root of the Origin Merkle Tree, origin domain and some additional metadata.
/// ## Origin Merkle Tree
/// Hash of every sent message is inserted in the Origin Merkle Tree, which changes
/// the value of Origin Merkle Root (which is the root for the mentioned tree).
/// - Origin has a single Merkle Tree for all messages, regardless of their destination domain.
/// - This leads to Origin state being updated if and only if a message was sent in a block.
/// - Origin contract is a "source of truth" for states: a state is considered "valid" in its Origin,
/// if it matches the state of the Origin contract after the N-th (nonce) message was sent.
///
/// # Memory layout of State fields
///
/// | Position | Field | Type | Bytes | Description |
/// | ---------- | ----------- | ------- | ----- | ------------------------------ |
/// | [000..032) | root | bytes32 | 32 | Root of the Origin Merkle Tree |
/// | [032..036) | origin | uint32 | 4 | Domain where Origin is located |
/// | [036..040) | nonce | uint32 | 4 | Amount of sent messages |
/// | [040..045) | blockNumber | uint40 | 5 | Block of last sent message |
/// | [045..050) | timestamp | uint40 | 5 | Time of last sent message |
/// | [050..062) | gasData | uint96 | 12 | Gas data for the chain |
///
/// @dev State could be used to form a Snapshot to be signed by a Guard or a Notary.
library StateLib {
using MemViewLib for bytes;
/// @dev The variables below are not supposed to be used outside of the library directly.
uint256 private constant OFFSET_ROOT = 0;
uint256 private constant OFFSET_ORIGIN = 32;
uint256 private constant OFFSET_NONCE = 36;
uint256 private constant OFFSET_BLOCK_NUMBER = 40;
uint256 private constant OFFSET_TIMESTAMP = 45;
uint256 private constant OFFSET_GAS_DATA = 50;
// ═══════════════════════════════════════════════════ STATE ═══════════════════════════════════════════════════════
/**
* @notice Returns a formatted State payload with provided fields
* @param root_ New merkle root
* @param origin_ Domain of Origin's chain
* @param nonce_ Nonce of the merkle root
* @param blockNumber_ Block number when root was saved in Origin
* @param timestamp_ Block timestamp when root was saved in Origin
* @param gasData_ Gas data for the chain
* @return Formatted state
*/
function formatState(
bytes32 root_,
uint32 origin_,
uint32 nonce_,
uint40 blockNumber_,
uint40 timestamp_,
GasData gasData_
) internal pure returns (bytes memory) {
return abi.encodePacked(root_, origin_, nonce_, blockNumber_, timestamp_, gasData_);
}
/**
* @notice Returns a State view over the given payload.
* @dev Will revert if the payload is not a state.
*/
function castToState(bytes memory payload) internal pure returns (State) {
return castToState(payload.ref());
}
/**
* @notice Casts a memory view to a State view.
* @dev Will revert if the memory view is not over a state.
*/
function castToState(MemView memView) internal pure returns (State) {
if (!isState(memView)) revert UnformattedState();
return State.wrap(MemView.unwrap(memView));
}
/// @notice Checks that a payload is a formatted State.
function isState(MemView memView) internal pure returns (bool) {
return memView.len() == STATE_LENGTH;
}
/// @notice Returns the hash of a State, that could be later signed by a Guard to signal
/// that the state is invalid.
function hashInvalid(State state) internal pure returns (bytes32) {
// The final hash to sign is keccak(stateInvalidSalt, keccak(state))
return state.unwrap().keccakSalted(STATE_INVALID_SALT);
}
/// @notice Convenience shortcut for unwrapping a view.
function unwrap(State state) internal pure returns (MemView) {
return MemView.wrap(State.unwrap(state));
}
/// @notice Compares two State structures.
function equals(State a, State b) internal pure returns (bool) {
// Length of a State payload is fixed, so we just need to compare the hashes
return a.unwrap().keccak() == b.unwrap().keccak();
}
// ═══════════════════════════════════════════════ STATE HASHING ═══════════════════════════════════════════════════
/// @notice Returns the hash of the State.
/// @dev We are using the Merkle Root of a tree with two leafs (see below) as state hash.
function leaf(State state) internal pure returns (bytes32) {
(bytes32 leftLeaf_, bytes32 rightLeaf_) = state.subLeafs();
// Final hash is the parent of these leafs
return keccak256(bytes.concat(leftLeaf_, rightLeaf_));
}
/// @notice Returns "sub-leafs" of the State. Hash of these "sub leafs" is going to be used
/// as a "state leaf" in the "Snapshot Merkle Tree".
/// This enables proving that leftLeaf = (root, origin) was a part of the "Snapshot Merkle Tree",
/// by combining `rightLeaf` with the remainder of the "Snapshot Merkle Proof".
function subLeafs(State state) internal pure returns (bytes32 leftLeaf_, bytes32 rightLeaf_) {
MemView memView = state.unwrap();
// Left leaf is (root, origin)
leftLeaf_ = memView.prefix({len_: OFFSET_NONCE}).keccak();
// Right leaf is (metadata), or (nonce, blockNumber, timestamp)
rightLeaf_ = memView.sliceFrom({index_: OFFSET_NONCE}).keccak();
}
/// @notice Returns the left "sub-leaf" of the State.
function leftLeaf(bytes32 root_, uint32 origin_) internal pure returns (bytes32) {
// We use encodePacked here to simulate the State memory layout
return keccak256(abi.encodePacked(root_, origin_));
}
/// @notice Returns the right "sub-leaf" of the State.
function rightLeaf(uint32 nonce_, uint40 blockNumber_, uint40 timestamp_, GasData gasData_)
internal
pure
returns (bytes32)
{
// We use encodePacked here to simulate the State memory layout
return keccak256(abi.encodePacked(nonce_, blockNumber_, timestamp_, gasData_));
}
// ═══════════════════════════════════════════════ STATE SLICING ═══════════════════════════════════════════════════
/// @notice Returns a historical Merkle root from the Origin contract.
function root(State state) internal pure returns (bytes32) {
return state.unwrap().index({index_: OFFSET_ROOT, bytes_: 32});
}
/// @notice Returns domain of chain where the Origin contract is deployed.
function origin(State state) internal pure returns (uint32) {
// Can be safely casted to uint32, since we index 4 bytes
return uint32(state.unwrap().indexUint({index_: OFFSET_ORIGIN, bytes_: 4}));
}
/// @notice Returns nonce of Origin contract at the time, when `root` was the Merkle root.
function nonce(State state) internal pure returns (uint32) {
// Can be safely casted to uint32, since we index 4 bytes
return uint32(state.unwrap().indexUint({index_: OFFSET_NONCE, bytes_: 4}));
}
/// @notice Returns a block number when `root` was saved in Origin.
function blockNumber(State state) internal pure returns (uint40) {
// Can be safely casted to uint40, since we index 5 bytes
return uint40(state.unwrap().indexUint({index_: OFFSET_BLOCK_NUMBER, bytes_: 5}));
}
/// @notice Returns a block timestamp when `root` was saved in Origin.
/// @dev This is the timestamp according to the origin chain.
function timestamp(State state) internal pure returns (uint40) {
// Can be safely casted to uint40, since we index 5 bytes
return uint40(state.unwrap().indexUint({index_: OFFSET_TIMESTAMP, bytes_: 5}));
}
/// @notice Returns gas data for the chain.
function gasData(State state) internal pure returns (GasData) {
return GasDataLib.wrapGasData(state.unwrap().indexUint({index_: OFFSET_GAS_DATA, bytes_: GAS_DATA_LENGTH}));
}
}