use std::cmp::Ordering;
use std::ops::Deref;
use namada_core::address::Address;
use namada_core::borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use namada_core::chain::Epoch;
use namada_core::collections::HashMap;
use namada_core::eth_abi::{AbiEncode, Encode, Token};
use namada_core::ethereum_events::EthAddress;
use namada_core::keccak::KeccakHash;
use namada_core::key::common::{self, Signature};
use namada_core::voting_power::{EthBridgeVotingPower, FractionalVotingPower};
use namada_core::{ethereum_structs, token};
use namada_macros::BorshDeserializer;
#[cfg(feature = "migrations")]
use namada_migrations::*;
use namada_tx::Signed;
const BRIDGE_CONTRACT_VERSION: u8 = 1;
const BRIDGE_CONTRACT_NAMESPACE: &str = "bridge";
const GOVERNANCE_CONTRACT_VERSION: u8 = 1;
const GOVERNANCE_CONTRACT_NAMESPACE: &str = "governance";
pub type VextDigest = ValidatorSetUpdateVextDigest;
#[derive(
Clone,
Debug,
PartialEq,
Eq,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
BorshSchema,
)]
pub struct ValidatorSetUpdateVextDigest {
pub signatures: HashMap<Address, Signature>,
pub voting_powers: VotingPowersMap,
}
impl VextDigest {
#[inline]
pub fn singleton(SignedVext(ext): SignedVext) -> VextDigest {
VextDigest {
signatures: HashMap::from([(
ext.data.validator_addr.clone(),
ext.sig,
)]),
voting_powers: ext.data.voting_powers,
}
}
pub fn decompress(self, signing_epoch: Epoch) -> Vec<SignedVext> {
let VextDigest {
signatures,
voting_powers,
} = self;
let mut extensions = vec![];
for (validator_addr, signature) in signatures.into_iter() {
let voting_powers = voting_powers.clone();
let data = Vext {
validator_addr,
voting_powers,
signing_epoch,
};
extensions.push(SignedVext(Signed::new_from(data, signature)));
}
extensions
}
}
#[derive(
Clone,
Debug,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
BorshSchema,
PartialEq,
Eq,
)]
pub struct SignedVext(pub Signed<Vext, SerializeWithAbiEncode>);
impl Deref for SignedVext {
type Target = Signed<Vext, SerializeWithAbiEncode>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub type Vext = ValidatorSetUpdateVext;
#[derive(
Eq,
PartialEq,
Clone,
Debug,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
BorshSchema,
)]
pub struct ValidatorSetUpdateVext {
pub voting_powers: VotingPowersMap,
pub validator_addr: Address,
pub signing_epoch: Epoch,
}
impl Vext {
#[inline]
pub fn sign(&self, sk: &common::SecretKey) -> SignedVext {
SignedVext(Signed::new(sk, self.clone()))
}
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
BorshSchema,
)]
pub struct EthAddrBook {
pub hot_key_addr: EthAddress,
pub cold_key_addr: EthAddress,
}
pub type VotingPowersMap = HashMap<EthAddrBook, token::Amount>;
pub trait VotingPowersMapExt {
fn get_sorted(&self) -> Vec<(&EthAddrBook, &token::Amount)>;
fn get_abi_encoded(&self) -> (Vec<Token>, Vec<Token>) {
let sorted = self.get_sorted();
let total_voting_power: token::Amount = token::Amount::sum(
sorted.iter().map(|&(_, &voting_power)| voting_power),
)
.expect("Voting power sum must not overflow");
sorted
.into_iter()
.map(|(addr_book, &voting_power)| {
let voting_power: EthBridgeVotingPower =
FractionalVotingPower::new(
voting_power.into(),
total_voting_power.into(),
)
.expect(
"Voting power in map can't be larger than the total \
voting power",
)
.try_into()
.expect(
"Must be able to convert to eth bridge voting power",
);
let &EthAddrBook {
hot_key_addr,
cold_key_addr,
} = addr_book;
(
Token::FixedBytes(
encode_validator_data(hot_key_addr, voting_power)
.into(),
),
Token::FixedBytes(
encode_validator_data(cold_key_addr, voting_power)
.into(),
),
)
})
.unzip()
}
#[inline]
fn get_bridge_and_gov_hashes(
&self,
next_epoch: Epoch,
) -> (KeccakHash, KeccakHash) {
let (bridge_validators, governance_validators) = self.get_abi_encoded();
valset_upd_toks_to_hashes(
next_epoch,
bridge_validators,
governance_validators,
)
}
}
pub fn valset_upd_toks_to_hashes(
next_epoch: Epoch,
bridge_validators: Vec<Token>,
governance_validators: Vec<Token>,
) -> (KeccakHash, KeccakHash) {
let bridge_hash = compute_hash(
next_epoch,
BRIDGE_CONTRACT_VERSION,
BRIDGE_CONTRACT_NAMESPACE,
bridge_validators,
);
let governance_hash = compute_hash(
next_epoch,
GOVERNANCE_CONTRACT_VERSION,
GOVERNANCE_CONTRACT_NAMESPACE,
governance_validators,
);
(bridge_hash, governance_hash)
}
fn compare_voting_powers_map_items(
first: &(&EthAddrBook, &token::Amount),
second: &(&EthAddrBook, &token::Amount),
) -> Ordering {
let (first_power, second_power) = (first.1, second.1);
let (first_addr, second_addr) = (first.0, second.0);
match second_power.cmp(first_power) {
Ordering::Equal => first_addr.cmp(second_addr),
ordering => ordering,
}
}
impl VotingPowersMapExt for VotingPowersMap {
fn get_sorted(&self) -> Vec<(&EthAddrBook, &token::Amount)> {
let mut pairs: Vec<_> = self.iter().collect();
pairs.sort_by(compare_voting_powers_map_items);
pairs
}
}
#[inline]
fn epoch_to_token(Epoch(e): Epoch) -> Token {
Token::Uint(e.into())
}
#[inline]
fn compute_hash(
next_epoch: Epoch,
contract_version: u8,
contract_namespace: &str,
validators: Vec<Token>,
) -> KeccakHash {
AbiEncode::keccak256(&[
Token::Uint(contract_version.into()),
Token::String(contract_namespace.into()),
Token::Array(validators),
epoch_to_token(next_epoch),
])
}
#[inline]
fn encode_validator_data(
address: EthAddress,
voting_power: EthBridgeVotingPower,
) -> [u8; 32] {
let address = address.0;
let voting_power = u128::from(voting_power).to_be_bytes();
let mut buffer = [0u8; 32];
buffer[..20].copy_from_slice(&address);
buffer[20..].copy_from_slice(&voting_power[4..]);
buffer
}
#[derive(
Debug,
Clone,
Default,
Eq,
PartialEq,
BorshSerialize,
BorshDeserialize,
BorshDeserializer,
BorshSchema,
)]
pub struct ValidatorSetArgs {
pub validators: Vec<EthAddress>,
pub voting_powers: Vec<EthBridgeVotingPower>,
pub epoch: Epoch,
}
impl From<ValidatorSetArgs> for ethereum_structs::ValidatorSetArgs {
fn from(valset: ValidatorSetArgs) -> Self {
let ValidatorSetArgs {
validators,
voting_powers,
epoch,
} = valset;
ethereum_structs::ValidatorSetArgs {
validator_set: validators
.into_iter()
.zip(voting_powers)
.map(|(addr, power)| encode_validator_data(addr, power))
.collect(),
nonce: epoch.0.into(),
}
}
}
impl Encode<1> for ValidatorSetArgs {
fn tokenize(&self) -> [Token; 1] {
let validator_set = Token::Array(
self.validators
.iter()
.zip(self.voting_powers.iter())
.map(|(&addr, &power)| {
Token::FixedBytes(encode_validator_data(addr, power).into())
})
.collect(),
);
let nonce = Token::Uint(self.epoch.0.into());
[Token::Tuple(vec![validator_set, nonce])]
}
}
mod tag {
use namada_core::eth_abi::{AbiEncode, Encode, Token};
use namada_core::hash::KeccakHasher;
use namada_core::keccak::KeccakHash;
use namada_core::key::Signable;
use serde::{Deserialize, Serialize};
use super::{
epoch_to_token, Vext, VotingPowersMapExt, GOVERNANCE_CONTRACT_VERSION,
};
#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)]
pub struct SerializeWithAbiEncode;
impl Signable<Vext> for SerializeWithAbiEncode {
type Hasher = KeccakHasher;
type Output = KeccakHash;
fn as_signable(ext: &Vext) -> Self::Output {
let next_epoch = ext.signing_epoch.next();
let (KeccakHash(bridge_hash), KeccakHash(gov_hash)) =
ext.voting_powers.get_bridge_and_gov_hashes(next_epoch);
AbiEncode::signable_keccak256(&[
Token::Uint(GOVERNANCE_CONTRACT_VERSION.into()),
Token::String("updateValidatorSet".into()),
Token::FixedBytes(bridge_hash.to_vec()),
Token::FixedBytes(gov_hash.to_vec()),
epoch_to_token(next_epoch),
])
}
}
}
#[doc(inline)]
pub use tag::SerializeWithAbiEncode;
#[cfg(test)]
mod tests {
use std::str::FromStr;
use data_encoding::HEXLOWER;
use super::*;
#[test]
fn test_validator_set_update_keccak_hash() {
const EXPECTED: &str =
"b97454f4c266c0d223651a52a705d76f3be337ace04be4590d9aedab9818dabc";
let KeccakHash(got) = compute_hash(
1u64.into(),
BRIDGE_CONTRACT_VERSION,
BRIDGE_CONTRACT_NAMESPACE,
vec![],
);
assert_eq!(&HEXLOWER.encode(&got[..]), EXPECTED);
}
#[test]
fn test_compare_voting_powers_map_items_identical_voting_powers() {
let same_voting_power = 200.into();
let validator_a = EthAddrBook {
hot_key_addr: EthAddress([0; 20]),
cold_key_addr: EthAddress([0; 20]),
};
let validator_b = EthAddrBook {
hot_key_addr: EthAddress([1; 20]),
cold_key_addr: EthAddress([1; 20]),
};
assert_eq!(
compare_voting_powers_map_items(
&(&validator_a, &same_voting_power),
&(&validator_b, &same_voting_power),
),
Ordering::Less
);
}
#[test]
fn test_compare_voting_powers_map_items_different_voting_powers() {
let validator_a = EthAddrBook {
hot_key_addr: EthAddress([0; 20]),
cold_key_addr: EthAddress([0; 20]),
};
let validator_a_voting_power = 200.into();
let validator_b = EthAddrBook {
hot_key_addr: EthAddress([1; 20]),
cold_key_addr: EthAddress([1; 20]),
};
let validator_b_voting_power = 100.into();
assert_eq!(
compare_voting_powers_map_items(
&(&validator_a, &validator_a_voting_power),
&(&validator_b, &validator_b_voting_power),
),
Ordering::Less
);
}
#[test]
fn test_voting_powers_map_get_abi_encoded_deterministic_with_identical_voting_powers()
{
let validator_a = EthAddrBook {
hot_key_addr: EthAddress([0; 20]),
cold_key_addr: EthAddress([0; 20]),
};
let validator_b = EthAddrBook {
hot_key_addr: EthAddress([1; 20]),
cold_key_addr: EthAddress([1; 20]),
};
let same_voting_power = 200.into();
let mut voting_powers_1 = VotingPowersMap::default();
voting_powers_1.insert(validator_a.clone(), same_voting_power);
voting_powers_1.insert(validator_b.clone(), same_voting_power);
let mut voting_powers_2 = VotingPowersMap::default();
voting_powers_2.insert(validator_b, same_voting_power);
voting_powers_2.insert(validator_a, same_voting_power);
let x = voting_powers_1.get_abi_encoded();
let y = voting_powers_2.get_abi_encoded();
assert_eq!(x, y);
}
#[test]
fn test_abi_encode_valset_args() {
let valset_update = ValidatorSetArgs {
validators: vec![
EthAddress::from_str(
"0x241D37B7Cf5233b3b0b204321420A86e8f7bfdb5",
)
.expect("Test failed"),
],
voting_powers: vec![8828299u64.into()],
epoch: 0.into(),
};
let encoded = valset_update.encode().into_inner();
let encoded = HEXLOWER.encode(&encoded);
let expected = "000000000000000000000000000000000000000000000000000000000000002\
000000000000000000000000000000000000000000000000000000000000000\
400000000000000000000000000000000000000000000000000000000000000\
000000000000000000000000000000000000000000000000000000000000000\
0001241d37b7cf5233b3b0b204321420a86e8f7bfdb50000000000000000008\
6b58b";
assert_eq!(expected, encoded);
}
}