mod state;
mod types;
pub use self::state::*;
pub use self::types::*;
use crate::{
make_map, make_map_with_root, resolve_to_id_addr, ActorDowncast, Map, CALLER_TYPES_SIGNABLE,
INIT_ACTOR_ADDR,
};
use address::{Address, Protocol};
use encoding::to_vec;
use fil_types::NetworkVersion;
use ipld_blockstore::BlockStore;
use num_bigint::Sign;
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use runtime::{ActorCode, Runtime, Syscalls};
use std::collections::HashSet;
use std::error::Error as StdError;
use vm::{
actor_error, ActorError, ExitCode, MethodNum, Serialized, TokenAmount, METHOD_CONSTRUCTOR,
};
#[derive(FromPrimitive)]
#[repr(u64)]
pub enum Method {
Constructor = METHOD_CONSTRUCTOR,
Propose = 2,
Approve = 3,
Cancel = 4,
AddSigner = 5,
RemoveSigner = 6,
SwapSigner = 7,
ChangeNumApprovalsThreshold = 8,
LockBalance = 9,
}
pub struct Actor;
impl Actor {
pub fn constructor<BS, RT>(rt: &mut RT, params: ConstructorParams) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_is(std::iter::once(&*INIT_ACTOR_ADDR))?;
if params.signers.is_empty() {
return Err(actor_error!(ErrIllegalArgument; "Must have at least one signer"));
}
let mut resolved_signers = Vec::with_capacity(params.signers.len());
let mut dedup_signers = HashSet::with_capacity(params.signers.len());
for signer in ¶ms.signers {
let resolved = resolve_to_id_addr(rt, signer).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to resolve addr {} to ID addr", signer),
)
})?;
if dedup_signers.contains(&resolved) {
return Err(
actor_error!(ErrIllegalArgument; "duplicate signer not allowed: {}", signer),
);
}
resolved_signers.push(resolved);
dedup_signers.insert(resolved);
}
if params.num_approvals_threshold > params.signers.len() {
return Err(
actor_error!(ErrIllegalArgument; "must not require more approvals than signers"),
);
}
if params.num_approvals_threshold < 1 {
return Err(actor_error!(ErrIllegalArgument; "must require at least one approval"));
}
if params.unlock_duration < 0 {
return Err(actor_error!(ErrIllegalArgument; "negative unlock duration disallowed"));
}
let empty_root = make_map::<_, ()>(rt.store()).flush().map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "Failed to create empty map")
})?;
let mut st: State = State {
signers: resolved_signers,
num_approvals_threshold: params.num_approvals_threshold,
pending_txs: empty_root,
initial_balance: TokenAmount::from(0),
next_tx_id: Default::default(),
start_epoch: Default::default(),
unlock_duration: Default::default(),
};
if params.unlock_duration != 0 {
st.set_locked(
rt.curr_epoch(),
params.unlock_duration,
rt.message().value_received().clone(),
);
}
rt.create(&st)?;
Ok(())
}
pub fn propose<BS, RT>(rt: &mut RT, params: ProposeParams) -> Result<ProposeReturn, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_type(CALLER_TYPES_SIGNABLE.iter())?;
let proposer: Address = *rt.message().caller();
if params.value.sign() == Sign::Minus {
return Err(
actor_error!(ErrIllegalArgument; "proposed value must be non-negative, was {}", params.value),
);
}
let (txn_id, txn) = rt.transaction(|st: &mut State, rt| {
if !is_signer(&proposer, &st.signers)? {
return Err(actor_error!(ErrForbidden; "{} is not a signer", proposer));
}
let mut ptx = make_map_with_root(&st.pending_txs, rt.store()).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to load pending transactions",
)
})?;
let t_id = st.next_tx_id;
st.next_tx_id.0 += 1;
let txn = Transaction {
to: params.to,
value: params.value,
method: params.method,
params: params.params,
approved: Vec::new(),
};
ptx.set(t_id.key(), txn.clone()).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to put transaction for propose",
)
})?;
st.pending_txs = ptx.flush().map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to flush pending transactions",
)
})?;
Ok((t_id, txn))
})?;
let (applied, ret, code) = Self::approve_transaction(rt, txn_id, txn)?;
Ok(ProposeReturn {
txn_id,
applied,
code,
ret,
})
}
pub fn approve<BS, RT>(rt: &mut RT, params: TxnIDParams) -> Result<ApproveReturn, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_type(CALLER_TYPES_SIGNABLE.iter())?;
let caller_addr: Address = *rt.message().caller();
let id = params.id;
let (st, txn) = rt.transaction(|st: &mut State, rt| {
if !is_signer(&caller_addr, &st.signers)? {
return Err(actor_error!(ErrForbidden; "{} is not a signer", caller_addr));
}
let ptx = make_map_with_root(&st.pending_txs, rt.store()).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to load pending transactions",
)
})?;
let txn = get_transaction(rt, &ptx, params.id, params.proposal_hash, true)?;
Ok((st.clone(), txn.clone()))
})?;
let (applied, ret, code) = execute_transaction_if_approved(rt, &st, id, &txn)?;
if !applied {
let (applied, ret, code) = Self::approve_transaction(rt, id, txn)?;
Ok(ApproveReturn { applied, code, ret })
} else {
Ok(ApproveReturn { applied, code, ret })
}
}
pub fn cancel<BS, RT>(rt: &mut RT, params: TxnIDParams) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_type(CALLER_TYPES_SIGNABLE.iter())?;
let caller_addr: Address = *rt.message().caller();
rt.transaction(|st: &mut State, rt| {
if !is_signer(&caller_addr, &st.signers)? {
return Err(actor_error!(ErrForbidden; "{} is not a signer", caller_addr));
}
let mut ptx = make_map_with_root(&st.pending_txs, rt.store()).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to load pending transactions",
)
})?;
let tx = get_pending_transaction(&ptx, params.id).map_err(|e| {
e.downcast_default(
ExitCode::ErrNotFound,
"Failed to get transaction for cancel",
)
})?;
if tx.approved.get(0) != Some(&caller_addr) {
return Err(
actor_error!(ErrForbidden; "Cannot cancel another signers transaction"),
);
}
let calculated_hash = compute_proposal_hash(&tx, rt).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to compute proposal hash for (tx: {:?})", params.id),
)
})?;
if params.proposal_hash != calculated_hash {
return Err(actor_error!(ErrIllegalState; "hash does not match proposal params"));
}
ptx.delete(¶ms.id.key())
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to delete pending transaction",
)
})?
.ok_or_else(|| {
actor_error!(
ErrIllegalState,
"failed to delete pending transaction: does not exist"
)
})?;
st.pending_txs = ptx.flush().map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to flush pending transactions",
)
})?;
Ok(())
})
}
pub fn add_signer<BS, RT>(rt: &mut RT, params: AddSignerParams) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let receiver = *rt.message().receiver();
rt.validate_immediate_caller_is(std::iter::once(&receiver))?;
let resolved_new_signer = resolve_to_id_addr(rt, ¶ms.signer).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to resolve address {}", params.signer),
)
})?;
rt.transaction(|st: &mut State, _| {
if is_signer(&resolved_new_signer, &st.signers)? {
return Err(
actor_error!(ErrForbidden; "{} is already a signer", resolved_new_signer),
);
}
st.signers.push(resolved_new_signer);
if params.increase {
st.num_approvals_threshold += 1;
}
Ok(())
})
}
pub fn remove_signer<BS, RT>(rt: &mut RT, params: RemoveSignerParams) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let receiver = *rt.message().receiver();
rt.validate_immediate_caller_is(std::iter::once(&receiver))?;
let resolved_old_signer = resolve_to_id_addr(rt, ¶ms.signer).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to resolve address {}", params.signer),
)
})?;
rt.transaction(|st: &mut State, _| {
if !is_signer(&resolved_old_signer, &st.signers)? {
return Err(actor_error!(ErrForbidden; "{} is not a signer", resolved_old_signer));
}
if st.signers.len() == 1 {
return Err(actor_error!(ErrForbidden; "Cannot remove only signer"));
}
st.signers.retain(|s| s != &resolved_old_signer);
if !params.decrease && st.signers.len() < st.num_approvals_threshold {
return Err(actor_error!(ErrIllegalArgument;
"can't reduce signers to {} below threshold {} with decrease=false",
st.signers.len(), st.num_approvals_threshold));
}
if params.decrease {
st.num_approvals_threshold -= 1;
}
Ok(())
})?;
Ok(())
}
pub fn swap_signer<BS, RT>(rt: &mut RT, params: SwapSignerParams) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let receiver = *rt.message().receiver();
rt.validate_immediate_caller_is(std::iter::once(&receiver))?;
let from_resolved = resolve_to_id_addr(rt, ¶ms.from).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to resolve address {}", params.from),
)
})?;
let to_resolved = resolve_to_id_addr(rt, ¶ms.to).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to resolve address {}", params.to),
)
})?;
rt.transaction(|st: &mut State, _| {
if !is_signer(&from_resolved, &st.signers)? {
return Err(actor_error!(ErrForbidden; "{} is not a signer", from_resolved));
}
if is_signer(&to_resolved, &st.signers)? {
return Err(
actor_error!(ErrIllegalArgument; "{} is already a signer", to_resolved),
);
}
st.signers.retain(|s| s != &from_resolved);
st.signers.push(to_resolved);
Ok(())
})?;
Ok(())
}
pub fn change_num_approvals_threshold<BS, RT>(
rt: &mut RT,
params: ChangeNumApprovalsThresholdParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let receiver = *rt.message().receiver();
rt.validate_immediate_caller_is(std::iter::once(&receiver))?;
rt.transaction(|st: &mut State, _| {
if params.new_threshold == 0 || params.new_threshold > st.signers.len() {
return Err(actor_error!(ErrIllegalArgument; "New threshold value not supported"));
}
st.num_approvals_threshold = params.new_threshold;
Ok(())
})?;
Ok(())
}
pub fn lock_balance<BS, RT>(rt: &mut RT, params: LockBalanceParams) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
if rt.network_version() < NetworkVersion::V2 {
return Err(actor_error!(
SysErrInvalidMethod,
"invalid method until network version 2"
));
}
let receiver = *rt.message().receiver();
rt.validate_immediate_caller_is(std::iter::once(&receiver))?;
if params.unlock_duration <= 0 {
return Err(actor_error!(
ErrIllegalArgument,
"unlock duration must be positive"
));
}
rt.transaction(|st: &mut State, _| {
if st.unlock_duration != 0 {
return Err(actor_error!(
ErrForbidden,
"modification of unlock disallowed"
));
}
st.set_locked(params.start_epoch, params.unlock_duration, params.amount);
Ok(())
})?;
Ok(())
}
fn approve_transaction<BS, RT>(
rt: &mut RT,
tx_id: TxnID,
mut txn: Transaction,
) -> Result<(bool, Serialized, ExitCode), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
for previous_approver in &txn.approved {
if previous_approver == rt.message().caller() {
return Err(actor_error!(ErrForbidden;
"{} already approved this message", previous_approver));
}
}
let st = rt.transaction(|st: &mut State, rt| {
let mut ptx = make_map_with_root(&st.pending_txs, rt.store()).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to load pending transactions",
)
})?;
txn.approved.push(*rt.message().caller());
ptx.set(tx_id.key(), txn.clone()).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to put transaction {} for approval", tx_id.0),
)
})?;
st.pending_txs = ptx.flush().map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to flush pending transactions",
)
})?;
Ok(st.clone())
})?;
execute_transaction_if_approved(rt, &st, tx_id, &txn)
}
}
fn execute_transaction_if_approved<BS, RT>(
rt: &mut RT,
st: &State,
txn_id: TxnID,
txn: &Transaction,
) -> Result<(bool, Serialized, ExitCode), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let mut out = Serialized::default();
let mut code = ExitCode::Ok;
let mut applied = false;
let threshold_met = txn.approved.len() >= st.num_approvals_threshold;
if threshold_met {
st.check_available(rt.current_balance()?, &txn.value, rt.curr_epoch())
.map_err(
|e| actor_error!(ErrInsufficientFunds; "insufficient funds unlocked: {}", e),
)?;
match rt.send(txn.to, txn.method, txn.params.clone(), txn.value.clone()) {
Ok(ser) => {
out = ser;
}
Err(e) => {
code = e.exit_code();
}
}
applied = true;
rt.transaction(|st: &mut State, rt| {
let mut ptx = make_map_with_root::<_, Transaction>(&st.pending_txs, rt.store())
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to load pending transactions",
)
})?;
ptx.delete(&txn_id.key())
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to delete transaction for cleanup",
)
})?
.ok_or_else(|| {
actor_error!(
ErrIllegalState,
"failed to delete transaction for cleanup: does not exist"
)
})?;
st.pending_txs = ptx.flush().map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to flush pending transactions",
)
})?;
Ok(())
})?;
}
Ok((applied, out, code))
}
fn get_pending_transaction<'bs, 'm, BS: BlockStore>(
ptx: &'m Map<'bs, BS, Transaction>,
txn_id: TxnID,
) -> Result<&'m Transaction, Box<dyn StdError>> {
Ok(ptx
.get(&txn_id.key())
.map_err(|e| e.downcast_wrap("failed to read transaction"))?
.ok_or_else(|| actor_error!(ErrNotFound, "failed to find transaction: {}", txn_id.0))?)
}
fn get_transaction<'bs, 'm, BS, RT>(
rt: &RT,
ptx: &'m Map<'bs, BS, Transaction>,
txn_id: TxnID,
proposal_hash: Vec<u8>,
check_hash: bool,
) -> Result<&'m Transaction, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let txn = get_pending_transaction(ptx, txn_id).map_err(|e| {
e.downcast_default(
ExitCode::ErrNotFound,
"failed to get transaction for approval",
)
})?;
if check_hash {
let calculated_hash = compute_proposal_hash(&txn, rt).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to compute proposal hash for (tx: {:?})", txn_id),
)
})?;
if proposal_hash != calculated_hash {
return Err(actor_error!(ErrIllegalArgument;
"hash does not match proposal params (ensure requester is an ID address)"));
}
}
Ok(txn)
}
fn is_signer(address: &Address, signers: &[Address]) -> Result<bool, ActorError> {
assert_eq!(
address.protocol(),
Protocol::ID,
"address {} passed to is_signer must be a resolved address",
address
);
for signer in signers {
if signer == address {
return Ok(true);
}
}
Ok(false)
}
fn compute_proposal_hash(
txn: &Transaction,
sys: &dyn Syscalls,
) -> Result<[u8; 32], Box<dyn StdError>> {
let proposal_hash = ProposalHashData {
requester: txn.approved.get(0),
to: &txn.to,
value: &txn.value,
method: &txn.method,
params: &txn.params,
};
let data = to_vec(&proposal_hash)
.map_err(|e| ActorError::from(e).wrap("failed to construct multisig approval hash"))?;
sys.hash_blake2b(&data)
}
impl ActorCode for Actor {
fn invoke_method<BS, RT>(
rt: &mut RT,
method: MethodNum,
params: &Serialized,
) -> Result<Serialized, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
match FromPrimitive::from_u64(method) {
Some(Method::Constructor) => {
Self::constructor(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::Propose) => {
let res = Self::propose(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::serialize(res)?)
}
Some(Method::Approve) => {
let res = Self::approve(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::serialize(res)?)
}
Some(Method::Cancel) => {
Self::cancel(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::AddSigner) => {
Self::add_signer(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::RemoveSigner) => {
Self::remove_signer(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::SwapSigner) => {
Self::swap_signer(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::ChangeNumApprovalsThreshold) => {
Self::change_num_approvals_threshold(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::LockBalance) => {
Self::lock_balance(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
None => Err(actor_error!(SysErrInvalidMethod; "Invalid method")),
}
}
}