Skip to content

Commit

Permalink
feat: attestation bridge call claim (#211)
Browse files Browse the repository at this point in the history
Co-authored-by: nulnut <151493716+nulnut@users.noreply.github.com>
  • Loading branch information
zakir-code and nulnut committed Feb 19, 2024
1 parent bb1945b commit 38ca328
Show file tree
Hide file tree
Showing 14 changed files with 572 additions and 18 deletions.
8 changes: 8 additions & 0 deletions app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ func NewAppKeeper(
appKeepers.IBCTransferKeeper,
appKeepers.Erc20Keeper,
appKeepers.AccountKeeper,
appKeepers.EvmKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
appKeepers.PolygonKeeper = crosschainkeeper.NewKeeper(
Expand All @@ -359,6 +360,7 @@ func NewAppKeeper(
appKeepers.IBCTransferKeeper,
appKeepers.Erc20Keeper,
appKeepers.AccountKeeper,
appKeepers.EvmKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
appKeepers.AvalancheKeeper = crosschainkeeper.NewKeeper(
Expand All @@ -372,6 +374,7 @@ func NewAppKeeper(
appKeepers.IBCTransferKeeper,
appKeepers.Erc20Keeper,
appKeepers.AccountKeeper,
appKeepers.EvmKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
appKeepers.EthKeeper = crosschainkeeper.NewKeeper(
Expand All @@ -385,6 +388,7 @@ func NewAppKeeper(
appKeepers.IBCTransferKeeper,
appKeepers.Erc20Keeper,
appKeepers.AccountKeeper,
appKeepers.EvmKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
appKeepers.ArbitrumKeeper = crosschainkeeper.NewKeeper(
Expand All @@ -398,6 +402,7 @@ func NewAppKeeper(
appKeepers.IBCTransferKeeper,
appKeepers.Erc20Keeper,
appKeepers.AccountKeeper,
appKeepers.EvmKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
appKeepers.OptimismKeeper = crosschainkeeper.NewKeeper(
Expand All @@ -411,6 +416,7 @@ func NewAppKeeper(
appKeepers.IBCTransferKeeper,
appKeepers.Erc20Keeper,
appKeepers.AccountKeeper,
appKeepers.EvmKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
appKeepers.Layer2Keeper = crosschainkeeper.NewKeeper(
Expand All @@ -424,6 +430,7 @@ func NewAppKeeper(
appKeepers.IBCTransferKeeper,
appKeepers.Erc20Keeper,
appKeepers.AccountKeeper,
appKeepers.EvmKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)
appKeepers.TronKeeper = tronkeeper.NewKeeper(crosschainkeeper.NewKeeper(
Expand All @@ -437,6 +444,7 @@ func NewAppKeeper(
appKeepers.IBCTransferKeeper,
appKeepers.Erc20Keeper,
appKeepers.AccountKeeper,
appKeepers.EvmKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
), appKeepers.Erc20Keeper)

Expand Down
16 changes: 16 additions & 0 deletions testutil/helpers/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import (
"github.com/evmos/ethermint/crypto/ethsecp256k1"
hd2 "github.com/evmos/ethermint/crypto/hd"
tronaddress "github.com/fbsobreira/gotron-sdk/pkg/address"

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

func NewMnemonic() string {
Expand Down Expand Up @@ -103,6 +105,20 @@ func GenerateAddressByModule(module string) string {
return addr.String()
}

func AddressToBytesByModule(addr, module string) ([]byte, error) {
if module == "tron" {
tronAddr, err := tronaddress.Base58ToAddress(addr)
if err != nil {
return nil, err
}
return tronAddr.Bytes(), nil
}
if err := fxtypes.ValidateEthereumAddress(addr); err != nil {
return nil, err
}
return common.HexToAddress(addr).Bytes(), nil
}

// GenerateZeroAddressByModule generates an Ethereum or Tron zero address.
func GenerateZeroAddressByModule(module string) string {
addr := common.HexToAddress(common.Address{}.Hex())
Expand Down
12 changes: 12 additions & 0 deletions x/crosschain/keeper/attestation_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,18 @@ func (k Keeper) AttestationHandler(ctx sdk.Context, externalClaim types.External
return nil
}

case *types.MsgBridgeCallClaim:
assetType, assetData, err := types.UnpackAssetType(claim.Asset)
if err != nil {
return errorsmod.Wrap(types.ErrInvalid, "asset")
}
switch assetType {
case types.AssetERC20:
return k.bridgeCallERC20Handler(ctx, assetData, claim.MustSenderBytes(), claim.MustToBytes(),
claim.MustReceiverBytes(), claim.DstChainId, claim.Message, claim.Value, claim.GasLimit, claim.EventNonce)
default:
return errorsmod.Wrap(types.ErrInvalid, "asset not support")
}
case *types.MsgSendToExternalClaim:
k.OutgoingTxBatchExecuted(ctx, claim.TokenContract, claim.BatchNonce)

Expand Down
4 changes: 3 additions & 1 deletion x/crosschain/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Keeper struct {
bankKeeper types.BankKeeper
ibcTransferKeeper types.IBCTransferKeeper
erc20Keeper types.Erc20Keeper
evmKeeper types.EVMKeeper

authority string
}
Expand All @@ -34,7 +35,7 @@ type Keeper struct {
func NewKeeper(cdc codec.BinaryCodec, moduleName string, storeKey storetypes.StoreKey,
stakingKeeper types.StakingKeeper, stakingMsgServer types.StakingMsgServer, distributionKeeper types.DistributionMsgServer,
bankKeeper types.BankKeeper, ibcTransferKeeper types.IBCTransferKeeper, erc20Keeper types.Erc20Keeper, ak types.AccountKeeper,
authority string,
evmKeeper types.EVMKeeper, authority string,
) Keeper {
if addr := ak.GetModuleAddress(moduleName); addr == nil {
panic(fmt.Sprintf("%s module account has not been set", moduleName))
Expand All @@ -51,6 +52,7 @@ func NewKeeper(cdc codec.BinaryCodec, moduleName string, storeKey storetypes.Sto
bankKeeper: bankKeeper,
ibcTransferKeeper: ibcTransferKeeper,
erc20Keeper: erc20Keeper,
evmKeeper: evmKeeper,
authority: authority,
}
}
Expand Down
64 changes: 54 additions & 10 deletions x/crosschain/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"encoding/hex"
"fmt"
"math"
"math/big"
"sort"
"testing"

errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
Expand All @@ -20,6 +22,7 @@ import (

"github.com/functionx/fx-core/v7/testutil/helpers"
"github.com/functionx/fx-core/v7/x/crosschain/types"
erc20types "github.com/functionx/fx-core/v7/x/erc20/types"
ethtypes "github.com/functionx/fx-core/v7/x/eth/types"
trontypes "github.com/functionx/fx-core/v7/x/tron/types"
)
Expand Down Expand Up @@ -1111,10 +1114,50 @@ func (suite *KeeperTestSuite) TestBridgeCallClaim() {
oracleLastEventNonce := suite.Keeper().GetLastEventNonceByOracle(suite.ctx, suite.oracleAddrs[0])
require.EqualValues(suite.T(), 0, oracleLastEventNonce)

sender := helpers.GenerateAddress().String()
if suite.chainName == trontypes.ModuleName {
sender = trontypes.AddressFromHex(sender)
}
tokenContract := helpers.GenerateAddressByModule(suite.chainName)
_, err = suite.MsgServer().BridgeTokenClaim(sdk.WrapSDKContext(suite.ctx), &types.MsgBridgeTokenClaim{
EventNonce: oracleLastEventNonce + 1,
BlockHeight: uint64(suite.ctx.BlockHeight()),
TokenContract: tokenContract,
Name: "Token Test",
Symbol: "TTT",
Decimals: 18,
BridgerAddress: suite.bridgerAddrs[0].String(),
ChannelIbc: "",
ChainName: suite.chainName,
})
require.NoError(suite.T(), err)

oracleLastEventNonce = suite.Keeper().GetLastEventNonceByOracle(suite.ctx, suite.oracleAddrs[0])
require.EqualValues(suite.T(), 1, oracleLastEventNonce)

_, err = suite.app.Erc20Keeper.RegisterCoin(sdk.WrapSDKContext(suite.ctx), &erc20types.MsgRegisterCoin{
Authority: authtypes.NewModuleAddress(govtypes.ModuleName).String(),
Metadata: banktypes.Metadata{
Description: "Test token",
DenomUnits: []*banktypes.DenomUnit{
{
Denom: "ttt",
Exponent: 0,
Aliases: []string{fmt.Sprintf("%s%s", suite.chainName, tokenContract)},
},
{
Denom: "TTT",
Exponent: 18,
},
},
Base: "ttt",
Display: "TTT",
Name: "Test Token",
Symbol: "TTT",
},
})
require.NoError(suite.T(), err)

tokenAddrBytes, err := helpers.AddressToBytesByModule(tokenContract, suite.chainName)
require.NoError(suite.T(), err)
assetBytes, err := types.PackERC20Asset([][]byte{tokenAddrBytes}, []*big.Int{big.NewInt(100)})
require.NoError(suite.T(), err)

testMsgs := []struct {
name string
Expand All @@ -1125,14 +1168,15 @@ func (suite *KeeperTestSuite) TestBridgeCallClaim() {
{
name: "success",
msg: &types.MsgBridgeCallClaim{
DstChainId: "123",
DstChainId: "530",
EventNonce: oracleLastEventNonce + 1,
Sender: sender,
Asset: "123",
Receiver: helpers.GenerateAddress().String(),
To: helpers.GenerateAddress().String(),
Message: "123",
Sender: helpers.GenerateAddressByModule(suite.chainName),
Asset: hex.EncodeToString(assetBytes),
Receiver: helpers.GenerateAddressByModule(suite.chainName),
To: helpers.GenerateAddressByModule(suite.chainName),
Message: "",
Value: sdkmath.NewInt(0),
GasLimit: 3000000,
BlockHeight: 1,
BridgerAddress: suite.bridgerAddrs[0].String(),
ChainName: suite.chainName,
Expand Down
103 changes: 103 additions & 0 deletions x/crosschain/keeper/relay_transfer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ package keeper
import (
"encoding/hex"
"fmt"
"math/big"
"strconv"
"strings"
"time"

errorsmod "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/bech32"
Expand Down Expand Up @@ -103,3 +106,103 @@ func (k Keeper) transferIBCHandler(ctx sdk.Context, eventNonce uint64, receive s
))
return err
}

func (k Keeper) bridgeCallERC20Handler(
ctx sdk.Context,
asset, sender, to, receiver []byte,
dstChainID, message string,
value sdkmath.Int,
gasLimit, eventNonce uint64,
) error {
tokens, amounts, err := types.UnpackERC20Asset(asset)
if err != nil {
return errorsmod.Wrap(types.ErrInvalid, "asset erc20")
}
senderAddr := common.BytesToAddress(sender)
targetCoins, err := k.bridgeCallTargetCoinsHandler(ctx, senderAddr, tokens, amounts)
if err != nil {
return err
}

switch dstChainID {
case types.FxcoreChainID:
// convert coin to erc20
for _, coin := range targetCoins {
// not convert FX
if coin.Denom == fxtypes.DefaultDenom {
continue
}
if err = k.transferErc20Handler(ctx, eventNonce, receiver, coin); err != nil {
return err
}
}
var toAddrPtr *common.Address
if len(to) > 0 {
toAddr := common.BytesToAddress(to)
toAddrPtr = &toAddr
}
if len(message) > 0 || toAddrPtr != nil {
k.bridgeCallEvmHandler(ctx, senderAddr, toAddrPtr, message, value, gasLimit, eventNonce)
}
default:
// not support chain, refund
}
// todo refund asset

return nil
}

func (k Keeper) bridgeCallTargetCoinsHandler(ctx sdk.Context, receiver common.Address, tokens []common.Address, amounts []*big.Int) (sdk.Coins, error) {
tokens, amounts = types.MergeDuplicationERC20(tokens, amounts)
targetCoins := sdk.NewCoins()
for i := 0; i < len(tokens); i++ {
bridgeToken := k.GetBridgeTokenDenom(ctx, tokens[i].String())
if bridgeToken == nil {
return nil, errorsmod.Wrap(types.ErrInvalid, "bridge token is not exist")
}
coin := sdk.NewCoin(bridgeToken.Denom, sdkmath.NewIntFromBigInt(amounts[i]))
isOriginOrConverted := k.erc20Keeper.IsOriginOrConvertedDenom(ctx, bridgeToken.Denom)
if !isOriginOrConverted {
if err := k.bankKeeper.MintCoins(ctx, k.moduleName, sdk.NewCoins(coin)); err != nil {
return nil, errorsmod.Wrapf(err, "mint vouchers coins")
}
}
if err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, k.moduleName, receiver.Bytes(), sdk.NewCoins(coin)); err != nil {
return nil, errorsmod.Wrap(err, "transfer vouchers")
}

targetCoin, err := k.erc20Keeper.ConvertDenomToTarget(ctx, receiver.Bytes(), coin, fxtypes.ParseFxTarget(fxtypes.ERC20Target))
if err != nil {
return nil, errorsmod.Wrap(err, "convert to target coin")
}
targetCoins = targetCoins.Add(targetCoin)
}
return targetCoins, nil
}

func (k Keeper) bridgeCallEvmHandler(
ctx sdk.Context,
sender common.Address,
to *common.Address,
message string, value sdkmath.Int,
gasLimit, eventNonce uint64,
) {
cacheCtx, commit := ctx.CacheContext()
txResp, err := k.evmKeeper.CallEVM(cacheCtx, sender, to, value.BigInt(), gasLimit, fxtypes.MustDecodeHex(message), true)
if err != nil {
k.Logger(ctx).Error("bridge call evm error", "nonce", eventNonce, "error", err.Error())
attrs := []sdk.Attribute{
sdk.NewAttribute(types.AttributeKeyEvmCallResult, strconv.FormatBool(false)),
sdk.NewAttribute(types.AttributeKeyEvmCallError, err.Error()),
}
ctx.EventManager().EmitEvents(sdk.Events{sdk.NewEvent(types.EventTypeBridgeCallEvm, attrs...)})
return
}
// whether the tx succeeds or fails, commit with tx logs
commit()
attrs := []sdk.Attribute{sdk.NewAttribute(types.AttributeKeyEvmCallResult, strconv.FormatBool(!txResp.Failed()))}
if txResp.Failed() {
attrs = append(attrs, sdk.NewAttribute(types.AttributeKeyEvmCallError, txResp.VmError))
}
ctx.EventManager().EmitEvents(sdk.Events{sdk.NewEvent(types.EventTypeBridgeCallEvm, attrs...)})
}
Loading

0 comments on commit 38ca328

Please sign in to comment.