-
Notifications
You must be signed in to change notification settings - Fork 781
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Snowbridge V2 - Rewards Pallet #6117
Closed
claravanstaden
wants to merge
32
commits into
paritytech:master
from
claravanstaden:sno-1168-rewards-pallet
Closed
Changes from 7 commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
d8895c2
Initialize bridge V2
yrong 10d7bcb
Revamp outbound for V2
yrong 821ffdc
Extrinsic for submit delivery proof
yrong d5cadfa
Improve error & more tests
yrong 0905c41
Move merkle impl to core
yrong 7a37072
Revert FeeManager
yrong db634a3
Route first to V2 then fallback to V1 with a custom instruction Expec…
yrong 0622b37
new pallet
claravanstaden 738a2de
Inbound queue v2
yrong 6211a8f
cleanup
claravanstaden bd66625
move rewards interface
claravanstaden a23e6c0
progress
claravanstaden c697baa
progress
claravanstaden 7a178e7
more tests
claravanstaden 88f33a2
more tests
claravanstaden 99ba809
Fix fee amount
yrong f6b9968
Respect V2 contract
yrong 2e827e8
More cleanup
yrong ca8e226
Comments
yrong ce0a056
tests
claravanstaden ec16524
Add blocknumber to LockedFee for easy scanning
yrong 58de17b
Fix abi encode command
yrong 9cd9890
Refactor merkle tree as a seperated package
yrong a0deaba
Seperate V1&V2 more clearly
yrong 1b07e42
Merge branch 'master' into sno-1168-rewards-pallet
claravanstaden 850fe96
Move governance origin to V2 package
yrong 831ef06
fix tests
claravanstaden 273b971
Fix breaking tests
yrong 96040bf
Merge remote-tracking branch 'ron/v2' into sno-1168-rewards-pallet
claravanstaden 154422d
Merge remote-tracking branch 'ron/inbound-queue-v2' into sno-1168-rew…
claravanstaden 1329923
Merge remote-tracking branch 'ron/outbound-queue-v2' into sno-1168-re…
claravanstaden 0b5e254
merge ron's work in
claravanstaden File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
[package] | ||
name = "snowbridge-pallet-rewards" | ||
description = "Snowbridge Rewards Pallet" | ||
version = "0.1.0" | ||
authors = ["Snowfork <[email protected]>"] | ||
edition.workspace = true | ||
repository.workspace = true | ||
license = "Apache-2.0" | ||
categories = ["cryptography::cryptocurrencies"] | ||
|
||
[lints] | ||
workspace = true | ||
|
||
[package.metadata.docs.rs] | ||
targets = ["x86_64-unknown-linux-gnu"] | ||
|
||
[dependencies] | ||
codec = { features = [ | ||
"derive", | ||
], workspace = true } | ||
scale-info = { features = ["derive"], workspace = true } | ||
frame-benchmarking = { optional = true, workspace = true } | ||
frame-support = { workspace = true } | ||
frame-system = { workspace = true } | ||
log = { workspace = true } | ||
|
||
sp-core = { workspace = true } | ||
sp-std = { workspace = true } | ||
sp-io = { workspace = true } | ||
sp-runtime = { workspace = true } | ||
|
||
xcm = { workspace = true } | ||
xcm-executor = { workspace = true } | ||
|
||
snowbridge-core = { workspace = true } | ||
snowbridge-router-primitives = { workspace = true } | ||
|
||
[dev-dependencies] | ||
hex = { workspace = true, default-features = true } | ||
hex-literal = { workspace = true, default-features = true } | ||
sp-keyring = { workspace = true, default-features = true } | ||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"codec/std", | ||
"frame-benchmarking?/std", | ||
"frame-support/std", | ||
"frame-system/std", | ||
"log/std", | ||
"scale-info/std", | ||
"snowbridge-core/std", | ||
"snowbridge-router-primitives/std", | ||
"sp-core/std", | ||
"sp-io/std", | ||
"sp-runtime/std", | ||
"sp-std/std", | ||
"sp-keyring/std", | ||
"xcm-executor/std", | ||
"xcm/std", | ||
] | ||
runtime-benchmarks = [ | ||
"frame-benchmarking/runtime-benchmarks", | ||
"frame-support/runtime-benchmarks", | ||
"frame-system/runtime-benchmarks", | ||
"snowbridge-core/runtime-benchmarks", | ||
"sp-runtime/runtime-benchmarks", | ||
"xcm-executor/runtime-benchmarks", | ||
] | ||
try-runtime = [ | ||
"frame-support/try-runtime", | ||
"frame-system/try-runtime", | ||
"sp-runtime/try-runtime", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// SPDX-FileCopyrightText: 2023 Snowfork <[email protected]> | ||
#![cfg_attr(not(feature = "std"), no_std)] | ||
|
||
pub mod weights; | ||
|
||
#[cfg(test)] | ||
mod mock; | ||
|
||
#[cfg(test)] | ||
mod tests; | ||
|
||
use frame_support::PalletError; | ||
use frame_system::pallet_prelude::*; | ||
use snowbridge_core::rewards::RewardLedger; | ||
use sp_core::H160; | ||
pub use weights::WeightInfo; | ||
use xcm::prelude::{send_xcm, SendError as XcmpSendError, *}; | ||
|
||
pub use pallet::*; | ||
|
||
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId; | ||
#[frame_support::pallet] | ||
pub mod pallet { | ||
use super::*; | ||
use frame_support::pallet_prelude::*; | ||
use sp_core::H256; | ||
|
||
#[pallet::pallet] | ||
pub struct Pallet<T>(_); | ||
|
||
#[pallet::config] | ||
pub trait Config: frame_system::Config { | ||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; | ||
type AssetHubParaId: Get<u32>; | ||
type EthereumNetwork: Get<NetworkId>; | ||
type WethAddress: Get<H160>; | ||
/// XCM message sender | ||
type XcmSender: SendXcm; | ||
type WeightInfo: WeightInfo; | ||
} | ||
|
||
#[pallet::event] | ||
#[pallet::generate_deposit(pub(super) fn deposit_event)] | ||
pub enum Event<T: Config> { | ||
/// A relayer reward was deposited | ||
RewardDeposited { | ||
/// The relayer account to which the reward was deposited. | ||
account_id: AccountIdOf<T>, | ||
/// The reward value. | ||
value: u128, | ||
}, | ||
RewardClaimed { | ||
/// The relayer account that claimed the reward. | ||
account_id: AccountIdOf<T>, | ||
/// The address that received the reward on AH. | ||
deposit_address: AccountIdOf<T>, | ||
/// The claimed reward value. | ||
value: u128, | ||
/// The message ID that was provided, used to track the claim | ||
message_id: H256, | ||
}, | ||
} | ||
|
||
#[pallet::error] | ||
pub enum Error<T> { | ||
/// XCMP send failure | ||
Send(SendError), | ||
/// The relayer rewards balance is lower than the claimed amount. | ||
InsufficientFunds, | ||
} | ||
|
||
#[derive(Clone, Encode, Decode, Eq, PartialEq, Debug, TypeInfo, PalletError)] | ||
pub enum SendError { | ||
NotApplicable, | ||
NotRoutable, | ||
Transport, | ||
DestinationUnsupported, | ||
ExceedsMaxMessageSize, | ||
MissingArgument, | ||
Fees, | ||
} | ||
|
||
impl<T: Config> From<XcmpSendError> for Error<T> { | ||
fn from(e: XcmpSendError) -> Self { | ||
match e { | ||
XcmpSendError::NotApplicable => Error::<T>::Send(SendError::NotApplicable), | ||
XcmpSendError::Unroutable => Error::<T>::Send(SendError::NotRoutable), | ||
XcmpSendError::Transport(_) => Error::<T>::Send(SendError::Transport), | ||
XcmpSendError::DestinationUnsupported => | ||
Error::<T>::Send(SendError::DestinationUnsupported), | ||
XcmpSendError::ExceedsMaxMessageSize => | ||
Error::<T>::Send(SendError::ExceedsMaxMessageSize), | ||
XcmpSendError::MissingArgument => Error::<T>::Send(SendError::MissingArgument), | ||
XcmpSendError::Fees => Error::<T>::Send(SendError::Fees), | ||
} | ||
} | ||
} | ||
|
||
#[pallet::storage] | ||
pub type RewardsMapping<T: Config> = StorageMap<_, Identity, AccountIdOf<T>, u128, ValueQuery>; | ||
|
||
#[pallet::call] | ||
impl<T: Config> Pallet<T> { | ||
#[pallet::call_index(0)] | ||
#[pallet::weight((T::WeightInfo::claim(), DispatchClass::Operational))] | ||
pub fn claim( | ||
origin: OriginFor<T>, | ||
deposit_address: AccountIdOf<T>, | ||
value: u128, | ||
message_id: H256, | ||
) -> DispatchResult { | ||
let account_id = ensure_signed(origin)?; | ||
Self::process_claim(account_id, deposit_address, value, message_id)?; | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl<T: Config> Pallet<T> { | ||
fn process_claim( | ||
account_id: AccountIdOf<T>, | ||
deposit_address: AccountIdOf<T>, | ||
value: u128, | ||
message_id: H256, | ||
) -> DispatchResult { | ||
// Check if the claim value is equal to or less than the accumulated balance. | ||
let reward_balance = RewardsMapping::<T>::get(account_id.clone()); | ||
if value > reward_balance { | ||
return Err(Error::<T>::InsufficientFunds.into()); | ||
} | ||
|
||
let reward_asset = snowbridge_core::location::convert_token_address( | ||
T::EthereumNetwork::get(), | ||
T::WethAddress::get(), | ||
); | ||
let deposit: Asset = (reward_asset, value).into(); | ||
let beneficiary: Location = | ||
Location::new(0, Parachain(T::AssetHubParaId::get().into())); | ||
|
||
let xcm: Xcm<()> = vec![ | ||
DepositAsset { assets: Definite(deposit.into()), beneficiary }, | ||
SetTopic(message_id.into()), | ||
] | ||
.into(); | ||
|
||
// Deduct the reward from the claimable balance | ||
RewardsMapping::<T>::mutate(account_id.clone(), |current_value| { | ||
*current_value = current_value.saturating_sub(value); | ||
}); | ||
|
||
let dest = Location::new(1, [Parachain(T::AssetHubParaId::get().into())]); | ||
let (_xcm_hash, _) = send_xcm::<T::XcmSender>(dest, xcm).map_err(Error::<T>::from)?; | ||
|
||
Self::deposit_event(Event::RewardClaimed { | ||
account_id, | ||
deposit_address, | ||
value, | ||
message_id, | ||
}); | ||
Ok(()) | ||
} | ||
} | ||
|
||
impl<T: Config> RewardLedger<T> for Pallet<T> { | ||
fn deposit(account_id: AccountIdOf<T>, value: u128) -> DispatchResult { | ||
RewardsMapping::<T>::mutate(account_id.clone(), |current_value| { | ||
*current_value = current_value.saturating_add(value); | ||
}); | ||
Self::deposit_event(Event::RewardDeposited { account_id, value }); | ||
|
||
Ok(()) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// SPDX-FileCopyrightText: 2023 Snowfork <[email protected]> | ||
use super::*; | ||
|
||
use codec::Encode; | ||
use frame_support::{derive_impl, parameter_types}; | ||
use hex_literal::hex; | ||
use sp_core::{ConstU32, H160}; | ||
use sp_runtime::{ | ||
traits::{IdentifyAccount, IdentityLookup, Verify}, | ||
BuildStorage, MultiSignature, | ||
}; | ||
use sp_std::{convert::From, default::Default}; | ||
use xcm::{latest::SendXcm, prelude::*}; | ||
|
||
use crate::{self as snowbridge_pallet_rewards}; | ||
|
||
type Block = frame_system::mocking::MockBlock<Test>; | ||
|
||
frame_support::construct_runtime!( | ||
pub enum Test | ||
{ | ||
System: frame_system::{Pallet, Call, Storage, Event<T>}, | ||
EthereumRewards: snowbridge_pallet_rewards::{Pallet, Call, Storage, Event<T>}, | ||
} | ||
); | ||
|
||
pub type Signature = MultiSignature; | ||
pub type AccountId = <<Signature as Verify>::Signer as IdentifyAccount>::AccountId; | ||
|
||
|
||
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] | ||
impl frame_system::Config for Test { | ||
type AccountId = AccountId; | ||
type Lookup = IdentityLookup<Self::AccountId>; | ||
type Block = Block; | ||
} | ||
|
||
parameter_types! { | ||
pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 }; | ||
pub WethAddress: H160 = hex!("774667629726ec1FaBEbCEc0D9139bD1C8f72a23").into(); | ||
} | ||
|
||
impl snowbridge_pallet_rewards::Config for Test { | ||
type RuntimeEvent = RuntimeEvent; | ||
type AssetHubParaId = ConstU32<1000>; | ||
type EthereumNetwork = EthereumNetwork; | ||
type WethAddress = WethAddress; | ||
type XcmSender = MockXcmSender; | ||
type WeightInfo = (); | ||
} | ||
|
||
// Mock XCM sender that always succeeds | ||
pub struct MockXcmSender; | ||
|
||
impl SendXcm for MockXcmSender { | ||
type Ticket = Xcm<()>; | ||
|
||
fn validate( | ||
dest: &mut Option<Location>, | ||
xcm: &mut Option<Xcm<()>>, | ||
) -> SendResult<Self::Ticket> { | ||
if let Some(location) = dest { | ||
match location.unpack() { | ||
(_, [Parachain(1001)]) => return Err(XcmpSendError::NotApplicable), | ||
_ => Ok((xcm.clone().unwrap(), Assets::default())), | ||
} | ||
} else { | ||
Ok((xcm.clone().unwrap(), Assets::default())) | ||
} | ||
} | ||
|
||
fn deliver(xcm: Self::Ticket) -> core::result::Result<XcmHash, XcmpSendError> { | ||
let hash = xcm.using_encoded(sp_io::hashing::blake2_256); | ||
Ok(hash) | ||
} | ||
} | ||
|
||
pub const WETH: u128 = 1_000_000_000_000_000_000; | ||
|
||
pub fn last_events(n: usize) -> Vec<RuntimeEvent> { | ||
frame_system::Pallet::<Test>::events() | ||
.into_iter() | ||
.rev() | ||
.take(n) | ||
.rev() | ||
.map(|e| e.event) | ||
.collect() | ||
} | ||
|
||
pub fn expect_events(e: Vec<RuntimeEvent>) { | ||
assert_eq!(last_events(e.len()), e); | ||
} | ||
|
||
pub fn new_tester() -> sp_io::TestExternalities { | ||
let t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap(); | ||
let ext = sp_io::TestExternalities::new(t); | ||
ext | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Withdraw/Teleport + BuyExecution is required to pass the Barrier check for executing any XCM.