feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
[package]
|
||||
name = "pezkuwi-sdk-frame"
|
||||
version = "0.1.0"
|
||||
authors = [
|
||||
"Kurdistan Tech Institute <info@pezkuwichain.io>",
|
||||
"Parity Technologies <admin@parity.io>",
|
||||
]
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "The single package to get you started with building frame pallets and runtimes"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
# external deps
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
# primitive deps, used for developing FRAME pallets.
|
||||
pezsp-arithmetic = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
# frame deps, for developing FRAME pallets.
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
|
||||
# primitive types used for developing FRAME runtimes.
|
||||
pezsp-api = { optional = true, workspace = true }
|
||||
pezsp-block-builder = { optional = true, workspace = true }
|
||||
pezsp-consensus-aura = { optional = true, workspace = true }
|
||||
pezsp-consensus-grandpa = { optional = true, workspace = true }
|
||||
pezsp-genesis-builder = { optional = true, workspace = true }
|
||||
pezsp-inherents = { optional = true, workspace = true }
|
||||
pezsp-keyring = { optional = true, workspace = true }
|
||||
pezsp-offchain = { optional = true, workspace = true }
|
||||
pezsp-session = { optional = true, workspace = true }
|
||||
pezsp-storage = { optional = true, workspace = true }
|
||||
pezsp-transaction-pool = { optional = true, workspace = true }
|
||||
pezsp-version = { optional = true, workspace = true }
|
||||
|
||||
pezframe-executive = { optional = true, workspace = true }
|
||||
pezframe-system-rpc-runtime-api = { optional = true, workspace = true }
|
||||
|
||||
# Used for runtime benchmarking
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-system-benchmarking = { optional = true, workspace = true }
|
||||
|
||||
# Used for try-runtime
|
||||
pezframe-try-runtime = { optional = true, workspace = true }
|
||||
|
||||
docify = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["runtime", "std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-executive?/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system-benchmarking?/std",
|
||||
"pezframe-system-rpc-runtime-api?/std",
|
||||
"pezframe-system/std",
|
||||
"pezframe-try-runtime?/std",
|
||||
"log/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"pezsp-api?/std",
|
||||
"pezsp-arithmetic/std",
|
||||
"pezsp-block-builder?/std",
|
||||
"pezsp-consensus-aura?/std",
|
||||
"pezsp-consensus-grandpa?/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-genesis-builder?/std",
|
||||
"pezsp-inherents?/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-keyring?/std",
|
||||
"pezsp-offchain?/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-session?/std",
|
||||
"pezsp-storage/std",
|
||||
"pezsp-transaction-pool?/std",
|
||||
"pezsp-version?/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-executive?/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system-benchmarking/runtime-benchmarks",
|
||||
"pezframe-system-rpc-runtime-api?/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezframe-try-runtime?/runtime-benchmarks",
|
||||
"pezsp-api?/runtime-benchmarks",
|
||||
"pezsp-block-builder?/runtime-benchmarks",
|
||||
"pezsp-consensus-aura?/runtime-benchmarks",
|
||||
"pezsp-consensus-grandpa?/runtime-benchmarks",
|
||||
"pezsp-genesis-builder?/runtime-benchmarks",
|
||||
"pezsp-inherents?/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-keyring?/runtime-benchmarks",
|
||||
"pezsp-offchain?/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-session?/runtime-benchmarks",
|
||||
"pezsp-transaction-pool?/runtime-benchmarks",
|
||||
"pezsp-version?/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-executive/try-runtime",
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezframe-try-runtime/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
experimental = ["pezframe-support/experimental"]
|
||||
runtime = [
|
||||
"pezframe-executive",
|
||||
"pezframe-system-rpc-runtime-api",
|
||||
"pezsp-api",
|
||||
"pezsp-block-builder",
|
||||
"pezsp-consensus-aura",
|
||||
"pezsp-consensus-grandpa",
|
||||
"pezsp-genesis-builder",
|
||||
"pezsp-inherents",
|
||||
"pezsp-keyring",
|
||||
"pezsp-offchain",
|
||||
"pezsp-session",
|
||||
"pezsp-storage",
|
||||
"pezsp-transaction-pool",
|
||||
"pezsp-version",
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
# FRAME
|
||||
|
||||
The FRAME development environment provides modules (called "pallets") and support libraries that you can use, modify,
|
||||
and extend to build the runtime logic to suit the needs of your blockchain.
|
||||
|
||||
## Documentation
|
||||
|
||||
https://docs.pezkuwichain.io/reference/frame-pallets/
|
||||
|
||||
## Issues
|
||||
|
||||
https://github.com/orgs/paritytech/projects/40
|
||||
@@ -0,0 +1,79 @@
|
||||
[package]
|
||||
name = "pezpallet-alliance"
|
||||
version = "27.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "The Alliance pallet provides a collective for standard-setting industry collaboration."
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
array-bytes = { optional = true, workspace = true, default-features = true }
|
||||
log = { workspace = true }
|
||||
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-crypto-hashing = { optional = true, workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
|
||||
pezpallet-collective = { optional = true, workspace = true }
|
||||
pezpallet-identity = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
array-bytes = { workspace = true, default-features = true }
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezpallet-collective = { workspace = true, default-features = true }
|
||||
pezsp-crypto-hashing = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-balances/std",
|
||||
"pezpallet-collective?/std",
|
||||
"pezpallet-identity/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-crypto-hashing?/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"array-bytes",
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-collective/runtime-benchmarks",
|
||||
"pezpallet-identity/runtime-benchmarks",
|
||||
"pezsp-crypto-hashing",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-collective?/try-runtime",
|
||||
"pezpallet-identity/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,66 @@
|
||||
# Alliance Pallet
|
||||
|
||||
The Alliance Pallet provides a collective that curates a list of accounts and URLs, deemed by
|
||||
the voting members to be unscrupulous actors. The Alliance
|
||||
|
||||
- provides a set of ethics against bad behavior, and
|
||||
- provides recognition and influence for those teams that contribute something back to the
|
||||
ecosystem.
|
||||
|
||||
## Overview
|
||||
|
||||
The network initializes the Alliance via a Root call. After that, anyone with an approved
|
||||
identity and website can join as an Ally. The `MembershipManager` origin can elevate Allies to
|
||||
Fellows, giving them voting rights within the Alliance.
|
||||
|
||||
Voting members of the Alliance maintain a list of accounts and websites. Members can also vote
|
||||
to update the Alliance's rule and make announcements.
|
||||
|
||||
### Terminology
|
||||
|
||||
- Rule: The IPFS CID (hash) of the Alliance rules for the community to read and the Alliance
|
||||
members to enforce. Similar to a Charter or Code of Conduct.
|
||||
- Announcement: An IPFS CID of some content that the Alliance want to announce.
|
||||
- Member: An account that is already in the group of the Alliance, including three types:
|
||||
Fellow, or Ally. A member can also be kicked by the `MembershipManager` origin
|
||||
or retire by itself.
|
||||
- Fellow: An account who is elevated from Ally by other Fellows.
|
||||
- Ally: An account who would like to join the Alliance. To become a voting member (Fellow), it
|
||||
will need approval from the `MembershipManager` origin. Any account can join as an Ally either
|
||||
by placing a deposit or by nomination from a voting member.
|
||||
- Unscrupulous List: A list of bad websites and addresses; items can be added or removed by
|
||||
voting members.
|
||||
|
||||
## Interface
|
||||
|
||||
### Dispatchable Functions
|
||||
|
||||
#### For General Users
|
||||
|
||||
- `join_alliance` - Join the Alliance as an Ally. This requires a slashable deposit.
|
||||
|
||||
#### For Members (All)
|
||||
|
||||
- `give_retirement_notice` - Give a retirement notice and start a retirement period required to
|
||||
pass in order to retire.
|
||||
- `retire` - Retire from the Alliance and release the caller's deposit.
|
||||
|
||||
#### For Voting Members
|
||||
|
||||
- `propose` - Propose a motion.
|
||||
- `vote` - Vote on a motion.
|
||||
- `close` - Close a motion with enough votes or that has expired.
|
||||
- `set_rule` - Initialize or update the Alliance's rule by IPFS CID.
|
||||
- `announce` - Make announcement by IPFS CID.
|
||||
- `nominate_ally` - Nominate a non-member to become an Ally, without deposit.
|
||||
- `elevate_ally` - Approve an ally to become a Fellow.
|
||||
- `kick_member` - Kick a member and slash its deposit.
|
||||
- `add_unscrupulous_items` - Add some items, either accounts or websites, to the list of
|
||||
unscrupulous items.
|
||||
- `remove_unscrupulous_items` - Remove some items from the list of unscrupulous items.
|
||||
- `abdicate_fellow_status` - Abdicate one's voting rights, demoting themselves to Ally.
|
||||
|
||||
#### Root Calls
|
||||
|
||||
- `init_members` - Initialize the Alliance, onboard fellows and allies.
|
||||
- `disband` - Disband the Alliance, remove all active members and unreserve deposits.
|
||||
@@ -0,0 +1,844 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Alliance pallet benchmarking.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use core::{cmp, mem::size_of};
|
||||
use pezsp_runtime::traits::{Bounded, Hash, StaticLookup};
|
||||
|
||||
use pezframe_benchmarking::{account, v2::*, BenchmarkError};
|
||||
use pezframe_support::traits::{EnsureOrigin, Get, UnfilteredDispatchable};
|
||||
use pezframe_system::{pezpallet_prelude::BlockNumberFor, Pallet as System, RawOrigin as SystemOrigin};
|
||||
|
||||
use super::{Call as AllianceCall, Pallet as Alliance, *};
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
const MAX_BYTES: u32 = 1_024;
|
||||
|
||||
fn assert_last_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::RuntimeEvent) {
|
||||
pezframe_system::Pallet::<T>::assert_last_event(generic_event.into());
|
||||
}
|
||||
|
||||
fn cid(input: impl AsRef<[u8]>) -> Cid {
|
||||
let result = pezsp_crypto_hashing::sha2_256(input.as_ref());
|
||||
Cid::new_v0(result)
|
||||
}
|
||||
|
||||
fn rule(input: impl AsRef<[u8]>) -> Cid {
|
||||
cid(input)
|
||||
}
|
||||
|
||||
fn announcement(input: impl AsRef<[u8]>) -> Cid {
|
||||
cid(input)
|
||||
}
|
||||
|
||||
fn funded_account<T: Config<I>, I: 'static>(name: &'static str, index: u32) -> T::AccountId {
|
||||
let account: T::AccountId = account(name, index, SEED);
|
||||
T::Currency::make_free_balance_be(&account, BalanceOf::<T, I>::max_value() / 100u8.into());
|
||||
account
|
||||
}
|
||||
|
||||
fn fellow<T: Config<I>, I: 'static>(index: u32) -> T::AccountId {
|
||||
funded_account::<T, I>("fellow", index)
|
||||
}
|
||||
|
||||
fn ally<T: Config<I>, I: 'static>(index: u32) -> T::AccountId {
|
||||
funded_account::<T, I>("ally", index)
|
||||
}
|
||||
|
||||
fn outsider<T: Config<I>, I: 'static>(index: u32) -> T::AccountId {
|
||||
funded_account::<T, I>("outsider", index)
|
||||
}
|
||||
|
||||
fn generate_unscrupulous_account<T: Config<I>, I: 'static>(index: u32) -> T::AccountId {
|
||||
funded_account::<T, I>("unscrupulous", index)
|
||||
}
|
||||
|
||||
fn set_members<T: Config<I>, I: 'static>() {
|
||||
let fellows: BoundedVec<_, T::MaxMembersCount> =
|
||||
BoundedVec::try_from(vec![fellow::<T, I>(1), fellow::<T, I>(2)]).unwrap();
|
||||
fellows.iter().for_each(|who| {
|
||||
T::Currency::reserve(&who, T::AllyDeposit::get()).unwrap();
|
||||
<DepositOf<T, I>>::insert(&who, T::AllyDeposit::get());
|
||||
});
|
||||
Members::<T, I>::insert(MemberRole::Fellow, fellows.clone());
|
||||
|
||||
let allies: BoundedVec<_, T::MaxMembersCount> =
|
||||
BoundedVec::try_from(vec![ally::<T, I>(1)]).unwrap();
|
||||
allies.iter().for_each(|who| {
|
||||
T::Currency::reserve(&who, T::AllyDeposit::get()).unwrap();
|
||||
<DepositOf<T, I>>::insert(&who, T::AllyDeposit::get());
|
||||
});
|
||||
Members::<T, I>::insert(MemberRole::Ally, allies);
|
||||
|
||||
T::InitializeMembers::initialize_members(&[fellows.as_slice()].concat());
|
||||
}
|
||||
|
||||
#[instance_benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
// This tests when proposal is created and queued as "proposed"
|
||||
#[benchmark]
|
||||
fn propose_proposed(
|
||||
b: Linear<1, MAX_BYTES>,
|
||||
m: Linear<2, { T::MaxFellows::get() }>,
|
||||
p: Linear<1, { T::MaxProposals::get() }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let bytes_in_storage = b + size_of::<Cid>() as u32 + 32;
|
||||
|
||||
// Construct `members`.
|
||||
let fellows = (0..m).map(fellow::<T, I>).collect::<Vec<_>>();
|
||||
let proposer = fellows[0].clone();
|
||||
|
||||
Alliance::<T, I>::init_members(SystemOrigin::Root.into(), fellows, vec![])?;
|
||||
|
||||
let threshold = m;
|
||||
// Add previous proposals.
|
||||
for i in 0..p - 1 {
|
||||
// Proposals should be different so that different proposal hashes are generated
|
||||
let proposal: T::Proposal =
|
||||
AllianceCall::<T, I>::set_rule { rule: rule(vec![i as u8; b as usize]) }.into();
|
||||
Alliance::<T, I>::propose(
|
||||
SystemOrigin::Signed(proposer.clone()).into(),
|
||||
threshold,
|
||||
Box::new(proposal),
|
||||
bytes_in_storage,
|
||||
)?;
|
||||
}
|
||||
|
||||
let proposal: T::Proposal =
|
||||
AllianceCall::<T, I>::set_rule { rule: rule(vec![p as u8; b as usize]) }.into();
|
||||
|
||||
#[extrinsic_call]
|
||||
propose(
|
||||
SystemOrigin::Signed(proposer.clone()),
|
||||
threshold,
|
||||
Box::new(proposal.clone()),
|
||||
bytes_in_storage,
|
||||
);
|
||||
|
||||
let proposal_hash = T::Hashing::hash_of(&proposal);
|
||||
assert_eq!(T::ProposalProvider::proposal_of(proposal_hash), Some(proposal));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn vote(m: Linear<5, { T::MaxFellows::get() }>) -> Result<(), BenchmarkError> {
|
||||
let p = T::MaxProposals::get();
|
||||
let b = MAX_BYTES;
|
||||
let _bytes_in_storage = b + size_of::<Cid>() as u32 + 32;
|
||||
|
||||
// Construct `members`.
|
||||
let fellows = (0..m).map(fellow::<T, I>).collect::<Vec<_>>();
|
||||
let proposer = fellows[0].clone();
|
||||
|
||||
let members = fellows.clone();
|
||||
|
||||
Alliance::<T, I>::init_members(SystemOrigin::Root.into(), fellows, vec![])?;
|
||||
|
||||
// Threshold is 1 less than the number of members so that one person can vote nay
|
||||
let threshold = m - 1;
|
||||
|
||||
// Add previous proposals
|
||||
let mut last_hash = T::Hash::default();
|
||||
for i in 0..p {
|
||||
// Proposals should be different so that different proposal hashes are generated
|
||||
let proposal: T::Proposal =
|
||||
AllianceCall::<T, I>::set_rule { rule: rule(vec![i as u8; b as usize]) }.into();
|
||||
Alliance::<T, I>::propose(
|
||||
SystemOrigin::Signed(proposer.clone()).into(),
|
||||
threshold,
|
||||
Box::new(proposal.clone()),
|
||||
b,
|
||||
)?;
|
||||
last_hash = T::Hashing::hash_of(&proposal);
|
||||
}
|
||||
|
||||
let index = p - 1;
|
||||
// Have almost everyone vote aye on last proposal, while keeping it from passing.
|
||||
for j in 0..m - 3 {
|
||||
let voter = &members[j as usize];
|
||||
Alliance::<T, I>::vote(
|
||||
SystemOrigin::Signed(voter.clone()).into(),
|
||||
last_hash,
|
||||
index,
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
|
||||
let voter = members[m as usize - 3].clone();
|
||||
// Voter votes aye without resolving the vote.
|
||||
Alliance::<T, I>::vote(SystemOrigin::Signed(voter.clone()).into(), last_hash, index, true)?;
|
||||
|
||||
// Voter switches vote to nay, but does not kill the vote, just updates + inserts
|
||||
let approve = false;
|
||||
|
||||
// Whitelist voter account from further DB operations.
|
||||
let voter_key = pezframe_system::Account::<T>::hashed_key_for(&voter);
|
||||
pezframe_benchmarking::benchmarking::add_to_whitelist(voter_key.into());
|
||||
|
||||
#[extrinsic_call]
|
||||
_(SystemOrigin::Signed(voter), last_hash, index, approve);
|
||||
|
||||
//nothing to verify
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn close_early_disapproved(
|
||||
m: Linear<4, { T::MaxFellows::get() }>,
|
||||
p: Linear<1, { T::MaxProposals::get() }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let bytes = 100;
|
||||
let bytes_in_storage = bytes + size_of::<Cid>() as u32 + 32;
|
||||
|
||||
// Construct `members`.
|
||||
let fellows = (0..m).map(fellow::<T, I>).collect::<Vec<_>>();
|
||||
|
||||
let members = fellows.clone();
|
||||
|
||||
Alliance::<T, I>::init_members(SystemOrigin::Root.into(), fellows, vec![])?;
|
||||
|
||||
let proposer = members[0].clone();
|
||||
let voter = members[1].clone();
|
||||
|
||||
// Threshold is total members so that one nay will disapprove the vote
|
||||
let threshold = m;
|
||||
|
||||
// Add previous proposals
|
||||
let mut last_hash = T::Hash::default();
|
||||
for i in 0..p {
|
||||
// Proposals should be different so that different proposal hashes are generated
|
||||
let proposal: T::Proposal =
|
||||
AllianceCall::<T, I>::set_rule { rule: rule(vec![i as u8; bytes as usize]) }.into();
|
||||
Alliance::<T, I>::propose(
|
||||
SystemOrigin::Signed(proposer.clone()).into(),
|
||||
threshold,
|
||||
Box::new(proposal.clone()),
|
||||
bytes_in_storage,
|
||||
)?;
|
||||
last_hash = T::Hashing::hash_of(&proposal);
|
||||
assert_eq!(T::ProposalProvider::proposal_of(last_hash), Some(proposal));
|
||||
}
|
||||
|
||||
let index = p - 1;
|
||||
// Have most everyone vote aye on last proposal, while keeping it from passing.
|
||||
for j in 2..m - 1 {
|
||||
let voter = &members[j as usize];
|
||||
Alliance::<T, I>::vote(
|
||||
SystemOrigin::Signed(voter.clone()).into(),
|
||||
last_hash,
|
||||
index,
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Voter votes aye without resolving the vote.
|
||||
Alliance::<T, I>::vote(SystemOrigin::Signed(voter.clone()).into(), last_hash, index, true)?;
|
||||
|
||||
// Voter switches vote to nay, which kills the vote
|
||||
Alliance::<T, I>::vote(
|
||||
SystemOrigin::Signed(voter.clone()).into(),
|
||||
last_hash,
|
||||
index,
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Whitelist voter account from further DB operations.
|
||||
let voter_key = pezframe_system::Account::<T>::hashed_key_for(&voter);
|
||||
pezframe_benchmarking::benchmarking::add_to_whitelist(voter_key.into());
|
||||
|
||||
#[extrinsic_call]
|
||||
close(SystemOrigin::Signed(voter), last_hash, index, Weight::MAX, bytes_in_storage);
|
||||
|
||||
assert_eq!(T::ProposalProvider::proposal_of(last_hash), None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// We choose 4 as a minimum for param m, so we always trigger a vote in the voting loop (`for j
|
||||
// in ...`)
|
||||
#[benchmark]
|
||||
fn close_early_approved(
|
||||
b: Linear<1, MAX_BYTES>,
|
||||
m: Linear<4, { T::MaxFellows::get() }>,
|
||||
p: Linear<1, { T::MaxProposals::get() }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let bytes_in_storage = b + size_of::<Cid>() as u32 + 32;
|
||||
|
||||
// Construct `members`.
|
||||
let fellows = (0..m).map(fellow::<T, I>).collect::<Vec<_>>();
|
||||
|
||||
let members = fellows.clone();
|
||||
|
||||
Alliance::<T, I>::init_members(SystemOrigin::Root.into(), fellows, vec![])?;
|
||||
|
||||
let proposer = members[0].clone();
|
||||
// Threshold is 2 so any two ayes will approve the vote
|
||||
let threshold = 2;
|
||||
|
||||
// Add previous proposals
|
||||
let mut last_hash = T::Hash::default();
|
||||
for i in 0..p {
|
||||
// Proposals should be different so that different proposal hashes are generated
|
||||
let proposal: T::Proposal =
|
||||
AllianceCall::<T, I>::set_rule { rule: rule(vec![i as u8; b as usize]) }.into();
|
||||
Alliance::<T, I>::propose(
|
||||
SystemOrigin::Signed(proposer.clone()).into(),
|
||||
threshold,
|
||||
Box::new(proposal.clone()),
|
||||
bytes_in_storage,
|
||||
)?;
|
||||
last_hash = T::Hashing::hash_of(&proposal);
|
||||
assert_eq!(T::ProposalProvider::proposal_of(last_hash), Some(proposal));
|
||||
}
|
||||
|
||||
let index = p - 1;
|
||||
// Caller switches vote to nay on their own proposal, allowing them to be the deciding
|
||||
// approval vote
|
||||
Alliance::<T, I>::vote(
|
||||
SystemOrigin::Signed(proposer.clone()).into(),
|
||||
last_hash,
|
||||
index,
|
||||
false,
|
||||
)?;
|
||||
|
||||
// Have almost everyone vote nay on last proposal, while keeping it from failing.
|
||||
for j in 2..m - 1 {
|
||||
let voter = &members[j as usize];
|
||||
Alliance::<T, I>::vote(
|
||||
SystemOrigin::Signed(voter.clone()).into(),
|
||||
last_hash,
|
||||
index,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
|
||||
// Member zero is the first aye
|
||||
Alliance::<T, I>::vote(
|
||||
SystemOrigin::Signed(members[0].clone()).into(),
|
||||
last_hash,
|
||||
index,
|
||||
true,
|
||||
)?;
|
||||
|
||||
let voter = members[1].clone();
|
||||
// Caller switches vote to aye, which passes the vote
|
||||
Alliance::<T, I>::vote(SystemOrigin::Signed(voter.clone()).into(), last_hash, index, true)?;
|
||||
|
||||
#[extrinsic_call]
|
||||
close(SystemOrigin::Signed(voter), last_hash, index, Weight::MAX, bytes_in_storage);
|
||||
|
||||
assert_eq!(T::ProposalProvider::proposal_of(last_hash), None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn close_disapproved(
|
||||
m: Linear<2, { T::MaxFellows::get() }>,
|
||||
p: Linear<1, { T::MaxProposals::get() }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let bytes = 100;
|
||||
let bytes_in_storage = bytes + size_of::<Cid>() as u32 + 32;
|
||||
|
||||
// Construct `members`.
|
||||
let fellows = (0..m).map(fellow::<T, I>).collect::<Vec<_>>();
|
||||
|
||||
let members = fellows.clone();
|
||||
|
||||
Alliance::<T, I>::init_members(SystemOrigin::Root.into(), fellows, vec![])?;
|
||||
|
||||
let proposer = members[0].clone();
|
||||
let voter = members[1].clone();
|
||||
|
||||
// Threshold is one less than total members so that two nays will disapprove the vote
|
||||
let threshold = m - 1;
|
||||
|
||||
// Add proposals
|
||||
let mut last_hash = T::Hash::default();
|
||||
for i in 0..p {
|
||||
// Proposals should be different so that different proposal hashes are generated
|
||||
let proposal: T::Proposal =
|
||||
AllianceCall::<T, I>::set_rule { rule: rule(vec![i as u8; bytes as usize]) }.into();
|
||||
Alliance::<T, I>::propose(
|
||||
SystemOrigin::Signed(proposer.clone()).into(),
|
||||
threshold,
|
||||
Box::new(proposal.clone()),
|
||||
bytes_in_storage,
|
||||
)?;
|
||||
last_hash = T::Hashing::hash_of(&proposal);
|
||||
assert_eq!(T::ProposalProvider::proposal_of(last_hash), Some(proposal));
|
||||
}
|
||||
|
||||
let index = p - 1;
|
||||
// Have almost everyone vote aye on last proposal, while keeping it from passing.
|
||||
// A few abstainers will be the nay votes needed to fail the vote.
|
||||
for j in 2..m - 1 {
|
||||
let voter = &members[j as usize];
|
||||
Alliance::<T, I>::vote(
|
||||
SystemOrigin::Signed(voter.clone()).into(),
|
||||
last_hash,
|
||||
index,
|
||||
true,
|
||||
)?;
|
||||
}
|
||||
|
||||
Alliance::<T, I>::vote(
|
||||
SystemOrigin::Signed(voter.clone()).into(),
|
||||
last_hash,
|
||||
index,
|
||||
false,
|
||||
)?;
|
||||
|
||||
System::<T>::set_block_number(BlockNumberFor::<T>::max_value());
|
||||
|
||||
#[extrinsic_call]
|
||||
close(SystemOrigin::Signed(voter), last_hash, index, Weight::MAX, bytes_in_storage);
|
||||
|
||||
// The last proposal is removed.
|
||||
assert_eq!(T::ProposalProvider::proposal_of(last_hash), None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// We choose 5 fellows as a minimum so we always trigger a vote in the voting loop (`for j in
|
||||
// ...`)
|
||||
#[benchmark]
|
||||
fn close_approved(
|
||||
b: Linear<1, MAX_BYTES>,
|
||||
m: Linear<5, { T::MaxFellows::get() }>,
|
||||
p: Linear<1, { T::MaxProposals::get() }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let bytes_in_storage = b + size_of::<Cid>() as u32 + 32;
|
||||
|
||||
// Construct `members`.
|
||||
let fellows = (0..m).map(fellow::<T, I>).collect::<Vec<_>>();
|
||||
|
||||
let members = fellows.clone();
|
||||
|
||||
Alliance::<T, I>::init_members(SystemOrigin::Root.into(), fellows, vec![])?;
|
||||
|
||||
let proposer = members[0].clone();
|
||||
// Threshold is two, so any two ayes will pass the vote
|
||||
let threshold = 2;
|
||||
|
||||
// Add proposals
|
||||
let mut last_hash = T::Hash::default();
|
||||
for i in 0..p {
|
||||
// Proposals should be different so that different proposal hashes are generated
|
||||
let proposal: T::Proposal =
|
||||
AllianceCall::<T, I>::set_rule { rule: rule(vec![i as u8; b as usize]) }.into();
|
||||
Alliance::<T, I>::propose(
|
||||
SystemOrigin::Signed(proposer.clone()).into(),
|
||||
threshold,
|
||||
Box::new(proposal.clone()),
|
||||
bytes_in_storage,
|
||||
)?;
|
||||
last_hash = T::Hashing::hash_of(&proposal);
|
||||
assert_eq!(T::ProposalProvider::proposal_of(last_hash), Some(proposal));
|
||||
}
|
||||
|
||||
// The prime member votes aye, so abstentions default to aye.
|
||||
Alliance::<T, I>::vote(
|
||||
SystemOrigin::Signed(proposer.clone()).into(),
|
||||
last_hash,
|
||||
p - 1,
|
||||
true, // Vote aye.
|
||||
)?;
|
||||
|
||||
let index = p - 1;
|
||||
// Have almost everyone vote nay on last proposal, while keeping it from failing.
|
||||
// A few abstainers will be the aye votes needed to pass the vote.
|
||||
for j in 2..m - 1 {
|
||||
let voter = &members[j as usize];
|
||||
Alliance::<T, I>::vote(
|
||||
SystemOrigin::Signed(voter.clone()).into(),
|
||||
last_hash,
|
||||
index,
|
||||
false,
|
||||
)?;
|
||||
}
|
||||
|
||||
// caller is prime, prime already votes aye by creating the proposal
|
||||
System::<T>::set_block_number(BlockNumberFor::<T>::max_value());
|
||||
|
||||
#[extrinsic_call]
|
||||
close(SystemOrigin::Signed(proposer), last_hash, index, Weight::MAX, bytes_in_storage);
|
||||
|
||||
assert_eq!(T::ProposalProvider::proposal_of(last_hash), None);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn init_members(
|
||||
m: Linear<1, { T::MaxFellows::get() }>,
|
||||
z: Linear<0, { T::MaxAllies::get() }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let mut fellows = (0..m).map(fellow::<T, I>).collect::<Vec<_>>();
|
||||
let mut allies = (0..z).map(ally::<T, I>).collect::<Vec<_>>();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(SystemOrigin::Root, fellows.clone(), allies.clone());
|
||||
|
||||
fellows.sort();
|
||||
allies.sort();
|
||||
assert_last_event::<T, I>(
|
||||
Event::MembersInitialized { fellows: fellows.clone(), allies: allies.clone() }.into(),
|
||||
);
|
||||
assert_eq!(Members::<T, I>::get(MemberRole::Fellow), fellows);
|
||||
assert_eq!(Members::<T, I>::get(MemberRole::Ally), allies);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn disband(
|
||||
x: Linear<1, { T::MaxFellows::get() }>,
|
||||
y: Linear<0, { T::MaxAllies::get() }>,
|
||||
z: Linear<0, { T::MaxMembersCount::get() / 2 }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let fellows = (0..x).map(fellow::<T, I>).collect::<Vec<_>>();
|
||||
let allies = (0..y).map(ally::<T, I>).collect::<Vec<_>>();
|
||||
let witness = DisbandWitness { fellow_members: x, ally_members: y };
|
||||
|
||||
// setting the Alliance to disband on the benchmark call
|
||||
Alliance::<T, I>::init_members(SystemOrigin::Root.into(), fellows.clone(), allies.clone())?;
|
||||
|
||||
// reserve deposits
|
||||
let deposit = T::AllyDeposit::get();
|
||||
for member in fellows.iter().chain(allies.iter()).take(z as usize) {
|
||||
T::Currency::reserve(&member, deposit)?;
|
||||
<DepositOf<T, I>>::insert(&member, deposit);
|
||||
}
|
||||
|
||||
assert_eq!(Alliance::<T, I>::voting_members_count(), x);
|
||||
assert_eq!(Alliance::<T, I>::ally_members_count(), y);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(SystemOrigin::Root, witness);
|
||||
|
||||
assert_last_event::<T, I>(
|
||||
Event::AllianceDisbanded {
|
||||
fellow_members: x,
|
||||
ally_members: y,
|
||||
unreserved: cmp::min(z, x + y),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
assert!(!Alliance::<T, I>::is_initialized());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_rule() -> Result<(), BenchmarkError> {
|
||||
set_members::<T, I>();
|
||||
|
||||
let rule = rule(b"hello world");
|
||||
|
||||
let call = Call::<T, I>::set_rule { rule: rule.clone() };
|
||||
let origin =
|
||||
T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
#[block]
|
||||
{
|
||||
call.dispatch_bypass_filter(origin)?;
|
||||
}
|
||||
assert_eq!(Rule::<T, I>::get(), Some(rule.clone()));
|
||||
assert_last_event::<T, I>(Event::NewRuleSet { rule }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn announce() -> Result<(), BenchmarkError> {
|
||||
set_members::<T, I>();
|
||||
|
||||
let announcement = announcement(b"hello world");
|
||||
|
||||
let call = Call::<T, I>::announce { announcement: announcement.clone() };
|
||||
let origin = T::AnnouncementOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
#[block]
|
||||
{
|
||||
call.dispatch_bypass_filter(origin)?;
|
||||
}
|
||||
|
||||
assert!(Announcements::<T, I>::get().contains(&announcement));
|
||||
assert_last_event::<T, I>(Event::Announced { announcement }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn remove_announcement() -> Result<(), BenchmarkError> {
|
||||
set_members::<T, I>();
|
||||
|
||||
let announcement = announcement(b"hello world");
|
||||
let announcements: BoundedVec<_, T::MaxAnnouncementsCount> =
|
||||
BoundedVec::try_from(vec![announcement.clone()]).unwrap();
|
||||
Announcements::<T, I>::put(announcements);
|
||||
|
||||
let call = Call::<T, I>::remove_announcement { announcement: announcement.clone() };
|
||||
let origin = T::AnnouncementOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
#[block]
|
||||
{
|
||||
call.dispatch_bypass_filter(origin)?;
|
||||
}
|
||||
|
||||
assert!(!Announcements::<T, I>::get().contains(&announcement));
|
||||
assert_last_event::<T, I>(Event::AnnouncementRemoved { announcement }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn join_alliance() -> Result<(), BenchmarkError> {
|
||||
set_members::<T, I>();
|
||||
|
||||
let outsider = outsider::<T, I>(1);
|
||||
assert!(!Alliance::<T, I>::is_member(&outsider));
|
||||
assert_eq!(DepositOf::<T, I>::get(&outsider), None);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(SystemOrigin::Signed(outsider.clone()));
|
||||
|
||||
assert!(Alliance::<T, I>::is_member_of(&outsider, MemberRole::Ally)); // outsider is now an ally
|
||||
assert_eq!(DepositOf::<T, I>::get(&outsider), Some(T::AllyDeposit::get())); // with a deposit
|
||||
assert!(!Alliance::<T, I>::has_voting_rights(&outsider)); // allies don't have voting rights
|
||||
assert_last_event::<T, I>(
|
||||
Event::NewAllyJoined {
|
||||
ally: outsider,
|
||||
nominator: None,
|
||||
reserved: Some(T::AllyDeposit::get()),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn nominate_ally() -> Result<(), BenchmarkError> {
|
||||
set_members::<T, I>();
|
||||
|
||||
let fellow1 = fellow::<T, I>(1);
|
||||
assert!(Alliance::<T, I>::is_member_of(&fellow1, MemberRole::Fellow));
|
||||
|
||||
let outsider = outsider::<T, I>(1);
|
||||
assert!(!Alliance::<T, I>::is_member(&outsider));
|
||||
assert_eq!(DepositOf::<T, I>::get(&outsider), None);
|
||||
|
||||
let outsider_lookup = T::Lookup::unlookup(outsider.clone());
|
||||
|
||||
#[extrinsic_call]
|
||||
_(SystemOrigin::Signed(fellow1.clone()), outsider_lookup);
|
||||
|
||||
assert!(Alliance::<T, I>::is_member_of(&outsider, MemberRole::Ally)); // outsider is now an ally
|
||||
assert_eq!(DepositOf::<T, I>::get(&outsider), None); // without a deposit
|
||||
assert!(!Alliance::<T, I>::has_voting_rights(&outsider)); // allies don't have voting rights
|
||||
assert_last_event::<T, I>(
|
||||
Event::NewAllyJoined { ally: outsider, nominator: Some(fellow1), reserved: None }
|
||||
.into(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn elevate_ally() -> Result<(), BenchmarkError> {
|
||||
set_members::<T, I>();
|
||||
|
||||
let ally1 = ally::<T, I>(1);
|
||||
assert!(Alliance::<T, I>::is_ally(&ally1));
|
||||
|
||||
let ally1_lookup = T::Lookup::unlookup(ally1.clone());
|
||||
let call = Call::<T, I>::elevate_ally { ally: ally1_lookup };
|
||||
let origin = T::MembershipManager::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
#[block]
|
||||
{
|
||||
call.dispatch_bypass_filter(origin)?;
|
||||
}
|
||||
|
||||
assert!(!Alliance::<T, I>::is_ally(&ally1));
|
||||
assert!(Alliance::<T, I>::has_voting_rights(&ally1));
|
||||
assert_last_event::<T, I>(Event::AllyElevated { ally: ally1 }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn give_retirement_notice() -> Result<(), BenchmarkError> {
|
||||
set_members::<T, I>();
|
||||
let fellow2 = fellow::<T, I>(2);
|
||||
|
||||
assert!(Alliance::<T, I>::has_voting_rights(&fellow2));
|
||||
|
||||
#[extrinsic_call]
|
||||
_(SystemOrigin::Signed(fellow2.clone()));
|
||||
|
||||
assert!(Alliance::<T, I>::is_member_of(&fellow2, MemberRole::Retiring));
|
||||
|
||||
assert_eq!(
|
||||
RetiringMembers::<T, I>::get(&fellow2),
|
||||
Some(System::<T>::block_number() + T::RetirementPeriod::get())
|
||||
);
|
||||
assert_last_event::<T, I>(Event::MemberRetirementPeriodStarted { member: fellow2 }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn retire() -> Result<(), BenchmarkError> {
|
||||
set_members::<T, I>();
|
||||
|
||||
let fellow2 = fellow::<T, I>(2);
|
||||
assert!(Alliance::<T, I>::has_voting_rights(&fellow2));
|
||||
|
||||
assert_eq!(
|
||||
Alliance::<T, I>::give_retirement_notice(SystemOrigin::Signed(fellow2.clone()).into()),
|
||||
Ok(())
|
||||
);
|
||||
System::<T>::set_block_number(System::<T>::block_number() + T::RetirementPeriod::get());
|
||||
|
||||
assert_eq!(DepositOf::<T, I>::get(&fellow2), Some(T::AllyDeposit::get()));
|
||||
|
||||
#[extrinsic_call]
|
||||
_(SystemOrigin::Signed(fellow2.clone()));
|
||||
|
||||
assert!(!Alliance::<T, I>::is_member(&fellow2));
|
||||
assert_eq!(DepositOf::<T, I>::get(&fellow2), None);
|
||||
assert_last_event::<T, I>(
|
||||
Event::MemberRetired { member: fellow2, unreserved: Some(T::AllyDeposit::get()) }
|
||||
.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn kick_member() -> Result<(), BenchmarkError> {
|
||||
set_members::<T, I>();
|
||||
|
||||
let fellow2 = fellow::<T, I>(2);
|
||||
assert!(Alliance::<T, I>::is_member_of(&fellow2, MemberRole::Fellow));
|
||||
assert_eq!(DepositOf::<T, I>::get(&fellow2), Some(T::AllyDeposit::get()));
|
||||
|
||||
let fellow2_lookup = T::Lookup::unlookup(fellow2.clone());
|
||||
let call = Call::<T, I>::kick_member { who: fellow2_lookup };
|
||||
let origin = T::MembershipManager::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
#[block]
|
||||
{
|
||||
call.dispatch_bypass_filter(origin)?;
|
||||
}
|
||||
|
||||
assert!(!Alliance::<T, I>::is_member(&fellow2));
|
||||
assert_eq!(DepositOf::<T, I>::get(&fellow2), None);
|
||||
assert_last_event::<T, I>(
|
||||
Event::MemberKicked { member: fellow2, slashed: Some(T::AllyDeposit::get()) }.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn add_unscrupulous_items(
|
||||
n: Linear<0, { T::MaxUnscrupulousItems::get() }>,
|
||||
l: Linear<0, { T::MaxWebsiteUrlLength::get() }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
set_members::<T, I>();
|
||||
|
||||
let accounts = (0..n).map(|i| generate_unscrupulous_account::<T, I>(i)).collect::<Vec<_>>();
|
||||
let websites = (0..n)
|
||||
.map(|i| -> BoundedVec<u8, T::MaxWebsiteUrlLength> {
|
||||
BoundedVec::try_from(vec![i as u8; l as usize]).unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut unscrupulous_list = Vec::with_capacity(accounts.len() + websites.len());
|
||||
unscrupulous_list.extend(accounts.into_iter().map(UnscrupulousItem::AccountId));
|
||||
unscrupulous_list.extend(websites.into_iter().map(UnscrupulousItem::Website));
|
||||
|
||||
let call = Call::<T, I>::add_unscrupulous_items { items: unscrupulous_list.clone() };
|
||||
let origin = T::AnnouncementOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
#[block]
|
||||
{
|
||||
call.dispatch_bypass_filter(origin)?;
|
||||
}
|
||||
|
||||
assert_last_event::<T, I>(Event::UnscrupulousItemAdded { items: unscrupulous_list }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn remove_unscrupulous_items(
|
||||
n: Linear<0, { T::MaxUnscrupulousItems::get() }>,
|
||||
l: Linear<0, { T::MaxWebsiteUrlLength::get() }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
set_members::<T, I>();
|
||||
|
||||
let mut accounts =
|
||||
(0..n).map(|i| generate_unscrupulous_account::<T, I>(i)).collect::<Vec<_>>();
|
||||
accounts.sort();
|
||||
let accounts: BoundedVec<_, T::MaxUnscrupulousItems> = accounts.try_into().unwrap();
|
||||
UnscrupulousAccounts::<T, I>::put(accounts.clone());
|
||||
|
||||
let mut websites = (0..n)
|
||||
.map(|i| -> BoundedVec<_, T::MaxWebsiteUrlLength> {
|
||||
BoundedVec::try_from(vec![i as u8; l as usize]).unwrap()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
websites.sort();
|
||||
let websites: BoundedVec<_, T::MaxUnscrupulousItems> = websites.try_into().unwrap();
|
||||
UnscrupulousWebsites::<T, I>::put(websites.clone());
|
||||
|
||||
let mut unscrupulous_list = Vec::with_capacity(accounts.len() + websites.len());
|
||||
unscrupulous_list.extend(accounts.into_iter().map(UnscrupulousItem::AccountId));
|
||||
unscrupulous_list.extend(websites.into_iter().map(UnscrupulousItem::Website));
|
||||
|
||||
let call = Call::<T, I>::remove_unscrupulous_items { items: unscrupulous_list.clone() };
|
||||
let origin = T::AnnouncementOrigin::try_successful_origin()
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
|
||||
#[block]
|
||||
{
|
||||
call.dispatch_bypass_filter(origin)?;
|
||||
}
|
||||
|
||||
assert_last_event::<T, I>(
|
||||
Event::UnscrupulousItemRemoved { items: unscrupulous_list }.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn abdicate_fellow_status() -> Result<(), BenchmarkError> {
|
||||
set_members::<T, I>();
|
||||
let fellow2 = fellow::<T, I>(2);
|
||||
assert!(Alliance::<T, I>::has_voting_rights(&fellow2));
|
||||
|
||||
#[extrinsic_call]
|
||||
_(SystemOrigin::Signed(fellow2.clone()));
|
||||
|
||||
assert_last_event::<T, I>(Event::FellowAbdicated { fellow: fellow2 }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Alliance, crate::mock::new_bench_ext(), crate::mock::Test);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,179 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::{Config, Pallet, Weight, LOG_TARGET};
|
||||
use pezframe_support::{pezpallet_prelude::*, storage::migration, traits::OnRuntimeUpgrade};
|
||||
use log;
|
||||
|
||||
/// The in-code storage version.
|
||||
pub const STORAGE_VERSION: StorageVersion = StorageVersion::new(2);
|
||||
|
||||
/// Wrapper for all migrations of this pallet.
|
||||
pub fn migrate<T: Config<I>, I: 'static>() -> Weight {
|
||||
let on_chain_version = Pallet::<T, I>::on_chain_storage_version();
|
||||
let mut weight: Weight = Weight::zero();
|
||||
|
||||
if on_chain_version < 1 {
|
||||
weight = weight.saturating_add(v0_to_v1::migrate::<T, I>());
|
||||
}
|
||||
|
||||
if on_chain_version < 2 {
|
||||
weight = weight.saturating_add(v1_to_v2::migrate::<T, I>());
|
||||
}
|
||||
|
||||
STORAGE_VERSION.put::<Pallet<T, I>>();
|
||||
weight = weight.saturating_add(T::DbWeight::get().writes(1));
|
||||
|
||||
weight
|
||||
}
|
||||
|
||||
/// Implements `OnRuntimeUpgrade` trait.
|
||||
pub struct Migration<T, I = ()>(PhantomData<(T, I)>);
|
||||
|
||||
impl<T: Config<I>, I: 'static> OnRuntimeUpgrade for Migration<T, I> {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
migrate::<T, I>()
|
||||
}
|
||||
}
|
||||
|
||||
/// v0_to_v1: `UpForKicking` is replaced by a retirement period.
|
||||
mod v0_to_v1 {
|
||||
use super::*;
|
||||
|
||||
pub fn migrate<T: Config<I>, I: 'static>() -> Weight {
|
||||
log::info!(target: LOG_TARGET, "Running migration v0_to_v1.");
|
||||
|
||||
let res = migration::clear_storage_prefix(
|
||||
<Pallet<T, I>>::name().as_bytes(),
|
||||
b"UpForKicking",
|
||||
b"",
|
||||
None,
|
||||
None,
|
||||
);
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Cleared '{}' entries from 'UpForKicking' storage prefix",
|
||||
res.unique
|
||||
);
|
||||
|
||||
if res.maybe_cursor.is_some() {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Storage prefix 'UpForKicking' is not completely cleared."
|
||||
);
|
||||
}
|
||||
|
||||
T::DbWeight::get().writes(res.unique.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// v1_to_v2: `Members` storage map collapses `Founder` and `Fellow` keys into one `Fellow`.
|
||||
/// Total number of `Founder`s and `Fellow`s must not be higher than `T::MaxMembersCount`.
|
||||
pub(crate) mod v1_to_v2 {
|
||||
use super::*;
|
||||
use crate::{MemberRole, Members};
|
||||
|
||||
/// V1 Role set.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
pub enum MemberRoleV1 {
|
||||
Founder,
|
||||
Fellow,
|
||||
Ally,
|
||||
Retiring,
|
||||
}
|
||||
|
||||
pub fn migrate<T: Config<I>, I: 'static>() -> Weight {
|
||||
log::info!(target: LOG_TARGET, "Running migration v1_to_v2: `Members` storage map collapses `Founder` and `Fellow` keys into one `Fellow`.");
|
||||
// fetch into the scope all members.
|
||||
let founders_vec = take_members::<T, I>(MemberRoleV1::Founder).into_inner();
|
||||
let mut fellows_vec = take_members::<T, I>(MemberRoleV1::Fellow).into_inner();
|
||||
let allies = take_members::<T, I>(MemberRoleV1::Ally);
|
||||
let retiring = take_members::<T, I>(MemberRoleV1::Retiring);
|
||||
if founders_vec
|
||||
.len()
|
||||
.saturating_add(fellows_vec.len())
|
||||
.saturating_add(allies.len())
|
||||
.saturating_add(retiring.len()) ==
|
||||
0
|
||||
{
|
||||
return T::DbWeight::get().reads(4);
|
||||
}
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Members storage v1 contains, '{}' founders, '{}' fellows, '{}' allies, '{}' retiring members.",
|
||||
founders_vec.len(),
|
||||
fellows_vec.len(),
|
||||
allies.len(),
|
||||
retiring.len(),
|
||||
);
|
||||
// merge founders with fellows and sort.
|
||||
fellows_vec.extend(founders_vec);
|
||||
fellows_vec.sort();
|
||||
if fellows_vec.len() as u32 > T::MaxMembersCount::get() {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Merged list of founders and fellows do not fit into `T::MaxMembersCount` bound. Truncating the merged set into max members count."
|
||||
);
|
||||
fellows_vec.truncate(T::MaxMembersCount::get() as usize);
|
||||
}
|
||||
let fellows: BoundedVec<T::AccountId, T::MaxMembersCount> =
|
||||
fellows_vec.try_into().unwrap_or_default();
|
||||
// insert members with new storage map key.
|
||||
Members::<T, I>::insert(&MemberRole::Fellow, fellows.clone());
|
||||
Members::<T, I>::insert(&MemberRole::Ally, allies.clone());
|
||||
Members::<T, I>::insert(&MemberRole::Retiring, retiring.clone());
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Members storage updated with, '{}' fellows, '{}' allies, '{}' retiring members.",
|
||||
fellows.len(),
|
||||
allies.len(),
|
||||
retiring.len(),
|
||||
);
|
||||
T::DbWeight::get().reads_writes(4, 4)
|
||||
}
|
||||
|
||||
fn take_members<T: Config<I>, I: 'static>(
|
||||
role: MemberRoleV1,
|
||||
) -> BoundedVec<T::AccountId, T::MaxMembersCount> {
|
||||
migration::take_storage_item::<
|
||||
MemberRoleV1,
|
||||
BoundedVec<T::AccountId, T::MaxMembersCount>,
|
||||
Twox64Concat,
|
||||
>(<Pallet<T, I>>::name().as_bytes(), b"Members", role)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::{mock::*, MemberRole, Members};
|
||||
|
||||
#[test]
|
||||
fn migration_v1_to_v2_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(Alliance::join_alliance(RuntimeOrigin::signed(4)));
|
||||
assert_eq!(Members::<Test>::get(MemberRole::Ally), vec![4]);
|
||||
assert_eq!(Members::<Test>::get(MemberRole::Fellow), vec![1, 2, 3]);
|
||||
v1_to_v2::migrate::<Test, ()>();
|
||||
assert_eq!(Members::<Test>::get(MemberRole::Fellow), vec![1, 2, 3, 4]);
|
||||
assert_eq!(Members::<Test>::get(MemberRole::Ally), vec![]);
|
||||
assert_eq!(Members::<Test>::get(MemberRole::Retiring), vec![]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test utilities
|
||||
|
||||
pub use pezsp_core::H256;
|
||||
use pezsp_runtime::traits::Hash;
|
||||
pub use pezsp_runtime::{
|
||||
traits::{BlakeTwo256, IdentifyAccount, Lazy, Verify},
|
||||
BuildStorage,
|
||||
};
|
||||
|
||||
pub use pezframe_support::{
|
||||
assert_noop, assert_ok, derive_impl, ord_parameter_types, parameter_types,
|
||||
traits::EitherOfDiverse, BoundedVec,
|
||||
};
|
||||
use pezframe_system::{EnsureRoot, EnsureSignedBy};
|
||||
use pezpallet_identity::{
|
||||
legacy::{IdentityField, IdentityInfo},
|
||||
Data, IdentityOf, Judgement, SuperOf,
|
||||
};
|
||||
|
||||
pub use crate as pezpallet_alliance;
|
||||
|
||||
use super::*;
|
||||
|
||||
type BlockNumber = u64;
|
||||
type AccountId = u64;
|
||||
|
||||
parameter_types! {
|
||||
pub BlockWeights: pezframe_system::limits::BlockWeights =
|
||||
pezframe_system::limits::BlockWeights::simple_max(Weight::MAX);
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u64>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
const MOTION_DURATION_IN_BLOCKS: BlockNumber = 3;
|
||||
|
||||
parameter_types! {
|
||||
pub const MotionDuration: BlockNumber = MOTION_DURATION_IN_BLOCKS;
|
||||
pub const MaxProposals: u32 = 100;
|
||||
pub const MaxMembers: u32 = 100;
|
||||
pub MaxProposalWeight: Weight = pezsp_runtime::Perbill::from_percent(50) * BlockWeights::get().max_block;
|
||||
}
|
||||
type AllianceCollective = pezpallet_collective::Instance1;
|
||||
impl pezpallet_collective::Config<AllianceCollective> for Test {
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type Proposal = RuntimeCall;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type MotionDuration = MotionDuration;
|
||||
type MaxProposals = MaxProposals;
|
||||
type MaxMembers = MaxMembers;
|
||||
type DefaultVote = pezpallet_collective::PrimeDefaultVote;
|
||||
type WeightInfo = ();
|
||||
type SetMembersOrigin = EnsureRoot<Self::AccountId>;
|
||||
type MaxProposalWeight = MaxProposalWeight;
|
||||
type DisapproveOrigin = EnsureRoot<Self::AccountId>;
|
||||
type KillOrigin = EnsureRoot<Self::AccountId>;
|
||||
type Consideration = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const BasicDeposit: u64 = 100;
|
||||
pub const ByteDeposit: u64 = 10;
|
||||
pub const UsernameDeposit: u64 = 10;
|
||||
pub const SubAccountDeposit: u64 = 100;
|
||||
pub const MaxSubAccounts: u32 = 2;
|
||||
pub const MaxAdditionalFields: u32 = 2;
|
||||
pub const MaxRegistrars: u32 = 20;
|
||||
pub const PendingUsernameExpiration: u64 = 100;
|
||||
pub const UsernameGracePeriod: u64 = 10;
|
||||
}
|
||||
ord_parameter_types! {
|
||||
pub const One: u64 = 1;
|
||||
pub const Two: u64 = 2;
|
||||
pub const Three: u64 = 3;
|
||||
pub const Four: u64 = 4;
|
||||
pub const Five: u64 = 5;
|
||||
}
|
||||
type EnsureOneOrRoot = EitherOfDiverse<EnsureRoot<AccountId>, EnsureSignedBy<One, AccountId>>;
|
||||
type EnsureTwoOrRoot = EitherOfDiverse<EnsureRoot<AccountId>, EnsureSignedBy<Two, AccountId>>;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub struct BenchmarkHelper;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl pezpallet_identity::BenchmarkHelper<AccountU64, AccountU64> for BenchmarkHelper {
|
||||
fn sign_message(_message: &[u8]) -> (AccountU64, AccountU64) {
|
||||
(AccountU64(0), AccountU64(0))
|
||||
}
|
||||
}
|
||||
|
||||
impl pezpallet_identity::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Currency = Balances;
|
||||
type BasicDeposit = BasicDeposit;
|
||||
type ByteDeposit = ByteDeposit;
|
||||
type UsernameDeposit = UsernameDeposit;
|
||||
type SubAccountDeposit = SubAccountDeposit;
|
||||
type MaxSubAccounts = MaxSubAccounts;
|
||||
type IdentityInformation = IdentityInfo<MaxAdditionalFields>;
|
||||
type MaxRegistrars = MaxRegistrars;
|
||||
type Slashed = ();
|
||||
type RegistrarOrigin = EnsureOneOrRoot;
|
||||
type ForceOrigin = EnsureTwoOrRoot;
|
||||
type OffchainSignature = AccountU64;
|
||||
type SigningPublicKey = AccountU64;
|
||||
type UsernameAuthorityOrigin = EnsureOneOrRoot;
|
||||
type PendingUsernameExpiration = PendingUsernameExpiration;
|
||||
type UsernameGracePeriod = UsernameGracePeriod;
|
||||
type MaxSuffixLength = ConstU32<7>;
|
||||
type MaxUsernameLength = ConstU32<32>;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = BenchmarkHelper;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, DecodeWithMemTracking, PartialEq, Eq, TypeInfo)]
|
||||
pub struct AccountU64(u64);
|
||||
impl IdentifyAccount for AccountU64 {
|
||||
type AccountId = u64;
|
||||
fn into_account(self) -> u64 {
|
||||
0u64
|
||||
}
|
||||
}
|
||||
impl Verify for AccountU64 {
|
||||
type Signer = AccountU64;
|
||||
fn verify<L: Lazy<[u8]>>(
|
||||
&self,
|
||||
_msg: L,
|
||||
_signer: &<Self::Signer as IdentifyAccount>::AccountId,
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AllianceIdentityVerifier;
|
||||
impl IdentityVerifier<AccountId> for AllianceIdentityVerifier {
|
||||
fn has_required_identities(who: &AccountId) -> bool {
|
||||
Identity::has_identity(who, (IdentityField::Display | IdentityField::Web).bits())
|
||||
}
|
||||
|
||||
fn has_good_judgement(who: &AccountId) -> bool {
|
||||
if let Some(judgements) =
|
||||
IdentityOf::<Test>::get(who).map(|registration| registration.judgements)
|
||||
{
|
||||
judgements
|
||||
.iter()
|
||||
.any(|(_, j)| matches!(j, Judgement::KnownGood | Judgement::Reasonable))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn super_account_id(who: &AccountId) -> Option<AccountId> {
|
||||
SuperOf::<Test>::get(who).map(|parent| parent.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AllianceProposalProvider;
|
||||
impl ProposalProvider<AccountId, H256, RuntimeCall> for AllianceProposalProvider {
|
||||
fn propose_proposal(
|
||||
who: AccountId,
|
||||
threshold: u32,
|
||||
proposal: Box<RuntimeCall>,
|
||||
length_bound: u32,
|
||||
) -> Result<(u32, u32), DispatchError> {
|
||||
AllianceMotion::do_propose_proposed(who, threshold, proposal, length_bound)
|
||||
}
|
||||
|
||||
fn vote_proposal(
|
||||
who: AccountId,
|
||||
proposal: H256,
|
||||
index: ProposalIndex,
|
||||
approve: bool,
|
||||
) -> Result<bool, DispatchError> {
|
||||
AllianceMotion::do_vote(who, proposal, index, approve)
|
||||
}
|
||||
|
||||
fn close_proposal(
|
||||
proposal_hash: H256,
|
||||
proposal_index: ProposalIndex,
|
||||
proposal_weight_bound: Weight,
|
||||
length_bound: u32,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
AllianceMotion::do_close(proposal_hash, proposal_index, proposal_weight_bound, length_bound)
|
||||
}
|
||||
|
||||
fn proposal_of(proposal_hash: H256) -> Option<RuntimeCall> {
|
||||
pezpallet_collective::ProposalOf::<Test, Instance1>::get(proposal_hash)
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MaxFellows: u32 = MaxMembers::get();
|
||||
pub const MaxAllies: u32 = 100;
|
||||
pub const AllyDeposit: u64 = 25;
|
||||
pub const RetirementPeriod: BlockNumber = MOTION_DURATION_IN_BLOCKS + 1;
|
||||
}
|
||||
impl Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Proposal = RuntimeCall;
|
||||
type AdminOrigin = EnsureSignedBy<One, AccountId>;
|
||||
type MembershipManager = EnsureSignedBy<Two, AccountId>;
|
||||
type AnnouncementOrigin = EnsureSignedBy<Three, AccountId>;
|
||||
type Currency = Balances;
|
||||
type Slashed = ();
|
||||
type InitializeMembers = AllianceMotion;
|
||||
type MembershipChanged = AllianceMotion;
|
||||
#[cfg(not(feature = "runtime-benchmarks"))]
|
||||
type IdentityVerifier = AllianceIdentityVerifier;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type IdentityVerifier = ();
|
||||
type ProposalProvider = AllianceProposalProvider;
|
||||
type MaxProposals = MaxProposals;
|
||||
type MaxFellows = MaxFellows;
|
||||
type MaxAllies = MaxAllies;
|
||||
type MaxUnscrupulousItems = ConstU32<100>;
|
||||
type MaxWebsiteUrlLength = ConstU32<255>;
|
||||
type MaxAnnouncementsCount = ConstU32<100>;
|
||||
type MaxMembersCount = MaxMembers;
|
||||
type AllyDeposit = AllyDeposit;
|
||||
type WeightInfo = ();
|
||||
type RetirementPeriod = RetirementPeriod;
|
||||
}
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
pezframe_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Balances: pezpallet_balances,
|
||||
Identity: pezpallet_identity,
|
||||
AllianceMotion: pezpallet_collective::<Instance1>,
|
||||
Alliance: pezpallet_alliance,
|
||||
}
|
||||
);
|
||||
|
||||
fn test_identity_info() -> IdentityInfo<MaxAdditionalFields> {
|
||||
IdentityInfo {
|
||||
additional: BoundedVec::default(),
|
||||
display: Data::Raw(b"name".to_vec().try_into().unwrap()),
|
||||
legal: Data::default(),
|
||||
web: Data::Raw(b"website".to_vec().try_into().unwrap()),
|
||||
riot: Data::default(),
|
||||
email: Data::default(),
|
||||
pgp_fingerprint: None,
|
||||
image: Data::default(),
|
||||
twitter: Data::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn test_identity_info_deposit() -> <Test as pezpallet_balances::Config>::Balance {
|
||||
let basic_deposit: u64 = <Test as pezpallet_identity::Config>::BasicDeposit::get();
|
||||
let byte_deposit: u64 = <Test as pezpallet_identity::Config>::ByteDeposit::get();
|
||||
byte_deposit * test_identity_info().encoded_size() as u64 + basic_deposit
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
|
||||
pezpallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![
|
||||
(1, 1000),
|
||||
(2, 1000),
|
||||
(3, 1000),
|
||||
(4, 1000),
|
||||
(5, test_identity_info_deposit() + 10),
|
||||
(6, 1000),
|
||||
(7, 1000),
|
||||
(8, 1000),
|
||||
(9, 1000),
|
||||
],
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
pezpallet_alliance::GenesisConfig::<Test> {
|
||||
fellows: vec![],
|
||||
allies: vec![],
|
||||
phantom: Default::default(),
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = pezsp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| {
|
||||
assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 1));
|
||||
|
||||
let info = test_identity_info();
|
||||
assert_ok!(Identity::set_identity(RuntimeOrigin::signed(1), Box::new(info.clone())));
|
||||
assert_ok!(Identity::provide_judgement(
|
||||
RuntimeOrigin::signed(1),
|
||||
0,
|
||||
1,
|
||||
Judgement::KnownGood,
|
||||
BlakeTwo256::hash_of(&info)
|
||||
));
|
||||
assert_ok!(Identity::set_identity(RuntimeOrigin::signed(2), Box::new(info.clone())));
|
||||
assert_ok!(Identity::provide_judgement(
|
||||
RuntimeOrigin::signed(1),
|
||||
0,
|
||||
2,
|
||||
Judgement::KnownGood,
|
||||
BlakeTwo256::hash_of(&info)
|
||||
));
|
||||
assert_ok!(Identity::set_identity(RuntimeOrigin::signed(3), Box::new(info.clone())));
|
||||
assert_ok!(Identity::provide_judgement(
|
||||
RuntimeOrigin::signed(1),
|
||||
0,
|
||||
3,
|
||||
Judgement::KnownGood,
|
||||
BlakeTwo256::hash_of(&info)
|
||||
));
|
||||
assert_ok!(Identity::set_identity(RuntimeOrigin::signed(4), Box::new(info.clone())));
|
||||
assert_ok!(Identity::provide_judgement(
|
||||
RuntimeOrigin::signed(1),
|
||||
0,
|
||||
4,
|
||||
Judgement::KnownGood,
|
||||
BlakeTwo256::hash_of(&info)
|
||||
));
|
||||
assert_ok!(Identity::set_identity(RuntimeOrigin::signed(5), Box::new(info.clone())));
|
||||
assert_ok!(Identity::provide_judgement(
|
||||
RuntimeOrigin::signed(1),
|
||||
0,
|
||||
5,
|
||||
Judgement::KnownGood,
|
||||
BlakeTwo256::hash_of(&info)
|
||||
));
|
||||
assert_ok!(Identity::set_identity(RuntimeOrigin::signed(6), Box::new(info.clone())));
|
||||
assert_ok!(Identity::set_identity(RuntimeOrigin::signed(8), Box::new(info.clone())));
|
||||
assert_ok!(Identity::provide_judgement(
|
||||
RuntimeOrigin::signed(1),
|
||||
0,
|
||||
8,
|
||||
Judgement::KnownGood,
|
||||
BlakeTwo256::hash_of(&info)
|
||||
));
|
||||
assert_ok!(Identity::set_identity(RuntimeOrigin::signed(9), Box::new(info.clone())));
|
||||
assert_ok!(Identity::provide_judgement(
|
||||
RuntimeOrigin::signed(1),
|
||||
0,
|
||||
9,
|
||||
Judgement::KnownGood,
|
||||
BlakeTwo256::hash_of(&info)
|
||||
));
|
||||
|
||||
// Joining before init should fail.
|
||||
assert_noop!(
|
||||
Alliance::join_alliance(RuntimeOrigin::signed(1)),
|
||||
Error::<Test, ()>::AllianceNotYetInitialized
|
||||
);
|
||||
|
||||
assert_ok!(Alliance::init_members(RuntimeOrigin::root(), vec![1, 2, 3], vec![]));
|
||||
|
||||
System::set_block_number(1);
|
||||
});
|
||||
ext
|
||||
}
|
||||
|
||||
pub fn build_and_execute(test: impl FnOnce()) {
|
||||
new_test_ext().execute_with(|| {
|
||||
test();
|
||||
Alliance::do_try_state().expect("All invariants must hold after a test");
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub fn new_bench_ext() -> pezsp_io::TestExternalities {
|
||||
RuntimeGenesisConfig::default().build_storage().unwrap().into()
|
||||
}
|
||||
|
||||
pub fn test_cid() -> Cid {
|
||||
let result = pezsp_crypto_hashing::sha2_256(b"hello world");
|
||||
Cid::new_v0(result)
|
||||
}
|
||||
|
||||
pub fn make_remark_proposal(value: u64) -> (RuntimeCall, u32, H256) {
|
||||
make_proposal(RuntimeCall::System(pezframe_system::Call::remark { remark: value.encode() }))
|
||||
}
|
||||
|
||||
pub fn make_kick_member_proposal(who: AccountId) -> (RuntimeCall, u32, H256) {
|
||||
make_proposal(RuntimeCall::Alliance(pezpallet_alliance::Call::kick_member { who }))
|
||||
}
|
||||
|
||||
pub fn make_proposal(proposal: RuntimeCall) -> (RuntimeCall, u32, H256) {
|
||||
let len: u32 = proposal.using_encoded(|p| p.len() as u32);
|
||||
let hash = BlakeTwo256::hash_of(&proposal);
|
||||
(proposal, len, hash)
|
||||
}
|
||||
|
||||
pub fn is_fellow(who: &AccountId) -> bool {
|
||||
Alliance::is_member_of(who, MemberRole::Fellow)
|
||||
}
|
||||
@@ -0,0 +1,652 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Tests for the alliance pallet.
|
||||
|
||||
use pezframe_support::{assert_noop, assert_ok};
|
||||
use pezframe_system::{EventRecord, Phase};
|
||||
use pezsp_runtime::traits::BadOrigin;
|
||||
|
||||
use super::*;
|
||||
use crate::{self as alliance, mock::*};
|
||||
|
||||
type AllianceMotionEvent = pezpallet_collective::Event<Test, pezpallet_collective::Instance1>;
|
||||
|
||||
fn assert_powerless(user: RuntimeOrigin, user_is_member: bool) {
|
||||
//vote / veto with a valid proposal
|
||||
let cid = test_cid();
|
||||
let (proposal, _, _) = make_kick_member_proposal(42);
|
||||
|
||||
assert_noop!(Alliance::init_members(user.clone(), vec![], vec![],), BadOrigin);
|
||||
|
||||
assert_noop!(
|
||||
Alliance::disband(user.clone(), DisbandWitness { fellow_members: 3, ..Default::default() }),
|
||||
BadOrigin
|
||||
);
|
||||
|
||||
assert_noop!(Alliance::set_rule(user.clone(), cid.clone()), BadOrigin);
|
||||
|
||||
assert_noop!(Alliance::retire(user.clone()), Error::<Test, ()>::RetirementNoticeNotGiven);
|
||||
|
||||
// Allies should be able to give retirement notice.
|
||||
if !user_is_member {
|
||||
assert_noop!(Alliance::give_retirement_notice(user.clone()), Error::<Test, ()>::NotMember);
|
||||
}
|
||||
|
||||
assert_noop!(Alliance::elevate_ally(user.clone(), 4), BadOrigin);
|
||||
|
||||
assert_noop!(Alliance::kick_member(user.clone(), 1), BadOrigin);
|
||||
|
||||
assert_noop!(Alliance::nominate_ally(user.clone(), 4), Error::<Test, ()>::NoVotingRights);
|
||||
|
||||
assert_noop!(
|
||||
Alliance::propose(user.clone(), 5, Box::new(proposal), 1000),
|
||||
Error::<Test, ()>::NoVotingRights
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn init_members_works() {
|
||||
build_and_execute(|| {
|
||||
// alliance must be reset first, no witness data
|
||||
assert_noop!(
|
||||
Alliance::init_members(RuntimeOrigin::root(), vec![8], vec![],),
|
||||
Error::<Test, ()>::AllianceAlreadyInitialized,
|
||||
);
|
||||
|
||||
// give a retirement notice to check later a retiring member not removed
|
||||
assert_ok!(Alliance::give_retirement_notice(RuntimeOrigin::signed(2)));
|
||||
assert!(Alliance::is_member_of(&2, MemberRole::Retiring));
|
||||
|
||||
// disband the Alliance to init new
|
||||
assert_ok!(Alliance::disband(RuntimeOrigin::root(), DisbandWitness::new(2, 0)));
|
||||
|
||||
// fails without root
|
||||
assert_noop!(Alliance::init_members(RuntimeOrigin::signed(1), vec![], vec![]), BadOrigin);
|
||||
|
||||
// fellows missing, other members given
|
||||
assert_noop!(
|
||||
Alliance::init_members(RuntimeOrigin::root(), vec![], vec![2],),
|
||||
Error::<Test, ()>::FellowsMissing,
|
||||
);
|
||||
|
||||
// success call
|
||||
assert_ok!(Alliance::init_members(RuntimeOrigin::root(), vec![8, 5], vec![2],));
|
||||
|
||||
// assert new set of voting members
|
||||
assert_eq!(Alliance::voting_members(), vec![5, 8]);
|
||||
// assert new members member
|
||||
assert!(is_fellow(&8));
|
||||
assert!(is_fellow(&5));
|
||||
assert!(Alliance::is_ally(&2));
|
||||
// assert a retiring member from previous Alliance not removed
|
||||
assert!(Alliance::is_member_of(&2, MemberRole::Retiring));
|
||||
|
||||
System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::MembersInitialized {
|
||||
fellows: vec![5, 8],
|
||||
allies: vec![2],
|
||||
}));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disband_works() {
|
||||
build_and_execute(|| {
|
||||
let id_deposit = test_identity_info_deposit();
|
||||
let expected_join_deposit = <Test as Config>::AllyDeposit::get();
|
||||
assert_eq!(Balances::free_balance(9), 1000 - id_deposit);
|
||||
// ensure alliance is set
|
||||
assert_eq!(Alliance::voting_members(), vec![1, 2, 3]);
|
||||
|
||||
// give a retirement notice to check later a retiring member not removed
|
||||
assert_ok!(Alliance::give_retirement_notice(RuntimeOrigin::signed(2)));
|
||||
assert!(Alliance::is_member_of(&2, MemberRole::Retiring));
|
||||
|
||||
// join alliance and reserve funds
|
||||
assert_eq!(Balances::free_balance(9), 1000 - id_deposit);
|
||||
assert_ok!(Alliance::join_alliance(RuntimeOrigin::signed(9)));
|
||||
assert_eq!(alliance::DepositOf::<Test>::get(9), Some(expected_join_deposit));
|
||||
assert_eq!(Balances::free_balance(9), 1000 - id_deposit - expected_join_deposit);
|
||||
assert!(Alliance::is_member_of(&9, MemberRole::Ally));
|
||||
|
||||
// fails without root
|
||||
assert_noop!(Alliance::disband(RuntimeOrigin::signed(1), Default::default()), BadOrigin);
|
||||
|
||||
// bad witness data checks
|
||||
assert_noop!(
|
||||
Alliance::disband(RuntimeOrigin::root(), Default::default(),),
|
||||
Error::<Test, ()>::BadWitness
|
||||
);
|
||||
|
||||
assert_noop!(
|
||||
Alliance::disband(RuntimeOrigin::root(), DisbandWitness::new(1, 1)),
|
||||
Error::<Test, ()>::BadWitness,
|
||||
);
|
||||
assert_noop!(
|
||||
Alliance::disband(RuntimeOrigin::root(), DisbandWitness::new(2, 0)),
|
||||
Error::<Test, ()>::BadWitness,
|
||||
);
|
||||
|
||||
// success call
|
||||
assert_ok!(Alliance::disband(RuntimeOrigin::root(), DisbandWitness::new(2, 1)));
|
||||
|
||||
// assert members disband
|
||||
assert!(!Alliance::is_member(&1));
|
||||
assert!(!Alliance::is_initialized());
|
||||
// assert a retiring member from the previous Alliance not removed
|
||||
assert!(Alliance::is_member_of(&2, MemberRole::Retiring));
|
||||
// deposit unreserved
|
||||
assert_eq!(Balances::free_balance(9), 1000 - id_deposit);
|
||||
|
||||
System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::AllianceDisbanded {
|
||||
fellow_members: 2,
|
||||
ally_members: 1,
|
||||
unreserved: 1,
|
||||
}));
|
||||
|
||||
// the Alliance must be set first
|
||||
assert_noop!(
|
||||
Alliance::disband(RuntimeOrigin::root(), DisbandWitness::new(100, 100)),
|
||||
Error::<Test, ()>::AllianceNotYetInitialized,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn propose_works() {
|
||||
build_and_execute(|| {
|
||||
let (proposal, proposal_len, hash) = make_remark_proposal(42);
|
||||
|
||||
// only voting member can propose proposal, 4 is ally not have vote rights
|
||||
assert_noop!(
|
||||
Alliance::propose(
|
||||
RuntimeOrigin::signed(4),
|
||||
3,
|
||||
Box::new(proposal.clone()),
|
||||
proposal_len
|
||||
),
|
||||
Error::<Test, ()>::NoVotingRights
|
||||
);
|
||||
|
||||
assert_ok!(Alliance::propose(
|
||||
RuntimeOrigin::signed(1),
|
||||
3,
|
||||
Box::new(proposal.clone()),
|
||||
proposal_len
|
||||
));
|
||||
assert_eq!(*pezpallet_collective::Proposals::<Test, Instance1>::get(), vec![hash]);
|
||||
assert_eq!(pezpallet_collective::ProposalOf::<Test, Instance1>::get(&hash), Some(proposal));
|
||||
assert_eq!(
|
||||
System::events(),
|
||||
vec![EventRecord {
|
||||
phase: Phase::Initialization,
|
||||
event: mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Proposed {
|
||||
account: 1,
|
||||
proposal_index: 0,
|
||||
proposal_hash: hash,
|
||||
threshold: 3,
|
||||
}),
|
||||
topics: vec![],
|
||||
}]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vote_works() {
|
||||
build_and_execute(|| {
|
||||
let (proposal, proposal_len, hash) = make_remark_proposal(42);
|
||||
assert_ok!(Alliance::propose(
|
||||
RuntimeOrigin::signed(1),
|
||||
3,
|
||||
Box::new(proposal.clone()),
|
||||
proposal_len
|
||||
));
|
||||
assert_ok!(Alliance::vote(RuntimeOrigin::signed(2), hash, 0, true));
|
||||
|
||||
let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] };
|
||||
assert_eq!(
|
||||
System::events(),
|
||||
vec![
|
||||
record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Proposed {
|
||||
account: 1,
|
||||
proposal_index: 0,
|
||||
proposal_hash: hash,
|
||||
threshold: 3
|
||||
})),
|
||||
record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Voted {
|
||||
account: 2,
|
||||
proposal_hash: hash,
|
||||
voted: true,
|
||||
yes: 1,
|
||||
no: 0,
|
||||
})),
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn close_works() {
|
||||
build_and_execute(|| {
|
||||
let (proposal, proposal_len, hash) = make_remark_proposal(42);
|
||||
let proposal_weight = proposal.get_dispatch_info().call_weight;
|
||||
assert_ok!(Alliance::propose(
|
||||
RuntimeOrigin::signed(1),
|
||||
3,
|
||||
Box::new(proposal.clone()),
|
||||
proposal_len
|
||||
));
|
||||
assert_ok!(Alliance::vote(RuntimeOrigin::signed(1), hash, 0, true));
|
||||
assert_ok!(Alliance::vote(RuntimeOrigin::signed(2), hash, 0, true));
|
||||
assert_ok!(Alliance::vote(RuntimeOrigin::signed(3), hash, 0, true));
|
||||
assert_ok!(Alliance::close(
|
||||
RuntimeOrigin::signed(1),
|
||||
hash,
|
||||
0,
|
||||
proposal_weight,
|
||||
proposal_len
|
||||
));
|
||||
|
||||
let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] };
|
||||
assert_eq!(
|
||||
System::events(),
|
||||
vec![
|
||||
record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Proposed {
|
||||
account: 1,
|
||||
proposal_index: 0,
|
||||
proposal_hash: hash,
|
||||
threshold: 3
|
||||
})),
|
||||
record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Voted {
|
||||
account: 1,
|
||||
proposal_hash: hash,
|
||||
voted: true,
|
||||
yes: 1,
|
||||
no: 0,
|
||||
})),
|
||||
record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Voted {
|
||||
account: 2,
|
||||
proposal_hash: hash,
|
||||
voted: true,
|
||||
yes: 2,
|
||||
no: 0,
|
||||
})),
|
||||
record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Voted {
|
||||
account: 3,
|
||||
proposal_hash: hash,
|
||||
voted: true,
|
||||
yes: 3,
|
||||
no: 0,
|
||||
})),
|
||||
record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Closed {
|
||||
proposal_hash: hash,
|
||||
yes: 3,
|
||||
no: 0,
|
||||
})),
|
||||
record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Approved {
|
||||
proposal_hash: hash
|
||||
})),
|
||||
record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Executed {
|
||||
proposal_hash: hash,
|
||||
result: Ok(()),
|
||||
}))
|
||||
]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_rule_works() {
|
||||
build_and_execute(|| {
|
||||
let cid = test_cid();
|
||||
assert_ok!(Alliance::set_rule(RuntimeOrigin::signed(1), cid.clone()));
|
||||
assert_eq!(alliance::Rule::<Test>::get(), Some(cid.clone()));
|
||||
|
||||
System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::NewRuleSet {
|
||||
rule: cid,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn announce_works() {
|
||||
build_and_execute(|| {
|
||||
let cid = test_cid();
|
||||
|
||||
assert_noop!(Alliance::announce(RuntimeOrigin::signed(2), cid.clone()), BadOrigin);
|
||||
|
||||
assert_ok!(Alliance::announce(RuntimeOrigin::signed(3), cid.clone()));
|
||||
assert_eq!(alliance::Announcements::<Test>::get(), vec![cid.clone()]);
|
||||
|
||||
System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::Announced {
|
||||
announcement: cid,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_announcement_works() {
|
||||
build_and_execute(|| {
|
||||
let cid = test_cid();
|
||||
assert_ok!(Alliance::announce(RuntimeOrigin::signed(3), cid.clone()));
|
||||
assert_eq!(alliance::Announcements::<Test>::get(), vec![cid.clone()]);
|
||||
System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::Announced {
|
||||
announcement: cid.clone(),
|
||||
}));
|
||||
|
||||
System::set_block_number(2);
|
||||
|
||||
assert_ok!(Alliance::remove_announcement(RuntimeOrigin::signed(3), cid.clone()));
|
||||
assert_eq!(alliance::Announcements::<Test>::get(), vec![]);
|
||||
System::assert_last_event(mock::RuntimeEvent::Alliance(
|
||||
crate::Event::AnnouncementRemoved { announcement: cid },
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_alliance_works() {
|
||||
build_and_execute(|| {
|
||||
let id_deposit = test_identity_info_deposit();
|
||||
let join_deposit = <Test as Config>::AllyDeposit::get();
|
||||
assert_eq!(Balances::free_balance(9), 1000 - id_deposit);
|
||||
// check already member
|
||||
assert_noop!(
|
||||
Alliance::join_alliance(RuntimeOrigin::signed(1)),
|
||||
Error::<Test, ()>::AlreadyMember
|
||||
);
|
||||
|
||||
// check already listed as unscrupulous
|
||||
assert_ok!(Alliance::add_unscrupulous_items(
|
||||
RuntimeOrigin::signed(3),
|
||||
vec![UnscrupulousItem::AccountId(4)]
|
||||
));
|
||||
assert_noop!(
|
||||
Alliance::join_alliance(RuntimeOrigin::signed(4)),
|
||||
Error::<Test, ()>::AccountNonGrata
|
||||
);
|
||||
assert_ok!(Alliance::remove_unscrupulous_items(
|
||||
RuntimeOrigin::signed(3),
|
||||
vec![UnscrupulousItem::AccountId(4)]
|
||||
));
|
||||
|
||||
// check deposit funds
|
||||
assert_noop!(
|
||||
Alliance::join_alliance(RuntimeOrigin::signed(5)),
|
||||
Error::<Test, ()>::InsufficientFunds
|
||||
);
|
||||
|
||||
assert_eq!(Balances::free_balance(4), 1000 - id_deposit);
|
||||
// success to submit
|
||||
assert_ok!(Alliance::join_alliance(RuntimeOrigin::signed(4)));
|
||||
assert_eq!(Balances::free_balance(4), 1000 - id_deposit - join_deposit);
|
||||
assert_eq!(alliance::DepositOf::<Test>::get(4), Some(25));
|
||||
assert_eq!(alliance::Members::<Test>::get(MemberRole::Ally), vec![4]);
|
||||
|
||||
// check already member
|
||||
assert_noop!(
|
||||
Alliance::join_alliance(RuntimeOrigin::signed(4)),
|
||||
Error::<Test, ()>::AlreadyMember
|
||||
);
|
||||
|
||||
// check missing identity judgement
|
||||
#[cfg(not(feature = "runtime-benchmarks"))]
|
||||
assert_noop!(
|
||||
Alliance::join_alliance(RuntimeOrigin::signed(6)),
|
||||
Error::<Test, ()>::WithoutGoodIdentityJudgement
|
||||
);
|
||||
// check missing identity info
|
||||
#[cfg(not(feature = "runtime-benchmarks"))]
|
||||
assert_noop!(
|
||||
Alliance::join_alliance(RuntimeOrigin::signed(7)),
|
||||
Error::<Test, ()>::WithoutRequiredIdentityFields
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nominate_ally_works() {
|
||||
build_and_execute(|| {
|
||||
// check already member
|
||||
assert_noop!(
|
||||
Alliance::nominate_ally(RuntimeOrigin::signed(1), 2),
|
||||
Error::<Test, ()>::AlreadyMember
|
||||
);
|
||||
|
||||
// only voting members (Fellows) have nominate right
|
||||
assert_noop!(
|
||||
Alliance::nominate_ally(RuntimeOrigin::signed(5), 4),
|
||||
Error::<Test, ()>::NoVotingRights
|
||||
);
|
||||
|
||||
// check already listed as unscrupulous
|
||||
assert_ok!(Alliance::add_unscrupulous_items(
|
||||
RuntimeOrigin::signed(3),
|
||||
vec![UnscrupulousItem::AccountId(4)]
|
||||
));
|
||||
assert_noop!(
|
||||
Alliance::nominate_ally(RuntimeOrigin::signed(1), 4),
|
||||
Error::<Test, ()>::AccountNonGrata
|
||||
);
|
||||
assert_ok!(Alliance::remove_unscrupulous_items(
|
||||
RuntimeOrigin::signed(3),
|
||||
vec![UnscrupulousItem::AccountId(4)]
|
||||
));
|
||||
|
||||
// success to nominate
|
||||
assert_ok!(Alliance::nominate_ally(RuntimeOrigin::signed(1), 4));
|
||||
assert_eq!(alliance::DepositOf::<Test>::get(4), None);
|
||||
assert_eq!(alliance::Members::<Test>::get(MemberRole::Ally), vec![4]);
|
||||
|
||||
// check already member
|
||||
assert_noop!(
|
||||
Alliance::nominate_ally(RuntimeOrigin::signed(1), 4),
|
||||
Error::<Test, ()>::AlreadyMember
|
||||
);
|
||||
|
||||
// check missing identity judgement
|
||||
#[cfg(not(feature = "runtime-benchmarks"))]
|
||||
assert_noop!(
|
||||
Alliance::join_alliance(RuntimeOrigin::signed(6)),
|
||||
Error::<Test, ()>::WithoutGoodIdentityJudgement
|
||||
);
|
||||
// check missing identity info
|
||||
#[cfg(not(feature = "runtime-benchmarks"))]
|
||||
assert_noop!(
|
||||
Alliance::join_alliance(RuntimeOrigin::signed(7)),
|
||||
Error::<Test, ()>::WithoutRequiredIdentityFields
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn elevate_ally_works() {
|
||||
build_and_execute(|| {
|
||||
assert_noop!(
|
||||
Alliance::elevate_ally(RuntimeOrigin::signed(2), 4),
|
||||
Error::<Test, ()>::NotAlly
|
||||
);
|
||||
|
||||
assert_ok!(Alliance::join_alliance(RuntimeOrigin::signed(4)));
|
||||
assert_eq!(alliance::Members::<Test>::get(MemberRole::Ally), vec![4]);
|
||||
assert_eq!(alliance::Members::<Test>::get(MemberRole::Fellow), vec![1, 2, 3]);
|
||||
|
||||
assert_ok!(Alliance::elevate_ally(RuntimeOrigin::signed(2), 4));
|
||||
assert_eq!(alliance::Members::<Test>::get(MemberRole::Ally), Vec::<u64>::new());
|
||||
assert_eq!(alliance::Members::<Test>::get(MemberRole::Fellow), vec![1, 2, 3, 4]);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn give_retirement_notice_work() {
|
||||
build_and_execute(|| {
|
||||
assert_noop!(
|
||||
Alliance::give_retirement_notice(RuntimeOrigin::signed(4)),
|
||||
Error::<Test, ()>::NotMember
|
||||
);
|
||||
|
||||
assert_eq!(alliance::Members::<Test>::get(MemberRole::Fellow), vec![1, 2, 3]);
|
||||
assert_ok!(Alliance::give_retirement_notice(RuntimeOrigin::signed(3)));
|
||||
assert_eq!(alliance::Members::<Test>::get(MemberRole::Fellow), vec![1, 2]);
|
||||
assert_eq!(alliance::Members::<Test>::get(MemberRole::Retiring), vec![3]);
|
||||
System::assert_last_event(mock::RuntimeEvent::Alliance(
|
||||
crate::Event::MemberRetirementPeriodStarted { member: (3) },
|
||||
));
|
||||
|
||||
assert_noop!(
|
||||
Alliance::give_retirement_notice(RuntimeOrigin::signed(3)),
|
||||
Error::<Test, ()>::AlreadyRetiring
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retire_works() {
|
||||
build_and_execute(|| {
|
||||
assert_noop!(
|
||||
Alliance::retire(RuntimeOrigin::signed(2)),
|
||||
Error::<Test, ()>::RetirementNoticeNotGiven
|
||||
);
|
||||
|
||||
assert_noop!(
|
||||
Alliance::retire(RuntimeOrigin::signed(4)),
|
||||
Error::<Test, ()>::RetirementNoticeNotGiven
|
||||
);
|
||||
|
||||
assert_eq!(alliance::Members::<Test>::get(MemberRole::Fellow), vec![1, 2, 3]);
|
||||
assert_ok!(Alliance::give_retirement_notice(RuntimeOrigin::signed(3)));
|
||||
assert_noop!(
|
||||
Alliance::retire(RuntimeOrigin::signed(3)),
|
||||
Error::<Test, ()>::RetirementPeriodNotPassed
|
||||
);
|
||||
System::set_block_number(System::block_number() + RetirementPeriod::get());
|
||||
assert_ok!(Alliance::retire(RuntimeOrigin::signed(3)));
|
||||
assert_eq!(alliance::Members::<Test>::get(MemberRole::Fellow), vec![1, 2]);
|
||||
System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::MemberRetired {
|
||||
member: (3),
|
||||
unreserved: None,
|
||||
}));
|
||||
|
||||
// Move time on:
|
||||
System::set_block_number(System::block_number() + RetirementPeriod::get());
|
||||
|
||||
assert_powerless(RuntimeOrigin::signed(3), false);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn abdicate_works() {
|
||||
build_and_execute(|| {
|
||||
assert_eq!(alliance::Members::<Test>::get(MemberRole::Fellow), vec![1, 2, 3]);
|
||||
assert_ok!(Alliance::abdicate_fellow_status(RuntimeOrigin::signed(3)));
|
||||
|
||||
System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::FellowAbdicated {
|
||||
fellow: (3),
|
||||
}));
|
||||
|
||||
assert_powerless(RuntimeOrigin::signed(3), true);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kick_member_works() {
|
||||
build_and_execute(|| {
|
||||
assert_noop!(Alliance::kick_member(RuntimeOrigin::signed(4), 4), BadOrigin);
|
||||
|
||||
assert_noop!(
|
||||
Alliance::kick_member(RuntimeOrigin::signed(2), 4),
|
||||
Error::<Test, ()>::NotMember
|
||||
);
|
||||
|
||||
<DepositOf<Test, ()>>::insert(2, 25);
|
||||
assert_eq!(alliance::Members::<Test>::get(MemberRole::Fellow), vec![1, 2, 3]);
|
||||
assert_ok!(Alliance::kick_member(RuntimeOrigin::signed(2), 2));
|
||||
assert_eq!(alliance::Members::<Test>::get(MemberRole::Fellow), vec![1, 3]);
|
||||
assert_eq!(<DepositOf<Test, ()>>::get(2), None);
|
||||
System::assert_last_event(mock::RuntimeEvent::Alliance(crate::Event::MemberKicked {
|
||||
member: (2),
|
||||
slashed: Some(25),
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_unscrupulous_items_works() {
|
||||
build_and_execute(|| {
|
||||
assert_noop!(Alliance::add_unscrupulous_items(RuntimeOrigin::signed(2), vec![]), BadOrigin);
|
||||
|
||||
assert_ok!(Alliance::add_unscrupulous_items(
|
||||
RuntimeOrigin::signed(3),
|
||||
vec![
|
||||
UnscrupulousItem::AccountId(3),
|
||||
UnscrupulousItem::Website("abc".as_bytes().to_vec().try_into().unwrap())
|
||||
]
|
||||
));
|
||||
assert_eq!(alliance::UnscrupulousAccounts::<Test>::get().into_inner(), vec![3]);
|
||||
assert_eq!(
|
||||
alliance::UnscrupulousWebsites::<Test>::get().into_inner(),
|
||||
vec!["abc".as_bytes().to_vec()]
|
||||
);
|
||||
|
||||
assert_noop!(
|
||||
Alliance::add_unscrupulous_items(
|
||||
RuntimeOrigin::signed(3),
|
||||
vec![UnscrupulousItem::AccountId(3)]
|
||||
),
|
||||
Error::<Test, ()>::AlreadyUnscrupulous
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_unscrupulous_items_works() {
|
||||
build_and_execute(|| {
|
||||
assert_noop!(
|
||||
Alliance::remove_unscrupulous_items(RuntimeOrigin::signed(2), vec![]),
|
||||
BadOrigin
|
||||
);
|
||||
|
||||
assert_noop!(
|
||||
Alliance::remove_unscrupulous_items(
|
||||
RuntimeOrigin::signed(3),
|
||||
vec![UnscrupulousItem::AccountId(3)]
|
||||
),
|
||||
Error::<Test, ()>::NotListedAsUnscrupulous
|
||||
);
|
||||
|
||||
assert_ok!(Alliance::add_unscrupulous_items(
|
||||
RuntimeOrigin::signed(3),
|
||||
vec![UnscrupulousItem::AccountId(3)]
|
||||
));
|
||||
assert_eq!(alliance::UnscrupulousAccounts::<Test>::get(), vec![3]);
|
||||
assert_ok!(Alliance::remove_unscrupulous_items(
|
||||
RuntimeOrigin::signed(3),
|
||||
vec![UnscrupulousItem::AccountId(3)]
|
||||
));
|
||||
assert_eq!(alliance::UnscrupulousAccounts::<Test>::get(), Vec::<u64>::new());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn weights_sane() {
|
||||
let info = crate::Call::<Test>::join_alliance {}.get_dispatch_info();
|
||||
assert_eq!(<() as crate::WeightInfo>::join_alliance(), info.call_weight);
|
||||
|
||||
let info = crate::Call::<Test>::nominate_ally { who: 10 }.get_dispatch_info();
|
||||
assert_eq!(<() as crate::WeightInfo>::nominate_ally(), info.call_weight);
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use pezframe_support::{traits::ConstU32, BoundedVec};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::RuntimeDebug;
|
||||
|
||||
/// A Multihash instance that only supports the basic functionality and no hashing.
|
||||
#[derive(
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
RuntimeDebug,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub struct Multihash {
|
||||
/// The code of the Multihash.
|
||||
pub code: u64,
|
||||
/// The digest.
|
||||
pub digest: BoundedVec<u8, ConstU32<68>>, // 4 byte dig size + 64 bytes hash digest
|
||||
}
|
||||
|
||||
impl Multihash {
|
||||
/// Returns the size of the digest.
|
||||
pub fn size(&self) -> usize {
|
||||
self.digest.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// The version of the CID.
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
RuntimeDebug,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub enum Version {
|
||||
/// CID version 0.
|
||||
V0,
|
||||
/// CID version 1.
|
||||
V1,
|
||||
}
|
||||
|
||||
/// Representation of a CID.
|
||||
///
|
||||
/// The generic is about the allocated size of the multihash.
|
||||
#[derive(
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
RuntimeDebug,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub struct Cid {
|
||||
/// The version of CID.
|
||||
pub version: Version,
|
||||
/// The codec of CID.
|
||||
pub codec: u64,
|
||||
/// The multihash of CID.
|
||||
pub hash: Multihash,
|
||||
}
|
||||
|
||||
impl Cid {
|
||||
/// Creates a new CIDv0.
|
||||
pub fn new_v0(sha2_256_digest: impl Into<Vec<u8>>) -> Self {
|
||||
/// DAG-PB multicodec code
|
||||
const DAG_PB: u64 = 0x70;
|
||||
/// The SHA_256 multicodec code
|
||||
const SHA2_256: u64 = 0x12;
|
||||
|
||||
let digest = sha2_256_digest.into();
|
||||
assert_eq!(digest.len(), 32);
|
||||
|
||||
Self {
|
||||
version: Version::V0,
|
||||
codec: DAG_PB,
|
||||
hash: Multihash { code: SHA2_256, digest: digest.try_into().expect("msg") },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Witness data for the `disband` call.
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Eq,
|
||||
PartialEq,
|
||||
RuntimeDebug,
|
||||
MaxEncodedLen,
|
||||
TypeInfo,
|
||||
Default,
|
||||
)]
|
||||
pub struct DisbandWitness {
|
||||
/// Total number of fellow members in the current Alliance.
|
||||
#[codec(compact)]
|
||||
pub(super) fellow_members: u32,
|
||||
/// Total number of ally members in the current Alliance.
|
||||
#[codec(compact)]
|
||||
pub(super) ally_members: u32,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl DisbandWitness {
|
||||
// Creates new DisbandWitness.
|
||||
pub(super) fn new(fellow_members: u32, ally_members: u32) -> Self {
|
||||
Self { fellow_members, ally_members }
|
||||
}
|
||||
}
|
||||
|
||||
impl DisbandWitness {
|
||||
pub(super) fn is_zero(self) -> bool {
|
||||
self == Self::default()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,932 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Autogenerated weights for `pezpallet_alliance`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// frame-omni-bencher
|
||||
// v1
|
||||
// benchmark
|
||||
// pallet
|
||||
// --extrinsic=*
|
||||
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
|
||||
// --pallet=pezpallet_alliance
|
||||
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/HEADER-APACHE2
|
||||
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/pezframe/alliance/src/weights.rs
|
||||
// --wasm-execution=compiled
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --heap-pages=4096
|
||||
// --template=bizinikiwi/.maintain/frame-weight-template.hbs
|
||||
// --no-storage-info
|
||||
// --no-min-squares
|
||||
// --no-median-slopes
|
||||
// --genesis-builder-policy=none
|
||||
// --exclude-pallets=pezpallet_xcm,pezpallet_xcm_benchmarks::fungible,pezpallet_xcm_benchmarks::generic,pezpallet_nomination_pools,pezpallet_remark,pezpallet_transaction_storage,pezpallet_election_provider_multi_block,pezpallet_election_provider_multi_block::signed,pezpallet_election_provider_multi_block::unsigned,pezpallet_election_provider_multi_block::verifier
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for `pezpallet_alliance`.
|
||||
pub trait WeightInfo {
|
||||
fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight;
|
||||
fn vote(m: u32, ) -> Weight;
|
||||
fn close_early_disapproved(m: u32, p: u32, ) -> Weight;
|
||||
fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight;
|
||||
fn close_disapproved(m: u32, p: u32, ) -> Weight;
|
||||
fn close_approved(b: u32, m: u32, p: u32, ) -> Weight;
|
||||
fn init_members(m: u32, z: u32, ) -> Weight;
|
||||
fn disband(x: u32, y: u32, z: u32, ) -> Weight;
|
||||
fn set_rule() -> Weight;
|
||||
fn announce() -> Weight;
|
||||
fn remove_announcement() -> Weight;
|
||||
fn join_alliance() -> Weight;
|
||||
fn nominate_ally() -> Weight;
|
||||
fn elevate_ally() -> Weight;
|
||||
fn give_retirement_notice() -> Weight;
|
||||
fn retire() -> Weight;
|
||||
fn kick_member() -> Weight;
|
||||
fn add_unscrupulous_items(n: u32, l: u32, ) -> Weight;
|
||||
fn remove_unscrupulous_items(n: u32, l: u32, ) -> Weight;
|
||||
fn abdicate_fellow_status() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_alliance` using the Bizinikiwi node and recommended hardware.
|
||||
pub struct BizinikiwiWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// Storage: `Alliance::Members` (r:1 w:0)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::ProposalOf` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::ProposalCount` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::ProposalCount` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Voting` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `b` is `[1, 1024]`.
|
||||
/// The range of component `m` is `[2, 100]`.
|
||||
/// The range of component `p` is `[1, 100]`.
|
||||
fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `393 + m * (32 ±0) + p * (36 ±0)`
|
||||
// Estimated: `6676 + m * (31 ±0) + p * (34 ±0)`
|
||||
// Minimum execution time: 28_698_000 picoseconds.
|
||||
Weight::from_parts(30_351_668, 6676)
|
||||
// Standard Error: 159
|
||||
.saturating_add(Weight::from_parts(426, 0).saturating_mul(b.into()))
|
||||
// Standard Error: 1_663
|
||||
.saturating_add(Weight::from_parts(58_244, 0).saturating_mul(m.into()))
|
||||
// Standard Error: 1_642
|
||||
.saturating_add(Weight::from_parts(167_166, 0).saturating_mul(p.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(4_u64))
|
||||
.saturating_add(Weight::from_parts(0, 31).saturating_mul(m.into()))
|
||||
.saturating_add(Weight::from_parts(0, 34).saturating_mul(p.into()))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:1 w:0)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Voting` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `m` is `[5, 100]`.
|
||||
fn vote(m: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `826 + m * (64 ±0)`
|
||||
// Estimated: `6676 + m * (64 ±0)`
|
||||
// Minimum execution time: 28_697_000 picoseconds.
|
||||
Weight::from_parts(31_067_187, 6676)
|
||||
// Standard Error: 1_039
|
||||
.saturating_add(Weight::from_parts(59_623, 0).saturating_mul(m.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
.saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into()))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:1 w:0)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Voting` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Members` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::ProposalOf` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `m` is `[4, 100]`.
|
||||
/// The range of component `p` is `[1, 100]`.
|
||||
fn close_early_disapproved(m: u32, p: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `353 + m * (96 ±0) + p * (36 ±0)`
|
||||
// Estimated: `6676 + m * (97 ±0) + p * (36 ±0)`
|
||||
// Minimum execution time: 38_262_000 picoseconds.
|
||||
Weight::from_parts(36_996_728, 6676)
|
||||
// Standard Error: 1_395
|
||||
.saturating_add(Weight::from_parts(57_361, 0).saturating_mul(m.into()))
|
||||
// Standard Error: 1_360
|
||||
.saturating_add(Weight::from_parts(153_310, 0).saturating_mul(p.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
.saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into()))
|
||||
.saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into()))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:1 w:0)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Voting` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Members` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::ProposalOf` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `SafeMode::EnteredUntil` (r:1 w:0)
|
||||
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TxPause::PausedCalls` (r:1 w:0)
|
||||
/// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `b` is `[1, 1024]`.
|
||||
/// The range of component `m` is `[4, 100]`.
|
||||
/// The range of component `p` is `[1, 100]`.
|
||||
fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `787 + m * (96 ±0) + p * (39 ±0)`
|
||||
// Estimated: `6676 + m * (97 ±0) + p * (40 ±0)`
|
||||
// Minimum execution time: 53_346_000 picoseconds.
|
||||
Weight::from_parts(54_867_557, 6676)
|
||||
// Standard Error: 193
|
||||
.saturating_add(Weight::from_parts(1_120, 0).saturating_mul(b.into()))
|
||||
// Standard Error: 2_044
|
||||
.saturating_add(Weight::from_parts(45_379, 0).saturating_mul(m.into()))
|
||||
// Standard Error: 1_992
|
||||
.saturating_add(Weight::from_parts(175_444, 0).saturating_mul(p.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(7_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
.saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into()))
|
||||
.saturating_add(Weight::from_parts(0, 40).saturating_mul(p.into()))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:1 w:0)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Voting` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Members` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Prime` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::ProposalOf` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `m` is `[2, 100]`.
|
||||
/// The range of component `p` is `[1, 100]`.
|
||||
fn close_disapproved(m: u32, p: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `354 + m * (96 ±0) + p * (36 ±0)`
|
||||
// Estimated: `6676 + m * (97 ±0) + p * (36 ±0)`
|
||||
// Minimum execution time: 39_385_000 picoseconds.
|
||||
Weight::from_parts(38_853_198, 6676)
|
||||
// Standard Error: 1_720
|
||||
.saturating_add(Weight::from_parts(52_287, 0).saturating_mul(m.into()))
|
||||
// Standard Error: 1_699
|
||||
.saturating_add(Weight::from_parts(152_517, 0).saturating_mul(p.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(5_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
.saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into()))
|
||||
.saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into()))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:1 w:0)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Voting` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Members` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Prime` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::ProposalOf` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `b` is `[1, 1024]`.
|
||||
/// The range of component `m` is `[5, 100]`.
|
||||
/// The range of component `p` is `[1, 100]`.
|
||||
fn close_approved(b: u32, m: u32, p: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `407 + m * (96 ±0) + p * (35 ±0)`
|
||||
// Estimated: `6676 + m * (97 ±0) + p * (36 ±0)`
|
||||
// Minimum execution time: 38_956_000 picoseconds.
|
||||
Weight::from_parts(38_958_561, 6676)
|
||||
// Standard Error: 127
|
||||
.saturating_add(Weight::from_parts(469, 0).saturating_mul(b.into()))
|
||||
// Standard Error: 1_360
|
||||
.saturating_add(Weight::from_parts(42_627, 0).saturating_mul(m.into()))
|
||||
// Standard Error: 1_311
|
||||
.saturating_add(Weight::from_parts(158_641, 0).saturating_mul(p.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(5_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
.saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into()))
|
||||
.saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into()))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:2 w:2)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Members` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `m` is `[1, 100]`.
|
||||
/// The range of component `z` is `[0, 100]`.
|
||||
fn init_members(m: u32, z: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `12362`
|
||||
// Minimum execution time: 24_313_000 picoseconds.
|
||||
Weight::from_parts(13_936_604, 12362)
|
||||
// Standard Error: 1_153
|
||||
.saturating_add(Weight::from_parts(124_789, 0).saturating_mul(m.into()))
|
||||
// Standard Error: 1_140
|
||||
.saturating_add(Weight::from_parts(113_862, 0).saturating_mul(z.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:2 w:2)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Alliance::DepositOf` (r:200 w:50)
|
||||
/// Proof: `Alliance::DepositOf` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:50 w:50)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Members` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Prime` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `x` is `[1, 100]`.
|
||||
/// The range of component `y` is `[0, 100]`.
|
||||
/// The range of component `z` is `[0, 50]`.
|
||||
fn disband(x: u32, y: u32, z: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0 + x * (83 ±0) + y * (52 ±0) + z * (248 ±0)`
|
||||
// Estimated: `12362 + x * (2539 ±0) + y * (2539 ±0) + z * (2603 ±1)`
|
||||
// Minimum execution time: 369_362_000 picoseconds.
|
||||
Weight::from_parts(373_165_000, 12362)
|
||||
// Standard Error: 30_558
|
||||
.saturating_add(Weight::from_parts(813_586, 0).saturating_mul(x.into()))
|
||||
// Standard Error: 30_411
|
||||
.saturating_add(Weight::from_parts(779_925, 0).saturating_mul(y.into()))
|
||||
// Standard Error: 60_767
|
||||
.saturating_add(Weight::from_parts(14_852_466, 0).saturating_mul(z.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(x.into())))
|
||||
.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(y.into())))
|
||||
.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(z.into())))
|
||||
.saturating_add(T::DbWeight::get().writes(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(z.into())))
|
||||
.saturating_add(Weight::from_parts(0, 2539).saturating_mul(x.into()))
|
||||
.saturating_add(Weight::from_parts(0, 2539).saturating_mul(y.into()))
|
||||
.saturating_add(Weight::from_parts(0, 2603).saturating_mul(z.into()))
|
||||
}
|
||||
/// Storage: `Alliance::Rule` (r:0 w:1)
|
||||
/// Proof: `Alliance::Rule` (`max_values`: Some(1), `max_size`: Some(87), added: 582, mode: `MaxEncodedLen`)
|
||||
fn set_rule() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 5_320_000 picoseconds.
|
||||
Weight::from_parts(5_541_000, 0)
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Announcements` (r:1 w:1)
|
||||
/// Proof: `Alliance::Announcements` (`max_values`: Some(1), `max_size`: Some(8702), added: 9197, mode: `MaxEncodedLen`)
|
||||
fn announce() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `54`
|
||||
// Estimated: `10187`
|
||||
// Minimum execution time: 7_541_000 picoseconds.
|
||||
Weight::from_parts(7_884_000, 10187)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Announcements` (r:1 w:1)
|
||||
/// Proof: `Alliance::Announcements` (`max_values`: Some(1), `max_size`: Some(8702), added: 9197, mode: `MaxEncodedLen`)
|
||||
fn remove_announcement() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `127`
|
||||
// Estimated: `10187`
|
||||
// Minimum execution time: 8_760_000 picoseconds.
|
||||
Weight::from_parts(9_201_000, 10187)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:3 w:1)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Alliance::UnscrupulousAccounts` (r:1 w:0)
|
||||
/// Proof: `Alliance::UnscrupulousAccounts` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Alliance::DepositOf` (r:0 w:1)
|
||||
/// Proof: `Alliance::DepositOf` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
fn join_alliance() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `221`
|
||||
// Estimated: `18048`
|
||||
// Minimum execution time: 39_045_000 picoseconds.
|
||||
Weight::from_parts(40_425_000, 18048)
|
||||
.saturating_add(T::DbWeight::get().reads(5_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:3 w:1)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Alliance::UnscrupulousAccounts` (r:1 w:0)
|
||||
/// Proof: `Alliance::UnscrupulousAccounts` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
fn nominate_ally() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `170`
|
||||
// Estimated: `18048`
|
||||
// Minimum execution time: 23_593_000 picoseconds.
|
||||
Weight::from_parts(24_151_000, 18048)
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:2 w:2)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Members` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Prime` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
fn elevate_ally() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `206`
|
||||
// Estimated: `12362`
|
||||
// Minimum execution time: 21_720_000 picoseconds.
|
||||
Weight::from_parts(22_214_000, 12362)
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:4 w:2)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Members` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Prime` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Alliance::RetiringMembers` (r:0 w:1)
|
||||
/// Proof: `Alliance::RetiringMembers` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn give_retirement_notice() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `206`
|
||||
// Estimated: `23734`
|
||||
// Minimum execution time: 26_820_000 picoseconds.
|
||||
Weight::from_parts(27_614_000, 23734)
|
||||
.saturating_add(T::DbWeight::get().reads(5_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(5_u64))
|
||||
}
|
||||
/// Storage: `Alliance::RetiringMembers` (r:1 w:1)
|
||||
/// Proof: `Alliance::RetiringMembers` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Alliance::Members` (r:1 w:1)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Alliance::DepositOf` (r:1 w:1)
|
||||
/// Proof: `Alliance::DepositOf` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
fn retire() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `458`
|
||||
// Estimated: `6676`
|
||||
// Minimum execution time: 36_070_000 picoseconds.
|
||||
Weight::from_parts(36_974_000, 6676)
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:3 w:1)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Alliance::DepositOf` (r:1 w:1)
|
||||
/// Proof: `Alliance::DepositOf` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Members` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Prime` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
fn kick_member() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `434`
|
||||
// Estimated: `18048`
|
||||
// Minimum execution time: 58_302_000 picoseconds.
|
||||
Weight::from_parts(59_775_000, 18048)
|
||||
.saturating_add(T::DbWeight::get().reads(6_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(5_u64))
|
||||
}
|
||||
/// Storage: `Alliance::UnscrupulousAccounts` (r:1 w:1)
|
||||
/// Proof: `Alliance::UnscrupulousAccounts` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Alliance::UnscrupulousWebsites` (r:1 w:1)
|
||||
/// Proof: `Alliance::UnscrupulousWebsites` (`max_values`: Some(1), `max_size`: Some(25702), added: 26197, mode: `MaxEncodedLen`)
|
||||
/// The range of component `n` is `[0, 100]`.
|
||||
/// The range of component `l` is `[0, 255]`.
|
||||
fn add_unscrupulous_items(n: u32, l: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `54`
|
||||
// Estimated: `27187`
|
||||
// Minimum execution time: 4_803_000 picoseconds.
|
||||
Weight::from_parts(4_858_000, 27187)
|
||||
// Standard Error: 3_655
|
||||
.saturating_add(Weight::from_parts(1_136_024, 0).saturating_mul(n.into()))
|
||||
// Standard Error: 1_431
|
||||
.saturating_add(Weight::from_parts(65_747, 0).saturating_mul(l.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `Alliance::UnscrupulousAccounts` (r:1 w:1)
|
||||
/// Proof: `Alliance::UnscrupulousAccounts` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Alliance::UnscrupulousWebsites` (r:1 w:1)
|
||||
/// Proof: `Alliance::UnscrupulousWebsites` (`max_values`: Some(1), `max_size`: Some(25702), added: 26197, mode: `MaxEncodedLen`)
|
||||
/// The range of component `n` is `[0, 100]`.
|
||||
/// The range of component `l` is `[0, 255]`.
|
||||
fn remove_unscrupulous_items(n: u32, _l: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0 + l * (100 ±0) + n * (289 ±0)`
|
||||
// Estimated: `27187`
|
||||
// Minimum execution time: 4_818_000 picoseconds.
|
||||
Weight::from_parts(4_893_000, 27187)
|
||||
// Standard Error: 184_675
|
||||
.saturating_add(Weight::from_parts(19_530_779, 0).saturating_mul(n.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(2_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:3 w:2)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Members` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Prime` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
fn abdicate_fellow_status() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `206`
|
||||
// Estimated: `18048`
|
||||
// Minimum execution time: 25_908_000 picoseconds.
|
||||
Weight::from_parts(26_640_000, 18048)
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(4_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `Alliance::Members` (r:1 w:0)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::ProposalOf` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::ProposalCount` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::ProposalCount` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Voting` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `b` is `[1, 1024]`.
|
||||
/// The range of component `m` is `[2, 100]`.
|
||||
/// The range of component `p` is `[1, 100]`.
|
||||
fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `393 + m * (32 ±0) + p * (36 ±0)`
|
||||
// Estimated: `6676 + m * (31 ±0) + p * (34 ±0)`
|
||||
// Minimum execution time: 28_698_000 picoseconds.
|
||||
Weight::from_parts(30_351_668, 6676)
|
||||
// Standard Error: 159
|
||||
.saturating_add(Weight::from_parts(426, 0).saturating_mul(b.into()))
|
||||
// Standard Error: 1_663
|
||||
.saturating_add(Weight::from_parts(58_244, 0).saturating_mul(m.into()))
|
||||
// Standard Error: 1_642
|
||||
.saturating_add(Weight::from_parts(167_166, 0).saturating_mul(p.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
.saturating_add(Weight::from_parts(0, 31).saturating_mul(m.into()))
|
||||
.saturating_add(Weight::from_parts(0, 34).saturating_mul(p.into()))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:1 w:0)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Voting` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `m` is `[5, 100]`.
|
||||
fn vote(m: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `826 + m * (64 ±0)`
|
||||
// Estimated: `6676 + m * (64 ±0)`
|
||||
// Minimum execution time: 28_697_000 picoseconds.
|
||||
Weight::from_parts(31_067_187, 6676)
|
||||
// Standard Error: 1_039
|
||||
.saturating_add(Weight::from_parts(59_623, 0).saturating_mul(m.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
.saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into()))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:1 w:0)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Voting` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Members` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::ProposalOf` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `m` is `[4, 100]`.
|
||||
/// The range of component `p` is `[1, 100]`.
|
||||
fn close_early_disapproved(m: u32, p: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `353 + m * (96 ±0) + p * (36 ±0)`
|
||||
// Estimated: `6676 + m * (97 ±0) + p * (36 ±0)`
|
||||
// Minimum execution time: 38_262_000 picoseconds.
|
||||
Weight::from_parts(36_996_728, 6676)
|
||||
// Standard Error: 1_395
|
||||
.saturating_add(Weight::from_parts(57_361, 0).saturating_mul(m.into()))
|
||||
// Standard Error: 1_360
|
||||
.saturating_add(Weight::from_parts(153_310, 0).saturating_mul(p.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
.saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into()))
|
||||
.saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into()))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:1 w:0)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Voting` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Members` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::ProposalOf` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `SafeMode::EnteredUntil` (r:1 w:0)
|
||||
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `TxPause::PausedCalls` (r:1 w:0)
|
||||
/// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `b` is `[1, 1024]`.
|
||||
/// The range of component `m` is `[4, 100]`.
|
||||
/// The range of component `p` is `[1, 100]`.
|
||||
fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `787 + m * (96 ±0) + p * (39 ±0)`
|
||||
// Estimated: `6676 + m * (97 ±0) + p * (40 ±0)`
|
||||
// Minimum execution time: 53_346_000 picoseconds.
|
||||
Weight::from_parts(54_867_557, 6676)
|
||||
// Standard Error: 193
|
||||
.saturating_add(Weight::from_parts(1_120, 0).saturating_mul(b.into()))
|
||||
// Standard Error: 2_044
|
||||
.saturating_add(Weight::from_parts(45_379, 0).saturating_mul(m.into()))
|
||||
// Standard Error: 1_992
|
||||
.saturating_add(Weight::from_parts(175_444, 0).saturating_mul(p.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(7_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
.saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into()))
|
||||
.saturating_add(Weight::from_parts(0, 40).saturating_mul(p.into()))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:1 w:0)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Voting` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Members` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Prime` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::ProposalOf` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `m` is `[2, 100]`.
|
||||
/// The range of component `p` is `[1, 100]`.
|
||||
fn close_disapproved(m: u32, p: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `354 + m * (96 ±0) + p * (36 ±0)`
|
||||
// Estimated: `6676 + m * (97 ±0) + p * (36 ±0)`
|
||||
// Minimum execution time: 39_385_000 picoseconds.
|
||||
Weight::from_parts(38_853_198, 6676)
|
||||
// Standard Error: 1_720
|
||||
.saturating_add(Weight::from_parts(52_287, 0).saturating_mul(m.into()))
|
||||
// Standard Error: 1_699
|
||||
.saturating_add(Weight::from_parts(152_517, 0).saturating_mul(p.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
.saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into()))
|
||||
.saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into()))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:1 w:0)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Voting` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Voting` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Members` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Prime` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::ProposalOf` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::ProposalOf` (`max_values`: None, `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `b` is `[1, 1024]`.
|
||||
/// The range of component `m` is `[5, 100]`.
|
||||
/// The range of component `p` is `[1, 100]`.
|
||||
fn close_approved(b: u32, m: u32, p: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `407 + m * (96 ±0) + p * (35 ±0)`
|
||||
// Estimated: `6676 + m * (97 ±0) + p * (36 ±0)`
|
||||
// Minimum execution time: 38_956_000 picoseconds.
|
||||
Weight::from_parts(38_958_561, 6676)
|
||||
// Standard Error: 127
|
||||
.saturating_add(Weight::from_parts(469, 0).saturating_mul(b.into()))
|
||||
// Standard Error: 1_360
|
||||
.saturating_add(Weight::from_parts(42_627, 0).saturating_mul(m.into()))
|
||||
// Standard Error: 1_311
|
||||
.saturating_add(Weight::from_parts(158_641, 0).saturating_mul(p.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
.saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into()))
|
||||
.saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into()))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:2 w:2)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Members` (r:1 w:1)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `m` is `[1, 100]`.
|
||||
/// The range of component `z` is `[0, 100]`.
|
||||
fn init_members(m: u32, z: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `12362`
|
||||
// Minimum execution time: 24_313_000 picoseconds.
|
||||
Weight::from_parts(13_936_604, 12362)
|
||||
// Standard Error: 1_153
|
||||
.saturating_add(Weight::from_parts(124_789, 0).saturating_mul(m.into()))
|
||||
// Standard Error: 1_140
|
||||
.saturating_add(Weight::from_parts(113_862, 0).saturating_mul(z.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:2 w:2)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Alliance::DepositOf` (r:200 w:50)
|
||||
/// Proof: `Alliance::DepositOf` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:50 w:50)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Members` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Prime` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// The range of component `x` is `[1, 100]`.
|
||||
/// The range of component `y` is `[0, 100]`.
|
||||
/// The range of component `z` is `[0, 50]`.
|
||||
fn disband(x: u32, y: u32, z: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0 + x * (83 ±0) + y * (52 ±0) + z * (248 ±0)`
|
||||
// Estimated: `12362 + x * (2539 ±0) + y * (2539 ±0) + z * (2603 ±1)`
|
||||
// Minimum execution time: 369_362_000 picoseconds.
|
||||
Weight::from_parts(373_165_000, 12362)
|
||||
// Standard Error: 30_558
|
||||
.saturating_add(Weight::from_parts(813_586, 0).saturating_mul(x.into()))
|
||||
// Standard Error: 30_411
|
||||
.saturating_add(Weight::from_parts(779_925, 0).saturating_mul(y.into()))
|
||||
// Standard Error: 60_767
|
||||
.saturating_add(Weight::from_parts(14_852_466, 0).saturating_mul(z.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(x.into())))
|
||||
.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(y.into())))
|
||||
.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(z.into())))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(z.into())))
|
||||
.saturating_add(Weight::from_parts(0, 2539).saturating_mul(x.into()))
|
||||
.saturating_add(Weight::from_parts(0, 2539).saturating_mul(y.into()))
|
||||
.saturating_add(Weight::from_parts(0, 2603).saturating_mul(z.into()))
|
||||
}
|
||||
/// Storage: `Alliance::Rule` (r:0 w:1)
|
||||
/// Proof: `Alliance::Rule` (`max_values`: Some(1), `max_size`: Some(87), added: 582, mode: `MaxEncodedLen`)
|
||||
fn set_rule() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 5_320_000 picoseconds.
|
||||
Weight::from_parts(5_541_000, 0)
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Announcements` (r:1 w:1)
|
||||
/// Proof: `Alliance::Announcements` (`max_values`: Some(1), `max_size`: Some(8702), added: 9197, mode: `MaxEncodedLen`)
|
||||
fn announce() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `54`
|
||||
// Estimated: `10187`
|
||||
// Minimum execution time: 7_541_000 picoseconds.
|
||||
Weight::from_parts(7_884_000, 10187)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Announcements` (r:1 w:1)
|
||||
/// Proof: `Alliance::Announcements` (`max_values`: Some(1), `max_size`: Some(8702), added: 9197, mode: `MaxEncodedLen`)
|
||||
fn remove_announcement() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `127`
|
||||
// Estimated: `10187`
|
||||
// Minimum execution time: 8_760_000 picoseconds.
|
||||
Weight::from_parts(9_201_000, 10187)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:3 w:1)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Alliance::UnscrupulousAccounts` (r:1 w:0)
|
||||
/// Proof: `Alliance::UnscrupulousAccounts` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Alliance::DepositOf` (r:0 w:1)
|
||||
/// Proof: `Alliance::DepositOf` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
fn join_alliance() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `221`
|
||||
// Estimated: `18048`
|
||||
// Minimum execution time: 39_045_000 picoseconds.
|
||||
Weight::from_parts(40_425_000, 18048)
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(3_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:3 w:1)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Alliance::UnscrupulousAccounts` (r:1 w:0)
|
||||
/// Proof: `Alliance::UnscrupulousAccounts` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
fn nominate_ally() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `170`
|
||||
// Estimated: `18048`
|
||||
// Minimum execution time: 23_593_000 picoseconds.
|
||||
Weight::from_parts(24_151_000, 18048)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:2 w:2)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Members` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Prime` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
fn elevate_ally() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `206`
|
||||
// Estimated: `12362`
|
||||
// Minimum execution time: 21_720_000 picoseconds.
|
||||
Weight::from_parts(22_214_000, 12362)
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:4 w:2)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Members` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Prime` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Alliance::RetiringMembers` (r:0 w:1)
|
||||
/// Proof: `Alliance::RetiringMembers` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn give_retirement_notice() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `206`
|
||||
// Estimated: `23734`
|
||||
// Minimum execution time: 26_820_000 picoseconds.
|
||||
Weight::from_parts(27_614_000, 23734)
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(5_u64))
|
||||
}
|
||||
/// Storage: `Alliance::RetiringMembers` (r:1 w:1)
|
||||
/// Proof: `Alliance::RetiringMembers` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Alliance::Members` (r:1 w:1)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Alliance::DepositOf` (r:1 w:1)
|
||||
/// Proof: `Alliance::DepositOf` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
fn retire() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `458`
|
||||
// Estimated: `6676`
|
||||
// Minimum execution time: 36_070_000 picoseconds.
|
||||
Weight::from_parts(36_974_000, 6676)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:3 w:1)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Alliance::DepositOf` (r:1 w:1)
|
||||
/// Proof: `Alliance::DepositOf` (`max_values`: None, `max_size`: Some(64), added: 2539, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Members` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Prime` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
fn kick_member() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `434`
|
||||
// Estimated: `18048`
|
||||
// Minimum execution time: 58_302_000 picoseconds.
|
||||
Weight::from_parts(59_775_000, 18048)
|
||||
.saturating_add(RocksDbWeight::get().reads(6_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(5_u64))
|
||||
}
|
||||
/// Storage: `Alliance::UnscrupulousAccounts` (r:1 w:1)
|
||||
/// Proof: `Alliance::UnscrupulousAccounts` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Alliance::UnscrupulousWebsites` (r:1 w:1)
|
||||
/// Proof: `Alliance::UnscrupulousWebsites` (`max_values`: Some(1), `max_size`: Some(25702), added: 26197, mode: `MaxEncodedLen`)
|
||||
/// The range of component `n` is `[0, 100]`.
|
||||
/// The range of component `l` is `[0, 255]`.
|
||||
fn add_unscrupulous_items(n: u32, l: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `54`
|
||||
// Estimated: `27187`
|
||||
// Minimum execution time: 4_803_000 picoseconds.
|
||||
Weight::from_parts(4_858_000, 27187)
|
||||
// Standard Error: 3_655
|
||||
.saturating_add(Weight::from_parts(1_136_024, 0).saturating_mul(n.into()))
|
||||
// Standard Error: 1_431
|
||||
.saturating_add(Weight::from_parts(65_747, 0).saturating_mul(l.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `Alliance::UnscrupulousAccounts` (r:1 w:1)
|
||||
/// Proof: `Alliance::UnscrupulousAccounts` (`max_values`: Some(1), `max_size`: Some(3202), added: 3697, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Alliance::UnscrupulousWebsites` (r:1 w:1)
|
||||
/// Proof: `Alliance::UnscrupulousWebsites` (`max_values`: Some(1), `max_size`: Some(25702), added: 26197, mode: `MaxEncodedLen`)
|
||||
/// The range of component `n` is `[0, 100]`.
|
||||
/// The range of component `l` is `[0, 255]`.
|
||||
fn remove_unscrupulous_items(n: u32, _l: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0 + l * (100 ±0) + n * (289 ±0)`
|
||||
// Estimated: `27187`
|
||||
// Minimum execution time: 4_818_000 picoseconds.
|
||||
Weight::from_parts(4_893_000, 27187)
|
||||
// Standard Error: 184_675
|
||||
.saturating_add(Weight::from_parts(19_530_779, 0).saturating_mul(n.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(2_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `Alliance::Members` (r:3 w:2)
|
||||
/// Proof: `Alliance::Members` (`max_values`: None, `max_size`: Some(3211), added: 5686, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AllianceMotion::Proposals` (r:1 w:0)
|
||||
/// Proof: `AllianceMotion::Proposals` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Members` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Members` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `AllianceMotion::Prime` (r:0 w:1)
|
||||
/// Proof: `AllianceMotion::Prime` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
fn abdicate_fellow_status() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `206`
|
||||
// Estimated: `18048`
|
||||
// Minimum execution time: 25_908_000 picoseconds.
|
||||
Weight::from_parts(26_640_000, 18048)
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
[package]
|
||||
name = "pezpallet-asset-conversion"
|
||||
version = "10.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME asset conversion pallet"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
log = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
pezsp-api = { workspace = true }
|
||||
pezsp-arithmetic = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-assets = { workspace = true, default-features = true }
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
primitive-types = { features = [
|
||||
"codec",
|
||||
"num-traits",
|
||||
"scale-info",
|
||||
], workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-assets/std",
|
||||
"pezpallet-balances/std",
|
||||
"primitive-types/std",
|
||||
"scale-info/std",
|
||||
"pezsp-api/std",
|
||||
"pezsp-arithmetic/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-assets/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezsp-api/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-assets/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# asset-conversion
|
||||
|
||||
## A swap pallet
|
||||
|
||||
This pallet allows assets to be converted from one type to another by means of a constant product formula.
|
||||
The pallet based is based on [Uniswap V2](https://github.com/Uniswap/v2-core) logic.
|
||||
|
||||
### Overview
|
||||
|
||||
This pallet allows you to:
|
||||
|
||||
- create a liquidity pool for 2 assets
|
||||
- provide the liquidity and receive back an LP token
|
||||
- exchange the LP token back to assets
|
||||
- swap 2 assets if there is a pool created
|
||||
- query for an exchange price via a new runtime call endpoint
|
||||
- query the size of a liquidity pool.
|
||||
|
||||
Please see the rust module documentation for full details:
|
||||
|
||||
`cargo doc -p pezpallet-asset-conversion --open`
|
||||
|
||||
### License
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,74 @@
|
||||
[package]
|
||||
name = "pezpallet-asset-conversion-ops"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME asset conversion pallet's operations suite"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
log = { workspace = true }
|
||||
pezpallet-asset-conversion = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
pezsp-arithmetic = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-assets = { workspace = true, default-features = true }
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
primitive-types = { features = [
|
||||
"codec",
|
||||
"num-traits",
|
||||
"scale-info",
|
||||
], workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-asset-conversion/std",
|
||||
"pezpallet-assets/std",
|
||||
"pezpallet-balances/std",
|
||||
"primitive-types/std",
|
||||
"scale-info/std",
|
||||
"pezsp-arithmetic/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-asset-conversion/runtime-benchmarks",
|
||||
"pezpallet-assets/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-asset-conversion/try-runtime",
|
||||
"pezpallet-assets/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,166 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Asset Conversion Ops pallet benchmarking.
|
||||
|
||||
use super::*;
|
||||
use crate::Pallet as AssetConversionOps;
|
||||
use pezframe_benchmarking::{v2::*, whitelisted_caller};
|
||||
use pezframe_support::{
|
||||
assert_ok,
|
||||
traits::fungibles::{Create, Inspect, Mutate},
|
||||
};
|
||||
use pezframe_system::RawOrigin as SystemOrigin;
|
||||
use pezpallet_asset_conversion::{BenchmarkHelper, Pallet as AssetConversion};
|
||||
use pezsp_core::Get;
|
||||
use pezsp_runtime::traits::One;
|
||||
|
||||
/// Provides a pair of amounts expected to serve as sufficient initial liquidity for a pool.
|
||||
fn valid_liquidity_amount<T: Config>(ed1: T::Balance, ed2: T::Balance) -> (T::Balance, T::Balance)
|
||||
where
|
||||
T::Assets: Inspect<T::AccountId>,
|
||||
{
|
||||
let l =
|
||||
ed1.max(ed2) + T::MintMinLiquidity::get() + T::MintMinLiquidity::get() + T::Balance::one();
|
||||
(l, l)
|
||||
}
|
||||
|
||||
/// Create the `asset` and mint the `amount` for the `caller`.
|
||||
fn create_asset<T: Config>(caller: &T::AccountId, asset: &T::AssetKind, amount: T::Balance)
|
||||
where
|
||||
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
|
||||
{
|
||||
if !T::Assets::asset_exists(asset.clone()) {
|
||||
assert_ok!(T::Assets::create(asset.clone(), caller.clone(), true, T::Balance::one()));
|
||||
}
|
||||
assert_ok!(T::Assets::mint_into(
|
||||
asset.clone(),
|
||||
&caller,
|
||||
amount + T::Assets::minimum_balance(asset.clone())
|
||||
));
|
||||
}
|
||||
|
||||
/// Create the designated fee asset for pool creation.
|
||||
fn create_fee_asset<T: Config>(caller: &T::AccountId)
|
||||
where
|
||||
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
|
||||
{
|
||||
let fee_asset = T::PoolSetupFeeAsset::get();
|
||||
if !T::Assets::asset_exists(fee_asset.clone()) {
|
||||
assert_ok!(T::Assets::create(fee_asset.clone(), caller.clone(), true, T::Balance::one()));
|
||||
}
|
||||
assert_ok!(T::Assets::mint_into(
|
||||
fee_asset.clone(),
|
||||
&caller,
|
||||
T::Assets::minimum_balance(fee_asset)
|
||||
));
|
||||
}
|
||||
|
||||
/// Mint the fee asset for the `caller` sufficient to cover the fee for creating a new pool.
|
||||
fn mint_setup_fee_asset<T: Config>(
|
||||
caller: &T::AccountId,
|
||||
asset1: &T::AssetKind,
|
||||
asset2: &T::AssetKind,
|
||||
lp_token: &T::PoolAssetId,
|
||||
) where
|
||||
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
|
||||
{
|
||||
assert_ok!(T::Assets::mint_into(
|
||||
T::PoolSetupFeeAsset::get(),
|
||||
&caller,
|
||||
T::PoolSetupFee::get() +
|
||||
T::Assets::deposit_required(asset1.clone()) +
|
||||
T::Assets::deposit_required(asset2.clone()) +
|
||||
T::PoolAssets::deposit_required(lp_token.clone())
|
||||
));
|
||||
}
|
||||
|
||||
/// Creates a pool for a given asset pair.
|
||||
///
|
||||
/// This action mints the necessary amounts of the given assets for the `caller` to provide initial
|
||||
/// liquidity. It returns the LP token ID along with a pair of amounts sufficient for the pool's
|
||||
/// initial liquidity.
|
||||
fn create_asset_and_pool<T: Config>(
|
||||
caller: &T::AccountId,
|
||||
asset1: &T::AssetKind,
|
||||
asset2: &T::AssetKind,
|
||||
) -> (T::PoolAssetId, T::Balance, T::Balance)
|
||||
where
|
||||
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
|
||||
{
|
||||
let (liquidity1, liquidity2) = valid_liquidity_amount::<T>(
|
||||
T::Assets::minimum_balance(asset1.clone()),
|
||||
T::Assets::minimum_balance(asset2.clone()),
|
||||
);
|
||||
create_asset::<T>(caller, asset1, liquidity1);
|
||||
create_asset::<T>(caller, asset2, liquidity2);
|
||||
let lp_token = AssetConversion::<T>::get_next_pool_asset_id();
|
||||
|
||||
mint_setup_fee_asset::<T>(caller, asset1, asset2, &lp_token);
|
||||
|
||||
assert_ok!(AssetConversion::<T>::create_pool(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
Box::new(asset1.clone()),
|
||||
Box::new(asset2.clone())
|
||||
));
|
||||
|
||||
(lp_token, liquidity1, liquidity2)
|
||||
}
|
||||
|
||||
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
|
||||
let events = pezframe_system::Pallet::<T>::events();
|
||||
let system_event: <T as pezframe_system::Config>::RuntimeEvent = generic_event.into();
|
||||
// compare to the last event record
|
||||
let pezframe_system::EventRecord { event, .. } = &events[events.len() - 1];
|
||||
assert_eq!(event, &system_event);
|
||||
}
|
||||
|
||||
#[benchmarks(where T::Assets: Create<T::AccountId> + Mutate<T::AccountId>, T::PoolAssetId: Into<u32>,)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn migrate_to_new_account() {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
|
||||
|
||||
create_fee_asset::<T>(&caller);
|
||||
let (_, liquidity1, liquidity2) = create_asset_and_pool::<T>(&caller, &asset1, &asset2);
|
||||
|
||||
assert_ok!(AssetConversion::<T>::add_liquidity(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
Box::new(asset1.clone()),
|
||||
Box::new(asset2.clone()),
|
||||
liquidity1,
|
||||
liquidity2,
|
||||
T::Balance::one(),
|
||||
T::Balance::zero(),
|
||||
caller.clone(),
|
||||
));
|
||||
|
||||
#[extrinsic_call]
|
||||
_(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone()));
|
||||
|
||||
let pool_id = T::PoolLocator::pool_id(&asset1, &asset2).unwrap();
|
||||
let (prior_account, new_account) = AssetConversionOps::<T>::addresses(&pool_id).unwrap();
|
||||
assert_last_event::<T>(
|
||||
Event::MigratedToNewAccount { pool_id, new_account, prior_account }.into(),
|
||||
);
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(AssetConversionOps, crate::mock::new_test_ext(), crate::mock::Test);
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! # Asset Conversion Operations Suite.
|
||||
//!
|
||||
//! This pallet provides operational functionalities for the Asset Conversion pallet,
|
||||
//! allowing you to perform various migration and one-time-use operations. These operations
|
||||
//! are designed to facilitate updates and changes to the Asset Conversion pallet without
|
||||
//! breaking its API.
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! This suite allows you to perform the following operations:
|
||||
//! - Perform migration to update account ID derivation methods for existing pools. The migration
|
||||
//! operation ensures that the required accounts are created, existing account deposits are
|
||||
//! transferred, and liquidity is moved to the new accounts.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub mod weights;
|
||||
pub use pallet::*;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use pezframe_support::traits::{
|
||||
fungible::{Inspect as FungibleInspect, Mutate as FungibleMutate},
|
||||
fungibles::{roles::ResetTeam, Inspect, Mutate, Refund},
|
||||
tokens::{Fortitude, Precision, Preservation},
|
||||
AccountTouch,
|
||||
};
|
||||
use pezpallet_asset_conversion::{PoolLocator, Pools};
|
||||
use pezsp_runtime::traits::{TryConvert, Zero};
|
||||
|
||||
#[pezframe_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
use pezframe_system::pezpallet_prelude::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config:
|
||||
pezpallet_asset_conversion::Config<
|
||||
PoolId = (
|
||||
<Self as pezpallet_asset_conversion::Config>::AssetKind,
|
||||
<Self as pezpallet_asset_conversion::Config>::AssetKind,
|
||||
),
|
||||
> + pezframe_system::Config
|
||||
{
|
||||
/// Overarching event type.
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
|
||||
/// Type previously used to derive the account ID for a pool. Indicates that the pool's
|
||||
/// liquidity assets are located at this account before the migration.
|
||||
type PriorAccountIdConverter: for<'a> TryConvert<
|
||||
&'a (Self::AssetKind, Self::AssetKind),
|
||||
Self::AccountId,
|
||||
>;
|
||||
|
||||
/// Retrieves information about an existing deposit for a given account ID and asset from
|
||||
/// the [`pezpallet_asset_conversion::Config::Assets`] registry and can initiate the refund.
|
||||
type AssetsRefund: Refund<
|
||||
Self::AccountId,
|
||||
AssetId = Self::AssetKind,
|
||||
Balance = <Self::DepositAsset as FungibleInspect<Self::AccountId>>::Balance,
|
||||
>;
|
||||
|
||||
/// Retrieves information about an existing deposit for a given account ID and asset from
|
||||
/// the [`pezpallet_asset_conversion::Config::PoolAssets`] registry and can initiate the
|
||||
/// refund.
|
||||
type PoolAssetsRefund: Refund<
|
||||
Self::AccountId,
|
||||
AssetId = Self::PoolAssetId,
|
||||
Balance = <Self::DepositAsset as FungibleInspect<Self::AccountId>>::Balance,
|
||||
>;
|
||||
|
||||
/// Means to reset the team for assets from the
|
||||
/// [`pezpallet_asset_conversion::Config::PoolAssets`] registry.
|
||||
type PoolAssetsTeam: ResetTeam<Self::AccountId, AssetId = Self::PoolAssetId>;
|
||||
|
||||
/// Registry of an asset used as an account deposit for the
|
||||
/// [`pezpallet_asset_conversion::Config::Assets`] and
|
||||
/// [`pezpallet_asset_conversion::Config::PoolAssets`] registries.
|
||||
type DepositAsset: FungibleMutate<Self::AccountId>;
|
||||
|
||||
/// Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
|
||||
// Pallet's events.
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// Indicates that a pool has been migrated to the new account ID.
|
||||
MigratedToNewAccount {
|
||||
/// Pool's ID.
|
||||
pool_id: T::PoolId,
|
||||
/// Pool's prior account ID.
|
||||
prior_account: T::AccountId,
|
||||
/// Pool's new account ID.
|
||||
new_account: T::AccountId,
|
||||
},
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Provided asset pair is not supported for pool.
|
||||
InvalidAssetPair,
|
||||
/// The pool doesn't exist.
|
||||
PoolNotFound,
|
||||
/// Pool's balance cannot be zero.
|
||||
ZeroBalance,
|
||||
/// Indicates a partial transfer of balance to the new account during a migration.
|
||||
PartialTransfer,
|
||||
}
|
||||
|
||||
/// Pallet's callable functions.
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Migrates an existing pool to a new account ID derivation method for a given asset pair.
|
||||
/// If the migration is successful, transaction fees are refunded to the caller.
|
||||
///
|
||||
/// Must be signed.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(<T as Config>::WeightInfo::migrate_to_new_account())]
|
||||
pub fn migrate_to_new_account(
|
||||
origin: OriginFor<T>,
|
||||
asset1: Box<T::AssetKind>,
|
||||
asset2: Box<T::AssetKind>,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
ensure_signed(origin)?;
|
||||
|
||||
let pool_id = T::PoolLocator::pool_id(&asset1, &asset2)
|
||||
.map_err(|_| Error::<T>::InvalidAssetPair)?;
|
||||
let info = Pools::<T>::get(&pool_id).ok_or(Error::<T>::PoolNotFound)?;
|
||||
|
||||
let (prior_account, new_account) =
|
||||
Self::addresses(&pool_id).ok_or(Error::<T>::InvalidAssetPair)?;
|
||||
|
||||
let (asset1, asset2) = pool_id.clone();
|
||||
|
||||
// Assets that must be transferred to the new account id.
|
||||
let balance1 = T::Assets::total_balance(asset1.clone(), &prior_account);
|
||||
let balance2 = T::Assets::total_balance(asset2.clone(), &prior_account);
|
||||
let lp_balance = T::PoolAssets::total_balance(info.lp_token.clone(), &prior_account);
|
||||
|
||||
ensure!(!balance1.is_zero(), Error::<T>::ZeroBalance);
|
||||
ensure!(!balance2.is_zero(), Error::<T>::ZeroBalance);
|
||||
ensure!(!lp_balance.is_zero(), Error::<T>::ZeroBalance);
|
||||
|
||||
// Check if a deposit needs to be placed for the new account. If so, mint the
|
||||
// required deposit amount to the depositor's account to ensure the deposit can be
|
||||
// provided. Once the deposit from the prior account is returned, the minted assets will
|
||||
// be burned. Touching the new account is necessary because it's not possible to
|
||||
// transfer assets to the new account if it's required. Additionally, the deposit cannot
|
||||
// be refunded from the prior account until its balance is zero.
|
||||
|
||||
let deposit_asset_ed = T::DepositAsset::minimum_balance();
|
||||
|
||||
if let Some((depositor, deposit)) =
|
||||
T::AssetsRefund::deposit_held(asset1.clone(), prior_account.clone())
|
||||
{
|
||||
T::DepositAsset::mint_into(&depositor, deposit + deposit_asset_ed)?;
|
||||
T::Assets::touch(asset1.clone(), &new_account, &depositor)?;
|
||||
}
|
||||
|
||||
if let Some((depositor, deposit)) =
|
||||
T::AssetsRefund::deposit_held(asset2.clone(), prior_account.clone())
|
||||
{
|
||||
T::DepositAsset::mint_into(&depositor, deposit + deposit_asset_ed)?;
|
||||
T::Assets::touch(asset2.clone(), &new_account, &depositor)?;
|
||||
}
|
||||
|
||||
if let Some((depositor, deposit)) =
|
||||
T::PoolAssetsRefund::deposit_held(info.lp_token.clone(), prior_account.clone())
|
||||
{
|
||||
T::DepositAsset::mint_into(&depositor, deposit + deposit_asset_ed)?;
|
||||
T::PoolAssets::touch(info.lp_token.clone(), &new_account, &depositor)?;
|
||||
}
|
||||
|
||||
// Transfer all pool related assets to the new account.
|
||||
|
||||
ensure!(
|
||||
balance1 ==
|
||||
T::Assets::transfer(
|
||||
asset1.clone(),
|
||||
&prior_account,
|
||||
&new_account,
|
||||
balance1,
|
||||
Preservation::Expendable,
|
||||
)?,
|
||||
Error::<T>::PartialTransfer
|
||||
);
|
||||
|
||||
ensure!(
|
||||
balance2 ==
|
||||
T::Assets::transfer(
|
||||
asset2.clone(),
|
||||
&prior_account,
|
||||
&new_account,
|
||||
balance2,
|
||||
Preservation::Expendable,
|
||||
)?,
|
||||
Error::<T>::PartialTransfer
|
||||
);
|
||||
|
||||
ensure!(
|
||||
lp_balance ==
|
||||
T::PoolAssets::transfer(
|
||||
info.lp_token.clone(),
|
||||
&prior_account,
|
||||
&new_account,
|
||||
lp_balance,
|
||||
Preservation::Expendable,
|
||||
)?,
|
||||
Error::<T>::PartialTransfer
|
||||
);
|
||||
|
||||
// Refund deposits from prior accounts and burn previously minted assets.
|
||||
|
||||
if let Some((depositor, deposit)) =
|
||||
T::AssetsRefund::deposit_held(asset1.clone(), prior_account.clone())
|
||||
{
|
||||
T::AssetsRefund::refund(asset1.clone(), prior_account.clone())?;
|
||||
T::DepositAsset::burn_from(
|
||||
&depositor,
|
||||
deposit + deposit_asset_ed,
|
||||
Preservation::Expendable,
|
||||
Precision::Exact,
|
||||
Fortitude::Force,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some((depositor, deposit)) =
|
||||
T::AssetsRefund::deposit_held(asset2.clone(), prior_account.clone())
|
||||
{
|
||||
T::AssetsRefund::refund(asset2.clone(), prior_account.clone())?;
|
||||
T::DepositAsset::burn_from(
|
||||
&depositor,
|
||||
deposit + deposit_asset_ed,
|
||||
Preservation::Expendable,
|
||||
Precision::Exact,
|
||||
Fortitude::Force,
|
||||
)?;
|
||||
}
|
||||
|
||||
if let Some((depositor, deposit)) =
|
||||
T::PoolAssetsRefund::deposit_held(info.lp_token.clone(), prior_account.clone())
|
||||
{
|
||||
T::PoolAssetsRefund::refund(info.lp_token.clone(), prior_account.clone())?;
|
||||
T::DepositAsset::burn_from(
|
||||
&depositor,
|
||||
deposit + deposit_asset_ed,
|
||||
Preservation::Expendable,
|
||||
Precision::Exact,
|
||||
Fortitude::Force,
|
||||
)?;
|
||||
}
|
||||
|
||||
T::PoolAssetsTeam::reset_team(
|
||||
info.lp_token,
|
||||
new_account.clone(),
|
||||
new_account.clone(),
|
||||
new_account.clone(),
|
||||
new_account.clone(),
|
||||
)?;
|
||||
|
||||
Self::deposit_event(Event::MigratedToNewAccount {
|
||||
pool_id,
|
||||
prior_account,
|
||||
new_account,
|
||||
});
|
||||
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Returns the prior and new account IDs for a given pool ID. The prior account ID comes
|
||||
/// first in the tuple.
|
||||
#[cfg(not(any(test, feature = "runtime-benchmarks")))]
|
||||
fn addresses(pool_id: &T::PoolId) -> Option<(T::AccountId, T::AccountId)> {
|
||||
match (
|
||||
T::PriorAccountIdConverter::try_convert(pool_id),
|
||||
T::PoolLocator::address(pool_id),
|
||||
) {
|
||||
(Ok(a), Ok(b)) if a != b => Some((a, b)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the prior and new account IDs for a given pool ID. The prior account ID comes
|
||||
/// first in the tuple.
|
||||
///
|
||||
/// This function is intended for use only in test and benchmark environments. The prior
|
||||
/// account ID represents the new account ID from [`Config::PoolLocator`], allowing the use
|
||||
/// of the main pallet's calls to set up a pool with liquidity placed in that account and
|
||||
/// migrate it to another account, which in this case is the result of
|
||||
/// [`Config::PriorAccountIdConverter`].
|
||||
#[cfg(any(test, feature = "runtime-benchmarks"))]
|
||||
pub(crate) fn addresses(pool_id: &T::PoolId) -> Option<(T::AccountId, T::AccountId)> {
|
||||
match (
|
||||
T::PoolLocator::address(pool_id),
|
||||
T::PriorAccountIdConverter::try_convert(pool_id),
|
||||
) {
|
||||
(Ok(a), Ok(b)) if a != b => Some((a, b)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test environment for Asset Conversion Ops pallet.
|
||||
|
||||
use crate as pezpallet_asset_conversion_ops;
|
||||
use core::default::Default;
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl,
|
||||
instances::{Instance1, Instance2},
|
||||
ord_parameter_types, parameter_types,
|
||||
traits::{
|
||||
tokens::{
|
||||
fungible::{NativeFromLeft, NativeOrWithId, UnionOf},
|
||||
imbalance::ResolveAssetTo,
|
||||
},
|
||||
AsEnsureOriginWithArg, ConstU32, ConstU64,
|
||||
},
|
||||
PalletId,
|
||||
};
|
||||
use pezframe_system::{EnsureSigned, EnsureSignedBy};
|
||||
use pezpallet_asset_conversion::{self, AccountIdConverter, AccountIdConverterNoSeed, Ascending};
|
||||
use pezsp_arithmetic::Permill;
|
||||
use pezsp_runtime::{traits::AccountIdConversion, BuildStorage};
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Balances: pezpallet_balances,
|
||||
Assets: pezpallet_assets::<Instance1>,
|
||||
PoolAssets: pezpallet_assets::<Instance2>,
|
||||
AssetConversion: pezpallet_asset_conversion,
|
||||
AssetConversionOps: pezpallet_asset_conversion_ops,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u64>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_assets::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_assets::Config<Instance1> for Test {
|
||||
type Currency = Balances;
|
||||
type CreateOrigin = AsEnsureOriginWithArg<EnsureSigned<Self::AccountId>>;
|
||||
type ForceOrigin = pezframe_system::EnsureRoot<Self::AccountId>;
|
||||
type Holder = ();
|
||||
type Freezer = ();
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_assets::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_assets::Config<Instance2> for Test {
|
||||
type Currency = Balances;
|
||||
type CreateOrigin =
|
||||
AsEnsureOriginWithArg<EnsureSignedBy<AssetConversionOrigin, Self::AccountId>>;
|
||||
type ForceOrigin = pezframe_system::EnsureRoot<Self::AccountId>;
|
||||
type Holder = ();
|
||||
type Freezer = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon");
|
||||
pub const Native: NativeOrWithId<u32> = NativeOrWithId::Native;
|
||||
pub storage LiquidityWithdrawalFee: Permill = Permill::from_percent(0);
|
||||
}
|
||||
|
||||
ord_parameter_types! {
|
||||
pub const AssetConversionOrigin: u64 = AccountIdConversion::<u64>::into_account_truncating(&AssetConversionPalletId::get());
|
||||
}
|
||||
|
||||
pub type NativeAndAssets = UnionOf<Balances, Assets, NativeFromLeft, NativeOrWithId<u32>, u64>;
|
||||
pub type PoolIdToAccountId =
|
||||
AccountIdConverter<AssetConversionPalletId, (NativeOrWithId<u32>, NativeOrWithId<u32>)>;
|
||||
pub type AscendingLocator = Ascending<u64, NativeOrWithId<u32>, PoolIdToAccountId>;
|
||||
|
||||
impl pezpallet_asset_conversion::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Balance = <Self as pezpallet_balances::Config>::Balance;
|
||||
type HigherPrecisionBalance = pezsp_core::U256;
|
||||
type AssetKind = NativeOrWithId<u32>;
|
||||
type Assets = NativeAndAssets;
|
||||
type PoolId = (Self::AssetKind, Self::AssetKind);
|
||||
type PoolLocator = AscendingLocator;
|
||||
type PoolAssetId = u32;
|
||||
type PoolAssets = PoolAssets;
|
||||
type PoolSetupFee = ConstU64<100>;
|
||||
type PoolSetupFeeAsset = Native;
|
||||
type PoolSetupFeeTarget = ResolveAssetTo<AssetConversionOrigin, Self::Assets>;
|
||||
type PalletId = AssetConversionPalletId;
|
||||
type WeightInfo = ();
|
||||
type LPFee = ConstU32<3>;
|
||||
type LiquidityWithdrawalFee = LiquidityWithdrawalFee;
|
||||
type MaxSwapPathLength = ConstU32<4>;
|
||||
type MintMinLiquidity = ConstU64<100>;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
||||
pub type OldPoolIdToAccountId =
|
||||
AccountIdConverterNoSeed<(NativeOrWithId<u32>, NativeOrWithId<u32>)>;
|
||||
|
||||
impl pezpallet_asset_conversion_ops::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type PriorAccountIdConverter = OldPoolIdToAccountId;
|
||||
type AssetsRefund = NativeAndAssets;
|
||||
type PoolAssetsRefund = PoolAssets;
|
||||
type PoolAssetsTeam = PoolAssets;
|
||||
type DepositAsset = Balances;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
pub(crate) fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
|
||||
pezpallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![(1, 10000), (2, 20000), (3, 30000), (4, 40000)],
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = pezsp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Asset Conversion Ops pallet tests.
|
||||
|
||||
use crate::{mock::*, *};
|
||||
use pezframe_support::{
|
||||
assert_noop, assert_ok,
|
||||
traits::{
|
||||
fungible::{Inspect as FungibleInspect, NativeOrWithId},
|
||||
fungibles::{Create, Inspect},
|
||||
Incrementable,
|
||||
},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn migrate_pool_account_id_with_native() {
|
||||
new_test_ext().execute_with(|| {
|
||||
type PoolLocator = <Test as pezpallet_asset_conversion::Config>::PoolLocator;
|
||||
let user = 1;
|
||||
let token_1 = NativeOrWithId::Native;
|
||||
let token_2 = NativeOrWithId::WithId(2);
|
||||
let pool_id = PoolLocator::pool_id(&token_1, &token_2).unwrap();
|
||||
let lp_token =
|
||||
<Test as pezpallet_asset_conversion::Config>::PoolAssetId::initial_value().unwrap();
|
||||
|
||||
// setup pool and provide some liquidity.
|
||||
assert_ok!(NativeAndAssets::create(token_2.clone(), user, false, 1));
|
||||
|
||||
assert_ok!(AssetConversion::create_pool(
|
||||
RuntimeOrigin::signed(user),
|
||||
Box::new(token_1.clone()),
|
||||
Box::new(token_2.clone())
|
||||
));
|
||||
|
||||
let ed = Balances::minimum_balance();
|
||||
assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 * 2 + ed));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000));
|
||||
|
||||
assert_ok!(AssetConversion::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
Box::new(token_1.clone()),
|
||||
Box::new(token_2.clone()),
|
||||
10000,
|
||||
10,
|
||||
10000,
|
||||
10,
|
||||
user,
|
||||
));
|
||||
|
||||
// assert user's balance.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &user), 10000 + ed);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &user), 1000 - 10);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &user), 216);
|
||||
|
||||
// record total issuances before migration.
|
||||
let total_issuance_token1 = NativeAndAssets::total_issuance(token_1.clone());
|
||||
let total_issuance_token2 = NativeAndAssets::total_issuance(token_2.clone());
|
||||
let total_issuance_lp_token = PoolAssets::total_issuance(lp_token);
|
||||
|
||||
let pool_account = PoolLocator::address(&pool_id).unwrap();
|
||||
let (prior_pool_account, new_pool_account) =
|
||||
AssetConversionOps::addresses(&pool_id).unwrap();
|
||||
assert_eq!(pool_account, prior_pool_account);
|
||||
|
||||
// assert pool's balances before migration.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &prior_pool_account), 10000);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &prior_pool_account), 10);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &prior_pool_account), 100);
|
||||
|
||||
// migrate.
|
||||
assert_ok!(AssetConversionOps::migrate_to_new_account(
|
||||
RuntimeOrigin::signed(user),
|
||||
Box::new(token_1.clone()),
|
||||
Box::new(token_2.clone()),
|
||||
));
|
||||
|
||||
// assert user's balance has not changed.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &user), 10000 + ed);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &user), 1000 - 10);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &user), 216);
|
||||
|
||||
// assert pool's balance on new account id is same as on prior account id.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &new_pool_account), 10000);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &new_pool_account), 10);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &new_pool_account), 100);
|
||||
|
||||
// assert pool's balance on prior account id is zero.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &prior_pool_account), 0);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &prior_pool_account), 0);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &prior_pool_account), 0);
|
||||
|
||||
// assert total issuance has not changed.
|
||||
assert_eq!(total_issuance_token1, NativeAndAssets::total_issuance(token_1));
|
||||
assert_eq!(total_issuance_token2, NativeAndAssets::total_issuance(token_2));
|
||||
assert_eq!(total_issuance_lp_token, PoolAssets::total_issuance(lp_token));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migrate_pool_account_id_with_insufficient_assets() {
|
||||
new_test_ext().execute_with(|| {
|
||||
type PoolLocator = <Test as pezpallet_asset_conversion::Config>::PoolLocator;
|
||||
let user = 1;
|
||||
let token_1 = NativeOrWithId::WithId(1);
|
||||
let token_2 = NativeOrWithId::WithId(2);
|
||||
let pool_id = PoolLocator::pool_id(&token_1, &token_2).unwrap();
|
||||
let lp_token =
|
||||
<Test as pezpallet_asset_conversion::Config>::PoolAssetId::initial_value().unwrap();
|
||||
|
||||
// setup pool and provide some liquidity.
|
||||
assert_ok!(NativeAndAssets::create(token_1.clone(), user, false, 1));
|
||||
assert_ok!(NativeAndAssets::create(token_2.clone(), user, false, 1));
|
||||
|
||||
assert_ok!(AssetConversion::create_pool(
|
||||
RuntimeOrigin::signed(user),
|
||||
Box::new(token_1.clone()),
|
||||
Box::new(token_2.clone())
|
||||
));
|
||||
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 1, user, 20000));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000));
|
||||
|
||||
assert_ok!(AssetConversion::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
Box::new(token_1.clone()),
|
||||
Box::new(token_2.clone()),
|
||||
10000,
|
||||
10,
|
||||
10000,
|
||||
10,
|
||||
user,
|
||||
));
|
||||
|
||||
// assert user's balance.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &user), 10000);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &user), 1000 - 10);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &user), 216);
|
||||
|
||||
// record total issuances before migration.
|
||||
let total_issuance_token1 = NativeAndAssets::total_issuance(token_1.clone());
|
||||
let total_issuance_token2 = NativeAndAssets::total_issuance(token_2.clone());
|
||||
let total_issuance_lp_token = PoolAssets::total_issuance(lp_token);
|
||||
|
||||
let pool_account = PoolLocator::address(&pool_id).unwrap();
|
||||
let (prior_pool_account, new_pool_account) =
|
||||
AssetConversionOps::addresses(&pool_id).unwrap();
|
||||
assert_eq!(pool_account, prior_pool_account);
|
||||
|
||||
// assert pool's balances before migration.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &prior_pool_account), 10000);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &prior_pool_account), 10);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &prior_pool_account), 100);
|
||||
|
||||
// migrate.
|
||||
assert_ok!(AssetConversionOps::migrate_to_new_account(
|
||||
RuntimeOrigin::signed(user),
|
||||
Box::new(token_1.clone()),
|
||||
Box::new(token_2.clone()),
|
||||
));
|
||||
|
||||
// assert user's balance has not changed.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &user), 10000);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &user), 1000 - 10);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &user), 216);
|
||||
|
||||
// assert pool's balance on new account id is same as on prior account id.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &new_pool_account), 10000);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &new_pool_account), 10);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &new_pool_account), 100);
|
||||
|
||||
// assert pool's balance on prior account id is zero.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &prior_pool_account), 0);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &prior_pool_account), 0);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &prior_pool_account), 0);
|
||||
|
||||
// assert total issuance has not changed.
|
||||
assert_eq!(total_issuance_token1, NativeAndAssets::total_issuance(token_1));
|
||||
assert_eq!(total_issuance_token2, NativeAndAssets::total_issuance(token_2));
|
||||
assert_eq!(total_issuance_lp_token, PoolAssets::total_issuance(lp_token));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migrate_pool_account_id_with_sufficient_assets() {
|
||||
new_test_ext().execute_with(|| {
|
||||
type PoolLocator = <Test as pezpallet_asset_conversion::Config>::PoolLocator;
|
||||
let user = 1;
|
||||
let token_1 = NativeOrWithId::WithId(1);
|
||||
let token_2 = NativeOrWithId::WithId(2);
|
||||
let pool_id = PoolLocator::pool_id(&token_1, &token_2).unwrap();
|
||||
let lp_token =
|
||||
<Test as pezpallet_asset_conversion::Config>::PoolAssetId::initial_value().unwrap();
|
||||
|
||||
// setup pool and provide some liquidity.
|
||||
assert_ok!(NativeAndAssets::create(token_1.clone(), user, true, 1));
|
||||
assert_ok!(NativeAndAssets::create(token_2.clone(), user, true, 1));
|
||||
|
||||
assert_ok!(AssetConversion::create_pool(
|
||||
RuntimeOrigin::signed(user),
|
||||
Box::new(token_1.clone()),
|
||||
Box::new(token_2.clone())
|
||||
));
|
||||
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 1, user, 20000));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000));
|
||||
|
||||
assert_ok!(AssetConversion::add_liquidity(
|
||||
RuntimeOrigin::signed(user),
|
||||
Box::new(token_1.clone()),
|
||||
Box::new(token_2.clone()),
|
||||
10000,
|
||||
10,
|
||||
10000,
|
||||
10,
|
||||
user,
|
||||
));
|
||||
|
||||
// assert user's balance.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &user), 10000);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &user), 1000 - 10);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &user), 216);
|
||||
|
||||
// record total issuances before migration.
|
||||
let total_issuance_token1 = NativeAndAssets::total_issuance(token_1.clone());
|
||||
let total_issuance_token2 = NativeAndAssets::total_issuance(token_2.clone());
|
||||
let total_issuance_lp_token = PoolAssets::total_issuance(lp_token);
|
||||
|
||||
let pool_account = PoolLocator::address(&pool_id).unwrap();
|
||||
let (prior_pool_account, new_pool_account) =
|
||||
AssetConversionOps::addresses(&pool_id).unwrap();
|
||||
assert_eq!(pool_account, prior_pool_account);
|
||||
|
||||
// assert pool's balances before migration.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &prior_pool_account), 10000);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &prior_pool_account), 10);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &prior_pool_account), 100);
|
||||
|
||||
// migrate.
|
||||
assert_ok!(AssetConversionOps::migrate_to_new_account(
|
||||
RuntimeOrigin::signed(user),
|
||||
Box::new(token_1.clone()),
|
||||
Box::new(token_2.clone()),
|
||||
));
|
||||
|
||||
// assert user's balance has not changed.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &user), 10000);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &user), 1000 - 10);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &user), 216);
|
||||
|
||||
// assert pool's balance on new account id is same as on prior account id.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &new_pool_account), 10000);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &new_pool_account), 10);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &new_pool_account), 100);
|
||||
|
||||
// assert pool's balance on prior account id is zero.
|
||||
assert_eq!(NativeAndAssets::balance(token_1.clone(), &prior_pool_account), 0);
|
||||
assert_eq!(NativeAndAssets::balance(token_2.clone(), &prior_pool_account), 0);
|
||||
assert_eq!(PoolAssets::balance(lp_token, &prior_pool_account), 0);
|
||||
|
||||
// assert total issuance has not changed.
|
||||
assert_eq!(total_issuance_token1, NativeAndAssets::total_issuance(token_1));
|
||||
assert_eq!(total_issuance_token2, NativeAndAssets::total_issuance(token_2));
|
||||
assert_eq!(total_issuance_lp_token, PoolAssets::total_issuance(lp_token));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migrate_empty_pool_account_id() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let user = 1;
|
||||
let token_1 = NativeOrWithId::Native;
|
||||
let token_2 = NativeOrWithId::WithId(2);
|
||||
|
||||
// setup pool and provide some liquidity.
|
||||
assert_ok!(NativeAndAssets::create(token_2.clone(), user, false, 1));
|
||||
|
||||
assert_ok!(AssetConversion::create_pool(
|
||||
RuntimeOrigin::signed(user),
|
||||
Box::new(token_1.clone()),
|
||||
Box::new(token_2.clone())
|
||||
));
|
||||
|
||||
// migrate.
|
||||
assert_noop!(
|
||||
AssetConversionOps::migrate_to_new_account(
|
||||
RuntimeOrigin::signed(user),
|
||||
Box::new(token_1.clone()),
|
||||
Box::new(token_2.clone()),
|
||||
),
|
||||
Error::<Test>::ZeroBalance
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Autogenerated weights for `pezpallet_asset_conversion_ops`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// frame-omni-bencher
|
||||
// v1
|
||||
// benchmark
|
||||
// pallet
|
||||
// --extrinsic=*
|
||||
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
|
||||
// --pallet=pezpallet_asset_conversion_ops
|
||||
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/HEADER-APACHE2
|
||||
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/pezframe/asset-conversion/ops/src/weights.rs
|
||||
// --wasm-execution=compiled
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --heap-pages=4096
|
||||
// --template=bizinikiwi/.maintain/frame-weight-template.hbs
|
||||
// --no-storage-info
|
||||
// --no-min-squares
|
||||
// --no-median-slopes
|
||||
// --genesis-builder-policy=none
|
||||
// --exclude-pallets=pezpallet_xcm,pezpallet_xcm_benchmarks::fungible,pezpallet_xcm_benchmarks::generic,pezpallet_nomination_pools,pezpallet_remark,pezpallet_transaction_storage,pezpallet_election_provider_multi_block,pezpallet_election_provider_multi_block::signed,pezpallet_election_provider_multi_block::unsigned,pezpallet_election_provider_multi_block::verifier
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for `pezpallet_asset_conversion_ops`.
|
||||
pub trait WeightInfo {
|
||||
fn migrate_to_new_account() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_asset_conversion_ops` using the Bizinikiwi node and recommended hardware.
|
||||
pub struct BizinikiwiWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// Storage: `AssetConversion::Pools` (r:1 w:0)
|
||||
/// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:4 w:4)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Account` (r:2 w:2)
|
||||
/// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Asset` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:2 w:2)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:2 w:2)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
fn migrate_to_new_account() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1307`
|
||||
// Estimated: `11426`
|
||||
// Minimum execution time: 230_668_000 picoseconds.
|
||||
Weight::from_parts(232_964_000, 11426)
|
||||
.saturating_add(T::DbWeight::get().reads(12_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(11_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `AssetConversion::Pools` (r:1 w:0)
|
||||
/// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:4 w:4)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Account` (r:2 w:2)
|
||||
/// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Asset` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:2 w:2)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:2 w:2)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
fn migrate_to_new_account() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1307`
|
||||
// Estimated: `11426`
|
||||
// Minimum execution time: 230_668_000 picoseconds.
|
||||
Weight::from_parts(232_964_000, 11426)
|
||||
.saturating_add(RocksDbWeight::get().reads(12_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(11_u64))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Asset Conversion pallet benchmarking.
|
||||
|
||||
use super::*;
|
||||
use crate::Pallet as AssetConversion;
|
||||
use alloc::vec;
|
||||
use core::marker::PhantomData;
|
||||
use pezframe_benchmarking::{v2::*, whitelisted_caller};
|
||||
use pezframe_support::{
|
||||
assert_ok,
|
||||
traits::{
|
||||
fungible::NativeOrWithId,
|
||||
fungibles::{Create, Inspect, Mutate, Refund},
|
||||
},
|
||||
};
|
||||
use pezframe_system::RawOrigin as SystemOrigin;
|
||||
use pezsp_core::Get;
|
||||
|
||||
/// Benchmark Helper
|
||||
pub trait BenchmarkHelper<AssetKind> {
|
||||
/// Returns a valid assets pair for the pool creation.
|
||||
///
|
||||
/// When a specific asset, such as the native asset, is required in every pool, it should be
|
||||
/// returned for each odd-numbered seed.
|
||||
fn create_pair(seed1: u32, seed2: u32) -> (AssetKind, AssetKind);
|
||||
}
|
||||
|
||||
impl<AssetKind> BenchmarkHelper<AssetKind> for ()
|
||||
where
|
||||
AssetKind: From<u32>,
|
||||
{
|
||||
fn create_pair(seed1: u32, seed2: u32) -> (AssetKind, AssetKind) {
|
||||
(seed1.into(), seed2.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Factory for creating a valid asset pairs with [`NativeOrWithId::Native`] always leading in the
|
||||
/// pair.
|
||||
pub struct NativeOrWithIdFactory<AssetId>(PhantomData<AssetId>);
|
||||
impl<AssetId: From<u32> + Ord> BenchmarkHelper<NativeOrWithId<AssetId>>
|
||||
for NativeOrWithIdFactory<AssetId>
|
||||
{
|
||||
fn create_pair(seed1: u32, seed2: u32) -> (NativeOrWithId<AssetId>, NativeOrWithId<AssetId>) {
|
||||
if seed1 % 2 == 0 {
|
||||
(NativeOrWithId::WithId(seed2.into()), NativeOrWithId::Native)
|
||||
} else {
|
||||
(NativeOrWithId::Native, NativeOrWithId::WithId(seed2.into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides a pair of amounts expected to serve as sufficient initial liquidity for a pool.
|
||||
fn valid_liquidity_amount<T: Config>(ed1: T::Balance, ed2: T::Balance) -> (T::Balance, T::Balance)
|
||||
where
|
||||
T::Assets: Inspect<T::AccountId>,
|
||||
{
|
||||
let l =
|
||||
ed1.max(ed2) + T::MintMinLiquidity::get() + T::MintMinLiquidity::get() + T::Balance::one();
|
||||
(l, l)
|
||||
}
|
||||
|
||||
/// Create the `asset` and mint the `amount` for the `caller`.
|
||||
fn create_asset<T: Config>(
|
||||
caller: &T::AccountId,
|
||||
asset: &T::AssetKind,
|
||||
amount: T::Balance,
|
||||
is_sufficient: bool,
|
||||
) where
|
||||
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
|
||||
{
|
||||
if !T::Assets::asset_exists(asset.clone()) {
|
||||
assert_ok!(T::Assets::create(
|
||||
asset.clone(),
|
||||
caller.clone(),
|
||||
is_sufficient,
|
||||
T::Balance::one()
|
||||
));
|
||||
}
|
||||
assert_ok!(T::Assets::mint_into(
|
||||
asset.clone(),
|
||||
&caller,
|
||||
amount + T::Assets::minimum_balance(asset.clone())
|
||||
));
|
||||
}
|
||||
|
||||
/// Create the designated fee asset for pool creation.
|
||||
fn create_fee_asset<T: Config>(caller: &T::AccountId)
|
||||
where
|
||||
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
|
||||
{
|
||||
let fee_asset = T::PoolSetupFeeAsset::get();
|
||||
if !T::Assets::asset_exists(fee_asset.clone()) {
|
||||
assert_ok!(T::Assets::create(fee_asset.clone(), caller.clone(), true, T::Balance::one()));
|
||||
}
|
||||
assert_ok!(T::Assets::mint_into(
|
||||
fee_asset.clone(),
|
||||
&caller,
|
||||
T::Assets::minimum_balance(fee_asset)
|
||||
));
|
||||
}
|
||||
|
||||
/// Mint the fee asset for the `caller` sufficient to cover the fee for creating a new pool.
|
||||
fn mint_setup_fee_asset<T: Config>(
|
||||
caller: &T::AccountId,
|
||||
asset1: &T::AssetKind,
|
||||
asset2: &T::AssetKind,
|
||||
lp_token: &T::PoolAssetId,
|
||||
) where
|
||||
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
|
||||
{
|
||||
assert_ok!(T::Assets::mint_into(
|
||||
T::PoolSetupFeeAsset::get(),
|
||||
&caller,
|
||||
T::PoolSetupFee::get() +
|
||||
T::Assets::deposit_required(asset1.clone()) +
|
||||
T::Assets::deposit_required(asset2.clone()) +
|
||||
T::PoolAssets::deposit_required(lp_token.clone())
|
||||
));
|
||||
}
|
||||
|
||||
/// Creates a pool for a given asset pair.
|
||||
///
|
||||
/// This action mints the necessary amounts of the given assets for the `caller` to provide initial
|
||||
/// liquidity. It returns the LP token ID along with a pair of amounts sufficient for the pool's
|
||||
/// initial liquidity.
|
||||
fn create_asset_and_pool<T: Config>(
|
||||
caller: &T::AccountId,
|
||||
asset1: &T::AssetKind,
|
||||
asset2: &T::AssetKind,
|
||||
) -> (T::PoolAssetId, T::Balance, T::Balance)
|
||||
where
|
||||
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
|
||||
{
|
||||
let (liquidity1, liquidity2) = valid_liquidity_amount::<T>(
|
||||
T::Assets::minimum_balance(asset1.clone()),
|
||||
T::Assets::minimum_balance(asset2.clone()),
|
||||
);
|
||||
create_asset::<T>(caller, asset1, liquidity1, true);
|
||||
create_asset::<T>(caller, asset2, liquidity2, true);
|
||||
let lp_token = AssetConversion::<T>::get_next_pool_asset_id();
|
||||
|
||||
mint_setup_fee_asset::<T>(caller, asset1, asset2, &lp_token);
|
||||
|
||||
assert_ok!(AssetConversion::<T>::create_pool(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
Box::new(asset1.clone()),
|
||||
Box::new(asset2.clone())
|
||||
));
|
||||
|
||||
(lp_token, liquidity1, liquidity2)
|
||||
}
|
||||
|
||||
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
|
||||
let events = pezframe_system::Pallet::<T>::events();
|
||||
let system_event: <T as pezframe_system::Config>::RuntimeEvent = generic_event.into();
|
||||
// compare to the last event record
|
||||
let pezframe_system::EventRecord { event, .. } = &events[events.len() - 1];
|
||||
assert_eq!(event, &system_event);
|
||||
}
|
||||
|
||||
#[benchmarks(where T::Assets: Create<T::AccountId> + Mutate<T::AccountId>, T::PoolAssetId: Into<u32>,)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn create_pool() {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
|
||||
create_asset::<T>(&caller, &asset1, T::Assets::minimum_balance(asset1.clone()), true);
|
||||
create_asset::<T>(&caller, &asset2, T::Assets::minimum_balance(asset2.clone()), true);
|
||||
|
||||
let lp_token = AssetConversion::<T>::get_next_pool_asset_id();
|
||||
create_fee_asset::<T>(&caller);
|
||||
mint_setup_fee_asset::<T>(&caller, &asset1, &asset2, &lp_token);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone()));
|
||||
|
||||
let pool_id = T::PoolLocator::pool_id(&asset1, &asset2).unwrap();
|
||||
let pool_account = T::PoolLocator::address(&pool_id).unwrap();
|
||||
assert_last_event::<T>(
|
||||
Event::PoolCreated { creator: caller, pool_account, pool_id, lp_token }.into(),
|
||||
);
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn add_liquidity() {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
|
||||
|
||||
create_fee_asset::<T>(&caller);
|
||||
let (lp_token, liquidity1, liquidity2) =
|
||||
create_asset_and_pool::<T>(&caller, &asset1, &asset2);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(
|
||||
SystemOrigin::Signed(caller.clone()),
|
||||
Box::new(asset1.clone()),
|
||||
Box::new(asset2.clone()),
|
||||
liquidity1,
|
||||
liquidity2,
|
||||
T::Balance::one(),
|
||||
T::Balance::zero(),
|
||||
caller.clone(),
|
||||
);
|
||||
|
||||
let pool_account = T::PoolLocator::pool_address(&asset1, &asset2).unwrap();
|
||||
let lp_minted =
|
||||
AssetConversion::<T>::calc_lp_amount_for_zero_supply(&liquidity1, &liquidity2).unwrap();
|
||||
assert_eq!(T::PoolAssets::balance(lp_token, &caller), lp_minted);
|
||||
assert_eq!(T::Assets::balance(asset1, &pool_account), liquidity1);
|
||||
assert_eq!(T::Assets::balance(asset2, &pool_account), liquidity2);
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn remove_liquidity() {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
|
||||
|
||||
create_fee_asset::<T>(&caller);
|
||||
let (lp_token, liquidity1, liquidity2) =
|
||||
create_asset_and_pool::<T>(&caller, &asset1, &asset2);
|
||||
|
||||
let remove_lp_amount = T::Balance::one();
|
||||
|
||||
assert_ok!(AssetConversion::<T>::add_liquidity(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
Box::new(asset1.clone()),
|
||||
Box::new(asset2.clone()),
|
||||
liquidity1,
|
||||
liquidity2,
|
||||
T::Balance::one(),
|
||||
T::Balance::zero(),
|
||||
caller.clone(),
|
||||
));
|
||||
let total_supply =
|
||||
<T::PoolAssets as Inspect<T::AccountId>>::total_issuance(lp_token.clone());
|
||||
|
||||
#[extrinsic_call]
|
||||
_(
|
||||
SystemOrigin::Signed(caller.clone()),
|
||||
Box::new(asset1),
|
||||
Box::new(asset2),
|
||||
remove_lp_amount,
|
||||
T::Balance::zero(),
|
||||
T::Balance::zero(),
|
||||
caller.clone(),
|
||||
);
|
||||
|
||||
let new_total_supply = <T::PoolAssets as Inspect<T::AccountId>>::total_issuance(lp_token);
|
||||
assert_eq!(new_total_supply, total_supply - remove_lp_amount);
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn swap_exact_tokens_for_tokens(n: Linear<2, { T::MaxSwapPathLength::get() }>) {
|
||||
let mut swap_amount = T::Balance::one();
|
||||
let mut path = vec![];
|
||||
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
create_fee_asset::<T>(&caller);
|
||||
for n in 1..n {
|
||||
let (asset1, asset2) = T::BenchmarkHelper::create_pair(n - 1, n);
|
||||
swap_amount = swap_amount + T::Balance::one();
|
||||
if path.len() == 0 {
|
||||
path = vec![Box::new(asset1.clone()), Box::new(asset2.clone())];
|
||||
} else {
|
||||
path.push(Box::new(asset2.clone()));
|
||||
}
|
||||
|
||||
let (_, liquidity1, liquidity2) = create_asset_and_pool::<T>(&caller, &asset1, &asset2);
|
||||
|
||||
assert_ok!(AssetConversion::<T>::add_liquidity(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
Box::new(asset1.clone()),
|
||||
Box::new(asset2.clone()),
|
||||
liquidity1,
|
||||
liquidity2,
|
||||
T::Balance::one(),
|
||||
T::Balance::zero(),
|
||||
caller.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
let asset_in = *path.first().unwrap().clone();
|
||||
assert_ok!(T::Assets::mint_into(
|
||||
asset_in.clone(),
|
||||
&caller,
|
||||
swap_amount + T::Balance::one()
|
||||
));
|
||||
let init_caller_balance = T::Assets::balance(asset_in.clone(), &caller);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(
|
||||
SystemOrigin::Signed(caller.clone()),
|
||||
path,
|
||||
swap_amount,
|
||||
T::Balance::one(),
|
||||
caller.clone(),
|
||||
true,
|
||||
);
|
||||
|
||||
let actual_balance = T::Assets::balance(asset_in, &caller);
|
||||
assert_eq!(actual_balance, init_caller_balance - swap_amount);
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn swap_tokens_for_exact_tokens(n: Linear<2, { T::MaxSwapPathLength::get() }>) {
|
||||
let mut max_swap_amount = T::Balance::one();
|
||||
let mut path = vec![];
|
||||
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
create_fee_asset::<T>(&caller);
|
||||
for n in 1..n {
|
||||
let (asset1, asset2) = T::BenchmarkHelper::create_pair(n - 1, n);
|
||||
max_swap_amount = max_swap_amount + T::Balance::one() + T::Balance::one();
|
||||
if path.len() == 0 {
|
||||
path = vec![Box::new(asset1.clone()), Box::new(asset2.clone())];
|
||||
} else {
|
||||
path.push(Box::new(asset2.clone()));
|
||||
}
|
||||
|
||||
let (_, liquidity1, liquidity2) = create_asset_and_pool::<T>(&caller, &asset1, &asset2);
|
||||
|
||||
assert_ok!(AssetConversion::<T>::add_liquidity(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
Box::new(asset1.clone()),
|
||||
Box::new(asset2.clone()),
|
||||
liquidity1,
|
||||
liquidity2,
|
||||
T::Balance::one(),
|
||||
T::Balance::zero(),
|
||||
caller.clone(),
|
||||
));
|
||||
}
|
||||
|
||||
let asset_in = *path.first().unwrap().clone();
|
||||
let asset_out = *path.last().unwrap().clone();
|
||||
assert_ok!(T::Assets::mint_into(asset_in, &caller, max_swap_amount));
|
||||
let init_caller_balance = T::Assets::balance(asset_out.clone(), &caller);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(
|
||||
SystemOrigin::Signed(caller.clone()),
|
||||
path,
|
||||
T::Balance::one(),
|
||||
max_swap_amount,
|
||||
caller.clone(),
|
||||
true,
|
||||
);
|
||||
|
||||
let actual_balance = T::Assets::balance(asset_out, &caller);
|
||||
assert_eq!(actual_balance, init_caller_balance + T::Balance::one());
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn touch(n: Linear<0, 3>) {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let (asset1, asset2) = T::BenchmarkHelper::create_pair(0, 1);
|
||||
let pool_id = T::PoolLocator::pool_id(&asset1, &asset2).unwrap();
|
||||
let pool_account = T::PoolLocator::address(&pool_id).unwrap();
|
||||
|
||||
create_fee_asset::<T>(&caller);
|
||||
create_asset::<T>(&caller, &asset1, <T as Config>::Balance::one(), false);
|
||||
create_asset::<T>(&caller, &asset2, <T as Config>::Balance::one(), false);
|
||||
let lp_token = AssetConversion::<T>::get_next_pool_asset_id();
|
||||
mint_setup_fee_asset::<T>(&caller, &asset1, &asset2, &lp_token);
|
||||
|
||||
assert_ok!(AssetConversion::<T>::create_pool(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
Box::new(asset1.clone()),
|
||||
Box::new(asset2.clone())
|
||||
));
|
||||
|
||||
if n > 0 &&
|
||||
<T as Config>::Assets::deposit_held(asset1.clone(), pool_account.clone()).is_some()
|
||||
{
|
||||
let _ = <T as Config>::Assets::refund(asset1.clone(), pool_account.clone());
|
||||
}
|
||||
if n > 1 &&
|
||||
<T as Config>::Assets::deposit_held(asset2.clone(), pool_account.clone()).is_some()
|
||||
{
|
||||
let _ = <T as Config>::Assets::refund(asset2.clone(), pool_account.clone());
|
||||
}
|
||||
if n > 2 &&
|
||||
<T as Config>::PoolAssets::deposit_held(lp_token.clone(), pool_account.clone())
|
||||
.is_some()
|
||||
{
|
||||
let _ = <T as Config>::PoolAssets::refund(lp_token, pool_account);
|
||||
}
|
||||
|
||||
#[extrinsic_call]
|
||||
_(SystemOrigin::Signed(caller.clone()), Box::new(asset1.clone()), Box::new(asset2.clone()));
|
||||
|
||||
assert_last_event::<T>(Event::Touched { pool_id, who: caller }.into());
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(AssetConversion, crate::mock::new_test_ext(), crate::mock::Test);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,138 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Trait for providing methods to mutate liquidity pools.
|
||||
|
||||
use pezframe_support::{traits::tokens::Balance, transactional};
|
||||
use pezsp_runtime::DispatchError;
|
||||
|
||||
use crate::{Config, Pallet};
|
||||
|
||||
/// A struct to represent an asset and its desired and minimum amounts for adding liquidity.
|
||||
pub struct AddLiquidityAsset<AssetKind, Balance> {
|
||||
/// The kind of asset.
|
||||
pub asset: AssetKind,
|
||||
/// The desired amount of the asset to add.
|
||||
pub amount_desired: Balance,
|
||||
/// The minimum amount of the asset to add.
|
||||
pub amount_min: Balance,
|
||||
}
|
||||
|
||||
/// Trait for providing methods to mutate liquidity pools. This includes creating pools,
|
||||
/// adding liquidity, and removing liquidity.
|
||||
pub trait MutateLiquidity<AccountId> {
|
||||
/// The balance type for assets.
|
||||
type Balance: Balance;
|
||||
/// The type used to identify assets.
|
||||
type AssetKind;
|
||||
/// The type used to identify a liquidity pool.
|
||||
type PoolId;
|
||||
|
||||
/// Creates a new liquidity pool for the given assets.
|
||||
///
|
||||
/// Mints LP tokens to the `creator` account.
|
||||
///
|
||||
/// Returns the ID of the newly created pool.
|
||||
fn create_pool(
|
||||
creator: &AccountId,
|
||||
asset1: Self::AssetKind,
|
||||
asset2: Self::AssetKind,
|
||||
) -> Result<Self::PoolId, DispatchError>;
|
||||
|
||||
/// Adds liquidity to an existing pool.
|
||||
///
|
||||
/// Mints LP tokens to the `mint_to` account.
|
||||
///
|
||||
/// Returns the amount of LP tokens minted.
|
||||
fn add_liquidity(
|
||||
who: &AccountId,
|
||||
asset1: AddLiquidityAsset<Self::AssetKind, Self::Balance>,
|
||||
asset2: AddLiquidityAsset<Self::AssetKind, Self::Balance>,
|
||||
mint_to: &AccountId,
|
||||
) -> Result<Self::Balance, DispatchError>;
|
||||
|
||||
/// Removes liquidity from a pool.
|
||||
///
|
||||
/// Burns LP tokens from the `who` account and transfers the withdrawn assets to the
|
||||
/// `withdraw_to` account.
|
||||
///
|
||||
/// Returns the amounts of assets withdrawn.
|
||||
fn remove_liquidity(
|
||||
who: &AccountId,
|
||||
asset1: Self::AssetKind,
|
||||
asset2: Self::AssetKind,
|
||||
lp_token_burn: Self::Balance,
|
||||
amount1_min_receive: Self::Balance,
|
||||
amount2_min_receive: Self::Balance,
|
||||
withdraw_to: &AccountId,
|
||||
) -> Result<(Self::Balance, Self::Balance), DispatchError>;
|
||||
}
|
||||
|
||||
impl<T: Config> MutateLiquidity<T::AccountId> for Pallet<T> {
|
||||
type Balance = T::Balance;
|
||||
type AssetKind = T::AssetKind;
|
||||
type PoolId = T::PoolId;
|
||||
|
||||
#[transactional]
|
||||
fn create_pool(
|
||||
creator: &T::AccountId,
|
||||
asset1: T::AssetKind,
|
||||
asset2: T::AssetKind,
|
||||
) -> Result<T::PoolId, DispatchError> {
|
||||
Self::do_create_pool(creator, asset1, asset2)
|
||||
}
|
||||
|
||||
#[transactional]
|
||||
fn add_liquidity(
|
||||
who: &T::AccountId,
|
||||
asset1: AddLiquidityAsset<Self::AssetKind, Self::Balance>,
|
||||
asset2: AddLiquidityAsset<Self::AssetKind, Self::Balance>,
|
||||
mint_to: &T::AccountId,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
Self::do_add_liquidity(
|
||||
who,
|
||||
asset1.asset,
|
||||
asset2.asset,
|
||||
asset1.amount_desired,
|
||||
asset2.amount_desired,
|
||||
asset1.amount_min,
|
||||
asset2.amount_min,
|
||||
mint_to,
|
||||
)
|
||||
}
|
||||
|
||||
#[transactional]
|
||||
fn remove_liquidity(
|
||||
who: &T::AccountId,
|
||||
asset1: T::AssetKind,
|
||||
asset2: T::AssetKind,
|
||||
lp_token_burn: T::Balance,
|
||||
amount1_min_receive: T::Balance,
|
||||
amount2_min_receive: T::Balance,
|
||||
withdraw_to: &T::AccountId,
|
||||
) -> Result<(T::Balance, T::Balance), DispatchError> {
|
||||
Self::do_remove_liquidity(
|
||||
who,
|
||||
asset1,
|
||||
asset2,
|
||||
lp_token_burn,
|
||||
amount1_min_receive,
|
||||
amount2_min_receive,
|
||||
withdraw_to,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test environment for Asset Conversion pallet.
|
||||
|
||||
use super::*;
|
||||
use crate as pezpallet_asset_conversion;
|
||||
use core::default::Default;
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl,
|
||||
instances::{Instance1, Instance2},
|
||||
ord_parameter_types, parameter_types,
|
||||
traits::{
|
||||
tokens::{
|
||||
fungible::{NativeFromLeft, NativeOrWithId, UnionOf},
|
||||
imbalance::ResolveAssetTo,
|
||||
},
|
||||
AsEnsureOriginWithArg, ConstU128, ConstU32,
|
||||
},
|
||||
PalletId,
|
||||
};
|
||||
use pezframe_system::{EnsureSigned, EnsureSignedBy};
|
||||
use pezsp_arithmetic::Permill;
|
||||
use pezsp_runtime::{
|
||||
traits::{AccountIdConversion, IdentityLookup},
|
||||
BuildStorage,
|
||||
};
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Balances: pezpallet_balances,
|
||||
Assets: pezpallet_assets::<Instance1>,
|
||||
PoolAssets: pezpallet_assets::<Instance2>,
|
||||
AssetConversion: pezpallet_asset_conversion,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type AccountId = u128;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u128>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type Balance = u128;
|
||||
type ExistentialDeposit = ConstU128<100>;
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
impl pezpallet_assets::Config<Instance1> for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Balance = u128;
|
||||
type RemoveItemsLimit = ConstU32<1000>;
|
||||
type AssetId = u32;
|
||||
type AssetIdParameter = u32;
|
||||
type ReserveData = ();
|
||||
type Currency = Balances;
|
||||
type CreateOrigin = AsEnsureOriginWithArg<EnsureSigned<Self::AccountId>>;
|
||||
type ForceOrigin = pezframe_system::EnsureRoot<Self::AccountId>;
|
||||
type AssetDeposit = ConstU128<1>;
|
||||
type AssetAccountDeposit = ConstU128<10>;
|
||||
type MetadataDepositBase = ConstU128<1>;
|
||||
type MetadataDepositPerByte = ConstU128<1>;
|
||||
type ApprovalDeposit = ConstU128<1>;
|
||||
type StringLimit = ConstU32<50>;
|
||||
type Holder = ();
|
||||
type Freezer = ();
|
||||
type Extra = ();
|
||||
type WeightInfo = ();
|
||||
type CallbackHandle = ();
|
||||
pezpallet_assets::runtime_benchmarks_enabled! {
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
}
|
||||
|
||||
impl pezpallet_assets::Config<Instance2> for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Balance = u128;
|
||||
type RemoveItemsLimit = ConstU32<1000>;
|
||||
type AssetId = u32;
|
||||
type AssetIdParameter = u32;
|
||||
type ReserveData = ();
|
||||
type Currency = Balances;
|
||||
type CreateOrigin =
|
||||
AsEnsureOriginWithArg<EnsureSignedBy<AssetConversionOrigin, Self::AccountId>>;
|
||||
type ForceOrigin = pezframe_system::EnsureRoot<Self::AccountId>;
|
||||
type AssetDeposit = ConstU128<0>;
|
||||
type AssetAccountDeposit = ConstU128<0>;
|
||||
type MetadataDepositBase = ConstU128<0>;
|
||||
type MetadataDepositPerByte = ConstU128<0>;
|
||||
type ApprovalDeposit = ConstU128<0>;
|
||||
type StringLimit = ConstU32<50>;
|
||||
type Holder = ();
|
||||
type Freezer = ();
|
||||
type Extra = ();
|
||||
type WeightInfo = ();
|
||||
type CallbackHandle = ();
|
||||
pezpallet_assets::runtime_benchmarks_enabled! {
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon");
|
||||
pub const Native: NativeOrWithId<u32> = NativeOrWithId::Native;
|
||||
pub storage LiquidityWithdrawalFee: Permill = Permill::from_percent(0);
|
||||
}
|
||||
|
||||
ord_parameter_types! {
|
||||
pub const AssetConversionOrigin: u128 = AccountIdConversion::<u128>::into_account_truncating(&AssetConversionPalletId::get());
|
||||
}
|
||||
|
||||
pub type NativeAndAssets = UnionOf<Balances, Assets, NativeFromLeft, NativeOrWithId<u32>, u128>;
|
||||
pub type PoolIdToAccountId =
|
||||
AccountIdConverter<AssetConversionPalletId, (NativeOrWithId<u32>, NativeOrWithId<u32>)>;
|
||||
pub type AscendingLocator = Ascending<u128, NativeOrWithId<u32>, PoolIdToAccountId>;
|
||||
pub type WithFirstAssetLocator =
|
||||
WithFirstAsset<Native, u128, NativeOrWithId<u32>, PoolIdToAccountId>;
|
||||
|
||||
impl Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Balance = <Self as pezpallet_balances::Config>::Balance;
|
||||
type HigherPrecisionBalance = pezsp_core::U256;
|
||||
type AssetKind = NativeOrWithId<u32>;
|
||||
type Assets = NativeAndAssets;
|
||||
type PoolId = (Self::AssetKind, Self::AssetKind);
|
||||
type PoolLocator = Chain<WithFirstAssetLocator, AscendingLocator>;
|
||||
type PoolAssetId = u32;
|
||||
type PoolAssets = PoolAssets;
|
||||
type PoolSetupFee = ConstU128<100>; // should be more or equal to the existential deposit
|
||||
type PoolSetupFeeAsset = Native;
|
||||
type PoolSetupFeeTarget = ResolveAssetTo<AssetConversionOrigin, Self::Assets>;
|
||||
type PalletId = AssetConversionPalletId;
|
||||
type WeightInfo = ();
|
||||
type LPFee = ConstU32<3>; // means 0.3%
|
||||
type LiquidityWithdrawalFee = LiquidityWithdrawalFee;
|
||||
type MaxSwapPathLength = ConstU32<4>;
|
||||
type MintMinLiquidity = ConstU128<100>; // 100 is good enough when the main currency has 12 decimals.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
||||
pub(crate) fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
|
||||
pezpallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![(1, 10000), (2, 20000), (3, 30000), (4, 40000)],
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = pezsp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Traits and implementations for swap between the various asset classes.
|
||||
|
||||
use super::*;
|
||||
use pezframe_support::{storage::with_transaction, transactional};
|
||||
|
||||
/// Trait for providing methods to swap between the various asset classes.
|
||||
pub trait Swap<AccountId> {
|
||||
/// Measure units of the asset classes for swapping.
|
||||
type Balance: Balance;
|
||||
/// Kind of assets that are going to be swapped.
|
||||
type AssetKind;
|
||||
|
||||
/// Returns the upper limit on the length of the swap path.
|
||||
fn max_path_len() -> u32;
|
||||
|
||||
/// Swap exactly `amount_in` of asset `path[0]` for asset `path[last]`.
|
||||
/// If an `amount_out_min` is specified, it will return an error if it is unable to acquire
|
||||
/// the amount desired.
|
||||
///
|
||||
/// Withdraws the `path[0]` asset from `sender`, deposits the `path[last]` asset to `send_to`,
|
||||
/// respecting `keep_alive`.
|
||||
///
|
||||
/// If successful, returns the amount of `path[last]` acquired for the `amount_in`.
|
||||
///
|
||||
/// This operation is expected to be atomic.
|
||||
fn swap_exact_tokens_for_tokens(
|
||||
sender: AccountId,
|
||||
path: Vec<Self::AssetKind>,
|
||||
amount_in: Self::Balance,
|
||||
amount_out_min: Option<Self::Balance>,
|
||||
send_to: AccountId,
|
||||
keep_alive: bool,
|
||||
) -> Result<Self::Balance, DispatchError>;
|
||||
|
||||
/// Take the `path[0]` asset and swap some amount for `amount_out` of the `path[last]`. If an
|
||||
/// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be
|
||||
/// too costly.
|
||||
///
|
||||
/// Withdraws `path[0]` asset from `sender`, deposits `path[last]` asset to `send_to`,
|
||||
/// respecting `keep_alive`.
|
||||
///
|
||||
/// If successful returns the amount of the `path[0]` taken to provide `path[last]`.
|
||||
///
|
||||
/// This operation is expected to be atomic.
|
||||
fn swap_tokens_for_exact_tokens(
|
||||
sender: AccountId,
|
||||
path: Vec<Self::AssetKind>,
|
||||
amount_out: Self::Balance,
|
||||
amount_in_max: Option<Self::Balance>,
|
||||
send_to: AccountId,
|
||||
keep_alive: bool,
|
||||
) -> Result<Self::Balance, DispatchError>;
|
||||
}
|
||||
|
||||
/// Trait providing methods to swap between the various asset classes.
|
||||
pub trait SwapCredit<AccountId> {
|
||||
/// Measure units of the asset classes for swapping.
|
||||
type Balance: Balance;
|
||||
/// Kind of assets that are going to be swapped.
|
||||
type AssetKind;
|
||||
/// Credit implying a negative imbalance in the system that can be placed into an account or
|
||||
/// alter the total supply.
|
||||
type Credit;
|
||||
|
||||
/// Returns the upper limit on the length of the swap path.
|
||||
fn max_path_len() -> u32;
|
||||
|
||||
/// Swap exactly `credit_in` of asset `path[0]` for asset `path[last]`. If `amount_out_min` is
|
||||
/// provided and the swap can't achieve at least this amount, an error is returned.
|
||||
///
|
||||
/// On a successful swap, the function returns the `credit_out` of `path[last]` obtained from
|
||||
/// the `credit_in`. On failure, it returns an `Err` containing the original `credit_in` and the
|
||||
/// associated error code.
|
||||
///
|
||||
/// This operation is expected to be atomic.
|
||||
fn swap_exact_tokens_for_tokens(
|
||||
path: Vec<Self::AssetKind>,
|
||||
credit_in: Self::Credit,
|
||||
amount_out_min: Option<Self::Balance>,
|
||||
) -> Result<Self::Credit, (Self::Credit, DispatchError)>;
|
||||
|
||||
/// Swaps a portion of `credit_in` of `path[0]` asset to obtain the desired `amount_out` of
|
||||
/// the `path[last]` asset. The provided `credit_in` must be adequate to achieve the target
|
||||
/// `amount_out`, or an error will occur.
|
||||
///
|
||||
/// On success, the function returns a (`credit_out`, `credit_change`) tuple, where `credit_out`
|
||||
/// represents the acquired amount of the `path[last]` asset, and `credit_change` is the
|
||||
/// remaining portion from the `credit_in`. On failure, an `Err` with the initial `credit_in`
|
||||
/// and error code is returned.
|
||||
///
|
||||
/// This operation is expected to be atomic.
|
||||
fn swap_tokens_for_exact_tokens(
|
||||
path: Vec<Self::AssetKind>,
|
||||
credit_in: Self::Credit,
|
||||
amount_out: Self::Balance,
|
||||
) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)>;
|
||||
}
|
||||
|
||||
/// Trait providing methods to quote swap prices between asset classes.
|
||||
///
|
||||
/// The quoted price is only guaranteed if no other swaps are made after the price is quoted and
|
||||
/// before the target swap (e.g., the swap is made immediately within the same transaction).
|
||||
pub trait QuotePrice {
|
||||
/// Measurement units of the asset classes for pricing.
|
||||
type Balance: Balance;
|
||||
/// Type representing the kind of assets for which the price is being quoted.
|
||||
type AssetKind;
|
||||
/// Quotes the amount of `asset1` required to obtain the exact `amount` of `asset2`.
|
||||
///
|
||||
/// If `include_fee` is set to `true`, the price will include the pool's fee.
|
||||
/// If the pool does not exist or the swap cannot be made, `None` is returned.
|
||||
fn quote_price_tokens_for_exact_tokens(
|
||||
asset1: Self::AssetKind,
|
||||
asset2: Self::AssetKind,
|
||||
amount: Self::Balance,
|
||||
include_fee: bool,
|
||||
) -> Option<Self::Balance>;
|
||||
/// Quotes the amount of `asset2` resulting from swapping the exact `amount` of `asset1`.
|
||||
///
|
||||
/// If `include_fee` is set to `true`, the price will include the pool's fee.
|
||||
/// If the pool does not exist or the swap cannot be made, `None` is returned.
|
||||
fn quote_price_exact_tokens_for_tokens(
|
||||
asset1: Self::AssetKind,
|
||||
asset2: Self::AssetKind,
|
||||
amount: Self::Balance,
|
||||
include_fee: bool,
|
||||
) -> Option<Self::Balance>;
|
||||
}
|
||||
|
||||
impl<T: Config> Swap<T::AccountId> for Pallet<T> {
|
||||
type Balance = T::Balance;
|
||||
type AssetKind = T::AssetKind;
|
||||
|
||||
fn max_path_len() -> u32 {
|
||||
T::MaxSwapPathLength::get()
|
||||
}
|
||||
|
||||
#[transactional]
|
||||
fn swap_exact_tokens_for_tokens(
|
||||
sender: T::AccountId,
|
||||
path: Vec<Self::AssetKind>,
|
||||
amount_in: Self::Balance,
|
||||
amount_out_min: Option<Self::Balance>,
|
||||
send_to: T::AccountId,
|
||||
keep_alive: bool,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
Self::do_swap_exact_tokens_for_tokens(
|
||||
sender,
|
||||
path,
|
||||
amount_in,
|
||||
amount_out_min,
|
||||
send_to,
|
||||
keep_alive,
|
||||
)
|
||||
}
|
||||
|
||||
#[transactional]
|
||||
fn swap_tokens_for_exact_tokens(
|
||||
sender: T::AccountId,
|
||||
path: Vec<Self::AssetKind>,
|
||||
amount_out: Self::Balance,
|
||||
amount_in_max: Option<Self::Balance>,
|
||||
send_to: T::AccountId,
|
||||
keep_alive: bool,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
Self::do_swap_tokens_for_exact_tokens(
|
||||
sender,
|
||||
path,
|
||||
amount_out,
|
||||
amount_in_max,
|
||||
send_to,
|
||||
keep_alive,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> SwapCredit<T::AccountId> for Pallet<T> {
|
||||
type Balance = T::Balance;
|
||||
type AssetKind = T::AssetKind;
|
||||
type Credit = CreditOf<T>;
|
||||
|
||||
fn max_path_len() -> u32 {
|
||||
T::MaxSwapPathLength::get()
|
||||
}
|
||||
|
||||
fn swap_exact_tokens_for_tokens(
|
||||
path: Vec<Self::AssetKind>,
|
||||
credit_in: Self::Credit,
|
||||
amount_out_min: Option<Self::Balance>,
|
||||
) -> Result<Self::Credit, (Self::Credit, DispatchError)> {
|
||||
let credit_asset = credit_in.asset();
|
||||
with_transaction(|| -> TransactionOutcome<Result<_, DispatchError>> {
|
||||
let res = Self::do_swap_exact_credit_tokens_for_tokens(path, credit_in, amount_out_min);
|
||||
match &res {
|
||||
Ok(_) => TransactionOutcome::Commit(Ok(res)),
|
||||
// wrapping `res` with `Ok`, since our `Err` doesn't satisfy the
|
||||
// `From<DispatchError>` bound of the `with_transaction` function.
|
||||
Err(_) => TransactionOutcome::Rollback(Ok(res)),
|
||||
}
|
||||
})
|
||||
// should never map an error since `with_transaction` above never returns it.
|
||||
.map_err(|_| (Self::Credit::zero(credit_asset), DispatchError::Corruption))?
|
||||
}
|
||||
|
||||
fn swap_tokens_for_exact_tokens(
|
||||
path: Vec<Self::AssetKind>,
|
||||
credit_in: Self::Credit,
|
||||
amount_out: Self::Balance,
|
||||
) -> Result<(Self::Credit, Self::Credit), (Self::Credit, DispatchError)> {
|
||||
let credit_asset = credit_in.asset();
|
||||
with_transaction(|| -> TransactionOutcome<Result<_, DispatchError>> {
|
||||
let res = Self::do_swap_credit_tokens_for_exact_tokens(path, credit_in, amount_out);
|
||||
match &res {
|
||||
Ok(_) => TransactionOutcome::Commit(Ok(res)),
|
||||
// wrapping `res` with `Ok`, since our `Err` doesn't satisfy the
|
||||
// `From<DispatchError>` bound of the `with_transaction` function.
|
||||
Err(_) => TransactionOutcome::Rollback(Ok(res)),
|
||||
}
|
||||
})
|
||||
// should never map an error since `with_transaction` above never returns it.
|
||||
.map_err(|_| (Self::Credit::zero(credit_asset), DispatchError::Corruption))?
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> QuotePrice for Pallet<T> {
|
||||
type Balance = T::Balance;
|
||||
type AssetKind = T::AssetKind;
|
||||
fn quote_price_exact_tokens_for_tokens(
|
||||
asset1: Self::AssetKind,
|
||||
asset2: Self::AssetKind,
|
||||
amount: Self::Balance,
|
||||
include_fee: bool,
|
||||
) -> Option<Self::Balance> {
|
||||
Self::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee)
|
||||
}
|
||||
fn quote_price_tokens_for_exact_tokens(
|
||||
asset1: Self::AssetKind,
|
||||
asset2: Self::AssetKind,
|
||||
amount: Self::Balance,
|
||||
include_fee: bool,
|
||||
) -> Option<Self::Balance> {
|
||||
Self::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee)
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,171 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::*;
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use core::marker::PhantomData;
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::traits::TryConvert;
|
||||
|
||||
/// Represents a swap path with associated asset amounts indicating how much of the asset needs to
|
||||
/// be deposited to get the following asset's amount withdrawn (this is inclusive of fees).
|
||||
///
|
||||
/// Example:
|
||||
/// Given path [(asset1, amount_in), (asset2, amount_out2), (asset3, amount_out3)], can be resolved:
|
||||
/// 1. `asset(asset1, amount_in)` take from `user` and move to the pool(asset1, asset2);
|
||||
/// 2. `asset(asset2, amount_out2)` transfer from pool(asset1, asset2) to pool(asset2, asset3);
|
||||
/// 3. `asset(asset3, amount_out3)` move from pool(asset2, asset3) to `user`.
|
||||
pub type BalancePath<T> = Vec<(<T as Config>::AssetKind, <T as Config>::Balance)>;
|
||||
|
||||
/// Credit of [Config::Assets].
|
||||
pub type CreditOf<T> = Credit<<T as pezframe_system::Config>::AccountId, <T as Config>::Assets>;
|
||||
|
||||
/// Stores the lp_token asset id a particular pool has been assigned.
|
||||
#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)]
|
||||
pub struct PoolInfo<PoolAssetId> {
|
||||
/// Liquidity pool asset
|
||||
pub lp_token: PoolAssetId,
|
||||
}
|
||||
|
||||
/// Provides means to resolve the `PoolId` and `AccountId` from a pair of assets.
|
||||
///
|
||||
/// Resulting `PoolId` remains consistent whether the asset pair is presented as (asset1, asset2)
|
||||
/// or (asset2, asset1). The derived `AccountId` may serve as an address for liquidity provider
|
||||
/// tokens.
|
||||
pub trait PoolLocator<AccountId, AssetKind, PoolId> {
|
||||
/// Retrieves the account address associated with a valid `PoolId`.
|
||||
fn address(id: &PoolId) -> Result<AccountId, ()>;
|
||||
/// Identifies the `PoolId` for a given pair of assets.
|
||||
///
|
||||
/// Returns an error if the asset pair isn't supported.
|
||||
fn pool_id(asset1: &AssetKind, asset2: &AssetKind) -> Result<PoolId, ()>;
|
||||
/// Retrieves the account address associated with a given asset pair.
|
||||
///
|
||||
/// Returns an error if the asset pair isn't supported.
|
||||
fn pool_address(asset1: &AssetKind, asset2: &AssetKind) -> Result<AccountId, ()> {
|
||||
if let Ok(id) = Self::pool_id(asset1, asset2) {
|
||||
Self::address(&id)
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pool locator that mandates the inclusion of the specified `FirstAsset` in every asset pair.
|
||||
///
|
||||
/// The `PoolId` is represented as a tuple of `AssetKind`s with `FirstAsset` always positioned as
|
||||
/// the first element.
|
||||
pub struct WithFirstAsset<FirstAsset, AccountId, AssetKind, AccountIdConverter>(
|
||||
PhantomData<(FirstAsset, AccountId, AssetKind, AccountIdConverter)>,
|
||||
);
|
||||
impl<FirstAsset, AccountId, AssetKind, AccountIdConverter>
|
||||
PoolLocator<AccountId, AssetKind, (AssetKind, AssetKind)>
|
||||
for WithFirstAsset<FirstAsset, AccountId, AssetKind, AccountIdConverter>
|
||||
where
|
||||
AssetKind: Eq + Clone + Encode,
|
||||
AccountId: Decode,
|
||||
FirstAsset: Get<AssetKind>,
|
||||
AccountIdConverter: for<'a> TryConvert<&'a (AssetKind, AssetKind), AccountId>,
|
||||
{
|
||||
fn pool_id(asset1: &AssetKind, asset2: &AssetKind) -> Result<(AssetKind, AssetKind), ()> {
|
||||
if asset1 == asset2 {
|
||||
return Err(());
|
||||
}
|
||||
let first = FirstAsset::get();
|
||||
if first == *asset1 {
|
||||
Ok((first, asset2.clone()))
|
||||
} else if first == *asset2 {
|
||||
Ok((first, asset1.clone()))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
fn address(id: &(AssetKind, AssetKind)) -> Result<AccountId, ()> {
|
||||
AccountIdConverter::try_convert(id).map_err(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Pool locator where the `PoolId` is a tuple of `AssetKind`s arranged in ascending order.
|
||||
pub struct Ascending<AccountId, AssetKind, AccountIdConverter>(
|
||||
PhantomData<(AccountId, AssetKind, AccountIdConverter)>,
|
||||
);
|
||||
impl<AccountId, AssetKind, AccountIdConverter>
|
||||
PoolLocator<AccountId, AssetKind, (AssetKind, AssetKind)>
|
||||
for Ascending<AccountId, AssetKind, AccountIdConverter>
|
||||
where
|
||||
AssetKind: Ord + Clone + Encode,
|
||||
AccountId: Decode,
|
||||
AccountIdConverter: for<'a> TryConvert<&'a (AssetKind, AssetKind), AccountId>,
|
||||
{
|
||||
fn pool_id(asset1: &AssetKind, asset2: &AssetKind) -> Result<(AssetKind, AssetKind), ()> {
|
||||
if asset1 > asset2 {
|
||||
Ok((asset2.clone(), asset1.clone()))
|
||||
} else if asset1 < asset2 {
|
||||
Ok((asset1.clone(), asset2.clone()))
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
fn address(id: &(AssetKind, AssetKind)) -> Result<AccountId, ()> {
|
||||
AccountIdConverter::try_convert(id).map_err(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
/// Pool locator that chains the `First` and `Second` implementations of [`PoolLocator`].
|
||||
///
|
||||
/// If the `First` implementation fails, it falls back to the `Second`.
|
||||
pub struct Chain<First, Second>(PhantomData<(First, Second)>);
|
||||
impl<First, Second, AccountId, AssetKind> PoolLocator<AccountId, AssetKind, (AssetKind, AssetKind)>
|
||||
for Chain<First, Second>
|
||||
where
|
||||
First: PoolLocator<AccountId, AssetKind, (AssetKind, AssetKind)>,
|
||||
Second: PoolLocator<AccountId, AssetKind, (AssetKind, AssetKind)>,
|
||||
{
|
||||
fn pool_id(asset1: &AssetKind, asset2: &AssetKind) -> Result<(AssetKind, AssetKind), ()> {
|
||||
First::pool_id(asset1, asset2).or(Second::pool_id(asset1, asset2))
|
||||
}
|
||||
fn address(id: &(AssetKind, AssetKind)) -> Result<AccountId, ()> {
|
||||
First::address(id).or(Second::address(id))
|
||||
}
|
||||
}
|
||||
|
||||
/// `PoolId` to `AccountId` conversion.
|
||||
pub struct AccountIdConverter<Seed, PoolId>(PhantomData<(Seed, PoolId)>);
|
||||
impl<Seed, PoolId, AccountId> TryConvert<&PoolId, AccountId> for AccountIdConverter<Seed, PoolId>
|
||||
where
|
||||
PoolId: Encode,
|
||||
AccountId: Decode,
|
||||
Seed: Get<PalletId>,
|
||||
{
|
||||
fn try_convert(id: &PoolId) -> Result<AccountId, &PoolId> {
|
||||
pezsp_io::hashing::blake2_256(&Encode::encode(&(Seed::get(), id))[..])
|
||||
.using_encoded(|e| Decode::decode(&mut TrailingZeroInput::new(e)).map_err(|_| id))
|
||||
}
|
||||
}
|
||||
|
||||
/// `PoolId` to `AccountId` conversion without an addition arguments to the seed.
|
||||
pub struct AccountIdConverterNoSeed<PoolId>(PhantomData<PoolId>);
|
||||
impl<PoolId, AccountId> TryConvert<&PoolId, AccountId> for AccountIdConverterNoSeed<PoolId>
|
||||
where
|
||||
PoolId: Encode,
|
||||
AccountId: Decode,
|
||||
{
|
||||
fn try_convert(id: &PoolId) -> Result<AccountId, &PoolId> {
|
||||
pezsp_io::hashing::blake2_256(&Encode::encode(id)[..])
|
||||
.using_encoded(|e| Decode::decode(&mut TrailingZeroInput::new(e)).map_err(|_| id))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Autogenerated weights for `pezpallet_asset_conversion`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// frame-omni-bencher
|
||||
// v1
|
||||
// benchmark
|
||||
// pallet
|
||||
// --extrinsic=*
|
||||
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
|
||||
// --pallet=pezpallet_asset_conversion
|
||||
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/HEADER-APACHE2
|
||||
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/pezframe/asset-conversion/src/weights.rs
|
||||
// --wasm-execution=compiled
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --heap-pages=4096
|
||||
// --template=bizinikiwi/.maintain/frame-weight-template.hbs
|
||||
// --no-storage-info
|
||||
// --no-min-squares
|
||||
// --no-median-slopes
|
||||
// --genesis-builder-policy=none
|
||||
// --exclude-pallets=pezpallet_xcm,pezpallet_xcm_benchmarks::fungible,pezpallet_xcm_benchmarks::generic,pezpallet_nomination_pools,pezpallet_remark,pezpallet_transaction_storage,pezpallet_election_provider_multi_block,pezpallet_election_provider_multi_block::signed,pezpallet_election_provider_multi_block::unsigned,pezpallet_election_provider_multi_block::verifier
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for `pezpallet_asset_conversion`.
|
||||
pub trait WeightInfo {
|
||||
fn create_pool() -> Weight;
|
||||
fn add_liquidity() -> Weight;
|
||||
fn remove_liquidity() -> Weight;
|
||||
fn swap_exact_tokens_for_tokens(n: u32, ) -> Weight;
|
||||
fn swap_tokens_for_exact_tokens(n: u32, ) -> Weight;
|
||||
fn touch(n: u32, ) -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_asset_conversion` using the Bizinikiwi node and recommended hardware.
|
||||
pub struct BizinikiwiWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// Storage: `AssetConversion::Pools` (r:1 w:1)
|
||||
/// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:2 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:2 w:0)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetConversion::NextPoolAssetId` (r:1 w:1)
|
||||
/// Proof: `AssetConversion::NextPoolAssetId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Asset` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::NextAssetId` (r:1 w:0)
|
||||
/// Proof: `PoolAssets::NextAssetId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Account` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
fn create_pool() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `476`
|
||||
// Estimated: `6360`
|
||||
// Minimum execution time: 81_898_000 picoseconds.
|
||||
Weight::from_parts(83_910_000, 6360)
|
||||
.saturating_add(T::DbWeight::get().reads(9_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(5_u64))
|
||||
}
|
||||
/// Storage: `AssetConversion::Pools` (r:1 w:0)
|
||||
/// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:2 w:2)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:4 w:4)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Asset` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Account` (r:2 w:2)
|
||||
/// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
fn add_liquidity() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1090`
|
||||
// Estimated: `11426`
|
||||
// Minimum execution time: 138_751_000 picoseconds.
|
||||
Weight::from_parts(141_390_000, 11426)
|
||||
.saturating_add(T::DbWeight::get().reads(11_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(10_u64))
|
||||
}
|
||||
/// Storage: `AssetConversion::Pools` (r:1 w:0)
|
||||
/// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:2 w:2)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:4 w:4)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Asset` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Account` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
fn remove_liquidity() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1233`
|
||||
// Estimated: `11426`
|
||||
// Minimum execution time: 124_722_000 picoseconds.
|
||||
Weight::from_parts(128_644_000, 11426)
|
||||
.saturating_add(T::DbWeight::get().reads(9_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(8_u64))
|
||||
}
|
||||
/// Storage: `Assets::Asset` (r:4 w:4)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:8 w:8)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// The range of component `n` is `[2, 4]`.
|
||||
fn swap_exact_tokens_for_tokens(n: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0 + n * (419 ±0)`
|
||||
// Estimated: `990 + n * (5218 ±0)`
|
||||
// Minimum execution time: 88_884_000 picoseconds.
|
||||
Weight::from_parts(91_036_000, 990)
|
||||
// Standard Error: 337_841
|
||||
.saturating_add(Weight::from_parts(11_478_919, 0).saturating_mul(n.into()))
|
||||
.saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into())))
|
||||
.saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into())))
|
||||
.saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into()))
|
||||
}
|
||||
/// Storage: `Assets::Asset` (r:4 w:4)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:8 w:8)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// The range of component `n` is `[2, 4]`.
|
||||
fn swap_tokens_for_exact_tokens(n: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0 + n * (419 ±0)`
|
||||
// Estimated: `990 + n * (5218 ±0)`
|
||||
// Minimum execution time: 89_080_000 picoseconds.
|
||||
Weight::from_parts(90_913_000, 990)
|
||||
// Standard Error: 340_609
|
||||
.saturating_add(Weight::from_parts(11_562_623, 0).saturating_mul(n.into()))
|
||||
.saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into())))
|
||||
.saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into())))
|
||||
.saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into()))
|
||||
}
|
||||
/// Storage: `AssetConversion::Pools` (r:1 w:0)
|
||||
/// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:2 w:2)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:0)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:2 w:2)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Asset` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Account` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// The range of component `n` is `[0, 3]`.
|
||||
fn touch(n: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1154`
|
||||
// Estimated: `6360`
|
||||
// Minimum execution time: 43_815_000 picoseconds.
|
||||
Weight::from_parts(46_005_208, 6360)
|
||||
// Standard Error: 68_937
|
||||
.saturating_add(Weight::from_parts(19_974_807, 0).saturating_mul(n.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(8_u64))
|
||||
.saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into())))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `AssetConversion::Pools` (r:1 w:1)
|
||||
/// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:2 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:2 w:0)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetConversion::NextPoolAssetId` (r:1 w:1)
|
||||
/// Proof: `AssetConversion::NextPoolAssetId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Asset` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::NextAssetId` (r:1 w:0)
|
||||
/// Proof: `PoolAssets::NextAssetId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Account` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
fn create_pool() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `476`
|
||||
// Estimated: `6360`
|
||||
// Minimum execution time: 81_898_000 picoseconds.
|
||||
Weight::from_parts(83_910_000, 6360)
|
||||
.saturating_add(RocksDbWeight::get().reads(9_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(5_u64))
|
||||
}
|
||||
/// Storage: `AssetConversion::Pools` (r:1 w:0)
|
||||
/// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:2 w:2)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:4 w:4)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Asset` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Account` (r:2 w:2)
|
||||
/// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
fn add_liquidity() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1090`
|
||||
// Estimated: `11426`
|
||||
// Minimum execution time: 138_751_000 picoseconds.
|
||||
Weight::from_parts(141_390_000, 11426)
|
||||
.saturating_add(RocksDbWeight::get().reads(11_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(10_u64))
|
||||
}
|
||||
/// Storage: `AssetConversion::Pools` (r:1 w:0)
|
||||
/// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:2 w:2)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:4 w:4)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Asset` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Account` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
fn remove_liquidity() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1233`
|
||||
// Estimated: `11426`
|
||||
// Minimum execution time: 124_722_000 picoseconds.
|
||||
Weight::from_parts(128_644_000, 11426)
|
||||
.saturating_add(RocksDbWeight::get().reads(9_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(8_u64))
|
||||
}
|
||||
/// Storage: `Assets::Asset` (r:4 w:4)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:8 w:8)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// The range of component `n` is `[2, 4]`.
|
||||
fn swap_exact_tokens_for_tokens(n: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0 + n * (419 ±0)`
|
||||
// Estimated: `990 + n * (5218 ±0)`
|
||||
// Minimum execution time: 88_884_000 picoseconds.
|
||||
Weight::from_parts(91_036_000, 990)
|
||||
// Standard Error: 337_841
|
||||
.saturating_add(Weight::from_parts(11_478_919, 0).saturating_mul(n.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into())))
|
||||
.saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into())))
|
||||
.saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into()))
|
||||
}
|
||||
/// Storage: `Assets::Asset` (r:4 w:4)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:8 w:8)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// The range of component `n` is `[2, 4]`.
|
||||
fn swap_tokens_for_exact_tokens(n: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0 + n * (419 ±0)`
|
||||
// Estimated: `990 + n * (5218 ±0)`
|
||||
// Minimum execution time: 89_080_000 picoseconds.
|
||||
Weight::from_parts(90_913_000, 990)
|
||||
// Standard Error: 340_609
|
||||
.saturating_add(Weight::from_parts(11_562_623, 0).saturating_mul(n.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into())))
|
||||
.saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into())))
|
||||
.saturating_add(Weight::from_parts(0, 5218).saturating_mul(n.into()))
|
||||
}
|
||||
/// Storage: `AssetConversion::Pools` (r:1 w:0)
|
||||
/// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:2 w:2)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:0)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:2 w:2)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Asset` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `PoolAssets::Account` (r:1 w:1)
|
||||
/// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// The range of component `n` is `[0, 3]`.
|
||||
fn touch(n: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `1154`
|
||||
// Estimated: `6360`
|
||||
// Minimum execution time: 43_815_000 picoseconds.
|
||||
Weight::from_parts(46_005_208, 6360)
|
||||
// Standard Error: 68_937
|
||||
.saturating_add(Weight::from_parts(19_974_807, 0).saturating_mul(n.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(8_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(n.into())))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
[package]
|
||||
name = "pezpallet-asset-rate"
|
||||
version = "7.0.0"
|
||||
description = "Whitelist non-native assets for treasury spending and provide conversion to native balance"
|
||||
authors.workspace = true
|
||||
homepage.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
repository.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
pezsp-core = { optional = true, workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-balances/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core?/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezsp-core",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,101 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The crate's benchmarks.
|
||||
|
||||
use super::*;
|
||||
use crate::{pallet as pezpallet_asset_rate, Pallet as AssetRate};
|
||||
|
||||
use codec::Encode;
|
||||
use pezframe_benchmarking::v2::*;
|
||||
use pezframe_support::assert_ok;
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_core::crypto::FromEntropy;
|
||||
|
||||
/// Trait describing the factory function for the `AssetKind` parameter.
|
||||
pub trait AssetKindFactory<AssetKind> {
|
||||
fn create_asset_kind(seed: u32) -> AssetKind;
|
||||
}
|
||||
impl<AssetKind> AssetKindFactory<AssetKind> for ()
|
||||
where
|
||||
AssetKind: FromEntropy,
|
||||
{
|
||||
fn create_asset_kind(seed: u32) -> AssetKind {
|
||||
AssetKind::from_entropy(&mut seed.encode().as_slice()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
const SEED: u32 = 1;
|
||||
|
||||
fn default_conversion_rate() -> FixedU128 {
|
||||
FixedU128::from_u32(1u32)
|
||||
}
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn create() -> Result<(), BenchmarkError> {
|
||||
let asset_kind: T::AssetKind = T::BenchmarkHelper::create_asset_kind(SEED);
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, Box::new(asset_kind.clone()), default_conversion_rate());
|
||||
|
||||
assert_eq!(
|
||||
pezpallet_asset_rate::ConversionRateToNative::<T>::get(asset_kind),
|
||||
Some(default_conversion_rate())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn update() -> Result<(), BenchmarkError> {
|
||||
let asset_kind: T::AssetKind = T::BenchmarkHelper::create_asset_kind(SEED);
|
||||
assert_ok!(AssetRate::<T>::create(
|
||||
RawOrigin::Root.into(),
|
||||
Box::new(asset_kind.clone()),
|
||||
default_conversion_rate()
|
||||
));
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, Box::new(asset_kind.clone()), FixedU128::from_u32(2));
|
||||
|
||||
assert_eq!(
|
||||
pezpallet_asset_rate::ConversionRateToNative::<T>::get(asset_kind),
|
||||
Some(FixedU128::from_u32(2))
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn remove() -> Result<(), BenchmarkError> {
|
||||
let asset_kind: T::AssetKind = T::BenchmarkHelper::create_asset_kind(SEED);
|
||||
assert_ok!(AssetRate::<T>::create(
|
||||
RawOrigin::Root.into(),
|
||||
Box::new(asset_kind.clone()),
|
||||
default_conversion_rate()
|
||||
));
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Root, Box::new(asset_kind.clone()));
|
||||
|
||||
assert!(pezpallet_asset_rate::ConversionRateToNative::<T>::get(asset_kind).is_none());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite! { AssetRate, crate::mock::new_test_ext(), crate::mock::Test }
|
||||
}
|
||||
@@ -0,0 +1,281 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! # Asset Rate Pallet
|
||||
//!
|
||||
//! - [`Config`]
|
||||
//! - [`Call`]
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! The AssetRate pallet provides means of setting conversion rates for some asset to native
|
||||
//! balance.
|
||||
//!
|
||||
//! The supported dispatchable functions are documented in the [`Call`] enum.
|
||||
//!
|
||||
//! ### Terminology
|
||||
//!
|
||||
//! * **Asset balance**: The balance type of an arbitrary asset. The network might only know about
|
||||
//! the identifier of the asset and nothing more.
|
||||
//! * **Native balance**: The balance type of the network's native currency.
|
||||
//!
|
||||
//! ### Goals
|
||||
//!
|
||||
//! The asset-rate system in Bizinikiwi is designed to make the following possible:
|
||||
//!
|
||||
//! * Providing a soft conversion for the balance of supported assets to a default asset class.
|
||||
//! * Updating existing conversion rates.
|
||||
//!
|
||||
//! ## Interface
|
||||
//!
|
||||
//! ### Permissioned Functions
|
||||
//!
|
||||
//! * `create`: Creates a new asset conversion rate.
|
||||
//! * `remove`: Removes an existing asset conversion rate.
|
||||
//! * `update`: Overwrites an existing assert conversion rate.
|
||||
//!
|
||||
//! Please refer to the [`Call`] enum and its associated variants for documentation on each
|
||||
//! function.
|
||||
//!
|
||||
//! ### Assumptions
|
||||
//!
|
||||
//! * Conversion rates are only used as estimates, and are not designed to be precise or closely
|
||||
//! tracking real world values.
|
||||
//! * All conversion rates reflect the ration of some asset to native, e.g. native = asset * rate.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use pezframe_support::traits::{
|
||||
fungible::Inspect,
|
||||
tokens::{ConversionFromAssetBalance, ConversionToAssetBalance},
|
||||
};
|
||||
use pezsp_runtime::{
|
||||
traits::{CheckedDiv, Zero},
|
||||
FixedPointNumber, FixedU128,
|
||||
};
|
||||
|
||||
pub use pallet::*;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
mod benchmarking;
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub mod weights;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub use benchmarking::AssetKindFactory;
|
||||
|
||||
// Type alias for `pezframe_system`'s account id.
|
||||
type AccountIdOf<T> = <T as pezframe_system::Config>::AccountId;
|
||||
// This pallet's asset kind and balance type.
|
||||
type AssetKindOf<T> = <T as Config>::AssetKind;
|
||||
// Generic fungible balance type.
|
||||
type BalanceOf<T> = <<T as Config>::Currency as Inspect<AccountIdOf<T>>>::Balance;
|
||||
|
||||
#[pezframe_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
use pezframe_system::pezpallet_prelude::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: pezframe_system::Config {
|
||||
/// The Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
|
||||
/// The runtime event type.
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
|
||||
/// The origin permissioned to create a conversion rate for an asset.
|
||||
type CreateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// The origin permissioned to remove an existing conversion rate for an asset.
|
||||
type RemoveOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// The origin permissioned to update an existing conversion rate for an asset.
|
||||
type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// The currency mechanism for this pallet.
|
||||
type Currency: Inspect<Self::AccountId>;
|
||||
|
||||
/// The type for asset kinds for which the conversion rate to native balance is set.
|
||||
type AssetKind: Parameter + MaxEncodedLen;
|
||||
|
||||
/// Helper type for benchmarks.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper: crate::AssetKindFactory<Self::AssetKind>;
|
||||
}
|
||||
|
||||
/// Maps an asset to its fixed point representation in the native balance.
|
||||
///
|
||||
/// E.g. `native_amount = asset_amount * ConversionRateToNative::<T>::get(asset_kind)`
|
||||
#[pallet::storage]
|
||||
pub type ConversionRateToNative<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, T::AssetKind, FixedU128, OptionQuery>;
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
// Some `asset_kind` conversion rate was created.
|
||||
AssetRateCreated { asset_kind: T::AssetKind, rate: FixedU128 },
|
||||
// Some `asset_kind` conversion rate was removed.
|
||||
AssetRateRemoved { asset_kind: T::AssetKind },
|
||||
// Some existing `asset_kind` conversion rate was updated from `old` to `new`.
|
||||
AssetRateUpdated { asset_kind: T::AssetKind, old: FixedU128, new: FixedU128 },
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// The given asset ID is unknown.
|
||||
UnknownAssetKind,
|
||||
/// The given asset ID already has an assigned conversion rate and cannot be re-created.
|
||||
AlreadyExists,
|
||||
/// Overflow ocurred when calculating the inverse rate.
|
||||
Overflow,
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Initialize a conversion rate to native balance for the given asset.
|
||||
///
|
||||
/// ## Complexity
|
||||
/// - O(1)
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(T::WeightInfo::create())]
|
||||
pub fn create(
|
||||
origin: OriginFor<T>,
|
||||
asset_kind: Box<T::AssetKind>,
|
||||
rate: FixedU128,
|
||||
) -> DispatchResult {
|
||||
T::CreateOrigin::ensure_origin(origin)?;
|
||||
|
||||
ensure!(
|
||||
!ConversionRateToNative::<T>::contains_key(asset_kind.as_ref()),
|
||||
Error::<T>::AlreadyExists
|
||||
);
|
||||
ConversionRateToNative::<T>::set(asset_kind.as_ref(), Some(rate));
|
||||
|
||||
Self::deposit_event(Event::AssetRateCreated { asset_kind: *asset_kind, rate });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update the conversion rate to native balance for the given asset.
|
||||
///
|
||||
/// ## Complexity
|
||||
/// - O(1)
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight(T::WeightInfo::update())]
|
||||
pub fn update(
|
||||
origin: OriginFor<T>,
|
||||
asset_kind: Box<T::AssetKind>,
|
||||
rate: FixedU128,
|
||||
) -> DispatchResult {
|
||||
T::UpdateOrigin::ensure_origin(origin)?;
|
||||
|
||||
let mut old = FixedU128::zero();
|
||||
ConversionRateToNative::<T>::mutate(asset_kind.as_ref(), |maybe_rate| {
|
||||
if let Some(r) = maybe_rate {
|
||||
old = *r;
|
||||
*r = rate;
|
||||
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::<T>::UnknownAssetKind)
|
||||
}
|
||||
})?;
|
||||
|
||||
Self::deposit_event(Event::AssetRateUpdated {
|
||||
asset_kind: *asset_kind,
|
||||
old,
|
||||
new: rate,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove an existing conversion rate to native balance for the given asset.
|
||||
///
|
||||
/// ## Complexity
|
||||
/// - O(1)
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(T::WeightInfo::remove())]
|
||||
pub fn remove(origin: OriginFor<T>, asset_kind: Box<T::AssetKind>) -> DispatchResult {
|
||||
T::RemoveOrigin::ensure_origin(origin)?;
|
||||
|
||||
ensure!(
|
||||
ConversionRateToNative::<T>::contains_key(asset_kind.as_ref()),
|
||||
Error::<T>::UnknownAssetKind
|
||||
);
|
||||
ConversionRateToNative::<T>::remove(asset_kind.as_ref());
|
||||
|
||||
Self::deposit_event(Event::AssetRateRemoved { asset_kind: *asset_kind });
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Exposes conversion of an arbitrary balance of an asset to native balance.
|
||||
impl<T> ConversionFromAssetBalance<BalanceOf<T>, AssetKindOf<T>, BalanceOf<T>> for Pallet<T>
|
||||
where
|
||||
T: Config,
|
||||
{
|
||||
type Error = pallet::Error<T>;
|
||||
|
||||
fn from_asset_balance(
|
||||
balance: BalanceOf<T>,
|
||||
asset_kind: AssetKindOf<T>,
|
||||
) -> Result<BalanceOf<T>, pallet::Error<T>> {
|
||||
let rate = pallet::ConversionRateToNative::<T>::get(asset_kind)
|
||||
.ok_or(pallet::Error::<T>::UnknownAssetKind.into())?;
|
||||
Ok(rate.saturating_mul_int(balance))
|
||||
}
|
||||
/// Set a conversion rate to `1` for the `asset_id`.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn ensure_successful(asset_id: AssetKindOf<T>) {
|
||||
pallet::ConversionRateToNative::<T>::set(asset_id.clone(), Some(1.into()));
|
||||
}
|
||||
}
|
||||
|
||||
/// Exposes conversion of a native balance to an asset balance.
|
||||
impl<T> ConversionToAssetBalance<BalanceOf<T>, AssetKindOf<T>, BalanceOf<T>> for Pallet<T>
|
||||
where
|
||||
T: Config,
|
||||
{
|
||||
type Error = pallet::Error<T>;
|
||||
|
||||
fn to_asset_balance(
|
||||
balance: BalanceOf<T>,
|
||||
asset_kind: AssetKindOf<T>,
|
||||
) -> Result<BalanceOf<T>, pallet::Error<T>> {
|
||||
let rate = pallet::ConversionRateToNative::<T>::get(asset_kind)
|
||||
.ok_or(pallet::Error::<T>::UnknownAssetKind.into())?;
|
||||
|
||||
// We cannot use `saturating_div` here so we use `checked_div`.
|
||||
Ok(FixedU128::from_u32(1)
|
||||
.checked_div(&rate)
|
||||
.ok_or(pallet::Error::<T>::Overflow.into())?
|
||||
.saturating_mul_int(balance))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The crate's mock.
|
||||
|
||||
use crate as pezpallet_asset_rate;
|
||||
use pezframe_support::derive_impl;
|
||||
use pezsp_runtime::BuildStorage;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
pezframe_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
AssetRate: pezpallet_asset_rate,
|
||||
Balances: pezpallet_balances,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u64>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
impl pezpallet_asset_rate::Config for Test {
|
||||
type WeightInfo = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type CreateOrigin = pezframe_system::EnsureRoot<u64>;
|
||||
type RemoveOrigin = pezframe_system::EnsureRoot<u64>;
|
||||
type UpdateOrigin = pezframe_system::EnsureRoot<u64>;
|
||||
type Currency = Balances;
|
||||
type AssetKind = u32;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
||||
// Build genesis storage according to the mock runtime.
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap().into()
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! The crate's tests.
|
||||
|
||||
use super::*;
|
||||
use crate::pallet as pezpallet_asset_rate;
|
||||
use pezframe_support::{assert_noop, assert_ok};
|
||||
use mock::{new_test_ext, AssetRate, RuntimeOrigin, Test};
|
||||
use pezsp_runtime::FixedU128;
|
||||
|
||||
const ASSET_ID: u32 = 42;
|
||||
|
||||
#[test]
|
||||
fn create_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert!(pezpallet_asset_rate::ConversionRateToNative::<Test>::get(ASSET_ID).is_none());
|
||||
assert_ok!(AssetRate::create(
|
||||
RuntimeOrigin::root(),
|
||||
Box::new(ASSET_ID),
|
||||
FixedU128::from_float(0.1)
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
pezpallet_asset_rate::ConversionRateToNative::<Test>::get(ASSET_ID),
|
||||
Some(FixedU128::from_float(0.1))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_existing_throws() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert!(pezpallet_asset_rate::ConversionRateToNative::<Test>::get(ASSET_ID).is_none());
|
||||
assert_ok!(AssetRate::create(
|
||||
RuntimeOrigin::root(),
|
||||
Box::new(ASSET_ID),
|
||||
FixedU128::from_float(0.1)
|
||||
));
|
||||
|
||||
assert_noop!(
|
||||
AssetRate::create(
|
||||
RuntimeOrigin::root(),
|
||||
Box::new(ASSET_ID),
|
||||
FixedU128::from_float(0.1)
|
||||
),
|
||||
Error::<Test>::AlreadyExists
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(AssetRate::create(
|
||||
RuntimeOrigin::root(),
|
||||
Box::new(ASSET_ID),
|
||||
FixedU128::from_float(0.1)
|
||||
));
|
||||
|
||||
assert_ok!(AssetRate::remove(RuntimeOrigin::root(), Box::new(ASSET_ID),));
|
||||
assert!(pezpallet_asset_rate::ConversionRateToNative::<Test>::get(ASSET_ID).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_unknown_throws() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(
|
||||
AssetRate::remove(RuntimeOrigin::root(), Box::new(ASSET_ID),),
|
||||
Error::<Test>::UnknownAssetKind
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(AssetRate::create(
|
||||
RuntimeOrigin::root(),
|
||||
Box::new(ASSET_ID),
|
||||
FixedU128::from_float(0.1)
|
||||
));
|
||||
assert_ok!(AssetRate::update(
|
||||
RuntimeOrigin::root(),
|
||||
Box::new(ASSET_ID),
|
||||
FixedU128::from_float(0.5)
|
||||
));
|
||||
|
||||
assert_eq!(
|
||||
pezpallet_asset_rate::ConversionRateToNative::<Test>::get(ASSET_ID),
|
||||
Some(FixedU128::from_float(0.5))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_unknown_throws() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(
|
||||
AssetRate::update(
|
||||
RuntimeOrigin::root(),
|
||||
Box::new(ASSET_ID),
|
||||
FixedU128::from_float(0.5)
|
||||
),
|
||||
Error::<Test>::UnknownAssetKind
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(AssetRate::create(
|
||||
RuntimeOrigin::root(),
|
||||
Box::new(ASSET_ID),
|
||||
FixedU128::from_float(2.51)
|
||||
));
|
||||
|
||||
let conversion_from_asset = <AssetRate as ConversionFromAssetBalance<
|
||||
BalanceOf<Test>,
|
||||
<Test as pezpallet_asset_rate::Config>::AssetKind,
|
||||
BalanceOf<Test>,
|
||||
>>::from_asset_balance(10, ASSET_ID);
|
||||
assert_eq!(conversion_from_asset.expect("Conversion rate exists for asset"), 25);
|
||||
|
||||
let conversion_to_asset = <AssetRate as ConversionToAssetBalance<
|
||||
BalanceOf<Test>,
|
||||
<Test as pezpallet_asset_rate::Config>::AssetKind,
|
||||
BalanceOf<Test>,
|
||||
>>::to_asset_balance(25, ASSET_ID);
|
||||
assert_eq!(conversion_to_asset.expect("Conversion rate exists for asset"), 9);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_unknown_throws() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let conversion = <AssetRate as ConversionFromAssetBalance<
|
||||
BalanceOf<Test>,
|
||||
<Test as pezpallet_asset_rate::Config>::AssetKind,
|
||||
BalanceOf<Test>,
|
||||
>>::from_asset_balance(10, ASSET_ID);
|
||||
assert!(conversion.is_err());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_overflow_throws() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(AssetRate::create(
|
||||
RuntimeOrigin::root(),
|
||||
Box::new(ASSET_ID),
|
||||
FixedU128::from_u32(0)
|
||||
));
|
||||
|
||||
let conversion = <AssetRate as ConversionToAssetBalance<
|
||||
BalanceOf<Test>,
|
||||
<Test as pezpallet_asset_rate::Config>::AssetKind,
|
||||
BalanceOf<Test>,
|
||||
>>::to_asset_balance(10, ASSET_ID);
|
||||
assert!(conversion.is_err());
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Autogenerated weights for `pezpallet_asset_rate`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// frame-omni-bencher
|
||||
// v1
|
||||
// benchmark
|
||||
// pallet
|
||||
// --extrinsic=*
|
||||
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
|
||||
// --pallet=pezpallet_asset_rate
|
||||
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/HEADER-APACHE2
|
||||
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/pezframe/asset-rate/src/weights.rs
|
||||
// --wasm-execution=compiled
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --heap-pages=4096
|
||||
// --template=bizinikiwi/.maintain/frame-weight-template.hbs
|
||||
// --no-storage-info
|
||||
// --no-min-squares
|
||||
// --no-median-slopes
|
||||
// --genesis-builder-policy=none
|
||||
// --exclude-pallets=pezpallet_xcm,pezpallet_xcm_benchmarks::fungible,pezpallet_xcm_benchmarks::generic,pezpallet_nomination_pools,pezpallet_remark,pezpallet_transaction_storage,pezpallet_election_provider_multi_block,pezpallet_election_provider_multi_block::signed,pezpallet_election_provider_multi_block::unsigned,pezpallet_election_provider_multi_block::verifier
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for `pezpallet_asset_rate`.
|
||||
pub trait WeightInfo {
|
||||
fn create() -> Weight;
|
||||
fn update() -> Weight;
|
||||
fn remove() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_asset_rate` using the Bizinikiwi node and recommended hardware.
|
||||
pub struct BizinikiwiWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// Storage: `AssetRate::ConversionRateToNative` (r:1 w:1)
|
||||
/// Proof: `AssetRate::ConversionRateToNative` (`max_values`: None, `max_size`: Some(37), added: 2512, mode: `MaxEncodedLen`)
|
||||
fn create() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `3502`
|
||||
// Minimum execution time: 6_788_000 picoseconds.
|
||||
Weight::from_parts(7_122_000, 3502)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `AssetRate::ConversionRateToNative` (r:1 w:1)
|
||||
/// Proof: `AssetRate::ConversionRateToNative` (`max_values`: None, `max_size`: Some(37), added: 2512, mode: `MaxEncodedLen`)
|
||||
fn update() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `38`
|
||||
// Estimated: `3502`
|
||||
// Minimum execution time: 7_787_000 picoseconds.
|
||||
Weight::from_parts(8_059_000, 3502)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `AssetRate::ConversionRateToNative` (r:1 w:1)
|
||||
/// Proof: `AssetRate::ConversionRateToNative` (`max_values`: None, `max_size`: Some(37), added: 2512, mode: `MaxEncodedLen`)
|
||||
fn remove() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `38`
|
||||
// Estimated: `3502`
|
||||
// Minimum execution time: 8_184_000 picoseconds.
|
||||
Weight::from_parts(8_486_000, 3502)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `AssetRate::ConversionRateToNative` (r:1 w:1)
|
||||
/// Proof: `AssetRate::ConversionRateToNative` (`max_values`: None, `max_size`: Some(37), added: 2512, mode: `MaxEncodedLen`)
|
||||
fn create() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `3502`
|
||||
// Minimum execution time: 6_788_000 picoseconds.
|
||||
Weight::from_parts(7_122_000, 3502)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `AssetRate::ConversionRateToNative` (r:1 w:1)
|
||||
/// Proof: `AssetRate::ConversionRateToNative` (`max_values`: None, `max_size`: Some(37), added: 2512, mode: `MaxEncodedLen`)
|
||||
fn update() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `38`
|
||||
// Estimated: `3502`
|
||||
// Minimum execution time: 7_787_000 picoseconds.
|
||||
Weight::from_parts(8_059_000, 3502)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `AssetRate::ConversionRateToNative` (r:1 w:1)
|
||||
/// Proof: `AssetRate::ConversionRateToNative` (`max_values`: None, `max_size`: Some(37), added: 2512, mode: `MaxEncodedLen`)
|
||||
fn remove() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `38`
|
||||
// Estimated: `3502`
|
||||
// Minimum execution time: 8_184_000 picoseconds.
|
||||
Weight::from_parts(8_486_000, 3502)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
[package]
|
||||
name = "pezpallet-asset-rewards"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME asset rewards pallet"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
pezframe-benchmarking = { workspace = true, optional = true }
|
||||
pezframe-support = { workspace = true, features = ["experimental"] }
|
||||
pezframe-system = { workspace = true }
|
||||
scale-info = { workspace = true, features = ["derive"] }
|
||||
pezsp-api = { workspace = true }
|
||||
pezsp-arithmetic = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-std = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-assets = { workspace = true }
|
||||
pezpallet-assets-freezer = { workspace = true }
|
||||
pezpallet-balances = { workspace = true }
|
||||
primitive-types = { workspace = true, features = [
|
||||
"codec",
|
||||
"num-traits",
|
||||
"scale-info",
|
||||
] }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-assets-freezer/std",
|
||||
"pezpallet-assets/std",
|
||||
"pezpallet-balances/std",
|
||||
"primitive-types/std",
|
||||
"scale-info/std",
|
||||
"pezsp-api/std",
|
||||
"pezsp-arithmetic/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-assets-freezer/runtime-benchmarks",
|
||||
"pezpallet-assets/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezsp-api/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-assets-freezer/try-runtime",
|
||||
"pezpallet-assets/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,359 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Asset Rewards pallet benchmarking.
|
||||
|
||||
use super::*;
|
||||
use crate::Pallet as AssetRewards;
|
||||
use pezframe_benchmarking::{v2::*, whitelisted_caller, BenchmarkError};
|
||||
use pezframe_support::{
|
||||
assert_ok,
|
||||
traits::{
|
||||
fungibles::{Create, Inspect, Mutate},
|
||||
Consideration, EnsureOrigin, Footprint,
|
||||
},
|
||||
};
|
||||
use pezframe_system::{Pallet as System, RawOrigin};
|
||||
use pezsp_runtime::{traits::One, Saturating};
|
||||
use pezsp_std::prelude::*;
|
||||
|
||||
/// Benchmark Helper
|
||||
pub trait BenchmarkHelper<AssetId> {
|
||||
/// Returns the staked asset id.
|
||||
///
|
||||
/// If the asset does not exist, it will be created by the benchmark.
|
||||
fn staked_asset() -> AssetId;
|
||||
/// Returns the reward asset id.
|
||||
///
|
||||
/// If the asset does not exist, it will be created by the benchmark.
|
||||
fn reward_asset() -> AssetId;
|
||||
}
|
||||
|
||||
fn pool_expire<T: Config>() -> DispatchTime<BlockNumberFor<T>> {
|
||||
DispatchTime::At(BlockNumberFor::<T>::from(100u32))
|
||||
}
|
||||
|
||||
fn create_reward_pool<T: Config>() -> Result<T::RuntimeOrigin, BenchmarkError>
|
||||
where
|
||||
T::Assets: Create<T::AccountId> + Mutate<T::AccountId>,
|
||||
{
|
||||
let caller_origin =
|
||||
T::CreatePoolOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
let caller = T::CreatePoolOrigin::ensure_origin(caller_origin.clone()).unwrap();
|
||||
|
||||
let footprint = Footprint::from_mel::<(PoolId, PoolInfoFor<T>)>();
|
||||
T::Consideration::ensure_successful(&caller, footprint);
|
||||
|
||||
let staked_asset = T::BenchmarkHelper::staked_asset();
|
||||
let reward_asset = T::BenchmarkHelper::reward_asset();
|
||||
|
||||
let min_staked_balance =
|
||||
T::Assets::minimum_balance(staked_asset.clone()).max(T::Balance::one());
|
||||
if !T::Assets::asset_exists(staked_asset.clone()) {
|
||||
assert_ok!(T::Assets::create(
|
||||
staked_asset.clone(),
|
||||
caller.clone(),
|
||||
true,
|
||||
min_staked_balance
|
||||
));
|
||||
}
|
||||
let min_reward_balance =
|
||||
T::Assets::minimum_balance(reward_asset.clone()).max(T::Balance::one());
|
||||
if !T::Assets::asset_exists(reward_asset.clone()) {
|
||||
assert_ok!(T::Assets::create(
|
||||
reward_asset.clone(),
|
||||
caller.clone(),
|
||||
true,
|
||||
min_reward_balance
|
||||
));
|
||||
}
|
||||
|
||||
assert_ok!(AssetRewards::<T>::create_pool(
|
||||
caller_origin.clone(),
|
||||
Box::new(staked_asset),
|
||||
Box::new(reward_asset),
|
||||
// reward rate per block
|
||||
min_reward_balance,
|
||||
pool_expire::<T>(),
|
||||
Some(caller),
|
||||
));
|
||||
|
||||
Ok(caller_origin)
|
||||
}
|
||||
|
||||
fn mint_into<T: Config>(caller: &T::AccountId, asset: &T::AssetId) -> T::Balance
|
||||
where
|
||||
T::Assets: Mutate<T::AccountId>,
|
||||
{
|
||||
let min_balance = T::Assets::minimum_balance(asset.clone());
|
||||
assert_ok!(T::Assets::mint_into(
|
||||
asset.clone(),
|
||||
&caller,
|
||||
min_balance.saturating_mul(10u32.into())
|
||||
));
|
||||
min_balance
|
||||
}
|
||||
|
||||
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
|
||||
System::<T>::assert_last_event(generic_event.into());
|
||||
}
|
||||
|
||||
#[benchmarks(where T::Assets: Create<T::AccountId> + Mutate<T::AccountId>)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn create_pool() -> Result<(), BenchmarkError> {
|
||||
let caller_origin =
|
||||
T::CreatePoolOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
let caller = T::CreatePoolOrigin::ensure_origin(caller_origin.clone()).unwrap();
|
||||
|
||||
let footprint = Footprint::from_mel::<(PoolId, PoolInfoFor<T>)>();
|
||||
T::Consideration::ensure_successful(&caller, footprint);
|
||||
|
||||
let staked_asset = T::BenchmarkHelper::staked_asset();
|
||||
let reward_asset = T::BenchmarkHelper::reward_asset();
|
||||
|
||||
let min_balance = T::Assets::minimum_balance(staked_asset.clone()).max(T::Balance::one());
|
||||
if !T::Assets::asset_exists(staked_asset.clone()) {
|
||||
assert_ok!(T::Assets::create(staked_asset.clone(), caller.clone(), true, min_balance));
|
||||
}
|
||||
let min_balance = T::Assets::minimum_balance(reward_asset.clone()).max(T::Balance::one());
|
||||
if !T::Assets::asset_exists(reward_asset.clone()) {
|
||||
assert_ok!(T::Assets::create(reward_asset.clone(), caller.clone(), true, min_balance));
|
||||
}
|
||||
|
||||
#[extrinsic_call]
|
||||
_(
|
||||
caller_origin as T::RuntimeOrigin,
|
||||
Box::new(staked_asset.clone()),
|
||||
Box::new(reward_asset.clone()),
|
||||
min_balance,
|
||||
pool_expire::<T>(),
|
||||
Some(caller.clone()),
|
||||
);
|
||||
|
||||
assert_last_event::<T>(
|
||||
Event::PoolCreated {
|
||||
creator: caller.clone(),
|
||||
admin: caller,
|
||||
staked_asset_id: staked_asset,
|
||||
reward_asset_id: reward_asset,
|
||||
reward_rate_per_block: min_balance,
|
||||
expiry_block: pool_expire::<T>()
|
||||
.evaluate(T::BlockNumberProvider::current_block_number()),
|
||||
pool_id: 0,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn stake() -> Result<(), BenchmarkError> {
|
||||
create_reward_pool::<T>()?;
|
||||
|
||||
let staker: T::AccountId = whitelisted_caller();
|
||||
let min_balance = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset());
|
||||
|
||||
// stake first to get worth case benchmark.
|
||||
assert_ok!(AssetRewards::<T>::stake(
|
||||
RawOrigin::Signed(staker.clone()).into(),
|
||||
0,
|
||||
min_balance
|
||||
));
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(staker.clone()), 0, min_balance);
|
||||
|
||||
assert_last_event::<T>(Event::Staked { staker, pool_id: 0, amount: min_balance }.into());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn unstake() -> Result<(), BenchmarkError> {
|
||||
create_reward_pool::<T>()?;
|
||||
|
||||
let staker: T::AccountId = whitelisted_caller();
|
||||
let min_balance = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset());
|
||||
|
||||
assert_ok!(AssetRewards::<T>::stake(
|
||||
RawOrigin::Signed(staker.clone()).into(),
|
||||
0,
|
||||
min_balance,
|
||||
));
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(staker.clone()), 0, min_balance, None);
|
||||
|
||||
assert_last_event::<T>(
|
||||
Event::Unstaked { caller: staker.clone(), staker, pool_id: 0, amount: min_balance }
|
||||
.into(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn harvest_rewards() -> Result<(), BenchmarkError> {
|
||||
create_reward_pool::<T>()?;
|
||||
|
||||
let pool_acc = AssetRewards::<T>::pool_account_id(&0u32);
|
||||
let min_reward_balance = mint_into::<T>(&pool_acc, &T::BenchmarkHelper::reward_asset());
|
||||
|
||||
let staker = whitelisted_caller();
|
||||
let _ = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset());
|
||||
assert_ok!(AssetRewards::<T>::stake(
|
||||
RawOrigin::Signed(staker.clone()).into(),
|
||||
0,
|
||||
T::Balance::one(),
|
||||
));
|
||||
|
||||
T::BlockNumberProvider::set_block_number(
|
||||
T::BlockNumberProvider::current_block_number() + BlockNumberFor::<T>::one(),
|
||||
);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(staker.clone()), 0, None);
|
||||
|
||||
assert_last_event::<T>(
|
||||
Event::RewardsHarvested {
|
||||
caller: staker.clone(),
|
||||
staker,
|
||||
pool_id: 0,
|
||||
amount: min_reward_balance,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_pool_reward_rate_per_block() -> Result<(), BenchmarkError> {
|
||||
let caller_origin = create_reward_pool::<T>()?;
|
||||
|
||||
// stake first to get worth case benchmark.
|
||||
{
|
||||
let staker: T::AccountId = whitelisted_caller();
|
||||
let min_balance = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset());
|
||||
|
||||
assert_ok!(AssetRewards::<T>::stake(RawOrigin::Signed(staker).into(), 0, min_balance));
|
||||
}
|
||||
|
||||
let new_reward_rate_per_block =
|
||||
T::Assets::minimum_balance(T::BenchmarkHelper::reward_asset()).max(T::Balance::one()) +
|
||||
T::Balance::one();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(caller_origin as T::RuntimeOrigin, 0, new_reward_rate_per_block);
|
||||
|
||||
assert_last_event::<T>(
|
||||
Event::PoolRewardRateModified { pool_id: 0, new_reward_rate_per_block }.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_pool_admin() -> Result<(), BenchmarkError> {
|
||||
let caller_origin = create_reward_pool::<T>()?;
|
||||
let new_admin: T::AccountId = whitelisted_caller();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(caller_origin as T::RuntimeOrigin, 0, new_admin.clone());
|
||||
|
||||
assert_last_event::<T>(Event::PoolAdminModified { pool_id: 0, new_admin }.into());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_pool_expiry_block() -> Result<(), BenchmarkError> {
|
||||
let create_origin = create_reward_pool::<T>()?;
|
||||
|
||||
// stake first to get worth case benchmark.
|
||||
{
|
||||
let staker: T::AccountId = whitelisted_caller();
|
||||
let min_balance = mint_into::<T>(&staker, &T::BenchmarkHelper::staked_asset());
|
||||
|
||||
assert_ok!(AssetRewards::<T>::stake(RawOrigin::Signed(staker).into(), 0, min_balance));
|
||||
}
|
||||
|
||||
let new_expiry_block = pool_expire::<T>()
|
||||
.evaluate(T::BlockNumberProvider::current_block_number()) +
|
||||
BlockNumberFor::<T>::one();
|
||||
|
||||
#[extrinsic_call]
|
||||
_(create_origin as T::RuntimeOrigin, 0, DispatchTime::At(new_expiry_block));
|
||||
|
||||
assert_last_event::<T>(
|
||||
Event::PoolExpiryBlockModified { pool_id: 0, new_expiry_block }.into(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn deposit_reward_tokens() -> Result<(), BenchmarkError> {
|
||||
create_reward_pool::<T>()?;
|
||||
let caller = whitelisted_caller();
|
||||
|
||||
let reward_asset = T::BenchmarkHelper::reward_asset();
|
||||
let pool_acc = AssetRewards::<T>::pool_account_id(&0u32);
|
||||
let min_balance = mint_into::<T>(&caller, &reward_asset);
|
||||
|
||||
let balance_before = T::Assets::balance(reward_asset.clone(), &pool_acc);
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller), 0, min_balance);
|
||||
|
||||
let balance_after = T::Assets::balance(reward_asset, &pool_acc);
|
||||
|
||||
assert_eq!(balance_after, balance_before + min_balance);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn cleanup_pool() -> Result<(), BenchmarkError> {
|
||||
let create_origin = create_reward_pool::<T>()?;
|
||||
let caller = T::CreatePoolOrigin::ensure_origin(create_origin.clone()).unwrap();
|
||||
|
||||
// deposit rewards tokens to get worth case benchmark.
|
||||
{
|
||||
let caller = whitelisted_caller();
|
||||
let reward_asset = T::BenchmarkHelper::reward_asset();
|
||||
let min_balance = mint_into::<T>(&caller, &reward_asset);
|
||||
assert_ok!(AssetRewards::<T>::deposit_reward_tokens(
|
||||
RawOrigin::Signed(caller).into(),
|
||||
0,
|
||||
min_balance
|
||||
));
|
||||
}
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller), 0);
|
||||
|
||||
assert_last_event::<T>(Event::PoolCleanedUp { pool_id: 0 }.into());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(AssetRewards, crate::mock::new_test_ext(), crate::mock::MockRuntime);
|
||||
}
|
||||
@@ -0,0 +1,964 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! # FRAME Staking Rewards Pallet
|
||||
//!
|
||||
//! Allows accounts to be rewarded for holding `fungible` asset/s, for example LP tokens.
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! Initiate an incentive program for a fungible asset by creating a new pool.
|
||||
//!
|
||||
//! During pool creation, a 'staking asset', 'reward asset', 'reward rate per block', 'expiry
|
||||
//! block', and 'admin' are specified.
|
||||
//!
|
||||
//! Once created, holders of the 'staking asset' can 'stake' them in a corresponding pool, which
|
||||
//! creates a Freeze on the asset.
|
||||
//!
|
||||
//! Once staked, rewards denominated in 'reward asset' begin accumulating to the staker,
|
||||
//! proportional to their share of the total staked tokens in the pool.
|
||||
//!
|
||||
//! Reward assets pending distribution are held in an account unique to each pool.
|
||||
//!
|
||||
//! Care should be taken by the pool operator to keep pool accounts adequately funded with the
|
||||
//! reward asset.
|
||||
//!
|
||||
//! The pool admin may increase reward rate per block, increase expiry block, and change admin.
|
||||
//!
|
||||
//! ## Disambiguation
|
||||
//!
|
||||
//! While this pallet shares some terminology with the `staking-pool` and similar native staking
|
||||
//! related pallets, it is distinct and is entirely unrelated to native staking.
|
||||
//!
|
||||
//! ## Permissioning
|
||||
//!
|
||||
//! Currently, pool creation and management restricted to a configured Origin.
|
||||
//!
|
||||
//! Future iterations of this pallet may allow permissionless creation and management of pools.
|
||||
//!
|
||||
//! Note: The permissioned origin must return an AccountId. This can be achieved for any Origin by
|
||||
//! wrapping it with `EnsureSuccess`.
|
||||
//!
|
||||
//! ## Implementation Notes
|
||||
//!
|
||||
//! Internal logic functions such as `update_pool_and_staker_rewards` were deliberately written
|
||||
//! without side-effects.
|
||||
//!
|
||||
//! Storage interaction such as reads and writes are instead all performed in the top level
|
||||
//! pallet Call method, which while slightly more verbose, makes it easier to understand the
|
||||
//! code and reason about how storage reads and writes occur in the pallet.
|
||||
//!
|
||||
//! ## Rewards Algorithm
|
||||
//!
|
||||
//! The rewards algorithm is based on the Synthetix [StakingRewards.sol](https://github.com/Synthetixio/synthetix/blob/develop/contracts/StakingRewards.sol)
|
||||
//! smart contract.
|
||||
//!
|
||||
//! Rewards are calculated JIT (just-in-time), and all operations are O(1) making the approach
|
||||
//! scalable to many pools and stakers.
|
||||
//!
|
||||
//! ### Resources
|
||||
//!
|
||||
//! - [This video series](https://www.youtube.com/watch?v=6ZO5aYg1GI8), which walks through the math
|
||||
//! of the algorithm.
|
||||
//! - [This dev.to article](https://dev.to/heymarkkop/understanding-sushiswaps-masterchef-staking-rewards-1m6f),
|
||||
//! which explains the algorithm of the SushiSwap MasterChef staking. While not identical to the
|
||||
//! Synthetix approach, they are quite similar.
|
||||
#![deny(missing_docs)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
use codec::{Codec, Decode, Encode, MaxEncodedLen};
|
||||
use pezframe_support::{
|
||||
ensure,
|
||||
traits::{
|
||||
fungibles::{Inspect, Mutate},
|
||||
schedule::DispatchTime,
|
||||
tokens::Balance,
|
||||
Consideration, RewardsPool,
|
||||
},
|
||||
PalletId,
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_core::Get;
|
||||
use pezsp_runtime::{
|
||||
traits::{BadOrigin, BlockNumberProvider, EnsureAdd, MaybeDisplay, Zero},
|
||||
DispatchError, DispatchResult,
|
||||
};
|
||||
use pezsp_std::boxed::Box;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub mod benchmarking;
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod weights;
|
||||
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
/// Unique id type for each pool.
|
||||
pub type PoolId = u32;
|
||||
|
||||
/// Multiplier to maintain precision when calculating rewards.
|
||||
pub(crate) const PRECISION_SCALING_FACTOR: u16 = 4096;
|
||||
|
||||
/// Convenience alias for `PoolInfo`.
|
||||
pub type PoolInfoFor<T> = PoolInfo<
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
<T as Config>::AssetId,
|
||||
<T as Config>::Balance,
|
||||
BlockNumberFor<T>,
|
||||
>;
|
||||
|
||||
/// The block number type for the pallet.
|
||||
///
|
||||
/// This type is derived from the `BlockNumberProvider` associated type in the `Config` trait.
|
||||
/// It represents the block number type that the pallet uses for scheduling and expiration.
|
||||
pub type BlockNumberFor<T> =
|
||||
<<T as Config>::BlockNumberProvider as BlockNumberProvider>::BlockNumber;
|
||||
|
||||
/// The state of a staker in a pool.
|
||||
#[derive(Debug, Default, Clone, Decode, Encode, MaxEncodedLen, TypeInfo)]
|
||||
pub struct PoolStakerInfo<Balance> {
|
||||
/// Amount of tokens staked.
|
||||
amount: Balance,
|
||||
/// Accumulated, unpaid rewards.
|
||||
rewards: Balance,
|
||||
/// Reward per token value at the time of the staker's last interaction with the contract.
|
||||
reward_per_token_paid: Balance,
|
||||
}
|
||||
|
||||
/// The state and configuration of an incentive pool.
|
||||
#[derive(Debug, Clone, Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)]
|
||||
pub struct PoolInfo<AccountId, AssetId, Balance, BlockNumber> {
|
||||
/// The asset staked in this pool.
|
||||
staked_asset_id: AssetId,
|
||||
/// The asset distributed as rewards by this pool.
|
||||
reward_asset_id: AssetId,
|
||||
/// The amount of tokens rewarded per block.
|
||||
reward_rate_per_block: Balance,
|
||||
/// The block the pool will cease distributing rewards.
|
||||
expiry_block: BlockNumber,
|
||||
/// The account authorized to manage this pool.
|
||||
admin: AccountId,
|
||||
/// The total amount of tokens staked in this pool.
|
||||
total_tokens_staked: Balance,
|
||||
/// Total rewards accumulated per token, up to the `last_update_block`.
|
||||
reward_per_token_stored: Balance,
|
||||
/// Last block number the pool was updated.
|
||||
last_update_block: BlockNumber,
|
||||
/// The account that holds the pool's rewards.
|
||||
account: AccountId,
|
||||
}
|
||||
|
||||
pezsp_api::decl_runtime_apis! {
|
||||
/// The runtime API for the asset rewards pallet.
|
||||
pub trait AssetRewards<Cost: MaybeDisplay + Codec> {
|
||||
/// Get the cost of creating a pool.
|
||||
///
|
||||
/// This is especially useful when the cost is dynamic.
|
||||
fn pool_creation_cost() -> Cost;
|
||||
}
|
||||
}
|
||||
|
||||
#[pezframe_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::*,
|
||||
traits::{
|
||||
fungibles::MutateFreeze,
|
||||
tokens::{AssetId, Fortitude, Preservation},
|
||||
Consideration, Footprint, RewardsPool,
|
||||
},
|
||||
};
|
||||
use pezframe_system::pezpallet_prelude::{
|
||||
ensure_signed, BlockNumberFor as SystemBlockNumberFor, OriginFor,
|
||||
};
|
||||
use pezsp_runtime::{
|
||||
traits::{
|
||||
AccountIdConversion, BadOrigin, EnsureAdd, EnsureAddAssign, EnsureDiv, EnsureMul,
|
||||
EnsureSub, EnsureSubAssign,
|
||||
},
|
||||
DispatchResult,
|
||||
};
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
/// A reason for the pallet placing a hold on funds.
|
||||
#[pallet::composite_enum]
|
||||
pub enum FreezeReason {
|
||||
/// Funds are staked in the pallet.
|
||||
#[codec(index = 0)]
|
||||
Staked,
|
||||
}
|
||||
|
||||
/// A reason for the pallet placing a hold on funds.
|
||||
#[pallet::composite_enum]
|
||||
pub enum HoldReason {
|
||||
/// Cost associated with storing pool information on-chain.
|
||||
#[codec(index = 0)]
|
||||
PoolCreation,
|
||||
}
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: pezframe_system::Config {
|
||||
/// Overarching event type.
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
|
||||
/// The pallet's unique identifier, used to derive the pool's account ID.
|
||||
///
|
||||
/// The account ID is derived once during pool creation and stored in the storage.
|
||||
#[pallet::constant]
|
||||
type PalletId: Get<PalletId>;
|
||||
|
||||
/// Identifier for each type of asset.
|
||||
type AssetId: AssetId + Member + Parameter;
|
||||
|
||||
/// The type in which the assets are measured.
|
||||
type Balance: Balance + TypeInfo;
|
||||
|
||||
/// The origin with permission to create pools.
|
||||
///
|
||||
/// The Origin must return an AccountId.
|
||||
type CreatePoolOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;
|
||||
|
||||
/// Registry of assets that can be configured to either stake for rewards, or be offered as
|
||||
/// rewards for staking.
|
||||
type Assets: Inspect<Self::AccountId, AssetId = Self::AssetId, Balance = Self::Balance>
|
||||
+ Mutate<Self::AccountId>;
|
||||
|
||||
/// Freezer for the Assets.
|
||||
type AssetsFreezer: MutateFreeze<
|
||||
Self::AccountId,
|
||||
Id = Self::RuntimeFreezeReason,
|
||||
AssetId = Self::AssetId,
|
||||
Balance = Self::Balance,
|
||||
>;
|
||||
|
||||
/// The overarching freeze reason.
|
||||
type RuntimeFreezeReason: From<FreezeReason>;
|
||||
|
||||
/// Means for associating a cost with the on-chain storage of pool information, which
|
||||
/// is incurred by the pool creator.
|
||||
///
|
||||
/// The passed `Footprint` specifically accounts for the storage footprint of the pool's
|
||||
/// information itself, excluding any potential storage footprint related to the stakers.
|
||||
type Consideration: Consideration<Self::AccountId, Footprint>;
|
||||
|
||||
/// Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
|
||||
/// Provider for the current block number.
|
||||
///
|
||||
/// This provider is used to determine the current block number for the pallet.
|
||||
/// It must return monotonically increasing values when called from consecutive blocks.
|
||||
///
|
||||
/// It can be configured to use the local block number (via `pezframe_system::Pallet`) or a
|
||||
/// remote block number (e.g., from a relay chain). However, note that using a remote
|
||||
/// block number might have implications for the behavior of the pallet, especially if the
|
||||
/// remote block number advances faster than the local block number.
|
||||
///
|
||||
/// It is recommended to use the local block number for solo chains and relay chains.
|
||||
type BlockNumberProvider: BlockNumberProvider;
|
||||
|
||||
/// Helper for benchmarking.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper: benchmarking::BenchmarkHelper<Self::AssetId>;
|
||||
}
|
||||
|
||||
/// State of pool stakers.
|
||||
#[pallet::storage]
|
||||
pub type PoolStakers<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
PoolId,
|
||||
Blake2_128Concat,
|
||||
T::AccountId,
|
||||
PoolStakerInfo<T::Balance>,
|
||||
>;
|
||||
|
||||
/// State and configuration of each staking pool.
|
||||
#[pallet::storage]
|
||||
pub type Pools<T: Config> = StorageMap<_, Blake2_128Concat, PoolId, PoolInfoFor<T>>;
|
||||
|
||||
/// The cost associated with storing pool information on-chain which was incurred by the pool
|
||||
/// creator.
|
||||
///
|
||||
/// This cost may be [`None`], as determined by [`Config::Consideration`].
|
||||
#[pallet::storage]
|
||||
pub type PoolCost<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, PoolId, (T::AccountId, T::Consideration)>;
|
||||
|
||||
/// Stores the [`PoolId`] to use for the next pool.
|
||||
///
|
||||
/// Incremented when a new pool is created.
|
||||
#[pallet::storage]
|
||||
pub type NextPoolId<T: Config> = StorageValue<_, PoolId, ValueQuery>;
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// An account staked some tokens in a pool.
|
||||
Staked {
|
||||
/// The account that staked assets.
|
||||
staker: T::AccountId,
|
||||
/// The pool.
|
||||
pool_id: PoolId,
|
||||
/// The staked asset amount.
|
||||
amount: T::Balance,
|
||||
},
|
||||
/// An account unstaked some tokens from a pool.
|
||||
Unstaked {
|
||||
/// The account that signed transaction.
|
||||
caller: T::AccountId,
|
||||
/// The account that unstaked assets.
|
||||
staker: T::AccountId,
|
||||
/// The pool.
|
||||
pool_id: PoolId,
|
||||
/// The unstaked asset amount.
|
||||
amount: T::Balance,
|
||||
},
|
||||
/// An account harvested some rewards.
|
||||
RewardsHarvested {
|
||||
/// The account that signed transaction.
|
||||
caller: T::AccountId,
|
||||
/// The staker whos rewards were harvested.
|
||||
staker: T::AccountId,
|
||||
/// The pool.
|
||||
pool_id: PoolId,
|
||||
/// The amount of harvested tokens.
|
||||
amount: T::Balance,
|
||||
},
|
||||
/// A new reward pool was created.
|
||||
PoolCreated {
|
||||
/// The account that created the pool.
|
||||
creator: T::AccountId,
|
||||
/// The unique ID for the new pool.
|
||||
pool_id: PoolId,
|
||||
/// The staking asset.
|
||||
staked_asset_id: T::AssetId,
|
||||
/// The reward asset.
|
||||
reward_asset_id: T::AssetId,
|
||||
/// The initial reward rate per block.
|
||||
reward_rate_per_block: T::Balance,
|
||||
/// The block the pool will cease to accumulate rewards.
|
||||
expiry_block: BlockNumberFor<T>,
|
||||
/// The account allowed to modify the pool.
|
||||
admin: T::AccountId,
|
||||
},
|
||||
/// A pool reward rate was modified by the admin.
|
||||
PoolRewardRateModified {
|
||||
/// The modified pool.
|
||||
pool_id: PoolId,
|
||||
/// The new reward rate per block.
|
||||
new_reward_rate_per_block: T::Balance,
|
||||
},
|
||||
/// A pool admin was modified.
|
||||
PoolAdminModified {
|
||||
/// The modified pool.
|
||||
pool_id: PoolId,
|
||||
/// The new admin.
|
||||
new_admin: T::AccountId,
|
||||
},
|
||||
/// A pool expiry block was modified by the admin.
|
||||
PoolExpiryBlockModified {
|
||||
/// The modified pool.
|
||||
pool_id: PoolId,
|
||||
/// The new expiry block.
|
||||
new_expiry_block: BlockNumberFor<T>,
|
||||
},
|
||||
/// A pool information was cleared after it's completion.
|
||||
PoolCleanedUp {
|
||||
/// The cleared pool.
|
||||
pool_id: PoolId,
|
||||
},
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// The staker does not have enough tokens to perform the operation.
|
||||
NotEnoughTokens,
|
||||
/// An operation was attempted on a non-existent pool.
|
||||
NonExistentPool,
|
||||
/// An operation was attempted for a non-existent staker.
|
||||
NonExistentStaker,
|
||||
/// An operation was attempted with a non-existent asset.
|
||||
NonExistentAsset,
|
||||
/// There was an error converting a block number.
|
||||
BlockNumberConversionError,
|
||||
/// The expiry block must be in the future.
|
||||
ExpiryBlockMustBeInTheFuture,
|
||||
/// Insufficient funds to create the freeze.
|
||||
InsufficientFunds,
|
||||
/// The expiry block can be only extended.
|
||||
ExpiryCut,
|
||||
/// The reward rate per block can be only increased.
|
||||
RewardRateCut,
|
||||
/// The pool still has staked tokens or rewards.
|
||||
NonEmptyPool,
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<SystemBlockNumberFor<T>> for Pallet<T> {
|
||||
fn integrity_test() {
|
||||
// The AccountId is at least 16 bytes to contain the unique PalletId.
|
||||
let pool_id: PoolId = 1;
|
||||
assert!(
|
||||
<pezframe_support::PalletId as AccountIdConversion<T::AccountId>>::try_into_sub_account(
|
||||
&T::PalletId::get(), pool_id,
|
||||
)
|
||||
.is_some()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pallet's callable functions.
|
||||
#[pallet::call(weight(<T as Config>::WeightInfo))]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Create a new reward pool.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - `origin`: must be `Config::CreatePoolOrigin`;
|
||||
/// - `staked_asset_id`: the asset to be staked in the pool;
|
||||
/// - `reward_asset_id`: the asset to be distributed as rewards;
|
||||
/// - `reward_rate_per_block`: the amount of reward tokens distributed per block;
|
||||
/// - `expiry`: the block number at which the pool will cease to accumulate rewards. The
|
||||
/// [`DispatchTime::After`] variant evaluated at the execution time.
|
||||
/// - `admin`: the account allowed to extend the pool expiration, increase the rewards rate
|
||||
/// and receive the unutilized reward tokens back after the pool completion. If `None`,
|
||||
/// the caller is set as an admin.
|
||||
#[pallet::call_index(0)]
|
||||
pub fn create_pool(
|
||||
origin: OriginFor<T>,
|
||||
staked_asset_id: Box<T::AssetId>,
|
||||
reward_asset_id: Box<T::AssetId>,
|
||||
reward_rate_per_block: T::Balance,
|
||||
expiry: DispatchTime<BlockNumberFor<T>>,
|
||||
admin: Option<T::AccountId>,
|
||||
) -> DispatchResult {
|
||||
let creator = T::CreatePoolOrigin::ensure_origin(origin)?;
|
||||
<Self as RewardsPool<_>>::create_pool(
|
||||
&creator,
|
||||
*staked_asset_id,
|
||||
*reward_asset_id,
|
||||
reward_rate_per_block,
|
||||
expiry,
|
||||
&admin.unwrap_or_else(|| creator.clone()),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stake additional tokens in a pool.
|
||||
///
|
||||
/// A freeze is placed on the staked tokens.
|
||||
#[pallet::call_index(1)]
|
||||
pub fn stake(origin: OriginFor<T>, pool_id: PoolId, amount: T::Balance) -> DispatchResult {
|
||||
let staker = ensure_signed(origin)?;
|
||||
|
||||
// Always start by updating staker and pool rewards.
|
||||
let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
|
||||
let staker_info = PoolStakers::<T>::get(pool_id, &staker).unwrap_or_default();
|
||||
let (mut pool_info, mut staker_info) =
|
||||
Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?;
|
||||
|
||||
T::AssetsFreezer::increase_frozen(
|
||||
pool_info.staked_asset_id.clone(),
|
||||
&FreezeReason::Staked.into(),
|
||||
&staker,
|
||||
amount,
|
||||
)?;
|
||||
|
||||
// Update Pools.
|
||||
pool_info.total_tokens_staked.ensure_add_assign(amount)?;
|
||||
|
||||
Pools::<T>::insert(pool_id, pool_info);
|
||||
|
||||
// Update PoolStakers.
|
||||
staker_info.amount.ensure_add_assign(amount)?;
|
||||
PoolStakers::<T>::insert(pool_id, &staker, staker_info);
|
||||
|
||||
// Emit event.
|
||||
Self::deposit_event(Event::Staked { staker, pool_id, amount });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unstake tokens from a pool.
|
||||
///
|
||||
/// Removes the freeze on the staked tokens.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - origin: must be the `staker` if the pool is still active. Otherwise, any account.
|
||||
/// - pool_id: the pool to unstake from.
|
||||
/// - amount: the amount of tokens to unstake.
|
||||
/// - staker: the account to unstake from. If `None`, the caller is used.
|
||||
#[pallet::call_index(2)]
|
||||
pub fn unstake(
|
||||
origin: OriginFor<T>,
|
||||
pool_id: PoolId,
|
||||
amount: T::Balance,
|
||||
staker: Option<T::AccountId>,
|
||||
) -> DispatchResult {
|
||||
let caller = ensure_signed(origin)?;
|
||||
let staker = staker.unwrap_or(caller.clone());
|
||||
|
||||
// Always start by updating the pool rewards.
|
||||
let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
|
||||
let now = T::BlockNumberProvider::current_block_number();
|
||||
ensure!(now > pool_info.expiry_block || caller == staker, BadOrigin);
|
||||
|
||||
let staker_info = PoolStakers::<T>::get(pool_id, &staker).unwrap_or_default();
|
||||
let (mut pool_info, mut staker_info) =
|
||||
Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?;
|
||||
|
||||
// Check the staker has enough staked tokens.
|
||||
ensure!(staker_info.amount >= amount, Error::<T>::NotEnoughTokens);
|
||||
|
||||
// Unfreeze staker assets.
|
||||
T::AssetsFreezer::decrease_frozen(
|
||||
pool_info.staked_asset_id.clone(),
|
||||
&FreezeReason::Staked.into(),
|
||||
&staker,
|
||||
amount,
|
||||
)?;
|
||||
|
||||
// Update Pools.
|
||||
pool_info.total_tokens_staked.ensure_sub_assign(amount)?;
|
||||
Pools::<T>::insert(pool_id, pool_info);
|
||||
|
||||
// Update PoolStakers.
|
||||
staker_info.amount.ensure_sub_assign(amount)?;
|
||||
|
||||
if staker_info.amount.is_zero() && staker_info.rewards.is_zero() {
|
||||
PoolStakers::<T>::remove(&pool_id, &staker);
|
||||
} else {
|
||||
PoolStakers::<T>::insert(&pool_id, &staker, staker_info);
|
||||
}
|
||||
|
||||
// Emit event.
|
||||
Self::deposit_event(Event::Unstaked { caller, staker, pool_id, amount });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Harvest unclaimed pool rewards.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - origin: must be the `staker` if the pool is still active. Otherwise, any account.
|
||||
/// - pool_id: the pool to harvest from.
|
||||
/// - staker: the account for which to harvest rewards. If `None`, the caller is used.
|
||||
#[pallet::call_index(3)]
|
||||
pub fn harvest_rewards(
|
||||
origin: OriginFor<T>,
|
||||
pool_id: PoolId,
|
||||
staker: Option<T::AccountId>,
|
||||
) -> DispatchResult {
|
||||
let caller = ensure_signed(origin)?;
|
||||
let staker = staker.unwrap_or(caller.clone());
|
||||
|
||||
// Always start by updating the pool and staker rewards.
|
||||
let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
|
||||
let now = T::BlockNumberProvider::current_block_number();
|
||||
ensure!(now > pool_info.expiry_block || caller == staker, BadOrigin);
|
||||
|
||||
let staker_info =
|
||||
PoolStakers::<T>::get(pool_id, &staker).ok_or(Error::<T>::NonExistentStaker)?;
|
||||
let (pool_info, mut staker_info) =
|
||||
Self::update_pool_and_staker_rewards(&pool_info, &staker_info)?;
|
||||
|
||||
// Transfer unclaimed rewards from the pool to the staker.
|
||||
T::Assets::transfer(
|
||||
pool_info.reward_asset_id,
|
||||
&pool_info.account,
|
||||
&staker,
|
||||
staker_info.rewards,
|
||||
// Could kill the account, but only if the pool was already almost empty.
|
||||
Preservation::Expendable,
|
||||
)?;
|
||||
|
||||
// Emit event.
|
||||
Self::deposit_event(Event::RewardsHarvested {
|
||||
caller,
|
||||
staker: staker.clone(),
|
||||
pool_id,
|
||||
amount: staker_info.rewards,
|
||||
});
|
||||
|
||||
// Reset staker rewards.
|
||||
staker_info.rewards = 0u32.into();
|
||||
|
||||
if staker_info.amount.is_zero() {
|
||||
PoolStakers::<T>::remove(&pool_id, &staker);
|
||||
} else {
|
||||
PoolStakers::<T>::insert(&pool_id, &staker, staker_info);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Modify a pool reward rate.
|
||||
///
|
||||
/// Currently the reward rate can only be increased.
|
||||
///
|
||||
/// Only the pool admin may perform this operation.
|
||||
#[pallet::call_index(4)]
|
||||
pub fn set_pool_reward_rate_per_block(
|
||||
origin: OriginFor<T>,
|
||||
pool_id: PoolId,
|
||||
new_reward_rate_per_block: T::Balance,
|
||||
) -> DispatchResult {
|
||||
let caller = T::CreatePoolOrigin::ensure_origin(origin.clone())
|
||||
.or_else(|_| ensure_signed(origin))?;
|
||||
<Self as RewardsPool<_>>::set_pool_reward_rate_per_block(
|
||||
&caller,
|
||||
pool_id,
|
||||
new_reward_rate_per_block,
|
||||
)
|
||||
}
|
||||
|
||||
/// Modify a pool admin.
|
||||
///
|
||||
/// Only the pool admin may perform this operation.
|
||||
#[pallet::call_index(5)]
|
||||
pub fn set_pool_admin(
|
||||
origin: OriginFor<T>,
|
||||
pool_id: PoolId,
|
||||
new_admin: T::AccountId,
|
||||
) -> DispatchResult {
|
||||
let caller = T::CreatePoolOrigin::ensure_origin(origin.clone())
|
||||
.or_else(|_| ensure_signed(origin))?;
|
||||
<Self as RewardsPool<_>>::set_pool_admin(&caller, pool_id, new_admin)
|
||||
}
|
||||
|
||||
/// Set when the pool should expire.
|
||||
///
|
||||
/// Currently the expiry block can only be extended.
|
||||
///
|
||||
/// Only the pool admin may perform this operation.
|
||||
#[pallet::call_index(6)]
|
||||
pub fn set_pool_expiry_block(
|
||||
origin: OriginFor<T>,
|
||||
pool_id: PoolId,
|
||||
new_expiry: DispatchTime<BlockNumberFor<T>>,
|
||||
) -> DispatchResult {
|
||||
let caller = T::CreatePoolOrigin::ensure_origin(origin.clone())
|
||||
.or_else(|_| ensure_signed(origin))?;
|
||||
<Self as RewardsPool<_>>::set_pool_expiry_block(&caller, pool_id, new_expiry)
|
||||
}
|
||||
|
||||
/// Convenience method to deposit reward tokens into a pool.
|
||||
///
|
||||
/// This method is not strictly necessary (tokens could be transferred directly to the
|
||||
/// pool pot address), but is provided for convenience so manual derivation of the
|
||||
/// account id is not required.
|
||||
#[pallet::call_index(7)]
|
||||
pub fn deposit_reward_tokens(
|
||||
origin: OriginFor<T>,
|
||||
pool_id: PoolId,
|
||||
amount: T::Balance,
|
||||
) -> DispatchResult {
|
||||
let caller = ensure_signed(origin)?;
|
||||
let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
|
||||
T::Assets::transfer(
|
||||
pool_info.reward_asset_id,
|
||||
&caller,
|
||||
&pool_info.account,
|
||||
amount,
|
||||
Preservation::Preserve,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cleanup a pool.
|
||||
///
|
||||
/// Origin must be the pool admin.
|
||||
///
|
||||
/// Cleanup storage, release any associated storage cost and return the remaining reward
|
||||
/// tokens to the admin.
|
||||
#[pallet::call_index(8)]
|
||||
pub fn cleanup_pool(origin: OriginFor<T>, pool_id: PoolId) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
|
||||
let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
|
||||
ensure!(pool_info.admin == who, BadOrigin);
|
||||
|
||||
let stakers = PoolStakers::<T>::iter_key_prefix(pool_id).next();
|
||||
ensure!(stakers.is_none(), Error::<T>::NonEmptyPool);
|
||||
|
||||
let pool_balance = T::Assets::reducible_balance(
|
||||
pool_info.reward_asset_id.clone(),
|
||||
&pool_info.account,
|
||||
Preservation::Expendable,
|
||||
Fortitude::Polite,
|
||||
);
|
||||
T::Assets::transfer(
|
||||
pool_info.reward_asset_id,
|
||||
&pool_info.account,
|
||||
&pool_info.admin,
|
||||
pool_balance,
|
||||
Preservation::Expendable,
|
||||
)?;
|
||||
|
||||
if let Some((who, cost)) = PoolCost::<T>::take(pool_id) {
|
||||
T::Consideration::drop(cost, &who)?;
|
||||
}
|
||||
|
||||
Pools::<T>::remove(pool_id);
|
||||
|
||||
Self::deposit_event(Event::PoolCleanedUp { pool_id });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// The pool creation footprint.
|
||||
///
|
||||
/// The footprint specifically accounts for the storage footprint of the pool's information
|
||||
/// itself, excluding any potential storage footprint related to the stakers.
|
||||
pub fn pool_creation_footprint() -> Footprint {
|
||||
Footprint::from_mel::<(PoolId, PoolInfoFor<T>)>()
|
||||
}
|
||||
|
||||
/// Derive a pool account ID from the pool's ID.
|
||||
pub fn pool_account_id(id: &PoolId) -> T::AccountId {
|
||||
T::PalletId::get().into_sub_account_truncating(id)
|
||||
}
|
||||
|
||||
/// Computes update pool and staker reward state.
|
||||
///
|
||||
/// Should be called prior to any operation involving a staker.
|
||||
///
|
||||
/// Returns the updated pool and staker info.
|
||||
///
|
||||
/// NOTE: this function has no side-effects. Side-effects such as storage modifications are
|
||||
/// the responsibility of the caller.
|
||||
pub fn update_pool_and_staker_rewards(
|
||||
pool_info: &PoolInfoFor<T>,
|
||||
staker_info: &PoolStakerInfo<T::Balance>,
|
||||
) -> Result<(PoolInfoFor<T>, PoolStakerInfo<T::Balance>), DispatchError> {
|
||||
let reward_per_token = Self::reward_per_token(&pool_info)?;
|
||||
let pool_info = Self::update_pool_rewards(pool_info, reward_per_token)?;
|
||||
|
||||
let mut new_staker_info = staker_info.clone();
|
||||
new_staker_info.rewards = Self::derive_rewards(&staker_info, &reward_per_token)?;
|
||||
new_staker_info.reward_per_token_paid = pool_info.reward_per_token_stored;
|
||||
return Ok((pool_info, new_staker_info));
|
||||
}
|
||||
|
||||
/// Computes update pool reward state.
|
||||
///
|
||||
/// Should be called every time the pool is adjusted, and a staker is not involved.
|
||||
///
|
||||
/// Returns the updated pool and staker info.
|
||||
///
|
||||
/// NOTE: this function has no side-effects. Side-effects such as storage modifications are
|
||||
/// the responsibility of the caller.
|
||||
pub fn update_pool_rewards(
|
||||
pool_info: &PoolInfoFor<T>,
|
||||
reward_per_token: T::Balance,
|
||||
) -> Result<PoolInfoFor<T>, DispatchError> {
|
||||
let mut new_pool_info = pool_info.clone();
|
||||
new_pool_info.last_update_block = T::BlockNumberProvider::current_block_number();
|
||||
new_pool_info.reward_per_token_stored = reward_per_token;
|
||||
|
||||
Ok(new_pool_info)
|
||||
}
|
||||
|
||||
/// Derives the current reward per token for this pool.
|
||||
pub(super) fn reward_per_token(
|
||||
pool_info: &PoolInfoFor<T>,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
if pool_info.total_tokens_staked.is_zero() {
|
||||
return Ok(pool_info.reward_per_token_stored);
|
||||
}
|
||||
|
||||
let rewardable_blocks_elapsed: u32 =
|
||||
match Self::last_block_reward_applicable(pool_info.expiry_block)
|
||||
.ensure_sub(pool_info.last_update_block)?
|
||||
.try_into()
|
||||
{
|
||||
Ok(b) => b,
|
||||
Err(_) => return Err(Error::<T>::BlockNumberConversionError.into()),
|
||||
};
|
||||
|
||||
Ok(pool_info.reward_per_token_stored.ensure_add(
|
||||
pool_info
|
||||
.reward_rate_per_block
|
||||
.ensure_mul(rewardable_blocks_elapsed.into())?
|
||||
.ensure_mul(PRECISION_SCALING_FACTOR.into())?
|
||||
.ensure_div(pool_info.total_tokens_staked)?,
|
||||
)?)
|
||||
}
|
||||
|
||||
/// Derives the amount of rewards earned by a staker.
|
||||
///
|
||||
/// This is a helper function for `update_pool_rewards` and should not be called directly.
|
||||
fn derive_rewards(
|
||||
staker_info: &PoolStakerInfo<T::Balance>,
|
||||
reward_per_token: &T::Balance,
|
||||
) -> Result<T::Balance, DispatchError> {
|
||||
Ok(staker_info
|
||||
.amount
|
||||
.ensure_mul(reward_per_token.ensure_sub(staker_info.reward_per_token_paid)?)?
|
||||
.ensure_div(PRECISION_SCALING_FACTOR.into())?
|
||||
.ensure_add(staker_info.rewards)?)
|
||||
}
|
||||
|
||||
fn last_block_reward_applicable(pool_expiry_block: BlockNumberFor<T>) -> BlockNumberFor<T> {
|
||||
let now = T::BlockNumberProvider::current_block_number();
|
||||
if now < pool_expiry_block {
|
||||
now
|
||||
} else {
|
||||
pool_expiry_block
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> RewardsPool<T::AccountId> for Pallet<T> {
|
||||
type AssetId = T::AssetId;
|
||||
type BlockNumber = BlockNumberFor<T>;
|
||||
type PoolId = PoolId;
|
||||
type Balance = T::Balance;
|
||||
|
||||
fn create_pool(
|
||||
creator: &T::AccountId,
|
||||
staked_asset_id: T::AssetId,
|
||||
reward_asset_id: T::AssetId,
|
||||
reward_rate_per_block: T::Balance,
|
||||
expiry: DispatchTime<BlockNumberFor<T>>,
|
||||
admin: &T::AccountId,
|
||||
) -> Result<PoolId, DispatchError> {
|
||||
// Ensure the assets exist.
|
||||
ensure!(T::Assets::asset_exists(staked_asset_id.clone()), Error::<T>::NonExistentAsset);
|
||||
ensure!(T::Assets::asset_exists(reward_asset_id.clone()), Error::<T>::NonExistentAsset);
|
||||
|
||||
// Check the expiry block.
|
||||
let now = T::BlockNumberProvider::current_block_number();
|
||||
let expiry_block = expiry.evaluate(now);
|
||||
ensure!(expiry_block > now, Error::<T>::ExpiryBlockMustBeInTheFuture);
|
||||
|
||||
let pool_id = NextPoolId::<T>::try_mutate(|id| -> Result<PoolId, DispatchError> {
|
||||
let current_id = *id;
|
||||
*id = id.ensure_add(1)?;
|
||||
Ok(current_id)
|
||||
})?;
|
||||
|
||||
let footprint = Self::pool_creation_footprint();
|
||||
let cost = T::Consideration::new(creator, footprint)?;
|
||||
PoolCost::<T>::insert(pool_id, (creator.clone(), cost));
|
||||
|
||||
// Create the pool.
|
||||
let pool = PoolInfoFor::<T> {
|
||||
staked_asset_id: staked_asset_id.clone(),
|
||||
reward_asset_id: reward_asset_id.clone(),
|
||||
reward_rate_per_block,
|
||||
total_tokens_staked: 0u32.into(),
|
||||
reward_per_token_stored: 0u32.into(),
|
||||
last_update_block: 0u32.into(),
|
||||
expiry_block,
|
||||
admin: admin.clone(),
|
||||
account: Self::pool_account_id(&pool_id),
|
||||
};
|
||||
|
||||
// Insert it into storage.
|
||||
Pools::<T>::insert(pool_id, pool);
|
||||
|
||||
// Emit created event.
|
||||
Self::deposit_event(Event::PoolCreated {
|
||||
creator: creator.clone(),
|
||||
pool_id,
|
||||
staked_asset_id,
|
||||
reward_asset_id,
|
||||
reward_rate_per_block,
|
||||
expiry_block,
|
||||
admin: admin.clone(),
|
||||
});
|
||||
|
||||
Ok(pool_id)
|
||||
}
|
||||
|
||||
fn set_pool_reward_rate_per_block(
|
||||
admin: &T::AccountId,
|
||||
pool_id: PoolId,
|
||||
new_reward_rate_per_block: T::Balance,
|
||||
) -> DispatchResult {
|
||||
let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
|
||||
ensure!(pool_info.admin == *admin, BadOrigin);
|
||||
ensure!(
|
||||
new_reward_rate_per_block > pool_info.reward_rate_per_block,
|
||||
Error::<T>::RewardRateCut
|
||||
);
|
||||
|
||||
// Always start by updating the pool rewards.
|
||||
let rewards_per_token = Self::reward_per_token(&pool_info)?;
|
||||
let mut pool_info = Self::update_pool_rewards(&pool_info, rewards_per_token)?;
|
||||
|
||||
pool_info.reward_rate_per_block = new_reward_rate_per_block;
|
||||
Pools::<T>::insert(pool_id, pool_info);
|
||||
|
||||
Self::deposit_event(Event::PoolRewardRateModified { pool_id, new_reward_rate_per_block });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_pool_admin(
|
||||
admin: &T::AccountId,
|
||||
pool_id: PoolId,
|
||||
new_admin: T::AccountId,
|
||||
) -> DispatchResult {
|
||||
let mut pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
|
||||
ensure!(pool_info.admin == *admin, BadOrigin);
|
||||
|
||||
pool_info.admin = new_admin.clone();
|
||||
Pools::<T>::insert(pool_id, pool_info);
|
||||
|
||||
Self::deposit_event(Event::PoolAdminModified { pool_id, new_admin });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_pool_expiry_block(
|
||||
admin: &T::AccountId,
|
||||
pool_id: PoolId,
|
||||
new_expiry: DispatchTime<BlockNumberFor<T>>,
|
||||
) -> DispatchResult {
|
||||
let now = T::BlockNumberProvider::current_block_number();
|
||||
let new_expiry_block = new_expiry.evaluate(now);
|
||||
ensure!(new_expiry_block > now, Error::<T>::ExpiryBlockMustBeInTheFuture);
|
||||
|
||||
let pool_info = Pools::<T>::get(pool_id).ok_or(Error::<T>::NonExistentPool)?;
|
||||
ensure!(pool_info.admin == *admin, BadOrigin);
|
||||
ensure!(new_expiry_block > pool_info.expiry_block, Error::<T>::ExpiryCut);
|
||||
|
||||
// Always start by updating the pool rewards.
|
||||
let reward_per_token = Self::reward_per_token(&pool_info)?;
|
||||
let mut pool_info = Self::update_pool_rewards(&pool_info, reward_per_token)?;
|
||||
|
||||
pool_info.expiry_block = new_expiry_block;
|
||||
Pools::<T>::insert(pool_id, pool_info);
|
||||
|
||||
Self::deposit_event(Event::PoolExpiryBlockModified { pool_id, new_expiry_block });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test environment for Asset Rewards pallet.
|
||||
|
||||
use super::*;
|
||||
use crate as pezpallet_asset_rewards;
|
||||
use core::default::Default;
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl,
|
||||
instances::Instance1,
|
||||
parameter_types,
|
||||
traits::{
|
||||
tokens::fungible::{HoldConsideration, NativeFromLeft, NativeOrWithId, UnionOf},
|
||||
AsEnsureOriginWithArg, ConstU128, ConstU32, EnsureOrigin, LinearStoragePrice,
|
||||
},
|
||||
PalletId,
|
||||
};
|
||||
use pezframe_system::EnsureSigned;
|
||||
use pezsp_runtime::{traits::IdentityLookup, BuildStorage};
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
use self::benchmarking::BenchmarkHelper;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<MockRuntime>;
|
||||
|
||||
construct_runtime!(
|
||||
pub enum MockRuntime
|
||||
{
|
||||
System: pezframe_system,
|
||||
Balances: pezpallet_balances,
|
||||
Assets: pezpallet_assets::<Instance1>,
|
||||
AssetsFreezer: pezpallet_assets_freezer::<Instance1>,
|
||||
StakingRewards: pezpallet_asset_rewards,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for MockRuntime {
|
||||
type AccountId = u128;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u128>;
|
||||
}
|
||||
|
||||
impl pezpallet_balances::Config for MockRuntime {
|
||||
type Balance = u128;
|
||||
type DustRemoval = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ExistentialDeposit = ConstU128<100>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ConstU32<50>;
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type FreezeIdentifier = RuntimeFreezeReason;
|
||||
type MaxFreezes = ConstU32<50>;
|
||||
type RuntimeHoldReason = RuntimeHoldReason;
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
type DoneSlashHandler = ();
|
||||
}
|
||||
|
||||
impl pezpallet_assets::Config<Instance1> for MockRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type Balance = u128;
|
||||
type RemoveItemsLimit = ConstU32<1000>;
|
||||
type AssetId = u32;
|
||||
type AssetIdParameter = u32;
|
||||
type ReserveData = ();
|
||||
type Currency = Balances;
|
||||
type CreateOrigin = AsEnsureOriginWithArg<EnsureSigned<Self::AccountId>>;
|
||||
type ForceOrigin = pezframe_system::EnsureRoot<Self::AccountId>;
|
||||
type AssetDeposit = ConstU128<1>;
|
||||
type AssetAccountDeposit = ConstU128<10>;
|
||||
type MetadataDepositBase = ConstU128<1>;
|
||||
type MetadataDepositPerByte = ConstU128<1>;
|
||||
type ApprovalDeposit = ConstU128<1>;
|
||||
type StringLimit = ConstU32<50>;
|
||||
type Freezer = AssetsFreezer;
|
||||
type Holder = ();
|
||||
type Extra = ();
|
||||
type WeightInfo = ();
|
||||
type CallbackHandle = ();
|
||||
pezpallet_assets::runtime_benchmarks_enabled! {
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const StakingRewardsPalletId: PalletId = PalletId(*b"py/stkrd");
|
||||
pub const Native: NativeOrWithId<u32> = NativeOrWithId::Native;
|
||||
pub const PermissionedAccountId: u128 = 0;
|
||||
}
|
||||
|
||||
/// Give Root Origin permission to create pools.
|
||||
pub struct MockPermissionedOrigin;
|
||||
impl EnsureOrigin<RuntimeOrigin> for MockPermissionedOrigin {
|
||||
type Success = <MockRuntime as pezframe_system::Config>::AccountId;
|
||||
|
||||
fn try_origin(origin: RuntimeOrigin) -> Result<Self::Success, RuntimeOrigin> {
|
||||
match origin.clone().into() {
|
||||
Ok(pezframe_system::RawOrigin::Root) => Ok(PermissionedAccountId::get()),
|
||||
_ => Err(origin),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
fn try_successful_origin() -> Result<RuntimeOrigin, ()> {
|
||||
Ok(RuntimeOrigin::root())
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow Freezes for the `Assets` pallet
|
||||
impl pezpallet_assets_freezer::Config<pezpallet_assets_freezer::Instance1> for MockRuntime {
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
pub type NativeAndAssets = UnionOf<Balances, Assets, NativeFromLeft, NativeOrWithId<u32>, u128>;
|
||||
|
||||
pub type NativeAndAssetsFreezer =
|
||||
UnionOf<Balances, AssetsFreezer, NativeFromLeft, NativeOrWithId<u32>, u128>;
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub struct AssetRewardsBenchmarkHelper;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl BenchmarkHelper<NativeOrWithId<u32>> for AssetRewardsBenchmarkHelper {
|
||||
fn staked_asset() -> NativeOrWithId<u32> {
|
||||
NativeOrWithId::<u32>::WithId(101)
|
||||
}
|
||||
fn reward_asset() -> NativeOrWithId<u32> {
|
||||
NativeOrWithId::<u32>::WithId(102)
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const CreationHoldReason: RuntimeHoldReason =
|
||||
RuntimeHoldReason::StakingRewards(pezpallet_asset_rewards::HoldReason::PoolCreation);
|
||||
}
|
||||
|
||||
impl Config for MockRuntime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type AssetId = NativeOrWithId<u32>;
|
||||
type Balance = <Self as pezpallet_balances::Config>::Balance;
|
||||
type Assets = NativeAndAssets;
|
||||
type AssetsFreezer = NativeAndAssetsFreezer;
|
||||
type PalletId = StakingRewardsPalletId;
|
||||
type CreatePoolOrigin = MockPermissionedOrigin;
|
||||
type WeightInfo = ();
|
||||
type RuntimeFreezeReason = RuntimeFreezeReason;
|
||||
type Consideration = HoldConsideration<
|
||||
u128,
|
||||
Balances,
|
||||
CreationHoldReason,
|
||||
LinearStoragePrice<ConstU128<100>, ConstU128<0>, u128>,
|
||||
>;
|
||||
type BlockNumberProvider = pezframe_system::Pallet<Self>;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = AssetRewardsBenchmarkHelper;
|
||||
}
|
||||
|
||||
pub(crate) fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let mut t = pezframe_system::GenesisConfig::<MockRuntime>::default().build_storage().unwrap();
|
||||
|
||||
pezpallet_assets::GenesisConfig::<MockRuntime, Instance1> {
|
||||
// Genesis assets: id, owner, is_sufficient, min_balance
|
||||
// pub assets: Vec<(T::AssetId, T::AccountId, bool, T::Balance)>,
|
||||
assets: vec![(1, 1, true, 1), (10, 1, true, 1), (20, 1, true, 1)],
|
||||
// Genesis metadata: id, name, symbol, decimals
|
||||
// pub metadata: Vec<(T::AssetId, Vec<u8>, Vec<u8>, u8)>,
|
||||
metadata: vec![
|
||||
(1, b"test".to_vec(), b"TST".to_vec(), 18),
|
||||
(10, b"test10".to_vec(), b"T10".to_vec(), 18),
|
||||
(20, b"test20".to_vec(), b"T20".to_vec(), 18),
|
||||
],
|
||||
// Genesis accounts: id, account_id, balance
|
||||
// pub accounts: Vec<(T::AssetId, T::AccountId, T::Balance)>,
|
||||
accounts: vec![
|
||||
(1, 1, 10000),
|
||||
(1, 2, 20000),
|
||||
(1, 3, 30000),
|
||||
(1, 4, 40000),
|
||||
(1, 10, 40000),
|
||||
(1, 20, 40000),
|
||||
],
|
||||
next_asset_id: None,
|
||||
reserves: vec![],
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let pool_zero_account_id = 31086825966906540362769395565;
|
||||
pezpallet_balances::GenesisConfig::<MockRuntime> {
|
||||
balances: vec![
|
||||
(0, 10000),
|
||||
(1, 10000),
|
||||
(2, 20000),
|
||||
(3, 30000),
|
||||
(4, 40000),
|
||||
(10, 40000),
|
||||
(20, 40000),
|
||||
(pool_zero_account_id, 100_000), // Top up the default pool account id
|
||||
],
|
||||
..Default::default()
|
||||
}
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let mut ext = pezsp_io::TestExternalities::new(t);
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,391 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Autogenerated weights for `pezpallet_asset_rewards`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// frame-omni-bencher
|
||||
// v1
|
||||
// benchmark
|
||||
// pallet
|
||||
// --extrinsic=*
|
||||
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
|
||||
// --pallet=pezpallet_asset_rewards
|
||||
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/HEADER-APACHE2
|
||||
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/pezframe/asset-rewards/src/weights.rs
|
||||
// --wasm-execution=compiled
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --heap-pages=4096
|
||||
// --template=bizinikiwi/.maintain/frame-weight-template.hbs
|
||||
// --no-storage-info
|
||||
// --no-min-squares
|
||||
// --no-median-slopes
|
||||
// --genesis-builder-policy=none
|
||||
// --exclude-pallets=pezpallet_xcm,pezpallet_xcm_benchmarks::fungible,pezpallet_xcm_benchmarks::generic,pezpallet_nomination_pools,pezpallet_remark,pezpallet_transaction_storage,pezpallet_election_provider_multi_block,pezpallet_election_provider_multi_block::signed,pezpallet_election_provider_multi_block::unsigned,pezpallet_election_provider_multi_block::verifier
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for `pezpallet_asset_rewards`.
|
||||
pub trait WeightInfo {
|
||||
fn create_pool() -> Weight;
|
||||
fn stake() -> Weight;
|
||||
fn unstake() -> Weight;
|
||||
fn harvest_rewards() -> Weight;
|
||||
fn set_pool_reward_rate_per_block() -> Weight;
|
||||
fn set_pool_admin() -> Weight;
|
||||
fn set_pool_expiry_block() -> Weight;
|
||||
fn deposit_reward_tokens() -> Weight;
|
||||
fn cleanup_pool() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_asset_rewards` using the Bizinikiwi node and recommended hardware.
|
||||
pub struct BizinikiwiWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// Storage: `Assets::Asset` (r:2 w:0)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::NextPoolId` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Balances::Holds` (r:1 w:1)
|
||||
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(427), added: 2902, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::PoolCost` (r:0 w:1)
|
||||
/// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::Pools` (r:0 w:1)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
fn create_pool() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `169`
|
||||
// Estimated: `6360`
|
||||
// Minimum execution time: 51_207_000 picoseconds.
|
||||
Weight::from_parts(52_880_000, 6360)
|
||||
.saturating_add(T::DbWeight::get().reads(5_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(5_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:1 w:0)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
|
||||
fn stake() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `638`
|
||||
// Estimated: `3615`
|
||||
// Minimum execution time: 44_515_000 picoseconds.
|
||||
Weight::from_parts(45_206_000, 3615)
|
||||
.saturating_add(T::DbWeight::get().reads(5_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:1 w:0)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
|
||||
fn unstake() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `638`
|
||||
// Estimated: `3615`
|
||||
// Minimum execution time: 46_068_000 picoseconds.
|
||||
Weight::from_parts(46_950_000, 3615)
|
||||
.saturating_add(T::DbWeight::get().reads(5_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:0)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:1 w:1)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:2 w:2)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
fn harvest_rewards() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `766`
|
||||
// Estimated: `6208`
|
||||
// Minimum execution time: 60_648_000 picoseconds.
|
||||
Weight::from_parts(62_025_000, 6208)
|
||||
.saturating_add(T::DbWeight::get().reads(5_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
fn set_pool_reward_rate_per_block() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `218`
|
||||
// Estimated: `3615`
|
||||
// Minimum execution time: 12_600_000 picoseconds.
|
||||
Weight::from_parts(13_049_000, 3615)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
fn set_pool_admin() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `218`
|
||||
// Estimated: `3615`
|
||||
// Minimum execution time: 12_074_000 picoseconds.
|
||||
Weight::from_parts(12_344_000, 3615)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
fn set_pool_expiry_block() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `218`
|
||||
// Estimated: `3615`
|
||||
// Minimum execution time: 13_587_000 picoseconds.
|
||||
Weight::from_parts(14_037_000, 3615)
|
||||
.saturating_add(T::DbWeight::get().reads(1_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:0)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:1 w:1)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:2 w:2)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
fn deposit_reward_tokens() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `585`
|
||||
// Estimated: `6208`
|
||||
// Minimum execution time: 51_501_000 picoseconds.
|
||||
Weight::from_parts(52_593_000, 6208)
|
||||
.saturating_add(T::DbWeight::get().reads(5_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::PoolStakers` (r:1 w:0)
|
||||
/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:1 w:1)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:2 w:2)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:2 w:2)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::PoolCost` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Balances::Holds` (r:1 w:1)
|
||||
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(427), added: 2902, mode: `MaxEncodedLen`)
|
||||
fn cleanup_pool() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `943`
|
||||
// Estimated: `6208`
|
||||
// Minimum execution time: 98_758_000 picoseconds.
|
||||
Weight::from_parts(100_771_000, 6208)
|
||||
.saturating_add(T::DbWeight::get().reads(9_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(8_u64))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `Assets::Asset` (r:2 w:0)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::NextPoolId` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::NextPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Balances::Holds` (r:1 w:1)
|
||||
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(427), added: 2902, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::PoolCost` (r:0 w:1)
|
||||
/// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::Pools` (r:0 w:1)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
fn create_pool() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `169`
|
||||
// Estimated: `6360`
|
||||
// Minimum execution time: 51_207_000 picoseconds.
|
||||
Weight::from_parts(52_880_000, 6360)
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(5_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:1 w:0)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
|
||||
fn stake() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `638`
|
||||
// Estimated: `3615`
|
||||
// Minimum execution time: 44_515_000 picoseconds.
|
||||
Weight::from_parts(45_206_000, 3615)
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::Freezes` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::Freezes` (`max_values`: None, `max_size`: Some(105), added: 2580, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:1 w:0)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetsFreezer::FrozenBalances` (r:1 w:1)
|
||||
/// Proof: `AssetsFreezer::FrozenBalances` (`max_values`: None, `max_size`: Some(84), added: 2559, mode: `MaxEncodedLen`)
|
||||
fn unstake() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `638`
|
||||
// Estimated: `3615`
|
||||
// Minimum execution time: 46_068_000 picoseconds.
|
||||
Weight::from_parts(46_950_000, 3615)
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:0)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::PoolStakers` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:1 w:1)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:2 w:2)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
fn harvest_rewards() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `766`
|
||||
// Estimated: `6208`
|
||||
// Minimum execution time: 60_648_000 picoseconds.
|
||||
Weight::from_parts(62_025_000, 6208)
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
fn set_pool_reward_rate_per_block() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `218`
|
||||
// Estimated: `3615`
|
||||
// Minimum execution time: 12_600_000 picoseconds.
|
||||
Weight::from_parts(13_049_000, 3615)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
fn set_pool_admin() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `218`
|
||||
// Estimated: `3615`
|
||||
// Minimum execution time: 12_074_000 picoseconds.
|
||||
Weight::from_parts(12_344_000, 3615)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
fn set_pool_expiry_block() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `218`
|
||||
// Estimated: `3615`
|
||||
// Minimum execution time: 13_587_000 picoseconds.
|
||||
Weight::from_parts(14_037_000, 3615)
|
||||
.saturating_add(RocksDbWeight::get().reads(1_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:0)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:1 w:1)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:2 w:2)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:1 w:1)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
fn deposit_reward_tokens() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `585`
|
||||
// Estimated: `6208`
|
||||
// Minimum execution time: 51_501_000 picoseconds.
|
||||
Weight::from_parts(52_593_000, 6208)
|
||||
.saturating_add(RocksDbWeight::get().reads(5_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(4_u64))
|
||||
}
|
||||
/// Storage: `AssetRewards::Pools` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::Pools` (`max_values`: None, `max_size`: Some(150), added: 2625, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::PoolStakers` (r:1 w:0)
|
||||
/// Proof: `AssetRewards::PoolStakers` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Asset` (r:1 w:1)
|
||||
/// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Assets::Account` (r:2 w:2)
|
||||
/// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`)
|
||||
/// Storage: `System::Account` (r:2 w:2)
|
||||
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
|
||||
/// Storage: `AssetRewards::PoolCost` (r:1 w:1)
|
||||
/// Proof: `AssetRewards::PoolCost` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Balances::Holds` (r:1 w:1)
|
||||
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(427), added: 2902, mode: `MaxEncodedLen`)
|
||||
fn cleanup_pool() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `943`
|
||||
// Estimated: `6208`
|
||||
// Minimum execution time: 98_758_000 picoseconds.
|
||||
Weight::from_parts(100_771_000, 6208)
|
||||
.saturating_add(RocksDbWeight::get().reads(9_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(8_u64))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
[package]
|
||||
name = "pezpallet-assets-freezer"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "MIT-0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Provides freezing features to `pezpallet-assets`"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
frame = { workspace = true, features = ["runtime"] }
|
||||
log = { workspace = true }
|
||||
pezpallet-assets = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-balances = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"frame/std",
|
||||
"log/std",
|
||||
"pezpallet-assets/std",
|
||||
"pezpallet-balances/std",
|
||||
"scale-info/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"frame/runtime-benchmarks",
|
||||
"pezpallet-assets/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"frame/try-runtime",
|
||||
"pezpallet-assets/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,172 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: MIT-0
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
use super::*;
|
||||
use frame::prelude::storage::StorageDoubleMap;
|
||||
use pezpallet_assets::FrozenBalance;
|
||||
|
||||
// Implements [`FrozenBalance`] from [`pezpallet-assets`], so it can understand how much of an
|
||||
// account balance is frozen, and is able to signal to this pallet when to clear the state of an
|
||||
// account.
|
||||
impl<T: Config<I>, I: 'static> FrozenBalance<T::AssetId, T::AccountId, T::Balance>
|
||||
for Pallet<T, I>
|
||||
{
|
||||
fn frozen_balance(asset: T::AssetId, who: &T::AccountId) -> Option<T::Balance> {
|
||||
FrozenBalances::<T, I>::get(asset, who)
|
||||
}
|
||||
|
||||
fn died(asset: T::AssetId, who: &T::AccountId) {
|
||||
defensive_assert!(
|
||||
Freezes::<T, I>::get(asset.clone(), who).is_empty(),
|
||||
"The list of Freezes should be empty before allowing an account to die"
|
||||
);
|
||||
defensive_assert!(
|
||||
FrozenBalances::<T, I>::get(asset.clone(), who).is_none(),
|
||||
"There should not be a frozen balance before allowing to die"
|
||||
);
|
||||
|
||||
FrozenBalances::<T, I>::remove(asset.clone(), who);
|
||||
Freezes::<T, I>::remove(asset, who);
|
||||
}
|
||||
|
||||
fn contains_freezes(asset: T::AssetId) -> bool {
|
||||
Freezes::<T, I>::contains_prefix(asset)
|
||||
}
|
||||
}
|
||||
|
||||
// Implement [`fungibles::Inspect`](pezframe_support::traits::fungibles::Inspect) as it is bound by
|
||||
// [`fungibles::InspectFreeze`](pezframe_support::traits::fungibles::InspectFreeze) and
|
||||
// [`fungibles::MutateFreeze`](pezframe_support::traits::fungibles::MutateFreeze). To do so, we'll
|
||||
// re-export all of `pezpallet-assets` implementation of the same trait.
|
||||
impl<T: Config<I>, I: 'static> Inspect<T::AccountId> for Pallet<T, I> {
|
||||
type AssetId = T::AssetId;
|
||||
type Balance = T::Balance;
|
||||
|
||||
fn total_issuance(asset: Self::AssetId) -> Self::Balance {
|
||||
pezpallet_assets::Pallet::<T, I>::total_issuance(asset)
|
||||
}
|
||||
|
||||
fn minimum_balance(asset: Self::AssetId) -> Self::Balance {
|
||||
pezpallet_assets::Pallet::<T, I>::minimum_balance(asset)
|
||||
}
|
||||
|
||||
fn total_balance(asset: Self::AssetId, who: &T::AccountId) -> Self::Balance {
|
||||
pezpallet_assets::Pallet::<T, I>::total_balance(asset, who)
|
||||
}
|
||||
|
||||
fn balance(asset: Self::AssetId, who: &T::AccountId) -> Self::Balance {
|
||||
pezpallet_assets::Pallet::<T, I>::balance(asset, who)
|
||||
}
|
||||
|
||||
fn reducible_balance(
|
||||
asset: Self::AssetId,
|
||||
who: &T::AccountId,
|
||||
preservation: Preservation,
|
||||
force: Fortitude,
|
||||
) -> Self::Balance {
|
||||
pezpallet_assets::Pallet::<T, I>::reducible_balance(asset, who, preservation, force)
|
||||
}
|
||||
|
||||
fn can_deposit(
|
||||
asset: Self::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
provenance: Provenance,
|
||||
) -> DepositConsequence {
|
||||
pezpallet_assets::Pallet::<T, I>::can_deposit(asset, who, amount, provenance)
|
||||
}
|
||||
|
||||
fn can_withdraw(
|
||||
asset: Self::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> WithdrawConsequence<Self::Balance> {
|
||||
pezpallet_assets::Pallet::<T, I>::can_withdraw(asset, who, amount)
|
||||
}
|
||||
|
||||
fn asset_exists(asset: Self::AssetId) -> bool {
|
||||
pezpallet_assets::Pallet::<T, I>::asset_exists(asset)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> InspectFreeze<T::AccountId> for Pallet<T, I> {
|
||||
type Id = T::RuntimeFreezeReason;
|
||||
|
||||
fn balance_frozen(asset: Self::AssetId, id: &Self::Id, who: &T::AccountId) -> Self::Balance {
|
||||
let freezes = Freezes::<T, I>::get(asset, who);
|
||||
freezes.into_iter().find(|l| &l.id == id).map_or(Zero::zero(), |l| l.amount)
|
||||
}
|
||||
|
||||
fn can_freeze(asset: Self::AssetId, id: &Self::Id, who: &T::AccountId) -> bool {
|
||||
let freezes = Freezes::<T, I>::get(asset, who);
|
||||
!freezes.is_full() || freezes.into_iter().any(|i| i.id == *id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> MutateFreeze<T::AccountId> for Pallet<T, I> {
|
||||
fn set_freeze(
|
||||
asset: Self::AssetId,
|
||||
id: &Self::Id,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
if amount.is_zero() {
|
||||
return Self::thaw(asset, id, who);
|
||||
}
|
||||
let mut freezes = Freezes::<T, I>::get(asset.clone(), who);
|
||||
if let Some(i) = freezes.iter_mut().find(|i| &i.id == id) {
|
||||
i.amount = amount;
|
||||
} else {
|
||||
freezes
|
||||
.try_push(IdAmount { id: *id, amount })
|
||||
.map_err(|_| Error::<T, I>::TooManyFreezes)?;
|
||||
}
|
||||
Self::update_freezes(asset, who, freezes.as_bounded_slice())
|
||||
}
|
||||
|
||||
fn extend_freeze(
|
||||
asset: Self::AssetId,
|
||||
id: &Self::Id,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
if amount.is_zero() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut freezes = Freezes::<T, I>::get(asset.clone(), who);
|
||||
if let Some(i) = freezes.iter_mut().find(|x| &x.id == id) {
|
||||
i.amount = i.amount.max(amount);
|
||||
} else {
|
||||
freezes
|
||||
.try_push(IdAmount { id: *id, amount })
|
||||
.map_err(|_| Error::<T, I>::TooManyFreezes)?;
|
||||
}
|
||||
Self::update_freezes(asset, who, freezes.as_bounded_slice())
|
||||
}
|
||||
|
||||
fn thaw(asset: Self::AssetId, id: &Self::Id, who: &T::AccountId) -> DispatchResult {
|
||||
let mut freezes = Freezes::<T, I>::get(asset.clone(), who);
|
||||
freezes.retain(|f| &f.id != id);
|
||||
Self::update_freezes(asset, who, freezes.as_bounded_slice())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: MIT-0
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//! # Assets Freezer Pallet
|
||||
//!
|
||||
//! A pallet capable of freezing fungibles from `pezpallet-assets`. This is an extension of
|
||||
//! `pezpallet-assets`, wrapping [`fungibles::Inspect`](`Inspect`).
|
||||
//! It implements both
|
||||
//! [`fungibles::freeze::Inspect`](InspectFreeze) and
|
||||
//! [`fungibles::freeze::Mutate`](MutateFreeze). The complexity
|
||||
//! of the operations is `O(n)`. where `n` is the variant count of `RuntimeFreezeReason`.
|
||||
//!
|
||||
//! ## Pallet API
|
||||
//!
|
||||
//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
|
||||
//! including its configuration trait, dispatchables, storage items, events and errors.
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! This pallet provides the following functionality:
|
||||
//!
|
||||
//! - Pallet hooks allowing [`pezpallet-assets`] to know the frozen balance for an account on a given
|
||||
//! asset (see [`pezpallet_assets::FrozenBalance`]).
|
||||
//! - An implementation of [`fungibles::freeze::Inspect`](InspectFreeze) and
|
||||
//! [`fungibles::freeze::Mutate`](MutateFreeze), allowing other pallets to manage freezes for the
|
||||
//! `pezpallet-assets` assets.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use frame::{
|
||||
prelude::*,
|
||||
traits::{
|
||||
fungibles::{Inspect, InspectFreeze, MutateFreeze},
|
||||
tokens::{
|
||||
DepositConsequence, Fortitude, IdAmount, Preservation, Provenance, WithdrawConsequence,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use frame::try_runtime::TryRuntimeError;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
mod impls;
|
||||
|
||||
#[frame::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
#[pallet::config(with_default)]
|
||||
pub trait Config<I: 'static = ()>: pezframe_system::Config + pezpallet_assets::Config<I> {
|
||||
/// The overarching freeze reason.
|
||||
#[pallet::no_default_bounds]
|
||||
type RuntimeFreezeReason: Parameter + Member + MaxEncodedLen + Copy + VariantCount;
|
||||
|
||||
/// The overarching event type.
|
||||
#[pallet::no_default_bounds]
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self, I>>
|
||||
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T, I = ()> {
|
||||
/// Number of freezes on an account would exceed `MaxFreezes`.
|
||||
TooManyFreezes,
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T, I = ()>(_);
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config<I>, I: 'static = ()> {
|
||||
// `who`s frozen balance was increased by `amount`.
|
||||
Frozen { who: T::AccountId, asset_id: T::AssetId, amount: T::Balance },
|
||||
// `who`s frozen balance was decreased by `amount`.
|
||||
Thawed { who: T::AccountId, asset_id: T::AssetId, amount: T::Balance },
|
||||
}
|
||||
|
||||
/// A map that stores freezes applied on an account for a given AssetId.
|
||||
#[pallet::storage]
|
||||
pub type Freezes<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
T::AssetId,
|
||||
Blake2_128Concat,
|
||||
T::AccountId,
|
||||
BoundedVec<
|
||||
IdAmount<T::RuntimeFreezeReason, T::Balance>,
|
||||
VariantCountOf<T::RuntimeFreezeReason>,
|
||||
>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
/// A map that stores the current total frozen balance for every account on a given AssetId.
|
||||
#[pallet::storage]
|
||||
pub type FrozenBalances<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
T::AssetId,
|
||||
Blake2_128Concat,
|
||||
T::AccountId,
|
||||
T::Balance,
|
||||
>;
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn try_state(_: BlockNumberFor<T>) -> Result<(), TryRuntimeError> {
|
||||
Self::do_try_state()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
fn update_freezes(
|
||||
asset: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
freezes: BoundedSlice<
|
||||
IdAmount<T::RuntimeFreezeReason, T::Balance>,
|
||||
VariantCountOf<T::RuntimeFreezeReason>,
|
||||
>,
|
||||
) -> DispatchResult {
|
||||
let prev_frozen = FrozenBalances::<T, I>::get(asset.clone(), who).unwrap_or_default();
|
||||
let after_frozen = freezes.into_iter().map(|f| f.amount).max().unwrap_or_else(Zero::zero);
|
||||
FrozenBalances::<T, I>::set(asset.clone(), who, Some(after_frozen));
|
||||
if freezes.is_empty() {
|
||||
Freezes::<T, I>::remove(asset.clone(), who);
|
||||
FrozenBalances::<T, I>::remove(asset.clone(), who);
|
||||
} else {
|
||||
Freezes::<T, I>::insert(asset.clone(), who, freezes);
|
||||
}
|
||||
if prev_frozen > after_frozen {
|
||||
let amount = prev_frozen.saturating_sub(after_frozen);
|
||||
Self::deposit_event(Event::Thawed { asset_id: asset, who: who.clone(), amount });
|
||||
} else if after_frozen > prev_frozen {
|
||||
let amount = after_frozen.saturating_sub(prev_frozen);
|
||||
Self::deposit_event(Event::Frozen { asset_id: asset, who: who.clone(), amount });
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn do_try_state() -> Result<(), TryRuntimeError> {
|
||||
for (asset, who, _) in FrozenBalances::<T, I>::iter() {
|
||||
let max_frozen_amount =
|
||||
Freezes::<T, I>::get(asset.clone(), who.clone()).iter().map(|l| l.amount).max();
|
||||
|
||||
ensure!(
|
||||
FrozenBalances::<T, I>::get(asset, who) == max_frozen_amount,
|
||||
"The `FrozenAmount` is not equal to the maximum amount in `Freezes` for (`asset`, `who`)"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: MIT-0
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//! Tests mock for `pezpallet-assets-freezer`.
|
||||
|
||||
use crate as pezpallet_assets_freezer;
|
||||
pub use crate::*;
|
||||
use codec::{Compact, Decode, Encode, MaxEncodedLen};
|
||||
use frame::testing_prelude::*;
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
pub type AccountId = u64;
|
||||
pub type Balance = u64;
|
||||
pub type AssetId = u32;
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Assets: pezpallet_assets,
|
||||
AssetsFreezer: pezpallet_assets_freezer,
|
||||
Balances: pezpallet_balances,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type BaseCallFilter = Everything;
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type DbWeight = ();
|
||||
type RuntimeOrigin = RuntimeOrigin;
|
||||
type Nonce = u64;
|
||||
type Hash = H256;
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type BlockHashCount = ConstU64<250>;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = pezpallet_balances::AccountData<u64>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type SystemWeightInfo = ();
|
||||
type SS58Prefix = ();
|
||||
type OnSetCode = ();
|
||||
type MaxConsumers = ConstU32<16>;
|
||||
}
|
||||
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type MaxLocks = ();
|
||||
type MaxReserves = ();
|
||||
type ReserveIdentifier = [u8; 8];
|
||||
type Balance = Balance;
|
||||
type DustRemoval = ();
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ExistentialDeposit = ConstU64<1>;
|
||||
type AccountStore = System;
|
||||
type WeightInfo = ();
|
||||
type FreezeIdentifier = ();
|
||||
type MaxFreezes = ();
|
||||
type RuntimeHoldReason = ();
|
||||
type RuntimeFreezeReason = ();
|
||||
type DoneSlashHandler = ();
|
||||
}
|
||||
|
||||
impl pezpallet_assets::Config for Test {
|
||||
type AssetId = AssetId;
|
||||
type AssetIdParameter = Compact<AssetId>;
|
||||
type ReserveData = ();
|
||||
type AssetDeposit = ConstU64<1>;
|
||||
type Balance = Balance;
|
||||
type AssetAccountDeposit = ConstU64<1>;
|
||||
type MetadataDepositBase = ();
|
||||
type MetadataDepositPerByte = ();
|
||||
type ApprovalDeposit = ();
|
||||
type CreateOrigin = AsEnsureOriginWithArg<pezframe_system::EnsureSigned<u64>>;
|
||||
type ForceOrigin = pezframe_system::EnsureRoot<u64>;
|
||||
type StringLimit = ConstU32<32>;
|
||||
type Extra = ();
|
||||
type RemoveItemsLimit = ConstU32<10>;
|
||||
type CallbackHandle = ();
|
||||
type Currency = Balances;
|
||||
type Holder = ();
|
||||
type Freezer = AssetsFreezer;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type WeightInfo = ();
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Encode,
|
||||
MaxEncodedLen,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
TypeInfo,
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
)]
|
||||
pub enum DummyFreezeReason {
|
||||
Governance,
|
||||
Staking,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl VariantCount for DummyFreezeReason {
|
||||
// Intentionally set below the actual count of variants, to allow testing for `can_freeze`
|
||||
const VARIANT_COUNT: u32 = 2;
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type RuntimeFreezeReason = DummyFreezeReason;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
pub fn new_test_ext(execute: impl FnOnce()) -> TestExternalities {
|
||||
let t = RuntimeGenesisConfig {
|
||||
assets: pezpallet_assets::GenesisConfig {
|
||||
assets: vec![(1, 0, true, 1)],
|
||||
metadata: vec![],
|
||||
accounts: vec![(1, 1, 100)],
|
||||
next_asset_id: None,
|
||||
reserves: vec![],
|
||||
},
|
||||
system: Default::default(),
|
||||
balances: Default::default(),
|
||||
}
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
let mut ext: TestExternalities = t.into();
|
||||
ext.execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
execute();
|
||||
#[cfg(feature = "try-runtime")]
|
||||
assert_ok!(AssetsFreezer::do_try_state());
|
||||
});
|
||||
|
||||
ext
|
||||
}
|
||||
@@ -0,0 +1,312 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: MIT-0
|
||||
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
// this software and associated documentation files (the "Software"), to deal in
|
||||
// the Software without restriction, including without limitation the rights to
|
||||
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
// of the Software, and to permit persons to whom the Software is furnished to do
|
||||
// so, subject to the following conditions:
|
||||
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//! Tests for pezpallet-assets-freezer.
|
||||
|
||||
use crate::mock::{self, *};
|
||||
|
||||
use codec::Compact;
|
||||
use frame::testing_prelude::*;
|
||||
use pezpallet_assets::FrozenBalance;
|
||||
|
||||
const WHO: AccountId = 1;
|
||||
const ASSET_ID: mock::AssetId = 1;
|
||||
|
||||
fn test_set_freeze(id: DummyFreezeReason, amount: mock::Balance) {
|
||||
let mut freezes = Freezes::<Test>::get(ASSET_ID, WHO);
|
||||
|
||||
if let Some(i) = freezes.iter_mut().find(|l| l.id == id) {
|
||||
i.amount = amount;
|
||||
} else {
|
||||
freezes
|
||||
.try_push(IdAmount { id, amount })
|
||||
.expect("freeze is added without exceeding bounds; qed");
|
||||
}
|
||||
|
||||
assert_ok!(AssetsFreezer::update_freezes(ASSET_ID, &WHO, freezes.as_bounded_slice()));
|
||||
}
|
||||
|
||||
fn test_thaw(id: DummyFreezeReason) {
|
||||
let mut freezes = Freezes::<Test>::get(ASSET_ID, WHO);
|
||||
freezes.retain(|l| l.id != id);
|
||||
|
||||
assert_ok!(AssetsFreezer::update_freezes(ASSET_ID, &WHO, freezes.as_bounded_slice()));
|
||||
}
|
||||
|
||||
mod impl_frozen_balance {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn frozen_balance_works() {
|
||||
new_test_ext(|| {
|
||||
assert_eq!(AssetsFreezer::frozen_balance(ASSET_ID, &WHO), None);
|
||||
test_set_freeze(DummyFreezeReason::Governance, 1);
|
||||
assert_eq!(AssetsFreezer::frozen_balance(ASSET_ID, &WHO), Some(1u64));
|
||||
test_set_freeze(DummyFreezeReason::Staking, 3);
|
||||
assert_eq!(AssetsFreezer::frozen_balance(ASSET_ID, &WHO), Some(3u64));
|
||||
test_set_freeze(DummyFreezeReason::Governance, 2);
|
||||
assert_eq!(AssetsFreezer::frozen_balance(ASSET_ID, &WHO), Some(3u64));
|
||||
// also test thawing works to reduce a balance, and finally thawing everything resets to
|
||||
// None
|
||||
test_thaw(DummyFreezeReason::Governance);
|
||||
assert_eq!(AssetsFreezer::frozen_balance(ASSET_ID, &WHO), Some(3u64));
|
||||
test_thaw(DummyFreezeReason::Staking);
|
||||
assert_eq!(AssetsFreezer::frozen_balance(ASSET_ID, &WHO), None);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "The list of Freezes should be empty before allowing an account to die"]
|
||||
fn died_fails_if_freezes_exist() {
|
||||
new_test_ext(|| {
|
||||
test_set_freeze(DummyFreezeReason::Governance, 1);
|
||||
AssetsFreezer::died(ASSET_ID, &WHO);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn died_works() {
|
||||
new_test_ext(|| {
|
||||
test_set_freeze(DummyFreezeReason::Governance, 1);
|
||||
test_thaw(DummyFreezeReason::Governance);
|
||||
AssetsFreezer::died(ASSET_ID, &WHO);
|
||||
assert!(FrozenBalances::<Test>::get(ASSET_ID, WHO).is_none());
|
||||
assert!(Freezes::<Test>::get(ASSET_ID, WHO).is_empty());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
mod impl_inspect_freeze {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn balance_frozen_works() {
|
||||
new_test_ext(|| {
|
||||
assert_eq!(
|
||||
AssetsFreezer::balance_frozen(ASSET_ID, &DummyFreezeReason::Governance, &WHO),
|
||||
0u64
|
||||
);
|
||||
test_set_freeze(DummyFreezeReason::Governance, 1);
|
||||
assert_eq!(
|
||||
AssetsFreezer::balance_frozen(ASSET_ID, &DummyFreezeReason::Governance, &WHO),
|
||||
1u64
|
||||
);
|
||||
test_set_freeze(DummyFreezeReason::Staking, 3);
|
||||
assert_eq!(
|
||||
AssetsFreezer::balance_frozen(ASSET_ID, &DummyFreezeReason::Staking, &WHO),
|
||||
3u64
|
||||
);
|
||||
test_set_freeze(DummyFreezeReason::Staking, 2);
|
||||
assert_eq!(
|
||||
AssetsFreezer::balance_frozen(ASSET_ID, &DummyFreezeReason::Staking, &WHO),
|
||||
2u64
|
||||
);
|
||||
// also test thawing works to reduce a balance, and finally thawing everything resets to
|
||||
// 0
|
||||
test_thaw(DummyFreezeReason::Governance);
|
||||
assert_eq!(
|
||||
AssetsFreezer::balance_frozen(ASSET_ID, &DummyFreezeReason::Governance, &WHO),
|
||||
0u64
|
||||
);
|
||||
test_thaw(DummyFreezeReason::Staking);
|
||||
assert_eq!(
|
||||
AssetsFreezer::balance_frozen(ASSET_ID, &DummyFreezeReason::Staking, &WHO),
|
||||
0u64
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// This tests it's not possible to freeze once the freezes [`BoundedVec`] is full. This is,
|
||||
/// the lenght of the vec is equal to [`Config::MaxFreezes`].
|
||||
/// This test assumes a mock configuration where this parameter is set to `2`.
|
||||
#[test]
|
||||
fn can_freeze_works() {
|
||||
new_test_ext(|| {
|
||||
test_set_freeze(DummyFreezeReason::Governance, 1);
|
||||
assert!(AssetsFreezer::can_freeze(ASSET_ID, &DummyFreezeReason::Staking, &WHO));
|
||||
test_set_freeze(DummyFreezeReason::Staking, 1);
|
||||
assert!(!AssetsFreezer::can_freeze(ASSET_ID, &DummyFreezeReason::Other, &WHO));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
mod impl_mutate_freeze {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn set_freeze_works() {
|
||||
new_test_ext(|| {
|
||||
assert_eq!(
|
||||
Assets::reducible_balance(
|
||||
ASSET_ID,
|
||||
&WHO,
|
||||
Preservation::Preserve,
|
||||
Fortitude::Polite,
|
||||
),
|
||||
99
|
||||
);
|
||||
assert_ok!(AssetsFreezer::set_freeze(
|
||||
ASSET_ID,
|
||||
&DummyFreezeReason::Governance,
|
||||
&WHO,
|
||||
10
|
||||
));
|
||||
assert_eq!(
|
||||
Assets::reducible_balance(
|
||||
ASSET_ID,
|
||||
&WHO,
|
||||
Preservation::Preserve,
|
||||
Fortitude::Polite,
|
||||
),
|
||||
90
|
||||
);
|
||||
System::assert_last_event(
|
||||
Event::<Test>::Frozen { asset_id: ASSET_ID, who: WHO, amount: 10 }.into(),
|
||||
);
|
||||
assert_ok!(AssetsFreezer::set_freeze(
|
||||
ASSET_ID,
|
||||
&DummyFreezeReason::Governance,
|
||||
&WHO,
|
||||
8
|
||||
));
|
||||
assert_eq!(
|
||||
Assets::reducible_balance(
|
||||
ASSET_ID,
|
||||
&WHO,
|
||||
Preservation::Preserve,
|
||||
Fortitude::Polite,
|
||||
),
|
||||
92
|
||||
);
|
||||
System::assert_last_event(
|
||||
Event::<Test>::Thawed { asset_id: ASSET_ID, who: WHO, amount: 2 }.into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extend_freeze_works() {
|
||||
new_test_ext(|| {
|
||||
assert_ok!(AssetsFreezer::set_freeze(
|
||||
ASSET_ID,
|
||||
&DummyFreezeReason::Governance,
|
||||
&WHO,
|
||||
10
|
||||
));
|
||||
assert_storage_noop!(assert_ok!(AssetsFreezer::extend_freeze(
|
||||
ASSET_ID,
|
||||
&DummyFreezeReason::Governance,
|
||||
&WHO,
|
||||
8
|
||||
)));
|
||||
System::assert_last_event(
|
||||
Event::<Test>::Frozen { asset_id: ASSET_ID, who: WHO, amount: 10 }.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
Assets::reducible_balance(
|
||||
ASSET_ID,
|
||||
&WHO,
|
||||
Preservation::Preserve,
|
||||
Fortitude::Polite,
|
||||
),
|
||||
90
|
||||
);
|
||||
assert_ok!(AssetsFreezer::extend_freeze(
|
||||
ASSET_ID,
|
||||
&DummyFreezeReason::Governance,
|
||||
&WHO,
|
||||
11
|
||||
));
|
||||
System::assert_last_event(
|
||||
Event::<Test>::Frozen { asset_id: ASSET_ID, who: WHO, amount: 1 }.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
Assets::reducible_balance(
|
||||
ASSET_ID,
|
||||
&WHO,
|
||||
Preservation::Preserve,
|
||||
Fortitude::Polite,
|
||||
),
|
||||
89
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thaw_works() {
|
||||
new_test_ext(|| {
|
||||
assert_ok!(AssetsFreezer::set_freeze(
|
||||
ASSET_ID,
|
||||
&DummyFreezeReason::Governance,
|
||||
&WHO,
|
||||
10
|
||||
));
|
||||
System::assert_has_event(
|
||||
Event::<Test>::Frozen { asset_id: ASSET_ID, who: WHO, amount: 10 }.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
Assets::reducible_balance(
|
||||
ASSET_ID,
|
||||
&WHO,
|
||||
Preservation::Preserve,
|
||||
Fortitude::Polite,
|
||||
),
|
||||
90
|
||||
);
|
||||
assert_ok!(AssetsFreezer::thaw(ASSET_ID, &DummyFreezeReason::Governance, &WHO));
|
||||
System::assert_has_event(
|
||||
Event::<Test>::Thawed { asset_id: ASSET_ID, who: WHO, amount: 10 }.into(),
|
||||
);
|
||||
assert_eq!(
|
||||
Assets::reducible_balance(
|
||||
ASSET_ID,
|
||||
&WHO,
|
||||
Preservation::Preserve,
|
||||
Fortitude::Polite,
|
||||
),
|
||||
99
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
mod with_pallet_assets {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn frozen_balance_affects_balance_transferring() {
|
||||
new_test_ext(|| {
|
||||
assert_ok!(AssetsFreezer::set_freeze(
|
||||
ASSET_ID,
|
||||
&DummyFreezeReason::Governance,
|
||||
&WHO,
|
||||
20
|
||||
));
|
||||
assert_noop!(
|
||||
Assets::transfer(RuntimeOrigin::signed(WHO), Compact(ASSET_ID), 2, 81),
|
||||
pezpallet_assets::Error::<Test>::BalanceLow,
|
||||
);
|
||||
assert_ok!(Assets::transfer(RuntimeOrigin::signed(WHO), Compact(ASSET_ID), 2, 80));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
[package]
|
||||
name = "pezpallet-assets-holder"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Provides holding features to `pezpallet-assets`"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
log = { workspace = true }
|
||||
pezpallet-assets = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-balances = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-assets/std",
|
||||
"pezpallet-balances/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-assets/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-assets/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,290 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::*;
|
||||
|
||||
use pezframe_support::traits::{
|
||||
fungibles::{Dust, Inspect, InspectHold, MutateHold, Unbalanced, UnbalancedHold},
|
||||
tokens::{
|
||||
DepositConsequence, Fortitude, Precision, Preservation, Provenance, WithdrawConsequence,
|
||||
},
|
||||
};
|
||||
use pezpallet_assets::BalanceOnHold;
|
||||
use pezsp_runtime::{
|
||||
traits::{CheckedAdd, CheckedSub, Zero},
|
||||
ArithmeticError,
|
||||
};
|
||||
use storage::StorageDoubleMap;
|
||||
|
||||
// Implements [`BalanceOnHold`] from [`pezpallet-assets`], so it can understand whether there's some
|
||||
// balance on hold for an asset account, and is able to signal to this pallet when to clear the
|
||||
// state of an account.
|
||||
impl<T: Config<I>, I: 'static> BalanceOnHold<T::AssetId, T::AccountId, T::Balance>
|
||||
for Pallet<T, I>
|
||||
{
|
||||
fn balance_on_hold(asset: T::AssetId, who: &T::AccountId) -> Option<T::Balance> {
|
||||
BalancesOnHold::<T, I>::get(asset, who)
|
||||
}
|
||||
|
||||
fn died(asset: T::AssetId, who: &T::AccountId) {
|
||||
defensive_assert!(
|
||||
Holds::<T, I>::get(asset.clone(), who).is_empty(),
|
||||
"The list of Holds should be empty before allowing an account to die"
|
||||
);
|
||||
defensive_assert!(
|
||||
BalancesOnHold::<T, I>::get(asset.clone(), who).is_none(),
|
||||
"The should not be a balance on hold before allowing to die"
|
||||
);
|
||||
|
||||
Holds::<T, I>::remove(asset.clone(), who);
|
||||
BalancesOnHold::<T, I>::remove(asset, who);
|
||||
}
|
||||
|
||||
fn contains_holds(asset: T::AssetId) -> bool {
|
||||
Holds::<T, I>::contains_prefix(asset)
|
||||
}
|
||||
}
|
||||
|
||||
// Implement [`fungibles::Inspect`](pezframe_support::traits::fungibles::Inspect) as it is bound by
|
||||
// [`fungibles::InspectHold`](pezframe_support::traits::fungibles::InspectHold) and
|
||||
// [`fungibles::MutateHold`](pezframe_support::traits::fungibles::MutateHold). To do so, we'll
|
||||
// re-export all of `pezpallet-assets` implementation of the same trait.
|
||||
impl<T: Config<I>, I: 'static> Inspect<T::AccountId> for Pallet<T, I> {
|
||||
type AssetId = T::AssetId;
|
||||
type Balance = T::Balance;
|
||||
|
||||
fn total_issuance(asset: Self::AssetId) -> Self::Balance {
|
||||
pezpallet_assets::Pallet::<T, I>::total_issuance(asset)
|
||||
}
|
||||
|
||||
fn minimum_balance(asset: Self::AssetId) -> Self::Balance {
|
||||
pezpallet_assets::Pallet::<T, I>::minimum_balance(asset)
|
||||
}
|
||||
|
||||
fn total_balance(asset: Self::AssetId, who: &T::AccountId) -> Self::Balance {
|
||||
pezpallet_assets::Pallet::<T, I>::total_balance(asset, who)
|
||||
}
|
||||
|
||||
fn balance(asset: Self::AssetId, who: &T::AccountId) -> Self::Balance {
|
||||
pezpallet_assets::Pallet::<T, I>::balance(asset, who)
|
||||
}
|
||||
|
||||
fn reducible_balance(
|
||||
asset: Self::AssetId,
|
||||
who: &T::AccountId,
|
||||
preservation: Preservation,
|
||||
force: Fortitude,
|
||||
) -> Self::Balance {
|
||||
pezpallet_assets::Pallet::<T, I>::reducible_balance(asset, who, preservation, force)
|
||||
}
|
||||
|
||||
fn can_deposit(
|
||||
asset: Self::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
provenance: Provenance,
|
||||
) -> DepositConsequence {
|
||||
pezpallet_assets::Pallet::<T, I>::can_deposit(asset, who, amount, provenance)
|
||||
}
|
||||
|
||||
fn can_withdraw(
|
||||
asset: Self::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> WithdrawConsequence<Self::Balance> {
|
||||
pezpallet_assets::Pallet::<T, I>::can_withdraw(asset, who, amount)
|
||||
}
|
||||
|
||||
fn asset_exists(asset: Self::AssetId) -> bool {
|
||||
pezpallet_assets::Pallet::<T, I>::asset_exists(asset)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> InspectHold<T::AccountId> for Pallet<T, I> {
|
||||
type Reason = T::RuntimeHoldReason;
|
||||
|
||||
fn total_balance_on_hold(asset: Self::AssetId, who: &T::AccountId) -> Self::Balance {
|
||||
BalancesOnHold::<T, I>::get(asset, who).unwrap_or_else(Zero::zero)
|
||||
}
|
||||
|
||||
fn balance_on_hold(
|
||||
asset: Self::AssetId,
|
||||
reason: &Self::Reason,
|
||||
who: &T::AccountId,
|
||||
) -> Self::Balance {
|
||||
Holds::<T, I>::get(asset, who)
|
||||
.iter()
|
||||
.find(|x| &x.id == reason)
|
||||
.map(|x| x.amount)
|
||||
.unwrap_or_else(Zero::zero)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Unbalanced<T::AccountId> for Pallet<T, I> {
|
||||
fn handle_dust(dust: Dust<T::AccountId, Self>) {
|
||||
let Dust(id, balance) = dust;
|
||||
pezpallet_assets::Pallet::<T, I>::handle_dust(Dust(id, balance));
|
||||
}
|
||||
|
||||
fn write_balance(
|
||||
asset: Self::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> Result<Option<Self::Balance>, DispatchError> {
|
||||
pezpallet_assets::Pallet::<T, I>::write_balance(asset, who, amount)
|
||||
}
|
||||
|
||||
fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance) {
|
||||
pezpallet_assets::Pallet::<T, I>::set_total_issuance(asset, amount)
|
||||
}
|
||||
|
||||
fn decrease_balance(
|
||||
asset: Self::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
preservation: Preservation,
|
||||
force: Fortitude,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
pezpallet_assets::Pallet::<T, I>::decrease_balance(
|
||||
asset,
|
||||
who,
|
||||
amount,
|
||||
precision,
|
||||
preservation,
|
||||
force,
|
||||
)
|
||||
}
|
||||
|
||||
fn increase_balance(
|
||||
asset: Self::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
pezpallet_assets::Pallet::<T, I>::increase_balance(asset, who, amount, precision)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> UnbalancedHold<T::AccountId> for Pallet<T, I> {
|
||||
fn set_balance_on_hold(
|
||||
asset: Self::AssetId,
|
||||
reason: &Self::Reason,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
let mut holds = Holds::<T, I>::get(asset.clone(), who);
|
||||
let amount_on_hold =
|
||||
BalancesOnHold::<T, I>::get(asset.clone(), who).unwrap_or_else(Zero::zero);
|
||||
|
||||
let amount_on_hold = if amount.is_zero() {
|
||||
if let Some(pos) = holds.iter().position(|x| &x.id == reason) {
|
||||
let item = &mut holds[pos];
|
||||
let amount = item.amount;
|
||||
|
||||
holds.swap_remove(pos);
|
||||
amount_on_hold.checked_sub(&amount).ok_or(ArithmeticError::Underflow)?
|
||||
} else {
|
||||
amount_on_hold
|
||||
}
|
||||
} else {
|
||||
let (increase, delta) = if let Some(pos) = holds.iter().position(|x| &x.id == reason) {
|
||||
let item = &mut holds[pos];
|
||||
let (increase, delta) =
|
||||
(amount > item.amount, item.amount.max(amount) - item.amount.min(amount));
|
||||
|
||||
item.amount = amount;
|
||||
if item.amount.is_zero() {
|
||||
holds.swap_remove(pos);
|
||||
}
|
||||
|
||||
(increase, delta)
|
||||
} else {
|
||||
holds
|
||||
.try_push(IdAmount { id: *reason, amount })
|
||||
.map_err(|_| Error::<T, I>::TooManyHolds)?;
|
||||
(true, amount)
|
||||
};
|
||||
|
||||
let amount_on_hold = if increase {
|
||||
amount_on_hold.checked_add(&delta).ok_or(ArithmeticError::Overflow)?
|
||||
} else {
|
||||
amount_on_hold.checked_sub(&delta).ok_or(ArithmeticError::Underflow)?
|
||||
};
|
||||
|
||||
amount_on_hold
|
||||
};
|
||||
|
||||
if !holds.is_empty() {
|
||||
Holds::<T, I>::insert(asset.clone(), who, holds);
|
||||
} else {
|
||||
Holds::<T, I>::remove(asset.clone(), who);
|
||||
}
|
||||
|
||||
if amount_on_hold.is_zero() {
|
||||
BalancesOnHold::<T, I>::remove(asset.clone(), who);
|
||||
} else {
|
||||
BalancesOnHold::<T, I>::insert(asset.clone(), who, amount_on_hold);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> MutateHold<T::AccountId> for Pallet<T, I> {
|
||||
fn done_hold(
|
||||
asset_id: Self::AssetId,
|
||||
reason: &Self::Reason,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) {
|
||||
Self::deposit_event(Event::<T, I>::Held {
|
||||
asset_id,
|
||||
who: who.clone(),
|
||||
reason: *reason,
|
||||
amount,
|
||||
});
|
||||
}
|
||||
|
||||
fn done_release(
|
||||
asset_id: Self::AssetId,
|
||||
reason: &Self::Reason,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) {
|
||||
Self::deposit_event(Event::<T, I>::Released {
|
||||
asset_id,
|
||||
who: who.clone(),
|
||||
reason: *reason,
|
||||
amount,
|
||||
});
|
||||
}
|
||||
|
||||
fn done_burn_held(
|
||||
asset_id: Self::AssetId,
|
||||
reason: &Self::Reason,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
) {
|
||||
Self::deposit_event(Event::<T, I>::Burned {
|
||||
asset_id,
|
||||
who: who.clone(),
|
||||
reason: *reason,
|
||||
amount,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! # Assets Holder Pallet
|
||||
//!
|
||||
//! A pallet capable of holding fungibles from `pezpallet-assets`. This is an extension of
|
||||
//! `pezpallet-assets`, wrapping [`fungibles::Inspect`](`pezframe_support::traits::fungibles::Inspect`).
|
||||
//! It implements both
|
||||
//! [`fungibles::hold::Inspect`](pezframe_support::traits::fungibles::hold::Inspect),
|
||||
//! [`fungibles::hold::Mutate`](pezframe_support::traits::fungibles::hold::Mutate), and especially
|
||||
//! [`fungibles::hold::Unbalanced`](pezframe_support::traits::fungibles::hold::Unbalanced). The
|
||||
//! complexity of the operations is `O(1)`.
|
||||
//!
|
||||
//! ## Pallet API
|
||||
//!
|
||||
//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
|
||||
//! including its configuration trait, dispatchables, storage items, events and errors.
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! This pallet provides the following functionality:
|
||||
//!
|
||||
//! - Pallet hooks allowing [`pezpallet-assets`] to know the balance on hold for an account on a given
|
||||
//! asset (see [`pezpallet_assets::BalanceOnHold`]).
|
||||
//! - An implementation of
|
||||
//! [`fungibles::hold::Inspect`](pezframe_support::traits::fungibles::hold::Inspect),
|
||||
//! [`fungibles::hold::Mutate`](pezframe_support::traits::fungibles::hold::Mutate) and
|
||||
//! [`fungibles::hold::Unbalanced`](pezframe_support::traits::fungibles::hold::Unbalanced), allowing
|
||||
//! other pallets to manage holds for the `pezpallet-assets` assets.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::*,
|
||||
traits::{tokens::IdAmount, VariantCount, VariantCountOf},
|
||||
BoundedVec,
|
||||
};
|
||||
use pezframe_system::pezpallet_prelude::BlockNumberFor;
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
mod impl_fungibles;
|
||||
|
||||
#[pezframe_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
#[pallet::config(with_default)]
|
||||
pub trait Config<I: 'static = ()>:
|
||||
pezframe_system::Config + pezpallet_assets::Config<I, Holder = Pallet<Self, I>>
|
||||
{
|
||||
/// The overarching freeze reason.
|
||||
#[pallet::no_default_bounds]
|
||||
type RuntimeHoldReason: Parameter + Member + MaxEncodedLen + Copy + VariantCount;
|
||||
|
||||
/// The overarching event type.
|
||||
#[pallet::no_default_bounds]
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self, I>>
|
||||
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T, I = ()> {
|
||||
/// Number of holds on an account would exceed the count of `RuntimeHoldReason`.
|
||||
TooManyHolds,
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T, I = ()>(_);
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config<I>, I: 'static = ()> {
|
||||
/// `who`s balance on hold was increased by `amount`.
|
||||
Held {
|
||||
who: T::AccountId,
|
||||
asset_id: T::AssetId,
|
||||
reason: T::RuntimeHoldReason,
|
||||
amount: T::Balance,
|
||||
},
|
||||
/// `who`s balance on hold was decreased by `amount`.
|
||||
Released {
|
||||
who: T::AccountId,
|
||||
asset_id: T::AssetId,
|
||||
reason: T::RuntimeHoldReason,
|
||||
amount: T::Balance,
|
||||
},
|
||||
/// `who`s balance on hold was burned by `amount`.
|
||||
Burned {
|
||||
who: T::AccountId,
|
||||
asset_id: T::AssetId,
|
||||
reason: T::RuntimeHoldReason,
|
||||
amount: T::Balance,
|
||||
},
|
||||
}
|
||||
|
||||
/// A map that stores holds applied on an account for a given AssetId.
|
||||
#[pallet::storage]
|
||||
pub(super) type Holds<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
T::AssetId,
|
||||
Blake2_128Concat,
|
||||
T::AccountId,
|
||||
BoundedVec<
|
||||
IdAmount<T::RuntimeHoldReason, T::Balance>,
|
||||
VariantCountOf<T::RuntimeHoldReason>,
|
||||
>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
/// A map that stores the current total balance on hold for every account on a given AssetId.
|
||||
#[pallet::storage]
|
||||
pub(super) type BalancesOnHold<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
T::AssetId,
|
||||
Blake2_128Concat,
|
||||
T::AccountId,
|
||||
T::Balance,
|
||||
>;
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn try_state(_: BlockNumberFor<T>) -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
Self::do_try_state()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Pallet<T, I> {
|
||||
#[cfg(any(test, feature = "try-runtime"))]
|
||||
fn do_try_state() -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
use pezsp_runtime::{
|
||||
traits::{CheckedAdd, Zero},
|
||||
ArithmeticError,
|
||||
};
|
||||
|
||||
for (asset, who, balance_on_hold) in BalancesOnHold::<T, I>::iter() {
|
||||
ensure!(balance_on_hold != Zero::zero(), "zero on hold must not be in state");
|
||||
|
||||
let mut amount_from_holds: T::Balance = Zero::zero();
|
||||
for l in Holds::<T, I>::get(asset.clone(), who.clone()).iter() {
|
||||
ensure!(l.amount != Zero::zero(), "zero amount is invalid");
|
||||
amount_from_holds =
|
||||
amount_from_holds.checked_add(&l.amount).ok_or(ArithmeticError::Overflow)?;
|
||||
}
|
||||
|
||||
pezframe_support::ensure!(
|
||||
balance_on_hold == amount_from_holds,
|
||||
"The `BalancesOnHold` amount is not equal to the sum of `Holds` for (`asset`, `who`)"
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Tests mock for `pezpallet-assets-freezer`.
|
||||
|
||||
use crate as pezpallet_assets_holder;
|
||||
pub use crate::*;
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use pezframe_support::{derive_impl, traits::AsEnsureOriginWithArg};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::BuildStorage;
|
||||
|
||||
pub type AccountId = <Test as pezframe_system::Config>::AccountId;
|
||||
pub type Balance = <Test as pezpallet_balances::Config>::Balance;
|
||||
pub type AssetId = <Test as pezpallet_assets::Config>::AssetId;
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
#[pezframe_support::runtime]
|
||||
mod runtime {
|
||||
#[runtime::runtime]
|
||||
#[runtime::derive(
|
||||
RuntimeCall,
|
||||
RuntimeEvent,
|
||||
RuntimeError,
|
||||
RuntimeOrigin,
|
||||
RuntimeTask,
|
||||
RuntimeHoldReason,
|
||||
RuntimeFreezeReason
|
||||
)]
|
||||
pub struct Test;
|
||||
|
||||
#[runtime::pezpallet_index(0)]
|
||||
pub type System = pezframe_system;
|
||||
#[runtime::pezpallet_index(10)]
|
||||
pub type Balances = pezpallet_balances;
|
||||
#[runtime::pezpallet_index(20)]
|
||||
pub type Assets = pezpallet_assets;
|
||||
#[runtime::pezpallet_index(21)]
|
||||
pub type AssetsHolder = pezpallet_assets_holder;
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u64>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig as pezpallet_balances::DefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_assets::config_preludes::TestDefaultConfig as pezpallet_assets::DefaultConfig)]
|
||||
impl pezpallet_assets::Config for Test {
|
||||
// type AssetAccountDeposit = ConstU64<1>;
|
||||
type CreateOrigin = AsEnsureOriginWithArg<pezframe_system::EnsureSigned<u64>>;
|
||||
type ForceOrigin = pezframe_system::EnsureRoot<u64>;
|
||||
type Currency = Balances;
|
||||
type Holder = AssetsHolder;
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Encode,
|
||||
MaxEncodedLen,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
TypeInfo,
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
)]
|
||||
pub enum DummyHoldReason {
|
||||
Governance,
|
||||
Staking,
|
||||
Other,
|
||||
}
|
||||
|
||||
impl VariantCount for DummyHoldReason {
|
||||
// Intentionally set below the actual count of variants, to allow testing for `can_freeze`
|
||||
const VARIANT_COUNT: u32 = 3;
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type RuntimeHoldReason = DummyHoldReason;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
}
|
||||
|
||||
pub fn new_test_ext(execute: impl FnOnce()) -> pezsp_io::TestExternalities {
|
||||
let t = RuntimeGenesisConfig {
|
||||
assets: pezpallet_assets::GenesisConfig {
|
||||
assets: vec![(1, 0, true, 1)],
|
||||
metadata: vec![],
|
||||
accounts: vec![(1, 1, 100)],
|
||||
next_asset_id: None,
|
||||
reserves: vec![],
|
||||
},
|
||||
system: Default::default(),
|
||||
balances: Default::default(),
|
||||
}
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
let mut ext: pezsp_io::TestExternalities = t.into();
|
||||
ext.execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
execute();
|
||||
pezframe_support::assert_ok!(AssetsHolder::do_try_state());
|
||||
});
|
||||
|
||||
ext
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Tests for pezpallet-assets-holder.
|
||||
|
||||
use crate::mock::*;
|
||||
|
||||
use pezframe_support::{
|
||||
assert_noop, assert_ok,
|
||||
traits::tokens::fungibles::{Inspect, InspectHold, MutateHold, UnbalancedHold},
|
||||
};
|
||||
use pezpallet_assets::BalanceOnHold;
|
||||
|
||||
const WHO: AccountId = 1;
|
||||
const ASSET_ID: AssetId = 1;
|
||||
|
||||
fn test_hold(id: DummyHoldReason, amount: Balance) {
|
||||
assert_ok!(AssetsHolder::set_balance_on_hold(ASSET_ID, &id, &WHO, amount));
|
||||
}
|
||||
|
||||
fn test_release(id: DummyHoldReason) {
|
||||
assert_ok!(AssetsHolder::set_balance_on_hold(ASSET_ID, &id, &WHO, 0));
|
||||
}
|
||||
|
||||
mod impl_balance_on_hold {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn balance_on_hold_works() {
|
||||
new_test_ext(|| {
|
||||
assert_eq!(
|
||||
<AssetsHolder as BalanceOnHold<_, _, _>>::balance_on_hold(ASSET_ID, &WHO),
|
||||
None
|
||||
);
|
||||
test_hold(DummyHoldReason::Governance, 1);
|
||||
assert_eq!(
|
||||
<AssetsHolder as BalanceOnHold<_, _, _>>::balance_on_hold(ASSET_ID, &WHO),
|
||||
Some(1u64)
|
||||
);
|
||||
test_hold(DummyHoldReason::Staking, 3);
|
||||
assert_eq!(
|
||||
<AssetsHolder as BalanceOnHold<_, _, _>>::balance_on_hold(ASSET_ID, &WHO),
|
||||
Some(4u64)
|
||||
);
|
||||
test_hold(DummyHoldReason::Governance, 2);
|
||||
assert_eq!(
|
||||
<AssetsHolder as BalanceOnHold<_, _, _>>::balance_on_hold(ASSET_ID, &WHO),
|
||||
Some(5u64)
|
||||
);
|
||||
// also test releasing works to reduce a balance, and finally releasing everything
|
||||
// resets to None
|
||||
test_release(DummyHoldReason::Governance);
|
||||
assert_eq!(
|
||||
<AssetsHolder as BalanceOnHold<_, _, _>>::balance_on_hold(ASSET_ID, &WHO),
|
||||
Some(3u64)
|
||||
);
|
||||
test_release(DummyHoldReason::Staking);
|
||||
assert_eq!(
|
||||
<AssetsHolder as BalanceOnHold<_, _, _>>::balance_on_hold(ASSET_ID, &WHO),
|
||||
None
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic = "The list of Holds should be empty before allowing an account to die"]
|
||||
fn died_fails_if_holds_exist() {
|
||||
new_test_ext(|| {
|
||||
test_hold(DummyHoldReason::Governance, 1);
|
||||
AssetsHolder::died(ASSET_ID, &WHO);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn died_works() {
|
||||
new_test_ext(|| {
|
||||
test_hold(DummyHoldReason::Governance, 1);
|
||||
test_release(DummyHoldReason::Governance);
|
||||
AssetsHolder::died(ASSET_ID, &WHO);
|
||||
assert!(BalancesOnHold::<Test>::get(ASSET_ID, WHO).is_none());
|
||||
assert!(Holds::<Test>::get(ASSET_ID, WHO).is_empty());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
mod impl_hold_inspect {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn total_balance_on_hold_works() {
|
||||
new_test_ext(|| {
|
||||
assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 0u64);
|
||||
test_hold(DummyHoldReason::Governance, 1);
|
||||
assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 1u64);
|
||||
test_hold(DummyHoldReason::Staking, 3);
|
||||
assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 4u64);
|
||||
test_hold(DummyHoldReason::Governance, 2);
|
||||
assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 5u64);
|
||||
// also test release to reduce a balance, and finally releasing everything resets to
|
||||
// 0
|
||||
test_release(DummyHoldReason::Governance);
|
||||
assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 3u64);
|
||||
test_release(DummyHoldReason::Staking);
|
||||
assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 0u64);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn balance_on_hold_works() {
|
||||
new_test_ext(|| {
|
||||
assert_eq!(
|
||||
<AssetsHolder as InspectHold<_>>::balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO
|
||||
),
|
||||
0u64
|
||||
);
|
||||
test_hold(DummyHoldReason::Governance, 1);
|
||||
assert_eq!(
|
||||
<AssetsHolder as InspectHold<_>>::balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO
|
||||
),
|
||||
1u64
|
||||
);
|
||||
test_hold(DummyHoldReason::Staking, 3);
|
||||
assert_eq!(
|
||||
<AssetsHolder as InspectHold<_>>::balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Staking,
|
||||
&WHO
|
||||
),
|
||||
3u64
|
||||
);
|
||||
test_hold(DummyHoldReason::Staking, 2);
|
||||
assert_eq!(
|
||||
<AssetsHolder as InspectHold<_>>::balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Staking,
|
||||
&WHO
|
||||
),
|
||||
2u64
|
||||
);
|
||||
// also test release to reduce a balance, and finally releasing everything resets to
|
||||
// 0
|
||||
test_release(DummyHoldReason::Governance);
|
||||
assert_eq!(
|
||||
<AssetsHolder as InspectHold<_>>::balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO
|
||||
),
|
||||
0u64
|
||||
);
|
||||
test_release(DummyHoldReason::Staking);
|
||||
assert_eq!(
|
||||
<AssetsHolder as InspectHold<_>>::balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Staking,
|
||||
&WHO
|
||||
),
|
||||
0u64
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
mod impl_hold_unbalanced {
|
||||
use super::*;
|
||||
|
||||
// Note: Tests for `handle_dust`, `write_balance`, `set_total_issuance`, `decrease_balance`
|
||||
// and `increase_balance` are intentionally left out without testing, since:
|
||||
// 1. It is expected these methods are tested within `pezpallet-assets`, and
|
||||
// 2. There are no valid cases that can be directly asserted using those methods in
|
||||
// the scope of this pallet.
|
||||
|
||||
#[test]
|
||||
fn set_balance_on_hold_works() {
|
||||
new_test_ext(|| {
|
||||
assert_eq!(Holds::<Test>::get(ASSET_ID, WHO).to_vec(), vec![]);
|
||||
assert_eq!(BalancesOnHold::<Test>::get(ASSET_ID, WHO), None);
|
||||
// Adding balance on hold works
|
||||
assert_ok!(AssetsHolder::set_balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO,
|
||||
1
|
||||
));
|
||||
assert_eq!(
|
||||
Holds::<Test>::get(ASSET_ID, WHO).to_vec(),
|
||||
vec![IdAmount { id: DummyHoldReason::Governance, amount: 1 }]
|
||||
);
|
||||
assert_eq!(BalancesOnHold::<Test>::get(ASSET_ID, WHO), Some(1));
|
||||
// Increasing hold works
|
||||
assert_ok!(AssetsHolder::set_balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO,
|
||||
3
|
||||
));
|
||||
assert_eq!(
|
||||
Holds::<Test>::get(ASSET_ID, WHO).to_vec(),
|
||||
vec![IdAmount { id: DummyHoldReason::Governance, amount: 3 }]
|
||||
);
|
||||
assert_eq!(BalancesOnHold::<Test>::get(ASSET_ID, WHO), Some(3));
|
||||
// Adding new balance on hold works
|
||||
assert_ok!(AssetsHolder::set_balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Staking,
|
||||
&WHO,
|
||||
2
|
||||
));
|
||||
assert_eq!(
|
||||
Holds::<Test>::get(ASSET_ID, WHO).to_vec(),
|
||||
vec![
|
||||
IdAmount { id: DummyHoldReason::Governance, amount: 3 },
|
||||
IdAmount { id: DummyHoldReason::Staking, amount: 2 }
|
||||
]
|
||||
);
|
||||
assert_eq!(BalancesOnHold::<Test>::get(ASSET_ID, WHO), Some(5));
|
||||
|
||||
// Note: Assertion skipped to meet @gavofyork's suggestion of matching the number of
|
||||
// variant count with the number of enum's variants.
|
||||
// // Adding more than max holds fails
|
||||
// assert_noop!(
|
||||
// AssetsHolder::set_balance_on_hold(ASSET_ID, &DummyHoldReason::Other, &WHO, 1),
|
||||
// Error::<Test>::TooManyHolds
|
||||
// );
|
||||
|
||||
// Decreasing balance on hold works
|
||||
assert_ok!(AssetsHolder::set_balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Staking,
|
||||
&WHO,
|
||||
1
|
||||
));
|
||||
assert_eq!(
|
||||
Holds::<Test>::get(ASSET_ID, WHO).to_vec(),
|
||||
vec![
|
||||
IdAmount { id: DummyHoldReason::Governance, amount: 3 },
|
||||
IdAmount { id: DummyHoldReason::Staking, amount: 1 }
|
||||
]
|
||||
);
|
||||
assert_eq!(BalancesOnHold::<Test>::get(ASSET_ID, WHO), Some(4));
|
||||
// Decreasing until removal of balance on hold works
|
||||
assert_ok!(AssetsHolder::set_balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO,
|
||||
0
|
||||
));
|
||||
assert_eq!(
|
||||
Holds::<Test>::get(ASSET_ID, WHO).to_vec(),
|
||||
vec![IdAmount { id: DummyHoldReason::Staking, amount: 1 }]
|
||||
);
|
||||
assert_eq!(BalancesOnHold::<Test>::get(ASSET_ID, WHO), Some(1));
|
||||
// Clearing ol all holds works
|
||||
assert_ok!(AssetsHolder::set_balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Staking,
|
||||
&WHO,
|
||||
0
|
||||
));
|
||||
assert_eq!(Holds::<Test>::get(ASSET_ID, WHO).to_vec(), vec![]);
|
||||
assert_eq!(BalancesOnHold::<Test>::get(ASSET_ID, WHO), None);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
mod impl_hold_mutate {
|
||||
use super::*;
|
||||
use pezframe_support::traits::tokens::{Fortitude, Precision, Preservation};
|
||||
use pezsp_runtime::TokenError;
|
||||
|
||||
#[test]
|
||||
fn hold_works() {
|
||||
super::new_test_ext(|| {
|
||||
// Holding some `amount` would decrease the asset account balance and change the
|
||||
// reducible balance, while total issuance is preserved.
|
||||
assert_ok!(AssetsHolder::hold(ASSET_ID, &DummyHoldReason::Governance, &WHO, 10));
|
||||
assert_eq!(Assets::balance(ASSET_ID, &WHO), 90);
|
||||
// Reducible balance is tested once to ensure token balance model is compliant.
|
||||
assert_eq!(
|
||||
Assets::reducible_balance(
|
||||
ASSET_ID,
|
||||
&WHO,
|
||||
Preservation::Expendable,
|
||||
Fortitude::Force
|
||||
),
|
||||
89
|
||||
);
|
||||
assert_eq!(
|
||||
<AssetsHolder as InspectHold<_>>::balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO
|
||||
),
|
||||
10
|
||||
);
|
||||
assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 10);
|
||||
// Holding preserves `total_balance`
|
||||
assert_eq!(Assets::total_balance(ASSET_ID, &WHO), 100);
|
||||
// Holding preserves `total_issuance`
|
||||
assert_eq!(Assets::total_issuance(ASSET_ID), 100);
|
||||
|
||||
// Increasing the amount on hold for the same reason has the same effect as described
|
||||
// above in `set_balance_on_hold_works`, while total issuance is preserved.
|
||||
// Consideration: holding for an amount `x` will increase the already amount on hold by
|
||||
// `x`.
|
||||
assert_ok!(AssetsHolder::hold(ASSET_ID, &DummyHoldReason::Governance, &WHO, 20));
|
||||
assert_eq!(Assets::balance(ASSET_ID, &WHO), 70);
|
||||
assert_eq!(
|
||||
<AssetsHolder as InspectHold<_>>::balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO
|
||||
),
|
||||
30
|
||||
);
|
||||
assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 30);
|
||||
assert_eq!(Assets::total_issuance(ASSET_ID), 100);
|
||||
|
||||
// Holding some amount for a different reason has the same effect as described above in
|
||||
// `set_balance_on_hold_works`, while total issuance is preserved.
|
||||
assert_ok!(AssetsHolder::hold(ASSET_ID, &DummyHoldReason::Staking, &WHO, 20));
|
||||
assert_eq!(Assets::balance(ASSET_ID, &WHO), 50);
|
||||
assert_eq!(
|
||||
<AssetsHolder as InspectHold<_>>::balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Staking,
|
||||
&WHO
|
||||
),
|
||||
20
|
||||
);
|
||||
assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 50);
|
||||
assert_eq!(Assets::total_issuance(ASSET_ID), 100);
|
||||
});
|
||||
}
|
||||
|
||||
fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
super::new_test_ext(|| {
|
||||
assert_ok!(AssetsHolder::hold(ASSET_ID, &DummyHoldReason::Governance, &WHO, 30));
|
||||
assert_ok!(AssetsHolder::hold(ASSET_ID, &DummyHoldReason::Staking, &WHO, 20));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn release_works() {
|
||||
// Releasing up to some amount will increase the balance by the released
|
||||
// amount, while preserving total issuance.
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(AssetsHolder::release(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO,
|
||||
20,
|
||||
Precision::Exact,
|
||||
));
|
||||
assert_eq!(
|
||||
<AssetsHolder as InspectHold<_>>::balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO
|
||||
),
|
||||
10
|
||||
);
|
||||
assert_eq!(Assets::balance(ASSET_ID, WHO), 70);
|
||||
});
|
||||
|
||||
// Releasing over the max amount on hold with `BestEffort` will increase the
|
||||
// balance by the previously amount on hold, while preserving total issuance.
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(AssetsHolder::release(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO,
|
||||
31,
|
||||
Precision::BestEffort,
|
||||
));
|
||||
assert_eq!(
|
||||
<AssetsHolder as InspectHold<_>>::balance_on_hold(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO
|
||||
),
|
||||
0
|
||||
);
|
||||
assert_eq!(Assets::balance(ASSET_ID, WHO), 80);
|
||||
});
|
||||
|
||||
// Releasing over the max amount on hold with `Exact` will fail.
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(
|
||||
AssetsHolder::release(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO,
|
||||
31,
|
||||
Precision::Exact,
|
||||
),
|
||||
TokenError::FundsUnavailable
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn burn_held_works() {
|
||||
// Burning works, reducing total issuance and `total_balance`.
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(AssetsHolder::burn_held(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO,
|
||||
1,
|
||||
Precision::BestEffort,
|
||||
Fortitude::Polite
|
||||
));
|
||||
assert_eq!(Assets::total_balance(ASSET_ID, &WHO), 99);
|
||||
assert_eq!(Assets::total_issuance(ASSET_ID), 99);
|
||||
});
|
||||
|
||||
// Burning by an amount up to the balance on hold with `Exact` works, reducing balance on
|
||||
// hold up to the given amount.
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(AssetsHolder::burn_held(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO,
|
||||
10,
|
||||
Precision::Exact,
|
||||
Fortitude::Polite
|
||||
));
|
||||
assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 40);
|
||||
assert_eq!(Assets::balance(ASSET_ID, WHO), 50);
|
||||
});
|
||||
|
||||
// Burning by an amount over the balance on hold with `BestEffort` works, reducing balance
|
||||
// on hold up to the given amount.
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(AssetsHolder::burn_held(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO,
|
||||
31,
|
||||
Precision::BestEffort,
|
||||
Fortitude::Polite
|
||||
));
|
||||
assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 20);
|
||||
assert_eq!(Assets::balance(ASSET_ID, WHO), 50);
|
||||
});
|
||||
|
||||
// Burning by an amount over the balance on hold with `Exact` fails.
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_noop!(
|
||||
AssetsHolder::burn_held(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO,
|
||||
31,
|
||||
Precision::Exact,
|
||||
Fortitude::Polite
|
||||
),
|
||||
TokenError::FundsUnavailable
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn burn_all_held_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// Burning all balance on hold works as burning passing it as amount with `BestEffort`
|
||||
assert_ok!(AssetsHolder::burn_all_held(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO,
|
||||
Precision::BestEffort,
|
||||
Fortitude::Polite,
|
||||
));
|
||||
assert_eq!(AssetsHolder::total_balance_on_hold(ASSET_ID, &WHO), 20);
|
||||
assert_eq!(Assets::balance(ASSET_ID, WHO), 50);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn done_held_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
System::assert_has_event(
|
||||
Event::<Test>::Held {
|
||||
who: WHO,
|
||||
asset_id: ASSET_ID,
|
||||
reason: DummyHoldReason::Governance,
|
||||
amount: 30,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn done_release_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(AssetsHolder::release(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO,
|
||||
31,
|
||||
Precision::BestEffort
|
||||
));
|
||||
System::assert_has_event(
|
||||
Event::<Test>::Released {
|
||||
who: WHO,
|
||||
asset_id: ASSET_ID,
|
||||
reason: DummyHoldReason::Governance,
|
||||
amount: 30,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn done_burn_held_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_ok!(AssetsHolder::burn_all_held(
|
||||
ASSET_ID,
|
||||
&DummyHoldReason::Governance,
|
||||
&WHO,
|
||||
Precision::BestEffort,
|
||||
Fortitude::Polite,
|
||||
));
|
||||
System::assert_has_event(
|
||||
Event::<Test>::Burned {
|
||||
who: WHO,
|
||||
asset_id: ASSET_ID,
|
||||
reason: DummyHoldReason::Governance,
|
||||
amount: 30,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
[package]
|
||||
name = "pezpallet-assets"
|
||||
version = "29.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME asset management pallet"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
impl-trait-for-tuples = { workspace = true }
|
||||
log = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
# Needed for various traits. In our case, `OnFinalize`.
|
||||
pezsp-runtime = { workspace = true }
|
||||
# Needed for type-safe access to storage DB.
|
||||
pezframe-support = { workspace = true }
|
||||
# `system` module provides us with all sorts of useful stuff and macros depend on it being around.
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-balances/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,124 @@
|
||||
# Assets Module
|
||||
|
||||
A simple, secure module for dealing with fungible assets.
|
||||
|
||||
## Overview
|
||||
|
||||
The Assets module provides functionality for asset management of fungible asset classes with a fixed supply, including:
|
||||
|
||||
* Asset Issuance
|
||||
* Asset Transfer
|
||||
* Asset Destruction
|
||||
|
||||
To use it in your runtime, you need to implement the assets
|
||||
[`assets::Config`](https://docs.rs/pezpallet-assets/latest/pallet_assets/pallet/trait.Config.html).
|
||||
|
||||
The supported dispatchable functions are documented in the
|
||||
[`assets::Call`](https://docs.rs/pezpallet-assets/latest/pallet_assets/pallet/enum.Call.html) enum.
|
||||
|
||||
### Terminology
|
||||
|
||||
* **Asset issuance:** The creation of a new asset, whose total supply will belong to the account that issues the asset.
|
||||
* **Asset transfer:** The action of transferring assets from one account to another.
|
||||
* **Asset destruction:** The process of an account removing its entire holding of an asset.
|
||||
* **Fungible asset:** An asset whose units are interchangeable.
|
||||
* **Non-fungible asset:** An asset for which each unit has unique characteristics.
|
||||
|
||||
### Goals
|
||||
|
||||
The assets system in Bizinikiwi is designed to make the following possible:
|
||||
|
||||
* Issue a unique asset to its creator's account.
|
||||
* Move assets between accounts.
|
||||
* Remove an account's balance of an asset when requested by that account's owner and update the asset's total supply.
|
||||
|
||||
## Interface
|
||||
|
||||
### Dispatchable Functions
|
||||
|
||||
* `issue` - Issues the total supply of a new fungible asset to the account of the caller of the function.
|
||||
* `transfer` - Transfers an `amount` of units of fungible asset `id` from the balance of the function caller's account
|
||||
(`origin`) to a `target` account.
|
||||
* `destroy` - Destroys the entire holding of a fungible asset `id` associated with the account that called the function.
|
||||
|
||||
Please refer to the [`Call`](https://docs.rs/pezpallet-assets/latest/pallet_assets/enum.Call.html) enum and its associated
|
||||
variants for documentation on each function.
|
||||
|
||||
### Public Functions
|
||||
<!-- Original author of descriptions: @gavofyork -->
|
||||
|
||||
* `balance` - Get the asset `id` balance of `who`.
|
||||
* `total_supply` - Get the total supply of an asset `id`.
|
||||
|
||||
Please refer to the [`Pallet`](https://docs.rs/pezpallet-assets/latest/pallet_assets/pallet/struct.Pallet.html) struct for
|
||||
details on publicly available functions.
|
||||
|
||||
## Usage
|
||||
|
||||
The following example shows how to use the Assets module in your runtime by exposing public functions to:
|
||||
|
||||
* Issue a new fungible asset for a token distribution event (airdrop).
|
||||
* Query the fungible asset holding balance of an account.
|
||||
* Query the total supply of a fungible asset that has been issued.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
Import the Assets module and types and derive your runtime's configuration traits from the Assets module trait.
|
||||
|
||||
### Simple Code Snippet
|
||||
|
||||
```rust
|
||||
use pallet_assets as assets;
|
||||
use sp_runtime::ArithmeticError;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config + assets::Config {}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
pub fn issue_token_airdrop(origin: OriginFor<T>) -> DispatchResult {
|
||||
let sender = ensure_signed(origin)?;
|
||||
|
||||
const ACCOUNT_ALICE: u64 = 1;
|
||||
const ACCOUNT_BOB: u64 = 2;
|
||||
const COUNT_AIRDROP_RECIPIENTS: u64 = 2;
|
||||
const TOKENS_FIXED_SUPPLY: u64 = 100;
|
||||
|
||||
ensure!(!COUNT_AIRDROP_RECIPIENTS.is_zero(), ArithmeticError::DivisionByZero);
|
||||
|
||||
let asset_id = Self::next_asset_id();
|
||||
|
||||
<NextAssetId<T>>::mutate(|asset_id| *asset_id += 1);
|
||||
<Balances<T>>::insert((asset_id, &ACCOUNT_ALICE), TOKENS_FIXED_SUPPLY / COUNT_AIRDROP_RECIPIENTS);
|
||||
<Balances<T>>::insert((asset_id, &ACCOUNT_BOB), TOKENS_FIXED_SUPPLY / COUNT_AIRDROP_RECIPIENTS);
|
||||
<TotalSupply<T>>::insert(asset_id, TOKENS_FIXED_SUPPLY);
|
||||
|
||||
Self::deposit_event(Event::Issued(asset_id, sender, TOKENS_FIXED_SUPPLY));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Assumptions
|
||||
|
||||
Below are assumptions that must be held when using this module. If any of them are violated, the behavior of this
|
||||
module is undefined.
|
||||
|
||||
* The total count of assets should be less than `Config::AssetId::max_value()`.
|
||||
|
||||
## Related Modules
|
||||
|
||||
* [`System`](https://docs.rs/pezframe-system/latest/frame_system/)
|
||||
* [`Support`](https://docs.rs/pezframe-support/latest/frame_support/)
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,62 @@
|
||||
[package]
|
||||
name = "pezpallet-assets-precompiles"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Provides precompiles for `pezpallet-assets`"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
ethereum-standards = { workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezpallet-assets = { workspace = true }
|
||||
pezpallet-revive = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
codec = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezpallet-balances = { workspace = true }
|
||||
scale-info = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-assets/std",
|
||||
"pezpallet-balances/std",
|
||||
"pezpallet-revive/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-assets/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-revive/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-assets/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-revive/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,325 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use core::marker::PhantomData;
|
||||
use ethereum_standards::{
|
||||
IERC20,
|
||||
IERC20::{IERC20Calls, IERC20Events},
|
||||
};
|
||||
use pezpallet_assets::{weights::WeightInfo, Call, Config, TransferFlags};
|
||||
use pezpallet_revive::precompiles::{
|
||||
alloy::{
|
||||
self,
|
||||
primitives::IntoLogData,
|
||||
sol_types::{Revert, SolCall},
|
||||
},
|
||||
AddressMapper, AddressMatcher, Error, Ext, Precompile, RuntimeCosts, H160, H256,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// Mean of extracting the asset id from the precompile address.
|
||||
pub trait AssetIdExtractor {
|
||||
type AssetId;
|
||||
/// Extracts the asset id from the address.
|
||||
fn asset_id_from_address(address: &[u8; 20]) -> Result<Self::AssetId, Error>;
|
||||
}
|
||||
|
||||
/// The configuration of a pezpallet-assets precompile.
|
||||
pub trait AssetPrecompileConfig {
|
||||
/// The Address matcher used by the precompile.
|
||||
const MATCHER: AddressMatcher;
|
||||
|
||||
/// The [`AssetIdExtractor`] used by the precompile.
|
||||
type AssetIdExtractor: AssetIdExtractor;
|
||||
}
|
||||
|
||||
/// An `AssetIdExtractor` that stores the asset id directly inside the address.
|
||||
pub struct InlineAssetIdExtractor;
|
||||
|
||||
impl AssetIdExtractor for InlineAssetIdExtractor {
|
||||
type AssetId = u32;
|
||||
fn asset_id_from_address(addr: &[u8; 20]) -> Result<Self::AssetId, Error> {
|
||||
let bytes: [u8; 4] = addr[0..4].try_into().expect("slice is 4 bytes; qed");
|
||||
let index = u32::from_be_bytes(bytes);
|
||||
return Ok(index.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// A precompile configuration that uses a prefix [`AddressMatcher`].
|
||||
pub struct InlineIdConfig<const PREFIX: u16>;
|
||||
|
||||
impl<const P: u16> AssetPrecompileConfig for InlineIdConfig<P> {
|
||||
const MATCHER: AddressMatcher = AddressMatcher::Prefix(core::num::NonZero::new(P).unwrap());
|
||||
type AssetIdExtractor = InlineAssetIdExtractor;
|
||||
}
|
||||
|
||||
/// An ERC20 precompile.
|
||||
pub struct ERC20<Runtime, PrecompileConfig, Instance = ()> {
|
||||
_phantom: PhantomData<(Runtime, PrecompileConfig, Instance)>,
|
||||
}
|
||||
|
||||
impl<Runtime, PrecompileConfig, Instance: 'static> Precompile
|
||||
for ERC20<Runtime, PrecompileConfig, Instance>
|
||||
where
|
||||
PrecompileConfig: AssetPrecompileConfig,
|
||||
Runtime: crate::Config<Instance> + pezpallet_revive::Config,
|
||||
<<PrecompileConfig as AssetPrecompileConfig>::AssetIdExtractor as AssetIdExtractor>::AssetId:
|
||||
Into<<Runtime as Config<Instance>>::AssetId>,
|
||||
Call<Runtime, Instance>: Into<<Runtime as pezpallet_revive::Config>::RuntimeCall>,
|
||||
alloy::primitives::U256: TryInto<<Runtime as Config<Instance>>::Balance>,
|
||||
|
||||
// Note can't use From as it's not implemented for alloy::primitives::U256 for unsigned types
|
||||
alloy::primitives::U256: TryFrom<<Runtime as Config<Instance>>::Balance>,
|
||||
{
|
||||
type T = Runtime;
|
||||
type Interface = IERC20::IERC20Calls;
|
||||
const MATCHER: AddressMatcher = PrecompileConfig::MATCHER;
|
||||
const HAS_CONTRACT_INFO: bool = false;
|
||||
|
||||
fn call(
|
||||
address: &[u8; 20],
|
||||
input: &Self::Interface,
|
||||
env: &mut impl Ext<T = Self::T>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
let asset_id = PrecompileConfig::AssetIdExtractor::asset_id_from_address(address)?.into();
|
||||
|
||||
match input {
|
||||
IERC20Calls::transfer(_) | IERC20Calls::approve(_) | IERC20Calls::transferFrom(_)
|
||||
if env.is_read_only() =>
|
||||
Err(Error::Error(pezpallet_revive::Error::<Self::T>::StateChangeDenied.into())),
|
||||
|
||||
IERC20Calls::transfer(call) => Self::transfer(asset_id, call, env),
|
||||
IERC20Calls::totalSupply(_) => Self::total_supply(asset_id, env),
|
||||
IERC20Calls::balanceOf(call) => Self::balance_of(asset_id, call, env),
|
||||
IERC20Calls::allowance(call) => Self::allowance(asset_id, call, env),
|
||||
IERC20Calls::approve(call) => Self::approve(asset_id, call, env),
|
||||
IERC20Calls::transferFrom(call) => Self::transfer_from(asset_id, call, env),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ERR_INVALID_CALLER: &str = "Invalid caller";
|
||||
const ERR_BALANCE_CONVERSION_FAILED: &str = "Balance conversion failed";
|
||||
|
||||
impl<Runtime, PrecompileConfig, Instance: 'static> ERC20<Runtime, PrecompileConfig, Instance>
|
||||
where
|
||||
PrecompileConfig: AssetPrecompileConfig,
|
||||
Runtime: crate::Config<Instance> + pezpallet_revive::Config,
|
||||
<<PrecompileConfig as AssetPrecompileConfig>::AssetIdExtractor as AssetIdExtractor>::AssetId:
|
||||
Into<<Runtime as Config<Instance>>::AssetId>,
|
||||
Call<Runtime, Instance>: Into<<Runtime as pezpallet_revive::Config>::RuntimeCall>,
|
||||
alloy::primitives::U256: TryInto<<Runtime as Config<Instance>>::Balance>,
|
||||
|
||||
// Note can't use From as it's not implemented for alloy::primitives::U256 for unsigned types
|
||||
alloy::primitives::U256: TryFrom<<Runtime as Config<Instance>>::Balance>,
|
||||
{
|
||||
/// Get the caller as an `H160` address.
|
||||
fn caller(env: &mut impl Ext<T = Runtime>) -> Result<H160, Error> {
|
||||
env.caller()
|
||||
.account_id()
|
||||
.map(<Runtime as pezpallet_revive::Config>::AddressMapper::to_address)
|
||||
.map_err(|_| Error::Revert(Revert { reason: ERR_INVALID_CALLER.into() }))
|
||||
}
|
||||
|
||||
/// Convert a `U256` value to the balance type of the pallet.
|
||||
fn to_balance(
|
||||
value: alloy::primitives::U256,
|
||||
) -> Result<<Runtime as Config<Instance>>::Balance, Error> {
|
||||
value
|
||||
.try_into()
|
||||
.map_err(|_| Error::Revert(Revert { reason: ERR_BALANCE_CONVERSION_FAILED.into() }))
|
||||
}
|
||||
|
||||
/// Convert a balance to a `U256` value.
|
||||
/// Note this is needed cause From is not implemented for unsigned integer types
|
||||
fn to_u256(
|
||||
value: <Runtime as Config<Instance>>::Balance,
|
||||
) -> Result<alloy::primitives::U256, Error> {
|
||||
alloy::primitives::U256::try_from(value)
|
||||
.map_err(|_| Error::Revert(Revert { reason: ERR_BALANCE_CONVERSION_FAILED.into() }))
|
||||
}
|
||||
|
||||
/// Deposit an event to the runtime.
|
||||
fn deposit_event(env: &mut impl Ext<T = Runtime>, event: IERC20Events) -> Result<(), Error> {
|
||||
let (topics, data) = event.into_log_data().split();
|
||||
let topics = topics.into_iter().map(|v| H256(v.0)).collect::<Vec<_>>();
|
||||
env.gas_meter_mut().charge(RuntimeCosts::DepositEvent {
|
||||
num_topic: topics.len() as u32,
|
||||
len: topics.len() as u32,
|
||||
})?;
|
||||
env.deposit_event(topics, data.to_vec());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Execute the transfer call.
|
||||
fn transfer(
|
||||
asset_id: <Runtime as Config<Instance>>::AssetId,
|
||||
call: &IERC20::transferCall,
|
||||
env: &mut impl Ext<T = Runtime>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
env.charge(<Runtime as Config<Instance>>::WeightInfo::transfer())?;
|
||||
|
||||
let from = Self::caller(env)?;
|
||||
let dest = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(
|
||||
&call.to.into_array().into(),
|
||||
);
|
||||
|
||||
let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false };
|
||||
pezpallet_assets::Pallet::<Runtime, Instance>::do_transfer(
|
||||
asset_id,
|
||||
&<Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&from),
|
||||
&dest,
|
||||
Self::to_balance(call.value)?,
|
||||
None,
|
||||
f,
|
||||
)?;
|
||||
|
||||
Self::deposit_event(
|
||||
env,
|
||||
IERC20Events::Transfer(IERC20::Transfer {
|
||||
from: from.0.into(),
|
||||
to: call.to,
|
||||
value: call.value,
|
||||
}),
|
||||
)?;
|
||||
|
||||
return Ok(IERC20::transferCall::abi_encode_returns(&true));
|
||||
}
|
||||
|
||||
/// Execute the total supply call.
|
||||
fn total_supply(
|
||||
asset_id: <Runtime as Config<Instance>>::AssetId,
|
||||
env: &mut impl Ext<T = Runtime>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
use pezframe_support::traits::fungibles::Inspect;
|
||||
env.charge(<Runtime as Config<Instance>>::WeightInfo::total_issuance())?;
|
||||
|
||||
let value =
|
||||
Self::to_u256(pezpallet_assets::Pallet::<Runtime, Instance>::total_issuance(asset_id))?;
|
||||
return Ok(IERC20::totalSupplyCall::abi_encode_returns(&value));
|
||||
}
|
||||
|
||||
/// Execute the balance_of call.
|
||||
fn balance_of(
|
||||
asset_id: <Runtime as Config<Instance>>::AssetId,
|
||||
call: &IERC20::balanceOfCall,
|
||||
env: &mut impl Ext<T = Runtime>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
env.charge(<Runtime as Config<Instance>>::WeightInfo::balance())?;
|
||||
let account = call.account.into_array().into();
|
||||
let account = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&account);
|
||||
let value =
|
||||
Self::to_u256(pezpallet_assets::Pallet::<Runtime, Instance>::balance(asset_id, account))?;
|
||||
return Ok(IERC20::balanceOfCall::abi_encode_returns(&value));
|
||||
}
|
||||
|
||||
/// Execute the allowance call.
|
||||
fn allowance(
|
||||
asset_id: <Runtime as Config<Instance>>::AssetId,
|
||||
call: &IERC20::allowanceCall,
|
||||
env: &mut impl Ext<T = Runtime>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
env.charge(<Runtime as Config<Instance>>::WeightInfo::allowance())?;
|
||||
use pezframe_support::traits::fungibles::approvals::Inspect;
|
||||
let owner = call.owner.into_array().into();
|
||||
let owner = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&owner);
|
||||
|
||||
let spender = call.spender.into_array().into();
|
||||
let spender = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&spender);
|
||||
let value = Self::to_u256(pezpallet_assets::Pallet::<Runtime, Instance>::allowance(
|
||||
asset_id, &owner, &spender,
|
||||
))?;
|
||||
|
||||
return Ok(IERC20::balanceOfCall::abi_encode_returns(&value));
|
||||
}
|
||||
|
||||
/// Execute the approve call.
|
||||
fn approve(
|
||||
asset_id: <Runtime as Config<Instance>>::AssetId,
|
||||
call: &IERC20::approveCall,
|
||||
env: &mut impl Ext<T = Runtime>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
env.charge(<Runtime as Config<Instance>>::WeightInfo::approve_transfer())?;
|
||||
let owner = Self::caller(env)?;
|
||||
let spender = call.spender.into_array().into();
|
||||
let spender = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&spender);
|
||||
|
||||
pezpallet_assets::Pallet::<Runtime, Instance>::do_approve_transfer(
|
||||
asset_id,
|
||||
&<Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&owner),
|
||||
&spender,
|
||||
Self::to_balance(call.value)?,
|
||||
)?;
|
||||
|
||||
Self::deposit_event(
|
||||
env,
|
||||
IERC20Events::Approval(IERC20::Approval {
|
||||
owner: owner.0.into(),
|
||||
spender: call.spender,
|
||||
value: call.value,
|
||||
}),
|
||||
)?;
|
||||
|
||||
return Ok(IERC20::approveCall::abi_encode_returns(&true));
|
||||
}
|
||||
|
||||
/// Execute the transfer_from call.
|
||||
fn transfer_from(
|
||||
asset_id: <Runtime as Config<Instance>>::AssetId,
|
||||
call: &IERC20::transferFromCall,
|
||||
env: &mut impl Ext<T = Runtime>,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
env.charge(<Runtime as Config<Instance>>::WeightInfo::transfer_approved())?;
|
||||
let spender = Self::caller(env)?;
|
||||
let spender = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&spender);
|
||||
|
||||
let from = call.from.into_array().into();
|
||||
let from = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&from);
|
||||
|
||||
let to = call.to.into_array().into();
|
||||
let to = <Runtime as pezpallet_revive::Config>::AddressMapper::to_account_id(&to);
|
||||
|
||||
pezpallet_assets::Pallet::<Runtime, Instance>::do_transfer_approved(
|
||||
asset_id,
|
||||
&from,
|
||||
&spender,
|
||||
&to,
|
||||
Self::to_balance(call.value)?,
|
||||
)?;
|
||||
|
||||
Self::deposit_event(
|
||||
env,
|
||||
IERC20Events::Transfer(IERC20::Transfer {
|
||||
from: call.from,
|
||||
to: call.to,
|
||||
value: call.value,
|
||||
}),
|
||||
)?;
|
||||
|
||||
return Ok(IERC20::transferFromCall::abi_encode_returns(&true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Tests mock for `pezpallet-assets-freezer`.
|
||||
|
||||
pub use super::*;
|
||||
use pezframe_support::{derive_impl, traits::AsEnsureOriginWithArg};
|
||||
use pezsp_runtime::BuildStorage;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
#[pezframe_support::runtime]
|
||||
mod runtime {
|
||||
#[runtime::runtime]
|
||||
#[runtime::derive(
|
||||
RuntimeCall,
|
||||
RuntimeEvent,
|
||||
RuntimeError,
|
||||
RuntimeOrigin,
|
||||
RuntimeTask,
|
||||
RuntimeHoldReason,
|
||||
RuntimeFreezeReason
|
||||
)]
|
||||
pub struct Test;
|
||||
|
||||
#[runtime::pezpallet_index(0)]
|
||||
pub type System = pezframe_system;
|
||||
#[runtime::pezpallet_index(10)]
|
||||
pub type Balances = pezpallet_balances;
|
||||
#[runtime::pezpallet_index(20)]
|
||||
pub type Assets = pezpallet_assets;
|
||||
#[runtime::pezpallet_index(21)]
|
||||
pub type Revive = pezpallet_revive;
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u64>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig as pezpallet_balances::DefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_assets::config_preludes::TestDefaultConfig as pezpallet_assets::DefaultConfig)]
|
||||
impl pezpallet_assets::Config for Test {
|
||||
type CreateOrigin = AsEnsureOriginWithArg<pezframe_system::EnsureSigned<u64>>;
|
||||
type ForceOrigin = pezframe_system::EnsureRoot<u64>;
|
||||
type Currency = Balances;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_revive::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_revive::Config for Test {
|
||||
type AddressMapper = pezpallet_revive::TestAccountMapper<Self>;
|
||||
type Balance = u64;
|
||||
type Currency = Balances;
|
||||
type Precompiles = (ERC20<Self, InlineIdConfig<0x0120>>,);
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let t = RuntimeGenesisConfig {
|
||||
assets: pezpallet_assets::GenesisConfig {
|
||||
assets: vec![(1, 0, true, 1)],
|
||||
metadata: vec![],
|
||||
accounts: vec![(1, 1, 100)],
|
||||
next_asset_id: None,
|
||||
reserves: vec![],
|
||||
},
|
||||
system: Default::default(),
|
||||
balances: Default::default(),
|
||||
revive: Default::default(),
|
||||
}
|
||||
.build_storage()
|
||||
.unwrap();
|
||||
let mut ext: pezsp_io::TestExternalities = t.into();
|
||||
ext.execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
});
|
||||
|
||||
ext
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
alloy::hex,
|
||||
mock::{new_test_ext, Assets, Balances, RuntimeEvent, RuntimeOrigin, System, Test},
|
||||
};
|
||||
use alloy::primitives::U256;
|
||||
use pezframe_support::{assert_ok, traits::Currency};
|
||||
use pezpallet_revive::ExecConfig;
|
||||
use pezsp_core::H160;
|
||||
use pezsp_runtime::Weight;
|
||||
|
||||
fn assert_contract_event(contract: H160, event: IERC20Events) {
|
||||
let (topics, data) = event.into_log_data().split();
|
||||
let topics = topics.into_iter().map(|v| H256(v.0)).collect::<Vec<_>>();
|
||||
System::assert_has_event(RuntimeEvent::Revive(pezpallet_revive::Event::ContractEmitted {
|
||||
contract,
|
||||
data: data.to_vec(),
|
||||
topics,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn asset_id_extractor_works() {
|
||||
let address: [u8; 20] =
|
||||
hex::const_decode_to_array(b"0000053900000000000000000000000001200000").unwrap();
|
||||
assert!(InlineIdConfig::<0x0120>::MATCHER.matches(&address));
|
||||
assert_eq!(
|
||||
<InlineIdConfig<0x0120> as AssetPrecompileConfig>::AssetIdExtractor::asset_id_from_address(
|
||||
&address
|
||||
)
|
||||
.unwrap(),
|
||||
1337u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn precompile_transfer_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset_id = 0u32;
|
||||
let asset_addr = H160::from(
|
||||
hex::const_decode_to_array(b"0000000000000000000000000000000001200000").unwrap(),
|
||||
);
|
||||
|
||||
let from = 123456789;
|
||||
let to = 987654321;
|
||||
|
||||
Balances::make_free_balance_be(&from, 100);
|
||||
Balances::make_free_balance_be(&to, 100);
|
||||
|
||||
let from_addr = <Test as pezpallet_revive::Config>::AddressMapper::to_address(&from);
|
||||
let to_addr = <Test as pezpallet_revive::Config>::AddressMapper::to_address(&to);
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, from, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(from), asset_id, from, 100));
|
||||
|
||||
let data =
|
||||
IERC20::transferCall { to: to_addr.0.into(), value: U256::from(10) }.abi_encode();
|
||||
|
||||
pezpallet_revive::Pallet::<Test>::bare_call(
|
||||
RuntimeOrigin::signed(from),
|
||||
H160::from(asset_addr),
|
||||
0u32.into(),
|
||||
Weight::MAX,
|
||||
u64::MAX,
|
||||
data,
|
||||
ExecConfig::new_bizinikiwi_tx(),
|
||||
);
|
||||
|
||||
assert_contract_event(
|
||||
asset_addr,
|
||||
IERC20Events::Transfer(IERC20::Transfer {
|
||||
from: from_addr.0.into(),
|
||||
to: to_addr.0.into(),
|
||||
value: U256::from(10),
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(Assets::balance(asset_id, from), 90);
|
||||
assert_eq!(Assets::balance(asset_id, to), 10);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn total_supply_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset_id = 0u32;
|
||||
let asset_addr =
|
||||
hex::const_decode_to_array(b"0000000000000000000000000000000001200000").unwrap();
|
||||
|
||||
let owner = 123456789;
|
||||
|
||||
Balances::make_free_balance_be(&owner, 100);
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, owner, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(owner), asset_id, owner, 1000));
|
||||
|
||||
let data = IERC20::totalSupplyCall {}.abi_encode();
|
||||
|
||||
let data = pezpallet_revive::Pallet::<Test>::bare_call(
|
||||
RuntimeOrigin::signed(owner),
|
||||
H160::from(asset_addr),
|
||||
0u32.into(),
|
||||
Weight::MAX,
|
||||
u64::MAX,
|
||||
data,
|
||||
ExecConfig::new_bizinikiwi_tx(),
|
||||
)
|
||||
.result
|
||||
.unwrap()
|
||||
.data;
|
||||
|
||||
let ret = IERC20::totalSupplyCall::abi_decode_returns(&data).unwrap();
|
||||
assert_eq!(ret, U256::from(1000));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn balance_of_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset_id = 0u32;
|
||||
let asset_addr =
|
||||
hex::const_decode_to_array(b"0000000000000000000000000000000001200000").unwrap();
|
||||
|
||||
let owner = 123456789;
|
||||
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, owner, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(owner), asset_id, owner, 1000));
|
||||
|
||||
let account = <Test as pezpallet_revive::Config>::AddressMapper::to_address(&owner).0.into();
|
||||
let data = IERC20::balanceOfCall { account }.abi_encode();
|
||||
|
||||
let data = pezpallet_revive::Pallet::<Test>::bare_call(
|
||||
RuntimeOrigin::signed(owner),
|
||||
H160::from(asset_addr),
|
||||
0u32.into(),
|
||||
Weight::MAX,
|
||||
u64::MAX,
|
||||
data,
|
||||
ExecConfig::new_bizinikiwi_tx(),
|
||||
)
|
||||
.result
|
||||
.unwrap()
|
||||
.data;
|
||||
|
||||
let ret = IERC20::balanceOfCall::abi_decode_returns(&data).unwrap();
|
||||
assert_eq!(ret, U256::from(1000));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approval_works() {
|
||||
use pezframe_support::traits::fungibles::approvals::Inspect;
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset_id = 0u32;
|
||||
let asset_addr = H160::from(
|
||||
hex::const_decode_to_array(b"0000000000000000000000000000000001200000").unwrap(),
|
||||
);
|
||||
|
||||
let owner = 123456789;
|
||||
let spender = 987654321;
|
||||
let other = 1122334455;
|
||||
|
||||
Balances::make_free_balance_be(&owner, 100);
|
||||
Balances::make_free_balance_be(&spender, 100);
|
||||
Balances::make_free_balance_be(&other, 100);
|
||||
|
||||
let owner_addr = <Test as pezpallet_revive::Config>::AddressMapper::to_address(&owner);
|
||||
let spender_addr = <Test as pezpallet_revive::Config>::AddressMapper::to_address(&spender);
|
||||
let other_addr = <Test as pezpallet_revive::Config>::AddressMapper::to_address(&other);
|
||||
|
||||
assert_ok!(Assets::force_create(RuntimeOrigin::root(), asset_id, owner, true, 1));
|
||||
assert_ok!(Assets::mint(RuntimeOrigin::signed(owner), asset_id, owner, 100));
|
||||
|
||||
let data = IERC20::approveCall { spender: spender_addr.0.into(), value: U256::from(25) }
|
||||
.abi_encode();
|
||||
|
||||
pezpallet_revive::Pallet::<Test>::bare_call(
|
||||
RuntimeOrigin::signed(owner),
|
||||
H160::from(asset_addr),
|
||||
0u32.into(),
|
||||
Weight::MAX,
|
||||
u64::MAX,
|
||||
data,
|
||||
ExecConfig::new_bizinikiwi_tx(),
|
||||
);
|
||||
|
||||
assert_contract_event(
|
||||
asset_addr,
|
||||
IERC20Events::Approval(IERC20::Approval {
|
||||
owner: owner_addr.0.into(),
|
||||
spender: spender_addr.0.into(),
|
||||
value: U256::from(25),
|
||||
}),
|
||||
);
|
||||
|
||||
let data =
|
||||
IERC20::allowanceCall { owner: owner_addr.0.into(), spender: spender_addr.0.into() }
|
||||
.abi_encode();
|
||||
|
||||
let data = pezpallet_revive::Pallet::<Test>::bare_call(
|
||||
RuntimeOrigin::signed(owner),
|
||||
H160::from(asset_addr),
|
||||
0u32.into(),
|
||||
Weight::MAX,
|
||||
u64::MAX,
|
||||
data,
|
||||
ExecConfig::new_bizinikiwi_tx(),
|
||||
)
|
||||
.result
|
||||
.unwrap()
|
||||
.data;
|
||||
|
||||
let ret = IERC20::allowanceCall::abi_decode_returns(&data).unwrap();
|
||||
assert_eq!(ret, U256::from(25));
|
||||
|
||||
let data = IERC20::transferFromCall {
|
||||
from: owner_addr.0.into(),
|
||||
to: other_addr.0.into(),
|
||||
value: U256::from(10),
|
||||
}
|
||||
.abi_encode();
|
||||
|
||||
pezpallet_revive::Pallet::<Test>::bare_call(
|
||||
RuntimeOrigin::signed(spender),
|
||||
H160::from(asset_addr),
|
||||
0u32.into(),
|
||||
Weight::MAX,
|
||||
u64::MAX,
|
||||
data,
|
||||
ExecConfig::new_bizinikiwi_tx(),
|
||||
);
|
||||
assert_eq!(Assets::balance(asset_id, owner), 90);
|
||||
assert_eq!(Assets::allowance(asset_id, &owner, &spender), 15);
|
||||
assert_eq!(Assets::balance(asset_id, other), 10);
|
||||
|
||||
assert_contract_event(
|
||||
asset_addr,
|
||||
IERC20Events::Transfer(IERC20::Transfer {
|
||||
from: owner_addr.0.into(),
|
||||
to: other_addr.0.into(),
|
||||
value: U256::from(10),
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,636 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Assets pallet benchmarking.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
use alloc::vec;
|
||||
use pezframe_benchmarking::{
|
||||
v1::{
|
||||
account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, BenchmarkError,
|
||||
},
|
||||
BenchmarkResult,
|
||||
};
|
||||
use pezframe_support::traits::{EnsureOrigin, Get, UnfilteredDispatchable};
|
||||
use pezframe_system::RawOrigin as SystemOrigin;
|
||||
use pezsp_runtime::{traits::Bounded, Weight};
|
||||
|
||||
use crate::Pallet as Assets;
|
||||
|
||||
const SEED: u32 = 0;
|
||||
const MIN_BALANCE: u32 = 1;
|
||||
|
||||
fn default_asset_id<T: Config<I>, I: 'static>() -> T::AssetIdParameter {
|
||||
T::BenchmarkHelper::create_asset_id_parameter(0)
|
||||
}
|
||||
|
||||
fn create_default_asset<T: Config<I>, I: 'static>(
|
||||
is_sufficient: bool,
|
||||
) -> (T::AssetIdParameter, T::AccountId, AccountIdLookupOf<T>) {
|
||||
let asset_id = default_asset_id::<T, I>();
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let caller_lookup = T::Lookup::unlookup(caller.clone());
|
||||
let root = SystemOrigin::Root.into();
|
||||
assert!(Assets::<T, I>::force_create(
|
||||
root,
|
||||
asset_id.clone(),
|
||||
caller_lookup.clone(),
|
||||
is_sufficient,
|
||||
MIN_BALANCE.into(),
|
||||
)
|
||||
.is_ok());
|
||||
(asset_id, caller, caller_lookup)
|
||||
}
|
||||
|
||||
fn create_default_reserves<T: Config<I>, I: 'static>(
|
||||
) -> (T::AssetIdParameter, T::AccountId, Vec<T::ReserveData>) {
|
||||
// create asset
|
||||
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
|
||||
// build max number of reserves
|
||||
let mut reserves = Vec::<T::ReserveData>::new();
|
||||
for i in 0..MAX_RESERVES {
|
||||
reserves.push(T::BenchmarkHelper::create_reserve_id_parameter(i));
|
||||
}
|
||||
(asset_id, caller, reserves)
|
||||
}
|
||||
|
||||
pub fn create_default_minted_asset<T: Config<I>, I: 'static>(
|
||||
is_sufficient: bool,
|
||||
amount: T::Balance,
|
||||
) -> (T::AssetIdParameter, T::AccountId, AccountIdLookupOf<T>) {
|
||||
let (asset_id, caller, caller_lookup) = create_default_asset::<T, I>(is_sufficient);
|
||||
if !is_sufficient {
|
||||
T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance());
|
||||
}
|
||||
assert!(Assets::<T, I>::mint(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
asset_id.clone(),
|
||||
caller_lookup.clone(),
|
||||
amount,
|
||||
)
|
||||
.is_ok());
|
||||
(asset_id, caller, caller_lookup)
|
||||
}
|
||||
|
||||
fn swap_is_sufficient<T: Config<I>, I: 'static>(s: &mut bool) {
|
||||
let asset_id = default_asset_id::<T, I>();
|
||||
Asset::<T, I>::mutate(&asset_id.into(), |maybe_a| {
|
||||
if let Some(ref mut a) = maybe_a {
|
||||
core::mem::swap(s, &mut a.is_sufficient)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn add_sufficients<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
|
||||
let asset_id = default_asset_id::<T, I>();
|
||||
let origin = SystemOrigin::Signed(minter);
|
||||
let mut s = true;
|
||||
swap_is_sufficient::<T, I>(&mut s);
|
||||
for i in 0..n {
|
||||
let target = account("sufficient", i, SEED);
|
||||
let target_lookup = T::Lookup::unlookup(target);
|
||||
assert!(Assets::<T, I>::mint(
|
||||
origin.clone().into(),
|
||||
asset_id.clone(),
|
||||
target_lookup,
|
||||
100u32.into()
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
swap_is_sufficient::<T, I>(&mut s);
|
||||
}
|
||||
|
||||
fn add_approvals<T: Config<I>, I: 'static>(minter: T::AccountId, n: u32) {
|
||||
let asset_id = default_asset_id::<T, I>();
|
||||
let _ = T::Currency::deposit_creating(
|
||||
&minter,
|
||||
T::ApprovalDeposit::get() * n.into() + T::Currency::minimum_balance(),
|
||||
);
|
||||
let minter_lookup = T::Lookup::unlookup(minter.clone());
|
||||
let origin = SystemOrigin::Signed(minter);
|
||||
Assets::<T, I>::mint(
|
||||
origin.clone().into(),
|
||||
asset_id.clone(),
|
||||
minter_lookup,
|
||||
(100 * (n + 1)).into(),
|
||||
)
|
||||
.unwrap();
|
||||
let enough = T::Currency::minimum_balance();
|
||||
for i in 0..n {
|
||||
let target = account("approval", i, SEED);
|
||||
T::Currency::make_free_balance_be(&target, enough);
|
||||
let target_lookup = T::Lookup::unlookup(target);
|
||||
Assets::<T, I>::approve_transfer(
|
||||
origin.clone().into(),
|
||||
asset_id.clone(),
|
||||
target_lookup,
|
||||
100u32.into(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_last_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::RuntimeEvent) {
|
||||
pezframe_system::Pallet::<T>::assert_last_event(generic_event.into());
|
||||
}
|
||||
|
||||
fn assert_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::RuntimeEvent) {
|
||||
pezframe_system::Pallet::<T>::assert_has_event(generic_event.into());
|
||||
}
|
||||
|
||||
benchmarks_instance_pallet! {
|
||||
create {
|
||||
let asset_id = default_asset_id::<T, I>();
|
||||
let origin = T::CreateOrigin::try_successful_origin(&asset_id.clone().into())
|
||||
.map_err(|_| BenchmarkError::Weightless)?;
|
||||
let caller = T::CreateOrigin::ensure_origin(origin.clone(), &asset_id.clone().into()).unwrap();
|
||||
let caller_lookup = T::Lookup::unlookup(caller.clone());
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
}: _<T::RuntimeOrigin>(origin, asset_id.clone(), caller_lookup, 1u32.into())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Created { asset_id: asset_id.into(), creator: caller.clone(), owner: caller }.into());
|
||||
}
|
||||
|
||||
force_create {
|
||||
let asset_id = default_asset_id::<T, I>();
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let caller_lookup = T::Lookup::unlookup(caller.clone());
|
||||
}: _(SystemOrigin::Root, asset_id.clone(), caller_lookup, true, 1u32.into())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::ForceCreated { asset_id: asset_id.into(), owner: caller }.into());
|
||||
}
|
||||
|
||||
start_destroy {
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
Assets::<T, I>::freeze_asset(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
asset_id.clone(),
|
||||
)?;
|
||||
}:_(SystemOrigin::Signed(caller), asset_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::DestructionStarted { asset_id: asset_id.into() }.into());
|
||||
}
|
||||
|
||||
destroy_accounts {
|
||||
let c in 0 .. T::RemoveItemsLimit::get();
|
||||
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
|
||||
add_sufficients::<T, I>(caller.clone(), c);
|
||||
Assets::<T, I>::freeze_asset(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
asset_id.clone(),
|
||||
)?;
|
||||
Assets::<T,I>::start_destroy(SystemOrigin::Signed(caller.clone()).into(), asset_id.clone())?;
|
||||
}:_(SystemOrigin::Signed(caller), asset_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::AccountsDestroyed {
|
||||
asset_id: asset_id.into(),
|
||||
accounts_destroyed: c,
|
||||
accounts_remaining: 0,
|
||||
}.into());
|
||||
}
|
||||
|
||||
destroy_approvals {
|
||||
let a in 0 .. T::RemoveItemsLimit::get();
|
||||
let (asset_id, caller, _) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
add_approvals::<T, I>(caller.clone(), a);
|
||||
Assets::<T, I>::freeze_asset(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
asset_id.clone(),
|
||||
)?;
|
||||
Assets::<T,I>::start_destroy(SystemOrigin::Signed(caller.clone()).into(), asset_id.clone())?;
|
||||
}:_(SystemOrigin::Signed(caller), asset_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::ApprovalsDestroyed {
|
||||
asset_id: asset_id.into(),
|
||||
approvals_destroyed: a,
|
||||
approvals_remaining: 0,
|
||||
}.into());
|
||||
}
|
||||
|
||||
finish_destroy {
|
||||
let (asset_id, caller, caller_lookup) = create_default_asset::<T, I>(true);
|
||||
Assets::<T, I>::freeze_asset(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
asset_id.clone(),
|
||||
)?;
|
||||
Assets::<T,I>::start_destroy(SystemOrigin::Signed(caller.clone()).into(), asset_id.clone())?;
|
||||
}:_(SystemOrigin::Signed(caller), asset_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Destroyed {
|
||||
asset_id: asset_id.into(),
|
||||
}.into()
|
||||
);
|
||||
}
|
||||
|
||||
mint {
|
||||
let (asset_id, caller, caller_lookup) = create_default_asset::<T, I>(true);
|
||||
let amount = T::Balance::from(100u32);
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), caller_lookup, amount)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Issued { asset_id: asset_id.into(), owner: caller, amount }.into());
|
||||
}
|
||||
|
||||
burn {
|
||||
let amount = T::Balance::from(100u32);
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, amount);
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), caller_lookup, amount)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Burned { asset_id: asset_id.into(), owner: caller, balance: amount }.into());
|
||||
}
|
||||
|
||||
transfer {
|
||||
let amount = T::Balance::from(100u32);
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, amount);
|
||||
let target: T::AccountId = account("target", 0, SEED);
|
||||
let target_lookup = T::Lookup::unlookup(target.clone());
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), target_lookup, amount)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into());
|
||||
}
|
||||
|
||||
transfer_keep_alive {
|
||||
let mint_amount = T::Balance::from(200u32);
|
||||
let amount = T::Balance::from(100u32);
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, mint_amount);
|
||||
let target: T::AccountId = account("target", 0, SEED);
|
||||
let target_lookup = T::Lookup::unlookup(target.clone());
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), target_lookup, amount)
|
||||
verify {
|
||||
assert!(pezframe_system::Pallet::<T>::account_exists(&caller));
|
||||
assert_last_event::<T, I>(Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into());
|
||||
}
|
||||
|
||||
force_transfer {
|
||||
let amount = T::Balance::from(100u32);
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, amount);
|
||||
let target: T::AccountId = account("target", 0, SEED);
|
||||
let target_lookup = T::Lookup::unlookup(target.clone());
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), caller_lookup, target_lookup, amount)
|
||||
verify {
|
||||
assert_last_event::<T, I>(
|
||||
Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into()
|
||||
);
|
||||
}
|
||||
|
||||
freeze {
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), caller_lookup)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Frozen { asset_id: asset_id.into(), who: caller }.into());
|
||||
}
|
||||
|
||||
thaw {
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
Assets::<T, I>::freeze(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
asset_id.clone(),
|
||||
caller_lookup.clone(),
|
||||
)?;
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), caller_lookup)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Thawed { asset_id: asset_id.into(), who: caller }.into());
|
||||
}
|
||||
|
||||
freeze_asset {
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::AssetFrozen { asset_id: asset_id.into() }.into());
|
||||
}
|
||||
|
||||
thaw_asset {
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
Assets::<T, I>::freeze_asset(
|
||||
SystemOrigin::Signed(caller.clone()).into(),
|
||||
asset_id.clone(),
|
||||
)?;
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::AssetThawed { asset_id: asset_id.into() }.into());
|
||||
}
|
||||
|
||||
transfer_ownership {
|
||||
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
|
||||
let target: T::AccountId = account("target", 0, SEED);
|
||||
let target_lookup = T::Lookup::unlookup(target.clone());
|
||||
}: _(SystemOrigin::Signed(caller), asset_id.clone(), target_lookup)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::OwnerChanged { asset_id: asset_id.into(), owner: target }.into());
|
||||
}
|
||||
|
||||
set_team {
|
||||
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
|
||||
let target0 = T::Lookup::unlookup(account("target", 0, SEED));
|
||||
let target1 = T::Lookup::unlookup(account("target", 1, SEED));
|
||||
let target2 = T::Lookup::unlookup(account("target", 2, SEED));
|
||||
}: _(SystemOrigin::Signed(caller), asset_id.clone(), target0, target1, target2)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::TeamChanged {
|
||||
asset_id: asset_id.into(),
|
||||
issuer: account("target", 0, SEED),
|
||||
admin: account("target", 1, SEED),
|
||||
freezer: account("target", 2, SEED),
|
||||
}.into());
|
||||
}
|
||||
|
||||
set_reserves {
|
||||
let (asset_id, caller, reserves) = create_default_reserves::<T, I>();
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
}: _(SystemOrigin::Signed(caller), asset_id.clone(), reserves.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::ReservesUpdated { asset_id: asset_id.into(), reserves: reserves }.into());
|
||||
}
|
||||
|
||||
set_metadata {
|
||||
let n in 0 .. T::StringLimit::get();
|
||||
let s in 0 .. T::StringLimit::get();
|
||||
|
||||
let name = vec![0u8; n as usize];
|
||||
let symbol = vec![0u8; s as usize];
|
||||
let decimals = 12;
|
||||
|
||||
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
}: _(SystemOrigin::Signed(caller), asset_id.clone(), name.clone(), symbol.clone(), decimals)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::MetadataSet { asset_id: asset_id.into(), name, symbol, decimals, is_frozen: false }.into());
|
||||
}
|
||||
|
||||
clear_metadata {
|
||||
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
let dummy = vec![0u8; T::StringLimit::get() as usize];
|
||||
let origin = SystemOrigin::Signed(caller.clone()).into();
|
||||
Assets::<T, I>::set_metadata(origin, asset_id.clone(), dummy.clone(), dummy, 12)?;
|
||||
}: _(SystemOrigin::Signed(caller), asset_id.clone())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::MetadataCleared { asset_id: asset_id.into() }.into());
|
||||
}
|
||||
|
||||
force_set_metadata {
|
||||
let n in 0 .. T::StringLimit::get();
|
||||
let s in 0 .. T::StringLimit::get();
|
||||
|
||||
let name = vec![0u8; n as usize];
|
||||
let symbol = vec![0u8; s as usize];
|
||||
let decimals = 12;
|
||||
|
||||
let (asset_id, _, _) = create_default_asset::<T, I>(true);
|
||||
|
||||
let origin =
|
||||
T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
let call = Call::<T, I>::force_set_metadata {
|
||||
id: asset_id.clone(),
|
||||
name: name.clone(),
|
||||
symbol: symbol.clone(),
|
||||
decimals,
|
||||
is_frozen: false,
|
||||
};
|
||||
}: { call.dispatch_bypass_filter(origin)? }
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::MetadataSet { asset_id: asset_id.into(), name, symbol, decimals, is_frozen: false }.into());
|
||||
}
|
||||
|
||||
force_clear_metadata {
|
||||
let (asset_id, caller, _) = create_default_asset::<T, I>(true);
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
let dummy = vec![0u8; T::StringLimit::get() as usize];
|
||||
let origin = SystemOrigin::Signed(caller).into();
|
||||
Assets::<T, I>::set_metadata(origin, asset_id.clone(), dummy.clone(), dummy, 12)?;
|
||||
|
||||
let origin =
|
||||
T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
let call = Call::<T, I>::force_clear_metadata { id: asset_id.clone() };
|
||||
}: { call.dispatch_bypass_filter(origin)? }
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::MetadataCleared { asset_id: asset_id.into() }.into());
|
||||
}
|
||||
|
||||
force_asset_status {
|
||||
let (asset_id, caller, caller_lookup) = create_default_asset::<T, I>(true);
|
||||
|
||||
let origin =
|
||||
T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
|
||||
let call = Call::<T, I>::force_asset_status {
|
||||
id: asset_id.clone(),
|
||||
owner: caller_lookup.clone(),
|
||||
issuer: caller_lookup.clone(),
|
||||
admin: caller_lookup.clone(),
|
||||
freezer: caller_lookup,
|
||||
min_balance: 100u32.into(),
|
||||
is_sufficient: true,
|
||||
is_frozen: false,
|
||||
};
|
||||
}: { call.dispatch_bypass_filter(origin)? }
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::AssetStatusChanged { asset_id: asset_id.into() }.into());
|
||||
}
|
||||
|
||||
approve_transfer {
|
||||
let (asset_id, caller, _) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
|
||||
let delegate: T::AccountId = account("delegate", 0, SEED);
|
||||
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
|
||||
let amount = 100u32.into();
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), delegate_lookup, amount)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::ApprovedTransfer { asset_id: asset_id.into(), source: caller, delegate, amount }.into());
|
||||
}
|
||||
|
||||
transfer_approved {
|
||||
let (asset_id, owner, owner_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
T::Currency::make_free_balance_be(&owner, DepositBalanceOf::<T, I>::max_value());
|
||||
|
||||
let delegate: T::AccountId = account("delegate", 0, SEED);
|
||||
whitelist_account!(delegate);
|
||||
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
|
||||
let amount = 100u32.into();
|
||||
let origin = SystemOrigin::Signed(owner.clone()).into();
|
||||
Assets::<T, I>::approve_transfer(origin, asset_id.clone(), delegate_lookup, amount)?;
|
||||
|
||||
let dest: T::AccountId = account("dest", 0, SEED);
|
||||
let dest_lookup = T::Lookup::unlookup(dest.clone());
|
||||
}: _(SystemOrigin::Signed(delegate.clone()), asset_id.clone(), owner_lookup, dest_lookup, amount)
|
||||
verify {
|
||||
assert!(T::Currency::reserved_balance(&owner).is_zero());
|
||||
assert_event::<T, I>(Event::Transferred { asset_id: asset_id.into(), from: owner, to: dest, amount }.into());
|
||||
}
|
||||
|
||||
cancel_approval {
|
||||
let (asset_id, caller, _) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
|
||||
let delegate: T::AccountId = account("delegate", 0, SEED);
|
||||
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
|
||||
let amount = 100u32.into();
|
||||
let origin = SystemOrigin::Signed(caller.clone()).into();
|
||||
Assets::<T, I>::approve_transfer(origin, asset_id.clone(), delegate_lookup.clone(), amount)?;
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), delegate_lookup)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::ApprovalCancelled { asset_id: asset_id.into(), owner: caller, delegate }.into());
|
||||
}
|
||||
|
||||
force_cancel_approval {
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
T::Currency::make_free_balance_be(&caller, DepositBalanceOf::<T, I>::max_value());
|
||||
|
||||
let delegate: T::AccountId = account("delegate", 0, SEED);
|
||||
let delegate_lookup = T::Lookup::unlookup(delegate.clone());
|
||||
let amount = 100u32.into();
|
||||
let origin = SystemOrigin::Signed(caller.clone()).into();
|
||||
Assets::<T, I>::approve_transfer(origin, asset_id.clone(), delegate_lookup.clone(), amount)?;
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), caller_lookup, delegate_lookup)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::ApprovalCancelled { asset_id: asset_id.into(), owner: caller, delegate }.into());
|
||||
}
|
||||
|
||||
set_min_balance {
|
||||
let (asset_id, caller, caller_lookup) = create_default_asset::<T, I>(false);
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), 50u32.into())
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::AssetMinBalanceChanged { asset_id: asset_id.into(), new_min_balance: 50u32.into() }.into());
|
||||
}
|
||||
|
||||
touch {
|
||||
let (asset_id, asset_owner, asset_owner_lookup) = create_default_asset::<T, I>(false);
|
||||
let new_account: T::AccountId = account("newaccount", 1, SEED);
|
||||
T::Currency::make_free_balance_be(&new_account, DepositBalanceOf::<T, I>::max_value());
|
||||
assert_ne!(asset_owner, new_account);
|
||||
assert!(!Account::<T, I>::contains_key(asset_id.clone().into(), &new_account));
|
||||
}: _(SystemOrigin::Signed(new_account.clone()), asset_id.clone())
|
||||
verify {
|
||||
assert!(Account::<T, I>::contains_key(asset_id.into(), &new_account));
|
||||
}
|
||||
|
||||
touch_other {
|
||||
let (asset_id, asset_owner, asset_owner_lookup) = create_default_asset::<T, I>(false);
|
||||
let new_account: T::AccountId = account("newaccount", 1, SEED);
|
||||
let new_account_lookup = T::Lookup::unlookup(new_account.clone());
|
||||
T::Currency::make_free_balance_be(&asset_owner, DepositBalanceOf::<T, I>::max_value());
|
||||
assert_ne!(asset_owner, new_account);
|
||||
assert!(!Account::<T, I>::contains_key(asset_id.clone().into(), &new_account));
|
||||
}: _(SystemOrigin::Signed(asset_owner.clone()), asset_id.clone(), new_account_lookup)
|
||||
verify {
|
||||
assert!(Account::<T, I>::contains_key(asset_id.into(), &new_account));
|
||||
}
|
||||
|
||||
refund {
|
||||
let (asset_id, asset_owner, asset_owner_lookup) = create_default_asset::<T, I>(false);
|
||||
let new_account: T::AccountId = account("newaccount", 1, SEED);
|
||||
T::Currency::make_free_balance_be(&new_account, DepositBalanceOf::<T, I>::max_value());
|
||||
assert_ne!(asset_owner, new_account);
|
||||
assert!(Assets::<T, I>::touch(
|
||||
SystemOrigin::Signed(new_account.clone()).into(),
|
||||
asset_id.clone()
|
||||
).is_ok());
|
||||
// `touch` should reserve balance of the caller according to the `AssetAccountDeposit` amount...
|
||||
assert_eq!(T::Currency::reserved_balance(&new_account), T::AssetAccountDeposit::get());
|
||||
// ...and also create an `Account` entry.
|
||||
assert!(Account::<T, I>::contains_key(asset_id.clone().into(), &new_account));
|
||||
}: _(SystemOrigin::Signed(new_account.clone()), asset_id, true)
|
||||
verify {
|
||||
// `refund`ing should of course repatriate the reserve
|
||||
assert!(T::Currency::reserved_balance(&new_account).is_zero());
|
||||
}
|
||||
|
||||
refund_other {
|
||||
let (asset_id, asset_owner, asset_owner_lookup) = create_default_asset::<T, I>(false);
|
||||
let new_account: T::AccountId = account("newaccount", 1, SEED);
|
||||
let new_account_lookup = T::Lookup::unlookup(new_account.clone());
|
||||
T::Currency::make_free_balance_be(&asset_owner, DepositBalanceOf::<T, I>::max_value());
|
||||
assert_ne!(asset_owner, new_account);
|
||||
assert!(Assets::<T, I>::touch_other(
|
||||
SystemOrigin::Signed(asset_owner.clone()).into(),
|
||||
asset_id.clone(),
|
||||
new_account_lookup.clone()
|
||||
).is_ok());
|
||||
// `touch` should reserve balance of the caller according to the `AssetAccountDeposit` amount...
|
||||
assert_eq!(T::Currency::reserved_balance(&asset_owner), T::AssetAccountDeposit::get());
|
||||
assert!(Account::<T, I>::contains_key(asset_id.clone().into(), &new_account));
|
||||
}: _(SystemOrigin::Signed(asset_owner.clone()), asset_id, new_account_lookup.clone())
|
||||
verify {
|
||||
// this should repatriate the reserved balance of the freezer
|
||||
assert!(T::Currency::reserved_balance(&asset_owner).is_zero());
|
||||
}
|
||||
|
||||
block {
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), caller_lookup)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Blocked { asset_id: asset_id.into(), who: caller }.into());
|
||||
}
|
||||
|
||||
transfer_all {
|
||||
let amount = T::Balance::from(2 * MIN_BALANCE);
|
||||
let (asset_id, caller, caller_lookup) = create_default_minted_asset::<T, I>(true, amount);
|
||||
let target: T::AccountId = account("target", 0, SEED);
|
||||
let target_lookup = T::Lookup::unlookup(target.clone());
|
||||
}: _(SystemOrigin::Signed(caller.clone()), asset_id.clone(), target_lookup, false)
|
||||
verify {
|
||||
assert_last_event::<T, I>(Event::Transferred { asset_id: asset_id.into(), from: caller, to: target, amount }.into());
|
||||
}
|
||||
|
||||
total_issuance {
|
||||
use pezframe_support::traits::fungibles::Inspect;
|
||||
let (asset_id, _, _) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
let amount;
|
||||
}: {
|
||||
amount = Pallet::<T, I>::total_issuance(asset_id.into());
|
||||
} verify {
|
||||
assert_eq!(amount, 100u32.into());
|
||||
}
|
||||
|
||||
balance {
|
||||
let (asset_id, caller, _) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
let amount;
|
||||
}: {
|
||||
amount = Pallet::<T, I>::balance(asset_id.into(), caller);
|
||||
} verify {
|
||||
assert_eq!(amount, 100u32.into());
|
||||
}
|
||||
|
||||
allowance {
|
||||
use pezframe_support::traits::fungibles::approvals::Inspect;
|
||||
let (asset_id, caller, _) = create_default_minted_asset::<T, I>(true, 100u32.into());
|
||||
add_approvals::<T, I>(caller.clone(), 1);
|
||||
let delegate: T::AccountId = account("approval", 0, SEED);
|
||||
let amount;
|
||||
}: {
|
||||
amount = Pallet::<T, I>::allowance(asset_id.into(), &caller, &delegate);
|
||||
} verify {
|
||||
assert_eq!(amount, 100u32.into());
|
||||
}
|
||||
|
||||
migration_v2_foreign_asset_set_reserve_weight {
|
||||
let (id, _, _) = create_default_asset::<T, I>(true);
|
||||
let id: <T as pallet::Config<I>>::AssetId = id.into();
|
||||
let reserve = T::BenchmarkHelper::create_reserve_id_parameter(42);
|
||||
}: {
|
||||
let asset_id = Asset::<T, I>::iter_keys().next()
|
||||
.ok_or_else(|| BenchmarkError::Override(BenchmarkResult::from_weight(Weight::MAX)))?;
|
||||
assert_eq!(id, asset_id);
|
||||
Pallet::<T, I>::unchecked_update_reserves(asset_id, vec![reserve.clone()]).unwrap();
|
||||
}
|
||||
verify {
|
||||
assert_eq!(Reserves::<T, I>::get(id)[0], reserve);
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Assets, crate::mock::new_test_ext(), crate::mock::Test)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Datatype for easy mutation of the extra "sidecar" data.
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A mutator type allowing inspection and possible modification of the extra "sidecar" data.
|
||||
///
|
||||
/// This may be used as a `Deref` for the pallet's extra data. If mutated (using `DerefMut`), then
|
||||
/// any uncommitted changes (see `commit` function) will be automatically committed to storage when
|
||||
/// dropped. Changes, even after committed, may be reverted to their original values with the
|
||||
/// `revert` function.
|
||||
pub struct ExtraMutator<T: Config<I>, I: 'static = ()> {
|
||||
id: T::AssetId,
|
||||
who: T::AccountId,
|
||||
original: T::Extra,
|
||||
pending: Option<T::Extra>,
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> Drop for ExtraMutator<T, I> {
|
||||
fn drop(&mut self) {
|
||||
debug_assert!(self.commit().is_ok(), "attempt to write to non-existent asset account");
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> core::ops::Deref for ExtraMutator<T, I> {
|
||||
type Target = T::Extra;
|
||||
fn deref(&self) -> &T::Extra {
|
||||
match self.pending {
|
||||
Some(ref value) => value,
|
||||
None => &self.original,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> core::ops::DerefMut for ExtraMutator<T, I> {
|
||||
fn deref_mut(&mut self) -> &mut T::Extra {
|
||||
if self.pending.is_none() {
|
||||
self.pending = Some(self.original.clone());
|
||||
}
|
||||
self.pending.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> ExtraMutator<T, I> {
|
||||
pub(super) fn maybe_new(
|
||||
id: T::AssetId,
|
||||
who: impl core::borrow::Borrow<T::AccountId>,
|
||||
) -> Option<ExtraMutator<T, I>> {
|
||||
if let Some(a) = Account::<T, I>::get(&id, who.borrow()) {
|
||||
Some(ExtraMutator::<T, I> {
|
||||
id,
|
||||
who: who.borrow().clone(),
|
||||
original: a.extra,
|
||||
pending: None,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Commit any changes to storage.
|
||||
pub fn commit(&mut self) -> Result<(), ()> {
|
||||
if let Some(extra) = self.pending.take() {
|
||||
Account::<T, I>::try_mutate(&self.id, &self.who, |maybe_account| {
|
||||
maybe_account.as_mut().ok_or(()).map(|account| account.extra = extra)
|
||||
})
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Revert any changes, even those already committed by `self` and drop self.
|
||||
pub fn revert(mut self) -> Result<(), ()> {
|
||||
self.pending = None;
|
||||
Account::<T, I>::try_mutate(&self.id, &self.who, |maybe_account| {
|
||||
maybe_account
|
||||
.as_mut()
|
||||
.ok_or(())
|
||||
.map(|account| account.extra = self.original.clone())
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,360 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Implementations for fungibles trait.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use pezframe_support::{
|
||||
defensive,
|
||||
traits::tokens::{
|
||||
Fortitude,
|
||||
Precision::{self, BestEffort},
|
||||
Preservation::{self, Expendable},
|
||||
Provenance::{self, Minted},
|
||||
},
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Inspect<<T as SystemConfig>::AccountId> for Pallet<T, I> {
|
||||
type AssetId = T::AssetId;
|
||||
type Balance = T::Balance;
|
||||
|
||||
fn total_issuance(asset: Self::AssetId) -> Self::Balance {
|
||||
Asset::<T, I>::get(asset).map(|x| x.supply).unwrap_or_else(Zero::zero)
|
||||
}
|
||||
|
||||
fn minimum_balance(asset: Self::AssetId) -> Self::Balance {
|
||||
Asset::<T, I>::get(asset).map(|x| x.min_balance).unwrap_or_else(Zero::zero)
|
||||
}
|
||||
|
||||
fn balance(asset: Self::AssetId, who: &<T as SystemConfig>::AccountId) -> Self::Balance {
|
||||
Pallet::<T, I>::balance(asset, who)
|
||||
}
|
||||
|
||||
fn total_balance(asset: Self::AssetId, who: &<T as SystemConfig>::AccountId) -> Self::Balance {
|
||||
Pallet::<T, I>::balance(asset.clone(), who)
|
||||
.saturating_add(T::Holder::balance_on_hold(asset, who).unwrap_or_default())
|
||||
}
|
||||
|
||||
fn reducible_balance(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
preservation: Preservation,
|
||||
_: Fortitude,
|
||||
) -> Self::Balance {
|
||||
Pallet::<T, I>::reducible_balance(asset, who, !matches!(preservation, Expendable))
|
||||
.unwrap_or(Zero::zero())
|
||||
}
|
||||
|
||||
fn can_deposit(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
provenance: Provenance,
|
||||
) -> DepositConsequence {
|
||||
Pallet::<T, I>::can_increase(asset, who, amount, provenance == Minted)
|
||||
}
|
||||
|
||||
fn can_withdraw(
|
||||
asset: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) -> WithdrawConsequence<Self::Balance> {
|
||||
Pallet::<T, I>::can_decrease(asset, who, amount, false)
|
||||
}
|
||||
|
||||
fn asset_exists(asset: Self::AssetId) -> bool {
|
||||
Asset::<T, I>::contains_key(asset)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Mutate<<T as SystemConfig>::AccountId> for Pallet<T, I> {
|
||||
fn done_mint_into(
|
||||
asset_id: Self::AssetId,
|
||||
beneficiary: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) {
|
||||
Self::deposit_event(Event::Issued { asset_id, owner: beneficiary.clone(), amount })
|
||||
}
|
||||
|
||||
fn done_burn_from(
|
||||
asset_id: Self::AssetId,
|
||||
target: &<T as SystemConfig>::AccountId,
|
||||
balance: Self::Balance,
|
||||
) {
|
||||
Self::deposit_event(Event::Burned { asset_id, owner: target.clone(), balance });
|
||||
}
|
||||
|
||||
fn done_transfer(
|
||||
asset_id: Self::AssetId,
|
||||
source: &<T as SystemConfig>::AccountId,
|
||||
dest: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) {
|
||||
Self::deposit_event(Event::Transferred {
|
||||
asset_id,
|
||||
from: source.clone(),
|
||||
to: dest.clone(),
|
||||
amount,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Balanced<<T as SystemConfig>::AccountId>
|
||||
for Pallet<T, I>
|
||||
{
|
||||
type OnDropCredit = fungibles::DecreaseIssuance<T::AccountId, Self>;
|
||||
type OnDropDebt = fungibles::IncreaseIssuance<T::AccountId, Self>;
|
||||
|
||||
fn done_deposit(
|
||||
asset_id: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) {
|
||||
Self::deposit_event(Event::Deposited { asset_id, who: who.clone(), amount })
|
||||
}
|
||||
|
||||
fn done_withdraw(
|
||||
asset_id: Self::AssetId,
|
||||
who: &<T as SystemConfig>::AccountId,
|
||||
amount: Self::Balance,
|
||||
) {
|
||||
Self::deposit_event(Event::Withdrawn { asset_id, who: who.clone(), amount })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Unbalanced<T::AccountId> for Pallet<T, I> {
|
||||
fn handle_raw_dust(_: Self::AssetId, _: Self::Balance) {}
|
||||
fn handle_dust(_: fungibles::Dust<T::AccountId, Self>) {
|
||||
defensive!("`decrease_balance` and `increase_balance` have non-default impls; nothing else calls this; qed");
|
||||
}
|
||||
fn write_balance(
|
||||
_: Self::AssetId,
|
||||
_: &T::AccountId,
|
||||
_: Self::Balance,
|
||||
) -> Result<Option<Self::Balance>, DispatchError> {
|
||||
defensive!("write_balance is not used if other functions are impl'd");
|
||||
Err(DispatchError::Unavailable)
|
||||
}
|
||||
fn set_total_issuance(id: T::AssetId, amount: Self::Balance) {
|
||||
Asset::<T, I>::mutate_exists(id, |maybe_asset| {
|
||||
if let Some(ref mut asset) = maybe_asset {
|
||||
asset.supply = amount
|
||||
}
|
||||
});
|
||||
}
|
||||
fn decrease_balance(
|
||||
asset: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
precision: Precision,
|
||||
preservation: Preservation,
|
||||
_: Fortitude,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
let f = DebitFlags {
|
||||
keep_alive: preservation != Expendable,
|
||||
best_effort: precision == BestEffort,
|
||||
};
|
||||
Self::decrease_balance(asset, who, amount, f, |_, _| Ok(()))
|
||||
}
|
||||
fn increase_balance(
|
||||
asset: T::AssetId,
|
||||
who: &T::AccountId,
|
||||
amount: Self::Balance,
|
||||
_: Precision,
|
||||
) -> Result<Self::Balance, DispatchError> {
|
||||
Self::increase_balance(asset, who, amount, |_| Ok(()))?;
|
||||
Ok(amount)
|
||||
}
|
||||
|
||||
// TODO: #13196 implement deactivate/reactivate once we have inactive balance tracking.
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Create<T::AccountId> for Pallet<T, I> {
|
||||
fn create(
|
||||
id: T::AssetId,
|
||||
admin: T::AccountId,
|
||||
is_sufficient: bool,
|
||||
min_balance: Self::Balance,
|
||||
) -> DispatchResult {
|
||||
Self::do_force_create(id, admin, is_sufficient, min_balance)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Destroy<T::AccountId> for Pallet<T, I> {
|
||||
fn start_destroy(id: T::AssetId, maybe_check_owner: Option<T::AccountId>) -> DispatchResult {
|
||||
Self::do_start_destroy(id, maybe_check_owner)
|
||||
}
|
||||
|
||||
fn destroy_accounts(id: T::AssetId, max_items: u32) -> Result<u32, DispatchError> {
|
||||
Self::do_destroy_accounts(id, max_items)
|
||||
}
|
||||
|
||||
fn destroy_approvals(id: T::AssetId, max_items: u32) -> Result<u32, DispatchError> {
|
||||
Self::do_destroy_approvals(id, max_items)
|
||||
}
|
||||
|
||||
fn finish_destroy(id: T::AssetId) -> DispatchResult {
|
||||
Self::do_finish_destroy(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::metadata::Inspect<<T as SystemConfig>::AccountId>
|
||||
for Pallet<T, I>
|
||||
{
|
||||
fn name(asset: T::AssetId) -> Vec<u8> {
|
||||
Metadata::<T, I>::get(asset).name.to_vec()
|
||||
}
|
||||
|
||||
fn symbol(asset: T::AssetId) -> Vec<u8> {
|
||||
Metadata::<T, I>::get(asset).symbol.to_vec()
|
||||
}
|
||||
|
||||
fn decimals(asset: T::AssetId) -> u8 {
|
||||
Metadata::<T, I>::get(asset).decimals
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::metadata::Mutate<<T as SystemConfig>::AccountId>
|
||||
for Pallet<T, I>
|
||||
{
|
||||
fn set(
|
||||
asset: T::AssetId,
|
||||
from: &<T as SystemConfig>::AccountId,
|
||||
name: Vec<u8>,
|
||||
symbol: Vec<u8>,
|
||||
decimals: u8,
|
||||
) -> DispatchResult {
|
||||
Self::do_set_metadata(asset, from, name, symbol, decimals)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static>
|
||||
fungibles::metadata::MetadataDeposit<
|
||||
<T::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance,
|
||||
> for Pallet<T, I>
|
||||
{
|
||||
fn calc_metadata_deposit(
|
||||
name: &[u8],
|
||||
symbol: &[u8],
|
||||
) -> <T::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance {
|
||||
Self::calc_metadata_deposit(&name, &symbol)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::approvals::Inspect<<T as SystemConfig>::AccountId>
|
||||
for Pallet<T, I>
|
||||
{
|
||||
// Check the amount approved to be spent by an owner to a delegate
|
||||
fn allowance(
|
||||
asset: T::AssetId,
|
||||
owner: &<T as SystemConfig>::AccountId,
|
||||
delegate: &<T as SystemConfig>::AccountId,
|
||||
) -> T::Balance {
|
||||
Approvals::<T, I>::get((asset, &owner, &delegate))
|
||||
.map(|x| x.amount)
|
||||
.unwrap_or_else(Zero::zero)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::approvals::Mutate<<T as SystemConfig>::AccountId>
|
||||
for Pallet<T, I>
|
||||
{
|
||||
// Approve spending tokens from a given account
|
||||
fn approve(
|
||||
asset: T::AssetId,
|
||||
owner: &<T as SystemConfig>::AccountId,
|
||||
delegate: &<T as SystemConfig>::AccountId,
|
||||
amount: T::Balance,
|
||||
) -> DispatchResult {
|
||||
Self::do_approve_transfer(asset, owner, delegate, amount)
|
||||
}
|
||||
|
||||
fn transfer_from(
|
||||
asset: T::AssetId,
|
||||
owner: &<T as SystemConfig>::AccountId,
|
||||
delegate: &<T as SystemConfig>::AccountId,
|
||||
dest: &<T as SystemConfig>::AccountId,
|
||||
amount: T::Balance,
|
||||
) -> DispatchResult {
|
||||
Self::do_transfer_approved(asset, owner, delegate, dest, amount)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::roles::Inspect<<T as SystemConfig>::AccountId>
|
||||
for Pallet<T, I>
|
||||
{
|
||||
fn owner(asset: T::AssetId) -> Option<<T as SystemConfig>::AccountId> {
|
||||
Asset::<T, I>::get(asset).map(|x| x.owner)
|
||||
}
|
||||
|
||||
fn issuer(asset: T::AssetId) -> Option<<T as SystemConfig>::AccountId> {
|
||||
Asset::<T, I>::get(asset).map(|x| x.issuer)
|
||||
}
|
||||
|
||||
fn admin(asset: T::AssetId) -> Option<<T as SystemConfig>::AccountId> {
|
||||
Asset::<T, I>::get(asset).map(|x| x.admin)
|
||||
}
|
||||
|
||||
fn freezer(asset: T::AssetId) -> Option<<T as SystemConfig>::AccountId> {
|
||||
Asset::<T, I>::get(asset).map(|x| x.freezer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::InspectEnumerable<T::AccountId> for Pallet<T, I> {
|
||||
type AssetsIterator = KeyPrefixIterator<<T as Config<I>>::AssetId>;
|
||||
|
||||
/// Returns an iterator of the assets in existence.
|
||||
///
|
||||
/// NOTE: iterating this list invokes a storage read per item.
|
||||
fn asset_ids() -> Self::AssetsIterator {
|
||||
Asset::<T, I>::iter_keys()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::roles::ResetTeam<T::AccountId> for Pallet<T, I> {
|
||||
fn reset_team(
|
||||
id: T::AssetId,
|
||||
owner: T::AccountId,
|
||||
admin: T::AccountId,
|
||||
issuer: T::AccountId,
|
||||
freezer: T::AccountId,
|
||||
) -> DispatchResult {
|
||||
Self::do_reset_team(id, owner, admin, issuer, freezer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config<I>, I: 'static> fungibles::Refund<T::AccountId> for Pallet<T, I> {
|
||||
type AssetId = T::AssetId;
|
||||
type Balance = DepositBalanceOf<T, I>;
|
||||
fn deposit_held(id: Self::AssetId, who: T::AccountId) -> Option<(T::AccountId, Self::Balance)> {
|
||||
use ExistenceReason::*;
|
||||
match Account::<T, I>::get(&id, &who).ok_or(Error::<T, I>::NoDeposit).ok()?.reason {
|
||||
DepositHeld(b) => Some((who, b)),
|
||||
DepositFrom(d, b) => Some((d, b)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
fn refund(id: Self::AssetId, who: T::AccountId) -> DispatchResult {
|
||||
match Self::deposit_held(id.clone(), who.clone()) {
|
||||
Some((d, _)) if d == who => Self::do_refund(id, who, false),
|
||||
Some(..) => Self::do_refund_other(id, &who, None),
|
||||
None => Err(Error::<T, I>::NoDeposit.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Assets pallet's `StoredMap` implementation.
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<T: Config<I>, I: 'static> StoredMap<(T::AssetId, T::AccountId), T::Extra> for Pallet<T, I> {
|
||||
fn get(id_who: &(T::AssetId, T::AccountId)) -> T::Extra {
|
||||
let (id, who) = id_who;
|
||||
Account::<T, I>::get(id, who).map(|a| a.extra).unwrap_or_default()
|
||||
}
|
||||
|
||||
fn try_mutate_exists<R, E: From<DispatchError>>(
|
||||
id_who: &(T::AssetId, T::AccountId),
|
||||
f: impl FnOnce(&mut Option<T::Extra>) -> Result<R, E>,
|
||||
) -> Result<R, E> {
|
||||
let (id, who) = id_who;
|
||||
let mut maybe_extra = Account::<T, I>::get(id, who).map(|a| a.extra);
|
||||
let r = f(&mut maybe_extra)?;
|
||||
// They want to write some value or delete it.
|
||||
// If the account existed and they want to write a value, then we write.
|
||||
// If the account didn't exist and they want to delete it, then we let it pass.
|
||||
// Otherwise, we fail.
|
||||
Account::<T, I>::try_mutate(id, who, |maybe_account| {
|
||||
if let Some(extra) = maybe_extra {
|
||||
// They want to write a value. Let this happen only if the account actually exists.
|
||||
if let Some(ref mut account) = maybe_account {
|
||||
account.extra = extra;
|
||||
} else {
|
||||
return Err(DispatchError::NoProviders.into());
|
||||
}
|
||||
} else {
|
||||
// They want to delete it. Let this pass if the item never existed anyway.
|
||||
ensure!(maybe_account.is_none(), DispatchError::ConsumerRemaining);
|
||||
}
|
||||
Ok(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,162 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use super::*;
|
||||
use pezframe_support::traits::OnRuntimeUpgrade;
|
||||
use log;
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use pezsp_runtime::TryRuntimeError;
|
||||
|
||||
pub mod next_asset_id {
|
||||
use super::*;
|
||||
use pezsp_core::Get;
|
||||
|
||||
/// Set [`NextAssetId`] to the value of `ID` if [`NextAssetId`] does not exist yet.
|
||||
pub struct SetNextAssetId<ID, T: Config<I>, I: 'static = ()>(
|
||||
core::marker::PhantomData<(ID, T, I)>,
|
||||
);
|
||||
impl<ID, T: Config<I>, I: 'static> OnRuntimeUpgrade for SetNextAssetId<ID, T, I>
|
||||
where
|
||||
T::AssetId: Incrementable,
|
||||
ID: Get<T::AssetId>,
|
||||
{
|
||||
fn on_runtime_upgrade() -> pezframe_support::weights::Weight {
|
||||
if !NextAssetId::<T, I>::exists() {
|
||||
NextAssetId::<T, I>::put(ID::get());
|
||||
T::DbWeight::get().reads_writes(1, 1)
|
||||
} else {
|
||||
T::DbWeight::get().reads(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod v1 {
|
||||
use pezframe_support::{pezpallet_prelude::*, weights::Weight};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Decode)]
|
||||
pub struct OldAssetDetails<Balance, AccountId, DepositBalance> {
|
||||
pub owner: AccountId,
|
||||
pub issuer: AccountId,
|
||||
pub admin: AccountId,
|
||||
pub freezer: AccountId,
|
||||
pub supply: Balance,
|
||||
pub deposit: DepositBalance,
|
||||
pub min_balance: Balance,
|
||||
pub is_sufficient: bool,
|
||||
pub accounts: u32,
|
||||
pub sufficients: u32,
|
||||
pub approvals: u32,
|
||||
pub is_frozen: bool,
|
||||
}
|
||||
|
||||
impl<Balance, AccountId, DepositBalance> OldAssetDetails<Balance, AccountId, DepositBalance> {
|
||||
fn migrate_to_v1(self) -> AssetDetails<Balance, AccountId, DepositBalance> {
|
||||
let status = if self.is_frozen { AssetStatus::Frozen } else { AssetStatus::Live };
|
||||
|
||||
AssetDetails {
|
||||
owner: self.owner,
|
||||
issuer: self.issuer,
|
||||
admin: self.admin,
|
||||
freezer: self.freezer,
|
||||
supply: self.supply,
|
||||
deposit: self.deposit,
|
||||
min_balance: self.min_balance,
|
||||
is_sufficient: self.is_sufficient,
|
||||
accounts: self.accounts,
|
||||
sufficients: self.sufficients,
|
||||
approvals: self.approvals,
|
||||
status,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MigrateToV1<T>(core::marker::PhantomData<T>);
|
||||
impl<T: Config> OnRuntimeUpgrade for MigrateToV1<T> {
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let in_code_version = Pallet::<T>::in_code_storage_version();
|
||||
let on_chain_version = Pallet::<T>::on_chain_storage_version();
|
||||
if on_chain_version == 0 && in_code_version == 1 {
|
||||
let mut translated = 0u64;
|
||||
Asset::<T>::translate::<
|
||||
OldAssetDetails<T::Balance, T::AccountId, DepositBalanceOf<T>>,
|
||||
_,
|
||||
>(|_key, old_value| {
|
||||
translated.saturating_inc();
|
||||
Some(old_value.migrate_to_v1())
|
||||
});
|
||||
in_code_version.put::<Pallet<T>>();
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Upgraded {} pools, storage to version {:?}",
|
||||
translated,
|
||||
in_code_version
|
||||
);
|
||||
T::DbWeight::get().reads_writes(translated + 1, translated + 1)
|
||||
} else {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Migration did not execute. This probably should be removed"
|
||||
);
|
||||
T::DbWeight::get().reads(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
pezframe_support::ensure!(
|
||||
Pallet::<T>::on_chain_storage_version() == 0,
|
||||
"must upgrade linearly"
|
||||
);
|
||||
let prev_count = Asset::<T>::iter().count();
|
||||
Ok((prev_count as u32).encode())
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(prev_count: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect(
|
||||
"the state parameter should be something that was generated by pre_upgrade",
|
||||
);
|
||||
let post_count = Asset::<T>::iter().count() as u32;
|
||||
ensure!(
|
||||
prev_count == post_count,
|
||||
"the asset count before and after the migration should be the same"
|
||||
);
|
||||
|
||||
let in_code_version = Pallet::<T>::in_code_storage_version();
|
||||
let on_chain_version = Pallet::<T>::on_chain_storage_version();
|
||||
|
||||
pezframe_support::ensure!(in_code_version == 1, "must_upgrade");
|
||||
ensure!(
|
||||
in_code_version == on_chain_version,
|
||||
"after migration, the in_code_version and on_chain_version should be the same"
|
||||
);
|
||||
|
||||
Asset::<T>::iter().try_for_each(|(_id, asset)| -> Result<(), TryRuntimeError> {
|
||||
ensure!(
|
||||
asset.status == AssetStatus::Live || asset.status == AssetStatus::Frozen,
|
||||
"assets should only be live or frozen. None should be in destroying status, or undefined state"
|
||||
);
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test environment for Assets pallet.
|
||||
|
||||
use super::*;
|
||||
use crate as pezpallet_assets;
|
||||
|
||||
use codec::Encode;
|
||||
use pezframe_support::{
|
||||
assert_ok, construct_runtime, derive_impl, parameter_types,
|
||||
traits::{AsEnsureOriginWithArg, ConstU32},
|
||||
};
|
||||
use pezsp_io::storage;
|
||||
use pezsp_runtime::BuildStorage;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Balances: pezpallet_balances,
|
||||
Assets: pezpallet_assets,
|
||||
}
|
||||
);
|
||||
|
||||
type AccountId = u64;
|
||||
type AssetId = u32;
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u64>;
|
||||
type MaxConsumers = ConstU32<3>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
pub struct AssetsCallbackHandle;
|
||||
impl AssetsCallback<AssetId, AccountId> for AssetsCallbackHandle {
|
||||
fn created(_id: &AssetId, _owner: &AccountId) -> Result<(), ()> {
|
||||
if Self::should_err() {
|
||||
Err(())
|
||||
} else {
|
||||
storage::set(Self::CREATED.as_bytes(), &().encode());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn destroyed(_id: &AssetId) -> Result<(), ()> {
|
||||
if Self::should_err() {
|
||||
Err(())
|
||||
} else {
|
||||
storage::set(Self::DESTROYED.as_bytes(), &().encode());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AssetsCallbackHandle {
|
||||
pub const CREATED: &'static str = "asset_created";
|
||||
pub const DESTROYED: &'static str = "asset_destroyed";
|
||||
|
||||
const RETURN_ERROR: &'static str = "return_error";
|
||||
|
||||
// Configures `Self` to return `Ok` when callbacks are invoked
|
||||
pub fn set_return_ok() {
|
||||
storage::clear(Self::RETURN_ERROR.as_bytes());
|
||||
}
|
||||
|
||||
// Configures `Self` to return `Err` when callbacks are invoked
|
||||
pub fn set_return_error() {
|
||||
storage::set(Self::RETURN_ERROR.as_bytes(), &().encode());
|
||||
}
|
||||
|
||||
// If `true`, callback should return `Err`, `Ok` otherwise.
|
||||
fn should_err() -> bool {
|
||||
storage::exists(Self::RETURN_ERROR.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive_impl(crate::config_preludes::TestDefaultConfig)]
|
||||
impl Config for Test {
|
||||
type Currency = Balances;
|
||||
type CreateOrigin = AsEnsureOriginWithArg<pezframe_system::EnsureSigned<u64>>;
|
||||
type ForceOrigin = pezframe_system::EnsureRoot<u64>;
|
||||
type Freezer = TestFreezer;
|
||||
type Holder = TestHolder;
|
||||
type CallbackHandle = (AssetsCallbackHandle, AutoIncAssetId<Test>);
|
||||
type ReserveData = u128;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = AssetsBenchmarkHelper;
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
pub struct AssetsBenchmarkHelper;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
impl<AssetIdParameter: From<u32>, ReserveIdParameter: From<u32>>
|
||||
BenchmarkHelper<AssetIdParameter, ReserveIdParameter> for AssetsBenchmarkHelper
|
||||
{
|
||||
fn create_asset_id_parameter(id: u32) -> AssetIdParameter {
|
||||
id.into()
|
||||
}
|
||||
fn create_reserve_id_parameter(id: u32) -> ReserveIdParameter {
|
||||
id.into()
|
||||
}
|
||||
}
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub enum Hook {
|
||||
Died(u32, u64),
|
||||
}
|
||||
parameter_types! {
|
||||
static Frozen: HashMap<(u32, u64), u64> = Default::default();
|
||||
static OnHold: HashMap<(u32, u64), u64> = Default::default();
|
||||
static Hooks: Vec<Hook> = Default::default();
|
||||
}
|
||||
|
||||
pub struct TestHolder;
|
||||
impl BalanceOnHold<u32, u64, u64> for TestHolder {
|
||||
fn balance_on_hold(asset: u32, who: &u64) -> Option<u64> {
|
||||
OnHold::get().get(&(asset, *who)).cloned()
|
||||
}
|
||||
|
||||
fn died(asset: u32, who: &u64) {
|
||||
Hooks::mutate(|v| v.push(Hook::Died(asset, *who)))
|
||||
}
|
||||
|
||||
fn contains_holds(asset: AssetId) -> bool {
|
||||
OnHold::get().iter().any(|((k, _), _)| &asset == k)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_balance_on_hold(asset: u32, who: u64, amount: u64) {
|
||||
OnHold::mutate(|v| {
|
||||
let amount_on_hold = v.get(&(asset, who)).unwrap_or(&0);
|
||||
|
||||
if &amount > amount_on_hold {
|
||||
// Hold more funds
|
||||
let amount = amount - amount_on_hold;
|
||||
let f = DebitFlags { keep_alive: true, best_effort: false };
|
||||
assert_ok!(Assets::decrease_balance(asset, &who, amount, f, |_, _| Ok(())));
|
||||
} else {
|
||||
// Release funds on hold
|
||||
let amount = amount_on_hold - amount;
|
||||
assert_ok!(Assets::increase_balance(asset, &who, amount, |_| Ok(())));
|
||||
}
|
||||
|
||||
// Asset amount still "exists", we just store it here
|
||||
v.insert((asset, who), amount);
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn clear_balance_on_hold(asset: u32, who: u64) {
|
||||
OnHold::mutate(|v| {
|
||||
v.remove(&(asset, who));
|
||||
});
|
||||
}
|
||||
pub struct TestFreezer;
|
||||
impl FrozenBalance<u32, u64, u64> for TestFreezer {
|
||||
fn frozen_balance(asset: u32, who: &u64) -> Option<u64> {
|
||||
Frozen::get().get(&(asset, *who)).cloned()
|
||||
}
|
||||
|
||||
fn died(asset: u32, who: &u64) {
|
||||
Hooks::mutate(|v| v.push(Hook::Died(asset, *who)));
|
||||
|
||||
// Sanity check: dead accounts have no balance.
|
||||
assert!(Assets::balance(asset, *who).is_zero());
|
||||
}
|
||||
|
||||
/// Return a value that indicates if there are registered freezes for a given asset.
|
||||
fn contains_freezes(asset: AssetId) -> bool {
|
||||
Frozen::get().iter().any(|((k, _), _)| &asset == k)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_frozen_balance(asset: u32, who: u64, amount: u64) {
|
||||
Frozen::mutate(|v| {
|
||||
v.insert((asset, who), amount);
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn clear_frozen_balance(asset: u32, who: u64) {
|
||||
Frozen::mutate(|v| {
|
||||
v.remove(&(asset, who));
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn hooks() -> Vec<Hook> {
|
||||
Hooks::get().clone()
|
||||
}
|
||||
|
||||
pub(crate) fn take_hooks() -> Vec<Hook> {
|
||||
Hooks::take()
|
||||
}
|
||||
|
||||
pub(crate) fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let mut storage = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
|
||||
let config: pezpallet_assets::GenesisConfig<Test> = pezpallet_assets::GenesisConfig {
|
||||
assets: vec![
|
||||
// id, owner, is_sufficient, min_balance
|
||||
(999, 0, true, 1),
|
||||
],
|
||||
metadata: vec![
|
||||
// id, name, symbol, decimals
|
||||
(999, "Token Name".into(), "TOKEN".into(), 10),
|
||||
],
|
||||
accounts: vec![
|
||||
// id, account_id, balance
|
||||
(999, 1, 100),
|
||||
],
|
||||
next_asset_id: None,
|
||||
reserves: vec![],
|
||||
};
|
||||
|
||||
config.assimilate_storage(&mut storage).unwrap();
|
||||
|
||||
let mut ext: pezsp_io::TestExternalities = storage.into();
|
||||
// Clear thread local vars for https://github.com/pezkuwichain/kurdistan-sdk/issues/2.
|
||||
ext.execute_with(|| take_hooks());
|
||||
ext.execute_with(|| System::set_block_number(1));
|
||||
ext
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,358 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Tests for [`ItemOf`], [`fungible::UnionOf`] and [`fungibles::UnionOf`] set types.
|
||||
|
||||
use super::*;
|
||||
use pezframe_support::{
|
||||
parameter_types,
|
||||
traits::{
|
||||
fungible,
|
||||
fungible::ItemOf,
|
||||
fungibles,
|
||||
tokens::{
|
||||
fungibles::{
|
||||
Balanced as FungiblesBalanced, Create as FungiblesCreate,
|
||||
Inspect as FungiblesInspect, Mutate as FungiblesMutate,
|
||||
},
|
||||
Fortitude, Precision, Preservation,
|
||||
},
|
||||
},
|
||||
};
|
||||
use pezsp_runtime::{traits::ConvertToValue, Either};
|
||||
|
||||
const FIRST_ASSET: u32 = 0;
|
||||
const UNKNOWN_ASSET: u32 = 10;
|
||||
|
||||
parameter_types! {
|
||||
pub const LeftAsset: Either<(), u32> = Either::Left(());
|
||||
pub const RightAsset: Either<u32, ()> = Either::Right(());
|
||||
pub const RightUnitAsset: Either<(), ()> = Either::Right(());
|
||||
}
|
||||
|
||||
/// Implementation of the `fungible` traits through the [`ItemOf`] type, specifically for a
|
||||
/// single asset class from [`T`] identified by [`FIRST_ASSET`].
|
||||
type FirstFungible<T> = ItemOf<T, pezframe_support::traits::ConstU32<{ FIRST_ASSET }>, u64>;
|
||||
|
||||
/// Implementation of the `fungible` traits through the [`ItemOf`] type, specifically for a
|
||||
/// single asset class from [`T`] identified by [`UNKNOWN_ASSET`].
|
||||
type UnknownFungible<T> = ItemOf<T, pezframe_support::traits::ConstU32<{ UNKNOWN_ASSET }>, u64>;
|
||||
|
||||
/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes
|
||||
/// the [`FirstFungible`] from the left.
|
||||
type LeftFungible<T> = fungible::UnionOf<FirstFungible<T>, T, ConvertToValue<LeftAsset>, (), u64>;
|
||||
|
||||
/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes
|
||||
/// the [`LeftFungible`] from the right.
|
||||
type RightFungible<T> =
|
||||
fungible::UnionOf<UnknownFungible<T>, LeftFungible<T>, ConvertToValue<RightUnitAsset>, (), u64>;
|
||||
|
||||
/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes
|
||||
/// the [`RightFungible`] from the left.
|
||||
type LeftFungibles<T> = fungibles::UnionOf<RightFungible<T>, T, ConvertToValue<LeftAsset>, (), u64>;
|
||||
|
||||
/// Implementation of `fungibles` traits using [`fungibles::UnionOf`] that exclusively utilizes
|
||||
/// the [`LeftFungibles`] from the right.
|
||||
///
|
||||
/// By using this type, we can navigate through each branch of [`fungible::UnionOf`],
|
||||
/// [`fungibles::UnionOf`], and [`ItemOf`] to access the underlying `fungibles::*`
|
||||
/// implementation provided by the pallet.
|
||||
type First<T> = fungibles::UnionOf<T, LeftFungibles<T>, ConvertToValue<RightAsset>, (), u64>;
|
||||
|
||||
#[test]
|
||||
fn deposit_from_set_types_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset1 = 0;
|
||||
let account1 = 1;
|
||||
let account2 = 2;
|
||||
|
||||
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
|
||||
assert_ok!(Assets::mint_into(asset1, &account1, 100));
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
|
||||
let imb = First::<Assets>::deposit((), &account2, 50, Precision::Exact).unwrap();
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 50);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
|
||||
System::assert_has_event(RuntimeEvent::Assets(crate::Event::Deposited {
|
||||
asset_id: asset1,
|
||||
who: account2,
|
||||
amount: 50,
|
||||
}));
|
||||
|
||||
assert_eq!(imb.peek(), 50);
|
||||
|
||||
let (imb1, imb2) = imb.split(30);
|
||||
assert_eq!(imb1.peek(), 30);
|
||||
assert_eq!(imb2.peek(), 20);
|
||||
|
||||
drop(imb2);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 120);
|
||||
|
||||
assert!(First::<Assets>::settle(&account1, imb1, Preservation::Preserve).is_ok());
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 70);
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 50);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 120);
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_from_set_types_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset1: u32 = 0;
|
||||
let account1: u64 = 1;
|
||||
|
||||
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
|
||||
assert_ok!(Assets::mint_into(asset1, &account1, 100));
|
||||
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
|
||||
let imb = First::<Assets>::issue((), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 200);
|
||||
assert_eq!(imb.peek(), 100);
|
||||
|
||||
let (imb1, imb2) = imb.split(30);
|
||||
assert_eq!(imb1.peek(), 30);
|
||||
assert_eq!(imb2.peek(), 70);
|
||||
|
||||
drop(imb2);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 130);
|
||||
|
||||
assert!(First::<Assets>::resolve(&account1, imb1).is_ok());
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 130);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 130);
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pair_from_set_types_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset1: u32 = 0;
|
||||
let account1: u64 = 1;
|
||||
|
||||
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
|
||||
assert_ok!(Assets::mint_into(asset1, &account1, 100));
|
||||
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
|
||||
let (debt, credit) = First::<Assets>::pair((), 100).unwrap();
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
assert_eq!(debt.peek(), 100);
|
||||
assert_eq!(credit.peek(), 100);
|
||||
|
||||
let (debt1, debt2) = debt.split(30);
|
||||
assert_eq!(debt1.peek(), 30);
|
||||
assert_eq!(debt2.peek(), 70);
|
||||
|
||||
drop(debt2);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 170);
|
||||
|
||||
assert!(First::<Assets>::settle(&account1, debt1, Preservation::Preserve).is_ok());
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 70);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 170);
|
||||
|
||||
let (credit1, credit2) = credit.split(40);
|
||||
assert_eq!(credit1.peek(), 40);
|
||||
assert_eq!(credit2.peek(), 60);
|
||||
|
||||
drop(credit2);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 110);
|
||||
|
||||
assert!(First::<Assets>::resolve(&account1, credit1).is_ok());
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 110);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 110);
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rescind_from_set_types_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset1: u32 = 0;
|
||||
let account1: u64 = 1;
|
||||
|
||||
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
|
||||
assert_ok!(Assets::mint_into(asset1, &account1, 100));
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
|
||||
let imb = First::<Assets>::rescind((), 20);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 80);
|
||||
|
||||
assert_eq!(imb.peek(), 20);
|
||||
|
||||
let (imb1, imb2) = imb.split(15);
|
||||
assert_eq!(imb1.peek(), 15);
|
||||
assert_eq!(imb2.peek(), 5);
|
||||
|
||||
drop(imb2);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 85);
|
||||
|
||||
assert!(First::<Assets>::settle(&account1, imb1, Preservation::Preserve).is_ok());
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 85);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 85);
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resolve_from_set_types_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset1: u32 = 0;
|
||||
let account1: u64 = 1;
|
||||
let account2: u64 = 2;
|
||||
let ed = 11;
|
||||
|
||||
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, ed));
|
||||
assert_ok!(Assets::mint_into(asset1, &account1, 100));
|
||||
|
||||
assert_eq!(First::<Assets>::balance((), &account1), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
|
||||
let imb = First::<Assets>::issue((), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 200);
|
||||
assert_eq!(imb.peek(), 100);
|
||||
|
||||
let (imb1, imb2) = imb.split(10);
|
||||
assert_eq!(imb1.peek(), 10);
|
||||
assert_eq!(imb2.peek(), 90);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 200);
|
||||
|
||||
// ed requirements not met.
|
||||
let imb1 = First::<Assets>::resolve(&account2, imb1).unwrap_err();
|
||||
assert_eq!(imb1.peek(), 10);
|
||||
drop(imb1);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 190);
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 0);
|
||||
|
||||
// resolve to new account `2`.
|
||||
assert_ok!(First::<Assets>::resolve(&account2, imb2));
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 190);
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 90);
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn settle_from_set_types_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset1: u32 = 0;
|
||||
let account1: u64 = 1;
|
||||
let account2: u64 = 2;
|
||||
let ed = 11;
|
||||
|
||||
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, ed));
|
||||
assert_ok!(Assets::mint_into(asset1, &account1, 100));
|
||||
assert_ok!(Assets::mint_into(asset1, &account2, 100));
|
||||
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 200);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
|
||||
let imb = First::<Assets>::rescind((), 100);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
assert_eq!(imb.peek(), 100);
|
||||
|
||||
let (imb1, imb2) = imb.split(10);
|
||||
assert_eq!(imb1.peek(), 10);
|
||||
assert_eq!(imb2.peek(), 90);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
|
||||
// ed requirements not met.
|
||||
let imb2 = First::<Assets>::settle(&account2, imb2, Preservation::Preserve).unwrap_err();
|
||||
assert_eq!(imb2.peek(), 90);
|
||||
drop(imb2);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 190);
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 100);
|
||||
|
||||
// settle to account `1`.
|
||||
assert_ok!(First::<Assets>::settle(&account2, imb1, Preservation::Preserve));
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 190);
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 90);
|
||||
|
||||
let imb = First::<Assets>::rescind((), 85);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 105);
|
||||
assert_eq!(imb.peek(), 85);
|
||||
|
||||
// settle to account `1` and expect some dust.
|
||||
let imb = First::<Assets>::settle(&account2, imb, Preservation::Expendable).unwrap();
|
||||
assert_eq!(imb.peek(), 5);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 105);
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 0);
|
||||
|
||||
drop(imb);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 100);
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn withdraw_from_set_types_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let asset1 = 0;
|
||||
let account1 = 1;
|
||||
let account2 = 2;
|
||||
|
||||
assert_ok!(<Assets as FungiblesCreate<u64>>::create(asset1, account1, true, 1));
|
||||
assert_ok!(Assets::mint_into(asset1, &account1, 100));
|
||||
assert_ok!(Assets::mint_into(asset1, &account2, 100));
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 200);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
|
||||
let imb = First::<Assets>::withdraw(
|
||||
(),
|
||||
&account2,
|
||||
50,
|
||||
Precision::Exact,
|
||||
Preservation::Preserve,
|
||||
Fortitude::Polite,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 50);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 200);
|
||||
|
||||
System::assert_has_event(RuntimeEvent::Assets(crate::Event::Withdrawn {
|
||||
asset_id: asset1,
|
||||
who: account2,
|
||||
amount: 50,
|
||||
}));
|
||||
|
||||
assert_eq!(imb.peek(), 50);
|
||||
drop(imb);
|
||||
assert_eq!(First::<Assets>::total_issuance(()), 150);
|
||||
assert_eq!(First::<Assets>::balance((), &account2), 50);
|
||||
|
||||
assert_eq!(First::<Assets>::total_issuance(()), Assets::total_issuance(asset1));
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,361 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Various basic types for use in the assets pallet.
|
||||
|
||||
use super::*;
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::*,
|
||||
traits::{fungible, tokens::ConversionToAssetBalance},
|
||||
};
|
||||
use pezsp_runtime::{traits::Convert, FixedPointNumber, FixedU128};
|
||||
|
||||
pub type DepositBalanceOf<T, I = ()> =
|
||||
<<T as Config<I>>::Currency as Currency<<T as SystemConfig>::AccountId>>::Balance;
|
||||
pub type AssetAccountOf<T, I> = AssetAccount<
|
||||
<T as Config<I>>::Balance,
|
||||
DepositBalanceOf<T, I>,
|
||||
<T as Config<I>>::Extra,
|
||||
<T as SystemConfig>::AccountId,
|
||||
>;
|
||||
pub type ExistenceReasonOf<T, I> =
|
||||
ExistenceReason<DepositBalanceOf<T, I>, <T as SystemConfig>::AccountId>;
|
||||
|
||||
/// AssetStatus holds the current state of the asset. It could either be Live and available for use,
|
||||
/// or in a Destroying state.
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub enum AssetStatus {
|
||||
/// The asset is active and able to be used.
|
||||
Live,
|
||||
/// Whether the asset is frozen for non-admin transfers.
|
||||
Frozen,
|
||||
/// The asset is currently being destroyed, and all actions are no longer permitted on the
|
||||
/// asset. Once set to `Destroying`, the asset can never transition back to a `Live` state.
|
||||
Destroying,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct AssetDetails<Balance, AccountId, DepositBalance> {
|
||||
/// Can change `owner`, `issuer`, `freezer` and `admin` accounts.
|
||||
pub owner: AccountId,
|
||||
/// Can mint tokens.
|
||||
pub issuer: AccountId,
|
||||
/// Can thaw tokens, force transfers and burn tokens from any account.
|
||||
pub admin: AccountId,
|
||||
/// Can freeze tokens.
|
||||
pub freezer: AccountId,
|
||||
/// The total supply across all accounts.
|
||||
pub supply: Balance,
|
||||
/// The balance deposited for this asset. This pays for the data stored here.
|
||||
pub deposit: DepositBalance,
|
||||
/// The ED for virtual accounts.
|
||||
pub min_balance: Balance,
|
||||
/// If `true`, then any account with this asset is given a provider reference. Otherwise, it
|
||||
/// requires a consumer reference.
|
||||
pub is_sufficient: bool,
|
||||
/// The total number of accounts.
|
||||
pub accounts: u32,
|
||||
/// The total number of accounts for which we have placed a self-sufficient reference.
|
||||
pub sufficients: u32,
|
||||
/// The total number of approvals.
|
||||
pub approvals: u32,
|
||||
/// The status of the asset
|
||||
pub status: AssetStatus,
|
||||
}
|
||||
|
||||
/// Data concerning an approval.
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, MaxEncodedLen, TypeInfo)]
|
||||
pub struct Approval<Balance, DepositBalance> {
|
||||
/// The amount of funds approved for the balance transfer from the owner to some delegated
|
||||
/// target.
|
||||
pub amount: Balance,
|
||||
/// The amount reserved on the owner's account to hold this item in storage.
|
||||
pub deposit: DepositBalance,
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_bool_decodes_to_consumer_or_sufficient() {
|
||||
assert_eq!(false.encode(), ExistenceReason::<(), ()>::Consumer.encode());
|
||||
assert_eq!(true.encode(), ExistenceReason::<(), ()>::Sufficient.encode());
|
||||
}
|
||||
|
||||
/// The reason for an account's existence within an asset class.
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub enum ExistenceReason<Balance, AccountId> {
|
||||
/// A consumer reference was used to create this account.
|
||||
#[codec(index = 0)]
|
||||
Consumer,
|
||||
/// The asset class is `sufficient` for account existence.
|
||||
#[codec(index = 1)]
|
||||
Sufficient,
|
||||
/// The account holder has placed a deposit to exist within an asset class.
|
||||
#[codec(index = 2)]
|
||||
DepositHeld(Balance),
|
||||
/// A deposit was placed for this account to exist, but it has been refunded.
|
||||
#[codec(index = 3)]
|
||||
DepositRefunded,
|
||||
/// Some other `AccountId` has placed a deposit to make this account exist.
|
||||
/// An account with such a reason might not be referenced in `system`.
|
||||
#[codec(index = 4)]
|
||||
DepositFrom(AccountId, Balance),
|
||||
}
|
||||
|
||||
impl<Balance, AccountId> ExistenceReason<Balance, AccountId>
|
||||
where
|
||||
AccountId: Clone,
|
||||
{
|
||||
pub fn take_deposit(&mut self) -> Option<Balance> {
|
||||
if !matches!(self, ExistenceReason::DepositHeld(_)) {
|
||||
return None;
|
||||
}
|
||||
if let ExistenceReason::DepositHeld(deposit) =
|
||||
core::mem::replace(self, ExistenceReason::DepositRefunded)
|
||||
{
|
||||
Some(deposit)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_deposit_from(&mut self) -> Option<(AccountId, Balance)> {
|
||||
if !matches!(self, ExistenceReason::DepositFrom(..)) {
|
||||
return None;
|
||||
}
|
||||
if let ExistenceReason::DepositFrom(depositor, deposit) =
|
||||
core::mem::replace(self, ExistenceReason::DepositRefunded)
|
||||
{
|
||||
Some((depositor, deposit))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ensure_bool_decodes_to_liquid_or_frozen() {
|
||||
assert_eq!(false.encode(), AccountStatus::Liquid.encode());
|
||||
assert_eq!(true.encode(), AccountStatus::Frozen.encode());
|
||||
}
|
||||
|
||||
/// The status of an asset account.
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub enum AccountStatus {
|
||||
/// Asset account can receive and transfer the assets.
|
||||
Liquid,
|
||||
/// Asset account cannot transfer the assets.
|
||||
Frozen,
|
||||
/// Asset account cannot receive and transfer the assets.
|
||||
Blocked,
|
||||
}
|
||||
impl AccountStatus {
|
||||
/// Returns `true` if frozen or blocked.
|
||||
pub fn is_frozen(&self) -> bool {
|
||||
matches!(self, AccountStatus::Frozen | AccountStatus::Blocked)
|
||||
}
|
||||
/// Returns `true` if blocked.
|
||||
pub fn is_blocked(&self) -> bool {
|
||||
matches!(self, AccountStatus::Blocked)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct AssetAccount<Balance, DepositBalance, Extra, AccountId> {
|
||||
/// The account's balance.
|
||||
///
|
||||
/// The part of the `balance` may be frozen by the [`Config::Freezer`]. The on-hold portion is
|
||||
/// not included here and is tracked by the [`Config::Holder`].
|
||||
pub balance: Balance,
|
||||
/// The status of the account.
|
||||
pub status: AccountStatus,
|
||||
/// The reason for the existence of the account.
|
||||
pub reason: ExistenceReason<DepositBalance, AccountId>,
|
||||
/// Additional "sidecar" data, in case some other pallet wants to use this storage item.
|
||||
pub extra: Extra,
|
||||
}
|
||||
|
||||
#[derive(Clone, Encode, Decode, Eq, PartialEq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
pub struct AssetMetadata<DepositBalance, BoundedString> {
|
||||
/// The balance deposited for this metadata.
|
||||
///
|
||||
/// This pays for the data stored in this struct.
|
||||
pub deposit: DepositBalance,
|
||||
/// The user friendly name of this asset. Limited in length by `StringLimit`.
|
||||
pub name: BoundedString,
|
||||
/// The ticker symbol for this asset. Limited in length by `StringLimit`.
|
||||
pub symbol: BoundedString,
|
||||
/// The number of decimals this asset uses to represent one unit.
|
||||
pub decimals: u8,
|
||||
/// Whether the asset metadata may be changed by a non Force origin.
|
||||
pub is_frozen: bool,
|
||||
}
|
||||
|
||||
/// Trait for allowing a minimum balance on the account to be specified, beyond the
|
||||
/// `minimum_balance` of the asset. This is additive - the `minimum_balance` of the asset must be
|
||||
/// met *and then* anything here in addition.
|
||||
pub trait FrozenBalance<AssetId, AccountId, Balance> {
|
||||
/// Return the frozen balance.
|
||||
///
|
||||
/// Generally, the balance of every account must be at least the sum of this (if `Some`) and
|
||||
/// the asset's `minimum_balance` (the latter since there may be complications to destroying an
|
||||
/// asset's account completely).
|
||||
///
|
||||
/// Under normal behaviour, the account balance should not go below the sum of this (if `Some`)
|
||||
/// and the asset's minimum balance. However, the account balance may reasonably begin below
|
||||
/// this sum (e.g. if less than the sum had ever been transferred into the account).
|
||||
///
|
||||
/// In special cases (privileged intervention) the account balance may also go below the sum.
|
||||
///
|
||||
/// If `None` is returned, then nothing special is enforced.
|
||||
fn frozen_balance(asset: AssetId, who: &AccountId) -> Option<Balance>;
|
||||
|
||||
/// Called after an account has been removed.
|
||||
fn died(asset: AssetId, who: &AccountId);
|
||||
|
||||
/// Return a value that indicates if there are registered freezes for a given asset.
|
||||
fn contains_freezes(asset: AssetId) -> bool;
|
||||
}
|
||||
|
||||
impl<AssetId, AccountId, Balance> FrozenBalance<AssetId, AccountId, Balance> for () {
|
||||
fn frozen_balance(_: AssetId, _: &AccountId) -> Option<Balance> {
|
||||
None
|
||||
}
|
||||
fn died(_: AssetId, _: &AccountId) {}
|
||||
fn contains_freezes(_: AssetId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// This trait indicates a balance that is _on hold_ for an asset account.
|
||||
///
|
||||
/// A balance _on hold_ is a balance that, while is assigned to an account,
|
||||
/// is outside the direct control of it. Instead, is being _held_ by the
|
||||
/// system logic (i.e. Pallets) and can be eventually burned or released.
|
||||
pub trait BalanceOnHold<AssetId, AccountId, Balance> {
|
||||
/// Return the held balance.
|
||||
///
|
||||
/// If `Some`, it means some balance is _on hold_, and it can be
|
||||
/// infallibly burned.
|
||||
///
|
||||
/// If `None` is returned, then no balance is _on hold_ for `who`'s asset
|
||||
/// account.
|
||||
fn balance_on_hold(asset: AssetId, who: &AccountId) -> Option<Balance>;
|
||||
|
||||
/// Called after an account has been removed.
|
||||
///
|
||||
/// It is expected that this method is called only when there is no balance
|
||||
/// on hold. Otherwise, an account should not be removed.
|
||||
fn died(asset: AssetId, who: &AccountId);
|
||||
|
||||
/// Return a value that indicates if there are registered holds for a given asset.
|
||||
fn contains_holds(asset: AssetId) -> bool;
|
||||
}
|
||||
|
||||
impl<AssetId, AccountId, Balance> BalanceOnHold<AssetId, AccountId, Balance> for () {
|
||||
fn balance_on_hold(_: AssetId, _: &AccountId) -> Option<Balance> {
|
||||
None
|
||||
}
|
||||
fn died(_: AssetId, _: &AccountId) {}
|
||||
fn contains_holds(_: AssetId) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct TransferFlags {
|
||||
/// The debited account must stay alive at the end of the operation; an error is returned if
|
||||
/// this cannot be achieved legally.
|
||||
pub keep_alive: bool,
|
||||
/// Less than the amount specified needs be debited by the operation for it to be considered
|
||||
/// successful. If `false`, then the amount debited will always be at least the amount
|
||||
/// specified.
|
||||
pub best_effort: bool,
|
||||
/// Any additional funds debited (due to minimum balance requirements) should be burned rather
|
||||
/// than credited to the destination account.
|
||||
pub burn_dust: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub struct DebitFlags {
|
||||
/// The debited account must stay alive at the end of the operation; an error is returned if
|
||||
/// this cannot be achieved legally.
|
||||
pub keep_alive: bool,
|
||||
/// Less than the amount specified needs be debited by the operation for it to be considered
|
||||
/// successful. If `false`, then the amount debited will always be at least the amount
|
||||
/// specified.
|
||||
pub best_effort: bool,
|
||||
}
|
||||
|
||||
impl From<TransferFlags> for DebitFlags {
|
||||
fn from(f: TransferFlags) -> Self {
|
||||
Self { keep_alive: f.keep_alive, best_effort: f.best_effort }
|
||||
}
|
||||
}
|
||||
|
||||
/// Possible errors when converting between external and asset balances.
|
||||
#[derive(Eq, PartialEq, Copy, Clone, RuntimeDebug, Encode, Decode)]
|
||||
pub enum ConversionError {
|
||||
/// The external minimum balance must not be zero.
|
||||
MinBalanceZero,
|
||||
/// The asset is not present in storage.
|
||||
AssetMissing,
|
||||
/// The asset is not sufficient and thus does not have a reliable `min_balance` so it cannot be
|
||||
/// converted.
|
||||
AssetNotSufficient,
|
||||
}
|
||||
|
||||
// Type alias for `pezframe_system`'s account id.
|
||||
type AccountIdOf<T> = <T as pezframe_system::Config>::AccountId;
|
||||
// This pallet's asset id and balance type.
|
||||
type AssetIdOf<T, I> = <T as Config<I>>::AssetId;
|
||||
type AssetBalanceOf<T, I> = <T as Config<I>>::Balance;
|
||||
// Generic fungible balance type.
|
||||
type BalanceOf<F, T> = <F as fungible::Inspect<AccountIdOf<T>>>::Balance;
|
||||
|
||||
/// Converts a balance value into an asset balance based on the ratio between the fungible's
|
||||
/// minimum balance and the minimum asset balance.
|
||||
pub struct BalanceToAssetBalance<F, T, CON, I = ()>(PhantomData<(F, T, CON, I)>);
|
||||
impl<F, T, CON, I> ConversionToAssetBalance<BalanceOf<F, T>, AssetIdOf<T, I>, AssetBalanceOf<T, I>>
|
||||
for BalanceToAssetBalance<F, T, CON, I>
|
||||
where
|
||||
F: fungible::Inspect<AccountIdOf<T>>,
|
||||
T: Config<I>,
|
||||
I: 'static,
|
||||
CON: Convert<BalanceOf<F, T>, AssetBalanceOf<T, I>>,
|
||||
{
|
||||
type Error = ConversionError;
|
||||
|
||||
/// Convert the given balance value into an asset balance based on the ratio between the
|
||||
/// fungible's minimum balance and the minimum asset balance.
|
||||
///
|
||||
/// Will return `Err` if the asset is not found, not sufficient or the fungible's minimum
|
||||
/// balance is zero.
|
||||
fn to_asset_balance(
|
||||
balance: BalanceOf<F, T>,
|
||||
asset_id: AssetIdOf<T, I>,
|
||||
) -> Result<AssetBalanceOf<T, I>, ConversionError> {
|
||||
let asset = Asset::<T, I>::get(asset_id).ok_or(ConversionError::AssetMissing)?;
|
||||
// only sufficient assets have a min balance with reliable value
|
||||
ensure!(asset.is_sufficient, ConversionError::AssetNotSufficient);
|
||||
let min_balance = CON::convert(F::minimum_balance());
|
||||
// make sure we don't divide by zero
|
||||
ensure!(!min_balance.is_zero(), ConversionError::MinBalanceZero);
|
||||
let balance = CON::convert(balance);
|
||||
// balance * asset.min_balance / min_balance
|
||||
Ok(FixedU128::saturating_from_rational(asset.min_balance, min_balance)
|
||||
.saturating_mul_int(balance))
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "pezpallet-atomic-swap"
|
||||
version = "28.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME atomic swap pallet"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
frame = { workspace = true, features = ["runtime"] }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["codec/std", "frame/std", "pezpallet-balances/std", "scale-info/std"]
|
||||
try-runtime = ["frame/try-runtime", "pezpallet-balances/try-runtime"]
|
||||
runtime-benchmarks = [
|
||||
"frame/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
# Atomic Swap
|
||||
|
||||
A module for atomically sending funds.
|
||||
|
||||
- [`atomic_swap::Config`](https://docs.rs/pezpallet-atomic-swap/latest/pallet_atomic_swap/trait.Config.html)
|
||||
- [`Call`](https://docs.rs/pezpallet-atomic-swap/latest/pallet_atomic_swap/enum.Call.html)
|
||||
- [`Module`](https://docs.rs/pezpallet-atomic-swap/latest/pallet_atomic_swap/struct.Module.html)
|
||||
|
||||
## Overview
|
||||
|
||||
A module for atomically sending funds from an origin to a target. A proof
|
||||
is used to allow the target to approve (claim) the swap. If the swap is not
|
||||
claimed within a specified duration of time, the sender may cancel it.
|
||||
|
||||
## Interface
|
||||
|
||||
### Dispatchable Functions
|
||||
|
||||
- `create_swap` - called by a sender to register a new atomic swap
|
||||
- `claim_swap` - called by the target to approve a swap
|
||||
- `cancel_swap` - may be called by a sender after a specified duration
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,362 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! # Atomic Swap
|
||||
//!
|
||||
//! A pallet for atomically sending funds.
|
||||
//!
|
||||
//! - [`Config`]
|
||||
//! - [`Call`]
|
||||
//! - [`Pallet`]
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! A pallet for atomically sending funds from an origin to a target. A proof
|
||||
//! is used to allow the target to approve (claim) the swap. If the swap is not
|
||||
//! claimed within a specified duration of time, the sender may cancel it.
|
||||
//!
|
||||
//! ## Interface
|
||||
//!
|
||||
//! ### Dispatchable Functions
|
||||
//!
|
||||
//! * [`create_swap`](Call::create_swap) - called by a sender to register a new atomic swap
|
||||
//! * [`claim_swap`](Call::claim_swap) - called by the target to approve a swap
|
||||
//! * [`cancel_swap`](Call::cancel_swap) - may be called by a sender after a specified duration
|
||||
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
mod tests;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use core::{
|
||||
marker::PhantomData,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
use frame::{
|
||||
prelude::*,
|
||||
traits::{BalanceStatus, Currency, ReservableCurrency},
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
/// Pending atomic swap operation.
|
||||
#[derive(
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
RuntimeDebugNoBound,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
#[codec(mel_bound())]
|
||||
pub struct PendingSwap<T: Config> {
|
||||
/// Source of the swap.
|
||||
pub source: T::AccountId,
|
||||
/// Action of this swap.
|
||||
pub action: T::SwapAction,
|
||||
/// End block of the lock.
|
||||
pub end_block: BlockNumberFor<T>,
|
||||
}
|
||||
|
||||
/// Hashed proof type.
|
||||
pub type HashedProof = [u8; 32];
|
||||
|
||||
/// Definition of a pending atomic swap action. It contains the following three phrases:
|
||||
///
|
||||
/// - **Reserve**: reserve the resources needed for a swap. This is to make sure that **Claim**
|
||||
/// succeeds with best efforts.
|
||||
/// - **Claim**: claim any resources reserved in the first phrase.
|
||||
/// - **Cancel**: cancel any resources reserved in the first phrase.
|
||||
pub trait SwapAction<AccountId, T: Config> {
|
||||
/// Reserve the resources needed for the swap, from the given `source`. The reservation is
|
||||
/// allowed to fail. If that is the case, the the full swap creation operation is cancelled.
|
||||
fn reserve(&self, source: &AccountId) -> DispatchResult;
|
||||
/// Claim the reserved resources, with `source` and `target`. Returns whether the claim
|
||||
/// succeeds.
|
||||
fn claim(&self, source: &AccountId, target: &AccountId) -> bool;
|
||||
/// Weight for executing the operation.
|
||||
fn weight(&self) -> Weight;
|
||||
/// Cancel the resources reserved in `source`.
|
||||
fn cancel(&self, source: &AccountId);
|
||||
}
|
||||
|
||||
/// A swap action that only allows transferring balances.
|
||||
#[derive(
|
||||
Clone,
|
||||
RuntimeDebug,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
TypeInfo,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
#[scale_info(skip_type_params(C))]
|
||||
#[codec(mel_bound())]
|
||||
pub struct BalanceSwapAction<AccountId, C: ReservableCurrency<AccountId>> {
|
||||
value: <C as Currency<AccountId>>::Balance,
|
||||
_marker: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<AccountId, C> BalanceSwapAction<AccountId, C>
|
||||
where
|
||||
C: ReservableCurrency<AccountId>,
|
||||
{
|
||||
/// Create a new swap action value of balance.
|
||||
pub fn new(value: <C as Currency<AccountId>>::Balance) -> Self {
|
||||
Self { value, _marker: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<AccountId, C> Deref for BalanceSwapAction<AccountId, C>
|
||||
where
|
||||
C: ReservableCurrency<AccountId>,
|
||||
{
|
||||
type Target = <C as Currency<AccountId>>::Balance;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<AccountId, C> DerefMut for BalanceSwapAction<AccountId, C>
|
||||
where
|
||||
C: ReservableCurrency<AccountId>,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config, AccountId, C> SwapAction<AccountId, T> for BalanceSwapAction<AccountId, C>
|
||||
where
|
||||
C: ReservableCurrency<AccountId>,
|
||||
{
|
||||
fn reserve(&self, source: &AccountId) -> DispatchResult {
|
||||
C::reserve(source, self.value)
|
||||
}
|
||||
|
||||
fn claim(&self, source: &AccountId, target: &AccountId) -> bool {
|
||||
C::repatriate_reserved(source, target, self.value, BalanceStatus::Free).is_ok()
|
||||
}
|
||||
|
||||
fn weight(&self) -> Weight {
|
||||
T::DbWeight::get().reads_writes(1, 1)
|
||||
}
|
||||
|
||||
fn cancel(&self, source: &AccountId) {
|
||||
C::unreserve(source, self.value);
|
||||
}
|
||||
}
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
#[frame::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
/// Atomic swap's pallet configuration trait.
|
||||
#[pallet::config]
|
||||
pub trait Config: pezframe_system::Config {
|
||||
/// The overarching event type.
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
/// Swap action.
|
||||
type SwapAction: SwapAction<Self::AccountId, Self> + Parameter + MaxEncodedLen;
|
||||
/// Limit of proof size.
|
||||
///
|
||||
/// Atomic swap is only atomic if once the proof is revealed, both parties can submit the
|
||||
/// proofs on-chain. If A is the one that generates the proof, then it requires that either:
|
||||
/// - A's blockchain has the same proof length limit as B's blockchain.
|
||||
/// - Or A's blockchain has shorter proof length limit as B's blockchain.
|
||||
///
|
||||
/// If B sees A is on a blockchain with larger proof length limit, then it should kindly
|
||||
/// refuse to accept the atomic swap request if A generates the proof, and asks that B
|
||||
/// generates the proof instead.
|
||||
#[pallet::constant]
|
||||
type ProofLimit: Get<u32>;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::storage]
|
||||
pub type PendingSwaps<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Twox64Concat,
|
||||
T::AccountId,
|
||||
Blake2_128Concat,
|
||||
HashedProof,
|
||||
PendingSwap<T>,
|
||||
>;
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Swap already exists.
|
||||
AlreadyExist,
|
||||
/// Swap proof is invalid.
|
||||
InvalidProof,
|
||||
/// Proof is too large.
|
||||
ProofTooLarge,
|
||||
/// Source does not match.
|
||||
SourceMismatch,
|
||||
/// Swap has already been claimed.
|
||||
AlreadyClaimed,
|
||||
/// Swap does not exist.
|
||||
NotExist,
|
||||
/// Claim action mismatch.
|
||||
ClaimActionMismatch,
|
||||
/// Duration has not yet passed for the swap to be cancelled.
|
||||
DurationNotPassed,
|
||||
}
|
||||
|
||||
/// Event of atomic swap pallet.
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// Swap created.
|
||||
NewSwap { account: T::AccountId, proof: HashedProof, swap: PendingSwap<T> },
|
||||
/// Swap claimed. The last parameter indicates whether the execution succeeds.
|
||||
SwapClaimed { account: T::AccountId, proof: HashedProof, success: bool },
|
||||
/// Swap cancelled.
|
||||
SwapCancelled { account: T::AccountId, proof: HashedProof },
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Register a new atomic swap, declaring an intention to send funds from origin to target
|
||||
/// on the current blockchain. The target can claim the fund using the revealed proof. If
|
||||
/// the fund is not claimed after `duration` blocks, then the sender can cancel the swap.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_.
|
||||
///
|
||||
/// - `target`: Receiver of the atomic swap.
|
||||
/// - `hashed_proof`: The blake2_256 hash of the secret proof.
|
||||
/// - `balance`: Funds to be sent from origin.
|
||||
/// - `duration`: Locked duration of the atomic swap. For safety reasons, it is recommended
|
||||
/// that the revealer uses a shorter duration than the counterparty, to prevent the
|
||||
/// situation where the revealer reveals the proof too late around the end block.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(T::DbWeight::get().reads_writes(1, 1).ref_time().saturating_add(40_000_000))]
|
||||
pub fn create_swap(
|
||||
origin: OriginFor<T>,
|
||||
target: T::AccountId,
|
||||
hashed_proof: HashedProof,
|
||||
action: T::SwapAction,
|
||||
duration: BlockNumberFor<T>,
|
||||
) -> DispatchResult {
|
||||
let source = ensure_signed(origin)?;
|
||||
ensure!(
|
||||
!PendingSwaps::<T>::contains_key(&target, hashed_proof),
|
||||
Error::<T>::AlreadyExist
|
||||
);
|
||||
|
||||
action.reserve(&source)?;
|
||||
|
||||
let swap = PendingSwap {
|
||||
source,
|
||||
action,
|
||||
end_block: pezframe_system::Pallet::<T>::block_number() + duration,
|
||||
};
|
||||
PendingSwaps::<T>::insert(target.clone(), hashed_proof, swap.clone());
|
||||
|
||||
Self::deposit_event(Event::NewSwap { account: target, proof: hashed_proof, swap });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Claim an atomic swap.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_.
|
||||
///
|
||||
/// - `proof`: Revealed proof of the claim.
|
||||
/// - `action`: Action defined in the swap, it must match the entry in blockchain. Otherwise
|
||||
/// the operation fails. This is used for weight calculation.
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight(
|
||||
T::DbWeight::get().reads_writes(1, 1)
|
||||
.saturating_add(action.weight())
|
||||
.ref_time()
|
||||
.saturating_add(40_000_000)
|
||||
.saturating_add((proof.len() as u64).saturating_mul(100))
|
||||
)]
|
||||
pub fn claim_swap(
|
||||
origin: OriginFor<T>,
|
||||
proof: Vec<u8>,
|
||||
action: T::SwapAction,
|
||||
) -> DispatchResult {
|
||||
ensure!(proof.len() <= T::ProofLimit::get() as usize, Error::<T>::ProofTooLarge);
|
||||
|
||||
let target = ensure_signed(origin)?;
|
||||
let hashed_proof = blake2_256(&proof);
|
||||
|
||||
let swap =
|
||||
PendingSwaps::<T>::get(&target, hashed_proof).ok_or(Error::<T>::InvalidProof)?;
|
||||
ensure!(swap.action == action, Error::<T>::ClaimActionMismatch);
|
||||
|
||||
let succeeded = swap.action.claim(&swap.source, &target);
|
||||
|
||||
PendingSwaps::<T>::remove(target.clone(), hashed_proof);
|
||||
|
||||
Self::deposit_event(Event::SwapClaimed {
|
||||
account: target,
|
||||
proof: hashed_proof,
|
||||
success: succeeded,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Cancel an atomic swap. Only possible after the originally set duration has passed.
|
||||
///
|
||||
/// The dispatch origin for this call must be _Signed_.
|
||||
///
|
||||
/// - `target`: Target of the original atomic swap.
|
||||
/// - `hashed_proof`: Hashed proof of the original atomic swap.
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(T::DbWeight::get().reads_writes(1, 1).ref_time().saturating_add(40_000_000))]
|
||||
pub fn cancel_swap(
|
||||
origin: OriginFor<T>,
|
||||
target: T::AccountId,
|
||||
hashed_proof: HashedProof,
|
||||
) -> DispatchResult {
|
||||
let source = ensure_signed(origin)?;
|
||||
|
||||
let swap = PendingSwaps::<T>::get(&target, hashed_proof).ok_or(Error::<T>::NotExist)?;
|
||||
ensure!(swap.source == source, Error::<T>::SourceMismatch);
|
||||
ensure!(
|
||||
pezframe_system::Pallet::<T>::block_number() >= swap.end_block,
|
||||
Error::<T>::DurationNotPassed,
|
||||
);
|
||||
|
||||
swap.action.cancel(&swap.source);
|
||||
PendingSwaps::<T>::remove(&target, hashed_proof);
|
||||
|
||||
Self::deposit_event(Event::SwapCancelled { account: target, proof: hashed_proof });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use crate as pezpallet_atomic_swap;
|
||||
use frame::testing_prelude::*;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Balances: pezpallet_balances,
|
||||
AtomicSwap: pezpallet_atomic_swap,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u64>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type SwapAction = BalanceSwapAction<u64, Balances>;
|
||||
type ProofLimit = ConstU32<1024>;
|
||||
}
|
||||
|
||||
const A: u64 = 1;
|
||||
const B: u64 = 2;
|
||||
|
||||
pub fn new_test_ext() -> TestExternalities {
|
||||
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
let genesis = pezpallet_balances::GenesisConfig::<Test> {
|
||||
balances: vec![(A, 100), (B, 200)],
|
||||
..Default::default()
|
||||
};
|
||||
genesis.assimilate_storage(&mut t).unwrap();
|
||||
t.into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn two_party_successful_swap() {
|
||||
let mut chain1 = new_test_ext();
|
||||
let mut chain2 = new_test_ext();
|
||||
|
||||
// A generates a random proof. Keep it secret.
|
||||
let proof: [u8; 2] = [4, 2];
|
||||
// The hashed proof is the blake2_256 hash of the proof. This is public.
|
||||
let hashed_proof = blake2_256(&proof);
|
||||
|
||||
// A creates the swap on chain1.
|
||||
chain1.execute_with(|| {
|
||||
AtomicSwap::create_swap(
|
||||
RuntimeOrigin::signed(A),
|
||||
B,
|
||||
hashed_proof,
|
||||
BalanceSwapAction::new(50),
|
||||
1000,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Balances::free_balance(A), 100 - 50);
|
||||
assert_eq!(Balances::free_balance(B), 200);
|
||||
});
|
||||
|
||||
// B creates the swap on chain2.
|
||||
chain2.execute_with(|| {
|
||||
AtomicSwap::create_swap(
|
||||
RuntimeOrigin::signed(B),
|
||||
A,
|
||||
hashed_proof,
|
||||
BalanceSwapAction::new(75),
|
||||
1000,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Balances::free_balance(A), 100);
|
||||
assert_eq!(Balances::free_balance(B), 200 - 75);
|
||||
});
|
||||
|
||||
// A reveals the proof and claims the swap on chain2.
|
||||
chain2.execute_with(|| {
|
||||
AtomicSwap::claim_swap(
|
||||
RuntimeOrigin::signed(A),
|
||||
proof.to_vec(),
|
||||
BalanceSwapAction::new(75),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Balances::free_balance(A), 100 + 75);
|
||||
assert_eq!(Balances::free_balance(B), 200 - 75);
|
||||
});
|
||||
|
||||
// B use the revealed proof to claim the swap on chain1.
|
||||
chain1.execute_with(|| {
|
||||
AtomicSwap::claim_swap(
|
||||
RuntimeOrigin::signed(B),
|
||||
proof.to_vec(),
|
||||
BalanceSwapAction::new(50),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(Balances::free_balance(A), 100 - 50);
|
||||
assert_eq!(Balances::free_balance(B), 200 + 50);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
[package]
|
||||
name = "pezpallet-aura"
|
||||
version = "27.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME AURA consensus pallet"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive", "max-encoded-len"], workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
log = { workspace = true }
|
||||
pezpallet-timestamp = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
pezsp-application-crypto = { workspace = true }
|
||||
pezsp-consensus-aura = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-timestamp/std",
|
||||
"scale-info/std",
|
||||
"pezsp-application-crypto/std",
|
||||
"pezsp-consensus-aura/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-timestamp/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-timestamp/runtime-benchmarks",
|
||||
"pezsp-consensus-aura/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
# Aura Module
|
||||
|
||||
- [`aura::Config`](https://docs.rs/pezpallet-aura/latest/pallet_aura/pallet/trait.Config.html)
|
||||
- [`Pallet`](https://docs.rs/pezpallet-aura/latest/pallet_aura/pallet/struct.Pallet.html)
|
||||
|
||||
## Overview
|
||||
|
||||
The Aura module extends Aura consensus by managing offline reporting.
|
||||
|
||||
## Interface
|
||||
|
||||
### Public Functions
|
||||
|
||||
- `slot_duration` - Determine the Aura slot-duration based on the Timestamp module configuration.
|
||||
|
||||
## Related Modules
|
||||
|
||||
- [Timestamp](https://docs.rs/pezpallet-timestamp/latest/pallet_timestamp/): The Timestamp module is used in Aura to track
|
||||
consensus rounds (via `slots`).
|
||||
|
||||
## References
|
||||
|
||||
If you're interested in hacking on this module, it is useful to understand the interaction with
|
||||
`bizinikiwi/primitives/inherents/src/lib.rs` and, specifically, the required implementation of
|
||||
[`ProvideInherent`](https://docs.rs/pezsp-inherents/latest/sp_inherents/trait.ProvideInherent.html) and
|
||||
[`ProvideInherentData`](https://docs.rs/pezsp-inherents/latest/sp_inherents/trait.ProvideInherentData.html) to create and
|
||||
check inherents.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,408 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! # Aura Module
|
||||
//!
|
||||
//! - [`Config`]
|
||||
//! - [`Pallet`]
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! The Aura module extends Aura consensus by managing offline reporting.
|
||||
//!
|
||||
//! ## Interface
|
||||
//!
|
||||
//! ### Public Functions
|
||||
//!
|
||||
//! - `slot_duration` - Determine the Aura slot-duration based on the Timestamp module
|
||||
//! configuration.
|
||||
//!
|
||||
//! ## Related Modules
|
||||
//!
|
||||
//! - [Timestamp](../pezpallet_timestamp/index.html): The Timestamp module is used in Aura to track
|
||||
//! consensus rounds (via `slots`).
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use pezframe_support::{
|
||||
traits::{DisabledValidators, FindAuthor, Get, OnTimestampSet, OneSessionHandler},
|
||||
BoundedSlice, BoundedVec, ConsensusEngineId, Parameter,
|
||||
};
|
||||
use log;
|
||||
use pezsp_consensus_aura::{AuthorityIndex, ConsensusLog, Slot, AURA_ENGINE_ID};
|
||||
use pezsp_runtime::{
|
||||
generic::DigestItem,
|
||||
traits::{IsMember, Member, SaturatedConversion, Saturating, Zero},
|
||||
RuntimeAppPublic,
|
||||
};
|
||||
|
||||
pub mod migrations;
|
||||
mod mock;
|
||||
mod tests;
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
const LOG_TARGET: &str = "runtime::aura";
|
||||
|
||||
/// A slot duration provider which infers the slot duration from the
|
||||
/// [`pezpallet_timestamp::Config::MinimumPeriod`] by multiplying it by two, to ensure
|
||||
/// that authors have the majority of their slot to author within.
|
||||
///
|
||||
/// This was the default behavior of the Aura pallet and may be used for
|
||||
/// backwards compatibility.
|
||||
pub struct MinimumPeriodTimesTwo<T>(core::marker::PhantomData<T>);
|
||||
|
||||
impl<T: pezpallet_timestamp::Config> Get<T::Moment> for MinimumPeriodTimesTwo<T> {
|
||||
fn get() -> T::Moment {
|
||||
<T as pezpallet_timestamp::Config>::MinimumPeriod::get().saturating_mul(2u32.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[pezframe_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
use pezframe_system::pezpallet_prelude::*;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: pezpallet_timestamp::Config + pezframe_system::Config {
|
||||
/// The identifier type for an authority.
|
||||
type AuthorityId: Member
|
||||
+ Parameter
|
||||
+ RuntimeAppPublic
|
||||
+ MaybeSerializeDeserialize
|
||||
+ MaxEncodedLen;
|
||||
/// The maximum number of authorities that the pallet can hold.
|
||||
type MaxAuthorities: Get<u32>;
|
||||
|
||||
/// A way to check whether a given validator is disabled and should not be authoring blocks.
|
||||
/// Blocks authored by a disabled validator will lead to a panic as part of this module's
|
||||
/// initialization.
|
||||
type DisabledValidators: DisabledValidators;
|
||||
|
||||
/// Whether to allow block authors to create multiple blocks per slot.
|
||||
///
|
||||
/// If this is `true`, the pallet will allow slots to stay the same across sequential
|
||||
/// blocks. If this is `false`, the pallet will require that subsequent blocks always have
|
||||
/// higher slots than previous ones.
|
||||
///
|
||||
/// Regardless of the setting of this storage value, the pallet will always enforce the
|
||||
/// invariant that slots don't move backwards as the chain progresses.
|
||||
///
|
||||
/// The typical value for this should be 'false' unless this pallet is being augmented by
|
||||
/// another pallet which enforces some limitation on the number of blocks authors can create
|
||||
/// using the same slot.
|
||||
type AllowMultipleBlocksPerSlot: Get<bool>;
|
||||
|
||||
/// The slot duration Aura should run with, expressed in milliseconds.
|
||||
/// The effective value of this type should not change while the chain is running.
|
||||
///
|
||||
/// For backwards compatibility either use [`MinimumPeriodTimesTwo`] or a const.
|
||||
#[pallet::constant]
|
||||
type SlotDuration: Get<<Self as pezpallet_timestamp::Config>::Moment>;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(core::marker::PhantomData<T>);
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn on_initialize(_: BlockNumberFor<T>) -> Weight {
|
||||
if let Some(new_slot) = Self::current_slot_from_digests() {
|
||||
let current_slot = CurrentSlot::<T>::get();
|
||||
|
||||
if T::AllowMultipleBlocksPerSlot::get() {
|
||||
assert!(current_slot <= new_slot, "Slot must not decrease");
|
||||
} else {
|
||||
assert!(current_slot < new_slot, "Slot must increase");
|
||||
}
|
||||
|
||||
CurrentSlot::<T>::put(new_slot);
|
||||
|
||||
if let Some(n_authorities) = <Authorities<T>>::decode_len() {
|
||||
let authority_index = *new_slot % n_authorities as u64;
|
||||
if T::DisabledValidators::is_disabled(authority_index as u32) {
|
||||
panic!(
|
||||
"Validator with index {:?} is disabled and should not be attempting to author blocks.",
|
||||
authority_index,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO [#3398] Generate offence report for all authorities that skipped their
|
||||
// slots.
|
||||
|
||||
T::DbWeight::get().reads_writes(2, 1)
|
||||
} else {
|
||||
T::DbWeight::get().reads(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn try_state(_: BlockNumberFor<T>) -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
Self::do_try_state()
|
||||
}
|
||||
}
|
||||
|
||||
/// The current authority set.
|
||||
#[pallet::storage]
|
||||
pub type Authorities<T: Config> =
|
||||
StorageValue<_, BoundedVec<T::AuthorityId, T::MaxAuthorities>, ValueQuery>;
|
||||
|
||||
/// The current slot of this block.
|
||||
///
|
||||
/// This will be set in `on_initialize`.
|
||||
#[pallet::storage]
|
||||
pub type CurrentSlot<T: Config> = StorageValue<_, Slot, ValueQuery>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
#[derive(pezframe_support::DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
pub authorities: Vec<T::AuthorityId>,
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
Pallet::<T>::initialize_authorities(&self.authorities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Change authorities.
|
||||
///
|
||||
/// The storage will be applied immediately.
|
||||
/// And aura consensus log will be appended to block's log.
|
||||
///
|
||||
/// This is a no-op if `new` is empty.
|
||||
pub fn change_authorities(new: BoundedVec<T::AuthorityId, T::MaxAuthorities>) {
|
||||
if new.is_empty() {
|
||||
log::warn!(target: LOG_TARGET, "Ignoring empty authority change.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
<Authorities<T>>::put(&new);
|
||||
|
||||
let log = DigestItem::Consensus(
|
||||
AURA_ENGINE_ID,
|
||||
ConsensusLog::AuthoritiesChange(new.into_inner()).encode(),
|
||||
);
|
||||
<pezframe_system::Pallet<T>>::deposit_log(log);
|
||||
}
|
||||
|
||||
/// Initial authorities.
|
||||
///
|
||||
/// The storage will be applied immediately.
|
||||
///
|
||||
/// The authorities length must be equal or less than T::MaxAuthorities.
|
||||
pub fn initialize_authorities(authorities: &[T::AuthorityId]) {
|
||||
if !authorities.is_empty() {
|
||||
assert!(<Authorities<T>>::get().is_empty(), "Authorities are already initialized!");
|
||||
let bounded = <BoundedSlice<'_, _, T::MaxAuthorities>>::try_from(authorities)
|
||||
.expect("Initial authority set must be less than T::MaxAuthorities");
|
||||
<Authorities<T>>::put(bounded);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return current authorities length.
|
||||
pub fn authorities_len() -> usize {
|
||||
Authorities::<T>::decode_len().unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Get the current slot from the pre-runtime digests.
|
||||
fn current_slot_from_digests() -> Option<Slot> {
|
||||
let digest = pezframe_system::Pallet::<T>::digest();
|
||||
let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime());
|
||||
for (id, mut data) in pre_runtime_digests {
|
||||
if id == AURA_ENGINE_ID {
|
||||
return Slot::decode(&mut data).ok();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Determine the Aura slot-duration based on the Timestamp module configuration.
|
||||
pub fn slot_duration() -> T::Moment {
|
||||
T::SlotDuration::get()
|
||||
}
|
||||
|
||||
/// Ensure the correctness of the state of this pallet.
|
||||
///
|
||||
/// This should be valid before or after each state transition of this pallet.
|
||||
///
|
||||
/// # Invariants
|
||||
///
|
||||
/// ## `CurrentSlot`
|
||||
///
|
||||
/// If we don't allow for multiple blocks per slot, then the current slot must be less than the
|
||||
/// maximal slot number. Otherwise, it can be arbitrary.
|
||||
///
|
||||
/// ## `Authorities`
|
||||
///
|
||||
/// * The authorities must be non-empty.
|
||||
/// * The current authority cannot be disabled.
|
||||
/// * The number of authorities must be less than or equal to `T::MaxAuthorities`. This however,
|
||||
/// is guarded by the type system.
|
||||
#[cfg(any(test, feature = "try-runtime"))]
|
||||
pub fn do_try_state() -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
// We don't have any guarantee that we are already after `on_initialize` and thus we have to
|
||||
// check the current slot from the digest or take the last known slot.
|
||||
let current_slot =
|
||||
Self::current_slot_from_digests().unwrap_or_else(|| CurrentSlot::<T>::get());
|
||||
|
||||
// Check that the current slot is less than the maximal slot number, unless we allow for
|
||||
// multiple blocks per slot.
|
||||
if !T::AllowMultipleBlocksPerSlot::get() {
|
||||
pezframe_support::ensure!(
|
||||
current_slot < u64::MAX,
|
||||
"Current slot has reached maximum value and cannot be incremented further.",
|
||||
);
|
||||
}
|
||||
|
||||
let authorities_len =
|
||||
<Authorities<T>>::decode_len().ok_or("Failed to decode authorities length")?;
|
||||
|
||||
// Check that the authorities are non-empty.
|
||||
pezframe_support::ensure!(!authorities_len.is_zero(), "Authorities must be non-empty.");
|
||||
|
||||
// Check that the current authority is not disabled.
|
||||
let authority_index = *current_slot % authorities_len as u64;
|
||||
pezframe_support::ensure!(
|
||||
!T::DisabledValidators::is_disabled(authority_index as u32),
|
||||
"Current validator is disabled and should not be attempting to author blocks.",
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> pezsp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
|
||||
type Public = T::AuthorityId;
|
||||
}
|
||||
|
||||
impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
||||
type Key = T::AuthorityId;
|
||||
|
||||
fn on_genesis_session<'a, I: 'a>(validators: I)
|
||||
where
|
||||
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
|
||||
{
|
||||
let authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
|
||||
Self::initialize_authorities(&authorities);
|
||||
}
|
||||
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued_validators: I)
|
||||
where
|
||||
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
|
||||
{
|
||||
// instant changes
|
||||
if changed {
|
||||
let next_authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
|
||||
let last_authorities = Authorities::<T>::get();
|
||||
if last_authorities != next_authorities {
|
||||
if next_authorities.len() as u32 > T::MaxAuthorities::get() {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"next authorities list larger than {}, truncating",
|
||||
T::MaxAuthorities::get(),
|
||||
);
|
||||
}
|
||||
let bounded = <BoundedVec<_, T::MaxAuthorities>>::truncate_from(next_authorities);
|
||||
Self::change_authorities(bounded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_disabled(i: u32) {
|
||||
let log = DigestItem::Consensus(
|
||||
AURA_ENGINE_ID,
|
||||
ConsensusLog::<T::AuthorityId>::OnDisabled(i as AuthorityIndex).encode(),
|
||||
);
|
||||
|
||||
<pezframe_system::Pallet<T>>::deposit_log(log);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> FindAuthor<u32> for Pallet<T> {
|
||||
fn find_author<'a, I>(digests: I) -> Option<u32>
|
||||
where
|
||||
I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
|
||||
{
|
||||
for (id, mut data) in digests.into_iter() {
|
||||
if id == AURA_ENGINE_ID {
|
||||
let slot = Slot::decode(&mut data).ok()?;
|
||||
let author_index = *slot % Self::authorities_len() as u64;
|
||||
return Some(author_index as u32);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// We can not implement `FindAuthor` twice, because the compiler does not know if
|
||||
/// `u32 == T::AuthorityId` and thus, prevents us to implement the trait twice.
|
||||
#[doc(hidden)]
|
||||
pub struct FindAccountFromAuthorIndex<T, Inner>(core::marker::PhantomData<(T, Inner)>);
|
||||
|
||||
impl<T: Config, Inner: FindAuthor<u32>> FindAuthor<T::AuthorityId>
|
||||
for FindAccountFromAuthorIndex<T, Inner>
|
||||
{
|
||||
fn find_author<'a, I>(digests: I) -> Option<T::AuthorityId>
|
||||
where
|
||||
I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
|
||||
{
|
||||
let i = Inner::find_author(digests)?;
|
||||
|
||||
let validators = Authorities::<T>::get();
|
||||
validators.get(i as usize).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Find the authority ID of the Aura authority who authored the current block.
|
||||
pub type AuraAuthorId<T> = FindAccountFromAuthorIndex<T, Pallet<T>>;
|
||||
|
||||
impl<T: Config> IsMember<T::AuthorityId> for Pallet<T> {
|
||||
fn is_member(authority_id: &T::AuthorityId) -> bool {
|
||||
Authorities::<T>::get().iter().any(|id| id == authority_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> OnTimestampSet<T::Moment> for Pallet<T> {
|
||||
fn on_timestamp_set(moment: T::Moment) {
|
||||
let slot_duration = Self::slot_duration();
|
||||
assert!(!slot_duration.is_zero(), "Aura slot duration cannot be zero.");
|
||||
|
||||
let timestamp_slot = moment / slot_duration;
|
||||
let timestamp_slot = Slot::from(timestamp_slot.saturated_into::<u64>());
|
||||
|
||||
assert_eq!(
|
||||
CurrentSlot::<T>::get(),
|
||||
timestamp_slot,
|
||||
"Timestamp slot must match `CurrentSlot`. This likely means that the configured block \
|
||||
time in the node and/or rest of the runtime is not compatible with Aura's \
|
||||
`SlotDuration`",
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Migrations for the AURA pallet.
|
||||
|
||||
use pezframe_support::{pezpallet_prelude::*, traits::Get, weights::Weight};
|
||||
|
||||
struct __LastTimestamp<T>(core::marker::PhantomData<T>);
|
||||
impl<T: RemoveLastTimestamp> pezframe_support::traits::StorageInstance for __LastTimestamp<T> {
|
||||
fn pezpallet_prefix() -> &'static str {
|
||||
T::PalletPrefix::get()
|
||||
}
|
||||
const STORAGE_PREFIX: &'static str = "LastTimestamp";
|
||||
}
|
||||
|
||||
type LastTimestamp<T> = StorageValue<__LastTimestamp<T>, (), ValueQuery>;
|
||||
|
||||
pub trait RemoveLastTimestamp: super::Config {
|
||||
type PalletPrefix: Get<&'static str>;
|
||||
}
|
||||
|
||||
/// Remove the `LastTimestamp` storage value.
|
||||
///
|
||||
/// This storage value was removed and replaced by `CurrentSlot`. As we only remove this storage
|
||||
/// value, it is safe to call this method multiple times.
|
||||
///
|
||||
/// This migration requires a type `T` that implements [`RemoveLastTimestamp`].
|
||||
pub fn remove_last_timestamp<T: RemoveLastTimestamp>() -> Weight {
|
||||
LastTimestamp::<T>::kill();
|
||||
T::DbWeight::get().writes(1)
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test utilities
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use crate as pezpallet_aura;
|
||||
use pezframe_support::{
|
||||
derive_impl, parameter_types,
|
||||
traits::{ConstU32, ConstU64, DisabledValidators},
|
||||
};
|
||||
use pezsp_consensus_aura::{ed25519::AuthorityId, AuthorityIndex};
|
||||
use pezsp_runtime::{testing::UintAuthorityId, BuildStorage};
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
const SLOT_DURATION: u64 = 2;
|
||||
|
||||
pezframe_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Timestamp: pezpallet_timestamp,
|
||||
Aura: pezpallet_aura,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
impl pezpallet_timestamp::Config for Test {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = Aura;
|
||||
type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
static DisabledValidatorTestValue: Vec<AuthorityIndex> = Default::default();
|
||||
pub static AllowMultipleBlocksPerSlot: bool = false;
|
||||
}
|
||||
|
||||
pub struct MockDisabledValidators;
|
||||
|
||||
impl MockDisabledValidators {
|
||||
pub fn disable_validator(index: AuthorityIndex) {
|
||||
DisabledValidatorTestValue::mutate(|v| {
|
||||
if let Err(i) = v.binary_search(&index) {
|
||||
v.insert(i, index);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DisabledValidators for MockDisabledValidators {
|
||||
fn is_disabled(index: AuthorityIndex) -> bool {
|
||||
DisabledValidatorTestValue::get().binary_search(&index).is_ok()
|
||||
}
|
||||
|
||||
fn disabled_validators() -> Vec<u32> {
|
||||
DisabledValidatorTestValue::get()
|
||||
}
|
||||
}
|
||||
|
||||
impl pezpallet_aura::Config for Test {
|
||||
type AuthorityId = AuthorityId;
|
||||
type DisabledValidators = MockDisabledValidators;
|
||||
type MaxAuthorities = ConstU32<10>;
|
||||
type AllowMultipleBlocksPerSlot = AllowMultipleBlocksPerSlot;
|
||||
type SlotDuration = ConstU64<SLOT_DURATION>;
|
||||
}
|
||||
|
||||
fn build_ext(authorities: Vec<u64>) -> pezsp_io::TestExternalities {
|
||||
let mut storage = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
pezpallet_aura::GenesisConfig::<Test> {
|
||||
authorities: authorities.into_iter().map(|a| UintAuthorityId(a).to_public_key()).collect(),
|
||||
}
|
||||
.assimilate_storage(&mut storage)
|
||||
.unwrap();
|
||||
storage.into()
|
||||
}
|
||||
|
||||
pub fn build_ext_and_execute_test(authorities: Vec<u64>, test: impl FnOnce() -> ()) {
|
||||
let mut ext = build_ext(authorities);
|
||||
ext.execute_with(|| {
|
||||
test();
|
||||
Aura::do_try_state().expect("Storage invariants should hold")
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Tests for the module.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::pallet;
|
||||
use crate::mock::{build_ext_and_execute_test, Aura, MockDisabledValidators, System, Test};
|
||||
use codec::Encode;
|
||||
use pezframe_support::traits::OnInitialize;
|
||||
use pezsp_consensus_aura::{Slot, AURA_ENGINE_ID};
|
||||
use pezsp_runtime::{Digest, DigestItem};
|
||||
|
||||
#[test]
|
||||
fn initial_values() {
|
||||
build_ext_and_execute_test(vec![0, 1, 2, 3], || {
|
||||
assert_eq!(pallet::CurrentSlot::<Test>::get(), 0u64);
|
||||
assert_eq!(pallet::Authorities::<Test>::get().len(), Aura::authorities_len());
|
||||
assert_eq!(Aura::authorities_len(), 4);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "Validator with index 1 is disabled and should not be attempting to author blocks."
|
||||
)]
|
||||
fn disabled_validators_cannot_author_blocks() {
|
||||
build_ext_and_execute_test(vec![0, 1, 2, 3], || {
|
||||
// slot 1 should be authored by validator at index 1
|
||||
let slot = Slot::from(1);
|
||||
let pre_digest =
|
||||
Digest { logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())] };
|
||||
|
||||
System::reset_events();
|
||||
System::initialize(&1, &System::parent_hash(), &pre_digest);
|
||||
|
||||
// let's disable the validator
|
||||
MockDisabledValidators::disable_validator(1);
|
||||
|
||||
// and we should not be able to initialize the block
|
||||
Aura::on_initialize(1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Slot must increase")]
|
||||
fn pezpallet_requires_slot_to_increase_unless_allowed() {
|
||||
build_ext_and_execute_test(vec![0, 1, 2, 3], || {
|
||||
crate::mock::AllowMultipleBlocksPerSlot::set(false);
|
||||
|
||||
let slot = Slot::from(1);
|
||||
let pre_digest =
|
||||
Digest { logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())] };
|
||||
|
||||
System::reset_events();
|
||||
System::initialize(&1, &System::parent_hash(), &pre_digest);
|
||||
|
||||
// and we should not be able to initialize the block with the same slot a second time.
|
||||
Aura::on_initialize(1);
|
||||
Aura::on_initialize(1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pezpallet_can_allow_unchanged_slot() {
|
||||
build_ext_and_execute_test(vec![0, 1, 2, 3], || {
|
||||
let slot = Slot::from(1);
|
||||
let pre_digest =
|
||||
Digest { logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())] };
|
||||
|
||||
System::reset_events();
|
||||
System::initialize(&1, &System::parent_hash(), &pre_digest);
|
||||
|
||||
crate::mock::AllowMultipleBlocksPerSlot::set(true);
|
||||
|
||||
// and we should be able to initialize the block with the same slot a second time.
|
||||
Aura::on_initialize(1);
|
||||
Aura::on_initialize(1);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "Slot must not decrease")]
|
||||
fn pezpallet_always_rejects_decreasing_slot() {
|
||||
build_ext_and_execute_test(vec![0, 1, 2, 3], || {
|
||||
let slot = Slot::from(2);
|
||||
let pre_digest =
|
||||
Digest { logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())] };
|
||||
|
||||
System::reset_events();
|
||||
System::initialize(&1, &System::parent_hash(), &pre_digest);
|
||||
|
||||
crate::mock::AllowMultipleBlocksPerSlot::set(true);
|
||||
|
||||
Aura::on_initialize(1);
|
||||
System::finalize();
|
||||
|
||||
let earlier_slot = Slot::from(1);
|
||||
let pre_digest =
|
||||
Digest { logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, earlier_slot.encode())] };
|
||||
System::initialize(&2, &System::parent_hash(), &pre_digest);
|
||||
Aura::on_initialize(2);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
[package]
|
||||
name = "pezpallet-authority-discovery"
|
||||
version = "28.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME pallet for authority discovery"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
pezpallet-session = { features = ["historical"], workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
pezsp-application-crypto = { workspace = true }
|
||||
pezsp-authority-discovery = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-balances = { workspace = true }
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"pezpallet-balances/std",
|
||||
"pezpallet-session/std",
|
||||
"scale-info/std",
|
||||
"pezsp-application-crypto/std",
|
||||
"pezsp-authority-discovery/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-session/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-session/runtime-benchmarks",
|
||||
"pezsp-authority-discovery/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
# Authority discovery module
|
||||
|
||||
This module is used by the `client/authority-discovery` to retrieve the
|
||||
current set of authorities.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,379 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! # Authority discovery pallet.
|
||||
//!
|
||||
//! This pallet is used by the `client/authority-discovery` and by pezkuwi's teyrchain logic
|
||||
//! to retrieve the current and the next set of authorities.
|
||||
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use pezframe_support::{
|
||||
traits::{Get, OneSessionHandler},
|
||||
WeakBoundedVec,
|
||||
};
|
||||
use pezsp_authority_discovery::AuthorityId;
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
#[pezframe_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
/// The pallet's config trait.
|
||||
pub trait Config: pezframe_system::Config + pezpallet_session::Config {
|
||||
/// The maximum number of authorities that can be added.
|
||||
type MaxAuthorities: Get<u32>;
|
||||
}
|
||||
|
||||
#[pallet::storage]
|
||||
/// Keys of the current authority set.
|
||||
pub type Keys<T: Config> =
|
||||
StorageValue<_, WeakBoundedVec<AuthorityId, T::MaxAuthorities>, ValueQuery>;
|
||||
|
||||
#[pallet::storage]
|
||||
/// Keys of the next authority set.
|
||||
pub type NextKeys<T: Config> =
|
||||
StorageValue<_, WeakBoundedVec<AuthorityId, T::MaxAuthorities>, ValueQuery>;
|
||||
|
||||
#[derive(pezframe_support::DefaultNoBound)]
|
||||
#[pallet::genesis_config]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
pub keys: Vec<AuthorityId>,
|
||||
#[serde(skip)]
|
||||
pub _config: core::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
Pallet::<T>::initialize_keys(&self.keys)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Retrieve authority identifiers of the current and next authority set
|
||||
/// sorted and deduplicated.
|
||||
pub fn authorities() -> Vec<AuthorityId> {
|
||||
let mut keys = Keys::<T>::get().to_vec();
|
||||
let next = NextKeys::<T>::get().to_vec();
|
||||
|
||||
keys.extend(next);
|
||||
keys.sort();
|
||||
keys.dedup();
|
||||
|
||||
keys.to_vec()
|
||||
}
|
||||
|
||||
/// Retrieve authority identifiers of the current authority set in the original order.
|
||||
pub fn current_authorities() -> WeakBoundedVec<AuthorityId, T::MaxAuthorities> {
|
||||
Keys::<T>::get()
|
||||
}
|
||||
|
||||
/// Retrieve authority identifiers of the next authority set in the original order.
|
||||
pub fn next_authorities() -> WeakBoundedVec<AuthorityId, T::MaxAuthorities> {
|
||||
NextKeys::<T>::get()
|
||||
}
|
||||
|
||||
fn initialize_keys(keys: &Vec<AuthorityId>) {
|
||||
if !keys.is_empty() {
|
||||
assert!(Keys::<T>::get().is_empty(), "Keys are already initialized!");
|
||||
|
||||
let bounded_keys =
|
||||
WeakBoundedVec::<AuthorityId, T::MaxAuthorities>::try_from((*keys).clone())
|
||||
.expect("Keys vec too big");
|
||||
|
||||
Keys::<T>::put(&bounded_keys);
|
||||
NextKeys::<T>::put(&bounded_keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> pezsp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
|
||||
type Public = AuthorityId;
|
||||
}
|
||||
|
||||
impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
||||
type Key = AuthorityId;
|
||||
|
||||
fn on_genesis_session<'a, I: 'a>(authorities: I)
|
||||
where
|
||||
I: Iterator<Item = (&'a T::AccountId, Self::Key)>,
|
||||
{
|
||||
Self::initialize_keys(&authorities.map(|x| x.1).collect::<Vec<_>>());
|
||||
}
|
||||
|
||||
fn on_new_session<'a, I: 'a>(changed: bool, validators: I, queued_validators: I)
|
||||
where
|
||||
I: Iterator<Item = (&'a T::AccountId, Self::Key)>,
|
||||
{
|
||||
// Remember who the authorities are for the new and next session.
|
||||
if changed {
|
||||
let keys = validators.map(|x| x.1).collect::<Vec<_>>();
|
||||
|
||||
let bounded_keys = WeakBoundedVec::<_, T::MaxAuthorities>::force_from(
|
||||
keys,
|
||||
Some(
|
||||
"Warning: The session has more validators than expected. \
|
||||
A runtime configuration adjustment may be needed.",
|
||||
),
|
||||
);
|
||||
|
||||
Keys::<T>::put(bounded_keys);
|
||||
}
|
||||
|
||||
// `changed` represents if queued_validators changed in the previous session not in the
|
||||
// current one.
|
||||
let next_keys = queued_validators.map(|x| x.1).collect::<Vec<_>>();
|
||||
|
||||
let next_bounded_keys = WeakBoundedVec::<_, T::MaxAuthorities>::force_from(
|
||||
next_keys,
|
||||
Some(
|
||||
"Warning: The session has more queued validators than expected. \
|
||||
A runtime configuration adjustment may be needed.",
|
||||
),
|
||||
);
|
||||
|
||||
NextKeys::<T>::put(next_bounded_keys);
|
||||
}
|
||||
|
||||
fn on_disabled(_i: u32) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate as pezpallet_authority_discovery;
|
||||
use alloc::vec;
|
||||
use pezframe_support::{derive_impl, parameter_types, traits::ConstU32};
|
||||
use pezsp_application_crypto::Pair;
|
||||
use pezsp_authority_discovery::AuthorityPair;
|
||||
use pezsp_core::crypto::key_types;
|
||||
use pezsp_io::TestExternalities;
|
||||
use pezsp_runtime::{
|
||||
testing::UintAuthorityId,
|
||||
traits::{ConvertInto, IdentityLookup, OpaqueKeys},
|
||||
BuildStorage, KeyTypeId, Perbill,
|
||||
};
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
pezframe_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Session: pezpallet_session,
|
||||
Balances: pezpallet_balances,
|
||||
AuthorityDiscovery: pezpallet_authority_discovery,
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(33);
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type MaxAuthorities = ConstU32<100>;
|
||||
}
|
||||
|
||||
impl pezpallet_session::Config for Test {
|
||||
type SessionManager = ();
|
||||
type Keys = UintAuthorityId;
|
||||
type ShouldEndSession = pezpallet_session::PeriodicSessions<Period, Offset>;
|
||||
type SessionHandler = TestSessionHandler;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ValidatorId = AuthorityId;
|
||||
type ValidatorIdOf = ConvertInto;
|
||||
type NextSessionRotation = pezpallet_session::PeriodicSessions<Period, Offset>;
|
||||
type DisablingStrategy = ();
|
||||
type WeightInfo = ();
|
||||
type Currency = Balances;
|
||||
type KeyDeposit = ();
|
||||
}
|
||||
|
||||
pub type BlockNumber = u64;
|
||||
|
||||
parameter_types! {
|
||||
pub const Period: BlockNumber = 1;
|
||||
pub const Offset: BlockNumber = 0;
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type AccountId = AuthorityId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u64>;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
pub struct TestSessionHandler;
|
||||
impl pezpallet_session::SessionHandler<AuthorityId> for TestSessionHandler {
|
||||
const KEY_TYPE_IDS: &'static [KeyTypeId] = &[key_types::DUMMY];
|
||||
|
||||
fn on_new_session<Ks: OpaqueKeys>(
|
||||
_changed: bool,
|
||||
_validators: &[(AuthorityId, Ks)],
|
||||
_queued_validators: &[(AuthorityId, Ks)],
|
||||
) {
|
||||
}
|
||||
|
||||
fn on_disabled(_validator_index: u32) {}
|
||||
|
||||
fn on_genesis_session<Ks: OpaqueKeys>(_validators: &[(AuthorityId, Ks)]) {}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authorities_returns_current_and_next_authority_set() {
|
||||
// The whole authority discovery pallet ignores account ids, but we still need them for
|
||||
// `pezpallet_session::OneSessionHandler::on_new_session`, thus its safe to use the same value
|
||||
// everywhere.
|
||||
let account_id = AuthorityPair::from_seed_slice(vec![10; 32].as_ref()).unwrap().public();
|
||||
|
||||
let mut first_authorities: Vec<AuthorityId> = vec![0, 1]
|
||||
.into_iter()
|
||||
.map(|i| AuthorityPair::from_seed_slice(vec![i; 32].as_ref()).unwrap().public())
|
||||
.map(AuthorityId::from)
|
||||
.collect();
|
||||
|
||||
let second_authorities: Vec<AuthorityId> = vec![2, 3]
|
||||
.into_iter()
|
||||
.map(|i| AuthorityPair::from_seed_slice(vec![i; 32].as_ref()).unwrap().public())
|
||||
.map(AuthorityId::from)
|
||||
.collect();
|
||||
// Needed for `pezpallet_session::OneSessionHandler::on_new_session`.
|
||||
let second_authorities_and_account_ids = second_authorities
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|id| (&account_id, id))
|
||||
.collect::<Vec<(&AuthorityId, AuthorityId)>>();
|
||||
|
||||
let third_authorities: Vec<AuthorityId> = vec![4, 5]
|
||||
.into_iter()
|
||||
.map(|i| AuthorityPair::from_seed_slice(vec![i; 32].as_ref()).unwrap().public())
|
||||
.map(AuthorityId::from)
|
||||
.collect();
|
||||
// Needed for `pezpallet_session::OneSessionHandler::on_new_session`.
|
||||
let third_authorities_and_account_ids = third_authorities
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|id| (&account_id, id))
|
||||
.collect::<Vec<(&AuthorityId, AuthorityId)>>();
|
||||
|
||||
let mut fourth_authorities: Vec<AuthorityId> = vec![6, 7]
|
||||
.into_iter()
|
||||
.map(|i| AuthorityPair::from_seed_slice(vec![i; 32].as_ref()).unwrap().public())
|
||||
.map(AuthorityId::from)
|
||||
.collect();
|
||||
// Needed for `pezpallet_session::OneSessionHandler::on_new_session`.
|
||||
let fourth_authorities_and_account_ids = fourth_authorities
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|id| (&account_id, id))
|
||||
.collect::<Vec<(&AuthorityId, AuthorityId)>>();
|
||||
|
||||
// Build genesis.
|
||||
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
|
||||
pezpallet_authority_discovery::GenesisConfig::<Test> { keys: vec![], ..Default::default() }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
// Create externalities.
|
||||
let mut externalities = TestExternalities::new(t);
|
||||
|
||||
externalities.execute_with(|| {
|
||||
use pezframe_support::traits::OneSessionHandler;
|
||||
|
||||
AuthorityDiscovery::on_genesis_session(
|
||||
first_authorities.iter().map(|id| (id, id.clone())),
|
||||
);
|
||||
first_authorities.sort();
|
||||
let mut authorities_returned = AuthorityDiscovery::authorities();
|
||||
authorities_returned.sort();
|
||||
assert_eq!(first_authorities, authorities_returned);
|
||||
|
||||
// When `changed` set to false, the authority set should not be updated.
|
||||
AuthorityDiscovery::on_new_session(
|
||||
false,
|
||||
second_authorities_and_account_ids.clone().into_iter(),
|
||||
third_authorities_and_account_ids.clone().into_iter(),
|
||||
);
|
||||
let authorities_returned = AuthorityDiscovery::authorities();
|
||||
let mut first_and_third_authorities = first_authorities
|
||||
.iter()
|
||||
.chain(third_authorities.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<AuthorityId>>();
|
||||
first_and_third_authorities.sort();
|
||||
|
||||
assert_eq!(
|
||||
first_and_third_authorities, authorities_returned,
|
||||
"Expected authority set not to change as `changed` was set to false.",
|
||||
);
|
||||
|
||||
// When `changed` set to true, the authority set should be updated.
|
||||
AuthorityDiscovery::on_new_session(
|
||||
true,
|
||||
third_authorities_and_account_ids.into_iter(),
|
||||
fourth_authorities_and_account_ids.clone().into_iter(),
|
||||
);
|
||||
|
||||
let mut third_and_fourth_authorities = third_authorities
|
||||
.iter()
|
||||
.chain(fourth_authorities.iter())
|
||||
.cloned()
|
||||
.collect::<Vec<AuthorityId>>();
|
||||
third_and_fourth_authorities.sort();
|
||||
assert_eq!(
|
||||
third_and_fourth_authorities,
|
||||
AuthorityDiscovery::authorities(),
|
||||
"Expected authority set to contain both the authorities of the new as well as the \
|
||||
next session."
|
||||
);
|
||||
|
||||
// With overlapping authority sets, `authorities()` should return a deduplicated set.
|
||||
AuthorityDiscovery::on_new_session(
|
||||
true,
|
||||
fourth_authorities_and_account_ids.clone().into_iter(),
|
||||
fourth_authorities_and_account_ids.clone().into_iter(),
|
||||
);
|
||||
fourth_authorities.sort();
|
||||
assert_eq!(
|
||||
fourth_authorities,
|
||||
AuthorityDiscovery::authorities(),
|
||||
"Expected authority set to be deduplicated."
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
[package]
|
||||
name = "pezpallet-authorship"
|
||||
version = "28.0.0"
|
||||
description = "Block and Uncle Author tracking for the FRAME"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
impl-trait-for-tuples = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
Authorship tracking for FRAME runtimes.
|
||||
|
||||
This tracks the current author of the block and recent uncles.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,177 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Authorship tracking for FRAME runtimes.
|
||||
//!
|
||||
//! This tracks the current author of the block.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use pezframe_support::traits::FindAuthor;
|
||||
|
||||
pub use pallet::*;
|
||||
|
||||
/// An event handler for the authorship pallet. There is a dummy implementation
|
||||
/// for `()`, which does nothing.
|
||||
#[impl_trait_for_tuples::impl_for_tuples(30)]
|
||||
pub trait EventHandler<Author, BlockNumber> {
|
||||
/// Note that the given account ID is the author of the current block.
|
||||
fn note_author(author: Author);
|
||||
}
|
||||
|
||||
#[pezframe_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use pezframe_support::pezpallet_prelude::*;
|
||||
use pezframe_system::pezpallet_prelude::*;
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: pezframe_system::Config {
|
||||
/// Find the author of a block.
|
||||
type FindAuthor: FindAuthor<Self::AccountId>;
|
||||
/// An event handler for authored blocks.
|
||||
type EventHandler: EventHandler<Self::AccountId, BlockNumberFor<Self>>;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn on_initialize(_: BlockNumberFor<T>) -> Weight {
|
||||
if let Some(author) = Self::author() {
|
||||
T::EventHandler::note_author(author);
|
||||
}
|
||||
|
||||
Weight::zero()
|
||||
}
|
||||
|
||||
fn on_finalize(_: BlockNumberFor<T>) {
|
||||
// ensure we never go to trie with these values.
|
||||
<Author<T>>::kill();
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::storage]
|
||||
#[pallet::whitelist_storage]
|
||||
/// Author of current block.
|
||||
pub(super) type Author<T: Config> = StorageValue<_, T::AccountId, OptionQuery>;
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Fetch the author of the block.
|
||||
///
|
||||
/// This is safe to invoke in `on_initialize` implementations, as well
|
||||
/// as afterwards.
|
||||
pub fn author() -> Option<T::AccountId> {
|
||||
// Check the memorized storage value.
|
||||
if let Some(author) = <Author<T>>::get() {
|
||||
return Some(author);
|
||||
}
|
||||
|
||||
let digest = <pezframe_system::Pallet<T>>::digest();
|
||||
let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime());
|
||||
T::FindAuthor::find_author(pre_runtime_digests).inspect(|a| {
|
||||
<Author<T>>::put(&a);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate as pezpallet_authorship;
|
||||
use codec::{Decode, Encode};
|
||||
use pezframe_support::{derive_impl, ConsensusEngineId};
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::{
|
||||
generic::DigestItem, testing::Header, traits::Header as HeaderT, BuildStorage,
|
||||
};
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
pezframe_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Authorship: pezpallet_authorship,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
impl pallet::Config for Test {
|
||||
type FindAuthor = AuthorGiven;
|
||||
type EventHandler = ();
|
||||
}
|
||||
|
||||
const TEST_ID: ConsensusEngineId = [1, 2, 3, 4];
|
||||
|
||||
pub struct AuthorGiven;
|
||||
|
||||
impl FindAuthor<u64> for AuthorGiven {
|
||||
fn find_author<'a, I>(digests: I) -> Option<u64>
|
||||
where
|
||||
I: 'a + IntoIterator<Item = (ConsensusEngineId, &'a [u8])>,
|
||||
{
|
||||
for (id, mut data) in digests {
|
||||
if id == TEST_ID {
|
||||
return u64::decode(&mut data).ok();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn seal_header(mut header: Header, author: u64) -> Header {
|
||||
{
|
||||
let digest = header.digest_mut();
|
||||
digest.logs.push(DigestItem::PreRuntime(TEST_ID, author.encode()));
|
||||
digest.logs.push(DigestItem::Seal(TEST_ID, author.encode()));
|
||||
}
|
||||
|
||||
header
|
||||
}
|
||||
|
||||
fn create_header(number: u64, parent_hash: H256, state_root: H256) -> Header {
|
||||
Header::new(number, Default::default(), state_root, parent_hash, Default::default())
|
||||
}
|
||||
|
||||
fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
t.into()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sets_author_lazily() {
|
||||
new_test_ext().execute_with(|| {
|
||||
let author = 42;
|
||||
let mut header =
|
||||
seal_header(create_header(1, Default::default(), [1; 32].into()), author);
|
||||
|
||||
header.digest_mut().pop(); // pop the seal off.
|
||||
System::reset_events();
|
||||
System::initialize(&1, &Default::default(), header.digest());
|
||||
|
||||
assert_eq!(Authorship::author(), Some(author));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
[package]
|
||||
name = "pezpallet-babe"
|
||||
version = "28.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Consensus extension module for BABE consensus. Collects on-chain randomness from VRF outputs and manages epoch transitions."
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
log = { workspace = true }
|
||||
pezpallet-authorship = { workspace = true }
|
||||
pezpallet-session = { workspace = true }
|
||||
pezpallet-timestamp = { workspace = true }
|
||||
scale-info = { features = ["derive", "serde"], workspace = true }
|
||||
pezsp-application-crypto = { features = ["serde"], workspace = true }
|
||||
pezsp-consensus-babe = { features = ["serde"], workspace = true }
|
||||
pezsp-core = { features = ["serde"], workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { features = ["serde"], workspace = true }
|
||||
pezsp-session = { workspace = true }
|
||||
pezsp-staking = { features = ["serde"], workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezframe-election-provider-support = { workspace = true, default-features = true }
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezpallet-offences = { workspace = true, default-features = true }
|
||||
pezpallet-staking = { workspace = true, default-features = true }
|
||||
pezpallet-staking-reward-curve = { workspace = true, default-features = true }
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
pezsp-tracing = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-election-provider-support/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-authorship/std",
|
||||
"pezpallet-balances/std",
|
||||
"pezpallet-offences/std",
|
||||
"pezpallet-session/std",
|
||||
"pezpallet-staking/std",
|
||||
"pezpallet-timestamp/std",
|
||||
"scale-info/std",
|
||||
"pezsp-application-crypto/std",
|
||||
"pezsp-consensus-babe/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-session/std",
|
||||
"pezsp-staking/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-election-provider-support/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-authorship/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-offences/runtime-benchmarks",
|
||||
"pezpallet-session/runtime-benchmarks",
|
||||
"pezpallet-staking-reward-curve/runtime-benchmarks",
|
||||
"pezpallet-staking/runtime-benchmarks",
|
||||
"pezpallet-timestamp/runtime-benchmarks",
|
||||
"pezsp-consensus-babe/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-session/runtime-benchmarks",
|
||||
"pezsp-staking/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-election-provider-support/try-runtime",
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-authorship/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-offences/try-runtime",
|
||||
"pezpallet-session/try-runtime",
|
||||
"pezpallet-staking/try-runtime",
|
||||
"pezpallet-timestamp/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
Consensus extension module for BABE consensus. Collects on-chain randomness
|
||||
from VRF outputs and manages epoch transitions.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,75 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Benchmarks for the BABE Pallet.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use super::*;
|
||||
use pezframe_benchmarking::v2::*;
|
||||
|
||||
type Header = pezsp_runtime::generic::Header<u64, pezsp_runtime::traits::BlakeTwo256>;
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark]
|
||||
fn check_equivocation_proof(x: Linear<0, 1>) {
|
||||
// NOTE: generated with the test below `test_generate_equivocation_report_blob`.
|
||||
// the output is not deterministic since keys are generated randomly (and therefore
|
||||
// signature content changes). it should not affect the benchmark.
|
||||
// with the current benchmark setup it is not possible to generate this programmatically
|
||||
// from the benchmark setup.
|
||||
const EQUIVOCATION_PROOF_BLOB: [u8; 416] = [
|
||||
222, 241, 46, 66, 243, 228, 135, 233, 177, 64, 149, 170, 141, 92, 193, 106, 51, 73, 31,
|
||||
27, 80, 218, 220, 248, 129, 29, 20, 128, 243, 250, 134, 39, 11, 0, 0, 0, 0, 0, 0, 0,
|
||||
158, 4, 7, 240, 67, 153, 134, 190, 251, 196, 229, 95, 136, 165, 234, 228, 255, 18, 2,
|
||||
187, 76, 125, 108, 50, 67, 33, 196, 108, 38, 115, 179, 86, 40, 36, 27, 5, 105, 58, 228,
|
||||
94, 198, 65, 212, 218, 213, 61, 170, 21, 51, 249, 182, 121, 101, 91, 204, 25, 31, 87,
|
||||
219, 208, 43, 119, 211, 185, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 6, 66, 65, 66, 69, 52, 2, 0, 0, 0, 0, 11,
|
||||
0, 0, 0, 0, 0, 0, 0, 5, 66, 65, 66, 69, 1, 1, 188, 192, 217, 91, 138, 78, 217, 80, 8,
|
||||
29, 140, 55, 242, 210, 170, 184, 73, 98, 135, 212, 236, 209, 115, 52, 200, 79, 175,
|
||||
172, 242, 161, 199, 47, 236, 93, 101, 95, 43, 34, 141, 16, 247, 220, 33, 59, 31, 197,
|
||||
27, 7, 196, 62, 12, 238, 236, 124, 136, 191, 29, 36, 22, 238, 242, 202, 57, 139, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 40, 23, 175, 153, 83, 6, 33, 65, 123, 51, 80, 223, 126, 186, 226, 225, 240, 105, 28,
|
||||
169, 9, 54, 11, 138, 46, 194, 201, 250, 48, 242, 125, 117, 116, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 6, 66, 65,
|
||||
66, 69, 52, 2, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 5, 66, 65, 66, 69, 1, 1, 142, 12,
|
||||
124, 11, 167, 227, 103, 88, 78, 23, 228, 33, 96, 41, 207, 183, 227, 189, 114, 70, 254,
|
||||
30, 128, 243, 233, 83, 214, 45, 74, 182, 120, 119, 64, 243, 219, 119, 63, 240, 205,
|
||||
123, 231, 82, 205, 174, 143, 70, 2, 86, 182, 20, 16, 141, 145, 91, 116, 195, 58, 223,
|
||||
175, 145, 255, 7, 121, 133,
|
||||
];
|
||||
|
||||
let equivocation_proof1: pezsp_consensus_babe::EquivocationProof<Header> =
|
||||
Decode::decode(&mut &EQUIVOCATION_PROOF_BLOB[..]).unwrap();
|
||||
|
||||
let equivocation_proof2 = equivocation_proof1.clone();
|
||||
|
||||
#[block]
|
||||
{
|
||||
pezsp_consensus_babe::check_equivocation_proof::<Header>(equivocation_proof1);
|
||||
}
|
||||
|
||||
assert!(pezsp_consensus_babe::check_equivocation_proof::<Header>(equivocation_proof2));
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(3), crate::mock::Test,);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Default weights for the Babe Pallet
|
||||
//! This file was not auto-generated.
|
||||
|
||||
use pezframe_support::weights::{
|
||||
constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_MICROS, WEIGHT_REF_TIME_PER_NANOS},
|
||||
Weight,
|
||||
};
|
||||
|
||||
impl crate::WeightInfo for () {
|
||||
fn plan_config_change() -> Weight {
|
||||
DbWeight::get().writes(1)
|
||||
}
|
||||
|
||||
fn report_equivocation(validator_count: u32, max_nominators_per_validator: u32) -> Weight {
|
||||
// we take the validator set count from the membership proof to
|
||||
// calculate the weight but we set a floor of 100 validators.
|
||||
let validator_count = validator_count.max(100) as u64;
|
||||
|
||||
// checking membership proof
|
||||
Weight::from_parts(35u64 * WEIGHT_REF_TIME_PER_MICROS, 0)
|
||||
.saturating_add(
|
||||
Weight::from_parts(175u64 * WEIGHT_REF_TIME_PER_NANOS, 0)
|
||||
.saturating_mul(validator_count),
|
||||
)
|
||||
.saturating_add(DbWeight::get().reads(5))
|
||||
// check equivocation proof
|
||||
.saturating_add(Weight::from_parts(110u64 * WEIGHT_REF_TIME_PER_MICROS, 0))
|
||||
// report offence
|
||||
.saturating_add(Weight::from_parts(110u64 * WEIGHT_REF_TIME_PER_MICROS, 0))
|
||||
.saturating_add(Weight::from_parts(
|
||||
25u64 * WEIGHT_REF_TIME_PER_MICROS * max_nominators_per_validator as u64,
|
||||
0,
|
||||
))
|
||||
.saturating_add(DbWeight::get().reads(14 + 3 * max_nominators_per_validator as u64))
|
||||
.saturating_add(DbWeight::get().writes(10 + 3 * max_nominators_per_validator as u64))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! An opt-in utility module for reporting equivocations.
|
||||
//!
|
||||
//! This module defines an offence type for BABE equivocations
|
||||
//! and some utility traits to wire together:
|
||||
//! - a system for reporting offences;
|
||||
//! - a system for submitting unsigned transactions;
|
||||
//! - a way to get the current block author;
|
||||
//!
|
||||
//! These can be used in an offchain context in order to submit equivocation
|
||||
//! reporting extrinsics (from the client that's import BABE blocks).
|
||||
//! And in a runtime context, so that the BABE pallet can validate the
|
||||
//! equivocation proofs in the extrinsic and report the offences.
|
||||
//!
|
||||
//! IMPORTANT:
|
||||
//! When using this module for enabling equivocation reporting it is required
|
||||
//! that the `ValidateUnsigned` for the BABE pallet is used in the runtime
|
||||
//! definition.
|
||||
|
||||
use alloc::{boxed::Box, vec, vec::Vec};
|
||||
use pezframe_support::traits::{Get, KeyOwnerProofSystem};
|
||||
use pezframe_system::pezpallet_prelude::HeaderFor;
|
||||
use log::{error, info};
|
||||
|
||||
use pezsp_consensus_babe::{AuthorityId, EquivocationProof, Slot, KEY_TYPE};
|
||||
use pezsp_runtime::{
|
||||
transaction_validity::{
|
||||
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
|
||||
TransactionValidityError, ValidTransaction,
|
||||
},
|
||||
DispatchError, KeyTypeId, Perbill,
|
||||
};
|
||||
use pezsp_session::{GetSessionNumber, GetValidatorCount};
|
||||
use pezsp_staking::{
|
||||
offence::{Kind, Offence, OffenceReportSystem, ReportOffence},
|
||||
SessionIndex,
|
||||
};
|
||||
|
||||
use crate::{Call, Config, Error, Pallet, LOG_TARGET};
|
||||
|
||||
/// BABE equivocation offence report.
|
||||
///
|
||||
/// When a validator released two or more blocks at the same slot.
|
||||
pub struct EquivocationOffence<Offender> {
|
||||
/// A babe slot in which this incident happened.
|
||||
pub slot: Slot,
|
||||
/// The session index in which the incident happened.
|
||||
pub session_index: SessionIndex,
|
||||
/// The size of the validator set at the time of the offence.
|
||||
pub validator_set_count: u32,
|
||||
/// The authority that produced the equivocation.
|
||||
pub offender: Offender,
|
||||
}
|
||||
|
||||
impl<Offender: Clone> Offence<Offender> for EquivocationOffence<Offender> {
|
||||
const ID: Kind = *b"babe:equivocatio";
|
||||
type TimeSlot = Slot;
|
||||
|
||||
fn offenders(&self) -> Vec<Offender> {
|
||||
vec![self.offender.clone()]
|
||||
}
|
||||
|
||||
fn session_index(&self) -> SessionIndex {
|
||||
self.session_index
|
||||
}
|
||||
|
||||
fn validator_set_count(&self) -> u32 {
|
||||
self.validator_set_count
|
||||
}
|
||||
|
||||
fn time_slot(&self) -> Self::TimeSlot {
|
||||
self.slot
|
||||
}
|
||||
|
||||
// The formula is min((3k / n)^2, 1)
|
||||
// where k = offenders_number and n = validators_number
|
||||
fn slash_fraction(&self, offenders_count: u32) -> Perbill {
|
||||
// Perbill type domain is [0, 1] by definition
|
||||
Perbill::from_rational(3 * offenders_count, self.validator_set_count).square()
|
||||
}
|
||||
}
|
||||
|
||||
/// BABE equivocation offence report system.
|
||||
///
|
||||
/// This type implements `OffenceReportSystem` such that:
|
||||
/// - Equivocation reports are published on-chain as unsigned extrinsic via
|
||||
/// `offchain::CreateTransactionBase`.
|
||||
/// - On-chain validity checks and processing are mostly delegated to the user provided generic
|
||||
/// types implementing `KeyOwnerProofSystem` and `ReportOffence` traits.
|
||||
/// - Offence reporter for unsigned transactions is fetched via the the authorship pallet.
|
||||
pub struct EquivocationReportSystem<T, R, P, L>(core::marker::PhantomData<(T, R, P, L)>);
|
||||
|
||||
impl<T, R, P, L>
|
||||
OffenceReportSystem<Option<T::AccountId>, (EquivocationProof<HeaderFor<T>>, T::KeyOwnerProof)>
|
||||
for EquivocationReportSystem<T, R, P, L>
|
||||
where
|
||||
T: Config + pezpallet_authorship::Config + pezframe_system::offchain::CreateBare<Call<T>>,
|
||||
R: ReportOffence<
|
||||
T::AccountId,
|
||||
P::IdentificationTuple,
|
||||
EquivocationOffence<P::IdentificationTuple>,
|
||||
>,
|
||||
P: KeyOwnerProofSystem<(KeyTypeId, AuthorityId), Proof = T::KeyOwnerProof>,
|
||||
P::IdentificationTuple: Clone,
|
||||
L: Get<u64>,
|
||||
{
|
||||
type Longevity = L;
|
||||
|
||||
fn publish_evidence(
|
||||
evidence: (EquivocationProof<HeaderFor<T>>, T::KeyOwnerProof),
|
||||
) -> Result<(), ()> {
|
||||
use pezframe_system::offchain::SubmitTransaction;
|
||||
let (equivocation_proof, key_owner_proof) = evidence;
|
||||
|
||||
let call = Call::report_equivocation_unsigned {
|
||||
equivocation_proof: Box::new(equivocation_proof),
|
||||
key_owner_proof,
|
||||
};
|
||||
let xt = T::create_bare(call.into());
|
||||
let res = SubmitTransaction::<T, Call<T>>::submit_transaction(xt);
|
||||
match res {
|
||||
Ok(_) => info!(target: LOG_TARGET, "Submitted equivocation report"),
|
||||
Err(e) => error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e),
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn check_evidence(
|
||||
evidence: (EquivocationProof<HeaderFor<T>>, T::KeyOwnerProof),
|
||||
) -> Result<(), TransactionValidityError> {
|
||||
let (equivocation_proof, key_owner_proof) = evidence;
|
||||
|
||||
// Check the membership proof to extract the offender's id
|
||||
let key = (pezsp_consensus_babe::KEY_TYPE, equivocation_proof.offender.clone());
|
||||
let offender =
|
||||
P::check_proof(key, key_owner_proof.clone()).ok_or(InvalidTransaction::BadProof)?;
|
||||
|
||||
// Check if the offence has already been reported, and if so then we can discard the report.
|
||||
if R::is_known_offence(&[offender], &equivocation_proof.slot) {
|
||||
Err(InvalidTransaction::Stale.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn process_evidence(
|
||||
reporter: Option<T::AccountId>,
|
||||
evidence: (EquivocationProof<HeaderFor<T>>, T::KeyOwnerProof),
|
||||
) -> Result<(), DispatchError> {
|
||||
let (equivocation_proof, key_owner_proof) = evidence;
|
||||
let reporter = reporter.or_else(|| <pezpallet_authorship::Pallet<T>>::author());
|
||||
let offender = equivocation_proof.offender.clone();
|
||||
let slot = equivocation_proof.slot;
|
||||
|
||||
// Validate the equivocation proof (check votes are different and signatures are valid)
|
||||
if !pezsp_consensus_babe::check_equivocation_proof(equivocation_proof) {
|
||||
return Err(Error::<T>::InvalidEquivocationProof.into());
|
||||
}
|
||||
|
||||
let validator_set_count = key_owner_proof.validator_count();
|
||||
let session_index = key_owner_proof.session();
|
||||
|
||||
let epoch_index =
|
||||
*slot.saturating_sub(crate::GenesisSlot::<T>::get()) / T::EpochDuration::get();
|
||||
|
||||
// Check that the slot number is consistent with the session index
|
||||
// in the key ownership proof (i.e. slot is for that epoch)
|
||||
if Pallet::<T>::session_index_for_epoch(epoch_index) != session_index {
|
||||
return Err(Error::<T>::InvalidKeyOwnershipProof.into());
|
||||
}
|
||||
|
||||
// Check the membership proof and extract the offender's id
|
||||
let offender = P::check_proof((KEY_TYPE, offender), key_owner_proof)
|
||||
.ok_or(Error::<T>::InvalidKeyOwnershipProof)?;
|
||||
|
||||
let offence = EquivocationOffence { slot, validator_set_count, offender, session_index };
|
||||
|
||||
R::report_offence(reporter.into_iter().collect(), offence)
|
||||
.map_err(|_| Error::<T>::DuplicateOffenceReport)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Methods for the `ValidateUnsigned` implementation:
|
||||
/// It restricts calls to `report_equivocation_unsigned` to local calls (i.e. extrinsics generated
|
||||
/// on this node) or that already in a block. This guarantees that only block authors can include
|
||||
/// unsigned equivocation reports.
|
||||
impl<T: Config> Pallet<T> {
|
||||
pub fn validate_unsigned(source: TransactionSource, call: &Call<T>) -> TransactionValidity {
|
||||
if let Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } = call {
|
||||
// discard equivocation report not coming from the local node
|
||||
match source {
|
||||
TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ },
|
||||
_ => {
|
||||
log::warn!(
|
||||
target: LOG_TARGET,
|
||||
"rejecting unsigned report equivocation transaction because it is not local/in-block.",
|
||||
);
|
||||
|
||||
return InvalidTransaction::Call.into();
|
||||
},
|
||||
}
|
||||
|
||||
// Check report validity
|
||||
let evidence = (*equivocation_proof.clone(), key_owner_proof.clone());
|
||||
T::EquivocationReportSystem::check_evidence(evidence)?;
|
||||
|
||||
let longevity =
|
||||
<T::EquivocationReportSystem as OffenceReportSystem<_, _>>::Longevity::get();
|
||||
|
||||
ValidTransaction::with_tag_prefix("BabeEquivocation")
|
||||
// We assign the maximum priority for any equivocation report.
|
||||
.priority(TransactionPriority::max_value())
|
||||
// Only one equivocation report for the same offender at the same slot.
|
||||
.and_provides((equivocation_proof.offender.clone(), *equivocation_proof.slot))
|
||||
.longevity(longevity)
|
||||
// We don't propagate this. This can never be included on a remote node.
|
||||
.propagate(false)
|
||||
.build()
|
||||
} else {
|
||||
InvalidTransaction::Call.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pre_dispatch(call: &Call<T>) -> Result<(), TransactionValidityError> {
|
||||
if let Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } = call {
|
||||
let evidence = (*equivocation_proof.clone(), key_owner_proof.clone());
|
||||
T::EquivocationReportSystem::check_evidence(evidence)
|
||||
} else {
|
||||
Err(InvalidTransaction::Call.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,415 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test utilities
|
||||
|
||||
use crate::{self as pezpallet_babe, Config, CurrentSlot};
|
||||
use codec::Encode;
|
||||
use pezframe_election_provider_support::{
|
||||
bounds::{ElectionBounds, ElectionBoundsBuilder},
|
||||
onchain, SequentialPhragmen,
|
||||
};
|
||||
use pezframe_support::{
|
||||
derive_impl, parameter_types,
|
||||
traits::{ConstU128, ConstU32, ConstU64, OnInitialize},
|
||||
};
|
||||
use pezpallet_session::historical as pezpallet_session_historical;
|
||||
use pezsp_consensus_babe::{AuthorityId, AuthorityPair, Randomness, Slot, VrfSignature};
|
||||
use pezsp_core::{
|
||||
crypto::{Pair, VrfSecret},
|
||||
ConstBool, U256,
|
||||
};
|
||||
use pezsp_io;
|
||||
use pezsp_runtime::{
|
||||
curve::PiecewiseLinear,
|
||||
impl_opaque_keys,
|
||||
testing::{Digest, DigestItem, Header, TestXt},
|
||||
traits::{Header as _, OpaqueKeys},
|
||||
BuildStorage, DispatchError, Perbill,
|
||||
};
|
||||
use pezsp_staking::{EraIndex, SessionIndex};
|
||||
|
||||
type DummyValidatorId = u64;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
pezframe_support::construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Authorship: pezpallet_authorship,
|
||||
Balances: pezpallet_balances,
|
||||
Historical: pezpallet_session_historical,
|
||||
Offences: pezpallet_offences,
|
||||
Babe: pezpallet_babe,
|
||||
Staking: pezpallet_staking,
|
||||
Session: pezpallet_session,
|
||||
Timestamp: pezpallet_timestamp,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u128>;
|
||||
}
|
||||
|
||||
impl<C> pezframe_system::offchain::CreateTransactionBase<C> for Test
|
||||
where
|
||||
RuntimeCall: From<C>,
|
||||
{
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Extrinsic = TestXt<RuntimeCall, ()>;
|
||||
}
|
||||
|
||||
impl<C> pezframe_system::offchain::CreateBare<C> for Test
|
||||
where
|
||||
RuntimeCall: From<C>,
|
||||
{
|
||||
fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic {
|
||||
TestXt::new_bare(call)
|
||||
}
|
||||
}
|
||||
|
||||
impl_opaque_keys! {
|
||||
pub struct MockSessionKeys {
|
||||
pub babe_authority: super::Pallet<Test>,
|
||||
}
|
||||
}
|
||||
|
||||
impl pezpallet_session::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ValidatorId = <Self as pezframe_system::Config>::AccountId;
|
||||
type ValidatorIdOf = pezsp_runtime::traits::ConvertInto;
|
||||
type ShouldEndSession = Babe;
|
||||
type NextSessionRotation = Babe;
|
||||
type SessionManager = pezpallet_session::historical::NoteHistoricalRoot<Self, Staking>;
|
||||
type SessionHandler = <MockSessionKeys as OpaqueKeys>::KeyTypeIdProviders;
|
||||
type Keys = MockSessionKeys;
|
||||
type DisablingStrategy = ();
|
||||
type WeightInfo = ();
|
||||
type Currency = Balances;
|
||||
type KeyDeposit = ();
|
||||
}
|
||||
|
||||
impl pezpallet_session::historical::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type FullIdentification = ();
|
||||
type FullIdentificationOf = pezpallet_staking::UnitIdentificationOf<Self>;
|
||||
}
|
||||
|
||||
impl pezpallet_authorship::Config for Test {
|
||||
type FindAuthor = pezpallet_session::FindAccountFromAuthorIndex<Self, Babe>;
|
||||
type EventHandler = ();
|
||||
}
|
||||
|
||||
impl pezpallet_timestamp::Config for Test {
|
||||
type Moment = u64;
|
||||
type OnTimestampSet = Babe;
|
||||
type MinimumPeriod = ConstU64<1>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
type Balance = u128;
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Test {
|
||||
type Balance = Balance;
|
||||
type ExistentialDeposit = ConstU128<1>;
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
pezpallet_staking_reward_curve::build! {
|
||||
const REWARD_CURVE: PiecewiseLinear<'static> = curve!(
|
||||
min_inflation: 0_025_000u64,
|
||||
max_inflation: 0_100_000,
|
||||
ideal_stake: 0_500_000,
|
||||
falloff: 0_050_000,
|
||||
max_piece_count: 40,
|
||||
test_precision: 0_005_000,
|
||||
);
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const SessionsPerEra: SessionIndex = 3;
|
||||
pub const BondingDuration: EraIndex = 3;
|
||||
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
|
||||
pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build();
|
||||
}
|
||||
|
||||
pub struct OnChainSeqPhragmen;
|
||||
impl onchain::Config for OnChainSeqPhragmen {
|
||||
type System = Test;
|
||||
type Solver = SequentialPhragmen<DummyValidatorId, Perbill>;
|
||||
type DataProvider = Staking;
|
||||
type WeightInfo = ();
|
||||
type MaxWinnersPerPage = ConstU32<100>;
|
||||
type MaxBackersPerWinner = ConstU32<100>;
|
||||
type Sort = ConstBool<true>;
|
||||
type Bounds = ElectionsBounds;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_staking::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_staking::Config for Test {
|
||||
type OldCurrency = Balances;
|
||||
type Currency = Balances;
|
||||
type SessionsPerEra = SessionsPerEra;
|
||||
type BondingDuration = BondingDuration;
|
||||
type AdminOrigin = pezframe_system::EnsureRoot<Self::AccountId>;
|
||||
type SessionInterface = Self;
|
||||
type UnixTime = pezpallet_timestamp::Pallet<Test>;
|
||||
type EraPayout = pezpallet_staking::ConvertCurve<RewardCurve>;
|
||||
type NextNewSession = Session;
|
||||
type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type VoterList = pezpallet_staking::UseNominatorsAndValidatorsMap<Self>;
|
||||
type TargetList = pezpallet_staking::UseValidatorsMap<Self>;
|
||||
}
|
||||
|
||||
impl pezpallet_offences::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type IdentificationTuple = pezpallet_session::historical::IdentificationTuple<Self>;
|
||||
type OnOffenceHandler = Staking;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const EpochDuration: u64 = 3;
|
||||
pub const ReportLongevity: u64 =
|
||||
BondingDuration::get() as u64 * SessionsPerEra::get() as u64 * EpochDuration::get();
|
||||
}
|
||||
|
||||
impl Config for Test {
|
||||
type EpochDuration = EpochDuration;
|
||||
type ExpectedBlockTime = ConstU64<1>;
|
||||
type EpochChangeTrigger = crate::ExternalTrigger;
|
||||
type DisabledValidators = Session;
|
||||
type WeightInfo = ();
|
||||
type MaxAuthorities = ConstU32<10>;
|
||||
type MaxNominators = ConstU32<100>;
|
||||
type KeyOwnerProof = pezsp_session::MembershipProof;
|
||||
type EquivocationReportSystem =
|
||||
super::EquivocationReportSystem<Self, Offences, Historical, ReportLongevity>;
|
||||
}
|
||||
|
||||
pub fn go_to_block(n: u64, s: u64) {
|
||||
use pezframe_support::traits::OnFinalize;
|
||||
|
||||
Babe::on_finalize(System::block_number());
|
||||
Session::on_finalize(System::block_number());
|
||||
Staking::on_finalize(System::block_number());
|
||||
|
||||
let parent_hash = if System::block_number() > 1 {
|
||||
let hdr = System::finalize();
|
||||
hdr.hash()
|
||||
} else {
|
||||
System::parent_hash()
|
||||
};
|
||||
|
||||
let pre_digest = make_secondary_plain_pre_digest(0, s.into());
|
||||
|
||||
System::reset_events();
|
||||
System::initialize(&n, &parent_hash, &pre_digest);
|
||||
|
||||
Babe::on_initialize(n);
|
||||
Session::on_initialize(n);
|
||||
Staking::on_initialize(n);
|
||||
}
|
||||
|
||||
/// Slots will grow accordingly to blocks
|
||||
pub fn progress_to_block(n: u64) {
|
||||
let mut slot = u64::from(CurrentSlot::<Test>::get()) + 1;
|
||||
for i in System::block_number() + 1..=n {
|
||||
go_to_block(i, slot);
|
||||
slot += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// Progress to the first block at the given session
|
||||
pub fn start_session(session_index: SessionIndex) {
|
||||
let missing = (session_index - Session::current_index()) * 3;
|
||||
progress_to_block(System::block_number() + missing as u64 + 1);
|
||||
assert_eq!(Session::current_index(), session_index);
|
||||
}
|
||||
|
||||
/// Progress to the first block at the given era
|
||||
pub fn start_era(era_index: EraIndex) {
|
||||
start_session((era_index * 3).into());
|
||||
assert_eq!(pezpallet_staking::CurrentEra::<Test>::get(), Some(era_index));
|
||||
}
|
||||
|
||||
pub fn make_primary_pre_digest(
|
||||
authority_index: pezsp_consensus_babe::AuthorityIndex,
|
||||
slot: pezsp_consensus_babe::Slot,
|
||||
vrf_signature: VrfSignature,
|
||||
) -> Digest {
|
||||
let digest_data = pezsp_consensus_babe::digests::PreDigest::Primary(
|
||||
pezsp_consensus_babe::digests::PrimaryPreDigest { authority_index, slot, vrf_signature },
|
||||
);
|
||||
let log = DigestItem::PreRuntime(pezsp_consensus_babe::BABE_ENGINE_ID, digest_data.encode());
|
||||
Digest { logs: vec![log] }
|
||||
}
|
||||
|
||||
pub fn make_secondary_plain_pre_digest(
|
||||
authority_index: pezsp_consensus_babe::AuthorityIndex,
|
||||
slot: pezsp_consensus_babe::Slot,
|
||||
) -> Digest {
|
||||
let digest_data = pezsp_consensus_babe::digests::PreDigest::SecondaryPlain(
|
||||
pezsp_consensus_babe::digests::SecondaryPlainPreDigest { authority_index, slot },
|
||||
);
|
||||
let log = DigestItem::PreRuntime(pezsp_consensus_babe::BABE_ENGINE_ID, digest_data.encode());
|
||||
Digest { logs: vec![log] }
|
||||
}
|
||||
|
||||
pub fn make_secondary_vrf_pre_digest(
|
||||
authority_index: pezsp_consensus_babe::AuthorityIndex,
|
||||
slot: pezsp_consensus_babe::Slot,
|
||||
vrf_signature: VrfSignature,
|
||||
) -> Digest {
|
||||
let digest_data = pezsp_consensus_babe::digests::PreDigest::SecondaryVRF(
|
||||
pezsp_consensus_babe::digests::SecondaryVRFPreDigest { authority_index, slot, vrf_signature },
|
||||
);
|
||||
let log = DigestItem::PreRuntime(pezsp_consensus_babe::BABE_ENGINE_ID, digest_data.encode());
|
||||
Digest { logs: vec![log] }
|
||||
}
|
||||
|
||||
pub fn make_vrf_signature_and_randomness(
|
||||
slot: Slot,
|
||||
pair: &pezsp_consensus_babe::AuthorityPair,
|
||||
) -> (VrfSignature, Randomness) {
|
||||
let transcript =
|
||||
pezsp_consensus_babe::make_vrf_transcript(&pezpallet_babe::Randomness::<Test>::get(), slot, 0);
|
||||
|
||||
let randomness =
|
||||
pair.as_ref().make_bytes(pezsp_consensus_babe::RANDOMNESS_VRF_CONTEXT, &transcript);
|
||||
|
||||
let signature = pair.as_ref().vrf_sign(&transcript.into());
|
||||
|
||||
(signature, randomness)
|
||||
}
|
||||
|
||||
pub fn new_test_ext(authorities_len: usize) -> pezsp_io::TestExternalities {
|
||||
new_test_ext_with_pairs(authorities_len).1
|
||||
}
|
||||
|
||||
pub fn new_test_ext_with_pairs(
|
||||
authorities_len: usize,
|
||||
) -> (Vec<AuthorityPair>, pezsp_io::TestExternalities) {
|
||||
let pairs = (0..authorities_len)
|
||||
.map(|i| AuthorityPair::from_seed(&U256::from(i).to_little_endian()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let public = pairs.iter().map(|p| p.public()).collect();
|
||||
|
||||
(pairs, new_test_ext_raw_authorities(public))
|
||||
}
|
||||
|
||||
pub fn new_test_ext_raw_authorities(authorities: Vec<AuthorityId>) -> pezsp_io::TestExternalities {
|
||||
pezsp_tracing::try_init_simple();
|
||||
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
|
||||
let balances: Vec<_> = (0..authorities.len()).map(|i| (i as u64, 10_000_000)).collect();
|
||||
|
||||
pezpallet_balances::GenesisConfig::<Test> { balances, ..Default::default() }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
// stashes are the index.
|
||||
let session_keys: Vec<_> = authorities
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, k)| {
|
||||
(i as u64, i as u64, MockSessionKeys { babe_authority: AuthorityId::from(k.clone()) })
|
||||
})
|
||||
.collect();
|
||||
|
||||
// NOTE: this will initialize the babe authorities
|
||||
// through OneSessionHandler::on_genesis_session
|
||||
pezpallet_session::GenesisConfig::<Test> { keys: session_keys, ..Default::default() }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
// controllers are same as stash
|
||||
let stakers: Vec<_> = (0..authorities.len())
|
||||
.map(|i| (i as u64, i as u64, 10_000, pezpallet_staking::StakerStatus::<u64>::Validator))
|
||||
.collect();
|
||||
|
||||
let staking_config = pezpallet_staking::GenesisConfig::<Test> {
|
||||
stakers,
|
||||
validator_count: 8,
|
||||
force_era: pezpallet_staking::Forcing::ForceNew,
|
||||
minimum_validator_count: 0,
|
||||
invulnerables: vec![],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
staking_config.assimilate_storage(&mut t).unwrap();
|
||||
|
||||
t.into()
|
||||
}
|
||||
|
||||
/// Creates an equivocation at the current block, by generating two headers.
|
||||
pub fn generate_equivocation_proof(
|
||||
offender_authority_index: u32,
|
||||
offender_authority_pair: &AuthorityPair,
|
||||
slot: Slot,
|
||||
) -> pezsp_consensus_babe::EquivocationProof<Header> {
|
||||
use pezsp_consensus_babe::digests::CompatibleDigestItem;
|
||||
|
||||
let current_block = System::block_number();
|
||||
let current_slot = CurrentSlot::<Test>::get();
|
||||
|
||||
let make_header = || {
|
||||
// We don't want to change any state, so we build the headers in a transaction and revert it
|
||||
// afterward.
|
||||
pezframe_support::storage::with_transaction(|| {
|
||||
let parent_hash = System::parent_hash();
|
||||
let pre_digest = make_secondary_plain_pre_digest(offender_authority_index, slot);
|
||||
System::reset_events();
|
||||
System::set_block_number(System::block_number() - 1);
|
||||
System::initialize(¤t_block, &parent_hash, &pre_digest);
|
||||
System::set_block_number(current_block);
|
||||
Timestamp::set_timestamp(*current_slot * Babe::slot_duration());
|
||||
let header = System::finalize();
|
||||
|
||||
pezsp_runtime::TransactionOutcome::Rollback(Ok::<_, DispatchError>(header))
|
||||
})
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
// Sign the header prehash and sign it, adding it to the block as the seal
|
||||
// digest item
|
||||
let seal_header = |header: &mut Header| {
|
||||
let prehash = header.hash();
|
||||
let seal = <DigestItem as CompatibleDigestItem>::babe_seal(
|
||||
offender_authority_pair.sign(prehash.as_ref()),
|
||||
);
|
||||
header.digest_mut().push(seal);
|
||||
};
|
||||
|
||||
// Generate two headers at the current block
|
||||
let mut h1 = make_header();
|
||||
let mut h2 = make_header();
|
||||
|
||||
seal_header(&mut h1);
|
||||
seal_header(&mut h2);
|
||||
|
||||
pezsp_consensus_babe::EquivocationProof {
|
||||
slot,
|
||||
offender: offender_authority_pair.public(),
|
||||
first_header: h1,
|
||||
second_header: h2,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Provides multiple implementations of the randomness trait based on the on-chain epoch
|
||||
//! randomness collected from VRF outputs.
|
||||
|
||||
use super::{
|
||||
AuthorVrfRandomness, Config, EpochStart, NextRandomness, Randomness, RANDOMNESS_LENGTH,
|
||||
};
|
||||
use pezframe_support::traits::Randomness as RandomnessT;
|
||||
use pezframe_system::pezpallet_prelude::BlockNumberFor;
|
||||
use pezsp_runtime::traits::{Hash, One, Saturating};
|
||||
|
||||
/// Randomness usable by consensus protocols that **depend** upon finality and take action
|
||||
/// based upon on-chain commitments made during the epoch before the previous epoch.
|
||||
///
|
||||
/// An off-chain consensus protocol requires randomness be finalized before usage, but one
|
||||
/// extra epoch delay beyond `RandomnessFromOneEpochAgo` suffices, under the assumption
|
||||
/// that finality never stalls for longer than one epoch.
|
||||
///
|
||||
/// All randomness is relative to commitments to any other inputs to the computation: If
|
||||
/// Alice samples randomness near perfectly using radioactive decay, but then afterwards
|
||||
/// Eve selects an arbitrary value with which to xor Alice's randomness, then Eve always
|
||||
/// wins whatever game they play.
|
||||
///
|
||||
/// All input commitments used with `RandomnessFromTwoEpochsAgo` should come from at least
|
||||
/// three epochs ago. We require BABE session keys be registered at least three epochs
|
||||
/// before being used to derive `ParentBlockRandomness` for example.
|
||||
///
|
||||
/// All users learn `RandomnessFromTwoEpochsAgo` when epoch `current_epoch - 1` starts,
|
||||
/// although some learn it a few block earlier inside epoch `current_epoch - 2`.
|
||||
///
|
||||
/// Adversaries with enough block producers could bias this randomness by choosing upon
|
||||
/// what their block producers build at the end of epoch `current_epoch - 2` or the
|
||||
/// beginning epoch `current_epoch - 1`, or skipping slots at the end of epoch
|
||||
/// `current_epoch - 2`.
|
||||
///
|
||||
/// Adversaries should not possess many block production slots towards the beginning or
|
||||
/// end of every epoch, but they possess some influence over when they possess more slots.
|
||||
pub struct RandomnessFromTwoEpochsAgo<T>(core::marker::PhantomData<T>);
|
||||
|
||||
/// Randomness usable by on-chain code that **does not depend** upon finality and takes
|
||||
/// action based upon on-chain commitments made during the previous epoch.
|
||||
///
|
||||
/// All randomness is relative to commitments to any other inputs to the computation: If
|
||||
/// Alice samples randomness near perfectly using radioactive decay, but then afterwards
|
||||
/// Eve selects an arbitrary value with which to xor Alice's randomness, then Eve always
|
||||
/// wins whatever game they play.
|
||||
///
|
||||
/// All input commitments used with `RandomnessFromOneEpochAgo` should come from at least
|
||||
/// two epochs ago, although the previous epoch might work in special cases under
|
||||
/// additional assumption.
|
||||
///
|
||||
/// All users learn `RandomnessFromOneEpochAgo` at the end of the previous epoch, although
|
||||
/// some block producers learn it several block earlier.
|
||||
///
|
||||
/// Adversaries with enough block producers could bias this randomness by choosing upon
|
||||
/// what their block producers build at either the end of the previous epoch or the
|
||||
/// beginning of the current epoch, or electing to skipping some of their own block
|
||||
/// production slots towards the end of the previous epoch.
|
||||
///
|
||||
/// Adversaries should not possess many block production slots towards the beginning or
|
||||
/// end of every epoch, but they possess some influence over when they possess more slots.
|
||||
///
|
||||
/// As an example usage, we determine teyrchain auctions ending times in Pezkuwi using
|
||||
/// `RandomnessFromOneEpochAgo` because it reduces bias from `ParentBlockRandomness` and
|
||||
/// does not require the extra finality delay of `RandomnessFromTwoEpochsAgo`.
|
||||
pub struct RandomnessFromOneEpochAgo<T>(core::marker::PhantomData<T>);
|
||||
|
||||
/// Randomness produced semi-freshly with each block, but inherits limitations of
|
||||
/// `RandomnessFromTwoEpochsAgo` from which it derives.
|
||||
///
|
||||
/// All randomness is relative to commitments to any other inputs to the computation: If
|
||||
/// Alice samples randomness near perfectly using radioactive decay, but then afterwards
|
||||
/// Eve selects an arbitrary value with which to xor Alice's randomness, then Eve always
|
||||
/// wins whatever game they play.
|
||||
///
|
||||
/// As with `RandomnessFromTwoEpochsAgo`, all input commitments combined with
|
||||
/// `ParentBlockRandomness` should come from at least two epoch ago, except preferably
|
||||
/// not near epoch ending, and thus ideally three epochs ago.
|
||||
///
|
||||
/// Almost all users learn this randomness for a given block by the time they receive it's
|
||||
/// parent block, which makes this randomness appear fresh enough. Yet, the block producer
|
||||
/// themselves learned this randomness at the beginning of epoch `current_epoch - 2`, at
|
||||
/// the same time as they learn `RandomnessFromTwoEpochsAgo`.
|
||||
///
|
||||
/// Aside from just biasing `RandomnessFromTwoEpochsAgo`, adversaries could also bias
|
||||
/// `ParentBlockRandomness` by never announcing their block if doing so yields an
|
||||
/// unfavorable randomness. As such, `ParentBlockRandomness` should be considered weaker
|
||||
/// than both other randomness sources provided by BABE, but `ParentBlockRandomness`
|
||||
/// remains constrained by declared staking, while a randomness source like block hash is
|
||||
/// only constrained by adversaries' unknowable computational power.
|
||||
///
|
||||
/// As an example use, teyrchains could assign block production slots based upon the
|
||||
/// `ParentBlockRandomness` of their relay parent or relay parent's parent, provided the
|
||||
/// teyrchain registers collators but avoids censorship sensitive functionality like
|
||||
/// slashing. Any teyrchain with slashing could operate BABE itself or perhaps better yet
|
||||
/// a BABE-like approach that derives its `ParentBlockRandomness`, and authorizes block
|
||||
/// production, based upon the relay parent's `ParentBlockRandomness` or more likely the
|
||||
/// relay parent's `RandomnessFromTwoEpochsAgo`.
|
||||
///
|
||||
/// NOTE: there is some nuance here regarding what is current and parent randomness. If
|
||||
/// you are using this trait from within the runtime (i.e. as part of block execution)
|
||||
/// then the randomness provided here will always be generated from the parent block. If
|
||||
/// instead you are using this randomness externally, i.e. after block execution, then
|
||||
/// this randomness will be provided by the "current" block (this stems from the fact that
|
||||
/// we process VRF outputs on block execution finalization, i.e. `on_finalize`).
|
||||
pub struct ParentBlockRandomness<T>(core::marker::PhantomData<T>);
|
||||
|
||||
/// Randomness produced semi-freshly with each block, but inherits limitations of
|
||||
/// `RandomnessFromTwoEpochsAgo` from which it derives.
|
||||
///
|
||||
/// See [`ParentBlockRandomness`].
|
||||
#[deprecated(note = "Should not be relied upon for correctness, \
|
||||
will not provide fresh randomness for the current block. \
|
||||
Please use `ParentBlockRandomness` instead.")]
|
||||
pub struct CurrentBlockRandomness<T>(core::marker::PhantomData<T>);
|
||||
|
||||
impl<T: Config> RandomnessT<T::Hash, BlockNumberFor<T>> for RandomnessFromTwoEpochsAgo<T> {
|
||||
fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor<T>) {
|
||||
let mut subject = subject.to_vec();
|
||||
subject.reserve(RANDOMNESS_LENGTH);
|
||||
subject.extend_from_slice(&Randomness::<T>::get()[..]);
|
||||
|
||||
(T::Hashing::hash(&subject[..]), EpochStart::<T>::get().0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> RandomnessT<T::Hash, BlockNumberFor<T>> for RandomnessFromOneEpochAgo<T> {
|
||||
fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor<T>) {
|
||||
let mut subject = subject.to_vec();
|
||||
subject.reserve(RANDOMNESS_LENGTH);
|
||||
subject.extend_from_slice(&NextRandomness::<T>::get()[..]);
|
||||
|
||||
(T::Hashing::hash(&subject[..]), EpochStart::<T>::get().1)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> RandomnessT<Option<T::Hash>, BlockNumberFor<T>> for ParentBlockRandomness<T> {
|
||||
fn random(subject: &[u8]) -> (Option<T::Hash>, BlockNumberFor<T>) {
|
||||
let random = AuthorVrfRandomness::<T>::get().map(|random| {
|
||||
let mut subject = subject.to_vec();
|
||||
subject.reserve(RANDOMNESS_LENGTH);
|
||||
subject.extend_from_slice(&random);
|
||||
|
||||
T::Hashing::hash(&subject[..])
|
||||
});
|
||||
|
||||
(random, <pezframe_system::Pallet<T>>::block_number().saturating_sub(One::one()))
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T: Config> RandomnessT<Option<T::Hash>, BlockNumberFor<T>> for CurrentBlockRandomness<T> {
|
||||
fn random(subject: &[u8]) -> (Option<T::Hash>, BlockNumberFor<T>) {
|
||||
let (random, _) = ParentBlockRandomness::<T>::random(subject);
|
||||
(random, <pezframe_system::Pallet<T>>::block_number())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,101 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Autogenerated weights for `pezpallet_babe`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// frame-omni-bencher
|
||||
// v1
|
||||
// benchmark
|
||||
// pallet
|
||||
// --extrinsic=*
|
||||
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
|
||||
// --pallet=pezpallet_babe
|
||||
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/HEADER-APACHE2
|
||||
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/pezframe/babe/src/weights.rs
|
||||
// --wasm-execution=compiled
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --heap-pages=4096
|
||||
// --template=bizinikiwi/.maintain/frame-weight-template.hbs
|
||||
// --no-storage-info
|
||||
// --no-min-squares
|
||||
// --no-median-slopes
|
||||
// --genesis-builder-policy=none
|
||||
// --exclude-pallets=pezpallet_xcm,pezpallet_xcm_benchmarks::fungible,pezpallet_xcm_benchmarks::generic,pezpallet_nomination_pools,pezpallet_remark,pezpallet_transaction_storage,pezpallet_election_provider_multi_block,pezpallet_election_provider_multi_block::signed,pezpallet_election_provider_multi_block::unsigned,pezpallet_election_provider_multi_block::verifier
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for `pezpallet_babe`.
|
||||
pub trait WeightInfo {
|
||||
fn check_equivocation_proof(x: u32, ) -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_babe` using the Bizinikiwi node and recommended hardware.
|
||||
pub struct BizinikiwiWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// The range of component `x` is `[0, 1]`.
|
||||
fn check_equivocation_proof(_x: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 86_746_000 picoseconds.
|
||||
Weight::from_parts(88_013_048, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// The range of component `x` is `[0, 1]`.
|
||||
fn check_equivocation_proof(_x: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 86_746_000 picoseconds.
|
||||
Weight::from_parts(88_013_048, 0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
[package]
|
||||
name = "pezpallet-bags-list"
|
||||
version = "27.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME pallet bags list"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
# parity
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
|
||||
# primitives
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
# FRAME
|
||||
pezframe-election-provider-support = { workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
|
||||
# third party
|
||||
aquamarine = { workspace = true }
|
||||
docify = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
# Optional imports for benchmarking
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezpallet-balances = { optional = true, workspace = true }
|
||||
pezsp-core = { optional = true, workspace = true }
|
||||
pezsp-io = { optional = true, workspace = true }
|
||||
pezsp-tracing = { optional = true, workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezframe-benchmarking = { workspace = true, default-features = true }
|
||||
pezframe-election-provider-support = { workspace = true, default-features = true }
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
pezsp-tracing = { workspace = true, default-features = true }
|
||||
bizinikiwi-test-utils = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-election-provider-support/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-balances?/std",
|
||||
"scale-info/std",
|
||||
"pezsp-core?/std",
|
||||
"pezsp-io?/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-tracing?/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-election-provider-support/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezsp-core",
|
||||
"pezsp-io",
|
||||
"pezsp-io?/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-tracing",
|
||||
]
|
||||
fuzz = [
|
||||
"pezframe-election-provider-support/fuzz",
|
||||
"pezpallet-balances",
|
||||
"pezsp-core",
|
||||
"pezsp-io",
|
||||
"pezsp-tracing",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-election-provider-support/try-runtime",
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-balances?/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,2 @@
|
||||
hfuzz_target
|
||||
hfuzz_workspace
|
||||
@@ -0,0 +1,32 @@
|
||||
[package]
|
||||
name = "pezpallet-bags-list-fuzzer"
|
||||
version = "4.0.0-dev"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Fuzzer for FRAME pallet bags list"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "bags-list"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
pezframe-election-provider-support = { features = [
|
||||
"fuzz",
|
||||
], workspace = true, default-features = true }
|
||||
honggfuzz = { workspace = true }
|
||||
pezpallet-bags-list = { features = [
|
||||
"fuzz",
|
||||
], workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-election-provider-support/runtime-benchmarks",
|
||||
"pezpallet-bags-list/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,94 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! # Running
|
||||
//! Running this fuzzer can be done with `cargo hfuzz run bags-list`. `honggfuzz` CLI options can
|
||||
//! be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads.
|
||||
//!
|
||||
//! # Debugging a panic
|
||||
//! Once a panic is found, it can be debugged with
|
||||
//! `cargo hfuzz run-debug fixed_point hfuzz_workspace/bags_list/*.fuzz`.
|
||||
//!
|
||||
//! # More information
|
||||
//! More information about `honggfuzz` can be found
|
||||
//! [here](https://docs.rs/honggfuzz/).
|
||||
|
||||
use pezframe_election_provider_support::{SortedListProvider, VoteWeight};
|
||||
use honggfuzz::fuzz;
|
||||
use pezpallet_bags_list::mock::{AccountId, BagsList, ExtBuilder};
|
||||
|
||||
const ID_RANGE: AccountId = 25_000;
|
||||
|
||||
/// Actions of a `SortedListProvider` that we fuzz.
|
||||
enum Action {
|
||||
Insert,
|
||||
Update,
|
||||
Remove,
|
||||
}
|
||||
|
||||
impl From<u32> for Action {
|
||||
fn from(v: u32) -> Self {
|
||||
let num_variants = Self::Remove as u32 + 1;
|
||||
match v % num_variants {
|
||||
_x if _x == Action::Insert as u32 => Action::Insert,
|
||||
_x if _x == Action::Update as u32 => Action::Update,
|
||||
_x if _x == Action::Remove as u32 => Action::Remove,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
ExtBuilder::default().build_and_execute(|| loop {
|
||||
fuzz!(|data: (AccountId, VoteWeight, u32)| {
|
||||
let (account_id_seed, vote_weight, action_seed) = data;
|
||||
|
||||
let id = account_id_seed % ID_RANGE;
|
||||
let action = Action::from(action_seed);
|
||||
|
||||
match action {
|
||||
Action::Insert => {
|
||||
if BagsList::on_insert(id, vote_weight).is_err() {
|
||||
// this was a duplicate id, which is ok. We can just update it.
|
||||
BagsList::on_update(&id, vote_weight).unwrap();
|
||||
}
|
||||
assert!(BagsList::contains(&id));
|
||||
},
|
||||
Action::Update => {
|
||||
let already_contains = BagsList::contains(&id);
|
||||
if already_contains {
|
||||
BagsList::on_update(&id, vote_weight).unwrap();
|
||||
assert!(BagsList::contains(&id));
|
||||
} else {
|
||||
BagsList::on_update(&id, vote_weight).unwrap_err();
|
||||
}
|
||||
},
|
||||
Action::Remove => {
|
||||
let already_contains = BagsList::contains(&id);
|
||||
if already_contains {
|
||||
BagsList::on_remove(&id).unwrap();
|
||||
} else {
|
||||
BagsList::on_remove(&id).unwrap_err();
|
||||
}
|
||||
assert!(!BagsList::contains(&id));
|
||||
},
|
||||
}
|
||||
|
||||
assert!(BagsList::do_try_state().is_ok());
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
[package]
|
||||
name = "pezpallet-bags-list-remote-tests"
|
||||
version = "4.0.0-dev"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME pallet bags list remote test"
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
# frame
|
||||
pezframe-election-provider-support = { workspace = true, default-features = true }
|
||||
pezframe-support = { workspace = true, default-features = true }
|
||||
pezframe-system = { workspace = true, default-features = true }
|
||||
pezpallet-bags-list = { features = [
|
||||
"fuzz",
|
||||
], workspace = true, default-features = true }
|
||||
pezpallet-staking = { workspace = true, default-features = true }
|
||||
|
||||
# core
|
||||
pezsp-core = { workspace = true, default-features = true }
|
||||
pezsp-runtime = { workspace = true, default-features = true }
|
||||
|
||||
# utils
|
||||
remote-externalities = { workspace = true, default-features = true }
|
||||
|
||||
# others
|
||||
log = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-election-provider-support/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-bags-list/runtime-benchmarks",
|
||||
"pezpallet-staking/runtime-benchmarks",
|
||||
"remote-externalities/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,160 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Utilities for remote-testing pezpallet-bags-list.
|
||||
|
||||
use pezframe_election_provider_support::ScoreProvider;
|
||||
use pezpallet_bags_list::Instance1;
|
||||
|
||||
/// A common log target to use.
|
||||
pub const LOG_TARGET: &str = "runtime::bags-list::remote-tests";
|
||||
|
||||
pub mod migration;
|
||||
pub mod snapshot;
|
||||
pub mod try_state;
|
||||
|
||||
/// A wrapper for a runtime that the functions of this crate expect.
|
||||
///
|
||||
/// For example, this can be the `Runtime` type of the Pezkuwi runtime.
|
||||
pub trait RuntimeT<I: 'static>:
|
||||
pezpallet_staking::Config + pezpallet_bags_list::Config<I> + pezframe_system::Config
|
||||
{
|
||||
}
|
||||
impl<
|
||||
I: 'static,
|
||||
T: pezpallet_staking::Config + pezpallet_bags_list::Config<I> + pezframe_system::Config,
|
||||
> RuntimeT<I> for T
|
||||
{
|
||||
}
|
||||
|
||||
fn percent(portion: u32, total: u32) -> f64 {
|
||||
(portion as f64 / total as f64) * 100f64
|
||||
}
|
||||
|
||||
/// Display the number of nodes in each bag, while identifying those that need a rebag.
|
||||
pub fn display_and_check_bags<Runtime: RuntimeT<Instance1>>(
|
||||
currency_unit: u64,
|
||||
currency_name: &'static str,
|
||||
) {
|
||||
use pezframe_election_provider_support::SortedListProvider;
|
||||
use pezframe_support::traits::Get;
|
||||
|
||||
let min_nominator_bond = <pezpallet_staking::MinNominatorBond<Runtime>>::get();
|
||||
log::info!(target: LOG_TARGET, "min nominator bond is {:?}", min_nominator_bond);
|
||||
|
||||
let voter_list_count = <Runtime as pezpallet_staking::Config>::VoterList::count();
|
||||
|
||||
// go through every bag to track the total number of voters within bags and log some info about
|
||||
// how voters are distributed within the bags.
|
||||
let mut seen_in_bags = 0;
|
||||
let mut rebaggable = 0;
|
||||
let mut active_bags = 0;
|
||||
for vote_weight_thresh in <Runtime as pezpallet_bags_list::Config<Instance1>>::BagThresholds::get()
|
||||
{
|
||||
let vote_weight_thresh_u64: u64 = (*vote_weight_thresh)
|
||||
.try_into()
|
||||
.map_err(|_| "runtime must configure score to at most u64 to use this test")
|
||||
.unwrap();
|
||||
// threshold in terms of UNITS (e.g. KSM, HEZ etc)
|
||||
let vote_weight_thresh_as_unit = vote_weight_thresh_u64 as f64 / currency_unit as f64;
|
||||
let pretty_thresh = format!("Threshold: {}. {}", vote_weight_thresh_as_unit, currency_name);
|
||||
|
||||
let bag = match pezpallet_bags_list::Pallet::<Runtime, Instance1>::list_bags_get(
|
||||
*vote_weight_thresh,
|
||||
) {
|
||||
Some(bag) => bag,
|
||||
None => {
|
||||
log::info!(target: LOG_TARGET, "{} NO VOTERS.", pretty_thresh);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
active_bags += 1;
|
||||
|
||||
for id in bag.std_iter().map(|node| node.std_id().clone()) {
|
||||
let vote_weight =
|
||||
<Runtime as pezpallet_bags_list::Config<Instance1>>::ScoreProvider::score(&id)
|
||||
.unwrap();
|
||||
let vote_weight_thresh_u64: u64 = (*vote_weight_thresh)
|
||||
.try_into()
|
||||
.map_err(|_| "runtime must configure score to at most u64 to use this test")
|
||||
.unwrap();
|
||||
let vote_weight_as_balance: pezpallet_staking::BalanceOf<Runtime> =
|
||||
vote_weight_thresh_u64.try_into().map_err(|_| "can't convert").unwrap();
|
||||
|
||||
if vote_weight_as_balance < min_nominator_bond {
|
||||
log::trace!(
|
||||
target: LOG_TARGET,
|
||||
"⚠️ {} Account found below min bond: {:?}.",
|
||||
pretty_thresh,
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
let node = pezpallet_bags_list::Node::<Runtime, Instance1>::get(&id)
|
||||
.expect("node in bag must exist.");
|
||||
if node.is_misplaced(vote_weight) {
|
||||
rebaggable += 1;
|
||||
let notional_bag = pezpallet_bags_list::notional_bag_for::<Runtime, _>(vote_weight);
|
||||
let notional_bag_as_u64: u64 = notional_bag
|
||||
.try_into()
|
||||
.map_err(|_| "runtime must configure score to at most u64 to use this test")
|
||||
.unwrap();
|
||||
log::trace!(
|
||||
target: LOG_TARGET,
|
||||
"Account {:?} can be rebagged from {:?} to {:?}",
|
||||
id,
|
||||
vote_weight_thresh_as_unit,
|
||||
notional_bag_as_u64 as f64 / currency_unit as f64
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// update our overall counter
|
||||
let voters_in_bag = bag.std_iter().count() as u32;
|
||||
seen_in_bags += voters_in_bag;
|
||||
|
||||
// percentage of all nominators
|
||||
let percent_of_voters = percent(voters_in_bag, voter_list_count);
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"{} Nominators: {} [%{:.3}]",
|
||||
pretty_thresh,
|
||||
voters_in_bag,
|
||||
percent_of_voters,
|
||||
);
|
||||
}
|
||||
|
||||
if seen_in_bags != voter_list_count {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"bags list population ({}) not on par whoever is voter_list ({})",
|
||||
seen_in_bags,
|
||||
voter_list_count,
|
||||
)
|
||||
}
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"a total of {} nodes are in {} active bags [{} total bags], {} of which can be rebagged.",
|
||||
voter_list_count,
|
||||
active_bags,
|
||||
<Runtime as pezpallet_bags_list::Config<Instance1>>::BagThresholds::get().len(),
|
||||
rebaggable,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Bizinikiwi.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! Test to check the migration of the voter bag.
|
||||
|
||||
use crate::{RuntimeT, LOG_TARGET};
|
||||
use pezframe_support::traits::PalletInfoAccess;
|
||||
use pezpallet_staking::Nominators;
|
||||
use remote_externalities::{Builder, Mode, OnlineConfig};
|
||||
use pezsp_runtime::{traits::Block as BlockT, DeserializeOwned};
|
||||
|
||||
/// Test voter bags migration. `currency_unit` is the number of planks per the the runtimes `UNITS`
|
||||
/// (i.e. number of decimal places per HEZ, KSM etc)
|
||||
pub async fn execute<Runtime, Block>(
|
||||
currency_unit: u64,
|
||||
currency_name: &'static str,
|
||||
ws_url: String,
|
||||
) where
|
||||
Runtime: RuntimeT<pezpallet_bags_list::Instance1>,
|
||||
Block: BlockT + DeserializeOwned,
|
||||
Block::Header: DeserializeOwned,
|
||||
{
|
||||
let mut ext = Builder::<Block>::new()
|
||||
.mode(Mode::Online(OnlineConfig {
|
||||
transport: ws_url.to_string().into(),
|
||||
pallets: vec![pezpallet_staking::Pallet::<Runtime>::name().to_string()],
|
||||
..Default::default()
|
||||
}))
|
||||
.build()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
ext.execute_with(|| {
|
||||
// get the nominator & validator count prior to migrating; these should be invariant.
|
||||
let pre_migrate_nominator_count = <Nominators<Runtime>>::iter().count() as u32;
|
||||
log::info!(target: LOG_TARGET, "Nominator count: {}", pre_migrate_nominator_count);
|
||||
|
||||
use pezframe_election_provider_support::SortedListProvider;
|
||||
// run the actual migration
|
||||
let moved = <Runtime as pezpallet_staking::Config>::VoterList::unsafe_regenerate(
|
||||
pezpallet_staking::Nominators::<Runtime>::iter().map(|(n, _)| n),
|
||||
Box::new(|x| Some(pezpallet_staking::Pallet::<Runtime>::weight_of(x))),
|
||||
);
|
||||
log::info!(target: LOG_TARGET, "Moved {} nominators", moved);
|
||||
|
||||
let voter_list_len = <Runtime as pezpallet_staking::Config>::VoterList::iter().count() as u32;
|
||||
let voter_list_count = <Runtime as pezpallet_staking::Config>::VoterList::count();
|
||||
// and confirm it is equal to the length of the `VoterList`.
|
||||
assert_eq!(pre_migrate_nominator_count, voter_list_len);
|
||||
assert_eq!(pre_migrate_nominator_count, voter_list_count);
|
||||
|
||||
crate::display_and_check_bags::<Runtime>(currency_unit, currency_name);
|
||||
});
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user