diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml
index ca44b36e6..7cca2b1fb 100644
--- a/.github/workflows/pre-release.yml
+++ b/.github/workflows/pre-release.yml
@@ -20,7 +20,7 @@ jobs:
id: commit_info
run: |
echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- echo "commit_message=$(git log -1 --pretty=%B)" >> $GITHUB_OUTPUT
+ echo "commit_message='$(git log -1 --pretty=%B)'" >> $GITHUB_OUTPUT
# Generating a GitHub token, so that tags created by the action
# triggers the other workflows
diff --git a/Cargo.lock b/Cargo.lock
index 14fffc772..bf3fa2ca5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4952,7 +4952,7 @@ dependencies = [
[[package]]
name = "frost-ed448"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"ed448-goldilocks-plus",
"parity-scale-codec",
@@ -4979,7 +4979,7 @@ dependencies = [
[[package]]
name = "frost-p384"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"p384",
"parity-scale-codec",
@@ -5033,7 +5033,7 @@ dependencies = [
[[package]]
name = "frost-secp256k1-tr"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"k256",
"parity-scale-codec",
@@ -8469,7 +8469,7 @@ dependencies = [
[[package]]
name = "pallet-airdrop-claims"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"frame-benchmarking",
"frame-support",
@@ -9220,6 +9220,7 @@ dependencies = [
"scale-info",
"serde",
"sha3",
+ "sp-arithmetic",
"sp-core",
"sp-io",
"sp-runtime",
@@ -9541,7 +9542,7 @@ dependencies = [
[[package]]
name = "pallet-multi-asset-delegation"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"ethabi",
"ethereum",
@@ -9711,7 +9712,7 @@ dependencies = [
[[package]]
name = "pallet-rewards"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"ethabi",
"ethereum",
@@ -9769,7 +9770,7 @@ dependencies = [
[[package]]
name = "pallet-rewards-rpc"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"jsonrpsee 0.23.2",
"pallet-rewards-rpc-runtime-api",
@@ -9782,7 +9783,7 @@ dependencies = [
[[package]]
name = "pallet-rewards-rpc-runtime-api"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"parity-scale-codec",
"sp-api",
@@ -9810,7 +9811,7 @@ dependencies = [
[[package]]
name = "pallet-services"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"ethabi",
"ethereum",
@@ -9867,7 +9868,7 @@ dependencies = [
[[package]]
name = "pallet-services-rpc"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"jsonrpsee 0.23.2",
"pallet-services-rpc-runtime-api",
@@ -9880,7 +9881,7 @@ dependencies = [
[[package]]
name = "pallet-services-rpc-runtime-api"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"parity-scale-codec",
"sp-api",
@@ -15817,7 +15818,7 @@ dependencies = [
[[package]]
name = "tangle"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"alloy",
"anyhow",
@@ -15913,7 +15914,7 @@ dependencies = [
[[package]]
name = "tangle-crypto-primitives"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"parity-scale-codec",
"scale-info",
@@ -15922,7 +15923,7 @@ dependencies = [
[[package]]
name = "tangle-primitives"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"ark-bn254",
"ark-crypto-primitives",
@@ -15953,7 +15954,7 @@ dependencies = [
[[package]]
name = "tangle-runtime"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"evm-tracer",
"fp-evm",
@@ -16080,7 +16081,7 @@ dependencies = [
[[package]]
name = "tangle-testnet-runtime"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"evm-tracer",
"fixed",
@@ -16267,7 +16268,7 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76"
[[package]]
name = "tg-frost-core"
-version = "1.2.5"
+version = "1.2.7"
dependencies = [
"byteorder",
"const-crc32-nostd",
diff --git a/Cargo.toml b/Cargo.toml
index 6e5df1c18..7cbe4fef1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
[workspace.package]
-version = "1.2.5"
+version = "1.2.7"
authors = ["Tangle Foundation."]
edition = "2021"
license = "Unlicense"
@@ -46,6 +46,8 @@ members = [
"precompiles/services",
"precompiles/tangle-lst",
"precompiles/assets",
+ "precompiles/vesting",
+ "precompiles/staking",
"tangle-subxt",
"evm-tracer",
]
diff --git a/client/evm-tracing/src/formatters/blockscout.rs b/client/evm-tracing/src/formatters/blockscout.rs
index 61b8033e2..214e47da5 100644
--- a/client/evm-tracing/src/formatters/blockscout.rs
+++ b/client/evm-tracing/src/formatters/blockscout.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
use crate::{
listeners::call_list::Listener,
diff --git a/client/evm-tracing/src/formatters/call_tracer.rs b/client/evm-tracing/src/formatters/call_tracer.rs
index 6b2d7ae4a..66e577343 100644
--- a/client/evm-tracing/src/formatters/call_tracer.rs
+++ b/client/evm-tracing/src/formatters/call_tracer.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
#[allow(clippy::all)]
use super::blockscout::BlockscoutCallInner;
use crate::types::{
diff --git a/client/evm-tracing/src/formatters/mod.rs b/client/evm-tracing/src/formatters/mod.rs
index 5af0bfb63..8237a0fc9 100644
--- a/client/evm-tracing/src/formatters/mod.rs
+++ b/client/evm-tracing/src/formatters/mod.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
pub mod blockscout;
pub mod call_tracer;
pub mod raw;
diff --git a/client/evm-tracing/src/formatters/raw.rs b/client/evm-tracing/src/formatters/raw.rs
index 30e352427..adf00c617 100644
--- a/client/evm-tracing/src/formatters/raw.rs
+++ b/client/evm-tracing/src/formatters/raw.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
use crate::{listeners::raw::Listener, types::single::TransactionTrace};
diff --git a/client/evm-tracing/src/formatters/trace_filter.rs b/client/evm-tracing/src/formatters/trace_filter.rs
index 9bb032107..4111ab1ea 100644
--- a/client/evm-tracing/src/formatters/trace_filter.rs
+++ b/client/evm-tracing/src/formatters/trace_filter.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
use super::blockscout::BlockscoutCallInner as CallInner;
use crate::{
diff --git a/client/evm-tracing/src/lib.rs b/client/evm-tracing/src/lib.rs
index 12a21b36c..ef97dcd60 100644
--- a/client/evm-tracing/src/lib.rs
+++ b/client/evm-tracing/src/lib.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
//! This crate contains the client-side part that interacts with our "v2" tracing design.
#![allow(clippy::all)]
diff --git a/client/evm-tracing/src/listeners/call_list.rs b/client/evm-tracing/src/listeners/call_list.rs
index 9da8a66a0..d0561e409 100644
--- a/client/evm-tracing/src/listeners/call_list.rs
+++ b/client/evm-tracing/src/listeners/call_list.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
use crate::{
formatters::blockscout::{BlockscoutCall as Call, BlockscoutCallInner as CallInner},
diff --git a/client/evm-tracing/src/listeners/mod.rs b/client/evm-tracing/src/listeners/mod.rs
index 5049d5f58..245e06580 100644
--- a/client/evm-tracing/src/listeners/mod.rs
+++ b/client/evm-tracing/src/listeners/mod.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
pub mod call_list;
pub mod raw;
diff --git a/client/evm-tracing/src/listeners/raw.rs b/client/evm-tracing/src/listeners/raw.rs
index 483ea0a3d..3a1b7408a 100644
--- a/client/evm-tracing/src/listeners/raw.rs
+++ b/client/evm-tracing/src/listeners/raw.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
use ethereum_types::{H160, H256};
use std::{collections::btree_map::BTreeMap, vec, vec::Vec};
diff --git a/client/evm-tracing/src/types/block.rs b/client/evm-tracing/src/types/block.rs
index e00b5e15a..b75608715 100644
--- a/client/evm-tracing/src/types/block.rs
+++ b/client/evm-tracing/src/types/block.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
//! Types for tracing all Ethereum transactions of a block.
diff --git a/client/evm-tracing/src/types/mod.rs b/client/evm-tracing/src/types/mod.rs
index b2989b9d5..2854ecdb3 100644
--- a/client/evm-tracing/src/types/mod.rs
+++ b/client/evm-tracing/src/types/mod.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
//! Runtime API allowing to debug/trace Ethereum
#![allow(clippy::all)]
diff --git a/client/evm-tracing/src/types/serialization.rs b/client/evm-tracing/src/types/serialization.rs
index f786ffafd..0dcc8e896 100644
--- a/client/evm-tracing/src/types/serialization.rs
+++ b/client/evm-tracing/src/types/serialization.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
//! Provide serialization functions for various types and formats.
diff --git a/client/evm-tracing/src/types/single.rs b/client/evm-tracing/src/types/single.rs
index 54a8b9d7d..60c623c6d 100644
--- a/client/evm-tracing/src/types/single.rs
+++ b/client/evm-tracing/src/types/single.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
//! Types for the tracing of a single Ethereum transaction.
//! Structure from "raw" debug_trace and a "call list" matching
@@ -68,7 +69,6 @@ pub struct RawStepLog {
#[serde(serialize_with = "u256_serialize")]
pub depth: U256,
- //error: TODO
#[serde(serialize_with = "u256_serialize")]
pub gas: U256,
diff --git a/client/rpc-core/debug/src/lib.rs b/client/rpc-core/debug/src/lib.rs
index a2be4a438..c2e2772fa 100644
--- a/client/rpc-core/debug/src/lib.rs
+++ b/client/rpc-core/debug/src/lib.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
use client_evm_tracing::types::single;
use ethereum::AccessListItem;
diff --git a/client/rpc-core/trace/src/lib.rs b/client/rpc-core/trace/src/lib.rs
index ea770c45b..7224fe26c 100644
--- a/client/rpc-core/trace/src/lib.rs
+++ b/client/rpc-core/trace/src/lib.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
use client_evm_tracing::types::block::TransactionTrace;
use ethereum_types::H160;
diff --git a/client/rpc-core/txpool/src/lib.rs b/client/rpc-core/txpool/src/lib.rs
index 87d6dbe82..40ddb733c 100644
--- a/client/rpc-core/txpool/src/lib.rs
+++ b/client/rpc-core/txpool/src/lib.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
use ethereum_types::U256;
use jsonrpsee::{core::RpcResult, proc_macros::rpc};
diff --git a/client/rpc-core/txpool/src/types/content.rs b/client/rpc-core/txpool/src/types/content.rs
index ec98a00f4..920ad83d5 100644
--- a/client/rpc-core/txpool/src/types/content.rs
+++ b/client/rpc-core/txpool/src/types/content.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
use crate::GetT;
use ethereum::{TransactionAction, TransactionV2 as EthereumTransaction};
@@ -37,7 +38,7 @@ pub struct Transaction {
/// Recipient
#[serde(serialize_with = "to_serialize")]
pub to: Option,
- /// Transfered value
+ /// Transferred value
pub value: U256,
/// Gas Price
pub gas_price: U256,
diff --git a/client/rpc-core/txpool/src/types/inspect.rs b/client/rpc-core/txpool/src/types/inspect.rs
index 95e7240bc..53b94253a 100644
--- a/client/rpc-core/txpool/src/types/inspect.rs
+++ b/client/rpc-core/txpool/src/types/inspect.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
use crate::GetT;
use ethereum::{TransactionAction, TransactionV2 as EthereumTransaction};
diff --git a/client/rpc-core/txpool/src/types/mod.rs b/client/rpc-core/txpool/src/types/mod.rs
index 67c71b087..cf2f233e1 100644
--- a/client/rpc-core/txpool/src/types/mod.rs
+++ b/client/rpc-core/txpool/src/types/mod.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
mod content;
mod inspect;
diff --git a/client/rpc-core/types/src/lib.rs b/client/rpc-core/types/src/lib.rs
index ae796f054..0c8800502 100644
--- a/client/rpc-core/types/src/lib.rs
+++ b/client/rpc-core/types/src/lib.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc..
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
use ethereum_types::H256;
use serde::{de::Error, Deserialize, Deserializer};
diff --git a/client/rpc/debug/README.md b/client/rpc/debug/README.md
index d46462f26..bcf905146 100644
--- a/client/rpc/debug/README.md
+++ b/client/rpc/debug/README.md
@@ -1,19 +1,17 @@
A port crate of some of the tracing related rpc requests from the go-ethereum [debug namespace](https://geth.ethereum.org/docs/interacting-with-geth/rpc/ns-debug). Includes `debug_traceTransaction`, `debug_traceBlockByNumber` and `debug_traceBlockByHash`.
-## How tracing works in Moonbeam
+## How tracing works in Tangle
Runtime wasms compiled with the `tracing` evm feature will emit events related to entering/exiting substates or opcode execution. This events are used by developers or indexer services to get a granular view on an evm transaction.
-Tracing wasms for each moonbeam/river/base runtime versions live at `moonbeam-runtime-overrides` repository in github.
+Tracing functionality in tangle makes heavy use of [environmental](https://docs.rs/environmental/latest/environmental/):
-Tracing functionality in Moonbeam makes heavy use of [environmental](https://docs.rs/environmental/latest/environmental/):
-
-- The rpc request must create a runtime api instance to replay the transaction. The runtime api call is made `using` `environmental`.
-- Once in the wasm, the target evm transaction is replayed by calling the evm also `using` `environmental`.
-- This allows:
- 1. Listen to new events from the evm in the moonbeam runtime wasm.
- 2. Proxy those events to the client (through a host function), which is also listening for events from the runtime.
-- This way we don't make use of (limited) wasm memory, and instead store the evm emitted events content in the client.
+- The rpc request must create a runtime api instance to replay the transaction. The runtime api call is made `using` `environmental`.
+- Once in the wasm, the target evm transaction is replayed by calling the evm also `using` `environmental`.
+- This allows:
+ 1. Listen to new events from the evm in the tangle runtime wasm.
+ 2. Proxy those events to the client (through a host function), which is also listening for events from the runtime.
+- This way we don't make use of (limited) wasm memory, and instead store the evm emitted events content in the client.
Once the evm execution concludes, the runtime context exited and all events have been stored in the client memory, we support formatting the captured events in different ways that are convenient for the end-user, like raw format (opcode level tracing), callTracer (used as a default formatter by geth) or blockscout custom tracer.
@@ -63,8 +61,8 @@ sp_api::decl_runtime_apis! {
Substrate provides two macro attributes to do what we want: `api_version` and `changed_in`.
-- `api_version`: is the current version of the Api. In our case we updated it to `#[api_version(2)]`.
-- changed_in: is meant to describe for `decl_runtime_apis` macro past implementations of methods. In this case, we anotate our previous implementation with `#[changed_in(2)]`, telling the `decl_runtime_apis` macro that this is the implementation to use before version 2. In fact, this attribute will rename the method name for the trait in the client side to `METHOD_before_version_VERSION`, so `trace_transaction_before_version_2` in our example.
+- `api_version`: is the current version of the Api. In our case we updated it to `#[api_version(2)]`.
+- changed_in: is meant to describe for `decl_runtime_apis` macro past implementations of methods. In this case, we anotate our previous implementation with `#[changed_in(2)]`, telling the `decl_runtime_apis` macro that this is the implementation to use before version 2. In fact, this attribute will rename the method name for the trait in the client side to `METHOD_before_version_VERSION`, so `trace_transaction_before_version_2` in our example.
The un-anotated method is considered the default implemetation, and holds the current `trace_transaction` signature, with the new header argument and the empty result.
diff --git a/client/rpc/debug/src/lib.rs b/client/rpc/debug/src/lib.rs
index 09b2b932c..9805a6174 100644
--- a/client/rpc/debug/src/lib.rs
+++ b/client/rpc/debug/src/lib.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
use futures::StreamExt;
use jsonrpsee::core::{async_trait, RpcResult};
pub use rpc_core_debug::{DebugServer, TraceCallParams, TraceParams};
diff --git a/client/rpc/trace/src/lib.rs b/client/rpc/trace/src/lib.rs
index 72c564dfd..73ba8aacc 100644
--- a/client/rpc/trace/src/lib.rs
+++ b/client/rpc/trace/src/lib.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
//! `trace_filter` RPC handler and its associated service task.
//! The RPC handler rely on `CacheTask` which provides a future that must be run inside a tokio
@@ -327,7 +328,7 @@ impl CacheRequester {
/// Data stored for each block in the cache.
/// `active_batch_count` represents the number of batches using this
-/// block. It will increase immediatly when a batch is created, but will be
+/// block. It will increase immediately when a batch is created, but will be
/// decrease only after the batch ends and its expiration delay passes.
/// It allows to keep the data in the cache for following requests that would use
/// this block, which is important to handle pagination efficiently.
@@ -594,7 +595,7 @@ where
}
/// Handle a request to get the traces of the provided block.
- /// - If the result is stored in the cache, it sends it immediatly.
+ /// - If the result is stored in the cache, it sends it immediately.
/// - If the block is currently being pooled, it is added in this block cache waiting list, and
/// all requests concerning this block will be satisfied when the tracing for this block is
/// finished.
@@ -688,9 +689,6 @@ where
// In some cases it might be possible to receive traces of a block
// that has no entry in the cache because it was removed of the pool
// and received a permit concurrently. We just ignore it.
- //
- // TODO : Should we add it back ? Should it have an active_batch_count
- // of 1 then ?
if let Some(block_cache) = self.cached_blocks.get_mut(&block_hash) {
if let CacheBlockState::Pooled { ref mut waiting_requests, .. } = block_cache.state {
tracing::trace!(
@@ -720,7 +718,10 @@ where
// last batch containing it.
let mut remove = false;
if let Some(block_cache) = self.cached_blocks.get_mut(&block) {
- block_cache.active_batch_count -= 1;
+ block_cache.active_batch_count = block_cache
+ .active_batch_count
+ .checked_sub(1)
+ .expect("active_batch_count underflow");
if block_cache.active_batch_count == 0 {
remove = true;
diff --git a/client/rpc/txpool/src/lib.rs b/client/rpc/txpool/src/lib.rs
index 101050c5b..2bb48e3fc 100644
--- a/client/rpc/txpool/src/lib.rs
+++ b/client/rpc/txpool/src/lib.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
use ethereum_types::{H160, H256, U256};
use fc_rpc::{internal_err, public_key};
diff --git a/evm-tracer/src/lib.rs b/evm-tracer/src/lib.rs
index 6fa5766f7..b23b497b4 100644
--- a/evm-tracer/src/lib.rs
+++ b/evm-tracer/src/lib.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
//! Substrate EVM tracing.
//!
diff --git a/frost/frost-secp256k1-tr/src/lib.rs b/frost/frost-secp256k1-tr/src/lib.rs
index 0a82729be..34aa0d1c7 100644
--- a/frost/frost-secp256k1-tr/src/lib.rs
+++ b/frost/frost-secp256k1-tr/src/lib.rs
@@ -212,7 +212,6 @@ fn negate_nonce(nonce: &frost_core::round1::Nonce) -> frost_core::round1::Non
// Negate a SigningNonces
fn negate_nonces(signing_nonces: &round1::SigningNonces) -> round1::SigningNonces {
- // TODO: this recomputes commitments which is expensive, and not needed.
// Create an `internals` SigningNonces::from_nonces_and_commitments or
// something similar.
round1::SigningNonces::from_nonces(
diff --git a/frost/src/scalar_mul.rs b/frost/src/scalar_mul.rs
index 130578dc7..2dcef6d39 100644
--- a/frost/src/scalar_mul.rs
+++ b/frost/src/scalar_mul.rs
@@ -22,8 +22,6 @@ use crate::{Ciphersuite, Element, Field, Group, Scalar};
///
/// This function is similar to `div_ceil` that is [available on
/// Nightly](https://github.com/rust-lang/rust/issues/88581).
-// TODO: remove this function and use `div_ceil()` instead when `int_roundings`
-// is stabilized.
const fn div_ceil(lhs: usize, rhs: usize) -> usize {
let d = lhs / rhs;
let r = lhs % rhs;
diff --git a/node/src/rpc/tracing.rs b/node/src/rpc/tracing.rs
index 18514cae5..2ea205ce6 100644
--- a/node/src/rpc/tracing.rs
+++ b/node/src/rpc/tracing.rs
@@ -1,18 +1,19 @@
-// Copyright 2019-2022 PureStake Inc.
-// This file is part of Moonbeam.
+// Copyright 2022-2025 Tangle Foundation.
+// This file is part of Tangle.
+// This file originated in Moonbeam's codebase.
-// Moonbeam is free software: you can redistribute it and/or modify
+// Tangle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
-// Moonbeam is distributed in the hope that it will be useful,
+// Tangle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
-// along with Moonbeam. If not, see .
+// along with Tangle. If not, see .
use std::time::Duration;
diff --git a/node/tests/evm_restaking.rs b/node/tests/evm_restaking.rs
index 187dcc967..553890a06 100644
--- a/node/tests/evm_restaking.rs
+++ b/node/tests/evm_restaking.rs
@@ -15,6 +15,7 @@ use alloy::sol;
use anyhow::bail;
use sp_runtime::traits::AccountIdConversion;
use sp_tracing::{error, info};
+use tangle_primitives::time::SECONDS_PER_BLOCK;
use tangle_runtime::PalletId;
use tangle_subxt::subxt;
use tangle_subxt::subxt::tx::TxStatus;
@@ -186,6 +187,12 @@ async fn deploy_tangle_lrt(
Ok(*token.address())
}
+// Mock values for consistent testing
+const EIGHTEEN_DECIMALS: u128 = 1_000_000_000_000_000_000_000;
+const MOCK_DEPOSIT_CAP: u128 = 1_000_000 * EIGHTEEN_DECIMALS; // 1M tokens with 18 decimals
+const MOCK_DEPOSIT: u128 = 100_000 * EIGHTEEN_DECIMALS; // 100k tokens with 18 decimals
+const MOCK_APY: u8 = 10; // 10% APY
+
/// Setup the E2E test environment.
#[track_caller]
pub fn run_mad_test(f: TFn)
@@ -249,18 +256,50 @@ where
// Create a new vault and these assets to it.
let vault_id = 0;
- let deposit_cap = parse_ether("100").unwrap();
- let incentive_cap = parse_ether("100").unwrap();
- let update_vault_reward_config = api::tx().sudo().sudo(
+ // in Manual Sealing and fast runtime, we have 1 block per sec
+ // we consider 1 year as 50 blocks, for testing purposes
+ let one_year_blocks = SECONDS_PER_BLOCK * 50;
+
+ let set_apy_blocks = api::tx().sudo().sudo(
+ api::runtime_types::tangle_testnet_runtime::RuntimeCall::Rewards(
+ api::runtime_types::pallet_rewards::pallet::Call::update_apy_blocks {
+ blocks: one_year_blocks,
+ },
+ ),
+ );
+
+ let mut result = subxt
+ .tx()
+ .sign_and_submit_then_watch_default(&set_apy_blocks, &alice.substrate_signer())
+ .await?;
+
+ while let Some(Ok(s)) = result.next().await {
+ if let TxStatus::InBestBlock(b) = s {
+ let evs = match b.wait_for_success().await {
+ Ok(evs) => evs,
+ Err(e) => {
+ error!("Error: {:?}", e);
+ break;
+ },
+ };
+ for ev in evs.iter() {
+ let metadata = ev.unwrap();
+ info!("{}.{}", metadata.pallet_name(), metadata.variant_name());
+ }
+ break;
+ }
+ }
+
+ let create_vault = api::tx().sudo().sudo(
api::runtime_types::tangle_testnet_runtime::RuntimeCall::Rewards(
- api::runtime_types::pallet_rewards::pallet::Call::update_vault_reward_config {
+ api::runtime_types::pallet_rewards::pallet::Call::create_reward_vault {
vault_id,
new_config:
api::runtime_types::pallet_rewards::types::RewardConfigForAssetVault {
- apy: api::runtime_types::sp_arithmetic::per_things::Percent(1),
- incentive_cap: incentive_cap.to::(),
- deposit_cap: deposit_cap.to::(),
- boost_multiplier: None,
+ apy: api::runtime_types::sp_arithmetic::per_things::Percent(MOCK_APY),
+ deposit_cap: MOCK_DEPOSIT_CAP,
+ incentive_cap: 1,
+ boost_multiplier: Some(1),
},
},
),
@@ -268,10 +307,7 @@ where
let mut result = subxt
.tx()
- .sign_and_submit_then_watch_default(
- &update_vault_reward_config,
- &alice.substrate_signer(),
- )
+ .sign_and_submit_then_watch_default(&create_vault, &alice.substrate_signer())
.await?;
while let Some(Ok(s)) = result.next().await {
@@ -283,8 +319,10 @@ where
break;
},
};
- evs.find_first::()?
- .expect("VaultRewardConfigUpdated event to be emitted");
+ for ev in evs.iter() {
+ let metadata = ev.unwrap();
+ info!("{}.{}", metadata.pallet_name(), metadata.variant_name());
+ }
break;
}
}
@@ -326,8 +364,10 @@ where
break;
},
};
- evs.find_first::()?
- .expect("AssetRewardVault event to be emitted");
+ for ev in evs.iter() {
+ let metadata = ev.unwrap();
+ info!("{}.{}", metadata.pallet_name(), metadata.variant_name());
+ }
break;
}
}
@@ -380,30 +420,37 @@ pub struct TestInputs {
}
/// Helper function for joining as an operator
-async fn join_as_operator(provider: &AlloyProviderWithWallet, stake: U256) -> anyhow::Result {
- let precompile = MultiAssetDelegation::new(MULTI_ASSET_DELEGATION, provider);
- let result = precompile
- .joinOperators(stake)
- .send()
- .await?
- .with_timeout(Some(Duration::from_secs(5)))
- .get_receipt()
- .await?;
- Ok(result.status())
+async fn join_as_operator(
+ client: &subxt::OnlineClient,
+ caller: tangle_subxt::subxt_signer::sr25519::Keypair,
+ stake: u128,
+) -> anyhow::Result {
+ let join_call = api::tx().multi_asset_delegation().join_operators(stake);
+ let mut result = client.tx().sign_and_submit_then_watch_default(&join_call, &caller).await?;
+ while let Some(Ok(s)) = result.next().await {
+ if let TxStatus::InBestBlock(b) = s {
+ let _evs = match b.wait_for_success().await {
+ Ok(evs) => evs,
+ Err(e) => {
+ error!("Error: {:?}", e);
+ break;
+ },
+ };
+ break;
+ }
+ }
+ Ok(true)
}
#[test]
fn operator_join_delegator_delegate_erc20() {
run_mad_test(|t| async move {
let alice = TestAccount::Alice;
- let alice_provider = alloy_provider_with_wallet(&t.provider, alice.evm_wallet());
// Join operators
let tnt = U256::from(100_000u128);
- assert!(join_as_operator(&alice_provider, tnt).await?);
+ assert!(join_as_operator(&t.subxt, alice.substrate_signer(), tnt.to::()).await?);
- let operator_key = api::storage()
- .multi_asset_delegation()
- .operators(alice.address().to_account_id());
+ let operator_key = api::storage().multi_asset_delegation().operators(alice.account_id());
let maybe_operator = t.subxt.storage().at_latest().await?.fetch(&operator_key).await?;
assert!(maybe_operator.is_some());
assert_eq!(maybe_operator.map(|p| p.stake), Some(tnt.to::()));
@@ -437,7 +484,7 @@ fn operator_join_delegator_delegate_erc20() {
let delegate_result = precompile
.delegate(
- alice.address().to_account_id().0.into(),
+ alice.account_id().0.into(),
U256::ZERO,
*usdc.address(),
delegate_amount,
@@ -472,15 +519,11 @@ fn operator_join_delegator_delegate_erc20() {
fn operator_join_delegator_delegate_asset_id() {
run_mad_test(|t| async move {
let alice = TestAccount::Alice;
- let alice_provider = alloy_provider_with_wallet(&t.provider, alice.evm_wallet());
-
// Join operators
let tnt = U256::from(100_000u128);
- assert!(join_as_operator(&alice_provider, tnt).await?);
+ assert!(join_as_operator(&t.subxt, alice.substrate_signer(), tnt.to::()).await?);
- let operator_key = api::storage()
- .multi_asset_delegation()
- .operators(alice.address().to_account_id());
+ let operator_key = api::storage().multi_asset_delegation().operators(alice.account_id());
let maybe_operator = t.subxt.storage().at_latest().await?.fetch(&operator_key).await?;
assert!(maybe_operator.is_some());
assert_eq!(maybe_operator.map(|p| p.stake), Some(tnt.to::()));
@@ -536,7 +579,7 @@ fn operator_join_delegator_delegate_asset_id() {
let delegate_result = precompile
.delegate(
- alice.address().to_account_id().0.into(),
+ alice.account_id().0.into(),
U256::from(t.usdc_asset_id),
Address::ZERO,
U256::from(delegate_amount),
@@ -736,12 +779,12 @@ fn lrt_deposit_withdraw_erc20() {
let alice_provider = alloy_provider_with_wallet(&t.provider, alice.evm_wallet());
// Join operators
let tnt = U256::from(100_000u128);
- assert!(join_as_operator(&alice_provider, tnt).await?);
+ assert!(join_as_operator(&t.subxt, alice.substrate_signer(), tnt.to::()).await?);
// Setup a LRT Vault for Alice.
let lrt_address = deploy_tangle_lrt(
alice_provider.clone(),
t.weth,
- alice.address().to_account_id().0,
+ alice.account_id().0,
"Liquid Restaked Ether",
"lrtETH",
)
@@ -789,9 +832,7 @@ fn lrt_deposit_withdraw_erc20() {
assert_eq!(mad_weth_balance._0, deposit_amount);
// LRT should be a delegator to the operator in the MAD pallet.
- let operator_key = api::storage()
- .multi_asset_delegation()
- .operators(alice.address().to_account_id());
+ let operator_key = api::storage().multi_asset_delegation().operators(alice.account_id());
let maybe_operator = t.subxt.storage().at_latest().await?.fetch(&operator_key).await?;
assert!(maybe_operator.is_some());
assert_eq!(maybe_operator.as_ref().map(|p| p.delegation_count), Some(1));
@@ -889,12 +930,19 @@ fn lrt_rewards() {
let alice_provider = alloy_provider_with_wallet(&t.provider, alice.evm_wallet());
// Join operators
let tnt = U256::from(100_000u128);
- assert!(join_as_operator(&alice_provider, tnt).await?);
+ assert!(join_as_operator(&t.subxt, alice.substrate_signer(), tnt.to::()).await?);
+
+ let vault_id = 0;
+ let cfg_addr = api::storage().rewards().reward_config_storage(vault_id);
+ let cfg = t.subxt.storage().at_latest().await?.fetch(&cfg_addr).await?.unwrap();
+
+ let deposit = U256::from(MOCK_DEPOSIT);
+
// Setup a LRT Vault for Alice.
let lrt_address = deploy_tangle_lrt(
alice_provider.clone(),
t.weth,
- alice.address().to_account_id().0,
+ alice.account_id().0,
"Liquid Restaked Ether",
"lrtETH",
)
@@ -904,12 +952,12 @@ fn lrt_rewards() {
let bob = TestAccount::Bob;
let bob_provider = alloy_provider_with_wallet(&t.provider, bob.evm_wallet());
// Mint WETH for Bob
- let weth_amount = parse_ether("10").unwrap();
+ let weth_amount = deposit;
let weth = MockERC20::new(t.weth, &bob_provider);
weth.mint(bob.address(), weth_amount).send().await?.get_receipt().await?;
// Approve LRT contract to spend WETH
- let deposit_amount = weth_amount.div(U256::from(2));
+ let deposit_amount = weth_amount;
let approve_result =
weth.approve(lrt_address, deposit_amount).send().await?.get_receipt().await?;
assert!(approve_result.status());
@@ -927,14 +975,10 @@ fn lrt_rewards() {
assert!(deposit_result.status());
info!("Deposited {} WETH in LRT", format_ether(deposit_amount));
- // Wait for two new sessions to happen
- let session_index = wait_for_next_session(&t.subxt).await?;
- info!("New session started: {}", session_index);
+ // Wait for one year to pass
+ wait_for_more_blocks(&t.provider, 51).await;
- let vault_id = 0;
- let cfg_addr = api::storage().rewards().reward_config_storage(vault_id);
- let cfg = t.subxt.storage().at_latest().await?.fetch(&cfg_addr).await?;
- let apy = cfg.map(|c| c.apy).unwrap();
+ let apy = cfg.apy;
info!("APY: {}%", apy.0);
let rewards_addr = api::apis().rewards_api().query_user_rewards(
diff --git a/pallets/multi-asset-delegation/src/functions/delegate.rs b/pallets/multi-asset-delegation/src/functions/delegate.rs
index 36c05f8c3..7f29acd40 100644
--- a/pallets/multi-asset-delegation/src/functions/delegate.rs
+++ b/pallets/multi-asset-delegation/src/functions/delegate.rs
@@ -21,7 +21,7 @@ use frame_support::{
traits::{fungibles::Mutate, tokens::Preservation, Get},
};
use sp_runtime::{
- traits::{CheckedSub, Zero},
+ traits::{CheckedAdd, CheckedSub, Zero},
DispatchError, Percent,
};
use sp_std::vec::Vec;
@@ -70,7 +70,8 @@ impl Pallet {
.iter_mut()
.find(|d| d.operator == operator && d.asset_id == asset_id)
{
- delegation.amount += amount;
+ delegation.amount =
+ delegation.amount.checked_add(&amount).ok_or(Error::::OverflowRisk)?;
} else {
// Create the new delegation
let new_delegation = BondInfoDelegator {
@@ -108,7 +109,10 @@ impl Pallet {
if let Some(existing_delegation) =
delegations.iter_mut().find(|d| d.delegator == who && d.asset_id == asset_id)
{
- existing_delegation.amount += amount;
+ existing_delegation.amount = existing_delegation
+ .amount
+ .checked_add(&amount)
+ .ok_or(Error::::OverflowRisk)?;
} else {
delegations
.try_push(delegation)
@@ -164,7 +168,8 @@ impl Pallet {
let delegation = &mut metadata.delegations[delegation_index];
ensure!(delegation.amount >= amount, Error::::InsufficientBalance);
- delegation.amount -= amount;
+ delegation.amount =
+ delegation.amount.checked_sub(&amount).ok_or(Error::::InsufficientBalance)?;
// Create the unstake request
let current_round = Self::current_round();
@@ -202,12 +207,18 @@ impl Pallet {
// Reduce the amount in the operator's delegation
ensure!(operator_delegation.amount >= amount, Error::::InsufficientBalance);
- operator_delegation.amount -= amount;
+ operator_delegation.amount = operator_delegation
+ .amount
+ .checked_sub(&amount)
+ .ok_or(Error::::InsufficientBalance)?;
// Remove the delegation if the remaining amount is zero
if operator_delegation.amount.is_zero() {
operator_metadata.delegations.remove(operator_delegation_index);
- operator_metadata.delegation_count -= 1;
+ operator_metadata.delegation_count = operator_metadata
+ .delegation_count
+ .checked_sub(1u32)
+ .ok_or(Error::::InsufficientBalance)?;
}
Ok(())
@@ -315,14 +326,20 @@ impl Pallet {
.iter_mut()
.find(|d| d.asset_id == asset_id && d.delegator == who.clone())
{
- delegation.amount += amount;
+ delegation.amount = delegation
+ .amount
+ .checked_add(&amount)
+ .ok_or(Error::::OverflowRisk)?;
} else {
delegations
.try_push(DelegatorBond { delegator: who.clone(), amount, asset_id })
.map_err(|_| Error::::MaxDelegationsExceeded)?;
// Increase the delegation count only when a new delegation is added
- operator_metadata.delegation_count += 1;
+ operator_metadata.delegation_count = operator_metadata
+ .delegation_count
+ .checked_add(1)
+ .ok_or(Error::::OverflowRisk)?;
}
operator_metadata.delegations = delegations;
@@ -337,7 +354,10 @@ impl Pallet {
if let Some(delegation) = delegations.iter_mut().find(|d| {
d.operator == unstake_request.operator && d.asset_id == unstake_request.asset_id
}) {
- delegation.amount += unstake_request.amount;
+ delegation.amount = delegation
+ .amount
+ .checked_add(&unstake_request.amount)
+ .ok_or(Error::::OverflowRisk)?;
} else {
// Create a new delegation
delegations
diff --git a/pallets/multi-asset-delegation/src/functions/deposit.rs b/pallets/multi-asset-delegation/src/functions/deposit.rs
index ae9dcf1ca..7058d9817 100644
--- a/pallets/multi-asset-delegation/src/functions/deposit.rs
+++ b/pallets/multi-asset-delegation/src/functions/deposit.rs
@@ -23,6 +23,7 @@ use frame_support::{
};
use sp_core::H160;
use tangle_primitives::services::{Asset, EvmAddressMapping};
+use tangle_primitives::traits::RewardsManager;
use tangle_primitives::types::rewards::LockMultiplier;
impl Pallet {
@@ -102,6 +103,9 @@ impl Pallet {
let new_deposit = Deposit::new(amount, lock_multiplier, now);
metadata.deposits.insert(asset_id, new_deposit);
}
+
+ let _ = T::RewardsManager::record_deposit(&who, asset_id, amount, lock_multiplier);
+
Ok(())
})?;
@@ -145,6 +149,8 @@ impl Pallet {
.map_err(|_| Error::::MaxWithdrawRequestsExceeded)?;
metadata.withdraw_requests = withdraw_requests;
+ let _ = T::RewardsManager::record_withdrawal(&who, asset_id, amount);
+
Ok(())
})
}
diff --git a/pallets/multi-asset-delegation/src/lib.rs b/pallets/multi-asset-delegation/src/lib.rs
index 864b64165..c83a00c82 100644
--- a/pallets/multi-asset-delegation/src/lib.rs
+++ b/pallets/multi-asset-delegation/src/lib.rs
@@ -69,7 +69,6 @@ mod tests;
pub mod weights;
// #[cfg(feature = "runtime-benchmarks")]
-// TODO(@1xstj): Fix benchmarking and re-enable
// mod benchmarking;
pub mod functions;
@@ -319,8 +318,6 @@ pub mod pallet {
AlreadyLeaving,
/// The account is not leaving as an operator.
NotLeavingOperator,
- /// The round does not match the scheduled leave round.
- NotLeavingRound,
/// Leaving round not reached
LeavingRoundNotReached,
/// There is no scheduled unstake request.
@@ -411,6 +408,8 @@ pub mod pallet {
LockViolation,
/// Above deposit caps setup
DepositExceedsCapForAsset,
+ /// Overflow from math
+ OverflowRisk,
}
/// Hooks for the pallet.
diff --git a/pallets/multi-asset-delegation/src/mock.rs b/pallets/multi-asset-delegation/src/mock.rs
index 368331658..26deb9af4 100644
--- a/pallets/multi-asset-delegation/src/mock.rs
+++ b/pallets/multi-asset-delegation/src/mock.rs
@@ -38,15 +38,17 @@ use serde_json::json;
use sp_core::{sr25519, H160};
use sp_keyring::AccountKeyring;
use sp_keystore::{testing::MemoryKeystore, KeystoreExt, KeystorePtr};
-use sp_runtime::DispatchError;
use sp_runtime::{
testing::UintAuthorityId,
traits::{ConvertInto, IdentityLookup, OpaqueKeys},
- AccountId32, BoundToRuntimeAppPublic, BuildStorage, Perbill,
+ AccountId32, BoundToRuntimeAppPublic, BuildStorage, DispatchError, Perbill,
+};
+use std::cell::RefCell;
+use tangle_primitives::{
+ services::{EvmAddressMapping, EvmGasWeightMapping, EvmRunner},
+ traits::RewardsManager,
+ types::rewards::LockMultiplier,
};
-use tangle_primitives::services::{EvmAddressMapping, EvmGasWeightMapping, EvmRunner};
-use tangle_primitives::traits::RewardsManager;
-use tangle_primitives::types::rewards::LockMultiplier;
use core::ops::Mul;
use std::{collections::BTreeMap, sync::Arc};
@@ -278,12 +280,12 @@ pub struct MockServiceManager;
impl tangle_primitives::traits::ServiceManager for MockServiceManager {
fn get_active_blueprints_count(_account: &AccountId) -> usize {
- // we dont care
+ // we don't care
Default::default()
}
fn get_active_services_count(_account: &AccountId) -> usize {
- // we dont care
+ // we don't care
Default::default()
}
@@ -293,7 +295,7 @@ impl tangle_primitives::traits::ServiceManager for MockServi
}
fn get_blueprints_by_operator(_account: &AccountId) -> Vec {
- todo!(); // we dont care
+ unimplemented!(); // we don't care
}
}
@@ -316,25 +318,39 @@ parameter_types! {
pub const MaxDelegations: u32 = 50;
}
+type DepositCall = (AccountId, Asset, Balance, Option);
+type WithdrawalCall = (AccountId, Asset, Balance);
+
+thread_local! {
+ static DEPOSIT_CALLS: RefCell> = RefCell::new(Vec::new());
+ static WITHDRAWAL_CALLS: RefCell> = RefCell::new(Vec::new());
+}
+
pub struct MockRewardsManager;
impl RewardsManager for MockRewardsManager {
type Error = DispatchError;
fn record_deposit(
- _account_id: &AccountId,
- _asset: Asset,
- _amount: Balance,
- _lock_multiplier: Option,
+ account_id: &AccountId,
+ asset: Asset,
+ amount: Balance,
+ lock_multiplier: Option,
) -> Result<(), Self::Error> {
+ DEPOSIT_CALLS.with(|calls| {
+ calls.borrow_mut().push((account_id.clone(), asset, amount, lock_multiplier));
+ });
Ok(())
}
fn record_withdrawal(
- _account_id: &AccountId,
- _asset: Asset,
- _amount: Balance,
+ account_id: &AccountId,
+ asset: Asset,
+ amount: Balance,
) -> Result<(), Self::Error> {
+ WITHDRAWAL_CALLS.with(|calls| {
+ calls.borrow_mut().push((account_id.clone(), asset, amount));
+ });
Ok(())
}
@@ -355,6 +371,21 @@ impl RewardsManager for MockRewardsMan
}
}
+impl MockRewardsManager {
+ pub fn record_deposit_calls() -> Vec {
+ DEPOSIT_CALLS.with(|calls| calls.borrow().clone())
+ }
+
+ pub fn record_withdrawal_calls() -> Vec {
+ WITHDRAWAL_CALLS.with(|calls| calls.borrow().clone())
+ }
+
+ pub fn clear_all() {
+ DEPOSIT_CALLS.with(|calls| calls.borrow_mut().clear());
+ WITHDRAWAL_CALLS.with(|calls| calls.borrow_mut().clear());
+ }
+}
+
impl pallet_multi_asset_delegation::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
@@ -421,19 +452,13 @@ pub fn account_id_to_address(account_id: AccountId) -> H160 {
H160::from_slice(&AsRef::<[u8; 32]>::as_ref(&account_id)[0..20])
}
-// pub fn address_to_account_id(address: H160) -> AccountId {
-// use pallet_evm::AddressMapping;
-// ::AddressMapping::into_account_id(address)
-// }
-
pub fn new_test_ext() -> sp_io::TestExternalities {
- new_test_ext_raw_authorities()
+ let ext = new_test_ext_raw_authorities();
+ MockRewardsManager::clear_all();
+ ext
}
pub const USDC_ERC20: H160 = H160([0x23; 20]);
-// pub const USDC: AssetId = 1;
-// pub const WETH: AssetId = 2;
-// pub const WBTC: AssetId = 3;
pub const VDOT: AssetId = 4;
// This function basically just builds a genesis storage key/value store according to
@@ -598,36 +623,3 @@ macro_rules! evm_log {
}
};
}
-
-// /// Asserts that the EVM logs are as expected.
-// #[track_caller]
-// pub fn assert_evm_logs(expected: &[fp_evm::Log]) {
-// assert_evm_events_contains(expected.iter().cloned().collect())
-// }
-
-// /// Asserts that the EVM events are as expected.
-// #[track_caller]
-// fn assert_evm_events_contains(expected: Vec) {
-// let actual: Vec = System::events()
-// .iter()
-// .filter_map(|e| match e.event {
-// RuntimeEvent::EVM(pallet_evm::Event::Log { ref log }) => Some(log.clone()),
-// _ => None,
-// })
-// .collect();
-
-// // Check if `expected` is a subset of `actual`
-// let mut any_matcher = false;
-// for evt in expected {
-// if !actual.contains(&evt) {
-// panic!("Events don't match\nactual: {actual:?}\nexpected: {evt:?}");
-// } else {
-// any_matcher = true;
-// }
-// }
-
-// // At least one event should be present
-// if !any_matcher {
-// panic!("No events found");
-// }
-// }
diff --git a/pallets/multi-asset-delegation/src/tests/deposit.rs b/pallets/multi-asset-delegation/src/tests/deposit.rs
index 40bd7f368..efa65e5c0 100644
--- a/pallets/multi-asset-delegation/src/tests/deposit.rs
+++ b/pallets/multi-asset-delegation/src/tests/deposit.rs
@@ -69,6 +69,12 @@ fn deposit_should_work_for_fungible_asset() {
asset_id: Asset::Custom(VDOT),
})
);
+
+ // Verify that rewards manager was called with correct parameters
+ assert_eq!(
+ MockRewardsManager::record_deposit_calls(),
+ vec![(who.clone(), Asset::Custom(VDOT), amount, None)]
+ );
});
}
@@ -234,6 +240,12 @@ fn schedule_withdraw_should_work() {
let deposit = metadata.deposits.get(&asset_id).unwrap();
assert_eq!(deposit.amount, 0_u32.into());
assert!(!metadata.withdraw_requests.is_empty());
+
+ // Ensure that rewards pallet was called
+ assert_eq!(
+ MockRewardsManager::record_withdrawal_calls(),
+ vec![(who.clone(), Asset::Custom(VDOT), amount)]
+ );
});
}
diff --git a/pallets/multi-asset-delegation/src/types.rs b/pallets/multi-asset-delegation/src/types.rs
index e5d740b93..5bd9bf632 100644
--- a/pallets/multi-asset-delegation/src/types.rs
+++ b/pallets/multi-asset-delegation/src/types.rs
@@ -25,11 +25,9 @@ use tangle_primitives::types::RoundIndex;
pub mod delegator;
pub mod operator;
-pub mod rewards;
pub use delegator::*;
pub use operator::*;
-pub use rewards::*;
pub type BalanceOf =
<::Currency as Currency<::AccountId>>::Balance;
diff --git a/pallets/multi-asset-delegation/src/types/delegator.rs b/pallets/multi-asset-delegation/src/types/delegator.rs
index 23ef2bc41..84aaaabd7 100644
--- a/pallets/multi-asset-delegation/src/types/delegator.rs
+++ b/pallets/multi-asset-delegation/src/types/delegator.rs
@@ -15,13 +15,14 @@
// along with Tangle. If not, see .
use super::*;
-use frame_support::ensure;
-use frame_support::{pallet_prelude::Get, BoundedVec};
-use sp_std::fmt::Debug;
-use sp_std::vec;
-use tangle_primitives::types::rewards::LockInfo;
-use tangle_primitives::types::rewards::LockMultiplier;
-use tangle_primitives::{services::Asset, BlueprintId};
+use frame_support::{ensure, pallet_prelude::Get, BoundedVec};
+use sp_runtime::traits::CheckedAdd;
+use sp_std::{fmt::Debug, vec};
+use tangle_primitives::{
+ services::Asset,
+ types::rewards::{LockInfo, LockMultiplier},
+ BlueprintId,
+};
/// Represents how a delegator selects which blueprints to work with.
#[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, Eq)]
@@ -186,13 +187,13 @@ impl<
pub fn calculate_delegation_by_asset(&self, asset_id: Asset) -> Balance
// Asset) -> Balance
where
- Balance: Default + core::ops::AddAssign + Clone,
+ Balance: Default + core::ops::AddAssign + Clone + CheckedAdd,
AssetId: Eq + PartialEq,
{
let mut total = Balance::default();
for stake in &self.delegations {
if stake.asset_id == asset_id {
- total += stake.amount.clone();
+ total = total.checked_add(&stake.amount).unwrap_or(total);
}
}
total
diff --git a/pallets/multi-asset-delegation/src/types/operator.rs b/pallets/multi-asset-delegation/src/types/operator.rs
index e0e7ca270..5c1977a62 100644
--- a/pallets/multi-asset-delegation/src/types/operator.rs
+++ b/pallets/multi-asset-delegation/src/types/operator.rs
@@ -16,6 +16,7 @@
use super::*;
use frame_support::{pallet_prelude::*, BoundedVec};
+use sp_runtime::traits::CheckedAdd;
use tangle_primitives::services::Asset;
/// A snapshot of the operator state at the start of the round.
@@ -34,14 +35,14 @@ impl>
OperatorSnapshot
where
AssetId: PartialEq + Ord + Copy,
- Balance: Default + core::ops::AddAssign + Copy,
+ Balance: Default + core::ops::AddAssign + Copy + CheckedAdd,
{
/// Calculates the total stake for a specific asset ID from all delegations.
pub fn get_stake_by_asset_id(&self, asset_id: Asset) -> Balance {
let mut total_stake = Balance::default();
for stake in &self.delegations {
if stake.asset_id == asset_id {
- total_stake += stake.amount;
+ total_stake = total_stake.checked_add(&stake.amount).unwrap_or(total_stake);
}
}
total_stake
@@ -53,7 +54,7 @@ where
for stake in &self.delegations {
let entry = stake_by_asset.entry(stake.asset_id).or_default();
- *entry += stake.amount;
+ *entry = entry.checked_add(&stake.amount).unwrap_or(*entry);
}
stake_by_asset.into_iter().collect()
diff --git a/pallets/multi-asset-delegation/src/types/rewards.rs b/pallets/multi-asset-delegation/src/types/rewards.rs
deleted file mode 100644
index dc3dff1ac..000000000
--- a/pallets/multi-asset-delegation/src/types/rewards.rs
+++ /dev/null
@@ -1,43 +0,0 @@
-// This file is part of Tangle.
-// Copyright (C) 2022-2024 Tangle Foundation.
-//
-// Tangle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Tangle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Tangle. If not, see .
-
-use super::*;
-use sp_runtime::Percent;
-
-/// Configuration for rewards associated with a specific asset.
-#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
-pub struct RewardConfigForAssetVault {
- // The annual percentage yield (APY) for the asset, represented as a Percent
- pub apy: Percent,
- // The minimum amount required before the asset can be rewarded.
- pub cap: Balance,
-}
-
-/// Configuration for rewards in the system.
-#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
-pub struct RewardConfig {
- // A map of asset IDs to their respective reward configurations.
- pub configs: BTreeMap>,
- // A list of blueprint IDs that are whitelisted for rewards.
- pub whitelisted_blueprint_ids: Vec,
-}
-
-/// Asset action for vaults
-#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq, Eq)]
-pub enum AssetAction {
- Add,
- Remove,
-}
diff --git a/pallets/rewards/src/functions.rs b/pallets/rewards/src/functions.rs
deleted file mode 100644
index 6c19531b0..000000000
--- a/pallets/rewards/src/functions.rs
+++ /dev/null
@@ -1,275 +0,0 @@
-// This file is part of Tangle.
-// Copyright (C) 2022-2024 Tangle Foundation.
-//
-// Tangle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Tangle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Tangle. If not, see .
-use crate::AssetLookupRewardVaults;
-use crate::Error;
-use crate::Event;
-use crate::RewardConfigForAssetVault;
-use crate::RewardConfigStorage;
-use crate::RewardVaults;
-use crate::TotalRewardVaultScore;
-use crate::UserClaimedReward;
-use crate::{BalanceOf, Config, Pallet};
-use frame_support::ensure;
-use frame_support::traits::Currency;
-use frame_system::pallet_prelude::BlockNumberFor;
-use sp_runtime::traits::{CheckedDiv, CheckedMul};
-use sp_runtime::traits::{Saturating, Zero};
-use sp_runtime::DispatchError;
-use sp_runtime::DispatchResult;
-use sp_std::vec::Vec;
-use tangle_primitives::services::Asset;
-use tangle_primitives::traits::MultiAssetDelegationInfo;
-use tangle_primitives::types::rewards::UserDepositWithLocks;
-
-impl Pallet {
- pub fn remove_asset_from_vault(
- vault_id: &T::VaultId,
- asset_id: &Asset,
- ) -> DispatchResult {
- // Update RewardVaults storage
- RewardVaults::::try_mutate(vault_id, |maybe_assets| -> DispatchResult {
- let assets = maybe_assets.as_mut().ok_or(Error::::VaultNotFound)?;
-
- // Ensure the asset is in the vault
- ensure!(assets.contains(asset_id), Error::::AssetNotInVault);
-
- assets.retain(|id| id != asset_id);
-
- Ok(())
- })?;
-
- // Update AssetLookupRewardVaults storage
- AssetLookupRewardVaults::::remove(asset_id);
-
- Ok(())
- }
-
- pub fn add_asset_to_vault(
- vault_id: &T::VaultId,
- asset_id: &Asset,
- ) -> DispatchResult {
- // Ensure the asset is not already associated with any vault
- ensure!(
- !AssetLookupRewardVaults::::contains_key(asset_id),
- Error::::AssetAlreadyInVault
- );
-
- // Update RewardVaults storage
- RewardVaults::::try_mutate(vault_id, |maybe_assets| -> DispatchResult {
- let assets = maybe_assets.get_or_insert_with(Vec::new);
-
- // Ensure the asset is not already in the vault
- ensure!(!assets.contains(asset_id), Error::::AssetAlreadyInVault);
-
- assets.push(*asset_id);
-
- Ok(())
- })?;
-
- // Update AssetLookupRewardVaults storage
- AssetLookupRewardVaults::::insert(asset_id, vault_id);
-
- Ok(())
- }
-
- pub fn calculate_rewards(
- account_id: &T::AccountId,
- asset: Asset,
- ) -> Result<(BalanceOf, BalanceOf), DispatchError> {
- // find the vault for the asset id
- // if the asset is not in a reward vault, do nothing
- let vault_id =
- AssetLookupRewardVaults::::get(asset).ok_or(Error::::AssetNotInVault)?;
-
- // lets read the user deposits from the delegation manager
- let deposit_info =
- T::DelegationManager::get_user_deposit_with_locks(&account_id.clone(), asset)
- .ok_or(Error::::NoRewardsAvailable)?;
-
- // read the asset reward config
- let reward_config = RewardConfigStorage::::get(vault_id);
-
- // find the total vault score
- let total_score = TotalRewardVaultScore::::get(vault_id);
-
- // get the users last claim
- let last_claim = UserClaimedReward::::get(account_id, vault_id);
-
- Self::calculate_deposit_rewards_with_lock_multiplier(
- total_score,
- deposit_info,
- reward_config.ok_or(Error::::RewardConfigNotFound)?,
- last_claim,
- )
- }
-
- /// Calculates and pays out rewards for a given account and asset.
- ///
- /// This function orchestrates the reward calculation and payout process by:
- /// 1. Finding the vault associated with the asset
- /// 2. Retrieving user deposit information including any locked amounts
- /// 3. Calculating rewards based on deposit amounts, lock periods, and APY
- ///
- /// # Arguments
- /// * `account_id` - The account to calculate rewards for
- /// * `asset` - The asset to calculate rewards for
- ///
- /// # Returns
- /// * `Ok(BalanceOf)` - The total rewards calculated
- /// * `Err(DispatchError)` - If any of the following conditions are met:
- /// - Asset is not in a reward vault
- /// - No rewards are available for the account
- /// - Reward configuration is not found for the vault
- /// - Arithmetic overflow occurs during calculation
- ///
- /// # Assumptions
- /// * The asset must be registered in a reward vault
- /// * The reward configuration must exist for the vault
- pub fn calculate_and_payout_rewards(
- account_id: &T::AccountId,
- asset: Asset,
- ) -> Result, DispatchError> {
- // find the vault for the asset id
- // if the asset is not in a reward vault, do nothing
- let vault_id =
- AssetLookupRewardVaults::::get(asset).ok_or(Error::::AssetNotInVault)?;
-
- let (total_rewards, rewards_to_be_paid) = Self::calculate_rewards(account_id, asset)?;
-
- // mint new TNT rewards and trasnfer to the user
- let _ = T::Currency::deposit_creating(account_id, rewards_to_be_paid);
-
- // update the last claim
- UserClaimedReward::::insert(
- account_id,
- vault_id,
- (frame_system::Pallet::::block_number(), total_rewards),
- );
-
- Self::deposit_event(Event::RewardsClaimed {
- account: account_id.clone(),
- asset,
- amount: rewards_to_be_paid,
- });
-
- Ok(total_rewards)
- }
-
- /// Calculates rewards for deposits considering both unlocked amounts and locked amounts with their respective multipliers.
- ///
- /// The reward calculation follows these formulas:
- /// 1. For unlocked amounts:
- /// ```text
- /// base_reward = APY * (user_deposit / total_deposits) * (total_deposits / deposit_capacity)
- /// ```
- ///
- /// 2. For locked amounts:
- /// ```text
- /// lock_reward = amount * APY * lock_multiplier * (remaining_lock_time / total_lock_time)
- /// ```
- ///
- /// # Arguments
- /// * `total_asset_score` - Total score for the asset across all deposits
- /// * `deposit` - User's deposit information including locked amounts
- /// * `reward` - Reward configuration for the asset vault
- /// * `last_claim` - Timestamp and amount of user's last reward claim
- ///
- /// # Returns
- /// * `Ok(BalanceOf)` - The calculated rewards
- /// * `Err(DispatchError)` - If any arithmetic operation overflows
- ///
- /// # Assumptions and Constraints
- /// * Lock multipliers are fixed at: 1x (1 month), 2x (2 months), 3x (3 months), 6x (6 months)
- /// * APY is applied proportionally to the lock period remaining
- /// * Rewards scale with:
- /// - The proportion of user's deposit to total deposits
- /// - The proportion of total deposits to deposit capacity
- /// - The lock multiplier (if applicable)
- /// - The remaining time in the lock period
- ///
- pub fn calculate_deposit_rewards_with_lock_multiplier(
- total_asset_score: BalanceOf,
- deposit: UserDepositWithLocks, BlockNumberFor>,
- reward: RewardConfigForAssetVault>,
- last_claim: Option<(BlockNumberFor, BalanceOf)>,
- ) -> Result<(BalanceOf, BalanceOf), DispatchError> {
- // The formula for rewards:
- // Base Reward = APY * (user_deposit / total_deposits) * (total_deposits / deposit_capacity)
- // For locked amounts: Base Reward * lock_multiplier * (remaining_lock_time / total_lock_time)
-
- let asset_apy = reward.apy;
- let deposit_capacity = reward.deposit_cap;
-
- // Start with unlocked amount as base score
- let mut total_rewards = deposit.unlocked_amount;
-
- // Get the current block and last claim block
- let current_block = frame_system::Pallet::::block_number();
- let last_claim_block = last_claim.map(|(block, _)| block).unwrap_or(current_block);
-
- // Calculate base reward rate
- // APY * (deposit / total_deposits) * (total_deposits / capacity)
- let base_reward_rate = if !total_asset_score.is_zero() {
- let deposit_ratio = total_rewards
- .checked_mul(&total_rewards)
- .and_then(|v| v.checked_div(&total_asset_score))
- .ok_or(Error::::ArithmeticError)?;
-
- let capacity_ratio = total_asset_score
- .checked_div(&deposit_capacity)
- .ok_or(Error::::ArithmeticError)?;
-
- asset_apy.mul_floor(deposit_ratio.saturating_mul(capacity_ratio))
- } else {
- Zero::zero()
- };
-
- total_rewards = total_rewards.saturating_add(base_reward_rate);
-
- // Add rewards for locked amounts if any exist
- if let Some(locks) = deposit.amount_with_locks {
- for lock in locks {
- if lock.expiry_block > last_claim_block {
- // Calculate remaining lock time as a ratio
- let blocks_remaining: u32 =
- TryInto::::try_into(lock.expiry_block.saturating_sub(current_block))
- .map_err(|_| Error::::ArithmeticError)?;
-
- let total_lock_blocks = lock.lock_multiplier.get_blocks();
- let time_ratio = BalanceOf::::from(blocks_remaining)
- .checked_div(&BalanceOf::::from(total_lock_blocks))
- .ok_or(Error::::ArithmeticError)?;
-
- // Calculate lock reward:
- // amount * APY * multiplier * time_ratio
- let multiplier = BalanceOf::::from(lock.lock_multiplier.value());
- let lock_reward = asset_apy
- .mul_floor(lock.amount)
- .saturating_mul(multiplier)
- .saturating_mul(time_ratio);
-
- total_rewards = total_rewards.saturating_add(lock_reward);
- }
- }
- }
-
- // lets remove any already claimed rewards
- let rewards_to_be_paid = total_rewards
- .saturating_sub(last_claim.map(|(_, amount)| amount).unwrap_or(Zero::zero()));
-
- Ok((total_rewards, rewards_to_be_paid))
- }
-}
diff --git a/pallets/rewards/src/functions/mod.rs b/pallets/rewards/src/functions/mod.rs
new file mode 100644
index 000000000..c7d9fa947
--- /dev/null
+++ b/pallets/rewards/src/functions/mod.rs
@@ -0,0 +1,89 @@
+// This file is part of Tangle.
+// Copyright (C) 2022-2024 Tangle Foundation.
+//
+// Tangle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Tangle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Tangle. If not, see .
+use crate::{
+ AssetLookupRewardVaults, Config, Error, Pallet, RewardVaults, RewardVaultsPotAccount,
+ SubaccountType,
+};
+use frame_support::{ensure, traits::Get};
+use sp_runtime::{traits::AccountIdConversion, DispatchError, DispatchResult};
+use sp_std::vec::Vec;
+use tangle_primitives::services::Asset;
+
+pub mod rewards;
+
+impl Pallet {
+ pub fn remove_asset_from_vault(
+ vault_id: &T::VaultId,
+ asset_id: &Asset,
+ ) -> DispatchResult {
+ // Update RewardVaults storage
+ RewardVaults::::try_mutate(vault_id, |maybe_assets| -> DispatchResult {
+ let assets = maybe_assets.as_mut().ok_or(Error::::VaultNotFound)?;
+
+ // Ensure the asset is in the vault
+ ensure!(assets.contains(asset_id), Error::::AssetNotInVault);
+
+ assets.retain(|id| id != asset_id);
+
+ Ok(())
+ })?;
+
+ // Update AssetLookupRewardVaults storage
+ AssetLookupRewardVaults::::remove(asset_id);
+
+ Ok(())
+ }
+
+ pub fn add_asset_to_vault(
+ vault_id: &T::VaultId,
+ asset_id: &Asset,
+ ) -> DispatchResult {
+ // Ensure the asset is not already associated with any vault
+ ensure!(
+ !AssetLookupRewardVaults::::contains_key(asset_id),
+ Error::::AssetAlreadyInVault
+ );
+
+ // Update RewardVaults storage
+ RewardVaults::::try_mutate(vault_id, |maybe_assets| -> DispatchResult {
+ let assets = maybe_assets.get_or_insert_with(Vec::new);
+
+ // Ensure the asset is not already in the vault
+ ensure!(!assets.contains(asset_id), Error::::AssetAlreadyInVault);
+
+ assets.push(*asset_id);
+
+ Ok(())
+ })?;
+
+ // Update AssetLookupRewardVaults storage
+ AssetLookupRewardVaults::::insert(asset_id, vault_id);
+
+ Ok(())
+ }
+
+ /// Creates a new pot account for a reward vault
+ pub fn create_reward_vault_pot(vault_id: T::VaultId) -> Result {
+ // Initialize the vault pot for rewards
+ let pot_account_for_vault: T::AccountId =
+ T::PalletId::get().into_sub_account_truncating((SubaccountType::RewardPot, vault_id));
+ // Ensure the pot does not already exist
+ ensure!(RewardVaultsPotAccount::::get(vault_id).is_none(), Error::::PotAlreadyExists);
+ // Store the pot
+ RewardVaultsPotAccount::::insert(vault_id, pot_account_for_vault.clone());
+ Ok(pot_account_for_vault)
+ }
+}
diff --git a/pallets/rewards/src/functions/rewards.rs b/pallets/rewards/src/functions/rewards.rs
new file mode 100644
index 000000000..3a7d175df
--- /dev/null
+++ b/pallets/rewards/src/functions/rewards.rs
@@ -0,0 +1,399 @@
+// This file is part of Tangle.
+// Copyright (C) 2022-2024 Tangle Foundation.
+//
+// Tangle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Tangle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Tangle. If not, see .
+use crate::{
+ ApyBlocks, AssetLookupRewardVaults, BalanceOf, Config, DecayRate, DecayStartPeriod, Error,
+ Event, Pallet, RewardConfigForAssetVault, RewardConfigStorage, RewardVaultsPotAccount,
+ TotalRewardVaultDeposit, TotalRewardVaultScore, UserClaimedReward,
+};
+use frame_support::{ensure, traits::Currency};
+use frame_system::pallet_prelude::BlockNumberFor;
+use scale_info::prelude::vec;
+use sp_runtime::{
+ traits::{CheckedMul, Saturating, Zero},
+ DispatchError, DispatchResult, Percent, SaturatedConversion,
+};
+use sp_std::vec::Vec;
+use tangle_primitives::{
+ services::Asset, traits::MultiAssetDelegationInfo, types::rewards::UserDepositWithLocks,
+};
+
+pub(crate) const LOG_TARGET: &str = "runtime::rewards";
+
+impl Pallet {
+ pub fn calculate_rewards(
+ account_id: &T::AccountId,
+ asset: Asset,
+ ) -> Result, DispatchError> {
+ // find the vault for the asset id
+ // if the asset is not in a reward vault, do nothing
+ let vault_id =
+ AssetLookupRewardVaults::::get(asset).ok_or(Error::::AssetNotInVault)?;
+
+ // lets read the user deposits from the delegation manager
+ let deposit_info =
+ T::DelegationManager::get_user_deposit_with_locks(&account_id.clone(), asset)
+ .ok_or(Error::::NoRewardsAvailable)?;
+
+ // read the asset reward config
+ let reward_config = RewardConfigStorage::::get(vault_id);
+
+ // find the total vault score
+ let total_score = TotalRewardVaultScore::::get(vault_id);
+
+ // get the users last claim
+ let last_claim = UserClaimedReward::::get(account_id, vault_id);
+
+ let total_deposit = TotalRewardVaultDeposit::::get(vault_id);
+
+ Self::calculate_deposit_rewards_with_lock_multiplier(
+ total_deposit,
+ total_score,
+ deposit_info,
+ reward_config.ok_or(Error::::RewardConfigNotFound)?,
+ last_claim,
+ )
+ }
+
+ /// Calculates and pays out rewards for a given account and asset.
+ ///
+ /// This function orchestrates the reward calculation and payout process by:
+ /// 1. Finding the vault associated with the asset
+ /// 2. Retrieving user deposit information including any locked amounts
+ /// 3. Calculating rewards based on deposit amounts, lock periods, and APY
+ ///
+ /// # Arguments
+ /// * `account_id` - The account to calculate rewards for
+ /// * `asset` - The asset to calculate rewards for
+ ///
+ /// # Returns
+ /// * `Ok(BalanceOf)` - The total rewards calculated
+ /// * `Err(DispatchError)` - If any of the following conditions are met:
+ /// - Asset is not in a reward vault
+ /// - No rewards are available for the account
+ /// - Reward configuration is not found for the vault
+ /// - Arithmetic overflow occurs during calculation
+ ///
+ /// # Assumptions
+ /// * The asset must be registered in a reward vault
+ /// * The reward configuration must exist for the vault
+ pub fn calculate_and_payout_rewards(
+ account_id: &T::AccountId,
+ asset: Asset,
+ ) -> Result, DispatchError> {
+ // find the vault for the asset id
+ // if the asset is not in a reward vault, do nothing
+ let vault_id =
+ AssetLookupRewardVaults::::get(asset).ok_or(Error::::AssetNotInVault)?;
+
+ let rewards_to_be_paid = Self::calculate_rewards(account_id, asset)?;
+
+ log::debug!(target: LOG_TARGET, "rewards_to_be_paid: {:?}", rewards_to_be_paid.saturated_into::());
+
+ // Get the pot account for this vault
+ let pot_account =
+ RewardVaultsPotAccount::::get(vault_id).ok_or(Error::::PotAccountNotFound)?;
+
+ // Transfer rewards from the pot account to the user
+ T::Currency::transfer(
+ &pot_account,
+ account_id,
+ rewards_to_be_paid,
+ frame_support::traits::ExistenceRequirement::AllowDeath,
+ )?;
+
+ // update the last claim
+ UserClaimedReward::::try_mutate(
+ account_id,
+ vault_id,
+ |maybe_claim| -> DispatchResult {
+ let current_block = frame_system::Pallet::::block_number();
+ let total_claimed = maybe_claim.map(|(_, amount)| amount).unwrap_or_default();
+ *maybe_claim =
+ Some((current_block, total_claimed.saturating_add(rewards_to_be_paid)));
+ Ok(())
+ },
+ )?;
+
+ Self::deposit_event(Event::RewardsClaimed {
+ account: account_id.clone(),
+ asset,
+ amount: rewards_to_be_paid,
+ });
+
+ Ok(rewards_to_be_paid)
+ }
+
+ /// Validates a reward configuration ensuring that:
+ /// 1. The incentive cap is not greater than the deposit cap
+ /// 2. If boost multiplier is set, it must be 1 (current limitation)
+ ///
+ /// # Arguments
+ /// * `config` - The reward configuration to validate
+ ///
+ /// # Returns
+ /// * `DispatchResult` - Ok if validation passes, Error otherwise
+ pub fn validate_reward_config(
+ config: &RewardConfigForAssetVault>,
+ ) -> Result<(), Error> {
+ ensure!(
+ config.incentive_cap <= config.deposit_cap,
+ Error::::IncentiveCapGreaterThanDepositCap
+ );
+
+ if let Some(boost_multiplier) = config.boost_multiplier {
+ // boost multipliers are handled by locks, this ensures the multiplier is 1
+ // we can change the multiplier to be customisable in the future, but for now we
+ // require it to be 1
+ ensure!(boost_multiplier == 1, Error::::BoostMultiplierMustBeOne);
+ }
+
+ Ok(())
+ }
+
+ /// Calculate the APY based on the total deposit and deposit cap.
+ /// The goal is to ensure the APY is proportional to the total deposit.
+ ///
+ /// # Returns
+ /// * `Ok(Percent)` - The normalized APY
+ /// * `Err(DispatchError)` - If any arithmetic operation overflows
+ ///
+ /// # Arguments
+ /// * `total_deposit` - The total amount of deposits for the asset vault
+ /// * `deposit_cap` - The maximum amount of deposits allowed for the asset vault
+ /// * `original_apy` - The original APY before normalization
+ pub fn calculate_propotional_apy(
+ total_deposit: BalanceOf,
+ deposit_cap: BalanceOf,
+ original_apy: Percent,
+ ) -> Option {
+ if deposit_cap.is_zero() {
+ return None;
+ }
+
+ log::debug!(target: LOG_TARGET, "calculate_propotional_apy : total_deposit: {:?}, deposit_cap: {:?}, original_apy: {:?}",
+ total_deposit, deposit_cap, original_apy);
+ let propotion = Percent::from_rational(total_deposit, deposit_cap);
+ original_apy.checked_mul(&propotion)
+ }
+
+ /// Calculate the per-block reward amount for a given total reward
+ ///
+ /// # Arguments
+ /// * `total_reward` - The total reward amount to be distributed
+ ///
+ /// # Returns
+ /// * `Option>` - The per-block reward amount, or None if division fails
+ pub fn calculate_reward_per_block(total_reward: BalanceOf) -> Option> {
+ let apy_blocks = ApyBlocks::::get();
+ if apy_blocks.is_zero() {
+ return None;
+ }
+
+ log::debug!(target: LOG_TARGET, "calculate_reward_per_block : total_reward: {:?}", total_reward);
+
+ let apy_blocks_balance = BalanceOf::::from(apy_blocks.saturated_into::());
+ Some(total_reward / apy_blocks_balance)
+ }
+
+ /// Calculate decay factor based on time since last claim
+ fn calculate_decay_factor(
+ current_block: BlockNumberFor,
+ last_claim_block: BlockNumberFor,
+ ) -> Percent {
+ let blocks_since_last_claim = current_block.saturating_sub(last_claim_block);
+ let start_period = DecayStartPeriod::::get();
+
+ // If we haven't reached the decay period yet, no decay
+ if blocks_since_last_claim <= start_period {
+ return Percent::from_percent(100);
+ }
+
+ let decay_rate = DecayRate::::get();
+ let decay_percent = 100_u8.saturating_sub(decay_rate.deconstruct());
+
+ // Ensure we don't decay below 90%
+ Percent::from_percent(decay_percent.max(90))
+ }
+
+ /// Calculates rewards for deposits considering both unlocked amounts and locked amounts with
+ /// their respective multipliers.
+ ///
+ /// The reward calculation follows these formulas:
+ /// 1. For unlocked amounts: ```text base_reward = APY * (user_deposit / total_deposits) *
+ /// (total_deposits / deposit_capacity) ```
+ ///
+ /// 2. For locked amounts: ```text lock_reward = amount * APY * lock_multiplier *
+ /// (remaining_lock_time / total_lock_time) ```
+ ///
+ /// # Arguments
+ /// * `total_asset_score` - Total score for the asset across all deposits
+ /// * `deposit` - User's deposit information including locked amounts
+ /// * `reward` - Reward configuration for the asset vault
+ /// * `last_claim` - Block number and amount of last claim, if any
+ ///
+ /// # Returns
+ /// * `Ok((BalanceOf, BalanceOf))` - Tuple of (total rewards, rewards to be paid)
+ /// * `Err(DispatchError)` - If any arithmetic operation fails
+ ///
+ /// The reward amount is affected by:
+ /// - The proportion of user's deposit to total deposits
+ /// - The proportion of total deposits to deposit capacity
+ /// - The lock multiplier (if applicable)
+ /// - The remaining time in the lock period
+ pub fn calculate_deposit_rewards_with_lock_multiplier(
+ total_deposit: BalanceOf,
+ total_asset_score: BalanceOf,
+ deposit: UserDepositWithLocks, BlockNumberFor>,
+ reward: RewardConfigForAssetVault>,
+ last_claim: Option<(BlockNumberFor, BalanceOf)>,
+ ) -> Result, DispatchError> {
+ // Calculate the propotional apy
+ let deposit_cap = reward.deposit_cap;
+
+ if reward.incentive_cap > total_deposit {
+ return Err(Error::::TotalDepositLessThanIncentiveCap.into());
+ }
+
+ log::debug!(target: LOG_TARGET, "total_deposit: {:?}, total_asset_score: {:?}, deposit: {:?}, reward: {:?}, last_claim: {:?}",
+ total_deposit, total_asset_score, deposit, reward, last_claim);
+ log::debug!(target: LOG_TARGET, "deposit_cap: {:?}, apy: {:?}",
+ deposit_cap, reward.apy);
+ let apy = Self::calculate_propotional_apy(total_deposit, deposit_cap, reward.apy)
+ .ok_or(Error::::CannotCalculatePropotionalApy)?;
+ log::debug!(target: LOG_TARGET, "Calculated propotional apy: {:?}", apy);
+
+ // Calculate total rewards pool from total issuance
+ let tnt_total_supply = T::Currency::total_issuance();
+ log::debug!(target: LOG_TARGET, "tnt_total_supply: {:?}", tnt_total_supply);
+
+ let total_annual_rewards = apy.mul_floor(tnt_total_supply);
+
+ // Calculate decay factor based on time since last claim
+ let decay_factor = Self::calculate_decay_factor(
+ frame_system::Pallet::::block_number(),
+ last_claim.map(|(block, _)| block).unwrap_or_default(),
+ );
+ log::debug!(target: LOG_TARGET, "total annual rewards before decay: {:?}", total_annual_rewards);
+ log::debug!(target: LOG_TARGET, "decay_factor: {:?}", decay_factor);
+
+ // Apply decay to total rewards
+ let total_annual_rewards = decay_factor.mul_floor(total_annual_rewards);
+ log::debug!(target: LOG_TARGET, "total annual rewards after decay: {:?}", total_annual_rewards);
+
+ // Calculate per block reward pool first to minimize precision loss
+ let total_reward_per_block = Self::calculate_reward_per_block(total_annual_rewards)
+ .ok_or(Error::::CannotCalculateRewardPerBlock)?;
+ log::debug!(target: LOG_TARGET, "total_reward_per_block: {:?}", total_reward_per_block);
+
+ // Start with unlocked amount as base score
+ let user_unlocked_score = deposit.unlocked_amount;
+ let user_score = user_unlocked_score;
+
+ // Get the current block and calculate last claim block
+ let current_block = frame_system::Pallet::::block_number();
+ let last_claim_block = last_claim.map(|(block, _)| block).unwrap_or(current_block);
+ let blocks_to_be_paid = current_block.saturating_sub(last_claim_block);
+ log::debug!(target: LOG_TARGET,
+ "Current Block {:?}, Last Claim Block {:?}, Blocks to be paid {:?}",
+ current_block,
+ last_claim_block,
+ blocks_to_be_paid
+ );
+
+ log::debug!(target: LOG_TARGET, "User unlocked score {:?}", user_score);
+
+ // array of (score, blocks)
+ let mut user_rewards_score_by_blocks: Vec<(BalanceOf, BlockNumberFor)> = vec![];
+ user_rewards_score_by_blocks.push((user_unlocked_score, blocks_to_be_paid));
+
+ // Add score with lock multipliers if any
+ // only if the admin has enabled boost multiplier for the vault
+ if reward.boost_multiplier.is_some() {
+ if let Some(locks) = deposit.amount_with_locks {
+ for lock in locks {
+ if lock.expiry_block > last_claim_block {
+ if lock.expiry_block > current_block {
+ // Calculate lock reward:
+ // amount * APY * lock_multiplier *
+ // (remaining_lock_time / total_lock_time)
+ let multiplier = BalanceOf::::from(lock.lock_multiplier.value());
+ let lock_score = lock.amount.saturating_mul(multiplier);
+ log::debug!(target: LOG_TARGET, "user lock has not expired and still active, lock_multiplier: {:?}, lock_score: {:?}", lock.lock_multiplier, lock_score);
+
+ user_rewards_score_by_blocks.push((lock_score, blocks_to_be_paid));
+ } else {
+ // the lock has expired, so we only apply the lock multiplier during the
+ // unexpired period
+ let multiplier = BalanceOf::::from(lock.lock_multiplier.value());
+ let lock_score = lock.amount.saturating_mul(multiplier);
+ let multiplier_applied_blocks =
+ lock.expiry_block.saturating_sub(last_claim_block);
+
+ log::debug!(target: LOG_TARGET, "user lock has partially expired, lock_multiplier: {:?}, lock_score: {:?}, multiplier_applied_blocks: {:?}, blocks_to_be_paid: {:?}",
+ lock.lock_multiplier, lock_score, multiplier_applied_blocks, blocks_to_be_paid);
+
+ user_rewards_score_by_blocks
+ .push((lock_score, multiplier_applied_blocks));
+
+ // for rest of the blocks, we do not apply the lock multiplier
+ user_rewards_score_by_blocks.push((
+ lock.amount,
+ blocks_to_be_paid.saturating_sub(multiplier_applied_blocks),
+ ));
+ }
+ } else {
+ // if the lock has expired, we only consider the base score
+ user_rewards_score_by_blocks.push((lock.amount, blocks_to_be_paid));
+ }
+ }
+ }
+ }
+
+ log::debug!(target: LOG_TARGET, "user rewards array {:?}", user_rewards_score_by_blocks);
+
+ // if the user has no score, return 0
+ // calculate the total score for the user
+ let total_score_for_user = user_rewards_score_by_blocks
+ .iter()
+ .fold(BalanceOf::::zero(), |acc, (score, _blocks)| acc.saturating_add(*score));
+ log::debug!(target: LOG_TARGET, "total score: {:?}", total_score_for_user);
+ ensure!(!total_score_for_user.is_zero(), Error::::NoRewardsAvailable);
+
+ // Calculate user's proportion of rewards based on their score
+
+ let mut total_rewards_to_be_paid_to_user = BalanceOf::::zero();
+ for (score, blocks) in user_rewards_score_by_blocks {
+ let user_proportion = Percent::from_rational(score, total_asset_score);
+ log::debug!(target: LOG_TARGET, "user_proportion: {:?}", user_proportion);
+ let user_reward_per_block = user_proportion.mul_floor(total_reward_per_block);
+
+ // Calculate total rewards for the period
+ log::debug!(target: LOG_TARGET, "last_claim_block: {:?}, total_reward_per_block: {:?}, user reward per block: {:?}, blocks: {:?}",
+ last_claim_block, total_reward_per_block, user_reward_per_block, blocks);
+
+ let rewards_to_be_paid = user_reward_per_block
+ .saturating_mul(BalanceOf::::from(blocks.saturated_into::()));
+
+ log::debug!(target: LOG_TARGET, "rewards_to_be_paid: {:?}", rewards_to_be_paid);
+
+ total_rewards_to_be_paid_to_user =
+ total_rewards_to_be_paid_to_user.saturating_add(rewards_to_be_paid);
+ }
+
+ log::debug!(target: LOG_TARGET, "total_rewards_to_be_paid_to_user: {:?}", total_rewards_to_be_paid_to_user);
+ Ok(total_rewards_to_be_paid_to_user)
+ }
+}
diff --git a/pallets/rewards/src/impls.rs b/pallets/rewards/src/impls.rs
index 7bd3a1aa8..3d67d69c0 100644
--- a/pallets/rewards/src/impls.rs
+++ b/pallets/rewards/src/impls.rs
@@ -14,18 +14,15 @@
// You should have received a copy of the GNU General Public License
// along with Tangle. If not, see .
-use crate::AssetLookupRewardVaults;
-use crate::BalanceOf;
-use crate::Error;
-use crate::RewardConfigStorage;
-use crate::TotalRewardVaultScore;
-use crate::UserServiceReward;
-use crate::{Config, Pallet};
+use crate::{
+ AssetLookupRewardVaults, BalanceOf, Config, Error, Event, Pallet, RewardConfigStorage,
+ TotalRewardVaultDeposit, TotalRewardVaultScore, UserClaimedReward, UserServiceReward,
+};
use frame_system::pallet_prelude::BlockNumberFor;
-use sp_runtime::traits::Saturating;
-use sp_runtime::DispatchError;
-use tangle_primitives::types::rewards::LockMultiplier;
-use tangle_primitives::{services::Asset, traits::rewards::RewardsManager};
+use sp_runtime::{traits::Saturating, DispatchError};
+use tangle_primitives::{
+ services::Asset, traits::rewards::RewardsManager, types::rewards::LockMultiplier,
+};
impl RewardsManager, BlockNumberFor>
for Pallet
@@ -33,17 +30,54 @@ impl RewardsManager, BlockNumb
type Error = DispatchError;
fn record_deposit(
- _account_id: &T::AccountId,
+ account_id: &T::AccountId,
asset: Asset,
amount: BalanceOf,
- _lock_multiplier: Option,
+ lock_multiplier: Option,
) -> Result<(), Self::Error> {
// find the vault for the asset id
// if the asset is not in a reward vault, do nothing
if let Some(vault_id) = AssetLookupRewardVaults::::get(asset) {
+ // Update the reward vault deposit
+ let deposit = TotalRewardVaultDeposit::::get(vault_id).saturating_add(amount);
+ TotalRewardVaultDeposit::::insert(vault_id, deposit);
+
+ // emit event
+ Self::deposit_event(Event::TotalDepositUpdated {
+ vault_id,
+ asset,
+ total_deposit: deposit,
+ });
+
// Update the reward vault score
- let score = TotalRewardVaultScore::::get(vault_id).saturating_add(amount);
- TotalRewardVaultScore::::insert(vault_id, score);
+ let score = if let Some(lock_multiplier) = lock_multiplier {
+ amount.saturating_mul(lock_multiplier.value().into())
+ } else {
+ amount
+ };
+
+ let new_score = TotalRewardVaultScore::::get(vault_id).saturating_add(score);
+ TotalRewardVaultScore::::insert(vault_id, new_score);
+
+ // emit event
+ Self::deposit_event(Event::TotalScoreUpdated {
+ vault_id,
+ total_score: new_score,
+ asset,
+ lock_multiplier,
+ });
+
+ // If this user has never claimed rewards, create an entry
+ // this will give us a starting point for reward claim
+ if !UserClaimedReward::::contains_key(account_id, vault_id) {
+ let current_block = frame_system::Pallet::::block_number();
+ let default_balance: BalanceOf = 0_u32.into();
+ UserClaimedReward::::insert(
+ account_id,
+ vault_id,
+ (current_block, default_balance),
+ );
+ }
}
Ok(())
}
@@ -56,6 +90,17 @@ impl RewardsManager, BlockNumb
// find the vault for the asset id
// if the asset is not in a reward vault, do nothing
if let Some(vault_id) = AssetLookupRewardVaults::::get(asset) {
+ // Update the reward vault deposit
+ let deposit = TotalRewardVaultDeposit::::get(vault_id).saturating_sub(amount);
+ TotalRewardVaultDeposit::::insert(vault_id, deposit);
+
+ // emit event
+ Self::deposit_event(Event::TotalDepositUpdated {
+ vault_id,
+ asset,
+ total_deposit: deposit,
+ });
+
// Update the reward vault score
let score = TotalRewardVaultScore::::get(vault_id).saturating_sub(amount);
TotalRewardVaultScore::::insert(vault_id, score);
diff --git a/pallets/rewards/src/lib.rs b/pallets/rewards/src/lib.rs
index 46cf5236f..f4593419d 100644
--- a/pallets/rewards/src/lib.rs
+++ b/pallets/rewards/src/lib.rs
@@ -16,13 +16,15 @@
//! # Rewards Pallet
//!
-//! A flexible reward distribution system that supports multiple vaults with configurable reward parameters.
+//! A flexible reward distribution system that supports multiple vaults with configurable reward
+//! parameters.
//!
//! ## Overview
//!
-//! The Rewards pallet provides a mechanism for distributing rewards to users who deposit assets into
-//! various vaults. Each vault can have its own reward configuration, including APY rates and deposit caps.
-//! The system supports both unlocked deposits and locked deposits with multipliers for longer lock periods.
+//! The Rewards pallet provides a mechanism for distributing rewards to users who deposit assets
+//! into various vaults. Each vault can have its own reward configuration, including APY rates and
+//! deposit caps. The system supports both unlocked deposits and locked deposits with multipliers
+//! for longer lock periods.
//!
//! ## Reward Vaults
//!
@@ -36,16 +38,11 @@
//!
//! Rewards are calculated based on several factors:
//!
-//! 1. Base Rewards:
-//! ```text
-//! Base Reward = APY * (user_deposit / total_deposits) * (total_deposits / deposit_capacity)
-//! ```
+//! 1. Base Rewards: ```text Base Reward = APY * (user_deposit / total_deposits) * (total_deposits /
+//! deposit_capacity) ```
//!
-//! 2. Locked Deposits:
-//! For locked deposits, additional rewards are calculated using:
-//! ```text
-//! Lock Reward = Base Reward * lock_multiplier * (remaining_lock_time / total_lock_time)
-//! ```
+//! 2. Locked Deposits: For locked deposits, additional rewards are calculated using: ```text Lock
+//! Reward = Base Reward * lock_multiplier * (remaining_lock_time / total_lock_time) ```
//!
//! Lock multipliers increase rewards based on lock duration:
//! - One Month: 1.1x
@@ -55,9 +52,8 @@
//!
//! ## Notes
//!
-//! - The reward vaults will consider all assets in parity, so only add the same type of asset in the same vault.
-//!
-//!
+//! - The reward vaults will consider all assets in parity, so only add the same type of asset in
+//! the same vault.
#![cfg_attr(not(feature = "std"), no_std)]
pub use pallet::*;
@@ -80,6 +76,7 @@ pub mod types;
pub use types::*;
pub mod functions;
pub mod impls;
+
use sp_std::vec::Vec;
use tangle_primitives::BlueprintId;
@@ -92,9 +89,9 @@ pub mod pallet {
traits::{Currency, LockableCurrency, ReservableCurrency},
PalletId,
};
-
use frame_system::pallet_prelude::*;
- use sp_runtime::traits::AccountIdConversion;
+ use sp_runtime::{traits::AccountIdConversion, Percent};
+ use tangle_primitives::rewards::LockMultiplier;
#[pallet::config]
pub trait Config: frame_system::Config {
@@ -138,12 +135,20 @@ pub mod pallet {
#[pallet::without_storage_info]
pub struct Pallet(_);
- /// Stores the total score for each asset
+ /// Stores the total score for each vault
+ /// The difference between this and total_reward_vault_deposit is that this includes locked
+ /// deposits multiplied by the lock multiplier
#[pallet::storage]
#[pallet::getter(fn total_reward_vault_score)]
pub type TotalRewardVaultScore =
StorageMap<_, Blake2_128Concat, T::VaultId, BalanceOf, ValueQuery>;
+ /// Stores the total deposit for each vault
+ #[pallet::storage]
+ #[pallet::getter(fn total_reward_vault_deposit)]
+ pub type TotalRewardVaultDeposit =
+ StorageMap<_, Blake2_128Concat, T::VaultId, BalanceOf, ValueQuery>;
+
/// Stores the service reward for a given user
#[pallet::storage]
#[pallet::getter(fn user_reward_score)]
@@ -192,34 +197,70 @@ pub mod pallet {
OptionQuery,
>;
+ #[pallet::storage]
+ #[pallet::getter(fn reward_vaults_pot_account)]
+ /// Storage for the reward vaults
+ pub type RewardVaultsPotAccount =
+ StorageMap<_, Blake2_128Concat, T::VaultId, T::AccountId, OptionQuery>;
+
+ #[pallet::storage]
+ #[pallet::getter(fn blocks_for_apy)]
+ /// Storage for the reward configuration, which includes APY, cap for assets
+ pub type ApyBlocks = StorageValue<_, BlockNumberFor, ValueQuery>;
+
+ #[pallet::storage]
+ #[pallet::getter(fn decay_start_period)]
+ /// Number of blocks after which decay starts (e.g., 432000 for 30 days with 6s blocks)
+ pub type DecayStartPeriod = StorageValue<_, BlockNumberFor, ValueQuery>;
+
+ #[pallet::storage]
+ #[pallet::getter(fn decay_rate)]
+ /// Per-block decay rate in basis points (1/10000). e.g., 1 = 0.01% per block
+ pub type DecayRate = StorageValue<_, Percent, ValueQuery>;
+
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event {
/// Rewards have been claimed by an account
- RewardsClaimed {
- account: T::AccountId,
- asset: Asset,
- amount: BalanceOf,
- },
+ RewardsClaimed { account: T::AccountId, asset: Asset, amount: BalanceOf },
/// Event emitted when an incentive APY and cap are set for a reward vault
- IncentiveAPYAndCapSet {
- vault_id: T::VaultId,
- apy: sp_runtime::Percent,
- cap: BalanceOf,
- },
+ IncentiveAPYAndCapSet { vault_id: T::VaultId, apy: sp_runtime::Percent, cap: BalanceOf },
/// Event emitted when a blueprint is whitelisted for rewards
- BlueprintWhitelisted {
- blueprint_id: BlueprintId,
- },
+ BlueprintWhitelisted { blueprint_id: BlueprintId },
/// Asset has been updated to reward vault
AssetUpdatedInVault {
vault_id: T::VaultId,
asset_id: Asset,
action: AssetAction,
},
+ /// Vault reward config updated
VaultRewardConfigUpdated {
vault_id: T::VaultId,
+ new_config: RewardConfigForAssetVault