Merge branch 'release/v1.20.0' into iand/eth-openrpc-validate
This commit is contained in:
commit
a9cd76907e
@ -473,7 +473,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Docker push
|
name: Docker push
|
||||||
command: |
|
command: |
|
||||||
docker push filecoin/<<parameters.image>>:<<parameters.channel>>
|
docker push filecoin/<<parameters.image>>:<<parameters.channel>>-<<parameters.network>>
|
||||||
if [[ ! -z $CIRCLE_SHA ]]; then
|
if [[ ! -z $CIRCLE_SHA ]]; then
|
||||||
docker image tag filecoin/<<parameters.image>>:<<parameters.channel>>-<<parameters.network>> filecoin/<<parameters.image>>:"${CIRCLE_SHA:0:7}"-<<parameters.network>>
|
docker image tag filecoin/<<parameters.image>>:<<parameters.channel>>-<<parameters.network>> filecoin/<<parameters.image>>:"${CIRCLE_SHA:0:7}"-<<parameters.network>>
|
||||||
docker push filecoin/<<parameters.image>>:"${CIRCLE_SHA:0:7}"-<<parameters.network>>
|
docker push filecoin/<<parameters.image>>:"${CIRCLE_SHA:0:7}"-<<parameters.network>>
|
||||||
|
@ -473,7 +473,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Docker push
|
name: Docker push
|
||||||
command: |
|
command: |
|
||||||
docker push filecoin/<<parameters.image>>:<<parameters.channel>>
|
docker push filecoin/<<parameters.image>>:<<parameters.channel>>-<<parameters.network>>
|
||||||
if [["[[ ! -z $CIRCLE_SHA ]]"]]; then
|
if [["[[ ! -z $CIRCLE_SHA ]]"]]; then
|
||||||
docker image tag filecoin/<<parameters.image>>:<<parameters.channel>>-<<parameters.network>> filecoin/<<parameters.image>>:"${CIRCLE_SHA:0:7}"-<<parameters.network>>
|
docker image tag filecoin/<<parameters.image>>:<<parameters.channel>>-<<parameters.network>> filecoin/<<parameters.image>>:"${CIRCLE_SHA:0:7}"-<<parameters.network>>
|
||||||
docker push filecoin/<<parameters.image>>:"${CIRCLE_SHA:0:7}"-<<parameters.network>>
|
docker push filecoin/<<parameters.image>>:"${CIRCLE_SHA:0:7}"-<<parameters.network>>
|
||||||
|
@ -836,6 +836,9 @@ type FullNode interface {
|
|||||||
// Unsubscribe from a websocket subscription
|
// Unsubscribe from a websocket subscription
|
||||||
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) //perm:write
|
EthUnsubscribe(ctx context.Context, id ethtypes.EthSubscriptionID) (bool, error) //perm:write
|
||||||
|
|
||||||
|
// Returns the client version
|
||||||
|
Web3ClientVersion(ctx context.Context) (string, error) //perm:read
|
||||||
|
|
||||||
// CreateBackup creates node backup onder the specified file name. The
|
// CreateBackup creates node backup onder the specified file name. The
|
||||||
// method requires that the lotus daemon is running with the
|
// method requires that the lotus daemon is running with the
|
||||||
// LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that
|
// LOTUS_BACKUP_BASE_PATH environment variable set to some path, and that
|
||||||
|
@ -41,4 +41,6 @@ func CreateEthRPCAliases(as apitypes.Aliaser) {
|
|||||||
|
|
||||||
as.AliasMethod("net_version", "Filecoin.NetVersion")
|
as.AliasMethod("net_version", "Filecoin.NetVersion")
|
||||||
as.AliasMethod("net_listening", "Filecoin.NetListening")
|
as.AliasMethod("net_listening", "Filecoin.NetListening")
|
||||||
|
|
||||||
|
as.AliasMethod("web3_clientVersion", "Filecoin.Web3ClientVersion")
|
||||||
}
|
}
|
||||||
|
@ -4096,3 +4096,18 @@ func (mr *MockFullNodeMockRecorder) WalletVerify(arg0, arg1, arg2, arg3 interfac
|
|||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletVerify", reflect.TypeOf((*MockFullNode)(nil).WalletVerify), arg0, arg1, arg2, arg3)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WalletVerify", reflect.TypeOf((*MockFullNode)(nil).WalletVerify), arg0, arg1, arg2, arg3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Web3ClientVersion mocks base method.
|
||||||
|
func (m *MockFullNode) Web3ClientVersion(arg0 context.Context) (string, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Web3ClientVersion", arg0)
|
||||||
|
ret0, _ := ret[0].(string)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Web3ClientVersion indicates an expected call of Web3ClientVersion.
|
||||||
|
func (mr *MockFullNodeMockRecorder) Web3ClientVersion(arg0 interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Web3ClientVersion", reflect.TypeOf((*MockFullNode)(nil).Web3ClientVersion), arg0)
|
||||||
|
}
|
||||||
|
@ -582,6 +582,8 @@ type FullNodeStruct struct {
|
|||||||
WalletValidateAddress func(p0 context.Context, p1 string) (address.Address, error) `perm:"read"`
|
WalletValidateAddress func(p0 context.Context, p1 string) (address.Address, error) `perm:"read"`
|
||||||
|
|
||||||
WalletVerify func(p0 context.Context, p1 address.Address, p2 []byte, p3 *crypto.Signature) (bool, error) `perm:"read"`
|
WalletVerify func(p0 context.Context, p1 address.Address, p2 []byte, p3 *crypto.Signature) (bool, error) `perm:"read"`
|
||||||
|
|
||||||
|
Web3ClientVersion func(p0 context.Context) (string, error) `perm:"read"`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3936,6 +3938,17 @@ func (s *FullNodeStub) WalletVerify(p0 context.Context, p1 address.Address, p2 [
|
|||||||
return false, ErrNotSupported
|
return false, ErrNotSupported
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *FullNodeStruct) Web3ClientVersion(p0 context.Context) (string, error) {
|
||||||
|
if s.Internal.Web3ClientVersion == nil {
|
||||||
|
return "", ErrNotSupported
|
||||||
|
}
|
||||||
|
return s.Internal.Web3ClientVersion(p0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *FullNodeStub) Web3ClientVersion(p0 context.Context) (string, error) {
|
||||||
|
return "", ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
func (s *GatewayStruct) ChainGetBlockMessages(p0 context.Context, p1 cid.Cid) (*BlockMessages, error) {
|
func (s *GatewayStruct) ChainGetBlockMessages(p0 context.Context, p1 cid.Cid) (*BlockMessages, error) {
|
||||||
if s.Internal.ChainGetBlockMessages == nil {
|
if s.Internal.ChainGetBlockMessages == nil {
|
||||||
return nil, ErrNotSupported
|
return nil, ErrNotSupported
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -12,13 +12,11 @@ import (
|
|||||||
|
|
||||||
"github.com/filecoin-project/go-address"
|
"github.com/filecoin-project/go-address"
|
||||||
gocrypto "github.com/filecoin-project/go-crypto"
|
gocrypto "github.com/filecoin-project/go-crypto"
|
||||||
"github.com/filecoin-project/go-state-types/abi"
|
|
||||||
"github.com/filecoin-project/go-state-types/big"
|
"github.com/filecoin-project/go-state-types/big"
|
||||||
builtintypes "github.com/filecoin-project/go-state-types/builtin"
|
builtintypes "github.com/filecoin-project/go-state-types/builtin"
|
||||||
typescrypto "github.com/filecoin-project/go-state-types/crypto"
|
typescrypto "github.com/filecoin-project/go-state-types/crypto"
|
||||||
|
|
||||||
"github.com/filecoin-project/lotus/build"
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -63,6 +61,7 @@ func EthTxArgsFromMessage(msg *types.Message) (EthTxArgs, error) {
|
|||||||
to *EthAddress
|
to *EthAddress
|
||||||
params []byte
|
params []byte
|
||||||
paramsReader = bytes.NewReader(msg.Params)
|
paramsReader = bytes.NewReader(msg.Params)
|
||||||
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
if msg.Version != 0 {
|
if msg.Version != 0 {
|
||||||
@ -72,11 +71,10 @@ func EthTxArgsFromMessage(msg *types.Message) (EthTxArgs, error) {
|
|||||||
if msg.To == builtintypes.EthereumAddressManagerActorAddr {
|
if msg.To == builtintypes.EthereumAddressManagerActorAddr {
|
||||||
switch msg.Method {
|
switch msg.Method {
|
||||||
case builtintypes.MethodsEAM.CreateExternal:
|
case builtintypes.MethodsEAM.CreateExternal:
|
||||||
var create abi.CborBytes
|
params, err = cbg.ReadByteArray(paramsReader, uint64(len(msg.Params)))
|
||||||
if err := create.UnmarshalCBOR(paramsReader); err != nil {
|
if err != nil {
|
||||||
return EthTxArgs{}, err
|
return EthTxArgs{}, xerrors.Errorf("failed to read params byte array: %w", err)
|
||||||
}
|
}
|
||||||
params = create
|
|
||||||
default:
|
default:
|
||||||
return EthTxArgs{}, fmt.Errorf("unsupported EAM method")
|
return EthTxArgs{}, fmt.Errorf("unsupported EAM method")
|
||||||
}
|
}
|
||||||
@ -103,11 +101,6 @@ func EthTxArgsFromMessage(msg *types.Message) (EthTxArgs, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return EthTxArgs{}, xerrors.Errorf("failed to read params byte array: %w", err)
|
return EthTxArgs{}, xerrors.Errorf("failed to read params byte array: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(params) == 0 {
|
|
||||||
// Otherwise, we don't get a guaranteed round-trip.
|
|
||||||
return EthTxArgs{}, xerrors.Errorf("cannot invoke contracts with empty parameters from an eth-account")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +108,11 @@ func EthTxArgsFromMessage(msg *types.Message) (EthTxArgs, error) {
|
|||||||
return EthTxArgs{}, xerrors.Errorf("extra data found in params")
|
return EthTxArgs{}, xerrors.Errorf("extra data found in params")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(params) == 0 && msg.Method != builtintypes.MethodSend {
|
||||||
|
// Otherwise, we don't get a guaranteed round-trip.
|
||||||
|
return EthTxArgs{}, xerrors.Errorf("msgs with empty parameters from an eth-account must be Sends (MethodNum: %d)", msg.Method)
|
||||||
|
}
|
||||||
|
|
||||||
return EthTxArgs{
|
return EthTxArgs{
|
||||||
ChainID: build.Eip155ChainId,
|
ChainID: build.Eip155ChainId,
|
||||||
Nonce: int(msg.Nonce),
|
Nonce: int(msg.Nonce),
|
||||||
@ -143,12 +141,13 @@ func (tx *EthTxArgs) ToUnsignedMessage(from address.Address) (*types.Message, er
|
|||||||
if len(tx.Input) == 0 {
|
if len(tx.Input) == 0 {
|
||||||
return nil, xerrors.New("cannot call CreateExternal without params")
|
return nil, xerrors.New("cannot call CreateExternal without params")
|
||||||
}
|
}
|
||||||
inputParams := abi.CborBytes(tx.Input)
|
|
||||||
params, err = actors.SerializeParams(&inputParams)
|
buf := new(bytes.Buffer)
|
||||||
if err != nil {
|
if err = cbg.WriteByteArray(buf, tx.Input); err != nil {
|
||||||
return nil, fmt.Errorf("failed to serialize Create params: %w", err)
|
return nil, xerrors.Errorf("failed to serialize Create params: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
params = buf.Bytes()
|
||||||
} else {
|
} else {
|
||||||
to, err = tx.To.ToFilecoinAddress()
|
to, err = tx.To.ToFilecoinAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -291,7 +291,10 @@ func DumpActorState(i *ActorRegistry, act *types.Actor, b []byte) (interface{},
|
|||||||
|
|
||||||
um := actInfo.vmActor.State()
|
um := actInfo.vmActor.State()
|
||||||
if um == nil {
|
if um == nil {
|
||||||
// TODO::FVM @arajasek I would like to assert that we have the empty object here
|
if act.Code != EmptyObjectCid {
|
||||||
|
return nil, xerrors.Errorf("actor with code %s should only have empty object (%s) as its Head, instead has %s", act.Code, EmptyObjectCid, act.Head)
|
||||||
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if err := um.UnmarshalCBOR(bytes.NewReader(b)); err != nil {
|
if err := um.UnmarshalCBOR(bytes.NewReader(b)); err != nil {
|
||||||
|
@ -1517,6 +1517,8 @@ func GetAsks(ctx context.Context, api lapi.FullNode) ([]QueriedAsk, error) {
|
|||||||
}
|
}
|
||||||
}(miner)
|
}(miner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
@ -1590,6 +1592,8 @@ loop:
|
|||||||
lk.Unlock()
|
lk.Unlock()
|
||||||
}(miner)
|
}(miner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
loop2:
|
loop2:
|
||||||
|
@ -291,6 +291,8 @@
|
|||||||
* [WalletSignMessage](#WalletSignMessage)
|
* [WalletSignMessage](#WalletSignMessage)
|
||||||
* [WalletValidateAddress](#WalletValidateAddress)
|
* [WalletValidateAddress](#WalletValidateAddress)
|
||||||
* [WalletVerify](#WalletVerify)
|
* [WalletVerify](#WalletVerify)
|
||||||
|
* [Web3](#Web3)
|
||||||
|
* [Web3ClientVersion](#Web3ClientVersion)
|
||||||
##
|
##
|
||||||
|
|
||||||
|
|
||||||
@ -9207,3 +9209,16 @@ Inputs:
|
|||||||
|
|
||||||
Response: `true`
|
Response: `true`
|
||||||
|
|
||||||
|
## Web3
|
||||||
|
|
||||||
|
|
||||||
|
### Web3ClientVersion
|
||||||
|
Returns the client version
|
||||||
|
|
||||||
|
|
||||||
|
Perms: read
|
||||||
|
|
||||||
|
Inputs: `null`
|
||||||
|
|
||||||
|
Response: `"string value"`
|
||||||
|
|
||||||
|
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/abi"
|
||||||
"github.com/filecoin-project/go-state-types/big"
|
"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/go-state-types/exitcode"
|
||||||
|
|
||||||
"github.com/filecoin-project/lotus/api"
|
"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.Error(t, err, "expected gas estimation to fail")
|
||||||
require.Contains(t, err.Error(), "SysErrSenderInvalid")
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -10,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/filecoin-project/go-address"
|
"github.com/filecoin-project/go-address"
|
||||||
builtintypes "github.com/filecoin-project/go-state-types/builtin"
|
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/go-state-types/manifest"
|
||||||
|
|
||||||
"github.com/filecoin-project/lotus/chain/types"
|
"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) {
|
func setupFEVMTest(t *testing.T) (context.Context, context.CancelFunc, *kit.TestFullNode) {
|
||||||
kit.QuietMiningLogs()
|
kit.QuietMiningLogs()
|
||||||
blockTime := 100 * time.Millisecond
|
blockTime := 5 * time.Millisecond
|
||||||
client, _, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.ThroughRPC())
|
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)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
|
||||||
return ctx, cancel, client
|
return ctx, cancel, client
|
||||||
}
|
}
|
||||||
@ -139,3 +141,78 @@ func TestEVMRpcDisable(t *testing.T) {
|
|||||||
_, err := client.EthBlockNumber(context.Background())
|
_, err := client.EthBlockNumber(context.Background())
|
||||||
require.ErrorContains(t, err, "module disabled, enable with Fevm.EnableEthRPC")
|
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, ex exitcode.ExitCode) func(t *testing.T) {
|
||||||
|
return func(t *testing.T) {
|
||||||
|
inputData := make([]byte, 32)
|
||||||
|
binary.BigEndian.PutUint64(inputData[24:], uint64(n))
|
||||||
|
|
||||||
|
client.EVM().InvokeContractByFuncNameExpectExit(ctx, fromAddr, actorAddr, "exec1(uint256)", inputData, ex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("n=0", testN(0, exitcode.Ok))
|
||||||
|
t.Run("n=1", testN(1, exitcode.Ok))
|
||||||
|
t.Run("n=20", testN(20, exitcode.Ok))
|
||||||
|
t.Run("n=200", testN(200, exitcode.Ok))
|
||||||
|
t.Run("n=507", testN(507, exitcode.Ok))
|
||||||
|
t.Run("n=508", testN(508, exitcode.ExitCode(23))) // 23 means stack overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"
|
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/builtin/v10/eam"
|
||||||
"github.com/filecoin-project/go-state-types/crypto"
|
"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/api"
|
||||||
|
"github.com/filecoin-project/lotus/build"
|
||||||
"github.com/filecoin-project/lotus/chain/actors"
|
"github.com/filecoin-project/lotus/chain/actors"
|
||||||
"github.com/filecoin-project/lotus/chain/types"
|
"github.com/filecoin-project/lotus/chain/types"
|
||||||
"github.com/filecoin-project/lotus/chain/types/ethtypes"
|
"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()
|
params = buffer.Bytes()
|
||||||
|
|
||||||
msg := &types.Message{
|
msg := &types.Message{
|
||||||
To: target,
|
To: target,
|
||||||
From: sender,
|
From: sender,
|
||||||
Value: big.Zero(),
|
Value: big.Zero(),
|
||||||
Method: builtintypes.MethodsEVM.InvokeContract,
|
Method: builtintypes.MethodsEVM.InvokeContract,
|
||||||
Params: params,
|
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")
|
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 {
|
func (e *EVM) InvokeContractByFuncName(ctx context.Context, fromAddr address.Address, idAddr address.Address, funcSignature string, inputData []byte) []byte {
|
||||||
entryPoint := CalcFuncSignature(funcSignature)
|
entryPoint := CalcFuncSignature(funcSignature)
|
||||||
wait := e.InvokeSolidity(ctx, fromAddr, idAddr, entryPoint, inputData)
|
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)))
|
result, err := cbg.ReadByteArray(bytes.NewBuffer(wait.Receipt.Return), uint64(len(wait.Receipt.Return)))
|
||||||
require.NoError(e.t, err)
|
require.NoError(e.t, err)
|
||||||
return result
|
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
|
// function signatures are the first 4 bytes of the hash of the function name and types
|
||||||
func CalcFuncSignature(funcName string) []byte {
|
func CalcFuncSignature(funcName string) []byte {
|
||||||
hasher := sha3.NewLegacyKeccak256()
|
hasher := sha3.NewLegacyKeccak256()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package kit
|
package kit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
@ -10,9 +11,11 @@ import (
|
|||||||
"github.com/libp2p/go-libp2p/core/peer"
|
"github.com/libp2p/go-libp2p/core/peer"
|
||||||
"github.com/multiformats/go-multiaddr"
|
"github.com/multiformats/go-multiaddr"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
cbg "github.com/whyrusleeping/cbor-gen"
|
||||||
|
|
||||||
"github.com/filecoin-project/go-address"
|
"github.com/filecoin-project/go-address"
|
||||||
"github.com/filecoin-project/go-state-types/abi"
|
"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"
|
||||||
"github.com/filecoin-project/lotus/api/v1api"
|
"github.com/filecoin-project/lotus/api/v1api"
|
||||||
@ -124,6 +127,50 @@ func (f *TestFullNode) AssignPrivKey(pkey *Libp2p) {
|
|||||||
f.Pkey = pkey
|
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.
|
// ChainPredicate encapsulates a chain condition.
|
||||||
type ChainPredicate func(set *types.TipSet) bool
|
type ChainPredicate func(set *types.TipSet) bool
|
||||||
|
|
||||||
|
@ -118,4 +118,8 @@ func (e *EthModuleDummy) EthSendRawTransaction(ctx context.Context, rawTx ethtyp
|
|||||||
return ethtypes.EthHash{}, ErrModuleDisabled
|
return ethtypes.EthHash{}, ErrModuleDisabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *EthModuleDummy) Web3ClientVersion(ctx context.Context) (string, error) {
|
||||||
|
return "", ErrModuleDisabled
|
||||||
|
}
|
||||||
|
|
||||||
var _ EthModuleAPI = &EthModuleDummy{}
|
var _ EthModuleAPI = &EthModuleDummy{}
|
||||||
|
@ -64,6 +64,7 @@ type EthModuleAPI interface {
|
|||||||
EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam string) (ethtypes.EthBytes, error)
|
EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam string) (ethtypes.EthBytes, error)
|
||||||
EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error)
|
EthMaxPriorityFeePerGas(ctx context.Context) (ethtypes.EthBigInt, error)
|
||||||
EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error)
|
EthSendRawTransaction(ctx context.Context, rawTx ethtypes.EthBytes) (ethtypes.EthHash, error)
|
||||||
|
Web3ClientVersion(ctx context.Context) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type EthEventAPI interface {
|
type EthEventAPI interface {
|
||||||
@ -500,18 +501,8 @@ func (a *EthModule) EthGetStorageAt(ctx context.Context, ethAddr ethtypes.EthAdd
|
|||||||
return nil, fmt.Errorf("failed to construct system sender address: %w", err)
|
return nil, fmt.Errorf("failed to construct system sender address: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO super duper hack (raulk). The EVM runtime actor uses the U256 parameter type in
|
|
||||||
// GetStorageAtParams, which serializes as a hex-encoded string. It should serialize
|
|
||||||
// as bytes. We didn't get to fix in time for Iron, so for now we just pass
|
|
||||||
// through the hex-encoded value passed through the Eth JSON-RPC API, by remarshalling it.
|
|
||||||
// We don't fix this at origin (builtin-actors) because we are not updating the bundle
|
|
||||||
// for Iron.
|
|
||||||
tmp, err := position.MarshalJSON()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
params, err := actors.SerializeParams(&evm.GetStorageAtParams{
|
params, err := actors.SerializeParams(&evm.GetStorageAtParams{
|
||||||
StorageKey: tmp[1 : len(tmp)-1], // TODO strip the JSON-encoding quotes -- yuck
|
StorageKey: position,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to serialize parameters: %w", err)
|
return nil, fmt.Errorf("failed to serialize parameters: %w", err)
|
||||||
@ -714,6 +705,10 @@ func (a *EthModule) EthSendRawTransaction(ctx context.Context, rawTx ethtypes.Et
|
|||||||
return ethtypes.EthHashFromTxBytes(rawTx), nil
|
return ethtypes.EthHashFromTxBytes(rawTx), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *EthModule) Web3ClientVersion(ctx context.Context) (string, error) {
|
||||||
|
return build.UserVersion(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *EthModule) ethCallToFilecoinMessage(ctx context.Context, tx ethtypes.EthCall) (*types.Message, error) {
|
func (a *EthModule) ethCallToFilecoinMessage(ctx context.Context, tx ethtypes.EthCall) (*types.Message, error) {
|
||||||
var from address.Address
|
var from address.Address
|
||||||
if tx.From == nil || *tx.From == (ethtypes.EthAddress{}) {
|
if tx.From == nil || *tx.From == (ethtypes.EthAddress{}) {
|
||||||
@ -827,8 +822,9 @@ func (a *EthModule) EthEstimateGas(ctx context.Context, tx ethtypes.EthCall) (et
|
|||||||
func (a *EthModule) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam string) (ethtypes.EthBytes, error) {
|
func (a *EthModule) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam string) (ethtypes.EthBytes, error) {
|
||||||
msg, err := a.ethCallToFilecoinMessage(ctx, tx)
|
msg, err := a.ethCallToFilecoinMessage(ctx, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Errorf("failed to convert ethcall to filecoin message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ts, err := a.parseBlkParam(ctx, blkParam)
|
ts, err := a.parseBlkParam(ctx, blkParam)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, xerrors.Errorf("cannot parse block param: %s", blkParam)
|
return nil, xerrors.Errorf("cannot parse block param: %s", blkParam)
|
||||||
@ -836,11 +832,17 @@ func (a *EthModule) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam s
|
|||||||
|
|
||||||
invokeResult, err := a.applyMessage(ctx, msg, ts.Key())
|
invokeResult, err := a.applyMessage(ctx, msg, ts.Key())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, xerrors.Errorf("failed to apply message: %w", err)
|
||||||
}
|
}
|
||||||
if len(invokeResult.MsgRct.Return) > 0 {
|
|
||||||
|
if msg.To == builtintypes.EthereumAddressManagerActorAddr {
|
||||||
|
// As far as I can tell, the Eth API always returns empty on contract deployment
|
||||||
|
return ethtypes.EthBytes{}, nil
|
||||||
|
|
||||||
|
} else if len(invokeResult.MsgRct.Return) > 0 {
|
||||||
return cbg.ReadByteArray(bytes.NewReader(invokeResult.MsgRct.Return), uint64(len(invokeResult.MsgRct.Return)))
|
return cbg.ReadByteArray(bytes.NewReader(invokeResult.MsgRct.Return), uint64(len(invokeResult.MsgRct.Return)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return ethtypes.EthBytes{}, nil
|
return ethtypes.EthBytes{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user