From ca6d3be75b8f2d84d3eb8aa272f3ce125e6ffece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Zwoli=C5=84ski?= Date: Thu, 17 Aug 2023 17:21:23 +0200 Subject: [PATCH] feat: Add estimate_gas and get_gas_price calls --- src/ethereum-canister/candid.did | 14 ++++++++ src/ethereum-canister/src/lib.rs | 25 +++++++++++++- src/ethereum-canister/src/utils.rs | 19 +++++++++++ src/ethereum-canister/tests/canister.rs | 34 +++++++++++++++++++ src/interface/src/lib.rs | 43 ++++++------------------- src/interface/src/network.rs | 37 +++++++++++++++++++++ 6 files changed, 138 insertions(+), 34 deletions(-) create mode 100644 src/interface/src/network.rs diff --git a/src/ethereum-canister/candid.did b/src/ethereum-canister/candid.did index 2c989d0..8bc44cd 100644 --- a/src/ethereum-canister/candid.did +++ b/src/ethereum-canister/candid.did @@ -12,6 +12,15 @@ type setup_request = record { execution_rpc_url: text; }; +type estimate_gas_request = record { + from: opt address; + to: opt address; + gas: opt u256; + gas_price: opt u256; + value: opt u256; + data: opt blob; +}; + type erc20_balance_of_request = record { contract: address; account: address; @@ -24,8 +33,13 @@ type erc721_owner_of_request = record { service : { "setup": (setup_request) -> (); + "get_block_number": () -> (nat) query; + "get_gas_price": () -> (u256) query; + + "estimate_gas": (estimate_gas_request) -> (u256); + "erc20_balance_of": (erc20_balance_of_request) -> (u256); "erc721_owner_of": (erc721_owner_of_request) -> (address); diff --git a/src/ethereum-canister/src/lib.rs b/src/ethereum-canister/src/lib.rs index cf92cc2..2ebcb93 100644 --- a/src/ethereum-canister/src/lib.rs +++ b/src/ethereum-canister/src/lib.rs @@ -4,7 +4,8 @@ use candid::Nat; use ic_cdk::{init, post_upgrade, pre_upgrade, query, update}; use ic_cdk_timers::set_timer; use interface::{ - Address, Erc20BalanceOfRequest, Erc721OwnerOfRequest, Network, SetupRequest, U256, + Address, Erc20BalanceOfRequest, Erc721OwnerOfRequest, EstimateGasRequest, Network, + SetupRequest, U256, }; use log::{debug, error}; @@ -12,6 +13,7 @@ use crate::stable_memory::{ init_stable_cell_default, load_static_string, save_static_string, StableCell, LAST_CHECKPOINT_ID, LAST_CONSENSUS_RPC_URL_ID, LAST_EXECUTION_RPC_URL_ID, LAST_NETWORK_ID, }; +use crate::utils::IntoCallOpts; mod erc20; mod erc721; @@ -71,6 +73,27 @@ async fn get_block_number() -> Nat { head_block_num.into() } +#[query] +async fn get_gas_price() -> U256 { + let helios = helios::client(); + + let gas_price = helios.get_gas_price().await.expect("get_gas_price failed"); + + gas_price.into() +} + +#[update] +async fn estimate_gas(request: EstimateGasRequest) -> U256 { + let helios = helios::client(); + + let gas_cost_estimation = helios + .estimate_gas(&request.into_call_opts()) + .await + .expect("estimate_gas failed"); + + gas_cost_estimation.into() +} + #[update] async fn erc20_balance_of(request: Erc20BalanceOfRequest) -> U256 { erc20::balance_of(request.contract.into(), request.account.into()) diff --git a/src/ethereum-canister/src/utils.rs b/src/ethereum-canister/src/utils.rs index 5a07d67..f91530c 100644 --- a/src/ethereum-canister/src/utils.rs +++ b/src/ethereum-canister/src/utils.rs @@ -1,5 +1,7 @@ use candid::Nat; use ethers_core::types::U256; +use helios_execution::types::CallOpts; +use interface::EstimateGasRequest; use num_bigint::BigUint; pub(crate) trait ToNat { @@ -13,3 +15,20 @@ impl ToNat for U256 { Nat(BigUint::from_bytes_le(&bytes)) } } + +pub(crate) trait IntoCallOpts { + fn into_call_opts(self) -> CallOpts; +} + +impl IntoCallOpts for EstimateGasRequest { + fn into_call_opts(self) -> CallOpts { + CallOpts { + from: self.from.map(Into::into), + to: self.to.map(Into::into), + gas: self.gas_limit.map(Into::into), + gas_price: self.gas_price.map(Into::into), + value: self.value.map(Into::into), + data: self.data, + } + } +} diff --git a/src/ethereum-canister/tests/canister.rs b/src/ethereum-canister/tests/canister.rs index 82a50e5..1dcd1e5 100644 --- a/src/ethereum-canister/tests/canister.rs +++ b/src/ethereum-canister/tests/canister.rs @@ -1,4 +1,7 @@ use candid::Nat; +use contracts_abi::erc20::BalanceOfCall; +use ethers_core::abi::AbiEncode; +use interface::EstimateGasRequest; mod test_canister; @@ -12,6 +15,37 @@ fn get_block_number() { assert!(block_num.0 > 17880732u128); } +#[test] +fn get_gas_price() { + let canister = setup_ethereum_canister(); + + let gas: (Nat,) = call!(canister, "get_gas_price").unwrap(); + assert_ne!(gas.0, 0u128); +} + +#[test] +fn estimate_gas() { + let canister = setup_ethereum_canister(); + + let erc20_balance_of = BalanceOfCall { + account: "0xF977814e90dA44bFA03b6295A0616a897441aceC" + .parse() + .unwrap(), + }; + let request = EstimateGasRequest { + to: Some( + "0xdAC17F958D2ee523a2206206994597C13D831ec7" // usdt + .parse() + .unwrap(), + ), + data: Some(erc20_balance_of.encode()), + ..Default::default() + }; + + let gas: (Nat,) = call!(canister, "estimate_gas", request).unwrap(); + assert_ne!(gas.0, 0u128); +} + mod erc20 { use interface::{Erc20BalanceOfRequest, U256}; diff --git a/src/interface/src/lib.rs b/src/interface/src/lib.rs index cc3c982..bfafe59 100644 --- a/src/interface/src/lib.rs +++ b/src/interface/src/lib.rs @@ -1,14 +1,12 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; - use candid::CandidType; use serde::Deserialize; -use thiserror::Error; mod address; +mod network; mod u256; pub use address::Address; +pub use network::{BadNetwork, Network}; pub use u256::{U256ConvertError, U256}; #[derive(Debug, Clone, PartialEq, Eq, CandidType, Deserialize)] @@ -30,33 +28,12 @@ pub struct Erc721OwnerOfRequest { pub token_id: U256, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, CandidType, Deserialize)] -pub enum Network { - Mainnet, - Goerli, -} - -impl Display for Network { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Network::Mainnet => f.write_str("Mainnet"), - Network::Goerli => f.write_str("Goerli"), - } - } -} - -#[derive(Debug, Error)] -#[error("Bad network")] -pub struct BadNetwork; - -impl FromStr for Network { - type Err = BadNetwork; - - fn from_str(s: &str) -> Result { - match s { - "Mainnet" | "mainnet" => Ok(Network::Mainnet), - "Goerli" | "goerli" => Ok(Network::Goerli), - _ => Err(BadNetwork), - } - } +#[derive(Debug, Clone, PartialEq, Eq, CandidType, Deserialize, Default)] +pub struct EstimateGasRequest { + pub from: Option
, + pub to: Option
, + pub gas_limit: Option, + pub gas_price: Option, + pub value: Option, + pub data: Option>, } diff --git a/src/interface/src/network.rs b/src/interface/src/network.rs new file mode 100644 index 0000000..6a1a343 --- /dev/null +++ b/src/interface/src/network.rs @@ -0,0 +1,37 @@ +use std::fmt::{self, Display}; +use std::str::FromStr; + +use candid::CandidType; +use serde::Deserialize; +use thiserror::Error; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, CandidType, Deserialize)] +pub enum Network { + Mainnet, + Goerli, +} + +impl Display for Network { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Network::Mainnet => f.write_str("Mainnet"), + Network::Goerli => f.write_str("Goerli"), + } + } +} + +#[derive(Debug, Error)] +#[error("Bad network")] +pub struct BadNetwork; + +impl FromStr for Network { + type Err = BadNetwork; + + fn from_str(s: &str) -> Result { + match s { + "Mainnet" | "mainnet" => Ok(Network::Mainnet), + "Goerli" | "goerli" => Ok(Network::Goerli), + _ => Err(BadNetwork), + } + } +}