From 759d08a6ebfff57e3330d57749d4ed066f53de4b Mon Sep 17 00:00:00 2001 From: MiniFrenchBread <103425574+MiniFrenchBread@users.noreply.github.com> Date: Tue, 29 Oct 2024 19:29:44 +0800 Subject: [PATCH] fix: app --- app/app.go | 10 +- precompiles/dasigners/dasigners_test.go | 5 +- precompiles/interfaces/contracts/IStaking.sol | 2 +- precompiles/staking/query_test.go | 65 +++++++++ precompiles/staking/staking_test.go | 102 ++++++++++++++ precompiles/staking/tx_test.go | 124 ++++++++++++++++++ precompiles/staking/types.go | 32 ++--- precompiles/testutil/suite.go | 13 +- 8 files changed, 331 insertions(+), 22 deletions(-) create mode 100644 precompiles/staking/query_test.go create mode 100644 precompiles/staking/staking_test.go create mode 100644 precompiles/staking/tx_test.go diff --git a/app/app.go b/app/app.go index 7b3605f8..0879f353 100644 --- a/app/app.go +++ b/app/app.go @@ -110,6 +110,7 @@ import ( chainparams "github.com/0glabs/0g-chain/app/params" "github.com/0glabs/0g-chain/chaincfg" dasignersprecompile "github.com/0glabs/0g-chain/precompiles/dasigners" + stakingprecompile "github.com/0glabs/0g-chain/precompiles/staking" "github.com/0glabs/0g-chain/x/bep3" bep3keeper "github.com/0glabs/0g-chain/x/bep3/keeper" @@ -499,11 +500,18 @@ func NewApp( app.dasignersKeeper = dasignerskeeper.NewKeeper(keys[dasignerstypes.StoreKey], appCodec, app.stakingKeeper, govAuthAddrStr) // precopmiles precompiles := make(map[common.Address]vm.PrecompiledContract) + // dasigners daSignersPrecompile, err := dasignersprecompile.NewDASignersPrecompile(app.dasignersKeeper) if err != nil { - panic("initialize precompile failed") + panic(fmt.Sprintf("initialize dasigners precompile failed: %v", err)) } precompiles[daSignersPrecompile.Address()] = daSignersPrecompile + // staking + stakingPrecompile, err := stakingprecompile.NewStakingPrecompile(app.stakingKeeper) + if err != nil { + panic(fmt.Sprintf("initialize staking precompile failed: %v", err)) + } + precompiles[stakingPrecompile.Address()] = stakingPrecompile app.evmKeeper = evmkeeper.NewKeeper( appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], diff --git a/precompiles/dasigners/dasigners_test.go b/precompiles/dasigners/dasigners_test.go index b36ef7d1..e9da44fd 100644 --- a/precompiles/dasigners/dasigners_test.go +++ b/precompiles/dasigners/dasigners_test.go @@ -14,6 +14,7 @@ import ( "github.com/0glabs/0g-chain/x/dasigners/v1/types" abci "github.com/cometbft/cometbft/abci/types" "github.com/consensys/gnark-crypto/ecc/bn254" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" @@ -23,7 +24,6 @@ import ( "cosmossdk.io/math" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/ethereum/go-ethereum/core/vm" - "github.com/evmos/ethermint/crypto/ethsecp256k1" ) type DASignersTestSuite struct { @@ -44,8 +44,7 @@ func (suite *DASignersTestSuite) AddDelegation(from string, to string, amount ma suite.Require().NoError(err) validator, found := suite.StakingKeeper.GetValidator(suite.Ctx, valAddr) if !found { - consPriv, err := ethsecp256k1.GenerateKey() - suite.Require().NoError(err) + consPriv := ed25519.GenPrivKey() newValidator, err := stakingtypes.NewValidator(valAddr, consPriv.PubKey(), stakingtypes.Description{}) suite.Require().NoError(err) validator = newValidator diff --git a/precompiles/interfaces/contracts/IStaking.sol b/precompiles/interfaces/contracts/IStaking.sol index 77c1dc77..a31daaab 100644 --- a/precompiles/interfaces/contracts/IStaking.sol +++ b/precompiles/interfaces/contracts/IStaking.sol @@ -185,7 +185,7 @@ interface IStaking { Description memory description, CommissionRates memory commission, uint minSelfDelegation, - string memory pubkey, + string memory pubkey, // 0gchaind tendermint show-validator uint value ) external; diff --git a/precompiles/staking/query_test.go b/precompiles/staking/query_test.go new file mode 100644 index 00000000..db9bb0e3 --- /dev/null +++ b/precompiles/staking/query_test.go @@ -0,0 +1,65 @@ +package staking_test + +import ( + stakingprecompile "github.com/0glabs/0g-chain/precompiles/staking" + sdk "github.com/cosmos/cosmos-sdk/types" + query "github.com/cosmos/cosmos-sdk/types/query" +) + +func (s *StakingTestSuite) TestValidators() { + method := stakingprecompile.StakingFunctionValidators + + testCases := []struct { + name string + malleate func() []byte + postCheck func(bz []byte) + gas uint64 + expErr bool + errContains string + }{ + { + "success", + func() []byte { + input, err := s.abi.Pack( + method, + "", + query.PageRequest{ + Limit: 10, + CountTotal: true, + }, + ) + s.Assert().NoError(err) + return input + }, + func(data []byte) { + out, err := s.abi.Methods[method].Outputs.Unpack(data) + s.Require().NoError(err, "failed to unpack output") + validators := out[0].([]stakingprecompile.Validator) + paginationResult := out[1].(stakingprecompile.PageResponse) + s.Assert().EqualValues(3, len(validators)) + s.Assert().EqualValues(3, paginationResult.Total) + }, + 100000, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + s.AddDelegation(s.signerOne.HexAddr, s.signerTwo.HexAddr, sdk.NewIntFromUint64(1000000)) + + bz, err := s.runTx(tc.malleate(), s.signerOne, 10000000) + + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.errContains) + } else { + s.Require().NoError(err) + s.Require().NotNil(bz) + tc.postCheck(bz) + } + }) + } +} diff --git a/precompiles/staking/staking_test.go b/precompiles/staking/staking_test.go new file mode 100644 index 00000000..86c7afac --- /dev/null +++ b/precompiles/staking/staking_test.go @@ -0,0 +1,102 @@ +package staking_test + +import ( + "math/big" + "strings" + "testing" + + "cosmossdk.io/math" + stakingprecompile "github.com/0glabs/0g-chain/precompiles/staking" + "github.com/0glabs/0g-chain/precompiles/testutil" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" + evmtypes "github.com/evmos/ethermint/x/evm/types" + "github.com/stretchr/testify/suite" +) + +type StakingTestSuite struct { + testutil.PrecompileTestSuite + + abi abi.ABI + addr common.Address + staking *stakingprecompile.StakingPrecompile + stakingKeeper *stakingkeeper.Keeper + signerOne *testutil.TestSigner + signerTwo *testutil.TestSigner +} + +func (suite *StakingTestSuite) SetupTest() { + suite.PrecompileTestSuite.SetupTest() + + suite.stakingKeeper = suite.App.GetStakingKeeper() + + suite.addr = common.HexToAddress(stakingprecompile.PrecompileAddress) + + precompiles := suite.EvmKeeper.GetPrecompiles() + precompile, ok := precompiles[suite.addr] + suite.Assert().EqualValues(ok, true) + + suite.staking = precompile.(*stakingprecompile.StakingPrecompile) + + suite.signerOne = suite.GenSigner() + suite.signerTwo = suite.GenSigner() + + abi, err := abi.JSON(strings.NewReader(stakingprecompile.StakingABI)) + suite.Assert().NoError(err) + suite.abi = abi +} + +func (suite *StakingTestSuite) AddDelegation(from string, to string, amount math.Int) { + accAddr, err := sdk.AccAddressFromHexUnsafe(from) + suite.Require().NoError(err) + valAddr, err := sdk.ValAddressFromHex(to) + suite.Require().NoError(err) + validator, found := suite.StakingKeeper.GetValidator(suite.Ctx, valAddr) + if !found { + consPriv := ed25519.GenPrivKey() + newValidator, err := stakingtypes.NewValidator(valAddr, consPriv.PubKey(), stakingtypes.Description{}) + suite.Require().NoError(err) + validator = newValidator + } + validator.Tokens = validator.Tokens.Add(amount) + validator.DelegatorShares = validator.DelegatorShares.Add(amount.ToLegacyDec()) + suite.StakingKeeper.SetValidator(suite.Ctx, validator) + bonded := suite.stakingKeeper.GetDelegatorBonded(suite.Ctx, accAddr) + suite.StakingKeeper.SetDelegation(suite.Ctx, stakingtypes.Delegation{ + DelegatorAddress: accAddr.String(), + ValidatorAddress: valAddr.String(), + Shares: bonded.Add(amount).ToLegacyDec(), + }) +} + +func (suite *StakingTestSuite) runTx(input []byte, signer *testutil.TestSigner, gas uint64) ([]byte, error) { + contract := vm.NewPrecompile(vm.AccountRef(signer.Addr), vm.AccountRef(suite.addr), big.NewInt(0), gas) + contract.Input = input + + msgEthereumTx := evmtypes.NewTx(suite.EvmKeeper.ChainID(), 0, &suite.addr, big.NewInt(0), gas, big.NewInt(0), big.NewInt(0), big.NewInt(0), input, nil) + msgEthereumTx.From = signer.HexAddr + err := msgEthereumTx.Sign(suite.EthSigner, signer.Signer) + suite.Assert().NoError(err, "failed to sign Ethereum message") + + proposerAddress := suite.Ctx.BlockHeader().ProposerAddress + cfg, err := suite.EvmKeeper.EVMConfig(suite.Ctx, proposerAddress, suite.EvmKeeper.ChainID()) + suite.Assert().NoError(err, "failed to instantiate EVM config") + + msg, err := msgEthereumTx.AsMessage(suite.EthSigner, big.NewInt(0)) + suite.Assert().NoError(err, "failed to instantiate Ethereum message") + + evm := suite.EvmKeeper.NewEVM(suite.Ctx, msg, cfg, nil, suite.Statedb) + precompiles := suite.EvmKeeper.GetPrecompiles() + evm.WithPrecompiles(precompiles, []common.Address{suite.addr}) + + return suite.staking.Run(evm, contract, false) +} + +func TestKeeperSuite(t *testing.T) { + suite.Run(t, new(StakingTestSuite)) +} diff --git a/precompiles/staking/tx_test.go b/precompiles/staking/tx_test.go new file mode 100644 index 00000000..0cd7f296 --- /dev/null +++ b/precompiles/staking/tx_test.go @@ -0,0 +1,124 @@ +package staking_test + +import ( + "encoding/base64" + "math/big" + + "cosmossdk.io/math" + stakingprecompile "github.com/0glabs/0g-chain/precompiles/staking" + "github.com/ethereum/go-ethereum/common" +) + +func (s *StakingTestSuite) TestCreateValidator() { + method := stakingprecompile.StakingFunctionCreateValidator + description := stakingprecompile.Description{ + Moniker: "test node", + Identity: "test node identity", + Website: "http://test.node.com", + SecurityContact: "test node security contract", + Details: "test node details", + } + commission := stakingprecompile.CommissionRates{ + Rate: math.LegacyOneDec().BigInt(), + MaxRate: math.LegacyOneDec().BigInt(), + MaxChangeRate: math.LegacyOneDec().BigInt(), + } + minSelfDelegation := big.NewInt(1) + pubkey := "eh/aR8BGUBIYI/Ust0NVBxZafLDAm7344F9dKzZU+7g=" + value := big.NewInt(100000000) + + testCases := []struct { + name string + malleate func() []byte + gas uint64 + callerAddress *common.Address + postCheck func(data []byte) + expError bool + errContains string + }{ + { + "fail - ErrPubKeyInvalidLength", + func() []byte { + input, err := s.abi.Pack( + method, + description, + commission, + minSelfDelegation, + s.signerOne.HexAddr, + value, + ) + s.Assert().NoError(err) + return input + }, + 200000, + nil, + func([]byte) {}, + true, + stakingprecompile.ErrPubKeyInvalidLength, + }, + { + "success", + func() []byte { + input, err := s.abi.Pack( + method, + description, + commission, + minSelfDelegation, + pubkey, + value, + ) + s.Assert().NoError(err) + return input + }, + 200000, + nil, + func(data []byte) {}, + false, + "", + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + s.SetupTest() + + bz, err := s.runTx(tc.malleate(), s.signerOne, 10000000) + + if tc.expError { + s.Require().ErrorContains(err, tc.errContains) + s.Require().Empty(bz) + } else { + s.Require().NoError(err) + // query the validator in the staking keeper + validator := s.StakingKeeper.Validator(s.Ctx, s.signerOne.ValAddr) + s.Require().NoError(err) + + s.Require().NotNil(validator, "expected validator not to be nil") + tc.postCheck(bz) + + isBonded := validator.IsBonded() + s.Require().Equal(false, isBonded, "expected validator bonded to be %t; got %t", false, isBonded) + + consPubKey, err := validator.ConsPubKey() + s.Require().NoError(err) + consPubKeyBase64 := base64.StdEncoding.EncodeToString(consPubKey.Bytes()) + s.Require().Equal(pubkey, consPubKeyBase64, "expected validator pubkey to be %s; got %s", pubkey, consPubKeyBase64) + + operator := validator.GetOperator() + s.Require().Equal(s.signerOne.ValAddr, operator, "expected validator operator to be %s; got %s", s.signerOne.ValAddr, operator) + + commissionRate := validator.GetCommission() + s.Require().Equal(commission.Rate.String(), commissionRate.BigInt().String(), "expected validator commission rate to be %s; got %s", commission.Rate.String(), commissionRate.String()) + + valMinSelfDelegation := validator.GetMinSelfDelegation() + s.Require().Equal(minSelfDelegation.String(), valMinSelfDelegation.String(), "expected validator min self delegation to be %s; got %s", minSelfDelegation.String(), valMinSelfDelegation.String()) + + moniker := validator.GetMoniker() + s.Require().Equal(description.Moniker, moniker, "expected validator moniker to be %s; got %s", description.Moniker, moniker) + + jailed := validator.IsJailed() + s.Require().Equal(false, jailed, "expected validator jailed to be %t; got %t", false, jailed) + } + }) + } +} diff --git a/precompiles/staking/types.go b/precompiles/staking/types.go index c30e4e99..dcd50569 100644 --- a/precompiles/staking/types.go +++ b/precompiles/staking/types.go @@ -17,29 +17,29 @@ import ( "github.com/ethereum/go-ethereum/common" ) -type Commission struct { +type Commission = struct { CommissionRates CommissionRates `json:"commissionRates"` UpdateTime *big.Int `json:"updateTime"` } -type CommissionRates struct { +type CommissionRates = struct { Rate *big.Int `json:"rate"` MaxRate *big.Int `json:"maxRate"` MaxChangeRate *big.Int `json:"maxChangeRate"` } -type Delegation struct { +type Delegation = struct { DelegatorAddress string `json:"delegatorAddress"` ValidatorAddress string `json:"validatorAddress"` Shares *big.Int `json:"shares"` } -type DelegationResponse struct { +type DelegationResponse = struct { Delegation Delegation `json:"delegation"` Balance *big.Int `json:"balance"` } -type Description struct { +type Description = struct { Moniker string `json:"moniker"` Identity string `json:"identity"` Website string `json:"website"` @@ -47,12 +47,12 @@ type Description struct { Details string `json:"details"` } -type NullableUint struct { +type NullableUint = struct { IsNull bool `json:"isNull"` Value *big.Int `json:"value"` } -type PageRequest struct { +type PageRequest = struct { Key []byte `json:"key"` Offset uint64 `json:"offset"` Limit uint64 `json:"limit"` @@ -60,12 +60,12 @@ type PageRequest struct { Reverse bool `json:"reverse"` } -type PageResponse struct { +type PageResponse = struct { NextKey []byte `json:"nextKey"` Total uint64 `json:"total"` } -type Params struct { +type Params = struct { UnbondingTime int64 `json:"unbondingTime"` MaxValidators uint32 `json:"maxValidators"` MaxEntries uint32 `json:"maxEntries"` @@ -74,14 +74,14 @@ type Params struct { MinCommissionRate *big.Int `json:"minCommissionRate"` } -type Redelegation struct { +type Redelegation = struct { DelegatorAddress string `json:"delegatorAddress"` ValidatorSrcAddress string `json:"validatorSrcAddress"` ValidatorDstAddress string `json:"validatorDstAddress"` Entries []RedelegationEntry `json:"entries"` } -type RedelegationEntry struct { +type RedelegationEntry = struct { CreationHeight int64 `json:"creationHeight"` CompletionTime int64 `json:"completionTime"` InitialBalance *big.Int `json:"initialBalance"` @@ -90,23 +90,23 @@ type RedelegationEntry struct { UnbondingOnHoldRefCount int64 `json:"unbondingOnHoldRefCount"` } -type RedelegationEntryResponse struct { +type RedelegationEntryResponse = struct { RedelegationEntry RedelegationEntry `json:"redelegationEntry"` Balance *big.Int `json:"balance"` } -type RedelegationResponse struct { +type RedelegationResponse = struct { Redelegation Redelegation `json:"redelegation"` Entries []RedelegationEntryResponse `json:"entries"` } -type UnbondingDelegation struct { +type UnbondingDelegation = struct { DelegatorAddress string `json:"delegatorAddress"` ValidatorAddress string `json:"validatorAddress"` Entries []UnbondingDelegationEntry `json:"entries"` } -type UnbondingDelegationEntry struct { +type UnbondingDelegationEntry = struct { CreationHeight int64 `json:"creationHeight"` CompletionTime int64 `json:"completionTime"` InitialBalance *big.Int `json:"initialBalance"` @@ -115,7 +115,7 @@ type UnbondingDelegationEntry struct { UnbondingOnHoldRefCount int64 `json:"unbondingOnHoldRefCount"` } -type Validator struct { +type Validator = struct { OperatorAddress string `json:"operatorAddress"` ConsensusPubkey string `json:"consensusPubkey"` Jailed bool `json:"jailed"` diff --git a/precompiles/testutil/suite.go b/precompiles/testutil/suite.go index d3206217..8ef65fc7 100644 --- a/precompiles/testutil/suite.go +++ b/precompiles/testutil/suite.go @@ -1,6 +1,7 @@ package testutil import ( + "math/big" "strings" "github.com/0glabs/0g-chain/app" @@ -44,15 +45,25 @@ type TestSigner struct { HexAddr string PrivKey cryptotypes.PrivKey Signer keyring.Signer + ValAddr sdk.ValAddress + AccAddr sdk.AccAddress } -func GenSigner() *TestSigner { +func (suite *PrecompileTestSuite) GenSigner() *TestSigner { var s TestSigner addr, priv := emtests.NewAddrKey() s.PrivKey = priv s.Addr = addr s.HexAddr = precopmiles_common.ToLowerHexWithoutPrefix(s.Addr) s.Signer = emtests.NewSigner(priv) + valAddr, _ := sdk.ValAddressFromHex(s.HexAddr) + accAddr, _ := sdk.AccAddressFromHexUnsafe(s.HexAddr) + s.ValAddr = valAddr + s.AccAddr = accAddr + + // 10000 a0gi for test + suite.App.GetBankKeeper().MintCoins(suite.Ctx, "mint", sdk.NewCoins(chaincfg.MakeCoinForGasDenom(big.NewInt(10000000000)))) + suite.App.GetBankKeeper().SendCoinsFromModuleToAccount(suite.Ctx, "mint", accAddr, sdk.NewCoins(chaincfg.MakeCoinForGasDenom(big.NewInt(10000000000)))) return &s }