Skip to content

Commit d21fcc5

Browse files
authored
test(evm): add staking delegate native-token-inflation guard (#351)
Add a real inflation regression guard for the staking precompile, using the same technique as the bank guard: the delegator is made a 7702-style delegated account (SetCode) so its stateObject balance is authoritative at Commit. Verified it is load-bearing: with the staking BalanceHandler disabled the test fails with supply += the delegated amount; with #332's BalanceHandler wired it stays flat. Guards against a regression that drops the handler from staking.
1 parent 38bae8c commit d21fcc5

1 file changed

Lines changed: 123 additions & 0 deletions

File tree

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package staking_test
2+
3+
import (
4+
"math/big"
5+
"testing"
6+
"time"
7+
8+
"cosmossdk.io/math"
9+
"github.com/stretchr/testify/suite"
10+
11+
"github.com/cometbft/cometbft/crypto/tmhash"
12+
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
13+
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
14+
sdk "github.com/cosmos/cosmos-sdk/types"
15+
teststaking "github.com/cosmos/cosmos-sdk/x/staking/testutil"
16+
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
17+
evmtestutil "github.com/cosmos/evm/testutil"
18+
"github.com/cosmos/evm/x/vm/statedb"
19+
"github.com/ethereum/go-ethereum/common"
20+
21+
"github.com/mocachain/moca/v2/app"
22+
"github.com/mocachain/moca/v2/testutil"
23+
utiltx "github.com/mocachain/moca/v2/testutil/tx"
24+
"github.com/mocachain/moca/v2/utils"
25+
"github.com/mocachain/moca/v2/x/evm/precompiles/staking"
26+
)
27+
28+
type InflationTestSuite struct {
29+
suite.Suite
30+
ctx sdk.Context
31+
app *app.Moca
32+
address common.Address
33+
}
34+
35+
func TestInflationTestSuite(t *testing.T) {
36+
suite.Run(t, new(InflationTestSuite))
37+
}
38+
39+
func (s *InflationTestSuite) SetupTest() {
40+
checkTx := false
41+
chainID := utils.TestnetChainID + "-1"
42+
43+
s.app = app.EthSetup(checkTx, nil)
44+
s.ctx = s.app.NewContext(checkTx)
45+
s.address = common.HexToAddress("0x1111111111111111111111111111111111111111")
46+
47+
valConsAddr, privkey := utiltx.NewAddrKey()
48+
pkAny, err := codectypes.NewAnyWithValue(privkey.PubKey())
49+
s.Require().NoError(err)
50+
validator := stakingtypes.Validator{
51+
OperatorAddress: sdk.AccAddress(s.address.Bytes()).String(),
52+
ConsensusPubkey: pkAny,
53+
}
54+
err = s.app.StakingKeeper.SetValidator(s.ctx, validator)
55+
s.Require().NoError(err)
56+
err = s.app.StakingKeeper.SetValidatorByConsAddr(s.ctx, validator)
57+
s.Require().NoError(err)
58+
59+
safeTime := time.Date(2025, time.January, 10, 0, 0, 0, 0, time.UTC)
60+
header := evmtestutil.NewHeader(1, safeTime, chainID, sdk.ConsAddress(valConsAddr.Bytes()), tmhash.Sum([]byte("app")), tmhash.Sum([]byte("validators")))
61+
s.ctx = s.ctx.WithBlockHeader(header).WithChainID(chainID)
62+
63+
err = testutil.FundAccountWithBaseDenom(s.ctx, s.app.BankKeeper, sdk.AccAddress(s.address.Bytes()), 1_000_000_000_000)
64+
s.Require().NoError(err)
65+
}
66+
67+
// TestDelegate_NoSupplyInflation is the native-token-inflation regression guard
68+
// for the staking precompile (delegate moves coins delegator -> bonded pool).
69+
// Like the bank guard, the delegator is made a 7702-style delegated account
70+
// (SetCode) so its stateObject balance is authoritative at Commit; without the
71+
// BalanceHandler reconciliation, Commit would mint the debited amount back.
72+
func (s *InflationTestSuite) TestDelegate_NoSupplyInflation() {
73+
// Bond in the base denom so the funded delegator can stake it.
74+
zeroDec := math.LegacyZeroDec()
75+
stakingParams, err := s.app.StakingKeeper.GetParams(s.ctx)
76+
s.Require().NoError(err)
77+
stakingParams.BondDenom = utils.BaseDenom
78+
stakingParams.MinCommissionRate = zeroDec
79+
s.Require().NoError(s.app.StakingKeeper.SetParams(s.ctx, stakingParams))
80+
81+
// Create a bonded validator to delegate to.
82+
valPriv := ed25519.GenPrivKey()
83+
valAddr, _ := utiltx.NewAccAddressAndKey()
84+
s.Require().NoError(testutil.FundAccountWithBaseDenom(s.ctx, s.app.BankKeeper, valAddr, 1_000_000))
85+
helper := teststaking.NewHelper(s.T(), s.ctx, s.app.StakingKeeper)
86+
helper.Commission = stakingtypes.NewCommissionRates(zeroDec, zeroDec, zeroDec)
87+
helper.Denom = utils.BaseDenom
88+
helper.CreateValidator(valAddr, valPriv.PubKey(), math.NewInt(500_000), true)
89+
_, err = s.app.StakingKeeper.EndBlocker(s.ctx)
90+
s.Require().NoError(err)
91+
92+
s.mustEnableStaticPrecompiles()
93+
94+
supplyBefore := s.app.BankKeeper.GetSupply(s.ctx, utils.BaseDenom).Amount
95+
96+
input := s.mustPackDelegateInput(common.BytesToAddress(valAddr.Bytes()), big.NewInt(100_000))
97+
precompileAddr := staking.GetAddress()
98+
stateDB := statedb.New(s.ctx, s.app.EvmKeeper, statedb.NewEmptyTxConfig())
99+
// 7702-style: give the delegator code and load its stateObject so its cached
100+
// balance is authoritative at Commit (the inflation trigger).
101+
stateDB.SetCode(s.address, []byte{0x60, 0x00})
102+
_ = stateDB.GetBalance(s.address)
103+
res, err := s.app.EvmKeeper.CallEVMWithData(s.ctx, stateDB, s.address, &precompileAddr, input, true, false, nil)
104+
s.Require().NoError(err)
105+
s.Require().False(res.Failed(), "evm call reverted: %s", res.VmError)
106+
107+
supplyAfter := s.app.BankKeeper.GetSupply(s.ctx, utils.BaseDenom).Amount
108+
s.Require().Equal(supplyBefore.String(), supplyAfter.String(), "delegate must not inflate total supply")
109+
}
110+
111+
func (s *InflationTestSuite) mustEnableStaticPrecompiles() {
112+
evmParams := s.app.EvmKeeper.GetParams(s.ctx)
113+
evmParams.EvmDenom = utils.BaseDenom
114+
evmParams.ActiveStaticPrecompiles = app.MocaActiveStaticPrecompiles()
115+
s.Require().NoError(s.app.EvmKeeper.SetParams(s.ctx, evmParams))
116+
}
117+
118+
func (s *InflationTestSuite) mustPackDelegateInput(validator common.Address, amount *big.Int) []byte {
119+
method := staking.MustMethod(staking.DelegateMethodName)
120+
packedArgs, err := method.Inputs.Pack(validator, amount)
121+
s.Require().NoError(err)
122+
return append(append([]byte{}, method.ID...), packedArgs...)
123+
}

0 commit comments

Comments
 (0)