Skip to content
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
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d8895c2
Initialize bridge V2
yrong Oct 8, 2024
10d7bcb
Revamp outbound for V2
yrong Oct 10, 2024
821ffdc
Extrinsic for submit delivery proof
yrong Oct 11, 2024
d5cadfa
Improve error & more tests
yrong Oct 14, 2024
0905c41
Move merkle impl to core
yrong Oct 14, 2024
7a37072
Revert FeeManager
yrong Oct 14, 2024
db634a3
Route first to V2 then fallback to V1 with a custom instruction Expec…
yrong Oct 16, 2024
0622b37
new pallet
claravanstaden Oct 17, 2024
738a2de
Inbound queue v2
yrong Oct 18, 2024
6211a8f
cleanup
claravanstaden Oct 18, 2024
bd66625
move rewards interface
claravanstaden Oct 18, 2024
a23e6c0
progress
claravanstaden Oct 18, 2024
c697baa
progress
claravanstaden Oct 18, 2024
7a178e7
more tests
claravanstaden Oct 18, 2024
88f33a2
more tests
claravanstaden Oct 18, 2024
99ba809
Fix fee amount
yrong Oct 20, 2024
f6b9968
Respect V2 contract
yrong Oct 20, 2024
2e827e8
More cleanup
yrong Oct 20, 2024
ca8e226
Comments
yrong Oct 20, 2024
ce0a056
tests
claravanstaden Oct 21, 2024
ec16524
Add blocknumber to LockedFee for easy scanning
yrong Oct 21, 2024
58de17b
Fix abi encode command
yrong Oct 22, 2024
9cd9890
Refactor merkle tree as a seperated package
yrong Oct 23, 2024
a0deaba
Seperate V1&V2 more clearly
yrong Oct 23, 2024
1b07e42
Merge branch 'master' into sno-1168-rewards-pallet
claravanstaden Oct 23, 2024
850fe96
Move governance origin to V2 package
yrong Oct 23, 2024
831ef06
fix tests
claravanstaden Oct 23, 2024
273b971
Fix breaking tests
yrong Oct 23, 2024
96040bf
Merge remote-tracking branch 'ron/v2' into sno-1168-rewards-pallet
claravanstaden Oct 23, 2024
154422d
Merge remote-tracking branch 'ron/inbound-queue-v2' into sno-1168-rew…
claravanstaden Oct 23, 2024
1329923
Merge remote-tracking branch 'ron/outbound-queue-v2' into sno-1168-re…
claravanstaden Oct 23, 2024
0b5e254
merge ron's work in
claravanstaden Oct 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ members = [
"bridges/snowbridge/pallets/outbound-queue",
"bridges/snowbridge/pallets/outbound-queue/merkle-tree",
"bridges/snowbridge/pallets/outbound-queue/runtime-api",
"bridges/snowbridge/pallets/rewards",
"bridges/snowbridge/pallets/system",
"bridges/snowbridge/pallets/system/runtime-api",
"bridges/snowbridge/primitives/beacon",
Expand Down
74 changes: 74 additions & 0 deletions bridges/snowbridge/pallets/rewards/Cargo.toml
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",
]
174 changes: 174 additions & 0 deletions bridges/snowbridge/pallets/rewards/src/lib.rs
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();
Copy link
Contributor

@yrong yrong Oct 21, 2024

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.


// 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(())
}
}
}
99 changes: 99 additions & 0 deletions bridges/snowbridge/pallets/rewards/src/mock.rs
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
}
Loading
Loading