|
| 1 | +// Copyright 2019-2025 ChainSafe Systems |
| 2 | +// SPDX-License-Identifier: Apache-2.0, MIT |
| 3 | + |
| 4 | +use crate::{ |
| 5 | + rpc::{ |
| 6 | + self, |
| 7 | + eth::types::{EthAddress, EthBytes, EthCallMessage}, |
| 8 | + f3::F3Manifest, |
| 9 | + state::StateCall, |
| 10 | + RpcMethodExt, |
| 11 | + }, |
| 12 | + shim::message::Message, |
| 13 | +}; |
| 14 | +use clap::Subcommand; |
| 15 | +use flate2::read::DeflateDecoder; |
| 16 | +use std::{io::Read, str::FromStr as _}; |
| 17 | + |
| 18 | +const MAX_PAYLOAD_LEN: usize = 4 << 10; |
| 19 | + |
| 20 | +#[derive(Debug, Subcommand)] |
| 21 | +pub enum F3Commands { |
| 22 | + CheckActivation { |
| 23 | + /// Contract address |
| 24 | + #[arg(long, required = true)] |
| 25 | + contract: String, |
| 26 | + }, |
| 27 | + CheckActivationRaw { |
| 28 | + /// Contract address |
| 29 | + #[arg(long, required = true)] |
| 30 | + contract: String, |
| 31 | + }, |
| 32 | +} |
| 33 | + |
| 34 | +impl F3Commands { |
| 35 | + #[allow(clippy::indexing_slicing)] |
| 36 | + pub async fn run(self, client: rpc::Client) -> anyhow::Result<()> { |
| 37 | + match self { |
| 38 | + Self::CheckActivation { contract: _ } => { |
| 39 | + unimplemented!("Will be done in a subsequent PR"); |
| 40 | + } |
| 41 | + Self::CheckActivationRaw { contract } => { |
| 42 | + let eth_call_message = EthCallMessage { |
| 43 | + to: Some(EthAddress::from_str(&contract)?), |
| 44 | + data: EthBytes::from_str("0x2587660d")?, // method ID of activationInformation() |
| 45 | + ..Default::default() |
| 46 | + }; |
| 47 | + let filecoin_message = Message::try_from(eth_call_message)?; |
| 48 | + let api_invoc_result = |
| 49 | + StateCall::call(&client, (filecoin_message, None.into())).await?; |
| 50 | + let Some(message_receipt) = api_invoc_result.msg_rct else { |
| 51 | + anyhow::bail!("No message receipt") |
| 52 | + }; |
| 53 | + anyhow::ensure!( |
| 54 | + message_receipt.exit_code().is_success(), |
| 55 | + "unsuccessful exit code {}", |
| 56 | + message_receipt.exit_code() |
| 57 | + ); |
| 58 | + let return_data = message_receipt.return_data(); |
| 59 | + let eth_return = |
| 60 | + fvm_ipld_encoding::from_slice::<fvm_ipld_encoding::BytesDe>(&return_data)?.0; |
| 61 | + println!("Raw data: {}", hex::encode(eth_return.as_slice())); |
| 62 | + // 3*32 because there should be 3 slots minimum |
| 63 | + anyhow::ensure!(eth_return.len() >= 3 * 32, "no activation information"); |
| 64 | + let mut activation_epoch_bytes: [u8; 8] = [0; 8]; |
| 65 | + activation_epoch_bytes.copy_from_slice(ð_return[24..32]); |
| 66 | + let activation_epoch = u64::from_be_bytes(activation_epoch_bytes); |
| 67 | + for (i, &v) in eth_return[32..63].iter().enumerate() { |
| 68 | + anyhow::ensure!( |
| 69 | + v == 0, |
| 70 | + "wrong value for offset (padding): slot[{i}] = 0x{v:x} != 0x00" |
| 71 | + ); |
| 72 | + } |
| 73 | + anyhow::ensure!( |
| 74 | + eth_return[63] == 0x40, |
| 75 | + "wrong value for offest : slot[31] = 0x{:x} != 0x40", |
| 76 | + eth_return[63] |
| 77 | + ); |
| 78 | + let mut payload_len_bytes: [u8; 8] = [0; 8]; |
| 79 | + payload_len_bytes.copy_from_slice(ð_return[88..96]); |
| 80 | + let payload_len = u64::from_be_bytes(payload_len_bytes) as usize; |
| 81 | + anyhow::ensure!( |
| 82 | + payload_len <= MAX_PAYLOAD_LEN, |
| 83 | + "too long declared payload: {payload_len} > {MAX_PAYLOAD_LEN}" |
| 84 | + ); |
| 85 | + let payload_bytes = ð_return[96..]; |
| 86 | + anyhow::ensure!( |
| 87 | + payload_len <= payload_bytes.len(), |
| 88 | + "not enough remaining bytes: {payload_len} > {}", |
| 89 | + payload_bytes.len() |
| 90 | + ); |
| 91 | + anyhow::ensure!( |
| 92 | + activation_epoch < u64::MAX && payload_len > 0, |
| 93 | + "no active activation" |
| 94 | + ); |
| 95 | + let compressed_manifest_bytes = &payload_bytes[..payload_len]; |
| 96 | + let mut deflater = DeflateDecoder::new(compressed_manifest_bytes); |
| 97 | + let mut manifest_bytes = vec![]; |
| 98 | + deflater.read_to_end(&mut manifest_bytes)?; |
| 99 | + let manifest: F3Manifest = serde_json::from_slice(&manifest_bytes)?; |
| 100 | + anyhow::ensure!( |
| 101 | + manifest.bootstrap_epoch >= 0 |
| 102 | + && manifest.bootstrap_epoch as u64 == activation_epoch, |
| 103 | + "bootstrap epoch does not match: {} != {activation_epoch}", |
| 104 | + manifest.bootstrap_epoch |
| 105 | + ); |
| 106 | + println!("{}", serde_json::to_string_pretty(&manifest)?); |
| 107 | + } |
| 108 | + } |
| 109 | + Ok(()) |
| 110 | + } |
| 111 | +} |
0 commit comments