#![cfg_attr(not(feature = "std"), no_std)] use frame_support::{ ensure, traits::{Currency, Get}, pallet_prelude::*, }; use frame_system::pallet_prelude::*; use sp_core::U256; use sp_std::vec::Vec; use sp_io::crypto::secp256k1_ecdsa_recover; use sp_runtime::traits::{AtLeast32BitUnsigned, CheckedAdd, UniqueSaturatedInto}; use ghost_helpers::{ SubstrateKeccakHasher, merkle_tree::verify_tree_proof, networks::{NetworkData, NetworkCurve, NetworkType, NetworkClaim}, }; use ghost_traits::{ hashing::GhostHasher, networks::{ NetworkDataBasicHandler, NetworkDataInspectHandler, }, }; pub use pallet::*; mod weights; pub use weights::WeightInfo; mod benchmarking; mod mock; mod tests; const MAX_POSSIBLE_PROOF_DEPTH: u32 = 256; type TokenId = u128; type EvmHash = sp_core::H256; type EvmAddress = sp_core::H160; type EvmSignature = [u8; 65]; #[derive(Default, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct ShareState where Balance: Default + Copy + Clone, { total_shares: Balance, claimed_shares: Balance, } impl ShareState where Balance: AtLeast32BitUnsigned + Default + Copy + Clone, { pub fn new(total_shares: Balance) -> Self { let mut new_state = ShareState::default(); new_state.total_shares = total_shares; new_state } pub fn try_increase_claimed(&mut self, increase_by: Balance) -> Option { let new_claimed_shares = self.claimed_shares .saturating_add(increase_by); if new_claimed_shares > self.total_shares { return None; } self.claimed_shares = new_claimed_shares; Some(new_claimed_shares) } } #[derive(Default, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct NetworkShare where Balance: Default + Copy + Clone, { proofs_size: u32, merkle_root: EvmHash, share_state: ShareState } impl NetworkShare where Balance: AtLeast32BitUnsigned + Default + Copy + Clone, { pub fn new( total_shares: Balance, merkle_root: EvmHash, proofs_size: u32 ) -> Self { let share_state = ShareState::new(total_shares); Self { merkle_root, share_state, proofs_size } } pub fn total_shares(&self) -> Balance { self.share_state.total_shares } pub fn claimed_shares(&self) -> Balance { self.share_state.claimed_shares } pub fn try_increase_claimed(&mut self, increase_by: Balance) -> Option { self.share_state.try_increase_claimed(increase_by) } } #[derive(Default, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct MemberShare where Balance: AtLeast32BitUnsigned + Default + Copy, NetworkId: Default + Copy, { initiated_network: Option, activated_shares: Balance, locked_balance: Balance, } impl MemberShare where Balance: AtLeast32BitUnsigned + Default + Copy, NetworkId: Default + Copy, { pub fn increase_activated_shares(&mut self, extra_shares: Balance) { self.activated_shares = self.activated_shares .saturating_add(extra_shares); } pub fn initiate_on_network(&mut self, network_id: NetworkId) { self.initiated_network = Some(network_id); } pub fn lock_balance(&mut self, extra_balance: Balance) { self.locked_balance.saturating_add(extra_balance); } } #[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo, PartialEq, Eq)] pub struct ClaimPackage where Balance: Default + Copy + Clone, { pub index: u32, pub token_id: TokenId, pub shares: Balance, pub merkle_proof: BoundedVec>, } impl ClaimPackage where Balance: Default + Copy + Clone + UniqueSaturatedInto, { 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 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 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(&shares_bytes); preimage[64..96].copy_from_slice(&address_bytes); preimage[96..128].copy_from_slice(&network_id_bytes); preimage } } pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; pub type NetworkIdOf = <::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId; #[frame_support::pallet] pub mod pallet { use super::*; #[pallet::pallet] #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::config] pub trait Config: frame_system::Config + core::fmt::Debug { type RuntimeEvent: From> + IsType<::RuntimeEvent>; type Currency: Currency; type NetworkDataHandler: NetworkDataInspectHandler + NetworkDataBasicHandler; #[pallet::constant] type MinimumDonation: Get; #[pallet::constant] type MaxProofDepth: Get; type WeightInfo: WeightInfo; } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { SharesClaimed { who: T::AccountId, shares: BalanceOf, network_id: NetworkIdOf, } } #[pallet::error] pub enum Error { PreimageAlreadyClaimed, BoundedToOtherNetwork, AuthorityNotGovernor, NetworkHasNoGenesis, InvalidMerkleProof, SharesOverflowed, BadSignature, } #[pallet::storage] #[pallet::getter(fn global_shares)] pub type GlobalShares = StorageValue< _, ShareState>, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn network_shares)] pub type NetworkShares = StorageMap< _, Twox64Concat, NetworkIdOf, NetworkShare>, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn member_shares)] pub type MemberShares = CountedStorageMap< _, Blake2_256, T::AccountId, MemberShare, NetworkIdOf>, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn claimed_hashes)] pub type ClaimedHashes = StorageMap< _, Blake2_256, EvmHash, bool, ValueQuery, >; #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { pub network_claims: Vec, BalanceOf>>, } #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { let mut global_shares: BalanceOf = Default::default(); self.network_claims .iter() .for_each(|c| { assert!( c.proof_size <= T::MaxProofDepth::get(), "CRITICAL: constant T::MaxProofDepth is not enough for network {:?}", c.network_id, ); let network_state = NetworkShare::new(c.amount, c.merkle_root, c.proof_size); NetworkShares::::insert(c.network_id, network_state); global_shares = global_shares.checked_add(&c.amount) .expect("Global total shares overflowed"); }); let global_state = ShareState::new(global_shares); GlobalShares::::put(global_state); } } #[pallet::call] impl Pallet { #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::claim())] pub fn claim( origin: OriginFor, network_id: NetworkIdOf, claim_package: ClaimPackage>, evm_address: EvmAddress, evm_signature: EvmSignature, ) -> DispatchResult { let who = ensure_signed(origin)?; let mut member_share = MemberShares::::get(&who); let minimum_donation: BalanceOf = T::MinimumDonation::get() .unique_saturated_into(); ensure!( member_share.locked_balance >= minimum_donation, Error::::AuthorityNotGovernor, ); let correct_network_bound = member_share.initiated_network .map(|inner_network_id| inner_network_id == network_id) .unwrap_or(true); ensure!(correct_network_bound, Error::::BoundedToOtherNetwork); let preimage = claim_package.get_preimage(&evm_address, network_id); let preimage_hash = SubstrateKeccakHasher::hash(&preimage); ensure!( !ClaimedHashes::::contains_key(&preimage_hash), Error::::PreimageAlreadyClaimed ); Self::verify_signature(&who, &network_id, &preimage_hash, &evm_address, &evm_signature)?; Self::verify_merkle_proof(&network_id, &preimage, &claim_package)?; NetworkShares::::try_mutate(&network_id, |state| -> DispatchResult { let package_proof_size = claim_package.merkle_proof.len() as u32; let proof_size_match = package_proof_size == state.proofs_size; ensure!(proof_size_match, Error::::SharesOverflowed); 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.shares); ensure!(increased.is_some(), Error::::SharesOverflowed); Ok(()) })?; 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 { shares: claim_package.shares, network_id, who, }); Ok(()) } } } impl Pallet { fn to_ascii_hex(data: &[u8]) -> Vec { let mut r = Vec::with_capacity(data.len() * 2); let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n }); for &b in data.iter() { push_nibble(b / 16); push_nibble(b % 16); } r } fn ethereum_signable_message( receiver: &T::AccountId, network_id: &NetworkIdOf, preimage_hash: &EvmHash, ) -> Vec { let prefix = b"GMV Claim:"; let network_id_u64: u64 = (*network_id).unique_saturated_into(); let receiver_ascii_hex = Self::to_ascii_hex(&receiver.encode()); let preimage_ascii_hex = Self::to_ascii_hex(preimage_hash.as_ref()); let network_ascii_hex = Self::to_ascii_hex(&network_id_u64.to_be_bytes()); let what_length = receiver_ascii_hex.len() .saturating_add(preimage_ascii_hex.len()) .saturating_add(network_ascii_hex.len()) .saturating_add(10 + 11 + 12 + 11); // all 4 prefixes let mut what = Vec::with_capacity(what_length); what.extend_from_slice(prefix); what.extend_from_slice(b"\naccount:0x"); what.extend_from_slice(&receiver_ascii_hex); what.extend_from_slice(b"\npreimage:0x"); what.extend_from_slice(&preimage_ascii_hex); what.extend_from_slice(b"\nnetwork:0x"); what.extend_from_slice(&network_ascii_hex); let mut l = what.len(); let mut rev = Vec::new(); while l > 0 { rev.push(b'0' + (l % 10) as u8); l /= 10; } let mut v = Vec::with_capacity(26 + rev.len() + what.len()); v.extend_from_slice(b"\x19Ethereum Signed Message:\n"); v.extend(rev.into_iter().rev()); v.extend(what); v } fn verify_signature( who: &T::AccountId, network_id: &NetworkIdOf, preimage_hash: &EvmHash, evm_address: &EvmAddress, evm_signature: &EvmSignature, ) -> DispatchResult { let message = Self::ethereum_signable_message(who, network_id, preimage_hash); let message_bytes = SubstrateKeccakHasher::hash(&message).to_fixed_bytes(); let recovered_pubkey = secp256k1_ecdsa_recover(evm_signature, &message_bytes) .map_err(|_| Error::::BadSignature)?; let recovered_pubkey_hash = SubstrateKeccakHasher::hash(&recovered_pubkey); let recovered_address = EvmAddress::from_slice(&recovered_pubkey_hash[12..32]); ensure!(recovered_address == *evm_address, Error::::BadSignature); Ok(()) } fn verify_merkle_proof( network_id: &NetworkIdOf, preimage: &[u8], claim_package: &ClaimPackage>, ) -> DispatchResult { let network_state = NetworkShares::::get(network_id); let is_merkle_proof_valid = verify_tree_proof::( preimage, &claim_package.merkle_proof, network_state.merkle_root, claim_package.index, ); ensure!(is_merkle_proof_valid, Error::::InvalidMerkleProof); Ok(()) } }