Skip to content

Commit

Permalink
feat: add refund timeout (FunctionX#231)
Browse files Browse the repository at this point in the history
  • Loading branch information
nulnut committed Mar 14, 2024
1 parent 6404cff commit ea00e46
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 16 deletions.
42 changes: 42 additions & 0 deletions x/crosschain/keeper/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func (k Keeper) EndBlocker(ctx sdk.Context) {
signedWindow := k.GetSignedWindow(ctx)
k.slashing(ctx, signedWindow)
k.cleanupTimedOutBatches(ctx)
k.cleanupTimeOutRefund(ctx)
k.createOracleSetRequest(ctx)
k.pruneOracleSet(ctx, signedWindow)
}
Expand Down Expand Up @@ -146,6 +147,47 @@ func (k Keeper) cleanupTimedOutBatches(ctx sdk.Context) {
})
}

func (k Keeper) cleanupTimeOutRefund(ctx sdk.Context) {
externalBlockHeight := k.GetLastObservedBlockHeight(ctx).ExternalBlockHeight
k.IterRefundRecord(ctx, func(record *types.RefundRecord) bool {
if record.Timeout > externalBlockHeight {
return true
}
receiver, coins, err := k.refundTokenToReceiver(ctx, record)
if err != nil {
k.Logger(ctx).Error("clean up refund timeout", "event nonce", record.EventNonce, "error", err.Error())
return false
}
k.DeleteRefundRecord(ctx, record)
k.DeleteRefundConfirm(ctx, record.EventNonce)
k.RemoveEventSnapshotOracle(ctx, record.OracleSetNonce, record.EventNonce)

ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(types.EventTypeRefundTimeout,
sdk.NewAttribute(sdk.AttributeKeySender, record.Receiver),
sdk.NewAttribute(types.AttributeKeyRefundAddress, receiver.String()),
sdk.NewAttribute(types.AttributeKeyEventNonce, fmt.Sprint(record.EventNonce)),
sdk.NewAttribute(sdk.AttributeKeyAmount, coins.String()),
),
})
return false
})
}

func (k Keeper) refundTokenToReceiver(ctx sdk.Context, record *types.RefundRecord) (sdk.AccAddress, sdk.Coins, error) {
receiverAddr := types.ExternalAddressToAccAddress(k.moduleName, record.Receiver)
cacheCtx, commit := ctx.CacheContext()
coins, err := k.bridgeCallTransferToSender(cacheCtx, receiverAddr.Bytes(), record.Tokens)
if err != nil {
return nil, nil, err
}
if err = k.bridgeCallTransferToReceiver(cacheCtx, receiverAddr, receiverAddr.Bytes(), coins); err != nil {
return nil, nil, err
}
commit()
return receiverAddr, coins, nil
}

func (k Keeper) pruneOracleSet(ctx sdk.Context, signedOracleSetsWindow uint64) {
// Oracle set pruning
// prune all Oracle sets with a nonce less than the
Expand Down
118 changes: 118 additions & 0 deletions x/crosschain/keeper/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ package keeper_test
import (
"encoding/hex"
"fmt"
"math/big"

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/common"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"

"github.com/functionx/fx-core/v7/testutil/helpers"
fxtypes "github.com/functionx/fx-core/v7/types"
Expand Down Expand Up @@ -556,3 +561,116 @@ func (suite *KeeperTestSuite) TestSlashOracle() {
require.Equal(suite.T(), int64(1), oracle.SlashTimes)
}
}

func (suite *KeeperTestSuite) TestCleanUpRefundTimeout() {
normalMsg := &types.MsgBondedOracle{
OracleAddress: suite.oracleAddrs[0].String(),
BridgerAddress: suite.bridgerAddrs[0].String(),
ExternalAddress: suite.PubKeyToExternalAddr(suite.externalPris[0].PublicKey),
ValidatorAddress: suite.valAddrs[0].String(),
DelegateAmount: types.NewDelegateAmount(sdkmath.NewInt(10 * 1e3).MulRaw(1e18)),
ChainName: suite.chainName,
}
_, err := suite.MsgServer().BondedOracle(sdk.WrapSDKContext(suite.ctx), normalMsg)
require.NoError(suite.T(), err)

suite.Commit()

bridgeToken := helpers.GenerateAddressByModule(suite.chainName)
addBridgeTokenClaim := &types.MsgBridgeTokenClaim{
EventNonce: 1,
BlockHeight: 1000,
TokenContract: bridgeToken,
Name: "Test Token",
Symbol: "TEST",
Decimals: 18,
BridgerAddress: suite.bridgerAddrs[0].String(),
ChannelIbc: hex.EncodeToString([]byte("transfer/channel-0")),
ChainName: suite.chainName,
}
_, err = suite.MsgServer().BridgeTokenClaim(sdk.WrapSDKContext(suite.ctx), addBridgeTokenClaim)
require.NoError(suite.T(), err)

denomResp, err := suite.QueryClient().TokenToDenom(sdk.WrapSDKContext(suite.ctx), &types.QueryTokenToDenomRequest{
ChainName: suite.chainName,
Token: bridgeToken,
})
suite.NoError(err)

_, err = suite.app.Erc20Keeper.RegisterNativeCoin(suite.ctx, banktypes.Metadata{
Description: "Function X cross chain token",
DenomUnits: []*banktypes.DenomUnit{
{
Denom: "test",
Exponent: 0,
Aliases: []string{denomResp.Denom},
},
{
Denom: "TEST",
Exponent: 18,
Aliases: nil,
},
},
Base: "test",
Display: "TEST",
Name: "Test Token",
Symbol: "TEST",
})
suite.NoError(err)

suite.Commit()

tokenAddr := common.BytesToAddress(types.ExternalAddressToAccAddress(suite.chainName, bridgeToken).Bytes())
asset, err := types.PackERC20AssetWithType([]common.Address{tokenAddr}, []*big.Int{big.NewInt(1)})
suite.NoError(err)

bridgeCallClaim := &types.MsgBridgeCallClaim{
DstChainId: types.FxcoreChainID,
EventNonce: 2,
Sender: helpers.GenerateAddressByModule(suite.chainName),
Receiver: helpers.GenerateAddressByModule(suite.chainName),
Asset: asset,
To: helpers.GenerateAddressByModule(suite.chainName),
Message: hex.EncodeToString([]byte{0x1}),
Value: sdkmath.NewInt(1),
GasLimit: 3000000,
BlockHeight: 1001,
BridgerAddress: suite.bridgerAddrs[0].String(),
ChainName: suite.chainName,
}
_, err = suite.MsgServer().BridgeCallClaim(sdk.WrapSDKContext(suite.ctx), bridgeCallClaim)
suite.NoError(err)

recordExist := false
for _, event := range suite.ctx.EventManager().Events() {
if event.Type == types.EventTypeBridgeCallRefund {
recordExist = true
}
}
suite.True(recordExist)
refundRecord, err := suite.QueryClient().RefundRecordByNonce(sdk.WrapSDKContext(suite.ctx), &types.QueryRefundRecordByNonceRequest{ChainName: suite.chainName, EventNonce: 2})
suite.NoError(err)
suite.Equal(uint64(2), refundRecord.Record.EventNonce)

suite.Commit()

sendToFxSendAddr := helpers.GenerateAddressByModule(suite.chainName)
sendToFxClaim := &types.MsgSendToFxClaim{
EventNonce: 3,
BlockHeight: refundRecord.Record.Timeout + 1,
TokenContract: bridgeToken,
Amount: sdkmath.NewInt(1234),
Sender: sendToFxSendAddr,
Receiver: sdk.AccAddress(helpers.GenerateAddress().Bytes()).String(),
TargetIbc: hex.EncodeToString([]byte("px/transfer/channel-0")),
BridgerAddress: suite.bridgerAddrs[0].String(),
ChainName: suite.chainName,
}
_, err = suite.MsgServer().SendToFxClaim(sdk.WrapSDKContext(suite.ctx), sendToFxClaim)
require.NoError(suite.T(), err)

suite.Commit()

_, err = suite.QueryClient().RefundRecordByNonce(sdk.WrapSDKContext(suite.ctx), &types.QueryRefundRecordByNonceRequest{ChainName: suite.chainName, EventNonce: 2})
suite.ErrorIs(err, status.Error(codes.NotFound, "refund record"), suite.chainName)
}
36 changes: 20 additions & 16 deletions x/crosschain/keeper/bridge_call_refund.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,7 @@ func (k Keeper) HandleRefundTokenClaim(ctx sdk.Context, claim *types.MsgRefundTo
k.DeleteRefundConfirm(ctx, claim.RefundNonce)

// 3. delete snapshot oracle event nonce or snapshot oracle
oracle, found := k.GetSnapshotOracle(ctx, record.OracleSetNonce)
if !found {
return
}

for i, nonce := range oracle.EventNonces {
if nonce == claim.RefundNonce {
oracle.EventNonces = append(oracle.EventNonces[:i], oracle.EventNonces[i+1:]...)
break
}
}
if len(oracle.EventNonces) == 0 {
k.DeleteSnapshotOracle(ctx, record.OracleSetNonce)
} else {
k.SetSnapshotOracle(ctx, oracle)
}
k.RemoveEventSnapshotOracle(ctx, record.OracleSetNonce, claim.RefundNonce)
}

func (k Keeper) AddRefundRecord(ctx sdk.Context, receiver string, eventNonce uint64, tokens []types.ERC20Token) error {
Expand Down Expand Up @@ -144,6 +129,25 @@ func (k Keeper) DeleteSnapshotOracle(ctx sdk.Context, nonce uint64) {
store.Delete(types.GetSnapshotOracleKey(nonce))
}

func (k Keeper) RemoveEventSnapshotOracle(ctx sdk.Context, oracleNonce, eventNonce uint64) {
oracle, found := k.GetSnapshotOracle(ctx, oracleNonce)
if !found {
return
}

for i, nonce := range oracle.EventNonces {
if nonce == eventNonce {
oracle.EventNonces = append(oracle.EventNonces[:i], oracle.EventNonces[i+1:]...)
break
}
}
if len(oracle.EventNonces) == 0 {
k.DeleteSnapshotOracle(ctx, oracleNonce)
} else {
k.SetSnapshotOracle(ctx, oracle)
}
}

func (k Keeper) GetRefundConfirm(ctx sdk.Context, nonce uint64, addr sdk.AccAddress) (*types.MsgConfirmRefund, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.GetRefundConfirmKey(nonce, addr))
Expand Down
11 changes: 11 additions & 0 deletions x/crosschain/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"testing"

sdkmath "cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/crypto"
tronaddress "github.com/fbsobreira/gotron-sdk/pkg/address"
Expand Down Expand Up @@ -89,6 +90,16 @@ func (suite *KeeperTestSuite) MsgServer() types.MsgServer {
return keeper.NewMsgServerImpl(suite.Keeper())
}

func (suite *KeeperTestSuite) QueryClient() types.QueryClient {
queryHelper := baseapp.NewQueryServerTestHelper(suite.ctx, suite.app.InterfaceRegistry())
if suite.chainName == trontypes.ModuleName {
types.RegisterQueryServer(queryHelper, suite.app.TronKeeper)
return types.NewQueryClient(queryHelper)
}
types.RegisterQueryServer(queryHelper, suite.Keeper())
return types.NewQueryClient(queryHelper)
}

func (suite *KeeperTestSuite) Keeper() keeper.Keeper {
switch suite.chainName {
case bsctypes.ModuleName:
Expand Down
2 changes: 2 additions & 0 deletions x/crosschain/types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,6 @@ const (

EventTypeBridgeCallRefund = "bridge_call_refund"
AttributeKeyRefundAddress = "refund_address"

EventTypeRefundTimeout = "refund_timeout"
)

0 comments on commit ea00e46

Please sign in to comment.