ref-fvm icon indicating copy to clipboard operation
ref-fvm copied to clipboard

EVM: Format "arbitrary message parameters" as solidity parameters

Open Stebalien opened this issue 2 years ago • 3 comments

Currently, if an EVM contract receives a message with a method number other than the standard invoke_contract method number, it will:

  1. Invoke the EVM with the paramters (as bytes).
  2. 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.

  1. Define a standard solidity function signature (receive_filecoin_message) that takes a method number (uint64), an IPLD codec (uint64), and a byte array.
  2. Compute the function selector for this method (f).
  3. 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:

  1. Let f (the function selector) be keccak256("receive_filecoin_message(uint64,uint64,bytes)")[..4].
  2. Format the parameters as {f}{be32(method_number)}{be32(params_codec)}{be32(0x60)}{be32(params.len())}{pad32(params)} where:
    1. be32 big-endien encodes a number as 32 bytes.
    2. pad32 pads bytes out to the next multiple of 32.
  3. Invoke the EVM contract with these parameters.

This way, a smart contract can handle all FVM messages by implementing a receive_filecoin_message method.

Stebalien avatar Sep 16 '22 03:09 Stebalien

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.

vyzo avatar Sep 16 '22 03:09 vyzo

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.

Stebalien avatar Sep 16 '22 04:09 Stebalien

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 avatar Sep 16 '22 04:09 Stebalien

@Stebalien what is the "must" scope (if any) for M2.1 here?

maciejwitowski avatar Oct 10 '22 14:10 maciejwitowski

We need to at least decide not to do this. This is a design decision for M2.1.

Stebalien avatar Oct 12 '22 06:10 Stebalien

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).

Stebalien avatar Nov 09 '22 03:11 Stebalien

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?

ZenGround0 avatar Nov 10 '22 13:11 ZenGround0

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.

ZenGround0 avatar Nov 10 '22 16:11 ZenGround0

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.

Stebalien avatar Nov 10 '22 18:11 Stebalien