From 5e9a27163882a9325e903c20024ed27ed92cb3d7 Mon Sep 17 00:00:00 2001 From: zakir <80246097+zakir-code@users.noreply.github.com> Date: Tue, 25 Jun 2024 11:50:32 +0800 Subject: [PATCH] refactor: crosschain precmpile contract --- contract/precompile.go | 13 + tests/precompile_suite.go | 5 +- x/crosschain/legacy/precompile_events.go | 66 ++++ x/crosschain/precompile/bridge_call.go | 82 ++++- x/crosschain/precompile/bridge_call_test.go | 39 +- x/crosschain/precompile/bridge_coin_amount.go | 103 ++++-- .../precompile/bridge_coin_amount_test.go | 97 +++++ .../precompile/cancel_send_to_external.go | 111 ++++++ .../cancel_send_to_external_test.go} | 21 +- .../precompile/cancel_sendtoexternal.go | 52 --- x/crosschain/precompile/contract.go | 116 +++--- .../precompile}/contract_test.go | 3 +- x/crosschain/precompile/crosschain.go | 343 +++--------------- .../precompile}/crosschain_test.go | 25 +- x/crosschain/precompile/fip20_crosschain.go | 114 ++++++ .../precompile}/fip20crosschain_test.go | 22 +- .../precompile/increase_bridge_fee.go | 130 +++++++ x/crosschain/precompile/increase_bridgefee.go | 78 ---- .../precompile}/increase_bridgefee_test.go | 26 +- x/crosschain/precompile/keeper.go | 202 +++++++++++ .../types/{methods.go => contract.go} | 63 ---- x/crosschain/types/events.go | 8 - x/evm/precompiles/tests/bridge_call_test.go | 18 - .../tests/bridge_coin_amount_test.go | 114 ------ 24 files changed, 1047 insertions(+), 804 deletions(-) create mode 100644 contract/precompile.go create mode 100644 x/crosschain/legacy/precompile_events.go create mode 100644 x/crosschain/precompile/bridge_coin_amount_test.go create mode 100644 x/crosschain/precompile/cancel_send_to_external.go rename x/{evm/precompiles/tests/cancel_sendtoexternal_test.go => crosschain/precompile/cancel_send_to_external_test.go} (96%) delete mode 100644 x/crosschain/precompile/cancel_sendtoexternal.go rename x/{evm/precompiles/tests => crosschain/precompile}/contract_test.go (99%) rename x/{evm/precompiles/tests => crosschain/precompile}/crosschain_test.go (99%) create mode 100644 x/crosschain/precompile/fip20_crosschain.go rename x/{evm/precompiles/tests => crosschain/precompile}/fip20crosschain_test.go (98%) create mode 100644 x/crosschain/precompile/increase_bridge_fee.go delete mode 100644 x/crosschain/precompile/increase_bridgefee.go rename x/{evm/precompiles/tests => crosschain/precompile}/increase_bridgefee_test.go (97%) create mode 100644 x/crosschain/precompile/keeper.go rename x/crosschain/types/{methods.go => contract.go} (61%) delete mode 100644 x/evm/precompiles/tests/bridge_call_test.go delete mode 100644 x/evm/precompiles/tests/bridge_coin_amount_test.go diff --git a/contract/precompile.go b/contract/precompile.go new file mode 100644 index 00000000..cdf19732 --- /dev/null +++ b/contract/precompile.go @@ -0,0 +1,13 @@ +package contract + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/core/vm" +) + +type PrecompileMethod interface { + GetMethodId() []byte + RequiredGas() uint64 + IsReadonly() bool + Run(ctx sdk.Context, evm *vm.EVM, contract *vm.Contract) ([]byte, error) +} diff --git a/tests/precompile_suite.go b/tests/precompile_suite.go index ad8aa335..ce96ecd3 100644 --- a/tests/precompile_suite.go +++ b/tests/precompile_suite.go @@ -14,6 +14,7 @@ import ( "github.com/functionx/fx-core/v7/contract" "github.com/functionx/fx-core/v7/testutil/helpers" fxtypes "github.com/functionx/fx-core/v7/types" + "github.com/functionx/fx-core/v7/x/crosschain/precompile" crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" ) @@ -134,7 +135,7 @@ func (suite *PrecompileTestSuite) CrossChain(token common.Address, recipient str func (suite *PrecompileTestSuite) CancelSendToExternal(chain string, txId uint64) *ethtypes.Transaction { privateKey := suite.privKey crossChainContract := crosschaintypes.GetAddress() - pack, err := crosschaintypes.GetABI().Pack(crosschaintypes.CancelSendToExternalMethodName, chain, big.NewInt(int64(txId))) + pack, err := precompile.NewCancelSendToExternalMethod(nil).PackInput(chain, big.NewInt(int64(txId))) suite.Require().NoError(err) ethTx, err := client.BuildEthTransaction(suite.ctx, suite.EthClient(), privateKey, &crossChainContract, nil, pack) suite.Require().NoError(err, chain) @@ -168,7 +169,7 @@ func (suite *PrecompileTestSuite) IncreaseBridgeFee(chain string, txId uint64, t privateKey := suite.privKey crossChainContract := crosschaintypes.GetAddress() suite.ApproveERC20(privateKey, token, crossChainContract, fee) - pack, err := crosschaintypes.GetABI().Pack(crosschaintypes.IncreaseBridgeFeeMethodName, chain, big.NewInt(int64(txId)), token, fee) + pack, err := precompile.NewIncreaseBridgeFeeMethod(nil).PackInput(chain, big.NewInt(int64(txId)), token, fee) suite.Require().NoError(err) ethTx, err := client.BuildEthTransaction(suite.ctx, suite.EthClient(), privateKey, &crossChainContract, nil, pack) suite.Require().NoError(err, chain) diff --git a/x/crosschain/legacy/precompile_events.go b/x/crosschain/legacy/precompile_events.go new file mode 100644 index 00000000..60f39856 --- /dev/null +++ b/x/crosschain/legacy/precompile_events.go @@ -0,0 +1,66 @@ +package legacy + +import ( + "math/big" + + "github.com/armon/go-metrics" + "github.com/cosmos/cosmos-sdk/telemetry" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" +) + +// Fip20CrossChainEvents use for fip20 cross chain +// Deprecated +func Fip20CrossChainEvents(ctx sdk.Context, from, token common.Address, recipient, target, denom string, amount, fee *big.Int) { + ctx.EventManager().EmitEvent(sdk.NewEvent( + EventTypeRelayTransferCrossChain, + sdk.NewAttribute(AttributeKeyFrom, from.String()), + sdk.NewAttribute(AttributeKeyRecipient, recipient), + sdk.NewAttribute(sdk.AttributeKeyAmount, amount.String()), + sdk.NewAttribute(sdk.AttributeKeyFee, fee.String()), + sdk.NewAttribute(AttributeKeyTarget, target), + sdk.NewAttribute(AttributeKeyTokenAddress, token.String()), + sdk.NewAttribute(AttributeKeyDenom, denom), + )) + + telemetry.IncrCounterWithLabels( + []string{"relay_transfer_cross_chain"}, + 1, + []metrics.Label{ + telemetry.NewLabel("erc20", token.String()), + telemetry.NewLabel("denom", denom), + telemetry.NewLabel("target", target), + }, + ) +} + +// CrossChainEvents +// Deprecated +func CrossChainEvents(ctx sdk.Context, from, token common.Address, recipient, target, denom, memo string, amount, fee *big.Int) { + ctx.EventManager().EmitEvent(sdk.NewEvent( + EventTypeCrossChain, + sdk.NewAttribute(AttributeKeyFrom, from.String()), + sdk.NewAttribute(AttributeKeyRecipient, recipient), + sdk.NewAttribute(sdk.AttributeKeyAmount, amount.String()), + sdk.NewAttribute(sdk.AttributeKeyFee, fee.String()), + sdk.NewAttribute(AttributeKeyTarget, target), + sdk.NewAttribute(AttributeKeyTokenAddress, token.String()), + sdk.NewAttribute(AttributeKeyDenom, denom), + sdk.NewAttribute(AttributeKeyMemo, memo), + )) +} + +const ( + // EventTypeRelayTransferCrossChain + // Deprecated + EventTypeRelayTransferCrossChain = "relay_transfer_cross_chain" + // EventTypeCrossChain new cross chain event type + EventTypeCrossChain = "cross_chain" + + AttributeKeyDenom = "coin" + AttributeKeyTokenAddress = "token_address" + AttributeKeyFrom = "from" + AttributeKeyRecipient = "recipient" + AttributeKeyTarget = "target" + AttributeKeyMemo = "memo" +) diff --git a/x/crosschain/precompile/bridge_call.go b/x/crosschain/precompile/bridge_call.go index 53295a96..32cbfe59 100644 --- a/x/crosschain/precompile/bridge_call.go +++ b/x/crosschain/precompile/bridge_call.go @@ -5,6 +5,7 @@ import ( "math/big" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" @@ -12,19 +13,43 @@ import ( evmtypes "github.com/functionx/fx-core/v7/x/evm/types" ) -func (c *Contract) BridgeCall(ctx sdk.Context, evm *vm.EVM, contract *vm.Contract, readonly bool) ([]byte, error) { - if readonly { - return nil, errors.New("bridge call method not readonly") +type BridgeCallMethod struct { + *Keeper + abi.Method + abi.Event +} + +func NewBridgeCallMethod(keeper *Keeper) *BridgeCallMethod { + return &BridgeCallMethod{ + Keeper: keeper, + Method: crosschaintypes.GetABI().Methods["bridgeCall"], + Event: crosschaintypes.GetABI().Events["BridgeCallEvent"], } - if c.router == nil { +} + +func (m *BridgeCallMethod) IsReadonly() bool { + return false +} + +func (m *BridgeCallMethod) GetMethodId() []byte { + return m.Method.ID +} + +func (m *BridgeCallMethod) RequiredGas() uint64 { + return 50_000 +} + +func (m *BridgeCallMethod) Run(ctx sdk.Context, evm *vm.EVM, contract *vm.Contract) ([]byte, error) { + if m.router == nil { return nil, errors.New("bridge call router is empty") } - var args crosschaintypes.BridgeCallArgs - if err := evmtypes.ParseMethodArgs(crosschaintypes.BridgeCallMethod, &args, contract.Input[4:]); err != nil { + args, err := m.UnpackInput(contract.Input) + if err != nil { return nil, err } - route, has := c.router.GetRoute(args.DstChain) + + route, has := m.router.GetRoute(args.DstChain) if !has { return nil, errors.New("invalid dstChain") } @@ -33,14 +58,14 @@ func (c *Contract) BridgeCall(ctx sdk.Context, evm *vm.EVM, contract *vm.Contrac coins := make([]sdk.Coin, 0, len(args.Tokens)+1) value := contract.Value() if value.Cmp(big.NewInt(0)) == 1 { - totalCoin, err := c.handlerOriginToken(ctx, evm, sender, value) + totalCoin, err := m.handlerOriginToken(ctx, evm, sender, value) if err != nil { return nil, err } coins = append(coins, totalCoin) } for i, token := range args.Tokens { - coin, err := c.handlerERC20Token(ctx, evm, sender, token, args.Amounts[i]) + coin, err := m.handlerERC20Token(ctx, evm, sender, token, args.Amounts[i]) if err != nil { return nil, err } @@ -60,9 +85,11 @@ func (c *Contract) BridgeCall(ctx sdk.Context, evm *vm.EVM, contract *vm.Contrac return nil, err } - nonceNonce := big.NewInt(0).SetUint64(nonce) - if err = c.AddLog(evm, crosschaintypes.BridgeCallEvent, - []common.Hash{sender.Hash(), args.Refund.Hash(), args.To.Hash()}, + nonceNonce := new(big.Int).SetUint64(nonce) + data, topic, err := m.NewBridgeCallEvent( + sender, + args.Refund, + args.To, evm.Origin, args.Value, nonceNonce, @@ -71,8 +98,35 @@ func (c *Contract) BridgeCall(ctx sdk.Context, evm *vm.EVM, contract *vm.Contrac args.Amounts, args.Data, args.Memo, - ); err != nil { + ) + if err != nil { + return nil, err + } + EmitEvent(evm, data, topic) + + return m.PackOutput(nonceNonce) +} + +func (m *BridgeCallMethod) NewBridgeCallEvent(sender, refund, to, origin common.Address, value, eventNonce *big.Int, dstChain string, tokens []common.Address, amounts []*big.Int, txData, memo []byte) (data []byte, topic []common.Hash, err error) { + data, topic, err = evmtypes.PackTopicData(m.Event, []common.Hash{sender.Hash(), refund.Hash(), to.Hash()}, origin, value, eventNonce, dstChain, tokens, amounts, txData, memo) + if err != nil { + return nil, nil, err + } + return data, topic, nil +} + +func (m *BridgeCallMethod) UnpackInput(data []byte) (*crosschaintypes.BridgeCallArgs, error) { + args := new(crosschaintypes.BridgeCallArgs) + if err := evmtypes.ParseMethodArgs(m.Method, args, data[4:]); err != nil { + return nil, err + } + return args, nil +} + +func (m *BridgeCallMethod) PackOutput(nonceNonce *big.Int) ([]byte, error) { + pack, err := m.Method.Outputs.Pack(nonceNonce) + if err != nil { return nil, err } - return crosschaintypes.BridgeCallMethod.Outputs.Pack(nonceNonce) + return pack, nil } diff --git a/x/crosschain/precompile/bridge_call_test.go b/x/crosschain/precompile/bridge_call_test.go index 6e288a5b..4508dd4f 100644 --- a/x/crosschain/precompile/bridge_call_test.go +++ b/x/crosschain/precompile/bridge_call_test.go @@ -6,18 +6,28 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/functionx/fx-core/v7/contract" "github.com/functionx/fx-core/v7/testutil/helpers" - "github.com/functionx/fx-core/v7/x/crosschain/types" + "github.com/functionx/fx-core/v7/x/crosschain/precompile" ) +func TestBridgeCallABI(t *testing.T) { + bridgeCall := precompile.NewBridgeCallMethod(nil) + + require.Equal(t, 8, len(bridgeCall.Method.Inputs)) + require.Equal(t, 1, len(bridgeCall.Method.Outputs)) +} + func TestContract_BridgeCall_Input(t *testing.T) { - assert.Equal(t, `bridgeCall(string,address,address[],uint256[],address,bytes,uint256,bytes)`, types.BridgeCallMethod.Sig) - assert.Equal(t, "payable", types.BridgeCallMethod.StateMutability) - assert.Equal(t, 8, len(types.BridgeCallMethod.Inputs)) + bridgeCall := precompile.NewBridgeCallMethod(nil) - inputs := types.BridgeCallMethod.Inputs + assert.Equal(t, `bridgeCall(string,address,address[],uint256[],address,bytes,uint256,bytes)`, bridgeCall.Method.Sig) + assert.Equal(t, "payable", bridgeCall.Method.StateMutability) + assert.Equal(t, 8, len(bridgeCall.Method.Inputs)) + + inputs := bridgeCall.Method.Inputs type Args struct { DstChain string Refund common.Address @@ -67,9 +77,10 @@ func TestContract_BridgeCall_Input(t *testing.T) { } func TestContract_BridgeCall_Output(t *testing.T) { - assert.Equal(t, 1, len(types.BridgeCallMethod.Outputs)) + bridgeCall := precompile.NewBridgeCallMethod(nil) + assert.Equal(t, 1, len(bridgeCall.Method.Outputs)) - outputs := types.BridgeCallMethod.Outputs + outputs := bridgeCall.Method.Outputs eventNonce := big.NewInt(1) outputData, err := outputs.Pack(eventNonce) assert.NoError(t, err) @@ -83,14 +94,16 @@ func TestContract_BridgeCall_Output(t *testing.T) { } func TestContract_BridgeCall_Event(t *testing.T) { - assert.Equal(t, `BridgeCallEvent(address,address,address,address,uint256,uint256,string,address[],uint256[],bytes,bytes)`, types.BridgeCallEvent.Sig) - assert.Equal(t, "0x4a9b24da6150ef33e7c41038842b7c94fe89a4fff22dccb2c3fd79f0176062c6", types.BridgeCallEvent.ID.String()) - assert.Equal(t, 11, len(types.BridgeCallEvent.Inputs)) - assert.Equal(t, 8, len(types.BridgeCallEvent.Inputs.NonIndexed())) + bridgeCall := precompile.NewBridgeCallMethod(nil) + + assert.Equal(t, `BridgeCallEvent(address,address,address,address,uint256,uint256,string,address[],uint256[],bytes,bytes)`, bridgeCall.Event.Sig) + assert.Equal(t, "0x4a9b24da6150ef33e7c41038842b7c94fe89a4fff22dccb2c3fd79f0176062c6", bridgeCall.Event.ID.String()) + assert.Equal(t, 11, len(bridgeCall.Event.Inputs)) + assert.Equal(t, 8, len(bridgeCall.Event.Inputs.NonIndexed())) for i := 0; i < 3; i++ { - assert.Equal(t, true, types.BridgeCallEvent.Inputs[i].Indexed) + assert.Equal(t, true, bridgeCall.Event.Inputs[i].Indexed) } - inputs := types.BridgeCallEvent.Inputs + inputs := bridgeCall.Event.Inputs args := contract.ICrossChainBridgeCallEvent{ TxOrigin: helpers.GenHexAddress(), diff --git a/x/crosschain/precompile/bridge_coin_amount.go b/x/crosschain/precompile/bridge_coin_amount.go index 6efabe69..37fbec18 100644 --- a/x/crosschain/precompile/bridge_coin_amount.go +++ b/x/crosschain/precompile/bridge_coin_amount.go @@ -2,67 +2,124 @@ package precompile import ( "fmt" + "math/big" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/core/vm" - "github.com/functionx/fx-core/v7/contract" + fxcontract "github.com/functionx/fx-core/v7/contract" fxtypes "github.com/functionx/fx-core/v7/types" crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" ethtypes "github.com/functionx/fx-core/v7/x/eth/types" evmtypes "github.com/functionx/fx-core/v7/x/evm/types" ) -func (c *Contract) BridgeCoinAmount(ctx sdk.Context, evm *vm.EVM, contractAddr *vm.Contract, _ bool) ([]byte, error) { - cacheCtx, _ := ctx.CacheContext() +type BridgeCoinAmountMethod struct { + *Keeper + abi.Method +} + +func NewBridgeCoinAmountMethod(keeper *Keeper) *BridgeCoinAmountMethod { + return &BridgeCoinAmountMethod{ + Keeper: keeper, + Method: crosschaintypes.GetABI().Methods["bridgeCoinAmount"], + } +} + +func (m *BridgeCoinAmountMethod) IsReadonly() bool { + return true +} + +func (m *BridgeCoinAmountMethod) GetMethodId() []byte { + return m.Method.ID +} + +func (m *BridgeCoinAmountMethod) RequiredGas() uint64 { + return 10_000 +} - var args crosschaintypes.BridgeCoinAmountArgs - if err := evmtypes.ParseMethodArgs(crosschaintypes.BridgeCoinAmountMethod, &args, contractAddr.Input[4:]); err != nil { +func (m *BridgeCoinAmountMethod) Run(ctx sdk.Context, evm *vm.EVM, contract *vm.Contract) ([]byte, error) { + args, err := m.UnpackInput(contract.Input) + if err != nil { return nil, err } - pair, has := c.erc20Keeper.GetTokenPair(cacheCtx, args.Token.Hex()) + + pair, has := m.erc20Keeper.GetTokenPair(ctx, args.Token.Hex()) if !has { return nil, fmt.Errorf("token not support: %s", args.Token.Hex()) } // FX - if contract.IsZeroEthAddress(args.Token) { - supply := c.bankKeeper.GetSupply(cacheCtx, fxtypes.DefaultDenom) - balance := c.bankKeeper.GetBalance(cacheCtx, c.accountKeeper.GetModuleAddress(ethtypes.ModuleName), fxtypes.DefaultDenom) - return crosschaintypes.BridgeCoinAmountMethod.Outputs.Pack(supply.Amount.Sub(balance.Amount).BigInt()) + if fxcontract.IsZeroEthAddress(args.Token) { + supply := m.bankKeeper.GetSupply(ctx, fxtypes.DefaultDenom) + balance := m.bankKeeper.GetBalance(ctx, m.accountKeeper.GetModuleAddress(ethtypes.ModuleName), fxtypes.DefaultDenom) + return m.PackOutput(supply.Amount.Sub(balance.Amount).BigInt()) } // OriginDenom - if c.erc20Keeper.IsOriginDenom(cacheCtx, pair.GetDenom()) { - erc20Call := contract.NewERC20Call(evm, c.Address(), args.Token, c.GetBlockGasLimit()) + if m.erc20Keeper.IsOriginDenom(ctx, pair.GetDenom()) { + erc20Call := fxcontract.NewERC20Call(evm, crosschaintypes.GetAddress(), args.Token, m.RequiredGas()) supply, err := erc20Call.TotalSupply() if err != nil { return nil, err } - return crosschaintypes.BridgeCoinAmountMethod.Outputs.Pack(supply) + return m.PackOutput(supply) } // one to one - _, has = c.erc20Keeper.HasDenomAlias(cacheCtx, pair.GetDenom()) + _, has = m.erc20Keeper.HasDenomAlias(ctx, pair.GetDenom()) if !has && pair.GetDenom() != fxtypes.DefaultDenom { - return crosschaintypes.BridgeCoinAmountMethod.Outputs.Pack( - c.bankKeeper.GetSupply(cacheCtx, pair.GetDenom()).Amount.BigInt(), + return m.PackOutput( + m.bankKeeper.GetSupply(ctx, pair.GetDenom()).Amount.BigInt(), ) } // many to one - md, has := c.bankKeeper.GetDenomMetaData(cacheCtx, pair.GetDenom()) + md, has := m.bankKeeper.GetDenomMetaData(ctx, pair.GetDenom()) if !has { return nil, fmt.Errorf("denom not support: %s", pair.GetDenom()) } - denom := c.erc20Keeper.ToTargetDenom( - cacheCtx, + denom := m.erc20Keeper.ToTargetDenom( + ctx, pair.GetDenom(), md.GetBase(), md.GetDenomUnits()[0].GetAliases(), fxtypes.ParseFxTarget(fxtypes.Byte32ToString(args.Target)), ) - balance := c.bankKeeper.GetBalance(cacheCtx, c.erc20Keeper.ModuleAddress().Bytes(), pair.GetDenom()) - supply := c.bankKeeper.GetSupply(cacheCtx, denom) + balance := m.bankKeeper.GetBalance(ctx, m.erc20Keeper.ModuleAddress().Bytes(), pair.GetDenom()) + supply := m.bankKeeper.GetSupply(ctx, denom) if balance.Amount.LT(supply.Amount) { - return crosschaintypes.BridgeCoinAmountMethod.Outputs.Pack(balance.Amount.BigInt()) + supply = balance + } + return m.PackOutput(supply.Amount.BigInt()) +} + +func (m *BridgeCoinAmountMethod) PackInput(args crosschaintypes.BridgeCoinAmountArgs) ([]byte, error) { + arguments, err := m.Method.Inputs.Pack(args.Token, args.Target) + if err != nil { + return nil, err + } + return append(m.GetMethodId(), arguments...), nil +} + +func (m *BridgeCoinAmountMethod) UnpackInput(data []byte) (*crosschaintypes.BridgeCoinAmountArgs, error) { + args := new(crosschaintypes.BridgeCoinAmountArgs) + if err := evmtypes.ParseMethodArgs(m.Method, args, data[4:]); err != nil { + return nil, err + } + return args, nil +} + +func (m *BridgeCoinAmountMethod) PackOutput(amount *big.Int) ([]byte, error) { + pack, err := m.Method.Outputs.Pack(amount) + if err != nil { + return nil, err + } + return pack, nil +} + +func (m *BridgeCoinAmountMethod) UnpackOutput(data []byte) (*big.Int, error) { + amount, err := m.Method.Outputs.Unpack(data) + if err != nil { + return nil, err } - return crosschaintypes.BridgeCoinAmountMethod.Outputs.Pack(supply.Amount.BigInt()) + return amount[0].(*big.Int), nil } diff --git a/x/crosschain/precompile/bridge_coin_amount_test.go b/x/crosschain/precompile/bridge_coin_amount_test.go new file mode 100644 index 00000000..bd6ec0d9 --- /dev/null +++ b/x/crosschain/precompile/bridge_coin_amount_test.go @@ -0,0 +1,97 @@ +package precompile_test + +import ( + "errors" + "fmt" + "math/big" + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/assert" + tmrand "github.com/tendermint/tendermint/libs/rand" + + "github.com/functionx/fx-core/v7/testutil/helpers" + fxtypes "github.com/functionx/fx-core/v7/types" + "github.com/functionx/fx-core/v7/x/crosschain/precompile" + "github.com/functionx/fx-core/v7/x/crosschain/types" +) + +func TestBridgeCoinAmountMethod_ABI(t *testing.T) { + bridgeCoinAmount := precompile.NewBridgeCoinAmountMethod(nil).Method + assert.Equal(t, 2, len(bridgeCoinAmount.Inputs)) + assert.Equal(t, 1, len(bridgeCoinAmount.Outputs)) +} + +func (suite *PrecompileTestSuite) TestBridgeCoinAmount() { + testCases := []struct { + name string + malleate func(token common.Address, target string) (types.BridgeCoinAmountArgs, error) + success bool + }{ + { + name: "ok", + malleate: func(token common.Address, target string) (types.BridgeCoinAmountArgs, error) { + return types.BridgeCoinAmountArgs{ + Token: token, + Target: fxtypes.MustStrToByte32(target), + }, nil + }, + success: true, + }, + { + name: "failed - invalid target", + malleate: func(token common.Address, target string) (types.BridgeCoinAmountArgs, error) { + return types.BridgeCoinAmountArgs{ + Token: token, + Target: fxtypes.MustStrToByte32(""), + }, errors.New("empty target: evm transaction execution failed") + }, + success: false, + }, + { + name: "failed - invalid token", + malleate: func(_ common.Address, target string) (types.BridgeCoinAmountArgs, error) { + token := helpers.GenHexAddress() + return types.BridgeCoinAmountArgs{ + Token: token, + Target: fxtypes.MustStrToByte32(target), + }, fmt.Errorf("token not support: %s: evm transaction execution failed", token.String()) + }, + success: false, + }, + } + for _, tc := range testCases { + suite.Run(fmt.Sprintf("Case %s", tc.name), func() { + signer := suite.RandSigner() + bridgeCoinAmount := precompile.NewBridgeCoinAmountMethod(nil) + + md := suite.GenerateCrossChainDenoms() + pair, err := suite.app.Erc20Keeper.RegisterNativeCoin(suite.ctx, md.GetMetadata()) + suite.Require().NoError(err) + randMint := big.NewInt(int64(tmrand.Uint32() + 10)) + suite.MintLockNativeTokenToModule(md.GetMetadata(), sdkmath.NewIntFromBigInt(randMint)) + + args, expectedErr := tc.malleate(pair.GetERC20Contract(), md.RandModule()) + packData, err := bridgeCoinAmount.PackInput(args) + suite.Require().NoError(err) + + contractAddr := types.GetAddress() + res, err := suite.app.EvmKeeper.CallEVMWithoutGas(suite.ctx, signer.Address(), &contractAddr, nil, packData, false) + + if tc.success { + suite.Require().NoError(err) + suite.Require().False(res.Failed(), res.VmError) + + shares, err := bridgeCoinAmount.UnpackOutput(res.Ret) + suite.Require().NoError(err) + suite.Require().Equal(shares.String(), randMint.String()) + } else { + suite.Require().True(err != nil || res.Failed()) + if err != nil { + suite.Require().EqualError(err, expectedErr.Error()) + } + } + }) + } +} diff --git a/x/crosschain/precompile/cancel_send_to_external.go b/x/crosschain/precompile/cancel_send_to_external.go new file mode 100644 index 00000000..26cb857d --- /dev/null +++ b/x/crosschain/precompile/cancel_send_to_external.go @@ -0,0 +1,111 @@ +package precompile + +import ( + "errors" + "fmt" + "math/big" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + + fxtypes "github.com/functionx/fx-core/v7/types" + crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" + evmtypes "github.com/functionx/fx-core/v7/x/evm/types" +) + +type CancelSendToExternalMethod struct { + *Keeper + Method abi.Method + Event abi.Event +} + +func NewCancelSendToExternalMethod(keeper *Keeper) *CancelSendToExternalMethod { + return &CancelSendToExternalMethod{ + Keeper: keeper, + Method: crosschaintypes.GetABI().Methods["cancelSendToExternal"], + Event: crosschaintypes.GetABI().Events["CancelSendToExternal"], + } +} + +func (m *CancelSendToExternalMethod) IsReadonly() bool { + return false +} + +func (m *CancelSendToExternalMethod) GetMethodId() []byte { + return m.Method.ID +} + +func (m *CancelSendToExternalMethod) RequiredGas() uint64 { + return 30_000 +} + +func (m *CancelSendToExternalMethod) Run(ctx sdk.Context, evm *vm.EVM, contract *vm.Contract) ([]byte, error) { + if m.router == nil { + return nil, errors.New("cross chain router is empty") + } + + args, err := m.UnpackInput(contract.Input) + if err != nil { + return nil, err + } + + sender := contract.Caller() + route, has := m.router.GetRoute(args.Chain) + if !has { + return nil, fmt.Errorf("chain not support: %s", args.Chain) + } + + // NOTE: must be get relation before cancel, cancel will delete it if relation exist + hasRelation := m.erc20Keeper.HasOutgoingTransferRelation(ctx, args.Chain, args.TxID.Uint64()) + + refundCoin, err := route.PrecompileCancelSendToExternal(ctx, args.TxID.Uint64(), sender.Bytes()) + if err != nil { + return nil, err + } + if !hasRelation && refundCoin.Denom == fxtypes.DefaultDenom { + // add refund to sender in evm state db, because bank keeper add refund to sender + evm.StateDB.AddBalance(sender, refundCoin.Amount.BigInt()) + } + + data, topic, err := m.NewCancelSendToExternalEvent(sender, args.Chain, args.TxID) + if err != nil { + return nil, err + } + EmitEvent(evm, data, topic) + + return m.PackOutput(true) +} + +func (m *CancelSendToExternalMethod) NewCancelSendToExternalEvent(sender common.Address, chainName string, txId *big.Int) (data []byte, topic []common.Hash, err error) { + data, topic, err = evmtypes.PackTopicData(m.Event, []common.Hash{sender.Hash()}, chainName, txId) + if err != nil { + return nil, nil, err + } + return data, topic, nil +} + +func (m *CancelSendToExternalMethod) PackInput(chainName string, txId *big.Int) ([]byte, error) { + data, err := m.Method.Inputs.Pack(chainName, txId) + if err != nil { + return nil, err + } + return append(m.GetMethodId(), data...), nil +} + +func (m *CancelSendToExternalMethod) UnpackInput(data []byte) (*crosschaintypes.CancelSendToExternalArgs, error) { + args := new(crosschaintypes.CancelSendToExternalArgs) + if err := evmtypes.ParseMethodArgs(m.Method, args, data[4:]); err != nil { + return nil, err + } + return args, nil +} + +func (m *CancelSendToExternalMethod) PackOutput(success bool) ([]byte, error) { + pack, err := m.Method.Outputs.Pack(success) + if err != nil { + return nil, err + } + return pack, nil +} diff --git a/x/evm/precompiles/tests/cancel_sendtoexternal_test.go b/x/crosschain/precompile/cancel_send_to_external_test.go similarity index 96% rename from x/evm/precompiles/tests/cancel_sendtoexternal_test.go rename to x/crosschain/precompile/cancel_send_to_external_test.go index 8508518a..b3815fd4 100644 --- a/x/evm/precompiles/tests/cancel_sendtoexternal_test.go +++ b/x/crosschain/precompile/cancel_send_to_external_test.go @@ -1,4 +1,4 @@ -package tests_test +package precompile_test import ( "encoding/hex" @@ -20,22 +20,19 @@ import ( fxtypes "github.com/functionx/fx-core/v7/types" bsctypes "github.com/functionx/fx-core/v7/x/bsc/types" crosschainkeeper "github.com/functionx/fx-core/v7/x/crosschain/keeper" + "github.com/functionx/fx-core/v7/x/crosschain/precompile" crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" "github.com/functionx/fx-core/v7/x/erc20/types" ethtypes "github.com/functionx/fx-core/v7/x/eth/types" ) func TestCancelSendToExternalABI(t *testing.T) { - crossChainABI := crosschaintypes.GetABI() + cancelSendToExternal := precompile.NewCancelSendToExternalMethod(nil) - method := crossChainABI.Methods[crosschaintypes.CancelSendToExternalMethodName] - require.Equal(t, method, crosschaintypes.CancelSendToExternalMethod) - require.Equal(t, 2, len(method.Inputs)) - require.Equal(t, 1, len(method.Outputs)) + require.Equal(t, 2, len(cancelSendToExternal.Method.Inputs)) + require.Equal(t, 1, len(cancelSendToExternal.Method.Outputs)) - event := crossChainABI.Events[crosschaintypes.CancelSendToExternalEventName] - require.Equal(t, event, crosschaintypes.CancelSendToExternalEvent) - require.Equal(t, 3, len(event.Inputs)) + require.Equal(t, 3, len(cancelSendToExternal.Event.Inputs)) } //gocyclo:ignore @@ -351,7 +348,6 @@ func (suite *PrecompileTestSuite) TestCancelSendToExternal() { for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.name), func() { - suite.SetupTest() // reset signer := suite.RandSigner() // token pair md := suite.GenerateCrossChainDenoms() @@ -448,10 +444,11 @@ func (suite *PrecompileTestSuite) TestCancelSendToExternal() { } for _, log := range res.Logs { - if log.Topics[0] == crosschaintypes.CancelSendToExternalEvent.ID.String() { + event := crosschaintypes.GetABI().Events["CancelSendToExternal"] + if log.Topics[0] == event.ID.String() { suite.Require().Equal(log.Address, crosschaintypes.GetAddress().String()) suite.Require().Equal(log.Topics[1], signer.Address().Hash().String()) - unpack, err := crosschaintypes.CancelSendToExternalEvent.Inputs.NonIndexed().Unpack(log.Data) + unpack, err := event.Inputs.NonIndexed().Unpack(log.Data) suite.Require().NoError(err) chain := unpack[0].(string) suite.Require().Equal(chain, moduleName) diff --git a/x/crosschain/precompile/cancel_sendtoexternal.go b/x/crosschain/precompile/cancel_sendtoexternal.go deleted file mode 100644 index 54f60c14..00000000 --- a/x/crosschain/precompile/cancel_sendtoexternal.go +++ /dev/null @@ -1,52 +0,0 @@ -package precompile - -import ( - "errors" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" - - fxtypes "github.com/functionx/fx-core/v7/types" - crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" - evmtypes "github.com/functionx/fx-core/v7/x/evm/types" -) - -func (c *Contract) CancelSendToExternal(ctx sdk.Context, evm *vm.EVM, contract *vm.Contract, readonly bool) ([]byte, error) { - if readonly { - return nil, errors.New("cancel send to external method not readonly") - } - if c.router == nil { - return nil, errors.New("cross chain router is empty") - } - - var args crosschaintypes.CancelSendToExternalArgs - if err := evmtypes.ParseMethodArgs(crosschaintypes.CancelSendToExternalMethod, &args, contract.Input[4:]); err != nil { - return nil, err - } - sender := contract.Caller() - route, has := c.router.GetRoute(args.Chain) - if !has { - return nil, fmt.Errorf("chain not support: %s", args.Chain) - } - - // NOTE: must be get relation before cancel, cancel will delete it if relation exist - hasRelation := c.erc20Keeper.HasOutgoingTransferRelation(ctx, args.Chain, args.TxID.Uint64()) - - refundCoin, err := route.PrecompileCancelSendToExternal(ctx, args.TxID.Uint64(), sender.Bytes()) - if err != nil { - return nil, err - } - if !hasRelation && refundCoin.Denom == fxtypes.DefaultDenom { - // add refund to sender in evm state db, because bank keeper add refund to sender - evm.StateDB.AddBalance(sender, refundCoin.Amount.BigInt()) - } - - // add event log - if err = c.AddLog(evm, crosschaintypes.CancelSendToExternalEvent, []common.Hash{sender.Hash()}, args.Chain, args.TxID); err != nil { - return nil, err - } - - return crosschaintypes.CancelSendToExternalMethod.Outputs.Pack(true) -} diff --git a/x/crosschain/precompile/contract.go b/x/crosschain/precompile/contract.go index 511f1a28..ec86e583 100644 --- a/x/crosschain/precompile/contract.go +++ b/x/crosschain/precompile/contract.go @@ -1,25 +1,21 @@ package precompile import ( - "errors" + "bytes" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" + "github.com/functionx/fx-core/v7/contract" crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" evmtypes "github.com/functionx/fx-core/v7/x/evm/types" ) type Contract struct { - ctx sdk.Context - router *Router - bankKeeper BankKeeper - erc20Keeper Erc20Keeper - ibcTransferKeeper IBCTransferKeeper - accountKeeper AccountKeeper + ctx sdk.Context + methods []contract.PrecompileMethod } func NewPrecompiledContract( @@ -30,14 +26,25 @@ func NewPrecompiledContract( accountKeeper AccountKeeper, router *Router, ) *Contract { - return &Contract{ - ctx: ctx, + keeper := &Keeper{ bankKeeper: bankKeeper, erc20Keeper: erc20Keeper, ibcTransferKeeper: ibcTransferKeeper, accountKeeper: accountKeeper, router: router, } + return &Contract{ + ctx: ctx, + methods: []contract.PrecompileMethod{ + NewBridgeCoinAmountMethod(keeper), + + NewCancelSendToExternalMethod(keeper), + NewIncreaseBridgeFeeMethod(keeper), + NewFIP20CrossChainMethod(keeper), + NewCrossChainMethod(keeper), + NewBridgeCallMethod(keeper), + }, + } } func (c *Contract) Address() common.Address { @@ -52,22 +59,12 @@ func (c *Contract) RequiredGas(input []byte) uint64 { if len(input) <= 4 { return 0 } - switch string(input[:4]) { - case string(crosschaintypes.FIP20CrossChainMethod.ID): - return crosschaintypes.FIP20CrossChainGas - case string(crosschaintypes.CrossChainMethod.ID): - return crosschaintypes.CrossChainGas - case string(crosschaintypes.CancelSendToExternalMethod.ID): - return crosschaintypes.CancelSendToExternalGas - case string(crosschaintypes.IncreaseBridgeFeeMethod.ID): - return crosschaintypes.IncreaseBridgeFeeGas - case string(crosschaintypes.BridgeCoinAmountMethod.ID): - return crosschaintypes.BridgeCoinAmountFeeGas - case string(crosschaintypes.BridgeCallMethod.ID): - return crosschaintypes.BridgeCallFeeGas - default: - return 0 + for _, method := range c.methods { + if bytes.Equal(method.GetMethodId(), input[:4]) { + return method.RequiredGas() + } } + return 0 } func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) (ret []byte, err error) { @@ -75,58 +72,33 @@ func (c *Contract) Run(evm *vm.EVM, contract *vm.Contract, readonly bool) (ret [ return evmtypes.PackRetErrV2("invalid input") } - cacheCtx, commit := c.ctx.CacheContext() - snapshot := evm.StateDB.Snapshot() - - // parse input - switch string(contract.Input[:4]) { - case string(crosschaintypes.FIP20CrossChainMethod.ID): - ret, err = c.FIP20CrossChain(cacheCtx, evm, contract, readonly) - case string(crosschaintypes.CrossChainMethod.ID): - ret, err = c.CrossChain(cacheCtx, evm, contract, readonly) - case string(crosschaintypes.CancelSendToExternalMethod.ID): - ret, err = c.CancelSendToExternal(cacheCtx, evm, contract, readonly) - case string(crosschaintypes.IncreaseBridgeFeeMethod.ID): - ret, err = c.IncreaseBridgeFee(cacheCtx, evm, contract, readonly) - case string(crosschaintypes.BridgeCoinAmountMethod.ID): - ret, err = c.BridgeCoinAmount(cacheCtx, evm, contract, readonly) - case string(crosschaintypes.BridgeCallMethod.ID): - ret, err = c.BridgeCall(cacheCtx, evm, contract, readonly) - - default: - err = errors.New("unknown method") - } - - if err != nil { - // revert evm state - evm.StateDB.RevertToSnapshot(snapshot) - return evmtypes.PackRetErrV2(err.Error()) + for _, method := range c.methods { + if bytes.Equal(method.GetMethodId(), contract.Input[:4]) { + if readonly && !method.IsReadonly() { + return evmtypes.PackRetErrV2("write protection") + } + cacheCtx, commit := c.ctx.CacheContext() + snapshot := evm.StateDB.Snapshot() + ret, err = method.Run(cacheCtx, evm, contract) + if err != nil { + // revert evm state + evm.StateDB.RevertToSnapshot(snapshot) + return evmtypes.PackRetErrV2(err.Error()) + } + if !method.IsReadonly() { + commit() + } + return ret, nil + } } - - // commit and append events - commit() - - return ret, nil + return evmtypes.PackRetErrV2("unknown method") } -func (c *Contract) AddLog(evm *vm.EVM, event abi.Event, topics []common.Hash, args ...interface{}) error { - data, newTopic, err := evmtypes.PackTopicData(event, topics, args...) - if err != nil { - return err - } +func EmitEvent(evm *vm.EVM, data []byte, topics []common.Hash) { evm.StateDB.AddLog(ðtypes.Log{ - Address: c.Address(), - Topics: newTopic, + Address: crosschaintypes.GetAddress(), + Topics: topics, Data: data, BlockNumber: evm.Context.BlockNumber.Uint64(), }) - return nil -} - -func (c *Contract) GetBlockGasLimit() uint64 { - params := c.ctx.ConsensusParams() - if params != nil && params.Block != nil && params.Block.MaxGas > 0 { - return uint64(params.Block.MaxGas) - } - return 0 } diff --git a/x/evm/precompiles/tests/contract_test.go b/x/crosschain/precompile/contract_test.go similarity index 99% rename from x/evm/precompiles/tests/contract_test.go rename to x/crosschain/precompile/contract_test.go index 072c2be6..02aae4eb 100644 --- a/x/evm/precompiles/tests/contract_test.go +++ b/x/crosschain/precompile/contract_test.go @@ -1,4 +1,4 @@ -package tests_test +package precompile_test import ( "encoding/json" @@ -62,7 +62,6 @@ func TestPrecompileTestSuite(t *testing.T) { suite.Run(t, new(PrecompileTestSuite)) } -// Test helpers func (suite *PrecompileTestSuite) SetupTest() { // account key priv, err := ethsecp256k1.GenerateKey() diff --git a/x/crosschain/precompile/crosschain.go b/x/crosschain/precompile/crosschain.go index 0ae1a1c4..05c0e9d2 100644 --- a/x/crosschain/precompile/crosschain.go +++ b/x/crosschain/precompile/crosschain.go @@ -2,121 +2,67 @@ package precompile import ( "errors" - "fmt" "math/big" - "strings" sdkmath "cosmossdk.io/math" - "github.com/armon/go-metrics" - "github.com/cosmos/cosmos-sdk/telemetry" sdk "github.com/cosmos/cosmos-sdk/types" - transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" - ibcclienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/vm" - evmtypes "github.com/evmos/ethermint/x/evm/types" - "github.com/functionx/fx-core/v7/contract" + fxcontract "github.com/functionx/fx-core/v7/contract" fxtypes "github.com/functionx/fx-core/v7/types" + "github.com/functionx/fx-core/v7/x/crosschain/legacy" crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" - erc20types "github.com/functionx/fx-core/v7/x/erc20/types" - fxevmtypes "github.com/functionx/fx-core/v7/x/evm/types" + evmtypes "github.com/functionx/fx-core/v7/x/evm/types" ) -// FIP20CrossChain only for fip20 contract transferCrossChain called -// -//gocyclo:ignore -func (c *Contract) FIP20CrossChain(ctx sdk.Context, evm *vm.EVM, contract *vm.Contract, readonly bool) ([]byte, error) { - if readonly { - return nil, errors.New("fip20 cross chain method not readonly") - } - - tokenContract := contract.Caller() - tokenPair, found := c.erc20Keeper.GetTokenPairByAddress(ctx, tokenContract) - if !found { - return nil, fmt.Errorf("token pair not found: %s", tokenContract.String()) - } - - var args crosschaintypes.FIP20CrossChainArgs - if err := fxevmtypes.ParseMethodArgs(crosschaintypes.FIP20CrossChainMethod, &args, contract.Input[4:]); err != nil { - return nil, err - } - - amountCoin := sdk.NewCoin(tokenPair.GetDenom(), sdkmath.NewIntFromBigInt(args.Amount)) - feeCoin := sdk.NewCoin(tokenPair.GetDenom(), sdkmath.NewIntFromBigInt(args.Fee)) - totalCoin := sdk.NewCoin(tokenPair.GetDenom(), amountCoin.Amount.Add(feeCoin.Amount)) - - // NOTE: if user call evm denom transferCrossChain with msg.value - // we need transfer msg.value from sender to contract in bank keeper - if tokenPair.GetDenom() == fxtypes.DefaultDenom { - balance := c.bankKeeper.GetBalance(ctx, tokenContract.Bytes(), fxtypes.DefaultDenom) - evmBalance := evm.StateDB.GetBalance(tokenContract) - - cmp := evmBalance.Cmp(balance.Amount.BigInt()) - if cmp == -1 { - return nil, fmt.Errorf("invalid balance(chain: %s,evm: %s)", balance.Amount.String(), evmBalance.String()) - } - if cmp == 1 { - // sender call transferCrossChain with msg.value, the msg.value evm denom should send to contract - value := big.NewInt(0).Sub(evmBalance, balance.Amount.BigInt()) - valueCoin := sdk.NewCoins(sdk.NewCoin(fxtypes.DefaultDenom, sdkmath.NewIntFromBigInt(value))) - if err := c.bankKeeper.SendCoins(ctx, args.Sender.Bytes(), tokenContract.Bytes(), valueCoin); err != nil { - return nil, fmt.Errorf("send coin: %s", err.Error()) - } - } - } - - // transfer token from evm to local chain - if err := c.convertERC20(ctx, evm, tokenPair, totalCoin, args.Sender); err != nil { - return nil, err - } - - fxTarget := fxtypes.ParseFxTarget(fxtypes.Byte32ToString(args.Target)) - if err := c.handlerCrossChain(ctx, args.Sender.Bytes(), args.Receipt, amountCoin, feeCoin, fxTarget, args.Memo, false); err != nil { - return nil, err - } +type CrossChainMethod struct { + *Keeper + abi.Method + abi.Event +} - // add event log - if err := c.AddLog(evm, crosschaintypes.CrossChainEvent, []common.Hash{args.Sender.Hash(), tokenPair.GetERC20Contract().Hash()}, - tokenPair.GetDenom(), args.Receipt, args.Amount, args.Fee, args.Target, args.Memo); err != nil { - return nil, err +func NewCrossChainMethod(keeper *Keeper) *CrossChainMethod { + return &CrossChainMethod{ + Keeper: keeper, + Method: crosschaintypes.GetABI().Methods["crossChain"], + Event: crosschaintypes.GetABI().Events["CrossChain"], } +} - // add fip20CrossChain events - fip20CrossChainEvents(ctx, args.Sender, tokenPair.GetERC20Contract(), args.Receipt, - fxtypes.Byte32ToString(args.Target), tokenPair.GetDenom(), args.Amount, args.Fee) +func (m *CrossChainMethod) IsReadonly() bool { + return false +} - return crosschaintypes.FIP20CrossChainMethod.Outputs.Pack(true) +func (m *CrossChainMethod) GetMethodId() []byte { + return m.Method.ID } -// CrossChain called at any address(account or contract) -// -//gocyclo:ignore -func (c *Contract) CrossChain(ctx sdk.Context, evm *vm.EVM, contractAddr *vm.Contract, readonly bool) ([]byte, error) { - if readonly { - return nil, errors.New("cross chain method not readonly") - } +func (m *CrossChainMethod) RequiredGas() uint64 { + return 40_000 +} - var args crosschaintypes.CrossChainArgs - err := fxevmtypes.ParseMethodArgs(crosschaintypes.CrossChainMethod, &args, contractAddr.Input[4:]) +func (m *CrossChainMethod) Run(ctx sdk.Context, evm *vm.EVM, contract *vm.Contract) ([]byte, error) { + args, err := m.UnpackInput(contract.Input) if err != nil { return nil, err } - value := contractAddr.Value() - sender := contractAddr.Caller() + value := contract.Value() + sender := contract.Caller() originToken := false totalCoin := sdk.Coin{} // cross-chain origin token - if value.Cmp(big.NewInt(0)) == 1 && contract.IsZeroEthAddress(args.Token) { + if value.Cmp(big.NewInt(0)) == 1 && fxcontract.IsZeroEthAddress(args.Token) { totalAmount := big.NewInt(0).Add(args.Amount, args.Fee) if totalAmount.Cmp(value) != 0 { return nil, errors.New("amount + fee not equal msg.value") } - totalCoin, err = c.handlerOriginToken(ctx, evm, sender, totalAmount) + totalCoin, err = m.handlerOriginToken(ctx, evm, sender, totalAmount) if err != nil { return nil, err } @@ -124,7 +70,7 @@ func (c *Contract) CrossChain(ctx sdk.Context, evm *vm.EVM, contractAddr *vm.Con // origin token flag is true when cross chain evm denom originToken = true } else { - totalCoin, err = c.handlerERC20Token(ctx, evm, sender, args.Token, big.NewInt(0).Add(args.Amount, args.Fee)) + totalCoin, err = m.handlerERC20Token(ctx, evm, sender, args.Token, big.NewInt(0).Add(args.Amount, args.Fee)) if err != nil { return nil, err } @@ -134,231 +80,42 @@ func (c *Contract) CrossChain(ctx sdk.Context, evm *vm.EVM, contractAddr *vm.Con amountCoin := sdk.NewCoin(totalCoin.Denom, sdkmath.NewIntFromBigInt(args.Amount)) feeCoin := sdk.NewCoin(totalCoin.Denom, sdkmath.NewIntFromBigInt(args.Fee)) - if err = c.handlerCrossChain(ctx, sender.Bytes(), args.Receipt, amountCoin, feeCoin, fxTarget, args.Memo, originToken); err != nil { + if err = m.handlerCrossChain(ctx, sender.Bytes(), args.Receipt, amountCoin, feeCoin, fxTarget, args.Memo, originToken); err != nil { return nil, err } - // add event log - if err = c.AddLog(evm, crosschaintypes.CrossChainEvent, []common.Hash{sender.Hash(), args.Token.Hash()}, - amountCoin.Denom, args.Receipt, args.Amount, args.Fee, args.Target, args.Memo); err != nil { + data, topic, err := m.NewCrossChainEvent(sender, args.Token, amountCoin.Denom, args.Receipt, args.Amount, args.Fee, args.Target, args.Memo) + if err != nil { return nil, err } + EmitEvent(evm, data, topic) - // add cross chain events - crossChainEvents(ctx, sender, args.Token, args.Receipt, fxtypes.Byte32ToString(args.Target), + legacy.CrossChainEvents(ctx, sender, args.Token, args.Receipt, fxtypes.Byte32ToString(args.Target), amountCoin.Denom, args.Memo, args.Amount, args.Fee) - return crosschaintypes.CrossChainMethod.Outputs.Pack(true) -} - -func (c *Contract) handlerOriginToken(ctx sdk.Context, evm *vm.EVM, sender common.Address, amount *big.Int) (sdk.Coin, error) { - // NOTE: stateDB sub sender balance,but bank keeper not update. - // so mint token to crosschain, end of stateDB commit will sub balance from bank keeper. - // if only allow depth 1, the sender is origin sender, we can sub balance from bank keeper and not need burn/mint denom - evm.StateDB.SubBalance(c.Address(), amount) - totalCoin := sdk.NewCoin(fxtypes.DefaultDenom, sdkmath.NewIntFromBigInt(amount)) - totalCoins := sdk.NewCoins(totalCoin) - - if err := c.bankKeeper.MintCoins(ctx, evmtypes.ModuleName, totalCoins); err != nil { - return sdk.Coin{}, err - } - if err := c.bankKeeper.SendCoinsFromModuleToAccount(ctx, evmtypes.ModuleName, sender.Bytes(), totalCoins); err != nil { - return sdk.Coin{}, err - } - return totalCoin, nil -} - -func (c *Contract) handlerERC20Token(ctx sdk.Context, evm *vm.EVM, sender, token common.Address, amount *big.Int) (sdk.Coin, error) { - tokenPair, found := c.erc20Keeper.GetTokenPairByAddress(ctx, token) - if !found { - return sdk.Coin{}, fmt.Errorf("token pair not found: %s", token.String()) - } - baseDenom := tokenPair.GetDenom() - - // transferFrom to erc20 module - erc20Call := contract.NewERC20Call(evm, c.Address(), token, c.GetBlockGasLimit()) - if err := erc20Call.TransferFrom(sender, c.erc20Keeper.ModuleAddress(), amount); err != nil { - return sdk.Coin{}, err - } - if err := c.convertERC20(ctx, evm, tokenPair, sdk.NewCoin(baseDenom, sdkmath.NewIntFromBigInt(amount)), sender); err != nil { - return sdk.Coin{}, err - } - return sdk.NewCoin(baseDenom, sdkmath.NewIntFromBigInt(amount)), nil + return m.PackOutput(true) } -func (c *Contract) convertERC20( - ctx sdk.Context, - evm *vm.EVM, - tokenPair erc20types.TokenPair, - amount sdk.Coin, - sender common.Address, -) error { - if tokenPair.IsNativeCoin() { - erc20Call := contract.NewERC20Call(evm, c.erc20Keeper.ModuleAddress(), tokenPair.GetERC20Contract(), c.GetBlockGasLimit()) - err := erc20Call.Burn(c.erc20Keeper.ModuleAddress(), amount.Amount.BigInt()) - if err != nil { - return err - } - if tokenPair.GetDenom() == fxtypes.DefaultDenom { - // cache token contract balance - evm.StateDB.GetBalance(tokenPair.GetERC20Contract()) - - err = c.bankKeeper.SendCoinsFromAccountToModule(ctx, tokenPair.GetERC20Contract().Bytes(), erc20types.ModuleName, sdk.NewCoins(amount)) - if err != nil { - return err - } - - // evm stateDB sub token contract balance - evm.StateDB.SubBalance(tokenPair.GetERC20Contract(), amount.Amount.BigInt()) - } - - } else if tokenPair.IsNativeERC20() { - if err := c.bankKeeper.MintCoins(ctx, erc20types.ModuleName, sdk.NewCoins(amount)); err != nil { - return err - } - } else { - return erc20types.ErrUndefinedOwner - } - - if err := c.bankKeeper.SendCoinsFromModuleToAccount(ctx, erc20types.ModuleName, sender.Bytes(), sdk.NewCoins(amount)); err != nil { - return err - } - return nil -} - -// handlerCrossChain cross chain handler -// originToken is true represent cross chain denom(FX) -// when refund it, will not refund to evm token -// NOTE: fip20CrossChain only use for contract token, so origin token flag always false -func (c *Contract) handlerCrossChain( - ctx sdk.Context, - from sdk.AccAddress, - receipt string, - amount, fee sdk.Coin, - fxTarget fxtypes.FxTarget, - memo string, - originToken bool, -) error { - total := sdk.NewCoin(amount.Denom, amount.Amount.Add(fee.Amount)) - // convert denom to target coin - targetCoin, err := c.erc20Keeper.ConvertDenomToTarget(ctx, from.Bytes(), total, fxTarget) - if err != nil && !erc20types.IsInsufficientLiquidityErr(err) { - return fmt.Errorf("convert denom: %s", err.Error()) - } - amount.Denom = targetCoin.Denom - fee.Denom = targetCoin.Denom - - if fxTarget.IsIBC() { - if err != nil { - return fmt.Errorf("convert denom: %s", err.Error()) - } - return c.ibcTransfer(ctx, from.Bytes(), receipt, amount, fee, fxTarget, memo, originToken) +func (m *CrossChainMethod) NewCrossChainEvent(sender common.Address, token common.Address, denom, receipt string, amount, fee *big.Int, target [32]byte, memo string) (data []byte, topic []common.Hash, err error) { + data, topic, err = evmtypes.PackTopicData(m.Event, []common.Hash{sender.Hash(), token.Hash()}, denom, receipt, amount, fee, target, memo) + if err != nil { + return nil, nil, err } - - return c.outgoingTransfer(ctx, from.Bytes(), receipt, amount, fee, fxTarget, originToken, err != nil) + return data, topic, nil } -func (c *Contract) outgoingTransfer( - ctx sdk.Context, - from sdk.AccAddress, - to string, - amount, fee sdk.Coin, - fxTarget fxtypes.FxTarget, - originToken, insufficientLiquidit bool, -) error { - if c.router == nil { - return errors.New("cross chain router empty") - } - route, has := c.router.GetRoute(fxTarget.GetTarget()) - if !has { - return errors.New("invalid target") - } - if err := route.TransferAfter(ctx, from, to, amount, fee, originToken, insufficientLiquidit); err != nil { - return fmt.Errorf("cross chain error: %s", err.Error()) +func (m *CrossChainMethod) UnpackInput(data []byte) (*crosschaintypes.CrossChainArgs, error) { + args := new(crosschaintypes.CrossChainArgs) + if err := evmtypes.ParseMethodArgs(m.Method, args, data[4:]); err != nil { + return nil, err } - return nil + return args, nil } -func (c *Contract) ibcTransfer( - ctx sdk.Context, - from sdk.AccAddress, - to string, - amount, fee sdk.Coin, - fxTarget fxtypes.FxTarget, - memo string, - originToken bool, -) error { - if !fee.IsZero() { - return fmt.Errorf("ibc transfer fee must be zero: %s", fee.String()) - } - if strings.ToLower(fxTarget.Prefix) == contract.EthereumAddressPrefix { - if err := contract.ValidateEthereumAddress(to); err != nil { - return fmt.Errorf("invalid to address: %s", to) - } - } else { - if _, err := sdk.GetFromBech32(to, fxTarget.Prefix); err != nil { - return fmt.Errorf("invalid to address: %s", to) - } - } - - ibcTimeoutTimestamp := uint64(ctx.BlockTime().UnixNano()) + uint64(c.erc20Keeper.GetIbcTimeout(ctx)) - transferResponse, err := c.ibcTransferKeeper.Transfer(sdk.WrapSDKContext(ctx), - transfertypes.NewMsgTransfer( - fxTarget.SourcePort, - fxTarget.SourceChannel, - amount, - from.String(), - to, - ibcclienttypes.ZeroHeight(), - ibcTimeoutTimestamp, - memo, - ), - ) +func (m *CrossChainMethod) PackOutput(success bool) ([]byte, error) { + pack, err := m.Method.Outputs.Pack(success) if err != nil { - return fmt.Errorf("ibc transfer error: %s", err.Error()) - } - - if !originToken { - c.erc20Keeper.SetIBCTransferRelation(ctx, fxTarget.SourceChannel, transferResponse.GetSequence()) + return nil, err } - return nil -} - -// transferCrossChainEvents use for fip20 cross chain -// Deprecated -func fip20CrossChainEvents(ctx sdk.Context, from, token common.Address, recipient, target, denom string, amount, fee *big.Int) { - ctx.EventManager().EmitEvent(sdk.NewEvent( - crosschaintypes.EventTypeRelayTransferCrossChain, - sdk.NewAttribute(crosschaintypes.AttributeKeyFrom, from.String()), - sdk.NewAttribute(crosschaintypes.AttributeKeyRecipient, recipient), - sdk.NewAttribute(sdk.AttributeKeyAmount, amount.String()), - sdk.NewAttribute(sdk.AttributeKeyFee, fee.String()), - sdk.NewAttribute(crosschaintypes.AttributeKeyTarget, target), - sdk.NewAttribute(crosschaintypes.AttributeKeyTokenAddress, token.String()), - sdk.NewAttribute(crosschaintypes.AttributeKeyDenom, denom), - )) - - telemetry.IncrCounterWithLabels( - []string{"relay_transfer_cross_chain"}, - 1, - []metrics.Label{ - telemetry.NewLabel("erc20", token.String()), - telemetry.NewLabel("denom", denom), - telemetry.NewLabel("target", target), - }, - ) -} - -func crossChainEvents(ctx sdk.Context, from, token common.Address, recipient, target, denom, memo string, amount, fee *big.Int) { - ctx.EventManager().EmitEvent(sdk.NewEvent( - crosschaintypes.EventTypeCrossChain, - sdk.NewAttribute(crosschaintypes.AttributeKeyFrom, from.String()), - sdk.NewAttribute(crosschaintypes.AttributeKeyRecipient, recipient), - sdk.NewAttribute(sdk.AttributeKeyAmount, amount.String()), - sdk.NewAttribute(sdk.AttributeKeyFee, fee.String()), - sdk.NewAttribute(crosschaintypes.AttributeKeyTarget, target), - sdk.NewAttribute(crosschaintypes.AttributeKeyTokenAddress, token.String()), - sdk.NewAttribute(crosschaintypes.AttributeKeyDenom, denom), - sdk.NewAttribute(crosschaintypes.AttributeKeyMemo, memo), - )) + return pack, nil } diff --git a/x/evm/precompiles/tests/crosschain_test.go b/x/crosschain/precompile/crosschain_test.go similarity index 99% rename from x/evm/precompiles/tests/crosschain_test.go rename to x/crosschain/precompile/crosschain_test.go index 5c0b0b71..5117469d 100644 --- a/x/evm/precompiles/tests/crosschain_test.go +++ b/x/crosschain/precompile/crosschain_test.go @@ -1,4 +1,4 @@ -package tests_test +package precompile_test import ( "encoding/hex" @@ -24,22 +24,19 @@ import ( fxtypes "github.com/functionx/fx-core/v7/types" bsctypes "github.com/functionx/fx-core/v7/x/bsc/types" crosschainkeeper "github.com/functionx/fx-core/v7/x/crosschain/keeper" + "github.com/functionx/fx-core/v7/x/crosschain/precompile" crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" "github.com/functionx/fx-core/v7/x/erc20/types" ethtypes "github.com/functionx/fx-core/v7/x/eth/types" ) func TestCrossChainABI(t *testing.T) { - crossChainABI := crosschaintypes.GetABI() + crossChain := precompile.NewCrossChainMethod(nil) - method := crossChainABI.Methods[crosschaintypes.CrossChainMethodName] - require.Equal(t, method, crosschaintypes.CrossChainMethod) - require.Equal(t, 6, len(method.Inputs)) - require.Equal(t, 1, len(method.Outputs)) + require.Equal(t, 6, len(crossChain.Method.Inputs)) + require.Equal(t, 1, len(crossChain.Method.Outputs)) - event := crossChainABI.Events[crosschaintypes.CrossChainEventName] - require.Equal(t, event, crosschaintypes.CrossChainEvent) - require.Equal(t, 8, len(event.Inputs)) + require.Equal(t, 8, len(crossChain.Event.Inputs)) } //gocyclo:ignore @@ -972,12 +969,13 @@ func (suite *PrecompileTestSuite) TestCrossChain() { } for _, log := range res.Logs { - if log.Topics[0] == crosschaintypes.CrossChainEvent.ID.String() { + event := crosschaintypes.GetABI().Events["IncreaseBridgeFee"] + if log.Topics[0] == event.ID.String() { suite.Require().Equal(3, len(log.Topics)) suite.Require().Equal(log.Address, crosschaintypes.GetAddress().String()) suite.Require().Equal(log.Topics[1], addrQuery.Hash().String()) - unpack, err := crosschaintypes.CrossChainEvent.Inputs.NonIndexed().Unpack(log.Data) + unpack, err := event.Inputs.NonIndexed().Unpack(log.Data) suite.Require().NoError(err) denom := unpack[0].(string) @@ -1386,12 +1384,13 @@ func (suite *PrecompileTestSuite) TestCrossChainExternal() { } for _, log := range res.Logs { - if log.Topics[0] == crosschaintypes.CrossChainEvent.ID.String() { + event := crosschaintypes.GetABI().Events["IncreaseBridgeFee"] + if log.Topics[0] == event.ID.String() { suite.Require().Equal(3, len(log.Topics)) suite.Require().Equal(log.Address, crosschaintypes.GetAddress().String()) suite.Require().Equal(log.Topics[1], addrQuery.Hash().String()) - unpack, err := crosschaintypes.CrossChainEvent.Inputs.NonIndexed().Unpack(log.Data) + unpack, err := event.Inputs.NonIndexed().Unpack(log.Data) suite.Require().NoError(err) denom := unpack[0].(string) diff --git a/x/crosschain/precompile/fip20_crosschain.go b/x/crosschain/precompile/fip20_crosschain.go new file mode 100644 index 00000000..55d0e9db --- /dev/null +++ b/x/crosschain/precompile/fip20_crosschain.go @@ -0,0 +1,114 @@ +package precompile + +import ( + "fmt" + "math/big" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/core/vm" + + fxtypes "github.com/functionx/fx-core/v7/types" + "github.com/functionx/fx-core/v7/x/crosschain/legacy" + crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" + evmtypes "github.com/functionx/fx-core/v7/x/evm/types" +) + +type FIP20CrossChainMethod struct { + *CrossChainMethod + abi.Method +} + +func NewFIP20CrossChainMethod(keeper *Keeper) *FIP20CrossChainMethod { + return &FIP20CrossChainMethod{ + CrossChainMethod: NewCrossChainMethod(keeper), + Method: crosschaintypes.GetABI().Methods["fip20CrossChain"], + } +} + +func (m *FIP20CrossChainMethod) IsReadonly() bool { + return false +} + +func (m *FIP20CrossChainMethod) GetMethodId() []byte { + return m.Method.ID +} + +func (m *FIP20CrossChainMethod) RequiredGas() uint64 { + return 40_000 +} + +func (m *FIP20CrossChainMethod) Run(ctx sdk.Context, evm *vm.EVM, contract *vm.Contract) ([]byte, error) { + tokenContract := contract.Caller() + tokenPair, found := m.erc20Keeper.GetTokenPairByAddress(ctx, tokenContract) + if !found { + return nil, fmt.Errorf("token pair not found: %s", tokenContract.String()) + } + + args, err := m.UnpackInput(contract.Input) + if err != nil { + return nil, err + } + + amountCoin := sdk.NewCoin(tokenPair.GetDenom(), sdkmath.NewIntFromBigInt(args.Amount)) + feeCoin := sdk.NewCoin(tokenPair.GetDenom(), sdkmath.NewIntFromBigInt(args.Fee)) + totalCoin := sdk.NewCoin(tokenPair.GetDenom(), amountCoin.Amount.Add(feeCoin.Amount)) + + // NOTE: if user call evm denom transferCrossChain with msg.value + // we need transfer msg.value from sender to contract in bank keeper + if tokenPair.GetDenom() == fxtypes.DefaultDenom { + balance := m.bankKeeper.GetBalance(ctx, tokenContract.Bytes(), fxtypes.DefaultDenom) + evmBalance := evm.StateDB.GetBalance(tokenContract) + + cmp := evmBalance.Cmp(balance.Amount.BigInt()) + if cmp == -1 { + return nil, fmt.Errorf("invalid balance(chain: %s,evm: %s)", balance.Amount.String(), evmBalance.String()) + } + if cmp == 1 { + // sender call transferCrossChain with msg.value, the msg.value evm denom should send to contract + value := big.NewInt(0).Sub(evmBalance, balance.Amount.BigInt()) + valueCoin := sdk.NewCoins(sdk.NewCoin(fxtypes.DefaultDenom, sdkmath.NewIntFromBigInt(value))) + if err := m.bankKeeper.SendCoins(ctx, args.Sender.Bytes(), tokenContract.Bytes(), valueCoin); err != nil { + return nil, fmt.Errorf("send coin: %s", err.Error()) + } + } + } + + // transfer token from evm to local chain + if err = m.convertERC20(ctx, evm, tokenPair, totalCoin, args.Sender); err != nil { + return nil, err + } + + fxTarget := fxtypes.ParseFxTarget(fxtypes.Byte32ToString(args.Target)) + if err = m.handlerCrossChain(ctx, args.Sender.Bytes(), args.Receipt, amountCoin, feeCoin, fxTarget, args.Memo, false); err != nil { + return nil, err + } + + data, topic, err := m.NewCrossChainEvent(args.Sender, tokenPair.GetERC20Contract(), tokenPair.GetDenom(), args.Receipt, args.Amount, args.Fee, args.Target, args.Memo) + if err != nil { + return nil, err + } + EmitEvent(evm, data, topic) + + legacy.Fip20CrossChainEvents(ctx, args.Sender, tokenPair.GetERC20Contract(), args.Receipt, + fxtypes.Byte32ToString(args.Target), tokenPair.GetDenom(), args.Amount, args.Fee) + + return m.PackOutput(true) +} + +func (m *FIP20CrossChainMethod) UnpackInput(data []byte) (*crosschaintypes.FIP20CrossChainArgs, error) { + args := new(crosschaintypes.FIP20CrossChainArgs) + if err := evmtypes.ParseMethodArgs(m.Method, args, data[4:]); err != nil { + return nil, err + } + return args, nil +} + +func (m *FIP20CrossChainMethod) PackOutput(success bool) ([]byte, error) { + pack, err := m.Method.Outputs.Pack(success) + if err != nil { + return nil, err + } + return pack, nil +} diff --git a/x/evm/precompiles/tests/fip20crosschain_test.go b/x/crosschain/precompile/fip20crosschain_test.go similarity index 98% rename from x/evm/precompiles/tests/fip20crosschain_test.go rename to x/crosschain/precompile/fip20crosschain_test.go index 1434c20b..88c0d19c 100644 --- a/x/evm/precompiles/tests/fip20crosschain_test.go +++ b/x/crosschain/precompile/fip20crosschain_test.go @@ -1,4 +1,4 @@ -package tests_test +package precompile_test import ( "bytes" @@ -22,18 +22,17 @@ import ( fxtypes "github.com/functionx/fx-core/v7/types" bsctypes "github.com/functionx/fx-core/v7/x/bsc/types" crosschainkeeper "github.com/functionx/fx-core/v7/x/crosschain/keeper" + "github.com/functionx/fx-core/v7/x/crosschain/precompile" crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" "github.com/functionx/fx-core/v7/x/erc20/types" ethtypes "github.com/functionx/fx-core/v7/x/eth/types" ) func TestFIP20CrossChainABI(t *testing.T) { - crossChainABI := crosschaintypes.GetABI() + fip20CrossChain := precompile.NewFIP20CrossChainMethod(nil) - method := crossChainABI.Methods[crosschaintypes.FIP20CrossChainMethod.Name] - require.Equal(t, method, crosschaintypes.FIP20CrossChainMethod) - require.Equal(t, 6, len(method.Inputs)) - require.Equal(t, 1, len(method.Outputs)) + require.Equal(t, 6, len(fip20CrossChain.Method.Inputs)) + require.Equal(t, 1, len(fip20CrossChain.Method.Outputs)) } func (suite *PrecompileTestSuite) TestFIP20CrossChain() { @@ -304,7 +303,6 @@ func (suite *PrecompileTestSuite) TestFIP20CrossChain() { for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.name), func() { - suite.SetupTest() // reset signer := suite.RandSigner() // token pair md := suite.GenerateCrossChainDenoms() @@ -865,7 +863,6 @@ func (suite *PrecompileTestSuite) TestFIP20CrossChainIBC() { for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.name), func() { - suite.SetupTest() // reset signer := suite.RandSigner() // set port channel sourcePort, sourceChannel := suite.RandTransferChannel() @@ -1250,7 +1247,7 @@ func (suite *PrecompileTestSuite) TestAccountFIP20CrossChain() { name: "failed - call with address - pair not found", malleate: func(pair *types.TokenPair, md Metadata, signer *helpers.Signer, randMint *big.Int) ([]byte, []string) { data, err := crossChainABI.Pack( - crosschaintypes.FIP20CrossChainMethod.Name, + "fip20CrossChain", signer.Address(), signer.Address().String(), big.NewInt(10), @@ -1272,9 +1269,9 @@ func (suite *PrecompileTestSuite) TestAccountFIP20CrossChain() { malleate: func(pair *types.TokenPair, md Metadata, signer *helpers.Signer, randMint *big.Int) ([]byte, []string) { suite.app.Erc20Keeper.AddTokenPair(suite.ctx, types.NewTokenPair(signer.Address(), "abc", true, types.OWNER_MODULE)) - method := otherABI.Methods[crosschaintypes.FIP20CrossChainMethod.Name] + method := otherABI.Methods["fip20CrossChain"] data, err := otherABI.Pack( - crosschaintypes.FIP20CrossChainMethod.Name, + "fip20CrossChain", signer.Address(), signer.Address(), signer.Address().String(), @@ -1285,7 +1282,7 @@ func (suite *PrecompileTestSuite) TestAccountFIP20CrossChain() { suite.Require().NoError(err) dateTrimPrefix := bytes.TrimPrefix(data, method.ID) - return append(crosschaintypes.FIP20CrossChainMethod.ID, dateTrimPrefix...), []string{hex.EncodeToString(data)} + return append(crosschaintypes.GetABI().Methods["fip20CrossChain"].ID, dateTrimPrefix...), []string{hex.EncodeToString(data)} }, error: func(args []string) string { return "abi: cannot marshal in to go slice: offset" @@ -1295,7 +1292,6 @@ func (suite *PrecompileTestSuite) TestAccountFIP20CrossChain() { } for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.name), func() { - suite.SetupTest() // reset signer := suite.RandSigner() // token pair md := suite.GenerateCrossChainDenoms() diff --git a/x/crosschain/precompile/increase_bridge_fee.go b/x/crosschain/precompile/increase_bridge_fee.go new file mode 100644 index 00000000..d2b275b0 --- /dev/null +++ b/x/crosschain/precompile/increase_bridge_fee.go @@ -0,0 +1,130 @@ +package precompile + +import ( + "errors" + "fmt" + "math/big" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + + fxcontract "github.com/functionx/fx-core/v7/contract" + fxtypes "github.com/functionx/fx-core/v7/types" + crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" + evmtypes "github.com/functionx/fx-core/v7/x/evm/types" +) + +type IncreaseBridgeFeeMethod struct { + *Keeper + abi.Method + abi.Event +} + +func NewIncreaseBridgeFeeMethod(keeper *Keeper) *IncreaseBridgeFeeMethod { + return &IncreaseBridgeFeeMethod{ + Keeper: keeper, + Method: crosschaintypes.GetABI().Methods["increaseBridgeFee"], + Event: crosschaintypes.GetABI().Events["IncreaseBridgeFee"], + } +} + +func (m *IncreaseBridgeFeeMethod) IsReadonly() bool { + return false +} + +func (m *IncreaseBridgeFeeMethod) GetMethodId() []byte { + return m.Method.ID +} + +func (m *IncreaseBridgeFeeMethod) RequiredGas() uint64 { + return 40_000 +} + +func (m *IncreaseBridgeFeeMethod) Run(ctx sdk.Context, evm *vm.EVM, contract *vm.Contract) ([]byte, error) { + if m.router == nil { + return nil, errors.New("cross chain router empty") + } + + args, err := m.UnpackInput(contract.Input) + if err != nil { + return nil, err + } + + fxTarget := fxtypes.ParseFxTarget(args.Chain) + route, has := m.router.GetRoute(fxTarget.GetTarget()) + if !has { + return nil, fmt.Errorf("chain not support: %s", args.Chain) + } + + value := contract.Value() + sender := contract.Caller() + totalCoin := sdk.Coin{} + if value.Cmp(big.NewInt(0)) == 1 && fxcontract.IsZeroEthAddress(args.Token) { + if args.Fee.Cmp(value) != 0 { + return nil, errors.New("add bridge fee not equal msg.value") + } + totalCoin, err = m.handlerOriginToken(ctx, evm, sender, args.Fee) + if err != nil { + return nil, err + } + } else { + totalCoin, err = m.handlerERC20Token(ctx, evm, sender, args.Token, args.Fee) + if err != nil { + return nil, err + } + } + + // convert token to bridge fee token + feeCoin := sdk.NewCoin(totalCoin.Denom, sdkmath.NewIntFromBigInt(args.Fee)) + addBridgeFee, err := m.erc20Keeper.ConvertDenomToTarget(ctx, sender.Bytes(), feeCoin, fxTarget) + if err != nil { + return nil, err + } + + if err = route.PrecompileIncreaseBridgeFee(ctx, args.TxID.Uint64(), sender.Bytes(), addBridgeFee); err != nil { + return nil, err + } + + data, topic, err := m.NewIncreaseBridgeFeeEvent(sender, args.Token, args.Chain, args.TxID, args.Fee) + if err != nil { + return nil, err + } + EmitEvent(evm, data, topic) + + return m.PackOutput(true) +} + +func (m *IncreaseBridgeFeeMethod) NewIncreaseBridgeFeeEvent(sender common.Address, token common.Address, chain string, txId, fee *big.Int) (data []byte, topic []common.Hash, err error) { + data, topic, err = evmtypes.PackTopicData(m.Event, []common.Hash{sender.Hash(), token.Hash()}, chain, txId, fee) + if err != nil { + return nil, nil, err + } + return data, topic, nil +} + +func (m *IncreaseBridgeFeeMethod) PackInput(chainName string, txId *big.Int, token common.Address, fee *big.Int) ([]byte, error) { + data, err := m.Method.Inputs.Pack(chainName, txId, token, fee) + if err != nil { + return nil, err + } + return append(m.GetMethodId(), data...), nil +} + +func (m *IncreaseBridgeFeeMethod) UnpackInput(data []byte) (*crosschaintypes.IncreaseBridgeFeeArgs, error) { + args := new(crosschaintypes.IncreaseBridgeFeeArgs) + if err := evmtypes.ParseMethodArgs(m.Method, args, data[4:]); err != nil { + return nil, err + } + return args, nil +} + +func (m *IncreaseBridgeFeeMethod) PackOutput(success bool) ([]byte, error) { + pack, err := m.Method.Outputs.Pack(success) + if err != nil { + return nil, err + } + return pack, nil +} diff --git a/x/crosschain/precompile/increase_bridgefee.go b/x/crosschain/precompile/increase_bridgefee.go deleted file mode 100644 index a4f9df8b..00000000 --- a/x/crosschain/precompile/increase_bridgefee.go +++ /dev/null @@ -1,78 +0,0 @@ -package precompile - -import ( - "errors" - "fmt" - "math/big" - - sdkmath "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" - - "github.com/functionx/fx-core/v7/contract" - fxtypes "github.com/functionx/fx-core/v7/types" - crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" - evmtypes "github.com/functionx/fx-core/v7/x/evm/types" -) - -// IncreaseBridgeFee add bridge fee to unbatched tx -// -//gocyclo:ignore -func (c *Contract) IncreaseBridgeFee(ctx sdk.Context, evm *vm.EVM, contractAddr *vm.Contract, readonly bool) ([]byte, error) { - if readonly { - return nil, errors.New("increase bridge fee method not readonly") - } - if c.router == nil { - return nil, errors.New("cross chain router empty") - } - - var args crosschaintypes.IncreaseBridgeFeeArgs - err := evmtypes.ParseMethodArgs(crosschaintypes.IncreaseBridgeFeeMethod, &args, contractAddr.Input[4:]) - if err != nil { - return nil, err - } - - fxTarget := fxtypes.ParseFxTarget(args.Chain) - route, has := c.router.GetRoute(fxTarget.GetTarget()) - if !has { - return nil, fmt.Errorf("chain not support: %s", args.Chain) - } - - value := contractAddr.Value() - sender := contractAddr.Caller() - totalCoin := sdk.Coin{} - if value.Cmp(big.NewInt(0)) == 1 && contract.IsZeroEthAddress(args.Token) { - if args.Fee.Cmp(value) != 0 { - return nil, errors.New("add bridge fee not equal msg.value") - } - totalCoin, err = c.handlerOriginToken(ctx, evm, sender, args.Fee) - if err != nil { - return nil, err - } - } else { - totalCoin, err = c.handlerERC20Token(ctx, evm, sender, args.Token, args.Fee) - if err != nil { - return nil, err - } - } - - // convert token to bridge fee token - feeCoin := sdk.NewCoin(totalCoin.Denom, sdkmath.NewIntFromBigInt(args.Fee)) - addBridgeFee, err := c.erc20Keeper.ConvertDenomToTarget(ctx, sender.Bytes(), feeCoin, fxTarget) - if err != nil { - return nil, err - } - - if err = route.PrecompileIncreaseBridgeFee(ctx, args.TxID.Uint64(), sender.Bytes(), addBridgeFee); err != nil { - return nil, err - } - - // add event log - if err = c.AddLog(evm, crosschaintypes.IncreaseBridgeFeeEvent, []common.Hash{sender.Hash(), args.Token.Hash()}, - args.Chain, args.TxID, args.Fee); err != nil { - return nil, err - } - - return crosschaintypes.IncreaseBridgeFeeMethod.Outputs.Pack(true) -} diff --git a/x/evm/precompiles/tests/increase_bridgefee_test.go b/x/crosschain/precompile/increase_bridgefee_test.go similarity index 97% rename from x/evm/precompiles/tests/increase_bridgefee_test.go rename to x/crosschain/precompile/increase_bridgefee_test.go index fd12e102..5f46acab 100644 --- a/x/evm/precompiles/tests/increase_bridgefee_test.go +++ b/x/crosschain/precompile/increase_bridgefee_test.go @@ -1,4 +1,4 @@ -package tests_test +package precompile_test import ( "encoding/hex" @@ -20,22 +20,19 @@ import ( fxtypes "github.com/functionx/fx-core/v7/types" bsctypes "github.com/functionx/fx-core/v7/x/bsc/types" crosschainkeeper "github.com/functionx/fx-core/v7/x/crosschain/keeper" + "github.com/functionx/fx-core/v7/x/crosschain/precompile" crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" "github.com/functionx/fx-core/v7/x/erc20/types" ethtypes "github.com/functionx/fx-core/v7/x/eth/types" ) func TestIncreaseBridgeFeeABI(t *testing.T) { - crossChainABI := crosschaintypes.GetABI() + increaseBridgeFee := precompile.NewIncreaseBridgeFeeMethod(nil) - method := crossChainABI.Methods[crosschaintypes.IncreaseBridgeFeeMethodName] - require.Equal(t, method, crosschaintypes.IncreaseBridgeFeeMethod) - require.Equal(t, 4, len(method.Inputs)) - require.Equal(t, 1, len(method.Outputs)) + require.Equal(t, 4, len(increaseBridgeFee.Method.Inputs)) + require.Equal(t, 1, len(increaseBridgeFee.Method.Outputs)) - event := crossChainABI.Events[crosschaintypes.IncreaseBridgeFeeEventName] - require.Equal(t, event, crosschaintypes.IncreaseBridgeFeeEvent) - require.Equal(t, 5, len(event.Inputs)) + require.Equal(t, 5, len(increaseBridgeFee.Event.Inputs)) } //gocyclo:ignore @@ -427,7 +424,6 @@ func (suite *PrecompileTestSuite) TestIncreaseBridgeFee() { for _, tc := range testCases { suite.Run(fmt.Sprintf("Case %s", tc.name), func() { - suite.SetupTest() // reset signer := suite.RandSigner() // token pair md := suite.GenerateCrossChainDenoms() @@ -487,11 +483,12 @@ func (suite *PrecompileTestSuite) TestIncreaseBridgeFee() { } for _, log := range res.Logs { - if log.Topics[0] == crosschaintypes.IncreaseBridgeFeeEvent.ID.String() { + event := crosschaintypes.GetABI().Events["IncreaseBridgeFee"] + if log.Topics[0] == event.ID.String() { suite.Require().Equal(log.Address, crosschaintypes.GetAddress().String()) suite.Require().Equal(log.Topics[1], signer.Address().Hash().String()) suite.Require().Equal(log.Topics[2], pair.GetERC20Contract().Hash().String()) - unpack, err := crosschaintypes.IncreaseBridgeFeeEvent.Inputs.NonIndexed().Unpack(log.Data) + unpack, err := event.Inputs.NonIndexed().Unpack(log.Data) suite.Require().NoError(err) chain := unpack[0].(string) suite.Require().Equal(chain, moduleName) @@ -827,11 +824,12 @@ func (suite *PrecompileTestSuite) TestIncreaseBridgeFeeExternal() { } for _, log := range res.Logs { - if log.Topics[0] == crosschaintypes.IncreaseBridgeFeeEvent.ID.String() { + event := crosschaintypes.GetABI().Events["IncreaseBridgeFee"] + if log.Topics[0] == event.ID.String() { suite.Require().Equal(log.Address, crosschaintypes.GetAddress().String()) suite.Require().Equal(log.Topics[1], signer.Address().Hash().String()) suite.Require().Equal(log.Topics[2], pair.GetERC20Contract().Hash().String()) - unpack, err := crosschaintypes.IncreaseBridgeFeeEvent.Inputs.NonIndexed().Unpack(log.Data) + unpack, err := event.Inputs.NonIndexed().Unpack(log.Data) suite.Require().NoError(err) chain := unpack[0].(string) suite.Require().Equal(chain, moduleName) diff --git a/x/crosschain/precompile/keeper.go b/x/crosschain/precompile/keeper.go new file mode 100644 index 00000000..7296e3a2 --- /dev/null +++ b/x/crosschain/precompile/keeper.go @@ -0,0 +1,202 @@ +package precompile + +import ( + "errors" + "fmt" + "math/big" + "strings" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + transfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" + ibcclienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + evmtypes "github.com/evmos/ethermint/x/evm/types" + + "github.com/functionx/fx-core/v7/contract" + fxtypes "github.com/functionx/fx-core/v7/types" + crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types" + erc20types "github.com/functionx/fx-core/v7/x/erc20/types" +) + +type Keeper struct { + router *Router + bankKeeper BankKeeper + erc20Keeper Erc20Keeper + ibcTransferKeeper IBCTransferKeeper + accountKeeper AccountKeeper +} + +func (c *Keeper) handlerOriginToken(ctx sdk.Context, evm *vm.EVM, sender common.Address, amount *big.Int) (sdk.Coin, error) { + // NOTE: stateDB sub sender balance,but bank keeper not update. + // so mint token to crosschain, end of stateDB commit will sub balance from bank keeper. + // if only allow depth 1, the sender is origin sender, we can sub balance from bank keeper and not need burn/mint denom + evm.StateDB.SubBalance(crosschaintypes.GetAddress(), amount) + totalCoin := sdk.NewCoin(fxtypes.DefaultDenom, sdkmath.NewIntFromBigInt(amount)) + totalCoins := sdk.NewCoins(totalCoin) + + if err := c.bankKeeper.MintCoins(ctx, evmtypes.ModuleName, totalCoins); err != nil { + return sdk.Coin{}, err + } + if err := c.bankKeeper.SendCoinsFromModuleToAccount(ctx, evmtypes.ModuleName, sender.Bytes(), totalCoins); err != nil { + return sdk.Coin{}, err + } + return totalCoin, nil +} + +func (c *Keeper) handlerERC20Token(ctx sdk.Context, evm *vm.EVM, sender, token common.Address, amount *big.Int) (sdk.Coin, error) { + tokenPair, found := c.erc20Keeper.GetTokenPairByAddress(ctx, token) + if !found { + return sdk.Coin{}, fmt.Errorf("token pair not found: %s", token.String()) + } + baseDenom := tokenPair.GetDenom() + + // transferFrom to erc20 module + erc20Call := contract.NewERC20Call(evm, crosschaintypes.GetAddress(), token, 0) + if err := erc20Call.TransferFrom(sender, c.erc20Keeper.ModuleAddress(), amount); err != nil { + return sdk.Coin{}, err + } + if err := c.convertERC20(ctx, evm, tokenPair, sdk.NewCoin(baseDenom, sdkmath.NewIntFromBigInt(amount)), sender); err != nil { + return sdk.Coin{}, err + } + return sdk.NewCoin(baseDenom, sdkmath.NewIntFromBigInt(amount)), nil +} + +func (c *Keeper) convertERC20( + ctx sdk.Context, + evm *vm.EVM, + tokenPair erc20types.TokenPair, + amount sdk.Coin, + sender common.Address, +) error { + if tokenPair.IsNativeCoin() { + erc20Call := contract.NewERC20Call(evm, c.erc20Keeper.ModuleAddress(), tokenPair.GetERC20Contract(), 0) + err := erc20Call.Burn(c.erc20Keeper.ModuleAddress(), amount.Amount.BigInt()) + if err != nil { + return err + } + if tokenPair.GetDenom() == fxtypes.DefaultDenom { + // cache token contract balance + evm.StateDB.GetBalance(tokenPair.GetERC20Contract()) + + err = c.bankKeeper.SendCoinsFromAccountToModule(ctx, tokenPair.GetERC20Contract().Bytes(), erc20types.ModuleName, sdk.NewCoins(amount)) + if err != nil { + return err + } + + // evm stateDB sub token contract balance + evm.StateDB.SubBalance(tokenPair.GetERC20Contract(), amount.Amount.BigInt()) + } + + } else if tokenPair.IsNativeERC20() { + if err := c.bankKeeper.MintCoins(ctx, erc20types.ModuleName, sdk.NewCoins(amount)); err != nil { + return err + } + } else { + return erc20types.ErrUndefinedOwner + } + + if err := c.bankKeeper.SendCoinsFromModuleToAccount(ctx, erc20types.ModuleName, sender.Bytes(), sdk.NewCoins(amount)); err != nil { + return err + } + return nil +} + +// handlerCrossChain cross chain handler +// originToken is true represent cross chain denom(FX) +// when refund it, will not refund to evm token +// NOTE: fip20CrossChain only use for contract token, so origin token flag always false +func (c *Keeper) handlerCrossChain( + ctx sdk.Context, + from sdk.AccAddress, + receipt string, + amount, fee sdk.Coin, + fxTarget fxtypes.FxTarget, + memo string, + originToken bool, +) error { + total := sdk.NewCoin(amount.Denom, amount.Amount.Add(fee.Amount)) + // convert denom to target coin + targetCoin, err := c.erc20Keeper.ConvertDenomToTarget(ctx, from.Bytes(), total, fxTarget) + if err != nil && !erc20types.IsInsufficientLiquidityErr(err) { + return fmt.Errorf("convert denom: %s", err.Error()) + } + amount.Denom = targetCoin.Denom + fee.Denom = targetCoin.Denom + + if fxTarget.IsIBC() { + if err != nil { + return fmt.Errorf("convert denom: %s", err.Error()) + } + return c.ibcTransfer(ctx, from.Bytes(), receipt, amount, fee, fxTarget, memo, originToken) + } + + return c.outgoingTransfer(ctx, from.Bytes(), receipt, amount, fee, fxTarget, originToken, err != nil) +} + +func (c *Keeper) outgoingTransfer( + ctx sdk.Context, + from sdk.AccAddress, + to string, + amount, fee sdk.Coin, + fxTarget fxtypes.FxTarget, + originToken, insufficientLiquidit bool, +) error { + if c.router == nil { + return errors.New("cross chain router empty") + } + route, has := c.router.GetRoute(fxTarget.GetTarget()) + if !has { + return errors.New("invalid target") + } + if err := route.TransferAfter(ctx, from, to, amount, fee, originToken, insufficientLiquidit); err != nil { + return fmt.Errorf("cross chain error: %s", err.Error()) + } + return nil +} + +func (c *Keeper) ibcTransfer( + ctx sdk.Context, + from sdk.AccAddress, + to string, + amount, fee sdk.Coin, + fxTarget fxtypes.FxTarget, + memo string, + originToken bool, +) error { + if !fee.IsZero() { + return fmt.Errorf("ibc transfer fee must be zero: %s", fee.String()) + } + if strings.ToLower(fxTarget.Prefix) == contract.EthereumAddressPrefix { + if err := contract.ValidateEthereumAddress(to); err != nil { + return fmt.Errorf("invalid to address: %s", to) + } + } else { + if _, err := sdk.GetFromBech32(to, fxTarget.Prefix); err != nil { + return fmt.Errorf("invalid to address: %s", to) + } + } + + ibcTimeoutTimestamp := uint64(ctx.BlockTime().UnixNano()) + uint64(c.erc20Keeper.GetIbcTimeout(ctx)) + transferResponse, err := c.ibcTransferKeeper.Transfer(sdk.WrapSDKContext(ctx), + transfertypes.NewMsgTransfer( + fxTarget.SourcePort, + fxTarget.SourceChannel, + amount, + from.String(), + to, + ibcclienttypes.ZeroHeight(), + ibcTimeoutTimestamp, + memo, + ), + ) + if err != nil { + return fmt.Errorf("ibc transfer error: %s", err.Error()) + } + + if !originToken { + c.erc20Keeper.SetIBCTransferRelation(ctx, fxTarget.SourceChannel, transferResponse.GetSequence()) + } + return nil +} diff --git a/x/crosschain/types/methods.go b/x/crosschain/types/contract.go similarity index 61% rename from x/crosschain/types/methods.go rename to x/crosschain/types/contract.go index c75c8423..b07b1994 100644 --- a/x/crosschain/types/methods.go +++ b/x/crosschain/types/contract.go @@ -10,42 +10,6 @@ import ( "github.com/functionx/fx-core/v7/contract" ) -const ( - FIP20CrossChainGas = 40_000 // 80000 - 160000 - CrossChainGas = 40_000 // 70000 - 155000 - CancelSendToExternalGas = 30_000 // 70000 - 126000 - IncreaseBridgeFeeGas = 40_000 // 70000 - 140000 - BridgeCoinAmountFeeGas = 10_000 // 9000 - BridgeCallFeeGas = 50_000 // 50000 - - FIP20CrossChainMethodName = "fip20CrossChain" - CrossChainMethodName = "crossChain" - CancelSendToExternalMethodName = "cancelSendToExternal" - IncreaseBridgeFeeMethodName = "increaseBridgeFee" - BridgeCoinAmountMethodName = "bridgeCoinAmount" - BridgeCallMethodName = "bridgeCall" - - CrossChainEventName = "CrossChain" - CancelSendToExternalEventName = "CancelSendToExternal" - IncreaseBridgeFeeEventName = "IncreaseBridgeFee" - BridgeCallEventName = "BridgeCallEvent" -) - -const ( - // EventTypeRelayTransferCrossChain - // Deprecated - EventTypeRelayTransferCrossChain = "relay_transfer_cross_chain" - // EventTypeCrossChain new cross chain event type - EventTypeCrossChain = "cross_chain" - - AttributeKeyDenom = "coin" - AttributeKeyTokenAddress = "token_address" - AttributeKeyFrom = "from" - AttributeKeyRecipient = "recipient" - AttributeKeyTarget = "target" - AttributeKeyMemo = "memo" -) - var ( crossChainAddress = common.HexToAddress(contract.CrossChainAddress) crossChainABI = contract.MustABIJson(contract.ICrossChainMetaData.ABI) @@ -59,33 +23,11 @@ func GetABI() abi.ABI { return crossChainABI } -var ( - // BridgeCoinAmountMethod query the amount of bridge coin - BridgeCoinAmountMethod = GetABI().Methods[BridgeCoinAmountMethodName] - - // CancelSendToExternalMethod cancel send to external tx - CancelSendToExternalMethod = GetABI().Methods[CancelSendToExternalMethodName] - - // FIP20CrossChainMethod cross chain with FIP20 token, only for FIP20 token - // Deprecated: use CrossChainMethod instead - FIP20CrossChainMethod = GetABI().Methods[FIP20CrossChainMethodName] - - // CrossChainMethod cross chain with FIP20 token - CrossChainMethod = GetABI().Methods[CrossChainMethodName] - - // IncreaseBridgeFeeMethod increase bridge fee - IncreaseBridgeFeeMethod = GetABI().Methods[IncreaseBridgeFeeMethodName] - - // BridgeCallMethod bridge call other chain - BridgeCallMethod = GetABI().Methods[BridgeCallMethodName] -) - type BridgeCoinAmountArgs struct { Token common.Address `abi:"_token"` Target [32]byte `abi:"_target"` } -// Validate validates the args func (args *BridgeCoinAmountArgs) Validate() error { if args.Target == [32]byte{} { return errors.New("empty target") @@ -98,7 +40,6 @@ type CancelSendToExternalArgs struct { TxID *big.Int `abi:"_txID"` } -// Validate validates the args func (args *CancelSendToExternalArgs) Validate() error { if err := ValidateModuleName(args.Chain); err != nil { return err @@ -118,7 +59,6 @@ type FIP20CrossChainArgs struct { Memo string `abi:"_memo"` } -// Validate validates the args func (args *FIP20CrossChainArgs) Validate() error { if args.Receipt == "" { return errors.New("empty receipt") @@ -144,7 +84,6 @@ type CrossChainArgs struct { Memo string `abi:"_memo"` } -// Validate validates the args func (args *CrossChainArgs) Validate() error { if args.Receipt == "" { return errors.New("empty receipt") @@ -168,7 +107,6 @@ type IncreaseBridgeFeeArgs struct { Fee *big.Int `abi:"_fee"` } -// Validate validates the args func (args *IncreaseBridgeFeeArgs) Validate() error { if err := ValidateModuleName(args.Chain); err != nil { return err @@ -194,7 +132,6 @@ type BridgeCallArgs struct { Memo []byte `abi:"_memo"` } -// Validate validates the args func (args *BridgeCallArgs) Validate() error { if err := ValidateModuleName(args.DstChain); err != nil { return err diff --git a/x/crosschain/types/events.go b/x/crosschain/types/events.go index 04d225a7..9b9cb2f5 100644 --- a/x/crosschain/types/events.go +++ b/x/crosschain/types/events.go @@ -54,11 +54,3 @@ const ( EventTypeBridgeCallResult = "bridge_call_result" ) - -// precompile contract events -var ( - CancelSendToExternalEvent = GetABI().Events[CancelSendToExternalEventName] - CrossChainEvent = GetABI().Events[CrossChainEventName] - IncreaseBridgeFeeEvent = GetABI().Events[IncreaseBridgeFeeEventName] - BridgeCallEvent = GetABI().Events[BridgeCallEventName] -) diff --git a/x/evm/precompiles/tests/bridge_call_test.go b/x/evm/precompiles/tests/bridge_call_test.go deleted file mode 100644 index 8d441e10..00000000 --- a/x/evm/precompiles/tests/bridge_call_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package tests_test - -import ( - "testing" - - "github.com/stretchr/testify/require" - - "github.com/functionx/fx-core/v7/x/crosschain/types" -) - -func TestBridgeCallABI(t *testing.T) { - crosschainABI := types.GetABI() - - method := crosschainABI.Methods[types.BridgeCallMethodName] - require.Equal(t, method, types.BridgeCallMethod) - require.Equal(t, 8, len(types.BridgeCallMethod.Inputs)) - require.Equal(t, 1, len(types.BridgeCallMethod.Outputs)) -} diff --git a/x/evm/precompiles/tests/bridge_coin_amount_test.go b/x/evm/precompiles/tests/bridge_coin_amount_test.go deleted file mode 100644 index b0a5e7bb..00000000 --- a/x/evm/precompiles/tests/bridge_coin_amount_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package tests_test - -import ( - "fmt" - "math/big" - "testing" - - sdkmath "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - evmtypes "github.com/evmos/ethermint/x/evm/types" - "github.com/stretchr/testify/require" - tmrand "github.com/tendermint/tendermint/libs/rand" - - "github.com/functionx/fx-core/v7/testutil/helpers" - fxtypes "github.com/functionx/fx-core/v7/types" - "github.com/functionx/fx-core/v7/x/crosschain/types" - erc20types "github.com/functionx/fx-core/v7/x/erc20/types" -) - -func TestBridgeCoinAmountABI(t *testing.T) { - crosschainABI := types.GetABI() - - method := crosschainABI.Methods[types.BridgeCoinAmountMethodName] - require.Equal(t, method, types.BridgeCoinAmountMethod) - require.Equal(t, 2, len(types.BridgeCoinAmountMethod.Inputs)) - require.Equal(t, 1, len(types.BridgeCoinAmountMethod.Outputs)) -} - -func (suite *PrecompileTestSuite) TestBridgeCoinAmount() { - prepareFunc := func() (Metadata, *erc20types.TokenPair, *big.Int) { - // token pair - md := suite.GenerateCrossChainDenoms() - pair, err := suite.app.Erc20Keeper.RegisterNativeCoin(suite.ctx, md.GetMetadata()) - suite.Require().NoError(err) - randMint := big.NewInt(int64(tmrand.Uint32() + 10)) - suite.MintLockNativeTokenToModule(md.GetMetadata(), sdkmath.NewIntFromBigInt(randMint)) - return md, pair, randMint - } - testCases := []struct { - name string - prepare func() (Metadata, *erc20types.TokenPair, *big.Int) - malleate func(token common.Address, target string) ([]byte, []string) - error func(args []string) string - result bool - }{ - { - name: "ok", - prepare: prepareFunc, - malleate: func(token common.Address, target string) ([]byte, []string) { - pack, err := types.GetABI().Pack(types.BridgeCoinAmountMethodName, token, fxtypes.MustStrToByte32(target)) - suite.Require().NoError(err) - return pack, nil - }, - result: true, - }, - { - name: "failed - invalid target", - prepare: prepareFunc, - malleate: func(token common.Address, target string) ([]byte, []string) { - pack, err := types.GetABI().Pack(types.BridgeCoinAmountMethodName, token, fxtypes.MustStrToByte32("")) - suite.Require().NoError(err) - return pack, nil - }, - error: func(args []string) string { - return "empty target" - }, - result: false, - }, - { - name: "failed - invalid token", - prepare: prepareFunc, - malleate: func(_ common.Address, target string) ([]byte, []string) { - token := helpers.GenHexAddress() - pack, err := types.GetABI().Pack(types.BridgeCoinAmountMethodName, token, fxtypes.MustStrToByte32(target)) - suite.Require().NoError(err) - return pack, []string{token.String()} - }, - error: func(args []string) string { - return fmt.Sprintf("token not support: %s", args[0]) - }, - result: false, - }, - } - for _, tc := range testCases { - suite.Run(fmt.Sprintf("Case %s", tc.name), func() { - suite.SetupTest() // reset - signer := suite.RandSigner() - // prepare - md, pair, randMint := tc.prepare() - // malleate - packData, errArgs := tc.malleate(pair.GetERC20Contract(), md.RandModule()) - tx, err := suite.PackEthereumTx(signer, types.GetAddress(), big.NewInt(0), packData) - var res *evmtypes.MsgEthereumTxResponse - if err == nil { - res, err = suite.app.EvmKeeper.EthereumTx(sdk.WrapSDKContext(suite.ctx), tx) - } - // check result - if tc.result { - suite.Require().NoError(err) - suite.Require().False(res.Failed(), res.VmError) - unpack, err := types.BridgeCoinAmountMethod.Outputs.Unpack(res.Ret) - suite.Require().NoError(err) - shares := unpack[0].(*big.Int) - suite.Require().Equal(shares.String(), randMint.String()) - } else { - suite.Require().True(err != nil || res.Failed()) - if err != nil { - suite.Require().EqualError(err, tc.error(errArgs)) - } - } - }) - } -}