diff --git a/pallets/governance/Cargo.toml b/pallets/governance/Cargo.toml index 5f10765..dafe092 100644 --- a/pallets/governance/Cargo.toml +++ b/pallets/governance/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-governance" -version = "0.3.0" +version = "0.3.1" description = "Full-chain and cross-chain governance pallet with early adopter share claims" license.workspace = true authors.workspace = true @@ -33,9 +33,7 @@ ghost-networks = { workspace = true } [dev-dependencies] libsecp256k1 = { workspace = true, default-features = true } -hex = { workspace = true } -# hex-literal = { workspace = true, default-features = true } -# serde_json = { workspace = true, default-features = true } +hex-literal = { workspace = true, default-features = true } [features] default = ["std"] @@ -43,7 +41,6 @@ std = [ "frame-benchmarking?/std", "serde/std", "codec/std", - "hex/std", "scale-info/std", "libsecp256k1/std", "frame-support/std", diff --git a/pallets/governance/src/benchmarking.rs b/pallets/governance/src/benchmarking.rs index 830de46..3ee9016 100644 --- a/pallets/governance/src/benchmarking.rs +++ b/pallets/governance/src/benchmarking.rs @@ -19,8 +19,8 @@ mod benchmarks { let minimum_donation_u128 = <::MinimumDonation as Get>::get(); let minimum_donation: BalanceOf = minimum_donation_u128.unique_saturated_into(); - let dummy_balance_u128 = 420u128; - let dummy_balance: BalanceOf = dummy_balance_u128.unique_saturated_into(); + let dummy_shares_u128 = 420u128; + let dummy_shares: BalanceOf = dummy_shares_u128.unique_saturated_into(); let secret_key = libsecp256k1::SecretKey::parse(&[69u8; 32]).unwrap(); let public_key = libsecp256k1::PublicKey::from_secret_key(&secret_key); @@ -42,12 +42,16 @@ mod benchmarks { raw_values, |index: TokenId| -> Result<(usize, Vec), ()> { let claim_package = ClaimPackage { - balance: dummy_balance, + shares: dummy_shares, merkle_proof: Default::default(), token_id: index, + index: index as u32, }; - let claim_preimage = claim_package.get_preimage(&dummy_evm_address); + let claim_preimage = claim_package.get_preimage( + &dummy_evm_address, + network_id, + ); Ok((index as usize, claim_preimage.to_vec())) } ).unwrap(); @@ -62,13 +66,13 @@ mod benchmarks { MemberShares::::insert(¤t_account, member_share); let network_state = NetworkShare::new( - dummy_balance, + dummy_shares, merkle_root, max_proof_depth, ); NetworkShares::::insert(&network_id, network_state); - let global_state = ShareState::new(dummy_balance); + let global_state = ShareState::new(dummy_shares); GlobalShares::::put(global_state); let merkle_proof = generate_proof::( @@ -78,12 +82,13 @@ mod benchmarks { ); let claim_package = ClaimPackage { + index: 0, merkle_proof: BoundedVec::try_from(merkle_proof).unwrap(), - balance: dummy_balance, + shares: dummy_shares, token_id: 0 }; - let preimage = claim_package.get_preimage(&dummy_evm_address); + let preimage = claim_package.get_preimage(&dummy_evm_address, network_id); let preimage_hash = SubstrateKeccakHasher::hash(&preimage); let message_string_bytes = Pallet::::ethereum_signable_message( @@ -116,13 +121,13 @@ mod benchmarks { evm_signature, ); - assert_eq!(dummy_balance, GlobalShares::::get().claimed_shares); + assert_eq!(dummy_shares, GlobalShares::::get().claimed_shares); assert_eq!( - dummy_balance, + dummy_shares, MemberShares::::get(¤t_account).activated_shares ); assert_eq!( - dummy_balance, + dummy_shares, NetworkShares::::get(&network_id).claimed_shares(), ); } diff --git a/pallets/governance/src/lib.rs b/pallets/governance/src/lib.rs index f526a4e..79c8490 100644 --- a/pallets/governance/src/lib.rs +++ b/pallets/governance/src/lib.rs @@ -143,8 +143,9 @@ pub struct ClaimPackage where Balance: Default + Copy + Clone, { + pub index: u32, pub token_id: TokenId, - pub balance: Balance, + pub shares: Balance, pub merkle_proof: BoundedVec>, } @@ -152,21 +153,33 @@ impl ClaimPackage where Balance: Default + Copy + Clone + UniqueSaturatedInto, { - pub fn get_preimage(&self, evm_address: &EvmAddress) -> [u8; 96] { + pub fn get_preimage( + &self, + evm_address: &EvmAddress, + network_id: NetworkId + ) -> [u8; 128] + where + NetworkId: Default + Copy + Clone + UniqueSaturatedInto, + { let mut token_id_bytes = [0u8; 32]; U256::from(self.token_id as u128).to_big_endian(&mut token_id_bytes); - let balance_u128: u128 = self.balance.unique_saturated_into(); - let mut balance_bytes = [0u8; 32]; - U256::from(balance_u128).to_big_endian(&mut balance_bytes); + let mut shares_bytes = [0u8; 32]; + let shares_u128: u128 = self.shares.unique_saturated_into(); + U256::from(shares_u128).to_big_endian(&mut shares_bytes); let mut address_bytes = [0u8; 32]; address_bytes[12..32].copy_from_slice(evm_address.as_bytes()); - let mut preimage = [0u8; 96]; + let mut network_id_bytes = [0u8; 32]; + let network_id_u128: u128 = network_id.unique_saturated_into(); + U256::from(network_id_u128).to_big_endian(&mut network_id_bytes); + + let mut preimage = [0u8; 128]; preimage[0..32].copy_from_slice(&token_id_bytes); - preimage[32..64].copy_from_slice(&balance_bytes); + preimage[32..64].copy_from_slice(&shares_bytes); preimage[64..96].copy_from_slice(&address_bytes); + preimage[96..128].copy_from_slice(&network_id_bytes); preimage } @@ -209,7 +222,7 @@ pub mod pallet { pub enum Event { SharesClaimed { who: T::AccountId, - balance: BalanceOf, + shares: BalanceOf, network_id: NetworkIdOf, } } @@ -320,7 +333,7 @@ pub mod pallet { ensure!(correct_network_bound, Error::::BoundedToOtherNetwork); - let preimage = claim_package.get_preimage(&evm_address); + let preimage = claim_package.get_preimage(&evm_address, network_id); let preimage_hash = SubstrateKeccakHasher::hash(&preimage); ensure!( @@ -336,25 +349,25 @@ pub mod pallet { let proof_size_match = package_proof_size == state.proofs_size; ensure!(proof_size_match, Error::::SharesOverflowed); - let increased = state.try_increase_claimed(claim_package.balance); + let increased = state.try_increase_claimed(claim_package.shares); ensure!(increased.is_some(), Error::::SharesOverflowed); Ok(()) })?; GlobalShares::::try_mutate(|state| -> DispatchResult { - let increased = state.try_increase_claimed(claim_package.balance); + let increased = state.try_increase_claimed(claim_package.shares); ensure!(increased.is_some(), Error::::SharesOverflowed); Ok(()) })?; - member_share.increase_activated_shares(claim_package.balance); + member_share.increase_activated_shares(claim_package.shares); member_share.initiate_on_network(network_id); MemberShares::::insert(&who, member_share); ClaimedHashes::::insert(&preimage_hash, true); Self::deposit_event(Event::::SharesClaimed { - balance: claim_package.balance, + shares: claim_package.shares, network_id, who, }); @@ -447,11 +460,11 @@ impl Pallet { let network_state = NetworkShares::::get(network_id); let is_merkle_proof_valid = - verify_tree_proof::( + verify_tree_proof::( preimage, &claim_package.merkle_proof, network_state.merkle_root, - claim_package.token_id, + claim_package.index, ); ensure!(is_merkle_proof_valid, Error::::InvalidMerkleProof); diff --git a/pallets/governance/src/mock.rs b/pallets/governance/src/mock.rs index 33c49f9..eb63c76 100644 --- a/pallets/governance/src/mock.rs +++ b/pallets/governance/src/mock.rs @@ -16,7 +16,7 @@ impl frame_system::Config for Test { type RuntimeCall = RuntimeCall; type Block = Block; type RuntimeEvent = RuntimeEvent; - type AccountData = pallet_balances::AccountData; + type AccountData = pallet_balances::AccountData; type MaxConsumers = frame_support::traits::ConstU32<16>; type AccountId = sp_runtime::AccountId32; @@ -25,7 +25,9 @@ impl frame_system::Config for Test { #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for Test { + type ExistentialDeposit = frame_support::traits::ConstU128<1>; type AccountStore = System; + type Balance = u128; } parameter_types! { diff --git a/pallets/governance/src/tests.rs b/pallets/governance/src/tests.rs index 6b58830..dd2e43c 100644 --- a/pallets/governance/src/tests.rs +++ b/pallets/governance/src/tests.rs @@ -2,6 +2,7 @@ use mock::*; use super::*; +use hex_literal::hex; use sp_runtime::AccountId32; use frame_support::traits::Get; @@ -15,7 +16,7 @@ pub struct NewTestUser { pub substrate_account: AccountId32, pub evm_secret: SecretKey, pub evm_address: EvmAddress, - pub allowed_balance: u64, + pub allowed_shares: u128, pub token_id: u128, } @@ -39,7 +40,7 @@ fn claim_happy_path() { substrate_account: AccountId32::new([11u8; 32]), evm_secret: secret_key_1, evm_address: EvmAddress::from(evm_addr_bytes_1), - allowed_balance: 420u64, + allowed_shares: 420u128, token_id: 0, }; @@ -55,7 +56,7 @@ fn claim_happy_path() { substrate_account: AccountId32::new([22u8; 32]), evm_secret: secret_key_2, evm_address: EvmAddress::from(evm_addr_bytes_2), - allowed_balance: 1337u64, + allowed_shares: 1337u128, token_id: 1, }; @@ -71,7 +72,7 @@ fn claim_happy_path() { substrate_account: AccountId32::new([69u8; 32]), evm_secret: secret_key_3, evm_address: EvmAddress::from(evm_addr_bytes_3), - allowed_balance: 69u64, + allowed_shares: 69u128, token_id: 2, }; @@ -87,12 +88,13 @@ fn claim_happy_path() { let (index, u) = item; let tmp_package = ClaimPackage { - balance: u.allowed_balance, + shares: u.allowed_shares, merkle_proof: Default::default(), token_id: u.token_id, + index: index as u32 }; - Ok((index as usize, tmp_package.get_preimage(&u.evm_address).to_vec())) + Ok((index as usize, tmp_package.get_preimage(&u.evm_address, network_id).to_vec())) } ).unwrap(); @@ -117,12 +119,13 @@ fn claim_happy_path() { let proof = generate_proof::(&merkle_tree, max_index, slot_index as u8); let claim_package = ClaimPackage { - balance: current_user.allowed_balance, + shares: current_user.allowed_shares, merkle_proof: BoundedVec::try_from(proof).unwrap(), token_id: current_user.token_id, + index: slot_index as u32, }; - let preimage = claim_package.get_preimage(¤t_user.evm_address); + let preimage = claim_package.get_preimage(¤t_user.evm_address, network_id); let preimage_hash = SubstrateKeccakHasher::hash(&preimage); let message_string_bytes = Governance::ethereum_signable_message( @@ -155,10 +158,10 @@ fn claim_happy_path() { )); let updated_share = MemberShares::::get(¤t_user.substrate_account); - assert_eq!(updated_share.activated_shares, current_user.allowed_balance); + assert_eq!(updated_share.activated_shares, current_user.allowed_shares); - global_activated += current_user.allowed_balance; - network_activated += current_user.allowed_balance; + global_activated += current_user.allowed_shares; + network_activated += current_user.allowed_shares; } assert_eq!(global_activated, GlobalShares::::get().claimed_shares); @@ -168,3 +171,139 @@ fn claim_happy_path() { ); }); } + +#[test] +fn verify_proof_from_python_preclaims_for_sepolia() { + new_test_ext().execute_with(|| { + // =========================================================================================================== + // === FINAL MERKLE ROOT FOR [SEPOLIA]: 0xd7f7f0061338c2c59a96caa8a2572491315755cdcce534954d4ce30150c3bb7c === + // =========================================================================================================== + // [+] Total padded leaves: 128 + // [+] Merkle tree size: 255 + // [+] Network ID: 11155111 + // [+] Proof size: 7 + // [+] Total shares: 694000000000000000000 + + let network_id = 11155111u64; + let merkle_root = EvmHash::from(hex!("d7f7f0061338c2c59a96caa8a2572491315755cdcce534954d4ce30150c3bb7c")); + let proof_size = 7; + let total_shares = 694000000000000000000; + + let network_state = NetworkShare::new(total_shares, merkle_root, proof_size); + let global_state = ShareState::new(total_shares); + NetworkShares::::insert(&network_id, network_state); + GlobalShares::::put(global_state); + + let evm_holder_1 = EvmAddress::from(hex!("D500EFDef75E89Bf6caF5C98F7633575d0049a72")); + let claim_package_1 = ClaimPackage { + index: 0, + token_id: 1, + shares: 670000000000000000000, + merkle_proof: BoundedVec::try_from( + vec![ + EvmHash::from(hex!("4dba08ba4fae6cbb0a6be6bdce81e91baf913d1857f470e97388157edd4d8d5c")), + EvmHash::from(hex!("7c1a4d2ebe6a500171b0c0f6c624898261da62d09c132106c4acec277660bd01")), + EvmHash::from(hex!("00c5bca05597b46f72d9ee739bd3c6ff80ebd1d20944879fe0c8d1dc9fda1643")), + EvmHash::from(hex!("5cf752cd738db6581182f07395e89cc61cd2ca630d6e85235d506f7395217d5f")), + EvmHash::from(hex!("4df068cecd63b6ccfd326df735afbfbe254df62613f7d9b52769f0e99f156957")), + EvmHash::from(hex!("d7f20777e3a1fed303fe1a459ce115e2561a92d66b34fd16dd793b8b69295082")), + EvmHash::from(hex!("4524fdc1aa98beafabe1341f53ee2373e541ad2efd152963fd9ce94571d283e4")), + ] + ).unwrap(), + }; + + let preimage_1 = claim_package_1.get_preimage(&evm_holder_1, network_id); + + assert_ok!( + Governance::verify_merkle_proof( + &network_id, + &preimage_1, + &claim_package_1, + ) + ); + + let evm_holder_2 = EvmAddress::from(hex!("Ee71538ae7677829676BD8dD18259Ed93E6B44e9")); + let claim_package_2 = ClaimPackage { + index: 2, + token_id: 3, + shares: 8000000000000000000, + merkle_proof: BoundedVec::try_from( + vec![ + EvmHash::from(hex!("a8bbe4e0578964e3e6c40b93380967d69b7a1fd0358bffe789473892ec82872d")), + EvmHash::from(hex!("9b771d13977930979a26632d74e350885d5750b00646a5b03114b5815afb9f2f")), + EvmHash::from(hex!("00c5bca05597b46f72d9ee739bd3c6ff80ebd1d20944879fe0c8d1dc9fda1643")), + EvmHash::from(hex!("5cf752cd738db6581182f07395e89cc61cd2ca630d6e85235d506f7395217d5f")), + EvmHash::from(hex!("4df068cecd63b6ccfd326df735afbfbe254df62613f7d9b52769f0e99f156957")), + EvmHash::from(hex!("d7f20777e3a1fed303fe1a459ce115e2561a92d66b34fd16dd793b8b69295082")), + EvmHash::from(hex!("4524fdc1aa98beafabe1341f53ee2373e541ad2efd152963fd9ce94571d283e4")), + ] + ).unwrap(), + }; + + let preimage_2 = claim_package_2.get_preimage(&evm_holder_2, network_id); + + assert_ok!( + Governance::verify_merkle_proof( + &network_id, + &preimage_2, + &claim_package_2, + ) + ); + + let evm_holder_3 = EvmAddress::from(hex!("29c6D9e3c3981e4FcF2AC6B486e4E4210daeA1Ee")); + let claim_package_3 = ClaimPackage { + index: 6, + token_id: 7, + shares: 4000000000000000000, + merkle_proof: BoundedVec::try_from( + vec![ + EvmHash::from(hex!("d28eec1544597d9e050c88a7a84535be82caf9b9e0b0c9b59b39283e7885a0d1")), + EvmHash::from(hex!("6e939188f98c3ed00931e66fdfc220c036b98edc54a212f5b18e2d3776c7c22f")), + EvmHash::from(hex!("4141baa2d892b0111bbb102078bd3ad5331c74f176b228c588741c1c306cb67d")), + EvmHash::from(hex!("5cf752cd738db6581182f07395e89cc61cd2ca630d6e85235d506f7395217d5f")), + EvmHash::from(hex!("4df068cecd63b6ccfd326df735afbfbe254df62613f7d9b52769f0e99f156957")), + EvmHash::from(hex!("d7f20777e3a1fed303fe1a459ce115e2561a92d66b34fd16dd793b8b69295082")), + EvmHash::from(hex!("4524fdc1aa98beafabe1341f53ee2373e541ad2efd152963fd9ce94571d283e4")) + ] + ).unwrap(), + }; + + let preimage_3 = claim_package_3.get_preimage(&evm_holder_3, network_id); + + assert_ok!( + Governance::verify_merkle_proof( + &network_id, + &preimage_3, + &claim_package_3, + ) + ); + + let evm_holder_4 = EvmAddress::from(hex!("Fe962E8BB2a6877d14b53E0a07932C040eC885ea")); + let claim_package_4 = ClaimPackage { + index: 35, + token_id: 36, + shares: 4000000000000000000, + merkle_proof: BoundedVec::try_from( + vec![ + EvmHash::from(hex!("6b405422bcab22b7c28a9b429096e31d664522154856efb0d7b16485080b2e2a")), + EvmHash::from(hex!("620bb1858eaf6c6bb543fcf5f4b0d0b5adfbb95506403038eb153f075a00f522")), + EvmHash::from(hex!("e0b7058849ff216808963201ad2336f5587ff02eff08aceddb4764cc785413fd")), + EvmHash::from(hex!("e23325a502fe4248c8e049400a92a1c655689499c129b0ce6be3fad5d4a12dc1")), + EvmHash::from(hex!("0cde8f646452003d6c8ff1fd265ea41cd3af2e733e63ec25a9dfc82ddf41e968")), + EvmHash::from(hex!("c012942dae89ee6d1e1e44e876ae29b9421aaf0597dd2345f07a1439a98e0cbd")), + EvmHash::from(hex!("4524fdc1aa98beafabe1341f53ee2373e541ad2efd152963fd9ce94571d283e4")) + ] + ).unwrap(), + }; + + let preimage_4 = claim_package_4.get_preimage(&evm_holder_4, network_id); + + assert_ok!( + Governance::verify_merkle_proof( + &network_id, + &preimage_4, + &claim_package_4, + ) + ); + }) +}