diff --git a/Dockerfile b/Dockerfile index 8785a09ef5..e6da909959 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,15 +16,9 @@ RUN ssh-keygen -b 2048 -t rsa -f /root/.ssh/localtest.pem -q -N "" WORKDIR /go/delivery/zeta-node COPY go.mod . COPY go.sum . -#RUN --mount=type=cache,target=/root/.cache/go-build \ -# go mod download + RUN go mod download COPY . . - -#RUN --mount=type=cache,target=/root/.cache/go-build \ -# make install -#RUN --mount=type=cache,target=/root/.cache/go-build \ -# make install-zetae2e RUN make install RUN make install-zetae2e # diff --git a/changelog.md b/changelog.md index 6f48d5264d..54903786f5 100644 --- a/changelog.md +++ b/changelog.md @@ -2,15 +2,16 @@ ## Unreleased -### Features - -* [1789](https://github.com/zeta-chain/node/issues/1789) - block cross-chain transactions that involve restricted addresses - ### Refactor +* [1511](https://github.com/zeta-chain/node/pull/1511) - move ballot voting logic from `crosschain` to `observer` * [1783](https://github.com/zeta-chain/node/pull/1783) - refactor zetaclient metrics naming and structure * [1774](https://github.com/zeta-chain/node/pull/1774) - split params and config in zetaclient +### Features + +* [1789](https://github.com/zeta-chain/node/issues/1789) - block cross-chain transactions that involve restricted addresses + ### Tests * [1767](https://github.com/zeta-chain/node/pull/1767) - add unit tests for emissions module begin blocker @@ -21,7 +22,10 @@ ## Version: v13.0.0 -* `zetaclientd start` : 2 inputs required from stdin +### Breaking Changes + +* `zetaclientd start`: now requires 2 inputs from stdin: hotkey password and tss keyshare password + Starting zetaclient now requires two passwords to be input; one for the hotkey and another for the tss key-share. ### Features @@ -36,9 +40,9 @@ *[1728] (https://github.com/zeta-chain/node/pull/1728) - allow aborted transactions to be refunded by minting tokens to zEvm. ### Refactor + * [1766](https://github.com/zeta-chain/node/pull/1766) - Refactors the `PostTxProcessing` EVM hook functionality to deal with invalid withdraw events -* [1630](https://github.com/zeta-chain/node/pull/1630) added password prompts for hotkey and tss keyshare in zetaclient - Starting zetaclient now requires two passwords to be input; one for the hotkey and another for the tss key-share. +* [1630](https://github.com/zeta-chain/node/pull/1630) - added password prompts for hotkey and tss keyshare in zetaclient * [1760](https://github.com/zeta-chain/node/pull/1760) - Make staking keeper private in crosschain module ### Fixes @@ -54,7 +58,6 @@ * [1721](https://github.com/zeta-chain/node/issues/1721) - zetaclient should provide bitcoin_chain_id when querying TSS address * [1744](https://github.com/zeta-chain/node/pull/1744) - added cmd to encrypt tss keyshare file, allowing empty tss password for backward compatibility. - ### Tests * [1584](https://github.com/zeta-chain/node/pull/1584) - allow to run E2E tests on any networks diff --git a/common/chain.go b/common/chain.go index 5254f150f6..8f02baa388 100644 --- a/common/chain.go +++ b/common/chain.go @@ -75,12 +75,17 @@ func (chain Chain) BTCAddressFromWitnessProgram(witnessProgram []byte) (string, // DecodeAddress decode the address string to bytes func (chain Chain) DecodeAddress(addr string) ([]byte, error) { - if IsEVMChain(chain.ChainId) { + return DecodeAddressFromChainID(chain.ChainId, addr) +} + +// DecodeAddressFromChainID decode the address string to bytes +func DecodeAddressFromChainID(chainID int64, addr string) ([]byte, error) { + if IsEVMChain(chainID) { return ethcommon.HexToAddress(addr).Bytes(), nil - } else if IsBitcoinChain(chain.ChainId) { + } else if IsBitcoinChain(chainID) { return []byte(addr), nil } - return nil, fmt.Errorf("chain (%d) not supported", chain.ChainId) + return nil, fmt.Errorf("chain (%d) not supported", chainID) } func IsZetaChain(chainID int64) bool { diff --git a/testutil/keeper/crosschain.go b/testutil/keeper/crosschain.go index 69f2ae68f4..45a68a5627 100644 --- a/testutil/keeper/crosschain.go +++ b/testutil/keeper/crosschain.go @@ -32,7 +32,7 @@ var ( CrosschainNoMocks = CrosschainMockOptions{} ) -// CrosschainKeeper initializes a crosschain keeper for testing purposes with option to mock specific keepers +// CrosschainKeeperWithMocks initializes a crosschain keeper for testing purposes with option to mock specific keepers func CrosschainKeeperWithMocks( t testing.TB, mockOptions CrosschainMockOptions, diff --git a/testutil/keeper/mocks/crosschain/fungible.go b/testutil/keeper/mocks/crosschain/fungible.go index 37bb347981..7a0a40a6de 100644 --- a/testutil/keeper/mocks/crosschain/fungible.go +++ b/testutil/keeper/mocks/crosschain/fungible.go @@ -597,9 +597,9 @@ func (_m *CrosschainFungibleKeeper) WithdrawFromGasStabilityPool(ctx types.Conte return r0 } -// ZRC20DepositAndCallContract provides a mock function with given fields: ctx, from, to, amount, senderChain, data, coinType, asset -func (_m *CrosschainFungibleKeeper) ZRC20DepositAndCallContract(ctx types.Context, from []byte, to common.Address, amount *big.Int, senderChain *zetacorecommon.Chain, data []byte, coinType zetacorecommon.CoinType, asset string) (*evmtypes.MsgEthereumTxResponse, bool, error) { - ret := _m.Called(ctx, from, to, amount, senderChain, data, coinType, asset) +// ZRC20DepositAndCallContract provides a mock function with given fields: ctx, from, to, amount, senderChainID, data, coinType, asset +func (_m *CrosschainFungibleKeeper) ZRC20DepositAndCallContract(ctx types.Context, from []byte, to common.Address, amount *big.Int, senderChainID int64, data []byte, coinType zetacorecommon.CoinType, asset string) (*evmtypes.MsgEthereumTxResponse, bool, error) { + ret := _m.Called(ctx, from, to, amount, senderChainID, data, coinType, asset) if len(ret) == 0 { panic("no return value specified for ZRC20DepositAndCallContract") @@ -608,25 +608,25 @@ func (_m *CrosschainFungibleKeeper) ZRC20DepositAndCallContract(ctx types.Contex var r0 *evmtypes.MsgEthereumTxResponse var r1 bool var r2 error - if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, *zetacorecommon.Chain, []byte, zetacorecommon.CoinType, string) (*evmtypes.MsgEthereumTxResponse, bool, error)); ok { - return rf(ctx, from, to, amount, senderChain, data, coinType, asset) + if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, zetacorecommon.CoinType, string) (*evmtypes.MsgEthereumTxResponse, bool, error)); ok { + return rf(ctx, from, to, amount, senderChainID, data, coinType, asset) } - if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, *zetacorecommon.Chain, []byte, zetacorecommon.CoinType, string) *evmtypes.MsgEthereumTxResponse); ok { - r0 = rf(ctx, from, to, amount, senderChain, data, coinType, asset) + if rf, ok := ret.Get(0).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, zetacorecommon.CoinType, string) *evmtypes.MsgEthereumTxResponse); ok { + r0 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*evmtypes.MsgEthereumTxResponse) } } - if rf, ok := ret.Get(1).(func(types.Context, []byte, common.Address, *big.Int, *zetacorecommon.Chain, []byte, zetacorecommon.CoinType, string) bool); ok { - r1 = rf(ctx, from, to, amount, senderChain, data, coinType, asset) + if rf, ok := ret.Get(1).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, zetacorecommon.CoinType, string) bool); ok { + r1 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset) } else { r1 = ret.Get(1).(bool) } - if rf, ok := ret.Get(2).(func(types.Context, []byte, common.Address, *big.Int, *zetacorecommon.Chain, []byte, zetacorecommon.CoinType, string) error); ok { - r2 = rf(ctx, from, to, amount, senderChain, data, coinType, asset) + if rf, ok := ret.Get(2).(func(types.Context, []byte, common.Address, *big.Int, int64, []byte, zetacorecommon.CoinType, string) error); ok { + r2 = rf(ctx, from, to, amount, senderChainID, data, coinType, asset) } else { r2 = ret.Error(2) } diff --git a/testutil/keeper/mocks/crosschain/observer.go b/testutil/keeper/mocks/crosschain/observer.go index ff3cf4cd72..8e2a40b9cb 100644 --- a/testutil/keeper/mocks/crosschain/observer.go +++ b/testutil/keeper/mocks/crosschain/observer.go @@ -800,6 +800,90 @@ func (_m *CrosschainObserverKeeper) SetTssAndUpdateNonce(ctx types.Context, tss _m.Called(ctx, tss) } +// VoteOnInboundBallot provides a mock function with given fields: ctx, senderChainID, receiverChainID, coinType, voter, ballotIndex, inTxHash +func (_m *CrosschainObserverKeeper) VoteOnInboundBallot(ctx types.Context, senderChainID int64, receiverChainID int64, coinType common.CoinType, voter string, ballotIndex string, inTxHash string) (bool, bool, error) { + ret := _m.Called(ctx, senderChainID, receiverChainID, coinType, voter, ballotIndex, inTxHash) + + if len(ret) == 0 { + panic("no return value specified for VoteOnInboundBallot") + } + + var r0 bool + var r1 bool + var r2 error + if rf, ok := ret.Get(0).(func(types.Context, int64, int64, common.CoinType, string, string, string) (bool, bool, error)); ok { + return rf(ctx, senderChainID, receiverChainID, coinType, voter, ballotIndex, inTxHash) + } + if rf, ok := ret.Get(0).(func(types.Context, int64, int64, common.CoinType, string, string, string) bool); ok { + r0 = rf(ctx, senderChainID, receiverChainID, coinType, voter, ballotIndex, inTxHash) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(types.Context, int64, int64, common.CoinType, string, string, string) bool); ok { + r1 = rf(ctx, senderChainID, receiverChainID, coinType, voter, ballotIndex, inTxHash) + } else { + r1 = ret.Get(1).(bool) + } + + if rf, ok := ret.Get(2).(func(types.Context, int64, int64, common.CoinType, string, string, string) error); ok { + r2 = rf(ctx, senderChainID, receiverChainID, coinType, voter, ballotIndex, inTxHash) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// VoteOnOutboundBallot provides a mock function with given fields: ctx, ballotIndex, outTxChainID, receiveStatus, voter +func (_m *CrosschainObserverKeeper) VoteOnOutboundBallot(ctx types.Context, ballotIndex string, outTxChainID int64, receiveStatus common.ReceiveStatus, voter string) (bool, bool, observertypes.Ballot, string, error) { + ret := _m.Called(ctx, ballotIndex, outTxChainID, receiveStatus, voter) + + if len(ret) == 0 { + panic("no return value specified for VoteOnOutboundBallot") + } + + var r0 bool + var r1 bool + var r2 observertypes.Ballot + var r3 string + var r4 error + if rf, ok := ret.Get(0).(func(types.Context, string, int64, common.ReceiveStatus, string) (bool, bool, observertypes.Ballot, string, error)); ok { + return rf(ctx, ballotIndex, outTxChainID, receiveStatus, voter) + } + if rf, ok := ret.Get(0).(func(types.Context, string, int64, common.ReceiveStatus, string) bool); ok { + r0 = rf(ctx, ballotIndex, outTxChainID, receiveStatus, voter) + } else { + r0 = ret.Get(0).(bool) + } + + if rf, ok := ret.Get(1).(func(types.Context, string, int64, common.ReceiveStatus, string) bool); ok { + r1 = rf(ctx, ballotIndex, outTxChainID, receiveStatus, voter) + } else { + r1 = ret.Get(1).(bool) + } + + if rf, ok := ret.Get(2).(func(types.Context, string, int64, common.ReceiveStatus, string) observertypes.Ballot); ok { + r2 = rf(ctx, ballotIndex, outTxChainID, receiveStatus, voter) + } else { + r2 = ret.Get(2).(observertypes.Ballot) + } + + if rf, ok := ret.Get(3).(func(types.Context, string, int64, common.ReceiveStatus, string) string); ok { + r3 = rf(ctx, ballotIndex, outTxChainID, receiveStatus, voter) + } else { + r3 = ret.Get(3).(string) + } + + if rf, ok := ret.Get(4).(func(types.Context, string, int64, common.ReceiveStatus, string) error); ok { + r4 = rf(ctx, ballotIndex, outTxChainID, receiveStatus, voter) + } else { + r4 = ret.Error(4) + } + + return r0, r1, r2, r3, r4 +} + // NewCrosschainObserverKeeper creates a new instance of CrosschainObserverKeeper. 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 NewCrosschainObserverKeeper(t interface { diff --git a/testutil/keeper/mocks/mocks.go b/testutil/keeper/mocks/mocks.go index ec32911a3f..1156061b90 100644 --- a/testutil/keeper/mocks/mocks.go +++ b/testutil/keeper/mocks/mocks.go @@ -4,6 +4,7 @@ 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" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) /** @@ -59,6 +60,10 @@ type FungibleEVMKeeper interface { fungibletypes.EVMKeeper } +/** + * Emissions Mocks + */ + //go:generate mockery --name EmissionAccountKeeper --filename account.go --case underscore --output ./emissions type EmissionAccountKeeper interface { emissionstypes.AccountKeeper @@ -78,3 +83,17 @@ type EmissionStakingKeeper interface { type EmissionObserverKeeper interface { emissionstypes.ObserverKeeper } + +/** + * Observer Mocks + */ + +//go:generate mockery --name ObserverStakingKeeper --filename staking.go --case underscore --output ./observer +type ObserverStakingKeeper interface { + observertypes.StakingKeeper +} + +//go:generate mockery --name ObserverSlashingKeeper --filename slashing.go --case underscore --output ./observer +type ObserverSlashingKeeper interface { + observertypes.SlashingKeeper +} diff --git a/testutil/keeper/mocks/observer/slashing.go b/testutil/keeper/mocks/observer/slashing.go new file mode 100644 index 0000000000..a7793ef8dc --- /dev/null +++ b/testutil/keeper/mocks/observer/slashing.go @@ -0,0 +1,53 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// ObserverSlashingKeeper is an autogenerated mock type for the ObserverSlashingKeeper type +type ObserverSlashingKeeper struct { + mock.Mock +} + +// IsTombstoned provides a mock function with given fields: ctx, addr +func (_m *ObserverSlashingKeeper) IsTombstoned(ctx types.Context, addr types.ConsAddress) bool { + ret := _m.Called(ctx, addr) + + if len(ret) == 0 { + panic("no return value specified for IsTombstoned") + } + + var r0 bool + if rf, ok := ret.Get(0).(func(types.Context, types.ConsAddress) bool); ok { + r0 = rf(ctx, addr) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// SetValidatorSigningInfo provides a mock function with given fields: ctx, address, info +func (_m *ObserverSlashingKeeper) SetValidatorSigningInfo(ctx types.Context, address types.ConsAddress, info slashingtypes.ValidatorSigningInfo) { + _m.Called(ctx, address, info) +} + +// NewObserverSlashingKeeper creates a new instance of ObserverSlashingKeeper. 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 NewObserverSlashingKeeper(t interface { + mock.TestingT + Cleanup(func()) +}) *ObserverSlashingKeeper { + mock := &ObserverSlashingKeeper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/mocks/observer/staking.go b/testutil/keeper/mocks/observer/staking.go new file mode 100644 index 0000000000..90007b6c35 --- /dev/null +++ b/testutil/keeper/mocks/observer/staking.go @@ -0,0 +1,91 @@ +// Code generated by mockery v2.38.0. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + + types "github.com/cosmos/cosmos-sdk/types" +) + +// ObserverStakingKeeper is an autogenerated mock type for the ObserverStakingKeeper type +type ObserverStakingKeeper struct { + mock.Mock +} + +// GetDelegation provides a mock function with given fields: ctx, delAddr, valAddr +func (_m *ObserverStakingKeeper) GetDelegation(ctx types.Context, delAddr types.AccAddress, valAddr types.ValAddress) (stakingtypes.Delegation, bool) { + ret := _m.Called(ctx, delAddr, valAddr) + + if len(ret) == 0 { + panic("no return value specified for GetDelegation") + } + + var r0 stakingtypes.Delegation + var r1 bool + if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress, types.ValAddress) (stakingtypes.Delegation, bool)); ok { + return rf(ctx, delAddr, valAddr) + } + if rf, ok := ret.Get(0).(func(types.Context, types.AccAddress, types.ValAddress) stakingtypes.Delegation); ok { + r0 = rf(ctx, delAddr, valAddr) + } else { + r0 = ret.Get(0).(stakingtypes.Delegation) + } + + if rf, ok := ret.Get(1).(func(types.Context, types.AccAddress, types.ValAddress) bool); ok { + r1 = rf(ctx, delAddr, valAddr) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// GetValidator provides a mock function with given fields: ctx, addr +func (_m *ObserverStakingKeeper) GetValidator(ctx types.Context, addr types.ValAddress) (stakingtypes.Validator, bool) { + ret := _m.Called(ctx, addr) + + if len(ret) == 0 { + panic("no return value specified for GetValidator") + } + + var r0 stakingtypes.Validator + var r1 bool + if rf, ok := ret.Get(0).(func(types.Context, types.ValAddress) (stakingtypes.Validator, bool)); ok { + return rf(ctx, addr) + } + if rf, ok := ret.Get(0).(func(types.Context, types.ValAddress) stakingtypes.Validator); ok { + r0 = rf(ctx, addr) + } else { + r0 = ret.Get(0).(stakingtypes.Validator) + } + + if rf, ok := ret.Get(1).(func(types.Context, types.ValAddress) bool); ok { + r1 = rf(ctx, addr) + } else { + r1 = ret.Get(1).(bool) + } + + return r0, r1 +} + +// SetValidator provides a mock function with given fields: ctx, validator +func (_m *ObserverStakingKeeper) SetValidator(ctx types.Context, validator stakingtypes.Validator) { + _m.Called(ctx, validator) +} + +// NewObserverStakingKeeper creates a new instance of ObserverStakingKeeper. 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 NewObserverStakingKeeper(t interface { + mock.TestingT + Cleanup(func()) +}) *ObserverStakingKeeper { + mock := &ObserverStakingKeeper{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/testutil/keeper/observer.go b/testutil/keeper/observer.go index 946dac84c6..1a6dd2e257 100644 --- a/testutil/keeper/observer.go +++ b/testutil/keeper/observer.go @@ -3,20 +3,36 @@ package keeper import ( "testing" - slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" - "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" tmdb "github.com/tendermint/tm-db" + observermocks "github.com/zeta-chain/zetacore/testutil/keeper/mocks/observer" "github.com/zeta-chain/zetacore/x/observer/keeper" "github.com/zeta-chain/zetacore/x/observer/types" ) +// ObserverMockOptions represents options for instantiating an observer keeper with mocks +type ObserverMockOptions struct { + UseStakingMock bool + UseSlashingMock bool +} + +var ( + ObserverMocksAll = ObserverMockOptions{ + UseStakingMock: true, + UseSlashingMock: true, + } + ObserverNoMocks = ObserverMockOptions{} +) + func initObserverKeeper( cdc codec.Codec, db *tmdb.MemDB, @@ -40,8 +56,8 @@ func initObserverKeeper( ) } -// ObserverKeeper instantiates an observer keeper for testing purposes -func ObserverKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { +// ObserverKeeperWithMocks instantiates an observer keeper for testing purposes with the option to mock specific keepers +func ObserverKeeperWithMocks(t testing.TB, mockOptions ObserverMockOptions) (*keeper.Keeper, sdk.Context, SDKKeepers) { storeKey := sdk.NewKVStoreKey(types.StoreKey) memStoreKey := storetypes.NewMemoryStoreKey(types.MemStoreKey) @@ -66,16 +82,67 @@ func ObserverKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) { // Add a proposer to the context ctx = sdkKeepers.InitBlockProposer(t, ctx) + // Initialize mocks for mocked keepers + var stakingKeeper types.StakingKeeper = sdkKeepers.StakingKeeper + var slashingKeeper types.SlashingKeeper = sdkKeepers.SlashingKeeper + if mockOptions.UseStakingMock { + stakingKeeper = observermocks.NewObserverStakingKeeper(t) + } + if mockOptions.UseSlashingMock { + slashingKeeper = observermocks.NewObserverSlashingKeeper(t) + } + k := keeper.NewKeeper( cdc, storeKey, memStoreKey, sdkKeepers.ParamsKeeper.Subspace(types.ModuleName), - sdkKeepers.StakingKeeper, - sdkKeepers.SlashingKeeper, + stakingKeeper, + slashingKeeper, ) k.SetParams(ctx, types.DefaultParams()) - return k, ctx + return k, ctx, sdkKeepers +} + +// ObserverKeeper instantiates an observer keeper for testing purposes +func ObserverKeeper(t testing.TB) (*keeper.Keeper, sdk.Context, SDKKeepers) { + return ObserverKeeperWithMocks(t, ObserverNoMocks) +} + +// GetObserverStakingMock returns a new observer staking keeper mock +func GetObserverStakingMock(t testing.TB, keeper *keeper.Keeper) *ObserverMockStakingKeeper { + k, ok := keeper.GetStakingKeeper().(*observermocks.ObserverStakingKeeper) + require.True(t, ok) + return &ObserverMockStakingKeeper{ + ObserverStakingKeeper: k, + } +} + +// GetObserverSlashingMock returns a new observer slashing keeper mock +func GetObserverSlashingMock(t testing.TB, keeper *keeper.Keeper) *ObserverMockSlashingKeeper { + k, ok := keeper.GetSlashingKeeper().(*observermocks.ObserverSlashingKeeper) + require.True(t, ok) + return &ObserverMockSlashingKeeper{ + ObserverSlashingKeeper: k, + } +} + +// ObserverMockStakingKeeper is a wrapper of the observer staking keeper mock that add methods to mock the GetValidator method +type ObserverMockStakingKeeper struct { + *observermocks.ObserverStakingKeeper +} + +func (m *ObserverMockStakingKeeper) MockGetValidator(validator stakingtypes.Validator) { + m.On("GetValidator", mock.Anything, mock.Anything).Return(validator, true) +} + +// ObserverMockSlashingKeeper is a wrapper of the observer slashing keeper mock that add methods to mock the IsTombstoned method +type ObserverMockSlashingKeeper struct { + *observermocks.ObserverSlashingKeeper +} + +func (m *ObserverMockSlashingKeeper) MockIsTombstoned(isTombstoned bool) { + m.On("IsTombstoned", mock.Anything, mock.Anything).Return(isTombstoned) } diff --git a/testutil/network/network_setup.go b/testutil/network/network_setup.go index 08bc9277fd..987bb38fe5 100644 --- a/testutil/network/network_setup.go +++ b/testutil/network/network_setup.go @@ -528,6 +528,8 @@ func (n *Network) WaitForHeightWithTimeout(h int64, t time.Duration) (int64, err if latestHeight >= h { return latestHeight, nil } + } else if err != nil { + fmt.Printf("error trying to fetch block height: %v\n", err) } } } diff --git a/testutil/sample/sample.go b/testutil/sample/sample.go index ba197ea932..020ff9104b 100644 --- a/testutil/sample/sample.go +++ b/testutil/sample/sample.go @@ -34,6 +34,11 @@ func newRandFromStringSeed(t *testing.T, s string) *rand.Rand { return newRandFromSeed(int64(h.Sum64())) } +// Rand returns a new random number generator +func Rand() *rand.Rand { + return newRandFromSeed(42) +} + // PubKey returns a sample account PubKey func PubKey(r *rand.Rand) cryptotypes.PubKey { seed := []byte(strconv.Itoa(r.Int())) diff --git a/x/crosschain/client/integrationtests/inbound_voter_test.go b/x/crosschain/client/integrationtests/inbound_voter_test.go index b838250745..50cf041a64 100644 --- a/x/crosschain/client/integrationtests/inbound_voter_test.go +++ b/x/crosschain/client/integrationtests/inbound_voter_test.go @@ -241,13 +241,10 @@ func (s *IntegrationTestSuite) TestCCTXInboundVoter() { s.Require().NoError(s.network.WaitForNBlocks(2)) out, err := clitestutil.ExecTestCLICmd(broadcaster.ClientCtx, observercli.CmdListPendingNonces(), []string{"--output", "json"}) s.Require().NoError(err) - //fmt.Println(out.String()) out, err = clitestutil.ExecTestCLICmd(broadcaster.ClientCtx, observercli.CmdGetSupportedChains(), []string{"--output", "json"}) s.Require().NoError(err) - //fmt.Println(out.String()) out, err = clitestutil.ExecTestCLICmd(broadcaster.ClientCtx, crosschaincli.CmdListGasPrice(), []string{"--output", "json"}) s.Require().NoError(err) - //fmt.Println(out.String()) // Vote the inbound tx for _, val := range s.network.Validators { @@ -264,8 +261,9 @@ func (s *IntegrationTestSuite) TestCCTXInboundVoter() { message = message + "falseVote" } signedTx := BuildSignedInboundVote(s.T(), val, s.cfg.BondDenom, account, message, i) - out, err = clitestutil.ExecTestCLICmd(broadcaster.ClientCtx, authcli.GetBroadcastCommand(), []string{signedTx.Name(), "--broadcast-mode", "sync"}) + out, err = clitestutil.ExecTestCLICmd(broadcaster.ClientCtx, authcli.GetBroadcastCommand(), []string{signedTx.Name(), "--broadcast-mode", "block"}) s.Require().NoError(err) + fmt.Println(out.String()) } s.Require().NoError(s.network.WaitForNBlocks(2)) @@ -277,15 +275,15 @@ func (s *IntegrationTestSuite) TestCCTXInboundVoter() { s.NoError(broadcaster.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &ballot)) // Check the vote in the ballot - s.Assert().Equal(len(test.votes), len(ballot.Voters)) + s.Require().Equal(len(test.votes), len(ballot.Voters)) for _, vote := range ballot.Voters { if test.votes[vote.VoterAddress] == observerTypes.VoteType_FailureObservation { s.Assert().Equal(observerTypes.VoteType_NotYetVoted.String(), vote.VoteType.String()) continue } - s.Assert().Equal(test.votes[vote.VoterAddress].String(), vote.VoteType.String()) + s.Assert().Equal(test.votes[vote.VoterAddress].String(), vote.VoteType.String(), "incorrect vote for voter: %s", vote.VoterAddress) } - s.Assert().Equal(test.ballotResult.String(), ballot.BallotStatus.String()) + s.Require().Equal(test.ballotResult.String(), ballot.BallotStatus.String()) // Get the cctx and check its status cctxIdentifier := ballotIdentifier @@ -298,7 +296,7 @@ func (s *IntegrationTestSuite) TestCCTXInboundVoter() { s.Require().Contains(out.String(), "not found") } else { s.NoError(broadcaster.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &cctx)) - s.Assert().Equal(test.cctxStatus.String(), cctx.CrossChainTx.CctxStatus.Status.String(), cctx.CrossChainTx.CctxStatus.StatusMessage) + s.Require().Equal(test.cctxStatus.String(), cctx.CrossChainTx.CctxStatus.Status.String(), cctx.CrossChainTx.CctxStatus.StatusMessage) } }) } diff --git a/x/crosschain/client/integrationtests/suite.go b/x/crosschain/client/integrationtests/suite.go index 65a45bc4b3..b5f16a683d 100644 --- a/x/crosschain/client/integrationtests/suite.go +++ b/x/crosschain/client/integrationtests/suite.go @@ -52,7 +52,7 @@ func (s *IntegrationTestSuite) SetupSuite() { network.AddCrosschainData(s.T(), 0, s.cfg.GenesisState, s.cfg.Codec) network.AddObserverData(s.T(), 0, s.cfg.GenesisState, s.cfg.Codec, nil) net, err := network.New(s.T(), app.NodeDir, s.cfg) - s.Assert().NoError(err) + s.Require().NoError(err) s.network = net time.Sleep(3 * time.Second) _, err = s.network.WaitForHeight(1) diff --git a/x/crosschain/keeper/cctx.go b/x/crosschain/keeper/cctx.go index 7dad9f4197..d6fb9b117b 100644 --- a/x/crosschain/keeper/cctx.go +++ b/x/crosschain/keeper/cctx.go @@ -99,13 +99,21 @@ func (k Keeper) RemoveCrossChainTx(ctx sdk.Context, index string) { store.Delete(types.KeyPrefix(index)) } -func (k Keeper) CreateNewCCTX(ctx sdk.Context, msg *types.MsgVoteOnObservedInboundTx, index string, tssPubkey string, s types.CctxStatus, senderChain, receiverChain *common.Chain) types.CrossChainTx { +func (k Keeper) CreateNewCCTX( + ctx sdk.Context, + msg *types.MsgVoteOnObservedInboundTx, + index string, + tssPubkey string, + s types.CctxStatus, + senderChainID, + receiverChainID int64, +) types.CrossChainTx { if msg.TxOrigin == "" { msg.TxOrigin = msg.Sender } inboundParams := &types.InboundTxParams{ Sender: msg.Sender, - SenderChainId: senderChain.ChainId, + SenderChainId: senderChainID, TxOrigin: msg.TxOrigin, Asset: msg.Asset, Amount: msg.Amount, @@ -118,7 +126,7 @@ func (k Keeper) CreateNewCCTX(ctx sdk.Context, msg *types.MsgVoteOnObservedInbou outBoundParams := &types.OutboundTxParams{ Receiver: msg.Receiver, - ReceiverChainId: receiverChain.ChainId, + ReceiverChainId: receiverChainID, OutboundTxHash: "", OutboundTxTssNonce: 0, OutboundTxGasLimit: msg.GasLimit, diff --git a/x/crosschain/keeper/evm_deposit.go b/x/crosschain/keeper/evm_deposit.go index 50d8277f15..c3392e005e 100644 --- a/x/crosschain/keeper/evm_deposit.go +++ b/x/crosschain/keeper/evm_deposit.go @@ -22,7 +22,7 @@ func (k Keeper) HandleEVMDeposit( ctx sdk.Context, cctx *types.CrossChainTx, msg types.MsgVoteOnObservedInboundTx, - senderChain *common.Chain, + senderChainID int64, ) (bool, error) { to := ethcommon.HexToAddress(msg.Receiver) var ethTxHash ethcommon.Hash @@ -51,7 +51,7 @@ func (k Keeper) HandleEVMDeposit( to = parsedAddress } - from, err := senderChain.DecodeAddress(msg.Sender) + from, err := common.DecodeAddressFromChainID(senderChainID, msg.Sender) if err != nil { return false, fmt.Errorf("HandleEVMDeposit: unable to decode address: %s", err.Error()) } @@ -61,7 +61,7 @@ func (k Keeper) HandleEVMDeposit( from, to, msg.Amount.BigInt(), - senderChain, + senderChainID, data, msg.CoinType, msg.Asset, diff --git a/x/crosschain/keeper/evm_deposit_test.go b/x/crosschain/keeper/evm_deposit_test.go index 65483d89c1..dd6528e639 100644 --- a/x/crosschain/keeper/evm_deposit_test.go +++ b/x/crosschain/keeper/evm_deposit_test.go @@ -39,7 +39,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { Amount: math.NewUintFromBigInt(amount), CoinType: common.CoinType_Zeta, }, - nil, + 0, ) require.NoError(t, err) require.False(t, reverted) @@ -68,7 +68,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { Amount: math.NewUintFromBigInt(amount), CoinType: common.CoinType_Zeta, }, - nil, + 0, ) require.ErrorIs(t, err, errDeposit) require.False(t, reverted) @@ -80,7 +80,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() @@ -124,7 +124,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() @@ -169,7 +169,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() @@ -214,7 +214,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() @@ -258,7 +258,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() @@ -302,7 +302,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() @@ -343,7 +343,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { k, ctx, _, _ := keepertest.CrosschainKeeperWithMocks(t, keepertest.CrosschainMockOptions{ UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) _, err := k.HandleEVMDeposit( ctx, @@ -366,7 +366,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() @@ -409,7 +409,7 @@ func TestMsgServer_HandleEVMDeposit(t *testing.T) { UseFungibleMock: true, }) - senderChain := getValidEthChain(t) + senderChain := getValidEthChainID(t) fungibleMock := keepertest.GetCrosschainFungibleMock(t, k) receiver := sample.EthAddress() diff --git a/x/crosschain/keeper/evm_hooks.go b/x/crosschain/keeper/evm_hooks.go index 998da57672..7eca564f3a 100644 --- a/x/crosschain/keeper/evm_hooks.go +++ b/x/crosschain/keeper/evm_hooks.go @@ -168,7 +168,15 @@ func (k Keeper) ProcessZRC20WithdrawalEvent(ctx sdk.Context, event *zrc20.ZRC20W ) sendHash := msg.Digest() - cctx := k.CreateNewCCTX(ctx, msg, sendHash, tss.TssPubkey, types.CctxStatus_PendingOutbound, &senderChain, receiverChain) + cctx := k.CreateNewCCTX( + ctx, + msg, + sendHash, + tss.TssPubkey, + types.CctxStatus_PendingOutbound, + senderChain.ChainId, + receiverChain.ChainId, + ) // Get gas price and amount gasprice, found := k.GetGasPrice(ctx, receiverChain.ChainId) @@ -208,7 +216,7 @@ func (k Keeper) ProcessZetaSentEvent(ctx sdk.Context, event *connectorzevm.ZetaC // Validation if we want to send ZETA to an external chain, but there is no ZETA token. chainParams, found := k.zetaObserverKeeper.GetChainParamsByChainID(ctx, receiverChain.ChainId) if !found { - return types.ErrNotFoundChainParams + return observertypes.ErrChainParamsNotFound } if receiverChain.IsExternalChain() && chainParams.ZetaTokenContractAddress == "" { return types.ErrUnableToSendCoinType @@ -239,7 +247,15 @@ func (k Keeper) ProcessZetaSentEvent(ctx sdk.Context, event *connectorzevm.ZetaC sendHash := msg.Digest() // Create the CCTX - cctx := k.CreateNewCCTX(ctx, msg, sendHash, tss.TssPubkey, types.CctxStatus_PendingOutbound, &senderChain, receiverChain) + cctx := k.CreateNewCCTX( + ctx, + msg, + sendHash, + tss.TssPubkey, + types.CctxStatus_PendingOutbound, + senderChain.ChainId, + receiverChain.ChainId, + ) if err := k.PayGasAndUpdateCctx( ctx, diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx.go b/x/crosschain/keeper/msg_server_vote_inbound_tx.go index 930bbd8421..db287e6b82 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx.go @@ -8,8 +8,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/x/crosschain/types" - observerKeeper "github.com/zeta-chain/zetacore/x/observer/keeper" - observerTypes "github.com/zeta-chain/zetacore/x/observer/types" ) // FIXME: use more specific error types & codes @@ -57,74 +55,60 @@ import ( // Only observer validators are authorized to broadcast this message. func (k msgServer) VoteOnObservedInboundTx(goCtx context.Context, msg *types.MsgVoteOnObservedInboundTx) (*types.MsgVoteOnObservedInboundTxResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - - observationType := observerTypes.ObservationType_InBoundTx - if !k.zetaObserverKeeper.IsInboundEnabled(ctx) { - return nil, types.ErrNotEnoughPermissions - } - - // 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, 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, cosmoserrors.Wrap(types.ErrUnsupportedChain, fmt.Sprintf("ChainID %d, Observation %s", msg.ReceiverChain, observationType.String())) - } - tssPub := "" - tss, tssFound := k.zetaObserverKeeper.GetTSS(ctx) - if tssFound { - tssPub = tss.TssPubkey - } - // IsAuthorized does various checks against the list of observer mappers - if ok := k.zetaObserverKeeper.IsAuthorized(ctx, msg.Creator); !ok { - return nil, observerTypes.ErrNotAuthorizedPolicy - } - index := msg.Digest() - // Add votes and Set Ballot - // GetBallot checks against the supported chains list before querying for Ballot - ballot, isNew, err := k.zetaObserverKeeper.FindBallot(ctx, index, observationChain, observationType) + + // vote on inbound ballot + // use a temporary context to not commit any ballot state change in case of error + tmpCtx, commit := ctx.CacheContext() + finalized, isNew, err := k.zetaObserverKeeper.VoteOnInboundBallot( + tmpCtx, + msg.SenderChainId, + msg.ReceiverChain, + msg.CoinType, + msg.Creator, + index, + msg.InTxHash, + ) if err != nil { return nil, err } + + // If it is a new ballot, check if an inbound with the same hash, sender chain and event index has already been finalized + // This may happen if the same inbound is observed twice where msg.Digest gives a different index + // This check prevents double spending if isNew { - // Check if the inbound has already been processed. - if k.IsFinalizedInbound(ctx, 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)) + if k.IsFinalizedInbound(tmpCtx, 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()) - } - // AddVoteToBallot adds a vote and sets the ballot - ballot, err = k.zetaObserverKeeper.AddVoteToBallot(ctx, ballot, msg.Creator, observerTypes.VoteType_SuccessObservation) - if err != nil { - return nil, err } + commit() - _, isFinalizedInThisBlock := k.zetaObserverKeeper.CheckIfFinalizingVote(ctx, ballot) - if !isFinalizedInThisBlock { - // Return nil here to add vote to ballot and commit state + // If the ballot is not finalized return nil here to add vote to commit state + if !finalized { return &types.MsgVoteOnObservedInboundTxResponse{}, nil } - // Validation if we want to send ZETA to external chain, but there is no ZETA token. - if receiverChain.IsExternalChain() { - chainParams, found := k.zetaObserverKeeper.GetChainParamsByChainID(ctx, receiverChain.ChainId) - if !found { - return nil, types.ErrNotFoundChainParams - } - if chainParams.ZetaTokenContractAddress == "" && msg.CoinType == common.CoinType_Zeta { - return nil, types.ErrUnableToSendCoinType - } + // get the latest TSS to set the TSS public key in the CCTX + tssPub := "" + tss, tssFound := k.zetaObserverKeeper.GetTSS(ctx) + if tssFound { + tssPub = tss.TssPubkey } - // ****************************************************************************** - // below only happens when ballot is finalized: exactly when threshold vote is in - // ****************************************************************************** + // create the CCTX + cctx := k.CreateNewCCTX( + ctx, + msg, + index, + tssPub, + types.CctxStatus_PendingInbound, + msg.SenderChainId, + msg.ReceiverChain, + ) - // Inbound Ballot has been finalized , Create CCTX - cctx := k.CreateNewCCTX(ctx, msg, index, tssPub, types.CctxStatus_PendingInbound, observationChain, receiverChain) defer func() { EmitEventInboundFinalized(ctx, &cctx) k.AddFinalizedInbound(ctx, msg.InTxHash, msg.SenderChainId, msg.EventIndex) @@ -134,11 +118,12 @@ func (k msgServer) VoteOnObservedInboundTx(goCtx context.Context, msg *types.Msg k.RemoveInTxTrackerIfExists(ctx, cctx.InboundTxParams.SenderChainId, cctx.InboundTxParams.InboundTxObservedHash) k.SetCctxAndNonceToCctxAndInTxHashToCctx(ctx, cctx) }() + // FinalizeInbound updates CCTX Prices and Nonce // Aborts is any of the updates fail - if receiverChain.IsZetaChain() { + if common.IsZetaChain(msg.ReceiverChain) { tmpCtx, commit := ctx.CacheContext() - isContractReverted, err := k.HandleEVMDeposit(tmpCtx, &cctx, *msg, observationChain) + isContractReverted, err := k.HandleEVMDeposit(tmpCtx, &cctx, *msg, msg.SenderChainId) if err != nil && !isContractReverted { // exceptional case; internal error; should abort CCTX cctx.CctxStatus.ChangeStatus(types.CctxStatus_Aborted, err.Error()) @@ -201,11 +186,11 @@ func (k msgServer) VoteOnObservedInboundTx(goCtx context.Context, msg *types.Msg } // Receiver is not ZetaChain: Cross Chain SWAP - tmpCtx, commit := ctx.CacheContext() + tmpCtx, commit = ctx.CacheContext() err = func() error { err := k.PayGasAndUpdateCctx( tmpCtx, - receiverChain.ChainId, + msg.ReceiverChain, &cctx, cctx.InboundTxParams.Amount, false, @@ -213,7 +198,7 @@ func (k msgServer) VoteOnObservedInboundTx(goCtx context.Context, msg *types.Msg if err != nil { return err } - return k.UpdateNonce(tmpCtx, receiverChain.ChainId, &cctx) + return k.UpdateNonce(tmpCtx, msg.ReceiverChain, &cctx) }() if err != nil { // do not commit anything here as the CCTX should be aborted 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 1605a37e33..803de84576 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "testing" - //"github.com/zeta-chain/zetacore/common" sdkmath "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" @@ -13,7 +12,6 @@ import ( "github.com/zeta-chain/zetacore/testutil/sample" "github.com/zeta-chain/zetacore/x/crosschain/keeper" "github.com/zeta-chain/zetacore/x/crosschain/types" - observerTypes "github.com/zeta-chain/zetacore/x/observer/types" observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) @@ -35,6 +33,9 @@ func setObservers(t *testing.T, k *keeper.Keeper, ctx sdk.Context, zk keepertest }) return validatorAddressListFormatted } + +// TODO: Complete the test cases +// https://github.com/zeta-chain/node/issues/1542 func TestKeeper_VoteOnObservedInboundTx(t *testing.T) { t.Run("successfully vote on evm deposit", func(t *testing.T) { k, ctx, _, zk := keepertest.CrosschainKeeper(t) @@ -59,97 +60,100 @@ 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) + 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) cctx, found := k.GetCrossChainTx(ctx, msg.Digest()) require.True(t, found) require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_OutboundMined) require.Equal(t, cctx.InboundTxParams.TxFinalizationStatus, types.TxFinalizationStatus_Executed) }) - // TODO : https://github.com/zeta-chain/node/issues/1542 -} -/* -Potential Double Event Submission -*/ -func TestNoDoubleEventProtections(t *testing.T) { - k, ctx, _, zk := keepertest.CrosschainKeeper(t) + t.Run("prevent double event submission", func(t *testing.T) { + k, ctx, _, zk := keepertest.CrosschainKeeper(t) - // MsgServer for the crosschain keeper - msgServer := keeper.NewMsgServerImpl(*k) + // MsgServer for the crosschain keeper + msgServer := keeper.NewMsgServerImpl(*k) - // Set the chain ids we want to use to be valid - params := observertypes.DefaultParams() - zk.ObserverKeeper.SetParams( - ctx, params, - ) + // Set the chain ids we want to use to be valid + params := observertypes.DefaultParams() + zk.ObserverKeeper.SetParams( + ctx, params, + ) - // Convert the validator address into a user address. - validators := k.GetStakingKeeper().GetAllValidators(ctx) - validatorAddress := validators[0].OperatorAddress - valAddr, _ := sdk.ValAddressFromBech32(validatorAddress) - addresstmp, _ := sdk.AccAddressFromHexUnsafe(hex.EncodeToString(valAddr.Bytes())) - validatorAddr := addresstmp.String() + // Convert the validator address into a user address. + validators := k.GetStakingKeeper().GetAllValidators(ctx) + validatorAddress := validators[0].OperatorAddress + valAddr, _ := sdk.ValAddressFromBech32(validatorAddress) + addresstmp, _ := sdk.AccAddressFromHexUnsafe(hex.EncodeToString(valAddr.Bytes())) + validatorAddr := addresstmp.String() - // Add validator to the observer list for voting - zk.ObserverKeeper.SetObserverSet(ctx, observertypes.ObserverSet{ - ObserverList: []string{validatorAddr}, - }) + // Add validator to the observer list for voting + zk.ObserverKeeper.SetObserverSet(ctx, observertypes.ObserverSet{ + ObserverList: []string{validatorAddr}, + }) - // Vote on the FIRST message. - msg := &types.MsgVoteOnObservedInboundTx{ - Creator: validatorAddr, - Sender: "0x954598965C2aCdA2885B037561526260764095B8", - SenderChainId: 1337, // ETH - Receiver: "0x954598965C2aCdA2885B037561526260764095B8", - ReceiverChain: 101, // zetachain - Amount: sdkmath.NewUintFromString("10000000"), - Message: "", - InBlockHeight: 1, - GasLimit: 1000000000, - InTxHash: "0x7a900ef978743f91f57ca47c6d1a1add75df4d3531da17671e9cf149e1aefe0b", - CoinType: 0, // zeta - TxOrigin: "0x954598965C2aCdA2885B037561526260764095B8", - Asset: "", - EventIndex: 1, - } - _, err := msgServer.VoteOnObservedInboundTx( - ctx, - msg, - ) - require.NoError(t, err) + // Vote on the FIRST message. + msg := &types.MsgVoteOnObservedInboundTx{ + Creator: validatorAddr, + Sender: "0x954598965C2aCdA2885B037561526260764095B8", + SenderChainId: 1337, // ETH + Receiver: "0x954598965C2aCdA2885B037561526260764095B8", + ReceiverChain: 101, // zetachain + Amount: sdkmath.NewUintFromString("10000000"), + Message: "", + InBlockHeight: 1, + GasLimit: 1000000000, + InTxHash: "0x7a900ef978743f91f57ca47c6d1a1add75df4d3531da17671e9cf149e1aefe0b", + CoinType: 0, // zeta + TxOrigin: "0x954598965C2aCdA2885B037561526260764095B8", + Asset: "", + EventIndex: 1, + } + _, err := msgServer.VoteOnObservedInboundTx( + ctx, + msg, + ) + require.NoError(t, err) - // 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) - //Perform the SAME event. Except, this time, we resubmit the event. - msg2 := &types.MsgVoteOnObservedInboundTx{ - Creator: validatorAddr, - Sender: "0x954598965C2aCdA2885B037561526260764095B8", - SenderChainId: 1337, - Receiver: "0x954598965C2aCdA2885B037561526260764095B8", - ReceiverChain: 101, - Amount: sdkmath.NewUintFromString("10000000"), - Message: "", - InBlockHeight: 1, - GasLimit: 1000000001, // <---- Change here - InTxHash: "0x7a900ef978743f91f57ca47c6d1a1add75df4d3531da17671e9cf149e1aefe0b", - CoinType: 0, - TxOrigin: "0x954598965C2aCdA2885B037561526260764095B8", - Asset: "", - EventIndex: 1, - } + // 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) + //Perform the SAME event. Except, this time, we resubmit the event. + msg2 := &types.MsgVoteOnObservedInboundTx{ + Creator: validatorAddr, + Sender: "0x954598965C2aCdA2885B037561526260764095B8", + SenderChainId: 1337, + Receiver: "0x954598965C2aCdA2885B037561526260764095B8", + ReceiverChain: 101, + Amount: sdkmath.NewUintFromString("10000000"), + Message: "", + InBlockHeight: 1, + GasLimit: 1000000001, // <---- Change here + InTxHash: "0x7a900ef978743f91f57ca47c6d1a1add75df4d3531da17671e9cf149e1aefe0b", + CoinType: 0, + TxOrigin: "0x954598965C2aCdA2885B037561526260764095B8", + Asset: "", + EventIndex: 1, + } - _, err = msgServer.VoteOnObservedInboundTx( - ctx, - msg2, - ) - require.ErrorIs(t, err, types.ErrObservedTxAlreadyFinalized) - _, found = zk.ObserverKeeper.GetBallot(ctx, msg2.Digest()) - require.False(t, found) + _, err = msgServer.VoteOnObservedInboundTx( + ctx, + msg2, + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrObservedTxAlreadyFinalized) + _, found = zk.ObserverKeeper.GetBallot(ctx, msg2.Digest()) + require.False(t, found) + }) } -func TestStatus_StatusTransition(t *testing.T) { + +func TestStatus_ChangeStatus(t *testing.T) { tt := []struct { Name string Status types.Status diff --git a/x/crosschain/keeper/msg_server_vote_outbound_tx.go b/x/crosschain/keeper/msg_server_vote_outbound_tx.go index 72555df8cd..5c36a2abf9 100644 --- a/x/crosschain/keeper/msg_server_vote_outbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_outbound_tx.go @@ -14,8 +14,8 @@ import ( "github.com/rs/zerolog/log" "github.com/zeta-chain/zetacore/common" "github.com/zeta-chain/zetacore/x/crosschain/types" - observerKeeper "github.com/zeta-chain/zetacore/x/observer/keeper" - observerTypes "github.com/zeta-chain/zetacore/x/observer/types" + observerkeeper "github.com/zeta-chain/zetacore/x/observer/keeper" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" ) // VoteOnObservedOutboundTx casts a vote on an outbound transaction observed on a connected chain (after @@ -61,59 +61,45 @@ import ( // Only observer validators are authorized to broadcast this message. func (k msgServer) VoteOnObservedOutboundTx(goCtx context.Context, msg *types.MsgVoteOnObservedOutboundTx) (*types.MsgVoteOnObservedOutboundTxResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) - observationType := observerTypes.ObservationType_OutBoundTx - // Observer Chain already checked then inbound is created - /* EDGE CASE : Params updated in during the finalization process - i.e Inbound has been finalized but outbound is still pending - */ - observationChain := k.zetaObserverKeeper.GetSupportedChainFromChainID(ctx, msg.OutTxChain) - if observationChain == nil { - return nil, observerTypes.ErrSupportedChains - } - err := observerTypes.CheckReceiveStatus(msg.Status) - if err != nil { - return nil, err - } - //Check is msg.Creator is authorized to vote - if ok := k.zetaObserverKeeper.IsAuthorized(ctx, msg.Creator); !ok { - return nil, observerTypes.ErrNotAuthorizedPolicy - } - // Check if CCTX exists + // check if CCTX exists and if the nonce matches cctx, found := k.GetCrossChainTx(ctx, msg.CctxHash) if !found { return nil, cosmoserrors.Wrap(sdkerrors.ErrInvalidRequest, fmt.Sprintf("CCTX %s does not exist", msg.CctxHash)) } - if cctx.GetCurrentOutTxParam().OutboundTxTssNonce != msg.OutTxTssNonce { return nil, cosmoserrors.Wrap(sdkerrors.ErrInvalidRequest, fmt.Sprintf("OutTxTssNonce %d does not match CCTX OutTxTssNonce %d", msg.OutTxTssNonce, cctx.GetCurrentOutTxParam().OutboundTxTssNonce)) } + // get ballot index ballotIndex := msg.Digest() - // Add votes and Set Ballot - ballot, isNew, err := k.zetaObserverKeeper.FindBallot(ctx, ballotIndex, observationChain, observationType) + + // vote on outbound ballot + isFinalized, isNew, ballot, observationChain, err := k.zetaObserverKeeper.VoteOnOutboundBallot( + ctx, + ballotIndex, + msg.OutTxChain, + msg.Status, + msg.Creator) if err != nil { return nil, err } + + // if the ballot is new, set the index to the CCTX if isNew { - observerKeeper.EmitEventBallotCreated(ctx, ballot, msg.ObservedOutTxHash, observationChain.String()) + observerkeeper.EmitEventBallotCreated(ctx, ballot, msg.ObservedOutTxHash, observationChain) // Set this the first time when the ballot is created // The ballot might change if there are more votes in a different outbound ballot for this cctx hash cctx.GetCurrentOutTxParam().OutboundTxBallotIndex = ballotIndex - //k.SetCctxAndNonceToCctxAndInTxHashToCctx(ctx, cctx) - } - // AddVoteToBallot adds a vote and sets the ballot - ballot, err = k.zetaObserverKeeper.AddVoteToBallot(ctx, ballot, msg.Creator, observerTypes.ConvertReceiveStatusToVoteType(msg.Status)) - if err != nil { - return nil, err } - ballot, isFinalizedInThisBlock := k.zetaObserverKeeper.CheckIfFinalizingVote(ctx, ballot) - if !isFinalizedInThisBlock { - // Return nil here to add vote to ballot and commit state + // if not finalized commit state here + if !isFinalized { return &types.MsgVoteOnObservedOutboundTxResponse{}, nil } - if ballot.BallotStatus != observerTypes.BallotStatus_BallotFinalized_FailureObservation { + + // if ballot successful, the value received should be the out tx amount + if ballot.BallotStatus != observertypes.BallotStatus_BallotFinalized_FailureObservation { if !msg.ValueReceived.Equal(cctx.GetCurrentOutTxParam().Amount) { log.Error().Msgf("VoteOnObservedOutboundTx: Mint mismatch: %s value received vs %s cctx amount", msg.ValueReceived, @@ -141,15 +127,12 @@ func (k msgServer) VoteOnObservedOutboundTx(goCtx context.Context, msg *types.Ms return nil, types.ErrCannotFindTSSKeys } - // FinalizeOutbound sets final status for a successful vote - // FinalizeOutbound updates CCTX Prices and Nonce for a revert - tmpCtx, commit := ctx.CacheContext() err = func() error { //err = FinalizeOutbound(k, ctx, &cctx, msg, ballot.BallotStatus) cctx.GetCurrentOutTxParam().OutboundTxObservedExternalHeight = msg.ObservedOutTxBlockHeight oldStatus := cctx.CctxStatus.Status switch ballot.BallotStatus { - case observerTypes.BallotStatus_BallotFinalized_SuccessObservation: + case observertypes.BallotStatus_BallotFinalized_SuccessObservation: switch oldStatus { case types.CctxStatus_PendingRevert: cctx.CctxStatus.ChangeStatus(types.CctxStatus_Reverted, "") @@ -158,7 +141,7 @@ func (k msgServer) VoteOnObservedOutboundTx(goCtx context.Context, msg *types.Ms } newStatus := cctx.CctxStatus.Status.String() EmitOutboundSuccess(tmpCtx, msg, oldStatus.String(), newStatus, cctx) - case observerTypes.BallotStatus_BallotFinalized_FailureObservation: + case observertypes.BallotStatus_BallotFinalized_FailureObservation: if msg.CoinType == common.CoinType_Cmd || common.IsZetaChain(cctx.InboundTxParams.SenderChainId) { // if the cctx is of coin type cmd or the sender chain is zeta chain, then we do not revert, the cctx is aborted cctx.CctxStatus.ChangeStatus(types.CctxStatus_Aborted, "") diff --git a/x/crosschain/types/errors.go b/x/crosschain/types/errors.go index 680e9b6096..2c5a595eb5 100644 --- a/x/crosschain/types/errors.go +++ b/x/crosschain/types/errors.go @@ -7,7 +7,6 @@ import ( var ( ErrUnsupportedChain = errorsmod.Register(ModuleName, 1102, "chain parse error") ErrInvalidChainID = errorsmod.Register(ModuleName, 1101, "chain id cannot be negative") - ErrInvalidPubKeySet = errorsmod.Register(ModuleName, 1106, "invalid pubkeyset") ErrUnableToGetGasPrice = errorsmod.Register(ModuleName, 1107, "unable to get gas price") ErrNotEnoughZetaBurnt = errorsmod.Register(ModuleName, 1109, "not enough zeta burnt") ErrCannotFindReceiverNonce = errorsmod.Register(ModuleName, 1110, "cannot find receiver chain nonce") @@ -15,11 +14,9 @@ var ( ErrUnableToParseAddress = errorsmod.Register(ModuleName, 1115, "cannot parse address and data") ErrCannotProcessWithdrawal = errorsmod.Register(ModuleName, 1116, "cannot process withdrawal event") ErrForeignCoinNotFound = errorsmod.Register(ModuleName, 1118, "foreign coin not found for sender chain") - ErrNotEnoughPermissions = errorsmod.Register(ModuleName, 1119, "not enough permissions for current actions") ErrCannotFindPendingNonces = errorsmod.Register(ModuleName, 1121, "cannot find pending nonces") ErrCannotFindTSSKeys = errorsmod.Register(ModuleName, 1122, "cannot find TSS keys") ErrNonceMismatch = errorsmod.Register(ModuleName, 1123, "nonce mismatch") - ErrNotFoundChainParams = errorsmod.Register(ModuleName, 1126, "not found chain chain params") ErrUnableToSendCoinType = errorsmod.Register(ModuleName, 1127, "unable to send this coin type to a receiver chain") ErrInvalidAddress = errorsmod.Register(ModuleName, 1128, "invalid address") ErrDeployContract = errorsmod.Register(ModuleName, 1129, "unable to deploy contract") @@ -44,6 +41,4 @@ var ( ErrUnableProcessRefund = errorsmod.Register(ModuleName, 1148, "unable to process refund") ErrUnableToFindZetaAccounting = errorsmod.Register(ModuleName, 1149, "unable to find zeta accounting") ErrInsufficientZetaAmount = errorsmod.Register(ModuleName, 1150, "insufficient zeta amount") - - ErrProcessingZRC20Withdrawal = errorsmod.Register(ModuleName, 1151, "error processing zrc20 withdrawal") ) diff --git a/x/crosschain/types/expected_keepers.go b/x/crosschain/types/expected_keepers.go index 540600b73b..d8d73487fa 100644 --- a/x/crosschain/types/expected_keepers.go +++ b/x/crosschain/types/expected_keepers.go @@ -72,6 +72,22 @@ type ObserverKeeper interface { SetTssAndUpdateNonce(ctx sdk.Context, tss observertypes.TSS) RemoveFromPendingNonces(ctx sdk.Context, tss string, chainID int64, nonce int64) GetAllNonceToCctx(ctx sdk.Context) (list []observertypes.NonceToCctx) + VoteOnInboundBallot( + ctx sdk.Context, + senderChainID int64, + receiverChainID int64, + coinType common.CoinType, + voter string, + ballotIndex string, + inTxHash string, + ) (bool, bool, error) + VoteOnOutboundBallot( + ctx sdk.Context, + ballotIndex string, + outTxChainID int64, + receiveStatus common.ReceiveStatus, + voter string, + ) (bool, bool, observertypes.Ballot, string, error) GetSupportedChainFromChainID(ctx sdk.Context, chainID int64) *common.Chain GetSupportedChains(ctx sdk.Context) []*common.Chain } @@ -103,7 +119,7 @@ type FungibleKeeper interface { from []byte, to eth.Address, amount *big.Int, - senderChain *common.Chain, + senderChainID int64, data []byte, coinType common.CoinType, asset string, diff --git a/x/fungible/keeper/deposits.go b/x/fungible/keeper/deposits.go index 832613aa2c..3d8ebfb28b 100644 --- a/x/fungible/keeper/deposits.go +++ b/x/fungible/keeper/deposits.go @@ -26,7 +26,7 @@ func (k Keeper) ZRC20DepositAndCallContract( from []byte, to eth.Address, amount *big.Int, - senderChain *common.Chain, + senderChainID int64, data []byte, coinType common.CoinType, asset string, @@ -37,12 +37,12 @@ func (k Keeper) ZRC20DepositAndCallContract( // get foreign coin if coinType == common.CoinType_Gas { - coin, found = k.GetGasCoinForForeignCoin(ctx, senderChain.ChainId) + coin, found = k.GetGasCoinForForeignCoin(ctx, senderChainID) if !found { return nil, false, crosschaintypes.ErrGasCoinNotFound } } else { - coin, found = k.GetForeignCoinFromAsset(ctx, asset, senderChain.ChainId) + coin, found = k.GetForeignCoinFromAsset(ctx, asset, senderChainID) if !found { return nil, false, crosschaintypes.ErrForeignCoinNotFound } @@ -75,7 +75,7 @@ func (k Keeper) ZRC20DepositAndCallContract( context := systemcontract.ZContext{ Origin: from, Sender: eth.Address{}, - ChainID: big.NewInt(senderChain.ChainId), + ChainID: big.NewInt(senderChainID), } res, err := k.DepositZRC20AndCallContract(ctx, context, ZRC20Contract, to, amount, data) return res, true, err diff --git a/x/fungible/keeper/deposits_test.go b/x/fungible/keeper/deposits_test.go index 4131a1e00d..bfd3f7eecd 100644 --- a/x/fungible/keeper/deposits_test.go +++ b/x/fungible/keeper/deposits_test.go @@ -22,11 +22,11 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", "foobar") + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", "foobar") // deposit to := sample.EthAddress() @@ -53,12 +53,12 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId assetAddress := sample.EthAddress().String() // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := deployZRC20(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", assetAddress, "foobar") + zrc20 := deployZRC20(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", assetAddress, "foobar") // deposit to := sample.EthAddress() @@ -85,12 +85,12 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId assetAddress := sample.EthAddress().String() // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - deployZRC20(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", assetAddress, "foobar") + deployZRC20(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", assetAddress, "foobar") // deposit to := sample.EthAddress() @@ -113,11 +113,11 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", "foobar") + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", "foobar") // there is an initial total supply minted during gas pool setup initialTotalSupply, err := k.TotalSupplyZRC4(ctx, zrc20) @@ -159,11 +159,11 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", "foobar") + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", "foobar") // pause the coin coin, found := k.GetForeignCoins(ctx, zrc20.String()) @@ -191,11 +191,11 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", "foobar") + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", "foobar") // there is an initial total supply minted during gas pool setup initialTotalSupply, err := k.TotalSupplyZRC4(ctx, zrc20) @@ -232,7 +232,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) @@ -257,7 +257,7 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId assetAddress := sample.EthAddress().String() // deploy the system contracts @@ -284,11 +284,11 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", "foobar") + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", "foobar") example, err := k.DeployContract(ctx, contracts.ExampleMetaData) require.NoError(t, err) @@ -322,11 +322,11 @@ func TestKeeper_ZRC20DepositAndCallContract(t *testing.T) { _ = k.GetAuthKeeper().GetModuleAccount(ctx, types.ModuleName) chainList := common.DefaultChainsList() - chain := chainList[0] + chain := chainList[0].ChainId // deploy the system contracts deploySystemContracts(t, ctx, k, sdkk.EvmKeeper) - zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain.ChainId, "foobar", "foobar") + zrc20 := setupGasCoin(t, ctx, k, sdkk.EvmKeeper, chain, "foobar", "foobar") reverter, err := k.DeployContract(ctx, contracts.ReverterMetaData) require.NoError(t, err) diff --git a/x/observer/genesis_test.go b/x/observer/genesis_test.go index 56cc00352f..73193de928 100644 --- a/x/observer/genesis_test.go +++ b/x/observer/genesis_test.go @@ -44,7 +44,7 @@ func TestGenesis(t *testing.T) { } // Init and export - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) observer.InitGenesis(ctx, *k, genesisState) got := observer.ExportGenesis(ctx, *k) require.NotNil(t, got) diff --git a/x/observer/keeper/chain_nonces_test.go b/x/observer/keeper/chain_nonces_test.go index 0c511d3f79..d133c7fc53 100644 --- a/x/observer/keeper/chain_nonces_test.go +++ b/x/observer/keeper/chain_nonces_test.go @@ -10,7 +10,7 @@ import ( func TestKeeper_GetChainNonces(t *testing.T) { t.Run("Get chain nonces", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) chainNoncesList := sample.ChainNoncesList(t, 10) for _, n := range chainNoncesList { k.SetChainNonces(ctx, n) @@ -22,7 +22,7 @@ func TestKeeper_GetChainNonces(t *testing.T) { } }) t.Run("Get chain nonces not found", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) chainNoncesList := sample.ChainNoncesList(t, 10) for _, n := range chainNoncesList { k.SetChainNonces(ctx, n) @@ -31,7 +31,7 @@ func TestKeeper_GetChainNonces(t *testing.T) { require.False(t, found) }) t.Run("Get all chain nonces", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) chainNoncesList := sample.ChainNoncesList(t, 10) for _, n := range chainNoncesList { k.SetChainNonces(ctx, n) diff --git a/x/observer/keeper/chain_params_test.go b/x/observer/keeper/chain_params_test.go index c9142b7526..9bf63cd65b 100644 --- a/x/observer/keeper/chain_params_test.go +++ b/x/observer/keeper/chain_params_test.go @@ -12,7 +12,7 @@ import ( func TestKeeper_GetSupportedChainFromChainID(t *testing.T) { t.Run("return nil if chain not found", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) // no core params list require.Nil(t, k.GetSupportedChainFromChainID(ctx, getValidEthChainIDWithIndex(t, 0))) @@ -31,7 +31,7 @@ func TestKeeper_GetSupportedChainFromChainID(t *testing.T) { }) t.Run("return chain if chain found", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) chainID := getValidEthChainIDWithIndex(t, 0) setSupportedChain(ctx, *k, getValidEthChainIDWithIndex(t, 1), chainID) chain := k.GetSupportedChainFromChainID(ctx, chainID) @@ -42,12 +42,12 @@ func TestKeeper_GetSupportedChainFromChainID(t *testing.T) { func TestKeeper_GetSupportedChains(t *testing.T) { t.Run("return empty list if no core params list", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) require.Empty(t, k.GetSupportedChains(ctx)) }) t.Run("return list containing supported chains", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) require.Greater(t, len(common.ExternalChainList()), 5) supported1 := common.ExternalChainList()[0] diff --git a/x/observer/keeper/grpc_query_blame_test.go b/x/observer/keeper/grpc_query_blame_test.go index 8e66e3a2da..141246af47 100644 --- a/x/observer/keeper/grpc_query_blame_test.go +++ b/x/observer/keeper/grpc_query_blame_test.go @@ -12,7 +12,7 @@ import ( ) func TestKeeper_BlameByIdentifier(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) var chainId int64 = 97 var nonce uint64 = 101 digest := "85f5e10431f69bc2a14046a13aabaefc660103b6de7a84f75c4b96181d03f0b5" @@ -31,7 +31,7 @@ func TestKeeper_BlameByIdentifier(t *testing.T) { } func TestKeeper_BlameByChainAndNonce(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) var chainId int64 = 97 var nonce uint64 = 101 digest := "85f5e10431f69bc2a14046a13aabaefc660103b6de7a84f75c4b96181d03f0b5" @@ -52,7 +52,7 @@ func TestKeeper_BlameByChainAndNonce(t *testing.T) { func TestKeeper_BlameAll(t *testing.T) { t.Run("GetBlameRecord by limit ", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) blameList := sample.BlameRecordsList(t, 10) for _, record := range blameList { k.SetBlame(ctx, record) @@ -69,7 +69,7 @@ func TestKeeper_BlameAll(t *testing.T) { require.Equal(t, len(blameList), int(pageRes.Total)) }) t.Run("GetBlameRecord by offset ", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) blameList := sample.BlameRecordsList(t, 20) offset := 10 for _, record := range blameList { @@ -88,7 +88,7 @@ func TestKeeper_BlameAll(t *testing.T) { require.Equal(t, len(blameList), int(pageRes.Total)) }) t.Run("GetAllBlameRecord", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) blameList := sample.BlameRecordsList(t, 100) for _, record := range blameList { k.SetBlame(ctx, record) @@ -103,7 +103,7 @@ func TestKeeper_BlameAll(t *testing.T) { require.Equal(t, blameList, rst) }) t.Run("Get no records if nothing is set", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) rst := k.GetAllBlame(ctx) require.Len(t, rst, 0) }) diff --git a/x/observer/keeper/grpc_query_nonces_test.go b/x/observer/keeper/grpc_query_nonces_test.go index 6ec06efd28..74f4777b5d 100644 --- a/x/observer/keeper/grpc_query_nonces_test.go +++ b/x/observer/keeper/grpc_query_nonces_test.go @@ -14,7 +14,7 @@ import ( ) func TestChainNoncesQuerySingle(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) wctx := sdk.WrapSDKContext(ctx) chainNonces := sample.ChainNoncesList(t, 2) for _, nonce := range chainNonces { @@ -59,7 +59,7 @@ func TestChainNoncesQuerySingle(t *testing.T) { } func TestChainNoncesQueryPaginated(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) wctx := sdk.WrapSDKContext(ctx) chainNonces := sample.ChainNoncesList(t, 5) for _, nonce := range chainNonces { diff --git a/x/observer/keeper/msg_server_add_block_header_test.go b/x/observer/keeper/msg_server_add_block_header_test.go index 34bde9c115..85a7ec505b 100644 --- a/x/observer/keeper/msg_server_add_block_header_test.go +++ b/x/observer/keeper/msg_server_add_block_header_test.go @@ -149,7 +149,7 @@ func TestMsgServer_AddBlockHeader(t *testing.T) { } for _, tc := range tt { t.Run(tc.name, func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) k.SetObserverSet(ctx, types.ObserverSet{ ObserverList: []string{observerAddress.String()}, diff --git a/x/observer/keeper/msg_server_remove_chain_params_test.go b/x/observer/keeper/msg_server_remove_chain_params_test.go index d99b301403..238f86e256 100644 --- a/x/observer/keeper/msg_server_remove_chain_params_test.go +++ b/x/observer/keeper/msg_server_remove_chain_params_test.go @@ -14,7 +14,7 @@ import ( func TestMsgServer_RemoveChainParams(t *testing.T) { t.Run("can update chain params", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) chain1 := common.ExternalChainList()[0].ChainId @@ -75,7 +75,7 @@ func TestMsgServer_RemoveChainParams(t *testing.T) { }) t.Run("cannot remove chain params if not authorized", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) _, err := srv.UpdateChainParams(sdk.WrapSDKContext(ctx), &types.MsgUpdateChainParams{ @@ -97,7 +97,7 @@ func TestMsgServer_RemoveChainParams(t *testing.T) { }) t.Run("cannot remove if chain ID not found", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // set admin diff --git a/x/observer/keeper/msg_server_test.go b/x/observer/keeper/msg_server_test.go deleted file mode 100644 index 98911a5a70..0000000000 --- a/x/observer/keeper/msg_server_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package keeper - -import ( - "context" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/zeta-chain/zetacore/x/observer/types" -) - -func setupMsgServer(t testing.TB) (types.MsgServer, context.Context) { - k, ctx := SetupKeeper(t) - return NewMsgServerImpl(*k), sdk.WrapSDKContext(ctx) -} diff --git a/x/observer/keeper/msg_server_update_chain_params_test.go b/x/observer/keeper/msg_server_update_chain_params_test.go index 7b2f8a5cb3..1e4a707aeb 100644 --- a/x/observer/keeper/msg_server_update_chain_params_test.go +++ b/x/observer/keeper/msg_server_update_chain_params_test.go @@ -14,7 +14,7 @@ import ( func TestMsgServer_UpdateChainParams(t *testing.T) { t.Run("can update chain params", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) chain1 := common.ExternalChainList()[0].ChainId @@ -92,7 +92,7 @@ func TestMsgServer_UpdateChainParams(t *testing.T) { }) t.Run("cannot update chain params if not authorized", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) _, err := srv.UpdateChainParams(sdk.WrapSDKContext(ctx), &types.MsgUpdateChainParams{ diff --git a/x/observer/keeper/msg_server_update_crosschain_flags_test.go b/x/observer/keeper/msg_server_update_crosschain_flags_test.go index 555988694c..aaa33f4e23 100644 --- a/x/observer/keeper/msg_server_update_crosschain_flags_test.go +++ b/x/observer/keeper/msg_server_update_crosschain_flags_test.go @@ -26,7 +26,7 @@ func setAdminCrossChainFlags(ctx sdk.Context, k *keeper.Keeper, admin string, gr func TestMsgServer_UpdateCrosschainFlags(t *testing.T) { t.Run("can update crosschain flags", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) admin := sample.AccAddress() @@ -157,7 +157,7 @@ func TestMsgServer_UpdateCrosschainFlags(t *testing.T) { }) t.Run("cannot update crosschain flags if not authorized", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) _, err := srv.UpdateCrosschainFlags(sdk.WrapSDKContext(ctx), &types.MsgUpdateCrosschainFlags{ diff --git a/x/observer/keeper/msg_server_update_observer_test.go b/x/observer/keeper/msg_server_update_observer_test.go index 7fe73734d8..679e9851c5 100644 --- a/x/observer/keeper/msg_server_update_observer_test.go +++ b/x/observer/keeper/msg_server_update_observer_test.go @@ -17,7 +17,7 @@ import ( func TestMsgServer_UpdateObserver(t *testing.T) { t.Run("successfully update tombstoned observer", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here r := rand.New(rand.NewSource(9)) @@ -73,7 +73,7 @@ func TestMsgServer_UpdateObserver(t *testing.T) { }) t.Run("unable to update to a non validator address", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here r := rand.New(rand.NewSource(9)) @@ -122,7 +122,7 @@ func TestMsgServer_UpdateObserver(t *testing.T) { }) t.Run("unable to update tombstoned validator with with non operator account", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here r := rand.New(rand.NewSource(9)) @@ -173,7 +173,7 @@ func TestMsgServer_UpdateObserver(t *testing.T) { require.ErrorIs(t, err, types.ErrUpdateObserver) }) t.Run("unable to update non-tombstoned observer with update reason tombstoned", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here r := rand.New(rand.NewSource(9)) @@ -223,7 +223,7 @@ func TestMsgServer_UpdateObserver(t *testing.T) { require.ErrorIs(t, err, types.ErrUpdateObserver) }) t.Run("unable to update observer with no node account", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here r := rand.New(rand.NewSource(9)) @@ -269,7 +269,7 @@ func TestMsgServer_UpdateObserver(t *testing.T) { require.ErrorIs(t, err, types.ErrNodeAccountNotFound) }) t.Run("unable to update observer when last observer count is missing", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here r := rand.New(rand.NewSource(9)) @@ -314,7 +314,7 @@ func TestMsgServer_UpdateObserver(t *testing.T) { require.ErrorIs(t, err, types.ErrLastObserverCountNotFound) }) t.Run("update observer using admin policy", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) admin := sample.AccAddress() @@ -369,7 +369,7 @@ func TestMsgServer_UpdateObserver(t *testing.T) { require.Equal(t, newOperatorAddress.String(), acc.Operator) }) t.Run("fail to update observer using regular account and update type admin", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) srv := keeper.NewMsgServerImpl(*k) // #nosec G404 test purpose - weak randomness is not an issue here diff --git a/x/observer/keeper/nonce_to_cctx_test.go b/x/observer/keeper/nonce_to_cctx_test.go index 1cbd023fbd..75df5eb311 100644 --- a/x/observer/keeper/nonce_to_cctx_test.go +++ b/x/observer/keeper/nonce_to_cctx_test.go @@ -10,7 +10,7 @@ import ( func TestKeeper_GetNonceToCctx(t *testing.T) { t.Run("Get nonce to cctx", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) nonceToCctxList := sample.NonceToCctxList(t, "sample", 1) for _, n := range nonceToCctxList { k.SetNonceToCctx(ctx, n) @@ -22,7 +22,7 @@ func TestKeeper_GetNonceToCctx(t *testing.T) { } }) t.Run("Get nonce to cctx not found", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) nonceToCctxList := sample.NonceToCctxList(t, "sample", 1) for _, n := range nonceToCctxList { k.SetNonceToCctx(ctx, n) @@ -31,7 +31,7 @@ func TestKeeper_GetNonceToCctx(t *testing.T) { require.False(t, found) }) t.Run("Get all nonce to cctx", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) nonceToCctxList := sample.NonceToCctxList(t, "sample", 10) for _, n := range nonceToCctxList { k.SetNonceToCctx(ctx, n) diff --git a/x/observer/keeper/nonces_test.go b/x/observer/keeper/nonces_test.go index aaf7369df1..aa972eab3e 100644 --- a/x/observer/keeper/nonces_test.go +++ b/x/observer/keeper/nonces_test.go @@ -9,7 +9,7 @@ import ( ) func TestChainNoncesGet(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) items := sample.ChainNoncesList(t, 10) for _, item := range items { k.SetChainNonces(ctx, item) @@ -21,7 +21,7 @@ func TestChainNoncesGet(t *testing.T) { } } func TestChainNoncesRemove(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) items := sample.ChainNoncesList(t, 10) for _, item := range items { k.SetChainNonces(ctx, item) @@ -34,7 +34,7 @@ func TestChainNoncesRemove(t *testing.T) { } func TestChainNoncesGetAll(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) items := sample.ChainNoncesList(t, 10) for _, item := range items { k.SetChainNonces(ctx, item) diff --git a/x/observer/keeper/observer_set_test.go b/x/observer/keeper/observer_set_test.go index ca7212704a..6561405acf 100644 --- a/x/observer/keeper/observer_set_test.go +++ b/x/observer/keeper/observer_set_test.go @@ -10,7 +10,7 @@ import ( func TestKeeper_GetObserverSet(t *testing.T) { t.Run("get observer set", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) os := sample.ObserverSet(10) k.SetObserverSet(ctx, os) tfm, found := k.GetObserverSet(ctx) @@ -21,7 +21,7 @@ func TestKeeper_GetObserverSet(t *testing.T) { func TestKeeper_IsAddressPartOfObserverSet(t *testing.T) { t.Run("address is part of observer set", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) os := sample.ObserverSet(10) k.SetObserverSet(ctx, os) require.True(t, k.IsAddressPartOfObserverSet(ctx, os.ObserverList[0])) @@ -31,7 +31,7 @@ func TestKeeper_IsAddressPartOfObserverSet(t *testing.T) { func TestKeeper_AddObserverToSet(t *testing.T) { t.Run("add observer to set", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) os := sample.ObserverSet(10) k.SetObserverSet(ctx, os) newObserver := sample.AccAddress() @@ -46,7 +46,7 @@ func TestKeeper_AddObserverToSet(t *testing.T) { func TestKeeper_RemoveObserverFromSet(t *testing.T) { t.Run("remove observer from set", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) os := sample.ObserverSet(10) k.SetObserverSet(ctx, os) k.RemoveObserverFromSet(ctx, os.ObserverList[0]) @@ -59,7 +59,7 @@ func TestKeeper_RemoveObserverFromSet(t *testing.T) { func TestKeeper_UpdateObserverAddress(t *testing.T) { t.Run("update observer address", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) oldObserverAddress := sample.AccAddress() newObserverAddress := sample.AccAddress() observerSet := sample.ObserverSet(10) @@ -72,7 +72,7 @@ func TestKeeper_UpdateObserverAddress(t *testing.T) { require.Equal(t, newObserverAddress, observerSet.ObserverList[len(observerSet.ObserverList)-1]) }) t.Run("update observer address long observerList", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) oldObserverAddress := sample.AccAddress() newObserverAddress := sample.AccAddress() observerSet := sample.ObserverSet(10000) @@ -85,7 +85,7 @@ func TestKeeper_UpdateObserverAddress(t *testing.T) { require.Equal(t, newObserverAddress, observerMappers.ObserverList[len(observerMappers.ObserverList)-1]) }) t.Run("update observer address short observerList", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) oldObserverAddress := sample.AccAddress() newObserverAddress := sample.AccAddress() observerSet := sample.ObserverSet(1) diff --git a/x/observer/keeper/pending_nonces_test.go b/x/observer/keeper/pending_nonces_test.go index 0cb61f090d..8ada35c5ba 100644 --- a/x/observer/keeper/pending_nonces_test.go +++ b/x/observer/keeper/pending_nonces_test.go @@ -12,7 +12,7 @@ import ( func TestKeeper_PendingNoncesAll(t *testing.T) { t.Run("Get all pending nonces paginated by limit", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) nonces := sample.PendingNoncesList(t, "sample", 10) sort.SliceStable(nonces, func(i, j int) bool { return nonces[i].ChainId < nonces[j].ChainId @@ -29,7 +29,7 @@ func TestKeeper_PendingNoncesAll(t *testing.T) { require.Equal(t, len(nonces), int(pageRes.Total)) }) t.Run("Get all pending nonces paginated by offset", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) nonces := sample.PendingNoncesList(t, "sample", 42) sort.SliceStable(nonces, func(i, j int) bool { return nonces[i].ChainId < nonces[j].ChainId @@ -48,7 +48,7 @@ func TestKeeper_PendingNoncesAll(t *testing.T) { require.Equal(t, len(nonces), int(pageRes.Total)) }) t.Run("Get all pending nonces ", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) nonces := sample.PendingNoncesList(t, "sample", 10) sort.SliceStable(nonces, func(i, j int) bool { return nonces[i].ChainId < nonces[j].ChainId diff --git a/x/observer/keeper/tss_funds_migrator_test.go b/x/observer/keeper/tss_funds_migrator_test.go index 62d622e179..46c8891ef9 100644 --- a/x/observer/keeper/tss_funds_migrator_test.go +++ b/x/observer/keeper/tss_funds_migrator_test.go @@ -10,7 +10,7 @@ import ( func TestKeeper_GetTssFundMigrator(t *testing.T) { t.Run("Successfully set funds migrator for chain", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) chain := sample.TssFundsMigrator(1) k.SetFundMigrator(ctx, chain) tfm, found := k.GetFundMigrator(ctx, chain.ChainId) @@ -18,7 +18,7 @@ func TestKeeper_GetTssFundMigrator(t *testing.T) { require.Equal(t, chain, tfm) }) t.Run("Verify only one migrator can be created for a chain", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) tfm1 := sample.TssFundsMigrator(1) k.SetFundMigrator(ctx, tfm1) tfm2 := tfm1 diff --git a/x/observer/keeper/tss_test.go b/x/observer/keeper/tss_test.go index 0af8bae7f3..06f763e4d3 100644 --- a/x/observer/keeper/tss_test.go +++ b/x/observer/keeper/tss_test.go @@ -17,7 +17,7 @@ import ( ) func TestTSSGet(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) tss := sample.Tss() k.SetTSS(ctx, tss) tssQueried, found := k.GetTSS(ctx) @@ -26,7 +26,7 @@ func TestTSSGet(t *testing.T) { } func TestTSSRemove(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) tss := sample.Tss() k.SetTSS(ctx, tss) k.RemoveTSS(ctx) @@ -35,7 +35,7 @@ func TestTSSRemove(t *testing.T) { } func TestTSSQuerySingle(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) wctx := sdk.WrapSDKContext(ctx) //msgs := createTSS(keeper, ctx, 1) tss := sample.Tss() @@ -69,7 +69,7 @@ func TestTSSQuerySingle(t *testing.T) { } func TestTSSQueryHistory(t *testing.T) { - keeper, ctx := keepertest.ObserverKeeper(t) + keeper, ctx, _ := keepertest.ObserverKeeper(t) wctx := sdk.WrapSDKContext(ctx) for _, tc := range []struct { desc string @@ -115,7 +115,7 @@ func TestTSSQueryHistory(t *testing.T) { func TestKeeper_TssHistory(t *testing.T) { t.Run("Get tss history paginated by limit", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) tssList := sample.TssList(10) for _, tss := range tssList { k.SetTSSHistory(ctx, tss) @@ -132,7 +132,7 @@ func TestKeeper_TssHistory(t *testing.T) { require.Equal(t, len(tssList), int(pageRes.Total)) }) t.Run("Get tss history paginated by offset", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) tssList := sample.TssList(100) offset := 20 for _, tss := range tssList { @@ -151,7 +151,7 @@ func TestKeeper_TssHistory(t *testing.T) { require.Equal(t, len(tssList), int(pageRes.Total)) }) t.Run("Get all TSS without pagination", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) tssList := sample.TssList(100) for _, tss := range tssList { k.SetTSSHistory(ctx, tss) @@ -166,7 +166,7 @@ func TestKeeper_TssHistory(t *testing.T) { require.Equal(t, tssList, rst) }) t.Run("Get historical TSS", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) tssList := sample.TssList(100) for _, tss := range tssList { k.SetTSSHistory(ctx, tss) diff --git a/x/observer/keeper/utils_test.go b/x/observer/keeper/utils_test.go index 6534fc6200..e68fb2edd7 100644 --- a/x/observer/keeper/utils_test.go +++ b/x/observer/keeper/utils_test.go @@ -43,7 +43,7 @@ func getValidEthChainIDWithIndex(t *testing.T, index int) int64 { func TestKeeper_IsAuthorized(t *testing.T) { t.Run("authorized observer", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) r := rand.New(rand.NewSource(9)) @@ -69,7 +69,7 @@ func TestKeeper_IsAuthorized(t *testing.T) { }) t.Run("not authorized for tombstoned observer", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) r := rand.New(rand.NewSource(9)) @@ -95,7 +95,7 @@ func TestKeeper_IsAuthorized(t *testing.T) { }) t.Run("not authorized for non-validator observer", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) r := rand.New(rand.NewSource(9)) diff --git a/x/observer/keeper/vote_inbound.go b/x/observer/keeper/vote_inbound.go new file mode 100644 index 0000000000..5bc70ede88 --- /dev/null +++ b/x/observer/keeper/vote_inbound.go @@ -0,0 +1,84 @@ +package keeper + +import ( + "fmt" + + 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/observer/types" +) + +// VoteOnInboundBallot casts a vote on an inbound transaction observed on a connected chain. If this +// is the first vote, a new ballot is created. When a threshold of votes is +// reached, the ballot is finalized. +func (k Keeper) VoteOnInboundBallot( + ctx sdk.Context, + senderChainID int64, + receiverChainID int64, + coinType common.CoinType, + voter string, + ballotIndex string, + inTxHash string, +) (bool, bool, error) { + if !k.IsInboundEnabled(ctx) { + return false, false, types.ErrInboundDisabled + } + + // makes sure we are getting only supported chains + // if a chain support has been turned on using gov proposal + // this function returns nil + senderChain := k.GetSupportedChainFromChainID(ctx, senderChainID) + if senderChain == nil { + return false, false, sdkerrors.Wrap(types.ErrSupportedChains, fmt.Sprintf( + "ChainID %d, Observation %s", + senderChainID, + types.ObservationType_InBoundTx.String()), + ) + } + + // checks the voter is authorized to vote on the observation chain + if ok := k.IsAuthorized(ctx, voter); !ok { + return false, false, types.ErrNotObserver + } + + // makes sure we are getting only supported chains + receiverChain := k.GetSupportedChainFromChainID(ctx, receiverChainID) + if receiverChain == nil { + return false, false, sdkerrors.Wrap(types.ErrSupportedChains, fmt.Sprintf( + "ChainID %d, Observation %s", + receiverChainID, + types.ObservationType_InBoundTx.String()), + ) + } + + // check if we want to send ZETA to external chain, but there is no ZETA token. + if receiverChain.IsExternalChain() { + coreParams, found := k.GetChainParamsByChainID(ctx, receiverChain.ChainId) + if !found { + return false, false, types.ErrChainParamsNotFound + } + if coreParams.ZetaTokenContractAddress == "" && coinType == common.CoinType_Zeta { + return false, false, types.ErrInvalidZetaCoinTypes + } + } + + // checks against the supported chains list before querying for Ballot + ballot, isNew, err := k.FindBallot(ctx, ballotIndex, senderChain, types.ObservationType_InBoundTx) + if err != nil { + return false, false, err + } + if isNew { + EmitEventBallotCreated(ctx, ballot, inTxHash, senderChain.String()) + } + + // adds a vote and sets the ballot + ballot, err = k.AddVoteToBallot(ctx, ballot, voter, types.VoteType_SuccessObservation) + if err != nil { + return false, isNew, err + } + + // checks if the ballot is finalized + _, isFinalized := k.CheckIfFinalizingVote(ctx, ballot) + return isFinalized, isNew, nil +} diff --git a/x/observer/keeper/vote_inbound_test.go b/x/observer/keeper/vote_inbound_test.go new file mode 100644 index 0000000000..e1391c051f --- /dev/null +++ b/x/observer/keeper/vote_inbound_test.go @@ -0,0 +1,438 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/common" + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestKeeper_VoteOnInboundBallot(t *testing.T) { + + t.Run("fail if inbound not enabled", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeper(t) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: false, + }) + + _, _, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + common.ZetaPrivnetChain().ChainId, + common.CoinType_ERC20, + sample.AccAddress(), + "index", + "inTxHash", + ) + + require.Error(t, err) + require.ErrorIs(t, err, types.ErrInboundDisabled) + }) + + t.Run("fail if sender chain not supported", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeper(t) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{}) + + _, _, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + common.ZetaPrivnetChain().ChainId, + common.CoinType_ERC20, + sample.AccAddress(), + "index", + "inTxHash", + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrSupportedChains) + + // set the chain but not supported + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: false, + }, + }, + }) + + _, _, err = k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + common.ZetaPrivnetChain().ChainId, + common.CoinType_ERC20, + sample.AccAddress(), + "index", + "inTxHash", + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrSupportedChains) + }) + + t.Run("fail if not authorized", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeper(t) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{}) + + _, _, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + common.ZetaPrivnetChain().ChainId, + common.CoinType_ERC20, + sample.AccAddress(), + "index", + "inTxHash", + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrNotObserver) + }) + + t.Run("fail if receiver chain not supported", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + _, _, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + common.ZetaPrivnetChain().ChainId, + common.CoinType_ERC20, + observer, + "index", + "inTxHash", + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrSupportedChains) + + // set the chain but not supported + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + { + ChainId: common.ZetaPrivnetChain().ChainId, + IsSupported: false, + }, + }, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + _, _, err = k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + common.ZetaPrivnetChain().ChainId, + common.CoinType_ERC20, + observer, + "index", + "inTxHash", + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrSupportedChains) + }) + + t.Run("fail if inbound contain ZETA but receiver chain doesn't support ZETA", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + { + ChainId: getValidEthChainIDWithIndex(t, 1), + IsSupported: true, + ZetaTokenContractAddress: "", + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + _, _, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + getValidEthChainIDWithIndex(t, 1), + common.CoinType_Zeta, + observer, + "index", + "inTxHash", + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrInvalidZetaCoinTypes) + }) + + t.Run("can add vote and create ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + { + ChainId: getValidEthChainIDWithIndex(t, 1), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + isFinalized, isNew, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + getValidEthChainIDWithIndex(t, 1), + common.CoinType_ERC20, + observer, + "index", + "inTxHash", + ) + require.NoError(t, err) + + // ballot should be finalized since there is only one observer + require.True(t, isFinalized) + require.True(t, isNew) + }) + + t.Run("can add vote and create ballot without finalizing ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + // threshold high enough to not finalize ballot + threshold, err := sdk.NewDecFromStr("0.7") + require.NoError(t, err) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + BallotThreshold: threshold, + }, + { + ChainId: getValidEthChainIDWithIndex(t, 1), + IsSupported: true, + BallotThreshold: threshold, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{ + observer, + sample.AccAddress(), + }, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + isFinalized, isNew, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + getValidEthChainIDWithIndex(t, 1), + common.CoinType_ERC20, + observer, + "index", + "inTxHash", + ) + require.NoError(t, err) + + // ballot should be finalized since there is only one observer + require.False(t, isFinalized) + require.True(t, isNew) + }) + + t.Run("can add vote to an existing ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + { + ChainId: getValidEthChainIDWithIndex(t, 1), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + // set a ballot + threshold, err := sdk.NewDecFromStr("0.7") + require.NoError(t, err) + ballot := types.Ballot{ + Index: "index", + BallotIdentifier: "index", + VoterList: []string{ + sample.AccAddress(), + sample.AccAddress(), + observer, + sample.AccAddress(), + sample.AccAddress(), + }, + Votes: types.CreateVotes(5), + ObservationType: types.ObservationType_InBoundTx, + BallotThreshold: threshold, + BallotStatus: types.BallotStatus_BallotInProgress, + } + k.SetBallot(ctx, &ballot) + + isFinalized, isNew, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + getValidEthChainIDWithIndex(t, 1), + common.CoinType_ERC20, + observer, + "index", + "inTxHash", + ) + require.NoError(t, err) + + // ballot should not be finalized as the threshold is not reached + require.False(t, isFinalized) + require.False(t, isNew) + }) + + t.Run("can add vote to an existing ballot and finalize ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetCrosschainFlags(ctx, types.CrosschainFlags{ + IsInboundEnabled: true, + }) + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + { + ChainId: getValidEthChainIDWithIndex(t, 1), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + // set a ballot + threshold, err := sdk.NewDecFromStr("0.1") + require.NoError(t, err) + ballot := types.Ballot{ + Index: "index", + BallotIdentifier: "index", + VoterList: []string{ + observer, + sample.AccAddress(), + sample.AccAddress(), + }, + Votes: types.CreateVotes(3), + ObservationType: types.ObservationType_InBoundTx, + BallotThreshold: threshold, + BallotStatus: types.BallotStatus_BallotInProgress, + } + k.SetBallot(ctx, &ballot) + + isFinalized, isNew, err := k.VoteOnInboundBallot( + ctx, + getValidEthChainIDWithIndex(t, 0), + getValidEthChainIDWithIndex(t, 1), + common.CoinType_ERC20, + observer, + "index", + "inTxHash", + ) + require.NoError(t, err) + + // ballot should not be finalized as the threshold is not reached + require.True(t, isFinalized) + require.False(t, isNew) + }) +} diff --git a/x/observer/keeper/vote_outbound.go b/x/observer/keeper/vote_outbound.go new file mode 100644 index 0000000000..3ee0c6b585 --- /dev/null +++ b/x/observer/keeper/vote_outbound.go @@ -0,0 +1,52 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/zeta-chain/zetacore/common" + observertypes "github.com/zeta-chain/zetacore/x/observer/types" +) + +// VoteOnOutboundBallot casts a vote on an outbound transaction observed on a connected chain (after +// it has been broadcasted to and finalized on a connected chain). If this is +// the first vote, a new ballot is created. When a threshold of votes is +// reached, the ballot is finalized. +// returns if the vote is finalized, if the ballot is new, the ballot status and the name of the observation chain +func (k Keeper) VoteOnOutboundBallot( + ctx sdk.Context, + ballotIndex string, + outTxChainID int64, + receiveStatus common.ReceiveStatus, + voter string, +) (isFinalized bool, isNew bool, ballot observertypes.Ballot, observationChainName string, err error) { + // Observer Chain already checked then inbound is created + /* EDGE CASE : Params updated in during the finalization process + i.e Inbound has been finalized but outbound is still pending + */ + observationChain := k.GetSupportedChainFromChainID(ctx, outTxChainID) + if observationChain == nil { + return false, false, ballot, "", observertypes.ErrSupportedChains + } + if observertypes.CheckReceiveStatus(receiveStatus) != nil { + return false, false, ballot, "", observertypes.ErrInvalidStatus + } + + // check if voter is authorized + if ok := k.IsAuthorized(ctx, voter); !ok { + return false, false, ballot, "", observertypes.ErrNotObserver + } + + // fetch or create ballot + ballot, isNew, err = k.FindBallot(ctx, ballotIndex, observationChain, observertypes.ObservationType_OutBoundTx) + if err != nil { + return false, false, ballot, "", err + } + + // add vote to ballot + ballot, err = k.AddVoteToBallot(ctx, ballot, voter, observertypes.ConvertReceiveStatusToVoteType(receiveStatus)) + if err != nil { + return false, false, ballot, "", err + } + + ballot, isFinalizedInThisBlock := k.CheckIfFinalizingVote(ctx, ballot) + return isFinalizedInThisBlock, isNew, ballot, observationChain.String(), nil +} diff --git a/x/observer/keeper/vote_outbound_test.go b/x/observer/keeper/vote_outbound_test.go new file mode 100644 index 0000000000..0e087bac1f --- /dev/null +++ b/x/observer/keeper/vote_outbound_test.go @@ -0,0 +1,295 @@ +package keeper_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/zeta-chain/zetacore/common" + keepertest "github.com/zeta-chain/zetacore/testutil/keeper" + "github.com/zeta-chain/zetacore/testutil/sample" + "github.com/zeta-chain/zetacore/x/observer/types" +) + +func TestKeeper_VoteOnOutboundBallot(t *testing.T) { + t.Run("fail if chain is not supported", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeper(t) + + k.SetChainParamsList(ctx, types.ChainParamsList{}) + + _, _, _, _, err := k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus_Success, + sample.AccAddress(), + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrSupportedChains) + + // set the chain but not supported + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: false, + }, + }, + }) + + _, _, _, _, err = k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus_Success, + sample.AccAddress(), + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrSupportedChains) + }) + + t.Run("fail if receive status is invalid", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeper(t) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + + _, _, _, _, err := k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus(1000), + sample.AccAddress(), + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrInvalidStatus) + }) + + t.Run("fail if sender is not authorized", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeper(t) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{}) + + _, _, _, _, err := k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus_Success, + sample.AccAddress(), + ) + require.Error(t, err) + require.ErrorIs(t, err, types.ErrNotObserver) + }) + + t.Run("can add vote and create ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + isFinalized, isNew, ballot, _, err := k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus_Success, + observer, + ) + require.NoError(t, err) + + // ballot should be finalized since there is only one observer + require.True(t, isFinalized) + require.True(t, isNew) + expectedBallot, found := k.GetBallot(ctx, "index") + require.True(t, found) + require.Equal(t, expectedBallot, ballot) + }) + + t.Run("can add vote and create ballot without finalizing ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + // threshold high enough to not finalize the ballot + threshold, err := sdk.NewDecFromStr("0.7") + require.NoError(t, err) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + BallotThreshold: threshold, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{ + observer, + sample.AccAddress(), + }, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + isFinalized, isNew, ballot, _, err := k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus_Success, + observer, + ) + require.NoError(t, err) + + // ballot should be finalized since there is only one observer + require.False(t, isFinalized) + require.True(t, isNew) + expectedBallot, found := k.GetBallot(ctx, "index") + require.True(t, found) + require.Equal(t, expectedBallot, ballot) + }) + + t.Run("can add vote to an existing ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + // set a ballot + threshold, err := sdk.NewDecFromStr("0.7") + require.NoError(t, err) + ballot := types.Ballot{ + Index: "index", + BallotIdentifier: "index", + VoterList: []string{ + sample.AccAddress(), + sample.AccAddress(), + observer, + sample.AccAddress(), + sample.AccAddress(), + }, + Votes: types.CreateVotes(5), + ObservationType: types.ObservationType_OutBoundTx, + BallotThreshold: threshold, + BallotStatus: types.BallotStatus_BallotInProgress, + } + k.SetBallot(ctx, &ballot) + + isFinalized, isNew, ballot, _, err := k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus_Success, + observer, + ) + require.NoError(t, err) + + // ballot should be finalized since there is only one observer + require.False(t, isFinalized) + require.False(t, isNew) + expectedBallot, found := k.GetBallot(ctx, "index") + require.True(t, found) + require.Equal(t, expectedBallot, ballot) + }) + + t.Run("can add vote to an existing ballot and finalize ballot", func(t *testing.T) { + k, ctx, _ := keepertest.ObserverKeeperWithMocks(t, keepertest.ObserverMocksAll) + + observer := sample.AccAddress() + stakingMock := keepertest.GetObserverStakingMock(t, k) + slashingMock := keepertest.GetObserverSlashingMock(t, k) + + k.SetChainParamsList(ctx, types.ChainParamsList{ + ChainParams: []*types.ChainParams{ + { + ChainId: getValidEthChainIDWithIndex(t, 0), + IsSupported: true, + }, + }, + }) + k.SetObserverSet(ctx, types.ObserverSet{ + ObserverList: []string{observer}, + }) + stakingMock.MockGetValidator(sample.Validator(t, sample.Rand())) + slashingMock.MockIsTombstoned(false) + + // set a ballot + threshold, err := sdk.NewDecFromStr("0.1") + require.NoError(t, err) + ballot := types.Ballot{ + Index: "index", + BallotIdentifier: "index", + VoterList: []string{ + observer, + sample.AccAddress(), + sample.AccAddress(), + }, + Votes: types.CreateVotes(3), + ObservationType: types.ObservationType_OutBoundTx, + BallotThreshold: threshold, + BallotStatus: types.BallotStatus_BallotInProgress, + } + k.SetBallot(ctx, &ballot) + + isFinalized, isNew, ballot, _, err := k.VoteOnOutboundBallot( + ctx, + "index", + getValidEthChainIDWithIndex(t, 0), + common.ReceiveStatus_Success, + observer, + ) + require.NoError(t, err) + + // ballot should be finalized since there is only one observer + require.True(t, isFinalized) + require.False(t, isNew) + expectedBallot, found := k.GetBallot(ctx, "index") + require.True(t, found) + require.Equal(t, expectedBallot, ballot) + }) +} diff --git a/x/observer/migrations/v3/migrate_test.go b/x/observer/migrations/v3/migrate_test.go index 604fd0a9c7..ce580cbf42 100644 --- a/x/observer/migrations/v3/migrate_test.go +++ b/x/observer/migrations/v3/migrate_test.go @@ -11,7 +11,7 @@ import ( ) func TestMigrateStore(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) // nothing if no admin policy params := types.DefaultParams() diff --git a/x/observer/migrations/v4/migrate_test.go b/x/observer/migrations/v4/migrate_test.go index a5bfd67f56..55c87d6541 100644 --- a/x/observer/migrations/v4/migrate_test.go +++ b/x/observer/migrations/v4/migrate_test.go @@ -11,7 +11,7 @@ import ( ) func TestMigrateCrosschainFlags(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) store := prefix.NewStore(ctx.KVStore(k.StoreKey()), types.KeyPrefix(types.CrosschainFlagsKey)) legacyFlags := types.LegacyCrosschainFlags{ IsInboundEnabled: false, diff --git a/x/observer/migrations/v5/migrate_test.go b/x/observer/migrations/v5/migrate_test.go index 8b83d5d0ed..f5aa035414 100644 --- a/x/observer/migrations/v5/migrate_test.go +++ b/x/observer/migrations/v5/migrate_test.go @@ -15,7 +15,7 @@ import ( func TestMigrateObserverMapper(t *testing.T) { t.Run("TestMigrateStore", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) legacyObserverMapperStore := prefix.NewStore(ctx.KVStore(k.StoreKey()), types.KeyPrefix(types.ObserverMapperKey)) legacyObserverMapperList := sample.LegacyObserverMapperList(t, 12, "sample") for _, legacyObserverMapper := range legacyObserverMapperList { @@ -43,7 +43,7 @@ func TestMigrateObserverMapper(t *testing.T) { } func TestMigrateObserverParams(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) // set chain params previousChainParamsList := types.ChainParamsList{ diff --git a/x/observer/migrations/v6/migrate_test.go b/x/observer/migrations/v6/migrate_test.go index 925c1b0b32..b99242aabc 100644 --- a/x/observer/migrations/v6/migrate_test.go +++ b/x/observer/migrations/v6/migrate_test.go @@ -12,7 +12,7 @@ import ( func TestMigrateObserverParams(t *testing.T) { t.Run("Migrate when keygen is Pending", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) k.SetKeygen(ctx, types.Keygen{ Status: types.KeygenStatus_PendingKeygen, BlockNumber: math.MaxInt64, @@ -57,7 +57,7 @@ func TestMigrateObserverParams(t *testing.T) { require.Equal(t, participantList, participantList) }) t.Run("Migrate when keygen is not Pending", func(t *testing.T) { - k, ctx := keepertest.ObserverKeeper(t) + k, ctx, _ := keepertest.ObserverKeeper(t) participantList := []string{ "zetapub1addwnpepqglunjrgl3qg08duxq9pf28jmvrer3crwnnfzp6m0u0yh9jk9mnn5p76utc", "zetapub1addwnpepqwwpjwwnes7cywfkr0afme7ymk8rf5jzhn8pfr6qqvfm9v342486qsrh4f5", diff --git a/x/observer/types/ballot.go b/x/observer/types/ballot.go index bc6bfd66ee..10f21119d1 100644 --- a/x/observer/types/ballot.go +++ b/x/observer/types/ballot.go @@ -34,7 +34,7 @@ func (m Ballot) GetVoterIndex(address string) int { return index } -// Is finalzing vote checks sets the ballot to a final status if enough votes have been added +// IsFinalizingVote checks sets the ballot to a final status if enough votes have been added // If it has already been finalized it returns false // It enough votes have not been added it returns false func (m Ballot) IsFinalizingVote() (Ballot, bool) { diff --git a/x/observer/types/errors.go b/x/observer/types/errors.go index 0add35d4ce..4d5fd240a5 100644 --- a/x/observer/types/errors.go +++ b/x/observer/types/errors.go @@ -12,17 +12,15 @@ var ( ErrSupportedChains = errorsmod.Register(ModuleName, 1102, "chain not supported") ErrInvalidStatus = errorsmod.Register(ModuleName, 1103, "invalid Voting Status") - ErrObserverNotPresent = errorsmod.Register(ModuleName, 1105, "observer for type and observation does not exist") - ErrNotValidator = errorsmod.Register(ModuleName, 1106, "user needs to be a validator before applying to become an observer") - ErrValidatorStatus = errorsmod.Register(ModuleName, 1107, "corresponding validator needs to be bonded and not jailed") - ErrInvalidAddress = errorsmod.Register(ModuleName, 1108, "invalid Address") - ErrSelfDelegation = errorsmod.Register(ModuleName, 1109, "self Delegation for operator not found") - ErrCheckObserverDelegation = errorsmod.Register(ModuleName, 1110, "observer delegation not sufficient") - ErrNotAuthorizedPolicy = errorsmod.Register(ModuleName, 1111, "msg Sender is not the authorized policy") - ErrKeygenNotFound = errorsmod.Register(ModuleName, 1113, "Keygen not found, Keygen block can only be updated,New keygen cannot be set") - ErrKeygenBlockTooLow = errorsmod.Register(ModuleName, 1114, "please set a block number at-least 10 blocks higher than the current block number") - ErrKeygenCompleted = errorsmod.Register(ModuleName, 1115, "keygen already completed") - ErrNotAuthorized = errorsmod.Register(ModuleName, 1116, "not authorized") + ErrNotValidator = errorsmod.Register(ModuleName, 1106, "user needs to be a validator before applying to become an observer") + ErrValidatorStatus = errorsmod.Register(ModuleName, 1107, "corresponding validator needs to be bonded and not jailed") + ErrInvalidAddress = errorsmod.Register(ModuleName, 1108, "invalid Address") + ErrSelfDelegation = errorsmod.Register(ModuleName, 1109, "self Delegation for operator not found") + ErrNotAuthorizedPolicy = errorsmod.Register(ModuleName, 1111, "msg Sender is not the authorized policy") + ErrKeygenNotFound = errorsmod.Register(ModuleName, 1113, "Keygen not found, Keygen block can only be updated,New keygen cannot be set") + ErrKeygenBlockTooLow = errorsmod.Register(ModuleName, 1114, "please set a block number at-least 10 blocks higher than the current block number") + ErrKeygenCompleted = errorsmod.Register(ModuleName, 1115, "keygen already completed") + ErrNotAuthorized = errorsmod.Register(ModuleName, 1116, "not authorized") ErrBlockAlreadyExist = errorsmod.Register(ModuleName, 1119, "block already exists") ErrNoParentHash = errorsmod.Register(ModuleName, 1120, "no parent hash") @@ -38,5 +36,7 @@ var ( ErrObserverSetNotFound = errorsmod.Register(ModuleName, 1130, "observer set not found") ErrTssNotFound = errorsmod.Register(ModuleName, 1131, "tss not found") - ErrInboundDisabled = errorsmod.Register(ModuleName, 1132, "inbound tx processing is disabled") + ErrInboundDisabled = errorsmod.Register(ModuleName, 1132, "inbound tx processing is disabled") + ErrInvalidZetaCoinTypes = errorsmod.Register(ModuleName, 1133, "invalid zeta coin types") + ErrNotObserver = errorsmod.Register(ModuleName, 1134, "sender is not an observer") ) diff --git a/x/observer/types/observer_mapper.go b/x/observer/types/observer_mapper.go deleted file mode 100644 index b78f5daa8c..0000000000 --- a/x/observer/types/observer_mapper.go +++ /dev/null @@ -1,28 +0,0 @@ -package types - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/zeta-chain/zetacore/common" -) - -// Validate observer mapper contains an existing chain -func (m *ObserverSet) Validate() error { - for _, observerAddress := range m.ObserverList { - _, err := sdk.AccAddressFromBech32(observerAddress) - if err != nil { - return err - } - } - return nil -} - -func CheckReceiveStatus(status common.ReceiveStatus) error { - switch status { - case common.ReceiveStatus_Success: - return nil - case common.ReceiveStatus_Failed: - return nil - default: - return ErrInvalidStatus - } -} diff --git a/x/observer/types/observer_set.go b/x/observer/types/observer_set.go index 71798f213f..e1b6c61c9b 100644 --- a/x/observer/types/observer_set.go +++ b/x/observer/types/observer_set.go @@ -1,5 +1,10 @@ package types +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/zeta-chain/zetacore/common" +) + func (m *ObserverSet) Len() int { return len(m.ObserverList) } @@ -7,3 +12,25 @@ func (m *ObserverSet) Len() int { func (m *ObserverSet) LenUint() uint64 { return uint64(len(m.ObserverList)) } + +// Validate observer mapper contains an existing chain +func (m *ObserverSet) Validate() error { + for _, observerAddress := range m.ObserverList { + _, err := sdk.AccAddressFromBech32(observerAddress) + if err != nil { + return err + } + } + return nil +} + +func CheckReceiveStatus(status common.ReceiveStatus) error { + switch status { + case common.ReceiveStatus_Success: + return nil + case common.ReceiveStatus_Failed: + return nil + default: + return ErrInvalidStatus + } +}