Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(crosschain): zeta withdraw fees #991

Merged
merged 28 commits into from
Aug 29, 2023
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
98 changes: 71 additions & 27 deletions x/crosschain/keeper/evm_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"

"cosmossdk.io/math"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
ethcommon "github.com/ethereum/go-ethereum/common"
Expand All @@ -17,7 +16,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 @@ -81,7 +80,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 @@ -91,31 +90,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 @@ -124,37 +154,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 @@ -163,7 +207,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
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
Loading