From 5e548fc2393b92027322a709e3c5610edb5281e4 Mon Sep 17 00:00:00 2001 From: skosito Date: Thu, 22 Feb 2024 09:43:10 +0000 Subject: [PATCH 1/3] test: improve fungible module coverage (system_contract tests) (#1782) --- app/ante/handler_options.go | 5 +- app/app.go | 5 +- changelog.md | 1 + .../keeper/msg_server_gas_price_voter.go | 4 +- .../keeper/msg_server_vote_inbound_tx.go | 9 +- .../keeper/msg_server_vote_inbound_tx_test.go | 4 +- .../keeper/msg_server_vote_outbound_tx.go | 7 +- x/crosschain/keeper/utils_test.go | 2 +- .../types/message_add_to_out_tx_tracker.go | 5 +- x/crosschain/types/message_gas_price_voter.go | 5 +- .../message_remove_from_out_tx_tracker.go | 5 +- x/crosschain/types/message_tss_voter.go | 3 +- .../message_vote_on_observed_inbound_tx.go | 9 +- .../message_vote_on_observed_outbound_tx.go | 5 +- x/crosschain/types/message_whitelist_erc20.go | 9 +- x/fungible/keeper/evm.go | 2 +- x/fungible/keeper/evm_test.go | 51 + x/fungible/keeper/gas_coin_and_pool.go | 22 +- x/fungible/keeper/gas_coin_and_pool_test.go | 70 + x/fungible/keeper/gas_price.go | 52 +- .../msg_server_deploy_fungible_coin_zrc20.go | 7 +- .../keeper/msg_server_remove_foreign_coin.go | 5 +- .../msg_server_update_system_contract.go | 17 +- x/fungible/keeper/system_contract.go | 42 +- x/fungible/keeper/system_contract_test.go | 1188 +++++++++++++---- .../message_deploy_fungible_coin_zrc20.go | 7 +- ...message_deploy_fungible_coin_zrc20_test.go | 3 +- .../types/message_deploy_system_contracts.go | 3 +- .../types/message_remove_foreign_coin.go | 3 +- .../types/message_update_system_contract.go | 5 +- .../keeper/msg_server_add_blame_vote.go | 4 +- x/observer/types/message_add_blame_vote.go | 5 +- x/observer/types/message_add_observer.go | 10 +- x/observer/types/message_update_keygen.go | 3 +- x/observer/types/message_update_observer.go | 12 +- 35 files changed, 1217 insertions(+), 372 deletions(-) diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go index e46f7b728e..4d116a581e 100644 --- a/app/ante/handler_options.go +++ b/app/ante/handler_options.go @@ -21,6 +21,7 @@ import ( observerkeeper "github.com/zeta-chain/zetacore/x/observer/keeper" + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/types/tx/signing" @@ -165,7 +166,7 @@ func (sud SetUpContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate // Set a gas meter with limit 0 as to prevent an infinite gas meter attack // during runTx. newCtx = SetGasMeter(simulate, ctx, 0) - return newCtx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be GasTx") + return newCtx, cosmoserrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be GasTx") } newCtx = SetGasMeter(simulate, ctx, gasTx.GetGas()) @@ -183,7 +184,7 @@ func (sud SetUpContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate "out of gas in location: %v; gasWanted: %d, gasUsed: %d", rType.Descriptor, gasTx.GetGas(), newCtx.GasMeter().GasConsumed()) - err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, log) + err = cosmoserrors.Wrap(sdkerrors.ErrOutOfGas, log) default: panic(r) } diff --git a/app/app.go b/app/app.go index c7c749cda3..63b889fa9c 100644 --- a/app/app.go +++ b/app/app.go @@ -6,6 +6,7 @@ import ( "os" "time" + cosmoserrors "cosmossdk.io/errors" "github.com/gorilla/mux" "github.com/rakyll/statik/fs" "github.com/spf13/cast" @@ -776,10 +777,10 @@ func initParamsKeeper(appCodec codec.BinaryCodec, legacyAmino *codec.LegacyAmino // VerifyAddressFormat verifies the address is compatible with ethereum func VerifyAddressFormat(bz []byte) error { if len(bz) == 0 { - return sdkerrors.Wrap(sdkerrors.ErrUnknownAddress, "invalid address; cannot be empty") + return cosmoserrors.Wrap(sdkerrors.ErrUnknownAddress, "invalid address; cannot be empty") } if len(bz) != AddrLen { - return sdkerrors.Wrapf( + return cosmoserrors.Wrapf( sdkerrors.ErrUnknownAddress, "invalid address length; got: %d, expect: %d", len(bz), AddrLen, ) diff --git a/changelog.md b/changelog.md index f2fe3e0815..2080bde846 100644 --- a/changelog.md +++ b/changelog.md @@ -42,6 +42,7 @@ * [1746](https://github.com/zeta-chain/node/pull/1746) - rename smoke tests to e2e tests * [1753](https://github.com/zeta-chain/node/pull/1753) - fix gosec errors on usage of rand package * [1762](https://github.com/zeta-chain/node/pull/1762) - improve coverage for fungibile module +* [1782](https://github.com/zeta-chain/node/pull/1782) - improve coverage for fungibile module system contract ### CI diff --git a/x/crosschain/keeper/msg_server_gas_price_voter.go b/x/crosschain/keeper/msg_server_gas_price_voter.go index 2c56b3f266..db38b4c8f4 100644 --- a/x/crosschain/keeper/msg_server_gas_price_voter.go +++ b/x/crosschain/keeper/msg_server_gas_price_voter.go @@ -7,10 +7,10 @@ import ( "sort" "strconv" + cosmoserrors "cosmossdk.io/errors" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/zeta-chain/zetacore/x/crosschain/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) @@ -31,7 +31,7 @@ func (k msgServer) GasPriceVoter(goCtx context.Context, msg *types.MsgGasPriceVo return nil, observertypes.ErrNotAuthorizedPolicy } if chain == nil { - return nil, sdkerrors.Wrap(types.ErrUnsupportedChain, fmt.Sprintf("ChainID : %d ", msg.ChainId)) + return nil, cosmoserrors.Wrap(types.ErrUnsupportedChain, fmt.Sprintf("ChainID : %d ", msg.ChainId)) } gasPrice, isFound := k.GetGasPrice(ctx, chain.ChainId) diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index ef98c85bf8..930bbd8421 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -4,9 +4,8 @@ import ( "context" "fmt" - errorsmod "cosmossdk.io/errors" + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/x/crosschain/types" observerKeeper "github.com/zeta-chain/zetacore/x/observer/keeper" @@ -67,11 +66,11 @@ func (k msgServer) VoteOnObservedInboundTx(goCtx context.Context, msg *types.Msg // GetChainFromChainID makes sure we are getting only supported chains , if a chain support has been turned on using gov proposal, this function returns nil observationChain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, msg.SenderChainId) if observationChain == nil { - return nil, sdkerrors.Wrap(types.ErrUnsupportedChain, fmt.Sprintf("ChainID %d, Observation %s", msg.SenderChainId, observationType.String())) + return nil, cosmoserrors.Wrap(types.ErrUnsupportedChain, fmt.Sprintf("ChainID %d, Observation %s", msg.SenderChainId, observationType.String())) } receiverChain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, msg.ReceiverChain) if receiverChain == nil { - return nil, sdkerrors.Wrap(types.ErrUnsupportedChain, fmt.Sprintf("ChainID %d, Observation %s", msg.ReceiverChain, observationType.String())) + return nil, cosmoserrors.Wrap(types.ErrUnsupportedChain, fmt.Sprintf("ChainID %d, Observation %s", msg.ReceiverChain, observationType.String())) } tssPub := "" tss, tssFound := k.zetaObserverKeeper.GetTSS(ctx) @@ -93,7 +92,7 @@ func (k msgServer) VoteOnObservedInboundTx(goCtx context.Context, msg *types.Msg if isNew { // Check if the inbound has already been processed. if k.IsFinalizedInbound(ctx, msg.InTxHash, msg.SenderChainId, msg.EventIndex) { - return nil, errorsmod.Wrap(types.ErrObservedTxAlreadyFinalized, fmt.Sprintf("InTxHash:%s, SenderChainID:%d, EventIndex:%d", msg.InTxHash, msg.SenderChainId, msg.EventIndex)) + return nil, cosmoserrors.Wrap(types.ErrObservedTxAlreadyFinalized, fmt.Sprintf("InTxHash:%s, SenderChainID:%d, EventIndex:%d", msg.InTxHash, msg.SenderChainId, msg.EventIndex)) } observerKeeper.EmitEventBallotCreated(ctx, ballot, msg.InTxHash, observationChain.String()) } diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go index 1a8c742c5b..1605a37e33 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go @@ -60,7 +60,7 @@ func TestKeeper_VoteOnObservedInboundTx(t *testing.T) { require.NoError(t, err) } ballot, _, _ := zk.ObserverKeeper.FindBallot(ctx, msg.Digest(), zk.ObserverKeeper.GetSupportedChainFromChainID(ctx, msg.SenderChainId), observerTypes.ObservationType_InBoundTx) - require.Equal(t, ballot.BallotStatus, observerTypes.BallotStatus_BallotFinalized_SuccessObservation) + require.Equal(t, ballot.BallotStatus, observertypes.BallotStatus_BallotFinalized_SuccessObservation) cctx, found := k.GetCrossChainTx(ctx, msg.Digest()) require.True(t, found) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_OutboundMined) @@ -122,7 +122,7 @@ func TestNoDoubleEventProtections(t *testing.T) { // Check that the vote passed ballot, found := zk.ObserverKeeper.GetBallot(ctx, msg.Digest()) require.True(t, found) - require.Equal(t, ballot.BallotStatus, observerTypes.BallotStatus_BallotFinalized_SuccessObservation) + require.Equal(t, ballot.BallotStatus, observertypes.BallotStatus_BallotFinalized_SuccessObservation) //Perform the SAME event. Except, this time, we resubmit the event. msg2 := &types.MsgVoteOnObservedInboundTx{ Creator: validatorAddr, diff --git a/x/crosschain/keeper/msg_server_vote_outbound_tx.go b/x/crosschain/keeper/msg_server_vote_outbound_tx.go index 17319d1f8a..72555df8cd 100644 --- a/x/crosschain/keeper/msg_server_vote_outbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_outbound_tx.go @@ -6,6 +6,7 @@ import ( "fmt" "math/big" + cosmoserrors "cosmossdk.io/errors" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" @@ -81,11 +82,11 @@ func (k msgServer) VoteOnObservedOutboundTx(goCtx context.Context, msg *types.Ms // Check if CCTX exists cctx, found := k.GetCrossChainTx(ctx, msg.CctxHash) if !found { - return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, fmt.Sprintf("CCTX %s does not exist", msg.CctxHash)) + return nil, cosmoserrors.Wrap(sdkerrors.ErrInvalidRequest, fmt.Sprintf("CCTX %s does not exist", msg.CctxHash)) } if cctx.GetCurrentOutTxParam().OutboundTxTssNonce != msg.OutTxTssNonce { - return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, fmt.Sprintf("OutTxTssNonce %d does not match CCTX OutTxTssNonce %d", msg.OutTxTssNonce, cctx.GetCurrentOutTxParam().OutboundTxTssNonce)) + return nil, cosmoserrors.Wrap(sdkerrors.ErrInvalidRequest, fmt.Sprintf("OutTxTssNonce %d does not match CCTX OutTxTssNonce %d", msg.OutTxTssNonce, cctx.GetCurrentOutTxParam().OutboundTxTssNonce)) } ballotIndex := msg.Digest() @@ -117,7 +118,7 @@ func (k msgServer) VoteOnObservedOutboundTx(goCtx context.Context, msg *types.Ms log.Error().Msgf("VoteOnObservedOutboundTx: Mint mismatch: %s value received vs %s cctx amount", msg.ValueReceived, cctx.GetCurrentOutTxParam().Amount) - return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, fmt.Sprintf("ValueReceived %s does not match sent value %s", msg.ValueReceived, cctx.GetCurrentOutTxParam().Amount)) + return nil, cosmoserrors.Wrap(sdkerrors.ErrInvalidRequest, fmt.Sprintf("ValueReceived %s does not match sent value %s", msg.ValueReceived, cctx.GetCurrentOutTxParam().Amount)) } } diff --git a/x/crosschain/keeper/utils_test.go b/x/crosschain/keeper/utils_test.go index 253e20dbb6..aed660b70b 100644 --- a/x/crosschain/keeper/utils_test.go +++ b/x/crosschain/keeper/utils_test.go @@ -175,7 +175,7 @@ func setupZRC20Pool( ) require.NoError(t, err) - // approve the router to spend the zeta + // approve the router to spend the zrc20 err = k.CallZRC20Approve( ctx, types.ModuleAddressEVM, diff --git a/x/crosschain/types/message_add_to_out_tx_tracker.go b/x/crosschain/types/message_add_to_out_tx_tracker.go index 06716762a1..44d7b7f3e0 100644 --- a/x/crosschain/types/message_add_to_out_tx_tracker.go +++ b/x/crosschain/types/message_add_to_out_tx_tracker.go @@ -1,6 +1,7 @@ package types import ( + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/zeta-chain/zetacore/common" @@ -54,10 +55,10 @@ func (msg *MsgAddToOutTxTracker) GetSignBytes() []byte { func (msg *MsgAddToOutTxTracker) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } if msg.ChainId < 0 { - return sdkerrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.ChainId) + return cosmoserrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.ChainId) } return nil } diff --git a/x/crosschain/types/message_gas_price_voter.go b/x/crosschain/types/message_gas_price_voter.go index 70e4738f91..a42d9b692e 100644 --- a/x/crosschain/types/message_gas_price_voter.go +++ b/x/crosschain/types/message_gas_price_voter.go @@ -1,6 +1,7 @@ package types import ( + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/zeta-chain/zetacore/common" @@ -42,10 +43,10 @@ func (msg *MsgGasPriceVoter) GetSignBytes() []byte { func (msg *MsgGasPriceVoter) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } if msg.ChainId < 0 { - return sdkerrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.ChainId) + return cosmoserrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.ChainId) } return nil } diff --git a/x/crosschain/types/message_remove_from_out_tx_tracker.go b/x/crosschain/types/message_remove_from_out_tx_tracker.go index d1f3bca387..145d20a76c 100644 --- a/x/crosschain/types/message_remove_from_out_tx_tracker.go +++ b/x/crosschain/types/message_remove_from_out_tx_tracker.go @@ -1,6 +1,7 @@ package types import ( + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -41,10 +42,10 @@ func (msg *MsgRemoveFromOutTxTracker) GetSignBytes() []byte { func (msg *MsgRemoveFromOutTxTracker) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } if msg.ChainId < 0 { - return sdkerrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.ChainId) + return cosmoserrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.ChainId) } return nil } diff --git a/x/crosschain/types/message_tss_voter.go b/x/crosschain/types/message_tss_voter.go index 683f792064..d88147ce5a 100644 --- a/x/crosschain/types/message_tss_voter.go +++ b/x/crosschain/types/message_tss_voter.go @@ -3,6 +3,7 @@ package types import ( "fmt" + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/zeta-chain/zetacore/common" @@ -43,7 +44,7 @@ func (msg *MsgCreateTSSVoter) GetSignBytes() []byte { func (msg *MsgCreateTSSVoter) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } return nil } diff --git a/x/crosschain/types/message_vote_on_observed_inbound_tx.go b/x/crosschain/types/message_vote_on_observed_inbound_tx.go index b94b0567a4..251a8a8077 100644 --- a/x/crosschain/types/message_vote_on_observed_inbound_tx.go +++ b/x/crosschain/types/message_vote_on_observed_inbound_tx.go @@ -1,6 +1,7 @@ package types import ( + cosmoserrors "cosmossdk.io/errors" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -74,19 +75,19 @@ func (msg *MsgVoteOnObservedInboundTx) GetSignBytes() []byte { func (msg *MsgVoteOnObservedInboundTx) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s): %s", err, msg.Creator) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s): %s", err, msg.Creator) } if msg.SenderChainId < 0 { - return sdkerrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.SenderChainId) + return cosmoserrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.SenderChainId) } if msg.ReceiverChain < 0 { - return sdkerrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.ReceiverChain) + return cosmoserrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.ReceiverChain) } if len(msg.Message) > MaxMessageLength { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "message is too long: %d", len(msg.Message)) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidRequest, "message is too long: %d", len(msg.Message)) } return nil diff --git a/x/crosschain/types/message_vote_on_observed_outbound_tx.go b/x/crosschain/types/message_vote_on_observed_outbound_tx.go index bf940f6984..12d0276847 100644 --- a/x/crosschain/types/message_vote_on_observed_outbound_tx.go +++ b/x/crosschain/types/message_vote_on_observed_outbound_tx.go @@ -1,6 +1,7 @@ package types import ( + cosmoserrors "cosmossdk.io/errors" "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -64,10 +65,10 @@ func (msg *MsgVoteOnObservedOutboundTx) GetSignBytes() []byte { func (msg *MsgVoteOnObservedOutboundTx) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } if msg.OutTxChain < 0 { - return sdkerrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.OutTxChain) + return cosmoserrors.Wrapf(ErrInvalidChainID, "chain id (%d)", msg.OutTxChain) } return nil diff --git a/x/crosschain/types/message_whitelist_erc20.go b/x/crosschain/types/message_whitelist_erc20.go index a2cfd069e0..e2811287b7 100644 --- a/x/crosschain/types/message_whitelist_erc20.go +++ b/x/crosschain/types/message_whitelist_erc20.go @@ -1,6 +1,7 @@ package types import ( + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ethcommon "github.com/ethereum/go-ethereum/common" @@ -49,17 +50,17 @@ func (msg *MsgWhitelistERC20) GetSignBytes() []byte { func (msg *MsgWhitelistERC20) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } // check if the system contract address is valid if ethcommon.HexToAddress(msg.Erc20Address) == (ethcommon.Address{}) { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid ERC20 contract address (%s)", msg.Erc20Address) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid ERC20 contract address (%s)", msg.Erc20Address) } if msg.Decimals > 128 { - return sdkerrors.Wrapf(types.ErrInvalidDecimals, "invalid decimals (%d)", msg.Decimals) + return cosmoserrors.Wrapf(types.ErrInvalidDecimals, "invalid decimals (%d)", msg.Decimals) } if msg.GasLimit <= 0 { - return sdkerrors.Wrapf(types.ErrInvalidGasLimit, "invalid gas limit (%d)", msg.GasLimit) + return cosmoserrors.Wrapf(types.ErrInvalidGasLimit, "invalid gas limit (%d)", msg.GasLimit) } return nil } diff --git a/x/fungible/keeper/evm.go b/x/fungible/keeper/evm.go index 53fe3c9859..04251028f0 100644 --- a/x/fungible/keeper/evm.go +++ b/x/fungible/keeper/evm.go @@ -432,7 +432,7 @@ func (k Keeper) QueryZRC20Data( zrc4ABI, err := zrc20.ZRC20MetaData.GetAbi() if err != nil { - return types.ZRC20Data{}, sdkerrors.Wrapf( + return types.ZRC20Data{}, cosmoserrors.Wrapf( types.ErrABIUnpack, "failed to get ABI: %s", err.Error(), ) } diff --git a/x/fungible/keeper/evm_test.go b/x/fungible/keeper/evm_test.go index df8037aa06..e1af5c041c 100644 --- a/x/fungible/keeper/evm_test.go +++ b/x/fungible/keeper/evm_test.go @@ -86,6 +86,57 @@ func deploySystemContracts( return } +type SystemContractDeployConfig struct { + DeployWZeta bool + DeployUniswapV2Factory bool + DeployUniswapV2Router bool +} + +// deploySystemContractsConfigurable deploys the system contracts and returns their addresses +// while having a possibility to skip some deployments to test different scenarios +func deploySystemContractsConfigurable( + t *testing.T, + ctx sdk.Context, + k *fungiblekeeper.Keeper, + evmk types.EVMKeeper, + config *SystemContractDeployConfig, +) (wzeta, uniswapV2Factory, uniswapV2Router, connector, systemContract common.Address) { + var err error + + if config.DeployWZeta { + wzeta, err = k.DeployWZETA(ctx) + require.NoError(t, err) + require.NotEmpty(t, wzeta) + assertContractDeployment(t, evmk, ctx, wzeta) + } + + if config.DeployUniswapV2Factory { + uniswapV2Factory, err = k.DeployUniswapV2Factory(ctx) + require.NoError(t, err) + require.NotEmpty(t, uniswapV2Factory) + assertContractDeployment(t, evmk, ctx, uniswapV2Factory) + } + + if config.DeployUniswapV2Router { + uniswapV2Router, err = k.DeployUniswapV2Router02(ctx, uniswapV2Factory, wzeta) + require.NoError(t, err) + require.NotEmpty(t, uniswapV2Router) + assertContractDeployment(t, evmk, ctx, uniswapV2Router) + } + + connector, err = k.DeployConnectorZEVM(ctx, wzeta) + require.NoError(t, err) + require.NotEmpty(t, connector) + assertContractDeployment(t, evmk, ctx, connector) + + systemContract, err = k.DeploySystemContract(ctx, wzeta, uniswapV2Factory, uniswapV2Router) + require.NoError(t, err) + require.NotEmpty(t, systemContract) + assertContractDeployment(t, evmk, ctx, systemContract) + + return +} + // assertExampleBarValue asserts value Bar of the contract Example, used to test onCrossChainCall func assertExampleBarValue( t *testing.T, diff --git a/x/fungible/keeper/gas_coin_and_pool.go b/x/fungible/keeper/gas_coin_and_pool.go index 7540b90f69..7a847f3906 100644 --- a/x/fungible/keeper/gas_coin_and_pool.go +++ b/x/fungible/keeper/gas_coin_and_pool.go @@ -4,8 +4,8 @@ import ( "fmt" "math/big" + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ethcommon "github.com/ethereum/go-ethereum/common" systemcontract "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" zrc20 "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/zrc20.sol" @@ -50,7 +50,7 @@ func (k Keeper) SetupChainGasCoinAndPool( zrc20Addr, err := k.DeployZRC20Contract(ctx, name, symbol, decimals, chain.ChainId, common.CoinType_Gas, "", transferGasLimit) if err != nil { - return ethcommon.Address{}, sdkerrors.Wrapf(err, "failed to DeployZRC20Contract") + return ethcommon.Address{}, cosmoserrors.Wrapf(err, "failed to DeployZRC20Contract") } ctx.EventManager().EmitEvent( sdk.NewEvent(sdk.EventTypeMessage, @@ -76,33 +76,33 @@ func (k Keeper) SetupChainGasCoinAndPool( } systemContractAddress, err := k.GetSystemContractAddress(ctx) if err != nil || systemContractAddress == (ethcommon.Address{}) { - return ethcommon.Address{}, sdkerrors.Wrapf(types.ErrContractNotFound, "system contract address invalid: %s", systemContractAddress) + return ethcommon.Address{}, cosmoserrors.Wrapf(types.ErrContractNotFound, "system contract address invalid: %s", systemContractAddress) } systemABI, err := systemcontract.SystemContractMetaData.GetAbi() if err != nil { - return ethcommon.Address{}, sdkerrors.Wrapf(err, "failed to get system contract abi") + return ethcommon.Address{}, cosmoserrors.Wrapf(err, "failed to get system contract abi") } _, err = k.CallEVM(ctx, *systemABI, types.ModuleAddressEVM, systemContractAddress, BigIntZero, nil, true, false, "setGasZetaPool", big.NewInt(chain.ChainId), zrc20Addr) if err != nil { - return ethcommon.Address{}, sdkerrors.Wrapf(err, "failed to CallEVM method setGasZetaPool(%d, %s)", chain.ChainId, zrc20Addr.String()) + return ethcommon.Address{}, cosmoserrors.Wrapf(err, "failed to CallEVM method setGasZetaPool(%d, %s)", chain.ChainId, zrc20Addr.String()) } // setup uniswap v2 pools gas/zeta routerAddress, err := k.GetUniswapV2Router02Address(ctx) if err != nil { - return ethcommon.Address{}, sdkerrors.Wrapf(err, "failed to GetUniswapV2Router02Address") + return ethcommon.Address{}, cosmoserrors.Wrapf(err, "failed to GetUniswapV2Router02Address") } routerABI, err := uniswapv2router02.UniswapV2Router02MetaData.GetAbi() if err != nil { - return ethcommon.Address{}, sdkerrors.Wrapf(err, "failed to get uniswap router abi") + return ethcommon.Address{}, cosmoserrors.Wrapf(err, "failed to get uniswap router abi") } ZRC20ABI, err := zrc20.ZRC20MetaData.GetAbi() if err != nil { - return ethcommon.Address{}, sdkerrors.Wrapf(err, "failed to GetAbi zrc20") + return ethcommon.Address{}, cosmoserrors.Wrapf(err, "failed to GetAbi zrc20") } _, err = k.CallEVM(ctx, *ZRC20ABI, types.ModuleAddressEVM, zrc20Addr, BigIntZero, nil, true, false, "approve", routerAddress, amount) if err != nil { - return ethcommon.Address{}, sdkerrors.Wrapf(err, "failed to CallEVM method approve(%s, %d)", routerAddress.String(), amount) + return ethcommon.Address{}, cosmoserrors.Wrapf(err, "failed to CallEVM method approve(%s, %d)", routerAddress.String(), amount) } //function addLiquidityETH( @@ -116,14 +116,14 @@ func (k Keeper) SetupChainGasCoinAndPool( res, err := k.CallEVM(ctx, *routerABI, types.ModuleAddressEVM, routerAddress, amountAZeta, big.NewInt(5_000_000), true, false, "addLiquidityETH", zrc20Addr, amount, BigIntZero, BigIntZero, types.ModuleAddressEVM, amountAZeta) if err != nil { - return ethcommon.Address{}, sdkerrors.Wrapf(err, "failed to CallEVM method addLiquidityETH(%s, %s)", zrc20Addr.String(), amountAZeta.String()) + return ethcommon.Address{}, cosmoserrors.Wrapf(err, "failed to CallEVM method addLiquidityETH(%s, %s)", zrc20Addr.String(), amountAZeta.String()) } AmountToken := new(*big.Int) AmountETH := new(*big.Int) Liquidity := new(*big.Int) err = routerABI.UnpackIntoInterface(&[]interface{}{AmountToken, AmountETH, Liquidity}, "addLiquidityETH", res.Ret) if err != nil { - return ethcommon.Address{}, sdkerrors.Wrapf(err, "failed to UnpackIntoInterface addLiquidityETH") + return ethcommon.Address{}, cosmoserrors.Wrapf(err, "failed to UnpackIntoInterface addLiquidityETH") } ctx.EventManager().EmitEvent( diff --git a/x/fungible/keeper/gas_coin_and_pool_test.go b/x/fungible/keeper/gas_coin_and_pool_test.go index b6f05431ac..9935f11ee6 100644 --- a/x/fungible/keeper/gas_coin_and_pool_test.go +++ b/x/fungible/keeper/gas_coin_and_pool_test.go @@ -9,6 +9,9 @@ import ( evmkeeper "github.com/evmos/ethermint/x/evm/keeper" "github.com/stretchr/testify/require" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + uniswapv2router02 "github.com/zeta-chain/protocol-contracts/pkg/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + "github.com/zeta-chain/zetacore/cmd/zetacored/config" testkeeper "github.com/zeta-chain/zetacore/testutil/keeper" fungiblekeeper "github.com/zeta-chain/zetacore/x/fungible/keeper" "github.com/zeta-chain/zetacore/x/fungible/types" @@ -62,6 +65,73 @@ func deployZRC20( return addr } +// setupZRC20Pool setup a Uniswap pool with liquidity for the pair zeta/asset +func setupZRC20Pool( + t *testing.T, + ctx sdk.Context, + k *fungiblekeeper.Keeper, + bankKeeper bankkeeper.Keeper, + zrc20Addr common.Address, +) { + routerAddress, err := k.GetUniswapV2Router02Address(ctx) + require.NoError(t, err) + routerABI, err := uniswapv2router02.UniswapV2Router02MetaData.GetAbi() + require.NoError(t, err) + + // enough for the small numbers used in test + liquidityAmount := big.NewInt(1e17) + + // mint some zrc20 and zeta + _, err = k.DepositZRC20(ctx, zrc20Addr, types.ModuleAddressEVM, liquidityAmount) + require.NoError(t, err) + err = bankKeeper.MintCoins( + ctx, + types.ModuleName, + sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewIntFromBigInt(liquidityAmount))), + ) + require.NoError(t, err) + + // approve the router to spend the zrc20 + err = k.CallZRC20Approve( + ctx, + types.ModuleAddressEVM, + zrc20Addr, + routerAddress, + liquidityAmount, + false, + ) + require.NoError(t, err) + + // k2 := liquidityAmount.Sub(liquidityAmount, big.NewInt(1000)) + // add the liquidity + //function addLiquidityETH( + // address token, + // uint amountTokenDesired, + // uint amountTokenMin, + // uint amountETHMin, + // address to, + // uint deadline + //) + _, err = k.CallEVM( + ctx, + *routerABI, + types.ModuleAddressEVM, + routerAddress, + liquidityAmount, + big.NewInt(5_000_000), + true, + false, + "addLiquidityETH", + zrc20Addr, + liquidityAmount, + fungiblekeeper.BigIntZero, + fungiblekeeper.BigIntZero, + types.ModuleAddressEVM, + liquidityAmount, + ) + require.NoError(t, err) +} + func TestKeeper_SetupChainGasCoinAndPool(t *testing.T) { t.Run("can setup a new chain gas coin", func(t *testing.T) { k, ctx, sdkk, _ := testkeeper.FungibleKeeper(t) diff --git a/x/fungible/keeper/gas_price.go b/x/fungible/keeper/gas_price.go index 58ca702b69..636cffeec9 100644 --- a/x/fungible/keeper/gas_price.go +++ b/x/fungible/keeper/gas_price.go @@ -3,9 +3,9 @@ package keeper import ( "math/big" + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/ethereum/go-ethereum/common" + ethcommon "github.com/ethereum/go-ethereum/common" systemcontract "github.com/zeta-chain/protocol-contracts/pkg/contracts/zevm/systemcontract.sol" "github.com/zeta-chain/zetacore/x/fungible/types" ) @@ -13,74 +13,74 @@ import ( // SetGasPrice sets gas price on the system contract in zEVM; return the gasUsed and error code func (k Keeper) SetGasPrice(ctx sdk.Context, chainid *big.Int, gasPrice *big.Int) (uint64, error) { if gasPrice == nil { - return 0, sdkerrors.Wrapf(types.ErrNilGasPrice, "gas price param should be set") + return 0, cosmoserrors.Wrapf(types.ErrNilGasPrice, "gas price param should be set") } system, found := k.GetSystemContract(ctx) if !found { - return 0, sdkerrors.Wrapf(types.ErrContractNotFound, "system contract state variable not found") + return 0, cosmoserrors.Wrapf(types.ErrContractNotFound, "system contract state variable not found") } - oracle := common.HexToAddress(system.SystemContract) - if oracle == common.HexToAddress("0x0") { - return 0, sdkerrors.Wrapf(types.ErrContractNotFound, "system contract invalid address") + oracle := ethcommon.HexToAddress(system.SystemContract) + if oracle == (ethcommon.Address{}) { + return 0, cosmoserrors.Wrapf(types.ErrContractNotFound, "system contract invalid address") } abi, err := systemcontract.SystemContractMetaData.GetAbi() if err != nil { - return 0, sdkerrors.Wrapf(types.ErrABIGet, "SystemContractMetaData") + return 0, cosmoserrors.Wrapf(types.ErrABIGet, "SystemContractMetaData") } res, err := k.CallEVM(ctx, *abi, types.ModuleAddressEVM, oracle, BigIntZero, big.NewInt(50_000), true, false, "setGasPrice", chainid, gasPrice) if err != nil { - return 0, sdkerrors.Wrapf(types.ErrContractCall, err.Error()) + return 0, cosmoserrors.Wrapf(types.ErrContractCall, err.Error()) } if res.Failed() { - return res.GasUsed, sdkerrors.Wrapf(types.ErrContractCall, "setGasPrice tx failed") + return res.GasUsed, cosmoserrors.Wrapf(types.ErrContractCall, "setGasPrice tx failed") } return res.GasUsed, nil } -func (k Keeper) SetGasCoin(ctx sdk.Context, chainid *big.Int, address common.Address) error { +func (k Keeper) SetGasCoin(ctx sdk.Context, chainid *big.Int, address ethcommon.Address) error { system, found := k.GetSystemContract(ctx) if !found { - return sdkerrors.Wrapf(types.ErrContractNotFound, "system contract state variable not found") + return cosmoserrors.Wrapf(types.ErrContractNotFound, "system contract state variable not found") } - oracle := common.HexToAddress(system.SystemContract) - if oracle == common.HexToAddress("0x0") { - return sdkerrors.Wrapf(types.ErrContractNotFound, "system contract invalid address") + oracle := ethcommon.HexToAddress(system.SystemContract) + if oracle == (ethcommon.Address{}) { + return cosmoserrors.Wrapf(types.ErrContractNotFound, "system contract invalid address") } abi, err := systemcontract.SystemContractMetaData.GetAbi() if err != nil { - return sdkerrors.Wrapf(types.ErrABIGet, "SystemContractMetaData") + return cosmoserrors.Wrapf(types.ErrABIGet, "SystemContractMetaData") } res, err := k.CallEVM(ctx, *abi, types.ModuleAddressEVM, oracle, BigIntZero, nil, true, false, "setGasCoinZRC20", chainid, address) if err != nil { - return sdkerrors.Wrapf(types.ErrContractCall, err.Error()) + return cosmoserrors.Wrapf(types.ErrContractCall, err.Error()) } if res.Failed() { - return sdkerrors.Wrapf(types.ErrContractCall, "setGasCoinZRC20 tx failed") + return cosmoserrors.Wrapf(types.ErrContractCall, "setGasCoinZRC20 tx failed") } return nil } -func (k Keeper) SetGasZetaPool(ctx sdk.Context, chainid *big.Int, pool common.Address) error { +func (k Keeper) SetGasZetaPool(ctx sdk.Context, chainid *big.Int, pool ethcommon.Address) error { system, found := k.GetSystemContract(ctx) if !found { - return sdkerrors.Wrapf(types.ErrContractNotFound, "system contract state variable not found") + return cosmoserrors.Wrapf(types.ErrContractNotFound, "system contract state variable not found") } - oracle := common.HexToAddress(system.SystemContract) - if oracle == common.HexToAddress("0x0") { - return sdkerrors.Wrapf(types.ErrContractNotFound, "system contract invalid address") + oracle := ethcommon.HexToAddress(system.SystemContract) + if oracle == (ethcommon.Address{}) { + return cosmoserrors.Wrapf(types.ErrContractNotFound, "system contract invalid address") } abi, err := systemcontract.SystemContractMetaData.GetAbi() if err != nil { - return sdkerrors.Wrapf(types.ErrABIGet, "SystemContractMetaData") + return cosmoserrors.Wrapf(types.ErrABIGet, "SystemContractMetaData") } res, err := k.CallEVM(ctx, *abi, types.ModuleAddressEVM, oracle, BigIntZero, nil, true, false, "setGasZetaPool", chainid, pool) if err != nil { - return sdkerrors.Wrapf(types.ErrContractCall, err.Error()) + return cosmoserrors.Wrapf(types.ErrContractCall, err.Error()) } if res.Failed() { - return sdkerrors.Wrapf(types.ErrContractCall, "setGasZetaPool tx failed") + return cosmoserrors.Wrapf(types.ErrContractCall, "setGasZetaPool tx failed") } return nil diff --git a/x/fungible/keeper/msg_server_deploy_fungible_coin_zrc20.go b/x/fungible/keeper/msg_server_deploy_fungible_coin_zrc20.go index 1bd16ce1a4..a490f060c6 100644 --- a/x/fungible/keeper/msg_server_deploy_fungible_coin_zrc20.go +++ b/x/fungible/keeper/msg_server_deploy_fungible_coin_zrc20.go @@ -4,6 +4,7 @@ import ( "context" "math/big" + cosmoserrors "cosmossdk.io/errors" "github.com/ethereum/go-ethereum/common" sdk "github.com/cosmos/cosmos-sdk/types" @@ -42,14 +43,14 @@ func (k msgServer) DeployFungibleCoinZRC20(goCtx context.Context, msg *types.Msg } if msg.Creator != k.observerKeeper.GetParams(ctx).GetAdminPolicyAccount(zetaObserverTypes.Policy_Type_group2) { - return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "Deploy can only be executed by the correct policy account") + return nil, cosmoserrors.Wrap(sdkerrors.ErrUnauthorized, "Deploy can only be executed by the correct policy account") } if msg.CoinType == zetacommon.CoinType_Gas { // #nosec G701 always in range address, err = k.SetupChainGasCoinAndPool(ctx, msg.ForeignChainId, msg.Name, msg.Symbol, uint8(msg.Decimals), big.NewInt(msg.GasLimit)) if err != nil { - return nil, sdkerrors.Wrapf(err, "failed to setupChainGasCoinAndPool") + return nil, cosmoserrors.Wrapf(err, "failed to setupChainGasCoinAndPool") } } else { // #nosec G701 always in range @@ -74,7 +75,7 @@ func (k msgServer) DeployFungibleCoinZRC20(goCtx context.Context, msg *types.Msg }, ) if err != nil { - return nil, sdkerrors.Wrapf(err, "failed to emit event") + return nil, cosmoserrors.Wrapf(err, "failed to emit event") } return &types.MsgDeployFungibleCoinZRC20Response{ diff --git a/x/fungible/keeper/msg_server_remove_foreign_coin.go b/x/fungible/keeper/msg_server_remove_foreign_coin.go index ccab6eb9e2..a66f02a9c1 100644 --- a/x/fungible/keeper/msg_server_remove_foreign_coin.go +++ b/x/fungible/keeper/msg_server_remove_foreign_coin.go @@ -3,6 +3,7 @@ package keeper import ( "context" + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/zeta-chain/zetacore/x/fungible/types" @@ -16,12 +17,12 @@ import ( func (k msgServer) RemoveForeignCoin(goCtx context.Context, msg *types.MsgRemoveForeignCoin) (*types.MsgRemoveForeignCoinResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) if msg.Creator != k.observerKeeper.GetParams(ctx).GetAdminPolicyAccount(zetaObserverTypes.Policy_Type_group2) { - return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "Removal can only be executed by the correct policy account") + return nil, cosmoserrors.Wrap(sdkerrors.ErrUnauthorized, "Removal can only be executed by the correct policy account") } index := msg.Name _, found := k.GetForeignCoins(ctx, index) if !found { - return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "foreign coin not found") + return nil, cosmoserrors.Wrapf(sdkerrors.ErrInvalidRequest, "foreign coin not found") } k.RemoveForeignCoins(ctx, index) return &types.MsgRemoveForeignCoinResponse{}, nil diff --git a/x/fungible/keeper/msg_server_update_system_contract.go b/x/fungible/keeper/msg_server_update_system_contract.go index a61ff78e97..1a5ce6dbc9 100644 --- a/x/fungible/keeper/msg_server_update_system_contract.go +++ b/x/fungible/keeper/msg_server_update_system_contract.go @@ -4,6 +4,7 @@ import ( "context" "math/big" + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ethcommon "github.com/ethereum/go-ethereum/common" @@ -18,21 +19,21 @@ import ( func (k msgServer) UpdateSystemContract(goCtx context.Context, msg *types.MsgUpdateSystemContract) (*types.MsgUpdateSystemContractResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) if msg.Creator != k.observerKeeper.GetParams(ctx).GetAdminPolicyAccount(zetaObserverTypes.Policy_Type_group2) { - return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "Deploy can only be executed by the correct policy account") + return nil, cosmoserrors.Wrap(sdkerrors.ErrUnauthorized, "Deploy can only be executed by the correct policy account") } newSystemContractAddr := ethcommon.HexToAddress(msg.NewSystemContractAddress) if newSystemContractAddr == (ethcommon.Address{}) { - return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid system contract address (%s)", msg.NewSystemContractAddress) + return nil, cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid system contract address (%s)", msg.NewSystemContractAddress) } // update contracts zrc20ABI, err := zrc20.ZRC20MetaData.GetAbi() if err != nil { - return nil, sdkerrors.Wrapf(types.ErrABIGet, "failed to get zrc20 abi") + return nil, cosmoserrors.Wrapf(types.ErrABIGet, "failed to get zrc20 abi") } sysABI, err := systemcontract.SystemContractMetaData.GetAbi() if err != nil { - return nil, sdkerrors.Wrapf(types.ErrABIGet, "failed to get system contract abi") + return nil, cosmoserrors.Wrapf(types.ErrABIGet, "failed to get system contract abi") } foreignCoins := k.GetAllForeignCoins(ctx) tmpCtx, commit := ctx.CacheContext() @@ -44,16 +45,16 @@ func (k msgServer) UpdateSystemContract(goCtx context.Context, msg *types.MsgUpd } _, err = k.CallEVM(tmpCtx, *zrc20ABI, types.ModuleAddressEVM, zrc20Addr, BigIntZero, nil, true, false, "updateSystemContractAddress", newSystemContractAddr) if err != nil { - return nil, sdkerrors.Wrapf(types.ErrContractCall, "failed to call zrc20 contract method updateSystemContractAddress (%s)", err.Error()) + return nil, cosmoserrors.Wrapf(types.ErrContractCall, "failed to call zrc20 contract method updateSystemContractAddress (%s)", err.Error()) } if fcoin.CoinType == common.CoinType_Gas { _, err = k.CallEVM(tmpCtx, *sysABI, types.ModuleAddressEVM, newSystemContractAddr, BigIntZero, nil, true, false, "setGasCoinZRC20", big.NewInt(fcoin.ForeignChainId), zrc20Addr) if err != nil { - return nil, sdkerrors.Wrapf(types.ErrContractCall, "failed to call system contract method setGasCoinZRC20 (%s)", err.Error()) + return nil, cosmoserrors.Wrapf(types.ErrContractCall, "failed to call system contract method setGasCoinZRC20 (%s)", err.Error()) } _, err = k.CallEVM(tmpCtx, *sysABI, types.ModuleAddressEVM, newSystemContractAddr, BigIntZero, nil, true, false, "setGasZetaPool", big.NewInt(fcoin.ForeignChainId), zrc20Addr) if err != nil { - return nil, sdkerrors.Wrapf(types.ErrContractCall, "failed to call system contract method setGasZetaPool (%s)", err.Error()) + return nil, cosmoserrors.Wrapf(types.ErrContractCall, "failed to call system contract method setGasZetaPool (%s)", err.Error()) } } } @@ -75,7 +76,7 @@ func (k msgServer) UpdateSystemContract(goCtx context.Context, msg *types.MsgUpd ) if err != nil { k.Logger(ctx).Error("failed to emit event", "error", err.Error()) - return nil, sdkerrors.Wrapf(types.ErrEmitEvent, "failed to emit event (%s)", err.Error()) + return nil, cosmoserrors.Wrapf(types.ErrEmitEvent, "failed to emit event (%s)", err.Error()) } commit() return &types.MsgUpdateSystemContractResponse{}, nil diff --git a/x/fungible/keeper/system_contract.go b/x/fungible/keeper/system_contract.go index be531f1597..af36727f54 100644 --- a/x/fungible/keeper/system_contract.go +++ b/x/fungible/keeper/system_contract.go @@ -78,7 +78,6 @@ func (k *Keeper) GetWZetaContractAddress(ctx sdk.Context) (ethcommon.Address, er ) if err != nil { return ethcommon.Address{}, cosmoserrors.Wrapf(types.ErrContractCall, "failed to call wZetaContractAddress (%s)", err.Error()) - } type AddressResponse struct { Value ethcommon.Address @@ -87,6 +86,10 @@ func (k *Keeper) GetWZetaContractAddress(ctx sdk.Context) (ethcommon.Address, er if err := sysABI.UnpackIntoInterface(&wzetaResponse, "wZetaContractAddress", res.Ret); err != nil { return ethcommon.Address{}, cosmoserrors.Wrapf(types.ErrABIUnpack, "failed to unpack wZetaContractAddress: %s", err.Error()) } + + if wzetaResponse.Value == (ethcommon.Address{}) { + return ethcommon.Address{}, cosmoserrors.Wrapf(types.ErrContractNotFound, "wzeta contract invalid address") + } return wzetaResponse.Value, nil } @@ -119,11 +122,15 @@ func (k *Keeper) GetUniswapV2FactoryAddress(ctx sdk.Context) (ethcommon.Address, type AddressResponse struct { Value ethcommon.Address } - var wzetaResponse AddressResponse - if err := sysABI.UnpackIntoInterface(&wzetaResponse, "uniswapv2FactoryAddress", res.Ret); err != nil { + var uniswapFactoryResponse AddressResponse + if err := sysABI.UnpackIntoInterface(&uniswapFactoryResponse, "uniswapv2FactoryAddress", res.Ret); err != nil { return ethcommon.Address{}, cosmoserrors.Wrapf(types.ErrABIUnpack, "failed to unpack uniswapv2FactoryAddress: %s", err.Error()) } - return wzetaResponse.Value, nil + + if uniswapFactoryResponse.Value == (ethcommon.Address{}) { + return ethcommon.Address{}, cosmoserrors.Wrapf(types.ErrContractNotFound, "uniswap factory contract invalid address") + } + return uniswapFactoryResponse.Value, nil } // GetUniswapV2Router02Address returns the uniswapv2 router02 address on ZetaChain @@ -159,6 +166,10 @@ func (k *Keeper) GetUniswapV2Router02Address(ctx sdk.Context) (ethcommon.Address if err := sysABI.UnpackIntoInterface(&routerResponse, "uniswapv2Router02Address", res.Ret); err != nil { return ethcommon.Address{}, cosmoserrors.Wrapf(types.ErrABIUnpack, "failed to unpack uniswapv2Router02Address: %s", err.Error()) } + + if routerResponse.Value == (ethcommon.Address{}) { + return ethcommon.Address{}, cosmoserrors.Wrapf(types.ErrContractNotFound, "uniswap router contract invalid address") + } return routerResponse.Value, nil } @@ -265,6 +276,9 @@ func (k *Keeper) QuerySystemContractGasCoinZRC20(ctx sdk.Context, chainid *big.I if err := sysABI.UnpackIntoInterface(&zrc20Res, "gasCoinZRC20ByChainId", res.Ret); err != nil { return ethcommon.Address{}, cosmoserrors.Wrapf(types.ErrABIUnpack, "failed to unpack gasCoinZRC20ByChainId: %s", err.Error()) } + if zrc20Res.Value == (ethcommon.Address{}) { + return ethcommon.Address{}, cosmoserrors.Wrapf(types.ErrContractNotFound, "gas coin contract invalid address") + } return zrc20Res.Value, nil } @@ -316,7 +330,7 @@ func (k *Keeper) CallUniswapV2RouterSwapExactTokensForTokens( big.NewInt(1e17), ) if err != nil { - return nil, cosmoserrors.Wrapf(err, "failed to CallEVM method swapExactTokensForTokens") + return nil, cosmoserrors.Wrapf(types.ErrContractCall, "failed to CallEVM method swapExactTokensForTokens (%s)", err.Error()) } amounts := new([3]*big.Int) @@ -374,7 +388,7 @@ func (k *Keeper) CallUniswapV2RouterSwapExactTokensForETH( big.NewInt(1e17), ) if err != nil { - return nil, cosmoserrors.Wrapf(err, "failed to CallEVM method swapExactTokensForETH") + return nil, cosmoserrors.Wrapf(types.ErrContractCall, "failed to CallEVM method swapExactTokensForETH (%s)", err.Error()) } amounts := new([2]*big.Int) @@ -426,7 +440,7 @@ func (k *Keeper) CallUniswapV2RouterSwapExactETHForToken( big.NewInt(1e17), ) if err != nil { - return nil, cosmoserrors.Wrapf(err, "failed to CallEVM method swapExactETHForTokens") + return nil, cosmoserrors.Wrapf(types.ErrContractCall, "failed to CallEVM method swapExactETHForTokens (%s)", err.Error()) } amounts := new([2]*big.Int) @@ -477,7 +491,7 @@ func (k *Keeper) CallUniswapV2RouterSwapEthForExactToken( big.NewInt(1e17), ) if err != nil { - return nil, cosmoserrors.Wrapf(err, "failed to CallEVM method swapETHForExactTokens") + return nil, cosmoserrors.Wrapf(types.ErrContractCall, "failed to CallEVM method swapETHForExactTokens (%s)", err.Error()) } amounts := new([2]*big.Int) @@ -519,7 +533,7 @@ func (k *Keeper) QueryUniswapV2RouterGetZetaAmountsIn(ctx sdk.Context, amountOut []ethcommon.Address{wzetaAddr, outZRC4}, ) if err != nil { - return nil, cosmoserrors.Wrapf(err, "failed to CallEVM method getAmountsIn") + return nil, cosmoserrors.Wrapf(types.ErrContractCall, "failed to CallEVM method getAmountsIn (%s)", err.Error()) } amounts := new([2]*big.Int) @@ -560,7 +574,7 @@ func (k *Keeper) QueryUniswapV2RouterGetZRC4AmountsIn(ctx sdk.Context, amountOut []ethcommon.Address{inZRC4, wzetaAddr}, ) if err != nil { - return nil, cosmoserrors.Wrapf(err, "failed to CallEVM method getAmountsIn") + return nil, cosmoserrors.Wrapf(types.ErrContractCall, "failed to CallEVM method getAmountsIn (%s)", err.Error()) } amounts := new([2]*big.Int) @@ -601,7 +615,7 @@ func (k *Keeper) QueryUniswapV2RouterGetZRC4ToZRC4AmountsIn(ctx sdk.Context, amo []ethcommon.Address{inZRC4, wzetaAddr, outZRC4}, ) if err != nil { - return nil, cosmoserrors.Wrapf(err, "failed to CallEVM method getAmountsIn") + return nil, cosmoserrors.Wrapf(types.ErrContractCall, "failed to CallEVM method getAmountsIn (%s)", err.Error()) } amounts := new([3]*big.Int) @@ -638,7 +652,7 @@ func (k *Keeper) CallZRC20Burn( amount, ) if err != nil { - return cosmoserrors.Wrapf(err, "failed to CallEVM method burn") + return cosmoserrors.Wrapf(types.ErrContractCall, "failed to CallEVM method burn (%s)", err.Error()) } return nil @@ -671,7 +685,7 @@ func (k *Keeper) CallZRC20Deposit( amount, ) if err != nil { - return cosmoserrors.Wrapf(err, "failed to CallEVM method burn") + return cosmoserrors.Wrapf(types.ErrContractCall, "failed to CallEVM method burn (%s)", err.Error()) } return nil } @@ -704,7 +718,7 @@ func (k *Keeper) CallZRC20Approve( amount, ) if err != nil { - return cosmoserrors.Wrapf(err, "failed to CallEVM method approve") + return cosmoserrors.Wrapf(types.ErrContractCall, "failed to CallEVM method approve (%s)", err.Error()) } return nil diff --git a/x/fungible/keeper/system_contract_test.go b/x/fungible/keeper/system_contract_test.go index e2340cb4c1..10f7908807 100644 --- a/x/fungible/keeper/system_contract_test.go +++ b/x/fungible/keeper/system_contract_test.go @@ -4,6 +4,7 @@ import ( "math/big" "testing" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" keepertest "github.com/zeta-chain/zetacore/testutil/keeper" @@ -12,347 +13,1036 @@ import ( ) func TestKeeper_GetSystemContract(t *testing.T) { - k, ctx, _, _ := keepertest.FungibleKeeper(t) - k.SetSystemContract(ctx, types.SystemContract{SystemContract: "test"}) - val, found := k.GetSystemContract(ctx) - require.True(t, found) - require.Equal(t, types.SystemContract{SystemContract: "test"}, val) - - // can remove contract - k.RemoveSystemContract(ctx) - _, found = k.GetSystemContract(ctx) - require.False(t, found) + t.Run("should get and remove system contract", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeper(t) + k.SetSystemContract(ctx, types.SystemContract{SystemContract: "test"}) + val, found := k.GetSystemContract(ctx) + require.True(t, found) + require.Equal(t, types.SystemContract{SystemContract: "test"}, val) + + // can remove contract + k.RemoveSystemContract(ctx) + _, found = k.GetSystemContract(ctx) + require.False(t, found) + }) } func TestKeeper_GetSystemContractAddress(t *testing.T) { - k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + t.Run("should fail to get system contract address if system contracts are not deployed", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + _, err := k.GetSystemContractAddress(ctx) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrStateVariableNotFound) + }) - _, err := k.GetSystemContractAddress(ctx) - require.Error(t, err) - require.ErrorIs(t, err, types.ErrStateVariableNotFound) + t.Run("should get system contract address if system contracts are deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - _, _, _, _, systemContract := deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - found, err := k.GetSystemContractAddress(ctx) - require.NoError(t, err) - require.Equal(t, systemContract, found) + _, _, _, _, systemContract := deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + found, err := k.GetSystemContractAddress(ctx) + require.NoError(t, err) + require.Equal(t, systemContract, found) + }) } func TestKeeper_GetWZetaContractAddress(t *testing.T) { - k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + t.Run("should fail to get wzeta contract address if system contracts are not deployed", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - _, err := k.GetWZetaContractAddress(ctx) - require.Error(t, err) - require.ErrorIs(t, err, types.ErrStateVariableNotFound) + _, err := k.GetWZetaContractAddress(ctx) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrStateVariableNotFound) + }) - wzeta, _, _, _, _ := deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - found, err := k.GetWZetaContractAddress(ctx) - require.NoError(t, err) - require.Equal(t, wzeta, found) -} + t.Run("should get wzeta contract address if system contracts are deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) -func TestKeeper_GetWZetaContractAddressFails(t *testing.T) { - k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ - UseEVMMock: true, + wzeta, _, _, _, _ := deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + found, err := k.GetWZetaContractAddress(ctx) + require.NoError(t, err) + require.Equal(t, wzeta, found) }) - mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - _, err := k.GetWZetaContractAddress(ctx) - require.Error(t, err) - require.ErrorIs(t, err, types.ErrStateVariableNotFound) + t.Run("should fail if wzeta not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: false, + DeployUniswapV2Router: true, + DeployUniswapV2Factory: true, + }) - mockEVMKeeper.MockEVMFailCallOnce() - _, err = k.GetWZetaContractAddress(ctx) - require.ErrorIs(t, err, types.ErrContractCall) -} + _, err := k.GetWZetaContractAddress(ctx) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) -func TestKeeper_GetWZetaContractAddressFailsToUnpack(t *testing.T) { - k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ - UseEVMMock: true, + t.Run("should fail if evm call fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ + UseEVMMock: true, + }) + mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + + mockEVMKeeper.MockEVMFailCallOnce() + + _, err := k.GetWZetaContractAddress(ctx) + require.ErrorIs(t, err, types.ErrContractCall) }) - mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - _, err := k.GetWZetaContractAddress(ctx) - require.Error(t, err) - require.ErrorIs(t, err, types.ErrStateVariableNotFound) + t.Run("should fail if abi unpack fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ + UseEVMMock: true, + }) + mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) - deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + mockEVMKeeper.MockEVMSuccessCallOnce() - mockEVMKeeper.MockEVMSuccessCallOnce() - _, err = k.GetWZetaContractAddress(ctx) - require.ErrorIs(t, err, types.ErrABIUnpack) + _, err := k.GetWZetaContractAddress(ctx) + require.ErrorIs(t, err, types.ErrABIUnpack) + }) } func TestKeeper_GetUniswapV2FactoryAddress(t *testing.T) { - k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + t.Run("should fail to get uniswapfactory contract address if system contracts are not deployed", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - _, err := k.GetUniswapV2FactoryAddress(ctx) - require.Error(t, err) - require.ErrorIs(t, err, types.ErrStateVariableNotFound) + _, err := k.GetUniswapV2FactoryAddress(ctx) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrStateVariableNotFound) + }) - _, factory, _, _, _ := deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - found, err := k.GetUniswapV2FactoryAddress(ctx) - require.NoError(t, err) - require.Equal(t, factory, found) -} + t.Run("should get uniswapfactory contract address if system contracts are deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) -func TestKeeper_GetUniswapV2FactoryAddressFails(t *testing.T) { - k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ - UseEVMMock: true, + _, factory, _, _, _ := deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + found, err := k.GetUniswapV2FactoryAddress(ctx) + require.NoError(t, err) + require.Equal(t, factory, found) }) - mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - _, err := k.GetUniswapV2FactoryAddress(ctx) - require.Error(t, err) - require.ErrorIs(t, err, types.ErrStateVariableNotFound) + t.Run("should fail in factory not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: true, + DeployUniswapV2Router: true, + DeployUniswapV2Factory: false, + }) - mockEVMKeeper.MockEVMFailCallOnce() - _, err = k.GetUniswapV2FactoryAddress(ctx) - require.ErrorIs(t, err, types.ErrContractCall) -} + _, err := k.GetUniswapV2FactoryAddress(ctx) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) + + t.Run("should fail if evm call fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ + UseEVMMock: true, + }) + mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + + mockEVMKeeper.MockEVMFailCallOnce() -func TestKeeper_GetUniswapV2FactoryAddressFailsToUnpack(t *testing.T) { - k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ - UseEVMMock: true, + _, err := k.GetUniswapV2FactoryAddress(ctx) + require.ErrorIs(t, err, types.ErrContractCall) }) - mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - _, err := k.GetUniswapV2FactoryAddress(ctx) - require.Error(t, err) - require.ErrorIs(t, err, types.ErrStateVariableNotFound) + t.Run("should fail if abi unpack fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ + UseEVMMock: true, + }) + mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) - mockEVMKeeper.MockEVMSuccessCallOnce() - _, err = k.GetUniswapV2FactoryAddress(ctx) - require.ErrorIs(t, err, types.ErrABIUnpack) + mockEVMKeeper.MockEVMSuccessCallOnce() + + _, err := k.GetUniswapV2FactoryAddress(ctx) + require.ErrorIs(t, err, types.ErrABIUnpack) + }) } func TestKeeper_GetUniswapV2Router02Address(t *testing.T) { - k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + t.Run("should fail to get uniswaprouter contract address if system contracts are not deployed", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + _, err := k.GetUniswapV2Router02Address(ctx) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrStateVariableNotFound) + }) + + t.Run("should get uniswaprouter contract address if system contracts are deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + _, _, router, _, _ := deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + found, err := k.GetUniswapV2Router02Address(ctx) + require.NoError(t, err) + require.Equal(t, router, found) + }) + + t.Run("should fail in router not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: true, + DeployUniswapV2Router: false, + DeployUniswapV2Factory: true, + }) + + _, err := k.GetUniswapV2Router02Address(ctx) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) + + t.Run("should fail if evm call fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ + UseEVMMock: true, + }) + mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - _, err := k.GetUniswapV2Router02Address(ctx) - require.Error(t, err) - require.ErrorIs(t, err, types.ErrStateVariableNotFound) + deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) - _, _, router, _, _ := deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - found, err := k.GetUniswapV2Router02Address(ctx) - require.NoError(t, err) - require.Equal(t, router, found) + mockEVMKeeper.MockEVMFailCallOnce() + + _, err := k.GetUniswapV2Router02Address(ctx) + require.ErrorIs(t, err, types.ErrContractCall) + }) + + t.Run("should fail if abi unpack fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ + UseEVMMock: true, + }) + mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + + mockEVMKeeper.MockEVMSuccessCallOnce() + + _, err := k.GetUniswapV2Router02Address(ctx) + require.ErrorIs(t, err, types.ErrABIUnpack) + }) } -func TestKeeper_GetUniswapV2Router02AddressFails(t *testing.T) { - k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ - UseEVMMock: true, +func TestKeeper_CallWZetaDeposit(t *testing.T) { + t.Run("should fail to deposit if system contracts are not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + // mint tokens + addr := sample.Bech32AccAddress() + ethAddr := common.BytesToAddress(addr.Bytes()) + coins := sample.Coins() + err := sdkk.BankKeeper.MintCoins(ctx, types.ModuleName, sample.Coins()) + require.NoError(t, err) + err = sdkk.BankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, coins) + require.NoError(t, err) + + // fail if no system contract + err = k.CallWZetaDeposit(ctx, ethAddr, big.NewInt(42)) + require.Error(t, err) }) - mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - _, err := k.GetUniswapV2Router02Address(ctx) - require.Error(t, err) - require.ErrorIs(t, err, types.ErrStateVariableNotFound) + t.Run("should deposit if system contracts are deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + // mint tokens + addr := sample.Bech32AccAddress() + ethAddr := common.BytesToAddress(addr.Bytes()) + coins := sample.Coins() + err := sdkk.BankKeeper.MintCoins(ctx, types.ModuleName, sample.Coins()) + require.NoError(t, err) + err = sdkk.BankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, coins) + require.NoError(t, err) - mockEVMKeeper.MockEVMFailCallOnce() - _, err = k.GetUniswapV2Router02Address(ctx) - require.ErrorIs(t, err, types.ErrContractCall) + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + + // deposit + err = k.CallWZetaDeposit(ctx, ethAddr, big.NewInt(42)) + require.NoError(t, err) + + balance, err := k.QueryWZetaBalanceOf(ctx, ethAddr) + require.NoError(t, err) + require.Equal(t, big.NewInt(42), balance) + }) +} + +func TestKeeper_QueryWZetaBalanceOf(t *testing.T) { + t.Run("should fail if system contracts are not deployed", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + // fail if no system contract + _, err := k.QueryWZetaBalanceOf(ctx, sample.EthAddress()) + require.ErrorIs(t, err, types.ErrStateVariableNotFound) + }) } -func TestKeeper_GetUniswapV2Router02AddressFailsToUnpack(t *testing.T) { - k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ - UseEVMMock: true, +func TestKeeper_QuerySystemContractGasCoinZRC20(t *testing.T) { + t.Run("should fail if system contracts are not deployed", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + chainID := getValidChainID(t) + + _, err := k.QuerySystemContractGasCoinZRC20(ctx, big.NewInt(chainID)) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrStateVariableNotFound) + }) + + t.Run("should query if system contracts are deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + chainID := getValidChainID(t) + + _, err := k.QuerySystemContractGasCoinZRC20(ctx, big.NewInt(chainID)) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrStateVariableNotFound) + + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chainID, "foobar", "foobar") + + found, err := k.QuerySystemContractGasCoinZRC20(ctx, big.NewInt(chainID)) + require.NoError(t, err) + require.Equal(t, zrc20, found) + }) + + t.Run("should fail if gas coin not setup", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + chainID := getValidChainID(t) + + _, err := k.QuerySystemContractGasCoinZRC20(ctx, big.NewInt(chainID)) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrStateVariableNotFound) + + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + + _, err = k.QuerySystemContractGasCoinZRC20(ctx, big.NewInt(chainID)) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) + + t.Run("should fail if evm call fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ + UseEVMMock: true, + }) + mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + + mockEVMKeeper.MockEVMFailCallOnce() + + _, err := k.QuerySystemContractGasCoinZRC20(ctx, big.NewInt(1)) + require.ErrorIs(t, err, types.ErrContractCall) }) - mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - _, err := k.GetUniswapV2Router02Address(ctx) - require.Error(t, err) - require.ErrorIs(t, err, types.ErrStateVariableNotFound) + t.Run("should fail if abi unpack fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ + UseEVMMock: true, + }) + mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) - mockEVMKeeper.MockEVMSuccessCallOnce() - _, err = k.GetUniswapV2Router02Address(ctx) - require.ErrorIs(t, err, types.ErrABIUnpack) + mockEVMKeeper.MockEVMSuccessCallOnce() + + _, err := k.QuerySystemContractGasCoinZRC20(ctx, big.NewInt(1)) + require.ErrorIs(t, err, types.ErrABIUnpack) + }) } -func TestKeeper_CallWZetaDeposit(t *testing.T) { - k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - - // mint tokens - addr := sample.Bech32AccAddress() - ethAddr := common.BytesToAddress(addr.Bytes()) - coins := sample.Coins() - err := sdkk.BankKeeper.MintCoins(ctx, types.ModuleName, sample.Coins()) - require.NoError(t, err) - err = sdkk.BankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, coins) - require.NoError(t, err) - - // fail if no system contract - err = k.CallWZetaDeposit(ctx, ethAddr, big.NewInt(42)) - require.Error(t, err) - - deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - - // deposit - err = k.CallWZetaDeposit(ctx, ethAddr, big.NewInt(42)) - require.NoError(t, err) - - balance, err := k.QueryWZetaBalanceOf(ctx, ethAddr) - require.NoError(t, err) - require.Equal(t, big.NewInt(42), balance) +func TestKeeper_CallUniswapV2RouterSwapExactETHForToken(t *testing.T) { + t.Run("should fail if system contracts are not deployed", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + // fail if no system contract + _, err := k.CallUniswapV2RouterSwapExactETHForToken( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + big.NewInt(1), + sample.EthAddress(), + true, + ) + require.ErrorIs(t, err, types.ErrStateVariableNotFound) + }) + + t.Run("should swap if system contracts are deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + chainID := getValidChainID(t) + + // deploy system contracts and swap exact eth for 1 token + tokenAmount := big.NewInt(1) + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chainID, "foobar", "foobar") + + amountToSwap, err := k.QueryUniswapV2RouterGetZetaAmountsIn(ctx, tokenAmount, zrc20) + require.NoError(t, err) + err = sdkk.BankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin("azeta", sdk.NewIntFromBigInt(amountToSwap)))) + require.NoError(t, err) + + amounts, err := k.CallUniswapV2RouterSwapExactETHForToken( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + amountToSwap, + zrc20, + true, + ) + require.NoError(t, err) + + require.Equal(t, 2, len(amounts)) + require.Equal(t, tokenAmount, amounts[1]) + }) + + t.Run("should fail if missing zeta balance", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + chainID := getValidChainID(t) + + // deploy system contracts and swap 1 token fails because of missing wrapped balance + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chainID, "foobar", "foobar") + + amountToSwap, err := k.QueryUniswapV2RouterGetZetaAmountsIn(ctx, big.NewInt(1), zrc20) + require.NoError(t, err) + + _, err = k.CallUniswapV2RouterSwapExactETHForToken( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + amountToSwap, + zrc20, + true, + ) + require.ErrorIs(t, err, types.ErrContractCall) + }) + + t.Run("should fail if wzeta not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: false, + DeployUniswapV2Factory: true, + DeployUniswapV2Router: true, + }) + + _, err := k.CallUniswapV2RouterSwapExactETHForToken( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + big.NewInt(1), + sample.EthAddress(), + true, + ) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) + + t.Run("should fail if router not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: true, + DeployUniswapV2Factory: true, + DeployUniswapV2Router: false, + }) + + _, err := k.CallUniswapV2RouterSwapExactETHForToken( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + big.NewInt(1), + sample.EthAddress(), + true, + ) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) } -func TestKeeper_CallWZetaDepositFails(t *testing.T) { - k, ctx, sdkk, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ - UseEVMMock: true, +func TestKeeper_CallUniswapV2RouterSwapEthForExactToken(t *testing.T) { + t.Run("should fail if system contracts are not deployed", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + // fail if no system contract + _, err := k.CallUniswapV2RouterSwapExactETHForToken( + ctx, types.ModuleAddressEVM, types.ModuleAddressEVM, big.NewInt(1), sample.EthAddress(), true) + require.ErrorIs(t, err, types.ErrStateVariableNotFound) }) - mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - // mint tokens - addr := sample.Bech32AccAddress() - ethAddr := common.BytesToAddress(addr.Bytes()) - coins := sample.Coins() - err := sdkk.BankKeeper.MintCoins(ctx, types.ModuleName, sample.Coins()) - require.NoError(t, err) - err = sdkk.BankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, coins) - require.NoError(t, err) + t.Run("should swap if system contracts are deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + chainID := getValidChainID(t) + + // deploy system contracts and swap exact 1 token + tokenAmount := big.NewInt(1) + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chainID, "foobar", "foobar") + + amountToSwap, err := k.QueryUniswapV2RouterGetZetaAmountsIn(ctx, tokenAmount, zrc20) + require.NoError(t, err) + err = sdkk.BankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(sdk.NewCoin("azeta", sdk.NewIntFromBigInt(amountToSwap)))) + require.NoError(t, err) + + amounts, err := k.CallUniswapV2RouterSwapEthForExactToken( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + amountToSwap, + tokenAmount, + zrc20, + ) + require.NoError(t, err) + + require.Equal(t, 2, len(amounts)) + require.Equal(t, big.NewInt(1), amounts[1]) + }) - // fail if no system contract - err = k.CallWZetaDeposit(ctx, ethAddr, big.NewInt(42)) - require.Error(t, err) + t.Run("should fail if missing zeta balance", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + chainID := getValidChainID(t) + + // deploy system contracts and swap 1 token fails because of missing wrapped balance + tokenAmount := big.NewInt(1) + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chainID, "foobar", "foobar") + + amountToSwap, err := k.QueryUniswapV2RouterGetZetaAmountsIn(ctx, tokenAmount, zrc20) + require.NoError(t, err) + + _, err = k.CallUniswapV2RouterSwapEthForExactToken( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + amountToSwap, + tokenAmount, + zrc20, + ) + require.ErrorIs(t, err, types.ErrContractCall) + }) - deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + t.Run("should fail if wzeta not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: false, + DeployUniswapV2Factory: true, + DeployUniswapV2Router: true, + }) + + _, err := k.CallUniswapV2RouterSwapEthForExactToken( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + big.NewInt(1), + big.NewInt(1), + sample.EthAddress(), + ) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) - // deposit - mockEVMKeeper.MockEVMFailCallOnce() - err = k.CallWZetaDeposit(ctx, ethAddr, big.NewInt(42)) - require.ErrorIs(t, err, types.ErrContractCall) + t.Run("should fail if router not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: true, + DeployUniswapV2Factory: true, + DeployUniswapV2Router: false, + }) + + _, err := k.CallUniswapV2RouterSwapEthForExactToken( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + big.NewInt(1), + big.NewInt(1), + sample.EthAddress(), + ) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) } -func TestKeeper_QueryWZetaBalanceOfFails(t *testing.T) { - k, ctx, sdkk, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ - UseEVMMock: true, +func TestKeeper_CallUniswapV2RouterSwapExactTokensForETH(t *testing.T) { + t.Run("should fail if system contracts are not deployed", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + // fail if no system contract + _, err := k.CallUniswapV2RouterSwapExactTokensForETH( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + big.NewInt(1), + sample.EthAddress(), + true, + ) + require.ErrorIs(t, err, types.ErrStateVariableNotFound) }) - mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - // mint tokens - addr := sample.Bech32AccAddress() - ethAddr := common.BytesToAddress(addr.Bytes()) - coins := sample.Coins() - err := sdkk.BankKeeper.MintCoins(ctx, types.ModuleName, sample.Coins()) - require.NoError(t, err) - err = sdkk.BankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, coins) - require.NoError(t, err) + t.Run("should swap if system contracts are deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + chainID := getValidChainID(t) + + // fail if no system contract + _, err := k.CallUniswapV2RouterSwapExactTokensForETH( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + big.NewInt(1), + sample.EthAddress(), + true, + ) + require.Error(t, err) + + // deploy system contracts and swap exact eth for 1 token + ethAmount := big.NewInt(1) + _, _, router, _, _ := deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chainID, "foobar", "foobar") + + amountToSwap, err := k.QueryUniswapV2RouterGetZRC4AmountsIn(ctx, ethAmount, zrc20) + require.NoError(t, err) + + _, err = k.DepositZRC20(ctx, zrc20, types.ModuleAddressEVM, amountToSwap) + require.NoError(t, err) + k.CallZRC20Approve( + ctx, + types.ModuleAddressEVM, + zrc20, + router, + amountToSwap, + false, + ) + + amounts, err := k.CallUniswapV2RouterSwapExactTokensForETH( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + amountToSwap, + zrc20, + true, + ) + require.NoError(t, err) + + require.Equal(t, 2, len(amounts)) + require.Equal(t, ethAmount, amounts[0]) + }) - // fail if no system contract - err = k.CallWZetaDeposit(ctx, ethAddr, big.NewInt(42)) - require.Error(t, err) + t.Run("should fail if missing tokens balance", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + chainID := getValidChainID(t) + + // fail if no system contract + _, err := k.CallUniswapV2RouterSwapExactTokensForETH( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + big.NewInt(1), + sample.EthAddress(), + true, + ) + require.Error(t, err) + + // deploy system contracts and swap fails because of missing balance + ethAmount := big.NewInt(1) + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chainID, "foobar", "foobar") + + amountToSwap, err := k.QueryUniswapV2RouterGetZRC4AmountsIn(ctx, ethAmount, zrc20) + require.NoError(t, err) + + _, err = k.CallUniswapV2RouterSwapExactTokensForETH( + ctx, types.ModuleAddressEVM, types.ModuleAddressEVM, amountToSwap, zrc20, true) + require.ErrorIs(t, err, types.ErrContractCall) + }) - deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + t.Run("should fail if wzeta not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: false, + DeployUniswapV2Factory: true, + DeployUniswapV2Router: true, + }) + _, err := k.CallUniswapV2RouterSwapExactTokensForETH( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + big.NewInt(1), + sample.EthAddress(), + true, + ) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) - // query - mockEVMKeeper.MockEVMFailCallOnce() - _, err = k.QueryWZetaBalanceOf(ctx, ethAddr) - require.ErrorIs(t, err, types.ErrContractCall) + t.Run("should fail if router not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: true, + DeployUniswapV2Factory: true, + DeployUniswapV2Router: false, + }) + _, err := k.CallUniswapV2RouterSwapExactTokensForETH( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + big.NewInt(1), + sample.EthAddress(), + true, + ) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) } -func TestKeeper_QueryWZetaBalanceOfFailsToUnpack(t *testing.T) { - k, ctx, sdkk, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ - UseEVMMock: true, +func TestKeeper_CallUniswapV2RouterSwapExactTokensForTokens(t *testing.T) { + t.Run("should fail if system contracts are not deployed", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + // fail if no system contract + _, err := k.CallUniswapV2RouterSwapExactTokensForTokens( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + big.NewInt(1), + sample.EthAddress(), + sample.EthAddress(), + true, + ) + require.ErrorIs(t, err, types.ErrStateVariableNotFound) }) - mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - // mint tokens - addr := sample.Bech32AccAddress() - ethAddr := common.BytesToAddress(addr.Bytes()) - coins := sample.Coins() - err := sdkk.BankKeeper.MintCoins(ctx, types.ModuleName, sample.Coins()) - require.NoError(t, err) - err = sdkk.BankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, addr, coins) - require.NoError(t, err) + t.Run("should swap if system contracts are deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + chainID := getValidChainID(t) + + // fail if no system contract + _, err := k.CallUniswapV2RouterSwapExactTokensForTokens( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + big.NewInt(1), + sample.EthAddress(), + sample.EthAddress(), + true, + ) + require.Error(t, err) + + // deploy system contracts and swap exact token for 1 token + tokenAmount := big.NewInt(1) + _, _, router, _, _ := deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + + inzrc20 := deployZRC20(t, ctx, k, sdkk.EvmKeeper, chainID, "foo", sample.EthAddress().String(), "foo") + outzrc20 := deployZRC20(t, ctx, k, sdkk.EvmKeeper, chainID, "bar", sample.EthAddress().String(), "bar") + setupZRC20Pool(t, ctx, k, sdkk.BankKeeper, inzrc20) + setupZRC20Pool(t, ctx, k, sdkk.BankKeeper, outzrc20) + + amountToSwap, err := k.QueryUniswapV2RouterGetZRC4ToZRC4AmountsIn(ctx, tokenAmount, inzrc20, outzrc20) + require.NoError(t, err) + + _, err = k.DepositZRC20(ctx, inzrc20, types.ModuleAddressEVM, amountToSwap) + require.NoError(t, err) + k.CallZRC20Approve( + ctx, + types.ModuleAddressEVM, + inzrc20, + router, + amountToSwap, + false, + ) + + amounts, err := k.CallUniswapV2RouterSwapExactTokensForTokens( + ctx, types.ModuleAddressEVM, types.ModuleAddressEVM, amountToSwap, inzrc20, outzrc20, true) + require.NoError(t, err) + require.Equal(t, 3, len(amounts)) + require.Equal(t, amounts[2], tokenAmount) + }) - // fail if no system contract - err = k.CallWZetaDeposit(ctx, ethAddr, big.NewInt(42)) - require.Error(t, err) + t.Run("should fail if missing tokens balance", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + chainID := getValidChainID(t) + + // deploy system contracts and swap fails because of missing balance + tokenAmount := big.NewInt(1) + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + + inzrc20 := deployZRC20(t, ctx, k, sdkk.EvmKeeper, chainID, "foo", sample.EthAddress().String(), "foo") + outzrc20 := deployZRC20(t, ctx, k, sdkk.EvmKeeper, chainID, "bar", sample.EthAddress().String(), "bar") + setupZRC20Pool(t, ctx, k, sdkk.BankKeeper, inzrc20) + setupZRC20Pool(t, ctx, k, sdkk.BankKeeper, outzrc20) + + amountToSwap, err := k.QueryUniswapV2RouterGetZRC4ToZRC4AmountsIn(ctx, tokenAmount, inzrc20, outzrc20) + require.NoError(t, err) + + _, err = k.CallUniswapV2RouterSwapExactTokensForTokens( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + amountToSwap, + inzrc20, + outzrc20, + true, + ) + require.ErrorIs(t, err, types.ErrContractCall) + }) - deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + t.Run("should fail if wzeta not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + // fail if no system contract + _, err := k.CallUniswapV2RouterSwapExactTokensForTokens( + ctx, types.ModuleAddressEVM, types.ModuleAddressEVM, big.NewInt(1), sample.EthAddress(), sample.EthAddress(), true) + require.Error(t, err) + + // deploy system contracts except router + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployUniswapV2Router: true, + DeployWZeta: false, + DeployUniswapV2Factory: true, + }) + + _, err = k.CallUniswapV2RouterSwapExactTokensForTokens( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + big.NewInt(1), + sample.EthAddress(), + sample.EthAddress(), + true, + ) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) + + t.Run("should fail if router not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + // fail if no system contract + _, err := k.CallUniswapV2RouterSwapExactTokensForTokens( + ctx, types.ModuleAddressEVM, types.ModuleAddressEVM, big.NewInt(1), sample.EthAddress(), sample.EthAddress(), true) + require.Error(t, err) + + // deploy system contracts except router + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployUniswapV2Router: false, + DeployWZeta: true, + DeployUniswapV2Factory: true, + }) + + _, err = k.CallUniswapV2RouterSwapExactTokensForTokens( + ctx, + types.ModuleAddressEVM, + types.ModuleAddressEVM, + big.NewInt(1), + sample.EthAddress(), + sample.EthAddress(), + true, + ) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) - // query - mockEVMKeeper.MockEVMSuccessCallOnce() - _, err = k.QueryWZetaBalanceOf(ctx, ethAddr) - require.ErrorIs(t, err, types.ErrABIUnpack) } -func TestKeeper_QuerySystemContractGasCoinZRC20(t *testing.T) { - k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - chainID := getValidChainID(t) +func TestKeeper_QueryUniswapV2RouterGetZRC4AmountsIn(t *testing.T) { + t.Run("should fail if no amounts out", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + + _, err := k.QueryUniswapV2RouterGetZRC4AmountsIn(ctx, big.NewInt(1), sample.EthAddress()) + require.ErrorIs(t, err, types.ErrContractCall) + }) + + t.Run("should fail if wzeta not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - _, err := k.QuerySystemContractGasCoinZRC20(ctx, big.NewInt(chainID)) - require.Error(t, err) - require.ErrorIs(t, err, types.ErrStateVariableNotFound) + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: false, + DeployUniswapV2Factory: true, + DeployUniswapV2Router: true, + }) - deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chainID, "foobar", "foobar") + _, err := k.QueryUniswapV2RouterGetZRC4AmountsIn(ctx, big.NewInt(1), sample.EthAddress()) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) + + t.Run("should fail if router not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - found, err := k.QuerySystemContractGasCoinZRC20(ctx, big.NewInt(chainID)) - require.NoError(t, err) - require.Equal(t, zrc20, found) + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: true, + DeployUniswapV2Factory: true, + DeployUniswapV2Router: false, + }) + + _, err := k.QueryUniswapV2RouterGetZRC4AmountsIn(ctx, big.NewInt(1), sample.EthAddress()) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) } -func TestKeeper_QuerySystemContractGasCoinZRC20Fails(t *testing.T) { - k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ - UseEVMMock: true, +func TestKeeper_QueryUniswapV2RouterGetZetaAmountsIn(t *testing.T) { + t.Run("should fail if no amounts out", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + + _, err := k.QueryUniswapV2RouterGetZetaAmountsIn(ctx, big.NewInt(1), sample.EthAddress()) + require.ErrorIs(t, err, types.ErrContractCall) }) - mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - chainID := getValidChainID(t) + t.Run("should fail if wzeta not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: false, + DeployUniswapV2Factory: true, + DeployUniswapV2Router: true, + }) + + _, err := k.QueryUniswapV2RouterGetZetaAmountsIn(ctx, big.NewInt(1), sample.EthAddress()) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) - _, err := k.QuerySystemContractGasCoinZRC20(ctx, big.NewInt(chainID)) - require.Error(t, err) - require.ErrorIs(t, err, types.ErrStateVariableNotFound) + t.Run("should fail if router not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: true, + DeployUniswapV2Factory: true, + DeployUniswapV2Router: false, + }) - mockEVMKeeper.MockEVMFailCallOnce() - _, err = k.QuerySystemContractGasCoinZRC20(ctx, big.NewInt(chainID)) - require.ErrorIs(t, err, types.ErrContractCall) + _, err := k.QueryUniswapV2RouterGetZetaAmountsIn(ctx, big.NewInt(1), sample.EthAddress()) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) } -func TestKeeper_QuerySystemContractGasCoinZRC20FailsToUnpack(t *testing.T) { - k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ - UseEVMMock: true, +func TestKeeper_QueryUniswapV2RouterGetZRC4ToZRC4AmountsIn(t *testing.T) { + t.Run("should fail if no amounts out", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) + + _, err := k.QueryUniswapV2RouterGetZRC4ToZRC4AmountsIn(ctx, big.NewInt(1), sample.EthAddress(), sample.EthAddress()) + require.ErrorIs(t, err, types.ErrContractCall) }) - mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) - k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - chainID := getValidChainID(t) + t.Run("should fail if wzeta not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - _, err := k.QuerySystemContractGasCoinZRC20(ctx, big.NewInt(chainID)) - require.Error(t, err) - require.ErrorIs(t, err, types.ErrStateVariableNotFound) + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: false, + DeployUniswapV2Factory: true, + DeployUniswapV2Router: true, + }) - deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + _, err := k.QueryUniswapV2RouterGetZRC4ToZRC4AmountsIn(ctx, big.NewInt(1), sample.EthAddress(), sample.EthAddress()) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) + + t.Run("should fail if router not deployed", func(t *testing.T) { + k, ctx, sdkk, _ := keepertest.FungibleKeeper(t) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) - mockEVMKeeper.MockEVMSuccessCallOnce() - _, err = k.QuerySystemContractGasCoinZRC20(ctx, big.NewInt(chainID)) - require.ErrorIs(t, err, types.ErrABIUnpack) + deploySystemContractsConfigurable(t, ctx, k, sdkk.EvmKeeper, &SystemContractDeployConfig{ + DeployWZeta: true, + DeployUniswapV2Factory: true, + DeployUniswapV2Router: false, + }) + + _, err := k.QueryUniswapV2RouterGetZRC4ToZRC4AmountsIn(ctx, big.NewInt(1), sample.EthAddress(), sample.EthAddress()) + require.ErrorIs(t, err, types.ErrContractNotFound) + }) +} + +func TestKeeper_CallZRC20Burn(t *testing.T) { + t.Run("should fail if evm call fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ + UseEVMMock: true, + }) + mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + + mockEVMKeeper.MockEVMFailCallOnce() + err := k.CallZRC20Burn(ctx, types.ModuleAddressEVM, sample.EthAddress(), big.NewInt(1), false) + require.ErrorIs(t, err, types.ErrContractCall) + }) +} + +func TestKeeper_CallZRC20Approve(t *testing.T) { + t.Run("should fail if evm call fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ + UseEVMMock: true, + }) + mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + + mockEVMKeeper.MockEVMFailCallOnce() + err := k.CallZRC20Approve(ctx, types.ModuleAddressEVM, sample.EthAddress(), types.ModuleAddressEVM, big.NewInt(1), false) + require.ErrorIs(t, err, types.ErrContractCall) + }) +} + +func TestKeeper_CallZRC20Deposit(t *testing.T) { + t.Run("should fail if evm call fails", func(t *testing.T) { + k, ctx, _, _ := keepertest.FungibleKeeperWithMocks(t, keepertest.FungibleMockOptions{ + UseEVMMock: true, + }) + mockEVMKeeper := keepertest.GetFungibleEVMMock(t, k) + k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) + + deploySystemContractsWithMockEvmKeeper(t, ctx, k, mockEVMKeeper) + + mockEVMKeeper.MockEVMFailCallOnce() + err := k.CallZRC20Deposit(ctx, types.ModuleAddressEVM, sample.EthAddress(), types.ModuleAddressEVM, big.NewInt(1)) + require.ErrorIs(t, err, types.ErrContractCall) + }) } diff --git a/x/fungible/types/message_deploy_fungible_coin_zrc20.go b/x/fungible/types/message_deploy_fungible_coin_zrc20.go index 6b23365001..1f695e63cb 100644 --- a/x/fungible/types/message_deploy_fungible_coin_zrc20.go +++ b/x/fungible/types/message_deploy_fungible_coin_zrc20.go @@ -1,6 +1,7 @@ package types import ( + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/zeta-chain/zetacore/common" @@ -47,14 +48,14 @@ func (msg *MsgDeployFungibleCoinZRC20) GetSignBytes() []byte { func (msg *MsgDeployFungibleCoinZRC20) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } if msg.GasLimit < 0 { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidGasLimit, "invalid gas limit") + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidGasLimit, "invalid gas limit") } if msg.Decimals > 77 { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "decimals must be less than 78") + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidRequest, "decimals must be less than 78") } return nil } diff --git a/x/fungible/types/message_deploy_fungible_coin_zrc20_test.go b/x/fungible/types/message_deploy_fungible_coin_zrc20_test.go index e107af7781..12e6e0750e 100644 --- a/x/fungible/types/message_deploy_fungible_coin_zrc20_test.go +++ b/x/fungible/types/message_deploy_fungible_coin_zrc20_test.go @@ -3,6 +3,7 @@ package types_test import ( "testing" + cosmoserrors "cosmossdk.io/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/stretchr/testify/require" "github.com/zeta-chain/zetacore/testutil/sample" @@ -36,7 +37,7 @@ func TestMsgDeployFungibleCoinZRC4_ValidateBasic(t *testing.T) { Creator: sample.AccAddress(), Decimals: 78, }, - err: sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "decimals must be less than 78"), + err: cosmoserrors.Wrapf(sdkerrors.ErrInvalidRequest, "decimals must be less than 78"), }, { name: "valid message", diff --git a/x/fungible/types/message_deploy_system_contracts.go b/x/fungible/types/message_deploy_system_contracts.go index 579df47a25..4c4515e856 100644 --- a/x/fungible/types/message_deploy_system_contracts.go +++ b/x/fungible/types/message_deploy_system_contracts.go @@ -1,6 +1,7 @@ package types import ( + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -39,7 +40,7 @@ func (msg *MsgDeploySystemContracts) GetSignBytes() []byte { func (msg *MsgDeploySystemContracts) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } return nil } diff --git a/x/fungible/types/message_remove_foreign_coin.go b/x/fungible/types/message_remove_foreign_coin.go index 999b3364b1..0c7282dc4a 100644 --- a/x/fungible/types/message_remove_foreign_coin.go +++ b/x/fungible/types/message_remove_foreign_coin.go @@ -1,6 +1,7 @@ package types import ( + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -40,7 +41,7 @@ func (msg *MsgRemoveForeignCoin) GetSignBytes() []byte { func (msg *MsgRemoveForeignCoin) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } return nil } diff --git a/x/fungible/types/message_update_system_contract.go b/x/fungible/types/message_update_system_contract.go index b6879f2ff6..2a347baada 100644 --- a/x/fungible/types/message_update_system_contract.go +++ b/x/fungible/types/message_update_system_contract.go @@ -1,6 +1,7 @@ package types import ( + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ethcommon "github.com/ethereum/go-ethereum/common" @@ -41,11 +42,11 @@ func (msg *MsgUpdateSystemContract) GetSignBytes() []byte { func (msg *MsgUpdateSystemContract) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } // check if the system contract address is valid if ethcommon.HexToAddress(msg.NewSystemContractAddress) == (ethcommon.Address{}) { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid system contract address (%s)", msg.NewSystemContractAddress) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid system contract address (%s)", msg.NewSystemContractAddress) } return nil diff --git a/x/observer/keeper/msg_server_add_blame_vote.go b/x/observer/keeper/msg_server_add_blame_vote.go index 40f2fe54b0..6ff74146b6 100644 --- a/x/observer/keeper/msg_server_add_blame_vote.go +++ b/x/observer/keeper/msg_server_add_blame_vote.go @@ -4,8 +4,8 @@ import ( "context" "fmt" + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" crosschainTypes "github.com/zeta-chain/zetacore/x/crosschain/types" "github.com/zeta-chain/zetacore/x/observer/types" ) @@ -16,7 +16,7 @@ func (k msgServer) AddBlameVote(goCtx context.Context, vote *types.MsgAddBlameVo // GetChainFromChainID makes sure we are getting only supported chains , if a chain support has been turned on using gov proposal, this function returns nil observationChain := k.GetSupportedChainFromChainID(ctx, vote.ChainId) if observationChain == nil { - return nil, sdkerrors.Wrap(crosschainTypes.ErrUnsupportedChain, fmt.Sprintf("ChainID %d, Blame vote", vote.ChainId)) + return nil, cosmoserrors.Wrap(crosschainTypes.ErrUnsupportedChain, fmt.Sprintf("ChainID %d, Blame vote", vote.ChainId)) } // IsAuthorized does various checks against the list of observer mappers if ok := k.IsAuthorized(ctx, vote.Creator); !ok { diff --git a/x/observer/types/message_add_blame_vote.go b/x/observer/types/message_add_blame_vote.go index 1d9422f757..d10411f026 100644 --- a/x/observer/types/message_add_blame_vote.go +++ b/x/observer/types/message_add_blame_vote.go @@ -1,6 +1,7 @@ package types import ( + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/ethereum/go-ethereum/crypto" @@ -30,10 +31,10 @@ func (m *MsgAddBlameVote) Type() string { func (m *MsgAddBlameVote) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(m.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } if common.GetChainFromChainID(m.ChainId) == nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidChainID, "chain id (%d)", m.ChainId) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidChainID, "chain id (%d)", m.ChainId) } return nil } diff --git a/x/observer/types/message_add_observer.go b/x/observer/types/message_add_observer.go index 3c27255590..991bb0afa1 100644 --- a/x/observer/types/message_add_observer.go +++ b/x/observer/types/message_add_observer.go @@ -1,7 +1,7 @@ package types import ( - errorsmod "cosmossdk.io/errors" + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/zeta-chain/zetacore/common" @@ -44,19 +44,19 @@ func (msg *MsgAddObserver) GetSignBytes() []byte { func (msg *MsgAddObserver) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } _, err = sdk.AccAddressFromBech32(msg.ObserverAddress) if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid observer address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid observer address (%s)", err) } _, err = common.NewPubKey(msg.ZetaclientGranteePubkey) if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidPubKey, "invalid zetaclient grantee pubkey (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidPubKey, "invalid zetaclient grantee pubkey (%s)", err) } _, err = common.GetAddressFromPubkeyString(msg.ZetaclientGranteePubkey) if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidPubKey, "invalid zetaclient grantee pubkey (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidPubKey, "invalid zetaclient grantee pubkey (%s)", err) } return nil } diff --git a/x/observer/types/message_update_keygen.go b/x/observer/types/message_update_keygen.go index 561a9319ff..e13c83dd7a 100644 --- a/x/observer/types/message_update_keygen.go +++ b/x/observer/types/message_update_keygen.go @@ -1,6 +1,7 @@ package types import ( + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -40,7 +41,7 @@ func (msg *MsgUpdateKeygen) GetSignBytes() []byte { func (msg *MsgUpdateKeygen) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } return nil } diff --git a/x/observer/types/message_update_observer.go b/x/observer/types/message_update_observer.go index e5b541859b..c6db5a1206 100644 --- a/x/observer/types/message_update_observer.go +++ b/x/observer/types/message_update_observer.go @@ -1,7 +1,7 @@ package types import ( - errorsmod "cosmossdk.io/errors" + cosmoserrors "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -43,21 +43,21 @@ func (msg *MsgUpdateObserver) GetSignBytes() []byte { func (msg *MsgUpdateObserver) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { - return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } _, err = sdk.AccAddressFromBech32(msg.OldObserverAddress) if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid old observer address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid old observer address (%s)", err) } _, err = sdk.AccAddressFromBech32(msg.NewObserverAddress) if err != nil { - return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid new observer address (%s)", err) + return cosmoserrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid new observer address (%s)", err) } if msg.UpdateReason != ObserverUpdateReason_Tombstoned && msg.UpdateReason != ObserverUpdateReason_AdminUpdate { - return errorsmod.Wrapf(ErrUpdateObserver, "invalid update reason (%s)", msg.UpdateReason) + return cosmoserrors.Wrapf(ErrUpdateObserver, "invalid update reason (%s)", msg.UpdateReason) } if msg.UpdateReason == ObserverUpdateReason_Tombstoned && msg.OldObserverAddress != msg.Creator { - return errorsmod.Wrapf(ErrUpdateObserver, "invalid old observer address (%s)", msg.OldObserverAddress) + return cosmoserrors.Wrapf(ErrUpdateObserver, "invalid old observer address (%s)", msg.OldObserverAddress) } return nil } From c68c21dc1fa53347b935da18f96b70aa04655f24 Mon Sep 17 00:00:00 2001 From: Tanmay Date: Thu, 22 Feb 2024 13:07:53 -0500 Subject: [PATCH 2/3] test: emissions unit test (#1767) --- changelog.md | 7 + testutil/keeper/emissions.go | 102 +++- testutil/keeper/keeper.go | 24 +- testutil/keeper/mocks/crosschain/account.go | 2 +- testutil/keeper/mocks/crosschain/bank.go | 2 +- testutil/keeper/mocks/crosschain/fungible.go | 2 +- testutil/keeper/mocks/crosschain/observer.go | 2 +- testutil/keeper/mocks/crosschain/staking.go | 2 +- testutil/keeper/mocks/emissions/account.go | 49 ++ testutil/keeper/mocks/emissions/bank.go | 64 +++ testutil/keeper/mocks/emissions/observer.go | 78 +++ testutil/keeper/mocks/emissions/staking.go | 46 ++ testutil/keeper/mocks/fungible/account.go | 2 +- testutil/keeper/mocks/fungible/bank.go | 2 +- testutil/keeper/mocks/fungible/evm.go | 2 +- testutil/keeper/mocks/fungible/observer.go | 2 +- testutil/keeper/mocks/mocks.go | 21 + testutil/sample/observer.go | 29 ++ testutil/sample/sample.go | 5 + x/emissions/abci.go | 4 +- x/emissions/abci_test.go | 513 +++++++++++-------- x/emissions/genesis_test.go | 2 +- x/emissions/keeper/params_test.go | 5 +- 23 files changed, 686 insertions(+), 281 deletions(-) create mode 100644 testutil/keeper/mocks/emissions/account.go create mode 100644 testutil/keeper/mocks/emissions/bank.go create mode 100644 testutil/keeper/mocks/emissions/observer.go create mode 100644 testutil/keeper/mocks/emissions/staking.go diff --git a/changelog.md b/changelog.md index 2080bde846..9116cc3651 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,11 @@ # CHANGELOG +## Unreleased + +### Tests + +* [1767](https://github.com/zeta-chain/node/pull/1767) - add unit tests for emissions module begin blocker + ## Version: v13.0.0 * `zetaclientd start` : 2 inputs required from stdin @@ -41,6 +47,7 @@ * [1584](https://github.com/zeta-chain/node/pull/1584) - allow to run E2E tests on any networks * [1746](https://github.com/zeta-chain/node/pull/1746) - rename smoke tests to e2e tests * [1753](https://github.com/zeta-chain/node/pull/1753) - fix gosec errors on usage of rand package + * [1762](https://github.com/zeta-chain/node/pull/1762) - improve coverage for fungibile module * [1782](https://github.com/zeta-chain/node/pull/1782) - improve coverage for fungibile module system contract diff --git a/testutil/keeper/emissions.go b/testutil/keeper/emissions.go index 4e57ced16d..988859c762 100644 --- a/testutil/keeper/emissions.go +++ b/testutil/keeper/emissions.go @@ -3,57 +3,105 @@ package keeper import ( "testing" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/store" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" - typesparams "github.com/cosmos/cosmos-sdk/x/params/types" - stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/libs/log" - tmproto "github.com/tendermint/tendermint/proto/tendermint/types" tmdb "github.com/tendermint/tm-db" + emissionsmocks "github.com/zeta-chain/zetacore/testutil/keeper/mocks/emissions" "github.com/zeta-chain/zetacore/x/emissions/keeper" "github.com/zeta-chain/zetacore/x/emissions/types" - observerkeeper "github.com/zeta-chain/zetacore/x/observer/keeper" ) -func EmissionsKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { +type EmissionMockOptions struct { + UseBankMock bool + UseStakingMock bool + UseObserverMock bool + UseAccountMock bool +} + +func EmissionsKeeper(t testing.TB) (*keeper.Keeper, sdk.Context, SDKKeepers, ZetaKeepers) { + return EmissionKeeperWithMockOptions(t, EmissionMockOptions{ + UseBankMock: false, + UseStakingMock: false, + UseObserverMock: false, + }) +} +func EmissionKeeperWithMockOptions( + t testing.TB, + mockOptions EmissionMockOptions, +) (*keeper.Keeper, sdk.Context, SDKKeepers, ZetaKeepers) { + SetConfig(false) storeKey := sdk.NewKVStoreKey(types.StoreKey) memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) + // Initialize local store db := tmdb.NewMemDB() stateStore := store.NewCommitMultiStore(db) + cdc := NewCodec() + + // Create regular keepers + sdkKeepers := NewSDKKeepers(cdc, db, stateStore) + + // Create zeta keepers + observerKeeperTmp := initObserverKeeper( + cdc, + db, + stateStore, + sdkKeepers.StakingKeeper, + sdkKeepers.SlashingKeeper, + sdkKeepers.ParamsKeeper, + ) + + zetaKeepers := ZetaKeepers{ + ObserverKeeper: observerKeeperTmp, + } + var observerKeeper types.ObserverKeeper = observerKeeperTmp + + // Create the fungible keeper stateStore.MountStoreWithDB(storeKey, storetypes.StoreTypeIAVL, db) - stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, db) + stateStore.MountStoreWithDB(memStoreKey, storetypes.StoreTypeMemory, nil) require.NoError(t, stateStore.LoadLatestVersion()) - registry := codectypes.NewInterfaceRegistry() - cdc := codec.NewProtoCodec(registry) + ctx := NewContext(stateStore) + + // Initialize modules genesis + sdkKeepers.InitGenesis(ctx) + zetaKeepers.InitGenesis(ctx) + + // Add a proposer to the context + ctx = sdkKeepers.InitBlockProposer(t, ctx) + + // Initialize mocks for mocked keepers + var authKeeper types.AccountKeeper = sdkKeepers.AuthKeeper + var bankKeeper types.BankKeeper = sdkKeepers.BankKeeper + var stakingKeeper types.StakingKeeper = sdkKeepers.StakingKeeper + if mockOptions.UseAccountMock { + authKeeper = emissionsmocks.NewEmissionAccountKeeper(t) + } + if mockOptions.UseBankMock { + bankKeeper = emissionsmocks.NewEmissionBankKeeper(t) + } + if mockOptions.UseStakingMock { + stakingKeeper = emissionsmocks.NewEmissionStakingKeeper(t) + } + if mockOptions.UseObserverMock { + observerKeeper = emissionsmocks.NewEmissionObserverKeeper(t) + } - paramsSubspace := typesparams.NewSubspace(cdc, - types.Amino, - storeKey, - memStoreKey, - "EmissionsParams", - ) k := keeper.NewKeeper( cdc, storeKey, memStoreKey, - paramsSubspace, + sdkKeepers.ParamsKeeper.Subspace(types.ModuleName), authtypes.FeeCollectorName, - bankkeeper.BaseKeeper{}, - stakingkeeper.Keeper{}, - observerkeeper.Keeper{}, - authkeeper.AccountKeeper{}, + bankKeeper, + stakingKeeper, + observerKeeper, + authKeeper, ) - - ctx := sdk.NewContext(stateStore, tmproto.Header{}, false, log.NewNopLogger()) k.SetParams(ctx, types.DefaultParams()) - return k, ctx + + return k, ctx, sdkKeepers, zetaKeepers } diff --git a/testutil/keeper/keeper.go b/testutil/keeper/keeper.go index b67e310544..82d02c9c4b 100644 --- a/testutil/keeper/keeper.go +++ b/testutil/keeper/keeper.go @@ -43,7 +43,7 @@ import ( crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" emissionsmodule "github.com/zeta-chain/zetacore/x/emissions" emissionskeeper "github.com/zeta-chain/zetacore/x/emissions/keeper" - types2 "github.com/zeta-chain/zetacore/x/emissions/types" + emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" fungiblemodule "github.com/zeta-chain/zetacore/x/fungible" fungiblekeeper "github.com/zeta-chain/zetacore/x/fungible/keeper" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" @@ -98,16 +98,16 @@ type ZetaKeepers struct { } var moduleAccountPerms = map[string][]string{ - authtypes.FeeCollectorName: nil, - distrtypes.ModuleName: nil, - stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, - stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, - evmtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - crosschaintypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - fungibletypes.ModuleName: {authtypes.Minter, authtypes.Burner}, - types2.ModuleName: nil, - types2.UndistributedObserverRewardsPool: nil, - types2.UndistributedTssRewardsPool: nil, + authtypes.FeeCollectorName: nil, + distrtypes.ModuleName: nil, + stakingtypes.BondedPoolName: {authtypes.Burner, authtypes.Staking}, + stakingtypes.NotBondedPoolName: {authtypes.Burner, authtypes.Staking}, + evmtypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + crosschaintypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + fungibletypes.ModuleName: {authtypes.Minter, authtypes.Burner}, + emissionstypes.ModuleName: {authtypes.Minter}, + emissionstypes.UndistributedObserverRewardsPool: nil, + emissionstypes.UndistributedTssRewardsPool: nil, } // ModuleAccountAddrs returns all the app's module account addresses. @@ -375,7 +375,7 @@ func (zk ZetaKeepers) InitGenesis(ctx sdk.Context) { crosschainmodule.InitGenesis(ctx, *zk.CrosschainKeeper, *crosschaintypes.DefaultGenesis()) } if zk.EmissionsKeeper != nil { - emissionsmodule.InitGenesis(ctx, *zk.EmissionsKeeper, *types2.DefaultGenesis()) + emissionsmodule.InitGenesis(ctx, *zk.EmissionsKeeper, *emissionstypes.DefaultGenesis()) } if zk.FungibleKeeper != nil { fungiblemodule.InitGenesis(ctx, *zk.FungibleKeeper, *fungibletypes.DefaultGenesis()) diff --git a/testutil/keeper/mocks/crosschain/account.go b/testutil/keeper/mocks/crosschain/account.go index 0560f21be8..fbd7c0377b 100644 --- a/testutil/keeper/mocks/crosschain/account.go +++ b/testutil/keeper/mocks/crosschain/account.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/crosschain/bank.go b/testutil/keeper/mocks/crosschain/bank.go index 9ac2b1278b..90f4e17e29 100644 --- a/testutil/keeper/mocks/crosschain/bank.go +++ b/testutil/keeper/mocks/crosschain/bank.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/crosschain/fungible.go b/testutil/keeper/mocks/crosschain/fungible.go index e8e090189d..37bb347981 100644 --- a/testutil/keeper/mocks/crosschain/fungible.go +++ b/testutil/keeper/mocks/crosschain/fungible.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/crosschain/observer.go b/testutil/keeper/mocks/crosschain/observer.go index a6fd222875..ff3cf4cd72 100644 --- a/testutil/keeper/mocks/crosschain/observer.go +++ b/testutil/keeper/mocks/crosschain/observer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/crosschain/staking.go b/testutil/keeper/mocks/crosschain/staking.go index 1fb8a14061..5b7d3c501f 100644 --- a/testutil/keeper/mocks/crosschain/staking.go +++ b/testutil/keeper/mocks/crosschain/staking.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/emissions/account.go b/testutil/keeper/mocks/emissions/account.go new file mode 100644 index 0000000000..a660d40e72 --- /dev/null +++ b/testutil/keeper/mocks/emissions/account.go @@ -0,0 +1,49 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + mock "github.com/stretchr/testify/mock" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// EmissionAccountKeeper is an autogenerated mock type for the EmissionAccountKeeper type +type EmissionAccountKeeper struct { + mock.Mock +} + +// GetModuleAccount provides a mock function with given fields: ctx, moduleName +func (_m *EmissionAccountKeeper) GetModuleAccount(ctx types.Context, moduleName string) authtypes.ModuleAccountI { + ret := _m.Called(ctx, moduleName) + + if len(ret) == 0 { + panic("no return value specified for GetModuleAccount") + } + + var r0 authtypes.ModuleAccountI + if rf, ok := ret.Get(0).(func(types.Context, string) authtypes.ModuleAccountI); ok { + r0 = rf(ctx, moduleName) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(authtypes.ModuleAccountI) + } + } + + return r0 +} + +// NewEmissionAccountKeeper creates a new instance of EmissionAccountKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEmissionAccountKeeper(t interface { + mock.TestingT + Cleanup(func()) +}) *EmissionAccountKeeper { + mock := &EmissionAccountKeeper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/mocks/emissions/bank.go b/testutil/keeper/mocks/emissions/bank.go new file mode 100644 index 0000000000..2e3d6a702e --- /dev/null +++ b/testutil/keeper/mocks/emissions/bank.go @@ -0,0 +1,64 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// EmissionBankKeeper is an autogenerated mock type for the EmissionBankKeeper type +type EmissionBankKeeper struct { + mock.Mock +} + +// GetBalance provides a mock function with given fields: ctx, addr, denom +func (_m *EmissionBankKeeper) GetBalance(ctx types.Context, addr types.AccAddress, denom string) types.Coin { + ret := _m.Called(ctx, addr, denom) + + if len(ret) == 0 { + panic("no return value specified for GetBalance") + } + + var r0 types.Coin + if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress, string) types.Coin); ok { + r0 = rf(ctx, addr, denom) + } else { + r0 = ret.Get(0).(types.Coin) + } + + return r0 +} + +// SendCoinsFromModuleToModule provides a mock function with given fields: ctx, senderModule, recipientModule, amt +func (_m *EmissionBankKeeper) SendCoinsFromModuleToModule(ctx types.Context, senderModule string, recipientModule string, amt types.Coins) error { + ret := _m.Called(ctx, senderModule, recipientModule, amt) + + if len(ret) == 0 { + panic("no return value specified for SendCoinsFromModuleToModule") + } + + var r0 error + if rf, ok := ret.Get(0).(func(types.Context, string, string, types.Coins) error); ok { + r0 = rf(ctx, senderModule, recipientModule, amt) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewEmissionBankKeeper creates a new instance of EmissionBankKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEmissionBankKeeper(t interface { + mock.TestingT + Cleanup(func()) +}) *EmissionBankKeeper { + mock := &EmissionBankKeeper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/mocks/emissions/observer.go b/testutil/keeper/mocks/emissions/observer.go new file mode 100644 index 0000000000..7c2cfb3c48 --- /dev/null +++ b/testutil/keeper/mocks/emissions/observer.go @@ -0,0 +1,78 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + observertypes "github.com/zeta-chain/zetacore/x/observer/types" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// EmissionObserverKeeper is an autogenerated mock type for the EmissionObserverKeeper type +type EmissionObserverKeeper struct { + mock.Mock +} + +// GetBallot provides a mock function with given fields: ctx, index +func (_m *EmissionObserverKeeper) GetBallot(ctx types.Context, index string) (observertypes.Ballot, bool) { + ret := _m.Called(ctx, index) + + if len(ret) == 0 { + panic("no return value specified for GetBallot") + } + + var r0 observertypes.Ballot + var r1 bool + if rf, ok := ret.Get(0).(func(types.Context, string) (observertypes.Ballot, bool)); ok { + return rf(ctx, index) + } + if rf, ok := ret.Get(0).(func(types.Context, string) observertypes.Ballot); ok { + r0 = rf(ctx, index) + } else { + r0 = ret.Get(0).(observertypes.Ballot) + } + + if rf, ok := ret.Get(1).(func(types.Context, string) bool); ok { + r1 = rf(ctx, index) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// GetMaturedBallotList provides a mock function with given fields: ctx +func (_m *EmissionObserverKeeper) GetMaturedBallotList(ctx types.Context) []string { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetMaturedBallotList") + } + + var r0 []string + if rf, ok := ret.Get(0).(func(types.Context) []string); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + return r0 +} + +// NewEmissionObserverKeeper creates a new instance of EmissionObserverKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEmissionObserverKeeper(t interface { + mock.TestingT + Cleanup(func()) +}) *EmissionObserverKeeper { + mock := &EmissionObserverKeeper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/mocks/emissions/staking.go b/testutil/keeper/mocks/emissions/staking.go new file mode 100644 index 0000000000..7c58333bb5 --- /dev/null +++ b/testutil/keeper/mocks/emissions/staking.go @@ -0,0 +1,46 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// EmissionStakingKeeper is an autogenerated mock type for the EmissionStakingKeeper type +type EmissionStakingKeeper struct { + mock.Mock +} + +// BondedRatio provides a mock function with given fields: ctx +func (_m *EmissionStakingKeeper) BondedRatio(ctx types.Context) types.Dec { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for BondedRatio") + } + + var r0 types.Dec + if rf, ok := ret.Get(0).(func(types.Context) types.Dec); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(types.Dec) + } + + return r0 +} + +// NewEmissionStakingKeeper creates a new instance of EmissionStakingKeeper. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewEmissionStakingKeeper(t interface { + mock.TestingT + Cleanup(func()) +}) *EmissionStakingKeeper { + mock := &EmissionStakingKeeper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/mocks/fungible/account.go b/testutil/keeper/mocks/fungible/account.go index f7a2788969..0522e833b4 100644 --- a/testutil/keeper/mocks/fungible/account.go +++ b/testutil/keeper/mocks/fungible/account.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/fungible/bank.go b/testutil/keeper/mocks/fungible/bank.go index 599ba3453f..db14226310 100644 --- a/testutil/keeper/mocks/fungible/bank.go +++ b/testutil/keeper/mocks/fungible/bank.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/fungible/evm.go b/testutil/keeper/mocks/fungible/evm.go index b59f7d477e..28fd46e25c 100644 --- a/testutil/keeper/mocks/fungible/evm.go +++ b/testutil/keeper/mocks/fungible/evm.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/fungible/observer.go b/testutil/keeper/mocks/fungible/observer.go index b5736cd960..3010f8faaf 100644 --- a/testutil/keeper/mocks/fungible/observer.go +++ b/testutil/keeper/mocks/fungible/observer.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.41.0. DO NOT EDIT. +// Code generated by mockery v2.38.0. DO NOT EDIT. package mocks diff --git a/testutil/keeper/mocks/mocks.go b/testutil/keeper/mocks/mocks.go index dbbdeccd76..ec32911a3f 100644 --- a/testutil/keeper/mocks/mocks.go +++ b/testutil/keeper/mocks/mocks.go @@ -2,6 +2,7 @@ package mocks import ( crosschaintypes "github.com/zeta-chain/zetacore/x/crosschain/types" + emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" fungibletypes "github.com/zeta-chain/zetacore/x/fungible/types" ) @@ -57,3 +58,23 @@ type FungibleObserverKeeper interface { type FungibleEVMKeeper interface { fungibletypes.EVMKeeper } + +//go:generate mockery --name EmissionAccountKeeper --filename account.go --case underscore --output ./emissions +type EmissionAccountKeeper interface { + emissionstypes.AccountKeeper +} + +//go:generate mockery --name EmissionBankKeeper --filename bank.go --case underscore --output ./emissions +type EmissionBankKeeper interface { + emissionstypes.BankKeeper +} + +//go:generate mockery --name EmissionStakingKeeper --filename staking.go --case underscore --output ./emissions +type EmissionStakingKeeper interface { + emissionstypes.StakingKeeper +} + +//go:generate mockery --name EmissionObserverKeeper --filename observer.go --case underscore --output ./emissions +type EmissionObserverKeeper interface { + emissionstypes.ObserverKeeper +} diff --git a/testutil/sample/observer.go b/testutil/sample/observer.go index b5d533c4e6..7cb0b9b27b 100644 --- a/testutil/sample/observer.go +++ b/testutil/sample/observer.go @@ -7,6 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/testutil/testdata" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/common/cosmos" "github.com/zeta-chain/zetacore/x/observer/types" @@ -236,3 +237,31 @@ func LegacyObserverMapperList(t *testing.T, n int, index string) []*types.Observ } return observerMapperList } + +func BallotList(n int, observerSet []string) []types.Ballot { + r := newRandFromSeed(int64(n)) + ballotList := make([]types.Ballot, n) + + for i := 0; i < n; i++ { + identifier := crypto.Keccak256Hash([]byte(fmt.Sprintf("%d-%d-%d", r.Int63(), r.Int63(), r.Int63()))) + ballotList[i] = types.Ballot{ + Index: identifier.Hex(), + BallotIdentifier: identifier.Hex(), + VoterList: observerSet, + Votes: VotesSuccessOnly(len(observerSet)), + ObservationType: types.ObservationType_InBoundTx, + BallotThreshold: sdk.OneDec(), + BallotStatus: types.BallotStatus_BallotFinalized_SuccessObservation, + BallotCreationHeight: 0, + } + } + return ballotList +} + +func VotesSuccessOnly(voteCount int) []types.VoteType { + votes := make([]types.VoteType, voteCount) + for i := 0; i < voteCount; i++ { + votes[i] = types.VoteType_SuccessObservation + } + return votes +} diff --git a/testutil/sample/sample.go b/testutil/sample/sample.go index 4fcc9ac9e8..ba197ea932 100644 --- a/testutil/sample/sample.go +++ b/testutil/sample/sample.go @@ -147,3 +147,8 @@ func UintInRange(low, high uint64) sdkmath.Uint { u := Uint64InRange(low, high) return sdkmath.NewUint(u) } + +func IntInRange(low, high int64) sdkmath.Int { + i := Int64InRange(low, high) + return sdkmath.NewInt(i) +} diff --git a/x/emissions/abci.go b/x/emissions/abci.go index bc15398209..2fb0561bb6 100644 --- a/x/emissions/abci.go +++ b/x/emissions/abci.go @@ -14,7 +14,6 @@ import ( func BeginBlocker(ctx sdk.Context, keeper keeper.Keeper) { emissionPoolBalance := keeper.GetReservesFactor(ctx) blockRewards := types.BlockReward - if blockRewards.GT(emissionPoolBalance) { ctx.Logger().Info(fmt.Sprintf("Block rewards %s are greater than emission pool balance %s", blockRewards.String(), emissionPoolBalance.String())) return @@ -22,6 +21,7 @@ func BeginBlocker(ctx sdk.Context, keeper keeper.Keeper) { validatorRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).ValidatorEmissionPercentage).Mul(blockRewards).TruncateInt() observerRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).ObserverEmissionPercentage).Mul(blockRewards).TruncateInt() tssSignerRewards := sdk.MustNewDecFromStr(keeper.GetParams(ctx).TssSignerEmissionPercentage).Mul(blockRewards).TruncateInt() + // Use a tmpCtx, which is a cache-wrapped context to avoid writing to the store // We commit only if all three distributions are successful, if not the funds stay in the emission pool tmpCtx, commit := ctx.CacheContext() @@ -93,7 +93,6 @@ func DistributeObserverRewards(ctx sdk.Context, amount sdkmath.Int, keeper keepe sortedKeys = append(sortedKeys, k) } sort.Strings(sortedKeys) - var finalDistributionList []*types.ObserverEmission for _, key := range sortedKeys { observerAddress, err := sdk.AccAddressFromBech32(key) @@ -123,6 +122,7 @@ func DistributeObserverRewards(ctx sdk.Context, amount sdkmath.Int, keeper keepe }) continue } + // Defensive check if rewardPerUnit.GT(sdk.ZeroInt()) { rewardAmount := rewardPerUnit.Mul(sdkmath.NewInt(observerRewardUnits)) diff --git a/x/emissions/abci_test.go b/x/emissions/abci_test.go index 28ca327d50..55e6188ba9 100644 --- a/x/emissions/abci_test.go +++ b/x/emissions/abci_test.go @@ -1,229 +1,288 @@ package emissions_test -//TODO : https://github.com/zeta-chain/node/issues/1659 -//func getaZetaFromString(amount string) sdk.Coins { -// emissionPoolInt, _ := sdk.NewIntFromString(amount) -// return sdk.NewCoins(sdk.NewCoin(config.BaseDenom, emissionPoolInt)) -//} -// -//func SetupApp(t *testing.T, params emissionsModuleTypes.Params, emissionPoolCoins sdk.Coins) (*zetaapp.App, sdk.Context, *tmtypes.ValidatorSet, *authtypes.BaseAccount) { -// pk1 := ed25519.GenPrivKey().PubKey() -// acc1 := authtypes.NewBaseAccountWithAddress(sdk.AccAddress(pk1.Address())) -// // genDelActs and genDelBalances need to have the same addresses -// // bondAmount is specified separately , the Balances here are additional tokens for delegators to have in their accounts -// genDelActs := make(authtypes.GenesisAccounts, 1) -// genDelBalances := make([]banktypes.Balance, 1) -// genDelActs[0] = acc1 -// genDelBalances[0] = banktypes.Balance{ -// Address: acc1.GetAddress().String(), -// Coins: emissionPoolCoins, -// } -// delBondAmount := getaZetaFromString("1000000000000000000000000") -// -// //genBalances := make([]banktypes.Balance, 1) -// //genBalances[0] = banktypes.Balance{ -// // Address: emissionsModuleTypes.EmissionsModuleAddress.String(), -// // Coins: emissionPoolCoins, -// //} -// -// vset := tmtypes.NewValidatorSet([]*tmtypes.Validator{}) -// for i := 0; i < 1; i++ { -// privKey := ed25519.GenPrivKey() -// pubKey := privKey.PubKey() -// val := tmtypes.NewValidator(pubKey, 1) -// err := vset.UpdateWithChangeSet([]*tmtypes.Validator{val}) -// if err != nil { -// panic("Failed to add validator") -// } -// } -// -// app := simapp.SetupWithGenesisValSet(t, vset, genDelActs, delBondAmount.AmountOf(config.BaseDenom), params, genDelBalances, nil) -// ctx := app.BaseApp.NewContext(false, tmproto.Header{}) -// ctx = ctx.WithBlockHeight(app.LastBlockHeight()) -// return app, ctx, vset, acc1 -//} -// -//type EmissionTestData struct { -// BlockHeight int64 `json:"blockHeight,omitempty"` -// BondFactor sdk.Dec `json:"bondFactor"` -// ReservesFactor sdk.Dec `json:"reservesFactor"` -// DurationFactor string `json:"durationFactor"` -//} -// -//func TestAppModule_GetBlockRewardComponents(t *testing.T) { -// -// tests := []struct { -// name string -// startingEmissionPool string -// params emissionsModuleTypes.Params -// testMaxHeight int64 -// inputFilename string -// checkValues []EmissionTestData -// generateOnly bool -// }{ -// { -// name: "default values", -// params: emissionsModuleTypes.DefaultParams(), -// startingEmissionPool: "1000000000000000000000000", -// testMaxHeight: 300, -// inputFilename: "simulations.json", -// generateOnly: false, -// }, -// { -// name: "higher starting pool", -// params: emissionsModuleTypes.DefaultParams(), -// startingEmissionPool: "100000000000000000000000000000000", -// testMaxHeight: 300, -// inputFilename: "simulations.json", -// generateOnly: false, -// }, -// { -// name: "lower starting pool", -// params: emissionsModuleTypes.DefaultParams(), -// startingEmissionPool: "100000000000000000", -// testMaxHeight: 300, -// inputFilename: "simulations.json", -// generateOnly: false, -// }, -// { -// name: "different distribution percentages", -// params: emissionsModuleTypes.Params{ -// MaxBondFactor: "1.25", -// MinBondFactor: "0.75", -// AvgBlockTime: "6.00", -// TargetBondRatio: "00.67", -// ValidatorEmissionPercentage: "00.10", -// ObserverEmissionPercentage: "00.85", -// TssSignerEmissionPercentage: "00.05", -// DurationFactorConstant: "0.001877876953694702", -// }, -// startingEmissionPool: "1000000000000000000000000", -// testMaxHeight: 300, -// inputFilename: "simulations.json", -// generateOnly: false, -// }, -// { -// name: "higher block time", -// params: emissionsModuleTypes.Params{ -// MaxBondFactor: "1.25", -// MinBondFactor: "0.75", -// AvgBlockTime: "20.00", -// TargetBondRatio: "00.67", -// ValidatorEmissionPercentage: "00.10", -// ObserverEmissionPercentage: "00.85", -// TssSignerEmissionPercentage: "00.05", -// DurationFactorConstant: "0.1", -// }, -// startingEmissionPool: "1000000000000000000000000", -// testMaxHeight: 300, -// inputFilename: "simulations.json", -// generateOnly: false, -// }, -// { -// name: "different duration constant", -// params: emissionsModuleTypes.Params{ -// MaxBondFactor: "1.25", -// MinBondFactor: "0.75", -// AvgBlockTime: "6.00", -// TargetBondRatio: "00.67", -// ValidatorEmissionPercentage: "00.10", -// ObserverEmissionPercentage: "00.85", -// TssSignerEmissionPercentage: "00.05", -// DurationFactorConstant: "0.1", -// }, -// startingEmissionPool: "1000000000000000000000000", -// testMaxHeight: 300, -// inputFilename: "simulations.json", -// generateOnly: false, -// }, -// } -// -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// app, ctx, _, minter := SetupApp(t, tt.params, getaZetaFromString(tt.startingEmissionPool)) -// err := app.BankKeeper.SendCoinsFromAccountToModule(ctx, minter.GetAddress(), emissionsModuleTypes.ModuleName, getaZetaFromString(tt.startingEmissionPool)) -// require.NoError(t, err) -// GenerateTestDataMaths(app, ctx, tt.testMaxHeight, tt.inputFilename) -// defer func(t *testing.T, fp string) { -// err := os.RemoveAll(fp) -// require.NoError(t, err) -// }(t, tt.inputFilename) -// -// if tt.generateOnly { -// return -// } -// inputTestData, err := GetInputData(tt.inputFilename) -// require.NoError(t, err) -// sort.SliceStable(inputTestData, func(i, j int) bool { return inputTestData[i].BlockHeight < inputTestData[j].BlockHeight }) -// startHeight := ctx.BlockHeight() -// require.Equal(t, startHeight, inputTestData[0].BlockHeight, "starting block height should be equal to the first block height in the input data") -// for i := startHeight; i < tt.testMaxHeight; i++ { -// //The First distribution will occur only when begin-block is triggered -// reservesFactor, bondFactor, durationFactor := app.EmissionsKeeper.GetBlockRewardComponents(ctx) -// require.Equal(t, inputTestData[i-1].ReservesFactor, reservesFactor, "reserves factor should be equal to the input data"+fmt.Sprintf(" , block height: %d", i)) -// require.Equal(t, inputTestData[i-1].BondFactor, bondFactor, "bond factor should be equal to the input data"+fmt.Sprintf(" , block height: %d", i)) -// require.Equal(t, inputTestData[i-1].DurationFactor, durationFactor.String(), "duration factor should be equal to the input data"+fmt.Sprintf(" , block height: %d", i)) -// emissionsModule.BeginBlocker(ctx, app.EmissionsKeeper) -// ctx = ctx.WithBlockHeight(i + 1) -// } -// }) -// } -//} -// -//func GetInputData(fp string) ([]EmissionTestData, error) { -// data := []EmissionTestData{} -// file, err := filepath.Abs(fp) -// if err != nil { -// -// return nil, err -// } -// file = filepath.Clean(file) -// input, err := ioutil.ReadFile(file) // #nosec G304 -// if err != nil { -// return nil, err -// } -// err = json.Unmarshal(input, &data) -// if err != nil { -// return nil, err -// } -// formatedData := make([]EmissionTestData, len(data)) -// for i, dd := range data { -// fl, err := strconv.ParseFloat(dd.DurationFactor, 64) -// if err != nil { -// return nil, err -// } -// dd.DurationFactor = fmt.Sprintf("%0.18f", fl) -// formatedData[i] = dd -// } -// return formatedData, nil -//} -// -//func GenerateTestDataMaths(app *zetaapp.App, ctx sdk.Context, testMaxHeight int64, fileName string) { -// var generatedTestData []EmissionTestData -// reserverCoins := app.BankKeeper.GetBalance(ctx, emissionsModuleTypes.EmissionsModuleAddress, config.BaseDenom) -// startHeight := ctx.BlockHeight() -// for i := startHeight; i < testMaxHeight; i++ { -// reservesFactor := sdk.NewDecFromInt(reserverCoins.Amount) -// bondFactor := app.EmissionsKeeper.GetBondFactor(ctx, app.StakingKeeper) -// durationFactor := app.EmissionsKeeper.GetDurationFactor(ctx) -// blockRewards := reservesFactor.Mul(bondFactor).Mul(durationFactor) -// generatedTestData = append(generatedTestData, EmissionTestData{ -// BlockHeight: i, -// BondFactor: bondFactor, -// DurationFactor: durationFactor.String(), -// ReservesFactor: reservesFactor, -// }) -// validatorRewards := sdk.MustNewDecFromStr(app.EmissionsKeeper.GetParams(ctx).ValidatorEmissionPercentage).Mul(blockRewards).TruncateInt() -// observerRewards := sdk.MustNewDecFromStr(app.EmissionsKeeper.GetParams(ctx).ObserverEmissionPercentage).Mul(blockRewards).TruncateInt() -// tssSignerRewards := sdk.MustNewDecFromStr(app.EmissionsKeeper.GetParams(ctx).TssSignerEmissionPercentage).Mul(blockRewards).TruncateInt() -// truncatedRewards := validatorRewards.Add(observerRewards).Add(tssSignerRewards) -// reserverCoins = reserverCoins.Sub(sdk.NewCoin(config.BaseDenom, truncatedRewards)) -// ctx = ctx.WithBlockHeight(i + 1) -// } -// GenerateSampleFile(fileName, generatedTestData) -//} -// -//func GenerateSampleFile(fp string, data []EmissionTestData) { -// file, _ := json.MarshalIndent(data, "", " ") -// _ = ioutil.WriteFile(fp, file, 0600) -//} +import ( + "testing" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/cmd/zetacored/config" + "github.com/zeta-chain/zetacore/common" + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + emissionsModule "github.com/zeta-chain/zetacore/x/emissions" + emissionstypes "github.com/zeta-chain/zetacore/x/emissions/types" + observerTypes "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestBeginBlocker(t *testing.T) { + t.Run("no observer distribution happens if emissions module account is empty", func(t *testing.T) { + k, ctx, _, zk := keepertest.EmissionsKeeper(t) + var ballotIdentifiers []string + + observerSet := sample.ObserverSet(10) + zk.ObserverKeeper.SetObserverSet(ctx, observerSet) + + ballotList := sample.BallotList(10, observerSet.ObserverList) + for _, ballot := range ballotList { + zk.ObserverKeeper.SetBallot(ctx, &ballot) + ballotIdentifiers = append(ballotIdentifiers, ballot.BallotIdentifier) + } + zk.ObserverKeeper.SetBallotList(ctx, &observerTypes.BallotListForHeight{ + Height: 0, + BallotsIndexList: ballotIdentifiers, + }) + for i := 0; i < 100; i++ { + emissionsModule.BeginBlocker(ctx, *k) + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + } + for _, observer := range observerSet.ObserverList { + _, found := k.GetWithdrawableEmission(ctx, observer) + require.False(t, found) + } + }) + t.Run("no validator distribution happens if emissions module account is empty", func(t *testing.T) { + k, ctx, sk, _ := keepertest.EmissionsKeeper(t) + feeCollectorAddress := sk.AuthKeeper.GetModuleAccount(ctx, types.FeeCollectorName).GetAddress() + for i := 0; i < 100; i++ { + emissionsModule.BeginBlocker(ctx, *k) + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + } + require.True(t, sk.BankKeeper.GetBalance(ctx, feeCollectorAddress, config.BaseDenom).Amount.IsZero()) + }) + t.Run("tmp ctx is not committed if any of the distribution fails", func(t *testing.T) { + k, ctx, sk, _ := keepertest.EmissionsKeeper(t) + // Fund the emission pool to start the emission process + err := sk.BankKeeper.MintCoins(ctx, emissionstypes.ModuleName, sdk.NewCoins(sdk.NewCoin(config.BaseDenom, sdk.NewInt(1000000000000)))) + require.NoError(t, err) + // Setup module accounts for emission pools except for observer pool , so that the observer distribution fails + _ = sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.UndistributedTssRewardsPool).GetAddress() + feeCollectorAddress := sk.AuthKeeper.GetModuleAccount(ctx, types.FeeCollectorName).GetAddress() + _ = sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.ModuleName).GetAddress() + + for i := 0; i < 100; i++ { + // produce a block + emissionsModule.BeginBlocker(ctx, *k) + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + } + require.True(t, sk.BankKeeper.GetBalance(ctx, feeCollectorAddress, config.BaseDenom).Amount.IsZero()) + require.True(t, sk.BankKeeper.GetBalance(ctx, emissionstypes.EmissionsModuleAddress, config.BaseDenom).Amount.Equal(sdk.NewInt(1000000000000))) + }) + t.Run("successfully distribute rewards", func(t *testing.T) { + numberOfTestBlocks := 100 + k, ctx, sk, zk := keepertest.EmissionsKeeper(t) + observerSet := sample.ObserverSet(10) + zk.ObserverKeeper.SetObserverSet(ctx, observerSet) + ballotList := sample.BallotList(10, observerSet.ObserverList) + + // set the ballot list + ballotIdentifiers := []string{} + for _, ballot := range ballotList { + zk.ObserverKeeper.SetBallot(ctx, &ballot) + ballotIdentifiers = append(ballotIdentifiers, ballot.BallotIdentifier) + } + zk.ObserverKeeper.SetBallotList(ctx, &observerTypes.BallotListForHeight{ + Height: 0, + BallotsIndexList: ballotIdentifiers, + }) + + // Total block rewards is the fixed amount of rewards that are distributed + totalBlockRewards, err := common.GetAzetaDecFromAmountInZeta(emissionstypes.BlockRewardsInZeta) + totalRewardCoins := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, totalBlockRewards.TruncateInt())) + require.NoError(t, err) + // Fund the emission pool to start the emission process + err = sk.BankKeeper.MintCoins(ctx, emissionstypes.ModuleName, totalRewardCoins) + require.NoError(t, err) + + // Setup module accounts for emission pools + undistributedObserverPoolAddress := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.UndistributedObserverRewardsPool).GetAddress() + undistributedTssPoolAddress := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.UndistributedTssRewardsPool).GetAddress() + feeCollecterAddress := sk.AuthKeeper.GetModuleAccount(ctx, types.FeeCollectorName).GetAddress() + emissionPool := sk.AuthKeeper.GetModuleAccount(ctx, emissionstypes.ModuleName).GetAddress() + + blockRewards := emissionstypes.BlockReward + observerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).ObserverEmissionPercentage)).TruncateInt() + validatorRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).ValidatorEmissionPercentage)).TruncateInt() + tssSignerRewardsForABlock := blockRewards.Mul(sdk.MustNewDecFromStr(k.GetParams(ctx).TssSignerEmissionPercentage)).TruncateInt() + distributedRewards := observerRewardsForABlock.Add(validatorRewardsForABlock).Add(tssSignerRewardsForABlock) + + require.True(t, blockRewards.TruncateInt().GT(distributedRewards)) + + for i := 0; i < numberOfTestBlocks; i++ { + emissionPoolBeforeBlockDistribution := sk.BankKeeper.GetBalance(ctx, emissionPool, config.BaseDenom).Amount + // produce a block + emissionsModule.BeginBlocker(ctx, *k) + + // require distribution amount + emissionPoolBalanceAfterBlockDistribution := sk.BankKeeper.GetBalance(ctx, emissionPool, config.BaseDenom).Amount + require.True(t, emissionPoolBeforeBlockDistribution.Sub(emissionPoolBalanceAfterBlockDistribution).Equal(distributedRewards)) + + // totalDistributedTillCurrentBlock is the net amount of rewards distributed till the current block, this works in a unit test as the fees are not being collected by validators + totalDistributedTillCurrentBlock := sk.BankKeeper.GetBalance(ctx, feeCollecterAddress, config.BaseDenom).Amount. + Add(sk.BankKeeper.GetBalance(ctx, undistributedObserverPoolAddress, config.BaseDenom).Amount). + Add(sk.BankKeeper.GetBalance(ctx, undistributedTssPoolAddress, config.BaseDenom).Amount) + // require we are always under the max limit of block rewards + require.True(t, totalRewardCoins.AmountOf(config.BaseDenom). + Sub(totalDistributedTillCurrentBlock).GTE(sdk.ZeroInt())) + + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + } + + // We can simplify the calculation as the rewards are distributed equally among all the observers + rewardPerUnit := observerRewardsForABlock.Quo(sdk.NewInt(int64(len(ballotList) * len(observerSet.ObserverList)))) + emissionAmount := rewardPerUnit.Mul(sdk.NewInt(int64(len(ballotList)))) + + // Check if the rewards are distributed equally among all the observers + for _, observer := range observerSet.ObserverList { + observerEmission, found := k.GetWithdrawableEmission(ctx, observer) + require.True(t, found) + require.Equal(t, emissionAmount, observerEmission.Amount) + } + + // Check pool balances after the distribution + feeCollectorBalance := sk.BankKeeper.GetBalance(ctx, feeCollecterAddress, config.BaseDenom).Amount + require.Equal(t, feeCollectorBalance, validatorRewardsForABlock.Mul(sdk.NewInt(int64(numberOfTestBlocks)))) + + tssPoolBalances := sk.BankKeeper.GetBalance(ctx, undistributedTssPoolAddress, config.BaseDenom).Amount + require.Equal(t, tssSignerRewardsForABlock.Mul(sdk.NewInt(int64(numberOfTestBlocks))).String(), tssPoolBalances.String()) + + observerPoolBalances := sk.BankKeeper.GetBalance(ctx, undistributedObserverPoolAddress, config.BaseDenom).Amount + require.Equal(t, observerRewardsForABlock.Mul(sdk.NewInt(int64(numberOfTestBlocks))).String(), observerPoolBalances.String()) + }) +} + +func TestDistributeObserverRewards(t *testing.T) { + + k, ctx, sk, zk := keepertest.EmissionsKeeper(t) + observerSet := sample.ObserverSet(4) + zk.ObserverKeeper.SetObserverSet(ctx, observerSet) + // Total block rewards is the fixed amount of rewards that are distributed + totalBlockRewards, err := common.GetAzetaDecFromAmountInZeta(emissionstypes.BlockRewardsInZeta) + totalRewardCoins := sdk.NewCoins(sdk.NewCoin(config.BaseDenom, totalBlockRewards.TruncateInt())) + require.NoError(t, err) + // Fund the emission pool to start the emission process + err = sk.BankKeeper.MintCoins(ctx, emissionstypes.ModuleName, totalRewardCoins) + require.NoError(t, err) + // Set starting emission for all observers to 100 so that we can calculate the rewards and slashes + for _, observer := range observerSet.ObserverList { + k.SetWithdrawableEmission(ctx, emissionstypes.WithdrawableEmissions{ + Address: observer, + Amount: sdkmath.NewInt(100), + }) + } + + tt := []struct { + name string + votes [][]observerTypes.VoteType + totalRewardsForBlock sdkmath.Int + expectedRewards map[string]int64 + ballotStatus observerTypes.BallotStatus + slashAmount sdkmath.Int + }{ + { + name: "all observers rewarded correctly", + votes: [][]observerTypes.VoteType{{observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation}}, + // total reward units would be 4 as all votes match the ballot status + totalRewardsForBlock: sdkmath.NewInt(100), + expectedRewards: map[string]int64{ + observerSet.ObserverList[0]: 125, + observerSet.ObserverList[1]: 125, + observerSet.ObserverList[2]: 125, + observerSet.ObserverList[3]: 125, + }, + ballotStatus: observerTypes.BallotStatus_BallotFinalized_SuccessObservation, + slashAmount: sdkmath.NewInt(25), + }, + { + name: "one observer slashed", + votes: [][]observerTypes.VoteType{{observerTypes.VoteType_FailureObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation}}, + // total reward units would be 3 as 3 votes match the ballot status + totalRewardsForBlock: sdkmath.NewInt(75), + expectedRewards: map[string]int64{ + observerSet.ObserverList[0]: 75, + observerSet.ObserverList[1]: 125, + observerSet.ObserverList[2]: 125, + observerSet.ObserverList[3]: 125, + }, + ballotStatus: observerTypes.BallotStatus_BallotFinalized_SuccessObservation, + slashAmount: sdkmath.NewInt(25), + }, + { + name: "all observer slashed", + votes: [][]observerTypes.VoteType{{observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation}}, + // total reward units would be 0 as no votes match the ballot status + totalRewardsForBlock: sdkmath.NewInt(100), + expectedRewards: map[string]int64{ + observerSet.ObserverList[0]: 75, + observerSet.ObserverList[1]: 75, + observerSet.ObserverList[2]: 75, + observerSet.ObserverList[3]: 75, + }, + ballotStatus: observerTypes.BallotStatus_BallotFinalized_FailureObservation, + slashAmount: sdkmath.NewInt(25), + }, + { + name: "slashed to zero if slash amount is greater than available emissions", + votes: [][]observerTypes.VoteType{{observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation}}, + // total reward units would be 0 as no votes match the ballot status + totalRewardsForBlock: sdkmath.NewInt(100), + expectedRewards: map[string]int64{ + observerSet.ObserverList[0]: 0, + observerSet.ObserverList[1]: 0, + observerSet.ObserverList[2]: 0, + observerSet.ObserverList[3]: 0, + }, + ballotStatus: observerTypes.BallotStatus_BallotFinalized_FailureObservation, + slashAmount: sdkmath.NewInt(2500), + }, + { + name: "withdraw able emissions unchanged if rewards and slashes are equal", + votes: [][]observerTypes.VoteType{ + {observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation}, + {observerTypes.VoteType_FailureObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation, observerTypes.VoteType_SuccessObservation}, + }, + // total reward units would be 7 as 7 votes match the ballot status, including both ballots + totalRewardsForBlock: sdkmath.NewInt(70), + expectedRewards: map[string]int64{ + observerSet.ObserverList[0]: 100, + observerSet.ObserverList[1]: 120, + observerSet.ObserverList[2]: 120, + observerSet.ObserverList[3]: 120, + }, + ballotStatus: observerTypes.BallotStatus_BallotFinalized_SuccessObservation, + slashAmount: sdkmath.NewInt(25), + }, + } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + params := emissionstypes.DefaultParams() + params.ObserverSlashAmount = tc.slashAmount + k.SetParams(ctx, params) + ballotIdentifiers := []string{} + for i, votes := range tc.votes { + ballot := observerTypes.Ballot{ + BallotIdentifier: "ballot" + string(rune(i)), + BallotStatus: tc.ballotStatus, + VoterList: observerSet.ObserverList, + Votes: votes, + } + zk.ObserverKeeper.SetBallot(ctx, &ballot) + ballotIdentifiers = append(ballotIdentifiers, ballot.BallotIdentifier) + } + zk.ObserverKeeper.SetBallotList(ctx, &observerTypes.BallotListForHeight{ + Height: 0, + BallotsIndexList: ballotIdentifiers, + }) + + ctx = ctx.WithBlockHeight(100) + err := emissionsModule.DistributeObserverRewards(ctx, tc.totalRewardsForBlock, *k) + require.NoError(t, err) + for _, observer := range observerSet.ObserverList { + observerEmission, found := k.GetWithdrawableEmission(ctx, observer) + require.True(t, found) + require.Equal(t, tc.expectedRewards[observer], observerEmission.Amount.Int64()) + } + }) + } +} diff --git a/x/emissions/genesis_test.go b/x/emissions/genesis_test.go index cae3948ce1..e1bebddee5 100644 --- a/x/emissions/genesis_test.go +++ b/x/emissions/genesis_test.go @@ -22,7 +22,7 @@ func TestGenesis(t *testing.T) { } // Init and export - k, ctx := keepertest.EmissionsKeeper(t) + k, ctx, _, _ := keepertest.EmissionsKeeper(t) emissions.InitGenesis(ctx, *k, genesisState) got := emissions.ExportGenesis(ctx, *k) require.NotNil(t, got) diff --git a/x/emissions/keeper/params_test.go b/x/emissions/keeper/params_test.go index 128d9ced3e..f861a944a3 100644 --- a/x/emissions/keeper/params_test.go +++ b/x/emissions/keeper/params_test.go @@ -228,14 +228,13 @@ func TestKeeper_GetParams(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - k, ctx := keepertest.EmissionsKeeper(t) - defaultParams := k.GetParams(ctx) + k, ctx, _, _ := keepertest.EmissionsKeeper(t) assertPanic(t, func() { k.SetParams(ctx, tt.params) }, tt.isPanic) if tt.isPanic != "" { - require.Equal(t, defaultParams, k.GetParams(ctx)) + require.Equal(t, emissionstypes.DefaultParams(), k.GetParams(ctx)) } else { require.Equal(t, tt.params, k.GetParams(ctx)) } From f15eb495b6050a68e1287c7e4a277c30949750ba Mon Sep 17 00:00:00 2001 From: Lucas Bertrand Date: Thu, 22 Feb 2024 21:55:57 +0100 Subject: [PATCH 3/3] ci: run build workflow on develop push for code coverage generation (#1793) --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f997dd9e02..c273f4633d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,6 +1,9 @@ name: PR Testing on: + push: + branches: + - develop pull_request: branches: - develop