Skip to content

Commit

Permalink
feat(crosschain): general improvements for gas payments and revert …
Browse files Browse the repository at this point in the history
…txs (#1064)

* pay gas on revert

* fix gas limit

* update smoke test

* make generate

* add revert gas limit note

* fix out amount from gas payment calculation

* add queryWithdrawalFee method

* implement pay gas native

* fix non-existing module account

* add conditional burning of the fees

* fix smoke test btc amount

* use Zeta coin for CLI integration tests

* add protocol gas fee and gas limit queries

* use gas limit and gas price in PayGasNative

* uncomment smoke tests

* fix unpack

* experiment integration tests fix

* fix no error check

* set addresses back

* fix erorr undefined

* fix mocks

* change error messages

* comments

* improve crosschain testkeeper

* fix gas price native, remove burning

* add test for pay gas native

* goimports

* test for PayGasInZetaAndUpdateCctx

* change test message

* refactor file naming

* improve system contract naming

* add make mocks

* fix gas payment tests

* get zrc20 from asset

* implement CallUniswapV2RouterSwapExactTokenForETH

* Implement PayGasInERC20AndUpdateCctx

* use cosmoserror

* add approve

* goimports

* add success test

* improve error messaging

* implement test error cases

* refund amount

* goimports

* use specific error for no liquidity pool

* use input amount

* smoke test commented

* goimport

* fix unit test

* complete smoke test

* add sender to erc20 cctx

* use pool path

* update mocks

* update post tx gas limit

* fix evm panic call

* add back all smoke test

* goimports

* fix undefined error

* fix coin 2

---------

Co-authored-by: brewmaster012 <[email protected]>
  • Loading branch information
lumtis and brewmaster012 authored Sep 21, 2023
1 parent 8e9c672 commit bc0e26d
Show file tree
Hide file tree
Showing 45 changed files with 2,874 additions and 441 deletions.
2 changes: 1 addition & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ func New(
groupmodule.NewAppModule(appCodec, app.GroupKeeper, app.AccountKeeper, app.BankKeeper, interfaceRegistry),
evm.NewAppModule(app.EvmKeeper, app.AccountKeeper, evmSs),
feemarket.NewAppModule(app.FeeMarketKeeper, feeSs),
zetaCoreModule.NewAppModule(appCodec, app.ZetaCoreKeeper, app.StakingKeeper),
zetaCoreModule.NewAppModule(appCodec, app.ZetaCoreKeeper, app.StakingKeeper, app.AccountKeeper),
zetaObserverModule.NewAppModule(appCodec, *app.ZetaObserverKeeper, app.AccountKeeper, app.BankKeeper),
fungibleModule.NewAppModule(appCodec, app.FungibleKeeper, app.AccountKeeper, app.BankKeeper),
emissionsModule.NewAppModule(appCodec, app.EmissionsKeeper, app.AccountKeeper),
Expand Down
3 changes: 3 additions & 0 deletions contrib/localnet/orchestrator/smoketest/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,9 @@ func LocalSmokeTest(_ *cobra.Command, _ []string) {
smokeTest.TestPauseZRC20()
smokeTest.CheckZRC20ReserveAndSupply()

smokeTest.TestERC20DepositAndCallRefund()
smokeTest.CheckZRC20ReserveAndSupply()

smokeTest.TestUpdateBytecode()
smokeTest.CheckZRC20ReserveAndSupply()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ func (sm *SmokeTest) TestCrosschainSwap() {
memo = append(sm.ZEVMSwapAppAddr.Bytes(), memo...)
fmt.Printf("memo length %d\n", len(memo))

txid, err := SendToTSSFromDeployerWithMemo(BTCTSSAddress, 0.001, utxos[0:2], sm.btcRPCClient, memo)
amount := 0.1
txid, err := SendToTSSFromDeployerWithMemo(BTCTSSAddress, amount, utxos[0:2], sm.btcRPCClient, memo)
fmt.Printf("Sent BTC to TSS txid %s; now mining 10 blocks for confirmation\n", txid)
_, err = sm.btcRPCClient.GenerateToAddress(10, BTCDeployerAddress, nil)
if err != nil {
Expand All @@ -182,6 +183,7 @@ func (sm *SmokeTest) TestCrosschainSwap() {
fmt.Printf(" inboudn tx hash %s\n", cctx.InboundTxParams.InboundTxObservedHash)
fmt.Printf(" status %s\n", cctx.CctxStatus.Status.String())
fmt.Printf(" status msg: %s\n", cctx.CctxStatus.StatusMessage)

if cctx.CctxStatus.Status != types.CctxStatus_Reverted {
panic(fmt.Sprintf("expected reverted status; got %s", cctx.CctxStatus.Status.String()))
}
Expand Down
42 changes: 38 additions & 4 deletions contrib/localnet/orchestrator/smoketest/test_deposit_eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,17 @@ func (sm *SmokeTest) TestDepositAndCallRefund() {
if err != nil {
panic(err)
}
value := big.NewInt(100000000000000000) // in wei (1 eth)
gasLimit := uint64(23000) // in units

// in wei (10 eth)
value := big.NewInt(1e18)
value = value.Mul(value, big.NewInt(10))

gasLimit := uint64(23000) // in units
gasPrice, err := goerliClient.SuggestGasPrice(context.Background())
if err != nil {
panic(err)
}

data := append(sm.BTCZRC20Addr.Bytes(), []byte("hello sailors")...) // this data
tx := ethtypes.NewTransaction(nonce, TSSAddress, value, gasLimit, gasPrice, data)
chainID, err := goerliClient.NetworkID(context.Background())
Expand Down Expand Up @@ -232,6 +237,7 @@ func (sm *SmokeTest) TestDepositAndCallRefund() {
fmt.Printf("cctx status message: %s", cctx.CctxStatus.StatusMessage)
revertTxHash := cctx.GetCurrentOutTxParam().OutboundTxHash
fmt.Printf("GOERLI revert tx receipt: status %d\n", receipt.Status)

tx, _, err := sm.goerliClient.TransactionByHash(context.Background(), ethcommon.HexToHash(revertTxHash))
if err != nil {
panic(err)
Expand All @@ -240,13 +246,41 @@ func (sm *SmokeTest) TestDepositAndCallRefund() {
if err != nil {
panic(err)
}
if cctx.CctxStatus.Status != types.CctxStatus_Reverted || receipt.Status == 0 || *tx.To() != DeployerAddress || tx.Value().Cmp(value) != 0 {

printTxInfo := func() {
// debug info when test fails
fmt.Printf(" tx: %+v\n", tx)
fmt.Printf(" receipt: %+v\n", receipt)
fmt.Printf("cctx http://localhost:1317/zeta-chain/crosschain/cctx/%s\n", cctx.Index)
panic(fmt.Sprintf("expected cctx status PendingRevert; got %s", cctx.CctxStatus.Status))
}

if cctx.CctxStatus.Status != types.CctxStatus_Reverted {
printTxInfo()
panic(fmt.Sprintf("expected cctx status to be PendingRevert; got %s", cctx.CctxStatus.Status))
}

if receipt.Status == 0 {
printTxInfo()
panic("expected the revert tx receipt to have status 1; got 0")
}

if *tx.To() != DeployerAddress {
printTxInfo()
panic(fmt.Sprintf("expected tx to %s; got %s", DeployerAddress.Hex(), tx.To().Hex()))
}

// the received value must be lower than the original value because of the paid fees for the revert tx
// we check that the value is still greater than 0
if tx.Value().Cmp(value) != -1 || tx.Value().Cmp(big.NewInt(0)) != 1 {
printTxInfo()
panic(fmt.Sprintf("expected tx value %s; should be non-null and lower than %s", tx.Value().String(), value.String()))
}

fmt.Printf("REVERT tx receipt: %d\n", receipt.Status)
fmt.Printf(" tx hash: %s\n", receipt.TxHash.String())
fmt.Printf(" to: %s\n", tx.To().String())
fmt.Printf(" value: %s\n", tx.Value().String())
fmt.Printf(" block num: %d\n", receipt.BlockNumber)
}()
}

Expand Down
200 changes: 200 additions & 0 deletions contrib/localnet/orchestrator/smoketest/test_erc20_refund.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
//go:build PRIVNET
// +build PRIVNET

package main

import (
"context"
"errors"
"fmt"
"math/big"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
ethcommon "github.com/ethereum/go-ethereum/common"

"github.com/zeta-chain/zetacore/x/crosschain/types"
)

func (sm *SmokeTest) TestERC20DepositAndCallRefund() {
startTime := time.Now()
defer func() {
fmt.Printf("test finishes in %s\n", time.Since(startTime))
}()
LoudPrintf("Deposit a non-gas ZRC20 into ZEVM and call a contract that reverts; should refund on ZetaChain if no liquidity pool, should refund on origin if liquidity pool\n")

// Get the initial balance of the deployer
initialBal, err := sm.USDTZRC20.BalanceOf(&bind.CallOpts{}, DeployerAddress)
if err != nil {
panic(err)
}

fmt.Println("Sending a deposit that should revert without a liquidity pool makes the cctx aborted")

amount := big.NewInt(1e4)

// send the deposit
inTxHash, err := sm.sendInvalidUSDTDeposit(amount)
if err != nil {
panic(err)
}

// There is no liquidity pool, therefore the cctx should abort
cctx := WaitCctxMinedByInTxHash(inTxHash, sm.cctxClient)
if cctx.CctxStatus.Status != types.CctxStatus_Aborted {
panic(fmt.Sprintf("expected cctx status to be Aborted; got %s", cctx.CctxStatus.Status))
}

// Check that the erc20 in the aborted cctx was refunded on ZetaChain
newBalance, err := sm.USDTZRC20.BalanceOf(&bind.CallOpts{}, DeployerAddress)
if err != nil {
panic(err)
}
expectedBalance := initialBal.Add(initialBal, amount)
if newBalance.Cmp(expectedBalance) != 0 {
panic(fmt.Sprintf("expected balance to be %s after refund; got %s", expectedBalance.String(), newBalance.String()))
}
fmt.Println("CCTX has been aborted and the erc20 has been refunded on ZetaChain")

amount = big.NewInt(1e7)
goerliBalance, err := sm.USDTERC20.BalanceOf(&bind.CallOpts{}, DeployerAddress)
if err != nil {
panic(err)
}

fmt.Println("Sending a deposit that should revert with a liquidity pool")

fmt.Println("Creating the liquidity pool USTD/ZETA")
err = sm.createZetaERC20LiquidityPool()
if err != nil {
panic(err)
}
fmt.Println("Liquidity pool created")

// send the deposit
inTxHash, err = sm.sendInvalidUSDTDeposit(amount)
if err != nil {
panic(err)
}

// there is a liquidity pool, therefore the cctx should revert
cctx = WaitCctxMinedByInTxHash(inTxHash, sm.cctxClient)

// the revert tx creation will fail because the sender, used as the recipient, is not defined in the cctx
if cctx.CctxStatus.Status != types.CctxStatus_Reverted {
panic(fmt.Sprintf("expected cctx status to be PendingRevert; got %s", cctx.CctxStatus.Status))
}

// get revert tx
revertTxHash := cctx.GetCurrentOutTxParam().OutboundTxHash
_, _, err = sm.goerliClient.TransactionByHash(context.Background(), ethcommon.HexToHash(revertTxHash))
if err != nil {
panic(err)
}
receipt, err := sm.goerliClient.TransactionReceipt(context.Background(), ethcommon.HexToHash(revertTxHash))
if err != nil {
panic(err)
}
if receipt.Status == 0 {
panic("expected the revert tx receipt to have status 1; got 0")
}

// check that the erc20 in the reverted cctx was refunded on Goerli
newGoerliBalance, err := sm.USDTERC20.BalanceOf(&bind.CallOpts{}, DeployerAddress)
if err != nil {
panic(err)
}
// the new balance must be higher than the previous one because of the revert refund
if goerliBalance.Cmp(newGoerliBalance) != -1 {
panic(fmt.Sprintf("expected balance to be higher than %s after refund; got %s", goerliBalance.String(), newGoerliBalance.String()))
}
// it must also be lower than the previous balance + the amount because of the gas fee for the revert tx
balancePlusAmount := goerliBalance.Add(goerliBalance, amount)
if newGoerliBalance.Cmp(balancePlusAmount) != -1 {
panic(fmt.Sprintf("expected balance to be lower than %s after refund; got %s", balancePlusAmount.String(), newGoerliBalance.String()))
}

fmt.Println("ERC20 CCTX successfully reverted")
fmt.Println("\tbalance before refund: ", goerliBalance.String())
fmt.Println("\tamount: ", amount.String())
fmt.Println("\tbalance after refund: ", newGoerliBalance.String())
}

func (sm *SmokeTest) createZetaERC20LiquidityPool() error {
amount := big.NewInt(1e10)
txHash := sm.DepositERC20(amount, []byte{})
WaitCctxMinedByInTxHash(txHash.Hex(), sm.cctxClient)

tx, err := sm.USDTZRC20.Approve(sm.zevmAuth, sm.UniswapV2RouterAddr, big.NewInt(1e10))
if err != nil {
return err
}
receipt := MustWaitForTxReceipt(sm.zevmClient, tx)
if receipt.Status == 0 {
return errors.New("approve failed")
}

previousValue := sm.zevmAuth.Value
sm.zevmAuth.Value = big.NewInt(1e10)
tx, err = sm.UniswapV2Router.AddLiquidityETH(
sm.zevmAuth,
sm.USDTZRC20Addr,
amount,
BigZero,
BigZero,
DeployerAddress,
big.NewInt(time.Now().Add(10*time.Minute).Unix()),
)
sm.zevmAuth.Value = previousValue
if err != nil {
return err
}
receipt = MustWaitForTxReceipt(sm.zevmClient, tx)
if receipt.Status == 0 {
return errors.New("add liquidity failed")
}

return nil
}

func (sm *SmokeTest) sendInvalidUSDTDeposit(amount *big.Int) (string, error) {
// send the tx
USDT := sm.USDTERC20
tx, err := USDT.Mint(sm.goerliAuth, amount)
if err != nil {
return "", err
}
receipt := MustWaitForTxReceipt(sm.goerliClient, tx)
fmt.Printf("Mint receipt tx hash: %s\n", tx.Hash().Hex())

tx, err = USDT.Approve(sm.goerliAuth, sm.ERC20CustodyAddr, amount)
if err != nil {
return "", err
}
receipt = MustWaitForTxReceipt(sm.goerliClient, tx)
fmt.Printf("USDT Approve receipt tx hash: %s\n", tx.Hash().Hex())

tx, err = sm.ERC20Custody.Deposit(
sm.goerliAuth,
DeployerAddress.Bytes(),
sm.USDTERC20Addr,
amount,
[]byte("this is an invalid msg that will cause the contract to revert"),
)
if err != nil {
return "", err
}

fmt.Printf("GOERLI tx sent: %s; to %s, nonce %d\n", tx.Hash().String(), tx.To().Hex(), tx.Nonce())
receipt = MustWaitForTxReceipt(sm.goerliClient, tx)
if receipt.Status == 0 {
return "", errors.New("expected the tx receipt to have status 1; got 0")
}
fmt.Printf("GOERLI tx receipt: %d\n", receipt.Status)
fmt.Printf(" tx hash: %s\n", receipt.TxHash.String())
fmt.Printf(" to: %s\n", tx.To().String())
fmt.Printf(" value: %d\n", tx.Value())
fmt.Printf(" block num: %d\n", receipt.BlockNumber)

return tx.Hash().Hex(), nil
}
4 changes: 2 additions & 2 deletions docs/spec/crosschain/messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ message MsgNonceVoter {

## MsgVoteOnObservedOutboundTx

Casts a vote on an outbound transaction observed on a connected chain (after
VoteOnObservedOutboundTx casts a vote on an outbound transaction observed on a connected chain (after
it has been broadcasted to and finalized on a connected chain). If this is
the first vote, a new ballot is created. When a threshold of votes is
reached, the ballot is finalized. When a ballot is finalized, the outbound
Expand Down Expand Up @@ -145,7 +145,7 @@ message MsgVoteOnObservedOutboundTx {

## MsgVoteOnObservedInboundTx

Casts a vote on an inbound transaction observed on a connected chain. If this
VoteOnObservedInboundTx casts a vote on an inbound transaction observed on a connected chain. If this
is the first vote, a new ballot is created. When a threshold of votes is
reached, the ballot is finalized. When a ballot is finalized, a new CCTX is
created.
Expand Down
5 changes: 1 addition & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ require (
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
Expand All @@ -197,8 +197,6 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect
github.com/ipfs/go-datastore v0.6.0 // indirect
github.com/ipfs/go-ipfs-util v0.0.2 // indirect
github.com/ipfs/go-ipns v0.3.0 // indirect
github.com/ipfs/go-log v1.0.5 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/ipld/go-ipld-prime v0.20.0 // indirect
Expand All @@ -217,7 +215,6 @@ require (
github.com/libp2p/go-flow-metrics v0.1.0 // indirect
github.com/libp2p/go-libp2p v0.27.8
github.com/libp2p/go-libp2p-asn-util v0.3.0 // indirect
github.com/libp2p/go-libp2p-core v0.20.0 // indirect
github.com/libp2p/go-libp2p-kad-dht v0.24.2
github.com/libp2p/go-libp2p-kbucket v0.6.3 // indirect
github.com/libp2p/go-libp2p-record v0.2.0 // indirect
Expand Down
Loading

0 comments on commit bc0e26d

Please sign in to comment.