From c6b63a6c58467d08df0a36e668bfecc4b31214e0 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Tue, 5 Dec 2023 11:21:57 +0100 Subject: [PATCH 01/34] Init commit --- Cargo.lock | 25 + Cargo.toml | 1 + pallets/dapp-staking-v3/src/lib.rs | 7 +- pallets/dapp-staking-v3/src/types.rs | 9 + precompiles/dapps-staking-v3/Cargo.toml | 56 ++ .../dapps-staking-v3/DappsStakingV1.sol | 90 +++ precompiles/dapps-staking-v3/src/lib.rs | 615 ++++++++++++++++ precompiles/dapps-staking-v3/src/mock.rs | 409 +++++++++++ precompiles/dapps-staking-v3/src/tests.rs | 695 ++++++++++++++++++ 9 files changed, 1903 insertions(+), 4 deletions(-) create mode 100644 precompiles/dapps-staking-v3/Cargo.toml create mode 100644 precompiles/dapps-staking-v3/DappsStakingV1.sol create mode 100644 precompiles/dapps-staking-v3/src/lib.rs create mode 100644 precompiles/dapps-staking-v3/src/mock.rs create mode 100644 precompiles/dapps-staking-v3/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 5ba5a7b2ac..65730d01c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7968,6 +7968,31 @@ dependencies = [ "substrate-bn", ] +[[package]] +name = "pallet-evm-precompile-dapp-staking-v3" +version = "0.1.0" +dependencies = [ + "derive_more", + "fp-evm", + "frame-support", + "frame-system", + "log", + "num_enum 0.5.11", + "pallet-balances", + "pallet-dapp-staking-v3", + "pallet-evm", + "pallet-timestamp", + "parity-scale-codec", + "precompile-utils", + "scale-info", + "serde", + "sha3", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-evm-precompile-dapps-staking" version = "3.6.3" diff --git a/Cargo.toml b/Cargo.toml index 423cec30c5..5d2e846b37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -293,6 +293,7 @@ pallet-evm-precompile-substrate-ecdsa = { path = "./precompiles/substrate-ecdsa" pallet-evm-precompile-xcm = { path = "./precompiles/xcm", default-features = false } pallet-evm-precompile-xvm = { path = "./precompiles/xvm", default-features = false } pallet-evm-precompile-dapps-staking = { path = "./precompiles/dapps-staking", default-features = false } +pallet-evm-precompile-dapp-staking-v3 = { path = "./precompiles/dapp-staking-v3", default-features = false } pallet-chain-extension-dapps-staking = { path = "./chain-extensions/dapps-staking", default-features = false } pallet-chain-extension-xvm = { path = "./chain-extensions/xvm", default-features = false } diff --git a/pallets/dapp-staking-v3/src/lib.rs b/pallets/dapp-staking-v3/src/lib.rs index e282f0959f..eccc8602bf 100644 --- a/pallets/dapp-staking-v3/src/lib.rs +++ b/pallets/dapp-staking-v3/src/lib.rs @@ -63,8 +63,7 @@ mod test; mod benchmarking; mod types; -use types::*; -pub use types::{PriceProvider, RewardPoolProvider, TierThreshold}; +pub use types::*; mod dsv3_weight; @@ -1640,7 +1639,7 @@ pub mod pallet { } /// Calculates the `EraRewardSpan` index for the specified era. - pub(crate) fn era_reward_span_index(era: EraNumber) -> EraNumber { + pub fn era_reward_span_index(era: EraNumber) -> EraNumber { era.saturating_sub(era % T::EraRewardSpanLength::get()) } @@ -1651,7 +1650,7 @@ pub mod pallet { } /// Unlocking period expressed in the number of blocks. - pub(crate) fn unlock_period() -> BlockNumberFor { + pub fn unlock_period() -> BlockNumberFor { T::StandardEraLength::get().saturating_mul(T::UnlockingPeriod::get().into()) } diff --git a/pallets/dapp-staking-v3/src/types.rs b/pallets/dapp-staking-v3/src/types.rs index b629a82774..3182befef7 100644 --- a/pallets/dapp-staking-v3/src/types.rs +++ b/pallets/dapp-staking-v3/src/types.rs @@ -85,6 +85,15 @@ pub type AccountLedgerFor = AccountLedger, ::M pub type DAppTierRewardsFor = DAppTierRewards<::MaxNumberOfContracts, ::NumberOfTiers>; +// Convenience type for `EraRewardSpan` usage. +pub type EraRewardSpanFor = EraRewardSpan<::EraRewardSpanLength>; + +// Convenience type for `DAppInfo` usage. +pub type DAppInfoFor = DAppInfo<::AccountId>; + +// Convenience type for `ProtocolState` usage. +pub type ProtocolStateFor = ProtocolState>; + /// Era number type pub type EraNumber = u32; /// Period number type diff --git a/precompiles/dapps-staking-v3/Cargo.toml b/precompiles/dapps-staking-v3/Cargo.toml new file mode 100644 index 0000000000..722a9504da --- /dev/null +++ b/precompiles/dapps-staking-v3/Cargo.toml @@ -0,0 +1,56 @@ +[package] +name = "pallet-evm-precompile-dapp-staking-v3" +version = "0.1.0" +license = "Apache-2.0" +description = "dApps Staking EVM precompiles" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +log = { workspace = true } +num_enum = { workspace = true } +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } + +frame-support = { workspace = true } +frame-system = { workspace = true } + +sp-core = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +# Astar +pallet-dapp-staking-v3 = { workspace = true } +precompile-utils = { workspace = true, default-features = false } + +# Frontier +fp-evm = { workspace = true } +pallet-evm = { workspace = true } + +[dev-dependencies] +derive_more = { workspace = true } +pallet-balances = { workspace = true, features = ["std"] } +pallet-timestamp = { workspace = true } +precompile-utils = { workspace = true, features = ["testing"] } +serde = { workspace = true } +sha3 = { workspace = true } +sp-io = { workspace = true } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "scale-info/std", + "sp-std/std", + "sp-core/std", + "sp-runtime/std", + "fp-evm/std", + "frame-support/std", + "frame-system/std", + "pallet-dapp-staking-v3/std", + "pallet-evm/std", + "precompile-utils/std", + "pallet-balances/std", +] diff --git a/precompiles/dapps-staking-v3/DappsStakingV1.sol b/precompiles/dapps-staking-v3/DappsStakingV1.sol new file mode 100644 index 0000000000..b6967d9247 --- /dev/null +++ b/precompiles/dapps-staking-v3/DappsStakingV1.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity >=0.7.0; + +/// Interface to the precompiled contract on Shibuya/Shiden/Astar +/// Predeployed at the address 0x0000000000000000000000000000000000005001 +/// For better understanding check the source code: +/// repo: https://github.com/AstarNetwork/astar +/// code: frame/dapps-staking/src/pallet +interface DappsStaking { + + // Storage getters + + /// @notice Read current era. + /// @return era: The current era + function read_current_era() external view returns (uint256); + + /// @notice Read unbonding period constant. + /// @return period: The unbonding period in eras + function read_unbonding_period() external view returns (uint256); + + /// @notice Read Total network reward for the given era + /// @return reward: Total network reward for the given era + function read_era_reward(uint32 era) external view returns (uint128); + + /// @notice Read Total staked amount for the given era + /// @return staked: Total staked amount for the given era + function read_era_staked(uint32 era) external view returns (uint128); + + /// @notice Read Staked amount for the staker + /// @param staker: The staker address in form of 20 or 32 hex bytes + /// @return amount: Staked amount by the staker + function read_staked_amount(bytes calldata staker) external view returns (uint128); + + /// @notice Read Staked amount on a given contract for the staker + /// @param contract_id: The smart contract address used for staking + /// @param staker: The staker address in form of 20 or 32 hex bytes + /// @return amount: Staked amount by the staker + function read_staked_amount_on_contract(address contract_id, bytes calldata staker) external view returns (uint128); + + /// @notice Read the staked amount from the era when the amount was last staked/unstaked + /// @return total: The most recent total staked amount on contract + function read_contract_stake(address contract_id) external view returns (uint128); + + + // Extrinsic calls + + /// @notice Register is root origin only and not allowed via evm precompile. + /// This should always fail. + function register(address) external; + + /// @notice Stake provided amount on the contract. + function bond_and_stake(address, uint128) external; + + /// @notice Start unbonding process and unstake balance from the contract. + function unbond_and_unstake(address, uint128) external; + + /// @notice Withdraw all funds that have completed the unbonding process. + function withdraw_unbonded() external; + + /// @notice Claim earned staker rewards for the oldest unclaimed era. + /// In order to claim multiple eras, this call has to be called multiple times. + /// Staker account is derived from the caller address. + /// @param smart_contract: The smart contract address used for staking + function claim_staker(address smart_contract) external; + + /// @notice Claim one era of unclaimed dapp rewards for the specified contract and era. + /// @param smart_contract: The smart contract address used for staking + /// @param era: The era to be claimed + function claim_dapp(address smart_contract, uint128 era) external; + + /// Instruction how to handle reward payout for staker. + /// `FreeBalance` - Reward will be paid out to the staker (free balance). + /// `StakeBalance` - Reward will be paid out to the staker and is immediately restaked (locked balance) + enum RewardDestination {FreeBalance, StakeBalance} + + /// @notice Set reward destination for staker rewards + /// @param reward_destination: The instruction on how the reward payout should be handled + function set_reward_destination(RewardDestination reward_destination) external; + + /// @notice Withdraw staked funds from an unregistered contract. + /// @param smart_contract: The smart contract address used for staking + function withdraw_from_unregistered(address smart_contract) external; + + /// @notice Transfer part or entire nomination from origin smart contract to target smart contract + /// @param origin_smart_contract: The origin smart contract address + /// @param amount: The amount to transfer from origin to target + /// @param target_smart_contract: The target smart contract address + function nomination_transfer(address origin_smart_contract, uint128 amount, address target_smart_contract) external; +} diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs new file mode 100644 index 0000000000..1420719277 --- /dev/null +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -0,0 +1,615 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +//! Astar dApps staking interface. + +#![cfg_attr(not(feature = "std"), no_std)] + +use fp_evm::{PrecompileHandle, PrecompileOutput}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; + +use frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, + traits::Currency, +}; +use frame_system::pallet_prelude::BlockNumberFor; + +use pallet_evm::{AddressMapping, Precompile}; +use precompile_utils::{ + error, revert, succeed, Address, Bytes, EvmData, EvmDataWriter, EvmResult, FunctionModifier, + PrecompileHandleExt, RuntimeHelper, +}; +use sp_core::H160; +use sp_runtime::traits::Zero; +use sp_std::marker::PhantomData; +use sp_std::prelude::*; +extern crate alloc; + +use pallet_dapp_staking_v3::{ + AccountLedgerFor, ActiveProtocolState, ContractStake, ContractStakeAmount, CurrentEraInfo, + DAppInfoFor, EraInfo, EraRewardSpanFor, EraRewards, IntegratedDApps, Ledger, + Pallet as DAppStaking, ProtocolStateFor, SingularStakingInfo, StakerInfo, +}; + +type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + +// #[cfg(test)] +// mod mock; +// #[cfg(test)] +// mod tests; + +/// This is only used to encode SmartContract enum +#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, Debug)] +pub enum Contract { + /// EVM smart contract instance. + Evm(H160), + /// Wasm smart contract instance. Not used in this precompile + Wasm(A), +} + +pub struct DappStakingV3Precompile(PhantomData); + +impl DappStakingV3Precompile +where + R: pallet_evm::Config + pallet_dapp_staking_v3::Config, + BalanceOf: EvmData, + BlockNumberFor: EvmData, + ::RuntimeOrigin: From>, + R::RuntimeCall: Dispatchable + GetDispatchInfo, + R::RuntimeCall: From>, + R::AccountId: From<[u8; 32]>, +{ + /// Read the ongoing `era` number. + fn read_current_era(handle: &mut impl PrecompileHandle) -> EvmResult { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: ActiveProtocolState: + // Twox64(8) + ActiveProtocolState::max_encoded_len + handle.record_db_read::(4 + ProtocolStateFor::::max_encoded_len())?; + + let current_era = ActiveProtocolState::::get().era; + + Ok(succeed(EvmDataWriter::new().write(current_era).build())) + } + + /// Read the `unbonding period` or `unlocking period` expressed in the number of blocks. + fn read_unbonding_period(_: &impl PrecompileHandle) -> EvmResult { + // constant, no DB read + Ok(succeed( + EvmDataWriter::new() + .write(DAppStaking::::unlock_period()) + .build(), + )) + } + + /// Read the total assigned reward pool for the given era. + /// + /// Total amount is sum of staker & dApp rewards. + fn read_era_reward(handle: &mut impl PrecompileHandle) -> EvmResult { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: EraRewards: + // Twox64Concat(8) + EraIndex(4) + EraRewardSpanFor::max_encoded_len + handle.record_db_read::(12 + EraRewardSpanFor::::max_encoded_len())?; + + let mut input = handle.read_input()?; + input.expect_arguments(1)?; + + // Parse era for which rewards are required + let era: u32 = input.read::()?; + + // Get the appropriate era reward span + let era_span_index = DAppStaking::::era_reward_span_index(era); + let reward_span = + EraRewards::::get(&era_span_index).unwrap_or(EraRewardSpanFor::::new()); + + // Sum up staker & dApp reward pools for the era + let reward = reward_span.get(era).map_or(Zero::zero(), |r| { + r.staker_reward_pool.saturating_add(r.dapp_reward_pool) + }); + + Ok(succeed(EvmDataWriter::new().write(reward).build())) + } + + /// Read the total staked amount for the given era. + /// + /// In case era is very far away in history, it's possible that the information is not available. + /// In that case, zero is returned. + /// + /// This is safe to use for current era and the next one. + fn read_era_staked(handle: &mut impl PrecompileHandle) -> EvmResult { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: ActiveProtocolState: + // Twox64(8) + ActiveProtocolState::max_encoded_len + handle.record_db_read::(4 + ProtocolStateFor::::max_encoded_len())?; + + let current_era = ActiveProtocolState::::get().era; + + // Parse era from the input + let mut input = handle.read_input()?; + input.expect_arguments(1)?; + let era: u32 = input.read::()?; + + // There are few distinct scenenarios: + // 1. Era is in the past so the value might exist. + // 2. Era is current or the next one, in which case we definitely have that information. + // 3. Era is from the future (more than the next era), in which case we don't have that information. + if era < current_era { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: EraRewards: + // Twox64Concat(8) + EraIndex(4) + EraRewardSpanFor::max_encoded_len + handle.record_db_read::(12 + EraRewardSpanFor::::max_encoded_len())?; + + let era_span_index = DAppStaking::::era_reward_span_index(era); + let reward_span = + EraRewards::::get(&era_span_index).unwrap_or(EraRewardSpanFor::::new()); + + let staked = reward_span.get(era).map_or(Zero::zero(), |r| r.staked); + + Ok(succeed(EvmDataWriter::new().write(staked).build())) + } else if era == current_era || era == current_era.saturating_add(1) { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: CurrentEraInfo: + // Twox64Concat(8) + EraInfo::max_encoded_len + handle.record_db_read::(8 + EraInfo::max_encoded_len())?; + + let current_era_info = CurrentEraInfo::::get(); + + if era == current_era { + Ok(succeed( + EvmDataWriter::new() + .write(current_era_info.current_stake_amount.total()) + .build(), + )) + } else { + Ok(succeed( + EvmDataWriter::new() + .write(current_era_info.next_stake_amount.total()) + .build(), + )) + } + } else { + Err(error("Era is in the future")) + } + } + + /// Read the total staked amount by the given account. + fn read_staked_amount(handle: &mut impl PrecompileHandle) -> EvmResult { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: ActiveProtocolState: + // Twox64(8) + ActiveProtocolState::max_encoded_len + // Storage item: Ledger: + // Blake2_128Concat(16 + 32) + Ledger::max_encoded_len + handle.record_db_read::( + 56 + AccountLedgerFor::::max_encoded_len() + + ProtocolStateFor::::max_encoded_len(), + )?; + + let mut input = handle.read_input()?; + input.expect_arguments(1)?; + + // parse the staker account + let staker_vec: Vec = input.read::()?.into(); + let staker = Self::parse_input_address(staker_vec)?; + + // read the account's ledger + let ledger = Ledger::::get(&staker); + log::trace!(target: "ds-precompile", "read_staked_amount for account: {:?}, ledger: {:?}", staker, ledger); + + // Make sure to check staked amount against the ongoing period (past period stakes are reset to zero). + let current_period_number = ActiveProtocolState::::get().period_number(); + + Ok(succeed( + EvmDataWriter::new() + .write(ledger.staked_amount(current_period_number)) + .build(), + )) + } + + /// Read the total staked amount by the given staker on the given contract. + fn read_staked_amount_on_contract( + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: ActiveProtocolState: + // Twox64(8) + ActiveProtocolState::max_encoded_len + // Storage item: StakerInfo: + // Blake2_128Concat(16 + 32) + Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len + handle.record_db_read::( + 72 + ProtocolStateFor::::max_encoded_len() + + ::SmartContract::max_encoded_len() + + SingularStakingInfo::max_encoded_len(), + )?; + + let mut input = handle.read_input()?; + input.expect_arguments(2)?; + + // parse contract address + let contract_h160 = input.read::
()?.0; + let contract_id = Self::decode_smart_contract(contract_h160)?; + + // parsae the staker account + let staker_vec: Vec = input.read::()?.into(); + let staker = Self::parse_input_address(staker_vec)?; + + // Get staking info for the staker/contract combination + let staking_info = StakerInfo::::get(&staker, &contract_id).unwrap_or_default(); + log::trace!(target: "ds-precompile", "read_staked_amount_on_contract for account:{:?}, staking_info: {:?}", staker, staking_info); + + // Ensure that the staking info is checked against the current period (stakes from past periods are reset) + let current_period_number = ActiveProtocolState::::get().period_number(); + + if staking_info.period_number() == current_period_number { + Ok(succeed( + EvmDataWriter::new() + .write(staking_info.total_staked_amount()) + .build(), + )) + } else { + Ok(succeed(EvmDataWriter::new().write(0_u128).build())) + } + } + + /// Read the amount staked on right now. + fn read_contract_stake(handle: &mut impl PrecompileHandle) -> EvmResult { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: ActiveProtocolState: + // Twox64(8) + ActiveProtocolState::max_encoded_len + // Storage item: IntegratedDApps: + // Blake2_128Concat(16 + SmartContract::max_encoded_len) + DAppInfoFor::max_encoded_len + // Storage item: ContractStake: + // Twox64Concat(8) + EraIndex(4) + ContractStakeAmount::max_encoded_len + handle.record_db_read::( + 36 + ProtocolStateFor::::max_encoded_len() + + ::SmartContract::max_encoded_len() + + DAppInfoFor::::max_encoded_len() + + ContractStakeAmount::max_encoded_len(), + )?; + + let mut input = handle.read_input()?; + input.expect_arguments(1)?; + + // parse input parameters for pallet-dapps-staking call + let contract_h160 = input.read::
()?.0; + let contract_id = Self::decode_smart_contract(contract_h160)?; + + let current_period_number = ActiveProtocolState::::get().period_number(); + let dapp_info = match IntegratedDApps::::get(&contract_id) { + Some(dapp_info) => dapp_info, + None => { + // If the contract is not registered, return 0 to keep the legacy behavior. + return Ok(succeed(EvmDataWriter::new().write(0_u128).build())); + } + }; + + // call pallet-dapps-staking + let contract_stake = ContractStake::::get(&dapp_info.id); + + Ok(succeed( + EvmDataWriter::new() + .write(contract_stake.total_staked_amount(current_period_number)) + .build(), + )) + } + + /// Register contract with the dapp-staking pallet + /// Register is root origin only. This should always fail when called via evm precompile. + fn register(_: &mut impl PrecompileHandle) -> EvmResult { + // register is root-origin call. it should always fail when called via evm precompiles. + Err(error("register via evm precompile is not allowed")) + } + + /// Lock up and stake balance of the origin account. + fn bond_and_stake(handle: &mut impl PrecompileHandle) -> EvmResult { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: ActiveProtocolState: + // Twox64(8) + ActiveProtocolState::max_encoded_len + // Storage item: Ledger: + // Blake2_128Concat(16 + 32) + Ledger::max_encoded_len + handle.record_db_read::( + 56 + AccountLedgerFor::::max_encoded_len() + + ProtocolStateFor::::max_encoded_len(), + )?; + + let mut input = handle.read_input()?; + input.expect_arguments(2)?; + + // Parse contract & amount + let contract_h160 = input.read::
()?.0; + let smart_contract = Self::decode_smart_contract(contract_h160)?; + let amount: BalanceOf = input.read()?; + + log::trace!(target: "ds-precompile", "bond_and_stake {:?}, {:?}", smart_contract, amount); + + // Read total locked & staked amounts + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let protocol_state = ActiveProtocolState::::get(); + let ledger = Ledger::::get(&origin); + + // Check if stakeable amount is enough to cover the given `amount` + let stakeable_amount = ledger.stakeable_amount(protocol_state.period_number()); + + // If it isn't, we need to first lock the additional amount. + if stakeable_amount < amount { + let delta = amount.saturating_sub(stakeable_amount); + + let lock_call = pallet_dapp_staking_v3::Call::::lock { amount: delta }; + RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), lock_call)?; + } + + // Now, with best effort, we can try & stake the given `value`. + let stake_call = pallet_dapp_staking_v3::Call::::stake { + smart_contract, + amount, + }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + // /// Start unbonding process and unstake balance from the contract. + // fn unbond_and_unstake(handle: &mut impl PrecompileHandle) -> EvmResult { + // let mut input = handle.read_input()?; + // input.expect_arguments(2)?; + + // // parse contract's address + // let contract_h160 = input.read::
()?.0; + // let contract_id = Self::decode_smart_contract(contract_h160)?; + + // // parse balance to be unstaked + // let value: BalanceOf = input.read()?; + // log::trace!(target: "ds-precompile", "unbond_and_unstake {:?}, {:?}", contract_id, value); + + // // Build call with origin. + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let call = pallet_dapp_staking_v3::Call::::unbond_and_unstake { contract_id, value }; + + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Start unbonding process and unstake balance from the contract. + // fn withdraw_unbonded(handle: &mut impl PrecompileHandle) -> EvmResult { + // // Build call with origin. + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let call = pallet_dapp_staking_v3::Call::::withdraw_unbonded {}; + + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Claim rewards for the contract in the dapps-staking pallet + // fn claim_dapp(handle: &mut impl PrecompileHandle) -> EvmResult { + // let mut input = handle.read_input()?; + // input.expect_arguments(2)?; + + // // parse contract's address + // let contract_h160 = input.read::
()?.0; + // let contract_id = Self::decode_smart_contract(contract_h160)?; + + // // parse era + // let era: u32 = input.read::()?; + // log::trace!(target: "ds-precompile", "claim_dapp {:?}, era {:?}", contract_id, era); + + // // Build call with origin. + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let call = pallet_dapp_staking_v3::Call::::claim_dapp { contract_id, era }; + + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Claim rewards for the contract in the dapps-staking pallet + // fn claim_staker(handle: &mut impl PrecompileHandle) -> EvmResult { + // let mut input = handle.read_input()?; + // input.expect_arguments(1)?; + + // // parse contract's address + // let contract_h160 = input.read::
()?.0; + // let contract_id = Self::decode_smart_contract(contract_h160)?; + // log::trace!(target: "ds-precompile", "claim_staker {:?}", contract_id); + + // // Build call with origin. + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let call = pallet_dapp_staking_v3::Call::::claim_staker { contract_id }; + + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + /// Set claim reward destination for the caller + fn set_reward_destination(_handle: &mut impl PrecompileHandle) -> EvmResult { + Err(error( + "set_reward_destination via evm precompile is no longer supported", + )) + } + + // /// Withdraw staked funds from the unregistered contract + // fn withdraw_from_unregistered( + // handle: &mut impl PrecompileHandle, + // ) -> EvmResult { + // let mut input = handle.read_input()?; + // input.expect_arguments(1)?; + + // // parse contract's address + // let contract_h160 = input.read::
()?.0; + // let contract_id = Self::decode_smart_contract(contract_h160)?; + // log::trace!(target: "ds-precompile", "withdraw_from_unregistered {:?}", contract_id); + + // // Build call with origin. + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let call = pallet_dapp_staking_v3::Call::::withdraw_from_unregistered { contract_id }; + + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Claim rewards for the contract in the dapps-staking pallet + // fn nomination_transfer(handle: &mut impl PrecompileHandle) -> EvmResult { + // let mut input = handle.read_input()?; + // input.expect_arguments(3)?; + + // // parse origin contract's address + // let origin_contract_h160 = input.read::
()?.0; + // let origin_contract_id = Self::decode_smart_contract(origin_contract_h160)?; + + // // parse balance to be transferred + // let value = input.read::>()?; + + // // parse target contract's address + // let target_contract_h160 = input.read::
()?.0; + // let target_contract_id = Self::decode_smart_contract(target_contract_h160)?; + + // log::trace!(target: "ds-precompile", "nomination_transfer {:?} {:?} {:?}", origin_contract_id, value, target_contract_id); + + // // Build call with origin. + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let call = pallet_dapp_staking_v3::Call::::nomination_transfer { + // origin_contract_id, + // value, + // target_contract_id, + // }; + + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + /// Helper method to decode type SmartContract enum + pub fn decode_smart_contract( + contract_h160: H160, + ) -> EvmResult<::SmartContract> { + // Encode contract address to fit SmartContract enum. + // Since the SmartContract enum type can't be accessed from this pecompile, + // use locally defined enum clone (see Contract enum) + let contract_enum_encoded = Contract::::Evm(contract_h160).encode(); + + // encoded enum will add one byte before the contract's address + // therefore we need to decode len(H160) + 1 byte = 21 + let smart_contract = ::SmartContract::decode( + &mut &contract_enum_encoded[..21], + ) + .map_err(|_| revert("Error while decoding SmartContract"))?; + + Ok(smart_contract) + } + + /// Helper method to parse H160 or SS58 address + fn parse_input_address(staker_vec: Vec) -> EvmResult { + let staker: R::AccountId = match staker_vec.len() { + // public address of the ss58 account has 32 bytes + 32 => { + let mut staker_bytes = [0_u8; 32]; + staker_bytes[..].clone_from_slice(&staker_vec[0..32]); + + staker_bytes.into() + } + // public address of the H160 account has 20 bytes + 20 => { + let mut staker_bytes = [0_u8; 20]; + staker_bytes[..].clone_from_slice(&staker_vec[0..20]); + + R::AddressMapping::into_account_id(staker_bytes.into()) + } + _ => { + // Return err if account length is wrong + return Err(revert("Error while parsing staker's address")); + } + }; + + Ok(staker) + } +} + +#[precompile_utils::generate_function_selector] +#[derive(Debug, PartialEq)] +pub enum Action { + ReadCurrentEra = "read_current_era()", + ReadUnbondingPeriod = "read_unbonding_period()", + ReadEraReward = "read_era_reward(uint32)", + ReadEraStaked = "read_era_staked(uint32)", + ReadStakedAmount = "read_staked_amount(bytes)", + ReadStakedAmountOnContract = "read_staked_amount_on_contract(address,bytes)", + ReadContractStake = "read_contract_stake(address)", + Register = "register(address)", + BondAndStake = "bond_and_stake(address,uint128)", + UnbondAndUnstake = "unbond_and_unstake(address,uint128)", + WithdrawUnbounded = "withdraw_unbonded()", + ClaimDapp = "claim_dapp(address,uint128)", + ClaimStaker = "claim_staker(address)", + SetRewardDestination = "set_reward_destination(uint8)", + WithdrawFromUnregistered = "withdraw_from_unregistered(address)", + NominationTransfer = "nomination_transfer(address,uint128,address)", +} + +impl Precompile for DappStakingV3Precompile +where + R: pallet_evm::Config + pallet_dapp_staking_v3::Config, + R::RuntimeCall: From> + + Dispatchable + + GetDispatchInfo, + ::RuntimeOrigin: From>, + BalanceOf: EvmData, + BlockNumberFor: EvmData, + R::AccountId: From<[u8; 32]>, +{ + fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { + log::trace!(target: "ds-precompile", "Execute input = {:?}", handle.input()); + + let selector = handle.read_selector()?; + + handle.check_function_modifier(match selector { + Action::ReadCurrentEra + | Action::ReadUnbondingPeriod + | Action::ReadEraReward + | Action::ReadEraStaked + | Action::ReadStakedAmount + | Action::ReadStakedAmountOnContract + | Action::ReadContractStake => FunctionModifier::View, + _ => FunctionModifier::NonPayable, + })?; + + match selector { + // read storage + Action::ReadCurrentEra => Self::read_current_era(handle), + Action::ReadUnbondingPeriod => Self::read_unbonding_period(handle), + Action::ReadEraReward => Self::read_era_reward(handle), + Action::ReadEraStaked => Self::read_era_staked(handle), + Action::ReadStakedAmount => Self::read_staked_amount(handle), + Action::ReadStakedAmountOnContract => Self::read_staked_amount_on_contract(handle), + Action::ReadContractStake => Self::read_contract_stake(handle), + + // Dispatchables + Action::Register => Self::register(handle), + Action::BondAndStake => Self::bond_and_stake(handle), + // Action::UnbondAndUnstake => Self::unbond_and_unstake(handle), + // Action::WithdrawUnbounded => Self::withdraw_unbonded(handle), + // Action::ClaimDapp => Self::claim_dapp(handle), + // Action::ClaimStaker => Self::claim_staker(handle), + Action::SetRewardDestination => Self::set_reward_destination(handle), + // Action::WithdrawFromUnregistered => Self::withdraw_from_unregistered(handle), + // Action::NominationTransfer => Self::nomination_transfer(handle), + _ => Err(error("Invalid function selector")), + } + } +} diff --git a/precompiles/dapps-staking-v3/src/mock.rs b/precompiles/dapps-staking-v3/src/mock.rs new file mode 100644 index 0000000000..976f7fd948 --- /dev/null +++ b/precompiles/dapps-staking-v3/src/mock.rs @@ -0,0 +1,409 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +use super::*; + +use fp_evm::IsPrecompileResult; +use frame_support::{ + construct_runtime, parameter_types, + traits::{ConstU64, Currency, OnFinalize, OnInitialize}, + weights::{RuntimeDbWeight, Weight}, + PalletId, +}; +use pallet_dapps_staking::weights; +use pallet_evm::{ + AddressMapping, EnsureAddressNever, EnsureAddressRoot, PrecompileResult, PrecompileSet, +}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use serde::{Deserialize, Serialize}; +use sp_core::{H160, H256}; +use sp_io::TestExternalities; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, ConstU32, IdentityLookup}, + AccountId32, +}; +extern crate alloc; + +pub(crate) type BlockNumber = u64; +pub(crate) type Balance = u128; +pub(crate) type EraIndex = u32; +pub(crate) const MILLIAST: Balance = 1_000_000_000_000_000; +pub(crate) const AST: Balance = 1_000 * MILLIAST; + +pub(crate) const TEST_CONTRACT: H160 = H160::repeat_byte(0x09); + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +/// Value shouldn't be less than 2 for testing purposes, otherwise we cannot test certain corner cases. +pub(crate) const MAX_NUMBER_OF_STAKERS: u32 = 4; +/// Value shouldn't be less than 2 for testing purposes, otherwise we cannot test certain corner cases. +pub(crate) const MINIMUM_STAKING_AMOUNT: Balance = 10 * AST; +pub(crate) const MINIMUM_REMAINING_AMOUNT: Balance = 1; +pub(crate) const MAX_UNLOCKING_CHUNKS: u32 = 4; +pub(crate) const UNBONDING_PERIOD: EraIndex = 3; +pub(crate) const MAX_ERA_STAKE_VALUES: u32 = 10; + +// Do note that this needs to at least be 3 for tests to be valid. It can be greater but not smaller. +pub(crate) const BLOCKS_PER_ERA: BlockNumber = 3; + +pub(crate) const REGISTER_DEPOSIT: Balance = 10 * AST; + +pub(crate) const STAKER_BLOCK_REWARD: Balance = 531911; +pub(crate) const DAPP_BLOCK_REWARD: Balance = 773333; + +#[derive( + Eq, + PartialEq, + Ord, + PartialOrd, + Clone, + Encode, + Decode, + Debug, + MaxEncodedLen, + Serialize, + Deserialize, + derive_more::Display, + scale_info::TypeInfo, +)] + +pub enum TestAccount { + Empty, + Alex, + Bobo, + Dino, +} + +impl Default for TestAccount { + fn default() -> Self { + Self::Empty + } +} + +// needed for associated type in pallet_evm +impl AddressMapping for TestAccount { + fn into_account_id(h160_account: H160) -> AccountId32 { + match h160_account { + a if a == H160::repeat_byte(0x01) => TestAccount::Alex.into(), + a if a == H160::repeat_byte(0x02) => TestAccount::Bobo.into(), + a if a == H160::repeat_byte(0x03) => TestAccount::Dino.into(), + _ => TestAccount::Empty.into(), + } + } +} + +impl From for H160 { + fn from(x: TestAccount) -> H160 { + match x { + TestAccount::Alex => H160::repeat_byte(0x01), + TestAccount::Bobo => H160::repeat_byte(0x02), + TestAccount::Dino => H160::repeat_byte(0x03), + _ => Default::default(), + } + } +} + +trait H160Conversion { + fn to_h160(&self) -> H160; +} + +impl H160Conversion for AccountId32 { + fn to_h160(&self) -> H160 { + let x = self.encode()[31]; + H160::repeat_byte(x) + } +} + +impl From for AccountId32 { + fn from(x: TestAccount) -> Self { + match x { + TestAccount::Alex => AccountId32::from([1u8; 32]), + TestAccount::Bobo => AccountId32::from([2u8; 32]), + TestAccount::Dino => AccountId32::from([3u8; 32]), + _ => AccountId32::from([0u8; 32]), + } + } +} + +pub const READ_WEIGHT: u64 = 3; +pub const WRITE_WEIGHT: u64 = 7; + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, 0)); + pub const TestWeights: RuntimeDbWeight = RuntimeDbWeight { + read: READ_WEIGHT, + write: WRITE_WEIGHT, + }; +} + +impl frame_system::Config for TestRuntime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type RuntimeCall = RuntimeCall; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type DbWeight = TestWeights; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const ExistentialDeposit: u128 = 1; +} +impl pallet_balances::Config for TestRuntime { + type MaxReserves = (); + type ReserveIdentifier = [u8; 4]; + type MaxLocks = (); + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} + +pub fn precompile_address() -> H160 { + H160::from_low_u64_be(0x5001) +} + +#[derive(Debug, Clone, Copy)] +pub struct DappPrecompile(PhantomData); + +impl PrecompileSet for DappPrecompile +where + R: pallet_evm::Config, + DappsStakingWrapper: Precompile, +{ + fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { + match handle.code_address() { + a if a == precompile_address() => Some(DappsStakingWrapper::::execute(handle)), + _ => None, + } + } + + fn is_precompile(&self, address: sp_core::H160, _gas: u64) -> IsPrecompileResult { + IsPrecompileResult::Answer { + is_precompile: address == precompile_address(), + extra_cost: 0, + } + } +} + +parameter_types! { + pub PrecompilesValue: DappPrecompile = DappPrecompile(Default::default()); + pub WeightPerGas: Weight = Weight::from_parts(1, 0); +} + +impl pallet_evm::Config for TestRuntime { + type FeeCalculator = (); + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + type CallOrigin = EnsureAddressRoot; + type WithdrawOrigin = EnsureAddressNever; + type AddressMapping = TestAccount; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type Runner = pallet_evm::runner::stack::Runner; + type PrecompilesType = DappPrecompile; + type PrecompilesValue = PrecompilesValue; + type Timestamp = Timestamp; + type ChainId = (); + type OnChargeTransaction = (); + type BlockGasLimit = (); + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type FindAuthor = (); + type OnCreate = (); + type WeightInfo = (); + type GasLimitPovSizeRatio = ConstU64<4>; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 5; +} +impl pallet_timestamp::Config for TestRuntime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +#[derive( + PartialEq, Eq, Copy, Clone, Encode, Decode, Debug, scale_info::TypeInfo, MaxEncodedLen, +)] +pub enum MockSmartContract { + Evm(sp_core::H160), + Wasm(AccountId32), +} + +impl Default for MockSmartContract { + fn default() -> Self { + MockSmartContract::Evm(H160::repeat_byte(0x00)) + } +} + +parameter_types! { + pub const RegisterDeposit: Balance = REGISTER_DEPOSIT; + pub const BlockPerEra: BlockNumber = BLOCKS_PER_ERA; + pub const MaxNumberOfStakersPerContract: u32 = MAX_NUMBER_OF_STAKERS; + pub const MinimumStakingAmount: Balance = MINIMUM_STAKING_AMOUNT; + pub const DappsStakingPalletId: PalletId = PalletId(*b"mokdpstk"); + pub const MinimumRemainingAmount: Balance = MINIMUM_REMAINING_AMOUNT; + pub const MaxUnlockingChunks: u32 = MAX_UNLOCKING_CHUNKS; + pub const UnbondingPeriod: EraIndex = UNBONDING_PERIOD; + pub const MaxEraStakeValues: u32 = MAX_ERA_STAKE_VALUES; +} + +impl pallet_dapps_staking::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type BlockPerEra = BlockPerEra; + type RegisterDeposit = RegisterDeposit; + type SmartContract = MockSmartContract; + type WeightInfo = weights::SubstrateWeight; + type MaxNumberOfStakersPerContract = MaxNumberOfStakersPerContract; + type MinimumStakingAmount = MinimumStakingAmount; + type PalletId = DappsStakingPalletId; + type MinimumRemainingAmount = MinimumRemainingAmount; + type MaxUnlockingChunks = MaxUnlockingChunks; + type UnbondingPeriod = UnbondingPeriod; + type MaxEraStakeValues = MaxEraStakeValues; + type UnregisteredDappRewardRetention = ConstU32<2>; +} + +pub struct ExternalityBuilder { + balances: Vec<(AccountId32, Balance)>, +} + +impl Default for ExternalityBuilder { + fn default() -> ExternalityBuilder { + ExternalityBuilder { balances: vec![] } + } +} + +impl ExternalityBuilder { + pub fn build(self) -> TestExternalities { + let mut storage = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + pallet_balances::GenesisConfig:: { + balances: self.balances, + } + .assimilate_storage(&mut storage) + .ok(); + + let mut ext = TestExternalities::from(storage); + ext.execute_with(|| System::set_block_number(1)); + ext + } + + pub(crate) fn with_balances(mut self, balances: Vec<(AccountId32, Balance)>) -> Self { + self.balances = balances; + self + } +} + +construct_runtime!( + pub struct TestRuntime + where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + Evm: pallet_evm, + Timestamp: pallet_timestamp, + DappsStaking: pallet_dapps_staking, + } +); + +/// Used to run to the specified block number +pub fn run_to_block(n: u64) { + while System::block_number() < n { + DappsStaking::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + // This is performed outside of dapps staking but we expect it before on_initialize + payout_block_rewards(); + DappsStaking::on_initialize(System::block_number()); + } +} + +/// Used to run the specified number of blocks +pub fn run_for_blocks(n: u64) { + run_to_block(System::block_number() + n); +} + +/// Advance blocks to the beginning of an era. +/// +/// Function has no effect if era is already passed. +pub fn advance_to_era(n: EraIndex) { + while DappsStaking::current_era() < n { + run_for_blocks(1); + } +} + +/// Initialize first block. +/// This method should only be called once in a UT otherwise the first block will get initialized multiple times. +pub fn initialize_first_block() { + // This assert prevents method misuse + assert_eq!(System::block_number(), 1 as BlockNumber); + + // This is performed outside of dapps staking but we expect it before on_initialize + payout_block_rewards(); + DappsStaking::on_initialize(System::block_number()); + run_to_block(2); +} + +/// Returns total block rewards that goes to dapps-staking. +/// Contains both `dapps` reward and `stakers` reward. +pub fn joint_block_reward() -> Balance { + STAKER_BLOCK_REWARD + DAPP_BLOCK_REWARD +} + +/// Payout block rewards to stakers & dapps +fn payout_block_rewards() { + DappsStaking::rewards( + Balances::issue(STAKER_BLOCK_REWARD.into()), + Balances::issue(DAPP_BLOCK_REWARD.into()), + ); +} diff --git a/precompiles/dapps-staking-v3/src/tests.rs b/precompiles/dapps-staking-v3/src/tests.rs new file mode 100644 index 0000000000..1285673b40 --- /dev/null +++ b/precompiles/dapps-staking-v3/src/tests.rs @@ -0,0 +1,695 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +extern crate alloc; +use crate::{ + mock::{ + advance_to_era, initialize_first_block, precompile_address, DappsStaking, EraIndex, + ExternalityBuilder, RuntimeOrigin, TestAccount, AST, UNBONDING_PERIOD, *, + }, + *, +}; +use fp_evm::ExitError; +use frame_support::assert_ok; +use pallet_dapps_staking::RewardDestination; +use precompile_utils::testing::*; +use sp_core::H160; +use sp_runtime::{traits::Zero, AccountId32, Perbill}; + +fn precompiles() -> DappPrecompile { + PrecompilesValue::get() +} + +#[test] +fn current_era_is_ok() { + ExternalityBuilder::default().build().execute_with(|| { + initialize_first_block(); + + let current_era = DappsStaking::current_era(); + + precompiles() + .prepare_test( + TestAccount::Alex, + precompile_address(), + EvmDataWriter::new_with_selector(Action::ReadCurrentEra).build(), + ) + .expect_cost(READ_WEIGHT) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(current_era).build()); + + // advance to era 5 and check output + advance_to_era(5); + let current_era = DappsStaking::current_era(); + + precompiles() + .prepare_test( + TestAccount::Alex, + precompile_address(), + EvmDataWriter::new_with_selector(Action::ReadCurrentEra).build(), + ) + .expect_cost(READ_WEIGHT) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(current_era).build()); + }); +} + +#[test] +fn read_unbonding_period_is_ok() { + ExternalityBuilder::default().build().execute_with(|| { + initialize_first_block(); + + precompiles() + .prepare_test( + TestAccount::Alex, + precompile_address(), + EvmDataWriter::new_with_selector(Action::ReadUnbondingPeriod).build(), + ) + .expect_cost(0) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(UNBONDING_PERIOD).build()); + }); +} + +#[test] +fn read_era_reward_is_ok() { + ExternalityBuilder::default().build().execute_with(|| { + initialize_first_block(); + + advance_to_era(3); + let era_reward = joint_block_reward() * BLOCKS_PER_ERA as u128; + let second_era: EraIndex = 2; + + precompiles() + .prepare_test( + TestAccount::Alex, + precompile_address(), + EvmDataWriter::new_with_selector(Action::ReadEraReward) + .write(second_era) + .build(), + ) + .expect_cost(READ_WEIGHT) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(era_reward).build()); + }); +} + +#[test] +fn read_era_staked_is_ok() { + ExternalityBuilder::default().build().execute_with(|| { + initialize_first_block(); + + let zero_era = EraIndex::zero(); + let staked = Balance::zero(); + + precompiles() + .prepare_test( + TestAccount::Alex, + precompile_address(), + EvmDataWriter::new_with_selector(Action::ReadEraStaked) + .write(zero_era) + .build(), + ) + .expect_cost(READ_WEIGHT) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(staked).build()); + }); +} + +#[test] +fn register_via_precompile_fails() { + ExternalityBuilder::default() + .with_balances(vec![(TestAccount::Alex.into(), 200 * AST)]) + .build() + .execute_with(|| { + initialize_first_block(); + + precompiles() + .prepare_test( + TestAccount::Alex, + precompile_address(), + EvmDataWriter::new_with_selector(Action::Register) + .write(Address(TEST_CONTRACT.clone())) + .build(), + ) + .expect_no_logs() + .execute_error(ExitError::Other(alloc::borrow::Cow::Borrowed( + "register via evm precompile is not allowed", + ))); + }); +} + +#[test] +fn bond_and_stake_is_ok() { + ExternalityBuilder::default() + .with_balances(vec![ + (TestAccount::Alex.into(), 200 * AST), + (TestAccount::Bobo.into(), 200 * AST), + (TestAccount::Dino.into(), 100 * AST), + ]) + .build() + .execute_with(|| { + initialize_first_block(); + + register_and_verify(TestAccount::Alex, TEST_CONTRACT); + + let amount_staked_bobo = 100 * AST; + bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); + + let amount_staked_dino = 50 * AST; + bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); + + contract_era_stake_verify(TEST_CONTRACT, amount_staked_bobo + amount_staked_dino); + verify_staked_amount(TEST_CONTRACT, TestAccount::Bobo.into(), amount_staked_bobo); + verify_staked_amount(TEST_CONTRACT, TestAccount::Dino.into(), amount_staked_dino); + }); +} + +#[test] +fn unbond_and_unstake_is_ok() { + ExternalityBuilder::default() + .with_balances(vec![ + (TestAccount::Alex.into(), 200 * AST), + (TestAccount::Bobo.into(), 200 * AST), + (TestAccount::Dino.into(), 100 * AST), + ]) + .build() + .execute_with(|| { + initialize_first_block(); + + // register new contract by Alex + let developer = TestAccount::Alex.into(); + register_and_verify(developer, TEST_CONTRACT); + + let amount_staked_bobo = 100 * AST; + bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); + let amount_staked_dino = 50 * AST; + bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); + + // Bobo unstakes all + let era = 2; + advance_to_era(era); + unbond_unstake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); + + contract_era_stake_verify(TEST_CONTRACT, amount_staked_dino); + verify_staked_amount(TEST_CONTRACT, TestAccount::Dino, amount_staked_dino); + + // withdraw unbonded funds + advance_to_era(era + UNBONDING_PERIOD + 1); + withdraw_unbonded_verify(TestAccount::Bobo); + }); +} + +#[test] +fn claim_dapp_is_ok() { + ExternalityBuilder::default() + .with_balances(vec![ + (TestAccount::Alex.into(), 200 * AST), + (TestAccount::Bobo.into(), 200 * AST), + (TestAccount::Dino.into(), 200 * AST), + ]) + .build() + .execute_with(|| { + initialize_first_block(); + + // register new contract by Alex + let developer = TestAccount::Alex; + register_and_verify(developer, TEST_CONTRACT); + + let stake_amount_total = 300 * AST; + let ratio_bobo = Perbill::from_rational(3u32, 5u32); + let ratio_dino = Perbill::from_rational(2u32, 5u32); + let amount_staked_bobo = ratio_bobo * stake_amount_total; + bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); + + let amount_staked_dino = ratio_dino * stake_amount_total; + bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); + + // advance era and claim reward + let era = 5; + advance_to_era(era); + claim_dapp_and_verify(TEST_CONTRACT, era - 1); + + //check that the reward is payed out to the developer + let developer_reward = DAPP_BLOCK_REWARD * BLOCKS_PER_ERA as Balance; + assert_eq!( + ::Currency::free_balance( + &TestAccount::Alex.into() + ), + (200 * AST) + developer_reward - REGISTER_DEPOSIT + ); + }); +} + +#[test] +fn claim_staker_is_ok() { + ExternalityBuilder::default() + .with_balances(vec![ + (TestAccount::Alex.into(), 200 * AST), + (TestAccount::Bobo.into(), 200 * AST), + (TestAccount::Dino.into(), 200 * AST), + ]) + .build() + .execute_with(|| { + initialize_first_block(); + + // register new contract by Alex + let developer = TestAccount::Alex; + register_and_verify(developer, TEST_CONTRACT); + + let stake_amount_total = 300 * AST; + let ratio_bobo = Perbill::from_rational(3u32, 5u32); + let ratio_dino = Perbill::from_rational(2u32, 5u32); + let amount_staked_bobo = ratio_bobo * stake_amount_total; + bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); + + let amount_staked_dino = ratio_dino * stake_amount_total; + bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); + + // advance era and claim reward + advance_to_era(5); + + let stakers_reward = STAKER_BLOCK_REWARD * BLOCKS_PER_ERA as Balance; + + // Ensure that all rewards can be claimed for the first staker + for era in 1..DappsStaking::current_era() as Balance { + claim_staker_and_verify(TestAccount::Bobo, TEST_CONTRACT); + assert_eq!( + ::Currency::free_balance( + &TestAccount::Bobo.into() + ), + (200 * AST) + ratio_bobo * stakers_reward * era + ); + } + + // Repeat the same thing for the second staker + for era in 1..DappsStaking::current_era() as Balance { + claim_staker_and_verify(TestAccount::Dino, TEST_CONTRACT); + assert_eq!( + ::Currency::free_balance( + &TestAccount::Dino.into() + ), + (200 * AST) + ratio_dino * stakers_reward * era + ); + } + }); +} + +#[test] +fn set_reward_destination() { + ExternalityBuilder::default() + .with_balances(vec![ + (TestAccount::Alex.into(), 200 * AST), + (TestAccount::Bobo.into(), 200 * AST), + ]) + .build() + .execute_with(|| { + initialize_first_block(); + // register contract and stake it + register_and_verify(TestAccount::Alex.into(), TEST_CONTRACT); + + // bond & stake the origin contract + bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, 100 * AST); + + // change destinations and verfiy it was successful + set_reward_destination_verify(TestAccount::Bobo.into(), RewardDestination::FreeBalance); + set_reward_destination_verify( + TestAccount::Bobo.into(), + RewardDestination::StakeBalance, + ); + set_reward_destination_verify(TestAccount::Bobo.into(), RewardDestination::FreeBalance); + }); +} + +#[test] +fn withdraw_from_unregistered() { + ExternalityBuilder::default() + .with_balances(vec![ + (TestAccount::Alex.into(), 200 * AST), + (TestAccount::Bobo.into(), 200 * AST), + ]) + .build() + .execute_with(|| { + initialize_first_block(); + + // register new contract by Alex + let developer = TestAccount::Alex.into(); + register_and_verify(developer, TEST_CONTRACT); + + let amount_staked_bobo = 100 * AST; + bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); + + let contract_id = + decode_smart_contract_from_array(TEST_CONTRACT.clone().to_fixed_bytes()).unwrap(); + assert_ok!(DappsStaking::unregister(RuntimeOrigin::root(), contract_id)); + + withdraw_from_unregistered_verify(TestAccount::Bobo.into(), TEST_CONTRACT); + }); +} + +#[test] +fn nomination_transfer() { + ExternalityBuilder::default() + .with_balances(vec![ + (TestAccount::Alex.into(), 200 * AST), + (TestAccount::Dino.into(), 200 * AST), + (TestAccount::Bobo.into(), 200 * AST), + ]) + .build() + .execute_with(|| { + initialize_first_block(); + + // register two contracts for nomination transfer test + let origin_contract = H160::repeat_byte(0x09); + let target_contract = H160::repeat_byte(0x0A); + register_and_verify(TestAccount::Alex.into(), origin_contract); + register_and_verify(TestAccount::Dino.into(), target_contract); + + // bond & stake the origin contract + let amount_staked_bobo = 100 * AST; + bond_stake_and_verify(TestAccount::Bobo, origin_contract, amount_staked_bobo); + + // transfer nomination and ensure it was successful + nomination_transfer_verify( + TestAccount::Bobo, + origin_contract, + 10 * AST, + target_contract, + ); + }); +} + +// **************************************************************************************************** +// Helper functions +// **************************************************************************************************** + +/// helper function to register and verify if registration is valid +fn register_and_verify(developer: TestAccount, contract: H160) { + let smart_contract = + decode_smart_contract_from_array(contract.clone().to_fixed_bytes()).unwrap(); + DappsStaking::register( + RuntimeOrigin::root(), + developer.clone().into(), + smart_contract, + ) + .unwrap(); + + // check the storage after the register + let dev_account_id: AccountId32 = developer.into(); + let smart_contract_bytes = + (DappsStaking::registered_contract(dev_account_id).unwrap_or_default()).encode(); + + assert_eq!( + // 0-th byte is enum value discriminator + smart_contract_bytes[1..21], + contract.to_fixed_bytes() + ); +} + +/// helper function to read ledger storage item +fn read_staked_amount_h160_verify(staker: TestAccount, amount: u128) { + precompiles() + .prepare_test( + staker.clone(), + precompile_address(), + EvmDataWriter::new_with_selector(Action::ReadStakedAmount) + .write(Bytes( + Into::::into(staker.clone()).to_fixed_bytes().to_vec(), + )) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(amount).build()); +} + +/// helper function to read ledger storage item for ss58 account +fn read_staked_amount_ss58_verify(staker: TestAccount, amount: u128) { + let staker_acc_id: AccountId32 = staker.clone().into(); + + precompiles() + .prepare_test( + staker.clone(), + precompile_address(), + EvmDataWriter::new_with_selector(Action::ReadStakedAmount) + .write(Bytes(staker_acc_id.encode())) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(amount).build()); +} + +/// helper function to bond, stake and verify if resulet is OK +fn bond_stake_and_verify(staker: TestAccount, contract: H160, amount: u128) { + precompiles() + .prepare_test( + staker.clone(), + precompile_address(), + EvmDataWriter::new_with_selector(Action::BondAndStake) + .write(Address(contract.clone())) + .write(amount) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + read_staked_amount_h160_verify(staker.clone(), amount); + read_staked_amount_ss58_verify(staker, amount); +} + +/// helper function to unbond, unstake and verify if result is OK +fn unbond_unstake_and_verify(staker: TestAccount, contract: H160, amount: u128) { + precompiles() + .prepare_test( + staker.clone(), + precompile_address(), + EvmDataWriter::new_with_selector(Action::UnbondAndUnstake) + .write(Address(contract.clone())) + .write(amount) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); +} + +/// helper function to withdraw unstaked funds and verify if result is OK +fn withdraw_unbonded_verify(staker: TestAccount) { + let staker_acc_id = AccountId32::from(staker.clone()); + + // call unbond_and_unstake(). Check usable_balance before and after the call + assert_ne!( + ::Currency::free_balance(&staker_acc_id), + ::Currency::usable_balance(&staker_acc_id) + ); + + precompiles() + .prepare_test( + staker.clone(), + precompile_address(), + EvmDataWriter::new_with_selector(Action::WithdrawUnbounded).build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + assert_eq!( + ::Currency::free_balance(&staker_acc_id), + ::Currency::usable_balance(&staker_acc_id) + ); +} + +/// helper function to verify change of reward destination for a staker +fn set_reward_destination_verify(staker: TestAccount, reward_destination: RewardDestination) { + // Read staker's ledger + let staker_acc_id = AccountId32::from(staker.clone()); + let init_ledger = DappsStaking::ledger(&staker_acc_id); + // Ensure that something is staked or being unbonded + assert!(!init_ledger.is_empty()); + + let reward_destination_raw: u8 = match reward_destination { + RewardDestination::FreeBalance => 0, + RewardDestination::StakeBalance => 1, + }; + precompiles() + .prepare_test( + staker.clone(), + precompile_address(), + EvmDataWriter::new_with_selector(Action::SetRewardDestination) + .write(reward_destination_raw) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let final_ledger = DappsStaking::ledger(&staker_acc_id); + assert_eq!(final_ledger.reward_destination(), reward_destination); +} + +/// helper function to withdraw funds from unregistered contract +fn withdraw_from_unregistered_verify(staker: TestAccount, contract: H160) { + let smart_contract = + decode_smart_contract_from_array(contract.clone().to_fixed_bytes()).unwrap(); + let staker_acc_id = AccountId32::from(staker.clone()); + let init_staker_info = DappsStaking::staker_info(&staker_acc_id, &smart_contract); + assert!(!init_staker_info.latest_staked_value().is_zero()); + + precompiles() + .prepare_test( + staker.clone(), + precompile_address(), + EvmDataWriter::new_with_selector(Action::WithdrawFromUnregistered) + .write(Address(contract.clone())) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let final_staker_info = DappsStaking::staker_info(&staker_acc_id, &smart_contract); + assert!(final_staker_info.latest_staked_value().is_zero()); +} + +/// helper function to verify nomination transfer from origin to target contract +fn nomination_transfer_verify( + staker: TestAccount, + origin_contract: H160, + amount: Balance, + target_contract: H160, +) { + let origin_smart_contract = + decode_smart_contract_from_array(origin_contract.clone().to_fixed_bytes()).unwrap(); + let target_smart_contract = + decode_smart_contract_from_array(target_contract.clone().to_fixed_bytes()).unwrap(); + let staker_acc_id = AccountId32::from(staker.clone()); + + // Read init data staker info states + let init_origin_staker_info = DappsStaking::staker_info(&staker_acc_id, &origin_smart_contract); + let init_target_staker_info = DappsStaking::staker_info(&staker_acc_id, &target_smart_contract); + + precompiles() + .prepare_test( + staker.clone(), + precompile_address(), + EvmDataWriter::new_with_selector(Action::NominationTransfer) + .write(Address(origin_contract.clone())) + .write(amount) + .write(Address(target_contract.clone())) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); + + let final_origin_staker_info = + DappsStaking::staker_info(&staker_acc_id, &origin_smart_contract); + let final_target_staker_info = + DappsStaking::staker_info(&staker_acc_id, &target_smart_contract); + + // Verify final state + let will_be_unstaked = init_origin_staker_info + .latest_staked_value() + .saturating_sub(amount) + < MINIMUM_STAKING_AMOUNT; + let transfer_amount = if will_be_unstaked { + init_origin_staker_info.latest_staked_value() + } else { + amount + }; + + assert_eq!( + final_origin_staker_info.latest_staked_value() + transfer_amount, + init_origin_staker_info.latest_staked_value() + ); + assert_eq!( + final_target_staker_info.latest_staked_value() - transfer_amount, + init_target_staker_info.latest_staked_value() + ); +} + +/// helper function to bond, stake and verify if result is OK +fn claim_dapp_and_verify(contract: H160, era: EraIndex) { + precompiles() + .prepare_test( + TestAccount::Bobo, + precompile_address(), + EvmDataWriter::new_with_selector(Action::ClaimDapp) + .write(Address(contract.clone())) + .write(era) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); +} + +/// helper function to bond, stake and verify if the result is OK +fn claim_staker_and_verify(staker: TestAccount, contract: H160) { + precompiles() + .prepare_test( + staker, + precompile_address(), + EvmDataWriter::new_with_selector(Action::ClaimStaker) + .write(Address(contract.clone())) + .build(), + ) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(true).build()); +} + +fn contract_era_stake_verify(contract: H160, amount: Balance) { + precompiles() + .prepare_test( + TestAccount::Alex, + precompile_address(), + EvmDataWriter::new_with_selector(Action::ReadContractStake) + .write(Address(contract.clone())) + .build(), + ) + .expect_cost(2 * READ_WEIGHT) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(amount).build()); +} + +/// helper function to verify latest staked amount +fn verify_staked_amount(contract: H160, staker: TestAccount, amount: Balance) { + precompiles() + .prepare_test( + staker.clone(), + precompile_address(), + EvmDataWriter::new_with_selector(Action::ReadStakedAmountOnContract) + .write(Address(contract.clone())) + .write(Bytes( + Into::::into(staker.clone()).to_fixed_bytes().to_vec(), + )) + .build(), + ) + .expect_cost(READ_WEIGHT) + .expect_no_logs() + .execute_returns(EvmDataWriter::new().write(amount).build()); +} + +/// Helper method to decode type SmartContract enum from [u8; 20] +fn decode_smart_contract_from_array( + contract_array: [u8; 20], +) -> Result<::SmartContract, String> { + // Encode contract address to fit SmartContract enum. + let mut contract_enum_encoded: [u8; 21] = [0; 21]; + contract_enum_encoded[0] = 0; // enum for EVM H160 address is 0 + contract_enum_encoded[1..21].copy_from_slice(&contract_array); + + let smart_contract = ::SmartContract::decode( + &mut &contract_enum_encoded[..21], + ) + .map_err(|_| "Error while decoding SmartContract")?; + + Ok(smart_contract) +} From e317f4d089e7bcede866a25eba88271268613cd2 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Tue, 5 Dec 2023 15:24:42 +0100 Subject: [PATCH 02/34] All legacy calls covered --- precompiles/dapps-staking-v3/src/lib.rs | 301 ++++++++++++++---------- 1 file changed, 180 insertions(+), 121 deletions(-) diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index 1420719277..35300a5e13 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -34,10 +34,9 @@ use precompile_utils::{ error, revert, succeed, Address, Bytes, EvmData, EvmDataWriter, EvmResult, FunctionModifier, PrecompileHandleExt, RuntimeHelper, }; -use sp_core::H160; +use sp_core::{Get, H160}; use sp_runtime::traits::Zero; -use sp_std::marker::PhantomData; -use sp_std::prelude::*; +use sp_std::{marker::PhantomData, prelude::*}; extern crate alloc; use pallet_dapp_staking_v3::{ @@ -80,7 +79,7 @@ where fn read_current_era(handle: &mut impl PrecompileHandle) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: - // Twox64(8) + ActiveProtocolState::max_encoded_len + // Twox64(8) + ProtocolState::max_encoded_len handle.record_db_read::(4 + ProtocolStateFor::::max_encoded_len())?; let current_era = ActiveProtocolState::::get().era; @@ -135,7 +134,7 @@ where fn read_era_staked(handle: &mut impl PrecompileHandle) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: - // Twox64(8) + ActiveProtocolState::max_encoded_len + // Twox64(8) + ProtocolState::max_encoded_len handle.record_db_read::(4 + ProtocolStateFor::::max_encoded_len())?; let current_era = ActiveProtocolState::::get().era; @@ -192,12 +191,13 @@ where fn read_staked_amount(handle: &mut impl PrecompileHandle) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: - // Twox64(8) + ActiveProtocolState::max_encoded_len + // Twox64(8) + ProtocolState::max_encoded_len // Storage item: Ledger: - // Blake2_128Concat(16 + 32) + Ledger::max_encoded_len + // Blake2_128Concat(16 + SmartContract::max_encoded_len) + Ledger::max_encoded_len handle.record_db_read::( - 56 + AccountLedgerFor::::max_encoded_len() - + ProtocolStateFor::::max_encoded_len(), + 24 + AccountLedgerFor::::max_encoded_len() + + ProtocolStateFor::::max_encoded_len() + + ::SmartContract::max_encoded_len(), )?; let mut input = handle.read_input()?; @@ -227,11 +227,11 @@ where ) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: - // Twox64(8) + ActiveProtocolState::max_encoded_len + // Twox64(8) + ProtocolState::max_encoded_len // Storage item: StakerInfo: - // Blake2_128Concat(16 + 32) + Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len + // Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len handle.record_db_read::( - 72 + ProtocolStateFor::::max_encoded_len() + 24 + ProtocolStateFor::::max_encoded_len() + ::SmartContract::max_encoded_len() + SingularStakingInfo::max_encoded_len(), )?; @@ -269,7 +269,7 @@ where fn read_contract_stake(handle: &mut impl PrecompileHandle) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: - // Twox64(8) + ActiveProtocolState::max_encoded_len + // Twox64(8) + ProtocolState::max_encoded_len // Storage item: IntegratedDApps: // Blake2_128Concat(16 + SmartContract::max_encoded_len) + DAppInfoFor::max_encoded_len // Storage item: ContractStake: @@ -314,22 +314,26 @@ where Err(error("register via evm precompile is not allowed")) } - /// Lock up and stake balance of the origin account. + /// Lock & stake some amount on the specified contract. + /// + /// In case existing `stakeable` is sufficient to cover the given `amount`, only the `stake` operation is performed. + /// Otherwise, best effort is done to lock the additional amount so `stakeable` amount can cover the given `amount`. fn bond_and_stake(handle: &mut impl PrecompileHandle) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: - // Twox64(8) + ActiveProtocolState::max_encoded_len + // Twox64(8) + ProtocolState::max_encoded_len // Storage item: Ledger: - // Blake2_128Concat(16 + 32) + Ledger::max_encoded_len + // Blake2_128Concat(16 + SmartContract::max_encoded_len()) + Ledger::max_encoded_len handle.record_db_read::( - 56 + AccountLedgerFor::::max_encoded_len() - + ProtocolStateFor::::max_encoded_len(), + 24 + AccountLedgerFor::::max_encoded_len() + + ProtocolStateFor::::max_encoded_len() + + ::SmartContract::max_encoded_len(), )?; let mut input = handle.read_input()?; input.expect_arguments(2)?; - // Parse contract & amount + // Parse smart contract & amount let contract_h160 = input.read::
()?.0; let smart_contract = Self::decode_smart_contract(contract_h160)?; let amount: BalanceOf = input.read()?; @@ -362,138 +366,194 @@ where Ok(succeed(EvmDataWriter::new().write(true).build())) } - // /// Start unbonding process and unstake balance from the contract. - // fn unbond_and_unstake(handle: &mut impl PrecompileHandle) -> EvmResult { - // let mut input = handle.read_input()?; - // input.expect_arguments(2)?; - - // // parse contract's address - // let contract_h160 = input.read::
()?.0; - // let contract_id = Self::decode_smart_contract(contract_h160)?; + /// Start unbonding process and unstake balance from the contract. + fn unbond_and_unstake(handle: &mut impl PrecompileHandle) -> EvmResult { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: ActiveProtocolState: + // Twox64(8) + ProtocolState::max_encoded_len + // Storage item: StakerInfo: + // Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len + handle.record_db_read::( + 24 + ProtocolStateFor::::max_encoded_len() + + ::SmartContract::max_encoded_len() + + SingularStakingInfo::max_encoded_len(), + )?; - // // parse balance to be unstaked - // let value: BalanceOf = input.read()?; - // log::trace!(target: "ds-precompile", "unbond_and_unstake {:?}, {:?}", contract_id, value); + let mut input = handle.read_input()?; + input.expect_arguments(2)?; - // // Build call with origin. - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let call = pallet_dapp_staking_v3::Call::::unbond_and_unstake { contract_id, value }; + // Parse smart contract & amount + let contract_h160 = input.read::
()?.0; + let smart_contract = Self::decode_smart_contract(contract_h160)?; + let amount: BalanceOf = input.read()?; + let origin = R::AddressMapping::into_account_id(handle.context().caller); + log::trace!(target: "ds-precompile", "unbond_and_unstake {:?}, {:?}", smart_contract, amount); - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + // Find out if there is something staked on the contract + let protocol_state = ActiveProtocolState::::get(); + let staker_info = StakerInfo::::get(&origin, &smart_contract).unwrap_or_default(); + + // If there is, we need to unstake it before calling `unlock` + if staker_info.period_number() == protocol_state.period_number() { + let unstake_call = pallet_dapp_staking_v3::Call::::unstake { + smart_contract, + amount, + }; + RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), unstake_call)?; + } - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + // Now we can try and `unlock` the given `amount` + let unlock_call = pallet_dapp_staking_v3::Call::::unlock { amount }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unlock_call)?; - // /// Start unbonding process and unstake balance from the contract. - // fn withdraw_unbonded(handle: &mut impl PrecompileHandle) -> EvmResult { - // // Build call with origin. - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let call = pallet_dapp_staking_v3::Call::::withdraw_unbonded {}; + Ok(succeed(EvmDataWriter::new().write(true).build())) + } - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + /// Claim back the unbonded (or unlocked) funds. + fn withdraw_unbonded(handle: &mut impl PrecompileHandle) -> EvmResult { + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let call = pallet_dapp_staking_v3::Call::::claim_unlocked {}; - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - // /// Claim rewards for the contract in the dapps-staking pallet - // fn claim_dapp(handle: &mut impl PrecompileHandle) -> EvmResult { - // let mut input = handle.read_input()?; - // input.expect_arguments(2)?; + Ok(succeed(EvmDataWriter::new().write(true).build())) + } - // // parse contract's address - // let contract_h160 = input.read::
()?.0; - // let contract_id = Self::decode_smart_contract(contract_h160)?; + /// Claim dApp rewards for the given era + fn claim_dapp(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(2)?; - // // parse era - // let era: u32 = input.read::()?; - // log::trace!(target: "ds-precompile", "claim_dapp {:?}, era {:?}", contract_id, era); + // Parse the smart contract & era + let contract_h160 = input.read::
()?.0; + let smart_contract = Self::decode_smart_contract(contract_h160)?; + let era: u32 = input.read::()?; + log::trace!(target: "ds-precompile", "claim_dapp {:?}, era {:?}", smart_contract, era); - // // Build call with origin. - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let call = pallet_dapp_staking_v3::Call::::claim_dapp { contract_id, era }; + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let call = pallet_dapp_staking_v3::Call::::claim_dapp_reward { + smart_contract, + era, + }; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + Ok(succeed(EvmDataWriter::new().write(true).build())) + } - // /// Claim rewards for the contract in the dapps-staking pallet - // fn claim_staker(handle: &mut impl PrecompileHandle) -> EvmResult { - // let mut input = handle.read_input()?; - // input.expect_arguments(1)?; + /// Claim staker rewards. + /// + /// Smart contract argument is legacy & is ignored in the new implementation. + fn claim_staker(handle: &mut impl PrecompileHandle) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(1)?; - // // parse contract's address - // let contract_h160 = input.read::
()?.0; - // let contract_id = Self::decode_smart_contract(contract_h160)?; - // log::trace!(target: "ds-precompile", "claim_staker {:?}", contract_id); + // Parse smart contract to keep in line with the legacy behavior. + let contract_h160 = input.read::
()?.0; + let _smart_contract = Self::decode_smart_contract(contract_h160)?; + log::trace!(target: "ds-precompile", "claim_staker {:?}", _smart_contract); - // // Build call with origin. - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let call = pallet_dapp_staking_v3::Call::::claim_staker { contract_id }; + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let call = pallet_dapp_staking_v3::Call::::claim_staker_rewards {}; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + Ok(succeed(EvmDataWriter::new().write(true).build())) + } - /// Set claim reward destination for the caller + /// Set claim reward destination for the caller. + /// + /// This call has been deprecated by dApp staking v3. fn set_reward_destination(_handle: &mut impl PrecompileHandle) -> EvmResult { Err(error( "set_reward_destination via evm precompile is no longer supported", )) } - // /// Withdraw staked funds from the unregistered contract - // fn withdraw_from_unregistered( - // handle: &mut impl PrecompileHandle, - // ) -> EvmResult { - // let mut input = handle.read_input()?; - // input.expect_arguments(1)?; + /// Withdraw staked funds from the unregistered contract + fn withdraw_from_unregistered( + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + let mut input = handle.read_input()?; + input.expect_arguments(1)?; - // // parse contract's address - // let contract_h160 = input.read::
()?.0; - // let contract_id = Self::decode_smart_contract(contract_h160)?; - // log::trace!(target: "ds-precompile", "withdraw_from_unregistered {:?}", contract_id); + // Parse smart contract + let contract_h160 = input.read::
()?.0; + let smart_contract = Self::decode_smart_contract(contract_h160)?; + log::trace!(target: "ds-precompile", "withdraw_from_unregistered {:?}", smart_contract); - // // Build call with origin. - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let call = pallet_dapp_staking_v3::Call::::withdraw_from_unregistered { contract_id }; + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let call = pallet_dapp_staking_v3::Call::::unstake_from_unregistered { smart_contract }; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + Ok(succeed(EvmDataWriter::new().write(true).build())) + } - // /// Claim rewards for the contract in the dapps-staking pallet - // fn nomination_transfer(handle: &mut impl PrecompileHandle) -> EvmResult { - // let mut input = handle.read_input()?; - // input.expect_arguments(3)?; + /// Transfers stake from one contract to another. + /// This is a legacy functionality that is no longer supported via direct call to dApp staking v3. + /// However, it can be achieved by chaining `unstake` and `stake` calls. + fn nomination_transfer(handle: &mut impl PrecompileHandle) -> EvmResult { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: StakerInfo: + // Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len + handle.record_db_read::( + 16 + ::SmartContract::max_encoded_len() + + SingularStakingInfo::max_encoded_len(), + )?; - // // parse origin contract's address - // let origin_contract_h160 = input.read::
()?.0; - // let origin_contract_id = Self::decode_smart_contract(origin_contract_h160)?; + let mut input = handle.read_input()?; + input.expect_arguments(3)?; + + // Parse origin smart contract, transfer amount & the target smart contract + let origin_contract_h160 = input.read::
()?.0; + let origin_smart_contract = Self::decode_smart_contract(origin_contract_h160)?; - // // parse balance to be transferred - // let value = input.read::>()?; + let amount = input.read::>()?; - // // parse target contract's address - // let target_contract_h160 = input.read::
()?.0; - // let target_contract_id = Self::decode_smart_contract(target_contract_h160)?; + let target_contract_h160 = input.read::
()?.0; + let target_smart_contract = Self::decode_smart_contract(target_contract_h160)?; + log::trace!(target: "ds-precompile", "nomination_transfer {:?} {:?} {:?}", origin_smart_contract, amount, target_smart_contract); - // log::trace!(target: "ds-precompile", "nomination_transfer {:?} {:?} {:?}", origin_contract_id, value, target_contract_id); + // Find out how much staker has staked on the origin contract + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let staker_info = StakerInfo::::get(&origin, &origin_smart_contract).unwrap_or_default(); + + // We don't care from which period the staked amount is, the logic takes care of the situation + // if value comes from the past period. + let staked_amount = staker_info.total_staked_amount(); + let minimum_allowed_stake_amount = + ::MinimumStakeAmount::get(); + + // In case the remaining staked amount on the origin contract is less than the minimum allowed stake amount, + // everything will be unstaked. To keep in line with legacy `nomination_transfer` behavior, we should transfer + // the entire amount from the origin to target contract. + // + // In case value comes from the past period, we don't care, since the `unstake` call will fall apart. + let stake_amount = if staked_amount > 0 + && staked_amount.saturating_sub(amount) < minimum_allowed_stake_amount + { + staked_amount + } else { + amount + }; - // // Build call with origin. - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let call = pallet_dapp_staking_v3::Call::::nomination_transfer { - // origin_contract_id, - // value, - // target_contract_id, - // }; + // First call unstake from the origin smart contract + let unstake_call = pallet_dapp_staking_v3::Call::::unstake { + smart_contract: origin_smart_contract, + amount, + }; + RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), unstake_call)?; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + // Then call stake on the target smart contract + let stake_call = pallet_dapp_staking_v3::Call::::stake { + smart_contract: target_smart_contract, + amount: stake_amount, + }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + Ok(succeed(EvmDataWriter::new().write(true).build())) + } /// Helper method to decode type SmartContract enum pub fn decode_smart_contract( @@ -602,14 +662,13 @@ where // Dispatchables Action::Register => Self::register(handle), Action::BondAndStake => Self::bond_and_stake(handle), - // Action::UnbondAndUnstake => Self::unbond_and_unstake(handle), - // Action::WithdrawUnbounded => Self::withdraw_unbonded(handle), - // Action::ClaimDapp => Self::claim_dapp(handle), - // Action::ClaimStaker => Self::claim_staker(handle), + Action::UnbondAndUnstake => Self::unbond_and_unstake(handle), + Action::WithdrawUnbounded => Self::withdraw_unbonded(handle), + Action::ClaimDapp => Self::claim_dapp(handle), + Action::ClaimStaker => Self::claim_staker(handle), Action::SetRewardDestination => Self::set_reward_destination(handle), - // Action::WithdrawFromUnregistered => Self::withdraw_from_unregistered(handle), - // Action::NominationTransfer => Self::nomination_transfer(handle), - _ => Err(error("Invalid function selector")), + Action::WithdrawFromUnregistered => Self::withdraw_from_unregistered(handle), + Action::NominationTransfer => Self::nomination_transfer(handle), } } } From b675c7761fe342d4ab76154405ecb6f73c869cf4 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Tue, 5 Dec 2023 15:48:19 +0100 Subject: [PATCH 03/34] v2 interface first definition --- precompiles/dapps-staking-v3/Cargo.toml | 2 +- .../dapps-staking-v3/DappsStakingV1.sol | 7 +-- .../dapps-staking-v3/DappsStakingV2.sol | 60 +++++++++++++++++++ precompiles/dapps-staking-v3/src/lib.rs | 6 +- 4 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 precompiles/dapps-staking-v3/DappsStakingV2.sol diff --git a/precompiles/dapps-staking-v3/Cargo.toml b/precompiles/dapps-staking-v3/Cargo.toml index 722a9504da..a5db3edd82 100644 --- a/precompiles/dapps-staking-v3/Cargo.toml +++ b/precompiles/dapps-staking-v3/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pallet-evm-precompile-dapp-staking-v3" version = "0.1.0" -license = "Apache-2.0" +license = "GPL-3.0-or-later" description = "dApps Staking EVM precompiles" authors.workspace = true edition.workspace = true diff --git a/precompiles/dapps-staking-v3/DappsStakingV1.sol b/precompiles/dapps-staking-v3/DappsStakingV1.sol index b6967d9247..23918e1460 100644 --- a/precompiles/dapps-staking-v3/DappsStakingV1.sol +++ b/precompiles/dapps-staking-v3/DappsStakingV1.sol @@ -1,8 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause -pragma solidity >=0.7.0; +pragma solidity >=0.8.0; -/// Interface to the precompiled contract on Shibuya/Shiden/Astar /// Predeployed at the address 0x0000000000000000000000000000000000005001 /// For better understanding check the source code: /// repo: https://github.com/AstarNetwork/astar @@ -15,11 +14,11 @@ interface DappsStaking { /// @return era: The current era function read_current_era() external view returns (uint256); - /// @notice Read unbonding period constant. + /// @notice Read the unbonding period (or unlocking period) in the number of eras. /// @return period: The unbonding period in eras function read_unbonding_period() external view returns (uint256); - /// @notice Read Total network reward for the given era + /// @notice Read Total network reward for the given era - sum of staker & dApp rewards. /// @return reward: Total network reward for the given era function read_era_reward(uint32 era) external view returns (uint128); diff --git a/precompiles/dapps-staking-v3/DappsStakingV2.sol b/precompiles/dapps-staking-v3/DappsStakingV2.sol new file mode 100644 index 0000000000..7c2aa271ad --- /dev/null +++ b/precompiles/dapps-staking-v3/DappsStakingV2.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: BSD-3-Clause + +pragma solidity >=0.8.0; + +/// Predeployed at the address 0x0000000000000000000000000000000000005001 +/// For better understanding check the source code: +/// repo: https://github.com/AstarNetwork/Astar +/// code: frame/dapps-staking-v3 +interface DappsStaking { + + // TODO: make a custom struct to represent protocol state? + + // TODO: Create a wrapper for smart contract enum, so we can support more than just plain EVM contracts. + + // Storage getters + + /// Describes the subperiod in which the protocol currently is. + enum Subperiod {Voting, BuildAndEarn} + + /// @notice Get the current protocol state. + /// @return (current era, current period number, current subperiod type). + function protocol_state() external view returns (uint256, uint256, Subperiod); + + /// @notice Get the unlocking period expressed in the number of blocks. + /// @return period: The unlocking period expressed in the number of blocks. + function unlocking_period() external view returns (uint256); + + + // Extrinsic calls + + /// @notice Lock the given amount of tokens into dApp staking protocol. + function lock(uint128) external; + + /// @notice Start the unlocking process for the given amount of tokens. + function unlock(uint128) external; + + /// @notice Claims unlocked tokens. + function claim_unlocked() external; + + /// @notice Stake the given amount of tokens on the specified smart contract. + function stake(address, uint128) external; + + /// @notice Unstake the given amount of tokens from the specified smart contract. + function unstake(address, uint128) external; + + /// @notice Claims one or more pending staker rewards. + function claim_staker_rewards() external; + + /// @notice Claim the bonus reward for the specified smart contract. + function claim_bonus_reward(address) external; + + /// @notice Claim dApp reward for the specified smart contract & era. + function claim_dapp_reward(address, uint128) external; + + /// @notice Unstake all funds from the unregistered smart contract. + function unstake_from_unregistered(address) external; + + /// @notice Used to cleanup all expired contract stake entries from the caller. + function cleanup_expired_entries() external; +} diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index 35300a5e13..80f4ce320c 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . -//! Astar dApps staking interface. +//! Astar dApp staking interface. #![cfg_attr(not(feature = "std"), no_std)] @@ -87,12 +87,12 @@ where Ok(succeed(EvmDataWriter::new().write(current_era).build())) } - /// Read the `unbonding period` or `unlocking period` expressed in the number of blocks. + /// Read the `unbonding period` or `unlocking period` expressed in the number of eras. fn read_unbonding_period(_: &impl PrecompileHandle) -> EvmResult { // constant, no DB read Ok(succeed( EvmDataWriter::new() - .write(DAppStaking::::unlock_period()) + .write(::UnlockingPeriod::get()) .build(), )) } From e2dbf48e292dfe7b735db5e0aec1978ec34f7143 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Tue, 5 Dec 2023 15:51:02 +0100 Subject: [PATCH 04/34] Rename --- precompiles/dapps-staking-v3/DappsStakingV2.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/precompiles/dapps-staking-v3/DappsStakingV2.sol b/precompiles/dapps-staking-v3/DappsStakingV2.sol index 7c2aa271ad..cf1fc5bf52 100644 --- a/precompiles/dapps-staking-v3/DappsStakingV2.sol +++ b/precompiles/dapps-staking-v3/DappsStakingV2.sol @@ -6,7 +6,7 @@ pragma solidity >=0.8.0; /// For better understanding check the source code: /// repo: https://github.com/AstarNetwork/Astar /// code: frame/dapps-staking-v3 -interface DappsStaking { +interface DAppStaking { // TODO: make a custom struct to represent protocol state? From f3b0804ba46fd31836af1457aa09c098bc3c510d Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Tue, 5 Dec 2023 17:42:39 +0100 Subject: [PATCH 05/34] Resolve merge errors --- Cargo.lock | 1 + pallets/dapp-staking-v3/src/types.rs | 3 -- precompiles/dapps-staking-v3/Cargo.toml | 2 ++ precompiles/dapps-staking-v3/src/lib.rs | 38 +++++++++---------------- 4 files changed, 17 insertions(+), 27 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 65730d01c6..fcb3b219c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7972,6 +7972,7 @@ dependencies = [ name = "pallet-evm-precompile-dapp-staking-v3" version = "0.1.0" dependencies = [ + "astar-primitives", "derive_more", "fp-evm", "frame-support", diff --git a/pallets/dapp-staking-v3/src/types.rs b/pallets/dapp-staking-v3/src/types.rs index 2ac7e83855..d3ae193ce5 100644 --- a/pallets/dapp-staking-v3/src/types.rs +++ b/pallets/dapp-staking-v3/src/types.rs @@ -90,9 +90,6 @@ pub type EraRewardSpanFor = EraRewardSpan<::EraRewardSpanLength> // Convenience type for `DAppInfo` usage. pub type DAppInfoFor = DAppInfo<::AccountId>; -// Convenience type for `ProtocolState` usage. -pub type ProtocolStateFor = ProtocolState>; - /// Era number type pub type EraNumber = u32; /// Period number type diff --git a/precompiles/dapps-staking-v3/Cargo.toml b/precompiles/dapps-staking-v3/Cargo.toml index a5db3edd82..2d660d4952 100644 --- a/precompiles/dapps-staking-v3/Cargo.toml +++ b/precompiles/dapps-staking-v3/Cargo.toml @@ -22,6 +22,7 @@ sp-runtime = { workspace = true } sp-std = { workspace = true } # Astar +astar-primitives = { workspace = true } pallet-dapp-staking-v3 = { workspace = true } precompile-utils = { workspace = true, default-features = false } @@ -43,6 +44,7 @@ default = ["std"] std = [ "parity-scale-codec/std", "scale-info/std", + "astar-primitives/std", "sp-std/std", "sp-core/std", "sp-runtime/std", diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index 80f4ce320c..e5fd18e94d 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -23,11 +23,7 @@ use fp_evm::{PrecompileHandle, PrecompileOutput}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{ - dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, - traits::Currency, -}; -use frame_system::pallet_prelude::BlockNumberFor; +use frame_support::dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}; use pallet_evm::{AddressMapping, Precompile}; use precompile_utils::{ @@ -39,16 +35,13 @@ use sp_runtime::traits::Zero; use sp_std::{marker::PhantomData, prelude::*}; extern crate alloc; +use astar_primitives::Balance; use pallet_dapp_staking_v3::{ AccountLedgerFor, ActiveProtocolState, ContractStake, ContractStakeAmount, CurrentEraInfo, DAppInfoFor, EraInfo, EraRewardSpanFor, EraRewards, IntegratedDApps, Ledger, - Pallet as DAppStaking, ProtocolStateFor, SingularStakingInfo, StakerInfo, + Pallet as DAppStaking, ProtocolState, SingularStakingInfo, StakerInfo, }; -type BalanceOf = <::Currency as Currency< - ::AccountId, ->>::Balance; - // #[cfg(test)] // mod mock; // #[cfg(test)] @@ -68,8 +61,6 @@ pub struct DappStakingV3Precompile(PhantomData); impl DappStakingV3Precompile where R: pallet_evm::Config + pallet_dapp_staking_v3::Config, - BalanceOf: EvmData, - BlockNumberFor: EvmData, ::RuntimeOrigin: From>, R::RuntimeCall: Dispatchable + GetDispatchInfo, R::RuntimeCall: From>, @@ -80,7 +71,7 @@ where // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: // Twox64(8) + ProtocolState::max_encoded_len - handle.record_db_read::(4 + ProtocolStateFor::::max_encoded_len())?; + handle.record_db_read::(4 + ProtocolState::max_encoded_len())?; let current_era = ActiveProtocolState::::get().era; @@ -135,7 +126,7 @@ where // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: // Twox64(8) + ProtocolState::max_encoded_len - handle.record_db_read::(4 + ProtocolStateFor::::max_encoded_len())?; + handle.record_db_read::(4 + ProtocolState::max_encoded_len())?; let current_era = ActiveProtocolState::::get().era; @@ -196,7 +187,7 @@ where // Blake2_128Concat(16 + SmartContract::max_encoded_len) + Ledger::max_encoded_len handle.record_db_read::( 24 + AccountLedgerFor::::max_encoded_len() - + ProtocolStateFor::::max_encoded_len() + + ProtocolState::max_encoded_len() + ::SmartContract::max_encoded_len(), )?; @@ -231,7 +222,7 @@ where // Storage item: StakerInfo: // Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len handle.record_db_read::( - 24 + ProtocolStateFor::::max_encoded_len() + 24 + ProtocolState::max_encoded_len() + ::SmartContract::max_encoded_len() + SingularStakingInfo::max_encoded_len(), )?; @@ -275,7 +266,7 @@ where // Storage item: ContractStake: // Twox64Concat(8) + EraIndex(4) + ContractStakeAmount::max_encoded_len handle.record_db_read::( - 36 + ProtocolStateFor::::max_encoded_len() + 36 + ProtocolState::max_encoded_len() + ::SmartContract::max_encoded_len() + DAppInfoFor::::max_encoded_len() + ContractStakeAmount::max_encoded_len(), @@ -326,7 +317,7 @@ where // Blake2_128Concat(16 + SmartContract::max_encoded_len()) + Ledger::max_encoded_len handle.record_db_read::( 24 + AccountLedgerFor::::max_encoded_len() - + ProtocolStateFor::::max_encoded_len() + + ProtocolState::max_encoded_len() + ::SmartContract::max_encoded_len(), )?; @@ -336,7 +327,7 @@ where // Parse smart contract & amount let contract_h160 = input.read::
()?.0; let smart_contract = Self::decode_smart_contract(contract_h160)?; - let amount: BalanceOf = input.read()?; + let amount: Balance = input.read()?; log::trace!(target: "ds-precompile", "bond_and_stake {:?}, {:?}", smart_contract, amount); @@ -374,7 +365,7 @@ where // Storage item: StakerInfo: // Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len handle.record_db_read::( - 24 + ProtocolStateFor::::max_encoded_len() + 24 + ProtocolState::max_encoded_len() + ::SmartContract::max_encoded_len() + SingularStakingInfo::max_encoded_len(), )?; @@ -385,7 +376,7 @@ where // Parse smart contract & amount let contract_h160 = input.read::
()?.0; let smart_contract = Self::decode_smart_contract(contract_h160)?; - let amount: BalanceOf = input.read()?; + let amount: Balance = input.read()?; let origin = R::AddressMapping::into_account_id(handle.context().caller); log::trace!(target: "ds-precompile", "unbond_and_unstake {:?}, {:?}", smart_contract, amount); @@ -509,7 +500,7 @@ where let origin_contract_h160 = input.read::
()?.0; let origin_smart_contract = Self::decode_smart_contract(origin_contract_h160)?; - let amount = input.read::>()?; + let amount = input.read::()?; let target_contract_h160 = input.read::
()?.0; let target_smart_contract = Self::decode_smart_contract(target_contract_h160)?; @@ -629,8 +620,7 @@ where + Dispatchable + GetDispatchInfo, ::RuntimeOrigin: From>, - BalanceOf: EvmData, - BlockNumberFor: EvmData, + Balance: EvmData, R::AccountId: From<[u8; 32]>, { fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { From 13fcfe6e1fe1272d396b8a7e8a954b0ae76ee2c8 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Wed, 6 Dec 2023 13:39:25 +0100 Subject: [PATCH 06/34] TODO --- precompiles/dapps-staking-v3/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index e5fd18e94d..95a0134e33 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -195,6 +195,7 @@ where input.expect_arguments(1)?; // parse the staker account + // TODO: replace with bounded bytes let staker_vec: Vec = input.read::()?.into(); let staker = Self::parse_input_address(staker_vec)?; @@ -235,6 +236,7 @@ where let contract_id = Self::decode_smart_contract(contract_h160)?; // parsae the staker account + // TODO: replace with bounded bytes let staker_vec: Vec = input.read::()?.into(); let staker = Self::parse_input_address(staker_vec)?; From d1668d609e2c7d378755e36a6574d0d5db8913c7 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Wed, 6 Dec 2023 16:37:41 +0100 Subject: [PATCH 07/34] v2 init implementation --- pallets/dapp-staking-v3/src/lib.rs | 4 +- pallets/dapp-staking-v3/src/test/tests.rs | 6 +- precompiles/dapps-staking-v3/src/lib.rs | 270 +++++++++++++++++++++- 3 files changed, 270 insertions(+), 10 deletions(-) diff --git a/pallets/dapp-staking-v3/src/lib.rs b/pallets/dapp-staking-v3/src/lib.rs index 0621394c36..c187b1b09e 100644 --- a/pallets/dapp-staking-v3/src/lib.rs +++ b/pallets/dapp-staking-v3/src/lib.rs @@ -788,7 +788,7 @@ pub mod pallet { ledger.subtract_lock_amount(amount_to_unlock); let current_block = frame_system::Pallet::::block_number(); - let unlock_block = current_block.saturating_add(Self::unlock_period()); + let unlock_block = current_block.saturating_add(Self::unlocking_period()); ledger .add_unlocking_chunk(amount_to_unlock, unlock_block) .map_err(|_| Error::::TooManyUnlockingChunks)?; @@ -1542,7 +1542,7 @@ pub mod pallet { } /// Unlocking period expressed in the number of blocks. - pub fn unlock_period() -> BlockNumber { + pub fn unlocking_period() -> BlockNumber { T::CycleConfiguration::blocks_per_era().saturating_mul(T::UnlockingPeriod::get().into()) } diff --git a/pallets/dapp-staking-v3/src/test/tests.rs b/pallets/dapp-staking-v3/src/test/tests.rs index 1a53113ad6..7b753d4dfc 100644 --- a/pallets/dapp-staking-v3/src/test/tests.rs +++ b/pallets/dapp-staking-v3/src/test/tests.rs @@ -671,7 +671,7 @@ fn unlock_with_exceeding_unlocking_chunks_storage_limits_fails() { #[test] fn claim_unlocked_is_ok() { ExtBuilder::build().execute_with(|| { - let unlocking_blocks = DappStaking::unlock_period(); + let unlocking_blocks = DappStaking::unlocking_period(); // Lock some amount in a few eras let account = 2; @@ -721,7 +721,7 @@ fn claim_unlocked_no_eligible_chunks_fails() { // Cannot claim if unlock period hasn't passed yet let lock_amount = 103; assert_lock(account, lock_amount); - let unlocking_blocks = DappStaking::unlock_period(); + let unlocking_blocks = DappStaking::unlocking_period(); run_for_blocks(unlocking_blocks - 1); assert_noop!( DappStaking::claim_unlocked(RuntimeOrigin::signed(account)), @@ -799,7 +799,7 @@ fn relock_unlocking_insufficient_lock_amount_fails() { }); // Make sure only one chunk is left - let unlocking_blocks = DappStaking::unlock_period(); + let unlocking_blocks = DappStaking::unlocking_period(); run_for_blocks(unlocking_blocks - 1); assert_claim_unlocked(account); diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index 95a0134e33..d66d75238f 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -30,7 +30,7 @@ use precompile_utils::{ error, revert, succeed, Address, Bytes, EvmData, EvmDataWriter, EvmResult, FunctionModifier, PrecompileHandleExt, RuntimeHelper, }; -use sp_core::{Get, H160}; +use sp_core::{Get, H160, U256}; use sp_runtime::traits::Zero; use sp_std::{marker::PhantomData, prelude::*}; extern crate alloc; @@ -38,8 +38,8 @@ extern crate alloc; use astar_primitives::Balance; use pallet_dapp_staking_v3::{ AccountLedgerFor, ActiveProtocolState, ContractStake, ContractStakeAmount, CurrentEraInfo, - DAppInfoFor, EraInfo, EraRewardSpanFor, EraRewards, IntegratedDApps, Ledger, - Pallet as DAppStaking, ProtocolState, SingularStakingInfo, StakerInfo, + DAppInfoFor, EraInfo, EraNumber, EraRewardSpanFor, EraRewards, IntegratedDApps, Ledger, + Pallet as DAppStaking, ProtocolState, SingularStakingInfo, StakerInfo, Subperiod, }; // #[cfg(test)] @@ -66,6 +66,8 @@ where R::RuntimeCall: From>, R::AccountId: From<[u8; 32]>, { + // v1 functions + /// Read the ongoing `era` number. fn read_current_era(handle: &mut impl PrecompileHandle) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly @@ -548,6 +550,210 @@ where Ok(succeed(EvmDataWriter::new().write(true).build())) } + // v2 functions + + /// Read the current protocol state. + /// + /// Returns: `(current era, current period number, subperiod)` + fn protocol_state(handle: &mut impl PrecompileHandle) -> EvmResult { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: ActiveProtocolState: + // Twox64(8) + ProtocolState::max_encoded_len + handle.record_db_read::(4 + ProtocolState::max_encoded_len())?; + + let protocol_state = ActiveProtocolState::::get(); + + Ok(succeed( + EvmDataWriter::new() + .write(U256::from(protocol_state.era)) + .write(U256::from(protocol_state.period_number())) + .write(Self::subperiod_id(&protocol_state.subperiod())) + .build(), + )) + } + + /// Read the `unbonding period` or `unlocking period` expressed in the number of eras. + fn unlocking_period(_: &impl PrecompileHandle) -> EvmResult { + // constant, no DB read + Ok(succeed( + EvmDataWriter::new() + .write(DAppStaking::::unlocking_period()) + .build(), + )) + } + + /// Attempt to lock the given amount into the dApp staking protocol. + fn lock(handle: &mut impl PrecompileHandle) -> EvmResult { + // Parse the amount + let mut input = handle.read_input()?; + input.expect_arguments(1)?; + let amount: Balance = input.read()?; + + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let lock_call = pallet_dapp_staking_v3::Call::::lock { amount }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), lock_call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + /// Attempt to unlock the given amount from the dApp staking protocol. + fn unlock(handle: &mut impl PrecompileHandle) -> EvmResult { + // Parse the amount + let mut input = handle.read_input()?; + input.expect_arguments(1)?; + let amount: Balance = input.read()?; + + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let unlock_call = pallet_dapp_staking_v3::Call::::unlock { amount }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unlock_call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + /// Attempts to claim unlocking chunks which have undergone the entire unlocking period. + fn claim_unlocked(handle: &mut impl PrecompileHandle) -> EvmResult { + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let claim_unlocked_call = pallet_dapp_staking_v3::Call::::claim_unlocked {}; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_unlocked_call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + /// Attempts to stake the given amount on the given smart contract. + fn stake(handle: &mut impl PrecompileHandle) -> EvmResult { + // Parse smart contract & amount + let mut input = handle.read_input()?; + input.expect_arguments(2)?; + + let contract_h160 = input.read::
()?.0; + let smart_contract = Self::decode_smart_contract(contract_h160)?; + let amount: Balance = input.read()?; + + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let stake_call = pallet_dapp_staking_v3::Call::::stake { + smart_contract, + amount, + }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + /// Attempts to unstake the given amount from the given smart contract. + fn unstake(handle: &mut impl PrecompileHandle) -> EvmResult { + // Parse smart contract & amount + let mut input = handle.read_input()?; + input.expect_arguments(2)?; + + let contract_h160 = input.read::
()?.0; + let smart_contract = Self::decode_smart_contract(contract_h160)?; + let amount: Balance = input.read()?; + + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let unstake_call = pallet_dapp_staking_v3::Call::::unstake { + smart_contract, + amount, + }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unstake_call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + /// Attempts to claim one or more pending staker rewards. + fn claim_staker_rewards(handle: &mut impl PrecompileHandle) -> EvmResult { + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let claim_staker_rewards_call = pallet_dapp_staking_v3::Call::::claim_staker_rewards {}; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_staker_rewards_call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + /// Attempts to claim bonus reward for being a loyal staker of the given dApp. + fn claim_bonus_reward(handle: &mut impl PrecompileHandle) -> EvmResult { + // Parse smart contract + let mut input = handle.read_input()?; + input.expect_arguments(1)?; + + let contract_h160 = input.read::
()?.0; + let smart_contract = Self::decode_smart_contract(contract_h160)?; + + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let claim_bonus_reward_call = + pallet_dapp_staking_v3::Call::::claim_bonus_reward { smart_contract }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_bonus_reward_call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + /// Attempts to claim dApp reward for the given dApp in the given era. + fn claim_dapp_reward(handle: &mut impl PrecompileHandle) -> EvmResult { + // Parse smart contract & era + let mut input = handle.read_input()?; + input.expect_arguments(2)?; + + let contract_h160 = input.read::
()?.0; + let smart_contract = Self::decode_smart_contract(contract_h160)?; + let era = input.read::()?; + + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let claim_dapp_reward_call = pallet_dapp_staking_v3::Call::::claim_dapp_reward { + smart_contract, + era, + }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_dapp_reward_call)?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + /// Attempts to unstake everything from an unregistered contract. + fn unstake_from_unregistered( + handle: &mut impl PrecompileHandle, + ) -> EvmResult { + // Parse smart contract + let mut input = handle.read_input()?; + input.expect_arguments(1)?; + + let contract_h160 = input.read::
()?.0; + let smart_contract = Self::decode_smart_contract(contract_h160)?; + + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let unstake_from_unregistered_call = + pallet_dapp_staking_v3::Call::::unstake_from_unregistered { smart_contract }; + RuntimeHelper::::try_dispatch( + handle, + Some(origin).into(), + unstake_from_unregistered_call, + )?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + /// Attempts to cleanup expired entries for the staker. + fn cleanup_expired_entries(handle: &mut impl PrecompileHandle) -> EvmResult { + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let cleanup_expired_entries_call = + pallet_dapp_staking_v3::Call::::cleanup_expired_entries {}; + RuntimeHelper::::try_dispatch( + handle, + Some(origin).into(), + cleanup_expired_entries_call, + )?; + + Ok(succeed(EvmDataWriter::new().write(true).build())) + } + + // Utility functions + /// Helper method to decode type SmartContract enum pub fn decode_smart_contract( contract_h160: H160, @@ -592,11 +798,23 @@ where Ok(staker) } + + /// Numeric Id of the subperiod enum value. + // TODO: add test for this to ensure it's the same as in the v2 interface + fn subperiod_id(subperiod: &Subperiod) -> u8 { + match subperiod { + Subperiod::Voting => 0, + Subperiod::BuildAndEarn => 1, + } + } } #[precompile_utils::generate_function_selector] #[derive(Debug, PartialEq)] pub enum Action { + // v1 functions + + // View ReadCurrentEra = "read_current_era()", ReadUnbondingPeriod = "read_unbonding_period()", ReadEraReward = "read_era_reward(uint32)", @@ -604,6 +822,8 @@ pub enum Action { ReadStakedAmount = "read_staked_amount(bytes)", ReadStakedAmountOnContract = "read_staked_amount_on_contract(address,bytes)", ReadContractStake = "read_contract_stake(address)", + + // Dispathables Register = "register(address)", BondAndStake = "bond_and_stake(address,uint128)", UnbondAndUnstake = "unbond_and_unstake(address,uint128)", @@ -613,6 +833,24 @@ pub enum Action { SetRewardDestination = "set_reward_destination(uint8)", WithdrawFromUnregistered = "withdraw_from_unregistered(address)", NominationTransfer = "nomination_transfer(address,uint128,address)", + + // v2 functions + + // View + ProtocolState = "protocol_state()", + UnlockingPeriod = "unlocking_period()", + + // Dispatchables + Lock = "lock(uint128)", + Unlock = "unlock(uint128)", + ClaimUnlocked = "claim_unlocked()", + Stake = "stake(address,uint128)", + Unstake = "unstake(address,uint128)", + ClaimStakerRewards = "claim_staker_rewards()", + ClaimBonusReward = "claim_bonus_reward(address)", + ClaimDappReward = "claim_dapp_reward(address,uint32)", + UnstakeFromUnregistered = "unstake_from_unregistered(address)", + CleanupExpiredEntries = "cleanup_expired_entries()", } impl Precompile for DappStakingV3Precompile @@ -637,12 +875,16 @@ where | Action::ReadEraStaked | Action::ReadStakedAmount | Action::ReadStakedAmountOnContract - | Action::ReadContractStake => FunctionModifier::View, + | Action::ReadContractStake + | Action::ProtocolState + | Action::UnlockingPeriod => FunctionModifier::View, _ => FunctionModifier::NonPayable, })?; match selector { - // read storage + // v1 functions + + // View Action::ReadCurrentEra => Self::read_current_era(handle), Action::ReadUnbondingPeriod => Self::read_unbonding_period(handle), Action::ReadEraReward => Self::read_era_reward(handle), @@ -661,6 +903,24 @@ where Action::SetRewardDestination => Self::set_reward_destination(handle), Action::WithdrawFromUnregistered => Self::withdraw_from_unregistered(handle), Action::NominationTransfer => Self::nomination_transfer(handle), + + // v2 functions + + // View + Action::ProtocolState => Self::protocol_state(handle), + Action::UnlockingPeriod => Self::unlocking_period(handle), + + // Dispatchables + Action::Lock => Self::lock(handle), + Action::Unlock => Self::unlock(handle), + Action::ClaimUnlocked => Self::claim_unlocked(handle), + Action::Stake => Self::stake(handle), + Action::Unstake => Self::unstake(handle), + Action::ClaimStakerRewards => Self::claim_staker_rewards(handle), + Action::ClaimBonusReward => Self::claim_bonus_reward(handle), + Action::ClaimDappReward => Self::claim_dapp_reward(handle), + Action::UnstakeFromUnregistered => Self::unstake_from_unregistered(handle), + Action::CleanupExpiredEntries => Self::cleanup_expired_entries(handle), } } } From cd66ee7ba542d378923748caeeb15f0ab10630d3 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Wed, 6 Dec 2023 17:04:49 +0100 Subject: [PATCH 08/34] Prepared mock --- Cargo.lock | 1 + precompiles/dapps-staking-v3/Cargo.toml | 2 + precompiles/dapps-staking-v3/src/lib.rs | 6 +- precompiles/dapps-staking-v3/src/mock.rs | 267 +++++++++-------------- 4 files changed, 115 insertions(+), 161 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fcb3b219c6..7cd5bbbb87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7988,6 +7988,7 @@ dependencies = [ "scale-info", "serde", "sha3", + "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", diff --git a/precompiles/dapps-staking-v3/Cargo.toml b/precompiles/dapps-staking-v3/Cargo.toml index 2d660d4952..1c962be6c0 100644 --- a/precompiles/dapps-staking-v3/Cargo.toml +++ b/precompiles/dapps-staking-v3/Cargo.toml @@ -37,6 +37,7 @@ pallet-timestamp = { workspace = true } precompile-utils = { workspace = true, features = ["testing"] } serde = { workspace = true } sha3 = { workspace = true } +sp-arithmetic = { workspace = true } sp-io = { workspace = true } [features] @@ -55,4 +56,5 @@ std = [ "pallet-evm/std", "precompile-utils/std", "pallet-balances/std", + "sp-arithmetic/std", ] diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index d66d75238f..9b12a0809d 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -42,8 +42,10 @@ use pallet_dapp_staking_v3::{ Pallet as DAppStaking, ProtocolState, SingularStakingInfo, StakerInfo, Subperiod, }; -// #[cfg(test)] -// mod mock; +#[cfg(test)] +mod mock; + +// TODO: uncomment & fix after uplift to new precompile utils // #[cfg(test)] // mod tests; diff --git a/precompiles/dapps-staking-v3/src/mock.rs b/precompiles/dapps-staking-v3/src/mock.rs index 976f7fd948..642401cde6 100644 --- a/precompiles/dapps-staking-v3/src/mock.rs +++ b/precompiles/dapps-staking-v3/src/mock.rs @@ -21,52 +21,32 @@ use super::*; use fp_evm::IsPrecompileResult; use frame_support::{ construct_runtime, parameter_types, - traits::{ConstU64, Currency, OnFinalize, OnInitialize}, + traits::{fungible::Mutate, ConstU128, ConstU64}, weights::{RuntimeDbWeight, Weight}, - PalletId, }; -use pallet_dapps_staking::weights; use pallet_evm::{ AddressMapping, EnsureAddressNever, EnsureAddressRoot, PrecompileResult, PrecompileSet, }; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use serde::{Deserialize, Serialize}; +use sp_arithmetic::fixed_point::FixedU64; use sp_core::{H160, H256}; use sp_io::TestExternalities; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, ConstU32, IdentityLookup}, AccountId32, }; extern crate alloc; -pub(crate) type BlockNumber = u64; -pub(crate) type Balance = u128; -pub(crate) type EraIndex = u32; -pub(crate) const MILLIAST: Balance = 1_000_000_000_000_000; -pub(crate) const AST: Balance = 1_000 * MILLIAST; - -pub(crate) const TEST_CONTRACT: H160 = H160::repeat_byte(0x09); - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -/// Value shouldn't be less than 2 for testing purposes, otherwise we cannot test certain corner cases. -pub(crate) const MAX_NUMBER_OF_STAKERS: u32 = 4; -/// Value shouldn't be less than 2 for testing purposes, otherwise we cannot test certain corner cases. -pub(crate) const MINIMUM_STAKING_AMOUNT: Balance = 10 * AST; -pub(crate) const MINIMUM_REMAINING_AMOUNT: Balance = 1; -pub(crate) const MAX_UNLOCKING_CHUNKS: u32 = 4; -pub(crate) const UNBONDING_PERIOD: EraIndex = 3; -pub(crate) const MAX_ERA_STAKE_VALUES: u32 = 10; - -// Do note that this needs to at least be 3 for tests to be valid. It can be greater but not smaller. -pub(crate) const BLOCKS_PER_ERA: BlockNumber = 3; - -pub(crate) const REGISTER_DEPOSIT: Balance = 10 * AST; +use astar_primitives::{ + dapp_staking::{CycleConfiguration, StakingRewardHandler}, + testing::Header, + Balance, BlockNumber, +}; +use pallet_dapp_staking_v3::PriceProvider; -pub(crate) const STAKER_BLOCK_REWARD: Balance = 531911; -pub(crate) const DAPP_BLOCK_REWARD: Balance = 773333; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; #[derive( Eq, @@ -146,16 +126,16 @@ pub const READ_WEIGHT: u64 = 3; pub const WRITE_WEIGHT: u64 = 7; parameter_types! { - pub const BlockHashCount: u64 = 250; + pub const BlockHashCount: BlockNumber = 250; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, 0)); - pub const TestWeights: RuntimeDbWeight = RuntimeDbWeight { - read: READ_WEIGHT, - write: WRITE_WEIGHT, - }; + pub const TestWeights: RuntimeDbWeight = RuntimeDbWeight { + read: READ_WEIGHT, + write: WRITE_WEIGHT, + }; } -impl frame_system::Config for TestRuntime { +impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); @@ -166,11 +146,11 @@ impl frame_system::Config for TestRuntime { type Hash = H256; type Hashing = BlakeTwo256; type AccountId = AccountId32; - type Lookup = IdentityLookup; + type Lookup = IdentityLookup; type Header = Header; type RuntimeEvent = RuntimeEvent; type BlockHashCount = BlockHashCount; - type DbWeight = TestWeights; + type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -182,23 +162,20 @@ impl frame_system::Config for TestRuntime { type MaxConsumers = frame_support::traits::ConstU32<16>; } -parameter_types! { - pub const ExistentialDeposit: u128 = 1; -} -impl pallet_balances::Config for TestRuntime { +impl pallet_balances::Config for Test { + type MaxLocks = ConstU32<4>; type MaxReserves = (); - type ReserveIdentifier = [u8; 4]; - type MaxLocks = (); + type ReserveIdentifier = [u8; 8]; type Balance = Balance; type RuntimeEvent = RuntimeEvent; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU128<1>; type AccountStore = System; - type WeightInfo = (); type HoldIdentifier = (); - type FreezeIdentifier = (); - type MaxHolds = (); - type MaxFreezes = (); + type FreezeIdentifier = RuntimeFreezeReason; + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<1>; + type WeightInfo = (); } pub fn precompile_address() -> H160 { @@ -206,16 +183,15 @@ pub fn precompile_address() -> H160 { } #[derive(Debug, Clone, Copy)] -pub struct DappPrecompile(PhantomData); - -impl PrecompileSet for DappPrecompile +pub struct DappStakingPrecompile(PhantomData); +impl PrecompileSet for DappStakingPrecompile where R: pallet_evm::Config, - DappsStakingWrapper: Precompile, + DappStakingV3Precompile: Precompile, { fn execute(&self, handle: &mut impl PrecompileHandle) -> Option { match handle.code_address() { - a if a == precompile_address() => Some(DappsStakingWrapper::::execute(handle)), + a if a == precompile_address() => Some(DappStakingV3Precompile::::execute(handle)), _ => None, } } @@ -229,11 +205,11 @@ where } parameter_types! { - pub PrecompilesValue: DappPrecompile = DappPrecompile(Default::default()); + pub PrecompilesValue: DappStakingPrecompile = DappStakingPrecompile(Default::default()); pub WeightPerGas: Weight = Weight::from_parts(1, 0); } -impl pallet_evm::Config for TestRuntime { +impl pallet_evm::Config for Test { type FeeCalculator = (); type GasWeightMapping = pallet_evm::FixedGasWeightMapping; type WeightPerGas = WeightPerGas; @@ -243,7 +219,7 @@ impl pallet_evm::Config for TestRuntime { type Currency = Balances; type RuntimeEvent = RuntimeEvent; type Runner = pallet_evm::runner::stack::Runner; - type PrecompilesType = DappPrecompile; + type PrecompilesType = DappStakingPrecompile; type PrecompilesValue = PrecompilesValue; type Timestamp = Timestamp; type ChainId = (); @@ -256,13 +232,10 @@ impl pallet_evm::Config for TestRuntime { type GasLimitPovSizeRatio = ConstU64<4>; } -parameter_types! { - pub const MinimumPeriod: u64 = 5; -} -impl pallet_timestamp::Config for TestRuntime { +impl pallet_timestamp::Config for Test { type Moment = u64; type OnTimestampSet = (); - type MinimumPeriod = MinimumPeriod; + type MinimumPeriod = ConstU64<5>; type WeightInfo = (); } @@ -280,70 +253,97 @@ impl Default for MockSmartContract { } } -parameter_types! { - pub const RegisterDeposit: Balance = REGISTER_DEPOSIT; - pub const BlockPerEra: BlockNumber = BLOCKS_PER_ERA; - pub const MaxNumberOfStakersPerContract: u32 = MAX_NUMBER_OF_STAKERS; - pub const MinimumStakingAmount: Balance = MINIMUM_STAKING_AMOUNT; - pub const DappsStakingPalletId: PalletId = PalletId(*b"mokdpstk"); - pub const MinimumRemainingAmount: Balance = MINIMUM_REMAINING_AMOUNT; - pub const MaxUnlockingChunks: u32 = MAX_UNLOCKING_CHUNKS; - pub const UnbondingPeriod: EraIndex = UNBONDING_PERIOD; - pub const MaxEraStakeValues: u32 = MAX_ERA_STAKE_VALUES; +pub struct DummyPriceProvider; +impl PriceProvider for DummyPriceProvider { + fn average_price() -> FixedU64 { + FixedU64::from_rational(1, 10) + } } -impl pallet_dapps_staking::Config for TestRuntime { - type RuntimeEvent = RuntimeEvent; - type Currency = Balances; - type BlockPerEra = BlockPerEra; - type RegisterDeposit = RegisterDeposit; - type SmartContract = MockSmartContract; - type WeightInfo = weights::SubstrateWeight; - type MaxNumberOfStakersPerContract = MaxNumberOfStakersPerContract; - type MinimumStakingAmount = MinimumStakingAmount; - type PalletId = DappsStakingPalletId; - type MinimumRemainingAmount = MinimumRemainingAmount; - type MaxUnlockingChunks = MaxUnlockingChunks; - type UnbondingPeriod = UnbondingPeriod; - type MaxEraStakeValues = MaxEraStakeValues; - type UnregisteredDappRewardRetention = ConstU32<2>; -} +pub struct DummyStakingRewardHandler; +impl StakingRewardHandler for DummyStakingRewardHandler { + fn staker_and_dapp_reward_pools(_total_staked_value: Balance) -> (Balance, Balance) { + ( + Balance::from(1_000_000_000_000_u128), + Balance::from(1_000_000_000_u128), + ) + } -pub struct ExternalityBuilder { - balances: Vec<(AccountId32, Balance)>, + fn bonus_reward_pool() -> Balance { + Balance::from(3_000_000_u128) + } + + fn payout_reward(beneficiary: &AccountId32, reward: Balance) -> Result<(), ()> { + let _ = Balances::mint_into(beneficiary, reward); + Ok(()) + } } -impl Default for ExternalityBuilder { - fn default() -> ExternalityBuilder { - ExternalityBuilder { balances: vec![] } +pub struct DummyCycleConfiguration; +impl CycleConfiguration for DummyCycleConfiguration { + fn periods_per_cycle() -> u32 { + 4 } + + fn eras_per_voting_subperiod() -> u32 { + 8 + } + + fn eras_per_build_and_earn_subperiod() -> u32 { + 16 + } + + fn blocks_per_era() -> u32 { + 10 + } +} + +impl pallet_dapp_staking_v3::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeFreezeReason = RuntimeFreezeReason; + type Currency = Balances; + type SmartContract = MockSmartContract; + type ManagerOrigin = frame_system::EnsureRoot; + type NativePriceProvider = DummyPriceProvider; + type StakingRewardHandler = DummyStakingRewardHandler; + type CycleConfiguration = DummyCycleConfiguration; + type EraRewardSpanLength = ConstU32<8>; + type RewardRetentionInPeriods = ConstU32<2>; + type MaxNumberOfContracts = ConstU32<10>; + type MaxUnlockingChunks = ConstU32<5>; + type MinimumLockedAmount = ConstU128<10>; + type UnlockingPeriod = ConstU32<2>; + type MaxNumberOfStakedContracts = ConstU32<5>; + type MinimumStakeAmount = ConstU128<3>; + type NumberOfTiers = ConstU32<4>; + type WeightInfo = pallet_dapp_staking_v3::weights::SubstrateWeight; } -impl ExternalityBuilder { - pub fn build(self) -> TestExternalities { +pub struct _ExternalityBuilder; +impl _ExternalityBuilder { + pub fn _build(self) -> TestExternalities { let mut storage = frame_system::GenesisConfig::default() - .build_storage::() + .build_storage::() .unwrap(); - pallet_balances::GenesisConfig:: { - balances: self.balances, - } - .assimilate_storage(&mut storage) - .ok(); + let balances = vec![10000; 9] + .into_iter() + .enumerate() + .map(|(idx, amount)| ([idx as u8; 32].into(), amount)) + .collect(); + + pallet_balances::GenesisConfig:: { balances: balances } + .assimilate_storage(&mut storage) + .ok(); let mut ext = TestExternalities::from(storage); ext.execute_with(|| System::set_block_number(1)); ext } - - pub(crate) fn with_balances(mut self, balances: Vec<(AccountId32, Balance)>) -> Self { - self.balances = balances; - self - } } construct_runtime!( - pub struct TestRuntime + pub struct Test where Block = Block, NodeBlock = Block, @@ -353,57 +353,6 @@ construct_runtime!( Balances: pallet_balances, Evm: pallet_evm, Timestamp: pallet_timestamp, - DappsStaking: pallet_dapps_staking, + DappsStaking: pallet_dapp_staking_v3, } ); - -/// Used to run to the specified block number -pub fn run_to_block(n: u64) { - while System::block_number() < n { - DappsStaking::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - // This is performed outside of dapps staking but we expect it before on_initialize - payout_block_rewards(); - DappsStaking::on_initialize(System::block_number()); - } -} - -/// Used to run the specified number of blocks -pub fn run_for_blocks(n: u64) { - run_to_block(System::block_number() + n); -} - -/// Advance blocks to the beginning of an era. -/// -/// Function has no effect if era is already passed. -pub fn advance_to_era(n: EraIndex) { - while DappsStaking::current_era() < n { - run_for_blocks(1); - } -} - -/// Initialize first block. -/// This method should only be called once in a UT otherwise the first block will get initialized multiple times. -pub fn initialize_first_block() { - // This assert prevents method misuse - assert_eq!(System::block_number(), 1 as BlockNumber); - - // This is performed outside of dapps staking but we expect it before on_initialize - payout_block_rewards(); - DappsStaking::on_initialize(System::block_number()); - run_to_block(2); -} - -/// Returns total block rewards that goes to dapps-staking. -/// Contains both `dapps` reward and `stakers` reward. -pub fn joint_block_reward() -> Balance { - STAKER_BLOCK_REWARD + DAPP_BLOCK_REWARD -} - -/// Payout block rewards to stakers & dapps -fn payout_block_rewards() { - DappsStaking::rewards( - Balances::issue(STAKER_BLOCK_REWARD.into()), - Balances::issue(DAPP_BLOCK_REWARD.into()), - ); -} From 777b0a89b6819b19ecd0bcf5d8674cc27c14f2fd Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Fri, 8 Dec 2023 09:22:45 +0100 Subject: [PATCH 09/34] todo --- precompiles/dapps-staking-v3/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index 9b12a0809d..cc32ff4376 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -79,6 +79,7 @@ where let current_era = ActiveProtocolState::::get().era; + // TODO: cast to u256, and check other types where u256 is expected but not exactly provided Ok(succeed(EvmDataWriter::new().write(current_era).build())) } From 58e5500d344c57a66d1548aedc9ee828ad3a3ee4 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Thu, 14 Dec 2023 11:18:30 +0100 Subject: [PATCH 10/34] Migration to v2 utils --- Cargo.lock | 2 +- precompiles/dapps-staking-v3/Cargo.toml | 4 +- .../dapps-staking-v3/DappsStakingV1.sol | 18 +- .../dapps-staking-v3/DappsStakingV2.sol | 20 +- precompiles/dapps-staking-v3/src/lib.rs | 1169 ++++++++--------- 5 files changed, 535 insertions(+), 678 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7cd5bbbb87..8fe6436392 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7984,7 +7984,7 @@ dependencies = [ "pallet-evm", "pallet-timestamp", "parity-scale-codec", - "precompile-utils", + "precompile-utils-v2", "scale-info", "serde", "sha3", diff --git a/precompiles/dapps-staking-v3/Cargo.toml b/precompiles/dapps-staking-v3/Cargo.toml index 1c962be6c0..1718fd7f9c 100644 --- a/precompiles/dapps-staking-v3/Cargo.toml +++ b/precompiles/dapps-staking-v3/Cargo.toml @@ -24,7 +24,7 @@ sp-std = { workspace = true } # Astar astar-primitives = { workspace = true } pallet-dapp-staking-v3 = { workspace = true } -precompile-utils = { workspace = true, default-features = false } +precompile-utils = { path = "../utils_v2", default-features = false, package = "precompile-utils-v2" } # Frontier fp-evm = { workspace = true } @@ -34,7 +34,7 @@ pallet-evm = { workspace = true } derive_more = { workspace = true } pallet-balances = { workspace = true, features = ["std"] } pallet-timestamp = { workspace = true } -precompile-utils = { workspace = true, features = ["testing"] } +precompile-utils = { path = "../utils_v2", default-features = false, package = "precompile-utils-v2", features = ["testing"] } serde = { workspace = true } sha3 = { workspace = true } sp-arithmetic = { workspace = true } diff --git a/precompiles/dapps-staking-v3/DappsStakingV1.sol b/precompiles/dapps-staking-v3/DappsStakingV1.sol index 23918e1460..e372aa7f6f 100644 --- a/precompiles/dapps-staking-v3/DappsStakingV1.sol +++ b/precompiles/dapps-staking-v3/DappsStakingV1.sol @@ -46,27 +46,27 @@ interface DappsStaking { /// @notice Register is root origin only and not allowed via evm precompile. /// This should always fail. - function register(address) external; + function register(address) external returns (bool); /// @notice Stake provided amount on the contract. - function bond_and_stake(address, uint128) external; + function bond_and_stake(address, uint128) external returns (bool); /// @notice Start unbonding process and unstake balance from the contract. - function unbond_and_unstake(address, uint128) external; + function unbond_and_unstake(address, uint128) external returns (bool); /// @notice Withdraw all funds that have completed the unbonding process. - function withdraw_unbonded() external; + function withdraw_unbonded() external returns (bool); /// @notice Claim earned staker rewards for the oldest unclaimed era. /// In order to claim multiple eras, this call has to be called multiple times. /// Staker account is derived from the caller address. /// @param smart_contract: The smart contract address used for staking - function claim_staker(address smart_contract) external; + function claim_staker(address smart_contract) external returns (bool); /// @notice Claim one era of unclaimed dapp rewards for the specified contract and era. /// @param smart_contract: The smart contract address used for staking /// @param era: The era to be claimed - function claim_dapp(address smart_contract, uint128 era) external; + function claim_dapp(address smart_contract, uint128 era) external returns (bool); /// Instruction how to handle reward payout for staker. /// `FreeBalance` - Reward will be paid out to the staker (free balance). @@ -75,15 +75,15 @@ interface DappsStaking { /// @notice Set reward destination for staker rewards /// @param reward_destination: The instruction on how the reward payout should be handled - function set_reward_destination(RewardDestination reward_destination) external; + function set_reward_destination(RewardDestination reward_destination) external returns (bool); /// @notice Withdraw staked funds from an unregistered contract. /// @param smart_contract: The smart contract address used for staking - function withdraw_from_unregistered(address smart_contract) external; + function withdraw_from_unregistered(address smart_contract) external returns (bool); /// @notice Transfer part or entire nomination from origin smart contract to target smart contract /// @param origin_smart_contract: The origin smart contract address /// @param amount: The amount to transfer from origin to target /// @param target_smart_contract: The target smart contract address - function nomination_transfer(address origin_smart_contract, uint128 amount, address target_smart_contract) external; + function nomination_transfer(address origin_smart_contract, uint128 amount, address target_smart_contract) external returns (bool); } diff --git a/precompiles/dapps-staking-v3/DappsStakingV2.sol b/precompiles/dapps-staking-v3/DappsStakingV2.sol index cf1fc5bf52..fc23e5b90b 100644 --- a/precompiles/dapps-staking-v3/DappsStakingV2.sol +++ b/precompiles/dapps-staking-v3/DappsStakingV2.sol @@ -29,32 +29,32 @@ interface DAppStaking { // Extrinsic calls /// @notice Lock the given amount of tokens into dApp staking protocol. - function lock(uint128) external; + function lock(uint128) external returns (bool); /// @notice Start the unlocking process for the given amount of tokens. - function unlock(uint128) external; + function unlock(uint128) external returns (bool); /// @notice Claims unlocked tokens. - function claim_unlocked() external; + function claim_unlocked() external returns (bool); /// @notice Stake the given amount of tokens on the specified smart contract. - function stake(address, uint128) external; + function stake(address, uint128) external returns (bool); /// @notice Unstake the given amount of tokens from the specified smart contract. - function unstake(address, uint128) external; + function unstake(address, uint128) external returns (bool); /// @notice Claims one or more pending staker rewards. - function claim_staker_rewards() external; + function claim_staker_rewards() external returns (bool); /// @notice Claim the bonus reward for the specified smart contract. - function claim_bonus_reward(address) external; + function claim_bonus_reward(address) external returns (bool); /// @notice Claim dApp reward for the specified smart contract & era. - function claim_dapp_reward(address, uint128) external; + function claim_dapp_reward(address, uint128) external returns (bool); /// @notice Unstake all funds from the unregistered smart contract. - function unstake_from_unregistered(address) external; + function unstake_from_unregistered(address) external returns (bool); /// @notice Used to cleanup all expired contract stake entries from the caller. - function cleanup_expired_entries() external; + function cleanup_expired_entries() external returns (bool); } diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index cc32ff4376..b5d4bcfc0c 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -20,16 +20,16 @@ #![cfg_attr(not(feature = "std"), no_std)] -use fp_evm::{PrecompileHandle, PrecompileOutput}; +use fp_evm::PrecompileHandle; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}; - -use pallet_evm::{AddressMapping, Precompile}; -use precompile_utils::{ - error, revert, succeed, Address, Bytes, EvmData, EvmDataWriter, EvmResult, FunctionModifier, - PrecompileHandleExt, RuntimeHelper, +use frame_support::{ + dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, + traits::ConstU32, }; + +use pallet_evm::AddressMapping; +use precompile_utils::prelude::*; use sp_core::{Get, H160, U256}; use sp_runtime::traits::Zero; use sp_std::{marker::PhantomData, prelude::*}; @@ -42,9 +42,11 @@ use pallet_dapp_staking_v3::{ Pallet as DAppStaking, ProtocolState, SingularStakingInfo, StakerInfo, Subperiod, }; +pub const STAKER_BYTES_LIMIT: u32 = 32; +type GetStakerBytesLimit = ConstU32; + #[cfg(test)] mod mock; - // TODO: uncomment & fix after uplift to new precompile utils // #[cfg(test)] // mod tests; @@ -60,6 +62,7 @@ pub enum Contract { pub struct DappStakingV3Precompile(PhantomData); +#[precompile_utils::precompile] impl DappStakingV3Precompile where R: pallet_evm::Config + pallet_dapp_staking_v3::Config, @@ -71,43 +74,38 @@ where // v1 functions /// Read the ongoing `era` number. - fn read_current_era(handle: &mut impl PrecompileHandle) -> EvmResult { + #[precompile::public("read_current_era()")] + #[precompile::view] + fn read_current_era(handle: &mut impl PrecompileHandle) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: // Twox64(8) + ProtocolState::max_encoded_len - handle.record_db_read::(4 + ProtocolState::max_encoded_len())?; + handle.record_db_read::(8 + ProtocolState::max_encoded_len())?; let current_era = ActiveProtocolState::::get().era; - // TODO: cast to u256, and check other types where u256 is expected but not exactly provided - Ok(succeed(EvmDataWriter::new().write(current_era).build())) + Ok(current_era.into()) } /// Read the `unbonding period` or `unlocking period` expressed in the number of eras. - fn read_unbonding_period(_: &impl PrecompileHandle) -> EvmResult { + #[precompile::public("read_unbonding_period()")] + #[precompile::view] + fn read_unbonding_period(_: &mut impl PrecompileHandle) -> EvmResult { // constant, no DB read - Ok(succeed( - EvmDataWriter::new() - .write(::UnlockingPeriod::get()) - .build(), - )) + Ok(::UnlockingPeriod::get().into()) } /// Read the total assigned reward pool for the given era. /// /// Total amount is sum of staker & dApp rewards. - fn read_era_reward(handle: &mut impl PrecompileHandle) -> EvmResult { + #[precompile::public("read_era_reward(uint32)")] + #[precompile::view] + fn read_era_reward(handle: &mut impl PrecompileHandle, era: u32) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: EraRewards: // Twox64Concat(8) + EraIndex(4) + EraRewardSpanFor::max_encoded_len handle.record_db_read::(12 + EraRewardSpanFor::::max_encoded_len())?; - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - - // Parse era for which rewards are required - let era: u32 = input.read::()?; - // Get the appropriate era reward span let era_span_index = DAppStaking::::era_reward_span_index(era); let reward_span = @@ -118,7 +116,7 @@ where r.staker_reward_pool.saturating_add(r.dapp_reward_pool) }); - Ok(succeed(EvmDataWriter::new().write(reward).build())) + Ok(reward) } /// Read the total staked amount for the given era. @@ -127,19 +125,16 @@ where /// In that case, zero is returned. /// /// This is safe to use for current era and the next one. - fn read_era_staked(handle: &mut impl PrecompileHandle) -> EvmResult { + #[precompile::public("read_era_staked(uint32)")] + #[precompile::view] + fn read_era_staked(handle: &mut impl PrecompileHandle, era: u32) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: // Twox64(8) + ProtocolState::max_encoded_len - handle.record_db_read::(4 + ProtocolState::max_encoded_len())?; + handle.record_db_read::(8 + ProtocolState::max_encoded_len())?; let current_era = ActiveProtocolState::::get().era; - // Parse era from the input - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - let era: u32 = input.read::()?; - // There are few distinct scenenarios: // 1. Era is in the past so the value might exist. // 2. Era is current or the next one, in which case we definitely have that information. @@ -147,8 +142,8 @@ where if era < current_era { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: EraRewards: - // Twox64Concat(8) + EraIndex(4) + EraRewardSpanFor::max_encoded_len - handle.record_db_read::(12 + EraRewardSpanFor::::max_encoded_len())?; + // Twox64Concat(8) + Twox64Concat(8 + EraIndex(4)) + EraRewardSpanFor::max_encoded_len + handle.record_db_read::(20 + EraRewardSpanFor::::max_encoded_len())?; let era_span_index = DAppStaking::::era_reward_span_index(era); let reward_span = @@ -156,7 +151,7 @@ where let staked = reward_span.get(era).map_or(Zero::zero(), |r| r.staked); - Ok(succeed(EvmDataWriter::new().write(staked).build())) + Ok(staked.into()) } else if era == current_era || era == current_era.saturating_add(1) { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: CurrentEraInfo: @@ -166,25 +161,22 @@ where let current_era_info = CurrentEraInfo::::get(); if era == current_era { - Ok(succeed( - EvmDataWriter::new() - .write(current_era_info.current_stake_amount.total()) - .build(), - )) + Ok(current_era_info.current_stake_amount.total()) } else { - Ok(succeed( - EvmDataWriter::new() - .write(current_era_info.next_stake_amount.total()) - .build(), - )) + Ok(current_era_info.next_stake_amount.total()) } } else { - Err(error("Era is in the future")) + Err(RevertReason::custom("Era is in the future").into()) } } /// Read the total staked amount by the given account. - fn read_staked_amount(handle: &mut impl PrecompileHandle) -> EvmResult { + #[precompile::public("read_staked_amount(bytes)")] + #[precompile::view] + fn read_staked_amount( + handle: &mut impl PrecompileHandle, + staker: BoundedBytes, + ) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: // Twox64(8) + ProtocolState::max_encoded_len @@ -196,13 +188,7 @@ where + ::SmartContract::max_encoded_len(), )?; - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - - // parse the staker account - // TODO: replace with bounded bytes - let staker_vec: Vec = input.read::()?.into(); - let staker = Self::parse_input_address(staker_vec)?; + let staker = Self::parse_input_address(staker.into())?; // read the account's ledger let ledger = Ledger::::get(&staker); @@ -211,17 +197,17 @@ where // Make sure to check staked amount against the ongoing period (past period stakes are reset to zero). let current_period_number = ActiveProtocolState::::get().period_number(); - Ok(succeed( - EvmDataWriter::new() - .write(ledger.staked_amount(current_period_number)) - .build(), - )) + Ok(ledger.staked_amount(current_period_number)) } /// Read the total staked amount by the given staker on the given contract. + #[precompile::public("read_staked_amount_on_contract(address,bytes)")] + #[precompile::view] fn read_staked_amount_on_contract( handle: &mut impl PrecompileHandle, - ) -> EvmResult { + contract_h160: Address, + staker: BoundedBytes, + ) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: // Twox64(8) + ProtocolState::max_encoded_len @@ -233,17 +219,11 @@ where + SingularStakingInfo::max_encoded_len(), )?; - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - // parse contract address - let contract_h160 = input.read::
()?.0; - let contract_id = Self::decode_smart_contract(contract_h160)?; + let contract_id = Self::decode_smart_contract(contract_h160.into())?; - // parsae the staker account - // TODO: replace with bounded bytes - let staker_vec: Vec = input.read::()?.into(); - let staker = Self::parse_input_address(staker_vec)?; + // parse the staker account + let staker = Self::parse_input_address(staker.into())?; // Get staking info for the staker/contract combination let staking_info = StakerInfo::::get(&staker, &contract_id).unwrap_or_default(); @@ -253,18 +233,19 @@ where let current_period_number = ActiveProtocolState::::get().period_number(); if staking_info.period_number() == current_period_number { - Ok(succeed( - EvmDataWriter::new() - .write(staking_info.total_staked_amount()) - .build(), - )) + Ok(staking_info.total_staked_amount()) } else { - Ok(succeed(EvmDataWriter::new().write(0_u128).build())) + Ok(0_u128) } } - /// Read the amount staked on right now. - fn read_contract_stake(handle: &mut impl PrecompileHandle) -> EvmResult { + /// Read the total amount staked on the given contract right now. + #[precompile::public("read_contract_stake(address)")] + #[precompile::view] + fn read_contract_stake( + handle: &mut impl PrecompileHandle, + contract_h160: Address, + ) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: // Twox64(8) + ProtocolState::max_encoded_len @@ -279,481 +260,473 @@ where + ContractStakeAmount::max_encoded_len(), )?; - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - // parse input parameters for pallet-dapps-staking call - let contract_h160 = input.read::
()?.0; - let contract_id = Self::decode_smart_contract(contract_h160)?; + let contract_id = Self::decode_smart_contract(contract_h160.into())?; let current_period_number = ActiveProtocolState::::get().period_number(); let dapp_info = match IntegratedDApps::::get(&contract_id) { Some(dapp_info) => dapp_info, None => { // If the contract is not registered, return 0 to keep the legacy behavior. - return Ok(succeed(EvmDataWriter::new().write(0_u128).build())); + return Ok(0_u128); } }; // call pallet-dapps-staking let contract_stake = ContractStake::::get(&dapp_info.id); - Ok(succeed( - EvmDataWriter::new() - .write(contract_stake.total_staked_amount(current_period_number)) - .build(), - )) - } - - /// Register contract with the dapp-staking pallet - /// Register is root origin only. This should always fail when called via evm precompile. - fn register(_: &mut impl PrecompileHandle) -> EvmResult { - // register is root-origin call. it should always fail when called via evm precompiles. - Err(error("register via evm precompile is not allowed")) - } - - /// Lock & stake some amount on the specified contract. - /// - /// In case existing `stakeable` is sufficient to cover the given `amount`, only the `stake` operation is performed. - /// Otherwise, best effort is done to lock the additional amount so `stakeable` amount can cover the given `amount`. - fn bond_and_stake(handle: &mut impl PrecompileHandle) -> EvmResult { - // TODO: benchmark this function so we can measure ref time & PoV correctly - // Storage item: ActiveProtocolState: - // Twox64(8) + ProtocolState::max_encoded_len - // Storage item: Ledger: - // Blake2_128Concat(16 + SmartContract::max_encoded_len()) + Ledger::max_encoded_len - handle.record_db_read::( - 24 + AccountLedgerFor::::max_encoded_len() - + ProtocolState::max_encoded_len() - + ::SmartContract::max_encoded_len(), - )?; - - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - - // Parse smart contract & amount - let contract_h160 = input.read::
()?.0; - let smart_contract = Self::decode_smart_contract(contract_h160)?; - let amount: Balance = input.read()?; - - log::trace!(target: "ds-precompile", "bond_and_stake {:?}, {:?}", smart_contract, amount); - - // Read total locked & staked amounts - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let protocol_state = ActiveProtocolState::::get(); - let ledger = Ledger::::get(&origin); - - // Check if stakeable amount is enough to cover the given `amount` - let stakeable_amount = ledger.stakeable_amount(protocol_state.period_number()); - - // If it isn't, we need to first lock the additional amount. - if stakeable_amount < amount { - let delta = amount.saturating_sub(stakeable_amount); - - let lock_call = pallet_dapp_staking_v3::Call::::lock { amount: delta }; - RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), lock_call)?; - } - - // Now, with best effort, we can try & stake the given `value`. - let stake_call = pallet_dapp_staking_v3::Call::::stake { - smart_contract, - amount, - }; - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Start unbonding process and unstake balance from the contract. - fn unbond_and_unstake(handle: &mut impl PrecompileHandle) -> EvmResult { - // TODO: benchmark this function so we can measure ref time & PoV correctly - // Storage item: ActiveProtocolState: - // Twox64(8) + ProtocolState::max_encoded_len - // Storage item: StakerInfo: - // Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len - handle.record_db_read::( - 24 + ProtocolState::max_encoded_len() - + ::SmartContract::max_encoded_len() - + SingularStakingInfo::max_encoded_len(), - )?; - - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - - // Parse smart contract & amount - let contract_h160 = input.read::
()?.0; - let smart_contract = Self::decode_smart_contract(contract_h160)?; - let amount: Balance = input.read()?; - let origin = R::AddressMapping::into_account_id(handle.context().caller); - log::trace!(target: "ds-precompile", "unbond_and_unstake {:?}, {:?}", smart_contract, amount); - - // Find out if there is something staked on the contract - let protocol_state = ActiveProtocolState::::get(); - let staker_info = StakerInfo::::get(&origin, &smart_contract).unwrap_or_default(); - - // If there is, we need to unstake it before calling `unlock` - if staker_info.period_number() == protocol_state.period_number() { - let unstake_call = pallet_dapp_staking_v3::Call::::unstake { - smart_contract, - amount, - }; - RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), unstake_call)?; - } - - // Now we can try and `unlock` the given `amount` - let unlock_call = pallet_dapp_staking_v3::Call::::unlock { amount }; - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unlock_call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Claim back the unbonded (or unlocked) funds. - fn withdraw_unbonded(handle: &mut impl PrecompileHandle) -> EvmResult { - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let call = pallet_dapp_staking_v3::Call::::claim_unlocked {}; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Claim dApp rewards for the given era - fn claim_dapp(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - - // Parse the smart contract & era - let contract_h160 = input.read::
()?.0; - let smart_contract = Self::decode_smart_contract(contract_h160)?; - let era: u32 = input.read::()?; - log::trace!(target: "ds-precompile", "claim_dapp {:?}, era {:?}", smart_contract, era); - - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let call = pallet_dapp_staking_v3::Call::::claim_dapp_reward { - smart_contract, - era, - }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Claim staker rewards. - /// - /// Smart contract argument is legacy & is ignored in the new implementation. - fn claim_staker(handle: &mut impl PrecompileHandle) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - - // Parse smart contract to keep in line with the legacy behavior. - let contract_h160 = input.read::
()?.0; - let _smart_contract = Self::decode_smart_contract(contract_h160)?; - log::trace!(target: "ds-precompile", "claim_staker {:?}", _smart_contract); - - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let call = pallet_dapp_staking_v3::Call::::claim_staker_rewards {}; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Set claim reward destination for the caller. - /// - /// This call has been deprecated by dApp staking v3. - fn set_reward_destination(_handle: &mut impl PrecompileHandle) -> EvmResult { - Err(error( - "set_reward_destination via evm precompile is no longer supported", - )) - } - - /// Withdraw staked funds from the unregistered contract - fn withdraw_from_unregistered( - handle: &mut impl PrecompileHandle, - ) -> EvmResult { - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - - // Parse smart contract - let contract_h160 = input.read::
()?.0; - let smart_contract = Self::decode_smart_contract(contract_h160)?; - log::trace!(target: "ds-precompile", "withdraw_from_unregistered {:?}", smart_contract); - - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let call = pallet_dapp_staking_v3::Call::::unstake_from_unregistered { smart_contract }; - - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Transfers stake from one contract to another. - /// This is a legacy functionality that is no longer supported via direct call to dApp staking v3. - /// However, it can be achieved by chaining `unstake` and `stake` calls. - fn nomination_transfer(handle: &mut impl PrecompileHandle) -> EvmResult { - // TODO: benchmark this function so we can measure ref time & PoV correctly - // Storage item: StakerInfo: - // Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len - handle.record_db_read::( - 16 + ::SmartContract::max_encoded_len() - + SingularStakingInfo::max_encoded_len(), - )?; - - let mut input = handle.read_input()?; - input.expect_arguments(3)?; - - // Parse origin smart contract, transfer amount & the target smart contract - let origin_contract_h160 = input.read::
()?.0; - let origin_smart_contract = Self::decode_smart_contract(origin_contract_h160)?; - - let amount = input.read::()?; - - let target_contract_h160 = input.read::
()?.0; - let target_smart_contract = Self::decode_smart_contract(target_contract_h160)?; - log::trace!(target: "ds-precompile", "nomination_transfer {:?} {:?} {:?}", origin_smart_contract, amount, target_smart_contract); - - // Find out how much staker has staked on the origin contract - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let staker_info = StakerInfo::::get(&origin, &origin_smart_contract).unwrap_or_default(); - - // We don't care from which period the staked amount is, the logic takes care of the situation - // if value comes from the past period. - let staked_amount = staker_info.total_staked_amount(); - let minimum_allowed_stake_amount = - ::MinimumStakeAmount::get(); - - // In case the remaining staked amount on the origin contract is less than the minimum allowed stake amount, - // everything will be unstaked. To keep in line with legacy `nomination_transfer` behavior, we should transfer - // the entire amount from the origin to target contract. - // - // In case value comes from the past period, we don't care, since the `unstake` call will fall apart. - let stake_amount = if staked_amount > 0 - && staked_amount.saturating_sub(amount) < minimum_allowed_stake_amount - { - staked_amount - } else { - amount - }; - - // First call unstake from the origin smart contract - let unstake_call = pallet_dapp_staking_v3::Call::::unstake { - smart_contract: origin_smart_contract, - amount, - }; - RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), unstake_call)?; - - // Then call stake on the target smart contract - let stake_call = pallet_dapp_staking_v3::Call::::stake { - smart_contract: target_smart_contract, - amount: stake_amount, - }; - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - // v2 functions - - /// Read the current protocol state. - /// - /// Returns: `(current era, current period number, subperiod)` - fn protocol_state(handle: &mut impl PrecompileHandle) -> EvmResult { - // TODO: benchmark this function so we can measure ref time & PoV correctly - // Storage item: ActiveProtocolState: - // Twox64(8) + ProtocolState::max_encoded_len - handle.record_db_read::(4 + ProtocolState::max_encoded_len())?; - - let protocol_state = ActiveProtocolState::::get(); - - Ok(succeed( - EvmDataWriter::new() - .write(U256::from(protocol_state.era)) - .write(U256::from(protocol_state.period_number())) - .write(Self::subperiod_id(&protocol_state.subperiod())) - .build(), - )) - } - - /// Read the `unbonding period` or `unlocking period` expressed in the number of eras. - fn unlocking_period(_: &impl PrecompileHandle) -> EvmResult { - // constant, no DB read - Ok(succeed( - EvmDataWriter::new() - .write(DAppStaking::::unlocking_period()) - .build(), - )) - } - - /// Attempt to lock the given amount into the dApp staking protocol. - fn lock(handle: &mut impl PrecompileHandle) -> EvmResult { - // Parse the amount - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - let amount: Balance = input.read()?; - - // Prepare call & dispatch it - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let lock_call = pallet_dapp_staking_v3::Call::::lock { amount }; - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), lock_call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Attempt to unlock the given amount from the dApp staking protocol. - fn unlock(handle: &mut impl PrecompileHandle) -> EvmResult { - // Parse the amount - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - let amount: Balance = input.read()?; - - // Prepare call & dispatch it - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let unlock_call = pallet_dapp_staking_v3::Call::::unlock { amount }; - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unlock_call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Attempts to claim unlocking chunks which have undergone the entire unlocking period. - fn claim_unlocked(handle: &mut impl PrecompileHandle) -> EvmResult { - // Prepare call & dispatch it - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let claim_unlocked_call = pallet_dapp_staking_v3::Call::::claim_unlocked {}; - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_unlocked_call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Attempts to stake the given amount on the given smart contract. - fn stake(handle: &mut impl PrecompileHandle) -> EvmResult { - // Parse smart contract & amount - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - - let contract_h160 = input.read::
()?.0; - let smart_contract = Self::decode_smart_contract(contract_h160)?; - let amount: Balance = input.read()?; - - // Prepare call & dispatch it - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let stake_call = pallet_dapp_staking_v3::Call::::stake { - smart_contract, - amount, - }; - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Attempts to unstake the given amount from the given smart contract. - fn unstake(handle: &mut impl PrecompileHandle) -> EvmResult { - // Parse smart contract & amount - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - - let contract_h160 = input.read::
()?.0; - let smart_contract = Self::decode_smart_contract(contract_h160)?; - let amount: Balance = input.read()?; - - // Prepare call & dispatch it - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let unstake_call = pallet_dapp_staking_v3::Call::::unstake { - smart_contract, - amount, - }; - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unstake_call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Attempts to claim one or more pending staker rewards. - fn claim_staker_rewards(handle: &mut impl PrecompileHandle) -> EvmResult { - // Prepare call & dispatch it - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let claim_staker_rewards_call = pallet_dapp_staking_v3::Call::::claim_staker_rewards {}; - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_staker_rewards_call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Attempts to claim bonus reward for being a loyal staker of the given dApp. - fn claim_bonus_reward(handle: &mut impl PrecompileHandle) -> EvmResult { - // Parse smart contract - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - - let contract_h160 = input.read::
()?.0; - let smart_contract = Self::decode_smart_contract(contract_h160)?; - - // Prepare call & dispatch it - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let claim_bonus_reward_call = - pallet_dapp_staking_v3::Call::::claim_bonus_reward { smart_contract }; - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_bonus_reward_call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Attempts to claim dApp reward for the given dApp in the given era. - fn claim_dapp_reward(handle: &mut impl PrecompileHandle) -> EvmResult { - // Parse smart contract & era - let mut input = handle.read_input()?; - input.expect_arguments(2)?; - - let contract_h160 = input.read::
()?.0; - let smart_contract = Self::decode_smart_contract(contract_h160)?; - let era = input.read::()?; - - // Prepare call & dispatch it - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let claim_dapp_reward_call = pallet_dapp_staking_v3::Call::::claim_dapp_reward { - smart_contract, - era, - }; - RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_dapp_reward_call)?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Attempts to unstake everything from an unregistered contract. - fn unstake_from_unregistered( - handle: &mut impl PrecompileHandle, - ) -> EvmResult { - // Parse smart contract - let mut input = handle.read_input()?; - input.expect_arguments(1)?; - - let contract_h160 = input.read::
()?.0; - let smart_contract = Self::decode_smart_contract(contract_h160)?; - - // Prepare call & dispatch it - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let unstake_from_unregistered_call = - pallet_dapp_staking_v3::Call::::unstake_from_unregistered { smart_contract }; - RuntimeHelper::::try_dispatch( - handle, - Some(origin).into(), - unstake_from_unregistered_call, - )?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } - - /// Attempts to cleanup expired entries for the staker. - fn cleanup_expired_entries(handle: &mut impl PrecompileHandle) -> EvmResult { - // Prepare call & dispatch it - let origin = R::AddressMapping::into_account_id(handle.context().caller); - let cleanup_expired_entries_call = - pallet_dapp_staking_v3::Call::::cleanup_expired_entries {}; - RuntimeHelper::::try_dispatch( - handle, - Some(origin).into(), - cleanup_expired_entries_call, - )?; - - Ok(succeed(EvmDataWriter::new().write(true).build())) - } + Ok(contract_stake.total_staked_amount(current_period_number)) + } + + // /// Register contract with the dapp-staking pallet + // /// Register is root origin only. This should always fail when called via evm precompile. + // fn register(_: &mut impl PrecompileHandle) -> EvmResult { + // // register is root-origin call. it should always fail when called via evm precompiles. + // Err(error("register via evm precompile is not allowed")) + // } + + // /// Lock & stake some amount on the specified contract. + // /// + // /// In case existing `stakeable` is sufficient to cover the given `amount`, only the `stake` operation is performed. + // /// Otherwise, best effort is done to lock the additional amount so `stakeable` amount can cover the given `amount`. + // fn bond_and_stake(handle: &mut impl PrecompileHandle) -> EvmResult { + // // TODO: benchmark this function so we can measure ref time & PoV correctly + // // Storage item: ActiveProtocolState: + // // Twox64(8) + ProtocolState::max_encoded_len + // // Storage item: Ledger: + // // Blake2_128Concat(16 + SmartContract::max_encoded_len()) + Ledger::max_encoded_len + // handle.record_db_read::( + // 24 + AccountLedgerFor::::max_encoded_len() + // + ProtocolState::max_encoded_len() + // + ::SmartContract::max_encoded_len(), + // )?; + + // let mut input = handle.read_input()?; + // input.expect_arguments(2)?; + + // // Parse smart contract & amount + // let contract_h160 = input.read::
()?.0; + // let smart_contract = Self::decode_smart_contract(contract_h160)?; + // let amount: Balance = input.read()?; + + // log::trace!(target: "ds-precompile", "bond_and_stake {:?}, {:?}", smart_contract, amount); + + // // Read total locked & staked amounts + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let protocol_state = ActiveProtocolState::::get(); + // let ledger = Ledger::::get(&origin); + + // // Check if stakeable amount is enough to cover the given `amount` + // let stakeable_amount = ledger.stakeable_amount(protocol_state.period_number()); + + // // If it isn't, we need to first lock the additional amount. + // if stakeable_amount < amount { + // let delta = amount.saturating_sub(stakeable_amount); + + // let lock_call = pallet_dapp_staking_v3::Call::::lock { amount: delta }; + // RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), lock_call)?; + // } + + // // Now, with best effort, we can try & stake the given `value`. + // let stake_call = pallet_dapp_staking_v3::Call::::stake { + // smart_contract, + // amount, + // }; + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Start unbonding process and unstake balance from the contract. + // fn unbond_and_unstake(handle: &mut impl PrecompileHandle) -> EvmResult { + // // TODO: benchmark this function so we can measure ref time & PoV correctly + // // Storage item: ActiveProtocolState: + // // Twox64(8) + ProtocolState::max_encoded_len + // // Storage item: StakerInfo: + // // Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len + // handle.record_db_read::( + // 24 + ProtocolState::max_encoded_len() + // + ::SmartContract::max_encoded_len() + // + SingularStakingInfo::max_encoded_len(), + // )?; + + // let mut input = handle.read_input()?; + // input.expect_arguments(2)?; + + // // Parse smart contract & amount + // let contract_h160 = input.read::
()?.0; + // let smart_contract = Self::decode_smart_contract(contract_h160)?; + // let amount: Balance = input.read()?; + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // log::trace!(target: "ds-precompile", "unbond_and_unstake {:?}, {:?}", smart_contract, amount); + + // // Find out if there is something staked on the contract + // let protocol_state = ActiveProtocolState::::get(); + // let staker_info = StakerInfo::::get(&origin, &smart_contract).unwrap_or_default(); + + // // If there is, we need to unstake it before calling `unlock` + // if staker_info.period_number() == protocol_state.period_number() { + // let unstake_call = pallet_dapp_staking_v3::Call::::unstake { + // smart_contract, + // amount, + // }; + // RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), unstake_call)?; + // } + + // // Now we can try and `unlock` the given `amount` + // let unlock_call = pallet_dapp_staking_v3::Call::::unlock { amount }; + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unlock_call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Claim back the unbonded (or unlocked) funds. + // fn withdraw_unbonded(handle: &mut impl PrecompileHandle) -> EvmResult { + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let call = pallet_dapp_staking_v3::Call::::claim_unlocked {}; + + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Claim dApp rewards for the given era + // fn claim_dapp(handle: &mut impl PrecompileHandle) -> EvmResult { + // let mut input = handle.read_input()?; + // input.expect_arguments(2)?; + + // // Parse the smart contract & era + // let contract_h160 = input.read::
()?.0; + // let smart_contract = Self::decode_smart_contract(contract_h160)?; + // let era: u32 = input.read::()?; + // log::trace!(target: "ds-precompile", "claim_dapp {:?}, era {:?}", smart_contract, era); + + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let call = pallet_dapp_staking_v3::Call::::claim_dapp_reward { + // smart_contract, + // era, + // }; + + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Claim staker rewards. + // /// + // /// Smart contract argument is legacy & is ignored in the new implementation. + // fn claim_staker(handle: &mut impl PrecompileHandle) -> EvmResult { + // let mut input = handle.read_input()?; + // input.expect_arguments(1)?; + + // // Parse smart contract to keep in line with the legacy behavior. + // let contract_h160 = input.read::
()?.0; + // let _smart_contract = Self::decode_smart_contract(contract_h160)?; + // log::trace!(target: "ds-precompile", "claim_staker {:?}", _smart_contract); + + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let call = pallet_dapp_staking_v3::Call::::claim_staker_rewards {}; + + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Set claim reward destination for the caller. + // /// + // /// This call has been deprecated by dApp staking v3. + // fn set_reward_destination(_handle: &mut impl PrecompileHandle) -> EvmResult { + // Err(error( + // "set_reward_destination via evm precompile is no longer supported", + // )) + // } + + // /// Withdraw staked funds from the unregistered contract + // fn withdraw_from_unregistered( + // handle: &mut impl PrecompileHandle, + // ) -> EvmResult { + // let mut input = handle.read_input()?; + // input.expect_arguments(1)?; + + // // Parse smart contract + // let contract_h160 = input.read::
()?.0; + // let smart_contract = Self::decode_smart_contract(contract_h160)?; + // log::trace!(target: "ds-precompile", "withdraw_from_unregistered {:?}", smart_contract); + + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let call = pallet_dapp_staking_v3::Call::::unstake_from_unregistered { smart_contract }; + + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Transfers stake from one contract to another. + // /// This is a legacy functionality that is no longer supported via direct call to dApp staking v3. + // /// However, it can be achieved by chaining `unstake` and `stake` calls. + // fn nomination_transfer(handle: &mut impl PrecompileHandle) -> EvmResult { + // // TODO: benchmark this function so we can measure ref time & PoV correctly + // // Storage item: StakerInfo: + // // Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len + // handle.record_db_read::( + // 16 + ::SmartContract::max_encoded_len() + // + SingularStakingInfo::max_encoded_len(), + // )?; + + // let mut input = handle.read_input()?; + // input.expect_arguments(3)?; + + // // Parse origin smart contract, transfer amount & the target smart contract + // let origin_contract_h160 = input.read::
()?.0; + // let origin_smart_contract = Self::decode_smart_contract(origin_contract_h160)?; + + // let amount = input.read::()?; + + // let target_contract_h160 = input.read::
()?.0; + // let target_smart_contract = Self::decode_smart_contract(target_contract_h160)?; + // log::trace!(target: "ds-precompile", "nomination_transfer {:?} {:?} {:?}", origin_smart_contract, amount, target_smart_contract); + + // // Find out how much staker has staked on the origin contract + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let staker_info = StakerInfo::::get(&origin, &origin_smart_contract).unwrap_or_default(); + + // // We don't care from which period the staked amount is, the logic takes care of the situation + // // if value comes from the past period. + // let staked_amount = staker_info.total_staked_amount(); + // let minimum_allowed_stake_amount = + // ::MinimumStakeAmount::get(); + + // // In case the remaining staked amount on the origin contract is less than the minimum allowed stake amount, + // // everything will be unstaked. To keep in line with legacy `nomination_transfer` behavior, we should transfer + // // the entire amount from the origin to target contract. + // // + // // In case value comes from the past period, we don't care, since the `unstake` call will fall apart. + // let stake_amount = if staked_amount > 0 + // && staked_amount.saturating_sub(amount) < minimum_allowed_stake_amount + // { + // staked_amount + // } else { + // amount + // }; + + // // First call unstake from the origin smart contract + // let unstake_call = pallet_dapp_staking_v3::Call::::unstake { + // smart_contract: origin_smart_contract, + // amount, + // }; + // RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), unstake_call)?; + + // // Then call stake on the target smart contract + // let stake_call = pallet_dapp_staking_v3::Call::::stake { + // smart_contract: target_smart_contract, + // amount: stake_amount, + // }; + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // // v2 functions + + // /// Read the current protocol state. + // /// + // /// Returns: `(current era, current period number, subperiod)` + // fn protocol_state(handle: &mut impl PrecompileHandle) -> EvmResult { + // // TODO: benchmark this function so we can measure ref time & PoV correctly + // // Storage item: ActiveProtocolState: + // // Twox64(8) + ProtocolState::max_encoded_len + // handle.record_db_read::(4 + ProtocolState::max_encoded_len())?; + + // let protocol_state = ActiveProtocolState::::get(); + + // Ok(succeed( + // EvmDataWriter::new() + // .write(U256::from(protocol_state.era)) + // .write(U256::from(protocol_state.period_number())) + // .write(Self::subperiod_id(&protocol_state.subperiod())) + // .build(), + // )) + // } + + // /// Read the `unbonding period` or `unlocking period` expressed in the number of eras. + // fn unlocking_period(_: &impl PrecompileHandle) -> EvmResult { + // // constant, no DB read + // Ok(succeed( + // EvmDataWriter::new() + // .write(DAppStaking::::unlocking_period()) + // .build(), + // )) + // } + + // /// Attempt to lock the given amount into the dApp staking protocol. + // fn lock(handle: &mut impl PrecompileHandle) -> EvmResult { + // // Parse the amount + // let mut input = handle.read_input()?; + // input.expect_arguments(1)?; + // let amount: Balance = input.read()?; + + // // Prepare call & dispatch it + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let lock_call = pallet_dapp_staking_v3::Call::::lock { amount }; + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), lock_call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Attempt to unlock the given amount from the dApp staking protocol. + // fn unlock(handle: &mut impl PrecompileHandle) -> EvmResult { + // // Parse the amount + // let mut input = handle.read_input()?; + // input.expect_arguments(1)?; + // let amount: Balance = input.read()?; + + // // Prepare call & dispatch it + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let unlock_call = pallet_dapp_staking_v3::Call::::unlock { amount }; + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unlock_call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Attempts to claim unlocking chunks which have undergone the entire unlocking period. + // fn claim_unlocked(handle: &mut impl PrecompileHandle) -> EvmResult { + // // Prepare call & dispatch it + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let claim_unlocked_call = pallet_dapp_staking_v3::Call::::claim_unlocked {}; + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_unlocked_call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Attempts to stake the given amount on the given smart contract. + // fn stake(handle: &mut impl PrecompileHandle) -> EvmResult { + // // Parse smart contract & amount + // let mut input = handle.read_input()?; + // input.expect_arguments(2)?; + + // let contract_h160 = input.read::
()?.0; + // let smart_contract = Self::decode_smart_contract(contract_h160)?; + // let amount: Balance = input.read()?; + + // // Prepare call & dispatch it + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let stake_call = pallet_dapp_staking_v3::Call::::stake { + // smart_contract, + // amount, + // }; + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Attempts to unstake the given amount from the given smart contract. + // fn unstake(handle: &mut impl PrecompileHandle) -> EvmResult { + // // Parse smart contract & amount + // let mut input = handle.read_input()?; + // input.expect_arguments(2)?; + + // let contract_h160 = input.read::
()?.0; + // let smart_contract = Self::decode_smart_contract(contract_h160)?; + // let amount: Balance = input.read()?; + + // // Prepare call & dispatch it + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let unstake_call = pallet_dapp_staking_v3::Call::::unstake { + // smart_contract, + // amount, + // }; + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unstake_call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Attempts to claim one or more pending staker rewards. + // fn claim_staker_rewards(handle: &mut impl PrecompileHandle) -> EvmResult { + // // Prepare call & dispatch it + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let claim_staker_rewards_call = pallet_dapp_staking_v3::Call::::claim_staker_rewards {}; + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_staker_rewards_call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Attempts to claim bonus reward for being a loyal staker of the given dApp. + // fn claim_bonus_reward(handle: &mut impl PrecompileHandle) -> EvmResult { + // // Parse smart contract + // let mut input = handle.read_input()?; + // input.expect_arguments(1)?; + + // let contract_h160 = input.read::
()?.0; + // let smart_contract = Self::decode_smart_contract(contract_h160)?; + + // // Prepare call & dispatch it + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let claim_bonus_reward_call = + // pallet_dapp_staking_v3::Call::::claim_bonus_reward { smart_contract }; + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_bonus_reward_call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Attempts to claim dApp reward for the given dApp in the given era. + // fn claim_dapp_reward(handle: &mut impl PrecompileHandle) -> EvmResult { + // // Parse smart contract & era + // let mut input = handle.read_input()?; + // input.expect_arguments(2)?; + + // let contract_h160 = input.read::
()?.0; + // let smart_contract = Self::decode_smart_contract(contract_h160)?; + // let era = input.read::()?; + + // // Prepare call & dispatch it + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let claim_dapp_reward_call = pallet_dapp_staking_v3::Call::::claim_dapp_reward { + // smart_contract, + // era, + // }; + // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_dapp_reward_call)?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Attempts to unstake everything from an unregistered contract. + // fn unstake_from_unregistered( + // handle: &mut impl PrecompileHandle, + // ) -> EvmResult { + // // Parse smart contract + // let mut input = handle.read_input()?; + // input.expect_arguments(1)?; + + // let contract_h160 = input.read::
()?.0; + // let smart_contract = Self::decode_smart_contract(contract_h160)?; + + // // Prepare call & dispatch it + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let unstake_from_unregistered_call = + // pallet_dapp_staking_v3::Call::::unstake_from_unregistered { smart_contract }; + // RuntimeHelper::::try_dispatch( + // handle, + // Some(origin).into(), + // unstake_from_unregistered_call, + // )?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } + + // /// Attempts to cleanup expired entries for the staker. + // fn cleanup_expired_entries(handle: &mut impl PrecompileHandle) -> EvmResult { + // // Prepare call & dispatch it + // let origin = R::AddressMapping::into_account_id(handle.context().caller); + // let cleanup_expired_entries_call = + // pallet_dapp_staking_v3::Call::::cleanup_expired_entries {}; + // RuntimeHelper::::try_dispatch( + // handle, + // Some(origin).into(), + // cleanup_expired_entries_call, + // )?; + + // Ok(succeed(EvmDataWriter::new().write(true).build())) + // } // Utility functions @@ -811,119 +784,3 @@ where } } } - -#[precompile_utils::generate_function_selector] -#[derive(Debug, PartialEq)] -pub enum Action { - // v1 functions - - // View - ReadCurrentEra = "read_current_era()", - ReadUnbondingPeriod = "read_unbonding_period()", - ReadEraReward = "read_era_reward(uint32)", - ReadEraStaked = "read_era_staked(uint32)", - ReadStakedAmount = "read_staked_amount(bytes)", - ReadStakedAmountOnContract = "read_staked_amount_on_contract(address,bytes)", - ReadContractStake = "read_contract_stake(address)", - - // Dispathables - Register = "register(address)", - BondAndStake = "bond_and_stake(address,uint128)", - UnbondAndUnstake = "unbond_and_unstake(address,uint128)", - WithdrawUnbounded = "withdraw_unbonded()", - ClaimDapp = "claim_dapp(address,uint128)", - ClaimStaker = "claim_staker(address)", - SetRewardDestination = "set_reward_destination(uint8)", - WithdrawFromUnregistered = "withdraw_from_unregistered(address)", - NominationTransfer = "nomination_transfer(address,uint128,address)", - - // v2 functions - - // View - ProtocolState = "protocol_state()", - UnlockingPeriod = "unlocking_period()", - - // Dispatchables - Lock = "lock(uint128)", - Unlock = "unlock(uint128)", - ClaimUnlocked = "claim_unlocked()", - Stake = "stake(address,uint128)", - Unstake = "unstake(address,uint128)", - ClaimStakerRewards = "claim_staker_rewards()", - ClaimBonusReward = "claim_bonus_reward(address)", - ClaimDappReward = "claim_dapp_reward(address,uint32)", - UnstakeFromUnregistered = "unstake_from_unregistered(address)", - CleanupExpiredEntries = "cleanup_expired_entries()", -} - -impl Precompile for DappStakingV3Precompile -where - R: pallet_evm::Config + pallet_dapp_staking_v3::Config, - R::RuntimeCall: From> - + Dispatchable - + GetDispatchInfo, - ::RuntimeOrigin: From>, - Balance: EvmData, - R::AccountId: From<[u8; 32]>, -{ - fn execute(handle: &mut impl PrecompileHandle) -> EvmResult { - log::trace!(target: "ds-precompile", "Execute input = {:?}", handle.input()); - - let selector = handle.read_selector()?; - - handle.check_function_modifier(match selector { - Action::ReadCurrentEra - | Action::ReadUnbondingPeriod - | Action::ReadEraReward - | Action::ReadEraStaked - | Action::ReadStakedAmount - | Action::ReadStakedAmountOnContract - | Action::ReadContractStake - | Action::ProtocolState - | Action::UnlockingPeriod => FunctionModifier::View, - _ => FunctionModifier::NonPayable, - })?; - - match selector { - // v1 functions - - // View - Action::ReadCurrentEra => Self::read_current_era(handle), - Action::ReadUnbondingPeriod => Self::read_unbonding_period(handle), - Action::ReadEraReward => Self::read_era_reward(handle), - Action::ReadEraStaked => Self::read_era_staked(handle), - Action::ReadStakedAmount => Self::read_staked_amount(handle), - Action::ReadStakedAmountOnContract => Self::read_staked_amount_on_contract(handle), - Action::ReadContractStake => Self::read_contract_stake(handle), - - // Dispatchables - Action::Register => Self::register(handle), - Action::BondAndStake => Self::bond_and_stake(handle), - Action::UnbondAndUnstake => Self::unbond_and_unstake(handle), - Action::WithdrawUnbounded => Self::withdraw_unbonded(handle), - Action::ClaimDapp => Self::claim_dapp(handle), - Action::ClaimStaker => Self::claim_staker(handle), - Action::SetRewardDestination => Self::set_reward_destination(handle), - Action::WithdrawFromUnregistered => Self::withdraw_from_unregistered(handle), - Action::NominationTransfer => Self::nomination_transfer(handle), - - // v2 functions - - // View - Action::ProtocolState => Self::protocol_state(handle), - Action::UnlockingPeriod => Self::unlocking_period(handle), - - // Dispatchables - Action::Lock => Self::lock(handle), - Action::Unlock => Self::unlock(handle), - Action::ClaimUnlocked => Self::claim_unlocked(handle), - Action::Stake => Self::stake(handle), - Action::Unstake => Self::unstake(handle), - Action::ClaimStakerRewards => Self::claim_staker_rewards(handle), - Action::ClaimBonusReward => Self::claim_bonus_reward(handle), - Action::ClaimDappReward => Self::claim_dapp_reward(handle), - Action::UnstakeFromUnregistered => Self::unstake_from_unregistered(handle), - Action::CleanupExpiredEntries => Self::cleanup_expired_entries(handle), - } - } -} From 5df87ca5152dcc235860b9eaea1d96d9639fb2be Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Thu, 14 Dec 2023 11:43:49 +0100 Subject: [PATCH 11/34] More adjustments --- precompiles/dapps-staking-v3/src/lib.rs | 505 ++++++++++++------------ 1 file changed, 247 insertions(+), 258 deletions(-) diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index b5d4bcfc0c..ac1caf32d2 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -278,253 +278,251 @@ where Ok(contract_stake.total_staked_amount(current_period_number)) } - // /// Register contract with the dapp-staking pallet - // /// Register is root origin only. This should always fail when called via evm precompile. - // fn register(_: &mut impl PrecompileHandle) -> EvmResult { - // // register is root-origin call. it should always fail when called via evm precompiles. - // Err(error("register via evm precompile is not allowed")) - // } - - // /// Lock & stake some amount on the specified contract. - // /// - // /// In case existing `stakeable` is sufficient to cover the given `amount`, only the `stake` operation is performed. - // /// Otherwise, best effort is done to lock the additional amount so `stakeable` amount can cover the given `amount`. - // fn bond_and_stake(handle: &mut impl PrecompileHandle) -> EvmResult { - // // TODO: benchmark this function so we can measure ref time & PoV correctly - // // Storage item: ActiveProtocolState: - // // Twox64(8) + ProtocolState::max_encoded_len - // // Storage item: Ledger: - // // Blake2_128Concat(16 + SmartContract::max_encoded_len()) + Ledger::max_encoded_len - // handle.record_db_read::( - // 24 + AccountLedgerFor::::max_encoded_len() - // + ProtocolState::max_encoded_len() - // + ::SmartContract::max_encoded_len(), - // )?; - - // let mut input = handle.read_input()?; - // input.expect_arguments(2)?; - - // // Parse smart contract & amount - // let contract_h160 = input.read::
()?.0; - // let smart_contract = Self::decode_smart_contract(contract_h160)?; - // let amount: Balance = input.read()?; - - // log::trace!(target: "ds-precompile", "bond_and_stake {:?}, {:?}", smart_contract, amount); - - // // Read total locked & staked amounts - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let protocol_state = ActiveProtocolState::::get(); - // let ledger = Ledger::::get(&origin); - - // // Check if stakeable amount is enough to cover the given `amount` - // let stakeable_amount = ledger.stakeable_amount(protocol_state.period_number()); - - // // If it isn't, we need to first lock the additional amount. - // if stakeable_amount < amount { - // let delta = amount.saturating_sub(stakeable_amount); - - // let lock_call = pallet_dapp_staking_v3::Call::::lock { amount: delta }; - // RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), lock_call)?; - // } - - // // Now, with best effort, we can try & stake the given `value`. - // let stake_call = pallet_dapp_staking_v3::Call::::stake { - // smart_contract, - // amount, - // }; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; - - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } - - // /// Start unbonding process and unstake balance from the contract. - // fn unbond_and_unstake(handle: &mut impl PrecompileHandle) -> EvmResult { - // // TODO: benchmark this function so we can measure ref time & PoV correctly - // // Storage item: ActiveProtocolState: - // // Twox64(8) + ProtocolState::max_encoded_len - // // Storage item: StakerInfo: - // // Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len - // handle.record_db_read::( - // 24 + ProtocolState::max_encoded_len() - // + ::SmartContract::max_encoded_len() - // + SingularStakingInfo::max_encoded_len(), - // )?; + /// Register contract with the dapp-staking pallet + /// Register is root origin only. This should always fail when called via evm precompile. + #[precompile::public("register(address)")] + fn register(_: &mut impl PrecompileHandle, _address: Address) -> EvmResult { + // register is root-origin call. it should always fail when called via evm precompiles. + Err(RevertReason::custom("register via evm precompile is not allowed").into()) + } - // let mut input = handle.read_input()?; - // input.expect_arguments(2)?; + /// Lock & stake some amount on the specified contract. + /// + /// In case existing `stakeable` is sufficient to cover the given `amount`, only the `stake` operation is performed. + /// Otherwise, best effort is done to lock the additional amount so `stakeable` amount can cover the given `amount`. + #[precompile::public("bond_and_stake(address,uint128)")] + fn bond_and_stake( + handle: &mut impl PrecompileHandle, + contract_h160: Address, + amount: u128, + ) -> EvmResult { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: ActiveProtocolState: + // Twox64(8) + ProtocolState::max_encoded_len + // Storage item: Ledger: + // Blake2_128Concat(16 + SmartContract::max_encoded_len()) + Ledger::max_encoded_len + handle.record_db_read::( + 24 + AccountLedgerFor::::max_encoded_len() + + ProtocolState::max_encoded_len() + + ::SmartContract::max_encoded_len(), + )?; - // // Parse smart contract & amount - // let contract_h160 = input.read::
()?.0; - // let smart_contract = Self::decode_smart_contract(contract_h160)?; - // let amount: Balance = input.read()?; - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // log::trace!(target: "ds-precompile", "unbond_and_unstake {:?}, {:?}", smart_contract, amount); + let smart_contract = Self::decode_smart_contract(contract_h160.into())?; + log::trace!(target: "ds-precompile", "bond_and_stake {:?}, {:?}", smart_contract, amount); - // // Find out if there is something staked on the contract - // let protocol_state = ActiveProtocolState::::get(); - // let staker_info = StakerInfo::::get(&origin, &smart_contract).unwrap_or_default(); + // Read total locked & staked amounts + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let protocol_state = ActiveProtocolState::::get(); + let ledger = Ledger::::get(&origin); - // // If there is, we need to unstake it before calling `unlock` - // if staker_info.period_number() == protocol_state.period_number() { - // let unstake_call = pallet_dapp_staking_v3::Call::::unstake { - // smart_contract, - // amount, - // }; - // RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), unstake_call)?; - // } + // Check if stakeable amount is enough to cover the given `amount` + let stakeable_amount = ledger.stakeable_amount(protocol_state.period_number()); - // // Now we can try and `unlock` the given `amount` - // let unlock_call = pallet_dapp_staking_v3::Call::::unlock { amount }; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unlock_call)?; + // If it isn't, we need to first lock the additional amount. + if stakeable_amount < amount { + let delta = amount.saturating_sub(stakeable_amount); - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + let lock_call = pallet_dapp_staking_v3::Call::::lock { amount: delta }; + RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), lock_call)?; + } - // /// Claim back the unbonded (or unlocked) funds. - // fn withdraw_unbonded(handle: &mut impl PrecompileHandle) -> EvmResult { - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let call = pallet_dapp_staking_v3::Call::::claim_unlocked {}; + // Now, with best effort, we can try & stake the given `value`. + let stake_call = pallet_dapp_staking_v3::Call::::stake { + smart_contract, + amount, + }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + Ok(true) + } - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + /// Start unbonding process and unstake balance from the contract. + #[precompile::public("unbond_and_unstake(address,uint128)")] + fn unbond_and_unstake( + handle: &mut impl PrecompileHandle, + contract_h160: Address, + amount: u128, + ) -> EvmResult { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: ActiveProtocolState: + // Twox64(8) + ProtocolState::max_encoded_len + // Storage item: StakerInfo: + // Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len + handle.record_db_read::( + 24 + ProtocolState::max_encoded_len() + + ::SmartContract::max_encoded_len() + + SingularStakingInfo::max_encoded_len(), + )?; - // /// Claim dApp rewards for the given era - // fn claim_dapp(handle: &mut impl PrecompileHandle) -> EvmResult { - // let mut input = handle.read_input()?; - // input.expect_arguments(2)?; + let smart_contract = Self::decode_smart_contract(contract_h160.into())?; + let origin = R::AddressMapping::into_account_id(handle.context().caller); + log::trace!(target: "ds-precompile", "unbond_and_unstake {:?}, {:?}", smart_contract, amount); + + // Find out if there is something staked on the contract + let protocol_state = ActiveProtocolState::::get(); + let staker_info = StakerInfo::::get(&origin, &smart_contract).unwrap_or_default(); + + // If there is, we need to unstake it before calling `unlock` + if staker_info.period_number() == protocol_state.period_number() { + let unstake_call = pallet_dapp_staking_v3::Call::::unstake { + smart_contract, + amount, + }; + RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), unstake_call)?; + } - // // Parse the smart contract & era - // let contract_h160 = input.read::
()?.0; - // let smart_contract = Self::decode_smart_contract(contract_h160)?; - // let era: u32 = input.read::()?; - // log::trace!(target: "ds-precompile", "claim_dapp {:?}, era {:?}", smart_contract, era); + // Now we can try and `unlock` the given `amount` + let unlock_call = pallet_dapp_staking_v3::Call::::unlock { amount }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unlock_call)?; - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let call = pallet_dapp_staking_v3::Call::::claim_dapp_reward { - // smart_contract, - // era, - // }; + Ok(true) + } - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + /// Claim back the unbonded (or unlocked) funds. + #[precompile::public("withdraw_unbonded()")] + fn withdraw_unbonded(handle: &mut impl PrecompileHandle) -> EvmResult { + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let call = pallet_dapp_staking_v3::Call::::claim_unlocked {}; - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - // /// Claim staker rewards. - // /// - // /// Smart contract argument is legacy & is ignored in the new implementation. - // fn claim_staker(handle: &mut impl PrecompileHandle) -> EvmResult { - // let mut input = handle.read_input()?; - // input.expect_arguments(1)?; - - // // Parse smart contract to keep in line with the legacy behavior. - // let contract_h160 = input.read::
()?.0; - // let _smart_contract = Self::decode_smart_contract(contract_h160)?; - // log::trace!(target: "ds-precompile", "claim_staker {:?}", _smart_contract); + Ok(true) + } - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let call = pallet_dapp_staking_v3::Call::::claim_staker_rewards {}; + /// Claim dApp rewards for the given era + #[precompile::public("claim_dapp(address,uint128)")] + fn claim_dapp( + handle: &mut impl PrecompileHandle, + contract_h160: Address, + era: u128, + ) -> EvmResult { + let smart_contract = Self::decode_smart_contract(contract_h160.into())?; + + // parse era + let era = era + .try_into() + .map_err::(|_| RevertReason::value_is_too_large("era type").into()) + .in_field("era")?; + + log::trace!(target: "ds-precompile", "claim_dapp {:?}, era {:?}", smart_contract, era); + + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let call = pallet_dapp_staking_v3::Call::::claim_dapp_reward { + smart_contract, + era, + }; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + Ok(true) + } - // /// Set claim reward destination for the caller. - // /// - // /// This call has been deprecated by dApp staking v3. - // fn set_reward_destination(_handle: &mut impl PrecompileHandle) -> EvmResult { - // Err(error( - // "set_reward_destination via evm precompile is no longer supported", - // )) - // } + /// Claim staker rewards. + /// + /// Smart contract argument is legacy & is ignored in the new implementation. + #[precompile::public("claim_staker(address)")] + fn claim_staker(handle: &mut impl PrecompileHandle, contract_h160: Address) -> EvmResult { + // Parse smart contract to keep in line with the legacy behavior. + let _smart_contract = Self::decode_smart_contract(contract_h160.into())?; + log::trace!(target: "ds-precompile", "claim_staker {:?}", _smart_contract); - // /// Withdraw staked funds from the unregistered contract - // fn withdraw_from_unregistered( - // handle: &mut impl PrecompileHandle, - // ) -> EvmResult { - // let mut input = handle.read_input()?; - // input.expect_arguments(1)?; + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let call = pallet_dapp_staking_v3::Call::::claim_staker_rewards {}; - // // Parse smart contract - // let contract_h160 = input.read::
()?.0; - // let smart_contract = Self::decode_smart_contract(contract_h160)?; - // log::trace!(target: "ds-precompile", "withdraw_from_unregistered {:?}", smart_contract); + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let call = pallet_dapp_staking_v3::Call::::unstake_from_unregistered { smart_contract }; - - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; + Ok(true) + } - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + /// Set claim reward destination for the caller. + /// + /// This call has been deprecated by dApp staking v3. + #[precompile::public("set_reward_destination(uint8)")] + fn set_reward_destination( + handle: &mut impl PrecompileHandle, + reward_destination_raw: u8, + ) -> EvmResult { + Err(RevertReason::custom("Unexpected reward destination value.").into()) + } - // /// Transfers stake from one contract to another. - // /// This is a legacy functionality that is no longer supported via direct call to dApp staking v3. - // /// However, it can be achieved by chaining `unstake` and `stake` calls. - // fn nomination_transfer(handle: &mut impl PrecompileHandle) -> EvmResult { - // // TODO: benchmark this function so we can measure ref time & PoV correctly - // // Storage item: StakerInfo: - // // Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len - // handle.record_db_read::( - // 16 + ::SmartContract::max_encoded_len() - // + SingularStakingInfo::max_encoded_len(), - // )?; + /// Withdraw staked funds from the unregistered contract + #[precompile::public("withdraw_from_unregistered(address)")] + fn withdraw_from_unregistered( + handle: &mut impl PrecompileHandle, + contract_h160: Address, + ) -> EvmResult { + let smart_contract = Self::decode_smart_contract(contract_h160.into())?; + log::trace!(target: "ds-precompile", "withdraw_from_unregistered {:?}", smart_contract); - // let mut input = handle.read_input()?; - // input.expect_arguments(3)?; + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let call = pallet_dapp_staking_v3::Call::::unstake_from_unregistered { smart_contract }; - // // Parse origin smart contract, transfer amount & the target smart contract - // let origin_contract_h160 = input.read::
()?.0; - // let origin_smart_contract = Self::decode_smart_contract(origin_contract_h160)?; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), call)?; - // let amount = input.read::()?; + Ok(true) + } - // let target_contract_h160 = input.read::
()?.0; - // let target_smart_contract = Self::decode_smart_contract(target_contract_h160)?; - // log::trace!(target: "ds-precompile", "nomination_transfer {:?} {:?} {:?}", origin_smart_contract, amount, target_smart_contract); + /// Transfers stake from one contract to another. + /// This is a legacy functionality that is no longer supported via direct call to dApp staking v3. + /// However, it can be achieved by chaining `unstake` and `stake` calls. + #[precompile::public("nomination_transfer(address,uint128,address)")] + fn nomination_transfer( + handle: &mut impl PrecompileHandle, + origin_contract_h160: Address, + amount: u128, + target_contract_h160: Address, + ) -> EvmResult { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: StakerInfo: + // Blake2_128Concat(16 + SmartContract::max_encoded_len) + SingularStakingInfo::max_encoded_len + handle.record_db_read::( + 16 + ::SmartContract::max_encoded_len() + + SingularStakingInfo::max_encoded_len(), + )?; - // // Find out how much staker has staked on the origin contract - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let staker_info = StakerInfo::::get(&origin, &origin_smart_contract).unwrap_or_default(); - - // // We don't care from which period the staked amount is, the logic takes care of the situation - // // if value comes from the past period. - // let staked_amount = staker_info.total_staked_amount(); - // let minimum_allowed_stake_amount = - // ::MinimumStakeAmount::get(); - - // // In case the remaining staked amount on the origin contract is less than the minimum allowed stake amount, - // // everything will be unstaked. To keep in line with legacy `nomination_transfer` behavior, we should transfer - // // the entire amount from the origin to target contract. - // // - // // In case value comes from the past period, we don't care, since the `unstake` call will fall apart. - // let stake_amount = if staked_amount > 0 - // && staked_amount.saturating_sub(amount) < minimum_allowed_stake_amount - // { - // staked_amount - // } else { - // amount - // }; + let origin_smart_contract = Self::decode_smart_contract(origin_contract_h160.into())?; + let target_smart_contract = Self::decode_smart_contract(target_contract_h160.into())?; + log::trace!(target: "ds-precompile", "nomination_transfer {:?} {:?} {:?}", origin_smart_contract, amount, target_smart_contract); + + // Find out how much staker has staked on the origin contract + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let staker_info = StakerInfo::::get(&origin, &origin_smart_contract).unwrap_or_default(); + + // We don't care from which period the staked amount is, the logic takes care of the situation + // if value comes from the past period. + let staked_amount = staker_info.total_staked_amount(); + let minimum_allowed_stake_amount = + ::MinimumStakeAmount::get(); + + // In case the remaining staked amount on the origin contract is less than the minimum allowed stake amount, + // everything will be unstaked. To keep in line with legacy `nomination_transfer` behavior, we should transfer + // the entire amount from the origin to target contract. + // + // In case value comes from the past period, we don't care, since the `unstake` call will fall apart. + let stake_amount = if staked_amount > 0 + && staked_amount.saturating_sub(amount) < minimum_allowed_stake_amount + { + staked_amount + } else { + amount + }; - // // First call unstake from the origin smart contract - // let unstake_call = pallet_dapp_staking_v3::Call::::unstake { - // smart_contract: origin_smart_contract, - // amount, - // }; - // RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), unstake_call)?; + // First call unstake from the origin smart contract + let unstake_call = pallet_dapp_staking_v3::Call::::unstake { + smart_contract: origin_smart_contract, + amount, + }; + RuntimeHelper::::try_dispatch(handle, Some(origin.clone()).into(), unstake_call)?; - // // Then call stake on the target smart contract - // let stake_call = pallet_dapp_staking_v3::Call::::stake { - // smart_contract: target_smart_contract, - // amount: stake_amount, - // }; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; + // Then call stake on the target smart contract + let stake_call = pallet_dapp_staking_v3::Call::::stake { + smart_contract: target_smart_contract, + amount: stake_amount, + }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + Ok(true) + } // // v2 functions @@ -548,55 +546,46 @@ where // )) // } - // /// Read the `unbonding period` or `unlocking period` expressed in the number of eras. - // fn unlocking_period(_: &impl PrecompileHandle) -> EvmResult { - // // constant, no DB read - // Ok(succeed( - // EvmDataWriter::new() - // .write(DAppStaking::::unlocking_period()) - // .build(), - // )) - // } - - // /// Attempt to lock the given amount into the dApp staking protocol. - // fn lock(handle: &mut impl PrecompileHandle) -> EvmResult { - // // Parse the amount - // let mut input = handle.read_input()?; - // input.expect_arguments(1)?; - // let amount: Balance = input.read()?; - - // // Prepare call & dispatch it - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let lock_call = pallet_dapp_staking_v3::Call::::lock { amount }; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), lock_call)?; + /// Read the `unbonding period` or `unlocking period` expressed in the number of eras. + #[precompile::public("unlocking_period()")] + #[precompile::view] + fn unlocking_period(_: &mut impl PrecompileHandle) -> EvmResult { + // constant, no DB read + Ok(DAppStaking::::unlocking_period().into()) + } - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + /// Attempt to lock the given amount into the dApp staking protocol. + #[precompile::public("lock(uint128)")] + fn lock(handle: &mut impl PrecompileHandle, amount: u128) -> EvmResult { + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let lock_call = pallet_dapp_staking_v3::Call::::lock { amount }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), lock_call)?; - // /// Attempt to unlock the given amount from the dApp staking protocol. - // fn unlock(handle: &mut impl PrecompileHandle) -> EvmResult { - // // Parse the amount - // let mut input = handle.read_input()?; - // input.expect_arguments(1)?; - // let amount: Balance = input.read()?; + Ok(true) + } - // // Prepare call & dispatch it - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let unlock_call = pallet_dapp_staking_v3::Call::::unlock { amount }; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unlock_call)?; + /// Attempt to unlock the given amount from the dApp staking protocol. + #[precompile::public("unlock(uint128)")] + fn unlock(handle: &mut impl PrecompileHandle, amount: u128) -> EvmResult { + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let unlock_call = pallet_dapp_staking_v3::Call::::unlock { amount }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unlock_call)?; - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + Ok(true) + } - // /// Attempts to claim unlocking chunks which have undergone the entire unlocking period. - // fn claim_unlocked(handle: &mut impl PrecompileHandle) -> EvmResult { - // // Prepare call & dispatch it - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let claim_unlocked_call = pallet_dapp_staking_v3::Call::::claim_unlocked {}; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_unlocked_call)?; + /// Attempts to claim unlocking chunks which have undergone the entire unlocking period. + #[precompile::public("claim_unlocked()")] + fn claim_unlocked(handle: &mut impl PrecompileHandle) -> EvmResult { + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let claim_unlocked_call = pallet_dapp_staking_v3::Call::::claim_unlocked {}; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_unlocked_call)?; - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + Ok(true) + } // /// Attempts to stake the given amount on the given smart contract. // fn stake(handle: &mut impl PrecompileHandle) -> EvmResult { From 791243f849522b8bddd1b2bd78b483b3c8fab63b Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Thu, 14 Dec 2023 17:07:21 +0100 Subject: [PATCH 12/34] Finish adapting implementation to v2 --- .../dapps-staking-v3/DappsStakingV2.sol | 38 +- precompiles/dapps-staking-v3/src/lib.rs | 397 ++++++++++-------- precompiles/dapps-staking-v3/src/tests.rs | 12 +- 3 files changed, 259 insertions(+), 188 deletions(-) diff --git a/precompiles/dapps-staking-v3/DappsStakingV2.sol b/precompiles/dapps-staking-v3/DappsStakingV2.sol index fc23e5b90b..16bab828d7 100644 --- a/precompiles/dapps-staking-v3/DappsStakingV2.sol +++ b/precompiles/dapps-staking-v3/DappsStakingV2.sol @@ -8,18 +8,36 @@ pragma solidity >=0.8.0; /// code: frame/dapps-staking-v3 interface DAppStaking { - // TODO: make a custom struct to represent protocol state? - - // TODO: Create a wrapper for smart contract enum, so we can support more than just plain EVM contracts. - // Storage getters /// Describes the subperiod in which the protocol currently is. enum Subperiod {Voting, BuildAndEarn} + /// Describes current smart contract types supported by the network. + enum SmartContractType {EVM, WASM} + + /// @notice Describes protocol state. + /// @param era: Ongoing era number. + /// @param period: Ongoing period number. + /// @param subperiod: Ongoing subperiod type. + struct ProtocolState { + uint256 era; + uint256 period; + Subperiod subperiod; + } + + /// @notice Used to describe smart contract. Astar supports both EVM & WASM smart contracts + /// so it's important to differentiate between the two. This approach also allows + /// easy extensibility in the future. + /// @param contract_type: Type of the smart contract to be used + struct SmartContract { + SmartContractType contract_type; + bytes contract_address; + } + /// @notice Get the current protocol state. /// @return (current era, current period number, current subperiod type). - function protocol_state() external view returns (uint256, uint256, Subperiod); + function protocol_state() external view returns (ProtocolState memory); /// @notice Get the unlocking period expressed in the number of blocks. /// @return period: The unlocking period expressed in the number of blocks. @@ -38,22 +56,22 @@ interface DAppStaking { function claim_unlocked() external returns (bool); /// @notice Stake the given amount of tokens on the specified smart contract. - function stake(address, uint128) external returns (bool); + function stake(SmartContract calldata, uint128) external returns (bool); /// @notice Unstake the given amount of tokens from the specified smart contract. - function unstake(address, uint128) external returns (bool); + function unstake(SmartContract calldata, uint128) external returns (bool); /// @notice Claims one or more pending staker rewards. function claim_staker_rewards() external returns (bool); /// @notice Claim the bonus reward for the specified smart contract. - function claim_bonus_reward(address) external returns (bool); + function claim_bonus_reward(SmartContract calldata) external returns (bool); /// @notice Claim dApp reward for the specified smart contract & era. - function claim_dapp_reward(address, uint128) external returns (bool); + function claim_dapp_reward(SmartContract calldata, uint256) external returns (bool); /// @notice Unstake all funds from the unregistered smart contract. - function unstake_from_unregistered(address) external returns (bool); + function unstake_from_unregistered(SmartContract calldata) external returns (bool); /// @notice Used to cleanup all expired contract stake entries from the caller. function cleanup_expired_entries() external returns (bool); diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index ac1caf32d2..e5a41f2a91 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -25,20 +25,21 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, + ensure, traits::ConstU32, }; use pallet_evm::AddressMapping; use precompile_utils::prelude::*; use sp_core::{Get, H160, U256}; -use sp_runtime::traits::Zero; +use sp_runtime::traits::{TrailingZeroInput, Zero}; use sp_std::{marker::PhantomData, prelude::*}; extern crate alloc; -use astar_primitives::Balance; +use astar_primitives::{AccountId, Balance}; use pallet_dapp_staking_v3::{ AccountLedgerFor, ActiveProtocolState, ContractStake, ContractStakeAmount, CurrentEraInfo, - DAppInfoFor, EraInfo, EraNumber, EraRewardSpanFor, EraRewards, IntegratedDApps, Ledger, + DAppInfoFor, EraInfo, EraRewardSpanFor, EraRewards, IntegratedDApps, Ledger, Pallet as DAppStaking, ProtocolState, SingularStakingInfo, StakerInfo, Subperiod, }; @@ -51,21 +52,39 @@ mod mock; // #[cfg(test)] // mod tests; +// TODO: move smart contract enum under primitives, so it can be reused in other pallets. +// Or at least introduce some trait on it so it can be more easily manipulated between runtimes/precompiles. + /// This is only used to encode SmartContract enum -#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, Debug)] -pub enum Contract { +#[derive(PartialEq, Eq, Clone, Encode, Decode, Debug)] +pub enum Contract { /// EVM smart contract instance. Evm(H160), /// Wasm smart contract instance. Not used in this precompile - Wasm(A), + Wasm(AccountId), } -pub struct DappStakingV3Precompile(PhantomData); +/// Helper struct used to encode protocol state. +#[derive(Debug, Clone, solidity::Codec)] +pub struct PrecompileProtocolState { + era: U256, + period: U256, + subperiod: u8, +} +#[derive(Debug, Clone, solidity::Codec)] +pub struct SmartContractV2 { + contract_type: u8, + address: BoundedBytes, +} + +pub struct DappStakingV3Precompile(PhantomData); #[precompile_utils::precompile] impl DappStakingV3Precompile where - R: pallet_evm::Config + pallet_dapp_staking_v3::Config, + R: pallet_evm::Config + + pallet_dapp_staking_v3::Config + + frame_system::Config, ::RuntimeOrigin: From>, R::RuntimeCall: Dispatchable + GetDispatchInfo, R::RuntimeCall: From>, @@ -220,7 +239,7 @@ where )?; // parse contract address - let contract_id = Self::decode_smart_contract(contract_h160.into())?; + let contract_id = Self::decode_smart_contract_v1(contract_h160.into())?; // parse the staker account let staker = Self::parse_input_address(staker.into())?; @@ -261,7 +280,7 @@ where )?; // parse input parameters for pallet-dapps-staking call - let contract_id = Self::decode_smart_contract(contract_h160.into())?; + let contract_id = Self::decode_smart_contract_v1(contract_h160.into())?; let current_period_number = ActiveProtocolState::::get().period_number(); let dapp_info = match IntegratedDApps::::get(&contract_id) { @@ -307,7 +326,7 @@ where + ::SmartContract::max_encoded_len(), )?; - let smart_contract = Self::decode_smart_contract(contract_h160.into())?; + let smart_contract = Self::decode_smart_contract_v1(contract_h160.into())?; log::trace!(target: "ds-precompile", "bond_and_stake {:?}, {:?}", smart_contract, amount); // Read total locked & staked amounts @@ -354,7 +373,7 @@ where + SingularStakingInfo::max_encoded_len(), )?; - let smart_contract = Self::decode_smart_contract(contract_h160.into())?; + let smart_contract = Self::decode_smart_contract_v1(contract_h160.into())?; let origin = R::AddressMapping::into_account_id(handle.context().caller); log::trace!(target: "ds-precompile", "unbond_and_unstake {:?}, {:?}", smart_contract, amount); @@ -396,7 +415,7 @@ where contract_h160: Address, era: u128, ) -> EvmResult { - let smart_contract = Self::decode_smart_contract(contract_h160.into())?; + let smart_contract = Self::decode_smart_contract_v1(contract_h160.into())?; // parse era let era = era @@ -423,7 +442,7 @@ where #[precompile::public("claim_staker(address)")] fn claim_staker(handle: &mut impl PrecompileHandle, contract_h160: Address) -> EvmResult { // Parse smart contract to keep in line with the legacy behavior. - let _smart_contract = Self::decode_smart_contract(contract_h160.into())?; + let _smart_contract = Self::decode_smart_contract_v1(contract_h160.into())?; log::trace!(target: "ds-precompile", "claim_staker {:?}", _smart_contract); let origin = R::AddressMapping::into_account_id(handle.context().caller); @@ -438,11 +457,8 @@ where /// /// This call has been deprecated by dApp staking v3. #[precompile::public("set_reward_destination(uint8)")] - fn set_reward_destination( - handle: &mut impl PrecompileHandle, - reward_destination_raw: u8, - ) -> EvmResult { - Err(RevertReason::custom("Unexpected reward destination value.").into()) + fn set_reward_destination(_: &mut impl PrecompileHandle, _destination: u8) -> EvmResult { + Err(RevertReason::custom("Call is no longer supported.").into()) } /// Withdraw staked funds from the unregistered contract @@ -451,7 +467,7 @@ where handle: &mut impl PrecompileHandle, contract_h160: Address, ) -> EvmResult { - let smart_contract = Self::decode_smart_contract(contract_h160.into())?; + let smart_contract = Self::decode_smart_contract_v1(contract_h160.into())?; log::trace!(target: "ds-precompile", "withdraw_from_unregistered {:?}", smart_contract); let origin = R::AddressMapping::into_account_id(handle.context().caller); @@ -480,8 +496,8 @@ where + SingularStakingInfo::max_encoded_len(), )?; - let origin_smart_contract = Self::decode_smart_contract(origin_contract_h160.into())?; - let target_smart_contract = Self::decode_smart_contract(target_contract_h160.into())?; + let origin_smart_contract = Self::decode_smart_contract_v1(origin_contract_h160.into())?; + let target_smart_contract = Self::decode_smart_contract_v1(target_contract_h160.into())?; log::trace!(target: "ds-precompile", "nomination_transfer {:?} {:?} {:?}", origin_smart_contract, amount, target_smart_contract); // Find out how much staker has staked on the origin contract @@ -524,27 +540,25 @@ where Ok(true) } - // // v2 functions + // v2 functions - // /// Read the current protocol state. - // /// - // /// Returns: `(current era, current period number, subperiod)` - // fn protocol_state(handle: &mut impl PrecompileHandle) -> EvmResult { - // // TODO: benchmark this function so we can measure ref time & PoV correctly - // // Storage item: ActiveProtocolState: - // // Twox64(8) + ProtocolState::max_encoded_len - // handle.record_db_read::(4 + ProtocolState::max_encoded_len())?; + /// Read the current protocol state. + #[precompile::public("protocol_state()")] + #[precompile::view] + fn protocol_state(handle: &mut impl PrecompileHandle) -> EvmResult { + // TODO: benchmark this function so we can measure ref time & PoV correctly + // Storage item: ActiveProtocolState: + // Twox64(8) + ProtocolState::max_encoded_len + handle.record_db_read::(8 + ProtocolState::max_encoded_len())?; - // let protocol_state = ActiveProtocolState::::get(); + let protocol_state = ActiveProtocolState::::get(); - // Ok(succeed( - // EvmDataWriter::new() - // .write(U256::from(protocol_state.era)) - // .write(U256::from(protocol_state.period_number())) - // .write(Self::subperiod_id(&protocol_state.subperiod())) - // .build(), - // )) - // } + Ok(PrecompileProtocolState { + era: protocol_state.era.into(), + period: protocol_state.period_number().into(), + subperiod: Self::subperiod_id(&protocol_state.subperiod()), + }) + } /// Read the `unbonding period` or `unlocking period` expressed in the number of eras. #[precompile::public("unlocking_period()")] @@ -587,146 +601,145 @@ where Ok(true) } - // /// Attempts to stake the given amount on the given smart contract. - // fn stake(handle: &mut impl PrecompileHandle) -> EvmResult { - // // Parse smart contract & amount - // let mut input = handle.read_input()?; - // input.expect_arguments(2)?; - - // let contract_h160 = input.read::
()?.0; - // let smart_contract = Self::decode_smart_contract(contract_h160)?; - // let amount: Balance = input.read()?; - - // // Prepare call & dispatch it - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let stake_call = pallet_dapp_staking_v3::Call::::stake { - // smart_contract, - // amount, - // }; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; - - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } - - // /// Attempts to unstake the given amount from the given smart contract. - // fn unstake(handle: &mut impl PrecompileHandle) -> EvmResult { - // // Parse smart contract & amount - // let mut input = handle.read_input()?; - // input.expect_arguments(2)?; - - // let contract_h160 = input.read::
()?.0; - // let smart_contract = Self::decode_smart_contract(contract_h160)?; - // let amount: Balance = input.read()?; - - // // Prepare call & dispatch it - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let unstake_call = pallet_dapp_staking_v3::Call::::unstake { - // smart_contract, - // amount, - // }; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unstake_call)?; - - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } - - // /// Attempts to claim one or more pending staker rewards. - // fn claim_staker_rewards(handle: &mut impl PrecompileHandle) -> EvmResult { - // // Prepare call & dispatch it - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let claim_staker_rewards_call = pallet_dapp_staking_v3::Call::::claim_staker_rewards {}; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_staker_rewards_call)?; - - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } - - // /// Attempts to claim bonus reward for being a loyal staker of the given dApp. - // fn claim_bonus_reward(handle: &mut impl PrecompileHandle) -> EvmResult { - // // Parse smart contract - // let mut input = handle.read_input()?; - // input.expect_arguments(1)?; - - // let contract_h160 = input.read::
()?.0; - // let smart_contract = Self::decode_smart_contract(contract_h160)?; - - // // Prepare call & dispatch it - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let claim_bonus_reward_call = - // pallet_dapp_staking_v3::Call::::claim_bonus_reward { smart_contract }; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_bonus_reward_call)?; - - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } - - // /// Attempts to claim dApp reward for the given dApp in the given era. - // fn claim_dapp_reward(handle: &mut impl PrecompileHandle) -> EvmResult { - // // Parse smart contract & era - // let mut input = handle.read_input()?; - // input.expect_arguments(2)?; - - // let contract_h160 = input.read::
()?.0; - // let smart_contract = Self::decode_smart_contract(contract_h160)?; - // let era = input.read::()?; - - // // Prepare call & dispatch it - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let claim_dapp_reward_call = pallet_dapp_staking_v3::Call::::claim_dapp_reward { - // smart_contract, - // era, - // }; - // RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_dapp_reward_call)?; - - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } - - // /// Attempts to unstake everything from an unregistered contract. - // fn unstake_from_unregistered( - // handle: &mut impl PrecompileHandle, - // ) -> EvmResult { - // // Parse smart contract - // let mut input = handle.read_input()?; - // input.expect_arguments(1)?; - - // let contract_h160 = input.read::
()?.0; - // let smart_contract = Self::decode_smart_contract(contract_h160)?; - - // // Prepare call & dispatch it - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let unstake_from_unregistered_call = - // pallet_dapp_staking_v3::Call::::unstake_from_unregistered { smart_contract }; - // RuntimeHelper::::try_dispatch( - // handle, - // Some(origin).into(), - // unstake_from_unregistered_call, - // )?; - - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } - - // /// Attempts to cleanup expired entries for the staker. - // fn cleanup_expired_entries(handle: &mut impl PrecompileHandle) -> EvmResult { - // // Prepare call & dispatch it - // let origin = R::AddressMapping::into_account_id(handle.context().caller); - // let cleanup_expired_entries_call = - // pallet_dapp_staking_v3::Call::::cleanup_expired_entries {}; - // RuntimeHelper::::try_dispatch( - // handle, - // Some(origin).into(), - // cleanup_expired_entries_call, - // )?; - - // Ok(succeed(EvmDataWriter::new().write(true).build())) - // } + /// Attempts to stake the given amount on the given smart contract. + #[precompile::public("stake((u8,bytes),uint128)")] + fn stake( + handle: &mut impl PrecompileHandle, + smart_contract: SmartContractV2, + amount: Balance, + ) -> EvmResult { + let smart_contract = Self::decode_smart_contract_v2(smart_contract)?; + + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let stake_call = pallet_dapp_staking_v3::Call::::stake { + smart_contract, + amount, + }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), stake_call)?; + + Ok(true) + } + + /// Attempts to unstake the given amount from the given smart contract. + #[precompile::public("unstake((u8,bytes),uint128)")] + fn unstake( + handle: &mut impl PrecompileHandle, + smart_contract: SmartContractV2, + amount: Balance, + ) -> EvmResult { + let smart_contract = Self::decode_smart_contract_v2(smart_contract)?; + + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let unstake_call = pallet_dapp_staking_v3::Call::::unstake { + smart_contract, + amount, + }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), unstake_call)?; + + Ok(true) + } + + /// Attempts to claim one or more pending staker rewards. + #[precompile::public("claim_staker_rewards()")] + fn claim_staker_rewards(handle: &mut impl PrecompileHandle) -> EvmResult { + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let claim_staker_rewards_call = pallet_dapp_staking_v3::Call::::claim_staker_rewards {}; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_staker_rewards_call)?; + + Ok(true) + } + + /// Attempts to claim bonus reward for being a loyal staker of the given dApp. + #[precompile::public("claim_bonus_reward((u8,bytes))")] + fn claim_bonus_reward( + handle: &mut impl PrecompileHandle, + smart_contract: SmartContractV2, + ) -> EvmResult { + let smart_contract = Self::decode_smart_contract_v2(smart_contract)?; + + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let claim_bonus_reward_call = + pallet_dapp_staking_v3::Call::::claim_bonus_reward { smart_contract }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_bonus_reward_call)?; + + Ok(true) + } + + /// Attempts to claim dApp reward for the given dApp in the given era. + #[precompile::public("claim_bonus_reward((u8,bytes),uint256)")] + fn claim_dapp_reward( + handle: &mut impl PrecompileHandle, + smart_contract: SmartContractV2, + era: U256, + ) -> EvmResult { + let smart_contract = Self::decode_smart_contract_v2(smart_contract)?; + let era = era + .try_into() + .map_err::(|_| RevertReason::value_is_too_large("Era number.").into()) + .in_field("era")?; + + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let claim_dapp_reward_call = pallet_dapp_staking_v3::Call::::claim_dapp_reward { + smart_contract, + era, + }; + RuntimeHelper::::try_dispatch(handle, Some(origin).into(), claim_dapp_reward_call)?; + + Ok(true) + } + + /// Attempts to unstake everything from an unregistered contract. + #[precompile::public("unstake_from_unregistered((u8,bytes))")] + fn unstake_from_unregistered( + handle: &mut impl PrecompileHandle, + smart_contract: SmartContractV2, + ) -> EvmResult { + let smart_contract = Self::decode_smart_contract_v2(smart_contract)?; + + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let unstake_from_unregistered_call = + pallet_dapp_staking_v3::Call::::unstake_from_unregistered { smart_contract }; + RuntimeHelper::::try_dispatch( + handle, + Some(origin).into(), + unstake_from_unregistered_call, + )?; + + Ok(true) + } + + /// Attempts to cleanup expired entries for the staker. + #[precompile::public("cleanup_expired_entries()")] + fn cleanup_expired_entries(handle: &mut impl PrecompileHandle) -> EvmResult { + // Prepare call & dispatch it + let origin = R::AddressMapping::into_account_id(handle.context().caller); + let cleanup_expired_entries_call = + pallet_dapp_staking_v3::Call::::cleanup_expired_entries {}; + RuntimeHelper::::try_dispatch( + handle, + Some(origin).into(), + cleanup_expired_entries_call, + )?; + + Ok(true) + } // Utility functions - /// Helper method to decode type SmartContract enum - pub fn decode_smart_contract( + /// Helper method to decode type SmartContract enum for v1 calls + pub fn decode_smart_contract_v1( contract_h160: H160, ) -> EvmResult<::SmartContract> { // Encode contract address to fit SmartContract enum. // Since the SmartContract enum type can't be accessed from this pecompile, // use locally defined enum clone (see Contract enum) - let contract_enum_encoded = Contract::::Evm(contract_h160).encode(); + let contract_enum_encoded = Contract::Evm(contract_h160).encode(); // encoded enum will add one byte before the contract's address // therefore we need to decode len(H160) + 1 byte = 21 @@ -738,6 +751,46 @@ where Ok(smart_contract) } + /// Helper method to decode smart contract struct for v2 calls + /// + /// TODO: this is temporary and must be improved! + pub fn decode_smart_contract_v2( + smart_contract: SmartContractV2, + ) -> EvmResult<::SmartContract> { + // TODO: this needs to be improved now since it's incredibly hacky and ugly + + let contract_encoded = match smart_contract.contract_type { + 0 => { + ensure!( + smart_contract.address.as_bytes().len() == 20, + revert("Invalid address length for Astar EVM smart contract.") + ); + let h160_address = H160::from_slice(smart_contract.address.as_bytes()); + Contract::Evm(h160_address).encode() + } + 1 => { + ensure!( + smart_contract.address.as_bytes().len() == 32, + revert("Invalid address length for Astar WASM smart contract.") + ); + let mut staker_bytes = [0_u8; 32]; + staker_bytes[..].clone_from_slice(&smart_contract.address.as_bytes()); + + Contract::Wasm(staker_bytes.into()).encode() + } + _ => { + return Err(revert("Error while decoding SmartContract")); + } + }; + + let smart_contract = ::SmartContract::decode( + &mut TrailingZeroInput::new(contract_encoded.as_ref()), + ) + .map_err(|_| revert("Error while decoding SmartContract"))?; + + Ok(smart_contract) + } + /// Helper method to parse H160 or SS58 address fn parse_input_address(staker_vec: Vec) -> EvmResult { let staker: R::AccountId = match staker_vec.len() { diff --git a/precompiles/dapps-staking-v3/src/tests.rs b/precompiles/dapps-staking-v3/src/tests.rs index 1285673b40..ad2986bc37 100644 --- a/precompiles/dapps-staking-v3/src/tests.rs +++ b/precompiles/dapps-staking-v3/src/tests.rs @@ -354,7 +354,7 @@ fn withdraw_from_unregistered() { bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); let contract_id = - decode_smart_contract_from_array(TEST_CONTRACT.clone().to_fixed_bytes()).unwrap(); + decode_smart_contract_v1_from_array(TEST_CONTRACT.clone().to_fixed_bytes()).unwrap(); assert_ok!(DappsStaking::unregister(RuntimeOrigin::root(), contract_id)); withdraw_from_unregistered_verify(TestAccount::Bobo.into(), TEST_CONTRACT); @@ -400,7 +400,7 @@ fn nomination_transfer() { /// helper function to register and verify if registration is valid fn register_and_verify(developer: TestAccount, contract: H160) { let smart_contract = - decode_smart_contract_from_array(contract.clone().to_fixed_bytes()).unwrap(); + decode_smart_contract_v1_from_array(contract.clone().to_fixed_bytes()).unwrap(); DappsStaking::register( RuntimeOrigin::root(), developer.clone().into(), @@ -540,7 +540,7 @@ fn set_reward_destination_verify(staker: TestAccount, reward_destination: Reward /// helper function to withdraw funds from unregistered contract fn withdraw_from_unregistered_verify(staker: TestAccount, contract: H160) { let smart_contract = - decode_smart_contract_from_array(contract.clone().to_fixed_bytes()).unwrap(); + decode_smart_contract_v1_from_array(contract.clone().to_fixed_bytes()).unwrap(); let staker_acc_id = AccountId32::from(staker.clone()); let init_staker_info = DappsStaking::staker_info(&staker_acc_id, &smart_contract); assert!(!init_staker_info.latest_staked_value().is_zero()); @@ -568,9 +568,9 @@ fn nomination_transfer_verify( target_contract: H160, ) { let origin_smart_contract = - decode_smart_contract_from_array(origin_contract.clone().to_fixed_bytes()).unwrap(); + decode_smart_contract_v1_from_array(origin_contract.clone().to_fixed_bytes()).unwrap(); let target_smart_contract = - decode_smart_contract_from_array(target_contract.clone().to_fixed_bytes()).unwrap(); + decode_smart_contract_v1_from_array(target_contract.clone().to_fixed_bytes()).unwrap(); let staker_acc_id = AccountId32::from(staker.clone()); // Read init data staker info states @@ -678,7 +678,7 @@ fn verify_staked_amount(contract: H160, staker: TestAccount, amount: Balance) { } /// Helper method to decode type SmartContract enum from [u8; 20] -fn decode_smart_contract_from_array( +fn decode_smart_contract_v1_from_array( contract_array: [u8; 20], ) -> Result<::SmartContract, String> { // Encode contract address to fit SmartContract enum. From dac1dd4aaf405b3f27824c350bb9dfc7cd33d672 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Thu, 14 Dec 2023 17:11:44 +0100 Subject: [PATCH 13/34] Mock & fixes --- precompiles/dapps-staking-v3/src/lib.rs | 10 +++++----- precompiles/dapps-staking-v3/src/mock.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index e5a41f2a91..965150fcc2 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -602,7 +602,7 @@ where } /// Attempts to stake the given amount on the given smart contract. - #[precompile::public("stake((u8,bytes),uint128)")] + #[precompile::public("stake((uint8,bytes),uint128)")] fn stake( handle: &mut impl PrecompileHandle, smart_contract: SmartContractV2, @@ -622,7 +622,7 @@ where } /// Attempts to unstake the given amount from the given smart contract. - #[precompile::public("unstake((u8,bytes),uint128)")] + #[precompile::public("unstake((uint8,bytes),uint128)")] fn unstake( handle: &mut impl PrecompileHandle, smart_contract: SmartContractV2, @@ -653,7 +653,7 @@ where } /// Attempts to claim bonus reward for being a loyal staker of the given dApp. - #[precompile::public("claim_bonus_reward((u8,bytes))")] + #[precompile::public("claim_bonus_reward((uint8,bytes))")] fn claim_bonus_reward( handle: &mut impl PrecompileHandle, smart_contract: SmartContractV2, @@ -670,7 +670,7 @@ where } /// Attempts to claim dApp reward for the given dApp in the given era. - #[precompile::public("claim_bonus_reward((u8,bytes),uint256)")] + #[precompile::public("claim_bonus_reward((uint8,bytes),uint256)")] fn claim_dapp_reward( handle: &mut impl PrecompileHandle, smart_contract: SmartContractV2, @@ -694,7 +694,7 @@ where } /// Attempts to unstake everything from an unregistered contract. - #[precompile::public("unstake_from_unregistered((u8,bytes))")] + #[precompile::public("unstake_from_unregistered((uint8,bytes))")] fn unstake_from_unregistered( handle: &mut impl PrecompileHandle, smart_contract: SmartContractV2, diff --git a/precompiles/dapps-staking-v3/src/mock.rs b/precompiles/dapps-staking-v3/src/mock.rs index 642401cde6..70680933e3 100644 --- a/precompiles/dapps-staking-v3/src/mock.rs +++ b/precompiles/dapps-staking-v3/src/mock.rs @@ -18,7 +18,7 @@ use super::*; -use fp_evm::IsPrecompileResult; +use fp_evm::{IsPrecompileResult, Precompile}; use frame_support::{ construct_runtime, parameter_types, traits::{fungible::Mutate, ConstU128, ConstU64}, From 0ffe023efaa262e1d6590a044be70d7ff974ff0f Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Thu, 14 Dec 2023 18:08:35 +0100 Subject: [PATCH 14/34] Primitive smart contract --- pallets/dapp-staking-v3/src/lib.rs | 7 +- pallets/dapp-staking-v3/src/test/mock.rs | 16 +--- pallets/dapp-staking-v3/src/test/tests.rs | 94 +++++++++++++---------- precompiles/dapps-staking-v3/src/mock.rs | 15 ++-- primitives/src/dapp_staking.rs | 53 +++++++++++++ runtime/local/src/chain_extensions.rs | 7 +- runtime/local/src/lib.rs | 27 +------ 7 files changed, 128 insertions(+), 91 deletions(-) diff --git a/pallets/dapp-staking-v3/src/lib.rs b/pallets/dapp-staking-v3/src/lib.rs index c187b1b09e..775b2b37b7 100644 --- a/pallets/dapp-staking-v3/src/lib.rs +++ b/pallets/dapp-staking-v3/src/lib.rs @@ -51,7 +51,7 @@ use sp_runtime::{ pub use sp_std::vec::Vec; use astar_primitives::{ - dapp_staking::{CycleConfiguration, StakingRewardHandler}, + dapp_staking::{CycleConfiguration, SmartContractHandle, StakingRewardHandler}, Balance, BlockNumber, }; @@ -118,7 +118,10 @@ pub mod pallet { >; /// Describes smart contract in the context required by dApp staking. - type SmartContract: Parameter + Member + MaxEncodedLen; + type SmartContract: Parameter + + Member + + MaxEncodedLen + + SmartContractHandle; /// Privileged origin for managing dApp staking pallet. type ManagerOrigin: EnsureOrigin<::RuntimeOrigin>; diff --git a/pallets/dapp-staking-v3/src/test/mock.rs b/pallets/dapp-staking-v3/src/test/mock.rs index 050eb112f2..886e06fa96 100644 --- a/pallets/dapp-staking-v3/src/test/mock.rs +++ b/pallets/dapp-staking-v3/src/test/mock.rs @@ -40,7 +40,7 @@ use sp_runtime::{ }; use sp_std::cell::RefCell; -use astar_primitives::{testing::Header, Balance, BlockNumber}; +use astar_primitives::{dapp_staking::SmartContract, testing::Header, Balance, BlockNumber}; pub(crate) type AccountId = u64; @@ -146,17 +146,7 @@ impl StakingRewardHandler for DummyStakingRewardHandler { } } -#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, Debug, TypeInfo, MaxEncodedLen, Hash)] -pub enum MockSmartContract { - Wasm(AccountId), - Other(AccountId), -} - -impl Default for MockSmartContract { - fn default() -> Self { - MockSmartContract::Wasm(1) - } -} +pub(crate) type MockSmartContract = SmartContract; #[cfg(feature = "runtime-benchmarks")] pub struct BenchmarkHelper(sp_std::marker::PhantomData<(SC, ACC)>); @@ -165,7 +155,7 @@ impl crate::BenchmarkHelper for BenchmarkHelper { fn get_smart_contract(id: u32) -> MockSmartContract { - MockSmartContract::Wasm(id as AccountId) + MockSmartContract::wasm(id as AccountId) } fn set_balance(account: &AccountId, amount: Balance) { diff --git a/pallets/dapp-staking-v3/src/test/tests.rs b/pallets/dapp-staking-v3/src/test/tests.rs index 7b753d4dfc..d800bfb02e 100644 --- a/pallets/dapp-staking-v3/src/test/tests.rs +++ b/pallets/dapp-staking-v3/src/test/tests.rs @@ -29,7 +29,10 @@ use frame_support::{ }; use sp_runtime::traits::Zero; -use astar_primitives::{dapp_staking::CycleConfiguration, Balance, BlockNumber}; +use astar_primitives::{ + dapp_staking::{CycleConfiguration, SmartContractTypes}, + Balance, BlockNumber, +}; #[test] fn maintenace_mode_works() { @@ -104,11 +107,19 @@ fn maintenace_mode_call_filtering_works() { Error::::Disabled ); assert_noop!( - DappStaking::stake(RuntimeOrigin::signed(1), MockSmartContract::default(), 100), + DappStaking::stake( + RuntimeOrigin::signed(1), + MockSmartContract::wasm(1 as AccountId), + 100 + ), Error::::Disabled ); assert_noop!( - DappStaking::unstake(RuntimeOrigin::signed(1), MockSmartContract::default(), 100), + DappStaking::unstake( + RuntimeOrigin::signed(1), + MockSmartContract::wasm(1 as AccountId), + 100 + ), Error::::Disabled ); assert_noop!( @@ -116,13 +127,16 @@ fn maintenace_mode_call_filtering_works() { Error::::Disabled ); assert_noop!( - DappStaking::claim_bonus_reward(RuntimeOrigin::signed(1), MockSmartContract::default()), + DappStaking::claim_bonus_reward( + RuntimeOrigin::signed(1), + MockSmartContract::wasm(1 as AccountId) + ), Error::::Disabled ); assert_noop!( DappStaking::claim_dapp_reward( RuntimeOrigin::signed(1), - MockSmartContract::default(), + MockSmartContract::wasm(1 as AccountId), 1 ), Error::::Disabled @@ -130,7 +144,7 @@ fn maintenace_mode_call_filtering_works() { assert_noop!( DappStaking::unstake_from_unregistered( RuntimeOrigin::signed(1), - MockSmartContract::default() + MockSmartContract::wasm(1 as AccountId) ), Error::::Disabled ); @@ -815,7 +829,7 @@ fn stake_basic_example_is_ok() { ExtBuilder::build().execute_with(|| { // Register smart contract & lock some amount let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); let account = 2; @@ -834,7 +848,7 @@ fn stake_after_expiry_is_ok() { ExtBuilder::build().execute_with(|| { // Register smart contract let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); // Lock & stake some amount @@ -867,7 +881,7 @@ fn stake_after_expiry_is_ok() { fn stake_with_zero_amount_fails() { ExtBuilder::build().execute_with(|| { // Register smart contract & lock some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(1, &smart_contract); let account = 2; assert_lock(account, 300); @@ -886,7 +900,7 @@ fn stake_on_invalid_dapp_fails() { assert_lock(account, 300); // Try to stake on non-existing contract - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_noop!( DappStaking::stake(RuntimeOrigin::signed(account), smart_contract, 100), Error::::NotOperatedDApp @@ -906,7 +920,7 @@ fn stake_on_invalid_dapp_fails() { fn stake_in_final_era_fails() { ExtBuilder::build().execute_with(|| { // Register smart contract & lock some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); let account = 2; assert_register(1, &smart_contract); assert_lock(account, 300); @@ -929,7 +943,7 @@ fn stake_in_final_era_fails() { fn stake_fails_if_unclaimed_staker_rewards_from_past_remain() { ExtBuilder::build().execute_with(|| { // Register smart contract & lock some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); let account = 2; assert_register(1, &smart_contract); assert_lock(account, 300); @@ -957,7 +971,7 @@ fn stake_fails_if_unclaimed_staker_rewards_from_past_remain() { fn stake_fails_if_claimable_bonus_rewards_from_past_remain() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); let account = 2; assert_register(1, &smart_contract); assert_lock(account, 300); @@ -1101,7 +1115,7 @@ fn unstake_basic_example_is_ok() { ExtBuilder::build().execute_with(|| { // Register smart contract & lock some amount let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); let account = 2; @@ -1123,7 +1137,7 @@ fn unstake_with_leftover_amount_below_minimum_works() { ExtBuilder::build().execute_with(|| { // Register smart contract & lock some amount let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); let account = 2; @@ -1142,7 +1156,7 @@ fn unstake_with_leftover_amount_below_minimum_works() { fn unstake_with_zero_amount_fails() { ExtBuilder::build().execute_with(|| { // Register smart contract & lock some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(1, &smart_contract); let account = 2; assert_lock(account, 300); @@ -1162,7 +1176,7 @@ fn unstake_on_invalid_dapp_fails() { assert_lock(account, 300); // Try to unstake from non-existing contract - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_noop!( DappStaking::unstake(RuntimeOrigin::signed(account), smart_contract, 100), Error::::NotOperatedDApp @@ -1289,7 +1303,7 @@ fn claim_staker_rewards_basic_example_is_ok() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); let account = 2; @@ -1324,7 +1338,7 @@ fn claim_staker_rewards_double_call_fails() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); let account = 2; @@ -1351,7 +1365,7 @@ fn claim_staker_rewards_no_claimable_rewards_fails() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); let account = 2; @@ -1391,7 +1405,7 @@ fn claim_staker_rewards_after_expiry_fails() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); let account = 2; @@ -1440,7 +1454,7 @@ fn claim_staker_rewards_after_expiry_fails() { fn claim_staker_rewards_fails_due_to_payout_failure() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(1, &smart_contract); let account = 2; @@ -1469,7 +1483,7 @@ fn claim_bonus_reward_works() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); let account = 2; @@ -1504,7 +1518,7 @@ fn claim_bonus_reward_double_call_fails() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); let account = 2; @@ -1529,7 +1543,7 @@ fn claim_bonus_reward_when_nothing_to_claim_fails() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); let account = 2; @@ -1557,7 +1571,7 @@ fn claim_bonus_reward_with_only_build_and_earn_stake_fails() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); let account = 2; @@ -1587,7 +1601,7 @@ fn claim_bonus_reward_after_expiry_fails() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); let account = 2; @@ -1622,7 +1636,7 @@ fn claim_bonus_reward_after_expiry_fails() { fn claim_bonus_reward_fails_due_to_payout_failure() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(1, &smart_contract); let account = 2; @@ -1651,7 +1665,7 @@ fn claim_dapp_reward_works() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); let account = 2; @@ -1684,7 +1698,7 @@ fn claim_dapp_reward_works() { #[test] fn claim_dapp_reward_from_non_existing_contract_fails() { ExtBuilder::build().execute_with(|| { - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_noop!( DappStaking::claim_dapp_reward(RuntimeOrigin::signed(1), smart_contract, 1), Error::::ContractNotFound, @@ -1696,7 +1710,7 @@ fn claim_dapp_reward_from_non_existing_contract_fails() { fn claim_dapp_reward_from_invalid_era_fails() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(1, &smart_contract); let account = 2; @@ -1758,7 +1772,7 @@ fn claim_dapp_reward_if_dapp_not_in_any_tier_fails() { fn claim_dapp_reward_twice_for_same_era_fails() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(1, &smart_contract); let account = 2; @@ -1791,7 +1805,7 @@ fn claim_dapp_reward_twice_for_same_era_fails() { fn claim_dapp_reward_for_expired_era_fails() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(1, &smart_contract); let account = 2; @@ -1821,7 +1835,7 @@ fn claim_dapp_reward_for_expired_era_fails() { fn claim_dapp_reward_fails_due_to_payout_failure() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(1, &smart_contract); let account = 2; @@ -1857,7 +1871,7 @@ fn claim_dapp_reward_fails_due_to_payout_failure() { fn unstake_from_unregistered_is_ok() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(1, &smart_contract); let account = 2; @@ -1875,7 +1889,7 @@ fn unstake_from_unregistered_is_ok() { fn unstake_from_unregistered_fails_for_active_contract() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(1, &smart_contract); let account = 2; @@ -1894,7 +1908,7 @@ fn unstake_from_unregistered_fails_for_active_contract() { fn unstake_from_unregistered_fails_for_not_staked_contract() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(1, &smart_contract); assert_unregister(&smart_contract); @@ -1909,7 +1923,7 @@ fn unstake_from_unregistered_fails_for_not_staked_contract() { fn unstake_from_unregistered_fails_for_past_period() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(1, &smart_contract); let account = 2; @@ -2377,7 +2391,7 @@ fn get_dapp_tier_assignment_zero_slots_per_tier_works() { fn unlock_after_staked_period_ends_is_ok() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(1, &smart_contract); let account = 2; @@ -2438,7 +2452,7 @@ fn stake_and_unstake_after_reward_claim_is_ok() { ExtBuilder::build().execute_with(|| { // Register smart contract, lock&stake some amount let dev_account = 1; - let smart_contract = MockSmartContract::default(); + let smart_contract = MockSmartContract::wasm(1 as AccountId); assert_register(dev_account, &smart_contract); let account = 2; diff --git a/precompiles/dapps-staking-v3/src/mock.rs b/precompiles/dapps-staking-v3/src/mock.rs index 70680933e3..ea2c45ac55 100644 --- a/precompiles/dapps-staking-v3/src/mock.rs +++ b/precompiles/dapps-staking-v3/src/mock.rs @@ -100,14 +100,13 @@ impl From for H160 { } } -trait H160Conversion { - fn to_h160(&self) -> H160; -} - -impl H160Conversion for AccountId32 { - fn to_h160(&self) -> H160 { - let x = self.encode()[31]; - H160::repeat_byte(x) +impl From for TestAccount { + fn from(address: H160) -> TestAccount { + match address { + a if a == H160::repeat_byte(0x01) => TestAccount::Alex, + a if a == H160::repeat_byte(0x02) => TestAccount::Bobo, + a if a == H160::repeat_byte(0x03) => TestAccount::Dino, + } } } diff --git a/primitives/src/dapp_staking.rs b/primitives/src/dapp_staking.rs index 92ead96b15..baf7ff34b5 100644 --- a/primitives/src/dapp_staking.rs +++ b/primitives/src/dapp_staking.rs @@ -18,6 +18,12 @@ use super::{Balance, BlockNumber}; +use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; + +use frame_support::RuntimeDebug; +use sp_core::H160; +use sp_std::hash::Hash; + /// Configuration for cycles, periods, subperiods & eras. /// /// * `cycle` - Time unit similar to 'year' in the real world. Consists of one or more periods. At the beginning of each cycle, inflation is recalculated. @@ -83,3 +89,50 @@ pub trait StakingRewardHandler { /// Attempts to pay out the rewards to the beneficiary. fn payout_reward(beneficiary: &AccountId, reward: Balance) -> Result<(), ()>; } + +/// Trait defining the interface for dApp staking `smart contract types` handler. +/// +/// It can be used to create a representation of the specified smart contract instance type. +pub trait SmartContractHandle { + /// Create a new smart contract representation for the specified EVM address. + fn evm(address: H160) -> Self; + /// Create a new smart contract representation for the specified Wasm address. + fn wasm(address: AccountId) -> Self; +} + +/// Multi-VM pointer to smart contract instance. +#[derive( + PartialEq, + Eq, + Copy, + Clone, + Encode, + Decode, + RuntimeDebug, + MaxEncodedLen, + Hash, + scale_info::TypeInfo, +)] +pub enum SmartContract { + /// EVM smart contract instance. + Evm(H160), + /// Wasm smart contract instance. + Wasm(AccountId), +} + +// TODO: remove this once dApps staking v2 has been removed. +impl Default for SmartContract { + fn default() -> Self { + Self::evm([0x01; 20].into()) + } +} + +impl SmartContractHandle for SmartContract { + fn evm(address: H160) -> Self { + Self::Evm(address) + } + + fn wasm(address: AccountId) -> Self { + Self::Wasm(address) + } +} diff --git a/runtime/local/src/chain_extensions.rs b/runtime/local/src/chain_extensions.rs index 516bc8040b..f7a9029c88 100644 --- a/runtime/local/src/chain_extensions.rs +++ b/runtime/local/src/chain_extensions.rs @@ -28,9 +28,10 @@ pub use pallet_chain_extension_xvm::XvmExtension; // Following impls defines chain extension IDs. -impl RegisteredChainExtension for DappsStakingExtension { - const ID: u16 = 00; -} +// This can be restored when integrated with dApp staking v3. +// impl RegisteredChainExtension for DappsStakingExtension { +// const ID: u16 = 00; +// } impl RegisteredChainExtension for XvmExtension { const ID: u16 = 01; diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index f5e318d898..dfddfead1d 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -63,7 +63,7 @@ use sp_runtime::{ use sp_std::prelude::*; pub use astar_primitives::{ - dapp_staking::{CycleConfiguration, StakingRewardHandler}, + dapp_staking::{CycleConfiguration, SmartContract, StakingRewardHandler}, evm::{EvmRevertCodeHandler, HashedDefaultMappings}, AccountId, Address, AssetId, Balance, BlockNumber, Hash, Header, Index, Signature, }; @@ -482,29 +482,6 @@ impl pallet_dapps_staking::Config for Runtime { type UnregisteredDappRewardRetention = ConstU32<3>; } -/// Multi-VM pointer to smart contract instance. -#[derive( - PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, MaxEncodedLen, scale_info::TypeInfo, -)] -pub enum SmartContract { - /// EVM smart contract instance. - Evm(sp_core::H160), - /// Wasm smart contract instance. - Wasm(AccountId), -} - -impl Default for SmartContract { - fn default() -> Self { - SmartContract::Evm(H160::repeat_byte(0x00)) - } -} - -impl> From<[u8; 32]> for SmartContract { - fn from(input: [u8; 32]) -> Self { - SmartContract::Wasm(input.into()) - } -} - pub struct DummyPriceProvider; impl pallet_dapp_staking_v3::PriceProvider for DummyPriceProvider { fn average_price() -> FixedU64 { @@ -937,7 +914,7 @@ impl pallet_contracts::Config for Runtime { type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_contracts::weights::SubstrateWeight; type ChainExtension = ( - DappsStakingExtension, + // DappsStakingExtension, XvmExtension, AssetsExtension>, UnifiedAccountsExtension, From 03ee458acb4b5c93680ae10fa0dc8ab2572812ad Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Thu, 14 Dec 2023 18:10:59 +0100 Subject: [PATCH 15/34] Fix dsv3 test --- pallets/dapp-staking-v3/src/test/mock.rs | 1 - pallets/dapp-staking-v3/src/test/tests.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pallets/dapp-staking-v3/src/test/mock.rs b/pallets/dapp-staking-v3/src/test/mock.rs index 886e06fa96..37db264053 100644 --- a/pallets/dapp-staking-v3/src/test/mock.rs +++ b/pallets/dapp-staking-v3/src/test/mock.rs @@ -30,7 +30,6 @@ use frame_support::{ }, weights::Weight, }; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use sp_arithmetic::fixed_point::FixedU64; use sp_core::H256; use sp_io::TestExternalities; diff --git a/pallets/dapp-staking-v3/src/test/tests.rs b/pallets/dapp-staking-v3/src/test/tests.rs index d800bfb02e..0efe2ebba7 100644 --- a/pallets/dapp-staking-v3/src/test/tests.rs +++ b/pallets/dapp-staking-v3/src/test/tests.rs @@ -30,7 +30,7 @@ use frame_support::{ use sp_runtime::traits::Zero; use astar_primitives::{ - dapp_staking::{CycleConfiguration, SmartContractTypes}, + dapp_staking::{CycleConfiguration, SmartContractHandle}, Balance, BlockNumber, }; From 9e8e81b2a651a2818b4e8920aed6a2f7797df225 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Thu, 14 Dec 2023 19:14:30 +0100 Subject: [PATCH 16/34] Prepare impl & mock --- precompiles/dapps-staking-v3/src/lib.rs | 1 - precompiles/dapps-staking-v3/src/mock.rs | 118 ++++------------------- 2 files changed, 19 insertions(+), 100 deletions(-) diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index 965150fcc2..842e5c99cd 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -88,7 +88,6 @@ where ::RuntimeOrigin: From>, R::RuntimeCall: Dispatchable + GetDispatchInfo, R::RuntimeCall: From>, - R::AccountId: From<[u8; 32]>, { // v1 functions diff --git a/precompiles/dapps-staking-v3/src/mock.rs b/precompiles/dapps-staking-v3/src/mock.rs index ea2c45ac55..c5c877315a 100644 --- a/precompiles/dapps-staking-v3/src/mock.rs +++ b/precompiles/dapps-staking-v3/src/mock.rs @@ -27,97 +27,29 @@ use frame_support::{ use pallet_evm::{ AddressMapping, EnsureAddressNever, EnsureAddressRoot, PrecompileResult, PrecompileSet, }; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; -use serde::{Deserialize, Serialize}; use sp_arithmetic::fixed_point::FixedU64; use sp_core::{H160, H256}; use sp_io::TestExternalities; -use sp_runtime::{ - traits::{BlakeTwo256, ConstU32, IdentityLookup}, - AccountId32, -}; +use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; extern crate alloc; use astar_primitives::{ - dapp_staking::{CycleConfiguration, StakingRewardHandler}, + dapp_staking::{CycleConfiguration, SmartContract, StakingRewardHandler}, testing::Header, - Balance, BlockNumber, + AccountId, Balance, BlockNumber, }; use pallet_dapp_staking_v3::PriceProvider; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; -#[derive( - Eq, - PartialEq, - Ord, - PartialOrd, - Clone, - Encode, - Decode, - Debug, - MaxEncodedLen, - Serialize, - Deserialize, - derive_more::Display, - scale_info::TypeInfo, -)] - -pub enum TestAccount { - Empty, - Alex, - Bobo, - Dino, -} - -impl Default for TestAccount { - fn default() -> Self { - Self::Empty - } -} - -// needed for associated type in pallet_evm -impl AddressMapping for TestAccount { - fn into_account_id(h160_account: H160) -> AccountId32 { - match h160_account { - a if a == H160::repeat_byte(0x01) => TestAccount::Alex.into(), - a if a == H160::repeat_byte(0x02) => TestAccount::Bobo.into(), - a if a == H160::repeat_byte(0x03) => TestAccount::Dino.into(), - _ => TestAccount::Empty.into(), - } - } -} - -impl From for H160 { - fn from(x: TestAccount) -> H160 { - match x { - TestAccount::Alex => H160::repeat_byte(0x01), - TestAccount::Bobo => H160::repeat_byte(0x02), - TestAccount::Dino => H160::repeat_byte(0x03), - _ => Default::default(), - } - } -} - -impl From for TestAccount { - fn from(address: H160) -> TestAccount { - match address { - a if a == H160::repeat_byte(0x01) => TestAccount::Alex, - a if a == H160::repeat_byte(0x02) => TestAccount::Bobo, - a if a == H160::repeat_byte(0x03) => TestAccount::Dino, - } - } -} - -impl From for AccountId32 { - fn from(x: TestAccount) -> Self { - match x { - TestAccount::Alex => AccountId32::from([1u8; 32]), - TestAccount::Bobo => AccountId32::from([2u8; 32]), - TestAccount::Dino => AccountId32::from([3u8; 32]), - _ => AccountId32::from([0u8; 32]), - } +pub struct AddressMapper; +impl AddressMapping for AddressMapper { + fn into_account_id(account: H160) -> AccountId { + account + .as_bytes() + .try_into() + .expect("H160 is 20 bytes long so it must fit into 32 bytes; QED") } } @@ -144,7 +76,7 @@ impl frame_system::Config for Test { type BlockNumber = BlockNumber; type Hash = H256; type Hashing = BlakeTwo256; - type AccountId = AccountId32; + type AccountId = AccountId; type Lookup = IdentityLookup; type Header = Header; type RuntimeEvent = RuntimeEvent; @@ -212,9 +144,9 @@ impl pallet_evm::Config for Test { type FeeCalculator = (); type GasWeightMapping = pallet_evm::FixedGasWeightMapping; type WeightPerGas = WeightPerGas; - type CallOrigin = EnsureAddressRoot; - type WithdrawOrigin = EnsureAddressNever; - type AddressMapping = TestAccount; + type CallOrigin = EnsureAddressRoot; + type WithdrawOrigin = EnsureAddressNever; + type AddressMapping = AddressMapper; type Currency = Balances; type RuntimeEvent = RuntimeEvent; type Runner = pallet_evm::runner::stack::Runner; @@ -238,19 +170,7 @@ impl pallet_timestamp::Config for Test { type WeightInfo = (); } -#[derive( - PartialEq, Eq, Copy, Clone, Encode, Decode, Debug, scale_info::TypeInfo, MaxEncodedLen, -)] -pub enum MockSmartContract { - Evm(sp_core::H160), - Wasm(AccountId32), -} - -impl Default for MockSmartContract { - fn default() -> Self { - MockSmartContract::Evm(H160::repeat_byte(0x00)) - } -} +type MockSmartContract = SmartContract<::AccountId>; pub struct DummyPriceProvider; impl PriceProvider for DummyPriceProvider { @@ -260,7 +180,7 @@ impl PriceProvider for DummyPriceProvider { } pub struct DummyStakingRewardHandler; -impl StakingRewardHandler for DummyStakingRewardHandler { +impl StakingRewardHandler for DummyStakingRewardHandler { fn staker_and_dapp_reward_pools(_total_staked_value: Balance) -> (Balance, Balance) { ( Balance::from(1_000_000_000_000_u128), @@ -272,7 +192,7 @@ impl StakingRewardHandler for DummyStakingRewardHandler { Balance::from(3_000_000_u128) } - fn payout_reward(beneficiary: &AccountId32, reward: Balance) -> Result<(), ()> { + fn payout_reward(beneficiary: &AccountId, reward: Balance) -> Result<(), ()> { let _ = Balances::mint_into(beneficiary, reward); Ok(()) } @@ -301,8 +221,8 @@ impl pallet_dapp_staking_v3::Config for Test { type RuntimeEvent = RuntimeEvent; type RuntimeFreezeReason = RuntimeFreezeReason; type Currency = Balances; - type SmartContract = MockSmartContract; - type ManagerOrigin = frame_system::EnsureRoot; + type SmartContract = MockSmartContract; + type ManagerOrigin = frame_system::EnsureRoot; type NativePriceProvider = DummyPriceProvider; type StakingRewardHandler = DummyStakingRewardHandler; type CycleConfiguration = DummyCycleConfiguration; From 94b480c0b6a78ccf6aa2c696f37b22ca759098a4 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Fri, 15 Dec 2023 09:41:50 +0100 Subject: [PATCH 17/34] Remove redundant code, adjust mock & prepare tests --- precompiles/dapps-staking-v3/src/lib.rs | 54 +- precompiles/dapps-staking-v3/src/tests.rs | 1327 ++++++++++----------- 2 files changed, 671 insertions(+), 710 deletions(-) diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index 842e5c99cd..3855cefdb7 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -21,7 +21,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use fp_evm::PrecompileHandle; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; +use parity_scale_codec::MaxEncodedLen; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, @@ -32,11 +32,11 @@ use frame_support::{ use pallet_evm::AddressMapping; use precompile_utils::prelude::*; use sp_core::{Get, H160, U256}; -use sp_runtime::traits::{TrailingZeroInput, Zero}; +use sp_runtime::traits::Zero; use sp_std::{marker::PhantomData, prelude::*}; extern crate alloc; -use astar_primitives::{AccountId, Balance}; +use astar_primitives::{dapp_staking::SmartContractHandle, AccountId, Balance}; use pallet_dapp_staking_v3::{ AccountLedgerFor, ActiveProtocolState, ContractStake, ContractStakeAmount, CurrentEraInfo, DAppInfoFor, EraInfo, EraRewardSpanFor, EraRewards, IntegratedDApps, Ledger, @@ -48,21 +48,8 @@ type GetStakerBytesLimit = ConstU32; #[cfg(test)] mod mock; -// TODO: uncomment & fix after uplift to new precompile utils -// #[cfg(test)] -// mod tests; - -// TODO: move smart contract enum under primitives, so it can be reused in other pallets. -// Or at least introduce some trait on it so it can be more easily manipulated between runtimes/precompiles. - -/// This is only used to encode SmartContract enum -#[derive(PartialEq, Eq, Clone, Encode, Decode, Debug)] -pub enum Contract { - /// EVM smart contract instance. - Evm(H160), - /// Wasm smart contract instance. Not used in this precompile - Wasm(AccountId), -} +#[cfg(test)] +mod tests; /// Helper struct used to encode protocol state. #[derive(Debug, Clone, solidity::Codec)] @@ -735,37 +722,23 @@ where pub fn decode_smart_contract_v1( contract_h160: H160, ) -> EvmResult<::SmartContract> { - // Encode contract address to fit SmartContract enum. - // Since the SmartContract enum type can't be accessed from this pecompile, - // use locally defined enum clone (see Contract enum) - let contract_enum_encoded = Contract::Evm(contract_h160).encode(); - - // encoded enum will add one byte before the contract's address - // therefore we need to decode len(H160) + 1 byte = 21 - let smart_contract = ::SmartContract::decode( - &mut &contract_enum_encoded[..21], - ) - .map_err(|_| revert("Error while decoding SmartContract"))?; - - Ok(smart_contract) + Ok(::SmartContract::evm( + contract_h160, + )) } /// Helper method to decode smart contract struct for v2 calls - /// - /// TODO: this is temporary and must be improved! pub fn decode_smart_contract_v2( smart_contract: SmartContractV2, ) -> EvmResult<::SmartContract> { - // TODO: this needs to be improved now since it's incredibly hacky and ugly - - let contract_encoded = match smart_contract.contract_type { + let smart_contract = match smart_contract.contract_type { 0 => { ensure!( smart_contract.address.as_bytes().len() == 20, revert("Invalid address length for Astar EVM smart contract.") ); let h160_address = H160::from_slice(smart_contract.address.as_bytes()); - Contract::Evm(h160_address).encode() + ::SmartContract::evm(h160_address) } 1 => { ensure!( @@ -775,18 +748,13 @@ where let mut staker_bytes = [0_u8; 32]; staker_bytes[..].clone_from_slice(&smart_contract.address.as_bytes()); - Contract::Wasm(staker_bytes.into()).encode() + ::SmartContract::wasm(staker_bytes.into()) } _ => { return Err(revert("Error while decoding SmartContract")); } }; - let smart_contract = ::SmartContract::decode( - &mut TrailingZeroInput::new(contract_encoded.as_ref()), - ) - .map_err(|_| revert("Error while decoding SmartContract"))?; - Ok(smart_contract) } diff --git a/precompiles/dapps-staking-v3/src/tests.rs b/precompiles/dapps-staking-v3/src/tests.rs index ad2986bc37..3e05f0d441 100644 --- a/precompiles/dapps-staking-v3/src/tests.rs +++ b/precompiles/dapps-staking-v3/src/tests.rs @@ -17,679 +17,672 @@ // along with Astar. If not, see . extern crate alloc; -use crate::{ - mock::{ - advance_to_era, initialize_first_block, precompile_address, DappsStaking, EraIndex, - ExternalityBuilder, RuntimeOrigin, TestAccount, AST, UNBONDING_PERIOD, *, - }, - *, -}; +use crate::{mock::*, *}; use fp_evm::ExitError; use frame_support::assert_ok; -use pallet_dapps_staking::RewardDestination; use precompile_utils::testing::*; use sp_core::H160; use sp_runtime::{traits::Zero, AccountId32, Perbill}; -fn precompiles() -> DappPrecompile { +fn precompiles() -> DappStakingPrecompile { PrecompilesValue::get() } -#[test] -fn current_era_is_ok() { - ExternalityBuilder::default().build().execute_with(|| { - initialize_first_block(); - - let current_era = DappsStaking::current_era(); - - precompiles() - .prepare_test( - TestAccount::Alex, - precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadCurrentEra).build(), - ) - .expect_cost(READ_WEIGHT) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(current_era).build()); - - // advance to era 5 and check output - advance_to_era(5); - let current_era = DappsStaking::current_era(); - - precompiles() - .prepare_test( - TestAccount::Alex, - precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadCurrentEra).build(), - ) - .expect_cost(READ_WEIGHT) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(current_era).build()); - }); -} - -#[test] -fn read_unbonding_period_is_ok() { - ExternalityBuilder::default().build().execute_with(|| { - initialize_first_block(); - - precompiles() - .prepare_test( - TestAccount::Alex, - precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadUnbondingPeriod).build(), - ) - .expect_cost(0) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(UNBONDING_PERIOD).build()); - }); -} - -#[test] -fn read_era_reward_is_ok() { - ExternalityBuilder::default().build().execute_with(|| { - initialize_first_block(); - - advance_to_era(3); - let era_reward = joint_block_reward() * BLOCKS_PER_ERA as u128; - let second_era: EraIndex = 2; - - precompiles() - .prepare_test( - TestAccount::Alex, - precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadEraReward) - .write(second_era) - .build(), - ) - .expect_cost(READ_WEIGHT) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(era_reward).build()); - }); -} - -#[test] -fn read_era_staked_is_ok() { - ExternalityBuilder::default().build().execute_with(|| { - initialize_first_block(); - - let zero_era = EraIndex::zero(); - let staked = Balance::zero(); - - precompiles() - .prepare_test( - TestAccount::Alex, - precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadEraStaked) - .write(zero_era) - .build(), - ) - .expect_cost(READ_WEIGHT) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(staked).build()); - }); -} - -#[test] -fn register_via_precompile_fails() { - ExternalityBuilder::default() - .with_balances(vec![(TestAccount::Alex.into(), 200 * AST)]) - .build() - .execute_with(|| { - initialize_first_block(); - - precompiles() - .prepare_test( - TestAccount::Alex, - precompile_address(), - EvmDataWriter::new_with_selector(Action::Register) - .write(Address(TEST_CONTRACT.clone())) - .build(), - ) - .expect_no_logs() - .execute_error(ExitError::Other(alloc::borrow::Cow::Borrowed( - "register via evm precompile is not allowed", - ))); - }); -} - -#[test] -fn bond_and_stake_is_ok() { - ExternalityBuilder::default() - .with_balances(vec![ - (TestAccount::Alex.into(), 200 * AST), - (TestAccount::Bobo.into(), 200 * AST), - (TestAccount::Dino.into(), 100 * AST), - ]) - .build() - .execute_with(|| { - initialize_first_block(); - - register_and_verify(TestAccount::Alex, TEST_CONTRACT); - - let amount_staked_bobo = 100 * AST; - bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); - - let amount_staked_dino = 50 * AST; - bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); - - contract_era_stake_verify(TEST_CONTRACT, amount_staked_bobo + amount_staked_dino); - verify_staked_amount(TEST_CONTRACT, TestAccount::Bobo.into(), amount_staked_bobo); - verify_staked_amount(TEST_CONTRACT, TestAccount::Dino.into(), amount_staked_dino); - }); -} - -#[test] -fn unbond_and_unstake_is_ok() { - ExternalityBuilder::default() - .with_balances(vec![ - (TestAccount::Alex.into(), 200 * AST), - (TestAccount::Bobo.into(), 200 * AST), - (TestAccount::Dino.into(), 100 * AST), - ]) - .build() - .execute_with(|| { - initialize_first_block(); - - // register new contract by Alex - let developer = TestAccount::Alex.into(); - register_and_verify(developer, TEST_CONTRACT); - - let amount_staked_bobo = 100 * AST; - bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); - let amount_staked_dino = 50 * AST; - bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); - - // Bobo unstakes all - let era = 2; - advance_to_era(era); - unbond_unstake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); - - contract_era_stake_verify(TEST_CONTRACT, amount_staked_dino); - verify_staked_amount(TEST_CONTRACT, TestAccount::Dino, amount_staked_dino); - - // withdraw unbonded funds - advance_to_era(era + UNBONDING_PERIOD + 1); - withdraw_unbonded_verify(TestAccount::Bobo); - }); -} - -#[test] -fn claim_dapp_is_ok() { - ExternalityBuilder::default() - .with_balances(vec![ - (TestAccount::Alex.into(), 200 * AST), - (TestAccount::Bobo.into(), 200 * AST), - (TestAccount::Dino.into(), 200 * AST), - ]) - .build() - .execute_with(|| { - initialize_first_block(); - - // register new contract by Alex - let developer = TestAccount::Alex; - register_and_verify(developer, TEST_CONTRACT); - - let stake_amount_total = 300 * AST; - let ratio_bobo = Perbill::from_rational(3u32, 5u32); - let ratio_dino = Perbill::from_rational(2u32, 5u32); - let amount_staked_bobo = ratio_bobo * stake_amount_total; - bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); - - let amount_staked_dino = ratio_dino * stake_amount_total; - bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); - - // advance era and claim reward - let era = 5; - advance_to_era(era); - claim_dapp_and_verify(TEST_CONTRACT, era - 1); - - //check that the reward is payed out to the developer - let developer_reward = DAPP_BLOCK_REWARD * BLOCKS_PER_ERA as Balance; - assert_eq!( - ::Currency::free_balance( - &TestAccount::Alex.into() - ), - (200 * AST) + developer_reward - REGISTER_DEPOSIT - ); - }); -} - -#[test] -fn claim_staker_is_ok() { - ExternalityBuilder::default() - .with_balances(vec![ - (TestAccount::Alex.into(), 200 * AST), - (TestAccount::Bobo.into(), 200 * AST), - (TestAccount::Dino.into(), 200 * AST), - ]) - .build() - .execute_with(|| { - initialize_first_block(); - - // register new contract by Alex - let developer = TestAccount::Alex; - register_and_verify(developer, TEST_CONTRACT); - - let stake_amount_total = 300 * AST; - let ratio_bobo = Perbill::from_rational(3u32, 5u32); - let ratio_dino = Perbill::from_rational(2u32, 5u32); - let amount_staked_bobo = ratio_bobo * stake_amount_total; - bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); - - let amount_staked_dino = ratio_dino * stake_amount_total; - bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); - - // advance era and claim reward - advance_to_era(5); - - let stakers_reward = STAKER_BLOCK_REWARD * BLOCKS_PER_ERA as Balance; - - // Ensure that all rewards can be claimed for the first staker - for era in 1..DappsStaking::current_era() as Balance { - claim_staker_and_verify(TestAccount::Bobo, TEST_CONTRACT); - assert_eq!( - ::Currency::free_balance( - &TestAccount::Bobo.into() - ), - (200 * AST) + ratio_bobo * stakers_reward * era - ); - } - - // Repeat the same thing for the second staker - for era in 1..DappsStaking::current_era() as Balance { - claim_staker_and_verify(TestAccount::Dino, TEST_CONTRACT); - assert_eq!( - ::Currency::free_balance( - &TestAccount::Dino.into() - ), - (200 * AST) + ratio_dino * stakers_reward * era - ); - } - }); -} - -#[test] -fn set_reward_destination() { - ExternalityBuilder::default() - .with_balances(vec![ - (TestAccount::Alex.into(), 200 * AST), - (TestAccount::Bobo.into(), 200 * AST), - ]) - .build() - .execute_with(|| { - initialize_first_block(); - // register contract and stake it - register_and_verify(TestAccount::Alex.into(), TEST_CONTRACT); - - // bond & stake the origin contract - bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, 100 * AST); - - // change destinations and verfiy it was successful - set_reward_destination_verify(TestAccount::Bobo.into(), RewardDestination::FreeBalance); - set_reward_destination_verify( - TestAccount::Bobo.into(), - RewardDestination::StakeBalance, - ); - set_reward_destination_verify(TestAccount::Bobo.into(), RewardDestination::FreeBalance); - }); -} - -#[test] -fn withdraw_from_unregistered() { - ExternalityBuilder::default() - .with_balances(vec![ - (TestAccount::Alex.into(), 200 * AST), - (TestAccount::Bobo.into(), 200 * AST), - ]) - .build() - .execute_with(|| { - initialize_first_block(); - - // register new contract by Alex - let developer = TestAccount::Alex.into(); - register_and_verify(developer, TEST_CONTRACT); - - let amount_staked_bobo = 100 * AST; - bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); - - let contract_id = - decode_smart_contract_v1_from_array(TEST_CONTRACT.clone().to_fixed_bytes()).unwrap(); - assert_ok!(DappsStaking::unregister(RuntimeOrigin::root(), contract_id)); - - withdraw_from_unregistered_verify(TestAccount::Bobo.into(), TEST_CONTRACT); - }); -} - -#[test] -fn nomination_transfer() { - ExternalityBuilder::default() - .with_balances(vec![ - (TestAccount::Alex.into(), 200 * AST), - (TestAccount::Dino.into(), 200 * AST), - (TestAccount::Bobo.into(), 200 * AST), - ]) - .build() - .execute_with(|| { - initialize_first_block(); - - // register two contracts for nomination transfer test - let origin_contract = H160::repeat_byte(0x09); - let target_contract = H160::repeat_byte(0x0A); - register_and_verify(TestAccount::Alex.into(), origin_contract); - register_and_verify(TestAccount::Dino.into(), target_contract); - - // bond & stake the origin contract - let amount_staked_bobo = 100 * AST; - bond_stake_and_verify(TestAccount::Bobo, origin_contract, amount_staked_bobo); - - // transfer nomination and ensure it was successful - nomination_transfer_verify( - TestAccount::Bobo, - origin_contract, - 10 * AST, - target_contract, - ); - }); -} - -// **************************************************************************************************** -// Helper functions -// **************************************************************************************************** - -/// helper function to register and verify if registration is valid -fn register_and_verify(developer: TestAccount, contract: H160) { - let smart_contract = - decode_smart_contract_v1_from_array(contract.clone().to_fixed_bytes()).unwrap(); - DappsStaking::register( - RuntimeOrigin::root(), - developer.clone().into(), - smart_contract, - ) - .unwrap(); - - // check the storage after the register - let dev_account_id: AccountId32 = developer.into(); - let smart_contract_bytes = - (DappsStaking::registered_contract(dev_account_id).unwrap_or_default()).encode(); - - assert_eq!( - // 0-th byte is enum value discriminator - smart_contract_bytes[1..21], - contract.to_fixed_bytes() - ); -} - -/// helper function to read ledger storage item -fn read_staked_amount_h160_verify(staker: TestAccount, amount: u128) { - precompiles() - .prepare_test( - staker.clone(), - precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadStakedAmount) - .write(Bytes( - Into::::into(staker.clone()).to_fixed_bytes().to_vec(), - )) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(amount).build()); -} - -/// helper function to read ledger storage item for ss58 account -fn read_staked_amount_ss58_verify(staker: TestAccount, amount: u128) { - let staker_acc_id: AccountId32 = staker.clone().into(); - - precompiles() - .prepare_test( - staker.clone(), - precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadStakedAmount) - .write(Bytes(staker_acc_id.encode())) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(amount).build()); -} - -/// helper function to bond, stake and verify if resulet is OK -fn bond_stake_and_verify(staker: TestAccount, contract: H160, amount: u128) { - precompiles() - .prepare_test( - staker.clone(), - precompile_address(), - EvmDataWriter::new_with_selector(Action::BondAndStake) - .write(Address(contract.clone())) - .write(amount) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - read_staked_amount_h160_verify(staker.clone(), amount); - read_staked_amount_ss58_verify(staker, amount); -} - -/// helper function to unbond, unstake and verify if result is OK -fn unbond_unstake_and_verify(staker: TestAccount, contract: H160, amount: u128) { - precompiles() - .prepare_test( - staker.clone(), - precompile_address(), - EvmDataWriter::new_with_selector(Action::UnbondAndUnstake) - .write(Address(contract.clone())) - .write(amount) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); -} - -/// helper function to withdraw unstaked funds and verify if result is OK -fn withdraw_unbonded_verify(staker: TestAccount) { - let staker_acc_id = AccountId32::from(staker.clone()); - - // call unbond_and_unstake(). Check usable_balance before and after the call - assert_ne!( - ::Currency::free_balance(&staker_acc_id), - ::Currency::usable_balance(&staker_acc_id) - ); - - precompiles() - .prepare_test( - staker.clone(), - precompile_address(), - EvmDataWriter::new_with_selector(Action::WithdrawUnbounded).build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - assert_eq!( - ::Currency::free_balance(&staker_acc_id), - ::Currency::usable_balance(&staker_acc_id) - ); -} - -/// helper function to verify change of reward destination for a staker -fn set_reward_destination_verify(staker: TestAccount, reward_destination: RewardDestination) { - // Read staker's ledger - let staker_acc_id = AccountId32::from(staker.clone()); - let init_ledger = DappsStaking::ledger(&staker_acc_id); - // Ensure that something is staked or being unbonded - assert!(!init_ledger.is_empty()); - - let reward_destination_raw: u8 = match reward_destination { - RewardDestination::FreeBalance => 0, - RewardDestination::StakeBalance => 1, - }; - precompiles() - .prepare_test( - staker.clone(), - precompile_address(), - EvmDataWriter::new_with_selector(Action::SetRewardDestination) - .write(reward_destination_raw) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - let final_ledger = DappsStaking::ledger(&staker_acc_id); - assert_eq!(final_ledger.reward_destination(), reward_destination); -} - -/// helper function to withdraw funds from unregistered contract -fn withdraw_from_unregistered_verify(staker: TestAccount, contract: H160) { - let smart_contract = - decode_smart_contract_v1_from_array(contract.clone().to_fixed_bytes()).unwrap(); - let staker_acc_id = AccountId32::from(staker.clone()); - let init_staker_info = DappsStaking::staker_info(&staker_acc_id, &smart_contract); - assert!(!init_staker_info.latest_staked_value().is_zero()); - - precompiles() - .prepare_test( - staker.clone(), - precompile_address(), - EvmDataWriter::new_with_selector(Action::WithdrawFromUnregistered) - .write(Address(contract.clone())) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - let final_staker_info = DappsStaking::staker_info(&staker_acc_id, &smart_contract); - assert!(final_staker_info.latest_staked_value().is_zero()); -} - -/// helper function to verify nomination transfer from origin to target contract -fn nomination_transfer_verify( - staker: TestAccount, - origin_contract: H160, - amount: Balance, - target_contract: H160, -) { - let origin_smart_contract = - decode_smart_contract_v1_from_array(origin_contract.clone().to_fixed_bytes()).unwrap(); - let target_smart_contract = - decode_smart_contract_v1_from_array(target_contract.clone().to_fixed_bytes()).unwrap(); - let staker_acc_id = AccountId32::from(staker.clone()); - - // Read init data staker info states - let init_origin_staker_info = DappsStaking::staker_info(&staker_acc_id, &origin_smart_contract); - let init_target_staker_info = DappsStaking::staker_info(&staker_acc_id, &target_smart_contract); - - precompiles() - .prepare_test( - staker.clone(), - precompile_address(), - EvmDataWriter::new_with_selector(Action::NominationTransfer) - .write(Address(origin_contract.clone())) - .write(amount) - .write(Address(target_contract.clone())) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); - - let final_origin_staker_info = - DappsStaking::staker_info(&staker_acc_id, &origin_smart_contract); - let final_target_staker_info = - DappsStaking::staker_info(&staker_acc_id, &target_smart_contract); - - // Verify final state - let will_be_unstaked = init_origin_staker_info - .latest_staked_value() - .saturating_sub(amount) - < MINIMUM_STAKING_AMOUNT; - let transfer_amount = if will_be_unstaked { - init_origin_staker_info.latest_staked_value() - } else { - amount - }; - - assert_eq!( - final_origin_staker_info.latest_staked_value() + transfer_amount, - init_origin_staker_info.latest_staked_value() - ); - assert_eq!( - final_target_staker_info.latest_staked_value() - transfer_amount, - init_target_staker_info.latest_staked_value() - ); -} - -/// helper function to bond, stake and verify if result is OK -fn claim_dapp_and_verify(contract: H160, era: EraIndex) { - precompiles() - .prepare_test( - TestAccount::Bobo, - precompile_address(), - EvmDataWriter::new_with_selector(Action::ClaimDapp) - .write(Address(contract.clone())) - .write(era) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); -} - -/// helper function to bond, stake and verify if the result is OK -fn claim_staker_and_verify(staker: TestAccount, contract: H160) { - precompiles() - .prepare_test( - staker, - precompile_address(), - EvmDataWriter::new_with_selector(Action::ClaimStaker) - .write(Address(contract.clone())) - .build(), - ) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(true).build()); -} - -fn contract_era_stake_verify(contract: H160, amount: Balance) { - precompiles() - .prepare_test( - TestAccount::Alex, - precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadContractStake) - .write(Address(contract.clone())) - .build(), - ) - .expect_cost(2 * READ_WEIGHT) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(amount).build()); -} - -/// helper function to verify latest staked amount -fn verify_staked_amount(contract: H160, staker: TestAccount, amount: Balance) { - precompiles() - .prepare_test( - staker.clone(), - precompile_address(), - EvmDataWriter::new_with_selector(Action::ReadStakedAmountOnContract) - .write(Address(contract.clone())) - .write(Bytes( - Into::::into(staker.clone()).to_fixed_bytes().to_vec(), - )) - .build(), - ) - .expect_cost(READ_WEIGHT) - .expect_no_logs() - .execute_returns(EvmDataWriter::new().write(amount).build()); -} - -/// Helper method to decode type SmartContract enum from [u8; 20] -fn decode_smart_contract_v1_from_array( - contract_array: [u8; 20], -) -> Result<::SmartContract, String> { - // Encode contract address to fit SmartContract enum. - let mut contract_enum_encoded: [u8; 21] = [0; 21]; - contract_enum_encoded[0] = 0; // enum for EVM H160 address is 0 - contract_enum_encoded[1..21].copy_from_slice(&contract_array); - - let smart_contract = ::SmartContract::decode( - &mut &contract_enum_encoded[..21], - ) - .map_err(|_| "Error while decoding SmartContract")?; - - Ok(smart_contract) -} +// #[test] +// fn current_era_is_ok() { +// ExternalityBuilder::default().build().execute_with(|| { +// initialize_first_block(); + +// let current_era = DappsStaking::current_era(); + +// precompiles() +// .prepare_test( +// TestAccount::Alex, +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::ReadCurrentEra).build(), +// ) +// .expect_cost(READ_WEIGHT) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(current_era).build()); + +// // advance to era 5 and check output +// advance_to_era(5); +// let current_era = DappsStaking::current_era(); + +// precompiles() +// .prepare_test( +// TestAccount::Alex, +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::ReadCurrentEra).build(), +// ) +// .expect_cost(READ_WEIGHT) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(current_era).build()); +// }); +// } + +// #[test] +// fn read_unbonding_period_is_ok() { +// ExternalityBuilder::default().build().execute_with(|| { +// initialize_first_block(); + +// precompiles() +// .prepare_test( +// TestAccount::Alex, +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::ReadUnbondingPeriod).build(), +// ) +// .expect_cost(0) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(UNBONDING_PERIOD).build()); +// }); +// } + +// #[test] +// fn read_era_reward_is_ok() { +// ExternalityBuilder::default().build().execute_with(|| { +// initialize_first_block(); + +// advance_to_era(3); +// let era_reward = joint_block_reward() * BLOCKS_PER_ERA as u128; +// let second_era: EraIndex = 2; + +// precompiles() +// .prepare_test( +// TestAccount::Alex, +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::ReadEraReward) +// .write(second_era) +// .build(), +// ) +// .expect_cost(READ_WEIGHT) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(era_reward).build()); +// }); +// } + +// #[test] +// fn read_era_staked_is_ok() { +// ExternalityBuilder::default().build().execute_with(|| { +// initialize_first_block(); + +// let zero_era = EraIndex::zero(); +// let staked = Balance::zero(); + +// precompiles() +// .prepare_test( +// TestAccount::Alex, +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::ReadEraStaked) +// .write(zero_era) +// .build(), +// ) +// .expect_cost(READ_WEIGHT) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(staked).build()); +// }); +// } + +// #[test] +// fn register_via_precompile_fails() { +// ExternalityBuilder::default() +// .with_balances(vec![(TestAccount::Alex.into(), 200 * AST)]) +// .build() +// .execute_with(|| { +// initialize_first_block(); + +// precompiles() +// .prepare_test( +// TestAccount::Alex, +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::Register) +// .write(Address(TEST_CONTRACT.clone())) +// .build(), +// ) +// .expect_no_logs() +// .execute_error(ExitError::Other(alloc::borrow::Cow::Borrowed( +// "register via evm precompile is not allowed", +// ))); +// }); +// } + +// #[test] +// fn bond_and_stake_is_ok() { +// ExternalityBuilder::default() +// .with_balances(vec![ +// (TestAccount::Alex.into(), 200 * AST), +// (TestAccount::Bobo.into(), 200 * AST), +// (TestAccount::Dino.into(), 100 * AST), +// ]) +// .build() +// .execute_with(|| { +// initialize_first_block(); + +// register_and_verify(TestAccount::Alex, TEST_CONTRACT); + +// let amount_staked_bobo = 100 * AST; +// bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); + +// let amount_staked_dino = 50 * AST; +// bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); + +// contract_era_stake_verify(TEST_CONTRACT, amount_staked_bobo + amount_staked_dino); +// verify_staked_amount(TEST_CONTRACT, TestAccount::Bobo.into(), amount_staked_bobo); +// verify_staked_amount(TEST_CONTRACT, TestAccount::Dino.into(), amount_staked_dino); +// }); +// } + +// #[test] +// fn unbond_and_unstake_is_ok() { +// ExternalityBuilder::default() +// .with_balances(vec![ +// (TestAccount::Alex.into(), 200 * AST), +// (TestAccount::Bobo.into(), 200 * AST), +// (TestAccount::Dino.into(), 100 * AST), +// ]) +// .build() +// .execute_with(|| { +// initialize_first_block(); + +// // register new contract by Alex +// let developer = TestAccount::Alex.into(); +// register_and_verify(developer, TEST_CONTRACT); + +// let amount_staked_bobo = 100 * AST; +// bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); +// let amount_staked_dino = 50 * AST; +// bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); + +// // Bobo unstakes all +// let era = 2; +// advance_to_era(era); +// unbond_unstake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); + +// contract_era_stake_verify(TEST_CONTRACT, amount_staked_dino); +// verify_staked_amount(TEST_CONTRACT, TestAccount::Dino, amount_staked_dino); + +// // withdraw unbonded funds +// advance_to_era(era + UNBONDING_PERIOD + 1); +// withdraw_unbonded_verify(TestAccount::Bobo); +// }); +// } + +// #[test] +// fn claim_dapp_is_ok() { +// ExternalityBuilder::default() +// .with_balances(vec![ +// (TestAccount::Alex.into(), 200 * AST), +// (TestAccount::Bobo.into(), 200 * AST), +// (TestAccount::Dino.into(), 200 * AST), +// ]) +// .build() +// .execute_with(|| { +// initialize_first_block(); + +// // register new contract by Alex +// let developer = TestAccount::Alex; +// register_and_verify(developer, TEST_CONTRACT); + +// let stake_amount_total = 300 * AST; +// let ratio_bobo = Perbill::from_rational(3u32, 5u32); +// let ratio_dino = Perbill::from_rational(2u32, 5u32); +// let amount_staked_bobo = ratio_bobo * stake_amount_total; +// bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); + +// let amount_staked_dino = ratio_dino * stake_amount_total; +// bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); + +// // advance era and claim reward +// let era = 5; +// advance_to_era(era); +// claim_dapp_and_verify(TEST_CONTRACT, era - 1); + +// //check that the reward is payed out to the developer +// let developer_reward = DAPP_BLOCK_REWARD * BLOCKS_PER_ERA as Balance; +// assert_eq!( +// ::Currency::free_balance( +// &TestAccount::Alex.into() +// ), +// (200 * AST) + developer_reward - REGISTER_DEPOSIT +// ); +// }); +// } + +// #[test] +// fn claim_staker_is_ok() { +// ExternalityBuilder::default() +// .with_balances(vec![ +// (TestAccount::Alex.into(), 200 * AST), +// (TestAccount::Bobo.into(), 200 * AST), +// (TestAccount::Dino.into(), 200 * AST), +// ]) +// .build() +// .execute_with(|| { +// initialize_first_block(); + +// // register new contract by Alex +// let developer = TestAccount::Alex; +// register_and_verify(developer, TEST_CONTRACT); + +// let stake_amount_total = 300 * AST; +// let ratio_bobo = Perbill::from_rational(3u32, 5u32); +// let ratio_dino = Perbill::from_rational(2u32, 5u32); +// let amount_staked_bobo = ratio_bobo * stake_amount_total; +// bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); + +// let amount_staked_dino = ratio_dino * stake_amount_total; +// bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); + +// // advance era and claim reward +// advance_to_era(5); + +// let stakers_reward = STAKER_BLOCK_REWARD * BLOCKS_PER_ERA as Balance; + +// // Ensure that all rewards can be claimed for the first staker +// for era in 1..DappsStaking::current_era() as Balance { +// claim_staker_and_verify(TestAccount::Bobo, TEST_CONTRACT); +// assert_eq!( +// ::Currency::free_balance( +// &TestAccount::Bobo.into() +// ), +// (200 * AST) + ratio_bobo * stakers_reward * era +// ); +// } + +// // Repeat the same thing for the second staker +// for era in 1..DappsStaking::current_era() as Balance { +// claim_staker_and_verify(TestAccount::Dino, TEST_CONTRACT); +// assert_eq!( +// ::Currency::free_balance( +// &TestAccount::Dino.into() +// ), +// (200 * AST) + ratio_dino * stakers_reward * era +// ); +// } +// }); +// } + +// #[test] +// fn set_reward_destination() { +// ExternalityBuilder::default() +// .with_balances(vec![ +// (TestAccount::Alex.into(), 200 * AST), +// (TestAccount::Bobo.into(), 200 * AST), +// ]) +// .build() +// .execute_with(|| { +// initialize_first_block(); +// // register contract and stake it +// register_and_verify(TestAccount::Alex.into(), TEST_CONTRACT); + +// // bond & stake the origin contract +// bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, 100 * AST); + +// // change destinations and verfiy it was successful +// set_reward_destination_verify(TestAccount::Bobo.into(), RewardDestination::FreeBalance); +// set_reward_destination_verify( +// TestAccount::Bobo.into(), +// RewardDestination::StakeBalance, +// ); +// set_reward_destination_verify(TestAccount::Bobo.into(), RewardDestination::FreeBalance); +// }); +// } + +// #[test] +// fn withdraw_from_unregistered() { +// ExternalityBuilder::default() +// .with_balances(vec![ +// (TestAccount::Alex.into(), 200 * AST), +// (TestAccount::Bobo.into(), 200 * AST), +// ]) +// .build() +// .execute_with(|| { +// initialize_first_block(); + +// // register new contract by Alex +// let developer = TestAccount::Alex.into(); +// register_and_verify(developer, TEST_CONTRACT); + +// let amount_staked_bobo = 100 * AST; +// bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); + +// let contract_id = +// decode_smart_contract_v1_from_array(TEST_CONTRACT.clone().to_fixed_bytes()).unwrap(); +// assert_ok!(DappsStaking::unregister(RuntimeOrigin::root(), contract_id)); + +// withdraw_from_unregistered_verify(TestAccount::Bobo.into(), TEST_CONTRACT); +// }); +// } + +// #[test] +// fn nomination_transfer() { +// ExternalityBuilder::default() +// .with_balances(vec![ +// (TestAccount::Alex.into(), 200 * AST), +// (TestAccount::Dino.into(), 200 * AST), +// (TestAccount::Bobo.into(), 200 * AST), +// ]) +// .build() +// .execute_with(|| { +// initialize_first_block(); + +// // register two contracts for nomination transfer test +// let origin_contract = H160::repeat_byte(0x09); +// let target_contract = H160::repeat_byte(0x0A); +// register_and_verify(TestAccount::Alex.into(), origin_contract); +// register_and_verify(TestAccount::Dino.into(), target_contract); + +// // bond & stake the origin contract +// let amount_staked_bobo = 100 * AST; +// bond_stake_and_verify(TestAccount::Bobo, origin_contract, amount_staked_bobo); + +// // transfer nomination and ensure it was successful +// nomination_transfer_verify( +// TestAccount::Bobo, +// origin_contract, +// 10 * AST, +// target_contract, +// ); +// }); +// } + +// // **************************************************************************************************** +// // Helper functions +// // **************************************************************************************************** + +// /// helper function to register and verify if registration is valid +// fn register_and_verify(developer: TestAccount, contract: H160) { +// let smart_contract = +// decode_smart_contract_v1_from_array(contract.clone().to_fixed_bytes()).unwrap(); +// DappsStaking::register( +// RuntimeOrigin::root(), +// developer.clone().into(), +// smart_contract, +// ) +// .unwrap(); + +// // check the storage after the register +// let dev_account_id: AccountId32 = developer.into(); +// let smart_contract_bytes = +// (DappsStaking::registered_contract(dev_account_id).unwrap_or_default()).encode(); + +// assert_eq!( +// // 0-th byte is enum value discriminator +// smart_contract_bytes[1..21], +// contract.to_fixed_bytes() +// ); +// } + +// /// helper function to read ledger storage item +// fn read_staked_amount_h160_verify(staker: TestAccount, amount: u128) { +// precompiles() +// .prepare_test( +// staker.clone(), +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::ReadStakedAmount) +// .write(Bytes( +// Into::::into(staker.clone()).to_fixed_bytes().to_vec(), +// )) +// .build(), +// ) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(amount).build()); +// } + +// /// helper function to read ledger storage item for ss58 account +// fn read_staked_amount_ss58_verify(staker: TestAccount, amount: u128) { +// let staker_acc_id: AccountId32 = staker.clone().into(); + +// precompiles() +// .prepare_test( +// staker.clone(), +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::ReadStakedAmount) +// .write(Bytes(staker_acc_id.encode())) +// .build(), +// ) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(amount).build()); +// } + +// /// helper function to bond, stake and verify if resulet is OK +// fn bond_stake_and_verify(staker: TestAccount, contract: H160, amount: u128) { +// precompiles() +// .prepare_test( +// staker.clone(), +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::BondAndStake) +// .write(Address(contract.clone())) +// .write(amount) +// .build(), +// ) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(true).build()); + +// read_staked_amount_h160_verify(staker.clone(), amount); +// read_staked_amount_ss58_verify(staker, amount); +// } + +// /// helper function to unbond, unstake and verify if result is OK +// fn unbond_unstake_and_verify(staker: TestAccount, contract: H160, amount: u128) { +// precompiles() +// .prepare_test( +// staker.clone(), +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::UnbondAndUnstake) +// .write(Address(contract.clone())) +// .write(amount) +// .build(), +// ) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(true).build()); +// } + +// /// helper function to withdraw unstaked funds and verify if result is OK +// fn withdraw_unbonded_verify(staker: TestAccount) { +// let staker_acc_id = AccountId32::from(staker.clone()); + +// // call unbond_and_unstake(). Check usable_balance before and after the call +// assert_ne!( +// ::Currency::free_balance(&staker_acc_id), +// ::Currency::usable_balance(&staker_acc_id) +// ); + +// precompiles() +// .prepare_test( +// staker.clone(), +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::WithdrawUnbounded).build(), +// ) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(true).build()); + +// assert_eq!( +// ::Currency::free_balance(&staker_acc_id), +// ::Currency::usable_balance(&staker_acc_id) +// ); +// } + +// /// helper function to verify change of reward destination for a staker +// fn set_reward_destination_verify(staker: TestAccount, reward_destination: RewardDestination) { +// // Read staker's ledger +// let staker_acc_id = AccountId32::from(staker.clone()); +// let init_ledger = DappsStaking::ledger(&staker_acc_id); +// // Ensure that something is staked or being unbonded +// assert!(!init_ledger.is_empty()); + +// let reward_destination_raw: u8 = match reward_destination { +// RewardDestination::FreeBalance => 0, +// RewardDestination::StakeBalance => 1, +// }; +// precompiles() +// .prepare_test( +// staker.clone(), +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::SetRewardDestination) +// .write(reward_destination_raw) +// .build(), +// ) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(true).build()); + +// let final_ledger = DappsStaking::ledger(&staker_acc_id); +// assert_eq!(final_ledger.reward_destination(), reward_destination); +// } + +// /// helper function to withdraw funds from unregistered contract +// fn withdraw_from_unregistered_verify(staker: TestAccount, contract: H160) { +// let smart_contract = +// decode_smart_contract_v1_from_array(contract.clone().to_fixed_bytes()).unwrap(); +// let staker_acc_id = AccountId32::from(staker.clone()); +// let init_staker_info = DappsStaking::staker_info(&staker_acc_id, &smart_contract); +// assert!(!init_staker_info.latest_staked_value().is_zero()); + +// precompiles() +// .prepare_test( +// staker.clone(), +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::WithdrawFromUnregistered) +// .write(Address(contract.clone())) +// .build(), +// ) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(true).build()); + +// let final_staker_info = DappsStaking::staker_info(&staker_acc_id, &smart_contract); +// assert!(final_staker_info.latest_staked_value().is_zero()); +// } + +// /// helper function to verify nomination transfer from origin to target contract +// fn nomination_transfer_verify( +// staker: TestAccount, +// origin_contract: H160, +// amount: Balance, +// target_contract: H160, +// ) { +// let origin_smart_contract = +// decode_smart_contract_v1_from_array(origin_contract.clone().to_fixed_bytes()).unwrap(); +// let target_smart_contract = +// decode_smart_contract_v1_from_array(target_contract.clone().to_fixed_bytes()).unwrap(); +// let staker_acc_id = AccountId32::from(staker.clone()); + +// // Read init data staker info states +// let init_origin_staker_info = DappsStaking::staker_info(&staker_acc_id, &origin_smart_contract); +// let init_target_staker_info = DappsStaking::staker_info(&staker_acc_id, &target_smart_contract); + +// precompiles() +// .prepare_test( +// staker.clone(), +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::NominationTransfer) +// .write(Address(origin_contract.clone())) +// .write(amount) +// .write(Address(target_contract.clone())) +// .build(), +// ) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(true).build()); + +// let final_origin_staker_info = +// DappsStaking::staker_info(&staker_acc_id, &origin_smart_contract); +// let final_target_staker_info = +// DappsStaking::staker_info(&staker_acc_id, &target_smart_contract); + +// // Verify final state +// let will_be_unstaked = init_origin_staker_info +// .latest_staked_value() +// .saturating_sub(amount) +// < MINIMUM_STAKING_AMOUNT; +// let transfer_amount = if will_be_unstaked { +// init_origin_staker_info.latest_staked_value() +// } else { +// amount +// }; + +// assert_eq!( +// final_origin_staker_info.latest_staked_value() + transfer_amount, +// init_origin_staker_info.latest_staked_value() +// ); +// assert_eq!( +// final_target_staker_info.latest_staked_value() - transfer_amount, +// init_target_staker_info.latest_staked_value() +// ); +// } + +// /// helper function to bond, stake and verify if result is OK +// fn claim_dapp_and_verify(contract: H160, era: EraIndex) { +// precompiles() +// .prepare_test( +// TestAccount::Bobo, +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::ClaimDapp) +// .write(Address(contract.clone())) +// .write(era) +// .build(), +// ) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(true).build()); +// } + +// /// helper function to bond, stake and verify if the result is OK +// fn claim_staker_and_verify(staker: TestAccount, contract: H160) { +// precompiles() +// .prepare_test( +// staker, +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::ClaimStaker) +// .write(Address(contract.clone())) +// .build(), +// ) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(true).build()); +// } + +// fn contract_era_stake_verify(contract: H160, amount: Balance) { +// precompiles() +// .prepare_test( +// TestAccount::Alex, +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::ReadContractStake) +// .write(Address(contract.clone())) +// .build(), +// ) +// .expect_cost(2 * READ_WEIGHT) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(amount).build()); +// } + +// /// helper function to verify latest staked amount +// fn verify_staked_amount(contract: H160, staker: TestAccount, amount: Balance) { +// precompiles() +// .prepare_test( +// staker.clone(), +// precompile_address(), +// EvmDataWriter::new_with_selector(Action::ReadStakedAmountOnContract) +// .write(Address(contract.clone())) +// .write(Bytes( +// Into::::into(staker.clone()).to_fixed_bytes().to_vec(), +// )) +// .build(), +// ) +// .expect_cost(READ_WEIGHT) +// .expect_no_logs() +// .execute_returns(EvmDataWriter::new().write(amount).build()); +// } + +// /// Helper method to decode type SmartContract enum from [u8; 20] +// fn decode_smart_contract_v1_from_array( +// contract_array: [u8; 20], +// ) -> Result<::SmartContract, String> { +// // Encode contract address to fit SmartContract enum. +// let mut contract_enum_encoded: [u8; 21] = [0; 21]; +// contract_enum_encoded[0] = 0; // enum for EVM H160 address is 0 +// contract_enum_encoded[1..21].copy_from_slice(&contract_array); + +// let smart_contract = ::SmartContract::decode( +// &mut &contract_enum_encoded[..21], +// ) +// .map_err(|_| "Error while decoding SmartContract")?; + +// Ok(smart_contract) +// } From ff0d6cdd1b8b40004ca045f4b2f0c70956ce3534 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Fri, 15 Dec 2023 11:03:38 +0100 Subject: [PATCH 18/34] Tests & utils --- precompiles/dapps-staking-v3/src/mock.rs | 149 +++++++++++++- precompiles/dapps-staking-v3/src/tests.rs | 230 +++++++++++++--------- 2 files changed, 279 insertions(+), 100 deletions(-) diff --git a/precompiles/dapps-staking-v3/src/mock.rs b/precompiles/dapps-staking-v3/src/mock.rs index c5c877315a..4a651b3927 100644 --- a/precompiles/dapps-staking-v3/src/mock.rs +++ b/precompiles/dapps-staking-v3/src/mock.rs @@ -20,10 +20,14 @@ use super::*; use fp_evm::{IsPrecompileResult, Precompile}; use frame_support::{ - construct_runtime, parameter_types, - traits::{fungible::Mutate, ConstU128, ConstU64}, + assert_ok, construct_runtime, parameter_types, + traits::{ + fungible::{Mutate as FunMutate, Unbalanced as FunUnbalanced}, + ConstU128, ConstU64, Hooks, + }, weights::{RuntimeDbWeight, Weight}, }; +use frame_system::RawOrigin; use pallet_evm::{ AddressMapping, EnsureAddressNever, EnsureAddressRoot, PrecompileResult, PrecompileSet, }; @@ -38,7 +42,7 @@ use astar_primitives::{ testing::Header, AccountId, Balance, BlockNumber, }; -use pallet_dapp_staking_v3::PriceProvider; +use pallet_dapp_staking_v3::{EraNumber, PeriodNumber, PriceProvider}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -46,8 +50,10 @@ type Block = frame_system::mocking::MockBlock; pub struct AddressMapper; impl AddressMapping for AddressMapper { fn into_account_id(account: H160) -> AccountId { - account - .as_bytes() + let mut account_id = [0u8; 32]; + account_id[0..20].clone_from_slice(&account.as_bytes()); + + account_id .try_into() .expect("H160 is 20 bytes long so it must fit into 32 bytes; QED") } @@ -135,6 +141,8 @@ where } } +pub type PrecompileCall = DappStakingV3PrecompileCall; + parameter_types! { pub PrecompilesValue: DappStakingPrecompile = DappStakingPrecompile(Default::default()); pub WeightPerGas: Weight = Weight::from_parts(1, 0); @@ -238,9 +246,9 @@ impl pallet_dapp_staking_v3::Config for Test { type WeightInfo = pallet_dapp_staking_v3::weights::SubstrateWeight; } -pub struct _ExternalityBuilder; -impl _ExternalityBuilder { - pub fn _build(self) -> TestExternalities { +pub struct ExternalityBuilder; +impl ExternalityBuilder { + pub fn build() -> TestExternalities { let mut storage = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); @@ -272,6 +280,129 @@ construct_runtime!( Balances: pallet_balances, Evm: pallet_evm, Timestamp: pallet_timestamp, - DappsStaking: pallet_dapp_staking_v3, + DappStaking: pallet_dapp_staking_v3, } ); + +// Utility functions + +pub const ALICE: H160 = H160::repeat_byte(0xAA); + +/// Used to register a smart contract, and stake some funds on it. +/// +/// Returns `(staker H160 address, smart contract, staked amount)`. +pub fn register_and_stake() -> ( + H160, + ::SmartContract, + Balance, +) { + let smart_contract = + ::SmartContract::evm(H160::repeat_byte(0xFA)); + + let alice_native = AddressMapper::into_account_id(ALICE); + + // 1. Register smart contract + assert_ok!(DappStaking::register( + RawOrigin::Root.into(), + alice_native.clone(), + smart_contract.clone() + )); + + // 2. Lock some amount + assert_ok!( + ::Currency::write_balance( + &alice_native, + 1000_000_000_000_000_000_000 as Balance, + ) + ); + let amount = 1_000_000_000_000; + assert_ok!(DappStaking::lock( + RawOrigin::Signed(alice_native.clone()).into(), + amount, + )); + + // 3. Stake the locked amount + assert_ok!(DappStaking::stake( + RawOrigin::Signed(alice_native.clone()).into(), + smart_contract.clone(), + amount, + )); + + (ALICE, smart_contract, amount) +} + +/// Initialize first block. +/// This method should only be called once in a UT otherwise the first block will get initialized multiple times. +pub fn initialize() { + // This assert prevents method misuse + assert_eq!(System::block_number(), 1 as BlockNumber); + DappStaking::on_initialize(System::block_number()); + run_to_block(2); +} + +/// Run to the specified block number. +/// Function assumes first block has been initialized. +pub(crate) fn run_to_block(n: BlockNumber) { + while System::block_number() < n { + DappStaking::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + DappStaking::on_initialize(System::block_number()); + } +} + +/// Run for the specified number of blocks. +/// Function assumes first block has been initialized. +pub(crate) fn run_for_blocks(n: BlockNumber) { + run_to_block(System::block_number() + n); +} + +/// Advance blocks until the specified era has been reached. +/// +/// Function has no effect if era is already passed. +pub(crate) fn advance_to_era(era: EraNumber) { + assert!(era >= ActiveProtocolState::::get().era); + while ActiveProtocolState::::get().era < era { + run_for_blocks(1); + } +} + +/// Advance blocks until next era has been reached. +pub(crate) fn advance_to_next_era() { + advance_to_era(ActiveProtocolState::::get().era + 1); +} + +/// Advance blocks until the specified period has been reached. +/// +/// Function has no effect if period is already passed. +pub(crate) fn advance_to_period(period: PeriodNumber) { + assert!(period >= ActiveProtocolState::::get().period_number()); + while ActiveProtocolState::::get().period_number() < period { + run_for_blocks(1); + } +} + +/// Advance blocks until next period has been reached. +pub(crate) fn advance_to_next_period() { + advance_to_period(ActiveProtocolState::::get().period_number() + 1); +} + +/// Advance blocks until next period type has been reached. +pub(crate) fn advance_to_next_subperiod() { + let subperiod = ActiveProtocolState::::get().subperiod(); + while ActiveProtocolState::::get().subperiod() == subperiod { + run_for_blocks(1); + } +} + +// Return all dApp staking events from the event buffer. +pub fn dapp_staking_events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + ::RuntimeEvent::from(e) + .try_into() + .ok() + }) + .collect::>() +} diff --git a/precompiles/dapps-staking-v3/src/tests.rs b/precompiles/dapps-staking-v3/src/tests.rs index 3e05f0d441..6c20a23c08 100644 --- a/precompiles/dapps-staking-v3/src/tests.rs +++ b/precompiles/dapps-staking-v3/src/tests.rs @@ -24,104 +24,152 @@ use precompile_utils::testing::*; use sp_core::H160; use sp_runtime::{traits::Zero, AccountId32, Perbill}; +use pallet_dapp_staking_v3::{AccountLedger, ActiveProtocolState, EraNumber, EraRewards}; + fn precompiles() -> DappStakingPrecompile { PrecompilesValue::get() } -// #[test] -// fn current_era_is_ok() { -// ExternalityBuilder::default().build().execute_with(|| { -// initialize_first_block(); - -// let current_era = DappsStaking::current_era(); - -// precompiles() -// .prepare_test( -// TestAccount::Alex, -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::ReadCurrentEra).build(), -// ) -// .expect_cost(READ_WEIGHT) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(current_era).build()); - -// // advance to era 5 and check output -// advance_to_era(5); -// let current_era = DappsStaking::current_era(); - -// precompiles() -// .prepare_test( -// TestAccount::Alex, -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::ReadCurrentEra).build(), -// ) -// .expect_cost(READ_WEIGHT) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(current_era).build()); -// }); -// } +#[test] +fn read_current_era_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + precompiles() + .prepare_test( + Alice, + precompile_address(), + PrecompileCall::read_current_era {}, + ) + .expect_no_logs() + .execute_returns(ActiveProtocolState::::get().era); + + // advance a few eras, check value again + advance_to_era(7); + precompiles() + .prepare_test( + Alice, + precompile_address(), + PrecompileCall::read_current_era {}, + ) + .expect_no_logs() + .execute_returns(ActiveProtocolState::::get().era); + }); +} -// #[test] -// fn read_unbonding_period_is_ok() { -// ExternalityBuilder::default().build().execute_with(|| { -// initialize_first_block(); - -// precompiles() -// .prepare_test( -// TestAccount::Alex, -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::ReadUnbondingPeriod).build(), -// ) -// .expect_cost(0) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(UNBONDING_PERIOD).build()); -// }); -// } +#[test] +fn read_unbonding_period_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + let unlocking_period_in_eras: EraNumber = + ::UnlockingPeriod::get(); + + precompiles() + .prepare_test( + Alice, + precompile_address(), + PrecompileCall::read_unbonding_period {}, + ) + .expect_no_logs() + .execute_returns(unlocking_period_in_eras); + }); +} -// #[test] -// fn read_era_reward_is_ok() { -// ExternalityBuilder::default().build().execute_with(|| { -// initialize_first_block(); - -// advance_to_era(3); -// let era_reward = joint_block_reward() * BLOCKS_PER_ERA as u128; -// let second_era: EraIndex = 2; - -// precompiles() -// .prepare_test( -// TestAccount::Alex, -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::ReadEraReward) -// .write(second_era) -// .build(), -// ) -// .expect_cost(READ_WEIGHT) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(era_reward).build()); -// }); -// } +#[test] +fn read_era_reward_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Check historic era for rewards + let era = 3; + advance_to_era(era + 1); + + let span_index = DAppStaking::::era_reward_span_index(era); + + let era_rewards_span = EraRewards::::get(span_index).expect("Entry must exist."); + let expected_reward = era_rewards_span + .get(era) + .map(|r| r.staker_reward_pool + r.dapp_reward_pool) + .expect("It's history era so it must exist."); + assert!(expected_reward > 0, "Sanity check."); + + precompiles() + .prepare_test( + Alice, + precompile_address(), + PrecompileCall::read_era_reward { era }, + ) + .expect_no_logs() + .execute_returns(expected_reward); + + // Check current era for rewards, must be zero + precompiles() + .prepare_test( + Alice, + precompile_address(), + PrecompileCall::read_era_reward { era: era + 1 }, + ) + .expect_no_logs() + .execute_returns(Balance::zero()); + }); +} -// #[test] -// fn read_era_staked_is_ok() { -// ExternalityBuilder::default().build().execute_with(|| { -// initialize_first_block(); - -// let zero_era = EraIndex::zero(); -// let staked = Balance::zero(); - -// precompiles() -// .prepare_test( -// TestAccount::Alex, -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::ReadEraStaked) -// .write(zero_era) -// .build(), -// ) -// .expect_cost(READ_WEIGHT) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(staked).build()); -// }); -// } +#[test] +fn read_era_staked_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + let (staker_h160, smart_contract, amount) = register_and_stake(); + let anchor_era = ActiveProtocolState::::get().era; + + // 1. Current era stake must be zero, since stake is only valid from the next era. + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::read_era_staked { era: anchor_era }, + ) + .expect_no_logs() + .execute_returns(Balance::zero()); + + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::read_era_staked { + era: anchor_era + 1, + }, + ) + .expect_no_logs() + .execute_returns(amount); + + // 2. Advance to next era, and check next era after the anchor. + advance_to_era(anchor_era + 5); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::read_era_staked { + era: anchor_era + 1, + }, + ) + .expect_no_logs() + .execute_returns(amount); + + // 3. Check era after the next one, must throw an error. + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::read_era_staked { + era: ActiveProtocolState::::get().era + 2, + }, + ) + .expect_no_logs() + .execute_reverts(|output| output == b"Era is in the future"); + }); +} // #[test] // fn register_via_precompile_fails() { From 578d0e2fc6412e04648bbabd1b62a073a6612aca Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Fri, 15 Dec 2023 12:12:46 +0100 Subject: [PATCH 19/34] Test for legacy getters/view functions --- precompiles/dapps-staking-v3/src/lib.rs | 8 +- precompiles/dapps-staking-v3/src/mock.rs | 28 ++-- precompiles/dapps-staking-v3/src/tests.rs | 174 +++++++++++++++++++++- 3 files changed, 195 insertions(+), 15 deletions(-) diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index 3855cefdb7..a751a5f71c 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -46,6 +46,8 @@ use pallet_dapp_staking_v3::{ pub const STAKER_BYTES_LIMIT: u32 = 32; type GetStakerBytesLimit = ConstU32; +pub type DynamicAddress = BoundedBytes; + #[cfg(test)] mod mock; #[cfg(test)] @@ -62,7 +64,7 @@ pub struct PrecompileProtocolState { #[derive(Debug, Clone, solidity::Codec)] pub struct SmartContractV2 { contract_type: u8, - address: BoundedBytes, + address: DynamicAddress, } pub struct DappStakingV3Precompile(PhantomData); @@ -180,7 +182,7 @@ where #[precompile::view] fn read_staked_amount( handle: &mut impl PrecompileHandle, - staker: BoundedBytes, + staker: DynamicAddress, ) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: @@ -211,7 +213,7 @@ where fn read_staked_amount_on_contract( handle: &mut impl PrecompileHandle, contract_h160: Address, - staker: BoundedBytes, + staker: DynamicAddress, ) -> EvmResult { // TODO: benchmark this function so we can measure ref time & PoV correctly // Storage item: ActiveProtocolState: diff --git a/precompiles/dapps-staking-v3/src/mock.rs b/precompiles/dapps-staking-v3/src/mock.rs index 4a651b3927..3ccb3e73db 100644 --- a/precompiles/dapps-staking-v3/src/mock.rs +++ b/precompiles/dapps-staking-v3/src/mock.rs @@ -290,16 +290,12 @@ pub const ALICE: H160 = H160::repeat_byte(0xAA); /// Used to register a smart contract, and stake some funds on it. /// -/// Returns `(staker H160 address, smart contract, staked amount)`. -pub fn register_and_stake() -> ( - H160, - ::SmartContract, - Balance, -) { - let smart_contract = - ::SmartContract::evm(H160::repeat_byte(0xFA)); - - let alice_native = AddressMapper::into_account_id(ALICE); +/// Returns staked amount. +pub fn register_and_stake( + account: H160, + smart_contract: ::SmartContract, +) -> Balance { + let alice_native = AddressMapper::into_account_id(account); // 1. Register smart contract assert_ok!(DappStaking::register( @@ -328,7 +324,17 @@ pub fn register_and_stake() -> ( amount, )); - (ALICE, smart_contract, amount) + amount +} + +/// TODO +pub fn into_dynamic_addresses(address: H160) -> [DynamicAddress; 2] { + [ + address.as_bytes().try_into().unwrap(), + >::as_ref(&AddressMapper::into_account_id(address)) + .try_into() + .unwrap(), + ] } /// Initialize first block. diff --git a/precompiles/dapps-staking-v3/src/tests.rs b/precompiles/dapps-staking-v3/src/tests.rs index 6c20a23c08..0f97866288 100644 --- a/precompiles/dapps-staking-v3/src/tests.rs +++ b/precompiles/dapps-staking-v3/src/tests.rs @@ -120,7 +120,11 @@ fn read_era_staked_is_ok() { ExternalityBuilder::build().execute_with(|| { initialize(); - let (staker_h160, smart_contract, amount) = register_and_stake(); + let staker_h160 = ALICE; + let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract = + ::SmartContract::evm(smart_contract_h160); + let amount = register_and_stake(staker_h160, smart_contract.clone()); let anchor_era = ActiveProtocolState::::get().era; // 1. Current era stake must be zero, since stake is only valid from the next era. @@ -171,6 +175,174 @@ fn read_era_staked_is_ok() { }); } +#[test] +fn read_staked_amount_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + let staker_h160 = ALICE; + let dynamic_addresses = into_dynamic_addresses(staker_h160); + + // 1. Sanity checks - must be zero before anything is staked. + for staker in &dynamic_addresses { + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::read_staked_amount { + staker: staker.clone(), + }, + ) + .expect_no_logs() + .execute_returns(Balance::zero()); + } + + // 2. Stake some amount and check again + let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract = + ::SmartContract::evm(smart_contract_h160); + let amount = register_and_stake(staker_h160, smart_contract.clone()); + for staker in &dynamic_addresses { + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::read_staked_amount { + staker: staker.clone(), + }, + ) + .expect_no_logs() + .execute_returns(amount); + } + + // 3. Advance into next period, it should be reset back to zero + advance_to_next_period(); + for staker in &dynamic_addresses { + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::read_staked_amount { + staker: staker.clone(), + }, + ) + .expect_no_logs() + .execute_returns(Balance::zero()); + } + }); +} + +#[test] +fn read_staked_amount_on_contract_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + let staker_h160 = ALICE; + let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract = + ::SmartContract::evm(smart_contract_h160); + let dynamic_addresses = into_dynamic_addresses(staker_h160); + + // 1. Sanity checks - must be zero before anything is staked. + for staker in &dynamic_addresses { + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::read_staked_amount_on_contract { + contract_h160: smart_contract_h160.into(), + staker: staker.clone(), + }, + ) + .expect_no_logs() + .execute_returns(Balance::zero()); + } + + // 2. Stake some amount and check again + let amount = register_and_stake(staker_h160, smart_contract.clone()); + for staker in &dynamic_addresses { + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::read_staked_amount_on_contract { + contract_h160: smart_contract_h160.into(), + staker: staker.clone(), + }, + ) + .expect_no_logs() + .execute_returns(amount); + } + + // 3. Advance into next period, it should be reset back to zero + advance_to_next_period(); + for staker in &dynamic_addresses { + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::read_staked_amount_on_contract { + contract_h160: smart_contract_h160.into(), + staker: staker.clone(), + }, + ) + .expect_no_logs() + .execute_returns(Balance::zero()); + } + }); +} + +#[test] +fn read_contract_stake_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + let staker_h160 = ALICE; + let smart_contract_h160 = H160::repeat_byte(0xFA); + + // 1. Sanity checks - must be zero before anything is staked. + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::read_contract_stake { + contract_h160: smart_contract_h160.into(), + }, + ) + .expect_no_logs() + .execute_returns(Balance::zero()); + + // 2. Stake some amount and check again + let smart_contract = + ::SmartContract::evm(smart_contract_h160); + let amount = register_and_stake(staker_h160, smart_contract.clone()); + + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::read_contract_stake { + contract_h160: smart_contract_h160.into(), + }, + ) + .expect_no_logs() + .execute_returns(amount); + + // 3. Advance into next period, it should be reset back to zero + advance_to_next_period(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::read_contract_stake { + contract_h160: smart_contract_h160.into(), + }, + ) + .expect_no_logs() + .execute_returns(Balance::zero()); + }); +} + // #[test] // fn register_via_precompile_fails() { // ExternalityBuilder::default() From 4ac288475340059df5ff7531a76d68de3c05cbc5 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Fri, 15 Dec 2023 14:08:53 +0100 Subject: [PATCH 20/34] More legacy tests --- Cargo.lock | 1 + precompiles/dapps-staking-v3/Cargo.toml | 1 + precompiles/dapps-staking-v3/src/lib.rs | 2 +- precompiles/dapps-staking-v3/src/mock.rs | 101 +++--- precompiles/dapps-staking-v3/src/tests.rs | 360 +++++++++++++++++----- 5 files changed, 350 insertions(+), 115 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8fe6436392..3f13fd2bea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7972,6 +7972,7 @@ dependencies = [ name = "pallet-evm-precompile-dapp-staking-v3" version = "0.1.0" dependencies = [ + "assert_matches", "astar-primitives", "derive_more", "fp-evm", diff --git a/precompiles/dapps-staking-v3/Cargo.toml b/precompiles/dapps-staking-v3/Cargo.toml index 1718fd7f9c..99e8599f24 100644 --- a/precompiles/dapps-staking-v3/Cargo.toml +++ b/precompiles/dapps-staking-v3/Cargo.toml @@ -39,6 +39,7 @@ serde = { workspace = true } sha3 = { workspace = true } sp-arithmetic = { workspace = true } sp-io = { workspace = true } +assert_matches = { workspace = true } [features] default = ["std"] diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index a751a5f71c..4f6313e091 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -446,7 +446,7 @@ where /// This call has been deprecated by dApp staking v3. #[precompile::public("set_reward_destination(uint8)")] fn set_reward_destination(_: &mut impl PrecompileHandle, _destination: u8) -> EvmResult { - Err(RevertReason::custom("Call is no longer supported.").into()) + Err(RevertReason::custom("Setting reward destination is no longer supported.").into()) } /// Withdraw staked funds from the unregistered contract diff --git a/precompiles/dapps-staking-v3/src/mock.rs b/precompiles/dapps-staking-v3/src/mock.rs index 3ccb3e73db..9a505e6dff 100644 --- a/precompiles/dapps-staking-v3/src/mock.rs +++ b/precompiles/dapps-staking-v3/src/mock.rs @@ -23,7 +23,7 @@ use frame_support::{ assert_ok, construct_runtime, parameter_types, traits::{ fungible::{Mutate as FunMutate, Unbalanced as FunUnbalanced}, - ConstU128, ConstU64, Hooks, + ConstU128, ConstU64, GenesisBuild, Hooks, }, weights::{RuntimeDbWeight, Weight}, }; @@ -31,7 +31,7 @@ use frame_system::RawOrigin; use pallet_evm::{ AddressMapping, EnsureAddressNever, EnsureAddressRoot, PrecompileResult, PrecompileSet, }; -use sp_arithmetic::fixed_point::FixedU64; +use sp_arithmetic::{fixed_point::FixedU64, Permill}; use sp_core::{H160, H256}; use sp_io::TestExternalities; use sp_runtime::traits::{BlakeTwo256, ConstU32, IdentityLookup}; @@ -42,7 +42,7 @@ use astar_primitives::{ testing::Header, AccountId, Balance, BlockNumber, }; -use pallet_dapp_staking_v3::{EraNumber, PeriodNumber, PriceProvider}; +use pallet_dapp_staking_v3::{EraNumber, PeriodNumber, PriceProvider, TierThreshold}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -246,29 +246,6 @@ impl pallet_dapp_staking_v3::Config for Test { type WeightInfo = pallet_dapp_staking_v3::weights::SubstrateWeight; } -pub struct ExternalityBuilder; -impl ExternalityBuilder { - pub fn build() -> TestExternalities { - let mut storage = frame_system::GenesisConfig::default() - .build_storage::() - .unwrap(); - - let balances = vec![10000; 9] - .into_iter() - .enumerate() - .map(|(idx, amount)| ([idx as u8; 32].into(), amount)) - .collect(); - - pallet_balances::GenesisConfig:: { balances: balances } - .assimilate_storage(&mut storage) - .ok(); - - let mut ext = TestExternalities::from(storage); - ext.execute_with(|| System::set_block_number(1)); - ext - } -} - construct_runtime!( pub struct Test where @@ -284,17 +261,74 @@ construct_runtime!( } ); +pub struct ExternalityBuilder; +impl ExternalityBuilder { + pub fn build() -> TestExternalities { + let mut storage = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + >::assimilate_storage( + &pallet_dapp_staking_v3::GenesisConfig { + reward_portion: vec![ + Permill::from_percent(40), + Permill::from_percent(30), + Permill::from_percent(20), + Permill::from_percent(10), + ], + slot_distribution: vec![ + Permill::from_percent(10), + Permill::from_percent(20), + Permill::from_percent(30), + Permill::from_percent(40), + ], + tier_thresholds: vec![ + TierThreshold::DynamicTvlAmount { + amount: 100, + minimum_amount: 80, + }, + TierThreshold::DynamicTvlAmount { + amount: 50, + minimum_amount: 40, + }, + TierThreshold::DynamicTvlAmount { + amount: 20, + minimum_amount: 20, + }, + TierThreshold::FixedTvlAmount { amount: 10 }, + ], + slots_per_tier: vec![10, 20, 30, 40], + }, + &mut storage, + ) + .ok(); + + let mut ext = TestExternalities::from(storage); + ext.execute_with(|| { + System::set_block_number(1); + + let alice_native = AddressMapper::into_account_id(ALICE); + assert_ok!( + ::Currency::write_balance( + &alice_native, + 1000_000_000_000_000_000_000 as Balance, + ) + ); + }); + ext + } +} + // Utility functions pub const ALICE: H160 = H160::repeat_byte(0xAA); /// Used to register a smart contract, and stake some funds on it. -/// -/// Returns staked amount. pub fn register_and_stake( account: H160, smart_contract: ::SmartContract, -) -> Balance { + amount: Balance, +) { let alice_native = AddressMapper::into_account_id(account); // 1. Register smart contract @@ -305,13 +339,6 @@ pub fn register_and_stake( )); // 2. Lock some amount - assert_ok!( - ::Currency::write_balance( - &alice_native, - 1000_000_000_000_000_000_000 as Balance, - ) - ); - let amount = 1_000_000_000_000; assert_ok!(DappStaking::lock( RawOrigin::Signed(alice_native.clone()).into(), amount, @@ -323,8 +350,6 @@ pub fn register_and_stake( smart_contract.clone(), amount, )); - - amount } /// TODO diff --git a/precompiles/dapps-staking-v3/src/tests.rs b/precompiles/dapps-staking-v3/src/tests.rs index 0f97866288..b353b89dda 100644 --- a/precompiles/dapps-staking-v3/src/tests.rs +++ b/precompiles/dapps-staking-v3/src/tests.rs @@ -20,11 +20,14 @@ extern crate alloc; use crate::{mock::*, *}; use fp_evm::ExitError; use frame_support::assert_ok; +use frame_system::RawOrigin; use precompile_utils::testing::*; use sp_core::H160; use sp_runtime::{traits::Zero, AccountId32, Perbill}; -use pallet_dapp_staking_v3::{AccountLedger, ActiveProtocolState, EraNumber, EraRewards}; +use assert_matches::assert_matches; + +use pallet_dapp_staking_v3::{AccountLedger, ActiveProtocolState, EraNumber, EraRewards, Event}; fn precompiles() -> DappStakingPrecompile { PrecompilesValue::get() @@ -124,7 +127,8 @@ fn read_era_staked_is_ok() { let smart_contract_h160 = H160::repeat_byte(0xFA); let smart_contract = ::SmartContract::evm(smart_contract_h160); - let amount = register_and_stake(staker_h160, smart_contract.clone()); + let amount = 1_000_000_000_000; + register_and_stake(staker_h160, smart_contract.clone(), amount); let anchor_era = ActiveProtocolState::::get().era; // 1. Current era stake must be zero, since stake is only valid from the next era. @@ -201,7 +205,8 @@ fn read_staked_amount_is_ok() { let smart_contract_h160 = H160::repeat_byte(0xFA); let smart_contract = ::SmartContract::evm(smart_contract_h160); - let amount = register_and_stake(staker_h160, smart_contract.clone()); + let amount = 1_000_000_000_000; + register_and_stake(staker_h160, smart_contract.clone(), amount); for staker in &dynamic_addresses { precompiles() .prepare_test( @@ -259,7 +264,8 @@ fn read_staked_amount_on_contract_is_ok() { } // 2. Stake some amount and check again - let amount = register_and_stake(staker_h160, smart_contract.clone()); + let amount = 1_000_000_000_000; + register_and_stake(staker_h160, smart_contract.clone(), amount); for staker in &dynamic_addresses { precompiles() .prepare_test( @@ -315,7 +321,8 @@ fn read_contract_stake_is_ok() { // 2. Stake some amount and check again let smart_contract = ::SmartContract::evm(smart_contract_h160); - let amount = register_and_stake(staker_h160, smart_contract.clone()); + let amount = 1_000_000_000_000; + register_and_stake(staker_h160, smart_contract.clone(), amount); precompiles() .prepare_test( @@ -343,89 +350,290 @@ fn read_contract_stake_is_ok() { }); } -// #[test] -// fn register_via_precompile_fails() { -// ExternalityBuilder::default() -// .with_balances(vec![(TestAccount::Alex.into(), 200 * AST)]) -// .build() -// .execute_with(|| { -// initialize_first_block(); +#[test] +fn register_is_unsupported() { + ExternalityBuilder::build().execute_with(|| { + initialize(); -// precompiles() -// .prepare_test( -// TestAccount::Alex, -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::Register) -// .write(Address(TEST_CONTRACT.clone())) -// .build(), -// ) -// .expect_no_logs() -// .execute_error(ExitError::Other(alloc::borrow::Cow::Borrowed( -// "register via evm precompile is not allowed", -// ))); -// }); -// } + precompiles() + .prepare_test( + ALICE, + precompile_address(), + PrecompileCall::register { + _address: Default::default(), + }, + ) + .expect_no_logs() + .execute_reverts(|output| output == b"register via evm precompile is not allowed"); + }); +} -// #[test] -// fn bond_and_stake_is_ok() { -// ExternalityBuilder::default() -// .with_balances(vec![ -// (TestAccount::Alex.into(), 200 * AST), -// (TestAccount::Bobo.into(), 200 * AST), -// (TestAccount::Dino.into(), 100 * AST), -// ]) -// .build() -// .execute_with(|| { -// initialize_first_block(); +#[test] +fn set_reward_destination_is_unsupported() { + ExternalityBuilder::build().execute_with(|| { + initialize(); -// register_and_verify(TestAccount::Alex, TEST_CONTRACT); + precompiles() + .prepare_test( + ALICE, + precompile_address(), + PrecompileCall::set_reward_destination { _destination: 0 }, + ) + .expect_no_logs() + .execute_reverts(|output| { + output == b"Setting reward destination is no longer supported." + }); + }); +} -// let amount_staked_bobo = 100 * AST; -// bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); +#[test] +fn bond_and_stake_with_two_calls_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); -// let amount_staked_dino = 50 * AST; -// bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); + // Register a dApp for staking + let staker_h160 = ALICE; + let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract = + ::SmartContract::evm(smart_contract_h160); + assert_ok!(DappStaking::register( + RawOrigin::Root.into(), + AddressMapper::into_account_id(staker_h160), + smart_contract.clone() + )); + + // Lock some amount, but not enough to cover the `bond_and_stake` call. + let pre_lock_amount = 500; + let stake_amount = 1_000_000; + assert_ok!(DappStaking::lock( + RawOrigin::Signed(AddressMapper::into_account_id(staker_h160)).into(), + pre_lock_amount, + )); + + // Execute legacy call, expect missing funds to be locked. + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::bond_and_stake { + contract_h160: smart_contract_h160.into(), + amount: stake_amount, + }, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 2); + let additional_lock_amount = stake_amount - pre_lock_amount; + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::Locked { + amount: additional_lock_amount, + .. + } + ); + assert_matches!( + events[1].clone(), + pallet_dapp_staking_v3::Event::Stake { + smart_contract, + amount: stake_amount, + .. + } + ); + }); +} -// contract_era_stake_verify(TEST_CONTRACT, amount_staked_bobo + amount_staked_dino); -// verify_staked_amount(TEST_CONTRACT, TestAccount::Bobo.into(), amount_staked_bobo); -// verify_staked_amount(TEST_CONTRACT, TestAccount::Dino.into(), amount_staked_dino); -// }); -// } +#[test] +fn bond_and_stake_with_single_call_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); -// #[test] -// fn unbond_and_unstake_is_ok() { -// ExternalityBuilder::default() -// .with_balances(vec![ -// (TestAccount::Alex.into(), 200 * AST), -// (TestAccount::Bobo.into(), 200 * AST), -// (TestAccount::Dino.into(), 100 * AST), -// ]) -// .build() -// .execute_with(|| { -// initialize_first_block(); + // Register a dApp for staking + let staker_h160 = ALICE; + let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract = + ::SmartContract::evm(smart_contract_h160); + assert_ok!(DappStaking::register( + RawOrigin::Root.into(), + AddressMapper::into_account_id(staker_h160), + smart_contract.clone() + )); + + // Lock enough amount to cover `bond_and_stake` call. + let amount = 3000; + assert_ok!(DappStaking::lock( + RawOrigin::Signed(AddressMapper::into_account_id(staker_h160)).into(), + amount, + )); + + // Execute legacy call, expect only single stake to be executed. + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::bond_and_stake { + contract_h160: smart_contract_h160.into(), + amount, + }, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 1); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::Stake { + smart_contract, + amount, + .. + } + ); + }); +} -// // register new contract by Alex -// let developer = TestAccount::Alex.into(); -// register_and_verify(developer, TEST_CONTRACT); +#[test] +fn unbond_and_unstake_with_two_calls_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); -// let amount_staked_bobo = 100 * AST; -// bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); -// let amount_staked_dino = 50 * AST; -// bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); + // Register a dApp for staking + let staker_h160 = ALICE; + let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract = + ::SmartContract::evm(smart_contract_h160); + let amount = 1_000_000_000_000; + register_and_stake(staker_h160, smart_contract.clone(), amount); -// // Bobo unstakes all -// let era = 2; -// advance_to_era(era); -// unbond_unstake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); + // Execute legacy call, expect funds to first unstaked, and then unlocked + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::unbond_and_unstake { + contract_h160: smart_contract_h160.into(), + amount, + }, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 2); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::Unstake { + smart_contract, + amount, + .. + } + ); + assert_matches!( + events[1].clone(), + pallet_dapp_staking_v3::Event::Unlocking { amount, .. } + ); + }); +} -// contract_era_stake_verify(TEST_CONTRACT, amount_staked_dino); -// verify_staked_amount(TEST_CONTRACT, TestAccount::Dino, amount_staked_dino); +#[test] +fn unbond_and_unstake_with_single_calls_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); -// // withdraw unbonded funds -// advance_to_era(era + UNBONDING_PERIOD + 1); -// withdraw_unbonded_verify(TestAccount::Bobo); -// }); -// } + // Register a dApp for staking + let staker_h160 = ALICE; + let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract = + ::SmartContract::evm(smart_contract_h160); + let amount = 1_000_000_000_000; + register_and_stake(staker_h160, smart_contract.clone(), amount); + + // Unstake the entire amount, so only unlock call is expected. + assert_ok!(DappStaking::unstake( + RawOrigin::Signed(AddressMapper::into_account_id(staker_h160)).into(), + smart_contract.clone(), + amount, + )); + + // Execute legacy call, expect funds to be unlocked + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::unbond_and_unstake { + contract_h160: smart_contract_h160.into(), + amount, + }, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 1); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::Unlocking { amount, .. } + ); + }); +} + +#[test] +fn withdraw_unbonded_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Register a dApp for staking + let staker_h160 = ALICE; + let staker_native = AddressMapper::into_account_id(staker_h160); + let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract = + ::SmartContract::evm(smart_contract_h160); + let amount = 1_000_000_000_000; + register_and_stake(staker_h160, smart_contract.clone(), amount); + + // Unlock some amount + assert_ok!(DappStaking::unstake( + RawOrigin::Signed(staker_native.clone()).into(), + smart_contract.clone(), + amount, + )); + let unlock_amount = amount / 7; + assert_ok!(DappStaking::unlock( + RawOrigin::Signed(staker_native.clone()).into(), + unlock_amount, + )); + + // Advance enough into time so unlocking chunk can be claimed + let unlock_block = Ledger::::get(&staker_native).unlocking[0].unlock_block; + run_to_block(unlock_block); + + // Execute legacy call, expect funds to be unlocked + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::withdraw_unbonded {}, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 1); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::ClaimedUnlocked { + amount: unlock_amount, + .. + } + ); + }); +} // #[test] // fn claim_dapp_is_ok() { From 96e7c2b727e623e5639d631259878745cbd463c1 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Fri, 15 Dec 2023 14:31:02 +0100 Subject: [PATCH 21/34] v1 interface covered with tests --- precompiles/dapps-staking-v3/src/tests.rs | 240 +++++++++++++++++++++- 1 file changed, 239 insertions(+), 1 deletion(-) diff --git a/precompiles/dapps-staking-v3/src/tests.rs b/precompiles/dapps-staking-v3/src/tests.rs index b353b89dda..8969437421 100644 --- a/precompiles/dapps-staking-v3/src/tests.rs +++ b/precompiles/dapps-staking-v3/src/tests.rs @@ -612,7 +612,7 @@ fn withdraw_unbonded_is_ok() { let unlock_block = Ledger::::get(&staker_native).unlocking[0].unlock_block; run_to_block(unlock_block); - // Execute legacy call, expect funds to be unlocked + // Execute legacy call, expect unlocked funds to be claimed back System::reset_events(); precompiles() .prepare_test( @@ -635,6 +635,244 @@ fn withdraw_unbonded_is_ok() { }); } +#[test] +fn claim_dapp_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Register a dApp for staking + let staker_h160 = ALICE; + let staker_native = AddressMapper::into_account_id(staker_h160); + let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract = + ::SmartContract::evm(smart_contract_h160); + let amount = 1_000_000_000_000; + register_and_stake(staker_h160, smart_contract.clone(), amount); + + // Advance enough eras so we can claim dApp reward + advance_to_era(3); + let claim_era = 2; + + // Execute legacy call, expect dApp rewards to be claimed + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::claim_dapp { + contract_h160: smart_contract_h160.into(), + era: claim_era, + }, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 1); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::DAppReward { + amount: claim_era, + smart_contract, + .. + } + ); + }); +} + +#[test] +fn claim_staker_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Register a dApp for staking + let staker_h160 = ALICE; + let staker_native = AddressMapper::into_account_id(staker_h160); + let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract = + ::SmartContract::evm(smart_contract_h160); + let amount = 1_000_000_000_000; + register_and_stake(staker_h160, smart_contract.clone(), amount); + + // Advance enough eras so we can claim dApp reward + advance_to_era(5); + let number_of_claims = (2..=4).count(); + + // Execute legacy call, expect dApp rewards to be claimed + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::claim_staker { + contract_h160: smart_contract_h160.into(), + }, + ) + .expect_no_logs() + .execute_returns(true); + + // We expect multiple reward to be claimed + let events = dapp_staking_events(); + assert_eq!(events.len(), number_of_claims as usize); + for era in 2..=4 { + assert_matches!( + events[era as usize - 2].clone(), + pallet_dapp_staking_v3::Event::Reward { era, .. } + ); + } + }); +} + +#[test] +fn withdraw_from_unregistered_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Register a dApp for staking + let staker_h160 = ALICE; + let staker_native = AddressMapper::into_account_id(staker_h160); + let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract = + ::SmartContract::evm(smart_contract_h160); + let amount = 1_000_000_000_000; + register_and_stake(staker_h160, smart_contract.clone(), amount); + + // Unregister the dApp + assert_ok!(DappStaking::unregister( + RawOrigin::Root.into(), + smart_contract.clone() + )); + + // Execute legacy call, expect funds to be unstaked & withdrawn + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::withdraw_from_unregistered { + contract_h160: smart_contract_h160.into(), + }, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 1); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::UnstakeFromUnregistered { + smart_contract, + amount, + .. + } + ); + }); +} + +#[test] +fn nomination_transfer_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Register the first dApp, and stke on it. + let staker_h160 = ALICE; + let staker_native = AddressMapper::into_account_id(staker_h160); + let smart_contract_h160_1 = H160::repeat_byte(0xFA); + let smart_contract_1 = + ::SmartContract::evm(smart_contract_h160_1); + let amount = 1_000_000_000_000; + register_and_stake(staker_h160, smart_contract_1.clone(), amount); + + // Register the second dApp. + let smart_contract_h160_2 = H160::repeat_byte(0xBF); + let smart_contract_2 = + ::SmartContract::evm(smart_contract_h160_2); + assert_ok!(DappStaking::register( + RawOrigin::Root.into(), + staker_native.clone(), + smart_contract_2.clone() + )); + + // 1st scenario - transfer enough amount from the first to second dApp to cover the stake, + // but not enough for full unstake. + let minimum_stake_amount: Balance = + ::MinimumStakeAmount::get(); + + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::nomination_transfer { + origin_contract_h160: smart_contract_h160_1.into(), + amount: minimum_stake_amount, + target_contract_h160: smart_contract_h160_2.into(), + }, + ) + .expect_no_logs() + .execute_returns(true); + + // We expect the same amount to be staked on the second contract + let events = dapp_staking_events(); + assert_eq!(events.len(), 2); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::Unstake { + smart_contract: smart_contract_1, + amount: minimum_stake_amount, + .. + } + ); + assert_matches!( + events[1].clone(), + pallet_dapp_staking_v3::Event::Stake { + smart_contract: smart_contract_2, + amount: minimum_stake_amount, + .. + } + ); + + // 2nd scenario - transfer almost the entire amount from the first to second dApp. + // The amount is large enough to trigger full unstake of the first contract. + let unstake_amount = amount - minimum_stake_amount - 1; + let expected_stake_unstake_amount = amount - minimum_stake_amount; + + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::nomination_transfer { + origin_contract_h160: smart_contract_h160_1.into(), + amount: unstake_amount, + target_contract_h160: smart_contract_h160_2.into(), + }, + ) + .expect_no_logs() + .execute_returns(true); + + // We expect the same amount to be staked on the second contract + let events = dapp_staking_events(); + assert_eq!(events.len(), 2); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::Unstake { + smart_contract: smart_contract_1, + amount: expected_stake_unstake_amount, + .. + } + ); + assert_matches!( + events[1].clone(), + pallet_dapp_staking_v3::Event::Stake { + smart_contract: smart_contract_2, + amount: expected_stake_unstake_amount, + .. + } + ); + }); +} + // #[test] // fn claim_dapp_is_ok() { // ExternalityBuilder::default() From fadf92824fe40c0c0757e46bd61725b34106f00e Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Fri, 15 Dec 2023 14:53:26 +0100 Subject: [PATCH 22/34] Minor refactor, organization, improvements --- precompiles/dapps-staking-v3/Cargo.toml | 2 +- precompiles/dapps-staking-v3/src/lib.rs | 65 +-- .../dapps-staking-v3/src/{ => test}/mock.rs | 19 +- precompiles/dapps-staking-v3/src/test/mod.rs | 20 + .../src/{tests.rs => test/tests_v1.rs} | 548 +----------------- 5 files changed, 86 insertions(+), 568 deletions(-) rename precompiles/dapps-staking-v3/src/{ => test}/mock.rs (96%) create mode 100644 precompiles/dapps-staking-v3/src/test/mod.rs rename precompiles/dapps-staking-v3/src/{tests.rs => test/tests_v1.rs} (57%) diff --git a/precompiles/dapps-staking-v3/Cargo.toml b/precompiles/dapps-staking-v3/Cargo.toml index 99e8599f24..aef927a3ed 100644 --- a/precompiles/dapps-staking-v3/Cargo.toml +++ b/precompiles/dapps-staking-v3/Cargo.toml @@ -31,6 +31,7 @@ fp-evm = { workspace = true } pallet-evm = { workspace = true } [dev-dependencies] +assert_matches = { workspace = true } derive_more = { workspace = true } pallet-balances = { workspace = true, features = ["std"] } pallet-timestamp = { workspace = true } @@ -39,7 +40,6 @@ serde = { workspace = true } sha3 = { workspace = true } sp-arithmetic = { workspace = true } sp-io = { workspace = true } -assert_matches = { workspace = true } [features] default = ["std"] diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index 4f6313e091..7728a9f22b 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -49,9 +49,7 @@ type GetStakerBytesLimit = ConstU32; pub type DynamicAddress = BoundedBytes; #[cfg(test)] -mod mock; -#[cfg(test)] -mod tests; +mod test; /// Helper struct used to encode protocol state. #[derive(Debug, Clone, solidity::Codec)] @@ -61,6 +59,7 @@ pub struct PrecompileProtocolState { subperiod: u8, } +/// Helper struct used to encode different smart contract types for the v2 interface. #[derive(Debug, Clone, solidity::Codec)] pub struct SmartContractV2 { contract_type: u8, @@ -226,14 +225,14 @@ where + SingularStakingInfo::max_encoded_len(), )?; - // parse contract address - let contract_id = Self::decode_smart_contract_v1(contract_h160.into())?; + let smart_contract = + ::SmartContract::evm(contract_h160.into()); // parse the staker account let staker = Self::parse_input_address(staker.into())?; // Get staking info for the staker/contract combination - let staking_info = StakerInfo::::get(&staker, &contract_id).unwrap_or_default(); + let staking_info = StakerInfo::::get(&staker, &smart_contract).unwrap_or_default(); log::trace!(target: "ds-precompile", "read_staked_amount_on_contract for account:{:?}, staking_info: {:?}", staker, staking_info); // Ensure that the staking info is checked against the current period (stakes from past periods are reset) @@ -267,11 +266,11 @@ where + ContractStakeAmount::max_encoded_len(), )?; - // parse input parameters for pallet-dapps-staking call - let contract_id = Self::decode_smart_contract_v1(contract_h160.into())?; + let smart_contract = + ::SmartContract::evm(contract_h160.into()); let current_period_number = ActiveProtocolState::::get().period_number(); - let dapp_info = match IntegratedDApps::::get(&contract_id) { + let dapp_info = match IntegratedDApps::::get(&smart_contract) { Some(dapp_info) => dapp_info, None => { // If the contract is not registered, return 0 to keep the legacy behavior. @@ -314,7 +313,8 @@ where + ::SmartContract::max_encoded_len(), )?; - let smart_contract = Self::decode_smart_contract_v1(contract_h160.into())?; + let smart_contract = + ::SmartContract::evm(contract_h160.into()); log::trace!(target: "ds-precompile", "bond_and_stake {:?}, {:?}", smart_contract, amount); // Read total locked & staked amounts @@ -361,7 +361,8 @@ where + SingularStakingInfo::max_encoded_len(), )?; - let smart_contract = Self::decode_smart_contract_v1(contract_h160.into())?; + let smart_contract = + ::SmartContract::evm(contract_h160.into()); let origin = R::AddressMapping::into_account_id(handle.context().caller); log::trace!(target: "ds-precompile", "unbond_and_unstake {:?}, {:?}", smart_contract, amount); @@ -403,7 +404,8 @@ where contract_h160: Address, era: u128, ) -> EvmResult { - let smart_contract = Self::decode_smart_contract_v1(contract_h160.into())?; + let smart_contract = + ::SmartContract::evm(contract_h160.into()); // parse era let era = era @@ -428,11 +430,10 @@ where /// /// Smart contract argument is legacy & is ignored in the new implementation. #[precompile::public("claim_staker(address)")] - fn claim_staker(handle: &mut impl PrecompileHandle, contract_h160: Address) -> EvmResult { - // Parse smart contract to keep in line with the legacy behavior. - let _smart_contract = Self::decode_smart_contract_v1(contract_h160.into())?; - log::trace!(target: "ds-precompile", "claim_staker {:?}", _smart_contract); - + fn claim_staker( + handle: &mut impl PrecompileHandle, + _contract_h160: Address, + ) -> EvmResult { let origin = R::AddressMapping::into_account_id(handle.context().caller); let call = pallet_dapp_staking_v3::Call::::claim_staker_rewards {}; @@ -455,7 +456,8 @@ where handle: &mut impl PrecompileHandle, contract_h160: Address, ) -> EvmResult { - let smart_contract = Self::decode_smart_contract_v1(contract_h160.into())?; + let smart_contract = + ::SmartContract::evm(contract_h160.into()); log::trace!(target: "ds-precompile", "withdraw_from_unregistered {:?}", smart_contract); let origin = R::AddressMapping::into_account_id(handle.context().caller); @@ -484,8 +486,10 @@ where + SingularStakingInfo::max_encoded_len(), )?; - let origin_smart_contract = Self::decode_smart_contract_v1(origin_contract_h160.into())?; - let target_smart_contract = Self::decode_smart_contract_v1(target_contract_h160.into())?; + let origin_smart_contract = + ::SmartContract::evm(origin_contract_h160.into()); + let target_smart_contract = + ::SmartContract::evm(target_contract_h160.into()); log::trace!(target: "ds-precompile", "nomination_transfer {:?} {:?} {:?}", origin_smart_contract, amount, target_smart_contract); // Find out how much staker has staked on the origin contract @@ -596,7 +600,7 @@ where smart_contract: SmartContractV2, amount: Balance, ) -> EvmResult { - let smart_contract = Self::decode_smart_contract_v2(smart_contract)?; + let smart_contract = Self::decode_smart_contract(smart_contract)?; // Prepare call & dispatch it let origin = R::AddressMapping::into_account_id(handle.context().caller); @@ -616,7 +620,7 @@ where smart_contract: SmartContractV2, amount: Balance, ) -> EvmResult { - let smart_contract = Self::decode_smart_contract_v2(smart_contract)?; + let smart_contract = Self::decode_smart_contract(smart_contract)?; // Prepare call & dispatch it let origin = R::AddressMapping::into_account_id(handle.context().caller); @@ -646,7 +650,7 @@ where handle: &mut impl PrecompileHandle, smart_contract: SmartContractV2, ) -> EvmResult { - let smart_contract = Self::decode_smart_contract_v2(smart_contract)?; + let smart_contract = Self::decode_smart_contract(smart_contract)?; // Prepare call & dispatch it let origin = R::AddressMapping::into_account_id(handle.context().caller); @@ -664,7 +668,7 @@ where smart_contract: SmartContractV2, era: U256, ) -> EvmResult { - let smart_contract = Self::decode_smart_contract_v2(smart_contract)?; + let smart_contract = Self::decode_smart_contract(smart_contract)?; let era = era .try_into() .map_err::(|_| RevertReason::value_is_too_large("Era number.").into()) @@ -687,7 +691,7 @@ where handle: &mut impl PrecompileHandle, smart_contract: SmartContractV2, ) -> EvmResult { - let smart_contract = Self::decode_smart_contract_v2(smart_contract)?; + let smart_contract = Self::decode_smart_contract(smart_contract)?; // Prepare call & dispatch it let origin = R::AddressMapping::into_account_id(handle.context().caller); @@ -720,17 +724,8 @@ where // Utility functions - /// Helper method to decode type SmartContract enum for v1 calls - pub fn decode_smart_contract_v1( - contract_h160: H160, - ) -> EvmResult<::SmartContract> { - Ok(::SmartContract::evm( - contract_h160, - )) - } - /// Helper method to decode smart contract struct for v2 calls - pub fn decode_smart_contract_v2( + pub fn decode_smart_contract( smart_contract: SmartContractV2, ) -> EvmResult<::SmartContract> { let smart_contract = match smart_contract.contract_type { diff --git a/precompiles/dapps-staking-v3/src/mock.rs b/precompiles/dapps-staking-v3/src/test/mock.rs similarity index 96% rename from precompiles/dapps-staking-v3/src/mock.rs rename to precompiles/dapps-staking-v3/src/test/mock.rs index 9a505e6dff..c3e15962b2 100644 --- a/precompiles/dapps-staking-v3/src/mock.rs +++ b/precompiles/dapps-staking-v3/src/test/mock.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . -use super::*; +use crate::*; use fp_evm::{IsPrecompileResult, Precompile}; use frame_support::{ @@ -319,6 +319,10 @@ impl ExternalityBuilder { } } +pub fn precompiles() -> DappStakingPrecompile { + PrecompilesValue::get() +} + // Utility functions pub const ALICE: H160 = H160::repeat_byte(0xAA); @@ -397,11 +401,6 @@ pub(crate) fn advance_to_era(era: EraNumber) { } } -/// Advance blocks until next era has been reached. -pub(crate) fn advance_to_next_era() { - advance_to_era(ActiveProtocolState::::get().era + 1); -} - /// Advance blocks until the specified period has been reached. /// /// Function has no effect if period is already passed. @@ -417,14 +416,6 @@ pub(crate) fn advance_to_next_period() { advance_to_period(ActiveProtocolState::::get().period_number() + 1); } -/// Advance blocks until next period type has been reached. -pub(crate) fn advance_to_next_subperiod() { - let subperiod = ActiveProtocolState::::get().subperiod(); - while ActiveProtocolState::::get().subperiod() == subperiod { - run_for_blocks(1); - } -} - // Return all dApp staking events from the event buffer. pub fn dapp_staking_events() -> Vec> { System::events() diff --git a/precompiles/dapps-staking-v3/src/test/mod.rs b/precompiles/dapps-staking-v3/src/test/mod.rs new file mode 100644 index 0000000000..02cfb3aef3 --- /dev/null +++ b/precompiles/dapps-staking-v3/src/test/mod.rs @@ -0,0 +1,20 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +mod mock; +mod tests_v1; diff --git a/precompiles/dapps-staking-v3/src/tests.rs b/precompiles/dapps-staking-v3/src/test/tests_v1.rs similarity index 57% rename from precompiles/dapps-staking-v3/src/tests.rs rename to precompiles/dapps-staking-v3/src/test/tests_v1.rs index 8969437421..b49e4ce87e 100644 --- a/precompiles/dapps-staking-v3/src/tests.rs +++ b/precompiles/dapps-staking-v3/src/test/tests_v1.rs @@ -17,21 +17,16 @@ // along with Astar. If not, see . extern crate alloc; -use crate::{mock::*, *}; -use fp_evm::ExitError; +use crate::{test::mock::*, *}; use frame_support::assert_ok; use frame_system::RawOrigin; use precompile_utils::testing::*; use sp_core::H160; -use sp_runtime::{traits::Zero, AccountId32, Perbill}; +use sp_runtime::traits::Zero; use assert_matches::assert_matches; -use pallet_dapp_staking_v3::{AccountLedger, ActiveProtocolState, EraNumber, EraRewards, Event}; - -fn precompiles() -> DappStakingPrecompile { - PrecompilesValue::get() -} +use pallet_dapp_staking_v3::{ActiveProtocolState, EraNumber, EraRewards}; #[test] fn read_current_era_is_ok() { @@ -430,17 +425,17 @@ fn bond_and_stake_with_two_calls_is_ok() { assert_matches!( events[0].clone(), pallet_dapp_staking_v3::Event::Locked { - amount: additional_lock_amount, + amount, .. - } + } if amount == additional_lock_amount ); assert_matches!( events[1].clone(), pallet_dapp_staking_v3::Event::Stake { smart_contract, - amount: stake_amount, + amount, .. - } + } if smart_contract == smart_contract && amount == stake_amount ); }); } @@ -490,7 +485,7 @@ fn bond_and_stake_with_single_call_is_ok() { smart_contract, amount, .. - } + } if smart_contract == smart_contract && amount == amount ); }); } @@ -530,11 +525,11 @@ fn unbond_and_unstake_with_two_calls_is_ok() { smart_contract, amount, .. - } + }if smart_contract == smart_contract && amount == amount ); assert_matches!( events[1].clone(), - pallet_dapp_staking_v3::Event::Unlocking { amount, .. } + pallet_dapp_staking_v3::Event::Unlocking { amount, .. } if amount == amount ); }); } @@ -577,7 +572,7 @@ fn unbond_and_unstake_with_single_calls_is_ok() { assert_eq!(events.len(), 1); assert_matches!( events[0].clone(), - pallet_dapp_staking_v3::Event::Unlocking { amount, .. } + pallet_dapp_staking_v3::Event::Unlocking { amount, .. } if amount == amount ); }); } @@ -628,9 +623,9 @@ fn withdraw_unbonded_is_ok() { assert_matches!( events[0].clone(), pallet_dapp_staking_v3::Event::ClaimedUnlocked { - amount: unlock_amount, + amount, .. - } + } if amount == unlock_amount ); }); } @@ -642,7 +637,6 @@ fn claim_dapp_is_ok() { // Register a dApp for staking let staker_h160 = ALICE; - let staker_native = AddressMapper::into_account_id(staker_h160); let smart_contract_h160 = H160::repeat_byte(0xFA); let smart_contract = ::SmartContract::evm(smart_contract_h160); @@ -672,10 +666,10 @@ fn claim_dapp_is_ok() { assert_matches!( events[0].clone(), pallet_dapp_staking_v3::Event::DAppReward { - amount: claim_era, + era, smart_contract, .. - } + } if era as u128 == claim_era && smart_contract == smart_contract ); }); } @@ -687,7 +681,6 @@ fn claim_staker_is_ok() { // Register a dApp for staking let staker_h160 = ALICE; - let staker_native = AddressMapper::into_account_id(staker_h160); let smart_contract_h160 = H160::repeat_byte(0xFA); let smart_contract = ::SmartContract::evm(smart_contract_h160); @@ -705,7 +698,7 @@ fn claim_staker_is_ok() { staker_h160, precompile_address(), PrecompileCall::claim_staker { - contract_h160: smart_contract_h160.into(), + _contract_h160: smart_contract_h160.into(), }, ) .expect_no_logs() @@ -717,7 +710,7 @@ fn claim_staker_is_ok() { for era in 2..=4 { assert_matches!( events[era as usize - 2].clone(), - pallet_dapp_staking_v3::Event::Reward { era, .. } + pallet_dapp_staking_v3::Event::Reward { era, .. } if era == era ); } }); @@ -730,7 +723,6 @@ fn withdraw_from_unregistered_is_ok() { // Register a dApp for staking let staker_h160 = ALICE; - let staker_native = AddressMapper::into_account_id(staker_h160); let smart_contract_h160 = H160::repeat_byte(0xFA); let smart_contract = ::SmartContract::evm(smart_contract_h160); @@ -764,7 +756,7 @@ fn withdraw_from_unregistered_is_ok() { smart_contract, amount, .. - } + } if smart_contract == smart_contract && amount == amount ); }); } @@ -818,18 +810,18 @@ fn nomination_transfer_is_ok() { assert_matches!( events[0].clone(), pallet_dapp_staking_v3::Event::Unstake { - smart_contract: smart_contract_1, - amount: minimum_stake_amount, + smart_contract, + amount, .. - } + } if smart_contract == smart_contract_1 && amount == minimum_stake_amount ); assert_matches!( events[1].clone(), pallet_dapp_staking_v3::Event::Stake { - smart_contract: smart_contract_2, - amount: minimum_stake_amount, + smart_contract, + amount, .. - } + } if smart_contract == smart_contract_2 && amount == minimum_stake_amount ); // 2nd scenario - transfer almost the entire amount from the first to second dApp. @@ -857,498 +849,18 @@ fn nomination_transfer_is_ok() { assert_matches!( events[0].clone(), pallet_dapp_staking_v3::Event::Unstake { - smart_contract: smart_contract_1, - amount: expected_stake_unstake_amount, + smart_contract, + amount, .. - } + } if smart_contract == smart_contract_1 && amount == expected_stake_unstake_amount ); assert_matches!( events[1].clone(), pallet_dapp_staking_v3::Event::Stake { - smart_contract: smart_contract_2, - amount: expected_stake_unstake_amount, + smart_contract, + amount, .. - } + } if smart_contract == smart_contract_2 && amount == expected_stake_unstake_amount ); }); } - -// #[test] -// fn claim_dapp_is_ok() { -// ExternalityBuilder::default() -// .with_balances(vec![ -// (TestAccount::Alex.into(), 200 * AST), -// (TestAccount::Bobo.into(), 200 * AST), -// (TestAccount::Dino.into(), 200 * AST), -// ]) -// .build() -// .execute_with(|| { -// initialize_first_block(); - -// // register new contract by Alex -// let developer = TestAccount::Alex; -// register_and_verify(developer, TEST_CONTRACT); - -// let stake_amount_total = 300 * AST; -// let ratio_bobo = Perbill::from_rational(3u32, 5u32); -// let ratio_dino = Perbill::from_rational(2u32, 5u32); -// let amount_staked_bobo = ratio_bobo * stake_amount_total; -// bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); - -// let amount_staked_dino = ratio_dino * stake_amount_total; -// bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); - -// // advance era and claim reward -// let era = 5; -// advance_to_era(era); -// claim_dapp_and_verify(TEST_CONTRACT, era - 1); - -// //check that the reward is payed out to the developer -// let developer_reward = DAPP_BLOCK_REWARD * BLOCKS_PER_ERA as Balance; -// assert_eq!( -// ::Currency::free_balance( -// &TestAccount::Alex.into() -// ), -// (200 * AST) + developer_reward - REGISTER_DEPOSIT -// ); -// }); -// } - -// #[test] -// fn claim_staker_is_ok() { -// ExternalityBuilder::default() -// .with_balances(vec![ -// (TestAccount::Alex.into(), 200 * AST), -// (TestAccount::Bobo.into(), 200 * AST), -// (TestAccount::Dino.into(), 200 * AST), -// ]) -// .build() -// .execute_with(|| { -// initialize_first_block(); - -// // register new contract by Alex -// let developer = TestAccount::Alex; -// register_and_verify(developer, TEST_CONTRACT); - -// let stake_amount_total = 300 * AST; -// let ratio_bobo = Perbill::from_rational(3u32, 5u32); -// let ratio_dino = Perbill::from_rational(2u32, 5u32); -// let amount_staked_bobo = ratio_bobo * stake_amount_total; -// bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); - -// let amount_staked_dino = ratio_dino * stake_amount_total; -// bond_stake_and_verify(TestAccount::Dino, TEST_CONTRACT, amount_staked_dino); - -// // advance era and claim reward -// advance_to_era(5); - -// let stakers_reward = STAKER_BLOCK_REWARD * BLOCKS_PER_ERA as Balance; - -// // Ensure that all rewards can be claimed for the first staker -// for era in 1..DappsStaking::current_era() as Balance { -// claim_staker_and_verify(TestAccount::Bobo, TEST_CONTRACT); -// assert_eq!( -// ::Currency::free_balance( -// &TestAccount::Bobo.into() -// ), -// (200 * AST) + ratio_bobo * stakers_reward * era -// ); -// } - -// // Repeat the same thing for the second staker -// for era in 1..DappsStaking::current_era() as Balance { -// claim_staker_and_verify(TestAccount::Dino, TEST_CONTRACT); -// assert_eq!( -// ::Currency::free_balance( -// &TestAccount::Dino.into() -// ), -// (200 * AST) + ratio_dino * stakers_reward * era -// ); -// } -// }); -// } - -// #[test] -// fn set_reward_destination() { -// ExternalityBuilder::default() -// .with_balances(vec![ -// (TestAccount::Alex.into(), 200 * AST), -// (TestAccount::Bobo.into(), 200 * AST), -// ]) -// .build() -// .execute_with(|| { -// initialize_first_block(); -// // register contract and stake it -// register_and_verify(TestAccount::Alex.into(), TEST_CONTRACT); - -// // bond & stake the origin contract -// bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, 100 * AST); - -// // change destinations and verfiy it was successful -// set_reward_destination_verify(TestAccount::Bobo.into(), RewardDestination::FreeBalance); -// set_reward_destination_verify( -// TestAccount::Bobo.into(), -// RewardDestination::StakeBalance, -// ); -// set_reward_destination_verify(TestAccount::Bobo.into(), RewardDestination::FreeBalance); -// }); -// } - -// #[test] -// fn withdraw_from_unregistered() { -// ExternalityBuilder::default() -// .with_balances(vec![ -// (TestAccount::Alex.into(), 200 * AST), -// (TestAccount::Bobo.into(), 200 * AST), -// ]) -// .build() -// .execute_with(|| { -// initialize_first_block(); - -// // register new contract by Alex -// let developer = TestAccount::Alex.into(); -// register_and_verify(developer, TEST_CONTRACT); - -// let amount_staked_bobo = 100 * AST; -// bond_stake_and_verify(TestAccount::Bobo, TEST_CONTRACT, amount_staked_bobo); - -// let contract_id = -// decode_smart_contract_v1_from_array(TEST_CONTRACT.clone().to_fixed_bytes()).unwrap(); -// assert_ok!(DappsStaking::unregister(RuntimeOrigin::root(), contract_id)); - -// withdraw_from_unregistered_verify(TestAccount::Bobo.into(), TEST_CONTRACT); -// }); -// } - -// #[test] -// fn nomination_transfer() { -// ExternalityBuilder::default() -// .with_balances(vec![ -// (TestAccount::Alex.into(), 200 * AST), -// (TestAccount::Dino.into(), 200 * AST), -// (TestAccount::Bobo.into(), 200 * AST), -// ]) -// .build() -// .execute_with(|| { -// initialize_first_block(); - -// // register two contracts for nomination transfer test -// let origin_contract = H160::repeat_byte(0x09); -// let target_contract = H160::repeat_byte(0x0A); -// register_and_verify(TestAccount::Alex.into(), origin_contract); -// register_and_verify(TestAccount::Dino.into(), target_contract); - -// // bond & stake the origin contract -// let amount_staked_bobo = 100 * AST; -// bond_stake_and_verify(TestAccount::Bobo, origin_contract, amount_staked_bobo); - -// // transfer nomination and ensure it was successful -// nomination_transfer_verify( -// TestAccount::Bobo, -// origin_contract, -// 10 * AST, -// target_contract, -// ); -// }); -// } - -// // **************************************************************************************************** -// // Helper functions -// // **************************************************************************************************** - -// /// helper function to register and verify if registration is valid -// fn register_and_verify(developer: TestAccount, contract: H160) { -// let smart_contract = -// decode_smart_contract_v1_from_array(contract.clone().to_fixed_bytes()).unwrap(); -// DappsStaking::register( -// RuntimeOrigin::root(), -// developer.clone().into(), -// smart_contract, -// ) -// .unwrap(); - -// // check the storage after the register -// let dev_account_id: AccountId32 = developer.into(); -// let smart_contract_bytes = -// (DappsStaking::registered_contract(dev_account_id).unwrap_or_default()).encode(); - -// assert_eq!( -// // 0-th byte is enum value discriminator -// smart_contract_bytes[1..21], -// contract.to_fixed_bytes() -// ); -// } - -// /// helper function to read ledger storage item -// fn read_staked_amount_h160_verify(staker: TestAccount, amount: u128) { -// precompiles() -// .prepare_test( -// staker.clone(), -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::ReadStakedAmount) -// .write(Bytes( -// Into::::into(staker.clone()).to_fixed_bytes().to_vec(), -// )) -// .build(), -// ) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(amount).build()); -// } - -// /// helper function to read ledger storage item for ss58 account -// fn read_staked_amount_ss58_verify(staker: TestAccount, amount: u128) { -// let staker_acc_id: AccountId32 = staker.clone().into(); - -// precompiles() -// .prepare_test( -// staker.clone(), -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::ReadStakedAmount) -// .write(Bytes(staker_acc_id.encode())) -// .build(), -// ) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(amount).build()); -// } - -// /// helper function to bond, stake and verify if resulet is OK -// fn bond_stake_and_verify(staker: TestAccount, contract: H160, amount: u128) { -// precompiles() -// .prepare_test( -// staker.clone(), -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::BondAndStake) -// .write(Address(contract.clone())) -// .write(amount) -// .build(), -// ) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(true).build()); - -// read_staked_amount_h160_verify(staker.clone(), amount); -// read_staked_amount_ss58_verify(staker, amount); -// } - -// /// helper function to unbond, unstake and verify if result is OK -// fn unbond_unstake_and_verify(staker: TestAccount, contract: H160, amount: u128) { -// precompiles() -// .prepare_test( -// staker.clone(), -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::UnbondAndUnstake) -// .write(Address(contract.clone())) -// .write(amount) -// .build(), -// ) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(true).build()); -// } - -// /// helper function to withdraw unstaked funds and verify if result is OK -// fn withdraw_unbonded_verify(staker: TestAccount) { -// let staker_acc_id = AccountId32::from(staker.clone()); - -// // call unbond_and_unstake(). Check usable_balance before and after the call -// assert_ne!( -// ::Currency::free_balance(&staker_acc_id), -// ::Currency::usable_balance(&staker_acc_id) -// ); - -// precompiles() -// .prepare_test( -// staker.clone(), -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::WithdrawUnbounded).build(), -// ) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(true).build()); - -// assert_eq!( -// ::Currency::free_balance(&staker_acc_id), -// ::Currency::usable_balance(&staker_acc_id) -// ); -// } - -// /// helper function to verify change of reward destination for a staker -// fn set_reward_destination_verify(staker: TestAccount, reward_destination: RewardDestination) { -// // Read staker's ledger -// let staker_acc_id = AccountId32::from(staker.clone()); -// let init_ledger = DappsStaking::ledger(&staker_acc_id); -// // Ensure that something is staked or being unbonded -// assert!(!init_ledger.is_empty()); - -// let reward_destination_raw: u8 = match reward_destination { -// RewardDestination::FreeBalance => 0, -// RewardDestination::StakeBalance => 1, -// }; -// precompiles() -// .prepare_test( -// staker.clone(), -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::SetRewardDestination) -// .write(reward_destination_raw) -// .build(), -// ) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(true).build()); - -// let final_ledger = DappsStaking::ledger(&staker_acc_id); -// assert_eq!(final_ledger.reward_destination(), reward_destination); -// } - -// /// helper function to withdraw funds from unregistered contract -// fn withdraw_from_unregistered_verify(staker: TestAccount, contract: H160) { -// let smart_contract = -// decode_smart_contract_v1_from_array(contract.clone().to_fixed_bytes()).unwrap(); -// let staker_acc_id = AccountId32::from(staker.clone()); -// let init_staker_info = DappsStaking::staker_info(&staker_acc_id, &smart_contract); -// assert!(!init_staker_info.latest_staked_value().is_zero()); - -// precompiles() -// .prepare_test( -// staker.clone(), -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::WithdrawFromUnregistered) -// .write(Address(contract.clone())) -// .build(), -// ) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(true).build()); - -// let final_staker_info = DappsStaking::staker_info(&staker_acc_id, &smart_contract); -// assert!(final_staker_info.latest_staked_value().is_zero()); -// } - -// /// helper function to verify nomination transfer from origin to target contract -// fn nomination_transfer_verify( -// staker: TestAccount, -// origin_contract: H160, -// amount: Balance, -// target_contract: H160, -// ) { -// let origin_smart_contract = -// decode_smart_contract_v1_from_array(origin_contract.clone().to_fixed_bytes()).unwrap(); -// let target_smart_contract = -// decode_smart_contract_v1_from_array(target_contract.clone().to_fixed_bytes()).unwrap(); -// let staker_acc_id = AccountId32::from(staker.clone()); - -// // Read init data staker info states -// let init_origin_staker_info = DappsStaking::staker_info(&staker_acc_id, &origin_smart_contract); -// let init_target_staker_info = DappsStaking::staker_info(&staker_acc_id, &target_smart_contract); - -// precompiles() -// .prepare_test( -// staker.clone(), -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::NominationTransfer) -// .write(Address(origin_contract.clone())) -// .write(amount) -// .write(Address(target_contract.clone())) -// .build(), -// ) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(true).build()); - -// let final_origin_staker_info = -// DappsStaking::staker_info(&staker_acc_id, &origin_smart_contract); -// let final_target_staker_info = -// DappsStaking::staker_info(&staker_acc_id, &target_smart_contract); - -// // Verify final state -// let will_be_unstaked = init_origin_staker_info -// .latest_staked_value() -// .saturating_sub(amount) -// < MINIMUM_STAKING_AMOUNT; -// let transfer_amount = if will_be_unstaked { -// init_origin_staker_info.latest_staked_value() -// } else { -// amount -// }; - -// assert_eq!( -// final_origin_staker_info.latest_staked_value() + transfer_amount, -// init_origin_staker_info.latest_staked_value() -// ); -// assert_eq!( -// final_target_staker_info.latest_staked_value() - transfer_amount, -// init_target_staker_info.latest_staked_value() -// ); -// } - -// /// helper function to bond, stake and verify if result is OK -// fn claim_dapp_and_verify(contract: H160, era: EraIndex) { -// precompiles() -// .prepare_test( -// TestAccount::Bobo, -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::ClaimDapp) -// .write(Address(contract.clone())) -// .write(era) -// .build(), -// ) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(true).build()); -// } - -// /// helper function to bond, stake and verify if the result is OK -// fn claim_staker_and_verify(staker: TestAccount, contract: H160) { -// precompiles() -// .prepare_test( -// staker, -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::ClaimStaker) -// .write(Address(contract.clone())) -// .build(), -// ) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(true).build()); -// } - -// fn contract_era_stake_verify(contract: H160, amount: Balance) { -// precompiles() -// .prepare_test( -// TestAccount::Alex, -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::ReadContractStake) -// .write(Address(contract.clone())) -// .build(), -// ) -// .expect_cost(2 * READ_WEIGHT) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(amount).build()); -// } - -// /// helper function to verify latest staked amount -// fn verify_staked_amount(contract: H160, staker: TestAccount, amount: Balance) { -// precompiles() -// .prepare_test( -// staker.clone(), -// precompile_address(), -// EvmDataWriter::new_with_selector(Action::ReadStakedAmountOnContract) -// .write(Address(contract.clone())) -// .write(Bytes( -// Into::::into(staker.clone()).to_fixed_bytes().to_vec(), -// )) -// .build(), -// ) -// .expect_cost(READ_WEIGHT) -// .expect_no_logs() -// .execute_returns(EvmDataWriter::new().write(amount).build()); -// } - -// /// Helper method to decode type SmartContract enum from [u8; 20] -// fn decode_smart_contract_v1_from_array( -// contract_array: [u8; 20], -// ) -> Result<::SmartContract, String> { -// // Encode contract address to fit SmartContract enum. -// let mut contract_enum_encoded: [u8; 21] = [0; 21]; -// contract_enum_encoded[0] = 0; // enum for EVM H160 address is 0 -// contract_enum_encoded[1..21].copy_from_slice(&contract_array); - -// let smart_contract = ::SmartContract::decode( -// &mut &contract_enum_encoded[..21], -// ) -// .map_err(|_| "Error while decoding SmartContract")?; - -// Ok(smart_contract) -// } From 127a80a5bc5f8a4ff06529401f84d8f67524257e Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Sat, 16 Dec 2023 12:57:20 +0100 Subject: [PATCH 23/34] v2 tests --- precompiles/dapps-staking-v3/Cargo.toml | 2 +- .../dapps-staking-v3/DappsStakingV2.sol | 4 +- precompiles/dapps-staking-v3/src/lib.rs | 61 +- precompiles/dapps-staking-v3/src/test/mock.rs | 13 + precompiles/dapps-staking-v3/src/test/mod.rs | 1 + .../dapps-staking-v3/src/test/tests_v1.rs | 97 ++-- .../dapps-staking-v3/src/test/tests_v2.rs | 523 ++++++++++++++++++ 7 files changed, 634 insertions(+), 67 deletions(-) create mode 100644 precompiles/dapps-staking-v3/src/test/tests_v2.rs diff --git a/precompiles/dapps-staking-v3/Cargo.toml b/precompiles/dapps-staking-v3/Cargo.toml index aef927a3ed..e39fca63f6 100644 --- a/precompiles/dapps-staking-v3/Cargo.toml +++ b/precompiles/dapps-staking-v3/Cargo.toml @@ -2,7 +2,7 @@ name = "pallet-evm-precompile-dapp-staking-v3" version = "0.1.0" license = "GPL-3.0-or-later" -description = "dApps Staking EVM precompiles" +description = "dApp Staking EVM precompiles" authors.workspace = true edition.workspace = true homepage.workspace = true diff --git a/precompiles/dapps-staking-v3/DappsStakingV2.sol b/precompiles/dapps-staking-v3/DappsStakingV2.sol index 16bab828d7..39fa8e6f55 100644 --- a/precompiles/dapps-staking-v3/DappsStakingV2.sol +++ b/precompiles/dapps-staking-v3/DappsStakingV2.sol @@ -8,7 +8,7 @@ pragma solidity >=0.8.0; /// code: frame/dapps-staking-v3 interface DAppStaking { - // Storage getters + // Types /// Describes the subperiod in which the protocol currently is. enum Subperiod {Voting, BuildAndEarn} @@ -35,6 +35,8 @@ interface DAppStaking { bytes contract_address; } + // Storage getters + /// @notice Get the current protocol state. /// @return (current era, current period number, current subperiod type). function protocol_state() external view returns (ProtocolState memory); diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index 7728a9f22b..3928d8eb7f 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -53,7 +53,7 @@ mod test; /// Helper struct used to encode protocol state. #[derive(Debug, Clone, solidity::Codec)] -pub struct PrecompileProtocolState { +pub(crate) struct PrecompileProtocolState { era: U256, period: U256, subperiod: u8, @@ -66,6 +66,34 @@ pub struct SmartContractV2 { address: DynamicAddress, } +// TODO - try to add this under the SmartContractV2 type??? +/// Convenience type to handle smart contract v2 handling. +pub(crate) enum SmartContractTypes { + Evm, + Wasm, +} + +impl TryFrom for SmartContractTypes { + type Error = Revert; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::Evm), + 1 => Ok(Self::Wasm), + _ => Err(RevertReason::custom("Unknown smart contract type").into()), + } + } +} + +impl Into for SmartContractTypes { + fn into(self) -> u8 { + match self { + Self::Evm => 0, + Self::Wasm => 1, + } + } +} + pub struct DappStakingV3Precompile(PhantomData); #[precompile_utils::precompile] impl DappStakingV3Precompile @@ -548,7 +576,7 @@ where Ok(PrecompileProtocolState { era: protocol_state.era.into(), period: protocol_state.period_number().into(), - subperiod: Self::subperiod_id(&protocol_state.subperiod()), + subperiod: subperiod_id(&protocol_state.subperiod()), }) } @@ -725,11 +753,13 @@ where // Utility functions /// Helper method to decode smart contract struct for v2 calls - pub fn decode_smart_contract( + pub(crate) fn decode_smart_contract( smart_contract: SmartContractV2, ) -> EvmResult<::SmartContract> { - let smart_contract = match smart_contract.contract_type { - 0 => { + let smart_contract_type = smart_contract.contract_type.try_into()?; + + let smart_contract = match smart_contract_type { + SmartContractTypes::Evm => { ensure!( smart_contract.address.as_bytes().len() == 20, revert("Invalid address length for Astar EVM smart contract.") @@ -737,7 +767,7 @@ where let h160_address = H160::from_slice(smart_contract.address.as_bytes()); ::SmartContract::evm(h160_address) } - 1 => { + SmartContractTypes::Wasm => { ensure!( smart_contract.address.as_bytes().len() == 32, revert("Invalid address length for Astar WASM smart contract.") @@ -747,16 +777,13 @@ where ::SmartContract::wasm(staker_bytes.into()) } - _ => { - return Err(revert("Error while decoding SmartContract")); - } }; Ok(smart_contract) } /// Helper method to parse H160 or SS58 address - fn parse_input_address(staker_vec: Vec) -> EvmResult { + pub(crate) fn parse_input_address(staker_vec: Vec) -> EvmResult { let staker: R::AccountId = match staker_vec.len() { // public address of the ss58 account has 32 bytes 32 => { @@ -780,13 +807,13 @@ where Ok(staker) } +} - /// Numeric Id of the subperiod enum value. - // TODO: add test for this to ensure it's the same as in the v2 interface - fn subperiod_id(subperiod: &Subperiod) -> u8 { - match subperiod { - Subperiod::Voting => 0, - Subperiod::BuildAndEarn => 1, - } +/// Numeric Id of the subperiod enum value. +// TODO: add test for this to ensure it's the same as in the v2 interface +pub(crate) fn subperiod_id(subperiod: &Subperiod) -> u8 { + match subperiod { + Subperiod::Voting => 0, + Subperiod::BuildAndEarn => 1, } } diff --git a/precompiles/dapps-staking-v3/src/test/mock.rs b/precompiles/dapps-staking-v3/src/test/mock.rs index c3e15962b2..1ae08bd76a 100644 --- a/precompiles/dapps-staking-v3/src/test/mock.rs +++ b/precompiles/dapps-staking-v3/src/test/mock.rs @@ -401,6 +401,19 @@ pub(crate) fn advance_to_era(era: EraNumber) { } } +/// Advance blocks until next era has been reached. +pub(crate) fn advance_to_next_era() { + advance_to_era(ActiveProtocolState::::get().era + 1); +} + +/// Advance blocks until next period type has been reached. +pub(crate) fn advance_to_next_subperiod() { + let subperiod = ActiveProtocolState::::get().subperiod(); + while ActiveProtocolState::::get().subperiod() == subperiod { + run_for_blocks(1); + } +} + /// Advance blocks until the specified period has been reached. /// /// Function has no effect if period is already passed. diff --git a/precompiles/dapps-staking-v3/src/test/mod.rs b/precompiles/dapps-staking-v3/src/test/mod.rs index 02cfb3aef3..c06a88e324 100644 --- a/precompiles/dapps-staking-v3/src/test/mod.rs +++ b/precompiles/dapps-staking-v3/src/test/mod.rs @@ -18,3 +18,4 @@ mod mock; mod tests_v1; +mod tests_v2; diff --git a/precompiles/dapps-staking-v3/src/test/tests_v1.rs b/precompiles/dapps-staking-v3/src/test/tests_v1.rs index b49e4ce87e..8d93b56198 100644 --- a/precompiles/dapps-staking-v3/src/test/tests_v1.rs +++ b/precompiles/dapps-staking-v3/src/test/tests_v1.rs @@ -119,9 +119,9 @@ fn read_era_staked_is_ok() { initialize(); let staker_h160 = ALICE; - let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract_address = H160::repeat_byte(0xFA); let smart_contract = - ::SmartContract::evm(smart_contract_h160); + ::SmartContract::evm(smart_contract_address); let amount = 1_000_000_000_000; register_and_stake(staker_h160, smart_contract.clone(), amount); let anchor_era = ActiveProtocolState::::get().era; @@ -197,9 +197,9 @@ fn read_staked_amount_is_ok() { } // 2. Stake some amount and check again - let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract_address = H160::repeat_byte(0xFA); let smart_contract = - ::SmartContract::evm(smart_contract_h160); + ::SmartContract::evm(smart_contract_address); let amount = 1_000_000_000_000; register_and_stake(staker_h160, smart_contract.clone(), amount); for staker in &dynamic_addresses { @@ -238,9 +238,9 @@ fn read_staked_amount_on_contract_is_ok() { initialize(); let staker_h160 = ALICE; - let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract_address = H160::repeat_byte(0xFA); let smart_contract = - ::SmartContract::evm(smart_contract_h160); + ::SmartContract::evm(smart_contract_address); let dynamic_addresses = into_dynamic_addresses(staker_h160); // 1. Sanity checks - must be zero before anything is staked. @@ -250,7 +250,7 @@ fn read_staked_amount_on_contract_is_ok() { staker_h160, precompile_address(), PrecompileCall::read_staked_amount_on_contract { - contract_h160: smart_contract_h160.into(), + contract_h160: smart_contract_address.into(), staker: staker.clone(), }, ) @@ -267,7 +267,7 @@ fn read_staked_amount_on_contract_is_ok() { staker_h160, precompile_address(), PrecompileCall::read_staked_amount_on_contract { - contract_h160: smart_contract_h160.into(), + contract_h160: smart_contract_address.into(), staker: staker.clone(), }, ) @@ -283,7 +283,7 @@ fn read_staked_amount_on_contract_is_ok() { staker_h160, precompile_address(), PrecompileCall::read_staked_amount_on_contract { - contract_h160: smart_contract_h160.into(), + contract_h160: smart_contract_address.into(), staker: staker.clone(), }, ) @@ -299,7 +299,7 @@ fn read_contract_stake_is_ok() { initialize(); let staker_h160 = ALICE; - let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract_address = H160::repeat_byte(0xFA); // 1. Sanity checks - must be zero before anything is staked. precompiles() @@ -307,7 +307,7 @@ fn read_contract_stake_is_ok() { staker_h160, precompile_address(), PrecompileCall::read_contract_stake { - contract_h160: smart_contract_h160.into(), + contract_h160: smart_contract_address.into(), }, ) .expect_no_logs() @@ -315,7 +315,7 @@ fn read_contract_stake_is_ok() { // 2. Stake some amount and check again let smart_contract = - ::SmartContract::evm(smart_contract_h160); + ::SmartContract::evm(smart_contract_address); let amount = 1_000_000_000_000; register_and_stake(staker_h160, smart_contract.clone(), amount); @@ -324,7 +324,7 @@ fn read_contract_stake_is_ok() { staker_h160, precompile_address(), PrecompileCall::read_contract_stake { - contract_h160: smart_contract_h160.into(), + contract_h160: smart_contract_address.into(), }, ) .expect_no_logs() @@ -337,7 +337,7 @@ fn read_contract_stake_is_ok() { staker_h160, precompile_address(), PrecompileCall::read_contract_stake { - contract_h160: smart_contract_h160.into(), + contract_h160: smart_contract_address.into(), }, ) .expect_no_logs() @@ -388,9 +388,9 @@ fn bond_and_stake_with_two_calls_is_ok() { // Register a dApp for staking let staker_h160 = ALICE; - let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract_address = H160::repeat_byte(0xFA); let smart_contract = - ::SmartContract::evm(smart_contract_h160); + ::SmartContract::evm(smart_contract_address); assert_ok!(DappStaking::register( RawOrigin::Root.into(), AddressMapper::into_account_id(staker_h160), @@ -412,7 +412,7 @@ fn bond_and_stake_with_two_calls_is_ok() { staker_h160, precompile_address(), PrecompileCall::bond_and_stake { - contract_h160: smart_contract_h160.into(), + contract_h160: smart_contract_address.into(), amount: stake_amount, }, ) @@ -447,9 +447,9 @@ fn bond_and_stake_with_single_call_is_ok() { // Register a dApp for staking let staker_h160 = ALICE; - let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract_address = H160::repeat_byte(0xFA); let smart_contract = - ::SmartContract::evm(smart_contract_h160); + ::SmartContract::evm(smart_contract_address); assert_ok!(DappStaking::register( RawOrigin::Root.into(), AddressMapper::into_account_id(staker_h160), @@ -470,7 +470,7 @@ fn bond_and_stake_with_single_call_is_ok() { staker_h160, precompile_address(), PrecompileCall::bond_and_stake { - contract_h160: smart_contract_h160.into(), + contract_h160: smart_contract_address.into(), amount, }, ) @@ -497,9 +497,9 @@ fn unbond_and_unstake_with_two_calls_is_ok() { // Register a dApp for staking let staker_h160 = ALICE; - let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract_address = H160::repeat_byte(0xFA); let smart_contract = - ::SmartContract::evm(smart_contract_h160); + ::SmartContract::evm(smart_contract_address); let amount = 1_000_000_000_000; register_and_stake(staker_h160, smart_contract.clone(), amount); @@ -510,7 +510,7 @@ fn unbond_and_unstake_with_two_calls_is_ok() { staker_h160, precompile_address(), PrecompileCall::unbond_and_unstake { - contract_h160: smart_contract_h160.into(), + contract_h160: smart_contract_address.into(), amount, }, ) @@ -541,9 +541,9 @@ fn unbond_and_unstake_with_single_calls_is_ok() { // Register a dApp for staking let staker_h160 = ALICE; - let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract_address = H160::repeat_byte(0xFA); let smart_contract = - ::SmartContract::evm(smart_contract_h160); + ::SmartContract::evm(smart_contract_address); let amount = 1_000_000_000_000; register_and_stake(staker_h160, smart_contract.clone(), amount); @@ -561,7 +561,7 @@ fn unbond_and_unstake_with_single_calls_is_ok() { staker_h160, precompile_address(), PrecompileCall::unbond_and_unstake { - contract_h160: smart_contract_h160.into(), + contract_h160: smart_contract_address.into(), amount, }, ) @@ -585,9 +585,9 @@ fn withdraw_unbonded_is_ok() { // Register a dApp for staking let staker_h160 = ALICE; let staker_native = AddressMapper::into_account_id(staker_h160); - let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract_address = H160::repeat_byte(0xFA); let smart_contract = - ::SmartContract::evm(smart_contract_h160); + ::SmartContract::evm(smart_contract_address); let amount = 1_000_000_000_000; register_and_stake(staker_h160, smart_contract.clone(), amount); @@ -637,9 +637,9 @@ fn claim_dapp_is_ok() { // Register a dApp for staking let staker_h160 = ALICE; - let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract_address = H160::repeat_byte(0xFA); let smart_contract = - ::SmartContract::evm(smart_contract_h160); + ::SmartContract::evm(smart_contract_address); let amount = 1_000_000_000_000; register_and_stake(staker_h160, smart_contract.clone(), amount); @@ -654,7 +654,7 @@ fn claim_dapp_is_ok() { staker_h160, precompile_address(), PrecompileCall::claim_dapp { - contract_h160: smart_contract_h160.into(), + contract_h160: smart_contract_address.into(), era: claim_era, }, ) @@ -681,15 +681,16 @@ fn claim_staker_is_ok() { // Register a dApp for staking let staker_h160 = ALICE; - let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract_address = H160::repeat_byte(0xFA); let smart_contract = - ::SmartContract::evm(smart_contract_h160); + ::SmartContract::evm(smart_contract_address); let amount = 1_000_000_000_000; register_and_stake(staker_h160, smart_contract.clone(), amount); // Advance enough eras so we can claim dApp reward - advance_to_era(5); - let number_of_claims = (2..=4).count(); + let target_era = 5; + advance_to_era(target_era); + let number_of_claims = (2..target_era).count(); // Execute legacy call, expect dApp rewards to be claimed System::reset_events(); @@ -698,7 +699,7 @@ fn claim_staker_is_ok() { staker_h160, precompile_address(), PrecompileCall::claim_staker { - _contract_h160: smart_contract_h160.into(), + _contract_h160: smart_contract_address.into(), }, ) .expect_no_logs() @@ -707,7 +708,7 @@ fn claim_staker_is_ok() { // We expect multiple reward to be claimed let events = dapp_staking_events(); assert_eq!(events.len(), number_of_claims as usize); - for era in 2..=4 { + for era in 2..target_era { assert_matches!( events[era as usize - 2].clone(), pallet_dapp_staking_v3::Event::Reward { era, .. } if era == era @@ -723,9 +724,9 @@ fn withdraw_from_unregistered_is_ok() { // Register a dApp for staking let staker_h160 = ALICE; - let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract_address = H160::repeat_byte(0xFA); let smart_contract = - ::SmartContract::evm(smart_contract_h160); + ::SmartContract::evm(smart_contract_address); let amount = 1_000_000_000_000; register_and_stake(staker_h160, smart_contract.clone(), amount); @@ -742,7 +743,7 @@ fn withdraw_from_unregistered_is_ok() { staker_h160, precompile_address(), PrecompileCall::withdraw_from_unregistered { - contract_h160: smart_contract_h160.into(), + contract_h160: smart_contract_address.into(), }, ) .expect_no_logs() @@ -769,16 +770,16 @@ fn nomination_transfer_is_ok() { // Register the first dApp, and stke on it. let staker_h160 = ALICE; let staker_native = AddressMapper::into_account_id(staker_h160); - let smart_contract_h160_1 = H160::repeat_byte(0xFA); + let smart_contract_address_1 = H160::repeat_byte(0xFA); let smart_contract_1 = - ::SmartContract::evm(smart_contract_h160_1); + ::SmartContract::evm(smart_contract_address_1); let amount = 1_000_000_000_000; register_and_stake(staker_h160, smart_contract_1.clone(), amount); // Register the second dApp. - let smart_contract_h160_2 = H160::repeat_byte(0xBF); + let smart_contract_address_2 = H160::repeat_byte(0xBF); let smart_contract_2 = - ::SmartContract::evm(smart_contract_h160_2); + ::SmartContract::evm(smart_contract_address_2); assert_ok!(DappStaking::register( RawOrigin::Root.into(), staker_native.clone(), @@ -796,9 +797,9 @@ fn nomination_transfer_is_ok() { staker_h160, precompile_address(), PrecompileCall::nomination_transfer { - origin_contract_h160: smart_contract_h160_1.into(), + origin_contract_h160: smart_contract_address_1.into(), amount: minimum_stake_amount, - target_contract_h160: smart_contract_h160_2.into(), + target_contract_h160: smart_contract_address_2.into(), }, ) .expect_no_logs() @@ -835,9 +836,9 @@ fn nomination_transfer_is_ok() { staker_h160, precompile_address(), PrecompileCall::nomination_transfer { - origin_contract_h160: smart_contract_h160_1.into(), + origin_contract_h160: smart_contract_address_1.into(), amount: unstake_amount, - target_contract_h160: smart_contract_h160_2.into(), + target_contract_h160: smart_contract_address_2.into(), }, ) .expect_no_logs() diff --git a/precompiles/dapps-staking-v3/src/test/tests_v2.rs b/precompiles/dapps-staking-v3/src/test/tests_v2.rs new file mode 100644 index 0000000000..d637e08139 --- /dev/null +++ b/precompiles/dapps-staking-v3/src/test/tests_v2.rs @@ -0,0 +1,523 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +extern crate alloc; +use crate::{test::mock::*, *}; +use frame_support::assert_ok; +use frame_system::RawOrigin; +use precompile_utils::testing::*; +use sp_core::H160; + +use assert_matches::assert_matches; + +use astar_primitives::{dapp_staking::CycleConfiguration, BlockNumber}; +use pallet_dapp_staking_v3::{ActiveProtocolState, EraNumber}; + +#[test] +fn protocol_state_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Prepare some mixed state in the future so not all entries are 'zero' + advance_to_next_period(); + advance_to_next_era(); + + let state = ActiveProtocolState::::get(); + + let expected_outcome = PrecompileProtocolState { + era: state.era.into(), + period: state.period_number().into(), + subperiod: subperiod_id(&state.subperiod()), + }; + + precompiles() + .prepare_test( + Alice, + precompile_address(), + PrecompileCall::protocol_state {}, + ) + .expect_no_logs() + .execute_returns(expected_outcome); + }); +} + +#[test] +fn unlocking_period_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + let unlocking_period_in_eras: EraNumber = + ::UnlockingPeriod::get(); + let era_length: BlockNumber = + ::CycleConfiguration::blocks_per_era(); + + let expected_outcome = era_length * unlocking_period_in_eras; + + precompiles() + .prepare_test( + Alice, + precompile_address(), + PrecompileCall::unlocking_period {}, + ) + .expect_no_logs() + .execute_returns(expected_outcome); + }); +} + +#[test] +fn lock_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Lock some amount and verify event + let amount = 1234; + System::reset_events(); + precompiles() + .prepare_test(ALICE, precompile_address(), PrecompileCall::lock { amount }) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 1); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::Locked { + amount, + .. + } if amount == amount + ); + }); +} + +#[test] +fn unlock_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + let lock_amount = 1234; + assert_ok!(DappStaking::lock( + RawOrigin::Signed(AddressMapper::into_account_id(ALICE)).into(), + lock_amount, + )); + + // Unlock some amount and verify event + System::reset_events(); + let unlock_amount = 1234 / 7; + precompiles() + .prepare_test( + ALICE, + precompile_address(), + PrecompileCall::unlock { + amount: unlock_amount, + }, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 1); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::Unlocking { + amount, + .. + } if amount == unlock_amount + ); + }); +} + +#[test] +fn claim_unlocked_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Lock/unlock some amount to create unlocking chunk + let amount = 1234; + assert_ok!(DappStaking::lock( + RawOrigin::Signed(AddressMapper::into_account_id(ALICE)).into(), + amount, + )); + assert_ok!(DappStaking::unlock( + RawOrigin::Signed(AddressMapper::into_account_id(ALICE)).into(), + amount, + )); + + // Advance enough into time so unlocking chunk can be claimed + let unlock_block = + Ledger::::get(&AddressMapper::into_account_id(ALICE)).unlocking[0].unlock_block; + run_to_block(unlock_block); + + // Claim unlocked chunk and verify event + System::reset_events(); + precompiles() + .prepare_test( + ALICE, + precompile_address(), + PrecompileCall::claim_unlocked {}, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 1); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::ClaimedUnlocked { + amount, + .. + } if amount == amount + ); + }); +} + +#[test] +fn stake_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Register a dApp for staking + let staker_h160 = ALICE; + let smart_contract_h160 = H160::repeat_byte(0xFA); + let smart_contract = + ::SmartContract::evm(smart_contract_h160); + assert_ok!(DappStaking::register( + RawOrigin::Root.into(), + AddressMapper::into_account_id(staker_h160), + smart_contract.clone() + )); + + // Lock some amount which will be used for staking + let amount = 2000; + assert_ok!(DappStaking::lock( + RawOrigin::Signed(AddressMapper::into_account_id(staker_h160)).into(), + amount, + )); + + let smart_contract_v2 = SmartContractV2 { + contract_type: SmartContractTypes::Evm.into(), + address: smart_contract_h160.as_bytes().try_into().unwrap(), + }; + + // Stake some amount and verify event + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::stake { + smart_contract: smart_contract_v2, + amount, + }, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 1); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::Stake { + smart_contract, + amount, + .. + } if smart_contract == smart_contract && amount == amount + ); + }); +} + +#[test] +fn unstake_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Register a dApp for staking + let staker_h160 = ALICE; + let smart_contract_address = [0xAF; 32]; + let smart_contract = ::SmartContract::wasm( + smart_contract_address.into(), + ); + assert_ok!(DappStaking::register( + RawOrigin::Root.into(), + AddressMapper::into_account_id(staker_h160), + smart_contract.clone() + )); + + // Lock & stake some amount + let amount = 2000; + assert_ok!(DappStaking::lock( + RawOrigin::Signed(AddressMapper::into_account_id(staker_h160)).into(), + amount, + )); + assert_ok!(DappStaking::stake( + RawOrigin::Signed(AddressMapper::into_account_id(staker_h160)).into(), + smart_contract.clone(), + amount, + )); + + let smart_contract_v2 = SmartContractV2 { + contract_type: SmartContractTypes::Wasm.into(), + address: smart_contract_address.into(), + }; + + // Unstake some amount and verify event + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::unstake { + smart_contract: smart_contract_v2, + amount, + }, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 1); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::Unstake { + smart_contract, + amount, + .. + } if smart_contract == smart_contract && amount == amount + ); + }); +} + +#[test] +fn claim_staker_rewards_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Register a dApp and stake on it + let staker_h160 = ALICE; + let smart_contract_address = [0xAF; 32]; + let smart_contract = ::SmartContract::wasm( + smart_contract_address.into(), + ); + let amount = 1234; + register_and_stake(staker_h160, smart_contract.clone(), amount); + + // Advance a few eras so we can claim a few rewards + let target_era = 7; + advance_to_era(target_era); + let number_of_claims = (2..target_era).count(); + + // Claim staker rewards and verify events + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::claim_staker_rewards {}, + ) + .expect_no_logs() + .execute_returns(true); + + // We expect multiple reward to be claimed + let events = dapp_staking_events(); + assert_eq!(events.len(), number_of_claims as usize); + for era in 2..target_era { + assert_matches!( + events[era as usize - 2].clone(), + pallet_dapp_staking_v3::Event::Reward { era, .. } if era == era + ); + } + }); +} + +#[test] +fn claim_bonus_reward_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Register a dApp and stake on it, loyally + let staker_h160 = ALICE; + let smart_contract_address = [0xAF; 32]; + let smart_contract = ::SmartContract::wasm( + smart_contract_address.into(), + ); + let amount = 1234; + register_and_stake(staker_h160, smart_contract.clone(), amount); + + // Advance to the next period + advance_to_next_period(); + + let smart_contract_v2 = SmartContractV2 { + contract_type: SmartContractTypes::Wasm.into(), + address: smart_contract_address.into(), + }; + + // Claim bonus reward and verify event + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::claim_bonus_reward { + smart_contract: smart_contract_v2, + }, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 1); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::BonusReward { smart_contract, .. } if smart_contract == smart_contract + ); + }); +} + +#[test] +fn claim_dapp_reward_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Register a dApp and stake on it + let staker_h160 = ALICE; + let smart_contract_address = [0xAF; 32]; + let smart_contract = ::SmartContract::wasm( + smart_contract_address.into(), + ); + let amount = 1234; + register_and_stake(staker_h160, smart_contract.clone(), amount); + + // Advance to 3rd era so we claim rewards for the 2nd era + advance_to_era(3); + + let smart_contract_v2 = SmartContractV2 { + contract_type: SmartContractTypes::Wasm.into(), + address: smart_contract_address.into(), + }; + + // Claim dApp reward and verify event + let claim_era: EraNumber = 2; + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::claim_dapp_reward { + smart_contract: smart_contract_v2, + era: claim_era.into(), + }, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 1); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::DAppReward { era, smart_contract, .. } if era == claim_era && smart_contract == smart_contract + ); + }); +} + +#[test] +fn unstake_from_unregistered_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Register a dApp and stake on it + let staker_h160 = ALICE; + let smart_contract_address = [0xAF; 32]; + let smart_contract = ::SmartContract::wasm( + smart_contract_address.into(), + ); + let amount = 1234; + register_and_stake(staker_h160, smart_contract.clone(), amount); + + // Unregister the dApp + assert_ok!(DappStaking::unregister( + RawOrigin::Root.into(), + smart_contract.clone() + )); + + let smart_contract_v2 = SmartContractV2 { + contract_type: SmartContractTypes::Wasm.into(), + address: smart_contract_address.into(), + }; + + // Unstake from the unregistered dApp and verify event + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::unstake_from_unregistered { + smart_contract: smart_contract_v2, + }, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 1); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::UnstakeFromUnregistered { smart_contract, amount, .. } if smart_contract == smart_contract && amount == amount + ); + }); +} + +#[test] +fn cleanup_expired_entries_is_ok() { + ExternalityBuilder::build().execute_with(|| { + initialize(); + + // Advance over to the Build&Earn subperiod + advance_to_next_subperiod(); + assert_eq!(ActiveProtocolState::::get().subperiod(), Subperiod::BuildEarn, "Sanity check."); + + // Register a dApp and stake on it + let staker_h160 = ALICE; + let smart_contract_address = [0xAF; 32]; + let smart_contract = ::SmartContract::wasm( + smart_contract_address.into(), + ); + let amount = 1234; + register_and_stake(staker_h160, smart_contract.clone(), amount); + + // Advance over to the next period so the entry for dApp becomes expired + advance_to_next_period(); + + // Cleanup single expired entry and verify event + System::reset_events(); + precompiles() + .prepare_test( + staker_h160, + precompile_address(), + PrecompileCall::cleanup_expired_entries { + }, + ) + .expect_no_logs() + .execute_returns(true); + + let events = dapp_staking_events(); + assert_eq!(events.len(), 1); + assert_matches!( + events[0].clone(), + pallet_dapp_staking_v3::Event::UnstakeFromUnregistered { smart_contract, amount, .. } if smart_contract == smart_contract && amount == amount + ); + }); +} From 7ce2370400b389fe7cef302bf274f445c55ac6ae Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Sat, 16 Dec 2023 13:26:48 +0100 Subject: [PATCH 24/34] Cleanup TODOs --- .../dapps-staking-v3/DappsStakingV1.sol | 12 +++-- precompiles/dapps-staking-v3/src/lib.rs | 46 +++++++++++++++---- precompiles/dapps-staking-v3/src/test/mock.rs | 4 +- .../dapps-staking-v3/src/test/tests_v2.rs | 21 +++++---- 4 files changed, 60 insertions(+), 23 deletions(-) diff --git a/precompiles/dapps-staking-v3/DappsStakingV1.sol b/precompiles/dapps-staking-v3/DappsStakingV1.sol index e372aa7f6f..6ce1893e0b 100644 --- a/precompiles/dapps-staking-v3/DappsStakingV1.sol +++ b/precompiles/dapps-staking-v3/DappsStakingV1.sol @@ -8,6 +8,13 @@ pragma solidity >=0.8.0; /// code: frame/dapps-staking/src/pallet interface DappsStaking { + // Types + + /// Instruction how to handle reward payout for staker. + /// `FreeBalance` - Reward will be paid out to the staker (free balance). + /// `StakeBalance` - Reward will be paid out to the staker and is immediately restaked (locked balance) + enum RewardDestination {FreeBalance, StakeBalance} + // Storage getters /// @notice Read current era. @@ -68,11 +75,6 @@ interface DappsStaking { /// @param era: The era to be claimed function claim_dapp(address smart_contract, uint128 era) external returns (bool); - /// Instruction how to handle reward payout for staker. - /// `FreeBalance` - Reward will be paid out to the staker (free balance). - /// `StakeBalance` - Reward will be paid out to the staker and is immediately restaked (locked balance) - enum RewardDestination {FreeBalance, StakeBalance} - /// @notice Set reward destination for staker rewards /// @param reward_destination: The instruction on how the reward payout should be handled function set_reward_destination(RewardDestination reward_destination) external returns (bool); diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index 3928d8eb7f..19d08ec435 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -30,7 +30,13 @@ use frame_support::{ }; use pallet_evm::AddressMapping; -use precompile_utils::prelude::*; +use precompile_utils::{ + prelude::*, + solidity::{ + codec::{Reader, Writer}, + Codec, + }, +}; use sp_core::{Get, H160, U256}; use sp_runtime::traits::Zero; use sp_std::{marker::PhantomData, prelude::*}; @@ -62,12 +68,12 @@ pub(crate) struct PrecompileProtocolState { /// Helper struct used to encode different smart contract types for the v2 interface. #[derive(Debug, Clone, solidity::Codec)] pub struct SmartContractV2 { - contract_type: u8, + contract_type: SmartContractTypes, address: DynamicAddress, } -// TODO - try to add this under the SmartContractV2 type??? -/// Convenience type to handle smart contract v2 handling. +/// Convenience type for smart contract type handling. +#[derive(Clone, Debug)] pub(crate) enum SmartContractTypes { Evm, Wasm, @@ -94,6 +100,33 @@ impl Into for SmartContractTypes { } } +impl Codec for SmartContractTypes { + fn read(reader: &mut Reader) -> MayRevert { + let value256: U256 = reader + .read() + .map_err(|_| RevertReason::read_out_of_bounds(Self::signature()))?; + + let value_as_u8: u8 = value256 + .try_into() + .map_err(|_| RevertReason::value_is_too_large(Self::signature()))?; + + value_as_u8.try_into() + } + + fn write(writer: &mut Writer, value: Self) { + let value_as_u8: u8 = value.into(); + U256::write(writer, value_as_u8.into()); + } + + fn has_static_size() -> bool { + true + } + + fn signature() -> String { + "uint8".into() + } +} + pub struct DappStakingV3Precompile(PhantomData); #[precompile_utils::precompile] impl DappStakingV3Precompile @@ -756,9 +789,7 @@ where pub(crate) fn decode_smart_contract( smart_contract: SmartContractV2, ) -> EvmResult<::SmartContract> { - let smart_contract_type = smart_contract.contract_type.try_into()?; - - let smart_contract = match smart_contract_type { + let smart_contract = match smart_contract.contract_type { SmartContractTypes::Evm => { ensure!( smart_contract.address.as_bytes().len() == 20, @@ -810,7 +841,6 @@ where } /// Numeric Id of the subperiod enum value. -// TODO: add test for this to ensure it's the same as in the v2 interface pub(crate) fn subperiod_id(subperiod: &Subperiod) -> u8 { match subperiod { Subperiod::Voting => 0, diff --git a/precompiles/dapps-staking-v3/src/test/mock.rs b/precompiles/dapps-staking-v3/src/test/mock.rs index 1ae08bd76a..250f53e278 100644 --- a/precompiles/dapps-staking-v3/src/test/mock.rs +++ b/precompiles/dapps-staking-v3/src/test/mock.rs @@ -356,7 +356,9 @@ pub fn register_and_stake( )); } -/// TODO +/// Utility function used to create `DynamicAddress` out of the given `H160` address. +/// The first one is simply byte representation of the H160 address. +/// The second one is byte representation of the derived `AccountId` from the H160 address. pub fn into_dynamic_addresses(address: H160) -> [DynamicAddress; 2] { [ address.as_bytes().try_into().unwrap(), diff --git a/precompiles/dapps-staking-v3/src/test/tests_v2.rs b/precompiles/dapps-staking-v3/src/test/tests_v2.rs index d637e08139..5977417b5e 100644 --- a/precompiles/dapps-staking-v3/src/test/tests_v2.rs +++ b/precompiles/dapps-staking-v3/src/test/tests_v2.rs @@ -209,7 +209,7 @@ fn stake_is_ok() { )); let smart_contract_v2 = SmartContractV2 { - contract_type: SmartContractTypes::Evm.into(), + contract_type: SmartContractTypes::Evm, address: smart_contract_h160.as_bytes().try_into().unwrap(), }; @@ -270,7 +270,7 @@ fn unstake_is_ok() { )); let smart_contract_v2 = SmartContractV2 { - contract_type: SmartContractTypes::Wasm.into(), + contract_type: SmartContractTypes::Wasm, address: smart_contract_address.into(), }; @@ -361,7 +361,7 @@ fn claim_bonus_reward_is_ok() { advance_to_next_period(); let smart_contract_v2 = SmartContractV2 { - contract_type: SmartContractTypes::Wasm.into(), + contract_type: SmartContractTypes::Wasm, address: smart_contract_address.into(), }; @@ -405,7 +405,7 @@ fn claim_dapp_reward_is_ok() { advance_to_era(3); let smart_contract_v2 = SmartContractV2 { - contract_type: SmartContractTypes::Wasm.into(), + contract_type: SmartContractTypes::Wasm, address: smart_contract_address.into(), }; @@ -454,7 +454,7 @@ fn unstake_from_unregistered_is_ok() { )); let smart_contract_v2 = SmartContractV2 { - contract_type: SmartContractTypes::Wasm.into(), + contract_type: SmartContractTypes::Wasm, address: smart_contract_address.into(), }; @@ -487,7 +487,11 @@ fn cleanup_expired_entries_is_ok() { // Advance over to the Build&Earn subperiod advance_to_next_subperiod(); - assert_eq!(ActiveProtocolState::::get().subperiod(), Subperiod::BuildEarn, "Sanity check."); + assert_eq!( + ActiveProtocolState::::get().subperiod(), + Subperiod::BuildAndEarn, + "Sanity check." + ); // Register a dApp and stake on it let staker_h160 = ALICE; @@ -507,8 +511,7 @@ fn cleanup_expired_entries_is_ok() { .prepare_test( staker_h160, precompile_address(), - PrecompileCall::cleanup_expired_entries { - }, + PrecompileCall::cleanup_expired_entries {}, ) .expect_no_logs() .execute_returns(true); @@ -517,7 +520,7 @@ fn cleanup_expired_entries_is_ok() { assert_eq!(events.len(), 1); assert_matches!( events[0].clone(), - pallet_dapp_staking_v3::Event::UnstakeFromUnregistered { smart_contract, amount, .. } if smart_contract == smart_contract && amount == amount + pallet_dapp_staking_v3::Event::ExpiredEntriesRemoved { count, .. } if count == 1 ); }); } From 391a3e8c7fba06315b945732d7859b3443c2210d Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Sat, 16 Dec 2023 14:12:27 +0100 Subject: [PATCH 25/34] More tests --- precompiles/dapps-staking-v3/src/lib.rs | 2 +- precompiles/dapps-staking-v3/src/test/mod.rs | 1 + .../dapps-staking-v3/src/test/types.rs | 154 ++++++++++++++++++ 3 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 precompiles/dapps-staking-v3/src/test/types.rs diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapps-staking-v3/src/lib.rs index 19d08ec435..c8b84f0ade 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapps-staking-v3/src/lib.rs @@ -73,7 +73,7 @@ pub struct SmartContractV2 { } /// Convenience type for smart contract type handling. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub(crate) enum SmartContractTypes { Evm, Wasm, diff --git a/precompiles/dapps-staking-v3/src/test/mod.rs b/precompiles/dapps-staking-v3/src/test/mod.rs index c06a88e324..a33eb22954 100644 --- a/precompiles/dapps-staking-v3/src/test/mod.rs +++ b/precompiles/dapps-staking-v3/src/test/mod.rs @@ -19,3 +19,4 @@ mod mock; mod tests_v1; mod tests_v2; +mod types; diff --git a/precompiles/dapps-staking-v3/src/test/types.rs b/precompiles/dapps-staking-v3/src/test/types.rs new file mode 100644 index 0000000000..186300e677 --- /dev/null +++ b/precompiles/dapps-staking-v3/src/test/types.rs @@ -0,0 +1,154 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +extern crate alloc; +use crate::{test::mock::*, *}; + +use assert_matches::assert_matches; + +#[test] +fn smart_contract_types_are_ok() { + // Verify Astar EVM smart contract type + { + let index: u8 = SmartContractTypes::Evm.into(); + assert_eq!(index, 0); + assert_eq!(Ok(SmartContractTypes::Evm), index.try_into()); + } + + // Verify Astar WASM smart contract type + { + let index: u8 = SmartContractTypes::Wasm.into(); + assert_eq!(index, 1); + assert_eq!(Ok(SmartContractTypes::Wasm), index.try_into()); + } + + // Negative case + { + let index: u8 = 2; + let maybe_smart_contract: Result = index.try_into(); + assert_matches!(maybe_smart_contract, Err(_)); + } +} + +#[test] +fn decode_smart_contract_is_ok() { + ExternalityBuilder::build().execute_with(|| { + // Astar EVM smart contract decoding + { + let address = H160::repeat_byte(0xCA); + let smart_contract_v2 = SmartContractV2 { + contract_type: SmartContractTypes::Evm, + address: address.as_bytes().into(), + }; + + assert_eq!( + Ok(::SmartContract::evm(address)), + DappStakingV3Precompile::::decode_smart_contract(smart_contract_v2) + ); + } + + // Astar WASM smart contract decoding + { + let address = [0x6E; 32]; + let smart_contract_v2 = SmartContractV2 { + contract_type: SmartContractTypes::Wasm, + address: address.into(), + }; + + assert_eq!( + Ok(::SmartContract::wasm(address.into())), + DappStakingV3Precompile::::decode_smart_contract(smart_contract_v2) + ); + } + }); +} + +#[test] +fn decode_smart_contract_fails_when_type_and_address_mismatch() { + ExternalityBuilder::build().execute_with(|| { + // H160 address for Wasm smart contract type + { + let address = H160::repeat_byte(0xCA); + let smart_contract_v2 = SmartContractV2 { + contract_type: SmartContractTypes::Wasm, + address: address.as_bytes().into(), + }; + + assert_matches!( + DappStakingV3Precompile::::decode_smart_contract(smart_contract_v2), + Err(_) + ); + } + + // Native address for EVM smart contract type + { + let address = [0x6E; 32]; + let smart_contract_v2 = SmartContractV2 { + contract_type: SmartContractTypes::Evm, + address: address.into(), + }; + + assert_matches!( + DappStakingV3Precompile::::decode_smart_contract(smart_contract_v2), + Err(_) + ); + } + }); +} + +#[test] +fn parse_input_address_is_ok() { + ExternalityBuilder::build().execute_with(|| { + // H160 address + { + let address_h160 = H160::repeat_byte(0xCA); + let address_native = AddressMapper::into_account_id(address_h160); + + assert_eq!( + DappStakingV3Precompile::::parse_input_address( + address_h160.as_bytes().into() + ), + Ok(address_native) + ); + } + + // Native address + { + let address_native = [0x6E; 32]; + + assert_eq!( + DappStakingV3Precompile::::parse_input_address(address_native.into()), + Ok(address_native.into()) + ); + } + }); +} + +#[test] +fn parse_input_address_fails_with_incorrect_address_length() { + ExternalityBuilder::build().execute_with(|| { + let addresses: Vec<&[u8]> = vec![&[0x6E; 19], &[0xA1; 21], &[0xC3; 31], &[0x99; 33]]; + + for address in addresses { + assert_matches!( + DappStakingV3Precompile::::parse_input_address(address.into()), + Err(_) + ); + } + }); +} From c6148b10d93f1d9074d386d3094c449597e3dbce Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Sat, 16 Dec 2023 14:19:53 +0100 Subject: [PATCH 26/34] Updates --- precompiles/dapps-staking-v3/DappsStakingV1.sol | 11 +++++++++-- precompiles/dapps-staking-v3/DappsStakingV2.sol | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/precompiles/dapps-staking-v3/DappsStakingV1.sol b/precompiles/dapps-staking-v3/DappsStakingV1.sol index 6ce1893e0b..9b13429413 100644 --- a/precompiles/dapps-staking-v3/DappsStakingV1.sol +++ b/precompiles/dapps-staking-v3/DappsStakingV1.sol @@ -4,8 +4,15 @@ pragma solidity >=0.8.0; /// Predeployed at the address 0x0000000000000000000000000000000000005001 /// For better understanding check the source code: -/// repo: https://github.com/AstarNetwork/astar -/// code: frame/dapps-staking/src/pallet +/// repo: https://github.com/AstarNetwork/Astar +/// +/// **NOTE:** This is a soft-deprecated interface used by the old dApps staking v2. +/// It is still supported by the network, but doesn't reflect how dApp staking v3 should be used. +/// Please refer to the `v2` interface for the latest version of the dApp staking contract. +/// +/// It is possible that dApp staking feature will once again evolve in the future so all developers are encouraged +/// to keep their smart contracts which utilize dApp staking precompile interface as upgradable, or implement their logic +/// in such a way it's relatively simple to migrate to the new version of the interface. interface DappsStaking { // Types diff --git a/precompiles/dapps-staking-v3/DappsStakingV2.sol b/precompiles/dapps-staking-v3/DappsStakingV2.sol index 39fa8e6f55..75a4497efe 100644 --- a/precompiles/dapps-staking-v3/DappsStakingV2.sol +++ b/precompiles/dapps-staking-v3/DappsStakingV2.sol @@ -5,7 +5,7 @@ pragma solidity >=0.8.0; /// Predeployed at the address 0x0000000000000000000000000000000000005001 /// For better understanding check the source code: /// repo: https://github.com/AstarNetwork/Astar -/// code: frame/dapps-staking-v3 +/// code: pallets/dapp-staking-v3 interface DAppStaking { // Types From 65db544396fd0372a5caaed27d79f2f42a2deeed Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Sat, 16 Dec 2023 14:24:33 +0100 Subject: [PATCH 27/34] docs --- .../dapps-staking-v3/DappsStakingV2.sol | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/precompiles/dapps-staking-v3/DappsStakingV2.sol b/precompiles/dapps-staking-v3/DappsStakingV2.sol index 75a4497efe..40c55af6c4 100644 --- a/precompiles/dapps-staking-v3/DappsStakingV2.sol +++ b/precompiles/dapps-staking-v3/DappsStakingV2.sol @@ -49,31 +49,43 @@ interface DAppStaking { // Extrinsic calls /// @notice Lock the given amount of tokens into dApp staking protocol. - function lock(uint128) external returns (bool); + /// @param amount: The amount of tokens to be locked. + function lock(uint128 amount) external returns (bool); /// @notice Start the unlocking process for the given amount of tokens. - function unlock(uint128) external returns (bool); + /// @param amount: The amount of tokens to be unlocked. + function unlock(uint128 amount) external returns (bool); - /// @notice Claims unlocked tokens. + /// @notice Claims unlocked tokens, if there are any function claim_unlocked() external returns (bool); /// @notice Stake the given amount of tokens on the specified smart contract. - function stake(SmartContract calldata, uint128) external returns (bool); + /// The amount specified must be precise, otherwise the call will fail. + /// @param smart_contract: The smart contract to be staked on. + /// @param amount: The amount of tokens to be staked. + function stake(SmartContract calldata smart_contract, uint128 amount) external returns (bool); /// @notice Unstake the given amount of tokens from the specified smart contract. - function unstake(SmartContract calldata, uint128) external returns (bool); + /// The amount specified must be precise, otherwise the call will fail. + /// @param smart_contract: The smart contract to be unstaked from. + /// @param amount: The amount of tokens to be unstaked. + function unstake(SmartContract calldata smart_contract, uint128 amount) external returns (bool); /// @notice Claims one or more pending staker rewards. function claim_staker_rewards() external returns (bool); /// @notice Claim the bonus reward for the specified smart contract. - function claim_bonus_reward(SmartContract calldata) external returns (bool); + /// @param smart_contract: The smart contract for which the bonus reward should be claimed. + function claim_bonus_reward(SmartContract calldata smart_contract) external returns (bool); /// @notice Claim dApp reward for the specified smart contract & era. - function claim_dapp_reward(SmartContract calldata, uint256) external returns (bool); + /// @param smart_contract: The smart contract for which the dApp reward should be claimed. + /// @param era: The era for which the dApp reward should be claimed. + function claim_dapp_reward(SmartContract calldata smart_contract, uint256 era) external returns (bool); /// @notice Unstake all funds from the unregistered smart contract. - function unstake_from_unregistered(SmartContract calldata) external returns (bool); + /// @param smart_contract: The smart contract which was unregistered and from which all funds should be unstaked. + function unstake_from_unregistered(SmartContract calldata smart_contract) external returns (bool); /// @notice Used to cleanup all expired contract stake entries from the caller. function cleanup_expired_entries() external returns (bool); From c880ed203c00dba371ed138e24f1fcadc5825e74 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Sat, 16 Dec 2023 14:47:25 +0100 Subject: [PATCH 28/34] Fixes --- pallets/dapp-staking-v3/src/lib.rs | 5 +++-- .../dapp-staking-v3/src/test/testing_utils.rs | 6 +++--- precompiles/dapps-staking-v3/Cargo.toml | 1 + precompiles/dapps-staking-v3/src/test/mock.rs | 16 ++++++++++++++++ 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/pallets/dapp-staking-v3/src/lib.rs b/pallets/dapp-staking-v3/src/lib.rs index 7709515fad..05e4acf419 100644 --- a/pallets/dapp-staking-v3/src/lib.rs +++ b/pallets/dapp-staking-v3/src/lib.rs @@ -1140,8 +1140,9 @@ pub mod pallet { let earliest_staked_era = ledger .earliest_staked_era() .ok_or(Error::::InternalClaimStakerError)?; - let era_rewards = EraRewards::::get(Self::era_reward_span_index(earliest_staked_era)) - .ok_or(Error::::NoClaimableRewards)?; + let era_rewards = + EraRewards::::get(Self::era_reward_span_index(earliest_staked_era)) + .ok_or(Error::::NoClaimableRewards)?; // The last era for which we can theoretically claim rewards. // And indicator if we know the period's ending era. diff --git a/pallets/dapp-staking-v3/src/test/testing_utils.rs b/pallets/dapp-staking-v3/src/test/testing_utils.rs index 29cfd9e40d..a973bba7ac 100644 --- a/pallets/dapp-staking-v3/src/test/testing_utils.rs +++ b/pallets/dapp-staking-v3/src/test/testing_utils.rs @@ -1288,7 +1288,7 @@ pub(crate) fn assert_block_bump(pre_snapshot: &MemorySnapshot) { } // 4. Verify era reward - let era_span_index = DappStaking::era_reward_index(pre_protoc_state.era); + let era_span_index = DappStaking::era_reward_span_index(pre_protoc_state.era); let maybe_pre_era_reward_span = pre_snapshot.era_rewards.get(&era_span_index); let post_era_reward_span = post_snapshot .era_rewards @@ -1467,10 +1467,10 @@ pub(crate) fn required_number_of_reward_claims(account: AccountId) -> u32 { }; let era_span_length: EraNumber = ::EraRewardSpanLength::get(); - let first = DappStaking::era_reward_index(range.0) + let first = DappStaking::era_reward_span_index(range.0) .checked_div(era_span_length) .unwrap(); - let second = DappStaking::era_reward_index(range.1) + let second = DappStaking::era_reward_span_index(range.1) .checked_div(era_span_length) .unwrap(); diff --git a/precompiles/dapps-staking-v3/Cargo.toml b/precompiles/dapps-staking-v3/Cargo.toml index e39fca63f6..9d10ccb882 100644 --- a/precompiles/dapps-staking-v3/Cargo.toml +++ b/precompiles/dapps-staking-v3/Cargo.toml @@ -59,3 +59,4 @@ std = [ "pallet-balances/std", "sp-arithmetic/std", ] +runtime-benchmarks = ["pallet-dapp-staking-v3/runtime-benchmarks"] diff --git a/precompiles/dapps-staking-v3/src/test/mock.rs b/precompiles/dapps-staking-v3/src/test/mock.rs index 250f53e278..2015ba6736 100644 --- a/precompiles/dapps-staking-v3/src/test/mock.rs +++ b/precompiles/dapps-staking-v3/src/test/mock.rs @@ -225,6 +225,20 @@ impl CycleConfiguration for DummyCycleConfiguration { } } +// Just to satsify the trait bound +#[cfg(feature = "runtime-benchmarks")] +pub struct BenchmarkHelper(sp_std::marker::PhantomData<(SC, ACC)>); +#[cfg(feature = "runtime-benchmarks")] +impl pallet_dapp_staking_v3::BenchmarkHelper + for BenchmarkHelper +{ + fn get_smart_contract(id: u32) -> MockSmartContract { + MockSmartContract::evm(H160::from_low_u64_be(id as u64)) + } + + fn set_balance(_account: &AccountId, _amount: Balance) {} +} + impl pallet_dapp_staking_v3::Config for Test { type RuntimeEvent = RuntimeEvent; type RuntimeFreezeReason = RuntimeFreezeReason; @@ -244,6 +258,8 @@ impl pallet_dapp_staking_v3::Config for Test { type MinimumStakeAmount = ConstU128<3>; type NumberOfTiers = ConstU32<4>; type WeightInfo = pallet_dapp_staking_v3::weights::SubstrateWeight; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = BenchmarkHelper; } construct_runtime!( From feab9d6c57e12a3578562ff820077f4c8137057c Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 18 Dec 2023 11:05:25 +0100 Subject: [PATCH 29/34] Address review comments --- Cargo.lock | 1 + .../Cargo.toml | 0 .../DappsStakingV1.sol | 0 .../DappsStakingV2.sol | 0 .../src/lib.rs | 29 ++++--------------- .../src/test/mock.rs | 0 .../src/test/mod.rs | 0 .../src/test/tests_v1.rs | 0 .../src/test/tests_v2.rs | 0 .../src/test/types.rs | 0 runtime/local/Cargo.toml | 2 ++ runtime/local/src/precompiles.rs | 6 ++-- 12 files changed, 12 insertions(+), 26 deletions(-) rename precompiles/{dapps-staking-v3 => dapp-staking-v3}/Cargo.toml (100%) rename precompiles/{dapps-staking-v3 => dapp-staking-v3}/DappsStakingV1.sol (100%) rename precompiles/{dapps-staking-v3 => dapp-staking-v3}/DappsStakingV2.sol (100%) rename precompiles/{dapps-staking-v3 => dapp-staking-v3}/src/lib.rs (98%) rename precompiles/{dapps-staking-v3 => dapp-staking-v3}/src/test/mock.rs (100%) rename precompiles/{dapps-staking-v3 => dapp-staking-v3}/src/test/mod.rs (100%) rename precompiles/{dapps-staking-v3 => dapp-staking-v3}/src/test/tests_v1.rs (100%) rename precompiles/{dapps-staking-v3 => dapp-staking-v3}/src/test/tests_v2.rs (100%) rename precompiles/{dapps-staking-v3 => dapp-staking-v3}/src/test/types.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 292da16beb..b738f3b1c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6053,6 +6053,7 @@ dependencies = [ "pallet-evm-precompile-assets-erc20", "pallet-evm-precompile-blake2", "pallet-evm-precompile-bn128", + "pallet-evm-precompile-dapp-staking-v3", "pallet-evm-precompile-dapps-staking", "pallet-evm-precompile-dispatch", "pallet-evm-precompile-ed25519", diff --git a/precompiles/dapps-staking-v3/Cargo.toml b/precompiles/dapp-staking-v3/Cargo.toml similarity index 100% rename from precompiles/dapps-staking-v3/Cargo.toml rename to precompiles/dapp-staking-v3/Cargo.toml diff --git a/precompiles/dapps-staking-v3/DappsStakingV1.sol b/precompiles/dapp-staking-v3/DappsStakingV1.sol similarity index 100% rename from precompiles/dapps-staking-v3/DappsStakingV1.sol rename to precompiles/dapp-staking-v3/DappsStakingV1.sol diff --git a/precompiles/dapps-staking-v3/DappsStakingV2.sol b/precompiles/dapp-staking-v3/DappsStakingV2.sol similarity index 100% rename from precompiles/dapps-staking-v3/DappsStakingV2.sol rename to precompiles/dapp-staking-v3/DappsStakingV2.sol diff --git a/precompiles/dapps-staking-v3/src/lib.rs b/precompiles/dapp-staking-v3/src/lib.rs similarity index 98% rename from precompiles/dapps-staking-v3/src/lib.rs rename to precompiles/dapp-staking-v3/src/lib.rs index c8b84f0ade..5178395be5 100644 --- a/precompiles/dapps-staking-v3/src/lib.rs +++ b/precompiles/dapp-staking-v3/src/lib.rs @@ -21,6 +21,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use fp_evm::PrecompileHandle; +use num_enum::{IntoPrimitive, TryFromPrimitive}; use parity_scale_codec::MaxEncodedLen; use frame_support::{ @@ -73,33 +74,13 @@ pub struct SmartContractV2 { } /// Convenience type for smart contract type handling. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)] +#[repr(u8)] pub(crate) enum SmartContractTypes { Evm, Wasm, } -impl TryFrom for SmartContractTypes { - type Error = Revert; - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(Self::Evm), - 1 => Ok(Self::Wasm), - _ => Err(RevertReason::custom("Unknown smart contract type").into()), - } - } -} - -impl Into for SmartContractTypes { - fn into(self) -> u8 { - match self { - Self::Evm => 0, - Self::Wasm => 1, - } - } -} - impl Codec for SmartContractTypes { fn read(reader: &mut Reader) -> MayRevert { let value256: U256 = reader @@ -110,7 +91,9 @@ impl Codec for SmartContractTypes { .try_into() .map_err(|_| RevertReason::value_is_too_large(Self::signature()))?; - value_as_u8.try_into() + value_as_u8 + .try_into() + .map_err(|_| RevertReason::custom("Unknown smart contract type").into()) } fn write(writer: &mut Writer, value: Self) { diff --git a/precompiles/dapps-staking-v3/src/test/mock.rs b/precompiles/dapp-staking-v3/src/test/mock.rs similarity index 100% rename from precompiles/dapps-staking-v3/src/test/mock.rs rename to precompiles/dapp-staking-v3/src/test/mock.rs diff --git a/precompiles/dapps-staking-v3/src/test/mod.rs b/precompiles/dapp-staking-v3/src/test/mod.rs similarity index 100% rename from precompiles/dapps-staking-v3/src/test/mod.rs rename to precompiles/dapp-staking-v3/src/test/mod.rs diff --git a/precompiles/dapps-staking-v3/src/test/tests_v1.rs b/precompiles/dapp-staking-v3/src/test/tests_v1.rs similarity index 100% rename from precompiles/dapps-staking-v3/src/test/tests_v1.rs rename to precompiles/dapp-staking-v3/src/test/tests_v1.rs diff --git a/precompiles/dapps-staking-v3/src/test/tests_v2.rs b/precompiles/dapp-staking-v3/src/test/tests_v2.rs similarity index 100% rename from precompiles/dapps-staking-v3/src/test/tests_v2.rs rename to precompiles/dapp-staking-v3/src/test/tests_v2.rs diff --git a/precompiles/dapps-staking-v3/src/test/types.rs b/precompiles/dapp-staking-v3/src/test/types.rs similarity index 100% rename from precompiles/dapps-staking-v3/src/test/types.rs rename to precompiles/dapp-staking-v3/src/test/types.rs diff --git a/runtime/local/Cargo.toml b/runtime/local/Cargo.toml index b2cadb6b70..9558f55ca6 100644 --- a/runtime/local/Cargo.toml +++ b/runtime/local/Cargo.toml @@ -74,6 +74,7 @@ pallet-dapp-staking-v3 = { workspace = true } pallet-dapps-staking = { workspace = true } pallet-dynamic-evm-base-fee = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } +pallet-evm-precompile-dapp-staking-v3 = { workspace = true } pallet-evm-precompile-dapps-staking = { workspace = true } pallet-evm-precompile-sr25519 = { workspace = true } pallet-evm-precompile-substrate-ecdsa = { workspace = true } @@ -138,6 +139,7 @@ std = [ "pallet-evm-precompile-modexp/std", "pallet-evm-precompile-sha3fips/std", "pallet-evm-precompile-dapps-staking/std", + "pallet-evm-precompile-dapp-staking-v3/std", "pallet-evm-precompile-sr25519/std", "pallet-evm-precompile-substrate-ecdsa/std", "pallet-evm-precompile-unified-accounts/std", diff --git a/runtime/local/src/precompiles.rs b/runtime/local/src/precompiles.rs index 4eca364f73..b5412bd0ad 100644 --- a/runtime/local/src/precompiles.rs +++ b/runtime/local/src/precompiles.rs @@ -28,7 +28,7 @@ use pallet_evm::{ use pallet_evm_precompile_assets_erc20::{AddressToAssetId, Erc20AssetsPrecompileSet}; use pallet_evm_precompile_blake2::Blake2F; use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; -use pallet_evm_precompile_dapps_staking::DappsStakingWrapper; +use pallet_evm_precompile_dapp_staking_v3::DappStakingV3Precompile; use pallet_evm_precompile_dispatch::Dispatch; use pallet_evm_precompile_ed25519::Ed25519Verify; use pallet_evm_precompile_modexp::Modexp; @@ -89,7 +89,7 @@ impl LocalNetworkPrecompiles { impl PrecompileSet for LocalNetworkPrecompiles where Erc20AssetsPrecompileSet: PrecompileSet, - DappsStakingWrapper: Precompile, + DappStakingV3Precompile: Precompile, XvmPrecompile>: Precompile, Dispatch>: Precompile, UnifiedAccountsPrecompile>: Precompile, @@ -132,7 +132,7 @@ where a if a == hash(1027) => Some(Ed25519Verify::execute(handle)), // Astar precompiles (starts from 0x5000): // DappStaking 0x5001 - a if a == hash(20481) => Some(DappsStakingWrapper::::execute(handle)), + a if a == hash(20481) => Some(DappStakingV3Precompile::::execute(handle)), // Sr25519 0x5002 a if a == hash(20482) => Some(Sr25519Precompile::::execute(handle)), // SubstrateEcdsa 0x5003 From 1aaa0be8d67e068439aecdf97231e8f8d18b4e5a Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 18 Dec 2023 11:29:54 +0100 Subject: [PATCH 30/34] Adjustments --- Cargo.lock | 3 +-- precompiles/dapp-staking-v3/Cargo.toml | 4 ++-- runtime/local/Cargo.toml | 2 -- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b51623f4bc..7aa8b2acf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6058,7 +6058,6 @@ dependencies = [ "pallet-evm-precompile-blake2", "pallet-evm-precompile-bn128", "pallet-evm-precompile-dapp-staking-v3", - "pallet-evm-precompile-dapps-staking", "pallet-evm-precompile-dispatch", "pallet-evm-precompile-ed25519", "pallet-evm-precompile-modexp", @@ -7988,7 +7987,7 @@ dependencies = [ "pallet-evm", "pallet-timestamp", "parity-scale-codec", - "precompile-utils-v2", + "precompile-utils", "scale-info", "serde", "sha3", diff --git a/precompiles/dapp-staking-v3/Cargo.toml b/precompiles/dapp-staking-v3/Cargo.toml index 9d10ccb882..5c40d95a5f 100644 --- a/precompiles/dapp-staking-v3/Cargo.toml +++ b/precompiles/dapp-staking-v3/Cargo.toml @@ -24,7 +24,7 @@ sp-std = { workspace = true } # Astar astar-primitives = { workspace = true } pallet-dapp-staking-v3 = { workspace = true } -precompile-utils = { path = "../utils_v2", default-features = false, package = "precompile-utils-v2" } +precompile-utils = { workspace = true, default-features = false } # Frontier fp-evm = { workspace = true } @@ -35,7 +35,7 @@ assert_matches = { workspace = true } derive_more = { workspace = true } pallet-balances = { workspace = true, features = ["std"] } pallet-timestamp = { workspace = true } -precompile-utils = { path = "../utils_v2", default-features = false, package = "precompile-utils-v2", features = ["testing"] } +precompile-utils = { workspace = true, features = ["testing"] } serde = { workspace = true } sha3 = { workspace = true } sp-arithmetic = { workspace = true } diff --git a/runtime/local/Cargo.toml b/runtime/local/Cargo.toml index d7475cf145..de1e2ac1a6 100644 --- a/runtime/local/Cargo.toml +++ b/runtime/local/Cargo.toml @@ -77,7 +77,6 @@ pallet-dapps-staking = { workspace = true } pallet-dynamic-evm-base-fee = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } pallet-evm-precompile-dapp-staking-v3 = { workspace = true } -pallet-evm-precompile-dapps-staking = { workspace = true } pallet-evm-precompile-sr25519 = { workspace = true } pallet-evm-precompile-substrate-ecdsa = { workspace = true } pallet-evm-precompile-unified-accounts = { workspace = true } @@ -143,7 +142,6 @@ std = [ "pallet-evm-precompile-ed25519/std", "pallet-evm-precompile-modexp/std", "pallet-evm-precompile-sha3fips/std", - "pallet-evm-precompile-dapps-staking/std", "pallet-evm-precompile-dapp-staking-v3/std", "pallet-evm-precompile-sr25519/std", "pallet-evm-precompile-substrate-ecdsa/std", From 10f101953a0dfce0b6e6b5035b4001c347d335d8 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 18 Dec 2023 11:45:43 +0100 Subject: [PATCH 31/34] Audit comments --- pallets/dapp-staking-v3/README.md | 3 ++- pallets/dapp-staking-v3/src/lib.rs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pallets/dapp-staking-v3/README.md b/pallets/dapp-staking-v3/README.md index c8de9b0036..48a5b531aa 100644 --- a/pallets/dapp-staking-v3/README.md +++ b/pallets/dapp-staking-v3/README.md @@ -227,7 +227,8 @@ be left out of tiers and won't earn **any** reward. In a special and unlikely case that two or more dApps have the exact same score and satisfy tier entry threshold, but there isn't enough leftover tier capacity to accomodate them all, this is considered _undefined_ behavior. Some of the dApps will manage to enter the tier, while others will be left out. There is no strict rule which defines this behavior - instead dApps are encouraged to ensure their tier entry by -having a larger stake than the other dApp(s). +having a larger stake than the other dApp(s). Tehnically, at the moment, the dApp with the lower `dApp Id` will have the advantage over a dApp with +the larger Id. ### Reward Expiry diff --git a/pallets/dapp-staking-v3/src/lib.rs b/pallets/dapp-staking-v3/src/lib.rs index 39e6f72b0e..a99b36af95 100644 --- a/pallets/dapp-staking-v3/src/lib.rs +++ b/pallets/dapp-staking-v3/src/lib.rs @@ -1659,7 +1659,9 @@ pub mod pallet { // In case when tier has 1 more free slot, but two dApps with exactly same score satisfy the threshold, // one of them will be assigned to the tier, and the other one will be assigned to the lower tier, if it exists. // - // There is no explicit definition of which dApp gets the advantage - it's decided by dApp IDs hash & the unstable sort algorithm. + // In the current implementation, the dApp with the lower dApp Id has the advantage. + // There is no guarantee this will persist in the future, so it's best for dApps to do their + // best to avoid getting themselves into such situations. // 4. Calculate rewards. let tier_rewards = tier_config From 95046709b97f10480b64243b611e5ffb90cdee5c Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 18 Dec 2023 14:17:56 +0100 Subject: [PATCH 32/34] Fix mock --- pallets/dapp-staking-migration/src/mock.rs | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/pallets/dapp-staking-migration/src/mock.rs b/pallets/dapp-staking-migration/src/mock.rs index 062de7f1fa..7f9365af2b 100644 --- a/pallets/dapp-staking-migration/src/mock.rs +++ b/pallets/dapp-staking-migration/src/mock.rs @@ -24,14 +24,13 @@ use frame_support::{ weights::Weight, PalletId, }; -use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use sp_arithmetic::fixed_point::FixedU64; use sp_core::H256; use sp_io::TestExternalities; use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; use astar_primitives::{ - dapp_staking::{CycleConfiguration, StakingRewardHandler}, + dapp_staking::{CycleConfiguration, StakingRewardHandler, SmartContract}, testing::Header, Balance, BlockNumber, }; @@ -134,17 +133,7 @@ impl StakingRewardHandler for DummyStakingRewardHandler { } } -#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, Debug, TypeInfo, MaxEncodedLen, Hash)] -pub enum MockSmartContract { - Wasm(AccountId), - Other(AccountId), -} - -impl Default for MockSmartContract { - fn default() -> Self { - MockSmartContract::Wasm(1) - } -} +pub(crate) type MockSmartContract = SmartContract; #[cfg(feature = "runtime-benchmarks")] pub struct BenchmarkHelper(sp_std::marker::PhantomData<(SC, ACC)>); From 30ac30dac750b14c5dab6533badbc24ead18e9dc Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 18 Dec 2023 14:43:53 +0100 Subject: [PATCH 33/34] FMT --- pallets/dapp-staking-migration/src/mock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/dapp-staking-migration/src/mock.rs b/pallets/dapp-staking-migration/src/mock.rs index 7f9365af2b..c33aa09620 100644 --- a/pallets/dapp-staking-migration/src/mock.rs +++ b/pallets/dapp-staking-migration/src/mock.rs @@ -30,7 +30,7 @@ use sp_io::TestExternalities; use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; use astar_primitives::{ - dapp_staking::{CycleConfiguration, StakingRewardHandler, SmartContract}, + dapp_staking::{CycleConfiguration, SmartContract, StakingRewardHandler}, testing::Header, Balance, BlockNumber, }; From 547d57f52ecd2c1a0380b170028a7afb17cb7f61 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Tue, 19 Dec 2023 12:00:17 +0100 Subject: [PATCH 34/34] Review comments --- Cargo.lock | 1 - runtime/local/src/chain_extensions.rs | 5 ----- runtime/shibuya/Cargo.toml | 2 -- runtime/shibuya/src/chain_extensions.rs | 5 ----- runtime/shibuya/src/lib.rs | 1 - 5 files changed, 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7aa8b2acf5..452564a7c6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13201,7 +13201,6 @@ dependencies = [ "pallet-balances", "pallet-block-rewards-hybrid", "pallet-chain-extension-assets", - "pallet-chain-extension-dapps-staking", "pallet-chain-extension-unified-accounts", "pallet-chain-extension-xvm", "pallet-collator-selection", diff --git a/runtime/local/src/chain_extensions.rs b/runtime/local/src/chain_extensions.rs index f7a9029c88..87fc75da1a 100644 --- a/runtime/local/src/chain_extensions.rs +++ b/runtime/local/src/chain_extensions.rs @@ -28,11 +28,6 @@ pub use pallet_chain_extension_xvm::XvmExtension; // Following impls defines chain extension IDs. -// This can be restored when integrated with dApp staking v3. -// impl RegisteredChainExtension for DappsStakingExtension { -// const ID: u16 = 00; -// } - impl RegisteredChainExtension for XvmExtension { const ID: u16 = 01; } diff --git a/runtime/shibuya/Cargo.toml b/runtime/shibuya/Cargo.toml index 49eb2d6c01..73c2b42296 100644 --- a/runtime/shibuya/Cargo.toml +++ b/runtime/shibuya/Cargo.toml @@ -97,7 +97,6 @@ orml-xtokens = { workspace = true } # Astar pallets astar-primitives = { workspace = true } pallet-block-rewards-hybrid = { workspace = true } -pallet-chain-extension-dapps-staking = { workspace = true } pallet-chain-extension-unified-accounts = { workspace = true } pallet-chain-extension-xvm = { workspace = true } pallet-collator-selection = { workspace = true } @@ -165,7 +164,6 @@ std = [ "pallet-block-rewards-hybrid/std", "pallet-contracts/std", "pallet-contracts-primitives/std", - "pallet-chain-extension-dapps-staking/std", "pallet-chain-extension-xvm/std", "pallet-chain-extension-unified-accounts/std", "pallet-dynamic-evm-base-fee/std", diff --git a/runtime/shibuya/src/chain_extensions.rs b/runtime/shibuya/src/chain_extensions.rs index 516bc8040b..a368fa8d18 100644 --- a/runtime/shibuya/src/chain_extensions.rs +++ b/runtime/shibuya/src/chain_extensions.rs @@ -22,16 +22,11 @@ use super::{Runtime, UnifiedAccounts, Xvm}; pub use pallet_chain_extension_assets::AssetsExtension; use pallet_contracts::chain_extension::RegisteredChainExtension; -pub use pallet_chain_extension_dapps_staking::DappsStakingExtension; pub use pallet_chain_extension_unified_accounts::UnifiedAccountsExtension; pub use pallet_chain_extension_xvm::XvmExtension; // Following impls defines chain extension IDs. -impl RegisteredChainExtension for DappsStakingExtension { - const ID: u16 = 00; -} - impl RegisteredChainExtension for XvmExtension { const ID: u16 = 01; } diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index 38a59fe5ed..3a4a0a3119 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -667,7 +667,6 @@ impl pallet_contracts::Config for Runtime { type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_contracts::weights::SubstrateWeight; type ChainExtension = ( - DappsStakingExtension, XvmExtension, AssetsExtension>, UnifiedAccountsExtension,