Skip to content

Commit

Permalink
fix(crosschain): zeta withdraw fees (#991)
Browse files Browse the repository at this point in the history
* fix value

* add pay gas

* comment methods

* add more log

* fix amount

* format

* update smoke test asserts

* uncomment evm calls

* experimentation

* contrib/localnet/orchestrator/smoketest/main.go

* more experimentations

* add no ethereum tx event option

* goimports

* prevent non zeta coin paying gas

* zeta condition fix

* uncomment out smoketests

* fix bitcoin gas price

* add more logs

* remove zeta check

* add TODO comment

* fix merge error
  • Loading branch information
lumtis authored Aug 29, 2023
1 parent a8415d0 commit 4173f36
Show file tree
Hide file tree
Showing 17 changed files with 319 additions and 152 deletions.
15 changes: 10 additions & 5 deletions contrib/localnet/orchestrator/smoketest/test_zeta_in_and_out.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func (sm *SmokeTest) TestSendZetaIn() {
if err != nil {
panic(err)
}

fmt.Printf("Send tx hash: %s\n", tx.Hash().Hex())
receipt = MustWaitForTxReceipt(sm.goerliClient, tx)
fmt.Printf("Send tx receipt: status %d\n", receipt.Status)
Expand Down Expand Up @@ -103,8 +104,12 @@ func (sm *SmokeTest) TestSendZetaOut() {
}
fmt.Printf("zevm chainid: %d\n", zchainid)

// 10 Zeta
amount := big.NewInt(1e18)
amount = amount.Mul(amount, big.NewInt(10))

zauth := sm.zevmAuth
zauth.Value = big.NewInt(1e18)
zauth.Value = amount
tx, err := wzeta.Deposit(zauth)
if err != nil {
panic(err)
Expand All @@ -115,7 +120,7 @@ func (sm *SmokeTest) TestSendZetaOut() {
receipt := MustWaitForTxReceipt(zevmClient, tx)
fmt.Printf("Deposit tx receipt: status %d\n", receipt.Status)

tx, err = wzeta.Approve(zauth, ConnectorZEVMAddr, big.NewInt(1e18))
tx, err = wzeta.Approve(zauth, ConnectorZEVMAddr, amount)
if err != nil {
panic(err)
}
Expand All @@ -127,7 +132,7 @@ func (sm *SmokeTest) TestSendZetaOut() {
DestinationAddress: DeployerAddress.Bytes(),
DestinationGasLimit: big.NewInt(250_000),
Message: nil,
ZetaValueAndGas: big.NewInt(1e17),
ZetaValueAndGas: amount,
ZetaParams: nil,
})
if err != nil {
Expand Down Expand Up @@ -162,8 +167,8 @@ func (sm *SmokeTest) TestSendZetaOut() {
fmt.Printf(" Dest Addr: %s\n", event.DestinationAddress.Hex())
fmt.Printf(" sender addr: %x\n", event.ZetaTxSenderAddress)
fmt.Printf(" Zeta Value: %d\n", event.ZetaValue)
if event.ZetaValue.Cmp(big.NewInt(1e17)) != 0 {
panic("wrong zeta value")
if event.ZetaValue.Cmp(amount) != -1 {
panic("wrong zeta value, gas should be paid in the amount")
}
}
}
Expand Down
11 changes: 10 additions & 1 deletion contrib/localnet/orchestrator/smoketest/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ package main
import (
"context"
"encoding/hex"
"errors"
"fmt"
"sync"
"time"

"github.com/ethereum/go-ethereum"

"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcutil"

Expand All @@ -28,6 +31,7 @@ func WaitCctxMinedByInTxHash(inTxHash string, cctxClient types.QueryClient) *typ
fmt.Printf("Waiting for cctx to be mined by inTxHash: %s\n", inTxHash)
res, err := cctxClient.InTxHashToCctx(context.Background(), &types.QueryGetInTxHashToCctxRequest{InTxHash: inTxHash})
if err != nil {
fmt.Println("Error getting cctx by inTxHash: ", err.Error())
continue
}
cctxIndexes = res.InTxHashToCctx.CctxIndex
Expand All @@ -47,6 +51,8 @@ func WaitCctxMinedByInTxHash(inTxHash string, cctxClient types.QueryClient) *typ
fmt.Printf("Deposit receipt cctx status: %+v; The cctx is processed\n", res.CrossChainTx.CctxStatus.Status.String())
cctxs = append(cctxs, res.CrossChainTx)
break
} else if err != nil {
fmt.Println("Error getting cctx by index: ", err.Error())
}
}
}()
Expand Down Expand Up @@ -84,14 +90,17 @@ func MustWaitForTxReceipt(client *ethclient.Client, tx *ethtypes.Transaction) *e
if time.Since(start) > 30*time.Second {
panic("waiting tx receipt timeout")
}
time.Sleep(1 * time.Second)
receipt, err := client.TransactionReceipt(context.Background(), tx.Hash())
if err != nil {
if !errors.Is(err, ethereum.NotFound) {
fmt.Println("fetching tx receipt error: ", err.Error())
}
continue
}
if receipt != nil {
return receipt
}
time.Sleep(1 * time.Second)
}
}

Expand Down
99 changes: 72 additions & 27 deletions x/crosschain/keeper/evm_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import (
"github.com/zeta-chain/zetacore/cmd/zetacored/config"
"github.com/zeta-chain/zetacore/common"

zetacoretypes "github.com/zeta-chain/zetacore/x/crosschain/types"
"github.com/zeta-chain/zetacore/x/crosschain/types"
fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types"
zetaObserverTypes "github.com/zeta-chain/zetacore/x/observer/types"
)
Expand Down Expand Up @@ -79,7 +79,7 @@ func (k Keeper) PostTxProcessing(
// transaction.
func (k Keeper) ProcessLogs(ctx sdk.Context, logs []*ethtypes.Log, emittingContract ethcommon.Address, txOrigin string) error {
if !k.zetaObserverKeeper.IsInboundEnabled(ctx) {
return zetacoretypes.ErrNotEnoughPermissions
return types.ErrNotEnoughPermissions
}
system, found := k.fungibleKeeper.GetSystemContract(ctx)
if !found {
Expand Down Expand Up @@ -107,7 +107,7 @@ func (k Keeper) ProcessLogs(ctx sdk.Context, logs []*ethtypes.Log, emittingContr
return nil
}

// create a new CCTX to process the withdrawal event
// ProcessZRC20WithdrawalEvent creates a new CCTX to process the withdrawal event
// error indicates system error and non-recoverable; should abort
func (k Keeper) ProcessZRC20WithdrawalEvent(ctx sdk.Context, event *zrc20.ZRC20Withdrawal, emittingContract ethcommon.Address, txOrigin string) error {
ctx.Logger().Info("ZRC20 withdrawal to %s amount %d\n", hex.EncodeToString(event.To), event.Value)
Expand All @@ -117,31 +117,62 @@ func (k Keeper) ProcessZRC20WithdrawalEvent(ctx sdk.Context, event *zrc20.ZRC20W
return fmt.Errorf("cannot find foreign coin with emittingContract address %s", event.Raw.Address.Hex())
}

recvChain := k.zetaObserverKeeper.GetParams(ctx).GetChainFromChainID(foreignCoin.ForeignChainId)
receiverChain := k.zetaObserverKeeper.GetParams(ctx).GetChainFromChainID(foreignCoin.ForeignChainId)
senderChain := common.ZetaChain()
toAddr, err := recvChain.EncodeAddress(event.To)
toAddr, err := receiverChain.EncodeAddress(event.To)
if err != nil {
return fmt.Errorf("cannot encode address %s: %s", event.To, err.Error())
}
gasLimit := foreignCoin.GasLimit
msg := zetacoretypes.NewMsgSendVoter("", emittingContract.Hex(), senderChain.ChainId, txOrigin, toAddr, foreignCoin.ForeignChainId, math.NewUintFromBigInt(event.Value),
"", event.Raw.TxHash.String(), event.Raw.BlockNumber, gasLimit, foreignCoin.CoinType, foreignCoin.Asset)
msg := types.NewMsgSendVoter(
"",
emittingContract.Hex(),
senderChain.ChainId,
txOrigin,
toAddr,
foreignCoin.ForeignChainId,
math.NewUintFromBigInt(event.Value),
"",
event.Raw.TxHash.String(),
event.Raw.BlockNumber,
gasLimit,
foreignCoin.CoinType,
foreignCoin.Asset,
)
sendHash := msg.Digest()
cctx := k.CreateNewCCTX(ctx, msg, sendHash, zetacoretypes.CctxStatus_PendingOutbound, &senderChain, recvChain)

cctx := k.CreateNewCCTX(ctx, msg, sendHash, types.CctxStatus_PendingOutbound, &senderChain, receiverChain)

// Get gas price and amount
gasprice, found := k.GetGasPrice(ctx, receiverChain.ChainId)
if !found {
fmt.Printf("gasprice not found for %s\n", receiverChain)
return fmt.Errorf("gasprice not found for %s", receiverChain)
}
cctx.GetCurrentOutTxParam().OutboundTxGasPrice = fmt.Sprintf("%d", gasprice.Prices[gasprice.MedianIndex])
cctx.GetCurrentOutTxParam().Amount = cctx.InboundTxParams.Amount

EmitZRCWithdrawCreated(ctx, cctx)
return k.ProcessCCTX(ctx, cctx, recvChain)
return k.ProcessCCTX(ctx, cctx, receiverChain)
}

func (k Keeper) ProcessZetaSentEvent(ctx sdk.Context, event *connectorzevm.ZetaConnectorZEVMZetaSent, emittingContract ethcommon.Address, txOrigin string) error {
ctx.Logger().Info("Zeta withdrawal to %s amount %d to chain with chainId %d\n", hex.EncodeToString(event.DestinationAddress), event.ZetaValueAndGas, event.DestinationChainId)
ctx.Logger().Info(fmt.Sprintf(
"Zeta withdrawal to %s amount %d to chain with chainId %d",
hex.EncodeToString(event.DestinationAddress),
event.ZetaValueAndGas,
event.DestinationChainId,
))

if err := k.bankKeeper.BurnCoins(
ctx,
fungibletypes.ModuleName,
sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewIntFromBigInt(event.ZetaValueAndGas))),
); err != nil {
fmt.Printf("burn coins failed: %s\n", err.Error())
return fmt.Errorf("ProcessWithdrawalEvent: failed to burn coins from fungible: %s", err.Error())
return fmt.Errorf("ProcessZetaSentEvent: failed to burn coins from fungible: %s", err.Error())
}

receiverChainID := event.DestinationChainId
receiverChain := k.zetaObserverKeeper.GetParams(ctx).GetChainFromChainID(receiverChainID.Int64())
if receiverChain == nil {
Expand All @@ -150,37 +181,51 @@ func (k Keeper) ProcessZetaSentEvent(ctx sdk.Context, event *connectorzevm.ZetaC
// Validation if we want to send ZETA to external chain, but there is no ZETA token.
coreParams, found := k.zetaObserverKeeper.GetCoreParamsByChainID(ctx, receiverChain.ChainId)
if !found {
return zetacoretypes.ErrNotFoundCoreParams
return types.ErrNotFoundCoreParams
}
if receiverChain.IsExternalChain() && coreParams.ZetaTokenContractAddress == "" {
return zetacoretypes.ErrUnableToSendCoinType
return types.ErrUnableToSendCoinType
}
toAddr := "0x" + hex.EncodeToString(event.DestinationAddress)
senderChain := common.ZetaChain()
amount := math.NewUintFromBigInt(event.ZetaValueAndGas)

// Bump gasLimit by event index (which is very unlikely to be larger than 1000) to always have different ZetaSent events msgs.
msg := zetacoretypes.NewMsgSendVoter("", emittingContract.Hex(), senderChain.ChainId, txOrigin, toAddr, receiverChain.ChainId, amount, "", event.Raw.TxHash.String(), event.Raw.BlockNumber, 90000+uint64(event.Raw.Index), common.CoinType_Zeta, "")
msg := types.NewMsgSendVoter(
"",
emittingContract.Hex(),
senderChain.ChainId,
txOrigin, toAddr,
receiverChain.ChainId,
amount,
"",
event.Raw.TxHash.String(),
event.Raw.BlockNumber,
90000+uint64(event.Raw.Index),
common.CoinType_Zeta,
"",
)
sendHash := msg.Digest()
cctx := k.CreateNewCCTX(ctx, msg, sendHash, zetacoretypes.CctxStatus_PendingOutbound, &senderChain, receiverChain)

// Create the CCTX
cctx := k.CreateNewCCTX(ctx, msg, sendHash, types.CctxStatus_PendingOutbound, &senderChain, receiverChain)

// Pay gas in Zeta and update the amount for the cctx
if err := k.PayGasInZetaAndUpdateCctx(ctx, receiverChain.ChainId, &cctx, true); err != nil {
return fmt.Errorf("ProcessWithdrawalEvent: pay gas failed: %s", err.Error())
}

EmitZetaWithdrawCreated(ctx, cctx)
return k.ProcessCCTX(ctx, cctx, receiverChain)
}

func (k Keeper) ProcessCCTX(ctx sdk.Context, cctx zetacoretypes.CrossChainTx, receiverChain *common.Chain) error {
cctx.GetCurrentOutTxParam().Amount = cctx.InboundTxParams.Amount
gasprice, found := k.GetGasPrice(ctx, receiverChain.ChainId)
if !found {
fmt.Printf("gasprice not found for %s\n", receiverChain)
return fmt.Errorf("gasprice not found for %s", receiverChain)
}
cctx.GetCurrentOutTxParam().OutboundTxGasPrice = fmt.Sprintf("%d", gasprice.Prices[gasprice.MedianIndex])
cctx.CctxStatus.Status = zetacoretypes.CctxStatus_PendingOutbound
func (k Keeper) ProcessCCTX(ctx sdk.Context, cctx types.CrossChainTx, receiverChain *common.Chain) error {
inCctxIndex, ok := ctx.Value("inCctxIndex").(string)
if ok {
cctx.InboundTxParams.InboundTxObservedHash = inCctxIndex
}
err := k.UpdateNonce(ctx, receiverChain.ChainId, &cctx)
if err != nil {

if err := k.UpdateNonce(ctx, receiverChain.ChainId, &cctx); err != nil {
return fmt.Errorf("ProcessWithdrawalEvent: update nonce failed: %s", err.Error())
}

Expand All @@ -189,7 +234,7 @@ func (k Keeper) ProcessCCTX(ctx sdk.Context, cctx zetacoretypes.CrossChainTx, re
return nil
}

// given a log entry, try extracting Withdrawal event from registered ZRC20 contract;
// ParseZRC20WithdrawalEvent tries extracting Withdrawal event from registered ZRC20 contract;
// returns error if the log entry is not a Withdrawal event, or is not emitted from a
// registered ZRC20 contract
func (k Keeper) ParseZRC20WithdrawalEvent(ctx sdk.Context, log ethtypes.Log) (*zrc20.ZRC20Withdrawal, error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (k msgServer) VoteOnObservedInboundTx(goCtx context.Context, msg *types.Msg
} else { // Cross Chain SWAP
tmpCtx, commit := ctx.CacheContext()
err = func() error {
err := k.PayGasInZetaAndUpdateCctx(tmpCtx, receiverChain.ChainId, &cctx)
err := k.PayGasInZetaAndUpdateCctx(tmpCtx, receiverChain.ChainId, &cctx, false)
if err != nil {
return err
}
Expand Down
6 changes: 4 additions & 2 deletions x/crosschain/keeper/keeper_cross_chain_tx_vote_outbound_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ func (k msgServer) VoteOnObservedOutboundTx(goCtx context.Context, msg *types.Ms
}
if ballot.BallotStatus != observerTypes.BallotStatus_BallotFinalized_FailureObservation {
if !msg.ZetaMinted.Equal(cctx.GetCurrentOutTxParam().Amount) {
log.Error().Msgf("ReceiveConfirmation: Mint mismatch: %s vs %s", msg.ZetaMinted, cctx.GetCurrentOutTxParam().Amount)
log.Error().Msgf("ReceiveConfirmation: Mint mismatch: %s zeta minted vs %s cctx amount",
msg.ZetaMinted,
cctx.GetCurrentOutTxParam().Amount)
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, fmt.Sprintf("ZetaMinted %s does not match send ZetaMint %s", msg.ZetaMinted, cctx.GetCurrentOutTxParam().Amount))
}
}
Expand Down Expand Up @@ -147,7 +149,7 @@ func (k msgServer) VoteOnObservedOutboundTx(goCtx context.Context, msg *types.Ms
CoinType: cctx.InboundTxParams.CoinType,
OutboundTxGasLimit: cctx.OutboundTxParams[0].OutboundTxGasLimit, // NOTE(pwu): revert gas limit = initial outbound gas limit set by user;
})
err := k.PayGasInZetaAndUpdateCctx(tmpCtx, cctx.InboundTxParams.SenderChainId, &cctx)
err := k.PayGasInZetaAndUpdateCctx(tmpCtx, cctx.InboundTxParams.SenderChainId, &cctx, false)
if err != nil {
return err
}
Expand Down
42 changes: 33 additions & 9 deletions x/crosschain/keeper/keeper_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import (
"fmt"
"math/big"

"github.com/zeta-chain/zetacore/cmd/zetacored/config"

"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/zeta-chain/zetacore/cmd/zetacored/config"
"github.com/zeta-chain/zetacore/common"
"github.com/zeta-chain/zetacore/x/crosschain/types"
zetaObserverTypes "github.com/zeta-chain/zetacore/x/observer/types"
)
Expand All @@ -26,15 +26,23 @@ func (k Keeper) IsAuthorizedNodeAccount(ctx sdk.Context, address string) bool {
// it also makes a trade to fulfill the outbound tx gas fee in ZETA by swapping ZETA for some gas ZRC20 balances
// The gas ZRC20 balance is subsequently burned to account for the expense of TSS address gas fee payment in the outbound tx.
// **Caller should feed temporary ctx into this function**
func (k Keeper) PayGasInZetaAndUpdateCctx(ctx sdk.Context, chainID int64, cctx *types.CrossChainTx) error {
func (k Keeper) PayGasInZetaAndUpdateCctx(ctx sdk.Context, chainID int64, cctx *types.CrossChainTx, noEthereumTxEvent bool) error {
// TODO: this check currently break the integration tests, enable it when integration tests are fixed
// https://github.com/zeta-chain/node/issues/1022
//if cctx.InboundTxParams.CoinType != common.CoinType_Zeta {
// return sdkerrors.Wrapf(zetaObserverTypes.ErrInvalidCoinType, "can't pay gas in zeta with %s", cctx.InboundTxParams.CoinType.String())
//}

chain := k.zetaObserverKeeper.GetParams(ctx).GetChainFromChainID(chainID)
if chain == nil {
return zetaObserverTypes.ErrSupportedChains
}
medianGasPrice, isFound := k.GetMedianGasPriceInUint(ctx, chain.ChainId)
if !isFound {
return sdkerrors.Wrap(types.ErrUnableToGetGasPrice, fmt.Sprintf(" chain %d | Identifiers : %s ", cctx.GetCurrentOutTxParam().ReceiverChainId, cctx.LogIdentifierForCCTX()))
return sdkerrors.Wrap(types.ErrUnableToGetGasPrice, fmt.Sprintf(" chain %d | Identifiers : %s ",
cctx.GetCurrentOutTxParam().ReceiverChainId,
cctx.LogIdentifierForCCTX()),
)
}
medianGasPrice = medianGasPrice.MulUint64(2) // overpays gas price by 2x
cctx.GetCurrentOutTxParam().OutboundTxGasPrice = medianGasPrice.String()
Expand All @@ -46,6 +54,7 @@ func (k Keeper) PayGasInZetaAndUpdateCctx(ctx sdk.Context, chainID int64, cctx *
if err != nil {
return sdkerrors.Wrap(err, "PayGasInZetaAndUpdateCctx: unable to get system contract gas coin")
}

outTxGasFeeInZeta, err := k.fungibleKeeper.QueryUniswapv2RouterGetAmountsIn(ctx, outTxGasFee.BigInt(), gasZRC20)
if err != nil {
return sdkerrors.Wrap(err, "PayGasInZetaAndUpdateCctx: unable to QueryUniswapv2RouterGetAmountsIn")
Expand All @@ -54,26 +63,41 @@ func (k Keeper) PayGasInZetaAndUpdateCctx(ctx sdk.Context, chainID int64, cctx *

cctx.ZetaFees = cctx.ZetaFees.Add(feeInZeta)

if cctx.ZetaFees.GT(cctx.InboundTxParams.Amount) && cctx.InboundTxParams.CoinType == common.CoinType_Zeta {
return sdkerrors.Wrap(types.ErrNotEnoughZetaBurnt, fmt.Sprintf("feeInZeta(%s) more than zetaBurnt (%s) | Identifiers : %s ", cctx.ZetaFees, cctx.InboundTxParams.Amount, cctx.LogIdentifierForCCTX()))
if cctx.ZetaFees.GT(cctx.InboundTxParams.Amount) {
return sdkerrors.Wrap(types.ErrNotEnoughZetaBurnt, fmt.Sprintf("feeInZeta(%s) more than zetaBurnt (%s) | Identifiers : %s ",
cctx.ZetaFees,
cctx.InboundTxParams.Amount,
cctx.LogIdentifierForCCTX()),
)
}

ctx.Logger().Info("Subtracting amount from inbound tx", "amount", cctx.InboundTxParams.Amount.String(), "feeInZeta",
cctx.ZetaFees.String())
cctx.GetCurrentOutTxParam().Amount = cctx.InboundTxParams.Amount.Sub(cctx.ZetaFees)

// ** The following logic converts the outTxGasFeeInZeta into gasZRC20 and burns it **
// swap the outTxGasFeeInZeta portion of zeta to the real gas ZRC20 and burn it, in a temporary context.
{
coins := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewIntFromBigInt(feeInZeta.BigInt())))
err = k.bankKeeper.MintCoins(ctx, types.ModuleName, coins)
err := k.bankKeeper.MintCoins(ctx, types.ModuleName, coins)
if err != nil {
return sdkerrors.Wrap(err, "PayGasInZetaAndUpdateCctx: unable to mint coins")
}
amounts, err := k.fungibleKeeper.CallUniswapv2RouterSwapExactETHForToken(ctx, types.ModuleAddressEVM, types.ModuleAddressEVM, outTxGasFeeInZeta, gasZRC20)

amounts, err := k.fungibleKeeper.CallUniswapv2RouterSwapExactETHForToken(
ctx,
types.ModuleAddressEVM,
types.ModuleAddressEVM,
outTxGasFeeInZeta,
gasZRC20,
noEthereumTxEvent,
)
if err != nil {
return sdkerrors.Wrap(err, "PayGasInZetaAndUpdateCctx: unable to CallUniswapv2RouterSwapExactETHForToken")
}
ctx.Logger().Info("gas fee", "outTxGasFee", outTxGasFee, "outTxGasFeeInZeta", outTxGasFeeInZeta)
ctx.Logger().Info("CallUniswapv2RouterSwapExactETHForToken", "zetaAmountIn", amounts[0], "zrc20AmountOut", amounts[1])
err = k.fungibleKeeper.CallZRC20Burn(ctx, types.ModuleAddressEVM, gasZRC20, amounts[1])
err = k.fungibleKeeper.CallZRC20Burn(ctx, types.ModuleAddressEVM, gasZRC20, amounts[1], noEthereumTxEvent)
if err != nil {
return sdkerrors.Wrap(err, "PayGasInZetaAndUpdateCctx: unable to CallZRC20Burn")
}
Expand Down
Loading

0 comments on commit 4173f36

Please sign in to comment.