use std::iter;
use num_traits::Zero;
use recall_fil_actors_evm_shared::address::EthAddress;
use ext::{
account::PUBKEY_ADDRESS_METHOD,
evm::RESURRECT_METHOD,
init::{Exec4Params, Exec4Return},
};
use recall_fil_actors_runtime::{
actor_dispatch_unrestricted, actor_error, deserialize_block, extract_send_result, ActorError,
AsActorError, EAM_ACTOR_ID, INIT_ACTOR_ADDR, SYSTEM_ACTOR_ADDR,
};
use fvm_ipld_encoding::ipld_block::IpldBlock;
use fvm_shared::{error::ExitCode, sys::SendFlags, ActorID, METHOD_CONSTRUCTOR};
use serde::{Deserialize, Serialize};
pub mod ext;
use recall_fil_actors_runtime::runtime::builtins::Type;
use recall_fil_actors_runtime::runtime::{ActorCode, Runtime};
use fvm_ipld_encoding::{strict_bytes, tuple::*, RawBytes};
use fvm_shared::address::{Address, Payload};
use fvm_shared::crypto::hash::SupportedHashes;
use num_derive::FromPrimitive;
#[cfg(feature = "fil-actor")]
recall_fil_actors_runtime::wasm_trampoline!(EamActor);
#[derive(FromPrimitive)]
#[repr(u64)]
pub enum Method {
Constructor = METHOD_CONSTRUCTOR,
Create = 2,
Create2 = 3,
CreateExternal = 4,
}
pub fn compute_address_create(rt: &impl Runtime, from: &EthAddress, nonce: u64) -> EthAddress {
let mut stream = rlp::RlpStream::new();
stream.begin_list(2).append(&&from.0[..]).append(&nonce);
EthAddress(hash_20(rt, &stream.out()))
}
pub fn compute_address_create2(
rt: &impl Runtime,
from: &EthAddress,
salt: &[u8; 32],
initcode: &[u8],
) -> EthAddress {
let inithash = rt.hash(SupportedHashes::Keccak256, initcode);
EthAddress(hash_20(rt, &[&[0xff], &from.0[..], salt, &inithash].concat()))
}
pub fn compute_address_create_external(rt: &impl Runtime, from: &EthAddress) -> EthAddress {
compute_address_create(rt, from, rt.message().nonce())
}
#[derive(Serialize_tuple, Deserialize_tuple)]
pub struct CreateParams {
#[serde(with = "strict_bytes")]
pub initcode: Vec<u8>,
pub nonce: u64,
}
#[derive(Serialize_tuple, Deserialize_tuple)]
pub struct Create2Params {
#[serde(with = "strict_bytes")]
pub initcode: Vec<u8>,
#[serde(with = "strict_bytes")]
pub salt: [u8; 32],
}
#[derive(Serialize, Deserialize)]
#[serde(transparent)]
pub struct CreateExternalParams(#[serde(with = "strict_bytes")] pub Vec<u8>);
#[derive(Serialize_tuple, Deserialize_tuple, Debug, PartialEq, Eq)]
pub struct Return {
pub actor_id: ActorID,
pub robust_address: Option<Address>,
pub eth_address: EthAddress,
}
pub type CreateReturn = Return;
pub type Create2Return = Return;
pub type CreateExternalReturn = Return;
impl Return {
fn from_exec4(exec4: Exec4Return, eth_address: EthAddress) -> Self {
Self {
actor_id: exec4.id_address.id().unwrap(),
robust_address: Some(exec4.robust_address),
eth_address,
}
}
}
fn hash_20(rt: &impl Runtime, data: &[u8]) -> [u8; 20] {
rt.hash(SupportedHashes::Keccak256, data)[12..32].try_into().unwrap()
}
fn can_assign_address(addr: &EthAddress) -> bool {
!addr.is_precompile() && !addr.is_id() && !addr.is_null()
}
fn create_actor(
rt: &impl Runtime,
creator: EthAddress,
new_addr: EthAddress,
initcode: Vec<u8>,
) -> Result<Return, ActorError> {
if !can_assign_address(&new_addr) {
return Err(ActorError::forbidden("cannot create address with a reserved prefix".into()));
}
let constructor_params =
RawBytes::serialize(ext::evm::ConstructorParams { creator, initcode: initcode.into() })?;
let value = rt.message().value_received();
let f4_addr = Address::new_delegated(EAM_ACTOR_ID, &new_addr.0).unwrap();
if let Some(id) = rt.resolve_address(&f4_addr) {
let caller_code_cid = rt.get_actor_code_cid(&id).expect("failed to lookup actor code");
match rt.resolve_builtin_actor_type(&caller_code_cid) {
Some(Type::EVM) => {
extract_send_result(rt.send_simple(
&Address::new_id(id),
RESURRECT_METHOD,
constructor_params.into(),
value,
))?;
return Ok(Return { actor_id: id, robust_address: None, eth_address: new_addr });
}
Some(Type::Placeholder) => {}
_ => {
return Err(
actor_error!(forbidden; "cannot deploy contract over existing contract at address {new_addr}"),
);
}
}
}
let init_params = Exec4Params {
code_cid: rt.get_code_cid_for_type(Type::EVM),
constructor_params,
subaddress: new_addr.0.to_vec().into(),
};
let ret: ext::init::Exec4Return = deserialize_block(extract_send_result(rt.send_simple(
&INIT_ACTOR_ADDR,
ext::init::EXEC4_METHOD,
IpldBlock::serialize_cbor(&init_params)?,
value,
))?)?;
Ok(Return::from_exec4(ret, new_addr))
}
fn resolve_eth_address(rt: &impl Runtime, actor_id: ActorID) -> Result<EthAddress, ActorError> {
match rt.lookup_delegated_address(actor_id).map(|a| *a.payload()) {
Some(Payload::Delegated(addr)) if addr.namespace() == EAM_ACTOR_ID => Ok(EthAddress(
addr.subaddress()
.try_into()
.context_code(ExitCode::USR_FORBIDDEN, "caller's eth address isn't valid")?,
)),
_ => Err(actor_error!(forbidden; "caller doesn't have an eth address")),
}
}
fn resolve_caller_external(rt: &impl Runtime) -> Result<(EthAddress, EthAddress), ActorError> {
let caller = rt.message().caller();
let caller_id = caller.id().unwrap();
let caller_code_cid = rt.get_actor_code_cid(&caller_id).expect("failed to lookup caller code");
match rt.resolve_builtin_actor_type(&caller_code_cid) {
Some(Type::Account) => {
let result = rt
.send(
&caller,
PUBKEY_ADDRESS_METHOD,
None,
Zero::zero(),
None,
SendFlags::READ_ONLY,
)
.context_code(
ExitCode::USR_ASSERTION_FAILED,
"account failed to return its key address",
)?;
if !result.exit_code.is_success() {
return Err(ActorError::checked(
result.exit_code,
"failed to retrieve account robust address".to_string(),
None,
));
}
let robust_addr: Address = deserialize_block(result.return_data)?;
let robust_eth_bytes = hash_20(rt, &robust_addr.to_bytes());
Ok((EthAddress::from_id(caller_id), EthAddress(robust_eth_bytes)))
}
Some(Type::EthAccount) => {
let addr = resolve_eth_address(rt, caller_id)?;
Ok((addr, addr))
}
Some(t) => Err(ActorError::forbidden(format!("disallowed caller type {}", t.name()))),
None => Err(ActorError::forbidden(format!("disallowed caller code {caller_code_cid}"))),
}
}
pub struct EamActor;
impl EamActor {
pub fn constructor(rt: &impl Runtime) -> Result<(), ActorError> {
let actor_id = rt.resolve_address(&rt.message().receiver()).unwrap();
if actor_id != EAM_ACTOR_ID {
return Err(ActorError::forbidden(format!(
"The Ethereum Address Manager must be deployed at {EAM_ACTOR_ID}, was deployed at {actor_id}"
)));
}
rt.validate_immediate_caller_is(iter::once(&SYSTEM_ACTOR_ADDR))
}
pub fn create(rt: &impl Runtime, params: CreateParams) -> Result<CreateReturn, ActorError> {
rt.validate_immediate_caller_type(&[Type::EVM])?;
let caller_addr = resolve_eth_address(rt, rt.message().caller().id().unwrap())?;
let eth_addr = compute_address_create(rt, &caller_addr, params.nonce);
create_actor(rt, caller_addr, eth_addr, params.initcode)
}
pub fn create2(rt: &impl Runtime, params: Create2Params) -> Result<Create2Return, ActorError> {
rt.validate_immediate_caller_type(&[Type::EVM])?;
let caller_addr = resolve_eth_address(rt, rt.message().caller().id().unwrap())?;
let eth_addr = compute_address_create2(rt, &caller_addr, ¶ms.salt, ¶ms.initcode);
create_actor(rt, caller_addr, eth_addr, params.initcode)
}
pub fn create_external(
rt: &impl Runtime,
params: CreateExternalParams,
) -> Result<CreateExternalReturn, ActorError> {
rt.validate_immediate_caller_is(&[rt.message().origin()])?;
let (owner_addr, stable_addr) = resolve_caller_external(rt)?;
let eth_addr = compute_address_create_external(rt, &stable_addr);
create_actor(rt, owner_addr, eth_addr, params.0)
}
}
impl ActorCode for EamActor {
type Methods = Method;
fn name() -> &'static str {
"EVMAddressManager"
}
actor_dispatch_unrestricted! {
Constructor => constructor,
Create => create,
Create2 => create2,
CreateExternal => create_external,
}
}
#[cfg(test)]
mod test {
use fvm_shared::error::ExitCode;
use recall_fil_actors_runtime::test_utils::MockRuntime;
use crate::compute_address_create2;
use super::{compute_address_create, create_actor, EthAddress};
#[test]
fn test_create_actor_rejects() {
let rt = MockRuntime::default();
let creator = EthAddress::from_id(1);
let new_addr = EthAddress::from_id(8224);
assert_eq!(
ExitCode::USR_FORBIDDEN,
create_actor(&rt, creator, new_addr, Vec::new()).unwrap_err().exit_code()
);
let mut new_addr = EthAddress::null();
new_addr.0[19] = 0x20;
assert_eq!(
ExitCode::USR_FORBIDDEN,
create_actor(&rt, creator, new_addr, Vec::new()).unwrap_err().exit_code()
);
new_addr.0[0] = 0xfe;
assert_eq!(
ExitCode::USR_FORBIDDEN,
create_actor(&rt, creator, new_addr, Vec::new()).unwrap_err().exit_code()
);
let new_addr = EthAddress::null();
assert_eq!(
ExitCode::USR_FORBIDDEN,
create_actor(&rt, creator, new_addr, Vec::new()).unwrap_err().exit_code()
);
}
#[test]
fn test_create_address() {
let rt = MockRuntime::default();
for (from, nonce, expected) in &[
([0u8; 20], 0u64, hex_literal::hex!("bd770416a3345f91e4b34576cb804a576fa48eb1")),
([0; 20], 200, hex_literal::hex!("a6b14387c1356b443061155e9c3e17f72c1777e5")),
([123; 20], 12345, hex_literal::hex!("809a9ab0471e78ee5100e96ca4d0828d1b97e2ba")),
] {
let result = compute_address_create(&rt, &EthAddress(*from), *nonce);
assert_eq!(result.0[..], expected[..]);
}
}
#[test]
fn test_create_address2() {
let rt = MockRuntime::default();
for (from, salt, initcode, expected) in &[
(
[0u8; 20],
[0u8; 32],
&b""[..],
hex_literal::hex!("e33c0c7f7df4809055c3eba6c09cfe4baf1bd9e0"),
),
(
[0x99u8; 20],
[0x42; 32],
&b"foobar"[..],
hex_literal::hex!("64425c93a90901271fa355c2bc462190803b97d4"),
),
] {
let result = compute_address_create2(&rt, &EthAddress(*from), salt, initcode);
assert_eq!(result.0[..], expected[..]);
}
}
}