Skip to content

Commit

Permalink
feat: add ibc call evm (#241)
Browse files Browse the repository at this point in the history
Co-authored-by: nulnut <151493716+nulnut@users.noreply.github.com>
Co-authored-by: fx0x55 <80245546+fx0x55@users.noreply.github.com>
  • Loading branch information
3 people committed Feb 29, 2024
1 parent bf2703b commit 00b456e
Show file tree
Hide file tree
Showing 14 changed files with 572 additions and 25 deletions.
6 changes: 3 additions & 3 deletions app/encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ func TestMakeEncodingConfig_RegisterInterfaces(t *testing.T) {
for interfaceNames.Next() {
count1++
}
assert.Equal(t, 32, count1)
assert.Equal(t, 33, count1)

interfaceImpls := interfaceRegistry.Field(1).MapRange()
var count2 int
for interfaceImpls.Next() {
count2++
}
assert.Equal(t, 32, count2)
assert.Equal(t, 33, count2)

typeURLMap := interfaceRegistry.Field(2).MapRange()
var count3 int
for typeURLMap.Next() {
count3++
}
assert.Equal(t, 264, count3)
assert.Equal(t, 265, count3)

govContent := encodingConfig.InterfaceRegistry.ListImplementations("cosmos.gov.v1beta1.Content")
assert.Equal(t, 14, len(govContent))
Expand Down
1 change: 1 addition & 0 deletions app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ func NewAppKeeper(
appKeepers.FxTransferKeeper = appKeepers.FxTransferKeeper.SetRouter(*ibcTransferRouter)
appKeepers.FxTransferKeeper = appKeepers.FxTransferKeeper.SetRefundHook(appKeepers.Erc20Keeper)
appKeepers.FxTransferKeeper = appKeepers.FxTransferKeeper.SetErc20Keeper(appKeepers.Erc20Keeper)
appKeepers.FxTransferKeeper = appKeepers.FxTransferKeeper.SetEvmKeeper(appKeepers.EvmKeeper)

ibcTransferModule := ibctransfer.NewIBCModule(appKeepers.IBCTransferKeeper)
transferIBCModule := fxtransfer.NewIBCMiddleware(appKeepers.FxTransferKeeper, ibcTransferModule)
Expand Down
19 changes: 19 additions & 0 deletions proto/fx/ibc/applications/transfer/v1/transfer.proto
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
syntax = "proto3";
package fx.ibc.applications.transfer.v1;

import "gogoproto/gogo.proto";

option go_package = "github.com/functionx/fx-core/x/ibc/applications/transfer/types";

// FungibleTokenPacketData defines a struct for the packet payload
Expand All @@ -22,3 +24,20 @@ message FungibleTokenPacketData {
// optional memo
string memo = 7;
}

enum IbcCallType {
option (gogoproto.goproto_enum_prefix) = false;

IBC_CALL_TYPE_UNSPECIFIED = 0;
IBC_CALL_TYPE_EVM = 1;
}

message IbcCallEvmPacket {
string to = 1;
string value = 2 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
uint64 gas_limit = 3;
string message = 4;
}
43 changes: 43 additions & 0 deletions x/ibc/applications/transfer/keeper/ibc_call.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package keeper

import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
evmtypes "github.com/evmos/ethermint/x/evm/types"

"github.com/functionx/fx-core/v7/x/ibc/applications/transfer/types"
)

func (k Keeper) HandlerIbcCall(ctx sdk.Context, sourcePort, sourceChannel string, data types.FungibleTokenPacketData) error {
var mp types.MemoPacket
if err := k.cdc.UnmarshalInterfaceJSON([]byte(data.Memo), &mp); err != nil {
return nil
}

if err := mp.ValidateBasic(); err != nil {
return err
}

attr := sdk.NewAttribute(types.AttributeKeyIBCCallType, types.IbcCallType_name[int32(mp.GetType())])
ctx.EventManager().EmitEvents(sdk.Events{sdk.NewEvent(types.EventTypeIBCCall, attr)})
switch packet := mp.(type) {
case *types.IbcCallEvmPacket:
hexSender := types.IntermediateSender(sourcePort, sourceChannel, data.Sender)
return k.HandlerIbcCallEvm(ctx, hexSender, packet)
default:
return types.ErrMemoNotSupport.Wrapf("invalid call type %s", mp.GetType())
}
}

func (k Keeper) HandlerIbcCallEvm(ctx sdk.Context, sender common.Address, evmData *types.IbcCallEvmPacket) error {
txResp, err := k.evmKeeper.CallEVM(ctx, sender,
evmData.MustGetToAddr(), evmData.Value.BigInt(), evmData.GasLimit, evmData.MustGetMessage(), true)
if err != nil {
return err
}
if txResp.Failed() {
return errorsmod.Wrap(evmtypes.ErrVMExecution, txResp.VmError)
}
return nil
}
10 changes: 8 additions & 2 deletions x/ibc/applications/transfer/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
type Keeper struct {
ibctransferkeeper.Keeper
storeKey storetypes.StoreKey
cdc codec.BinaryCodec
cdc codec.Codec
paramSpace paramtypes.Subspace

ics4Wrapper porttypes.ICS4Wrapper
Expand All @@ -30,13 +30,14 @@ type Keeper struct {
bankKeeper transfertypes.BankKeeper
scopedKeeper capabilitykeeper.ScopedKeeper
erc20Keeper types.Erc20Keeper
evmKeeper types.EvmKeeper
router *fxtypes.Router
refundHook types.RefundHook
}

// NewKeeper creates a new IBC transfer Keeper instance
func NewKeeper(keeper ibctransferkeeper.Keeper,
cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace,
cdc codec.Codec, key storetypes.StoreKey, paramSpace paramtypes.Subspace,
ics4Wrapper porttypes.ICS4Wrapper, channelKeeper transfertypes.ChannelKeeper, portKeeper transfertypes.PortKeeper,
authKeeper transfertypes.AccountKeeper, bankKeeper transfertypes.BankKeeper, scopedKeeper capabilitykeeper.ScopedKeeper,
) Keeper {
Expand Down Expand Up @@ -80,6 +81,11 @@ func (k Keeper) SetErc20Keeper(erc20Keeper types.Erc20Keeper) Keeper {
return k
}

func (k Keeper) SetEvmKeeper(evmKeeper types.EvmKeeper) Keeper {
k.evmKeeper = evmKeeper
return k
}

// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", "x/"+host.ModuleName+"-"+types.CompatibleModuleName)
Expand Down
8 changes: 8 additions & 0 deletions x/ibc/applications/transfer/keeper/relay.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,16 @@ func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, data t
return err
}
}

// NOTE: if not router, emit onRecvPacketCtx event, only error is nil emit
ctx.EventManager().EmitEvents(onRecvPacketCtxWithNewEvent.EventManager().Events())

// ibc call
if len(data.Memo) > 0 {
if err = k.HandlerIbcCall(ctx, packet.SourcePort, packet.SourceChannel, data); err != nil {
return err
}
}
return nil
}
route, exists := k.router.GetRoute(data.Router)
Expand Down
43 changes: 43 additions & 0 deletions x/ibc/applications/transfer/keeper/relay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/ibc-go/v6/modules/apps/transfer"
transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types"
Expand Down Expand Up @@ -392,6 +393,48 @@ func (suite *KeeperTestSuite) TestOnRecvPacket() {
checkCoinAddr: senderAddr,
expCoins: sdk.NewCoins(sdk.NewCoin(ibcDenomTrace.IBCDenom(), sdkmath.ZeroInt())),
},
{
name: "pass - any memo",
malleate: func(packet *channeltypes.Packet) {
packetData := transfertypes.FungibleTokenPacketData{}
transfertypes.ModuleCdc.MustUnmarshalJSON(packet.GetData(), &packetData)
packetData.Memo = "0000"
packet.Data = packetData.GetBytes()
},
expPass: true,
errorStr: "",
checkBalance: true,
checkCoinAddr: senderAddr,
expCoins: sdk.NewCoins(sdk.NewCoin(ibcDenomTrace.IBCDenom(), sdkmath.ZeroInt())),
},
{
name: "pass - ibc call evm",
malleate: func(packet *channeltypes.Packet) {
packetData := transfertypes.FungibleTokenPacketData{}
transfertypes.ModuleCdc.MustUnmarshalJSON(packet.GetData(), &packetData)

hexIbcSender := fxtransfertypes.IntermediateSender(ibctesting.TransferPort, "channel-0", senderAddr.String())
ibcCallBaseAcc := authtypes.NewBaseAccountWithAddress(hexIbcSender.Bytes())
suite.NoError(ibcCallBaseAcc.SetSequence(0))
suite.GetApp(suite.chainA.App).AccountKeeper.SetAccount(suite.chainA.GetContext(), ibcCallBaseAcc)
evmPacket := fxtransfertypes.IbcCallEvmPacket{
To: common.BigToAddress(big.NewInt(0)).String(),
Value: sdkmath.ZeroInt(),
GasLimit: 300000,
Message: "",
}
cdc := suite.GetApp(suite.chainA.App).AppCodec()
bz, err := cdc.MarshalInterfaceJSON(&evmPacket)
suite.Require().NoError(err)
packetData.Memo = string(bz)
packet.Data = packetData.GetBytes()
},
expPass: true,
errorStr: "",
checkBalance: true,
checkCoinAddr: senderAddr,
expCoins: sdk.NewCoins(sdk.NewCoin(ibcDenomTrace.IBCDenom(), sdkmath.ZeroInt())),
},
}

for _, tc := range testCases {
Expand Down
9 changes: 9 additions & 0 deletions x/ibc/applications/transfer/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,23 @@ import (
// RegisterLegacyAminoCodec registers the necessary x/ibc transfer interfaces and concrete types
// on the provided LegacyAmino codec. These types are used for Amino JSON serialization.
func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
cdc.RegisterInterface((*MemoPacket)(nil), nil)

cdc.RegisterConcrete(&MsgTransfer{}, fmt.Sprintf("%s/%s", CompatibleModuleName, "MsgTransfer"), nil)
cdc.RegisterConcrete(&IbcCallEvmPacket{}, fmt.Sprintf("%s/%s", CompatibleModuleName, "IbcCallEvmPacket"), nil)
}

// RegisterInterfaces register the ibc transfer module interfaces to protobuf
// Any.
func RegisterInterfaces(registry codectypes.InterfaceRegistry) {
registry.RegisterImplementations((*sdk.Msg)(nil), &MsgTransfer{})

registry.RegisterInterface(
"fx.ibc.applications.transfer.v1.MemoPacket",
(*MemoPacket)(nil),
&IbcCallEvmPacket{},
)

msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
}

Expand Down
2 changes: 2 additions & 0 deletions x/ibc/applications/transfer/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ import (
var (
ErrFeeDenomNotMatchTokenDenom = errorsmod.Register(ModuleName, 100, "invalid fee denom, must match token denom")
ErrRouterNotFound = errorsmod.Register(ModuleName, 103, "router not found")
ErrMemoNotSupport = errorsmod.Register(ModuleName, 104, "memo not support")
ErrInvalidEvmData = errorsmod.Register(ModuleName, 105, "invalid evm data")
)
3 changes: 3 additions & 0 deletions x/ibc/applications/transfer/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ const (
AttributeKeyRouteSuccess = "success"
AttributeKeyRoute = "route"
AttributeKeyRouteError = "error"

EventTypeIBCCall = "ibc_call"
AttributeKeyIBCCallType = "ibc_call_type"
)
7 changes: 7 additions & 0 deletions x/ibc/applications/transfer/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ package types

import (
"context"
"math/big"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
evmtypes "github.com/evmos/ethermint/x/evm/types"

fxtypes "github.com/functionx/fx-core/v7/types"
erc20types "github.com/functionx/fx-core/v7/x/erc20/types"
Expand All @@ -18,3 +21,7 @@ type Erc20Keeper interface {
ConvertDenomToTarget(ctx sdk.Context, from sdk.AccAddress, coin sdk.Coin, fxTarget fxtypes.FxTarget) (sdk.Coin, error)
ConvertCoin(goCtx context.Context, msg *erc20types.MsgConvertCoin) (*erc20types.MsgConvertCoinResponse, error)
}

type EvmKeeper interface {
CallEVM(ctx sdk.Context, from common.Address, contract *common.Address, value *big.Int, gasLimit uint64, data []byte, commit bool) (*evmtypes.MsgEthereumTxResponse, error)
}
13 changes: 13 additions & 0 deletions x/ibc/applications/transfer/types/keys.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package types

import (
"fmt"

"github.com/cosmos/cosmos-sdk/types/address"
"github.com/ethereum/go-ethereum/common"
)

const (
// ModuleName defines the IBC transfer name
ModuleName = "transfer"
Expand All @@ -10,3 +17,9 @@ const (
// RouterKey is the message route for IBC transfer
RouterKey = CompatibleModuleName
)

func IntermediateSender(sourcePort, sourceChannel, sender string) common.Address {
prefix := fmt.Sprintf("%s/%s", sourcePort, sourceChannel)
senderHash32 := address.Hash(prefix, []byte(sender))
return common.BytesToAddress(senderHash32)
}
54 changes: 53 additions & 1 deletion x/ibc/applications/transfer/types/packet.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

import (
"encoding/hex"
"strings"
"time"

Expand All @@ -9,8 +10,20 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types"
"github.com/ethereum/go-ethereum/common"
"github.com/gogo/protobuf/proto"

fxtypes "github.com/functionx/fx-core/v7/types"
)

type MemoPacket interface {
proto.Message
GetType() IbcCallType
ValidateBasic() error
}

var _ MemoPacket = &IbcCallEvmPacket{}

// DefaultRelativePacketTimeoutTimestamp is the default packet timeout timestamp (in nanoseconds)
// relative to the current block timestamp of the counterparty chain provided by the client
// state. The timeout is disabled when set to 0. The default is currently set to a 12-hour
Expand Down Expand Up @@ -54,7 +67,10 @@ func (ftpd FungibleTokenPacketData) ValidateBasic() error {
if fee.IsNegative() {
return errorsmod.Wrapf(transfertypes.ErrInvalidAmount, "fee must be strictly not negative: got %d", fee)
}
return transfertypes.ValidatePrefixedDenom(ftpd.Denom)
if err := transfertypes.ValidatePrefixedDenom(ftpd.Denom); err != nil {
return err
}
return nil
}

// GetBytes is a helper for serialising
Expand All @@ -71,3 +87,39 @@ func (ftpd FungibleTokenPacketData) ToIBCPacketData() transfertypes.FungibleToke
result.Memo = ftpd.Memo
return result
}

func (icep IbcCallEvmPacket) GetType() IbcCallType {
return IBC_CALL_TYPE_EVM
}

func (icep IbcCallEvmPacket) ValidateBasic() error {
if err := fxtypes.ValidateEthereumAddress(icep.To); err != nil {
return ErrInvalidEvmData.Wrap("to")
}
if icep.Value.IsNegative() {
return ErrInvalidEvmData.Wrap("value")
}

// gas limit

if _, err := hex.DecodeString(icep.Message); err != nil {
return ErrInvalidEvmData.Wrap("message")
}
return nil
}

func (icep IbcCallEvmPacket) MustGetToAddr() *common.Address {
if err := fxtypes.ValidateEthereumAddress(icep.To); err != nil {
panic(err)
}
to := common.HexToAddress(icep.To)
return &to
}

func (icep IbcCallEvmPacket) MustGetMessage() []byte {
bz, err := hex.DecodeString(icep.Message)
if err != nil {
panic(err)
}
return bz
}
Loading

0 comments on commit 00b456e

Please sign in to comment.