Skip to content

Commit 5e8c741

Browse files
authored
test(evm): add payment deposit native-token-inflation guard (#352)
Add a real inflation regression guard for the payment precompile (deposit moves coins depositor -> payment module). Payment was the balance-handler-audit's highest-risk module because of its StreamRecord ledger; this proves the real coin move (SendCoinsFromAccountToModule) is reconciled by the BalanceHandler. Verified load-bearing: with the payment BalanceHandler disabled the test fails with supply += the deposit amount; with #332's handler wired it stays flat.
1 parent 03b144d commit 5e8c741

1 file changed

Lines changed: 108 additions & 0 deletions

File tree

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package payment_test
2+
3+
import (
4+
"math/big"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/suite"
9+
10+
"github.com/cometbft/cometbft/crypto/tmhash"
11+
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
12+
sdk "github.com/cosmos/cosmos-sdk/types"
13+
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
14+
evmtestutil "github.com/cosmos/evm/testutil"
15+
"github.com/cosmos/evm/x/vm/statedb"
16+
"github.com/ethereum/go-ethereum/common"
17+
18+
"github.com/mocachain/moca/v2/app"
19+
"github.com/mocachain/moca/v2/testutil"
20+
utiltx "github.com/mocachain/moca/v2/testutil/tx"
21+
"github.com/mocachain/moca/v2/utils"
22+
"github.com/mocachain/moca/v2/x/evm/precompiles/payment"
23+
paymenttypes "github.com/mocachain/moca/v2/x/payment/types"
24+
)
25+
26+
type InflationTestSuite struct {
27+
suite.Suite
28+
ctx sdk.Context
29+
app *app.Moca
30+
address common.Address
31+
}
32+
33+
func TestInflationTestSuite(t *testing.T) {
34+
suite.Run(t, new(InflationTestSuite))
35+
}
36+
37+
func (s *InflationTestSuite) SetupTest() {
38+
checkTx := false
39+
chainID := utils.TestnetChainID + "-1"
40+
41+
s.app = app.EthSetup(checkTx, nil)
42+
s.ctx = s.app.NewContext(checkTx)
43+
s.address = common.HexToAddress("0x1111111111111111111111111111111111111111")
44+
45+
valConsAddr, privkey := utiltx.NewAddrKey()
46+
pkAny, err := codectypes.NewAnyWithValue(privkey.PubKey())
47+
s.Require().NoError(err)
48+
validator := stakingtypes.Validator{
49+
OperatorAddress: sdk.AccAddress(s.address.Bytes()).String(),
50+
ConsensusPubkey: pkAny,
51+
}
52+
err = s.app.StakingKeeper.SetValidator(s.ctx, validator)
53+
s.Require().NoError(err)
54+
err = s.app.StakingKeeper.SetValidatorByConsAddr(s.ctx, validator)
55+
s.Require().NoError(err)
56+
57+
safeTime := time.Date(2025, time.January, 10, 0, 0, 0, 0, time.UTC)
58+
header := evmtestutil.NewHeader(1, safeTime, chainID, sdk.ConsAddress(valConsAddr.Bytes()), tmhash.Sum([]byte("app")), tmhash.Sum([]byte("validators")))
59+
s.ctx = s.ctx.WithBlockHeader(header).WithChainID(chainID)
60+
61+
err = testutil.FundAccountWithBaseDenom(s.ctx, s.app.BankKeeper, sdk.AccAddress(s.address.Bytes()), 1_000_000_000_000)
62+
s.Require().NoError(err)
63+
}
64+
65+
// TestDeposit_NoSupplyInflation is the native-token-inflation regression guard
66+
// for the payment precompile (deposit moves coins depositor -> payment module).
67+
// The depositor is made a 7702-style delegated account (SetCode) so its
68+
// stateObject balance is authoritative at Commit; without the BalanceHandler
69+
// reconciliation, Commit would mint the debited amount back.
70+
func (s *InflationTestSuite) TestDeposit_NoSupplyInflation() {
71+
// EthSetup's genesis leaves payment params zero-valued, so install defaults
72+
// (DefaultFeeDenom is the base denom "amoca") before depositing.
73+
s.Require().NoError(s.app.PaymentKeeper.SetParams(s.ctx, paymenttypes.DefaultParams()))
74+
75+
s.mustEnableStaticPrecompiles()
76+
77+
supplyBefore := s.app.BankKeeper.GetSupply(s.ctx, utils.BaseDenom).Amount
78+
79+
// self-deposit: the caller account exists, so a stream record is created
80+
to := sdk.AccAddress(s.address.Bytes()).String()
81+
input := s.mustPackDepositInput(to, big.NewInt(100_000))
82+
precompileAddr := payment.GetAddress()
83+
stateDB := statedb.New(s.ctx, s.app.EvmKeeper, statedb.NewEmptyTxConfig())
84+
// 7702-style: give the depositor code and load its stateObject so its cached
85+
// balance is authoritative at Commit (the inflation trigger).
86+
stateDB.SetCode(s.address, []byte{0x60, 0x00})
87+
_ = stateDB.GetBalance(s.address)
88+
res, err := s.app.EvmKeeper.CallEVMWithData(s.ctx, stateDB, s.address, &precompileAddr, input, true, false, nil)
89+
s.Require().NoError(err)
90+
s.Require().False(res.Failed(), "evm call reverted: %s", res.VmError)
91+
92+
supplyAfter := s.app.BankKeeper.GetSupply(s.ctx, utils.BaseDenom).Amount
93+
s.Require().Equal(supplyBefore.String(), supplyAfter.String(), "deposit must not inflate total supply")
94+
}
95+
96+
func (s *InflationTestSuite) mustEnableStaticPrecompiles() {
97+
evmParams := s.app.EvmKeeper.GetParams(s.ctx)
98+
evmParams.EvmDenom = utils.BaseDenom
99+
evmParams.ActiveStaticPrecompiles = app.MocaActiveStaticPrecompiles()
100+
s.Require().NoError(s.app.EvmKeeper.SetParams(s.ctx, evmParams))
101+
}
102+
103+
func (s *InflationTestSuite) mustPackDepositInput(to string, amount *big.Int) []byte {
104+
method := payment.GetAbiMethod(payment.DepositMethodName)
105+
packedArgs, err := method.Inputs.Pack(to, amount)
106+
s.Require().NoError(err)
107+
return append(append([]byte{}, method.ID...), packedArgs...)
108+
}

0 commit comments

Comments
 (0)