From e61e26e43cc44296737dc6b3d1aee2e514edb536 Mon Sep 17 00:00:00 2001 From: anorth <445306+anorth@users.noreply.github.com> Date: Mon, 1 Aug 2022 15:06:55 +1200 Subject: [PATCH] Implements datacap token actor. Not yet integrated with anything. --- Cargo.lock | 40 +- actors/datacap/Cargo.toml | 34 ++ actors/datacap/src/lib.rs | 515 +++++++++++++++++++++ actors/datacap/src/state.rs | 24 + actors/datacap/src/testing.rs | 20 + actors/datacap/src/types.rs | 93 ++++ actors/datacap/tests/datacap_actor_test.rs | 27 ++ actors/datacap/tests/harness/mod.rs | 59 +++ runtime/Cargo.toml | 2 +- runtime/src/actor_error.rs | 94 ++++ 10 files changed, 905 insertions(+), 3 deletions(-) create mode 100644 actors/datacap/Cargo.toml create mode 100644 actors/datacap/src/lib.rs create mode 100644 actors/datacap/src/state.rs create mode 100644 actors/datacap/src/testing.rs create mode 100644 actors/datacap/src/types.rs create mode 100644 actors/datacap/tests/datacap_actor_test.rs create mode 100644 actors/datacap/tests/harness/mod.rs diff --git a/Cargo.lock b/Cargo.lock index d64b62703..d3c2c9de0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -604,6 +604,23 @@ dependencies = [ "serde", ] +[[package]] +name = "fil_actor_datacap" +version = "9.0.0-alpha.1" +dependencies = [ + "cid", + "fil_actors_runtime", + "fil_fungible_token", + "fvm_ipld_blockstore", + "fvm_ipld_encoding", + "fvm_ipld_hamt", + "fvm_shared", + "lazy_static", + "num-derive", + "num-traits", + "serde", +] + [[package]] name = "fil_actor_init" version = "9.0.0-alpha.1" @@ -860,6 +877,25 @@ dependencies = [ "serde", ] +[[package]] +name = "fil_fungible_token" +version = "0.1.0" +source = "git+https://github.com/helix-onchain/filecoin?rev=b5c55eadf13946155d06d76a3409fc800b9a6e64#b5c55eadf13946155d06d76a3409fc800b9a6e64" +dependencies = [ + "anyhow", + "cid", + "fvm_ipld_amt", + "fvm_ipld_blockstore", + "fvm_ipld_encoding", + "fvm_ipld_hamt", + "fvm_sdk", + "fvm_shared", + "num-traits", + "serde", + "serde_tuple", + "thiserror", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1077,9 +1113,9 @@ dependencies = [ [[package]] name = "fvm_sdk" -version = "1.0.0-rc.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd162cff8cab78c3798762bc6879429ecbd0f5515856bcb3bc31d197208db70" +checksum = "d013aaca686496e85886d96e5c4c228555141849d59347d1f22d793d5be2572b" dependencies = [ "cid", "fvm_ipld_encoding", diff --git a/actors/datacap/Cargo.toml b/actors/datacap/Cargo.toml new file mode 100644 index 000000000..7fa8f7adc --- /dev/null +++ b/actors/datacap/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "fil_actor_datacap" +description = "Builtin data cap actor for Filecoin" +version = "9.0.0-alpha.1" +license = "MIT OR Apache-2.0" +authors = ["Protocol Labs", "Filecoin Core Devs"] +edition = "2018" +repository = "https://github.com/filecoin-project/builtin-actors" +keywords = ["filecoin", "web3", "wasm"] + +[lib] +## lib is necessary for integration tests +## cdylib is necessary for Wasm build +crate-type = ["cdylib", "lib"] + +[dependencies] +fil_actors_runtime = { version = "9.0.0-alpha.1", path = "../../runtime", features = ["fil-actor"] } + +cid = { version = "0.8.3", default-features = false, features = ["serde-codec"] } +fil_fungible_token = { git = "https://github.com/helix-onchain/filecoin", rev = "b5c55eadf13946155d06d76a3409fc800b9a6e64" } +fvm_ipld_blockstore = "0.1.1" +fvm_ipld_encoding = "0.2.2" +fvm_ipld_hamt = "0.5.1" +fvm_shared = { version = "0.8.0", default-features = false } +lazy_static = "1.4.0" +num-derive = "0.3.3" +num-traits = "0.2.14" +serde = { version = "1.0.136", features = ["derive"] } + +[dev-dependencies] +fil_actors_runtime = { path = "../../runtime", features = ["test_utils", "sector-default"] } +[features] +fil-actor = [] + diff --git a/actors/datacap/src/lib.rs b/actors/datacap/src/lib.rs new file mode 100644 index 000000000..4b16bc7ec --- /dev/null +++ b/actors/datacap/src/lib.rs @@ -0,0 +1,515 @@ +use std::marker::PhantomData; + +use fil_fungible_token::runtime::messaging::{Messaging, MessagingError}; +use fil_fungible_token::token::{Token, TOKEN_PRECISION}; +use fvm_ipld_blockstore::Blockstore; +use fvm_ipld_encoding::RawBytes; +use fvm_shared::address::Address; +use fvm_shared::bigint::bigint_ser::BigIntSer; +use fvm_shared::econ::TokenAmount; +use fvm_shared::error::{ErrorNumber, ExitCode}; +use fvm_shared::receipt::Receipt; +use fvm_shared::{ActorID, MethodNum, METHOD_CONSTRUCTOR, METHOD_SEND}; +use num_derive::FromPrimitive; +use num_traits::{FromPrimitive, Zero}; + +use fil_actors_runtime::cbor::serialize; +use fil_actors_runtime::runtime::{ActorCode, Runtime}; +use fil_actors_runtime::{ + actor_error, cbor, ActorContext, ActorError, AsActorError, SYSTEM_ACTOR_ADDR, +}; + +pub use self::state::State; +pub use self::types::*; + +#[cfg(feature = "fil-actor")] +fil_actors_runtime::wasm_trampoline!(Actor); + +mod state; +pub mod testing; +mod types; + +/// TODO: +/// - Map all token library errors to an exit code other than ExitCode::USR_UNSPECIFIED +/// (probably via a mapping implemented in the token library) + +pub const DATACAP_GRANULARITY: u64 = TOKEN_PRECISION as u64; + +/// Static method numbers for builtin-actor private dispatch. +/// The methods are also expected to be exposed via FRC-XXXX standard calling convention, +/// with numbers determined by name. +#[derive(FromPrimitive)] +#[repr(u64)] +pub enum Method { + Constructor = METHOD_CONSTRUCTOR, + // Non-standard. + Mint = 2, + Destroy = 3, + // Static method numbers for token standard methods, for private use. + Name = 10, + Symbol = 11, + TotalSupply = 12, + BalanceOf = 13, + Transfer = 14, + TransferFrom = 15, + IncreaseAllowance = 16, + DecreaseAllowance = 17, + RevokeAllowance = 18, + Burn = 19, + BurnFrom = 20, +} + +pub struct Actor; + +impl Actor { + /// Constructor for DataCap Actor + pub fn constructor(rt: &mut RT, verifreg: Address) -> Result<(), ActorError> + where + BS: Blockstore, + RT: Runtime, + { + rt.validate_immediate_caller_is(std::iter::once(&*SYSTEM_ACTOR_ADDR))?; + + // Confirm the registry address is an ID. + let verifreg_id = rt + .resolve_address(&verifreg) + .ok_or_else(|| actor_error!(illegal_argument, "failed to resolve registry address"))?; + + let st = State::new(rt.store(), verifreg_id).context("failed to create verifreg state")?; + rt.create(&st)?; + Ok(()) + } + + pub fn name(_: &RT, _: ()) -> Result<&'static str, ActorError> + where + BS: Blockstore, + RT: Runtime, + { + Ok("DataCap") + } + + pub fn symbol(_: &RT, _: ()) -> Result<&'static str, ActorError> + where + BS: Blockstore, + RT: Runtime, + { + Ok("DCAP") + } + + pub fn total_supply(rt: &mut RT, _: ()) -> Result + where + BS: Blockstore, + RT: Runtime, + { + let mut st: State = rt.state()?; + let msg = Messenger { rt, dummy: Default::default() }; + Token::with(msg.rt.store(), &msg, &mut st.token, DATACAP_GRANULARITY, |token| { + Ok(token.total_supply()) + }) + } + + pub fn balance_of(rt: &mut RT, address: Address) -> Result + where + BS: Blockstore, + RT: Runtime, + { + // NOTE: mutability and method caller here are awkward for a read-only call + let mut st: State = rt.state()?; + let msg = Messenger { rt, dummy: Default::default() }; + Token::with(msg.rt.store(), &msg, &mut st.token, DATACAP_GRANULARITY, |token| { + token.balance_of(&address).exit_code(ExitCode::USR_UNSPECIFIED) + }) + } + + pub fn allowance( + rt: &mut RT, + params: AllowanceParams, + ) -> Result + where + BS: Blockstore, + RT: Runtime, + { + let mut st: State = rt.state()?; + let msg = Messenger { rt, dummy: Default::default() }; + Token::with(msg.rt.store(), &msg, &mut st.token, DATACAP_GRANULARITY, |token| { + token.allowance(¶ms.owner, ¶ms.operator).exit_code(ExitCode::USR_UNSPECIFIED) + }) + } + + /// Mints new data cap tokens for an address (a verified client). + /// Only the registry can call this method. + /// This method is not part of the fungible token standard. + pub fn mint(rt: &mut RT, params: MintParams) -> Result<(), ActorError> + where + BS: Blockstore, + RT: Runtime, + { + rt.transaction(|st: &mut State, rt| { + // Only the registry can mint datacap tokens. + rt.validate_immediate_caller_is(std::iter::once(&st.registry))?; + let operator = st.registry; + + let msg = Messenger { rt, dummy: Default::default() }; + Token::with(msg.rt.store(), &msg, &mut st.token, DATACAP_GRANULARITY, |token| { + // Mint tokens "from" the operator to the beneficiary. + token + .mint(&operator, ¶ms.to, ¶ms.amount, &RawBytes::default()) + .exit_code(ExitCode::USR_UNSPECIFIED) + }) + }) + .context("state transaction failed")?; + Ok(()) + } + + /// Destroys data cap tokens for an address (a verified client). + /// Only the registry can call this method. + /// This method is not part of the fungible token standard, and is named distinctly from + /// "burn" to reflect that distinction. + pub fn destroy(rt: &mut RT, params: DestroyParams) -> Result + where + BS: Blockstore, + RT: Runtime, + { + rt.transaction(|st: &mut State, rt| { + // Only the registry can destroy datacap tokens on behalf of a holder. + rt.validate_immediate_caller_is(std::iter::once(&st.registry))?; + let operator = ¶ms.owner; + + let msg = Messenger { rt, dummy: Default::default() }; + Token::with(msg.rt.store(), &msg, &mut st.token, DATACAP_GRANULARITY, |token| { + // Burn tokens as if the holder had invoked burn() themselves. + // The registry doesn't need an allowance. + token + .burn(operator, ¶ms.owner, ¶ms.amount) + .exit_code(ExitCode::USR_UNSPECIFIED) + }) + }) + .context("state transaction failed") + } + + /// Transfers data cap tokens to an address. + /// Data cap tokens are not generally transferable. + /// Succeeds if the to address is the registry, otherwise always fails. + pub fn transfer(rt: &mut RT, params: TransferParams) -> Result<(), ActorError> + where + BS: Blockstore, + RT: Runtime, + { + rt.validate_immediate_caller_accept_any()?; + let operator = &rt.message().caller(); + let from = operator; + // Resolve to address for comparison with registry address. + let to = &rt + .resolve_address(¶ms.to) + .context_code(ExitCode::USR_ILLEGAL_ARGUMENT, "to must be ID address")?; + + rt.transaction(|st: &mut State, rt| { + let allowed = *to == st.registry; + if !allowed { + return Err(actor_error!(forbidden, "transfer not allowed")); + } + + let msg = Messenger { rt, dummy: Default::default() }; + Token::with(msg.rt.store(), &msg, &mut st.token, DATACAP_GRANULARITY, |token| { + token + .transfer(operator, from, to, ¶ms.amount, ¶ms.data) + .exit_code(ExitCode::USR_UNSPECIFIED) + }) + }) + .context("state transaction failed")?; + Ok(()) + } + + /// Transfers data cap tokens between addresses. + /// Data cap tokens are not generally transferable between addresses. + /// Succeeds if the to address is the registry, otherwise always fails. + pub fn transfer_from(rt: &mut RT, params: TransferFromParams) -> Result<(), ActorError> + where + BS: Blockstore, + RT: Runtime, + { + rt.validate_immediate_caller_accept_any()?; + let operator = rt.message().caller(); + let from = params.from; + // Resolve to address for comparison with registry. + let to = rt + .resolve_address(¶ms.to) + .context_code(ExitCode::USR_ILLEGAL_ARGUMENT, "to must be an ID address")?; + + rt.transaction(|st: &mut State, rt| { + let allowed = to == st.registry; + if !allowed { + return Err(actor_error!(forbidden, "transfer not allowed")); + } + + let msg = Messenger { rt, dummy: Default::default() }; + Token::with(msg.rt.store(), &msg, &mut st.token, DATACAP_GRANULARITY, |token| { + token + .transfer(&operator, &from, &to, ¶ms.amount, ¶ms.data) + .exit_code(ExitCode::USR_UNSPECIFIED) + }) + }) + .context("state transaction failed")?; + Ok(()) + } + + pub fn increase_allowance( + rt: &mut RT, + params: IncreaseAllowanceParams, + ) -> Result<(), ActorError> + where + BS: Blockstore, + RT: Runtime, + { + rt.validate_immediate_caller_accept_any()?; + let owner = rt.message().caller(); + let operator = params.operator; + + rt.transaction(|st: &mut State, rt| { + let msg = Messenger { rt, dummy: Default::default() }; + Token::with(msg.rt.store(), &msg, &mut st.token, DATACAP_GRANULARITY, |token| { + token + .increase_allowance(&owner, &operator, ¶ms.amount) + .exit_code(ExitCode::USR_UNSPECIFIED) + }) + }) + .context("state transaction failed")?; + Ok(()) + } + + pub fn decrease_allowance( + rt: &mut RT, + params: DecreaseAllowanceParams, + ) -> Result<(), ActorError> + where + BS: Blockstore, + RT: Runtime, + { + rt.validate_immediate_caller_accept_any()?; + let owner = &rt.message().caller(); + let operator = ¶ms.operator; + + rt.transaction(|st: &mut State, rt| { + let msg = Messenger { rt, dummy: Default::default() }; + Token::with(msg.rt.store(), &msg, &mut st.token, DATACAP_GRANULARITY, |token| { + token + .decrease_allowance(owner, operator, ¶ms.amount) + .exit_code(ExitCode::USR_UNSPECIFIED) + }) + }) + .context("state transaction failed")?; + Ok(()) + } + + pub fn revoke_allowance( + rt: &mut RT, + params: RevokeAllowanceParams, + ) -> Result<(), ActorError> + where + BS: Blockstore, + RT: Runtime, + { + rt.validate_immediate_caller_accept_any()?; + let owner = &rt.message().caller(); + let operator = ¶ms.operator; + + rt.transaction(|st: &mut State, rt| { + let msg = Messenger { rt, dummy: Default::default() }; + Token::with(msg.rt.store(), &msg, &mut st.token, DATACAP_GRANULARITY, |token| { + token.revoke_allowance(owner, operator).exit_code(ExitCode::USR_UNSPECIFIED) + }) + }) + .context("state transaction failed")?; + Ok(()) + } + + pub fn burn(rt: &mut RT, params: BurnParams) -> Result<(), ActorError> + where + BS: Blockstore, + RT: Runtime, + { + rt.validate_immediate_caller_accept_any()?; + let owner = &rt.message().caller(); + + rt.transaction(|st: &mut State, rt| { + let msg = Messenger { rt, dummy: Default::default() }; + Token::with(msg.rt.store(), &msg, &mut st.token, DATACAP_GRANULARITY, |token| { + token.burn(owner, owner, ¶ms.amount).exit_code(ExitCode::USR_UNSPECIFIED) + }) + }) + .context("state transaction failed")?; + Ok(()) + } + + pub fn burn_from(rt: &mut RT, params: BurnFromParams) -> Result<(), ActorError> + where + BS: Blockstore, + RT: Runtime, + { + rt.validate_immediate_caller_accept_any()?; + let operator = &rt.message().caller(); + let owner = ¶ms.from; + + rt.transaction(|st: &mut State, rt| { + let msg = Messenger { rt, dummy: Default::default() }; + Token::with(msg.rt.store(), &msg, &mut st.token, DATACAP_GRANULARITY, |token| { + token.burn(operator, owner, ¶ms.amount).exit_code(ExitCode::USR_UNSPECIFIED) + }) + }) + .context("state transaction failed")?; + Ok(()) + } +} + +// fn msg(rt: &mut RT) -> Messenger +// where +// BS: Blockstore, +// RT: Runtime, +// { +// Messenger { rt, dummy: Default::default() } +// } + +/// Implementation of the token library's messenger trait in terms of the built-in actors' +/// runtime library. +struct Messenger<'a, BS, RT> +where + BS: Blockstore, + RT: Runtime, +{ + rt: &'a mut RT, + // Without this, Rust complains the BS parameter is unused. + // This might be solved better by having BS as an associated type of the Runtime trait. + dummy: PhantomData, +} + +// The trait is implemented for Messenger _reference_ since the mutable ref to rt has been +// moved into it and we can't move the messenger instance since callers need to get at the +// rt that's now in there. +impl<'a, BS, RT> Messaging for &Messenger<'a, BS, RT> +where + BS: Blockstore, + RT: Runtime, +{ + fn actor_id(&self) -> ActorID { + // The Runtime unhelpfully wraps caller in an ID, while the Messaging trait + // is closer to the syscall interface. + self.rt.message().caller().id().unwrap() + } + + fn send( + &self, + to: &Address, + method: MethodNum, + params: &RawBytes, + value: &TokenAmount, + ) -> fil_fungible_token::runtime::messaging::Result { + // The Runtime discards some of the information from the syscall :-( + let fake_gas_used = 0; + let fake_syscall_error_number = ErrorNumber::NotFound; + self.rt + .send(*to, method, params.clone(), value.clone()) + .map(|bytes| Receipt { + exit_code: ExitCode::OK, + return_data: bytes, + gas_used: fake_gas_used, + }) + .map_err(|_| MessagingError::Syscall(fake_syscall_error_number)) + } + + fn resolve_id( + &self, + address: &Address, + ) -> fil_fungible_token::runtime::messaging::Result { + self.rt + .resolve_address(address) + .map(|add| add.id().unwrap()) + .ok_or(MessagingError::AddressNotInitialized(*address)) + } + + fn initialize_account( + &self, + address: &Address, + ) -> fil_fungible_token::runtime::messaging::Result { + let fake_syscall_error_number = ErrorNumber::NotFound; + if self.rt.send(*address, METHOD_SEND, Default::default(), TokenAmount::zero()).is_err() { + return Err(MessagingError::Syscall(fake_syscall_error_number)); + } + self.resolve_id(address) + } +} + +impl ActorCode for Actor { + fn invoke_method( + rt: &mut RT, + method: MethodNum, + params: &RawBytes, + ) -> Result + where + BS: Blockstore, + RT: Runtime, + { + // I'm trying to find a fixed template for these blocks so we can macro it. + // Current blockers: + // - the serialize method maps () to CBOR null (we want no bytes instead) + // - the serialize method can't do BigInts + match FromPrimitive::from_u64(method) { + Some(Method::Constructor) => { + Self::constructor(rt, cbor::deserialize_params(params)?)?; + Ok(RawBytes::default()) + } + Some(Method::Mint) => { + Self::mint(rt, cbor::deserialize_params(params)?)?; + Ok(RawBytes::default()) + } + Some(Method::Destroy) => { + Self::destroy(rt, cbor::deserialize_params(params)?)?; + Ok(RawBytes::default()) + } + Some(Method::Name) => { + let ret = Self::name(rt, cbor::deserialize_params(params)?)?; + serialize(&ret, "name result") + } + Some(Method::Symbol) => { + let ret = Self::symbol(rt, cbor::deserialize_params(params)?)?; + serialize(&ret, "symbol result") + } + Some(Method::TotalSupply) => { + let ret = Self::total_supply(rt, cbor::deserialize_params(params)?)?; + serialize(&BigIntSer(&ret), "total_supply result") + } + Some(Method::BalanceOf) => { + let ret = Self::balance_of(rt, cbor::deserialize_params(params)?)?; + serialize(&BigIntSer(&ret), "balance_of result") + } + Some(Method::Transfer) => { + Self::transfer(rt, cbor::deserialize_params(params)?)?; + Ok(RawBytes::default()) + } + Some(Method::TransferFrom) => { + Self::transfer_from(rt, cbor::deserialize_params(params)?)?; + Ok(RawBytes::default()) + } + Some(Method::IncreaseAllowance) => { + Self::increase_allowance(rt, cbor::deserialize_params(params)?)?; + Ok(RawBytes::default()) + } + Some(Method::DecreaseAllowance) => { + Self::decrease_allowance(rt, cbor::deserialize_params(params)?)?; + Ok(RawBytes::default()) + } + Some(Method::RevokeAllowance) => { + Self::revoke_allowance(rt, cbor::deserialize_params(params)?)?; + Ok(RawBytes::default()) + } + Some(Method::Burn) => { + Self::burn(rt, cbor::deserialize_params(params)?)?; + Ok(RawBytes::default()) + } + Some(Method::BurnFrom) => { + Self::burn_from(rt, cbor::deserialize_params(params)?)?; + Ok(RawBytes::default()) + } + None => Err(actor_error!(unhandled_message; "Invalid method")), + } + } +} diff --git a/actors/datacap/src/state.rs b/actors/datacap/src/state.rs new file mode 100644 index 000000000..335bbab14 --- /dev/null +++ b/actors/datacap/src/state.rs @@ -0,0 +1,24 @@ +use fil_fungible_token::token; +use fvm_ipld_blockstore::Blockstore; +use fvm_ipld_encoding::tuple::*; +use fvm_ipld_encoding::Cbor; +use fvm_shared::address::Address; +use fvm_shared::error::ExitCode; + +use fil_actors_runtime::{ActorError, AsActorError}; + +#[derive(Serialize_tuple, Deserialize_tuple)] +pub struct State { + pub registry: Address, + pub token: token::state::TokenState, +} + +impl State { + pub fn new(store: &BS, registry: Address) -> Result { + let token_state = token::state::TokenState::new(store) + .context_code(ExitCode::USR_ILLEGAL_STATE, "failed to create token state")?; + Ok(State { registry, token: token_state }) + } +} + +impl Cbor for State {} diff --git a/actors/datacap/src/testing.rs b/actors/datacap/src/testing.rs new file mode 100644 index 000000000..e268c24c1 --- /dev/null +++ b/actors/datacap/src/testing.rs @@ -0,0 +1,20 @@ +use fvm_ipld_blockstore::Blockstore; +use fvm_shared::address::Protocol; + +use fil_actors_runtime::MessageAccumulator; + +use crate::State; + +pub struct StateSummary {} + +/// Checks internal invariants of verified registry state. +pub fn check_state_invariants( + state: &State, + _store: &BS, +) -> (StateSummary, MessageAccumulator) { + let acc = MessageAccumulator::default(); + acc.require(state.registry.protocol() == Protocol::ID, "registry must be ID address"); + // TODO: Check invariants in token state. + + (StateSummary {}, acc) +} diff --git a/actors/datacap/src/types.rs b/actors/datacap/src/types.rs new file mode 100644 index 000000000..689249289 --- /dev/null +++ b/actors/datacap/src/types.rs @@ -0,0 +1,93 @@ +use fvm_ipld_encoding::tuple::*; +use fvm_ipld_encoding::{Cbor, RawBytes}; +use fvm_shared::address::Address; +use fvm_shared::bigint::{bigint_ser, BigInt}; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)] +pub struct AllowanceParams { + pub owner: Address, + pub operator: Address, +} + +impl Cbor for AllowanceParams {} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)] +pub struct MintParams { + pub to: Address, + #[serde(with = "bigint_ser")] + pub amount: BigInt, +} + +impl Cbor for MintParams {} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)] +pub struct DestroyParams { + pub owner: Address, + #[serde(with = "bigint_ser")] + pub amount: BigInt, +} + +impl Cbor for DestroyParams {} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)] +pub struct TransferParams { + pub to: Address, + #[serde(with = "bigint_ser")] + pub amount: BigInt, + pub data: RawBytes, +} + +impl Cbor for TransferParams {} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)] +pub struct TransferFromParams { + pub from: Address, + pub to: Address, + #[serde(with = "bigint_ser")] + pub amount: BigInt, + pub data: RawBytes, +} + +impl Cbor for TransferFromParams {} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)] +pub struct IncreaseAllowanceParams { + pub operator: Address, + #[serde(with = "bigint_ser")] + pub amount: BigInt, +} + +impl Cbor for IncreaseAllowanceParams {} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)] +pub struct DecreaseAllowanceParams { + pub operator: Address, + #[serde(with = "bigint_ser")] + pub amount: BigInt, +} + +impl Cbor for DecreaseAllowanceParams {} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)] +pub struct RevokeAllowanceParams { + pub operator: Address, +} + +impl Cbor for RevokeAllowanceParams {} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)] +pub struct BurnParams { + #[serde(with = "bigint_ser")] + pub amount: BigInt, +} + +impl Cbor for BurnParams {} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)] +pub struct BurnFromParams { + pub from: Address, + #[serde(with = "bigint_ser")] + pub amount: BigInt, +} + +impl Cbor for BurnFromParams {} diff --git a/actors/datacap/tests/datacap_actor_test.rs b/actors/datacap/tests/datacap_actor_test.rs new file mode 100644 index 000000000..3f4147448 --- /dev/null +++ b/actors/datacap/tests/datacap_actor_test.rs @@ -0,0 +1,27 @@ +use fvm_shared::address::Address; +use lazy_static::lazy_static; + +mod harness; + +lazy_static! { + static ref VERIFIER: Address = Address::new_id(201); + static ref VERIFIER2: Address = Address::new_id(202); + static ref CLIENT: Address = Address::new_id(301); + static ref CLIENT2: Address = Address::new_id(302); + static ref CLIENT3: Address = Address::new_id(303); + static ref CLIENT4: Address = Address::new_id(304); +} + +mod construction { + use harness::*; + + use crate::*; + + #[test] + fn construct_with_verified() { + let mut rt = new_runtime(); + let h = Harness { registry: *REGISTRY_ADDR }; + h.construct_and_verify(&mut rt, &h.registry); + h.check_state(&rt); + } +} diff --git a/actors/datacap/tests/harness/mod.rs b/actors/datacap/tests/harness/mod.rs new file mode 100644 index 000000000..074876a3a --- /dev/null +++ b/actors/datacap/tests/harness/mod.rs @@ -0,0 +1,59 @@ +use fvm_ipld_encoding::RawBytes; +use fvm_shared::address::Address; +use fvm_shared::MethodNum; +use lazy_static::lazy_static; + +use fil_actor_datacap::testing::check_state_invariants; +use fil_actor_datacap::{Actor as DataCapActor, Method, State}; +use fil_actors_runtime::runtime::Runtime; +use fil_actors_runtime::test_utils::*; +use fil_actors_runtime::{SYSTEM_ACTOR_ADDR, VERIFIED_REGISTRY_ACTOR_ADDR}; + +lazy_static! { + pub static ref RECEIVER_ADDR: Address = Address::new_id(10); + pub static ref REGISTRY_ADDR: Address = *VERIFIED_REGISTRY_ACTOR_ADDR; +} + +pub fn new_runtime() -> MockRuntime { + MockRuntime { + receiver: *RECEIVER_ADDR, + caller: *SYSTEM_ACTOR_ADDR, + caller_type: *SYSTEM_ACTOR_CODE_ID, + ..Default::default() + } +} + +#[allow(dead_code)] +pub fn new_harness() -> (Harness, MockRuntime) { + let mut rt = new_runtime(); + let h = Harness { registry: *REGISTRY_ADDR }; + h.construct_and_verify(&mut rt, &h.registry); + (h, rt) +} + +pub struct Harness { + pub registry: Address, +} + +impl Harness { + pub fn construct_and_verify(&self, rt: &mut MockRuntime, registry: &Address) { + rt.expect_validate_caller_addr(vec![*SYSTEM_ACTOR_ADDR]); + let ret = rt + .call::( + Method::Constructor as MethodNum, + &RawBytes::serialize(registry).unwrap(), + ) + .unwrap(); + + assert_eq!(RawBytes::default(), ret); + rt.verify(); + + let state: State = rt.get_state(); + assert_eq!(self.registry, state.registry); + } + + pub fn check_state(&self, rt: &MockRuntime) { + let (_, acc) = check_state_invariants(&rt.get_state(), rt.store()); + acc.assert_empty(); + } +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index b8c47361f..32061ad84 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -27,7 +27,7 @@ thiserror = "1.0.30" getrandom = { version = "0.2.5", features = ["js"] } hex = { version = "0.4.3", optional = true } anyhow = "1.0.56" -fvm_sdk = { version = "1.0.0-rc.3", optional = true } +fvm_sdk = { version = "1.0.0", optional = true } blake2b_simd = "1.0" fvm_ipld_blockstore = "0.1.1" fvm_ipld_encoding = "0.2.2" diff --git a/runtime/src/actor_error.rs b/runtime/src/actor_error.rs index b3e3a93f4..b4ea67c70 100644 --- a/runtime/src/actor_error.rs +++ b/runtime/src/actor_error.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use fvm_shared::error::ExitCode; use thiserror::Error; @@ -108,3 +110,95 @@ macro_rules! actor_error { $crate::actor_error!($code; $msg $(, $ex)*) }; } + +// Adds context to an actor error's descriptive message. +pub trait ActorContext { + fn context(self, context: C) -> Result + where + C: Display + 'static; + + fn with_context(self, f: F) -> Result + where + C: Display + 'static, + F: FnOnce() -> C; +} + +impl ActorContext for Result { + fn context(self, context: C) -> Result + where + C: Display + 'static, + { + self.map_err(|mut err| { + err.msg = format!("{}: {}", context, err.msg); + err + }) + } + + fn with_context(self, f: F) -> Result + where + C: Display + 'static, + F: FnOnce() -> C, + { + self.map_err(|mut err| { + err.msg = format!("{}: {}", f(), err.msg); + err + }) + } +} + +// Adapts a target into an actor error. +pub trait AsActorError: Sized { + fn exit_code(self, code: ExitCode) -> Result; + + fn context_code(self, code: ExitCode, context: C) -> Result + where + C: Display + 'static; + + fn with_context_code(self, code: ExitCode, f: F) -> Result + where + C: Display + 'static, + F: FnOnce() -> C; +} + +// Note: E should be std::error::Error, revert to this after anyhow:Error is no longer used. +impl AsActorError for Result { + fn exit_code(self, code: ExitCode) -> Result { + self.map_err(|err| ActorError { exit_code: code, msg: err.to_string() }) + } + + fn context_code(self, code: ExitCode, context: C) -> Result + where + C: Display + 'static, + { + self.map_err(|err| ActorError { exit_code: code, msg: format!("{}: {}", context, err) }) + } + + fn with_context_code(self, code: ExitCode, f: F) -> Result + where + C: Display + 'static, + F: FnOnce() -> C, + { + self.map_err(|err| ActorError { exit_code: code, msg: format!("{}: {}", f(), err) }) + } +} + +impl AsActorError for Option { + fn exit_code(self, code: ExitCode) -> Result { + self.ok_or_else(|| ActorError { exit_code: code, msg: "None".to_string() }) + } + + fn context_code(self, code: ExitCode, context: C) -> Result + where + C: Display + 'static, + { + self.ok_or_else(|| ActorError { exit_code: code, msg: context.to_string() }) + } + + fn with_context_code(self, code: ExitCode, f: F) -> Result + where + C: Display + 'static, + F: FnOnce() -> C, + { + self.ok_or_else(|| ActorError { exit_code: code, msg: f().to_string() }) + } +}