Skip to content

Commit

Permalink
Fund with token (#504)
Browse files Browse the repository at this point in the history
* fund with token

* fmt code

* debug logs

* add xnet facet
  • Loading branch information
cryptoAtwill authored Jan 8, 2024
1 parent 5ac9058 commit 0c5f8f7
Show file tree
Hide file tree
Showing 7 changed files with 143 additions and 7 deletions.
4 changes: 4 additions & 0 deletions fendermint/fendermint/vm/actor_interface/src/ipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ lazy_static! {
name: "GatewayMessengerFacet",
abi: ia::gateway_messenger_facet::GATEWAYMESSENGERFACET_ABI.to_owned(),
},
EthFacet {
name: "XnetMessagingFacet",
abi: ia::xnet_messaging_facet::XNETMESSAGINGFACET_ABI.to_owned(),
},
],
},
),
Expand Down
15 changes: 9 additions & 6 deletions fendermint/fendermint/vm/interpreter/src/fvm/topdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,17 @@ where
DB: Blockstore + Sync + Send + 'static,
{
let minted_tokens = tokens_to_mint(&messages);
tracing::debug!(token = minted_tokens.to_string(), "tokens to mint in child");

gateway_caller
.mint_to_gateway(state, minted_tokens.clone())
.context("failed to mint to gateway")?;
if !minted_tokens.is_zero() {
gateway_caller
.mint_to_gateway(state, minted_tokens.clone())
.context("failed to mint to gateway")?;

state.update_circ_supply(|circ_supply| {
*circ_supply += minted_tokens;
});
state.update_circ_supply(|circ_supply| {
*circ_supply += minted_tokens;
});
}

gateway_caller.apply_cross_messages(state, messages)
}
49 changes: 49 additions & 0 deletions ipc/ipc/cli/src/commands/crossmsg/fund.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,52 @@ pub struct PreFundArgs {
#[arg(help = "Add an initial balance for the address in genesis in the subnet")]
pub initial_balance: f64,
}

/// The command to send ERC20 tokens to a subnet from parent
pub(crate) struct FundWithToken;

#[async_trait]
impl CommandLineHandler for FundWithToken {
type Arguments = FundWithTokenArgs;

async fn handle(global: &GlobalArguments, arguments: &Self::Arguments) -> anyhow::Result<()> {
log::debug!("fund with token operation with args: {:?}", arguments);

let mut provider = get_ipc_provider(global)?;
let subnet = SubnetID::from_str(&arguments.subnet)?;
let from = match &arguments.from {
Some(address) => Some(require_fil_addr_from_str(address)?),
None => None,
};
let to = match &arguments.to {
Some(address) => Some(require_fil_addr_from_str(address)?),
None => None,
};

println!(
"fund with token performed in epoch: {:?}",
provider
.fund_with_token(subnet, from, to, f64_to_token_amount(arguments.amount)?,)
.await?,
);

Ok(())
}
}

#[derive(Debug, Args)]
#[command(about = "Send erc20 tokens from a parent to a child subnet")]
pub(crate) struct FundWithTokenArgs {
#[arg(long, short, help = "The address to send funds from")]
pub from: Option<String>,
#[arg(
long,
short,
help = "The address to send funds to (if not set, amount sent to from address)"
)]
pub to: Option<String>,
#[arg(long, short, help = "The subnet to fund")]
pub subnet: String,
#[arg(help = "The amount to fund in erc20, in ether, supports up to 9 decimal places")]
pub amount: f64,
}
4 changes: 3 additions & 1 deletion ipc/ipc/cli/src/commands/crossmsg/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Copyright 2022-2023 Protocol Labs
// SPDX-License-Identifier: MIT
use self::fund::{PreFund, PreFundArgs};
use self::fund::{FundWithToken, FundWithTokenArgs, PreFund, PreFundArgs};
use self::release::{PreRelease, PreReleaseArgs};
use self::topdown_cross::{
LatestParentFinality, LatestParentFinalityArgs, ListTopdownMsgs, ListTopdownMsgsArgs,
Expand Down Expand Up @@ -32,6 +32,7 @@ impl CrossMsgsCommandsArgs {
pub async fn handle(&self, global: &GlobalArguments) -> anyhow::Result<()> {
match &self.command {
Commands::Fund(args) => Fund::handle(global, args).await,
Commands::FundWithToken(args) => FundWithToken::handle(global, args).await,
Commands::PreFund(args) => PreFund::handle(global, args).await,
Commands::Release(args) => Release::handle(global, args).await,
Commands::PreRelease(args) => PreRelease::handle(global, args).await,
Expand All @@ -45,6 +46,7 @@ impl CrossMsgsCommandsArgs {
#[derive(Debug, Subcommand)]
pub(crate) enum Commands {
Fund(FundArgs),
FundWithToken(FundWithTokenArgs),
PreFund(PreFundArgs),
Release(ReleaseArgs),
PreRelease(PreReleaseArgs),
Expand Down
24 changes: 24 additions & 0 deletions ipc/ipc/provider/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,30 @@ impl IpcProvider {
.await
}

/// Funds an account in a child subnet with erc20 token, provided that the supply source kind is
/// `ERC20`. If `from` is None, it will use the default address config in `ipc.toml`.
/// If `to` is `None`, the `from` account will be funded.
pub async fn fund_with_token(
&mut self,
subnet: SubnetID,
from: Option<Address>,
to: Option<Address>,
amount: TokenAmount,
) -> anyhow::Result<ChainEpoch> {
let parent = subnet.parent().ok_or_else(|| anyhow!("no parent found"))?;
let conn = match self.connection(&parent) {
None => return Err(anyhow!("target parent subnet not found")),
Some(conn) => conn,
};

let subnet_config = conn.subnet();
let sender = self.check_sender(subnet_config, from)?;

conn.manager()
.fund_with_token(subnet, sender, to.unwrap_or(sender), amount)
.await
}

/// Release to an account in a child subnet, if `to` is `None`, the self account
/// is funded.
pub async fn release(
Expand Down
35 changes: 35 additions & 0 deletions ipc/ipc/provider/src/manager/evm/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -570,6 +570,36 @@ impl SubnetManager for EthSubnetManager {
block_number_from_receipt(receipt)
}

async fn fund_with_token(
&self,
subnet: SubnetID,
from: Address,
to: Address,
amount: TokenAmount,
) -> Result<ChainEpoch> {
log::debug!("fund with token, subnet: {subnet}, amount: {amount}, from: {from}, to: {to}");

let value = fil_amount_to_eth_amount(&amount)?;
let evm_subnet_id = gateway_manager_facet::SubnetID::try_from(&subnet)?;

let signer = Arc::new(self.get_signer(&from)?);
let gateway_contract = gateway_manager_facet::GatewayManagerFacet::new(
self.ipc_contract_info.gateway_addr,
signer.clone(),
);

let txn = gateway_contract.fund_with_token(
evm_subnet_id,
gateway_manager_facet::FvmAddress::try_from(to)?,
value,
);
let txn = call_with_premium_estimation(signer, txn).await?;

let pending_tx = txn.send().await?;
let receipt = pending_tx.retries(TRANSACTION_RECEIPT_RETRIES).await?;
block_number_from_receipt(receipt)
}

async fn release(
&self,
gateway_addr: Address,
Expand Down Expand Up @@ -1336,6 +1366,11 @@ fn into_genesis_balance_map(
Ok(map)
}

pub(crate) fn fil_amount_to_eth_amount(amount: &TokenAmount) -> Result<ethers::types::U256> {
let v = ethers::types::U256::from_dec_str(&amount.atto().to_string())?;
Ok(v)
}

/// Convert the ipc SubnetID type to an evm address. It extracts the last address from the Subnet id
/// children and turns it into evm address.
pub(crate) fn contract_address_from_subnet(subnet: &SubnetID) -> Result<ethers::types::Address> {
Expand Down
19 changes: 19 additions & 0 deletions ipc/ipc/provider/src/manager/subnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,25 @@ pub trait SubnetManager: Send + Sync + TopDownFinalityQuery + BottomUpCheckpoint
amount: TokenAmount,
) -> Result<ChainEpoch>;

/// Sends funds to a specified subnet receiver using ERC20 tokens.
/// This function locks the amount of ERC20 tokens into custody and then mints the supply in the specified subnet.
/// It checks if the subnet's supply strategy is ERC20 and if not, the operation is reverted.
/// It allows for free injection of funds into a subnet and is protected against reentrancy.
///
/// # Arguments
///
/// * `subnetId` - The ID of the subnet where the funds will be sent to.
/// * `from` - The funding address.
/// * `to` - The funded address.
/// * `amount` - The amount of ERC20 tokens to be sent.
async fn fund_with_token(
&self,
subnet: SubnetID,
from: Address,
to: Address,
amount: TokenAmount,
) -> Result<ChainEpoch>;

/// Release creates a new check message to release funds in parent chain
/// Returns the epoch that the released is executed in the child.
async fn release(
Expand Down

0 comments on commit 0c5f8f7

Please sign in to comment.