NV18: FEVM: Basic smoke test (#9635)
* unknown return types should not be treated as errors from WaitForMessage * simplecoin FEVM smoke test * add itest-fevm to circle matrix * use a named error for metadata lookup failures * hand-write the fevm basic test * make gen * address nits
This commit is contained in:
parent
8a7367f1c9
commit
af39ec27b8
@ -911,6 +911,11 @@ workflows:
|
||||
suite: itest-dup_mpool_messages
|
||||
target: "./itests/dup_mpool_messages_test.go"
|
||||
|
||||
- test:
|
||||
name: test-itest-fevm
|
||||
suite: itest-fevm
|
||||
target: "./itests/fevm_test.go"
|
||||
|
||||
- test:
|
||||
name: test-itest-gas_estimation
|
||||
suite: itest-gas_estimation
|
||||
|
@ -2,6 +2,7 @@ package stmgr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
@ -27,6 +28,8 @@ import (
|
||||
"github.com/filecoin-project/lotus/node/modules/dtypes"
|
||||
)
|
||||
|
||||
var ErrMetadataNotFound = errors.New("actor metadata not found")
|
||||
|
||||
func GetReturnType(ctx context.Context, sm *StateManager, to address.Address, method abi.MethodNum, ts *types.TipSet) (cbg.CBORUnmarshaler, error) {
|
||||
act, err := sm.LoadActor(ctx, to, ts)
|
||||
if err != nil {
|
||||
@ -35,7 +38,7 @@ func GetReturnType(ctx context.Context, sm *StateManager, to address.Address, me
|
||||
|
||||
m, found := sm.tsExec.NewActorRegistry().Methods[act.Code][method]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("unknown method %d for actor %s", method, act.Code)
|
||||
return nil, fmt.Errorf("unknown method %d for actor %s: %w", method, act.Code, ErrMetadataNotFound)
|
||||
}
|
||||
|
||||
return reflect.New(m.Ret.Elem()).Interface().(cbg.CBORUnmarshaler), nil
|
||||
@ -44,7 +47,7 @@ func GetReturnType(ctx context.Context, sm *StateManager, to address.Address, me
|
||||
func GetParamType(ar *vm.ActorRegistry, actCode cid.Cid, method abi.MethodNum) (cbg.CBORUnmarshaler, error) {
|
||||
m, found := ar.Methods[actCode][method]
|
||||
if !found {
|
||||
return nil, fmt.Errorf("unknown method %d for actor %s", method, actCode)
|
||||
return nil, fmt.Errorf("unknown method %d for actor %s: %w", method, actCode, ErrMetadataNotFound)
|
||||
}
|
||||
return reflect.New(m.Params.Elem()).Interface().(cbg.CBORUnmarshaler), nil
|
||||
}
|
||||
|
1
itests/contracts/SimpleCoin.bin
Normal file
1
itests/contracts/SimpleCoin.bin
Normal file
@ -0,0 +1 @@
|
||||
608060405234801561001057600080fd5b506127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061051c806100656000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80637bd703e81461004657806390b98a1114610076578063f8b2cb4f146100a6575b600080fd5b610060600480360381019061005b919061030a565b6100d6565b60405161006d9190610350565b60405180910390f35b610090600480360381019061008b9190610397565b6100f4565b60405161009d91906103f2565b60405180910390f35b6100c060048036038101906100bb919061030a565b61025f565b6040516100cd9190610350565b60405180910390f35b600060026100e38361025f565b6100ed919061043c565b9050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156101455760009050610259565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610193919061047e565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101e891906104b2565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161024c9190610350565b60405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006102d7826102ac565b9050919050565b6102e7816102cc565b81146102f257600080fd5b50565b600081359050610304816102de565b92915050565b6000602082840312156103205761031f6102a7565b5b600061032e848285016102f5565b91505092915050565b6000819050919050565b61034a81610337565b82525050565b60006020820190506103656000830184610341565b92915050565b61037481610337565b811461037f57600080fd5b50565b6000813590506103918161036b565b92915050565b600080604083850312156103ae576103ad6102a7565b5b60006103bc858286016102f5565b92505060206103cd85828601610382565b9150509250929050565b60008115159050919050565b6103ec816103d7565b82525050565b600060208201905061040760008301846103e3565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061044782610337565b915061045283610337565b925082820261046081610337565b915082820484148315176104775761047661040d565b5b5092915050565b600061048982610337565b915061049483610337565b92508282039050818111156104ac576104ab61040d565b5b92915050565b60006104bd82610337565b91506104c883610337565b92508282019050808211156104e0576104df61040d565b5b9291505056fea26469706673582212205ede41ff9072784ccc19ac18de0781558d305a8139361fa85dc51a8614e47d8c64736f6c63430008110033
|
31
itests/contracts/SimpleCoin.sol
Normal file
31
itests/contracts/SimpleCoin.sol
Normal file
@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.4.2;
|
||||
|
||||
contract SimpleCoin {
|
||||
mapping(address => uint256) balances;
|
||||
|
||||
event Transfer(address indexed _from, address indexed _to, uint256 _value);
|
||||
|
||||
constructor() {
|
||||
balances[tx.origin] = 10000;
|
||||
}
|
||||
|
||||
function sendCoin(address receiver, uint256 amount)
|
||||
public
|
||||
returns (bool sufficient)
|
||||
{
|
||||
if (balances[msg.sender] < amount) return false;
|
||||
balances[msg.sender] -= amount;
|
||||
balances[receiver] += amount;
|
||||
emit Transfer(msg.sender, receiver, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
function getBalanceInEth(address addr) public view returns (uint256) {
|
||||
return getBalance(addr) * 2;
|
||||
}
|
||||
|
||||
function getBalance(address addr) public view returns (uint256) {
|
||||
return balances[addr];
|
||||
}
|
||||
}
|
171
itests/fevm_test.go
Normal file
171
itests/fevm_test.go
Normal file
@ -0,0 +1,171 @@
|
||||
package itests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
cbg "github.com/whyrusleeping/cbor-gen"
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/big"
|
||||
builtintypes "github.com/filecoin-project/go-state-types/builtin"
|
||||
"github.com/filecoin-project/go-state-types/builtin/v10/eam"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/itests/kit"
|
||||
)
|
||||
|
||||
// TestFEVMBasic does a basic fevm contract installation and invocation
|
||||
func TestFEVMBasic(t *testing.T) {
|
||||
// TODO the contract installation and invocation can be lifted into utility methods
|
||||
// He who writes the second test, shall do that.
|
||||
kit.QuietMiningLogs()
|
||||
|
||||
blockTime := 100 * time.Millisecond
|
||||
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC())
|
||||
ens.InterconnectAll().BeginMining(blockTime)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// install contract
|
||||
contractHex, err := os.ReadFile("contracts/SimpleCoin.bin")
|
||||
require.NoError(t, err)
|
||||
|
||||
contract, err := hex.DecodeString(string(contractHex))
|
||||
require.NoError(t, err)
|
||||
|
||||
fromAddr, err := client.WalletDefaultAddress(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
nonce, err := client.MpoolGetNonce(ctx, fromAddr)
|
||||
if err != nil {
|
||||
nonce = 0 // assume a zero nonce on error (e.g. sender doesn't exist).
|
||||
}
|
||||
|
||||
var salt [32]byte
|
||||
binary.BigEndian.PutUint64(salt[:], nonce)
|
||||
|
||||
method := builtintypes.MethodsEAM.Create2
|
||||
params, err := actors.SerializeParams(&eam.Create2Params{
|
||||
Initcode: contract,
|
||||
Salt: salt,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
msg := &types.Message{
|
||||
To: builtintypes.EthereumAddressManagerActorAddr,
|
||||
From: fromAddr,
|
||||
Value: big.Zero(),
|
||||
Method: method,
|
||||
Params: params,
|
||||
}
|
||||
|
||||
t.Log("sending create message")
|
||||
smsg, err := client.MpoolPushMessage(ctx, msg, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Log("waiting for message to execute")
|
||||
wait, err := client.StateWaitMsg(ctx, smsg.Cid(), 0, 0, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, wait.Receipt.ExitCode.IsSuccess(), "contract installation failed")
|
||||
|
||||
var result eam.CreateReturn
|
||||
r := bytes.NewReader(wait.Receipt.Return)
|
||||
err = result.UnmarshalCBOR(r)
|
||||
require.NoError(t, err)
|
||||
|
||||
idAddr, err := address.NewIDAddress(result.ActorID)
|
||||
require.NoError(t, err)
|
||||
t.Logf("actor ID address is %s", idAddr)
|
||||
|
||||
// invoke the contract with owner
|
||||
{
|
||||
entryPoint, err := hex.DecodeString("f8b2cb4f")
|
||||
require.NoError(t, err)
|
||||
|
||||
inputData, err := hex.DecodeString("000000000000000000000000ff00000000000000000000000000000000000064")
|
||||
require.NoError(t, err)
|
||||
|
||||
params := append(entryPoint, inputData...)
|
||||
var buffer bytes.Buffer
|
||||
err = cbg.WriteByteArray(&buffer, params)
|
||||
require.NoError(t, err)
|
||||
params = buffer.Bytes()
|
||||
|
||||
msg := &types.Message{
|
||||
To: idAddr,
|
||||
From: fromAddr,
|
||||
Value: big.Zero(),
|
||||
Method: abi.MethodNum(2),
|
||||
Params: params,
|
||||
}
|
||||
|
||||
t.Log("sending invoke message")
|
||||
smsg, err := client.MpoolPushMessage(ctx, msg, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Log("waiting for message to execute")
|
||||
wait, err := client.StateWaitMsg(ctx, smsg.Cid(), 0, 0, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, wait.Receipt.ExitCode.IsSuccess(), "contract execution failed")
|
||||
|
||||
result, err := cbg.ReadByteArray(bytes.NewBuffer(wait.Receipt.Return), uint64(len(wait.Receipt.Return)))
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedResult, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000002710")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, result, expectedResult)
|
||||
}
|
||||
|
||||
// invoke the contract with non owner
|
||||
{
|
||||
entryPoint, err := hex.DecodeString("f8b2cb4f")
|
||||
require.NoError(t, err)
|
||||
|
||||
inputData, err := hex.DecodeString("000000000000000000000000ff00000000000000000000000000000000000065")
|
||||
require.NoError(t, err)
|
||||
|
||||
params := append(entryPoint, inputData...)
|
||||
var buffer bytes.Buffer
|
||||
err = cbg.WriteByteArray(&buffer, params)
|
||||
require.NoError(t, err)
|
||||
params = buffer.Bytes()
|
||||
|
||||
msg := &types.Message{
|
||||
To: idAddr,
|
||||
From: fromAddr,
|
||||
Value: big.Zero(),
|
||||
Method: abi.MethodNum(2),
|
||||
Params: params,
|
||||
}
|
||||
|
||||
t.Log("sending invoke message")
|
||||
smsg, err := client.MpoolPushMessage(ctx, msg, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Log("waiting for message to execute")
|
||||
wait, err := client.StateWaitMsg(ctx, smsg.Cid(), 0, 0, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.True(t, wait.Receipt.ExitCode.IsSuccess(), "contract execution failed")
|
||||
|
||||
result, err := cbg.ReadByteArray(bytes.NewBuffer(wait.Receipt.Return), uint64(len(wait.Receipt.Return)))
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedResult, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, result, expectedResult)
|
||||
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
@ -617,14 +618,22 @@ func (m *StateModule) StateWaitMsg(ctx context.Context, msg cid.Cid, confidence
|
||||
|
||||
t, err := stmgr.GetReturnType(ctx, m.StateManager, vmsg.To, vmsg.Method, ts)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("failed to get return type: %w", err)
|
||||
if errors.Is(err, stmgr.ErrMetadataNotFound) {
|
||||
// This is not nececessary an error -- EVM methods (and in the future native actors) may
|
||||
// return just bytes, and in the not so distant future we'll have native wasm actors
|
||||
// that are by definition not in the registry.
|
||||
// So in this case, log a debug message and retun the raw bytes.
|
||||
log.Debugf("failed to get return type: %s", err)
|
||||
returndec = recpt.Return
|
||||
} else {
|
||||
return nil, xerrors.Errorf("failed to get return type: %w", err)
|
||||
}
|
||||
} else {
|
||||
if err := t.UnmarshalCBOR(bytes.NewReader(recpt.Return)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
returndec = t
|
||||
}
|
||||
|
||||
if err := t.UnmarshalCBOR(bytes.NewReader(recpt.Return)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
returndec = t
|
||||
}
|
||||
|
||||
return &api.MsgLookup{
|
||||
|
Loading…
Reference in New Issue
Block a user