fip | title | author | discussions-to | status | type | category | created | spec-sections | requires | replaces |
---|---|---|---|---|---|---|---|---|---|---|
0054 |
Filecoin EVM runtime (FEVM) |
Raúl Kripalani (@raulk), Steven Allen (@stebalien) |
Final |
Technical Core |
Core |
2022-12-02 |
N/A |
N/A |
Table of Contents generated with DocToc
- Simple Summary
- Abstract
- Change Motivation
- Specification: Filecoin EVM (FEVM) Runtime Actor
- Installation and wiring
- Instantiatable actor
- State
- Contract storage (KAMT)
- Actor interface (methods)
Constructor
(method number 1)Resurrect
(method number 2)GetBytecode
(method number 3)GetBytecodeHash
(method number 4)GetStorageAt
(method number 5)InvokeContractDelegate
(method number 6)InvokeContract
(method number 38444508371)HandleFilecoinMethod
(general handler for method numbers >= 1024)
- Addressing
- Opcode support
- Precompiles
- Other considerations
- Specification: Filecoin Virtual Machine changes
- Specification: Client changes
- Design Rationale
- Backwards Compatibility
- Test Cases
- Security Considerations
- Incentive Considerations
- Product Considerations
- Implementation
- Copyright
We introduce a new built-in actor: the Filecoin EVM (FEVM) runtime actor, capable of running EVM smart contracts on top of the Filecoin Virtual Machine. We also introduce various changes to the Filecoin Virtual Machine and to client implementations, necessary to support the operation of this new actor.
The Filecoin EVM (FEVM) runtime built-in actor runs EVM smart contracts compatible with the Ethereum Paris fork, plus EIP-3855 (PUSH0).
It achieves this by embedding a special-purpose EVM interpreter, implementing the integration logic with the Filecoin environment, translating environment-dependent opcodes into their corresponding Filecoin primitives, and mapping all state I/O to the underlying IPLD model.
The EVM interpreter strives for maximal as-is portability of existing EVM bytecode. For this reason, the EVM interpreter supports all required opcodes and Ethereum precompiles to strive for maximal portability. Functional and technical departures from Ethereum's standard expectations are documented herein.
The FEVM runtime actor motivates some changes in the FVM. Syscalls are modified, syscalls are added, new environmental data is required, and new exit codes are created. The concept of a TipsetCID is also introduced formally, and is of required implementation by clients.
This FIP is dependent on FIP-0048 (f4 address class), FIP-0049 (Actor events), and FIP-0055 (Supporting Ethereum Accounts, Addresses, and Transactions).
A basic requirement to achieve EVM compatibility is to be able to run EVM smart contracts. Given the inability to deploy user-defined Wasm actors (arriving at a later stage of the FVM roadmap), we introduce this capability by adding a new built-in actor to the network. This actor is accompanied by FVM and client changes necessary for functional completeness.
The FEVM runtime actor is the Filecoin built-in actor that hosts and executes EVM bytecode. It contains:
- An embedded EVM interpreter. It processes EVM bytecode handling every instruction according to EVM expectations, with functional departures described herein.
- The integration logic connecting the EVM interpreter with the Filecoin environment, chain, and virtual machine (FVM).
- A collection of Ethereum and Filecoin-specific precompiles, as specified below.
On the migration for the network upgrade where this FIP goes live:
- The Wasm bytecode for the EVM runtime actor is loaded onto the node's blockstore.
- Its CodeCID is linked to the System actor's state under the key
"evm"
. - An Ethereum Account actor (as per FIP-0055) is created in the state tree with balance zero, nonce zero, and delegated address
f410faaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaonc6iji
, corresponding to the Ethereum zero address.
The Filecoin EVM runtime is an actor that can be instantiated by users (and therefore, not a singleton actor). A new instance is to be created for each EVM smart contract deployed on the network. Instantiation can only originate in the Ethereum Address Manager (EAM), as defined in FIP-0055, who assigns an f410 subaddress prior to creating the actor via the Init actor, as per standard Filecoin mechanics.
At the time of writing, this makes the EVM runtime actor comparable to the Miner, Payment Channel, and Multisig built-in actors, and unlike the Storage Power, Storage Market, Cron, and other singleton built-in actors.
The state of the EVM runtime actor is as follows:
/// DAG-CBOR tuple-encoded.
pub struct State {
/// The EVM contract bytecode resulting from calling the
/// initialization code (init code) by the constructor.
pub bytecode: Cid,
/// The EVM contract bytecode hash keccak256(bytecode)
pub bytecode_hash: [u8; 32],
/// The EVM contract state dictionary.
/// All EVM contract state is a map of U256 -> U256 values.
///
/// Root of KAMT<U256, U256>
pub contract_state: Cid,
/// The EVM nonce used to track how many times CREATE or
/// CREATE2 have been called.
pub nonce: u64,
/// Possibly a tombstone if this actor has been self-destructed.
/// In the EVM, self-destructed contracts are "alive" until the current top-level transaction
/// ends. We track this by recording the origin and nonce.
///
/// Specifically:
///
/// 1. On SELFDESTRUCT, they mark themselves as "deleted" (by setting a tombstone with the
/// current origin/nonce), send away all funds, and return immediately.
/// 2. For the rest of the current transaction (as long as the tombstone's origin/nonce matches
/// the currently executing top-level transaction), the contract continues to behave
/// normally.
/// 3. After the current transaction ends, the contract behaves as if it were an "empty"
/// contract, kind of like a placeholder. At this point, the contract can be "resurrected"
/// (recreated) by via CREATE/CREATE2.
///
/// See https://github.com/filecoin-project/ref-fvm/issues/1174 for some context.
pub tombstone: Option<Tombstone>
}
/// A tombstone indicating that the contract has been self-destructed.
/// DAG-CBOR tuple-encoded.
pub struct Tombstone {
/// The message origin when this actor was self-destructed.
pub origin: ActorID,
/// The message nonce when this actor was self-destructed.
pub nonce: u64,
}
EVM storage (u256 => u256 map) is backed by a specialized data structure based on the Filecoin HAMT: the fixed size Keyed AMT (KAMT).
This data structure is more mechanically sympathetic to typical EVM storage read and write patterns (refer to Design Rationale for more detail).
SLOAD
and SSTORE
EVM opcodes map to reads and writes onto this KAMT, respectively, and ultimately to ipld
syscalls specified in [FIP-0030].
This is how the KAMT compares to its Filecoin HAMT:
- Key length. The HAMT uses 256-bit hashed keys. With the KAMT it is possible to specify an arbitrary key length.
- Key content. The HAMT assumes arbitrary keys, and hashes them on read and writes. The KAMT obviates the need for key hashing and assumes the caller has hashed keys to a fixed-width binary format.
- Overlapping prefixes and extensions. The HAMT assumes hashing function that results in unpredictable keys which are uniformly distributed across the keyspace. The KAMT optimizes for long overlaps in keys. Whenever a node in the data structure is full, it looks for the longest common prefix of the keys and uses extensions to skip multiple levels in the tree that would otherwise only contain a single pointer pointing at the next level.
Contrary to traditional EVM storage, the KAMT is an enumerable data structure. However, no operations are provided for smart contracts to enumerate keys, at least not at this stage. This useful property merely facilitates external observability and debuggability.
The Filecoin EVM runtime actor offers these entrypoints, each of which handles messages sent with the specified Filecoin method numbers. Method numbers respect the FRC42 calling convention.
Initializes a new EVM smart contract with some init bytecode supplied as a constructor parameter.
It is only be invocable by the Ethereum Address Manager (EAM), specified in FIP-0055, indirectly via the Init actor.
This precondition is validated by checking that an f4 address with namespace 10
(corresponding to the EAM) was assigned to the current actor, prior to its Constructor
method being called.
The Constructor runs the init bytecode with the EVM interpreter, and populates the actor's state accordingly:
bytecode
field: the execution bytecode is stored as a raw IPLD block and linked by CID from this field.contract_state
field: contains the root CID of the storage KAMT resulting fromSSTORE
operations during construction.nonce
field: set to 0, unless any calls to CREATE or CREATE2 happen during initialization.bytecode_hash
: the Keccak-256 hash of the bytecode.tombstone
: may be non-empty if the contract selfdestructed itself during construction.
Input parameters
// CBOR tuple encoded.
pub struct ConstructorParams {
/// The actor's "creator" (specified by the EAM).
pub creator: EthAddress,
/// The EVM initcode that will construct the new EVM actor.
pub initcode: RawBytes,
}
Return value
None.
Errors
USR_ASSERTION_FAILED
(exit code 24) if the caller does not have an f4 delegated address under namespace10
(that of the Ethereum Address Manager), or if it's lacking a subaddress.USR_ILLEGAL_ARGUMENT
(exit code 16) if the EVM execution bytecode is longer than 24KiB (same limit as Ethereum), or if the bytecode starts with 0xef (EIP-3541).USR_ILLEGAL_STATE
(exit code 20) if we failed to hash, or write the bytecode to the blockstore.EVM_CONTRACT_REVERTED
(exit code 33) if the init bytecode reverted.
Reinstates a contract at a previously self-destructed actor.
This is a special version of the Constructor
that the EAM invokes directly (not through the Init actor) when an EVM smart contract already existed at the address.
Input parameters
Equal to Constructor
.
Return value
Equal to Constructor
.
Errors
Same as Constructor
.
USR_ASSERTION_FAILED
(exit code 24) if the caller does not have an f4 delegated address under namespace10
(that of the Ethereum Address Manager), or if it's lacking a subaddress.USR_ILLEGAL_ARGUMENT
(exit code 16) if the EVM execution bytecode is longer than 24KiB (same limit as Ethereum), or if the bytecode starts with 0xef (EIP-3541).USR_ILLEGAL_STATE
(exit code 20) if we failed to hash, or write the bytecode to the blockstore.EVM_CONTRACT_REVERTED
(exit code 33) if the init bytecode reverted.
Returns the CID of the contract's EVM bytecode block, adding it to the caller's reachable set.
This method is used internally to resolve EXTCODE*
opcodes.
Input parameters
None.
Return value
Nillable CID of the contract's bytecode as stored in state. Returns nil if the contract is dead.
Errors
USR_ILLEGAL_ARGUMENT
(exit code 16) when we fail to load the state.
Returns the keccak-256 hash of the execution bytecode, which was computed at construction time and stored in state.
Input parameters
None.
Return value
Keccak-256 hash of the contract's bytecode as stored in state.
Errors
USR_ILLEGAL_ARGUMENT
(exit code 16) when we fail to load the state.
Returns the value stored at the specified EVM storage slot.
This method exists purely for encapsulation purposes; concretely, to enable tools to inspect EVM storage without having to parse the state object, or understand the KAMT.
Calling is restricted to f00
(system actor), and therefore cannot be invoked via messages or internal sends.
It can be used by the Ethereum JSON-RPC eth_getStorageAt
operation to resolve requests by constructing a local call and processing it with the FVM.
Input parameters
// CBOR tuple encoded.
pub struct GetStorageAtParams {
pub storage_key: U256, // encoded as a CBOR byte string
}
Return value
Storage value (U256), encoded as a CBOR byte string. Returns a 0-filled 32-byte array, if the key doesn't exist, or if the contract is dead.
Errors
USR_FORBIDDEN
(exit code 18) when not called by address f00.USR_ASSERTION_FAILED
(exit code 24) on internal errors.USR_UNSPECIFIED
(exit code 23) when we fail to get the storage slot due to an internal error.
Recursive invocation entrypoint backing calls made through the DELEGATECALL
opcode.
Only callable by self.
See remarks on the DELEGATECALL
opcode below for more information.
Input parameters
// CBOR tuple encoded.
pub struct DelegateCallParams {
pub code: Cid,
/// The contract invocation parameters
/// Encoded as a CBOR byte string
pub input: Vec<u8>,
/// The original caller's Eth address.
pub caller: EthAddress,
/// The value passed in the original call.
pub value: TokenAmount,
}
Return value
Same as InvokeContract
.
Errors
- Exit code
USR_FORBIDDEN
(18) when caller is not self.
InvokeContract
(method number 38444508371)
Invokes an EVM smart contract by loading the execution bytecode from state, and dispatching input data to it.
The input data is expected to be framed as a CBOR byte string. This method unframes it before handing it over to the contract (subsequently retrievable through CALLDATA* opcodes). This method is universally callable.
Input parameters
Raw bytes, encoded as a CBOR byte string.
Return value
Raw bytes, encoded as a CBOR byte string.
Errors
USR_UNSPECIFIED
(exit code 23) on internal errors instantiating the EVM runtime.USR_NOT_FOUND
(exit code 17) when failing to load the bytecode.EVM_CONTRACT_REVERTED
(exit code 33) when the contract reverts.EVM_CONTRACT_INVALID_INSTRUCTION
(exit code 34) the INVALID instruction was executed.EVM_CONTRACT_UNDEFINED_INSTRUCTION
(exit code 35) an undefined instruction was executed.EVM_CONTRACT_STACK_UNDERFLOW
(exit code 36) there was a stack underflow when executing an instruction.EVM_CONTRACT_STACK_OVERFLOW
(exit code 37) there was a stack overflow when executing an instruction.EVM_CONTRACT_ILLEGAL_MEMORY_ACCESS
(exit code 38) there was an illegal memory access.EVM_CONTRACT_BAD_JUMPDEST
(exit code 39) the contract jumped to an illegal offset.- Any other error that is raised during bytecode execution.
Filecoin native messages carrying a method number above or equal to 1024 (a superset of the public range of the FRC42 calling convention) are processed by this entrypoint. This path enables processing transactions sent from non-Ethereum sending sites, e.g. built-in actors, Filecoin wallets, and future Wasm actors, with arbitrary Filecoin method numbers.
Calling this entrypoint performs the following logic:
- Map the Filecoin native message to Solidity input data satisfying the signature below.
- Hand off execution to the EVM bytecode.
- If execution succeeds, interpret the return data according to the signature below, and call the
fvm::exit
syscall with the extracted exit code and return data.
When called with no parameters, the input codec
will be 0 and the input params
will be empty. To return no result, return the codec
0 and empty return_data
.
// Function selector: 0x868e10c4
// Note: return parameters have been named for specification purposes only.
// Naming return parameters is not valid Solidity.
function handle_filecoin_method(uint64 method, uint64 codec, bytes calldata params) public
returns (exit_code uint32, codec uint64, return_data bytes memory) {}
For more information, see the Solidity Contract ABI Specification for more details about the call convention, parameter layouts, and packing.
Input parameters
Limited to nothing or a valid CBOR object (for now). In the future, other codecs will be supported.
Return value
Limited to nothing or a valid CBOR object (for now). In the future, other codecs will be supported.
EVM smart contracts deployed on Filecoin calling opcodes that take addresses as parameters can use two types of addresses. Both these addresses conform to EVM addressing expectations (160-bit width):
- Masked Filecoin ID addresses.
- Actual Ethereum addresses.
Masked Filecoin ID addresses are addresses conforming to Ethereum's type (160-bit) that encode Filecoin ID addresses.
They are distinguished through a byte mask.
The high byte is 0xff
(discriminator), followed by thirteen 0 bytes of padding, then the uint64 ID of the actor.
|- disc -| |----- padding ------| |----- actor id -----|
0xff || 0000000000000000000000 || <uint64 ID big endian>
Ethereum addresses are all addresses that do not satisfy the above mask.
They are converted to f410
addresses as per the conversion specified in FIP-0055.
This includes the Ethereum Zero Address and precompile addresses.
All opcodes from the [Ethereum Paris hard fork] are supported, plus PUSH0 from EIP-3855. This section enumerates all supported opcodes, noting functional departures from their Ethereum counterparts, as well as relevant remarks about how they operate. Opcodes are referred to by their mnemonic name.
These opcodes are handled locally within the EVM interpreter and have no departures from their original behaviour in Ethereum.
- Arithmetic family:
ADD
,MUL
,SUB
,DIV
,SDIV
,MOD
,SMOD
,ADDMOD
,MULMOD
,EXP
,SIGNEXTEND
. - Boolean operators:
LT
,GT
,SLT
,SGT
,EQ
,ISZERO
,AND
,OR
,XOR
,NOT
. - Bitwise operations:
BYTE
,SHL
,SHR
,SAR
. - Control flow:
STOP
,JUMPDEST
,JUMP
,JUMPI
,PC
,STOP
,RETURN
,INVALID
. - Parameters:
CALLDATALOAD
,CALLDATASIZE
,CALLDATACOPY
,RETURNDATASIZE
,RETURNDATACOPY
. - Stack manipulation:
POP
,PUSH{0..32}
,DUP{1..32}
,SWAP{1..16}
.
Memory: MLOAD
, MSTORE
, MSTORE8
, MSIZE
.
EVM memory is modelled as an object inside the interpreter, ultimately backed by 32-bit Wasm memory.
Usage of of these instructions incurs in Wasm memory expansion costs and is bounded by the memory limits specified in FIP-0057.
Contrary to Ethereum, Filecoin does enforce a maximum memory limit.
In Ethereum, memory is bounded implicitly by gas.
A failure to allocate will cause the system to abort with the SYS_ILLEGAL_INSTRUCTION
exit code.
Accessors: ADDRESS
.
Returns the Ethereum address of the executing contract.
Accessors: BALANCE
.
Returns the filecoin balance of the contract, in atto precision (same precision as Ethereum).
Accessors: ORIGIN
.
Returns the Ethereum address of the account where the chain message originated.
Accessors: CALLER
.
Returns the Ethereum address of the immediate caller of the contract.
Accessors: CALLVALUE
.
Returns the filecoin value sent in the message, in atto precision (same precision as Ethereum).
Accessors: GASPRICE
.
Returns the base fee plus the gas premium, in atto precision (same precision as Ethereum).
This is functionally equivalent to Ethereum's GASPRICE
definition following EIP-1559, which is to return the effective_gas_price
, defined as the following:
effective_gas_price = effective_gas_premium + BaseFee
In Filecoin, the effective_gas_premium
is bounded by the Message.GasFeeCap
and the BaseFee
and clamped at 0:
effective_gas_premium = max(min(Message.GasPremium, Message.GasFeeCap - BaseFee), 0)
Accessors: BLOCKHASH
.
Returns the hash within the tipset CID of the requested epoch, with a maximum lookback of 256 epochs, truncated to preserve the left-most 32 bytes.
If the requested epoch is a null round, it seeks to the last non-null tipset.
This means that the lookup can range beyond 256 epochs if null rounds are involved.
Accessors: COINBASE
.
Nil implementation.
Returns a fixed 0x0 value.
Accessors: TIMESTAMP
.
Returns the timestamp of the epoch being executed, as a Unix timestamp, as supplied by the client during Machine construction.
Accessors: NUMBER
.
Returns the epoch being executed.
Accessors: PREVRANDAO
(former DIFFICULTY
, see EIP-4399).
Returns beacon randomness for the current epoch, by calling the rand::get_beacon_randomness
syscall as defined in [FIP-0030], supplying:
- domain separation tag
10
- entropy equivalent to the UTF-8 bytes of string "prevrandao"
Accessors: GASLIMIT
.
Returns the fixed number 10e9 (10B gas).
Accessors: CHAINID
.
Returns the EIP-155 Chain ID of the current network.
Accessors: BASEFEE
.
Returns the base fee, in atto precision (same precision as Ethereum).
Accessors: SELFBALANCE
.
Returns the contract's filecoin balance, in atto precision (same precision as Ethereum).
Accessors: GAS
.
Returns the Filecoin gas available.
Control flow: REVERT
.
Aborts by calling the fvm::exit
syscall with user-space exit code 33 (EVM_CONTRACT_REVERTED
), passing in the supplied value to be returned as return data, and reverting all changes.
Hashing: KECCAK256
.
Makes a syscall to crypto::hash
with the supplied preimage and the Keccak-256 multihash.
Logging: LOG{0..4}
. LOG{0..4}
opcodes emit FIP-0049 compliant actor events conforming to the following template:
// Values are encoded as DAG-CBOR byte strings.
ActorEvent {
entries: [
(0x03, "t1", <first topic word>), // when LOG1, LOG2, LOG3, LOG4
(0x03, "t2", <second topic word>), // when LOG2, LOG3, LOG4
(0x03, "t3", <third topic word>), // when LOG3, LOG4
(0x03, "t4", <fourth topic word>), // when LOG4
(0x03, "d", <data word>), // event data, omitted with LOG0
],
}
All fields are indexed by key and value because standard Ethereum queries against log data need to take the order of topics into account, as well as identify the data.
Storage: SLOAD
, SSTORE
.
Maps storage operations to underlying reads and writes on the contract storage KAMT, which eventually result in ipld
syscalls.
Lifecycle: CREATE
.
Calls the Create
method of the Ethereum Address Manager (FIP-0055) to deploy the smart contract, applying the 1/64th gas reservation rule (EIP-150) to the entire call .
Lifecycle: CREATE2
.
Calls the Create2
method of the Ethereum Address Manager (FIP-0055) to deploy the smart contract, applying the 1/64th gas reservation rule (EIP-150) to the entire call.
Lifecycle: SELFDESTRUCT
.
- Marks the current contract as deleted by setting the tombstone in state, recording the current message origin and nonce.
- Transfers the balance out to the designated beneficiary.
- Returns immediately.
The contract isn't considered to be "dead" until it has a tombstone where the recorded origin and nonce differ from the origin and nonce of the currently executing message. This respects Ethereum semantics in that the contract continues to be "alive" until the end of the transaction.
- Subsequent calls to the self-destructed contract within the same transaction will continue to run the existing bytecode.
- Calls to this contract in subsequent transactions will return successfully with no code being executed.
Calls: CALL
.
First, if the recipient is a precompile, FEVM invokes the precompile directly as follows:
- It invokes the precompile.
- The
call_actor
precompile applies the specified gas limit (if any) to the subsequent actor call. - All other precompiles ignore the specified gas limit.
- The
- On success, if the user has requested a non-zero value transfer, FEVM transfers the funds to the precompile's Filecoin address (which will auto-create a Placeholder at that address, if necessary).
Otherwise, FEVM converts the CALL
operation into a Filecoin "send" as follows:
- The method number is always
EVM::InvokeContract
(3844450837). - The receiver's address is converted into a Filecoin address.
- The input data is treated as
IPLD_RAW
if non-empty, or the empty block (block0
) if empty. - The value is treated as a Filecoin token amount, as per Native currency.
- The gas limit is computed as follows:
- If the value zero and the gas limit is 2300, or the gas limit is zero and the value is non-zero, FEVM sets the gas limit to 10M. This ensures that bare "transfers" continue to work as 2300 isn't enough gas to perform a call on Filecoin. 10M was picked to be enough to "always work" but not so much that it becomes a security concern.
- FEVM then applies the 1/64th rule from Ethereum, limiting the the sent gas to at most 63/64 of the remaining gas.
- The send flags are set to the default value (0).
Note that all gas values are Filecoin gas, not Ethereum gas. See Product considerations: Gas for more information.
Calls: CALLCODE
.
Not supported.
Aborts with EVM_CONTRACT_UNDEFINED_INSTRUCTION
(exit code 36) when used.
This is because EIP-7 introduced DELEGATECALL
as a hardened replacement for CALLCODE
.
Solidity no longer supports CALLCODE
.
Calls: DELEGATECALL
.
DELEGATECALL
behaves differently depending on the recipient:
- If the target is a precompile address, it is handled internally according to the precompile logic defined in
CALL
above. - If the target actor is an EVM runtime actor, it fetches the bytecode of the target and calls
InvokeContractDelegate
on itself to create a new call stack "frame" whilst still operating on our own state. - If the target is an account, placeholder, or an EthAccount actor (FIP-0055), it returns nothing and success (pushes 1 onto the stack).
- If the target is any other type of actor, it returns nothing and failure (pushes 0 onto the stack).
Calls: STATICCALL
.
Performs the CALL
in read-only mode, disallowing state mutations, event emission, and actor deletions.
Specifically, it sets the "read-only" bit in the send flags (bit 0x1
). See send::send
for more information.
External code: EXTCODESIZE
.
- If the supplied address belongs to an EVM smart contract, it retrieves the size of the bytecode by calling the
GetBytecode
method, fetching the block via anipld::block_read
operation, and returning the length of the block. - If the supplied address is deemed an account, whether a native account, a placeholder, or an Eth account, it returns 0 (no code).
- If the supplied address does not exist, it returns 0 (no code).
- Otherwise, it returns the non-zero sentinel value 1, to indicate that there is code. However, it's not EVM bytecode so we can't return the real size.
NOTE: this operation is used by popular utilities to check if an address is a contract, e.g. OpenZeppelin's toolbox.
External code: EXTCODECOPY
.
- If the supplied address belongs to an EVM smart contract, it copies its bytecode into the designated memory region by calling the
GetBytecode
method, fetching the block via anipld::block_read
operation, and copying it into the memory segment. - If the supplied address is deemed an account, whether a native account, a placeholder, or an Eth account, it copies nothing.
- If the supplied address does not exist, it copies nothing.
- Otherwise, it copies the sentinel value
0xfe
.
External code: EXTCODEHASH
.
- If the supplied address belongs to an EVM smart contract, it copies its bytecode into the designated memory region by calling the
GetBytecode
method, fetching the block via anipld::block_read
operation, and copying it into the memory segment. - If the supplied address is deemed an account, whether a native account, a placeholder, or an Eth account, it copies nothing.
- If the supplied address does not exist, it copies nothing.
- Otherwise, it copies the sentinel value
0xfe
.
There are two kinds of precompiles available to EVM smart contracts running within the FEVM runtime actor:
- Ethereum precompiles
- Filecoin precompiles
The FEVM runtime actor supports all Ethereum precompiles available in the Ethereum Paris fork. These precompiles sit at their original Ethereum addresses. There are no functional departures with respect to their original behaviors. However, gas accounting follows Filecoin mechanics instead of Ethereum's. See the Product considerations: gas section for more information. Refer to Ethereum documentation for more information.
Ethereum Address | Precompile |
---|---|
0x01 |
ecRecover |
0x02 |
SHA2-256 |
0x03 |
RIPEMD-160 |
0x04 |
identity |
0x05 |
modexp |
0x06 |
ecAdd |
0x07 |
ecMul |
0x08 |
ecPairing |
0x09 |
blake2f |
The following Filecoin-specific precompiles are exposed to EVM smart contracts at the designated addresses:
Ethereum Address | Precompile |
---|---|
0xfe00..01 |
resolve_address |
0xfe00..02 |
lookup_delegated_address |
0xfe00..03 |
call_actor |
0xfe00..05 |
call_actor_id |
resolve_address
precompile
Reads a Filecoin address and resolves it into a BE-encoded u64 actor ID, or an empty array if nothing found.
A failure to parse the address will result in a 1
being placed on the stack, which leads high-level languages like Solidity to REVERT.
Input data layout
<address> as bytes
Return data layout
<actor id> as u64 BE bytes (left padded to u256)
lookup_delegated_address
precompile
Reads a BE-encoded low u64 ID address from an u256 word, interprets it as an actor ID, and looks up the f4 address, returning an empty array if not found.
Input data layout
<actor id> u64 (right padded to fit u256)
Return data layout
<address> as bytes
Or empty if error, or inexistent.
call_actor
precompile
Calls an actor with the ability to supply a method number, using any Filecoin address.
Input data layout
<method number> as u64 (left padded to u256)
|| <value> as u256
|| <send flags> as u64 (left padded to u256)
|| <input data codec> as u64 (left padded to u256)
|| <input data offset> as u32 (left padded to u256)
|| <callee address offset> as u64 (left padded to u256)
at <input data offset>:
<input data length> as u32 (left padded to u256)
|| <input data> as bytes
at <callee address offset>:
<callee address length> as u32 (left padded to u256)
|| <callee address> as bytes
uint64 method;
uint256 value;
uint64 flags;
uint64 codec;
bytes memory params;
bytes memory target_address;
abi.encode(method, value, flags, codec, params, target_address)
Return data layout
<exit code> as u32 (left padded to u256)
|| <codec> as u64 (left padded to u256)
|| <return data offset> as u32 (left padded to u256)
at <return data offset>:
<return data length> as u32 (left padded to u256)
|| <return data> as bytes
|| optional 0 padding to round out an Ethereum word
abi.decode(data, (int256, uint64, bytes));
call_actor_id
precompile
Calls an actor with the ability to supply a method number, using an actor ID.
Input data layout
<method number> as u64 (left padded to u256)
|| <value> as u256
|| <send flags> as u64 (left padded to u256)
|| <input data codec> as u64 (left padded to u256)
|| <input data offset> as u32 (left padded to u256)
|| <actor id> as u64 (left padded to u256)
at <input data offset>:
<input data length> as u32 (left padded to u256)
|| <input data> as bytes
uint64 method;
uint256 value;
uint64 flags;
uint64 codec;
bytes memory params;
uint64 actor_id
abi.encode(method, value, flags, codec, params, actor_id)
Return data layout
<exit code> as u32 (left padded to u256)
|| <codec> as u64 (left padded to u256)
|| <return data offset> as u32 (left padded to u256)
at <return data offset>:
<return data length> as u32 (left padded to u256)
|| <return data> as bytes
|| optional 0 padding to round out an Ethereum word
abi.decode(data, (int256, uint64, bytes));
Historical Ethereum behaviors are not supported. EVM opcode and precompile support is restricted to the Ethereum Paris fork, plus PUSH0 from EIP-3855.
The only supported Ethereum transaction type is the EIP-1559 transaction (type 2 in the RLP-encoded transaction format). Such transactions carry a gas fee cap and a gas premium, both of which map cleanly to Filecoin's message model.
The native currency of the Filecoin EVM runtime is filecoin. This environment has no dependence to, or understanding of, Ether as a currency.
New environmental data is required by the FVM to satisfy new data returned in syscalls:
- EIP-155 Chain ID of the network.
- Timestamp of the execution epoch. If the execution epoch happens to be a null round (no blocks, and therefore no tipset), the client must pass the timestamp of the epoch at which the null round occurred (i.e. the timestamp that a block would've carried should that epoch not have been a null round).
We define the following EIP-155 Chain IDs for Filecoin networks:
- Mainnet: 314
- Hyperspace testnet: 3141
- Wallaby testnet: 31415
- Calibration testnet: 314159
- Butterfly testnet: 3141592
- Local/private testnets: 31415926
These Chain IDs have been registered in the ethereum-lists/chains
registry, which in turn is prepared for CAIP-2 compliance.
network::context
#[repr(packed, C)]
pub struct NetworkContext {
/// The current epoch.
pub epoch: ChainEpoch,
/// The current time (seconds since the unix epoch).
pub timestamp: u64,
/// The current base-fee.
pub base_fee: TokenAmount,
/// The Chain ID of the network.
pub chain_id: u64,
/// The network version.
pub network_version: u32,
}
/// Returns the details about the network.
///
/// # Errors
///
/// None
pub fn context() -> Result<NetworkContext>;
This syscall charges no gas beyond the base p_syscall_gas
.
network::tipset_cid
See Tipset CID for more context.
/// Retrieves a tipset's CID within the last finality, if available.
///
/// # Arguments
///
/// - `epoch` the epoch being queried.
/// - `ret_off` and `ret_len` specify the location and length of the buffer into which the
/// tipset CID will be written.
///
/// # Returns
///
/// Returns the length of the CID written to the output buffer.
///
/// # Errors
///
/// | Error | Reason |
/// |---------------------|----------------------------------------------|
/// | [`IllegalArgument`] | specified epoch is negative or in the future |
/// | [`LimitExceeded`] | specified epoch exceeds finality |
pub fn tipset_cid(
epoch: i64,
ret_off: *mut u8,
ret_len: u32,
) -> Result<u32>;
The lookback is limited to [current_epoch - 900, current_epoch - 1]
.
In addition to the base p_syscall_gas
, this syscall charges:
- A flat
50,000
gas fee to lookup the tipset CID at the previous epoch. - A flat
215,000
gas fee to lookup the tipset CID at any other epoch.
actor::lookup_address
/// Looks up the "delegated" (f4) address of the target actor (if any).
///
/// # Arguments
///
/// `addr_buf_off` and `addr_buf_len` specify the location and length of the output buffer in
/// which to store the address.
///
/// # Returns
///
/// The length of the address written to the output buffer, or 0 if the target actor has no
/// delegated (f4) address.
///
/// # Errors
///
/// | Error | Reason |
/// |---------------------|------------------------------------------------------------------|
/// | [`NotFound`] | if the target actor does not exist |
/// | [`BufferTooSmall`] | if the output buffer isn't large enough to fit the address |
/// | [`IllegalArgument`] | if the output buffer isn't valid, in memory, etc. |
pub fn lookup_delegated_address(
actor_id: u64,
addr_buf_off: *mut u8,
addr_buf_len: u32,
) -> Result<u32>;
This syscall charges to lookup the target actor state (see FIP-0057), in addition to the base p_syscall_gas
actor::balance_of
/// Gets the balance of the specified actor.
///
/// # Arguments
///
/// - `actor_id` is the ID of the target actor.
///
/// # Errors
///
/// | Error | Reason |
/// |----------------------|------------------------------------------------|
/// | [`NotFound`] | the target actor does not exist |
pub fn balance_of(
actor_id: u64
) -> Result<super::TokenAmount>;
This syscall charges to lookup the target actor state (see FIP-0057), in addition to the base p_syscall_gas
actor::next_actor_address
/// Generates a new actor address for an actor deployed by the calling actor.
///
/// **Privileged:** May only be called by the init actor.
pub fn next_actor_address(obuf_off: *mut u8, obuf_len: u32) -> Result<u32>;
This syscall charges no gas beyond the base p_syscall_gas
.
crypto::recover_secp_public_key
/// Recovers the signer public key from a signed message hash and its signature.
///
/// Returns the public key in uncompressed 65 bytes form.
///
/// # Arguments
///
/// - `hash_off` specify location of a 32-byte message hash.
/// - `sig_off` specify location of a 65-byte signature.
///
/// # Errors
///
/// | Error | Reason |
/// |---------------------|------------------------------------------------------|
/// | [`IllegalArgument`] | signature or hash buffers are invalid |
pub fn recover_secp_public_key(
hash_off: *const u8,
sig_off: *const u8,
) -> Result<[u8; SECP_PUB_LEN]>;
This syscall charges 1,637,292 gas (in addition to the base syscall gas), the same as is charged to verify a secp256k1 signature.
event::emit_event
Originally defined in FIP-0049 (Actor events). Redefined and bundled here for convenience.
/// Emits an actor event to be recorded in the receipt.
///
/// Expects a DAG-CBOR representation of the ActorEvent struct.
///
/// # Errors
///
/// | Error | Reason |
/// |---------------------|---------------------------------------------------------------------|
/// | [`IllegalArgument`] | entries failed to validate due to improper encoding or invalid data |
pub fn emit_event(
evt_off: *const u8,
evt_len: u32,
) -> Result<()>;
gas::available
/// Returns the amount of gas remaining.
pub fn available() -> Result<u64>;
This syscall charges no gas beyond the base p_syscall_gas
.
vm::exit
/// Abort execution with the given code and optional message and data for the return value.
/// The code and return value are recorded in the receipt, the message is for debugging only.
///
/// # Arguments
///
/// - `code` is the `ExitCode` to abort with.
/// If this code is zero, then the exit indicates a successful non-local return from
/// the current execution context.
/// If this code is not zero and less than the minimum "user" exit code, it will be replaced with
/// `SYS_ILLEGAL_EXIT_CODE`.
/// - `blk_id` is the optional data block id; it should be 0 if there are no data attached to
/// this exit.
/// - `message_off` and `message_len` specify the offset and length (in wasm memory) of an
/// optional debug message associated with this abort. These parameters may be null/0 and will
/// be ignored if invalid.
///
/// # Errors
///
/// None. This function doesn't return.
pub fn exit(code: u32, blk_id: u32, message_off: *const u8, message_len: u32) -> !;
This syscall charges no gas beyond the base p_syscall_gas
.
vm::message_context
#[repr(packed, C)]
pub struct MessageContext {
/// The current call's origin actor ID.
pub origin: ActorID,
/// The caller's actor ID.
pub caller: ActorID,
/// The receiver's actor ID (i.e. ourselves).
pub receiver: ActorID,
/// The method number from the message.
pub method_number: MethodNum,
/// The value that was received.
pub value_received: TokenAmount,
/// The current gas premium
pub gas_premium: TokenAmount,
/// The current gas limit
pub gas_limit: u64,
/// Flags pertaining to the currently executing actor's invocation context.
/// Where ContextFlags is an u64-encoded bitmap, accepting values:
/// - 0x01: read only
pub flags: ContextFlags,
}
/// Returns the details about the message causing this invocation.
///
/// # Errors
///
/// None
pub fn message_context() -> Result<MessageContext>;
This syscall charges no gas beyond the base p_syscall_gas
.
This syscall now takes two extra fields:
gas_limit
(u64), the gas limit applicable to the send (whereu64::MAX
can be safely used to mean "no limit"). This value is always clamped to the maximum gas available.send_flags
(u64), encoding a bitmap of send flags.
The possible send flags are:
0x01
: Read-only. Nor the callee nor any of its transitive callees can cause any side effects: state updates, value transfers, or events. Specifically, the following calls all fail with theReadOnly
error number:self::self_destruct
self::set_root
event::emit_event
send::send
when transferring a non-zero balance or auto-creating the recipient.actor::create_actor
. This causesInit::Exec
andInit::Exec4
to exit withUSR_READ_ONLY
(see New general exit codes).
This syscall now returns a new error number:
ReadOnly
(number 13): returned when the called actor tries to perform one of the restricted mutations.
This syscall now supports the following hashes, specified by their multicodec value:
- Sha2_256 (
0x12
) - Blake2b256 (
0xb220
) - Blake2b512 (
0xb240
) - Keccak256 (
0x1b
) - Ripemd160 (
0x1053
)
vm::abort
is replaced by the more general syscallvm::exit
, which can accept a zero exit code, as well as return data on error.vm::context
is renamed tovm::mesage_context
.network::base_fee
is superseded bynetwork::context
, which returns the base fee, in addition to other fields.actor::new_actor_address
is replaced byactor::next_actor_address
.
We define a new extern, a function provided by the host client, to retrieve the Tipset CID of a given epoch.
/// Gets the CID for a given tipset.
///
/// If the supplied epoch happens to be a null round, the client must return the CID
/// of the previous first non-null round.
fn get_tipset_cid(&self, epoch: ChainEpoch) -> anyhow::Result<Cid>;
- USR_READ_ONLY (exit code 25). To be returned by actors when the requested operation cannot be performed in "read-only" mode. Specifically:
- Value transfers in
send::send
. - Actor state-root changes (
self::set_root
). self::selfdestruct
- Actor creation.
- Value transfers in
We introduce the concept of the tipset CID, which uniquely identifies the set of blocks that were incorporated to the blockchain at a given epoch.
In the past, tipsets were identified by a composite key enumerating the block CIDs of every block in canonical order, also known as "tipset key".
The tipset key is the concatenation of the bytes of the CIDs of the blocks that make up the tipset, in lexicographic order of their Ticket
field (block canonical order).
It is CBOR-serialized as a byte string (major type 2).
Starting from now, clients must compute and remember the tipset CID of every tipset committed to the chain. This is achieved by inserting the CBOR-serialized tipset key into the chain blockstore, and computing its CID using the Blake2b-256 multihash function.
The Tipset CID computation gas was determined by benchmarking Lotus and adding a security factor, but the numbers should hold for most implementations. Specifically, the benchmark assumes that:
- The last 900 tipsets are cached in-memory.
- There exists a skip-list allowing one to skip 20 tipsets at a time.
This true for all major Filecoin clients: Forest, Venus, and Lotus.
Benchmarking yielded:
- ~1,500 gas/20 tipsets (skip list)
- ~5,800 gas to traverse each tipset directly.
- ~15,000 gas offset (includes overhead, computing the tipset CID, etc.).
Purely for the client-side operations. We then add on an additional 21,000 gas (p_extern_gas
) to charge for calling into the client.
To keep the common case of getting the last tipset CID (e.g., for randomness) affordable, we split this charge into:
- The most recent tipset CID lookup:
15000+5800+21000 = 41800
. We propose50,000
for security. - Everything else:
(900/20)*1500 + 5800*19 + 15000 + 21000 = 213700
. We propose215,000
for security.
These charges intentionally leave space for changing the underlying tipset caching algorithm.
The EVM has a concept called the gas "stipend". Every call with a non-zero value transfer is granted 2300 gas, automatically. Solidity further extended this concept by introducing special send
and transfer
functions that explicitly apply this stipend to zero-value transfers.
Unfortunately, in FEVM, 2300 gas isn't enough to do anything due to the differences in the gas model. To ensure that these functions actually work, the FVM detects this case and sets the gas limit to 10M, which should be more than enough gas to:
- Lookup an address.
- Create a placeholder actor, if necessary.
- Transfer funds, persisting both the sender and recipient's states.
- Launch the EVM actor and run a bit of code.
All together, this should cost no more than ~6-7M gas where the majority of that gas accounts for state read costs. These state-read costs are not expected to increase in the future.
We discarded the following alternatives:
- Keep the gas limit as specified by the caller. This would have broken
send
andtransfer
, so it was never really an option. - Set the gas limit to "unlimited" (or, really, 63/64 of the remaining gas). This would have made the transfer "work", but it would have introduced a potential security issue: any contracts relying on send terminating after spending a reasonable amount of gas would be broken. This could have been used to get contracts into "stuck" states. The correct fix for this is to use the "Withdrawal" pattern, but not all contracts are written correctly.
- Avoid running code when this gas limit is detected (e.g., by using method 0). This option was discarded as some dapps rely on contracts being able to emit events when they receive funds.
- Set a precise limit. Any precise limits would have required adjusting the limit over time as network gas costs changed.
Early on, we had to choose the broad architecture by which EVM smart contracts would become an entity on-chain. We considered two large architectural options:
- A flat deployment model, where each EVM smart contract is an independent actor on-chain, with its own entry in the state tree, and its own address set.
- A nested deployment model, where a singleton built-in actor would contain the entirety of the EVM domain within it, with some form of internal fragmentation to model every smart contract as an independent entity within a monolithic state.
The choice went hand-in-hand with the addressing model, since we needed to assign and recognize Ethereum addresses either way. With (1), we'd need to find a way to delegate addressing to another actor that controlled a discretionary address space. With (2), addressing could be recursive, where the address first referred to the singleton actor, followed by an opaque component to be interpreted by such actor for internal routing.
This choice is a foundational one, as it defines the architectural trajectory of the Filecoin network for years to come, insofar programmability is concerned. These are the main reasons we chose to adopt (1):
- It places EVM smart contracts, as well as any other future foreign program, at equal footing and hierarchical level as any other actor, such as built-in actors, eventual user-deployed Wasm actors, and other future entities.
- It physically isolates state between actors through a shared-nothing model. This simplifies state management, makes state more granular, prevents undesired churn on update, eliminates the possibility of side effects, and contagion of IO overheads across smart contracts.
- It enables future versioning of the EVM runtime with explicit opt-in from developers at the contract level (i.e. optionality).
- It forces us to introduce general protocol advancements to facilitate seamless interoperability across supported runtimes, versus hyperspecialization within runtimes.
See the discussion under filecoin-project/ref-fvm#742 for further context.
The EVM contract storage model makes no assumptions about concrete key patterns, slot placement of state variables, nor slot addressing in the case of complex and/or dynamic types such as structs, maps, or arrays. Languages such as Solidity and Vyper are responsible mapping state to EVM storage by adopting a concrete storage layout for its types. When designing the KAMT, we optimized for the Solidity storage layout, such that:
- Contiguous slots are adjacently placed within the same node as adjacent pointers, or in adjacent nodes if overflowing.
- Access to slots sharing common prefixes is shortcut through extensions that skip otherwise-sparse tree levels.
Optimizing for Solidity makes sense because it is, by far, the most popular Ethereum smart contract programming language. Furthermore, Vyper has adopted Solidity's storage layout for complex and dynamic state types.
Ethereum does not distinguish between bare value sends and contract invocations. Bare value sends can trigger smart contract logic in Ethereum.
Filecoin distinguishes one from the other through the method number. Method number 0 designates a bare value send and does not dispatch the call to actor logic on the recipient. Method numbers other than 0 result in a Wasm actor instantiation and the consequent call dispatch.
We have not altered these mechanisms in this FIP. As a result, bare value sends to EVM smart contracts will not trigger EVM smart contract logic. This is a significant functional difference between Filecoin and Ethereum.
Refer to the community discussion at filecoin-project/ref-fvm#835
for more context.
This method has been assigned a number in the non-reserved range compliant with the [FRC42 call convention] to pave the way for future Wasm actors to be able to act as Filecoin EVM smart contracts by, for example, processing native ERC-20, ERC-721, etc. transactions submitted from Ethereum wallets.
Having chosen a reserved number, e.g. 2
, would've raised the risk of method number conflicts down the line.
Built-in actors must be updated to absorb the syscall changes specified herein to retain backwards compatibility. This will lead to a new version of actors being produced. A migration is required to add their Wasm bytecode to the blockstore, to link in their CodeCIDs in the System actor, and to update all existing actors to the new CodeCIDs.
Furthermore, changes in syscalls must be absorbed by other built-in actors to preserve backwards compatibility.
See integration tests in filecoin-project/ref-fvm
, as well as unit and integration tests of the EVM runtime actor.
This FIP enables developers to deploy custom, untrusted code to the Filecoin network for the first time. The security considerations are multi-faceted:
- Value will no longer flow exclusively through trusted smart contract logic. Users must exercise caution and carefully analyze and decide what and who to trust.
- EVM smart contract execution is doubly sandboxed. First, within the Filecoin EVM runtime actor; and second within the Wasm invocation container. Thus, the risk of malicious systemic outcomes is contained.
- Inside the current gas architecture, the popularity of user contracts may conflict with the need for storage providers to send system messages to continue operating and managing the network. It is hard or impossible to forecast how the gas market will be impacted by the arrival of user-defined contracts to Filecoin. The FVM team has requested the Cryptoecon Lab to produce a community report to evaluate possible scenarios for preparedness.
This is a technical FIP that independently raises no incentive considerations. However, smart contracts may introduce new incentive structures to the Filecoin network that users could engage with.
Product considerations are broken down into subsections covering various topics.
Gas metering and execution halt are performed according to the Filecoin gas model. EVM smart contracts accrue:
- Execution costs: resulting from the handling of Wasm instructions executed during the interpretation of the EVM bytecode, as well as the EVM runtime actor logic (e.g. method dispatch, payload handling, and more).
- Syscall and extern costs: resulting from the syscalls made by the EVM opcode handlers, and the EVM runtime actor logic itself.
- Memory expansion costs: resulting from the allocation of Wasm memory pages.
- Storage costs: resulting from IPLD state reads and accesses, directly by the smart contract as a result of
SSTORE
andSLOAD
opcodes, or indirectly by the EVM runtime actor during dispatch or opcode handling.
As specified above, gas-related opcodes such as GAS
and GASLIMIT
return Filecoin gas, coercing their natural u64 type to u256 (EVM type).
The gas limit supplied to the CALL
, DELEGATECALL
and STATICCALL
opcodes is also Filecoin gas.
Furthermore, gas limits specified when calling precompiles are disregarded.
Consequently, contracts ported from Ethereum that use literal gas values (e.g. the well known 2300 gas price for bare value transfers) may require adaptation, as these gas values won't directly translate into the Filecoin gas model.
The EVM runtime actor backing EVM smart contracts may be upgraded through future migrations.
Upgrades will impact gas costs in hard-to-predict ways, with compute gas being the most sensitive component.
We discourage smart contract developers to rely on specific gas values in their contract logic.
This includes gas limits passed in *CALL*
operations.
While the system honors those limits, costs will change across upgrades, so it's unsafe to hardcode assumptions.
- Gas metering and execution halt are performed according to the Filecoin gas model. Ethereum gas accounting does not apply in the Filecoin EVM.
- Gas limits set when CALLing precompiles do not apply, except for the
call_*
precompiles where the gas limit supplied to the CALL constrains the resulting send. - Filecoin gas costs are not stable over network updates. Developers must not make gas usage assumptions, as Filecoin gas is highly sensitive to implementation details, and they should refrain from using hardcoded gas limits in calls.
- Bare value sends (messages with method number 0) to Filecoin EVM smart contracts do not trigger smart contract logic, even if the message carries parameters.
- The
CALLCODE
opcode is not supported, since it was superseded in Ethereum by the more robustDELEGATECALL
opcode, there are requests to deprecate, and Solidity no longer supports it as of v0.5.0. SELFDESTRUCT
behavior:- Contrary to Ethereum, if a self-destructed contract gets sent funds after it calls
SELFDESTRUCT
but before the transaction ends, those funds don't vanish. Instead, they remain in the tombstoned smart contract. - Contrary to Ethereum,
SELFDESTRUCT
does not trigger a physical deletion of the contract, but rather a logical deletion by way of tombstoning. Thus, there is no gas refund for deleting an EVM smart contract.
- Contrary to Ethereum, if a self-destructed contract gets sent funds after it calls
Only Ethereum Accounts and EVM smart contracts are assigned an f410
address.
See FIP-0055 for more context.
All other actors need to be addressed through Masked ID addresses.
Unfortunately, contrary to f410
addresses, ID addresses are not reorg stable.
In other words, the ID may be reassigned within the chain's finality window.
This means that only these interactions benefit from addressing stability within the 900 epochs of the creation of the callee actor, when being addressed through their Ethereum address from within an EVM smart contract:
- EVM <> EVM interactions.
- EVM <> EthAccount interactions.
Calls from EVM to non-EVM actors must used Masked ID addresses, which do not benefit from stability until after 900 epochs from creation. This means that the ID address may be reassigned within 900 epochs from creation.
EVM smart contracts can only address the following actors through Masked ID addresses, thus making these interactions subject to recency instability:
- Non-Ethereum accounts (using f1/f3 addresses is not possible from within EVM smart contracts).
- Other built-in actors like the miner actor, multisigs, etc.
The stability issue is not a problem for singleton actors (e.g. power actor, storage market actor, etc.), as these actors sit at fixed ID addresses.
Stemming from this, transferring value to inexistent f1 and f3 addresses from EVM smart contracts is not possible. These are counterfactual interactions where the target still doesn't have an ID address, and thus cannot be currently addressed from an EVM smart contract.
Note that we discarded assigning f410
addresses to non-Ethereum related actors at this stage in the name of simplicity, but we may revisit this decision in the future, depending on user feedback.
At the time of writing, the EVM runtime actor implementation resides in the next
branch of ref-fvm
.
Copyright and related rights waived via CC0.