2023-01-13 19:11:13 +00:00
package kit
import (
"bytes"
"context"
"encoding/binary"
2023-01-18 17:29:06 +00:00
"encoding/hex"
2023-02-06 20:17:15 +00:00
"errors"
2023-01-13 19:11:13 +00:00
"fmt"
2023-01-18 17:29:06 +00:00
"os"
2023-02-01 05:13:13 +00:00
"testing"
"time"
2023-01-13 19:11:13 +00:00
"github.com/ipfs/go-cid"
2023-02-01 05:13:13 +00:00
logging "github.com/ipfs/go-log/v2"
2023-01-13 19:11:13 +00:00
"github.com/multiformats/go-varint"
"github.com/stretchr/testify/require"
cbg "github.com/whyrusleeping/cbor-gen"
"golang.org/x/crypto/sha3"
"github.com/filecoin-project/go-address"
amt4 "github.com/filecoin-project/go-amt-ipld/v4"
2023-01-13 20:31:08 +00:00
"github.com/filecoin-project/go-state-types/abi"
2023-01-13 19:11:13 +00:00
"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/go-state-types/crypto"
2023-01-17 16:38:40 +00:00
"github.com/filecoin-project/go-state-types/exitcode"
2023-01-13 19:11:13 +00:00
"github.com/filecoin-project/lotus/api"
2023-01-17 16:38:40 +00:00
"github.com/filecoin-project/lotus/build"
2023-01-13 19:11:13 +00:00
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/types/ethtypes"
"github.com/filecoin-project/lotus/chain/wallet/key"
"github.com/filecoin-project/lotus/lib/sigs"
)
// EVM groups EVM-related actions.
type EVM struct { * TestFullNode }
func ( f * TestFullNode ) EVM ( ) * EVM {
return & EVM { f }
}
2023-02-06 20:35:35 +00:00
func ( e * EVM ) DeployContractWithValue ( ctx context . Context , sender address . Address , bytecode [ ] byte , value big . Int ) eam . CreateReturn {
2023-01-13 19:11:13 +00:00
require := require . New ( e . t )
2023-01-13 20:31:08 +00:00
method := builtintypes . MethodsEAM . CreateExternal
initcode := abi . CborBytes ( bytecode )
2023-02-06 20:35:35 +00:00
params , errActors := actors . SerializeParams ( & initcode )
require . NoError ( errActors )
2023-01-13 19:11:13 +00:00
msg := & types . Message {
To : builtintypes . EthereumAddressManagerActorAddr ,
From : sender ,
2023-02-01 06:51:32 +00:00
Value : value ,
2023-01-13 19:11:13 +00:00
Method : method ,
Params : params ,
}
e . t . Log ( "sending create message" )
smsg , err := e . MpoolPushMessage ( ctx , msg , nil )
require . NoError ( err )
e . t . Log ( "waiting for message to execute" )
wait , err := e . StateWaitMsg ( ctx , smsg . Cid ( ) , 0 , 0 , false )
require . NoError ( err )
require . True ( wait . Receipt . ExitCode . IsSuccess ( ) , "contract installation failed" )
var result eam . CreateReturn
r := bytes . NewReader ( wait . Receipt . Return )
err = result . UnmarshalCBOR ( r )
require . NoError ( err )
return result
}
2023-02-01 06:51:32 +00:00
func ( e * EVM ) DeployContract ( ctx context . Context , sender address . Address , bytecode [ ] byte ) eam . CreateReturn {
2023-02-06 20:35:35 +00:00
return e . DeployContractWithValue ( ctx , sender , bytecode , big . Zero ( ) )
2023-02-01 06:51:32 +00:00
}
2023-01-13 19:11:13 +00:00
2023-02-06 20:35:35 +00:00
func ( e * EVM ) DeployContractFromFilenameWithValue ( ctx context . Context , binFilename string , value big . Int ) ( address . Address , address . Address ) {
2023-01-12 04:29:56 +00:00
contractHex , err := os . ReadFile ( binFilename )
require . NoError ( e . t , err )
2023-01-18 17:29:06 +00:00
// strip any trailing newlines from the file
contractHex = bytes . TrimRight ( contractHex , "\n" )
2023-01-12 04:29:56 +00:00
contract , err := hex . DecodeString ( string ( contractHex ) )
require . NoError ( e . t , err )
fromAddr , err := e . WalletDefaultAddress ( ctx )
require . NoError ( e . t , err )
2023-02-06 20:35:35 +00:00
result := e . DeployContractWithValue ( ctx , fromAddr , contract , value )
2023-01-18 17:29:06 +00:00
2023-01-12 04:29:56 +00:00
idAddr , err := address . NewIDAddress ( result . ActorID )
require . NoError ( e . t , err )
return fromAddr , idAddr
2023-01-18 17:29:06 +00:00
}
2023-02-01 06:51:32 +00:00
func ( e * EVM ) DeployContractFromFilename ( ctx context . Context , binFilename string ) ( address . Address , address . Address ) {
2023-02-06 20:35:35 +00:00
return e . DeployContractFromFilenameWithValue ( ctx , binFilename , big . Zero ( ) )
2023-02-01 06:51:32 +00:00
}
2023-01-18 17:29:06 +00:00
2023-02-01 05:13:13 +00:00
func ( e * EVM ) InvokeSolidity ( ctx context . Context , sender address . Address , target address . Address , selector [ ] byte , inputData [ ] byte ) ( * api . MsgLookup , error ) {
2023-01-13 19:11:13 +00:00
params := append ( selector , inputData ... )
var buffer bytes . Buffer
err := cbg . WriteByteArray ( & buffer , params )
2023-02-01 05:13:13 +00:00
if err != nil {
return nil , err
}
2023-01-13 19:11:13 +00:00
params = buffer . Bytes ( )
msg := & types . Message {
2023-01-17 16:38:40 +00:00
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 ,
2023-01-13 19:11:13 +00:00
}
e . t . Log ( "sending invoke message" )
smsg , err := e . MpoolPushMessage ( ctx , msg , nil )
2023-02-01 05:13:13 +00:00
if err != nil {
return nil , err
}
2023-01-13 19:11:13 +00:00
e . t . Log ( "waiting for message to execute" )
wait , err := e . StateWaitMsg ( ctx , smsg . Cid ( ) , 0 , 0 , false )
2023-02-01 05:13:13 +00:00
if err != nil {
return nil , err
}
2023-02-06 20:17:15 +00:00
if ! wait . Receipt . ExitCode . IsSuccess ( ) {
result , err := e . StateReplay ( ctx , types . EmptyTSK , wait . Message )
require . NoError ( e . t , err )
e . t . Log ( result . Error )
}
2023-02-01 05:13:13 +00:00
return wait , nil
2023-01-13 19:11:13 +00:00
}
// LoadEvents loads all events in an event AMT.
func ( e * EVM ) LoadEvents ( ctx context . Context , eventsRoot cid . Cid ) [ ] types . Event {
require := require . New ( e . t )
s := & apiIpldStore { ctx , e }
amt , err := amt4 . LoadAMT ( ctx , s , eventsRoot , amt4 . UseTreeBitWidth ( types . EventAMTBitwidth ) )
require . NoError ( err )
ret := make ( [ ] types . Event , 0 , amt . Len ( ) )
err = amt . ForEach ( ctx , func ( u uint64 , deferred * cbg . Deferred ) error {
var evt types . Event
if err := evt . UnmarshalCBOR ( bytes . NewReader ( deferred . Raw ) ) ; err != nil {
return err
}
ret = append ( ret , evt )
return nil
} )
require . NoError ( err )
return ret
}
func ( e * EVM ) NewAccount ( ) ( * key . Key , ethtypes . EthAddress , address . Address ) {
// Generate a secp256k1 key; this will back the Ethereum identity.
key , err := key . GenerateKey ( types . KTSecp256k1 )
require . NoError ( e . t , err )
ethAddr , err := ethtypes . EthAddressFromPubKey ( key . PublicKey )
require . NoError ( e . t , err )
ea , err := ethtypes . CastEthAddress ( ethAddr )
require . NoError ( e . t , err )
addr , err := ea . ToFilecoinAddress ( )
require . NoError ( e . t , err )
return key , * ( * ethtypes . EthAddress ) ( ethAddr ) , addr
}
// AssertAddressBalanceConsistent checks that the balance reported via the
// Filecoin and Ethereum operations for an f410 address is identical, returning
// the balance.
func ( e * EVM ) AssertAddressBalanceConsistent ( ctx context . Context , addr address . Address ) big . Int {
// Validate the arg is an f410 address.
require . Equal ( e . t , address . Delegated , addr . Protocol ( ) )
payload := addr . Payload ( )
namespace , _ , err := varint . FromUvarint ( payload )
require . NoError ( e . t , err )
require . Equal ( e . t , builtintypes . EthereumAddressManagerActorID , namespace )
fbal , err := e . WalletBalance ( ctx , addr )
require . NoError ( e . t , err )
ethAddr , err := ethtypes . EthAddressFromFilecoinAddress ( addr )
require . NoError ( e . t , err )
ebal , err := e . EthGetBalance ( ctx , ethAddr , "latest" )
require . NoError ( e . t , err )
require . Equal ( e . t , fbal , types . BigInt ( ebal ) )
return fbal
}
// SignTransaction signs an Ethereum transaction in place with the supplied private key.
func ( e * EVM ) SignTransaction ( tx * ethtypes . EthTxArgs , privKey [ ] byte ) {
preimage , err := tx . ToRlpUnsignedMsg ( )
require . NoError ( e . t , err )
// sign the RLP payload
signature , err := sigs . Sign ( crypto . SigTypeDelegated , privKey , preimage )
require . NoError ( e . t , err )
r , s , v , err := ethtypes . RecoverSignature ( * signature )
require . NoError ( e . t , err )
tx . V = big . Int ( v )
tx . R = big . Int ( r )
tx . S = big . Int ( s )
}
// SubmitTransaction submits the transaction via the Eth endpoint.
func ( e * EVM ) SubmitTransaction ( ctx context . Context , tx * ethtypes . EthTxArgs ) ethtypes . EthHash {
signed , err := tx . ToRlpSignedMsg ( )
require . NoError ( e . t , err )
hash , err := e . EthSendRawTransaction ( ctx , signed )
require . NoError ( e . t , err )
return hash
}
// ComputeContractAddress computes the address of a contract deployed by the
// specified address with the specified nonce.
func ( e * EVM ) ComputeContractAddress ( deployer ethtypes . EthAddress , nonce uint64 ) ethtypes . EthAddress {
nonceRlp , err := formatInt ( int ( nonce ) )
require . NoError ( e . t , err )
encoded , err := ethtypes . EncodeRLP ( [ ] interface { } {
deployer [ : ] ,
nonceRlp ,
} )
require . NoError ( e . t , err )
hasher := sha3 . NewLegacyKeccak256 ( )
hasher . Write ( encoded )
return * ( * ethtypes . EthAddress ) ( hasher . Sum ( nil ) [ 12 : ] )
}
2023-02-01 05:13:13 +00:00
func ( e * EVM ) InvokeContractByFuncName ( ctx context . Context , fromAddr address . Address , idAddr address . Address , funcSignature string , inputData [ ] byte ) ( [ ] byte , * api . MsgLookup , error ) {
2023-01-12 04:29:56 +00:00
entryPoint := CalcFuncSignature ( funcSignature )
2023-02-01 05:13:13 +00:00
wait , err := e . InvokeSolidity ( ctx , fromAddr , idAddr , entryPoint , inputData )
if err != nil {
return nil , wait , err
}
if ! wait . Receipt . ExitCode . IsSuccess ( ) {
2023-02-06 20:17:15 +00:00
result , err := e . StateReplay ( ctx , types . EmptyTSK , wait . Message )
require . NoError ( e . t , err )
return nil , wait , errors . New ( result . Error )
2023-02-01 05:13:13 +00:00
}
2023-01-12 04:29:56 +00:00
result , err := cbg . ReadByteArray ( bytes . NewBuffer ( wait . Receipt . Return ) , uint64 ( len ( wait . Receipt . Return ) ) )
2023-02-01 05:13:13 +00:00
if err != nil {
return nil , wait , err
}
return result , wait , nil
2023-01-12 04:29:56 +00:00
}
2023-01-17 16:38:40 +00:00
func ( e * EVM ) InvokeContractByFuncNameExpectExit ( ctx context . Context , fromAddr address . Address , idAddr address . Address , funcSignature string , inputData [ ] byte , exit exitcode . ExitCode ) {
entryPoint := CalcFuncSignature ( funcSignature )
2023-02-01 05:13:13 +00:00
wait , _ := e . InvokeSolidity ( ctx , fromAddr , idAddr , entryPoint , inputData )
2023-01-17 16:38:40 +00:00
require . Equal ( e . t , exit , wait . Receipt . ExitCode )
}
2023-01-17 16:53:28 +00:00
// function signatures are the first 4 bytes of the hash of the function name and types
2023-01-12 04:29:56 +00:00
func CalcFuncSignature ( funcName string ) [ ] byte {
hasher := sha3 . NewLegacyKeccak256 ( )
hasher . Write ( [ ] byte ( funcName ) )
hash := hasher . Sum ( nil )
return hash [ : 4 ]
}
2023-01-13 19:11:13 +00:00
// TODO: cleanup and put somewhere reusable.
type apiIpldStore struct {
ctx context . Context
api api . FullNode
}
func ( ht * apiIpldStore ) Context ( ) context . Context {
return ht . ctx
}
func ( ht * apiIpldStore ) Get ( ctx context . Context , c cid . Cid , out interface { } ) error {
raw , err := ht . api . ChainReadObj ( ctx , c )
if err != nil {
return err
}
cu , ok := out . ( cbg . CBORUnmarshaler )
if ok {
if err := cu . UnmarshalCBOR ( bytes . NewReader ( raw ) ) ; err != nil {
return err
}
return nil
}
return fmt . Errorf ( "object does not implement CBORUnmarshaler" )
}
func ( ht * apiIpldStore ) Put ( ctx context . Context , v interface { } ) ( cid . Cid , error ) {
panic ( "No mutations allowed" )
}
func formatInt ( val int ) ( [ ] byte , error ) {
buf := new ( bytes . Buffer )
err := binary . Write ( buf , binary . BigEndian , int64 ( val ) )
if err != nil {
return nil , err
}
return removeLeadingZeros ( buf . Bytes ( ) ) , nil
}
func removeLeadingZeros ( data [ ] byte ) [ ] byte {
firstNonZeroIndex := len ( data )
for i , b := range data {
if b > 0 {
firstNonZeroIndex = i
break
}
}
return data [ firstNonZeroIndex : ]
}
2023-01-27 15:13:38 +00:00
2023-02-01 05:13:13 +00:00
func SetupFEVMTest ( t * testing . T ) ( context . Context , context . CancelFunc , * TestFullNode ) {
2023-02-01 16:19:17 +00:00
// make all logs extra quiet for fevm tests
2023-02-01 05:13:13 +00:00
lvl , err := logging . LevelFromString ( "error" )
if err != nil {
panic ( err )
}
logging . SetAllLoggers ( lvl )
blockTime := 100 * time . Millisecond
client , _ , ens := EnsembleMinimal ( t , MockProofs ( ) , ThroughRPC ( ) )
ens . InterconnectAll ( ) . BeginMining ( blockTime )
ctx , cancel := context . WithTimeout ( context . Background ( ) , time . Minute )
// require that the initial balance is 100 million FIL in setup
// this way other tests can count on this initial wallet balance
fromAddr := client . DefaultKey . Address
bal , err := client . WalletBalance ( ctx , fromAddr )
require . NoError ( t , err )
originalBalance := types . FromFil ( uint64 ( 100_000_000 ) ) // 100 million FIL
require . Equal ( t , originalBalance , bal )
return ctx , cancel , client
}
func ( e * EVM ) TransferValueOrFail ( ctx context . Context , fromAddr address . Address , toAddr address . Address , sendAmount big . Int ) {
sendMsg := & types . Message {
From : fromAddr ,
To : toAddr ,
Value : sendAmount ,
}
signedMsg , err := e . MpoolPushMessage ( ctx , sendMsg , nil )
require . NoError ( e . t , err )
mLookup , err := e . StateWaitMsg ( ctx , signedMsg . Cid ( ) , 3 , api . LookbackNoLimit , true )
require . NoError ( e . t , err )
require . Equal ( e . t , exitcode . Ok , mLookup . Receipt . ExitCode )
}
2023-02-01 16:19:17 +00:00
2023-01-27 15:13:38 +00:00
func NewEthFilterBuilder ( ) * EthFilterBuilder {
return & EthFilterBuilder { }
}
type EthFilterBuilder struct {
filter ethtypes . EthFilterSpec
}
func ( e * EthFilterBuilder ) Filter ( ) * ethtypes . EthFilterSpec { return & e . filter }
func ( e * EthFilterBuilder ) FromBlock ( v string ) * EthFilterBuilder {
e . filter . FromBlock = & v
return e
}
func ( e * EthFilterBuilder ) FromBlockEpoch ( v abi . ChainEpoch ) * EthFilterBuilder {
s := ethtypes . EthUint64 ( v ) . Hex ( )
e . filter . FromBlock = & s
return e
}
func ( e * EthFilterBuilder ) ToBlock ( v string ) * EthFilterBuilder {
e . filter . ToBlock = & v
return e
}
func ( e * EthFilterBuilder ) ToBlockEpoch ( v abi . ChainEpoch ) * EthFilterBuilder {
s := ethtypes . EthUint64 ( v ) . Hex ( )
e . filter . ToBlock = & s
return e
}
func ( e * EthFilterBuilder ) BlockHash ( h ethtypes . EthHash ) * EthFilterBuilder {
e . filter . BlockHash = & h
return e
}
func ( e * EthFilterBuilder ) AddressOneOf ( as ... ethtypes . EthAddress ) * EthFilterBuilder {
e . filter . Address = as
return e
}
func ( e * EthFilterBuilder ) Topic1OneOf ( hs ... ethtypes . EthHash ) * EthFilterBuilder {
if len ( e . filter . Topics ) == 0 {
e . filter . Topics = make ( ethtypes . EthTopicSpec , 1 )
}
e . filter . Topics [ 0 ] = hs
return e
}
func ( e * EthFilterBuilder ) Topic2OneOf ( hs ... ethtypes . EthHash ) * EthFilterBuilder {
for len ( e . filter . Topics ) < 2 {
e . filter . Topics = append ( e . filter . Topics , nil )
}
e . filter . Topics [ 1 ] = hs
return e
}
func ( e * EthFilterBuilder ) Topic3OneOf ( hs ... ethtypes . EthHash ) * EthFilterBuilder {
for len ( e . filter . Topics ) < 3 {
e . filter . Topics = append ( e . filter . Topics , nil )
}
e . filter . Topics [ 2 ] = hs
return e
}
func ( e * EthFilterBuilder ) Topic4OneOf ( hs ... ethtypes . EthHash ) * EthFilterBuilder {
for len ( e . filter . Topics ) < 4 {
e . filter . Topics = append ( e . filter . Topics , nil )
}
e . filter . Topics [ 3 ] = hs
return e
}