1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
use std::{cell::RefCell, collections::HashMap};

use cid::Cid;
use fvm_ipld_encoding::ipld_block::IpldBlock;
use fvm_shared::{
    address::Address, econ::TokenAmount, error::ErrorNumber, error::ExitCode, ActorID, Response,
};

use super::Syscalls;

#[derive(Clone, Default, Debug)]
pub struct TestMessage {
    pub method: u64,
    pub params: Option<IpldBlock>,
    pub value: TokenAmount,
}

#[derive(Clone, Default, Debug)]
pub struct FakeSyscalls {
    /// The root of the receiving actor
    pub root: RefCell<Cid>,
    /// The f0 ID of the receiving actor
    pub actor_id: ActorID,

    /// Actor ID to return as caller ID
    pub caller_id: RefCell<ActorID>,

    /// A map of addresses that were instantiated in this runtime
    pub addresses: RefCell<HashMap<Address, ActorID>>,
    /// The next-to-allocate f0 address
    pub next_actor_id: RefCell<ActorID>,

    /// The last message sent via this runtime
    pub last_message: RefCell<Option<TestMessage>>,
    /// Flag to control message success
    pub abort_next_send: RefCell<bool>,
}

impl FakeSyscalls {
    /// Set the ActorID returned as caller
    pub fn set_caller_id(&self, new_id: ActorID) {
        self.caller_id.replace(new_id);
    }
}

impl Syscalls for FakeSyscalls {
    fn root(&self) -> Result<Cid, super::NoStateError> {
        Ok(*self.root.borrow())
    }

    fn set_root(&self, cid: &Cid) -> Result<(), super::NoStateError> {
        self.root.replace(*cid);
        Ok(())
    }

    fn receiver(&self) -> fvm_shared::ActorID {
        self.actor_id
    }

    fn caller(&self) -> fvm_shared::ActorID {
        *self.caller_id.borrow()
    }

    fn send(
        &self,
        to: &fvm_shared::address::Address,
        method: fvm_shared::MethodNum,
        params: Option<fvm_ipld_encoding::ipld_block::IpldBlock>,
        value: fvm_shared::econ::TokenAmount,
    ) -> Result<Response, ErrorNumber> {
        if *self.abort_next_send.borrow() {
            self.abort_next_send.replace(false);
            Err(ErrorNumber::AssertionFailed)
        } else {
            // sending to an address instantiates it if it isn't already
            let mut map = self.addresses.borrow_mut();

            match to.payload() {
                // TODO: in a real system, this is fallible if the address does not exist
                // This impl assumes that any f0 form address is in the map/instantiated but does not check so
                // Sending to actors should succeed if the actor exists but not instantiate it
                fvm_shared::address::Payload::ID(_) | fvm_shared::address::Payload::Actor(_) => {
                    Ok(())
                }
                // Sending to public keys should instantiate the actor
                fvm_shared::address::Payload::Secp256k1(_)
                | fvm_shared::address::Payload::BLS(_)
                | fvm_shared::address::Payload::Delegated(_) => {
                    if !map.contains_key(to) {
                        let actor_id = self.next_actor_id.replace_with(|old| *old + 1);
                        map.insert(*to, actor_id);
                    }
                    Ok(())
                }
            }?;

            // save the fake message as being sent
            let message = TestMessage { method, params: params.clone(), value };
            self.last_message.replace(Some(message));

            Ok(Response { exit_code: ExitCode::OK, return_data: params })
        }
    }

    fn resolve_address(&self, addr: &Address) -> Option<ActorID> {
        // if it is already an ID-address, just return it
        if let fvm_shared::address::Payload::ID(id) = addr.payload() {
            return Some(*id);
        }

        let map = self.addresses.borrow();
        map.get(addr).copied()
    }
}