diff --git a/cmd/zetae2e/local/bitcoin.go b/cmd/zetae2e/local/bitcoin.go index c11b00494c..443ab26c0c 100644 --- a/cmd/zetae2e/local/bitcoin.go +++ b/cmd/zetae2e/local/bitcoin.go @@ -58,7 +58,8 @@ func bitcoinTestRoutines( _, err := runnerDeposit.GenerateToAddressIfLocalBitcoin(101, runnerDeposit.BTCDeployerAddress) require.NoError(runnerDeposit, err) - // send BTC to ZEVM addresses + // donate BTC to TSS and send BTC to ZEVM addresses + runnerDeposit.DonateBTC() runnerDeposit.DepositBTC(runnerDeposit.EVMAddress()) runnerDeposit.DepositBTC(runnerWithdraw.EVMAddress()) } diff --git a/cmd/zetae2e/local/local.go b/cmd/zetae2e/local/local.go index e32bfc0d77..66bff30953 100644 --- a/cmd/zetae2e/local/local.go +++ b/cmd/zetae2e/local/local.go @@ -298,6 +298,7 @@ func localE2ETest(cmd *cobra.Command, _ []string) { e2etests.TestBitcoinStdMemoDepositAndCallName, e2etests.TestBitcoinStdMemoDepositAndCallRevertName, e2etests.TestBitcoinStdMemoInscribedDepositAndCallName, + e2etests.TestBitcoinDepositAndAbortWithLowDepositFeeName, e2etests.TestCrosschainSwapName, } bitcoinDepositTestsAdvanced := []string{ diff --git a/docs/spec/crosschain/messages.md b/docs/spec/crosschain/messages.md index 3182bec082..0c492b2b0c 100644 --- a/docs/spec/crosschain/messages.md +++ b/docs/spec/crosschain/messages.md @@ -192,6 +192,7 @@ message MsgVoteInbound { RevertOptions revert_options = 17; CallOptions call_options = 18; bool is_cross_chain_call = 19; + string error_message = 20; } ``` diff --git a/e2e/e2etests/e2etests.go b/e2e/e2etests/e2etests.go index 884573d2fa..2261bb37e2 100644 --- a/e2e/e2etests/e2etests.go +++ b/e2e/e2etests/e2etests.go @@ -82,6 +82,7 @@ const ( TestBitcoinStdMemoDepositAndCallRevertName = "bitcoin_std_memo_deposit_and_call_revert" TestBitcoinStdMemoDepositAndCallRevertOtherAddressName = "bitcoin_std_memo_deposit_and_call_revert_other_address" TestBitcoinStdMemoInscribedDepositAndCallName = "bitcoin_std_memo_inscribed_deposit_and_call" + TestBitcoinDepositAndAbortWithLowDepositFeeName = "bitcoin_deposit_and_abort_with_low_deposit_fee" TestBitcoinWithdrawSegWitName = "bitcoin_withdraw_segwit" TestBitcoinWithdrawTaprootName = "bitcoin_withdraw_taproot" TestBitcoinWithdrawMultipleName = "bitcoin_withdraw_multiple" @@ -647,6 +648,12 @@ var AllE2ETests = []runner.E2ETest{ }, TestBitcoinStdMemoInscribedDepositAndCall, ), + runner.NewE2ETest( + TestBitcoinDepositAndAbortWithLowDepositFeeName, + "deposit Bitcoin into ZEVM that aborts due to insufficient deposit fee", + []runner.ArgDefinition{}, + TestBitcoinDepositAndAbortWithLowDepositFee, + ), runner.NewE2ETest( TestBitcoinWithdrawSegWitName, "withdraw BTC from ZEVM to a SegWit address", diff --git a/e2e/e2etests/test_bitcoin_deposit.go b/e2e/e2etests/test_bitcoin_deposit.go index 3cedc00994..f8055ff532 100644 --- a/e2e/e2etests/test_bitcoin_deposit.go +++ b/e2e/e2etests/test_bitcoin_deposit.go @@ -13,7 +13,7 @@ func TestBitcoinDeposit(r *runner.E2ERunner, args []string) { depositAmount := utils.ParseFloat(r, args[0]) - txHash := r.DepositBTCWithAmount(depositAmount, nil) + txHash := r.DepositBTCWithAmount(depositAmount, nil, true) // wait for the cctx to be mined cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout) diff --git a/e2e/e2etests/test_bitcoin_deposit_abort_with_low_deposit_fee.go b/e2e/e2etests/test_bitcoin_deposit_abort_with_low_deposit_fee.go new file mode 100644 index 0000000000..c3376033c1 --- /dev/null +++ b/e2e/e2etests/test_bitcoin_deposit_abort_with_low_deposit_fee.go @@ -0,0 +1,35 @@ +package e2etests + +import ( + "fmt" + + "github.com/btcsuite/btcd/btcutil" + "github.com/stretchr/testify/require" + + "github.com/zeta-chain/node/e2e/runner" + "github.com/zeta-chain/node/e2e/utils" + zetabitcoin "github.com/zeta-chain/node/zetaclient/chains/bitcoin/common" +) + +func TestBitcoinDepositAndAbortWithLowDepositFee(r *runner.E2ERunner, args []string) { + require.Len(r, args, 0) + + // Given small amount + depositAmount := zetabitcoin.DefaultDepositorFee - float64(1)/btcutil.SatoshiPerBitcoin + + // ACT + txHash := r.DepositBTCWithAmount(depositAmount, nil, false) + + // ASSERT + // cctx status should be aborted + cctx := utils.WaitCctxAbortedByInboundHash(r.Ctx, r, txHash.String(), r.CctxClient) + r.Logger.CCTX(cctx, "deposit aborted") + + // check cctx details + require.Equal(r, cctx.InboundParams.Amount.Uint64(), uint64(0)) + require.Equal(r, cctx.GetCurrentOutboundParam().Amount.Uint64(), uint64(0)) + + // check cctx error message + expectedError := fmt.Sprintf("deposited amount %v is less than depositor fee", depositAmount) + require.Contains(r, cctx.CctxStatus.ErrorMessage, expectedError) +} diff --git a/e2e/e2etests/test_bitcoin_deposit_and_call_revert.go b/e2e/e2etests/test_bitcoin_deposit_and_call_revert.go index c94e6cb6b7..16f32a736d 100644 --- a/e2e/e2etests/test_bitcoin_deposit_and_call_revert.go +++ b/e2e/e2etests/test_bitcoin_deposit_and_call_revert.go @@ -20,16 +20,14 @@ func TestBitcoinDepositAndCallRevert(r *runner.E2ERunner, args []string) { amount += zetabitcoin.DefaultDepositorFee // Given a list of UTXOs - utxos, err := r.ListDeployerUTXOs() - require.NoError(r, err) - require.NotEmpty(r, utxos) + utxos := r.ListDeployerUTXOs() // ACT // Send BTC to TSS address with a dummy memo // zetacore should revert cctx if call is made on a non-existing address nonExistReceiver := sample.EthAddress() badMemo := append(nonExistReceiver.Bytes(), []byte("gibberish-memo")...) - txHash, err := r.SendToTSSFromDeployerWithMemo(amount, utxos, badMemo) + txHash, err := r.SendToTSSFromDeployerWithMemo(amount, utxos[:1], badMemo) require.NoError(r, err) require.NotEmpty(r, txHash) diff --git a/e2e/e2etests/test_bitcoin_deposit_and_call_revert_with_dust.go b/e2e/e2etests/test_bitcoin_deposit_and_call_revert_with_dust.go index c7ae665423..adf2a354d5 100644 --- a/e2e/e2etests/test_bitcoin_deposit_and_call_revert_with_dust.go +++ b/e2e/e2etests/test_bitcoin_deposit_and_call_revert_with_dust.go @@ -32,16 +32,14 @@ func TestBitcoinDepositAndCallRevertWithDust(r *runner.E2ERunner, args []string) amount := depositAmount + zetabitcoin.DefaultDepositorFee // Given a list of UTXOs - utxos, err := r.ListDeployerUTXOs() - require.NoError(r, err) - require.NotEmpty(r, utxos) + utxos := r.ListDeployerUTXOs() // ACT // Send BTC to TSS address with a dummy memo // zetacore should revert cctx if call is made on a non-existing address nonExistReceiver := sample.EthAddress() anyMemo := append(nonExistReceiver.Bytes(), []byte("gibberish-memo")...) - txHash, err := r.SendToTSSFromDeployerWithMemo(amount, utxos, anyMemo) + txHash, err := r.SendToTSSFromDeployerWithMemo(amount, utxos[:1], anyMemo) require.NoError(r, err) require.NotEmpty(r, txHash) diff --git a/e2e/e2etests/test_bitcoin_deposit_and_withdraw_with_dust.go b/e2e/e2etests/test_bitcoin_deposit_and_withdraw_with_dust.go index 8b108f2103..bf8f1bf133 100644 --- a/e2e/e2etests/test_bitcoin_deposit_and_withdraw_with_dust.go +++ b/e2e/e2etests/test_bitcoin_deposit_and_withdraw_with_dust.go @@ -32,13 +32,11 @@ func TestBitcoinDepositAndWithdrawWithDust(r *runner.E2ERunner, args []string) { require.Equal(r, receipt.Status, uint64(1)) // Given a list of UTXOs - utxos, err := r.ListDeployerUTXOs() - require.NoError(r, err) - require.NotEmpty(r, utxos) + utxos := r.ListDeployerUTXOs() // ACT // Deposit 0.01 BTC to withdrawer, this is an arbitrary amount, must be greater than dust amount - txHash, err := r.SendToTSSFromDeployerWithMemo(0.01, utxos, withdrawerAddr.Bytes()) + txHash, err := r.SendToTSSFromDeployerWithMemo(0.01, utxos[:1], withdrawerAddr.Bytes()) require.NoError(r, err) require.NotEmpty(r, txHash) diff --git a/e2e/e2etests/test_bitcoin_deposit_call.go b/e2e/e2etests/test_bitcoin_deposit_call.go index c73bc3d6b0..3cd7491192 100644 --- a/e2e/e2etests/test_bitcoin_deposit_call.go +++ b/e2e/e2etests/test_bitcoin_deposit_call.go @@ -23,9 +23,7 @@ func TestBitcoinDepositAndCall(r *runner.E2ERunner, args []string) { amountTotal := amount + common.DefaultDepositorFee // Given a list of UTXOs - utxos, err := r.ListDeployerUTXOs() - require.NoError(r, err) - require.NotEmpty(r, utxos) + utxos := r.ListDeployerUTXOs() // deploy an example contract in ZEVM contractAddr, _, contract, err := testcontract.DeployExample(r.ZEVMAuth, r.ZEVMClient) @@ -36,7 +34,7 @@ func TestBitcoinDepositAndCall(r *runner.E2ERunner, args []string) { // Send BTC to TSS address with a dummy memo data := []byte("hello satoshi") memo := append(contractAddr.Bytes(), data...) - txHash, err := r.SendToTSSFromDeployerWithMemo(amountTotal, utxos, memo) + txHash, err := r.SendToTSSFromDeployerWithMemo(amountTotal, utxos[:1], memo) require.NoError(r, err) // wait for the cctx to be mined diff --git a/e2e/e2etests/test_bitcoin_donation.go b/e2e/e2etests/test_bitcoin_donation.go index ccddb91c51..38dbf33290 100644 --- a/e2e/e2etests/test_bitcoin_donation.go +++ b/e2e/e2etests/test_bitcoin_donation.go @@ -23,14 +23,12 @@ func TestBitcoinDonation(r *runner.E2ERunner, args []string) { amountTotal := amount + zetabitcoin.DefaultDepositorFee // Given a list of UTXOs - utxos, err := r.ListDeployerUTXOs() - require.NoError(r, err) - require.NotEmpty(r, utxos) + utxos := r.ListDeployerUTXOs() // ACT // Send BTC to TSS address with donation message memo := []byte(constant.DonationMessage) - txHash, err := r.SendToTSSFromDeployerWithMemo(amountTotal, utxos, memo) + txHash, err := r.SendToTSSFromDeployerWithMemo(amountTotal, utxos[:1], memo) require.NoError(r, err) // ASSERT after 4 Zeta blocks diff --git a/e2e/e2etests/test_bitcoin_std_deposit.go b/e2e/e2etests/test_bitcoin_std_deposit.go index ff8e9de4cd..ad69193fd3 100644 --- a/e2e/e2etests/test_bitcoin_std_deposit.go +++ b/e2e/e2etests/test_bitcoin_std_deposit.go @@ -40,7 +40,7 @@ func TestBitcoinStdMemoDeposit(r *runner.E2ERunner, args []string) { } // deposit BTC with standard memo - txHash := r.DepositBTCWithAmount(amount, memo) + txHash := r.DepositBTCWithAmount(amount, memo, true) // wait for the cctx to be mined cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout) diff --git a/e2e/e2etests/test_bitcoin_std_deposit_and_call.go b/e2e/e2etests/test_bitcoin_std_deposit_and_call.go index e1d897fca5..3b52b34905 100644 --- a/e2e/e2etests/test_bitcoin_std_deposit_and_call.go +++ b/e2e/e2etests/test_bitcoin_std_deposit_and_call.go @@ -40,7 +40,7 @@ func TestBitcoinStdMemoDepositAndCall(r *runner.E2ERunner, args []string) { } // deposit BTC with standard memo - txHash := r.DepositBTCWithAmount(amount, memo) + txHash := r.DepositBTCWithAmount(amount, memo, true) // wait for the cctx to be mined cctx := utils.WaitCctxMinedByInboundHash(r.Ctx, txHash.String(), r.CctxClient, r.Logger, r.CctxTimeout) diff --git a/e2e/e2etests/test_bitcoin_std_deposit_and_call_revert.go b/e2e/e2etests/test_bitcoin_std_deposit_and_call_revert.go index f94ec4c0ba..8e74d08cdb 100644 --- a/e2e/e2etests/test_bitcoin_std_deposit_and_call_revert.go +++ b/e2e/e2etests/test_bitcoin_std_deposit_and_call_revert.go @@ -33,7 +33,7 @@ func TestBitcoinStdMemoDepositAndCallRevert(r *runner.E2ERunner, args []string) // ACT // Deposit - txHash := r.DepositBTCWithAmount(amount, memo) + txHash := r.DepositBTCWithAmount(amount, memo, true) // ASSERT // Now we want to make sure revert TX is completed. diff --git a/e2e/e2etests/test_bitcoin_std_deposit_and_call_revert_other_address.go b/e2e/e2etests/test_bitcoin_std_deposit_and_call_revert_other_address.go index 6a5f4dd956..17619807d7 100644 --- a/e2e/e2etests/test_bitcoin_std_deposit_and_call_revert_other_address.go +++ b/e2e/e2etests/test_bitcoin_std_deposit_and_call_revert_other_address.go @@ -38,7 +38,7 @@ func TestBitcoinStdMemoDepositAndCallRevertOtherAddress(r *runner.E2ERunner, arg // ACT // Deposit - txHash := r.DepositBTCWithAmount(amount, memo) + txHash := r.DepositBTCWithAmount(amount, memo, true) // ASSERT // Now we want to make sure revert TX is completed. diff --git a/e2e/e2etests/test_crosschain_swap.go b/e2e/e2etests/test_crosschain_swap.go index d13e8d9057..cedaedfe65 100644 --- a/e2e/e2etests/test_crosschain_swap.go +++ b/e2e/e2etests/test_crosschain_swap.go @@ -102,8 +102,7 @@ func TestCrosschainSwap(r *runner.E2ERunner, _ []string) { r.Logger.Info("******* Second test: BTC -> ERC20ZRC20") // list deployer utxos - utxos, err := r.ListDeployerUTXOs() - require.NoError(r, err) + utxos := r.ListDeployerUTXOs() r.Logger.Info("#utxos %d", len(utxos)) r.Logger.Info("memo address %s", r.ERC20ZRC20Addr) @@ -143,8 +142,7 @@ func TestCrosschainSwap(r *runner.E2ERunner, _ []string) { r.Logger.Info("memo length %d", len(memo)) amount := 0.1 - utxos, err = r.ListDeployerUTXOs() - require.NoError(r, err) + utxos = r.ListDeployerUTXOs() txid, err := r.SendToTSSFromDeployerWithMemo(amount, utxos[0:1], memo) require.NoError(r, err) diff --git a/e2e/e2etests/test_stress_btc_deposit.go b/e2e/e2etests/test_stress_btc_deposit.go index c8496618bf..adb8bbdf6a 100644 --- a/e2e/e2etests/test_stress_btc_deposit.go +++ b/e2e/e2etests/test_stress_btc_deposit.go @@ -28,7 +28,7 @@ func TestStressBTCDeposit(r *runner.E2ERunner, args []string) { // send the deposits for i := 0; i < numDeposits; i++ { i := i - txHash := r.DepositBTCWithAmount(depositAmount, nil) + txHash := r.DepositBTCWithAmount(depositAmount, nil, true) r.Logger.Print("index %d: starting deposit, tx hash: %s", i, txHash.String()) eg.Go(func() error { return monitorBTCDeposit(r, txHash, i, time.Now()) }) diff --git a/e2e/runner/bitcoin.go b/e2e/runner/bitcoin.go index 619844a30c..17e9032d8d 100644 --- a/e2e/runner/bitcoin.go +++ b/e2e/runner/bitcoin.go @@ -29,29 +29,38 @@ import ( ) // ListDeployerUTXOs list the deployer's UTXOs -func (r *E2ERunner) ListDeployerUTXOs() ([]btcjson.ListUnspentResult, error) { +func (r *E2ERunner) ListDeployerUTXOs() []btcjson.ListUnspentResult { // query UTXOs from node utxos, err := r.BtcRPCClient.ListUnspentMinMaxAddresses( 1, 9999999, []btcutil.Address{r.BTCDeployerAddress}, ) - if err != nil { - return nil, err - } + require.NoError(r, err) // filter big-enough UTXOs for test if running on Regtest if r.IsLocalBitcoin() { - utxosFiltered := []btcjson.ListUnspentResult{} + spendableAmount := 0.0 + spendableUTXOs := []btcjson.ListUnspentResult{} for _, utxo := range utxos { - if utxo.Amount >= 1.0 { - utxosFiltered = append(utxosFiltered, utxo) + // 'Spendable' indicates whether we have the private keys to spend this output + if utxo.Spendable && utxo.Amount >= 1.0 { + spendableAmount += utxo.Amount + spendableUTXOs = append(spendableUTXOs, utxo) } } - return utxosFiltered, nil + r.Logger.Info("ListUnspent:") + r.Logger.Info(" spendableUTXOs: %d", len(spendableUTXOs)) + r.Logger.Info(" spendableAmount: %f", spendableAmount) + + require.GreaterOrEqual(r, spendableAmount, 1.5, "not enough spendable BTC to run E2E test") + require.GreaterOrEqual(r, len(spendableUTXOs), 5, "not enough spendable BTC UTXOs to run E2E test") + + return spendableUTXOs } + require.NotEmpty(r, utxos) - return utxos, nil + return utxos } // GetTop20UTXOsForTssAddress returns the top 20 UTXOs for the TSS address. @@ -78,45 +87,66 @@ func (r *E2ERunner) GetTop20UTXOsForTssAddress() ([]btcjson.ListUnspentResult, e } // DepositBTCWithAmount deposits BTC into ZetaChain with a specific amount and memo -func (r *E2ERunner) DepositBTCWithAmount(amount float64, memo *memo.InboundMemo) *chainhash.Hash { +func (r *E2ERunner) DepositBTCWithAmount(amount float64, memo *memo.InboundMemo, addDepositFee bool) *chainhash.Hash { // list deployer utxos - utxos, err := r.ListDeployerUTXOs() - require.NoError(r, err) + utxos := r.ListDeployerUTXOs() - spendableAmount := 0.0 - spendableUTXOs := 0 - for _, utxo := range utxos { - if utxo.Spendable { - spendableAmount += utxo.Amount - spendableUTXOs++ - } + // add depositor fee so that receiver gets the exact given 'amount' in ZetaChain + if addDepositFee { + amount += zetabtc.DefaultDepositorFee } - require.LessOrEqual(r, amount, spendableAmount, "not enough spendable BTC to run the test") + var ( + err error + txHash *chainhash.Hash + ) - r.Logger.Info("ListUnspent:") - r.Logger.Info(" spendableAmount: %f", spendableAmount) - r.Logger.Info(" spendableUTXOs: %d", spendableUTXOs) - r.Logger.Info("Now sending two txs to TSS address...") + // deposit BTC into ZEVM + if memo != nil { + r.Logger.Info("⏳ depositing BTC into ZEVM with standard memo") - // add depositor fee so that receiver gets the exact given 'amount' in ZetaChain - amount += zetabtc.DefaultDepositorFee + // encode memo to bytes + memoBytes, err := memo.EncodeToBytes() + require.NoError(r, err) - // deposit to TSS address - var txHash *chainhash.Hash - if memo != nil { - txHash, err = r.DepositBTCWithStandardMemo(amount, utxos, memo) + txHash, err = r.SendToTSSFromDeployerWithMemo(amount, utxos[:1], memoBytes) + require.NoError(r, err) } else { - txHash, err = r.DepositBTCWithLegacyMemo(amount, utxos, r.EVMAddress()) - } - require.NoError(r, err) + // the legacy memo layout: [20-byte receiver] + [payload] + r.Logger.Info("⏳ depositing BTC into ZEVM with legacy memo") + + // encode 20-byte receiver, no payload + memoBytes := r.EVMAddress().Bytes() - r.Logger.Info("send BTC to TSS txHash: %s", txHash.String()) + txHash, err = r.SendToTSSFromDeployerWithMemo(amount, utxos[:1], memoBytes) + require.NoError(r, err) + } + r.Logger.Info("deposited BTC to TSS txHash: %s", txHash.String()) return txHash } -// DepositBTC deposits BTC from the Bitcoin node wallet into ZetaChain. +// DonateBTC donates BTC from the Bitcoin node wallet to the TSS address. +func (r *E2ERunner) DonateBTC() { + r.Logger.Info("⏳ donating BTC to TSS address") + startTime := time.Now() + defer func() { + r.Logger.Info("✅ BTC donated in %s", time.Since(startTime)) + }() + + // list deployer utxos + utxos := r.ListDeployerUTXOs() + + r.Logger.Info("Now donating BTC to TSS address...") + + // send a donation to the TSS address to compensate for the funds minted automatically during pool creation + // and prevent accounting errors + // it also serves as gas fee for the TSS to send BTC to other addresses + _, err := r.SendToTSSFromDeployerWithMemo(0.11, utxos[:2], []byte(constant.DonationMessage)) + require.NoError(r, err) +} + +// DepositBTC deposits BTC from the Bitcoin node wallet into ZEVM address. // Note: This function only works for node wallet based deployer account. func (r *E2ERunner) DepositBTC(receiver common.Address) { r.Logger.Print("⏳ depositing BTC into ZEVM") @@ -126,36 +156,12 @@ func (r *E2ERunner) DepositBTC(receiver common.Address) { }() // list deployer utxos - utxos, err := r.ListDeployerUTXOs() - require.NoError(r, err) - - spendableAmount := 0.0 - spendableUTXOs := 0 - for _, utxo := range utxos { - // 'Spendable' indicates whether we have the private keys to spend this output - if utxo.Spendable { - spendableAmount += utxo.Amount - spendableUTXOs++ - } - } - - require.GreaterOrEqual(r, spendableAmount, 1.15, "not enough spendable BTC to run the test") - require.GreaterOrEqual(r, spendableUTXOs, 5, "not enough spendable BTC UTXOs to run the test") - - r.Logger.Info("ListUnspent:") - r.Logger.Info(" spendableAmount: %f", spendableAmount) - r.Logger.Info(" spendableUTXOs: %d", spendableUTXOs) - r.Logger.Info("Now sending two txs to TSS address and tester ZEVM address...") + utxos := r.ListDeployerUTXOs() + r.Logger.Info("Now depositing BTC to ZEVM address...") // send initial BTC to the tester ZEVM address amount := 1.15 + zetabtc.DefaultDepositorFee - txHash, err := r.DepositBTCWithLegacyMemo(amount, utxos[:2], receiver) - require.NoError(r, err) - - // send a donation to the TSS address to compensate for the funds minted automatically during pool creation - // and prevent accounting errors - // it also serves as gas fee for the TSS to send BTC to other addresses - _, err = r.SendToTSSFromDeployerWithMemo(0.11, utxos[2:4], []byte(constant.DonationMessage)) + txHash, err := r.SendToTSSFromDeployerWithMemo(amount, utxos[:1], receiver.Bytes()) require.NoError(r, err) r.Logger.Info("testing if the deposit into BTC ZRC20 is successful...") @@ -174,37 +180,6 @@ func (r *E2ERunner) DepositBTC(receiver common.Address) { require.Equal(r, 1, balance.Sign(), "balance should be positive") } -// DepositBTCWithLegacyMemo deposits BTC from the deployer address to the TSS using legacy memo -// -// The legacy memo layout: [20-byte receiver] + [payload] -func (r *E2ERunner) DepositBTCWithLegacyMemo( - amount float64, - inputUTXOs []btcjson.ListUnspentResult, - receiver common.Address, -) (*chainhash.Hash, error) { - r.Logger.Info("⏳ depositing BTC into ZEVM with legacy memo") - - // payload is not needed for pure deposit - memoBytes := receiver.Bytes() - - return r.SendToTSSFromDeployerWithMemo(amount, inputUTXOs, memoBytes) -} - -// DepositBTCWithStandardMemo deposits BTC from the deployer address to the TSS using standard `InboundMemo` struct -func (r *E2ERunner) DepositBTCWithStandardMemo( - amount float64, - inputUTXOs []btcjson.ListUnspentResult, - memoStd *memo.InboundMemo, -) (*chainhash.Hash, error) { - r.Logger.Info("⏳ depositing BTC into ZEVM with standard memo") - - // encode memo to bytes - memoBytes, err := memoStd.EncodeToBytes() - require.NoError(r, err) - - return r.SendToTSSFromDeployerWithMemo(amount, inputUTXOs, memoBytes) -} - func (r *E2ERunner) SendToTSSFromDeployerWithMemo( amount float64, inputUTXOs []btcjson.ListUnspentResult, @@ -335,8 +310,7 @@ func (r *E2ERunner) InscribeToTSSFromDeployerWithMemo( feeRate int64, ) (*chainhash.Hash, int64) { // list deployer utxos - utxos, err := r.ListDeployerUTXOs() - require.NoError(r, err) + utxos := r.ListDeployerUTXOs() // generate commit address builder := NewTapscriptSpender(r.BitcoinParams) @@ -345,7 +319,7 @@ func (r *E2ERunner) InscribeToTSSFromDeployerWithMemo( r.Logger.Info("received inscription commit address: %s", receiver) // send funds to the commit address - commitTxHash, err := r.sendToAddrFromDeployerWithMemo(amount, receiver, utxos, nil) + commitTxHash, err := r.sendToAddrFromDeployerWithMemo(amount, receiver, utxos[:1], nil) require.NoError(r, err) r.Logger.Info("obtained inscription commit txn hash: %s", commitTxHash.String()) diff --git a/proto/zetachain/zetacore/crosschain/tx.proto b/proto/zetachain/zetacore/crosschain/tx.proto index d4b9af05cf..d3ff373cbc 100644 --- a/proto/zetachain/zetacore/crosschain/tx.proto +++ b/proto/zetachain/zetacore/crosschain/tx.proto @@ -184,6 +184,9 @@ message MsgVoteInbound { // define if a smart contract call should be made with asset bool is_cross_chain_call = 19; + + // it carries the error message that caused the inbound to fail + string error_message = 20; } message MsgVoteInboundResponse {} diff --git a/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts b/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts index 0db06c03e2..475ca69bea 100644 --- a/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts +++ b/typescript/zetachain/zetacore/crosschain/tx_pb.d.ts @@ -684,6 +684,13 @@ export declare class MsgVoteInbound extends Message { */ isCrossChainCall: boolean; + /** + * it carries the error message that caused the inbound to fail + * + * @generated from field: string error_message = 20; + */ + errorMessage: string; + constructor(data?: PartialMessage); static readonly runtime: typeof proto3; diff --git a/x/crosschain/client/cli/tx_vote_inbound.go b/x/crosschain/client/cli/tx_vote_inbound.go index e4b8b9ecc9..9a2189497e 100644 --- a/x/crosschain/client/cli/tx_vote_inbound.go +++ b/x/crosschain/client/cli/tx_vote_inbound.go @@ -89,6 +89,7 @@ func CmdVoteInbound() *cobra.Command { uint(argsEventIndex), protocolContractVersion, isArbitraryCall, + "", ) return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) diff --git a/x/crosschain/keeper/cctx_gateway_zevm.go b/x/crosschain/keeper/cctx_gateway_zevm.go index fc247ad7a4..74dcd71e85 100644 --- a/x/crosschain/keeper/cctx_gateway_zevm.go +++ b/x/crosschain/keeper/cctx_gateway_zevm.go @@ -23,6 +23,13 @@ func (c CCTXGatewayZEVM) InitiateOutbound( ctx sdk.Context, config InitiateOutboundConfig, ) (newCCTXStatus types.CctxStatus, err error) { + // abort if CCTX already contains an initial error message from inbound vote msg + if config.CCTX.CctxStatus.ErrorMessage != "" { + config.CCTX.SetAbort("observation failed", "") + return types.CctxStatus_Aborted, nil + } + + // process the deposit tmpCtx, commit := ctx.CacheContext() isContractReverted, err := c.crosschainKeeper.HandleEVMDeposit(tmpCtx, config.CCTX) diff --git a/x/crosschain/keeper/evm_hooks.go b/x/crosschain/keeper/evm_hooks.go index d09f771fbb..9647db6ee1 100644 --- a/x/crosschain/keeper/evm_hooks.go +++ b/x/crosschain/keeper/evm_hooks.go @@ -207,6 +207,7 @@ func (k Keeper) ProcessZRC20WithdrawalEvent( event.Raw.Index, types.ProtocolContractVersion_V1, false, // not relevant for v1 + "", ) cctx, err := k.ValidateInbound(ctx, msg, false) @@ -288,6 +289,7 @@ func (k Keeper) ProcessZetaSentEvent( event.Raw.Index, types.ProtocolContractVersion_V1, false, // not relevant for v1 + "", ) cctx, err := k.ValidateInbound(ctx, msg, true) diff --git a/x/crosschain/types/cctx.go b/x/crosschain/types/cctx.go index b2354a79e1..3d6432ca7c 100644 --- a/x/crosschain/types/cctx.go +++ b/x/crosschain/types/cctx.go @@ -272,7 +272,7 @@ func NewCCTX(ctx sdk.Context, msg MsgVoteInbound, tssPubkey string) (CrossChainT status := &Status{ Status: CctxStatus_PendingInbound, StatusMessage: "", - ErrorMessage: "", + ErrorMessage: msg.ErrorMessage, CreatedTimestamp: ctx.BlockHeader().Time.Unix(), LastUpdateTimestamp: ctx.BlockHeader().Time.Unix(), IsAbortRefunded: false, diff --git a/x/crosschain/types/inbound_parsing.go b/x/crosschain/types/inbound_parsing.go index e057183f2c..c2afee84f2 100644 --- a/x/crosschain/types/inbound_parsing.go +++ b/x/crosschain/types/inbound_parsing.go @@ -143,6 +143,7 @@ func NewWithdrawalInbound( event.Raw.Index, ProtocolContractVersion_V2, event.CallOptions.IsArbitraryCall, + "", WithZEVMRevertOptions(event.RevertOptions), WithCrossChainCall(isCrossChainCall), ), nil @@ -191,6 +192,7 @@ func NewCallInbound( event.Raw.Index, ProtocolContractVersion_V2, event.CallOptions.IsArbitraryCall, + "", WithZEVMRevertOptions(event.RevertOptions), ), nil } @@ -240,6 +242,7 @@ func NewWithdrawAndCallInbound( event.Raw.Index, ProtocolContractVersion_V2, event.CallOptions.IsArbitraryCall, + "", WithZEVMRevertOptions(event.RevertOptions), WithCrossChainCall(true), ), nil diff --git a/x/crosschain/types/message_vote_inbound.go b/x/crosschain/types/message_vote_inbound.go index e612fe582a..ffbf76e611 100644 --- a/x/crosschain/types/message_vote_inbound.go +++ b/x/crosschain/types/message_vote_inbound.go @@ -71,6 +71,7 @@ func NewMsgVoteInbound( eventIndex uint, protocolContractVersion ProtocolContractVersion, isArbitraryCall bool, + errMessage string, options ...InboundVoteOption, ) *MsgVoteInbound { msg := &MsgVoteInbound{ @@ -94,6 +95,7 @@ func NewMsgVoteInbound( ProtocolContractVersion: protocolContractVersion, RevertOptions: NewEmptyRevertOptions(), IsCrossChainCall: false, + ErrorMessage: errMessage, } for _, option := range options { diff --git a/x/crosschain/types/message_vote_inbound_test.go b/x/crosschain/types/message_vote_inbound_test.go index 2c30b2a343..c007557b67 100644 --- a/x/crosschain/types/message_vote_inbound_test.go +++ b/x/crosschain/types/message_vote_inbound_test.go @@ -38,6 +38,7 @@ func TestNewMsgVoteInbound(t *testing.T) { 42, types.ProtocolContractVersion_V1, true, + "", ) require.EqualValues(t, types.NewEmptyRevertOptions(), msg.RevertOptions) }) @@ -64,6 +65,7 @@ func TestNewMsgVoteInbound(t *testing.T) { 31, types.ProtocolContractVersion_V2, true, + "", types.WithRevertOptions(types.RevertOptions{ RevertAddress: revertAddress.Hex(), CallOnRevert: true, @@ -103,6 +105,7 @@ func TestNewMsgVoteInbound(t *testing.T) { 42, types.ProtocolContractVersion_V1, true, + "", types.WithZEVMRevertOptions(gatewayzevm.RevertOptions{ RevertAddress: revertAddress, CallOnRevert: true, @@ -137,6 +140,7 @@ func TestNewMsgVoteInbound(t *testing.T) { 42, types.ProtocolContractVersion_V1, true, + "", types.WithZEVMRevertOptions(gatewayzevm.RevertOptions{ RevertAddress: revertAddress, CallOnRevert: true, @@ -175,6 +179,7 @@ func TestNewMsgVoteInbound(t *testing.T) { 42, types.ProtocolContractVersion_V1, true, + "", types.WithEVMRevertOptions(gatewayevm.RevertOptions{ RevertAddress: revertAddress, CallOnRevert: true, @@ -208,6 +213,7 @@ func TestNewMsgVoteInbound(t *testing.T) { 42, types.ProtocolContractVersion_V1, true, + "", types.WithEVMRevertOptions(gatewayevm.RevertOptions{ RevertAddress: revertAddress, CallOnRevert: true, @@ -243,6 +249,7 @@ func TestNewMsgVoteInbound(t *testing.T) { 42, types.ProtocolContractVersion_V1, true, + "", ) require.False(t, msg.IsCrossChainCall) @@ -263,6 +270,7 @@ func TestNewMsgVoteInbound(t *testing.T) { 42, types.ProtocolContractVersion_V1, true, + "", types.WithCrossChainCall(true), ) require.True(t, msg.IsCrossChainCall) @@ -284,6 +292,7 @@ func TestNewMsgVoteInbound(t *testing.T) { 42, types.ProtocolContractVersion_V1, true, + "", types.WithCrossChainCall(false), ) require.False(t, msg.IsCrossChainCall) @@ -317,6 +326,7 @@ func TestMsgVoteInbound_ValidateBasic(t *testing.T) { 42, types.ProtocolContractVersion_V1, true, + "", ), }, { @@ -338,6 +348,7 @@ func TestMsgVoteInbound_ValidateBasic(t *testing.T) { 42, types.ProtocolContractVersion_V1, true, + "", ), err: sdkerrors.ErrInvalidAddress, }, @@ -360,6 +371,7 @@ func TestMsgVoteInbound_ValidateBasic(t *testing.T) { 42, types.ProtocolContractVersion_V1, true, + "", ), err: types.ErrInvalidChainID, }, @@ -382,6 +394,7 @@ func TestMsgVoteInbound_ValidateBasic(t *testing.T) { 42, types.ProtocolContractVersion_V1, true, + "", ), err: types.ErrInvalidChainID, }, @@ -404,6 +417,7 @@ func TestMsgVoteInbound_ValidateBasic(t *testing.T) { 42, types.ProtocolContractVersion_V1, true, + "", ), err: sdkerrors.ErrInvalidRequest, }, diff --git a/x/crosschain/types/tx.pb.go b/x/crosschain/types/tx.pb.go index bd4c1845ac..5d59d72b85 100644 --- a/x/crosschain/types/tx.pb.go +++ b/x/crosschain/types/tx.pb.go @@ -1021,6 +1021,8 @@ type MsgVoteInbound struct { CallOptions *CallOptions `protobuf:"bytes,18,opt,name=call_options,json=callOptions,proto3" json:"call_options,omitempty"` // define if a smart contract call should be made with asset IsCrossChainCall bool `protobuf:"varint,19,opt,name=is_cross_chain_call,json=isCrossChainCall,proto3" json:"is_cross_chain_call,omitempty"` + // it carries the error message that caused the inbound to fail + ErrorMessage string `protobuf:"bytes,20,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` } func (m *MsgVoteInbound) Reset() { *m = MsgVoteInbound{} } @@ -1175,6 +1177,13 @@ func (m *MsgVoteInbound) GetIsCrossChainCall() bool { return false } +func (m *MsgVoteInbound) GetErrorMessage() string { + if m != nil { + return m.ErrorMessage + } + return "" +} + type MsgVoteInboundResponse struct { } @@ -1740,123 +1749,124 @@ func init() { } var fileDescriptor_15f0860550897740 = []byte{ - // 1848 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x59, 0x4b, 0x6f, 0x1b, 0xc9, + // 1865 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x59, 0xcd, 0x6e, 0x1b, 0xc9, 0x11, 0xf6, 0xd8, 0x12, 0x45, 0x16, 0x25, 0x59, 0x6e, 0xcb, 0x36, 0x3d, 0x5a, 0xc9, 0x32, 0x1d, 0x3b, 0xc2, 0xc2, 0x22, 0x1d, 0x7a, 0xe3, 0x6c, 0xbc, 0xc1, 0x6e, 0x2c, 0xee, 0x5a, 0x2b, 0xc0, - 0xb4, 0x85, 0x59, 0x79, 0xf3, 0xb8, 0x0c, 0x86, 0x33, 0xad, 0xd1, 0x40, 0xe4, 0x34, 0x31, 0xdd, - 0xe4, 0x52, 0x46, 0x80, 0x04, 0x01, 0x02, 0xe4, 0x98, 0x04, 0x39, 0xed, 0x3d, 0x87, 0xe4, 0x47, - 0xe4, 0xbc, 0x47, 0x23, 0xa7, 0x20, 0x07, 0x23, 0xb0, 0x0f, 0xb9, 0x06, 0xb9, 0x06, 0x01, 0x82, - 0xae, 0xee, 0x19, 0x91, 0xc3, 0xa7, 0x28, 0x04, 0xb9, 0x88, 0xdd, 0xd5, 0xf5, 0x55, 0x57, 0x55, - 0x57, 0x75, 0x57, 0x8d, 0xe0, 0xde, 0x2b, 0x2a, 0x1c, 0xf7, 0xc8, 0x09, 0xc2, 0x32, 0x8e, 0x58, - 0x44, 0xcb, 0x6e, 0xc4, 0x38, 0x57, 0x34, 0xd1, 0x2d, 0xb5, 0x22, 0x26, 0x18, 0x59, 0x4f, 0xf8, - 0x4a, 0x31, 0x5f, 0xe9, 0x94, 0xcf, 0x5c, 0xf5, 0x99, 0xcf, 0x90, 0xb3, 0x2c, 0x47, 0x0a, 0x64, - 0xbe, 0x3f, 0x44, 0x78, 0xeb, 0xd8, 0x2f, 0x23, 0x89, 0xeb, 0x1f, 0xcd, 0x7b, 0x6f, 0x14, 0x2f, - 0x0b, 0x42, 0xfc, 0x33, 0x41, 0x66, 0x2b, 0x62, 0xec, 0x90, 0xeb, 0x1f, 0xcd, 0xfb, 0x68, 0xbc, - 0x71, 0x91, 0x23, 0xa8, 0xdd, 0x08, 0x9a, 0x81, 0xa0, 0x91, 0x7d, 0xd8, 0x70, 0xfc, 0x18, 0x57, - 0x19, 0x8f, 0xc3, 0xa1, 0x8d, 0x63, 0x3b, 0x76, 0x50, 0xf1, 0x77, 0x06, 0x90, 0x1a, 0xf7, 0x6b, - 0x81, 0x2f, 0xc5, 0x1e, 0x70, 0xfe, 0xb4, 0x1d, 0x7a, 0x9c, 0x14, 0x60, 0xc1, 0x8d, 0xa8, 0x23, - 0x58, 0x54, 0x30, 0x36, 0x8d, 0xad, 0x9c, 0x15, 0x4f, 0xc9, 0x4d, 0xc8, 0x2a, 0x11, 0x81, 0x57, - 0xb8, 0xb8, 0x69, 0x6c, 0x5d, 0xb2, 0x16, 0x70, 0xbe, 0xe7, 0x91, 0x5d, 0xc8, 0x38, 0x4d, 0xd6, - 0x0e, 0x45, 0xe1, 0x92, 0xc4, 0xec, 0x94, 0xbf, 0x79, 0x73, 0xeb, 0xc2, 0xdf, 0xde, 0xdc, 0xfa, - 0xb6, 0x1f, 0x88, 0xa3, 0x76, 0xbd, 0xe4, 0xb2, 0x66, 0xd9, 0x65, 0xbc, 0xc9, 0xb8, 0xfe, 0xd9, - 0xe6, 0xde, 0x71, 0x59, 0x9c, 0xb4, 0x28, 0x2f, 0xbd, 0x0c, 0x42, 0x61, 0x69, 0x78, 0xf1, 0x3d, - 0x30, 0x07, 0x75, 0xb2, 0x28, 0x6f, 0xb1, 0x90, 0xd3, 0xe2, 0x73, 0xb8, 0x5a, 0xe3, 0xfe, 0xcb, - 0x96, 0xa7, 0x16, 0x9f, 0x78, 0x5e, 0x44, 0xf9, 0x38, 0x95, 0xd7, 0x01, 0x04, 0xe7, 0x76, 0xab, - 0x5d, 0x3f, 0xa6, 0x27, 0xa8, 0x74, 0xce, 0xca, 0x09, 0xce, 0xf7, 0x91, 0x50, 0x5c, 0x87, 0xb5, - 0x21, 0xf2, 0x92, 0xed, 0xfe, 0x74, 0x11, 0x56, 0x6b, 0xdc, 0x7f, 0xe2, 0x79, 0x7b, 0x61, 0x9d, - 0xb5, 0x43, 0xef, 0x20, 0x72, 0xdc, 0x63, 0x1a, 0xcd, 0xe6, 0xa3, 0x1b, 0xb0, 0x20, 0xba, 0xf6, - 0x91, 0xc3, 0x8f, 0x94, 0x93, 0xac, 0x8c, 0xe8, 0x7e, 0xee, 0xf0, 0x23, 0xb2, 0x03, 0x39, 0x19, - 0x2e, 0xb6, 0x74, 0x47, 0x61, 0x6e, 0xd3, 0xd8, 0x5a, 0xae, 0xdc, 0x2d, 0x0d, 0x89, 0xde, 0xd6, - 0xb1, 0x5f, 0xc2, 0xb8, 0xaa, 0xb2, 0x20, 0x3c, 0x38, 0x69, 0x51, 0x2b, 0xeb, 0xea, 0x11, 0xf9, - 0x18, 0xe6, 0x31, 0x90, 0x0a, 0xf3, 0x9b, 0xc6, 0x56, 0xbe, 0xf2, 0xad, 0x51, 0x78, 0x1d, 0x6d, - 0xfb, 0xf2, 0x67, 0xe7, 0x62, 0xc1, 0xb0, 0x14, 0x8c, 0xdc, 0x06, 0xa8, 0x37, 0x98, 0x7b, 0xac, - 0xf4, 0xcb, 0xe0, 0x21, 0xca, 0xe5, 0x1c, 0x52, 0x51, 0xcd, 0x75, 0xc8, 0x8a, 0xae, 0x1d, 0x84, - 0x1e, 0xed, 0x16, 0x16, 0xa4, 0x69, 0xc8, 0xb0, 0x20, 0xba, 0x7b, 0x92, 0x54, 0xdc, 0x80, 0xf7, - 0x86, 0xf9, 0x2a, 0x71, 0xe6, 0x5f, 0x0c, 0xb8, 0x52, 0xe3, 0xfe, 0x8f, 0x8e, 0x02, 0x41, 0x1b, - 0x01, 0x17, 0x9f, 0x59, 0xd5, 0xca, 0x83, 0x31, 0x9e, 0xbc, 0x03, 0x4b, 0x34, 0x72, 0x2b, 0x0f, - 0x6c, 0x47, 0x9d, 0x8a, 0x3e, 0xbd, 0x45, 0x24, 0xc6, 0x27, 0xdf, 0xeb, 0xee, 0x4b, 0xfd, 0xee, - 0x26, 0x30, 0x17, 0x3a, 0x4d, 0xe5, 0xd0, 0x9c, 0x85, 0x63, 0x72, 0x1d, 0x32, 0xfc, 0xa4, 0x59, - 0x67, 0x0d, 0x74, 0x53, 0xce, 0xd2, 0x33, 0x62, 0x42, 0xd6, 0xa3, 0x6e, 0xd0, 0x74, 0x1a, 0x1c, - 0x6d, 0x5f, 0xb2, 0x92, 0x39, 0x59, 0x83, 0x9c, 0xef, 0x70, 0x95, 0x75, 0xca, 0x6e, 0x2b, 0xeb, - 0x3b, 0xfc, 0x99, 0x9c, 0x17, 0x6d, 0xb8, 0x39, 0x60, 0x53, 0x6c, 0xb1, 0xb4, 0xe0, 0x55, 0x9f, - 0x05, 0xca, 0xc2, 0xc5, 0x57, 0xbd, 0x16, 0xac, 0x03, 0xb8, 0x6e, 0xe2, 0x57, 0x1d, 0xa1, 0x92, - 0xa2, 0xbc, 0xfa, 0x1f, 0x03, 0xae, 0x29, 0xb7, 0xbe, 0x68, 0x8b, 0xf3, 0xc7, 0xe0, 0x2a, 0xcc, - 0x87, 0x2c, 0x74, 0x29, 0x3a, 0x6b, 0xce, 0x52, 0x93, 0xde, 0xc8, 0x9c, 0xeb, 0x8b, 0xcc, 0xff, - 0x7f, 0x54, 0x7d, 0x0c, 0xeb, 0x43, 0xcd, 0x4f, 0x9c, 0xbc, 0x0e, 0x10, 0x70, 0x3b, 0xa2, 0x4d, - 0xd6, 0xa1, 0x1e, 0x7a, 0x22, 0x6b, 0xe5, 0x02, 0x6e, 0x29, 0x42, 0x91, 0x42, 0xa1, 0xc6, 0x7d, - 0x35, 0xfb, 0xdf, 0x79, 0xb0, 0x58, 0x84, 0xcd, 0x51, 0xdb, 0x24, 0x09, 0xf0, 0x67, 0x03, 0x2e, - 0xd7, 0xb8, 0xff, 0x25, 0x13, 0x74, 0xd7, 0xe1, 0xfb, 0x51, 0xe0, 0xd2, 0x99, 0x55, 0x68, 0x49, - 0x74, 0xac, 0x02, 0x4e, 0xc8, 0x6d, 0x58, 0x6c, 0x45, 0x01, 0x8b, 0x02, 0x71, 0x62, 0x1f, 0x52, - 0x8a, 0xde, 0x9e, 0xb3, 0xf2, 0x31, 0xed, 0x29, 0x45, 0x16, 0x75, 0x1c, 0x61, 0xbb, 0x59, 0xa7, - 0x11, 0x1e, 0xf6, 0x9c, 0x95, 0x47, 0xda, 0x73, 0x24, 0x11, 0x13, 0x32, 0xbc, 0xdd, 0x6a, 0x35, - 0x4e, 0x54, 0x86, 0xe0, 0x61, 0x68, 0x4a, 0xf1, 0x26, 0xdc, 0x48, 0xe9, 0x9f, 0xd8, 0xf6, 0x87, - 0x4c, 0x62, 0x5b, 0x6c, 0xfe, 0x18, 0xdb, 0xd6, 0x00, 0x23, 0x5c, 0x45, 0x85, 0x0a, 0xf9, 0xac, - 0x24, 0x60, 0x40, 0x7c, 0x00, 0xd7, 0x59, 0x9d, 0xd3, 0xa8, 0x43, 0x3d, 0x9b, 0x69, 0x59, 0xbd, - 0xb7, 0xe6, 0x6a, 0xbc, 0x1a, 0x6f, 0x84, 0xa8, 0x2a, 0x6c, 0x0c, 0xa2, 0x74, 0xec, 0xd1, 0xc0, - 0x3f, 0x12, 0xda, 0xd8, 0xb5, 0x34, 0x7a, 0x07, 0x23, 0x11, 0x59, 0xc8, 0x47, 0x60, 0x0e, 0x0a, - 0x91, 0xc9, 0xdf, 0xe6, 0xd4, 0x2b, 0x00, 0x0a, 0xb8, 0x91, 0x16, 0xb0, 0xeb, 0xf0, 0x97, 0x9c, - 0x7a, 0xe4, 0x17, 0x06, 0xdc, 0x1d, 0x44, 0xd3, 0xc3, 0x43, 0xea, 0x8a, 0xa0, 0x43, 0x51, 0x8e, - 0x3a, 0xb6, 0x3c, 0x7a, 0xb6, 0xa4, 0x9f, 0xc8, 0x7b, 0x53, 0x3c, 0x91, 0x7b, 0xa1, 0xb0, 0x6e, - 0xa7, 0x37, 0xfe, 0x2c, 0x16, 0x9d, 0x44, 0xd3, 0xfe, 0x64, 0x0d, 0xd4, 0x35, 0xb6, 0x88, 0xa6, - 0x8c, 0x95, 0x88, 0xf7, 0x1b, 0x61, 0xb0, 0xdc, 0x71, 0x1a, 0x6d, 0x6a, 0x47, 0xd4, 0xa5, 0x81, - 0xcc, 0x30, 0x15, 0x16, 0x9f, 0x9f, 0xf1, 0x7d, 0xff, 0xd7, 0x9b, 0x5b, 0xd7, 0x4e, 0x9c, 0x66, - 0xe3, 0x71, 0xb1, 0x5f, 0x5c, 0xd1, 0x5a, 0x42, 0x82, 0xa5, 0xe7, 0xe4, 0x53, 0xc8, 0x70, 0xe1, - 0x88, 0xb6, 0xba, 0x87, 0x97, 0x2b, 0xf7, 0x47, 0x3e, 0x84, 0xaa, 0x14, 0xd3, 0xc0, 0x2f, 0x10, - 0x63, 0x69, 0x2c, 0xb9, 0x0b, 0xcb, 0x89, 0xfd, 0xc8, 0xa8, 0x2f, 0xee, 0xa5, 0x98, 0x5a, 0x95, - 0x44, 0x72, 0x1f, 0x48, 0xc2, 0x26, 0xcb, 0x04, 0x95, 0xd8, 0x59, 0x74, 0xce, 0x4a, 0xbc, 0x72, - 0xc0, 0xf9, 0x73, 0xbc, 0x25, 0xfb, 0x9e, 0xe9, 0xdc, 0x4c, 0xcf, 0x74, 0x4f, 0x0a, 0xc5, 0x3e, - 0x4f, 0x52, 0xe8, 0x1f, 0x19, 0x58, 0xd6, 0x6b, 0xfa, 0x05, 0x1d, 0x93, 0x41, 0xf2, 0x21, 0xa3, - 0xa1, 0x47, 0x23, 0x9d, 0x3e, 0x7a, 0x46, 0xee, 0xc1, 0x65, 0x35, 0xb2, 0x53, 0xcf, 0xe2, 0x92, - 0x22, 0x57, 0xf5, 0x15, 0x62, 0x42, 0x56, 0x1f, 0x41, 0xa4, 0xaf, 0xfc, 0x64, 0x2e, 0x9d, 0x17, - 0x8f, 0xb5, 0xf3, 0xe6, 0x95, 0x88, 0x98, 0xaa, 0x9c, 0x77, 0x5a, 0xf2, 0x65, 0xce, 0x55, 0xf2, - 0x49, 0x2b, 0x9b, 0x94, 0x73, 0xc7, 0x57, 0xae, 0xcf, 0x59, 0xf1, 0x54, 0xde, 0x57, 0x41, 0xd8, - 0x73, 0x01, 0xe4, 0x70, 0x39, 0xaf, 0x69, 0x98, 0xf7, 0x0f, 0x60, 0x35, 0x66, 0xe9, 0xcb, 0x76, - 0x95, 0xac, 0x44, 0xaf, 0xf5, 0x26, 0x79, 0xdf, 0x7b, 0x9e, 0x47, 0xb6, 0xe4, 0x3d, 0xef, 0x3f, - 0xe3, 0xc5, 0xd9, 0x4a, 0xb1, 0x35, 0xc8, 0x89, 0xae, 0xcd, 0xa2, 0xc0, 0x0f, 0xc2, 0xc2, 0x92, - 0x72, 0xae, 0xe8, 0xbe, 0xc0, 0xb9, 0xbc, 0xbb, 0x1d, 0xce, 0xa9, 0x28, 0x2c, 0xe3, 0x82, 0x9a, - 0x90, 0x5b, 0x90, 0xa7, 0x1d, 0x1a, 0x0a, 0xfd, 0x0e, 0x5e, 0x46, 0xad, 0x00, 0x49, 0xf8, 0x0c, - 0x92, 0x08, 0x6e, 0x62, 0xd1, 0xee, 0xb2, 0x86, 0xed, 0xb2, 0x50, 0x44, 0x8e, 0x2b, 0xec, 0x0e, - 0x8d, 0x78, 0xc0, 0xc2, 0xc2, 0x0a, 0xea, 0xf9, 0xa8, 0x34, 0xb6, 0xe1, 0x91, 0x8f, 0x33, 0xe2, - 0xab, 0x1a, 0xfe, 0xa5, 0x42, 0x5b, 0x37, 0x5a, 0xc3, 0x17, 0xc8, 0x4f, 0x64, 0x1c, 0x74, 0x68, - 0x24, 0x6c, 0xd6, 0x12, 0x01, 0x0b, 0x79, 0xe1, 0x0a, 0x56, 0x01, 0xf7, 0x27, 0x6c, 0x64, 0x21, - 0xe8, 0x85, 0xc2, 0xec, 0xcc, 0xc9, 0xb0, 0x90, 0xb1, 0xd3, 0x43, 0x24, 0x35, 0x58, 0x74, 0x9d, - 0x46, 0x23, 0x11, 0x4c, 0x50, 0xf0, 0xfb, 0x13, 0x04, 0x57, 0x9d, 0x46, 0x43, 0x4b, 0xb0, 0xf2, - 0xee, 0xe9, 0x84, 0x6c, 0xc3, 0xd5, 0x80, 0xdb, 0xbd, 0x4d, 0x8e, 0x5c, 0x2d, 0x5c, 0xc5, 0x62, - 0x60, 0x25, 0xe0, 0x55, 0xb9, 0x82, 0x51, 0x2b, 0x45, 0x14, 0x0b, 0x70, 0xbd, 0x3f, 0xd1, 0x92, - 0x1c, 0x7c, 0x86, 0x25, 0xea, 0x93, 0x3a, 0x8b, 0xc4, 0x17, 0xa2, 0xed, 0x1e, 0x57, 0xab, 0x07, - 0x3f, 0x1e, 0xdf, 0x5d, 0x8c, 0xab, 0xdd, 0xd6, 0xb0, 0x38, 0xec, 0x97, 0x96, 0x6c, 0xd5, 0xc1, - 0xd6, 0xc2, 0xa2, 0x87, 0xed, 0xd0, 0x43, 0x16, 0xea, 0x9d, 0x6b, 0x37, 0x95, 0xb6, 0x52, 0x5a, - 0x52, 0x6e, 0xaa, 0xf7, 0x72, 0x49, 0x51, 0x75, 0xbd, 0xa9, 0xcb, 0xf4, 0x81, 0x7d, 0x13, 0xbd, - 0xbe, 0x36, 0x50, 0x6b, 0xd5, 0x13, 0x59, 0x8e, 0xa0, 0xcf, 0x54, 0xbb, 0xf9, 0x54, 0x76, 0x9b, - 0x63, 0xb4, 0x73, 0x81, 0x0c, 0x76, 0xa7, 0xa8, 0x65, 0xbe, 0x52, 0x9e, 0x14, 0x31, 0xa9, 0x6d, - 0x74, 0xd0, 0xac, 0x44, 0x29, 0x7a, 0xf1, 0x0e, 0xdc, 0x1e, 0xa9, 0x5b, 0x62, 0xc1, 0x3f, 0x0d, - 0xec, 0xea, 0x74, 0x0f, 0x89, 0x25, 0x79, 0xb5, 0xcd, 0x05, 0xf3, 0x4e, 0xce, 0xd1, 0xe0, 0x96, - 0xe0, 0x6a, 0x48, 0xbf, 0xb2, 0x5d, 0x25, 0x28, 0xe5, 0xe2, 0x2b, 0x21, 0xfd, 0x4a, 0x6f, 0x11, - 0x97, 0xf5, 0x03, 0xdd, 0xcb, 0xdc, 0x90, 0xee, 0xe5, 0xf4, 0x0a, 0x9d, 0x3f, 0x5f, 0xd7, 0xfc, - 0x29, 0xdc, 0x19, 0x63, 0x71, 0x6f, 0xad, 0xdc, 0x13, 0x41, 0x46, 0x3a, 0x5e, 0x9b, 0x58, 0xc4, - 0x2a, 0xef, 0xf6, 0x0a, 0xd9, 0x77, 0xda, 0x5c, 0xbf, 0xb0, 0xb3, 0x17, 0xac, 0x52, 0x06, 0xba, - 0x2b, 0x6b, 0xa9, 0x49, 0x71, 0x0f, 0xb6, 0x26, 0x6d, 0x37, 0xa5, 0xe6, 0x95, 0x7f, 0x2f, 0xc3, - 0xa5, 0x1a, 0xf7, 0xc9, 0xaf, 0x0d, 0x20, 0x43, 0x5a, 0xa5, 0x0f, 0x26, 0xc4, 0xdf, 0xd0, 0x0e, - 0xc3, 0xfc, 0xc1, 0x2c, 0xa8, 0x44, 0xe3, 0x5f, 0x19, 0x70, 0x65, 0xf0, 0xc3, 0xc1, 0xc3, 0xa9, - 0x64, 0xf6, 0x83, 0xcc, 0x8f, 0x66, 0x00, 0x25, 0x7a, 0xfc, 0xd6, 0x80, 0x6b, 0xc3, 0xdb, 0x9f, - 0xef, 0x4d, 0x16, 0x3b, 0x14, 0x68, 0x7e, 0x32, 0x23, 0x30, 0xd1, 0xa9, 0x03, 0x8b, 0x7d, 0x5d, - 0x50, 0x69, 0xb2, 0xc0, 0x5e, 0x7e, 0xf3, 0xd1, 0xd9, 0xf8, 0xd3, 0xfb, 0x26, 0x1d, 0xca, 0x94, - 0xfb, 0xc6, 0xfc, 0xd3, 0xee, 0x9b, 0x2e, 0xed, 0x08, 0x87, 0x7c, 0x6f, 0x59, 0xb7, 0x3d, 0x9d, - 0x18, 0xcd, 0x6e, 0x7e, 0xf7, 0x4c, 0xec, 0xc9, 0xa6, 0x3f, 0x83, 0xe5, 0xd4, 0xb7, 0x96, 0x07, - 0x93, 0x05, 0xf5, 0x23, 0xcc, 0x0f, 0xcf, 0x8a, 0x48, 0x76, 0xff, 0xa5, 0x01, 0x2b, 0x03, 0xdf, - 0xe9, 0x2a, 0x93, 0xc5, 0xa5, 0x31, 0xe6, 0xe3, 0xb3, 0x63, 0x12, 0x25, 0x7e, 0x0e, 0x97, 0xd3, - 0x5f, 0x37, 0xbf, 0x33, 0x59, 0x5c, 0x0a, 0x62, 0x7e, 0xff, 0xcc, 0x90, 0xde, 0x33, 0x48, 0x15, - 0x13, 0x53, 0x9c, 0x41, 0x3f, 0x62, 0x9a, 0x33, 0x18, 0x5e, 0x62, 0xe0, 0x15, 0x34, 0x58, 0x60, - 0x3c, 0x9c, 0x26, 0x7b, 0x53, 0xa0, 0x69, 0xae, 0xa0, 0x91, 0x25, 0x05, 0xf9, 0xbd, 0x01, 0xd7, - 0x47, 0xd4, 0x13, 0x1f, 0x4e, 0x7b, 0xba, 0x69, 0xa4, 0xf9, 0xc3, 0x59, 0x91, 0x89, 0x5a, 0x5f, - 0x1b, 0x50, 0x18, 0x59, 0x24, 0x3c, 0x9e, 0xfa, 0xd0, 0x07, 0xb0, 0xe6, 0xce, 0xec, 0xd8, 0x44, - 0xb9, 0x3f, 0x1a, 0xb0, 0x3e, 0xfe, 0x25, 0xfe, 0x64, 0x5a, 0x07, 0x8c, 0x10, 0x60, 0xee, 0x9e, - 0x53, 0x40, 0xac, 0xeb, 0xce, 0xee, 0x37, 0x6f, 0x37, 0x8c, 0xd7, 0x6f, 0x37, 0x8c, 0xbf, 0xbf, - 0xdd, 0x30, 0x7e, 0xf3, 0x6e, 0xe3, 0xc2, 0xeb, 0x77, 0x1b, 0x17, 0xfe, 0xfa, 0x6e, 0xe3, 0xc2, - 0x4f, 0xb7, 0x7b, 0x0a, 0x19, 0xb9, 0xc5, 0xb6, 0xfa, 0x77, 0x44, 0xc8, 0x3c, 0x5a, 0xee, 0xf6, - 0xfd, 0xd7, 0x46, 0xd6, 0x34, 0xf5, 0x0c, 0xb6, 0x22, 0x0f, 0xff, 0x1b, 0x00, 0x00, 0xff, 0xff, - 0x84, 0xba, 0x82, 0x4a, 0xe3, 0x19, 0x00, 0x00, + 0xb4, 0x85, 0x59, 0x79, 0xf3, 0x73, 0x19, 0x0c, 0x67, 0x5a, 0xa3, 0x81, 0xc8, 0x69, 0x62, 0xba, + 0xc9, 0xa5, 0x8c, 0x00, 0x09, 0x02, 0x04, 0xc8, 0x21, 0x87, 0x24, 0xc8, 0x69, 0xef, 0x39, 0x24, + 0x0f, 0x91, 0xf3, 0x1e, 0x8d, 0x9c, 0x82, 0x1c, 0x8c, 0xc0, 0x7e, 0x81, 0x20, 0xd7, 0x20, 0x40, + 0xd0, 0xd5, 0x3d, 0x23, 0x72, 0xf8, 0x2b, 0x0a, 0x41, 0x2e, 0x62, 0x77, 0x75, 0x7d, 0xd5, 0x55, + 0xd5, 0x55, 0xdd, 0x55, 0x23, 0xb8, 0xf7, 0x8a, 0x0a, 0xc7, 0x3d, 0x72, 0x82, 0xb0, 0x8c, 0x23, + 0x16, 0xd1, 0xb2, 0x1b, 0x31, 0xce, 0x15, 0x4d, 0x74, 0x4b, 0xad, 0x88, 0x09, 0x46, 0xd6, 0x13, + 0xbe, 0x52, 0xcc, 0x57, 0x3a, 0xe5, 0x33, 0x57, 0x7d, 0xe6, 0x33, 0xe4, 0x2c, 0xcb, 0x91, 0x02, + 0x99, 0xef, 0x0f, 0x11, 0xde, 0x3a, 0xf6, 0xcb, 0x48, 0xe2, 0xfa, 0x47, 0xf3, 0xde, 0x1b, 0xc5, + 0xcb, 0x82, 0x10, 0xff, 0x4c, 0x90, 0xd9, 0x8a, 0x18, 0x3b, 0xe4, 0xfa, 0x47, 0xf3, 0x3e, 0x1a, + 0x6f, 0x5c, 0xe4, 0x08, 0x6a, 0x37, 0x82, 0x66, 0x20, 0x68, 0x64, 0x1f, 0x36, 0x1c, 0x3f, 0xc6, + 0x55, 0xc6, 0xe3, 0x70, 0x68, 0xe3, 0xd8, 0x8e, 0x1d, 0x54, 0xfc, 0xbd, 0x01, 0xa4, 0xc6, 0xfd, + 0x5a, 0xe0, 0x4b, 0xb1, 0x07, 0x9c, 0x3f, 0x6d, 0x87, 0x1e, 0x27, 0x05, 0x58, 0x70, 0x23, 0xea, + 0x08, 0x16, 0x15, 0x8c, 0x4d, 0x63, 0x2b, 0x67, 0xc5, 0x53, 0x72, 0x13, 0xb2, 0x4a, 0x44, 0xe0, + 0x15, 0x2e, 0x6e, 0x1a, 0x5b, 0x97, 0xac, 0x05, 0x9c, 0xef, 0x79, 0x64, 0x17, 0x32, 0x4e, 0x93, + 0xb5, 0x43, 0x51, 0xb8, 0x24, 0x31, 0x3b, 0xe5, 0x6f, 0xde, 0xdc, 0xba, 0xf0, 0xf7, 0x37, 0xb7, + 0xbe, 0xed, 0x07, 0xe2, 0xa8, 0x5d, 0x2f, 0xb9, 0xac, 0x59, 0x76, 0x19, 0x6f, 0x32, 0xae, 0x7f, + 0xb6, 0xb9, 0x77, 0x5c, 0x16, 0x27, 0x2d, 0xca, 0x4b, 0x2f, 0x83, 0x50, 0x58, 0x1a, 0x5e, 0x7c, + 0x0f, 0xcc, 0x41, 0x9d, 0x2c, 0xca, 0x5b, 0x2c, 0xe4, 0xb4, 0xf8, 0x1c, 0xae, 0xd6, 0xb8, 0xff, + 0xb2, 0xe5, 0xa9, 0xc5, 0x27, 0x9e, 0x17, 0x51, 0x3e, 0x4e, 0xe5, 0x75, 0x00, 0xc1, 0xb9, 0xdd, + 0x6a, 0xd7, 0x8f, 0xe9, 0x09, 0x2a, 0x9d, 0xb3, 0x72, 0x82, 0xf3, 0x7d, 0x24, 0x14, 0xd7, 0x61, + 0x6d, 0x88, 0xbc, 0x64, 0xbb, 0x3f, 0x5f, 0x84, 0xd5, 0x1a, 0xf7, 0x9f, 0x78, 0xde, 0x5e, 0x58, + 0x67, 0xed, 0xd0, 0x3b, 0x88, 0x1c, 0xf7, 0x98, 0x46, 0xb3, 0xf9, 0xe8, 0x06, 0x2c, 0x88, 0xae, + 0x7d, 0xe4, 0xf0, 0x23, 0xe5, 0x24, 0x2b, 0x23, 0xba, 0x9f, 0x3b, 0xfc, 0x88, 0xec, 0x40, 0x4e, + 0x86, 0x8b, 0x2d, 0xdd, 0x51, 0x98, 0xdb, 0x34, 0xb6, 0x96, 0x2b, 0x77, 0x4b, 0x43, 0xa2, 0xb7, + 0x75, 0xec, 0x97, 0x30, 0xae, 0xaa, 0x2c, 0x08, 0x0f, 0x4e, 0x5a, 0xd4, 0xca, 0xba, 0x7a, 0x44, + 0x3e, 0x86, 0x79, 0x0c, 0xa4, 0xc2, 0xfc, 0xa6, 0xb1, 0x95, 0xaf, 0x7c, 0x6b, 0x14, 0x5e, 0x47, + 0xdb, 0xbe, 0xfc, 0xd9, 0xb9, 0x58, 0x30, 0x2c, 0x05, 0x23, 0xb7, 0x01, 0xea, 0x0d, 0xe6, 0x1e, + 0x2b, 0xfd, 0x32, 0x78, 0x88, 0x72, 0x39, 0x87, 0x54, 0x54, 0x73, 0x1d, 0xb2, 0xa2, 0x6b, 0x07, + 0xa1, 0x47, 0xbb, 0x85, 0x05, 0x69, 0x1a, 0x32, 0x2c, 0x88, 0xee, 0x9e, 0x24, 0x15, 0x37, 0xe0, + 0xbd, 0x61, 0xbe, 0x4a, 0x9c, 0xf9, 0x57, 0x03, 0xae, 0xd4, 0xb8, 0xff, 0xa3, 0xa3, 0x40, 0xd0, + 0x46, 0xc0, 0xc5, 0x67, 0x56, 0xb5, 0xf2, 0x60, 0x8c, 0x27, 0xef, 0xc0, 0x12, 0x8d, 0xdc, 0xca, + 0x03, 0xdb, 0x51, 0xa7, 0xa2, 0x4f, 0x6f, 0x11, 0x89, 0xf1, 0xc9, 0xf7, 0xba, 0xfb, 0x52, 0xbf, + 0xbb, 0x09, 0xcc, 0x85, 0x4e, 0x53, 0x39, 0x34, 0x67, 0xe1, 0x98, 0x5c, 0x87, 0x0c, 0x3f, 0x69, + 0xd6, 0x59, 0x03, 0xdd, 0x94, 0xb3, 0xf4, 0x8c, 0x98, 0x90, 0xf5, 0xa8, 0x1b, 0x34, 0x9d, 0x06, + 0x47, 0xdb, 0x97, 0xac, 0x64, 0x4e, 0xd6, 0x20, 0xe7, 0x3b, 0x5c, 0x65, 0x9d, 0xb2, 0xdb, 0xca, + 0xfa, 0x0e, 0x7f, 0x26, 0xe7, 0x45, 0x1b, 0x6e, 0x0e, 0xd8, 0x14, 0x5b, 0x2c, 0x2d, 0x78, 0xd5, + 0x67, 0x81, 0xb2, 0x70, 0xf1, 0x55, 0xaf, 0x05, 0xeb, 0x00, 0xae, 0x9b, 0xf8, 0x55, 0x47, 0xa8, + 0xa4, 0x28, 0xaf, 0xfe, 0xc7, 0x80, 0x6b, 0xca, 0xad, 0x2f, 0xda, 0xe2, 0xfc, 0x31, 0xb8, 0x0a, + 0xf3, 0x21, 0x0b, 0x5d, 0x8a, 0xce, 0x9a, 0xb3, 0xd4, 0xa4, 0x37, 0x32, 0xe7, 0xfa, 0x22, 0xf3, + 0xff, 0x1f, 0x55, 0x1f, 0xc3, 0xfa, 0x50, 0xf3, 0x13, 0x27, 0xaf, 0x03, 0x04, 0xdc, 0x8e, 0x68, + 0x93, 0x75, 0xa8, 0x87, 0x9e, 0xc8, 0x5a, 0xb9, 0x80, 0x5b, 0x8a, 0x50, 0xa4, 0x50, 0xa8, 0x71, + 0x5f, 0xcd, 0xfe, 0x77, 0x1e, 0x2c, 0x16, 0x61, 0x73, 0xd4, 0x36, 0x49, 0x02, 0xfc, 0xc5, 0x80, + 0xcb, 0x35, 0xee, 0x7f, 0xc9, 0x04, 0xdd, 0x75, 0xf8, 0x7e, 0x14, 0xb8, 0x74, 0x66, 0x15, 0x5a, + 0x12, 0x1d, 0xab, 0x80, 0x13, 0x72, 0x1b, 0x16, 0x5b, 0x51, 0xc0, 0xa2, 0x40, 0x9c, 0xd8, 0x87, + 0x94, 0xa2, 0xb7, 0xe7, 0xac, 0x7c, 0x4c, 0x7b, 0x4a, 0x91, 0x45, 0x1d, 0x47, 0xd8, 0x6e, 0xd6, + 0x69, 0x84, 0x87, 0x3d, 0x67, 0xe5, 0x91, 0xf6, 0x1c, 0x49, 0xc4, 0x84, 0x0c, 0x6f, 0xb7, 0x5a, + 0x8d, 0x13, 0x95, 0x21, 0x78, 0x18, 0x9a, 0x52, 0xbc, 0x09, 0x37, 0x52, 0xfa, 0x27, 0xb6, 0xfd, + 0x31, 0x93, 0xd8, 0x16, 0x9b, 0x3f, 0xc6, 0xb6, 0x35, 0xc0, 0x08, 0x57, 0x51, 0xa1, 0x42, 0x3e, + 0x2b, 0x09, 0x18, 0x10, 0x1f, 0xc0, 0x75, 0x56, 0xe7, 0x34, 0xea, 0x50, 0xcf, 0x66, 0x5a, 0x56, + 0xef, 0xad, 0xb9, 0x1a, 0xaf, 0xc6, 0x1b, 0x21, 0xaa, 0x0a, 0x1b, 0x83, 0x28, 0x1d, 0x7b, 0x34, + 0xf0, 0x8f, 0x84, 0x36, 0x76, 0x2d, 0x8d, 0xde, 0xc1, 0x48, 0x44, 0x16, 0xf2, 0x11, 0x98, 0x83, + 0x42, 0x64, 0xf2, 0xb7, 0x39, 0xf5, 0x0a, 0x80, 0x02, 0x6e, 0xa4, 0x05, 0xec, 0x3a, 0xfc, 0x25, + 0xa7, 0x1e, 0xf9, 0x85, 0x01, 0x77, 0x07, 0xd1, 0xf4, 0xf0, 0x90, 0xba, 0x22, 0xe8, 0x50, 0x94, + 0xa3, 0x8e, 0x2d, 0x8f, 0x9e, 0x2d, 0xe9, 0x27, 0xf2, 0xde, 0x14, 0x4f, 0xe4, 0x5e, 0x28, 0xac, + 0xdb, 0xe9, 0x8d, 0x3f, 0x8b, 0x45, 0x27, 0xd1, 0xb4, 0x3f, 0x59, 0x03, 0x75, 0x8d, 0x2d, 0xa2, + 0x29, 0x63, 0x25, 0xe2, 0xfd, 0x46, 0x18, 0x2c, 0x77, 0x9c, 0x46, 0x9b, 0xda, 0x11, 0x75, 0x69, + 0x20, 0x33, 0x4c, 0x85, 0xc5, 0xe7, 0x67, 0x7c, 0xdf, 0xff, 0xf5, 0xe6, 0xd6, 0xb5, 0x13, 0xa7, + 0xd9, 0x78, 0x5c, 0xec, 0x17, 0x57, 0xb4, 0x96, 0x90, 0x60, 0xe9, 0x39, 0xf9, 0x14, 0x32, 0x5c, + 0x38, 0xa2, 0xad, 0xee, 0xe1, 0xe5, 0xca, 0xfd, 0x91, 0x0f, 0xa1, 0x2a, 0xc5, 0x34, 0xf0, 0x0b, + 0xc4, 0x58, 0x1a, 0x4b, 0xee, 0xc2, 0x72, 0x62, 0x3f, 0x32, 0xea, 0x8b, 0x7b, 0x29, 0xa6, 0x56, + 0x25, 0x91, 0xdc, 0x07, 0x92, 0xb0, 0xc9, 0x32, 0x41, 0x25, 0x76, 0x16, 0x9d, 0xb3, 0x12, 0xaf, + 0x1c, 0x70, 0xfe, 0x1c, 0x6f, 0xc9, 0xbe, 0x67, 0x3a, 0x37, 0xd3, 0x33, 0xdd, 0x93, 0x42, 0xb1, + 0xcf, 0x93, 0x14, 0xfa, 0xcd, 0x02, 0x2c, 0xeb, 0x35, 0xfd, 0x82, 0x8e, 0xc9, 0x20, 0xf9, 0x90, + 0xd1, 0xd0, 0xa3, 0x91, 0x4e, 0x1f, 0x3d, 0x23, 0xf7, 0xe0, 0xb2, 0x1a, 0xd9, 0xa9, 0x67, 0x71, + 0x49, 0x91, 0xab, 0xfa, 0x0a, 0x31, 0x21, 0xab, 0x8f, 0x20, 0xd2, 0x57, 0x7e, 0x32, 0x97, 0xce, + 0x8b, 0xc7, 0xda, 0x79, 0xf3, 0x4a, 0x44, 0x4c, 0x55, 0xce, 0x3b, 0x2d, 0xf9, 0x32, 0xe7, 0x2a, + 0xf9, 0xa4, 0x95, 0x4d, 0xca, 0xb9, 0xe3, 0x2b, 0xd7, 0xe7, 0xac, 0x78, 0x2a, 0xef, 0xab, 0x20, + 0xec, 0xb9, 0x00, 0x72, 0xb8, 0x9c, 0xd7, 0x34, 0xcc, 0xfb, 0x07, 0xb0, 0x1a, 0xb3, 0xf4, 0x65, + 0xbb, 0x4a, 0x56, 0xa2, 0xd7, 0x7a, 0x93, 0xbc, 0xef, 0x3d, 0xcf, 0x23, 0x5b, 0xf2, 0x9e, 0xf7, + 0x9f, 0xf1, 0xe2, 0x6c, 0xa5, 0xd8, 0x1a, 0xe4, 0x44, 0xd7, 0x66, 0x51, 0xe0, 0x07, 0x61, 0x61, + 0x49, 0x39, 0x57, 0x74, 0x5f, 0xe0, 0x5c, 0xde, 0xdd, 0x0e, 0xe7, 0x54, 0x14, 0x96, 0x71, 0x41, + 0x4d, 0xc8, 0x2d, 0xc8, 0xd3, 0x0e, 0x0d, 0x85, 0x7e, 0x07, 0x2f, 0xa3, 0x56, 0x80, 0x24, 0x7c, + 0x06, 0x49, 0x04, 0x37, 0xb1, 0x68, 0x77, 0x59, 0xc3, 0x76, 0x59, 0x28, 0x22, 0xc7, 0x15, 0x76, + 0x87, 0x46, 0x3c, 0x60, 0x61, 0x61, 0x05, 0xf5, 0x7c, 0x54, 0x1a, 0xdb, 0xf0, 0xc8, 0xc7, 0x19, + 0xf1, 0x55, 0x0d, 0xff, 0x52, 0xa1, 0xad, 0x1b, 0xad, 0xe1, 0x0b, 0xe4, 0x27, 0x32, 0x0e, 0x3a, + 0x34, 0x12, 0x36, 0x6b, 0x89, 0x80, 0x85, 0xbc, 0x70, 0x05, 0xab, 0x80, 0xfb, 0x13, 0x36, 0xb2, + 0x10, 0xf4, 0x42, 0x61, 0x76, 0xe6, 0x64, 0x58, 0xc8, 0xd8, 0xe9, 0x21, 0x92, 0x1a, 0x2c, 0xba, + 0x4e, 0xa3, 0x91, 0x08, 0x26, 0x28, 0xf8, 0xfd, 0x09, 0x82, 0xab, 0x4e, 0xa3, 0xa1, 0x25, 0x58, + 0x79, 0xf7, 0x74, 0x42, 0xb6, 0xe1, 0x6a, 0xc0, 0xed, 0xde, 0x26, 0x47, 0xae, 0x16, 0xae, 0x62, + 0x31, 0xb0, 0x12, 0xf0, 0xaa, 0x5c, 0xc1, 0xa8, 0x95, 0x22, 0x54, 0x65, 0x19, 0xb1, 0xc8, 0x8e, + 0xc3, 0x6e, 0x35, 0xae, 0x2c, 0x23, 0x16, 0xd5, 0x14, 0xad, 0x58, 0x80, 0xeb, 0xfd, 0xd9, 0x98, + 0x24, 0xea, 0x33, 0xac, 0x63, 0x9f, 0xd4, 0x59, 0x24, 0xbe, 0x10, 0x6d, 0xf7, 0xb8, 0x5a, 0x3d, + 0xf8, 0xf1, 0xf8, 0x16, 0x64, 0x5c, 0x81, 0xb7, 0x86, 0x15, 0x64, 0xbf, 0xb4, 0x64, 0xab, 0x0e, + 0xf6, 0x1f, 0x16, 0x3d, 0x6c, 0x87, 0x1e, 0xb2, 0x50, 0xef, 0x5c, 0xbb, 0xa9, 0xdc, 0x96, 0xd2, + 0x92, 0x9a, 0x54, 0x3d, 0xaa, 0x4b, 0x8a, 0xaa, 0x8b, 0x52, 0x5d, 0xcb, 0x0f, 0xec, 0x9b, 0xe8, + 0xf5, 0xb5, 0x81, 0x5a, 0xab, 0xc6, 0xc9, 0x72, 0x04, 0x7d, 0xa6, 0x7a, 0xd2, 0xa7, 0xb2, 0x25, + 0x1d, 0xa3, 0x9d, 0x0b, 0x64, 0xb0, 0x85, 0x45, 0x2d, 0xf3, 0x95, 0xf2, 0xa4, 0xb0, 0x4a, 0x6d, + 0xa3, 0x23, 0x6b, 0x25, 0x4a, 0xd1, 0x8b, 0x77, 0xe0, 0xf6, 0x48, 0xdd, 0x12, 0x0b, 0xfe, 0x69, + 0x60, 0xeb, 0xa7, 0x1b, 0x4d, 0xac, 0xdb, 0xab, 0x6d, 0x2e, 0x98, 0x77, 0x72, 0x8e, 0x2e, 0xb8, + 0x04, 0x57, 0x43, 0xfa, 0x95, 0xed, 0x2a, 0x41, 0x29, 0x17, 0x5f, 0x09, 0xe9, 0x57, 0x7a, 0x8b, + 0xb8, 0xf6, 0x1f, 0x68, 0x71, 0xe6, 0x86, 0xb4, 0x38, 0xa7, 0xf7, 0xec, 0xfc, 0xf9, 0x5a, 0xeb, + 0x4f, 0xe1, 0xce, 0x18, 0x8b, 0x7b, 0x0b, 0xea, 0x9e, 0x08, 0x32, 0xd2, 0xf1, 0xda, 0xc4, 0x4a, + 0x57, 0x79, 0xb7, 0x57, 0xc8, 0xbe, 0xd3, 0xe6, 0xfa, 0x19, 0x9e, 0xbd, 0xaa, 0x95, 0x32, 0xd0, + 0x5d, 0x59, 0x4b, 0x4d, 0x8a, 0x7b, 0xb0, 0x35, 0x69, 0xbb, 0x29, 0x35, 0xaf, 0xfc, 0x7b, 0x19, + 0x2e, 0xd5, 0xb8, 0x4f, 0x7e, 0x6d, 0x00, 0x19, 0xd2, 0x4f, 0x7d, 0x30, 0x21, 0xfe, 0x86, 0xb6, + 0x21, 0xe6, 0x0f, 0x66, 0x41, 0x25, 0x1a, 0xff, 0xca, 0x80, 0x2b, 0x83, 0x5f, 0x17, 0x1e, 0x4e, + 0x25, 0xb3, 0x1f, 0x64, 0x7e, 0x34, 0x03, 0x28, 0xd1, 0xe3, 0x77, 0x06, 0x5c, 0x1b, 0xde, 0x23, + 0x7d, 0x6f, 0xb2, 0xd8, 0xa1, 0x40, 0xf3, 0x93, 0x19, 0x81, 0x89, 0x4e, 0x1d, 0x58, 0xec, 0x6b, + 0x95, 0x4a, 0x93, 0x05, 0xf6, 0xf2, 0x9b, 0x8f, 0xce, 0xc6, 0x9f, 0xde, 0x37, 0x69, 0x63, 0xa6, + 0xdc, 0x37, 0xe6, 0x9f, 0x76, 0xdf, 0x74, 0xfd, 0x47, 0x38, 0xe4, 0x7b, 0x6b, 0xbf, 0xed, 0xe9, + 0xc4, 0x68, 0x76, 0xf3, 0xbb, 0x67, 0x62, 0x4f, 0x36, 0xfd, 0x19, 0x2c, 0xa7, 0x3e, 0xc8, 0x3c, + 0x98, 0x2c, 0xa8, 0x1f, 0x61, 0x7e, 0x78, 0x56, 0x44, 0xb2, 0xfb, 0x2f, 0x0d, 0x58, 0x19, 0xf8, + 0x98, 0x57, 0x99, 0x2c, 0x2e, 0x8d, 0x31, 0x1f, 0x9f, 0x1d, 0x93, 0x28, 0xf1, 0x73, 0xb8, 0x9c, + 0xfe, 0x04, 0xfa, 0x9d, 0xc9, 0xe2, 0x52, 0x10, 0xf3, 0xfb, 0x67, 0x86, 0xf4, 0x9e, 0x41, 0xaa, + 0x98, 0x98, 0xe2, 0x0c, 0xfa, 0x11, 0xd3, 0x9c, 0xc1, 0xf0, 0x12, 0x03, 0xaf, 0xa0, 0xc1, 0x02, + 0xe3, 0xe1, 0x34, 0xd9, 0x9b, 0x02, 0x4d, 0x73, 0x05, 0x8d, 0x2c, 0x29, 0xc8, 0x1f, 0x0c, 0xb8, + 0x3e, 0xa2, 0x9e, 0xf8, 0x70, 0xda, 0xd3, 0x4d, 0x23, 0xcd, 0x1f, 0xce, 0x8a, 0x4c, 0xd4, 0xfa, + 0xda, 0x80, 0xc2, 0xc8, 0x22, 0xe1, 0xf1, 0xd4, 0x87, 0x3e, 0x80, 0x35, 0x77, 0x66, 0xc7, 0x26, + 0xca, 0xfd, 0xc9, 0x80, 0xf5, 0xf1, 0x2f, 0xf1, 0x27, 0xd3, 0x3a, 0x60, 0x84, 0x00, 0x73, 0xf7, + 0x9c, 0x02, 0x62, 0x5d, 0x77, 0x76, 0xbf, 0x79, 0xbb, 0x61, 0xbc, 0x7e, 0xbb, 0x61, 0xfc, 0xe3, + 0xed, 0x86, 0xf1, 0xdb, 0x77, 0x1b, 0x17, 0x5e, 0xbf, 0xdb, 0xb8, 0xf0, 0xb7, 0x77, 0x1b, 0x17, + 0x7e, 0xba, 0xdd, 0x53, 0xc8, 0xc8, 0x2d, 0xb6, 0xd5, 0xff, 0x2c, 0x42, 0xe6, 0xd1, 0x72, 0xb7, + 0xef, 0x5f, 0x3b, 0xb2, 0xa6, 0xa9, 0x67, 0xb0, 0x5f, 0x79, 0xf8, 0xdf, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xef, 0x07, 0xa7, 0x60, 0x08, 0x1a, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -3121,6 +3131,15 @@ func (m *MsgVoteInbound) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.ErrorMessage) > 0 { + i -= len(m.ErrorMessage) + copy(dAtA[i:], m.ErrorMessage) + i = encodeVarintTx(dAtA, i, uint64(len(m.ErrorMessage))) + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0xa2 + } if m.IsCrossChainCall { i-- if m.IsCrossChainCall { @@ -4026,6 +4045,10 @@ func (m *MsgVoteInbound) Size() (n int) { if m.IsCrossChainCall { n += 3 } + l = len(m.ErrorMessage) + if l > 0 { + n += 2 + l + sovTx(uint64(l)) + } return n } @@ -6807,6 +6830,38 @@ func (m *MsgVoteInbound) Unmarshal(dAtA []byte) error { } } m.IsCrossChainCall = bool(v != 0) + case 20: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ErrorMessage", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ErrorMessage = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipTx(dAtA[iNdEx:]) diff --git a/zetaclient/chains/bitcoin/observer/event.go b/zetaclient/chains/bitcoin/observer/event.go index 1695a4d414..f0ca149c62 100644 --- a/zetaclient/chains/bitcoin/observer/event.go +++ b/zetaclient/chains/bitcoin/observer/event.go @@ -47,6 +47,9 @@ type BTCInboundEvent struct { // TxHash is the hash of the inbound TxHash string + + // ErrMessage is reason for failed inbound observation + ErrMessage string } // Category returns the category of the inbound event @@ -195,6 +198,7 @@ func (ob *Observer) NewInboundVoteFromLegacyMemo( 0, crosschaintypes.ProtocolContractVersion_V1, false, // not relevant for v1 + event.ErrMessage, ) } @@ -232,6 +236,7 @@ func (ob *Observer) NewInboundVoteFromStdMemo( 0, crosschaintypes.ProtocolContractVersion_V1, false, // not relevant for v1 + event.ErrMessage, crosschaintypes.WithRevertOptions(revertOptions), ) } diff --git a/zetaclient/chains/bitcoin/observer/inbound.go b/zetaclient/chains/bitcoin/observer/inbound.go index ecaf9f1e7a..1439c7fd1e 100644 --- a/zetaclient/chains/bitcoin/observer/inbound.go +++ b/zetaclient/chains/bitcoin/observer/inbound.go @@ -313,6 +313,7 @@ func GetBtcEventWithoutWitness( value float64 depositorFee float64 memo []byte + errMessage string ) if len(tx.Vout) >= 2 { @@ -337,13 +338,14 @@ func GetBtcEventWithoutWitness( return nil, errors.Wrapf(err, "error calculating depositor fee for inbound %s", tx.Txid) } - // deposit amount has to be no less than the minimum depositor fee - if vout0.Value < depositorFee { - logger.Info(). - Msgf("GetBtcEvent: btc deposit amount %v in txid %s is less than depositor fee %v", vout0.Value, tx.Txid, depositorFee) - return nil, nil + // deduct depositor fee + // to allow developers to track failed deposit caused by insufficient depositor fee, + // the error message will be forwarded to zetacore to register a failed CCTX + value, err = DeductDepositorFee(vout0.Value, depositorFee) + if err != nil { + errMessage = err.Error() + logger.Info().Err(err).Msgf("unable to deduct depositor fee for tx %s", tx.Txid) } - value = vout0.Value - depositorFee // 2nd vout must be a valid OP_RETURN memo vout1 := tx.Vout[1] @@ -380,6 +382,7 @@ func GetBtcEventWithoutWitness( MemoBytes: memo, BlockNumber: blockNumber, TxHash: tx.Txid, + ErrMessage: errMessage, }, nil } return nil, nil diff --git a/zetaclient/chains/bitcoin/observer/witness.go b/zetaclient/chains/bitcoin/observer/witness.go index 69d2726459..0c421ab95f 100644 --- a/zetaclient/chains/bitcoin/observer/witness.go +++ b/zetaclient/chains/bitcoin/observer/witness.go @@ -46,11 +46,14 @@ func GetBtcEventWithWitness( return nil, errors.Wrapf(err, "error calculating depositor fee for inbound %s", tx.Txid) } - isAmountValid, amount := isValidAmount(tx.Vout[0].Value, depositorFee) - if !isAmountValid { - logger.Info(). - Msgf("GetBtcEventWithWitness: btc deposit amount %v in txid %s is less than depositor fee %v", tx.Vout[0].Value, tx.Txid, depositorFee) - return nil, nil + // deduct depositor fee + // to allow developers to track failed deposit caused by insufficient depositor fee, + // the error message will be forwarded to zetacore to register a failed CCTX + var errMessage string + amount, err := DeductDepositorFee(tx.Vout[0].Value, depositorFee) + if err != nil { + errMessage = err.Error() + logger.Info().Err(err).Msgf("unable to deduct depositor fee for tx %s", tx.Txid) } // Try to extract the memo from the BTC txn. First try to extract from OP_RETURN @@ -88,6 +91,7 @@ func GetBtcEventWithWitness( MemoBytes: memo, BlockNumber: blockNumber, TxHash: tx.Txid, + ErrMessage: errMessage, }, nil } @@ -172,14 +176,12 @@ func tryExtractInscription(tx btcjson.TxRawResult, logger zerolog.Logger) []byte return nil } -func isValidAmount( - incoming float64, - minimal float64, -) (bool, float64) { - if incoming < minimal { - return false, 0 +// DeductDepositorFee returns the inbound amount after deducting the depositor fee. +func DeductDepositorFee(deposited, depositorFee float64) (float64, error) { + if deposited < depositorFee { + return 0, fmt.Errorf("deposited amount %v is less than depositor fee %v", deposited, depositorFee) } - return true, incoming - minimal + return deposited - depositorFee, nil } func isValidRecipient( diff --git a/zetaclient/chains/evm/observer/v2_inbound.go b/zetaclient/chains/evm/observer/v2_inbound.go index 6422034273..bf1bb04d2c 100644 --- a/zetaclient/chains/evm/observer/v2_inbound.go +++ b/zetaclient/chains/evm/observer/v2_inbound.go @@ -198,6 +198,7 @@ func (ob *Observer) newDepositInboundVote(event *gatewayevm.GatewayEVMDeposited) event.Raw.Index, types.ProtocolContractVersion_V2, false, // currently not relevant since calls are not arbitrary + "", types.WithEVMRevertOptions(event.RevertOptions), types.WithCrossChainCall(isCrossChainCall), ) @@ -334,6 +335,7 @@ func (ob *Observer) newCallInboundVote(event *gatewayevm.GatewayEVMCalled) types event.Raw.Index, types.ProtocolContractVersion_V2, false, // currently not relevant since calls are not arbitrary + "", types.WithEVMRevertOptions(event.RevertOptions), ) } @@ -471,6 +473,7 @@ func (ob *Observer) newDepositAndCallInboundVote(event *gatewayevm.GatewayEVMDep event.Raw.Index, types.ProtocolContractVersion_V2, false, // currently not relevant since calls are not arbitrary + "", types.WithEVMRevertOptions(event.RevertOptions), types.WithCrossChainCall(true), ) diff --git a/zetaclient/chains/solana/observer/inbound.go b/zetaclient/chains/solana/observer/inbound.go index 6cc1c8d84c..91fa983ba1 100644 --- a/zetaclient/chains/solana/observer/inbound.go +++ b/zetaclient/chains/solana/observer/inbound.go @@ -312,6 +312,7 @@ func (ob *Observer) BuildInboundVoteMsgFromEvent(event *clienttypes.InboundEvent 0, // not a smart contract call crosschaintypes.ProtocolContractVersion_V1, false, // not relevant for v1 + "", ) } diff --git a/zetaclient/zetacore/tx.go b/zetaclient/zetacore/tx.go index 59fbc92edd..92abc8c866 100644 --- a/zetaclient/zetacore/tx.go +++ b/zetaclient/zetacore/tx.go @@ -51,6 +51,7 @@ func GetInboundVoteMessage( eventIndex, types.ProtocolContractVersion_V1, false, // not relevant for v1 + "", ) return msg }