Merge pull request #10068 from filecoin-project/test/eam-tests
itests: Test EAM interactions from accounts
This commit is contained in:
commit
74b33bd4a2
1
itests/contracts/RecCall.hex
Normal file
1
itests/contracts/RecCall.hex
Normal file
@ -0,0 +1 @@
|
||||
608060405234801561001057600080fd5b5061025b806100206000396000f3fe60806040526004361061001e5760003560e01c8063cb7786d714610023575b600080fd5b61003d60048036038101906100389190610129565b61003f565b005b600083036100d15760008111156100cc573073ffffffffffffffffffffffffffffffffffffffff1663cb7786d7838460018561007b91906101ab565b6040518463ffffffff1660e01b8152600401610099939291906101ee565b600060405180830381600087803b1580156100b357600080fd5b505af11580156100c7573d6000803e3d6000fd5b505050505b6100e9565b6100e86001846100e191906101ab565b838361003f565b5b505050565b600080fd5b6000819050919050565b610106816100f3565b811461011157600080fd5b50565b600081359050610123816100fd565b92915050565b600080600060608486031215610142576101416100ee565b5b600061015086828701610114565b935050602061016186828701610114565b925050604061017286828701610114565b9150509250925092565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b60006101b6826100f3565b91506101c1836100f3565b92508282039050818111156101d9576101d861017c565b5b92915050565b6101e8816100f3565b82525050565b600060608201905061020360008301866101df565b61021060208301856101df565b61021d60408301846101df565b94935050505056fea26469706673582212209a21ff59c642e2970917c07bf498271c2a6df8e3929677952c0c2d8031db15cc64736f6c63430008110033
|
16
itests/contracts/RecCall.sol
Normal file
16
itests/contracts/RecCall.sol
Normal file
@ -0,0 +1,16 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
contract StackRecCall {
|
||||
function exec1(uint256 n, uint256 m, uint256 r) public payable {
|
||||
if(n == 0) {
|
||||
if(r > 0) {
|
||||
StackRecCall(address(this)).exec1(m, m, r-1);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
exec1(n-1, m, r);
|
||||
}
|
||||
}
|
1
itests/contracts/StackFunc.hex
Normal file
1
itests/contracts/StackFunc.hex
Normal file
@ -0,0 +1 @@
|
||||
608060405234801561001057600080fd5b50610162806100206000396000f3fe60806040526004361061001e5760003560e01c8063c38e07dd14610023575b600080fd5b61003d6004803603810190610038919061009c565b61003f565b005b600081031561005e5761005d60018261005891906100f8565b61003f565b5b50565b600080fd5b6000819050919050565b61007981610066565b811461008457600080fd5b50565b60008135905061009681610070565b92915050565b6000602082840312156100b2576100b1610061565b5b60006100c084828501610087565b91505092915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600061010382610066565b915061010e83610066565b9250828203905081811115610126576101256100c9565b5b9291505056fea2646970667358221220ee8f18bfd33b1e0156cfe68e9071dd32960b370c7e63ec53c62dd48e28cb5d3b64736f6c63430008110033
|
12
itests/contracts/StackFunc.sol
Normal file
12
itests/contracts/StackFunc.sol
Normal file
@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.17;
|
||||
|
||||
contract StackSelf {
|
||||
function exec1(uint256 n) public payable {
|
||||
if(n == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
exec1(n-1);
|
||||
}
|
||||
}
|
@ -11,6 +11,8 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-state-types/abi"
|
||||
"github.com/filecoin-project/go-state-types/big"
|
||||
builtin2 "github.com/filecoin-project/go-state-types/builtin"
|
||||
"github.com/filecoin-project/go-state-types/builtin/v10/eam"
|
||||
"github.com/filecoin-project/go-state-types/exitcode"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
@ -313,3 +315,51 @@ func TestEthAccountAbstractionFailsFromEvmActor(t *testing.T) {
|
||||
require.Error(t, err, "expected gas estimation to fail")
|
||||
require.Contains(t, err.Error(), "SysErrSenderInvalid")
|
||||
}
|
||||
|
||||
func TestEthAccountManagerPermissions(t *testing.T) {
|
||||
kit.QuietMiningLogs()
|
||||
|
||||
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC())
|
||||
ens.InterconnectAll().BeginMining(10 * time.Millisecond)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// setup f1/f3/f4 accounts
|
||||
|
||||
wsp, err := client.WalletNew(ctx, types.KTSecp256k1)
|
||||
require.NoError(t, err)
|
||||
|
||||
wbl, err := client.WalletNew(ctx, types.KTBLS)
|
||||
require.NoError(t, err)
|
||||
|
||||
wdl, err := client.WalletNew(ctx, types.KTDelegated)
|
||||
require.NoError(t, err)
|
||||
|
||||
def := client.DefaultKey.Address
|
||||
|
||||
// send some funds
|
||||
client.ExpectSend(ctx, def, wsp, types.FromFil(10), "")
|
||||
client.ExpectSend(ctx, def, wbl, types.FromFil(10), "")
|
||||
client.ExpectSend(ctx, def, wdl, types.FromFil(10), "")
|
||||
require.NoError(t, err)
|
||||
|
||||
// make sure that EAM only allows CreateExternal to be called by accounts
|
||||
client.ExpectSend(ctx, wsp, builtin2.EthereumAddressManagerActorAddr, big.Zero(), "not one of supported (18)", client.MakeSendCall(builtin2.MethodsEAM.Create, &eam.CreateParams{Nonce: 0}))
|
||||
client.ExpectSend(ctx, wbl, builtin2.EthereumAddressManagerActorAddr, big.Zero(), "not one of supported (18)", client.MakeSendCall(builtin2.MethodsEAM.Create, &eam.CreateParams{Nonce: 0}))
|
||||
client.ExpectSend(ctx, wdl, builtin2.EthereumAddressManagerActorAddr, big.Zero(), "not one of supported (18)", client.MakeSendCall(builtin2.MethodsEAM.Create, &eam.CreateParams{Nonce: 0}))
|
||||
|
||||
client.ExpectSend(ctx, wsp, builtin2.EthereumAddressManagerActorAddr, big.Zero(), "not one of supported (18)", client.MakeSendCall(builtin2.MethodsEAM.Create2, &eam.Create2Params{}))
|
||||
client.ExpectSend(ctx, wbl, builtin2.EthereumAddressManagerActorAddr, big.Zero(), "not one of supported (18)", client.MakeSendCall(builtin2.MethodsEAM.Create2, &eam.Create2Params{}))
|
||||
client.ExpectSend(ctx, wdl, builtin2.EthereumAddressManagerActorAddr, big.Zero(), "not one of supported (18)", client.MakeSendCall(builtin2.MethodsEAM.Create2, &eam.Create2Params{}))
|
||||
|
||||
contractHex, err := os.ReadFile("contracts/SimpleCoin.hex")
|
||||
require.NoError(t, err)
|
||||
contract, err := hex.DecodeString(string(contractHex))
|
||||
require.NoError(t, err)
|
||||
contractParams := abi.CborBytes(contract)
|
||||
|
||||
client.ExpectSend(ctx, wsp, builtin2.EthereumAddressManagerActorAddr, big.Zero(), "", client.MakeSendCall(builtin2.MethodsEAM.CreateExternal, &contractParams))
|
||||
client.ExpectSend(ctx, wbl, builtin2.EthereumAddressManagerActorAddr, big.Zero(), "", client.MakeSendCall(builtin2.MethodsEAM.CreateExternal, &contractParams))
|
||||
client.ExpectSend(ctx, wdl, builtin2.EthereumAddressManagerActorAddr, big.Zero(), "", client.MakeSendCall(builtin2.MethodsEAM.CreateExternal, &contractParams))
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package itests
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
"time"
|
||||
@ -10,6 +11,7 @@ import (
|
||||
|
||||
"github.com/filecoin-project/go-address"
|
||||
builtintypes "github.com/filecoin-project/go-state-types/builtin"
|
||||
"github.com/filecoin-project/go-state-types/exitcode"
|
||||
"github.com/filecoin-project/go-state-types/manifest"
|
||||
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
@ -38,9 +40,9 @@ func inputDataFromFrom(ctx context.Context, t *testing.T, client *kit.TestFullNo
|
||||
|
||||
func setupFEVMTest(t *testing.T) (context.Context, context.CancelFunc, *kit.TestFullNode) {
|
||||
kit.QuietMiningLogs()
|
||||
blockTime := 100 * time.Millisecond
|
||||
blockTime := 5 * time.Millisecond
|
||||
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC())
|
||||
ens.InterconnectAll().BeginMining(blockTime)
|
||||
ens.InterconnectAll().BeginMiningMustPost(blockTime)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||
return ctx, cancel, client
|
||||
}
|
||||
@ -139,3 +141,78 @@ func TestEVMRpcDisable(t *testing.T) {
|
||||
_, err := client.EthBlockNumber(context.Background())
|
||||
require.ErrorContains(t, err, "module disabled, enable with Fevm.EnableEthRPC")
|
||||
}
|
||||
|
||||
// TestFEVMRecursiveFuncCall deploys a contract and makes a recursive function calls
|
||||
func TestFEVMRecursiveFuncCall(t *testing.T) {
|
||||
ctx, cancel, client := setupFEVMTest(t)
|
||||
defer cancel()
|
||||
|
||||
//install contract Actor
|
||||
filenameActor := "contracts/StackFunc.hex"
|
||||
fromAddr, actorAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor)
|
||||
|
||||
testN := func(n int) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
inputData := make([]byte, 32)
|
||||
binary.BigEndian.PutUint64(inputData[24:], uint64(n))
|
||||
|
||||
result := client.EVM().InvokeContractByFuncName(ctx, fromAddr, actorAddr, "exec1(uint256)", inputData)
|
||||
require.Equal(t, result, []byte{})
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("n=0", testN(0))
|
||||
t.Run("n=1", testN(1))
|
||||
t.Run("n=20", testN(20))
|
||||
t.Run("n=200", testN(200))
|
||||
t.Run("n=293", testN(293))
|
||||
}
|
||||
|
||||
// TestFEVMRecursiveActorCall deploys a contract and makes a recursive actor calls
|
||||
func TestFEVMRecursiveActorCall(t *testing.T) {
|
||||
ctx, cancel, client := setupFEVMTest(t)
|
||||
defer cancel()
|
||||
|
||||
//install contract Actor
|
||||
filenameActor := "contracts/RecCall.hex"
|
||||
fromAddr, actorAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor)
|
||||
|
||||
testN := func(n, r int, ex exitcode.ExitCode) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
inputData := make([]byte, 32*3)
|
||||
binary.BigEndian.PutUint64(inputData[24:], uint64(n))
|
||||
binary.BigEndian.PutUint64(inputData[32+24:], uint64(n))
|
||||
binary.BigEndian.PutUint64(inputData[32+32+24:], uint64(r))
|
||||
|
||||
client.EVM().InvokeContractByFuncNameExpectExit(ctx, fromAddr, actorAddr, "exec1(uint256,uint256,uint256)", inputData, ex)
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("n=0,r=1", testN(0, 1, exitcode.Ok))
|
||||
t.Run("n=1,r=1", testN(1, 1, exitcode.Ok))
|
||||
t.Run("n=20,r=1", testN(20, 1, exitcode.Ok))
|
||||
t.Run("n=200,r=1", testN(200, 1, exitcode.Ok))
|
||||
t.Run("n=251,r=1", testN(251, 1, exitcode.Ok))
|
||||
|
||||
t.Run("n=252,r=1-fails", testN(252, 1, exitcode.ExitCode(23))) // 23 means stack overflow
|
||||
|
||||
t.Run("n=0,r=10", testN(0, 10, exitcode.Ok))
|
||||
t.Run("n=1,r=10", testN(1, 10, exitcode.Ok))
|
||||
t.Run("n=20,r=10", testN(20, 10, exitcode.Ok))
|
||||
t.Run("n=200,r=10", testN(200, 10, exitcode.Ok))
|
||||
t.Run("n=251,r=10", testN(251, 10, exitcode.Ok))
|
||||
|
||||
t.Run("n=252,r=10-fails", testN(252, 10, exitcode.ExitCode(23)))
|
||||
|
||||
t.Run("n=0,r=32", testN(0, 32, exitcode.Ok))
|
||||
t.Run("n=1,r=32", testN(1, 32, exitcode.Ok))
|
||||
t.Run("n=20,r=32", testN(20, 32, exitcode.Ok))
|
||||
t.Run("n=200,r=32", testN(200, 32, exitcode.Ok))
|
||||
t.Run("n=251,r=32", testN(251, 32, exitcode.Ok))
|
||||
|
||||
t.Run("n=0,r=254", testN(0, 254, exitcode.Ok))
|
||||
t.Run("n=251,r=170", testN(251, 170, exitcode.Ok))
|
||||
|
||||
t.Run("n=0,r=255-fails", testN(0, 255, exitcode.ExitCode(33))) // 33 means transaction reverted
|
||||
t.Run("n=251,r=171-fails", testN(251, 171, exitcode.ExitCode(33)))
|
||||
}
|
||||
|
@ -21,8 +21,10 @@ import (
|
||||
builtintypes "github.com/filecoin-project/go-state-types/builtin"
|
||||
"github.com/filecoin-project/go-state-types/builtin/v10/eam"
|
||||
"github.com/filecoin-project/go-state-types/crypto"
|
||||
"github.com/filecoin-project/go-state-types/exitcode"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/build"
|
||||
"github.com/filecoin-project/lotus/chain/actors"
|
||||
"github.com/filecoin-project/lotus/chain/types"
|
||||
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
||||
@ -109,11 +111,12 @@ func (e *EVM) InvokeSolidity(ctx context.Context, sender address.Address, target
|
||||
params = buffer.Bytes()
|
||||
|
||||
msg := &types.Message{
|
||||
To: target,
|
||||
From: sender,
|
||||
Value: big.Zero(),
|
||||
Method: builtintypes.MethodsEVM.InvokeContract,
|
||||
Params: params,
|
||||
To: target,
|
||||
From: sender,
|
||||
Value: big.Zero(),
|
||||
Method: builtintypes.MethodsEVM.InvokeContract,
|
||||
GasLimit: build.BlockGasLimit, // note: we hardcode block gas limit due to slightly broken gas estimation - https://github.com/filecoin-project/lotus/issues/10041
|
||||
Params: params,
|
||||
}
|
||||
|
||||
e.t.Log("sending invoke message")
|
||||
@ -237,12 +240,18 @@ func (e *EVM) ComputeContractAddress(deployer ethtypes.EthAddress, nonce uint64)
|
||||
func (e *EVM) InvokeContractByFuncName(ctx context.Context, fromAddr address.Address, idAddr address.Address, funcSignature string, inputData []byte) []byte {
|
||||
entryPoint := CalcFuncSignature(funcSignature)
|
||||
wait := e.InvokeSolidity(ctx, fromAddr, idAddr, entryPoint, inputData)
|
||||
require.True(e.t, wait.Receipt.ExitCode.IsSuccess(), "contract execution failed")
|
||||
require.True(e.t, wait.Receipt.ExitCode.IsSuccess(), "contract execution failed: %d", wait.Receipt.ExitCode)
|
||||
result, err := cbg.ReadByteArray(bytes.NewBuffer(wait.Receipt.Return), uint64(len(wait.Receipt.Return)))
|
||||
require.NoError(e.t, err)
|
||||
return result
|
||||
}
|
||||
|
||||
func (e *EVM) InvokeContractByFuncNameExpectExit(ctx context.Context, fromAddr address.Address, idAddr address.Address, funcSignature string, inputData []byte, exit exitcode.ExitCode) {
|
||||
entryPoint := CalcFuncSignature(funcSignature)
|
||||
wait := e.InvokeSolidity(ctx, fromAddr, idAddr, entryPoint, inputData)
|
||||
require.Equal(e.t, exit, wait.Receipt.ExitCode)
|
||||
}
|
||||
|
||||
// function signatures are the first 4 bytes of the hash of the function name and types
|
||||
func CalcFuncSignature(funcName string) []byte {
|
||||
hasher := sha3.NewLegacyKeccak256()
|
||||
|
@ -1,6 +1,7 @@
|
||||
package kit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
@ -10,9 +11,11 @@ import (
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"github.com/multiformats/go-multiaddr"
|
||||
"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/exitcode"
|
||||
|
||||
"github.com/filecoin-project/lotus/api"
|
||||
"github.com/filecoin-project/lotus/api/v1api"
|
||||
@ -124,6 +127,50 @@ func (f *TestFullNode) AssignPrivKey(pkey *Libp2p) {
|
||||
f.Pkey = pkey
|
||||
}
|
||||
|
||||
type SendCall struct {
|
||||
Method abi.MethodNum
|
||||
Params []byte
|
||||
}
|
||||
|
||||
func (f *TestFullNode) MakeSendCall(m abi.MethodNum, params cbg.CBORMarshaler) SendCall {
|
||||
var b bytes.Buffer
|
||||
err := params.MarshalCBOR(&b)
|
||||
require.NoError(f.t, err)
|
||||
return SendCall{
|
||||
Method: m,
|
||||
Params: b.Bytes(),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *TestFullNode) ExpectSend(ctx context.Context, from, to address.Address, value types.BigInt, errContains string, sc ...SendCall) *types.SignedMessage {
|
||||
msg := &types.Message{From: from, To: to, Value: value}
|
||||
|
||||
if len(sc) == 1 {
|
||||
msg.Method = sc[0].Method
|
||||
msg.Params = sc[0].Params
|
||||
}
|
||||
|
||||
_, err := f.GasEstimateMessageGas(ctx, msg, nil, types.EmptyTSK)
|
||||
if errContains != "" {
|
||||
require.ErrorContains(f.t, err, errContains)
|
||||
return nil
|
||||
}
|
||||
require.NoError(f.t, err)
|
||||
|
||||
if errContains == "" {
|
||||
m, err := f.MpoolPushMessage(ctx, msg, nil)
|
||||
require.NoError(f.t, err)
|
||||
|
||||
r, err := f.StateWaitMsg(ctx, m.Cid(), 1, api.LookbackNoLimit, true)
|
||||
require.NoError(f.t, err)
|
||||
|
||||
require.Equal(f.t, exitcode.Ok, r.Receipt.ExitCode)
|
||||
return m
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChainPredicate encapsulates a chain condition.
|
||||
type ChainPredicate func(set *types.TipSet) bool
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user