Skip to content

Commit

Permalink
pallet mosaic (#547)
Browse files Browse the repository at this point in the history
* initial commit

* save

* add a couple of tests

* add events everywhere

* Fix updating of penalty

* Add test for linear_decay

* remove unused variables

* add rescind_timelocked_mit and set_timelock_duration

* add tests for transfer_to and accept_transfer

* more tests and a fix

* enable todo in do_transfer_to

* avoid burning the full balance

* introduce tuple in scope instead of indexing

* fixup

* Split account into sub_accounts

* Fix do_transfer_to test by checking if the correct Event was emitted

* Fix timelocked_mint tests

* Fix issue where any user could remove IncomingTransactions belonging to any other account

* More mosaic docs

* introduce `TransactionType` and `SubAccount` to split incoming/ougoing accounts

Users were having their incoming/outgoing funds merged together into a single
sub_account within the pallet. Which would allow a bug to impact all the funds.
By splitting them, they are isolated.

* use tuple deconstruction instead of indexing

* format tests

* use `AssetId` instead of `CurrencyId` for consistency in tests

* implements more tests & fix some types used by mocking (accountId too small)

* Group transfer_to tests

* Add more test todos

* set_relayer tests

* rotate_relayer tests

* set_network tests

* more budget tests

* more mosaic tests

* mosaic timelocked_mint tests

* more mosaic timelocked_mint tests

* add a couple of tests

* Fix updating of penalty

* add tests for transfer_to and accept_transfer

* more tests and a fix

* Split account into sub_accounts

* Fix timelocked_mint tests

* More mosaic docs

* introduce `TransactionType` and `SubAccount` to split incoming/ougoing accounts

Users were having their incoming/outgoing funds merged together into a single
sub_account within the pallet. Which would allow a bug to impact all the funds.
By splitting them, they are isolated.

* format tests

* use `AssetId` instead of `CurrencyId` for consistency in tests

* implements more tests & fix some types used by mocking (accountId too small)

* Group transfer_to tests

* Add more test todos

* more mosaic tests

* mosaic timelocked_mint tests

* mosaic test claim_to

* convert first mosaic test to proptest

* proptest with unique AccountIds

* add prop_assert_err and prop_assert_noop

* use prop_assert_err and prop_assert_noop in mosaic tests

* rename decay types

* test decay of budget penalties for mints

* cargo fmt

* cargo fmt nightly

* forbid impossible state by avoiding default instance for RelayerConfig

We were using Default + ValueQuery for RelayerConfig.
It was fine because RelayerConfig was containing an `Option<AccountId>`
but this would allow inconsistent states such as None for RelayerConfig.current
& None for RelayerConfig.next.
By removing the option within RelayerConfig and using an OptionQuery for the
storage,
we it's more clear that a Relayer can be Set/Unset. And if set, an AcccountId
must be present.

* better naming

* fmt

* update test-helpers edition

* remove rust-version from mosaic

* refactor for consistency

* remove unused newline

* better naming

* more comments on decayer

* reflect the partial/complete nature of a transfer via the event

* rebase on main

* fiix difffd

* bump spec version for simnode

* fmt

Co-authored-by: kaiserkarel <k.l.kubat@gmail.com>
Co-authored-by: Hussein Ait Lahcen <hussein.aitlahcen@gmail.com>
Co-authored-by: Seun Lanlege <seunlanlege@gmail.com>
  • Loading branch information
4 people authored Jan 28, 2022
1 parent 089aeba commit bf8adbf
Show file tree
Hide file tree
Showing 17 changed files with 2,625 additions and 10 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ rococo-local-raw.json
rococo-local.json
scripts/polkadot-launch/bin/polkadot
scripts/polkadot-launch/*.log

.rust-analyzer
target
.env

Expand Down
8 changes: 4 additions & 4 deletions .maintain/common/lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ has_client_changes() {
# checks if the spec/impl version has increased
check_runtime() {
VERSIONS_FILE="$1"
add_spec_version="$(git diff "${LATEST_TAG_NAME}" "${GITHUB_BRANCH_NAME}" -- "${VERSIONS_FILE}" |
add_spec_version="$(git diff "${LATEST_TAG_NAME}" "origin/${GITHUB_BRANCH_NAME}" -- "${VERSIONS_FILE}" |
sed -n -r "s/^\+[[:space:]]+spec_version: +([0-9]+),$/\1/p")"
sub_spec_version="$(git diff "${LATEST_TAG_NAME}" "${GITHUB_BRANCH_NAME}" -- "${VERSIONS_FILE}" |
sub_spec_version="$(git diff "${LATEST_TAG_NAME}" "origin/${GITHUB_BRANCH_NAME}" -- "${VERSIONS_FILE}" |
sed -n -r "s/^\-[[:space:]]+spec_version: +([0-9]+),$/\1/p")"
if [ "${add_spec_version}" != "${sub_spec_version}" ]; then

Expand All @@ -70,9 +70,9 @@ check_runtime() {
# check for impl_version updates: if only the impl versions changed, we assume
# there is no consensus-critical logic that has changed.

add_impl_version="$(git diff "${LATEST_TAG_NAME}" "${GITHUB_BRANCH_NAME}" -- "${VERSIONS_FILE}" |
add_impl_version="$(git diff "${LATEST_TAG_NAME}" "origin/${GITHUB_BRANCH_NAME}" -- "${VERSIONS_FILE}" |
sed -n -r 's/^\+[[:space:]]+impl_version: +([0-9]+),$/\1/p')"
sub_impl_version="$(git diff "${LATEST_TAG_NAME}" "${GITHUB_BRANCH_NAME}" -- "${VERSIONS_FILE}" |
sub_impl_version="$(git diff "${LATEST_TAG_NAME}" "origin/${GITHUB_BRANCH_NAME}" -- "${VERSIONS_FILE}" |
sed -n -r 's/^\-[[:space:]]+impl_version: +([0-9]+),$/\1/p')"

# see if the impl version changed
Expand Down
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion frame/composable-tests-helpers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "composable-tests-helpers"
version = "0.0.1"
authors = ["Composable Developers"]
homepage = "https://composable.finance"
edition = "2018"
edition = "2021"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
Expand All @@ -14,6 +14,8 @@ serde = { version = "1", optional = true }
sp-arithmetic = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }
sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }
sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }
frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }


[dependencies.codec]
default-features = false
Expand All @@ -28,4 +30,5 @@ std = [
"codec/std",
"sp-runtime/std",
"scale-info/std",
"frame-support/std",
]
34 changes: 33 additions & 1 deletion frame/composable-tests-helpers/src/test/proptest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#[macro_export]
macro_rules! prop_assert_ok {
($cond:expr) => {
prop_assert_ok!($cond, concat!("assertion failed: ", stringify!($cond)))
prop_assert_ok!($cond, concat!("ok assertion failed: ", stringify!($cond)))
};

($cond:expr, $($fmt:tt)*) => {
Expand All @@ -15,6 +15,38 @@ macro_rules! prop_assert_ok {
};
}

#[macro_export]
macro_rules! prop_assert_err {
($cond:expr, $err:expr) => {
composable_tests_helpers::prop_assert_err!($cond, $err, concat!("error assertion failed: ", stringify!($cond)))
};

($cond:expr, $err:expr, $($fmt:tt)*) => {

match $cond {
Result::Err(e) if e == $err.into() => {}
v => {
let message = format!($($fmt)*);
let message = format!("Expected {:?}, got {:?}, {} at {}:{}", $err, v, message, file!(), line!());
return ::std::result::Result::Err(
proptest::test_runner::TestCaseError::fail(message));
}
}
};
}

#[macro_export]
macro_rules! prop_assert_noop {
(
$x:expr,
$y:expr $(,)?
) => {
let h = frame_support::storage_root();
composable_tests_helpers::prop_assert_err!($x, $y);
proptest::prop_assert_eq!(h, frame_support::storage_root());
};
}

/// Accept a `dust` deviation.
#[macro_export]
macro_rules! prop_assert_acceptable_computation_error {
Expand Down
72 changes: 72 additions & 0 deletions frame/mosaic/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
[package]
name = "pallet-mosaic"
version = "0.1.0"
authors = ["Composable Developers"]
homepage = "https://composable.finance"
edition = "2021"

[[bin]]
name = "plot"
path = "src/plots.rs"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[package.metadata.cargo-udeps.ignore]
development = ["pallet-balances"]

# alias "parity-scale-code" to "codec"
[dependencies.codec]
default-features = false
features = ["derive"]
package = "parity-scale-codec"
version = "2.0.0"

[dependencies]
composable-traits = { path = "../composable-traits", default-features = false }
frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.13" }
frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }
frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }

sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }
sp-io = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }
sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }
sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }
xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.13", default-features = false }

log = { version = "0.4.14", default-features = false }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
num-traits = "0.2.14"
plotters = {version = "0.3.1", optional = true}

[dev-dependencies]
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }
proptest = "0.9.6"
orml-tokens = { git = "https://github.com/open-web3-stack/open-runtime-module-library", rev = "17a791edf431d7d7aee1ea3dfaeeb7bc21944301" }
orml-traits = { git = "https://github.com/open-web3-stack/open-runtime-module-library", rev = "17a791edf431d7d7aee1ea3dfaeeb7bc21944301", default-features = false }
composable-tests-helpers = { version = "0.0.1", path = "../composable-tests-helpers", default-features = false }


[features]
default = ["std"]
std = [
"codec/std",
"log/std",
"composable-traits/std",
"scale-info/std",
"frame-support/std",
"frame-system/std",
"sp-runtime/std",
"sp-io/std",
"sp-core/std",
"sp-std/std",
"xcm/std",
]

runtime-benchmarks = [
'frame-benchmarking',
'frame-support/runtime-benchmarks',
'frame-system/runtime-benchmarks',
]

visualization = ["plotters"]
10 changes: 10 additions & 0 deletions frame/mosaic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Mosaic

Pallet implementing an interface for the Mosaic Relayer. As opposed to the EVM-EVM bridge, this pallet takes a different approach and uses `mint` and `burn` operations.
Because of that it also limits the mintable amount by the relayer using a decaying penalty.

## Decaying Penalty

At moment N, the relayer has a maximum budget `budget`. Minting a token adds a penalty `penalty` to the relayer. The penalty decreases each block according to decay function `decayer`,
which depends on the penalty, current_block, and last_decay_block. The current maximum amount that the relayer can mint is given by `budget - decayer(penalty, current_block, last_decay_block)`.
The new penalty is the decayed previous penalty plus the minted amount.
8 changes: 8 additions & 0 deletions frame/mosaic/proptest-regressions/tests.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 622d5f60d70240f7b19156918e818b27925f88f0440f60e4e5975def08a99b84 # shrinks to amount = 10000
cc 1e4de59b29da912d7745cbfdc8d9893eb2d492cb817275ae19c38e6387c46a35 # shrinks to amount = 1, account_a = 1, lock_time = 1, early = 3
103 changes: 103 additions & 0 deletions frame/mosaic/src/decay.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
use frame_support::pallet_prelude::*;
use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, One, Saturating, Zero};

pub trait Decayer<Balance, BlockNumber> {
/// Decay the `amount` proportionally to the time elapsed `current_block - last_decay_block`
/// Returns `None` if an input value is invalid
fn checked_decay(
&self,
amount: Balance,
current_block: BlockNumber,
last_decay_block: BlockNumber,
) -> Option<Balance>;

/// Determine how many blocks are required to pass until the `amount` fully recover from this
/// decayer. Returns `None` if the recovery period cannot be computed.
fn full_recovery_period(&self, amount: Balance) -> Option<BlockNumber>;
}

/// Recommend type for storing the decay function of a penalty.
#[derive(Decode, Encode, TypeInfo, Debug, PartialEq, Clone)]
pub enum BudgetPenaltyDecayer<Balance, BlockNumber> {
/// Linear variant of the decay function, which decreases every block.
Linear(LinearDecay<Balance, BlockNumber>),
}

impl<Balance, BlockNumber> BudgetPenaltyDecayer<Balance, BlockNumber> {
#[allow(dead_code)]
pub fn linear(n: Balance) -> BudgetPenaltyDecayer<Balance, BlockNumber> {
BudgetPenaltyDecayer::Linear(LinearDecay { factor: n, _marker: PhantomData })
}
}

impl<Balance, BlockNumber> Decayer<Balance, BlockNumber>
for BudgetPenaltyDecayer<Balance, BlockNumber>
where
BlockNumber: CheckedSub + Saturating + Into<Balance> + TryFrom<Balance> + One + CheckedAdd,
Balance: CheckedMul + CheckedDiv + Saturating + Zero,
{
fn checked_decay(
&self,
amount: Balance,
current: BlockNumber,
last: BlockNumber,
) -> Option<Balance> {
match self {
BudgetPenaltyDecayer::Linear(lin) => lin.checked_decay(amount, current, last),
}
}

fn full_recovery_period(&self, amount: Balance) -> Option<BlockNumber> {
match self {
BudgetPenaltyDecayer::Linear(lin) => lin.full_recovery_period(amount),
}
}
}

#[derive(Decode, Encode, TypeInfo, Default, Debug, PartialEq, Clone)]
pub struct LinearDecay<Balance, BlockNumber> {
/// Factor by which we decay every block.
factor: Balance,
_marker: core::marker::PhantomData<BlockNumber>,
}

impl<Balance, BlockNumber> Decayer<Balance, BlockNumber> for LinearDecay<Balance, BlockNumber>
where
BlockNumber: CheckedSub + Saturating + Into<Balance> + TryFrom<Balance> + One + CheckedAdd,
Balance: CheckedMul + CheckedDiv + Saturating + Zero,
{
fn checked_decay(
&self,
amount: Balance,
current: BlockNumber,
last: BlockNumber,
) -> Option<Balance> {
let diff = current.saturating_sub(last);
let reduction = diff.into().checked_mul(&self.factor)?;
Some(amount.saturating_sub(reduction))
}

fn full_recovery_period(&self, amount: Balance) -> Option<BlockNumber> {
let full_period = amount.checked_div(&self.factor)?;
let block_full_period: BlockNumber = TryFrom::<Balance>::try_from(full_period).ok()?;
let block_full_period_plus_one: BlockNumber = block_full_period.checked_add(&One::one())?;
Some(block_full_period_plus_one)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_linear_decrease() {
let mut penalty = 1000;
let prev = penalty.clone();
let penalty_decayer = BudgetPenaltyDecayer::linear(10);

(0..=100).for_each(|x| {
penalty = penalty_decayer.checked_decay(penalty, x, x - 1).unwrap();
assert!(prev > penalty);
});
}
}
Loading

0 comments on commit bf8adbf

Please sign in to comment.