Skip to content

Commit

Permalink
feat: add refund token claim (#263)
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 authored Mar 12, 2024
1 parent 0c69a3e commit baf4371
Show file tree
Hide file tree
Showing 13 changed files with 893 additions and 252 deletions.
4 changes: 2 additions & 2 deletions app/encoding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ func TestMakeEncodingConfig_RegisterInterfaces(t *testing.T) {
for typeURLMap.Next() {
count3++
}
assert.Equal(t, 267, count3)
assert.Equal(t, 269, count3)

govContent := encodingConfig.InterfaceRegistry.ListImplementations("cosmos.gov.v1beta1.Content")
assert.Equal(t, 14, len(govContent))

msgImplementations := encodingConfig.InterfaceRegistry.ListImplementations(sdk.MsgInterfaceProtoName)
assert.Equal(t, 103, len(msgImplementations))
assert.Equal(t, 104, len(msgImplementations))

type govProposalMsg interface {
GetAuthority() string
Expand Down
11 changes: 11 additions & 0 deletions proto/fx/crosschain/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ service Msg {

rpc SendToFxClaim(MsgSendToFxClaim) returns (MsgSendToFxClaimResponse);
rpc BridgeCallClaim(MsgBridgeCallClaim) returns (MsgBridgeCallClaimResponse);
rpc RefundTokenClaim(MsgRefundTokenClaim) returns (MsgRefundTokenClaimResponse);

rpc SendToExternal(MsgSendToExternal) returns (MsgSendToExternalResponse);
rpc CancelSendToExternal(MsgCancelSendToExternal) returns (MsgCancelSendToExternalResponse);
Expand Down Expand Up @@ -172,6 +173,16 @@ message MsgBridgeCallClaim {

message MsgBridgeCallClaimResponse {}

message MsgRefundTokenClaim {
uint64 refund_nonce = 1;
uint64 event_nonce = 2;
uint64 block_height = 3;
string bridger_address = 4;
string chain_name = 5;
}

message MsgRefundTokenClaimResponse {}

// MsgSendToExternal
// This is the message that a user calls when they want to bridge an asset
// it will later be removed when it is included in a batch and successfully
Expand Down
1 change: 1 addition & 0 deletions proto/fx/crosschain/v1/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ enum ClaimType {
CLAIM_TYPE_BRIDGE_TOKEN = 3;
CLAIM_TYPE_ORACLE_SET_UPDATED = 4;
CLAIM_TYPE_BRIDGE_CALL = 5;
CLAIM_TYPE_REFUND_TOKEN = 6;
}

// Attestation is an aggregate of `claims` that eventually becomes `observed` by
Expand Down
3 changes: 3 additions & 0 deletions x/crosschain/keeper/attestation_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ func (k Keeper) AttestationHandler(ctx sdk.Context, externalClaim types.External
}
k.SetLastObservedOracleSet(ctx, observedOracleSet)

case *types.MsgRefundTokenClaim:
k.HandleRefundTokenClaim(ctx, claim)
return nil
default:
return errorsmod.Wrapf(types.ErrInvalid, "event type: %s", claim.GetType())
}
Expand Down
57 changes: 54 additions & 3 deletions x/crosschain/keeper/bridge_call_refund.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,37 @@ import (
"github.com/functionx/fx-core/v7/x/crosschain/types"
)

func (k Keeper) HandleRefundTokenClaim(ctx sdk.Context, claim *types.MsgRefundTokenClaim) {
record, found := k.GetRefundRecord(ctx, claim.RefundNonce)
if !found {
return
}
// todo: If need be to slash unsigned oracles, can't delete refund record and refund confirm here
// 1. delete refund record
k.DeleteRefundRecord(ctx, record)

// 2. delete record confirm
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)
}
}

func (k Keeper) AddRefundRecord(ctx sdk.Context, receiver string, eventNonce uint64, tokens []types.ERC20Token) error {
oracleSet := k.GetLatestOracleSet(ctx)
if oracleSet == nil {
Expand All @@ -35,10 +66,16 @@ func (k Keeper) AddRefundRecord(ctx sdk.Context, receiver string, eventNonce uin
return nil
}

func (k Keeper) SetRefundRecord(ctx sdk.Context, refundRecord *types.RefundRecord) {
func (k Keeper) SetRefundRecord(ctx sdk.Context, record *types.RefundRecord) {
store := ctx.KVStore(k.storeKey)
store.Set(types.GetBridgeCallRefundEventNonceKey(record.EventNonce), k.cdc.MustMarshal(record))
store.Set(types.GetBridgeCallRefundKey(record.Receiver, record.EventNonce), sdk.Uint64ToBigEndian(record.OracleSetNonce))
}

func (k Keeper) DeleteRefundRecord(ctx sdk.Context, record *types.RefundRecord) {
store := ctx.KVStore(k.storeKey)
store.Set(types.GetBridgeCallRefundEventNonceKey(refundRecord.EventNonce), k.cdc.MustMarshal(refundRecord))
store.Set(types.GetBridgeCallRefundKey(refundRecord.Receiver, refundRecord.EventNonce), sdk.Uint64ToBigEndian(refundRecord.OracleSetNonce))
store.Delete(types.GetBridgeCallRefundEventNonceKey(record.EventNonce))
store.Delete(types.GetBridgeCallRefundKey(record.Receiver, record.EventNonce))
}

func (k Keeper) GetRefundRecord(ctx sdk.Context, eventNonce uint64) (*types.RefundRecord, bool) {
Expand Down Expand Up @@ -68,6 +105,11 @@ func (k Keeper) GetSnapshotOracle(ctx sdk.Context, oracleSetNonce uint64) (*type
return snapshotOracle, true
}

func (k Keeper) DeleteSnapshotOracle(ctx sdk.Context, nonce uint64) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.GetSnapshotOracleKey(nonce))
}

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 All @@ -83,3 +125,12 @@ func (k Keeper) SetRefundConfirm(ctx sdk.Context, addr sdk.AccAddress, msg *type
store := ctx.KVStore(k.storeKey)
store.Set(types.GetRefundConfirmKey(msg.Nonce, addr), k.cdc.MustMarshal(msg))
}

func (k Keeper) DeleteRefundConfirm(ctx sdk.Context, nonce uint64) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, types.GetRefundConfirmKeyByNonce(nonce))
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
store.Delete(iterator.Key())
}
}
8 changes: 8 additions & 0 deletions x/crosschain/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,14 @@ func (s MsgServer) BridgeCallClaim(c context.Context, msg *types.MsgBridgeCallCl
return &types.MsgBridgeCallClaimResponse{}, nil
}

func (s MsgServer) RefundTokenClaim(c context.Context, msg *types.MsgRefundTokenClaim) (*types.MsgRefundTokenClaimResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
if err := s.claimHandlerCommon(ctx, msg); err != nil {
return nil, err
}
return &types.MsgRefundTokenClaimResponse{}, nil
}

func (s MsgServer) BridgeTokenClaim(c context.Context, msg *types.MsgBridgeTokenClaim) (*types.MsgBridgeTokenClaimResponse, error) {
ctx := sdk.UnwrapSDKContext(c)
if err := s.claimHandlerCommon(ctx, msg); err != nil {
Expand Down
8 changes: 8 additions & 0 deletions x/crosschain/keeper/msg_server_router.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ func (k msgServer) BridgeCallClaim(ctx context.Context, msg *types.MsgBridgeCall
}
}

func (k msgServer) RefundTokenClaim(ctx context.Context, msg *types.MsgRefundTokenClaim) (*types.MsgRefundTokenClaimResponse, error) {
if queryServer, err := k.getMsgServerByChainName(msg.GetChainName()); err != nil {
return nil, err
} else {
return queryServer.RefundTokenClaim(ctx, msg)
}
}

func (k msgServer) SendToExternal(ctx context.Context, msg *types.MsgSendToExternal) (*types.MsgSendToExternalResponse, error) {
if queryServer, err := k.getMsgServerByChainName(msg.GetChainName()); err != nil {
return nil, err
Expand Down
3 changes: 3 additions & 0 deletions x/crosschain/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func RegisterInterfaces(registry types.InterfaceRegistry) {
&MsgCancelSendToExternal{},
&MsgIncreaseBridgeFee{},
&MsgSendToExternalClaim{},
&MsgRefundTokenClaim{},

&MsgRequestBatch{},
&MsgConfirmBatch{},
Expand All @@ -64,6 +65,7 @@ func RegisterInterfaces(registry types.InterfaceRegistry) {
&MsgBridgeCallClaim{},
&MsgBridgeTokenClaim{},
&MsgOracleSetUpdatedClaim{},
&MsgRefundTokenClaim{},
)

registry.RegisterImplementations(
Expand Down Expand Up @@ -97,6 +99,7 @@ func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
cdc.RegisterConcrete(&MsgCancelSendToExternal{}, fmt.Sprintf("%s/%s", ModuleName, "MsgCancelSendToExternal"), nil)
cdc.RegisterConcrete(&MsgIncreaseBridgeFee{}, fmt.Sprintf("%s/%s", ModuleName, "MsgIncreaseBridgeFee"), nil)
cdc.RegisterConcrete(&MsgSendToExternalClaim{}, fmt.Sprintf("%s/%s", ModuleName, "MsgSendToExternalClaim"), nil)
cdc.RegisterConcrete(&MsgRefundTokenClaim{}, fmt.Sprintf("%s/%s", ModuleName, "MsgRefundTokenClaim"), nil)

cdc.RegisterConcrete(&MsgRequestBatch{}, fmt.Sprintf("%s/%s", ModuleName, "MsgRequestBatch"), nil)
cdc.RegisterConcrete(&MsgConfirmBatch{}, fmt.Sprintf("%s/%s", ModuleName, "MsgConfirmBatch"), nil)
Expand Down
4 changes: 4 additions & 0 deletions x/crosschain/types/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,7 @@ func GetSnapshotOracleKey(oracleSetNonce uint64) []byte {
func GetRefundConfirmKey(nonce uint64, addr sdk.AccAddress) []byte {
return append(BridgeCallRefundConfirmKey, append(sdk.Uint64ToBigEndian(nonce), addr.Bytes()...)...)
}

func GetRefundConfirmKeyByNonce(nonce uint64) []byte {
return append(BridgeCallRefundConfirmKey, sdk.Uint64ToBigEndian(nonce)...)
}
16 changes: 16 additions & 0 deletions x/crosschain/types/msg_validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,22 @@ func (b MsgValidate) MsgBridgeCallClaimValidate(m *MsgBridgeCallClaim) (err erro
return nil
}

func (b MsgValidate) MsgRefundTokenClaimValidate(m *MsgRefundTokenClaim) (err error) {
if _, err = sdk.AccAddressFromBech32(m.BridgerAddress); err != nil {
return errortypes.ErrInvalidAddress.Wrapf("invalid bridger address: %s", err)
}
if m.RefundNonce == 0 {
return errortypes.ErrInvalidRequest.Wrap("zero refund nonce")
}
if m.EventNonce == 0 {
return errortypes.ErrInvalidRequest.Wrap("zero event nonce")
}
if m.BlockHeight == 0 {
return errortypes.ErrInvalidRequest.Wrap("zero block height")
}
return nil
}

func (b MsgValidate) MsgSendToExternalValidate(m *MsgSendToExternal) (err error) {
if _, err = sdk.AccAddressFromBech32(m.Sender); err != nil {
return errortypes.ErrInvalidAddress.Wrapf("invalid sender address: %s", err)
Expand Down
51 changes: 49 additions & 2 deletions x/crosschain/types/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ const (

TypeMsgBridgeTokenClaim = "bridge_token_claim"

TypeMsgSendToFxClaim = "send_to_fx_claim"
TypeMsgBridgeCallClaim = "bridge_call_claim"
TypeMsgSendToFxClaim = "send_to_fx_claim"
TypeMsgBridgeCallClaim = "bridge_call_claim"
TypeMsgRefundTokenClaim = "refund_token_claim" //nolint:gosec

TypeMsgSendToExternal = "send_to_external"
TypeMsgCancelSendToExternal = "cancel_send_to_external"
Expand Down Expand Up @@ -80,6 +81,8 @@ var (
_ CrossChainMsg = &MsgSendToFxClaim{}
_ sdk.Msg = &MsgBridgeCallClaim{}
_ CrossChainMsg = &MsgBridgeCallClaim{}
_ sdk.Msg = &MsgRefundTokenClaim{}
_ CrossChainMsg = &MsgRefundTokenClaim{}

_ sdk.Msg = &MsgSendToExternal{}
_ CrossChainMsg = &MsgSendToExternal{}
Expand Down Expand Up @@ -120,6 +123,7 @@ type MsgValidateBasic interface {

MsgSendToFxClaimValidate(m *MsgSendToFxClaim) (err error)
MsgBridgeCallClaimValidate(m *MsgBridgeCallClaim) (err error)
MsgRefundTokenClaimValidate(m *MsgRefundTokenClaim) (err error)
MsgSendToExternalValidate(m *MsgSendToExternal) (err error)

MsgCancelSendToExternalValidate(m *MsgCancelSendToExternal) (err error)
Expand Down Expand Up @@ -528,6 +532,7 @@ var (
_ ExternalClaim = &MsgBridgeTokenClaim{}
_ ExternalClaim = &MsgSendToExternalClaim{}
_ ExternalClaim = &MsgOracleSetUpdatedClaim{}
_ ExternalClaim = &MsgRefundTokenClaim{}
)

func UnpackAttestationClaim(cdc codectypes.AnyUnpacker, att *Attestation) (ExternalClaim, error) {
Expand Down Expand Up @@ -647,6 +652,48 @@ func (m *MsgBridgeCallClaim) MustMessage() []byte {
return bz
}

// MsgRefundTokenClaim

// GetType returns the type of the claim
func (m *MsgRefundTokenClaim) GetType() ClaimType {
return CLAIM_TYPE_REFUND_TOKEN
}

// ValidateBasic performs stateless checks
func (m *MsgRefundTokenClaim) ValidateBasic() (err error) {
if router, ok := msgValidateBasicRouter[m.ChainName]; !ok {
return errortypes.ErrInvalidRequest.Wrap("unrecognized cross chain name")
} else {
return router.MsgRefundTokenClaimValidate(m)
}
}

// GetSignBytes encodes the message for signing
func (m *MsgRefundTokenClaim) GetSignBytes() []byte {
return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(m))
}

func (m *MsgRefundTokenClaim) GetClaimer() sdk.AccAddress {
return sdk.MustAccAddressFromBech32(m.BridgerAddress)
}

// GetSigners defines whose signature is required
func (m *MsgRefundTokenClaim) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{sdk.MustAccAddressFromBech32(m.BridgerAddress)}
}

// Type should return the action
func (m *MsgRefundTokenClaim) Type() string { return TypeMsgRefundTokenClaim }

// Route should return the name of the module
func (m *MsgRefundTokenClaim) Route() string { return RouterKey }

// ClaimHash Hash implements BridgeSendToExternal.Hash
func (m *MsgRefundTokenClaim) ClaimHash() []byte {
path := fmt.Sprintf("%d/%d/%d", m.BlockHeight, m.EventNonce, m.RefundNonce)
return tmhash.Sum([]byte(path))
}

// MsgSendToExternalClaim

// GetType returns the claim type
Expand Down
Loading

0 comments on commit baf4371

Please sign in to comment.