Skip to content

Commit

Permalink
more work on substrate fees
Browse files Browse the repository at this point in the history
  • Loading branch information
Nutomic committed Mar 24, 2023
1 parent 352e528 commit a7fa47e
Show file tree
Hide file tree
Showing 12 changed files with 203 additions and 56 deletions.
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: 4 additions & 8 deletions crates/relayer-handlers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,13 +220,9 @@ pub async fn handle_evm_fee_info(
}
pub async fn handle_substrate_fee_info(
State(ctx): State<Arc<RelayerContext>>,
Path(chain_id): Path<u64>,
Path((chain_id, estimated_tx_fees)): Path<(u64, u128)>,
) -> Result<Json<SubstrateFeeInfo>, HandlerError> {
let chain_id = TypedChainId::from(chain_id);
get_substrate_fee_info(chain_id, ctx.as_ref())
.await
.map(Json)
.map_err(|e| {
HandlerError(StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
})
Ok(Json(
get_substrate_fee_info(chain_id, estimated_tx_fees, ctx.as_ref()).await,
))
}
1 change: 1 addition & 0 deletions crates/tx-relay/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,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
36 changes: 32 additions & 4 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};
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,18 +13,18 @@ use webb::evm::contract::protocol_solidity::{
};
use webb::evm::ethers::middleware::SignerMiddleware;
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_proposals::TypedChainId;
use webb_relayer_context::RelayerContext;
use webb_relayer_handler_utils::CommandResponse::{Error, Network};
use webb_relayer_handler_utils::{CommandResponse, NetworkStatus};
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`.
Expand Down Expand Up @@ -154,9 +156,13 @@ async fn generate_fee_info(
.into();

// Calculate the maximum refund amount per relay transaction in `nativeToken`.
// Ensuring that refund <= relayer balance
let relayer_balance =
relayer_balance(chain_id.chain_id(), ctx).await.unwrap();
let max_refund =
parse_units(MAX_REFUND_USD / native_token_price, native_token.1)?
.into();
let max_refund = min(relayer_balance, max_refund);

Ok(EvmFeeInfo {
estimated_fee,
Expand All @@ -170,6 +176,28 @@ async fn generate_fee_info(
})
}

pub(super) async fn relayer_balance(
chain_id: u64,
ctx: &RelayerContext,
) -> std::result::Result<U256, CommandResponse> {
let wallet = ctx.evm_wallet(&chain_id.to_string()).await.map_err(|e| {
Error(format!("Misconfigured Network: {:?}, {e}", chain_id))
})?;
let provider =
ctx.evm_provider(&chain_id.to_string()).await.map_err(|e| {
Network(NetworkStatus::Failed {
reason: e.to_string(),
})
})?;
let relayer_balance = provider
.get_balance(wallet.address(), None)
.await
.map_err(|e| {
Error(format!("Failed to retrieve relayer balance: {e}"))
})?;
Ok(relayer_balance)
}

/// 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
9 changes: 2 additions & 7 deletions crates/tx-relay/src/evm/vanchor.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::*;
use crate::evm::fees::{get_evm_fee_info, EvmFeeInfo};
use crate::evm::fees::{get_evm_fee_info, relayer_balance, EvmFeeInfo};
use crate::evm::handle_evm_tx;
use ethereum_types::U256;
use std::{collections::HashMap, sync::Arc};
Expand Down Expand Up @@ -89,12 +89,7 @@ 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}"))
})?;
let relayer_balance = relayer_balance(requested_chain, &ctx).await?;
if cmd.ext_data.refund > relayer_balance {
return Err(Error(
"Requested refund is higher than relayer balance".to_string(),
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.;
64 changes: 57 additions & 7 deletions crates/tx-relay/src/substrate/fees.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,65 @@
use serde::Serialize;
use webb_proposals::TypedChainId;
use crate::{MAX_REFUND_USD, TRANSACTION_PROFIT_USD};
use chrono::{DateTime, Utc};
use serde::Deserialize;
use serde::{Deserializer, Serialize};
use std::str::FromStr;
use webb_relayer_context::RelayerContext;
use webb_relayer_utils::Result;

const TOKEN_PRICE_USD: f64 = 0.1;
const TOKEN_DECIMALS: i32 = 18;

#[derive(Debug, Serialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct SubstrateFeeInfo {}
pub struct SubstrateFeeInfo {
/// Estimated fee for an average relay transaction, in `wrappedToken`. Includes network fees
/// and relay fee.
pub estimated_fee: u128,
/// Exchange rate for refund from `wrappedToken` to `nativeToken`
pub refund_exchange_rate: u128,
/// Maximum amount of `nativeToken` which can be exchanged to `wrappedToken` by relay
pub max_refund: u128,
/// Time when this FeeInfo was generated
timestamp: DateTime<Utc>,
}

pub async fn get_substrate_fee_info(
chain_id: TypedChainId,
chain_id: u64,
estimated_tx_fees: u128,
ctx: &RelayerContext,
) -> Result<SubstrateFeeInfo> {
Ok(SubstrateFeeInfo {})
) -> SubstrateFeeInfo {
let estimated_fee = estimated_tx_fees
+ matic_to_wei(TRANSACTION_PROFIT_USD / TOKEN_PRICE_USD);
let refund_exchange_rate = matic_to_wei(1.);
// TODO: should ensure that refund <= relayer balance
let max_refund = matic_to_wei(MAX_REFUND_USD / TOKEN_PRICE_USD);
SubstrateFeeInfo {
estimated_fee,
refund_exchange_rate,
max_refund,
timestamp: Utc::now(),
}
}

/// Convert from full matic coin amount to smallest unit amount (also called wei).
///
/// It looks like subxt has no built-in functionality for this.
fn matic_to_wei(matic: f64) -> u128 {
(matic * 10_f64.powi(TOKEN_DECIMALS)) as u128
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(super) struct RpcFeeDetailsResponse {
#[serde(deserialize_with = "deserialize_number_string")]
pub partial_fee: u128,
}

fn deserialize_number_string<'de, D>(
deserializer: D,
) -> std::result::Result<u128, D::Error>
where
D: Deserializer<'de>,
{
let hex: String = Deserialize::deserialize(deserializer)?;
Ok(u128::from_str(&hex).unwrap())
}
2 changes: 0 additions & 2 deletions crates/tx-relay/src/substrate/mixer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ use webb_relayer_handler_utils::SubstrateMixerCommand;

/// Handler for Substrate Mixer commands
///
/// TODO: dont change anything here
///
/// # Arguments
///
/// * `ctx` - RelayContext reference that holds the configuration
Expand Down
59 changes: 53 additions & 6 deletions crates/tx-relay/src/substrate/vanchor.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
use super::*;
use crate::substrate::fees::{get_substrate_fee_info, RpcFeeDetailsResponse};
use crate::substrate::handle_substrate_tx;
use sp_core::crypto::Pair;
use std::ops::Deref;
use webb::evm::ethers::utils::__serde_json::Value;
use webb::evm::ethers::utils::hex;
use webb::substrate::protocol_substrate_runtime::api as RuntimeApi;
use webb::substrate::subxt::rpc::RpcParams;
use webb::substrate::subxt::utils::AccountId32;
use webb::substrate::{
protocol_substrate_runtime::api::runtime_types::{
Expand All @@ -17,8 +23,6 @@ use webb_relayer_utils::metric::Metrics;

/// Handler for Substrate Anchor commands
///
/// TODO: add fee here
///
/// # Arguments
///
/// * `ctx` - RelayContext reference that holds the configuration
Expand Down Expand Up @@ -76,17 +80,60 @@ pub async fn handle_substrate_vanchor_relay_tx<'a>(
Error(format!("Misconfigured Network {:?}: {e}", cmd.chain_id))
})?;

let signer = PairSigner::new(pair);
let signer = PairSigner::new(pair.clone());

// TODO: add refund
let transact_tx = RuntimeApi::tx().v_anchor_bn254().transact(
cmd.id,
proof_elements,
ext_data_elements,
);
let transact_tx_hash = client

let mut params = RpcParams::new();
let signed = client
.tx()
.sign_and_submit_then_watch_default(&transact_tx, &signer)
.await;
.create_signed(&transact_tx, &signer, Default::default())
.await
.unwrap();
params.push(hex::encode(signed.encoded())).unwrap();
let payment_info = client
.rpc()
.request::<RpcFeeDetailsResponse>("payment_queryInfo", params)
.await
.unwrap();
dbg!(&payment_info);
let fee_info =
get_substrate_fee_info(requested_chain, payment_info.partial_fee, &ctx)
.await;

// TODO: check refund amount <= relayer wallet balance
let mut params = RpcParams::new();
params.push(hex::encode(pair.public().deref())).unwrap();
//let balance = client.storage().at(None).await.unwrap().

// validate refund amount
if cmd.ext_data.refund > fee_info.max_refund {
// TODO: use error enum for these messages so they dont have to be duplicated between
// evm/substrate
let msg = format!(
"User requested a refund which is higher than the maximum of {}",
fee_info.max_refund
);
return Err(Error(msg));
}

// Check that transaction fee is enough to cover network fee and relayer fee
// TODO: refund needs to be converted from wrapped token to native token once there
// is an exchange rate
if cmd.ext_data.fee < fee_info.estimated_fee + cmd.ext_data.refund {
let msg = format!(
"User sent a fee that is too low {} but expected {}",
cmd.ext_data.fee, fee_info.estimated_fee
);
return Err(Error(msg));
}

let transact_tx_hash = signed.submit_and_watch().await;

let event_stream = transact_tx_hash
.map_err(|e| Error(format!("Error while sending Tx: {e}")))?;
Expand Down
2 changes: 1 addition & 1 deletion services/webb-relayer/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ pub async fn build_web_services(ctx: RelayerContext) -> crate::Result<()> {
get(handle_evm_fee_info),
)
.route(
"/fee_info/substrate/:chain_id",
"/fee_info/substrate/:chain_id/:estimated_tx_fees",
get(handle_substrate_fee_info),
);

Expand Down
15 changes: 8 additions & 7 deletions tests/lib/webbRelayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ export class WebbRelayer {
const response = await fetch(endpoint);
return response;
}

public async getEvmFeeInfo(
chainId: number,
vanchor: string,
Expand All @@ -229,11 +229,9 @@ export class WebbRelayer {
const response = await fetch(endpoint);
return response;
}

public async getSubstrateFeeInfo(
chainId: number,
) {
const endpoint = `http://127.0.0.1:${this.opts.commonConfig.port}/api/v1/fee_info/substrate/${chainId}`;

public async getSubstrateFeeInfo(chainId: number, partialFee: number) {
const endpoint = `http://127.0.0.1:${this.opts.commonConfig.port}/api/v1/fee_info/substrate/${chainId}/${partialFee}`;
const response = await fetch(endpoint);
return response;
}
Expand Down Expand Up @@ -650,7 +648,10 @@ export interface EvmFeeInfo {
}

export interface SubstrateFeeInfo {

estimatedFee: BigNumber;
refundExchangeRate: BigNumber;
maxRefund: BigNumber;
timestamp: string;
}

export interface Contract {
Expand Down
Loading

0 comments on commit a7fa47e

Please sign in to comment.