Skip to content

Commit 1cf2f60

Browse files
authored
feat: make Bitcoin deposit with invalid memo reverting (#3615)
* move new inbound function * revert invalid memo cct * add e2e test * add test with invalid memo * changelog * fix unit test * e2e tests * generate * reduce tests run * make generate
1 parent e9b9bce commit 1cf2f60

File tree

17 files changed

+492
-333
lines changed

17 files changed

+492
-333
lines changed

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
* [3600](https://github.com/zeta-chain/node/pull/3600) - add dedicated zetaclient restricted addresses config. This file will be automatically reloaded when it changes without needing to restart zetaclient.
2828
* [3578](https://github.com/zeta-chain/node/pull/3578) - Add disable_tss_block_scan parameter. This parameter will be used to disable expensive block scanning actions on non-ethereum EVM Chains.
2929
* [3551](https://github.com/zeta-chain/node/pull/3551) - support for EVM chain and Bitcoin chain inbound fast confirmation
30+
* [3615](https://github.com/zeta-chain/node/pull/3615) - make Bitcoin deposit with invalid memo reverting
3031

3132
### Refactor
3233

cmd/zetae2e/local/bitcoin.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func startBitcoinTests(
3030
e2etests.TestBitcoinDepositFastConfirmationName,
3131
e2etests.TestBitcoinDepositAndCallName,
3232
e2etests.TestBitcoinDepositAndCallRevertName,
33+
e2etests.TestBitcoinDepositInvalidMemoRevertName,
3334
e2etests.TestBitcoinStdMemoDepositName,
3435
e2etests.TestBitcoinStdMemoDepositAndCallName,
3536
e2etests.TestBitcoinStdMemoDepositAndCallRevertName,

docs/openapi/openapi.swagger.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57685,11 +57685,13 @@ definitions:
5768557685
- SUCCESS
5768657686
- INSUFFICIENT_DEPOSITOR_FEE
5768757687
- INVALID_RECEIVER_ADDRESS
57688+
- INVALID_MEMO
5768857689
default: SUCCESS
5768957690
description: |-
5769057691
- INSUFFICIENT_DEPOSITOR_FEE: this field is specifically for Bitcoin when the deposit amount is less than
5769157692
depositor fee
5769257693
- INVALID_RECEIVER_ADDRESS: the receiver address parsed from the inbound is invalid
57694+
- INVALID_MEMO: parse memo is invalid
5769357695
title: InboundStatus represents the status of an observed inbound
5769457696
zetachain.zetacore.crosschain.InboundTracker:
5769557697
type: object

e2e/e2etests/e2etests.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ const (
119119
TestBitcoinWithdrawP2SHName = "bitcoin_withdraw_p2sh"
120120
TestBitcoinWithdrawInvalidAddressName = "bitcoin_withdraw_invalid"
121121
TestBitcoinWithdrawRestrictedName = "bitcoin_withdraw_restricted"
122+
TestBitcoinDepositInvalidMemoRevertName = "bitcoin_deposit_invalid_memo_revert"
122123

123124
/*
124125
Application tests
@@ -908,6 +909,13 @@ var AllE2ETests = []runner.E2ETest{
908909
},
909910
TestBitcoinWithdrawRestricted,
910911
),
912+
runner.NewE2ETest(
913+
TestBitcoinDepositInvalidMemoRevertName,
914+
"deposit Bitcoin with invalid memo; expect revert",
915+
[]runner.ArgDefinition{},
916+
TestBitcoinDepositInvalidMemoRevert,
917+
runner.WithMinimumVersion("v29.0.0"),
918+
),
911919
/*
912920
Application tests
913921
*/

e2e/e2etests/test_bitcoin_deposit_abort_with_low_deposit_fee.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66

77
"github.com/zeta-chain/node/e2e/runner"
88
"github.com/zeta-chain/node/e2e/utils"
9-
"github.com/zeta-chain/node/x/crosschain/types"
9+
crosschaintypes "github.com/zeta-chain/node/x/crosschain/types"
1010
zetabitcoin "github.com/zeta-chain/node/zetaclient/chains/bitcoin/common"
1111
)
1212

@@ -30,5 +30,7 @@ func TestBitcoinDepositAndAbortWithLowDepositFee(r *runner.E2ERunner, args []str
3030
require.Equal(r, cctx.GetCurrentOutboundParam().Amount.Uint64(), uint64(0))
3131

3232
// check cctx error message
33-
require.Contains(r, cctx.CctxStatus.StatusMessage, types.InboundStatus_INSUFFICIENT_DEPOSITOR_FEE.String())
33+
require.Contains(r, cctx.CctxStatus.StatusMessage, "inbound observation failed")
34+
require.Contains(r, cctx.CctxStatus.ErrorMessage, "insufficient depositor fee")
35+
require.EqualValues(r, crosschaintypes.InboundStatus_INSUFFICIENT_DEPOSITOR_FEE, cctx.InboundParams.Status)
3436
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package e2etests
2+
3+
import (
4+
"github.com/stretchr/testify/require"
5+
6+
"github.com/zeta-chain/node/e2e/runner"
7+
"github.com/zeta-chain/node/e2e/utils"
8+
crosschaintypes "github.com/zeta-chain/node/x/crosschain/types"
9+
)
10+
11+
func TestBitcoinDepositInvalidMemoRevert(r *runner.E2ERunner, args []string) {
12+
require.Len(r, args, 0)
13+
14+
// make a deposit with a empty memo
15+
utxos := r.ListDeployerUTXOs()
16+
txHash, err := r.SendToTSSFromDeployerWithMemo(0.1, utxos[:1], []byte{})
17+
require.NoError(r, err)
18+
19+
// wait for the cctx to be mined
20+
cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout)
21+
r.Logger.CCTX(*cctx, "deposit empty memo")
22+
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_Reverted)
23+
require.EqualValues(r, crosschaintypes.InboundStatus_INVALID_MEMO, cctx.InboundParams.Status)
24+
25+
// make a deposit with an invalid memo
26+
utxos = r.ListDeployerUTXOs()
27+
txHash, err = r.SendToTSSFromDeployerWithMemo(0.1, utxos[:1], []byte("invalid memo"))
28+
require.NoError(r, err)
29+
30+
// wait for the cctx to be mined
31+
cctx = utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout)
32+
r.Logger.CCTX(*cctx, "deposit invalid memo")
33+
utils.RequireCCTXStatus(r, cctx, crosschaintypes.CctxStatus_Reverted)
34+
require.EqualValues(r, crosschaintypes.InboundStatus_INVALID_MEMO, cctx.InboundParams.Status)
35+
}

proto/zetachain/zetacore/crosschain/cross_chain_tx.proto

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ enum InboundStatus {
4242
INSUFFICIENT_DEPOSITOR_FEE = 1;
4343
// the receiver address parsed from the inbound is invalid
4444
INVALID_RECEIVER_ADDRESS = 2;
45+
// parse memo is invalid
46+
INVALID_MEMO = 3;
4547
}
4648

4749
message InboundParams {

testutil/sample/crosschain.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,14 @@ func InboundParams(r *rand.Rand) *types.InboundParams {
172172
Sender: EthAddress().String(),
173173
SenderChainId: r.Int63(),
174174
TxOrigin: EthAddress().String(),
175-
CoinType: coin.CoinType(r.Intn(100)),
175+
CoinType: coin.CoinType_Gas,
176176
Asset: StringRandom(r, 32),
177177
Amount: sdkmath.NewUint(uint64(r.Int63())),
178178
ObservedHash: StringRandom(r, 32),
179179
ObservedExternalHeight: r.Uint64(),
180180
BallotIndex: StringRandom(r, 32),
181181
FinalizedZetaHeight: r.Uint64(),
182-
Status: InboundStatusFromRand(r),
182+
Status: types.InboundStatus_SUCCESS,
183183
ConfirmationMode: ConfirmationModeFromRand(r),
184184
}
185185
}

typescript/zetachain/zetacore/crosschain/cross_chain_tx_pb.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,13 @@ export declare enum InboundStatus {
124124
* @generated from enum value: INVALID_RECEIVER_ADDRESS = 2;
125125
*/
126126
INVALID_RECEIVER_ADDRESS = 2,
127+
128+
/**
129+
* parse memo is invalid
130+
*
131+
* @generated from enum value: INVALID_MEMO = 3;
132+
*/
133+
INVALID_MEMO = 3,
127134
}
128135

129136
/**
Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package keeper
22

33
import (
4+
"errors"
45
"fmt"
56

67
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -26,36 +27,46 @@ func (c CCTXGatewayZEVM) InitiateOutbound(
2627
ctx sdk.Context,
2728
config InitiateOutboundConfig,
2829
) (newCCTXStatus types.CctxStatus, err error) {
29-
// abort if CCTX inbound observation status indicates failure
30-
// this is specifically for Bitcoin inbound error 'INSUFFICIENT_DEPOSITOR_FEE'
31-
if config.CCTX.InboundParams.Status == types.InboundStatus_INSUFFICIENT_DEPOSITOR_FEE {
30+
switch config.CCTX.InboundParams.Status {
31+
case types.InboundStatus_INSUFFICIENT_DEPOSITOR_FEE:
32+
// abort if CCTX has insufficient depositor fee for Bitcoin, the CCTX can't be reverted in this case
33+
// because there is no fund to pay for the revert tx
3234
c.crosschainKeeper.ProcessAbort(ctx, config.CCTX, types.StatusMessages{
33-
StatusMessage: fmt.Sprintf(
34-
"observation failed, reason %s",
35-
types.InboundStatus_INSUFFICIENT_DEPOSITOR_FEE.String(),
36-
),
35+
ErrorMessageOutbound: "insufficient depositor fee",
36+
StatusMessage: "inbound observation failed",
3737
})
3838
return types.CctxStatus_Aborted, nil
39-
}
39+
case types.InboundStatus_INVALID_MEMO:
40+
// when invalid memo is reported, the CCTX is reverted to the sender
41+
newCCTXStatus = c.crosschainKeeper.ValidateOutboundZEVM(ctx, config.CCTX, errors.New("invalid memo"), true)
42+
return newCCTXStatus, nil
43+
case types.InboundStatus_SUCCESS:
44+
// process the deposit normally
45+
tmpCtx, commit := ctx.CacheContext()
46+
isContractReverted, err := c.crosschainKeeper.HandleEVMDeposit(tmpCtx, config.CCTX)
47+
48+
if err != nil && !isContractReverted {
49+
// exceptional case; internal error; should abort CCTX
50+
// use ctx as tmpCtx is dismissed to not save any side effects performed during the evm deposit
51+
c.crosschainKeeper.ProcessAbort(ctx, config.CCTX, types.StatusMessages{
52+
StatusMessage: "outbound failed but the universal contract did not revert",
53+
ErrorMessageOutbound: cctxerror.NewCCTXErrorJSONMessage("failed to deposit tokens in ZEVM", err),
54+
})
55+
return types.CctxStatus_Aborted, err
56+
}
4057

41-
// process the deposit
42-
tmpCtx, commit := ctx.CacheContext()
43-
isContractReverted, err := c.crosschainKeeper.HandleEVMDeposit(tmpCtx, config.CCTX)
58+
newCCTXStatus = c.crosschainKeeper.ValidateOutboundZEVM(ctx, config.CCTX, err, isContractReverted)
59+
if newCCTXStatus == types.CctxStatus_OutboundMined || newCCTXStatus == types.CctxStatus_PendingRevert {
60+
commit()
61+
}
4462

45-
if err != nil && !isContractReverted {
46-
// exceptional case; internal error; should abort CCTX
47-
// use ctx as tmpCtx is dismissed to not save any side effects performed during the evm deposit
63+
return newCCTXStatus, nil
64+
default:
65+
// unknown observation status, abort the CCTX
4866
c.crosschainKeeper.ProcessAbort(ctx, config.CCTX, types.StatusMessages{
49-
StatusMessage: "outbound failed but the universal contract did not revert",
50-
ErrorMessageOutbound: cctxerror.NewCCTXErrorJSONMessage("failed to deposit tokens in ZEVM", err),
67+
ErrorMessageOutbound: fmt.Sprintf("invalid observation status %d", config.CCTX.InboundParams.Status),
68+
StatusMessage: "inbound observation failed",
5169
})
52-
return types.CctxStatus_Aborted, err
53-
}
54-
55-
newCCTXStatus = c.crosschainKeeper.ValidateOutboundZEVM(ctx, config.CCTX, err, isContractReverted)
56-
if newCCTXStatus == types.CctxStatus_OutboundMined || newCCTXStatus == types.CctxStatus_PendingRevert {
57-
commit()
70+
return types.CctxStatus_Aborted, nil
5871
}
59-
60-
return newCCTXStatus, nil
6172
}

0 commit comments

Comments
 (0)