This repository has been archived by the owner on Jan 11, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 10
/
ipc.rs
542 lines (475 loc) · 21.2 KB
/
ipc.rs
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
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
// Copyright 2022-2023 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT
// The IPC actors have bindings in `ipc_actors_abis`.
// Here we define stable IDs for them, so we can deploy the
// Solidity contracts during genesis.
use anyhow::Context;
use ethers::core::abi::Tokenize;
use ethers::core::types as et;
use ethers::core::utils::keccak256;
use fendermint_vm_genesis::{Power, Validator};
use fvm_shared::address::Error as AddressError;
use fvm_shared::address::Payload;
use ipc_actors_abis as ia;
pub use ipc_actors_abis::gateway_router_facet::BottomUpCheckpoint;
use ipc_sdk::subnet_id::SubnetID;
use lazy_static::lazy_static;
use merkle_tree_rs::{
core::{process_proof, Hash},
format::Raw,
standard::{standard_leaf_hash, LeafType, StandardMerkleTree},
};
use crate::{
diamond::{EthContract, EthContractMap, EthFacet},
eam::{EthAddress, EAM_ACTOR_ID},
};
define_id!(GATEWAY { id: 64 });
define_id!(SUBNETREGISTRY { id: 65 });
lazy_static! {
/// Contracts deployed at genesis with well-known IDs.
pub static ref IPC_CONTRACTS: EthContractMap = {
[
(
gateway::CONTRACT_NAME,
EthContract {
actor_id: GATEWAY_ACTOR_ID,
abi: ia::gateway_diamond::GATEWAYDIAMOND_ABI.to_owned(),
facets: vec![
EthFacet {
name: "GatewayGetterFacet",
abi: ia::gateway_getter_facet::GATEWAYGETTERFACET_ABI.to_owned(),
},
EthFacet {
name: "GatewayManagerFacet",
abi: ia::gateway_manager_facet::GATEWAYMANAGERFACET_ABI.to_owned(),
},
EthFacet {
name: "GatewayRouterFacet",
abi: ia::gateway_router_facet::GATEWAYROUTERFACET_ABI.to_owned(),
},
EthFacet {
name: "GatewayMessengerFacet",
abi: ia::gateway_messenger_facet::GATEWAYMESSENGERFACET_ABI.to_owned(),
},
],
},
),
(
registry::CONTRACT_NAME,
EthContract {
actor_id: SUBNETREGISTRY_ACTOR_ID,
abi: ia::subnet_registry_diamond::SUBNETREGISTRYDIAMOND_ABI.to_owned(),
facets: vec![
// The registry incorporates the SubnetActor facets, although these aren't expected differently in the constructor.
EthFacet {
name: "SubnetActorGetterFacet",
abi: ia::subnet_actor_getter_facet::SUBNETACTORGETTERFACET_ABI
.to_owned(),
},
EthFacet {
name: "SubnetActorManagerFacet",
abi: ia::subnet_actor_manager_facet::SUBNETACTORMANAGERFACET_ABI
.to_owned(),
},
// The registry has its own facets:
// https://github.com/consensus-shipyard/ipc-solidity-actors/blob/b01a2dffe367745f55111a65536a3f6fea9165f5/scripts/deploy-registry.template.ts#L58-L67
EthFacet {
name: "RegisterSubnetFacet",
abi: ia::register_subnet_facet::REGISTERSUBNETFACET_ABI
.to_owned(),
},
EthFacet {
name: "SubnetGetterFacet",
abi: ia::subnet_getter_facet::SUBNETGETTERFACET_ABI
.to_owned(),
},
EthFacet {
name: "DiamondLoupeFacet",
abi: ia::diamond_loupe_facet::DIAMONDLOUPEFACET_ABI
.to_owned(),
},
EthFacet {
name: "DiamondCutFacet",
abi: ia::diamond_cut_facet::DIAMONDCUTFACET_ABI
.to_owned(),
},
],
},
),
]
.into_iter()
.collect()
};
/// Contracts that need to be deployed afresh for each subnet.
///
/// See [deploy-sa-diamond.ts](https://github.com/consensus-shipyard/ipc-solidity-actors/blob/dev/scripts/deploy-sa-diamond.ts)
///
/// But it turns out that the [SubnetRegistry](https://github.com/consensus-shipyard/ipc-solidity-actors/blob/3b0f3528b79e53e3c90f15016a40892122938ef0/src/SubnetRegistry.sol#L67)
/// actor has this `SubnetActorDiamond` and its facets baked into it, and able to deploy without further ado.
pub static ref SUBNET_CONTRACTS: EthContractMap = {
[
(
subnet::CONTRACT_NAME,
EthContract {
actor_id: 0,
abi: ia::subnet_actor_diamond::SUBNETACTORDIAMOND_ABI.to_owned(),
facets: vec![
EthFacet {
name: "SubnetActorGetterFacet",
abi: ia::subnet_actor_getter_facet::SUBNETACTORGETTERFACET_ABI.to_owned(),
},
EthFacet {
name: "SubnetActorManagerFacet",
abi: ia::subnet_actor_manager_facet::SUBNETACTORMANAGERFACET_ABI.to_owned(),
},
],
},
),
]
.into_iter()
.collect()
};
/// ABI types of the Merkle tree which contains validator addresses and their voting power.
pub static ref VALIDATOR_TREE_FIELDS: Vec<String> =
vec!["address".to_owned(), "uint256".to_owned()];
}
/// Construct a Merkle tree from the power table in a format which can be validated by
/// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MerkleProof.sol
///
/// The reference implementation is https://github.com/OpenZeppelin/merkle-tree/
pub struct ValidatorMerkleTree {
tree: StandardMerkleTree<Raw>,
}
impl ValidatorMerkleTree {
pub fn new(validators: &[Validator<Power>]) -> anyhow::Result<Self> {
// Using the 20 byte address for keys because that's what the Solidity library returns
// when recovering a public key from a signature.
let values = validators
.iter()
.map(Self::validator_to_vec)
.collect::<anyhow::Result<Vec<_>>>()?;
let tree = StandardMerkleTree::of(&values, &VALIDATOR_TREE_FIELDS)
.context("failed to construct Merkle tree")?;
Ok(Self { tree })
}
pub fn root_hash(&self) -> Hash {
self.tree.root()
}
/// Create a Merkle proof for a validator.
pub fn prove(&self, validator: &Validator<Power>) -> anyhow::Result<Vec<Hash>> {
let v = Self::validator_to_vec(validator)?;
let proof = self
.tree
.get_proof(LeafType::LeafBytes(v))
.context("failed to produce Merkle proof")?;
Ok(proof)
}
/// Validate a proof against a known root hash.
pub fn validate(
validator: &Validator<Power>,
root: &Hash,
proof: &[Hash],
) -> anyhow::Result<bool> {
let v = Self::validator_to_vec(validator)?;
let h = standard_leaf_hash(v, &VALIDATOR_TREE_FIELDS)?;
let r = process_proof(&h, proof).context("failed to process Merkle proof")?;
Ok(*root == r)
}
/// Convert a validator to what we can pass to the tree.
fn validator_to_vec(validator: &Validator<Power>) -> anyhow::Result<Vec<String>> {
let addr = EthAddress::from(validator.public_key.0);
let addr = et::Address::from_slice(&addr.0);
let addr = format!("{addr:?}");
let power = et::U256::from(validator.power.0);
let power = power.to_string();
Ok(vec![addr, power])
}
}
/// Decompose a subnet ID into a root ID and a route of Ethereum addresses
pub fn subnet_id_to_eth(subnet_id: &SubnetID) -> Result<(u64, Vec<et::Address>), AddressError> {
// Every step along the way in the subnet ID we have an Ethereum address.
let mut route = Vec::new();
for addr in subnet_id.children() {
let addr = match addr.payload() {
Payload::ID(id) => EthAddress::from_id(*id),
Payload::Delegated(da)
if da.namespace() == EAM_ACTOR_ID && da.subaddress().len() == 20 =>
{
EthAddress(da.subaddress().try_into().expect("checked length"))
}
_ => return Err(AddressError::InvalidPayload),
};
route.push(et::H160::from(addr.0))
}
Ok((subnet_id.root_id(), route))
}
/// Hash some value in the same way we'd hash it in Solidity.
///
/// Be careful that if we have to hash a single struct,
/// Solidity's `abi.encode` function will treat it as a tuple,
/// so it has to be passed as a tuple in Rust. Vectors are fine.
pub fn abi_hash<T: Tokenize>(value: T) -> [u8; 32] {
keccak256(ethers::abi::encode(&value.into_tokens()))
}
/// Types where we need to match the way we sign them in Solidity and Rust.
pub trait AbiHash {
/// Hash the item the way we would in Solidity.
fn abi_hash(self) -> [u8; 32];
}
macro_rules! abi_hash {
(struct $name:ty) => {
// Structs have to be hashed as a tuple.
impl AbiHash for $name {
fn abi_hash(self) -> [u8; 32] {
abi_hash((self,))
}
}
};
(Vec < $name:ty >) => {
// Vectors can be hashed as-is
impl AbiHash for Vec<$name> {
fn abi_hash(self) -> [u8; 32] {
abi_hash(self)
}
}
};
}
abi_hash!(struct ipc_actors_abis::gateway_router_facet::BottomUpCheckpoint);
abi_hash!(struct ipc_actors_abis::subnet_actor_manager_facet::BottomUpCheckpoint);
abi_hash!(Vec<ipc_actors_abis::gateway_getter_facet::CrossMsg>);
abi_hash!(Vec<ipc_actors_abis::subnet_actor_manager_facet::CrossMsg>);
abi_hash!(Vec<ipc_actors_abis::subnet_actor_getter_facet::CrossMsg>);
pub mod gateway {
use super::subnet_id_to_eth;
use ethers::contract::{EthAbiCodec, EthAbiType};
use ethers::core::types::{Bytes, H160, U256};
use fendermint_vm_genesis::ipc::GatewayParams;
use fendermint_vm_genesis::{Collateral, Validator};
use fvm_shared::address::Error as AddressError;
use fvm_shared::econ::TokenAmount;
pub use ipc_actors_abis::gateway_getter_facet::Validator as GatewayValidator;
use ipc_actors_abis::gateway_router_facet::SubnetID as GatewaySubnetID;
use crate::eam::EthAddress;
pub const CONTRACT_NAME: &str = "GatewayDiamond";
pub const METHOD_INVOKE_CONTRACT: u64 = crate::evm::Method::InvokeContract as u64;
// Constructor parameters aren't generated as part of the Rust bindings.
// TODO: Remove these once https://github.com/gakonst/ethers-rs/pull/2631 is merged.
/// Container type `ConstructorParameters`.
///
/// See [GatewayDiamond.sol](https://github.com/consensus-shipyard/ipc-solidity-actors/blob/255da67fd6ad885f0ab633311be276a4fa936d45/src/GatewayDiamond.sol#L21)
#[derive(Clone, EthAbiType, EthAbiCodec, Default, Debug, PartialEq, Eq, Hash)]
pub struct ConstructorParameters {
pub network_name: GatewaySubnetID,
pub bottom_up_check_period: u64,
pub min_collateral: U256,
pub msg_fee: U256,
pub majority_percentage: u8,
pub validators: Vec<GatewayValidator>,
pub active_validators_limit: u16,
}
impl ConstructorParameters {
pub fn new(
params: GatewayParams,
validators: Vec<Validator<Collateral>>,
) -> Result<Self, AddressError> {
// Every validator has an Ethereum address.
let validators = validators
.into_iter()
.map(|v| {
let pk = v.public_key.0.serialize();
let addr = EthAddress::new_secp256k1(&pk)?;
let collateral = tokens_to_u256(v.power.0);
Ok(GatewayValidator {
addr: H160::from(addr.0),
weight: collateral,
metadata: Bytes::from(pk),
})
})
.collect::<Result<Vec<_>, AddressError>>()?;
let (root, route) = subnet_id_to_eth(¶ms.subnet_id)?;
Ok(Self {
network_name: GatewaySubnetID { root, route },
bottom_up_check_period: params.bottom_up_check_period,
min_collateral: tokens_to_u256(params.min_collateral),
msg_fee: tokens_to_u256(params.msg_fee),
majority_percentage: params.majority_percentage,
validators,
active_validators_limit: params.active_validators_limit,
})
}
}
fn tokens_to_u256(value: TokenAmount) -> U256 {
// XXX: Ignoring any error resulting from larger fee than what fits into U256. This is in genesis after all.
U256::from_big_endian(&value.atto().to_bytes_be().1)
}
#[cfg(test)]
mod tests {
use ethers::core::types::{Selector, U256};
use ethers_core::{
abi::Tokenize,
types::{Bytes, H160},
};
use fvm_shared::{bigint::BigInt, econ::TokenAmount};
use ipc_actors_abis::gateway_getter_facet::Validator as GatewayValidator;
use ipc_actors_abis::gateway_router_facet::SubnetID as GatewaySubnetID;
use std::str::FromStr;
use crate::ipc::tests::{check_param_types, constructor_param_types};
use super::{tokens_to_u256, ConstructorParameters};
#[test]
fn tokenize_constructor_params() {
let cp = ConstructorParameters {
network_name: GatewaySubnetID {
root: 0,
route: Vec::new(),
},
bottom_up_check_period: 100,
min_collateral: U256::from(1),
msg_fee: U256::from(0),
majority_percentage: 67,
validators: vec![GatewayValidator {
addr: H160::zero(),
weight: U256::zero(),
metadata: Bytes::new(),
}],
active_validators_limit: 100,
};
// It looks like if we pass just the record then it will be passed as 5 tokens,
// but the constructor only expects one parameter, and it has to be a tuple.
let cp = (Vec::<Selector>::new(), cp);
let tokens = cp.into_tokens();
let cons = ipc_actors_abis::gateway_diamond::GATEWAYDIAMOND_ABI
.constructor()
.expect("Gateway has a constructor");
let param_types = constructor_param_types(cons);
check_param_types(&tokens, ¶m_types).unwrap();
cons.encode_input(vec![], &tokens)
.expect("should encode constructor input");
}
#[test]
#[should_panic]
fn max_fee_exceeded() {
let mut value = BigInt::from_str(&U256::MAX.to_string()).unwrap();
value += 1;
let value = TokenAmount::from_atto(value);
let _ = tokens_to_u256(value);
}
}
}
pub mod registry {
use ethers::contract::{EthAbiCodec, EthAbiType};
use ethers::core::types::Address;
type FunctionSelector = [u8; 4];
pub const CONTRACT_NAME: &str = "SubnetRegistryDiamond";
/// Container type `ConstructorParameters`.
///
/// See [SubnetRegistry.sol](https://github.com/consensus-shipyard/ipc-solidity-actors/blob/a830a52b1362f3d2abf2e3cc3db62aa40ee45355/src/SubnetRegistryDiamond.sol#L17-L23)
#[derive(Clone, EthAbiType, EthAbiCodec, Default, Debug, PartialEq, Eq, Hash)]
pub struct ConstructorParameters {
pub gateway: Address,
pub getter_facet: Address,
pub manager_facet: Address,
pub subnet_getter_selectors: Vec<FunctionSelector>,
pub subnet_manager_selectors: Vec<FunctionSelector>,
}
}
pub mod subnet {
use crate::revert_errors;
use ipc_actors_abis::gateway_manager_facet::GatewayManagerFacetErrors;
use ipc_actors_abis::gateway_router_facet::GatewayRouterFacetErrors;
use ipc_actors_abis::subnet_actor_manager_facet::SubnetActorManagerFacetErrors;
pub const CONTRACT_NAME: &str = "SubnetActorDiamond";
// The subnet actor has its own errors, but it also invokes the gateway, which might revert for its own reasons.
revert_errors! {
SubnetActorErrors {
SubnetActorManagerFacetErrors,
GatewayManagerFacetErrors,
GatewayRouterFacetErrors
}
}
#[cfg(test)]
mod tests {
use ethers::abi::{AbiType, Tokenize};
use ethers::core::types::Bytes;
use ipc_actors_abis::subnet_actor_manager_facet::{BottomUpCheckpoint, SubnetID};
#[test]
fn checkpoint_abi() {
// Some random checkpoint printed in a test that failed because the Rust ABI was different then the Solidity ABI.
let checkpoint = BottomUpCheckpoint {
subnet_id: SubnetID {
root: 12378393254986206693,
route: vec![
"0x7b11cf9ca8ccee13bb3d003c97af5c18434067a9",
"0x3d9019b8bf3bfd5e979ddc3b2761be54af867c47",
]
.into_iter()
.map(|h| h.parse().unwrap())
.collect(),
},
block_height: 21,
block_hash: [
107, 115, 111, 52, 42, 179, 77, 154, 254, 66, 52, 169, 43, 219, 25, 12, 53,
178, 232, 216, 34, 217, 96, 27, 0, 185, 215, 8, 155, 25, 15, 1,
],
next_configuration_number: 1,
cross_messages_hash: [
86, 158, 117, 252, 119, 193, 168, 86, 246, 218, 175, 158, 105, 216, 169, 86,
108, 163, 74, 164, 127, 145, 51, 113, 28, 224, 101, 165, 113, 175, 12, 253,
],
};
let param_type = BottomUpCheckpoint::param_type();
// Captured value of `abi.encode` in Solidity.
let expected_abi: Bytes = "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000156b736f342ab34d9afe4234a92bdb190c35b2e8d822d9601b00b9d7089b190f010000000000000000000000000000000000000000000000000000000000000001569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd000000000000000000000000000000000000000000000000abc8e314f58b4de5000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000007b11cf9ca8ccee13bb3d003c97af5c18434067a90000000000000000000000003d9019b8bf3bfd5e979ddc3b2761be54af867c47".parse().unwrap();
// XXX: It doesn't work with `decode_whole`.
let expected_tokens =
ethers::abi::decode(&[param_type], &expected_abi).expect("invalid Solidity ABI");
// The data needs to be wrapped into a tuple.
let observed_tokens = (checkpoint,).into_tokens();
let observed_abi: Bytes = ethers::abi::encode(&observed_tokens).into();
assert_eq!(observed_tokens, expected_tokens);
assert_eq!(observed_abi, expected_abi);
}
}
}
#[cfg(test)]
mod tests {
use anyhow::bail;
use ethers_core::abi::{Constructor, ParamType, Token};
use fendermint_vm_genesis::{Power, Validator};
use quickcheck_macros::quickcheck;
use super::ValidatorMerkleTree;
/// Check all tokens against expected parameters; return any offending one.
///
/// Based on [Tokens::types_check]
pub fn check_param_types(tokens: &[Token], param_types: &[ParamType]) -> anyhow::Result<()> {
if param_types.len() != tokens.len() {
bail!(
"different number of parameters; expected {}, got {}",
param_types.len(),
tokens.len()
);
}
for (i, (pt, t)) in param_types.iter().zip(tokens).enumerate() {
if !t.type_check(pt) {
bail!("parameter {i} didn't type check: expected {pt:?}, got {t:?}");
}
}
Ok(())
}
/// Returns all input params of given constructor.
///
/// Based on [Constructor::param_types]
pub fn constructor_param_types(cons: &Constructor) -> Vec<ParamType> {
cons.inputs.iter().map(|p| p.kind.clone()).collect()
}
#[quickcheck]
fn merkleize_validators(validators: Vec<Validator<Power>>) {
if validators.is_empty() {
return;
}
let tree = ValidatorMerkleTree::new(&validators).expect("failed to create tree");
let root = tree.root_hash();
let validator = validators.first().unwrap();
let proof = tree.prove(validator).expect("failed to prove");
assert!(ValidatorMerkleTree::validate(validator, &root, &proof).expect("failed to validate"))
}
}