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

Fees for substrate #375

Merged
merged 39 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ead5d03
WIP: fees for substrate
Nutomic Mar 21, 2023
352e528
Merge branch 'develop' into felix/substrate-fees
shekohex Mar 21, 2023
a7fa47e
more work on substrate fees
Nutomic Mar 24, 2023
c370faa
fix dkd node connection (#378)
salman01zp Mar 27, 2023
38c1a6c
some more progress
Nutomic Mar 28, 2023
574e9c4
Merge branch 'develop' into felix/substrate-fees
Nutomic Mar 30, 2023
ad7f66c
wip
Nutomic Apr 3, 2023
95d3855
Merge branch 'develop' into felix/substrate-fees
Nutomic Apr 11, 2023
87ef042
update deps for node 18
Nutomic Apr 17, 2023
39b4d11
Merge branch 'develop' into felix/substrate-fees
Nutomic Apr 17, 2023
49cb458
Revert "update deps for node 18"
Nutomic Apr 18, 2023
c9db26b
Merge branch 'develop' into felix/substrate-fees
shekohex Apr 28, 2023
88a9a61
Merge branch 'develop' into felix/substrate-fees
shekohex Apr 28, 2023
fb152ee
fix fees
shekohex Apr 28, 2023
a9de09c
Merge remote-tracking branch 'origin/felix/substrate-fees' into felix…
Nutomic May 2, 2023
143777b
Merge branch 'develop' into felix/substrate-fees
Nutomic May 2, 2023
d03a039
node version
Nutomic May 2, 2023
d87b315
fixing test
Nutomic May 3, 2023
32a9e28
more fixes
Nutomic May 3, 2023
8a2f721
cleanup dbg! statements
Nutomic May 4, 2023
3555918
changes to get tests partially working
Nutomic May 4, 2023
bc5a7d4
type errors fixed
Nutomic May 5, 2023
67fff26
fix fee amount
Nutomic May 8, 2023
e8184af
attempt to fix signature issue
Nutomic May 9, 2023
57ff74f
Merge branch 'develop' into felix/substrate-fees
drewstone May 9, 2023
94f4471
remove unwraps, fix clippy
Nutomic May 10, 2023
4a80b88
cleanup substrate balance code
Nutomic May 10, 2023
f15d1fb
update tests
salman01zp May 10, 2023
a06e643
Merge branch 'felix/substrate-fees' of https://github.com/webb-tools/…
salman01zp May 10, 2023
8e818d6
remove comment, add .env to gitignore
Nutomic May 10, 2023
34390b0
use i128
Nutomic May 10, 2023
c48022f
remove comment
Nutomic May 10, 2023
fba419b
Merge branch 'develop' into felix/substrate-fees
shekohex May 10, 2023
3daace3
fix tx payload
salman01zp May 10, 2023
32ffa41
Merge branch 'develop' into felix/substrate-fees
drewstone May 11, 2023
6838cd9
update test and fix ext data hash issue
salman01zp May 12, 2023
45a0359
cargo clippy
salman01zp May 12, 2023
1bfe27e
add webbI128 type
salman01zp May 12, 2023
70dfb79
recalculate max_refund when returning cached evm fee value
Nutomic May 12, 2023
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
1 change: 1 addition & 0 deletions Cargo.lock

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

12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -488,18 +488,20 @@ cargo test

### To run E2E tests

First you will need [`protocol-substrate`](https://github.com/webb-tools/protocol-substrate) node, compiled locally (in release mode) and both the `protocol-substrate` and `relayer` project must be next to each other. The relayer must be compiled using `--features integration-tests,cli`.
First you will need [`protocol-substrate`](https://github.com/webb-tools/protocol-substrate) and [`tangle`](https://github.com/webb-tools/tangle) nodes, compiled locally (in release mode) and both the `protocol-substrate` and `relayer` project must be next to each other. The relayer must be compiled using `--features integration-tests,cli`.

Here is the basic setup you will need:

1. Clone the Relayer repo `git clone https://github.com/webb-tools/relayer.git`
2. Clone Protocol Substrate node `https://github.com/webb-tools/protocol-substrate.git`
3. Then fetch the submodules for the node `cd protocol-substrate && git submodule update --init`
4. While you are there, build the standalone node `cargo build --release -p webb-standalone-node`
5. And then go back to the relayer `cd ../relayer`
6. Run `cd tests && dvc pull`
7. Run `yarn install` (in `tests` dir)
8. `yarn test`
5. Clone Tangle repo `https://github.com/webb-tools/tangle.git`
6. Build tangle `cd tangle && cargo build --release --features integration-tests -p tangle-standalone`
7. And then go back to the relayer `cd ../relayer`
8. Run `cd tests && dvc pull`
9. Run `yarn install` (in `tests` dir)
10. `yarn test`

### Tips for E2E tests

Expand Down
24 changes: 21 additions & 3 deletions crates/relayer-handler-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
use serde::{Deserialize, Deserializer, Serialize};
use tokio::sync::mpsc;
use webb::evm::ethers::abi::Address;
use webb::evm::ethers::prelude::{ContractError, I256};
use webb::evm::ethers::prelude::{ContractError, I256, U128};
use webb::evm::ethers::providers::Middleware;
use webb::evm::ethers::types::Bytes;
use webb::evm::ethers::types::{H256, U256};
Expand Down Expand Up @@ -52,6 +52,24 @@ impl<'de> Deserialize<'de> for WebbI256 {
Ok(WebbI256(i128_val))
}
}
/// A wrapper type around [`i128`] that implements a correct way for [`Serialize`] and [`Deserialize`].
///
/// This supports the signed integer hex values that are not originally supported by the [`i128`] type.
#[derive(Debug, Clone, Serialize)]
#[serde(transparent)]
pub struct WebbI128(pub i128);

impl<'de> Deserialize<'de> for WebbI128 {
fn deserialize<D>(deserializer: D) -> Result<WebbI128, D::Error>
where
D: Deserializer<'de>,
{
let i128_str = String::deserialize(deserializer)?;
let value = i128::from_str_radix(&i128_str, 16)
.map_err(serde::de::Error::custom)?;
Ok(WebbI128(value))
}
}

/// Type of Command to use
#[derive(Debug, Clone, Deserialize)]
Expand Down Expand Up @@ -170,8 +188,8 @@ type P = Vec<u8>; // Substrate raw proof bytes
type R = Vec<[u8; 32]>; // Substrate roots format
type E = [u8; 32]; // Substrate element type
type I = AccountId32; // Substrate account identifier
type B = u128; // Substrate balance type
type A = i128; // Substrate signed amount type
type B = U128; // Substrate balance type
type A = WebbI128; // Substrate signed amount type
type T = u32; // Substrate assetId

/// The command type for Substrate mixer txes
Expand Down
34 changes: 29 additions & 5 deletions crates/relayer-handlers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use std::sync::Arc;
use futures::prelude::*;

use axum::extract::ws::{Message, WebSocket};
use axum::http::StatusCode;
use axum::response::Response;
use axum::Json;
use axum_client_ip::InsecureClientIp;
Expand All @@ -36,9 +37,12 @@ use webb_relayer_handler_utils::{
Command, CommandResponse, CommandStream, EvmCommandType,
IpInformationResponse, SubstrateCommandType,
};
use webb_relayer_tx_relay::evm::fees::{get_fee_info, FeeInfo};
use webb_relayer_tx_relay::evm::fees::{get_evm_fee_info, EvmFeeInfo};

use webb_relayer_tx_relay::evm::vanchor::handle_vanchor_relay_tx;
use webb_relayer_tx_relay::substrate::fees::{
get_substrate_fee_info, SubstrateFeeInfo,
};
use webb_relayer_tx_relay::substrate::mixer::handle_substrate_mixer_relay_tx;
use webb_relayer_tx_relay::substrate::vanchor::handle_substrate_vanchor_relay_tx;
use webb_relayer_utils::HandlerError;
Expand Down Expand Up @@ -201,13 +205,33 @@ pub async fn handle_cmd(
/// * `vanchor` - Address of the smart contract
/// * `gas_amount` - How much gas the transaction needs. Don't use U256 here because it
/// gets parsed incorrectly.
pub async fn handle_fee_info(
pub async fn handle_evm_fee_info(
State(ctx): State<Arc<RelayerContext>>,
Path((chain_id, vanchor, gas_amount)): Path<(u64, Address, u64)>,
) -> Result<Json<FeeInfo>, HandlerError> {
) -> Result<Json<EvmFeeInfo>, HandlerError> {
let chain_id = TypedChainId::from(chain_id);
let gas_amount = U256::from(gas_amount);
Ok(get_fee_info(chain_id, vanchor, gas_amount, ctx.as_ref())
Ok(
get_evm_fee_info(chain_id, vanchor, gas_amount, ctx.as_ref())
.await
.map(Json)?,
)
}

/// Handler for fee estimation
///
/// # Arguments
/// * `chain_id` - ID of the blockchain
/// * `estimated_tx_fees` - Estimated transaction fees
/// * `ctx` - RelayContext reference that holds the configuration
pub async fn handle_substrate_fee_info(
State(ctx): State<Arc<RelayerContext>>,
Path((chain_id, estimated_tx_fees)): Path<(u64, u128)>,
) -> Result<Json<SubstrateFeeInfo>, HandlerError> {
get_substrate_fee_info(chain_id, estimated_tx_fees.into(), ctx.as_ref())
.await
.map(Json)?)
.map(Json)
.map_err(|e| {
HandlerError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
})
}
1 change: 1 addition & 0 deletions crates/tx-relay/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ native-tls = { workspace = true, optional = true }
webb-proposals = { workspace = true }
ethereum-types = { workspace = true }
serde = { workspace = true }
sp-core = { workspace = true }

once_cell = "1.17.0"
chrono = { version = "0.4.23", features = ["serde"] }
Expand Down
71 changes: 51 additions & 20 deletions crates/tx-relay/src/evm/fees.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::{MAX_REFUND_USD, TRANSACTION_PROFIT_USD};
shekohex marked this conversation as resolved.
Show resolved Hide resolved
use chrono::DateTime;
use chrono::Duration;
use chrono::Utc;
use once_cell::sync::Lazy;
use serde::Serialize;
use std::cmp::min;
use std::collections::HashMap;
use std::ops::Add;
use std::sync::{Arc, Mutex};
Expand All @@ -11,6 +13,8 @@ use webb::evm::contract::protocol_solidity::{
};
use webb::evm::ethers::middleware::gas_oracle::GasOracle;
use webb::evm::ethers::prelude::U256;
use webb::evm::ethers::providers::Middleware;
use webb::evm::ethers::signers::Signer;
use webb::evm::ethers::types::Address;
use webb::evm::ethers::utils::{format_units, parse_units};
use webb_chains_info::chain_info_by_chain_id;
Expand All @@ -19,22 +23,19 @@ use webb_proposals::TypedChainId;
use webb_relayer_context::RelayerContext;
use webb_relayer_utils::Result;

/// Maximum refund amount per relay transaction in USD.
const MAX_REFUND_USD: f64 = 5.;
/// Amount of time for which a `FeeInfo` is valid after creation
static FEE_CACHE_TIME: Lazy<Duration> = Lazy::new(|| Duration::minutes(1));
/// Amount of profit that the relay should make with each transaction (in USD).
const TRANSACTION_PROFIT_USD: f64 = 5.;

/// Cache for previously generated fee info. Key consists of the VAnchor address and chain id.
/// Entries are valid as long as `timestamp` is no older than `FEE_CACHE_TIME`.
static FEE_INFO_CACHED: Lazy<Mutex<HashMap<(Address, TypedChainId), FeeInfo>>> =
Lazy::new(|| Mutex::new(HashMap::new()));
static FEE_INFO_CACHED: Lazy<
Mutex<HashMap<(Address, TypedChainId), EvmFeeInfo>>,
> = Lazy::new(|| Mutex::new(HashMap::new()));

/// Return value of fee_info API call. Contains information about relay transaction fee and refunds.
#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct FeeInfo {
pub struct EvmFeeInfo {
/// Estimated fee for an average relay transaction, in `wrappedToken`. This is only for
/// display to the user
pub estimated_fee: U256,
Expand All @@ -49,6 +50,9 @@ pub struct FeeInfo {
/// Price of the native token in USD, internally cached to recalculate estimated fee
#[serde(skip)]
native_token_price: f64,
/// Number of decimals of the native token, internally cached to recalculate max refund
#[serde(skip)]
native_token_decimals: u8,
/// Price of the wrapped token in USD, internally cached to recalculate estimated fee
#[serde(skip)]
wrapped_token_price: f64,
Expand All @@ -61,12 +65,12 @@ pub struct FeeInfo {
///
/// If fee info was recently requested, the cached value is used. Otherwise it is regenerated
/// based on the current exchange rate and estimated gas price.
pub async fn get_fee_info(
pub async fn get_evm_fee_info(
chain_id: TypedChainId,
vanchor: Address,
gas_amount: U256,
ctx: &RelayerContext,
) -> Result<FeeInfo> {
) -> Result<EvmFeeInfo> {
// Retrieve cached fee info item
let fee_info_cached = {
let mut lock =
Expand All @@ -89,6 +93,14 @@ pub async fn get_fee_info(
fee_info.wrapped_token_price,
fee_info.wrapped_token_decimals,
)?;
// Recalculate max refund in case relayer balance changed.
fee_info.max_refund = max_refund(
chain_id,
fee_info.native_token_price,
fee_info.native_token_decimals,
ctx,
)
.await?;
Ok(fee_info)
} else {
let fee_info =
Expand All @@ -109,7 +121,7 @@ async fn generate_fee_info(
vanchor: Address,
gas_amount: U256,
ctx: &RelayerContext,
) -> Result<FeeInfo> {
) -> Result<EvmFeeInfo> {
// Get token names
let (native_token, native_token_decimals) =
get_native_token_name_and_decimals(chain_id)?;
Expand Down Expand Up @@ -162,25 +174,44 @@ async fn generate_fee_info(
)?
.into();

// Calculate the maximum refund amount per relay transaction in `nativeToken`.
let max_refund = parse_units(
MAX_REFUND_USD / native_token_price,
u32::from(native_token_decimals),
)?
.into();

Ok(FeeInfo {
Ok(EvmFeeInfo {
estimated_fee,
gas_price,
refund_exchange_rate,
max_refund,
max_refund: max_refund(
chain_id,
native_token_price,
native_token_decimals,
ctx,
)
.await?,
timestamp: Utc::now(),
native_token_price,
native_token_decimals,
wrapped_token_price,
wrapped_token_decimals,
})
}

async fn max_refund(
chain_id: TypedChainId,
native_token_price: f64,
native_token_decimals: u8,
ctx: &RelayerContext,
) -> Result<U256> {
let wallet = ctx.evm_wallet(chain_id.underlying_chain_id()).await?;
let provider = ctx.evm_provider(chain_id.underlying_chain_id()).await?;
let relayer_balance = provider.get_balance(wallet.address(), None).await?;
// Calculate the maximum refund amount per relay transaction in `nativeToken`.
// Ensuring that refund <= relayer balance
let max_refund = parse_units(
MAX_REFUND_USD / native_token_price,
u32::from(native_token_decimals),
)?
.into();
Ok(min(relayer_balance, max_refund))
}

/// Pull USD prices of base token from coingecko.com, and use this to calculate the transaction
/// fee in `wrappedToken` wei. This fee includes a profit for the relay of `TRANSACTION_PROFIT_USD`.
///
Expand Down Expand Up @@ -265,7 +296,7 @@ fn get_native_token_name_and_decimals(
},
),
Substrate(id) => match id {
1080 => Ok(("tTNT", 18)),
1081 => Ok(("tTNT", 18)),
_ => {
// During testing, we will use the tTNT token for all substrate chains.
if cfg!(debug_assertions) {
Expand Down
29 changes: 12 additions & 17 deletions crates/tx-relay/src/evm/vanchor.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use super::*;
use crate::evm::fees::{get_fee_info, FeeInfo};
use crate::evm::fees::{get_evm_fee_info, EvmFeeInfo};
use crate::evm::handle_evm_tx;
use ethereum_types::U256;
use futures::TryFutureExt;
use std::{collections::HashMap, sync::Arc};
use webb::evm::ethers::utils::{format_units, parse_ether};
use webb::evm::{
Expand Down Expand Up @@ -81,19 +82,6 @@ pub async fn handle_vanchor_relay_tx<'a>(
})?;
let _ = stream.send(Network(NetworkStatus::Connected)).await;

// ensure that relayer has enough balance for refund
let relayer_balance = provider
.get_balance(wallet.address(), None)
.await
.map_err(|e| {
Error(format!("Failed to retrieve relayer balance: {e}"))
})?;
if cmd.ext_data.refund > relayer_balance {
return Err(Error(
"Requested refund is higher than relayer balance".to_string(),
));
}

salman01zp marked this conversation as resolved.
Show resolved Hide resolved
let client = Arc::new(SignerMiddleware::new(provider, wallet));
let contract = VAnchorContract::new(cmd.id, client.clone());

Expand Down Expand Up @@ -142,6 +130,7 @@ pub async fn handle_vanchor_relay_tx<'a>(
public_inputs,
encryptions,
);

if !cmd.ext_data.refund.is_zero() {
call = call.value(cmd.ext_data.refund);
}
Expand All @@ -153,7 +142,7 @@ pub async fn handle_vanchor_relay_tx<'a>(
})
})?;
let typed_chain_id = TypedChainId::Evm(chain.chain_id);
let fee_info = get_fee_info(
let fee_info = get_evm_fee_info(
typed_chain_id,
contract_config.common.address,
gas_amount,
Expand Down Expand Up @@ -186,7 +175,8 @@ pub async fn handle_vanchor_relay_tx<'a>(
if cmd.ext_data.fee < adjusted_fee + wrapped_amount {
let msg = format!(
"User sent a fee that is too low {} but expected {}",
cmd.ext_data.fee, fee_info.estimated_fee
cmd.ext_data.fee,
adjusted_fee + wrapped_amount
);
return Err(Error(msg));
}
Expand Down Expand Up @@ -214,6 +204,11 @@ pub async fn handle_vanchor_relay_tx<'a>(
.total_fee_earned
.inc_by(cmd.ext_data.fee.as_u128() as f64);

let relayer_balance = client
.get_balance(client.signer().address(), None)
.unwrap_or_else(|_| U256::zero())
.await;

metrics
.account_balance_entry(typed_chain_id)
.set(wei_to_gwei(relayer_balance));
Expand All @@ -222,7 +217,7 @@ pub async fn handle_vanchor_relay_tx<'a>(

fn calculate_wrapped_refund_amount(
refund: U256,
fee_info: &FeeInfo,
fee_info: &EvmFeeInfo,
) -> webb_relayer_utils::Result<U256> {
let refund_exchange_rate: f32 =
format_units(fee_info.refund_exchange_rate, "ether")?.parse()?;
Expand Down
5 changes: 5 additions & 0 deletions crates/tx-relay/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ pub mod evm;
/// Substrate Transactional Relayer.
#[cfg(feature = "substrate")]
pub mod substrate;

/// Maximum refund amount per relay transaction in USD.
const MAX_REFUND_USD: f64 = 5.;
/// Amount of profit that the relay should make with each transaction (in USD).
const TRANSACTION_PROFIT_USD: f64 = 5.;
Loading