Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: crosschain precmpile contract #579

Merged
merged 1 commit into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions contract/precompile.go
Original file line number Diff line number Diff line change
@@ -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)
}
5 changes: 3 additions & 2 deletions tests/precompile_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
66 changes: 66 additions & 0 deletions x/crosschain/legacy/precompile_events.go
Original file line number Diff line number Diff line change
@@ -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),
},
)
}
Comment on lines +14 to +35
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

Deprecated function usage

The function Fip20CrossChainEvents is marked as deprecated but is still in use in x/crosschain/precompile/fip20_crosschain.go. Consider replacing it with the CrossChainEvents function found in the same file, if it serves the same purpose. This will help in cleaning up the legacy code.

  • File: x/crosschain/precompile/fip20_crosschain.go
  • Line: legacy.Fip20CrossChainEvents(ctx, args.Sender, tokenPair.GetERC20Contract(), args.Receipt, fxtypes.Byte32ToString(args.Target), tokenPair.GetDenom(), args.Amount, args.Fee)
Analysis chain

Deprecated function usage

The function Fip20CrossChainEvents is marked as deprecated. Ensure that it is no longer used in the codebase or consider removing it to clean up the legacy code.

Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify that the deprecated function `Fip20CrossChainEvents` is not used anywhere else in the codebase.

# Test: Search for the function usage. Expect: No occurrences outside this file.
rg --type go $'Fip20CrossChainEvents'

Length of output: 434



Script:

#!/bin/bash
# Description: Gather the surrounding lines of the usage of the deprecated function `Fip20CrossChainEvents` in `x/crosschain/precompile/fip20_crosschain.go`.

# Test: Extract 10 lines before and after the usage for context.
rg --type go --context 10 $'Fip20CrossChainEvents' x/crosschain/precompile/fip20_crosschain.go

Length of output: 943



Script:

#!/bin/bash
# Description: Search for potential alternative functions to `Fip20CrossChainEvents` in the codebase.

# Test: Look for other functions related to cross-chain events.
rg --type go 'func .*CrossChainEvents' 

Length of output: 381


// 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),
))
}
Comment on lines +39 to +51
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tip

Codebase Verification

Deprecated function check

The CrossChainEvents function is still in use within x/crosschain/precompile/crosschain.go. Therefore, it cannot be removed or archived at this time. Consider marking the function as deprecated and updating the documentation to reflect its status.

  • x/crosschain/precompile/crosschain.go: legacy.CrossChainEvents(ctx, sender, args.Token, args.Receipt, fxtypes.Byte32ToString(args.Target), ...
Analysis chain

Deprecated function check

The CrossChainEvents function is also deprecated. Verify its usage across the codebase and consider removing or archiving if it is no longer needed.

Scripts executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Description: Verify that the deprecated function `CrossChainEvents` is not used elsewhere in the codebase.

# Test: Search for the function usage. Expect: No occurrences outside this file.
rg --type go $'CrossChainEvents'

Length of output: 800


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"
)
82 changes: 68 additions & 14 deletions x/crosschain/precompile/bridge_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,51 @@ 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"

crosschaintypes "github.com/functionx/fx-core/v7/x/crosschain/types"
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")
}
Expand All @@ -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
}
Expand All @@ -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,
Expand All @@ -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
}
39 changes: 26 additions & 13 deletions x/crosschain/precompile/bridge_call_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Comment on lines +16 to +21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improve Test Coverage

The tests for BridgeCallMethod are basic and only check the count of inputs and outputs. Consider adding more comprehensive tests that cover different scenarios and edge cases.

+ func TestBridgeCallMethod_ErrorHandling(t *testing.T) {
+   _, err := bridgeCall.Run(ctx, nil, nil)
+   require.Error(t, err, "should handle nil contexts and contracts")
+ }

Committable suggestion was skipped due to low confidence.


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
Expand Down Expand Up @@ -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)
Expand All @@ -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(),
Expand Down
Loading