Skip to content

Commit

Permalink
refactor: crosschain precmpile contract (#579)
Browse files Browse the repository at this point in the history
  • Loading branch information
zakir-code authored Jun 25, 2024
1 parent 7b5e453 commit 3c4f227
Show file tree
Hide file tree
Showing 24 changed files with 1,047 additions and 804 deletions.
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),
},
)
}

// 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"
)
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))
}

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

0 comments on commit 3c4f227

Please sign in to comment.