use std::collections::HashMap;
use std::iter;
use cid::Cid;
use ext::init::{ExecParams, ExecReturn};
use fvm_ipld_encoding::{ipld_block::IpldBlock, tuple::*, RawBytes};
use fvm_shared::{address::Address, error::ExitCode, ActorID, METHOD_CONSTRUCTOR};
use num_derive::FromPrimitive;
use recall_fil_actors_runtime::{
actor_dispatch_unrestricted, actor_error, deserialize_block, extract_send_result,
runtime::{builtins::Type, ActorCode, Runtime},
ActorDowncast, ActorError, ADM_ACTOR_ID, INIT_ACTOR_ADDR, SYSTEM_ACTOR_ADDR,
};
use crate::state::PermissionMode;
pub use crate::state::{Kind, Metadata, PermissionModeParams, State};
pub mod ext;
mod state;
#[cfg(feature = "fil-actor")]
recall_fil_actors_runtime::wasm_trampoline!(AdmActor);
#[derive(FromPrimitive)]
#[repr(u64)]
pub enum Method {
Constructor = METHOD_CONSTRUCTOR,
CreateExternal = 1214262202,
UpdateDeployers = 1768606754,
ListMetadata = 2283215593,
}
#[derive(Debug, Serialize_tuple, Deserialize_tuple)]
pub struct ConstructorParams {
pub machine_codes: HashMap<Kind, Cid>,
pub permission_mode: PermissionModeParams,
}
#[derive(Debug, Serialize_tuple, Deserialize_tuple)]
pub struct CreateExternalParams {
pub owner: Address,
pub kind: Kind,
pub metadata: HashMap<String, String>,
}
#[derive(Serialize_tuple, Deserialize_tuple, Debug, PartialEq, Eq)]
pub struct CreateExternalReturn {
pub actor_id: ActorID,
pub robust_address: Option<Address>,
}
#[derive(Debug, Serialize_tuple, Deserialize_tuple)]
pub struct ListMetadataParams {
pub owner: Address,
}
fn create_machine(
rt: &impl Runtime,
owner: Address,
code_cid: Cid,
metadata: HashMap<String, String>,
) -> Result<CreateExternalReturn, ActorError> {
let constructor_params =
RawBytes::serialize(ext::machine::ConstructorParams { owner, metadata })?;
let ret: ExecReturn = deserialize_block(extract_send_result(rt.send_simple(
&INIT_ACTOR_ADDR,
ext::init::EXEC_METHOD,
IpldBlock::serialize_cbor(&ExecParams { code_cid, constructor_params })?,
rt.message().value_received(),
))?)?;
let actor_id = ret.id_address.id().unwrap();
let address = Address::new_id(actor_id);
extract_send_result(rt.send_simple(
&ret.id_address,
ext::machine::INIT_METHOD,
IpldBlock::serialize_cbor(&ext::machine::InitParams { address })?,
rt.message().value_received(),
))?;
Ok(CreateExternalReturn { actor_id, robust_address: Some(ret.robust_address) })
}
fn ensure_deployer_allowed(rt: &impl Runtime) -> Result<(), ActorError> {
let caller_id = rt.message().caller().id().unwrap();
let code_cid = rt.get_actor_code_cid(&caller_id).expect("caller has code");
if rt.resolve_builtin_actor_type(&code_cid) == Some(Type::EVM) {
return Ok(());
}
let state: State = rt.state()?;
if !state.can_deploy(rt, caller_id)? {
return Err(ActorError::forbidden(String::from("sender not allowed to deploy contracts")));
}
Ok(())
}
fn get_machine_code(rt: &impl Runtime, kind: &Kind) -> Result<Cid, ActorError> {
rt.state::<State>()?
.get_machine_code(rt.store(), kind)?
.ok_or(ActorError::not_found(format!("machine code for kind '{}' not found", kind)))
}
pub struct AdmActor;
impl AdmActor {
pub fn constructor(rt: &impl Runtime, args: ConstructorParams) -> Result<(), ActorError> {
let actor_id = rt.resolve_address(&rt.message().receiver()).unwrap();
if actor_id != ADM_ACTOR_ID {
return Err(ActorError::forbidden(format!(
"The ADM must be deployed at {ADM_ACTOR_ID}, was deployed at {actor_id}"
)));
}
rt.validate_immediate_caller_is(iter::once(&SYSTEM_ACTOR_ADDR))?;
let st = State::new(rt.store(), args.machine_codes, args.permission_mode)?;
rt.create(&st)
}
fn update_deployers(rt: &impl Runtime, deployers: Vec<Address>) -> Result<(), ActorError> {
rt.validate_immediate_caller_accept_any()?;
let state: State = rt.state()?;
if !matches!(state.permission_mode, PermissionMode::AllowList(_)) {
return Err(ActorError::forbidden(String::from(
"deployers can only be updated in allowlist mode",
)));
};
let caller_id = rt.message().caller().id().unwrap();
if !state.can_deploy(rt, caller_id)? {
return Err(ActorError::forbidden(String::from(
"sender not allowed to update deployers",
)));
}
rt.transaction(|st: &mut State, rt| {
st.set_deployers(rt.store(), deployers).map_err(|e| {
e.downcast_default(ExitCode::USR_ILLEGAL_ARGUMENT, "failed to set deployers")
})
})?;
Ok(())
}
pub fn create_external(
rt: &impl Runtime,
params: CreateExternalParams,
) -> Result<CreateExternalReturn, ActorError> {
ensure_deployer_allowed(rt)?;
rt.validate_immediate_caller_accept_any()?;
let owner_id = rt.resolve_address(¶ms.owner).ok_or(ActorError::illegal_argument(
format!("failed to resolve actor for address {}", params.owner),
))?;
let owner = Address::new_id(owner_id);
let machine_code = get_machine_code(rt, ¶ms.kind)?;
let ret = create_machine(rt, owner, machine_code, params.metadata.clone())?;
let address = Address::new_id(ret.actor_id);
rt.transaction(|st: &mut State, rt| {
st.set_metadata(rt.store(), owner, address, params.kind, params.metadata).map_err(|e| {
e.downcast_default(ExitCode::USR_ILLEGAL_ARGUMENT, "failed to set machine metadata")
})
})?;
Ok(ret)
}
pub fn list_metadata(
rt: &impl Runtime,
params: ListMetadataParams,
) -> Result<Vec<Metadata>, ActorError> {
rt.validate_immediate_caller_accept_any()?;
let owner_id = rt.resolve_address(¶ms.owner).ok_or(ActorError::illegal_argument(
format!("failed to resolve actor for address {}", params.owner),
))?;
let owner_address = Address::new_id(owner_id);
let st: State = rt.state()?;
let metadata = st.get_metadata(rt.store(), owner_address).map_err(|e| {
e.downcast_default(ExitCode::USR_ILLEGAL_ARGUMENT, "failed to get metadata")
})?;
Ok(metadata)
}
}
impl ActorCode for AdmActor {
type Methods = Method;
fn name() -> &'static str {
"ADMAddressManager"
}
actor_dispatch_unrestricted! {
Constructor => constructor,
CreateExternal => create_external,
UpdateDeployers => update_deployers,
ListMetadata => list_metadata,
}
}