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

feat: allow setting liquidity cap for ZRC20 #1205

Merged
merged 17 commits into from
Oct 3, 2023
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ jobs:
run: rm -rf *

smoke-test:
runs-on: ["zeta-runners"] # 20 minutes
runs-on: ["zeta-runners"] # 25 minutes
timeout-minutes: 25
steps:
- uses: actions/checkout@v3
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 @@ -312,6 +312,9 @@ func LocalSmokeTest(_ *cobra.Command, _ []string) {
smokeTest.TestUpdateBytecode()
smokeTest.CheckZRC20ReserveAndSupply()

smokeTest.TestDepositEtherLiquidityCap()
smokeTest.CheckZRC20ReserveAndSupply()

// add your dev test here
smokeTest.TestMyTest()

Expand Down
110 changes: 107 additions & 3 deletions contrib/localnet/orchestrator/smoketest/test_deposit_eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@ import (
"math/big"
"time"

"github.com/zeta-chain/zetacore/common/ethereum"
observertypes "github.com/zeta-chain/zetacore/x/observer/types"

"cosmossdk.io/math"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
ethcommon "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
zrc20 "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol"
"github.com/zeta-chain/zetacore/common"
"github.com/zeta-chain/zetacore/common/ethereum"
"github.com/zeta-chain/zetacore/x/crosschain/types"
fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types"
observertypes "github.com/zeta-chain/zetacore/x/observer/types"
"github.com/zeta-chain/zetacore/zetaclient"
)

Expand Down Expand Up @@ -285,6 +286,109 @@ func (sm *SmokeTest) TestDepositAndCallRefund() {
}()
}

// TestDepositEtherLiquidityCap tests depositing Ethers in a context where a liquidity cap is set
func (sm *SmokeTest) TestDepositEtherLiquidityCap() {
startTime := time.Now()
defer func() {
fmt.Printf("test finishes in %s\n", time.Since(startTime))
}()
LoudPrintf("Deposit Ethers into ZEVM with a liquidity cap\n")

supply, err := sm.ETHZRC20.TotalSupply(&bind.CallOpts{})
if err != nil {
panic(err)
}

// Set a liquidity cap slightly above the current supply
fmt.Println("Setting a liquidity cap")
liquidityCap := math.NewUintFromBigInt(supply).Add(math.NewUint(1e16))
msg := fungibletypes.NewMsgUpdateZRC20LiquidityCap(
FungibleAdminAddress,
sm.ETHZRC20Addr.Hex(),
liquidityCap,
)
res, err := sm.zetaTxServer.BroadcastTx(FungibleAdminName, msg)
if err != nil {
panic(err)
}
fmt.Printf("set liquidity cap tx hash: %s\n", res.TxHash)

fmt.Println("Depositing more than liquidity cap should make cctx reverted")
signedTx, err := sm.SendEther(TSSAddress, big.NewInt(1e17), nil)
if err != nil {
panic(err)
}
receipt := MustWaitForTxReceipt(sm.goerliClient, signedTx)
if receipt.Status == 0 {
panic("deposit eth tx failed")
}
cctx := WaitCctxMinedByInTxHash(signedTx.Hash().Hex(), sm.cctxClient)
if cctx.CctxStatus.Status != types.CctxStatus_Reverted {
panic(fmt.Sprintf("expected cctx status to be Reverted; got %s", cctx.CctxStatus.Status))
}
fmt.Println("CCTX has been reverted")

fmt.Println("Depositing less than liquidity cap should still succeed")
initialBal, err := sm.ETHZRC20.BalanceOf(&bind.CallOpts{}, DeployerAddress)
if err != nil {
panic(err)
}
signedTx, err = sm.SendEther(TSSAddress, big.NewInt(1e15), nil)
if err != nil {
panic(err)
}
receipt = MustWaitForTxReceipt(sm.goerliClient, signedTx)
if receipt.Status == 0 {
panic("deposit eth tx failed")
}
WaitCctxMinedByInTxHash(signedTx.Hash().Hex(), sm.cctxClient)
expectedBalance := big.NewInt(0).Add(initialBal, big.NewInt(1e15))

bal, err := sm.ETHZRC20.BalanceOf(&bind.CallOpts{}, DeployerAddress)
if err != nil {
panic(err)
}
if bal.Cmp(expectedBalance) != 0 {
panic(fmt.Sprintf("expected balance to be %s; got %s", expectedBalance.String(), bal.String()))
}
fmt.Println("Deposit succeeded")

fmt.Println("Removing the liquidity cap")
msg = fungibletypes.NewMsgUpdateZRC20LiquidityCap(
FungibleAdminAddress,
sm.ETHZRC20Addr.Hex(),
math.ZeroUint(),
)
res, err = sm.zetaTxServer.BroadcastTx(FungibleAdminName, msg)
if err != nil {
panic(err)
}
fmt.Printf("remove liquidity cap tx hash: %s\n", res.TxHash)
initialBal, err = sm.ETHZRC20.BalanceOf(&bind.CallOpts{}, DeployerAddress)
if err != nil {
panic(err)
}
signedTx, err = sm.SendEther(TSSAddress, big.NewInt(1e17), nil)
if err != nil {
panic(err)
}
receipt = MustWaitForTxReceipt(sm.goerliClient, signedTx)
if receipt.Status == 0 {
panic("deposit eth tx failed")
}
WaitCctxMinedByInTxHash(signedTx.Hash().Hex(), sm.cctxClient)
expectedBalance = big.NewInt(0).Add(initialBal, big.NewInt(1e17))

bal, err = sm.ETHZRC20.BalanceOf(&bind.CallOpts{}, DeployerAddress)
if err != nil {
panic(err)
}
if bal.Cmp(expectedBalance) != 0 {
panic(fmt.Sprintf("expected balance to be %s; got %s", expectedBalance.String(), bal.String()))
}
fmt.Println("New deposit succeeded")
}

func (sm *SmokeTest) SendEther(to ethcommon.Address, value *big.Int, data []byte) (*ethtypes.Transaction, error) {
goerliClient := sm.goerliClient

Expand Down
4 changes: 3 additions & 1 deletion contrib/localnet/orchestrator/smoketest/test_erc20.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ func (sm *SmokeTest) DepositERC20(amount *big.Int, msg []byte) ethcommon.Hash {
panic(err)
}
receipt = MustWaitForTxReceipt(sm.goerliClient, tx)
if receipt.Status == 0 {
panic("deposit failed")
}
fmt.Printf("Deposit receipt tx hash: %s, status %d\n", receipt.TxHash.Hex(), receipt.Status)
for _, log := range receipt.Logs {
event, err := sm.ERC20Custody.ParseDeposited(*log)
Expand All @@ -83,7 +86,6 @@ func (sm *SmokeTest) DepositERC20(amount *big.Int, msg []byte) ethcommon.Hash {
}
fmt.Printf("gas limit %d\n", sm.zevmAuth.GasLimit)
return tx.Hash()
//WaitCctxMinedByInTxHash(tx.Hash().Hex(), sm.cctxClient)
}

func (sm *SmokeTest) TestERC20Withdraw() {
Expand Down
4 changes: 4 additions & 0 deletions docs/openapi/openapi.swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50783,6 +50783,8 @@ definitions:
format: uint64
paused:
type: boolean
liquidity_cap:
type: string
fungibleMsgDeployFungibleCoinZRC20Response:
type: object
properties:
Expand All @@ -50798,6 +50800,8 @@ definitions:
format: byte
fungibleMsgUpdateSystemContractResponse:
type: object
fungibleMsgUpdateZRC20LiquidityCapResponse:
type: object
fungibleMsgUpdateZRC20PausedStatusResponse:
type: object
fungibleMsgUpdateZRC20WithdrawFeeResponse:
Expand Down
32 changes: 22 additions & 10 deletions docs/spec/fungible/messages.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,6 @@ message MsgUpdateSystemContract {
}
```

## MsgUpdateZRC20WithdrawFee

```proto
message MsgUpdateZRC20WithdrawFee {
string creator = 1;
string zrc20_address = 2;
string new_withdraw_fee = 6;
}
```

## MsgUpdateContractBytecode

UpdateContractBytecode updates the bytecode of a contract from the bytecode of an existing contract
Expand All @@ -81,6 +71,16 @@ message MsgUpdateContractBytecode {
}
```

## MsgUpdateZRC20WithdrawFee

```proto
message MsgUpdateZRC20WithdrawFee {
string creator = 1;
string zrc20_address = 2;
string new_withdraw_fee = 6;
}
```

## MsgUpdateZRC20PausedStatus

UpdateZRC20PausedStatus updates the paused status of a ZRC20
Expand All @@ -94,3 +94,15 @@ message MsgUpdateZRC20PausedStatus {
}
```

## MsgUpdateZRC20LiquidityCap

UpdateZRC20LiquidityCap updates the liquidity cap for a ZRC20 token.

```proto
message MsgUpdateZRC20LiquidityCap {
string creator = 1;
string zrc20_address = 2;
string liquidity_cap = 3;
}
```

5 changes: 5 additions & 0 deletions proto/fungible/foreign_coins.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ syntax = "proto3";
package zetachain.zetacore.fungible;

import "common/common.proto";
import "gogoproto/gogo.proto";

option go_package = "github.com/zeta-chain/zetacore/x/fungible/types";

Expand All @@ -16,4 +17,8 @@ message ForeignCoins {
common.CoinType coin_type = 8;
uint64 gas_limit = 9;
bool paused = 10;
string liquidity_cap = 11 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint",
(gogoproto.nullable) = false
];
}
14 changes: 13 additions & 1 deletion proto/fungible/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ service Msg {
rpc DeployFungibleCoinZRC20(MsgDeployFungibleCoinZRC20) returns (MsgDeployFungibleCoinZRC20Response);
rpc RemoveForeignCoin(MsgRemoveForeignCoin) returns (MsgRemoveForeignCoinResponse);
rpc UpdateSystemContract(MsgUpdateSystemContract) returns (MsgUpdateSystemContractResponse);
rpc UpdateZRC20WithdrawFee(MsgUpdateZRC20WithdrawFee) returns (MsgUpdateZRC20WithdrawFeeResponse);
rpc UpdateContractBytecode(MsgUpdateContractBytecode) returns (MsgUpdateContractBytecodeResponse);
rpc UpdateZRC20WithdrawFee(MsgUpdateZRC20WithdrawFee) returns (MsgUpdateZRC20WithdrawFeeResponse);
rpc UpdateZRC20PausedStatus(MsgUpdateZRC20PausedStatus) returns (MsgUpdateZRC20PausedStatusResponse);
rpc UpdateZRC20LiquidityCap(MsgUpdateZRC20LiquidityCap) returns (MsgUpdateZRC20LiquidityCapResponse);
}

message MsgUpdateZRC20WithdrawFee {
Expand Down Expand Up @@ -78,3 +79,14 @@ message MsgUpdateZRC20PausedStatus {
}

message MsgUpdateZRC20PausedStatusResponse {}

message MsgUpdateZRC20LiquidityCap {
string creator = 1;
string zrc20_address = 2;
string liquidity_cap = 3 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Uint",
(gogoproto.nullable) = false
];
}

message MsgUpdateZRC20LiquidityCapResponse {}
6 changes: 6 additions & 0 deletions testutil/nullify/nullify.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import (
"reflect"
"unsafe"

"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
)

var (
coinType = reflect.TypeOf(sdk.Coin{})
coinsType = reflect.TypeOf(sdk.Coins{})
uintType = reflect.TypeOf(math.Uint{})
)

// Fill analyze all struct fields and slices with
Expand Down Expand Up @@ -46,6 +48,10 @@ func Fill(x interface{}) interface{} {
coins := reflect.New(coinsType).Interface()
s := reflect.ValueOf(coins).Elem()
f.Set(s)
case uintType:
mathUint := reflect.New(uintType).Interface()
s := reflect.ValueOf(mathUint).Elem()
f.Set(s)
default:
//#nosec G103
objPt := reflect.NewAt(f.Type(), unsafe.Pointer(f.UnsafeAddr())).Interface()
Expand Down
48 changes: 28 additions & 20 deletions x/crosschain/keeper/evm_deposit.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"encoding/hex"
"fmt"

fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types"

sdk "github.com/cosmos/cosmos-sdk/types"
ethcommon "github.com/ethereum/go-ethereum/common"
evmtypes "github.com/evmos/ethermint/x/evm/types"
Expand All @@ -14,9 +16,10 @@ import (
"github.com/zeta-chain/zetacore/x/crosschain/types"
)

// HandleEVMDeposit handles a deposit from an inbound tx
// returns (isContractReverted, err)
// (true, non-nil) means CallEVM() reverted
func (k msgServer) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx, msg types.MsgVoteOnObservedInboundTx, senderChain *common.Chain) (bool, error) {
func (k Keeper) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx, msg types.MsgVoteOnObservedInboundTx, senderChain *common.Chain) (bool, error) {
to := ethcommon.HexToAddress(msg.Receiver)
var ethTxHash ethcommon.Hash
if len(ctx.TxBytes()) > 0 {
Expand Down Expand Up @@ -48,36 +51,41 @@ func (k msgServer) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx, m
evmTxResponse, err := k.fungibleKeeper.ZRC20DepositAndCallContract(ctx, from, to, msg.Amount.BigInt(), senderChain, msg.Message, contract, data, msg.CoinType, msg.Asset)
if err != nil {
isContractReverted := false
if evmTxResponse != nil && evmTxResponse.Failed() {

// consider the contract as reverted if foreign coin liquidity cap is reached
if (evmTxResponse != nil && evmTxResponse.Failed()) || errors.Is(err, fungibletypes.ErrForeignCoinCapReached) {
isContractReverted = true
}

return isContractReverted, err
}

// non-empty msg.Message means this is a contract call; therefore the logs should be processed.
// a withdrawal event in the logs could generate cctxs for outbound transactions.
if !evmTxResponse.Failed() && len(msg.Message) > 0 {
logs := evmtypes.LogsToEthereum(evmTxResponse.Logs)
ctx = ctx.WithValue("inCctxIndex", cctx.Index)
txOrigin := msg.TxOrigin
if txOrigin == "" {
txOrigin = msg.Sender
}
if len(logs) > 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does len(logs) == 0 mean something wrong (e.g., we always expect some logs)? Just curious about what pre-assumption we had in our mind.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It prevents doing unnecessary call to fetch system contracts since there would be no logs to iterate

ctx = ctx.WithValue("inCctxIndex", cctx.Index)
txOrigin := msg.TxOrigin
if txOrigin == "" {
txOrigin = msg.Sender
}

err = k.ProcessLogs(ctx, logs, contract, txOrigin)
if err != nil {
// ProcessLogs should not error; error indicates exception, should abort
return false, errors.Wrap(types.ErrCannotProcessWithdrawal, err.Error())
err = k.ProcessLogs(ctx, logs, contract, txOrigin)
if err != nil {
// ProcessLogs should not error; error indicates exception, should abort
return false, errors.Wrap(types.ErrCannotProcessWithdrawal, err.Error())
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute("action", "DepositZRC20AndCallContract"),
sdk.NewAttribute("contract", contract.String()),
sdk.NewAttribute("data", hex.EncodeToString(data)),
sdk.NewAttribute("cctxIndex", cctx.Index),
),
)
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute("action", "DepositZRC20AndCallContract"),
sdk.NewAttribute("contract", contract.String()),
sdk.NewAttribute("data", hex.EncodeToString(data)),
sdk.NewAttribute("cctxIndex", cctx.Index),
),
)
}
}
return false, nil
Expand Down
Loading