From a41ac2136a6147f711afa47fd3176477db6977d6 Mon Sep 17 00:00:00 2001 From: Alfonso de la Rocha Date: Wed, 1 Nov 2023 13:40:39 +0100 Subject: [PATCH] IPC-347: add pre-fund command and genesis balances in GenesisInfo call --- Cargo.lock | 2 +- Cargo.toml | 8 ++-- ipc/cli/src/commands/subnet/join.rs | 59 ++++++++++++++++++++++++- ipc/provider/src/lib.rs | 18 ++++++++ ipc/provider/src/manager/evm/manager.rs | 36 +++++++++++++++ ipc/provider/src/manager/subnet.rs | 9 +++- 6 files changed, 124 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 13d99e06..628d9531 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2697,7 +2697,7 @@ dependencies = [ [[package]] name = "ipc_actors_abis" version = "0.1.0" -source = "git+https://github.com/consensus-shipyard/ipc-solidity-actors.git?branch=dev#3f72d21d0ad601fbb673b6a9bc650d14f7be9c9f" +source = "git+https://github.com/consensus-shipyard/ipc-solidity-actors.git?branch=fm-347-subnet-prefund#55f427ba3f786db6c5463f28061d2ea25a12e292" dependencies = [ "anyhow", "ethers", diff --git a/Cargo.toml b/Cargo.toml index 67eee1f0..5e9d3095 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ [workspace] resolver = "2" members = [ - # "ipc/daemon", + # "ipc/daemon", "ipc/cli", - # "ipc/testing/e2e", + # "ipc/testing/e2e", # "ipc/testing/*", "ipc/provider", - "ipc/identity", + "ipc/identity", "ipc/sdk" ] @@ -42,7 +42,7 @@ hex = "0.4.3" tempfile = "3.4.0" serde_json = { version = "1.0.91", features = ["raw_value"] } -ipc_actors_abis = { git = "https://github.com/consensus-shipyard/ipc-solidity-actors.git", branch = "dev" } +ipc_actors_abis = { git = "https://github.com/consensus-shipyard/ipc-solidity-actors.git", branch = "fm-347-subnet-prefund" } libipld = { version = "0.14", default-features = false, features = ["dag-cbor"] } libsecp256k1 = "0.7" diff --git a/ipc/cli/src/commands/subnet/join.rs b/ipc/cli/src/commands/subnet/join.rs index 658fd296..72dff082 100644 --- a/ipc/cli/src/commands/subnet/join.rs +++ b/ipc/cli/src/commands/subnet/join.rs @@ -29,6 +29,16 @@ impl CommandLineHandler for JoinSubnet { None => None, }; let public_key = hex::decode(&arguments.public_key)?; + if let Some(initial_balance) = arguments.initial_balance { + println!("pre-funding address with {initial_balance}"); + provider + .prefund_subnet( + subnet.clone(), + from, + f64_to_token_amount(initial_balance.clone())?, + ) + .await?; + } let epoch = provider .join_subnet( subnet, @@ -37,7 +47,7 @@ impl CommandLineHandler for JoinSubnet { public_key, ) .await?; - log::info!("joined at epoch: {epoch}"); + println!("joined at epoch: {epoch}"); Ok(()) } @@ -58,6 +68,11 @@ pub struct JoinSubnetArgs { pub collateral: f64, #[arg(long, short, help = "The validator's metadata, hex encoded")] pub public_key: String, + #[arg( + long, + help = "Optionally add an initial balance to the validator in genesis in the subnet" + )] + pub initial_balance: Option, } /// The command to stake in a subnet from validator @@ -136,3 +151,45 @@ pub struct UnstakeSubnetArgs { )] pub collateral: f64, } + +pub struct PreFundSubnet; + +#[async_trait] +impl CommandLineHandler for PreFundSubnet { + type Arguments = PreFundSubnetArgs; + + async fn handle(global: &GlobalArguments, arguments: &Self::Arguments) -> anyhow::Result<()> { + log::debug!("pre-fund subnet 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, + }; + provider + .prefund_subnet( + subnet.clone(), + from, + f64_to_token_amount(arguments.initial_balance)?, + ) + .await?; + println!("address pre-funded successfully"); + + Ok(()) + } +} + +#[derive(Debug, Args)] +#[command( + name = "pre-fund", + about = "Pre fund with some funds in genesis in a child-subnet" +)] +pub struct PreFundSubnetArgs { + #[arg(long, short, help = "The address funded in the subnet")] + pub from: Option, + #[arg(long, short, help = "The subnet to add balance to")] + pub subnet: String, + #[arg(help = "Optionally add an initial balance to the validator in genesis in the subnet")] + pub initial_balance: f64, +} diff --git a/ipc/provider/src/lib.rs b/ipc/provider/src/lib.rs index 939d87fb..5e105015 100644 --- a/ipc/provider/src/lib.rs +++ b/ipc/provider/src/lib.rs @@ -298,6 +298,24 @@ impl IpcProvider { .await } + pub async fn prefund_subnet( + &mut self, + subnet: SubnetID, + from: Option
, + balance: TokenAmount, + ) -> anyhow::Result<()> { + 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().pre_fund(subnet, sender, balance).await + } + pub async fn stake( &mut self, subnet: SubnetID, diff --git a/ipc/provider/src/manager/evm/manager.rs b/ipc/provider/src/manager/evm/manager.rs index 5a9ae278..e32e457f 100644 --- a/ipc/provider/src/manager/evm/manager.rs +++ b/ipc/provider/src/manager/evm/manager.rs @@ -312,6 +312,27 @@ impl SubnetManager for EthSubnetManager { block_number_from_receipt(receipt) } + async fn pre_fund(&self, subnet: SubnetID, from: Address, balance: TokenAmount) -> Result<()> { + let balance = balance + .atto() + .to_u128() + .ok_or_else(|| anyhow!("invalid min validator stake"))?; + + let address = contract_address_from_subnet(&subnet)?; + log::info!("interacting with evm subnet contract: {address:} with balance: {balance:}"); + + let signer = Arc::new(self.get_signer(&from)?); + let contract = + subnet_actor_manager_facet::SubnetActorManagerFacet::new(address, signer.clone()); + + let mut txn = contract.pre_fund(); + txn.tx.set_value(balance); + let txn = call_with_premium_estimation(signer, txn).await?; + + txn.send().await?; + Ok(()) + } + async fn stake(&self, subnet: SubnetID, from: Address, collateral: TokenAmount) -> Result<()> { let collateral = collateral .atto() @@ -642,6 +663,9 @@ impl SubnetManager for EthSubnetManager { address, Arc::new(self.ipc_contract_info.provider.clone()), ); + + let genesis_balances = contract.genesis_balances().await?; + Ok(SubnetGenesisInfo { // Active validators limit set for the child subnet. active_validators_limit: contract.active_validators_limit().call().await?, @@ -656,6 +680,7 @@ impl SubnetManager for EthSubnetManager { // Custom message fee that the child subnet wants to set for cross-net messages msg_fee: eth_to_fil_amount(&contract.min_cross_msg_fee().call().await?)?, validators: from_contract_validators(contract.genesis_validators().call().await?)?, + genesis_balances: into_genesis_balance_map(genesis_balances.0, genesis_balances.1)?, }) } @@ -1162,6 +1187,17 @@ fn is_valid_bootstrap_addr(input: &str) -> Option<(String, IpAddr, u16)> { None } +fn into_genesis_balance_map( + addrs: Vec, + balances: Vec, +) -> Result> { + let mut map = HashMap::new(); + for (a, b) in addrs.into_iter().zip(balances) { + map.insert(ethers_address_to_fil_address(&a)?, eth_to_fil_amount(&b)?); + } + Ok(map) +} + /// 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 { diff --git a/ipc/provider/src/manager/subnet.rs b/ipc/provider/src/manager/subnet.rs index ab28783d..e4b71870 100644 --- a/ipc/provider/src/manager/subnet.rs +++ b/ipc/provider/src/manager/subnet.rs @@ -37,12 +37,16 @@ pub trait SubnetManager: Send + Sync + TopDownCheckpointQuery + BottomUpCheckpoi metadata: Vec, ) -> Result; + /// Adds some initial balance to an address before a child subnet bootstraps to make + /// it available in the subnet at genesis. + async fn pre_fund(&self, subnet: SubnetID, from: Address, balance: TokenAmount) -> Result<()>; + /// Allows validators that have already joined the subnet to stake more collateral - /// and increase their power in the subnet + /// and increase their power in the subnet. async fn stake(&self, subnet: SubnetID, from: Address, collateral: TokenAmount) -> Result<()>; /// Allows validators that have already joined the subnet to unstake collateral - /// and reduce their power in the subnet + /// and reduce their power in the subnet. async fn unstake(&self, subnet: SubnetID, from: Address, collateral: TokenAmount) -> Result<()>; @@ -142,6 +146,7 @@ pub struct SubnetGenesisInfo { pub min_collateral: TokenAmount, pub genesis_epoch: ChainEpoch, pub validators: Vec, + pub genesis_balances: HashMap, } /// The generic payload that returns the block hash of the data returning block with the actual