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:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
+139
View File
@@ -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",
]
+12
View File
@@ -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
+79
View File
@@ -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",
]
+66
View File
@@ -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![]);
});
}
}
+420
View File
@@ -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)
}
+652
View File
@@ -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);
}
+153
View File
@@ -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()
}
}
+932
View File
@@ -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())))
}
}
+58
View File
@@ -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 }
}
+281
View File
@@ -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()
}
+178
View File
@@ -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(),
);
});
}
}
+63
View File
@@ -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",
]
+124
View File
@@ -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
+162
View File
@@ -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(())
}
}
}
+245
View File
@@ -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));
});
}
+361
View File
@@ -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",
]
+23
View File
@@ -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
+362
View File
@@ -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);
});
}
+61
View File
@@ -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",
]
+29
View File
@@ -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
+408
View File
@@ -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)
}
+106
View File
@@ -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")
});
}
+119
View File
@@ -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."
);
});
}
}
+51
View File
@@ -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",
]
+5
View File
@@ -0,0 +1,5 @@
Authorship tracking for FRAME runtimes.
This tracks the current author of the block and recent uncles.
License: Apache-2.0
+177
View File
@@ -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));
});
}
}
+98
View File
@@ -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",
]
+4
View File
@@ -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
+415
View File
@@ -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(&current_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,
}
}
+173
View File
@@ -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
+101
View File
@@ -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)
}
}
+92
View File
@@ -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