ref-fvm
ref-fvm copied to clipboard
EVM: Format "arbitrary message parameters" as solidity parameters
Currently, if an EVM contract receives a message with a method number other than the standard invoke_contract
method number, it will:
- Invoke the EVM with the paramters (as bytes).
- Make the method number available via an opcode.
Unfortunately:
- If an EVM contract fails to check the method number, it will misinterpret the parameters.
- Using this mechanism from solidity is non-trivial because the parameters won't conform to the solidity calling convention.
Proposal: Transform the method number and parameters into a valid call per the solidity calling convention.
- Define a standard solidity function signature (
receive_filecoin_message
) that takes a method number (uint64
), an IPLD codec (uint64
), and a byte array. - Compute the function selector for this method (
f
). - When an EVM Contract is invoked with an "undefined" Filecoin method number, invoke the EVM contract with the method number, parameter's IPLD codec, and byte array encoded as a call to
receive_filecoin_message
.
Specifically:
- Let
f
(the function selector) bekeccak256("receive_filecoin_message(uint64,uint64,bytes)")[..4]
. - Format the parameters as
{f}{be32(method_number)}{be32(params_codec)}{be32(0x60)}{be32(params.len())}{pad32(params)}
where:-
be32
big-endien encodes a number as 32 bytes. -
pad32
pads bytes out to the next multiple of 32.
-
- Invoke the EVM contract with these parameters.
This way, a smart contract can handle all FVM messages by implementing a receive_filecoin_message
method.
I dont think we should be in the business of transforming input, especially in the absence of metadata.
Also it is not necessary that the contract is written in solidity.
It might not be written in solidity, but solidity is the defacto ABI. If we present these messages without conforming to that ABI, it becomes very difficult to receive these messages.
Ok, really, this isn't strictly necessary. We can do this with tooling (write a tool that wraps an EVM contract in a bit of code that checks the method number and potentially takes a new code path if it isn't the expected one).
Really, my main concern is:
If an EVM contract fails to check the method number, it will misinterpret the parameters.
But we can also just shrug? I guess? The benefit of the current approach is that there's no ambiguity.
@Stebalien what is the "must" scope (if any) for M2.1 here?
We need to at least decide not to do this. This is a design decision for M2.1.
Ok, I've done some thinking here and I'm convinced we should do this. As it stands:
- The feature is very difficult to use at all because it requires wrapping a solidity contract in some manual EVM bytecode.
- The feature is dangerous because EVM contracts won't, by default, check the METHOD opcode.
- We're not transforming arguments, we're just wrapping them into a form that EVM contracts will understand. At the end of the day, the solidity calling convention is the de-facto calling EVM convention.
- We should implement as few opcodes as possible. Otherwise, we're not going to be able to add new opcodes as they're added by the EVM (e.g., through EIPs).
I think the proposed receive_filecoin_message
is nice, though there might be some collision with the existing solidity fallback
method which is how contracts do arbitrary call catches. We should make sure this doesn't break existing fallback behavior.
I recently got it working so that with only a little bit of bytecode rewriting we can handle filecoin message receives inside of a solidity contract's fallback method.
All a contract needs to do is call inside of the fallback method
function readMethodNum() private view returns (uint method) {
// HACK HACK HACK: we'll sub out difficulty opcode after compiling with the FVM extension
assembly {
method := difficulty()
}
}
then after compiling we swap out difficulty opcode 0x44 for fvm extension 0xB1. Raw parameter bytes are available as part of calldata input to the fallback method too so in principle the contract can receive arbitrary filecoin actor messages.
Obviously rewriting bytecode isn't a long term solution, but maybe adding a single precompile to replace readMethodNum
is enough. Then we can have standard patterns to implement inside of fallbacks to receive both ethereum and filecoin messages.
--edit-- nvm I think I get it. The fallback function would be used for function selectors that don't match filecoin method receive so the fallback function would be left alone and would not break.
One thing to consider is what happens when this function selector is called explicitly from invoke-evm-actor since we can't trust that its always invoked from filecoin send if we're using the regular solidity calling conventions. It's probably fine?
Wait it looks like the calldata passed to fallback function is always different than the solidity convention. Is this the case? As far as I understand it (i.e. see here the calldata for function selection f = 0xdeadbeef
should look like 0xdeadbeef<abi-encoding-of-params>
but trying this out and storing the calldata bytes on an fvm contract I get the 4 function selector bytes passed in as an abi encoded byte slice, not as the first bytes of the calldata: 00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000004deadbeef00000000000000000000000000000000000000000000000000000000
which looks like its encoding evm method 2, 4 bytes of data, then the function selector.
This seems to match up 1-1 with the filecoin method invocation calldata with filecoin invoke on method 7 and params 818181818180
000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000068181818181800000000000000000000000000000000000000000000000000000
This makes me confused by
If an EVM contract fails to check the method number, it will misinterpret the parameters.
It looks like all fallback functions, even those invoked with an explicit solidity function selector, will misinterpret the calldata parameter.
nvm I think I get it. The fallback function would be used for function selectors that don't match filecoin method receive so the fallback function would be left alone and would not break.
Yep!
I recently got it working so that with only a little bit of bytecode rewriting we can handle filecoin message receives inside of a solidity contract's fallback method.
Well, if and only if the method parameters don't just happen to conflict with an existing EVM method.
Wait it looks like the calldata passed to fallback function is always different than the solidity convention.
It'll just see the raw parameters.