|
| 1 | +#include "imports/stdlib.fc"; |
| 2 | +#include "imports/errors.fc"; |
| 3 | +#include "imports/utils.fc"; |
| 4 | +#include "imports/storage.fc"; |
| 5 | + |
| 6 | +;; Signature verification function |
| 7 | +;; ECRECOVER: Recovers the signer's address from the signature |
| 8 | +;; It returns 1 value (0) on failure and 4 values on success |
| 9 | +;; NULLSWAPIFNOT and NULLSWAPIFNOT2: Ensure consistent return of 4 values |
| 10 | +;; These opcodes swap nulls onto the stack if ECRECOVER fails, maintaining the 4-value return |
| 11 | +(int, int, int, int) check_sig (int hash, int v, int r, int s) asm |
| 12 | + "ECRECOVER" ;; Attempt to recover the signer's address |
| 13 | + "NULLSWAPIFNOT" ;; If recovery failed, insert null under the top of the stack |
| 14 | + "NULLSWAPIFNOT2"; ;; If recovery failed, insert two more nulls under the top of the stack |
| 15 | + |
| 16 | +;; Constants |
| 17 | +const int GUARDIAN_SET_EXPIRY = 86400; ;; 1 day in seconds |
| 18 | +const int UPGRADE_MODULE = 0x0000000000000000000000000000000000000000000000000000000000436f7265; ;; "Core" (left-padded to 256 bits) in hex |
| 19 | + |
| 20 | +;; For troubleshooting purposes |
| 21 | +() dump_guardian_sets(cell keys) impure { |
| 22 | + int key = -1; |
| 23 | + do { |
| 24 | + (key, slice value, int found) = keys.udict_get_next?(32, key); |
| 25 | + if (found) { |
| 26 | + ~dump(key); |
| 27 | + ~dump(value); |
| 28 | + } |
| 29 | + } until (~ found); |
| 30 | +} |
| 31 | + |
| 32 | + |
| 33 | +;; Internal helper methods |
| 34 | +(int, cell, int) parse_guardian_set(slice guardian_set) { |
| 35 | + slice cs = guardian_set~load_ref().begin_parse(); |
| 36 | + int expiration_time = cs~load_uint(64); |
| 37 | + ;; slice keys = cs~load_ref().begin_parse(); |
| 38 | + cell keys_dict = cs~load_dict(); |
| 39 | + int key_count = 0; |
| 40 | + int key = -1; |
| 41 | + do { |
| 42 | + (key, slice address, int found) = keys_dict.udict_get_next?(8, key); |
| 43 | + if (found) { |
| 44 | + key_count += 1; |
| 45 | + } |
| 46 | + } until (~ found); |
| 47 | + |
| 48 | + return (expiration_time, keys_dict, key_count); |
| 49 | +} |
| 50 | + |
| 51 | +(int, cell, int) get_guardian_set_internal(int index) { |
| 52 | + (slice guardian_set, int found?) = guardian_sets.udict_get?(32, index); |
| 53 | + throw_unless(ERROR_GUARDIAN_SET_NOT_FOUND, found?); |
| 54 | + (int expiration_time, cell keys, int key_count) = parse_guardian_set(guardian_set); |
| 55 | + return (expiration_time, keys, key_count); |
| 56 | +} |
| 57 | + |
| 58 | +;; store_data stores data in the contract |
| 59 | +() store_data() impure inline_ref { |
| 60 | + begin_cell() |
| 61 | + .store_uint(current_guardian_set_index, 32) |
| 62 | + .store_dict(guardian_sets) |
| 63 | + .store_uint(chain_id, 16) |
| 64 | + .store_uint(governance_chain_id, 16) |
| 65 | + .store_uint(governance_contract, 256) |
| 66 | + .store_dict(consumed_governance_actions) |
| 67 | + .end_cell() |
| 68 | + .set_data(); |
| 69 | +} |
| 70 | + |
| 71 | +;; load_data populates storage variables using stored data |
| 72 | +() load_data() impure inline_ref { |
| 73 | + var ds = get_data().begin_parse(); |
| 74 | + current_guardian_set_index = ds~load_uint(32); |
| 75 | + guardian_sets = ds~load_dict(); |
| 76 | + (int expiration_time, cell keys, int key_count) = get_guardian_set_internal(current_guardian_set_index); |
| 77 | + chain_id = ds~load_uint(16); |
| 78 | + governance_chain_id = ds~load_uint(16); |
| 79 | + governance_contract = ds~load_uint(256); |
| 80 | + consumed_governance_actions = ds~load_dict(); |
| 81 | + ds.end_parse(); |
| 82 | +} |
| 83 | + |
| 84 | + |
| 85 | +;; Get methods |
| 86 | +int get_current_guardian_set_index() method_id { |
| 87 | + return current_guardian_set_index; |
| 88 | +} |
| 89 | + |
| 90 | +(int, cell, int) get_guardian_set(int index) method_id { |
| 91 | + return get_guardian_set_internal(index); |
| 92 | +} |
| 93 | + |
| 94 | +int get_chain_id() method_id { |
| 95 | + return chain_id; |
| 96 | +} |
| 97 | + |
| 98 | +int get_governance_chain_id() method_id { |
| 99 | + return governance_chain_id; |
| 100 | +} |
| 101 | + |
| 102 | +int get_governance_contract() method_id { |
| 103 | + return governance_contract; |
| 104 | +} |
| 105 | + |
| 106 | +int governance_action_is_consumed(int hash) method_id { |
| 107 | + (_, int found?) = consumed_governance_actions.udict_get?(256, hash); |
| 108 | + return found?; |
| 109 | +} |
| 110 | + |
| 111 | + |
| 112 | +() verify_signatures(int hash, slice signatures, int signers_length, cell guardian_set_keys, int guardian_set_size) impure { |
| 113 | + slice cs = signatures; |
| 114 | + int i = 0; |
| 115 | + int valid_signatures = 0; |
| 116 | + |
| 117 | + while (i < signers_length) { |
| 118 | + int guardian_index = cs~load_uint(8); |
| 119 | + (_, int found?) = guardian_sets.udict_get?(32, guardian_index); |
| 120 | + throw_unless(ERROR_GUARDIAN_SET_NOT_FOUND, found?); |
| 121 | + int r = cs~load_uint(256); |
| 122 | + int s = cs~load_uint(256); |
| 123 | + int v = cs~load_uint(8); |
| 124 | + (_, int x1, int x2, int valid) = check_sig(hash, v >= 27 ? v - 27 : v, r, s); |
| 125 | + throw_unless(ERROR_INVALID_SIGNATURES, valid); |
| 126 | + int parsed_address = pubkey_to_eth_address(x1, x2); |
| 127 | + (slice guardian_key, int found?) = guardian_set_keys.udict_get?(8, guardian_index); |
| 128 | + int guardian_address = guardian_key~load_uint(160); |
| 129 | + throw_unless(ERROR_INVALID_GUARDIAN_ADDRESS, parsed_address == guardian_address); |
| 130 | + valid_signatures += 1; |
| 131 | + i += 1; |
| 132 | + } |
| 133 | + |
| 134 | + ;; Check quorum (2/3 + 1) |
| 135 | + ;; We're using a fixed point number transformation with 1 decimal to deal with rounding. |
| 136 | + throw_unless(ERROR_NO_QUORUM, valid_signatures >= (((guardian_set_size * 10) / 3) * 2) / 10 + 1); |
| 137 | +} |
| 138 | + |
| 139 | +(int, int, int, int, int, int, int, int, slice, int) parse_and_verify_wormhole_vm(slice in_msg_body) impure { |
| 140 | + ;; Parse VM fields |
| 141 | + int version = in_msg_body~load_uint(8); |
| 142 | + throw_unless(ERROR_INVALID_VERSION, version == 1); |
| 143 | + int vm_guardian_set_index = in_msg_body~load_uint(32); |
| 144 | + ;; Verify and check if guardian set is valid |
| 145 | + (int expiration_time, cell keys, int key_count) = get_guardian_set_internal(vm_guardian_set_index); |
| 146 | + throw_if(ERROR_INVALID_GUARDIAN_SET_KEYS_LENGTH, cell_null?(keys)); |
| 147 | + throw_unless(ERROR_INVALID_GUARDIAN_SET, |
| 148 | + (current_guardian_set_index == vm_guardian_set_index) & |
| 149 | + ((expiration_time == 0) | (expiration_time > now())) |
| 150 | + ); |
| 151 | + int signers_length = in_msg_body~load_uint(8); |
| 152 | + ;; Calculate signatures_size in bits (66 bytes per signature: 1 (guardianIndex) + 32 (r) + 32 (s) + 1 (v)) |
| 153 | + int signatures_size = signers_length * 66 * 8; |
| 154 | + |
| 155 | + ;; Load signatures |
| 156 | + (cell signatures, slice remaining_body) = read_and_store_large_data(in_msg_body, signatures_size); |
| 157 | + in_msg_body = remaining_body; |
| 158 | + |
| 159 | + ;; Calculate total body length across all references |
| 160 | + int body_length = 0; |
| 161 | + int continue? = -1; ;; -1 is true |
| 162 | + do { |
| 163 | + body_length += remaining_body.slice_bits(); |
| 164 | + if (remaining_body.slice_refs_empty?()) { |
| 165 | + continue? = 0; |
| 166 | + } else { |
| 167 | + remaining_body = remaining_body~load_ref().begin_parse(); |
| 168 | + } |
| 169 | + } until (~ continue?); |
| 170 | + |
| 171 | + ;; Load body |
| 172 | + (cell body_cell, _) = read_and_store_large_data(in_msg_body, body_length); |
| 173 | + |
| 174 | + int hash = hash_vm_body(body_cell.begin_parse()); |
| 175 | + ;; Verify signatures |
| 176 | + verify_signatures(hash, signatures.begin_parse(), signers_length, keys, key_count); |
| 177 | + |
| 178 | + slice body_slice = body_cell.begin_parse(); |
| 179 | + int timestamp = body_slice~load_uint(32); |
| 180 | + int nonce = body_slice~load_uint(32); |
| 181 | + int emitter_chain_id = body_slice~load_uint(16); |
| 182 | + int emitter_address = body_slice~load_uint(256); |
| 183 | + int sequence = body_slice~load_uint(64); |
| 184 | + int consistency_level = body_slice~load_uint(8); |
| 185 | + slice payload = body_slice; |
| 186 | + |
| 187 | + return ( |
| 188 | + version, |
| 189 | + vm_guardian_set_index, |
| 190 | + timestamp, |
| 191 | + nonce, |
| 192 | + emitter_chain_id, |
| 193 | + emitter_address, |
| 194 | + sequence, |
| 195 | + consistency_level, |
| 196 | + payload, |
| 197 | + hash |
| 198 | + ); |
| 199 | +} |
| 200 | + |
| 201 | +(int, int, int, cell, int) parse_encoded_upgrade(int current_guardian_set_index, slice payload) impure { |
| 202 | + int module = payload~load_uint(256); |
| 203 | + throw_unless(ERROR_INVALID_MODULE, module == UPGRADE_MODULE); |
| 204 | + |
| 205 | + int action = payload~load_uint(8); |
| 206 | + throw_unless(ERROR_INVALID_GOVERNANCE_ACTION, action == 2); |
| 207 | + |
| 208 | + int chain = payload~load_uint(16); |
| 209 | + int new_guardian_set_index = payload~load_uint(32); |
| 210 | + throw_unless(ERROR_NEW_GUARDIAN_SET_INDEX_IS_INVALID, new_guardian_set_index == (current_guardian_set_index + 1)); |
| 211 | + |
| 212 | + int guardian_length = payload~load_uint(8); |
| 213 | + cell new_guardian_set_keys = new_dict(); |
| 214 | + int key_count = 0; |
| 215 | + while (key_count < guardian_length) { |
| 216 | + builder key = begin_cell(); |
| 217 | + int key_bits_loaded = 0; |
| 218 | + while (key_bits_loaded < 160) { |
| 219 | + int bits_to_load = min(payload.slice_bits(), 160 - key_bits_loaded); |
| 220 | + key = key.store_slice(payload~load_bits(bits_to_load)); |
| 221 | + key_bits_loaded += bits_to_load; |
| 222 | + if (key_bits_loaded < 160) { |
| 223 | + throw_unless(ERROR_INVALID_GUARDIAN_SET_UPGRADE_LENGTH, ~ payload.slice_refs_empty?()); |
| 224 | + payload = payload~load_ref().begin_parse(); |
| 225 | + } |
| 226 | + } |
| 227 | + slice key_slice = key.end_cell().begin_parse(); |
| 228 | + new_guardian_set_keys~udict_set(8, key_count, key_slice); |
| 229 | + key_count += 1; |
| 230 | + } |
| 231 | + throw_unless(ERROR_GUARDIAN_SET_KEYS_LENGTH_NOT_EQUAL, key_count == guardian_length); |
| 232 | + throw_unless(ERROR_INVALID_GUARDIAN_SET_UPGRADE_LENGTH, payload.slice_empty?()); |
| 233 | + |
| 234 | + return (action, chain, module, new_guardian_set_keys, new_guardian_set_index); |
| 235 | +} |
| 236 | + |
| 237 | +() update_guardian_set(slice in_msg_body) impure { |
| 238 | + ;; Verify governance VM |
| 239 | + (int version, int vm_guardian_set_index, int timestamp, int nonce, int emitter_chain_id, int emitter_address, int sequence, int consistency_level, slice payload, int hash) = parse_and_verify_wormhole_vm(in_msg_body); |
| 240 | + |
| 241 | + ;; Verify the emitter chain and address |
| 242 | + int governance_chain_id = get_governance_chain_id(); |
| 243 | + throw_unless(ERROR_INVALID_GOVERNANCE_CHAIN, emitter_chain_id == governance_chain_id); |
| 244 | + int governance_contract_address = get_governance_contract(); |
| 245 | + throw_unless(ERROR_INVALID_GOVERNANCE_CONTRACT, emitter_address == governance_contract_address); |
| 246 | + |
| 247 | + ;; Check if the governance action has already been consumed |
| 248 | + throw_if(ERROR_GOVERNANCE_ACTION_ALREADY_CONSUMED, governance_action_is_consumed(hash)); |
| 249 | + |
| 250 | + ;; Parse the new guardian set from the payload |
| 251 | + (int action, int chain, int module, cell new_guardian_set_keys, int new_guardian_set_index) = parse_encoded_upgrade(current_guardian_set_index, payload); |
| 252 | + |
| 253 | + ;; Set expiry if current GuardianSet exists |
| 254 | + (slice current_guardian_set, int found?) = guardian_sets.udict_get?(32, current_guardian_set_index); |
| 255 | + if (found?) { |
| 256 | + (int expiration_time, cell keys, int key_count) = parse_guardian_set(current_guardian_set); |
| 257 | + cell updated_guardian_set = begin_cell() |
| 258 | + .store_uint(now() + GUARDIAN_SET_EXPIRY, 64) ;; expiration time |
| 259 | + .store_dict(keys) ;; keys |
| 260 | + .end_cell(); |
| 261 | + guardian_sets~udict_set(32, current_guardian_set_index, updated_guardian_set.begin_parse()); |
| 262 | + } |
| 263 | + |
| 264 | + ;; Store the new guardian set |
| 265 | + cell new_guardian_set = begin_cell() |
| 266 | + .store_uint(0, 64) ;; expiration_time, set to 0 initially |
| 267 | + .store_ref(new_guardian_set_keys) |
| 268 | + .end_cell(); |
| 269 | + guardian_sets~udict_set(32, new_guardian_set_index, new_guardian_set.begin_parse()); |
| 270 | + |
| 271 | + ;; Update the current guardian set index |
| 272 | + current_guardian_set_index = new_guardian_set_index; |
| 273 | + |
| 274 | + ;; Mark the governance action as consumed |
| 275 | + consumed_governance_actions~udict_set(256, hash, begin_cell().store_int(true, 1).end_cell().begin_parse()); |
| 276 | +} |
| 277 | + |
| 278 | +() execute_governance_action(slice in_msg_body) impure { |
| 279 | + ;; TODO: Implement |
| 280 | +} |
0 commit comments