Skip to content

Conversation

@aeddi
Copy link
Contributor

@aeddi aeddi commented Dec 9, 2025

Summary

This PR refactors the gas metering system to make its usage more consistent across the codebase, supporting detailed metering and reporting.

Changes

1. Move gas meter to dedicated package (a9f9eeb)

This commit moves the gas meter out of the tm2/pkg/store package to its own package under tm2/pkg/gas, since it's not only related to store operations anymore. It also renames some functions to be more aligned with Go best practices (e.g. gas.NewGasMetergas.NewMeter).

2. Centralize gas operations and costs (00cb42e)

This commit makes gas metering usage more consistent. Previously, the metering was scattered across the codebase:

  • Store: descriptors and costs located in gas package
  • Opcode: costs and only one literal descriptor for all opcodes located in gnovm/pkg/gnolang/machine.go, using a multiplier in gnovm/pkg/gnolang/machine.go
  • Alloc: literal descriptor and cost located in gnovm/pkg/gnolang/alloc.go
  • Garbage: literal descriptor and cost located in gnovm/pkg/gnolang/garbage_collector.go, using a multiplier in gnovm/pkg/gnolang/machine.go
  • Parsing: literal descriptor and costs located in gnovm/pkg/gnolang/go2gno.go
  • KVStore: literal descriptor and default costs located in gnovm/pkg/gnolang/store.go, costs modifiable by param of the store
  • Native print: literal descriptor and cost located in gnovm/pkg/gnolang/uverse.go
  • Transaction: literal descriptor and costs located in tm2/pkg/sdk/auth/ante.go, costs can be changed by params in tm2/pkg/sdk/auth/params.go

After this commit:

  • Everything is centralized inside the gas package with operations in tm2/pkg/gas/operation.go and the associated costs in tm2/pkg/gas/config.go.
  • The cost config (including the global multiplier) is passed at gas meter creation and the API requires specifying an operation ID + a multiplier. So:
    • If a cost is flat (like for a CPU opcode operation), the usage is gasMeter.GasConsume(OpCPUCall, 1).
    • If a cost is per unit (like printing bytes), the usage is gasMeter.GasConsume(OpNativePrintPerByte, len(output)).
  • Cost and multiplier are both float64 to allow division through this simple API (multiply by 0.5 to get half, etc.), but the gas counting still uses int64.

3. Add float support to overflow package (ebdf7a1)

This commit adds support for float operations to the overflow package, since the gas metering now uses float64 for cost and multiplier.

4. Add detailed gas reporting (dd954ff)

This commit adds detailed gas report support. The gas meter now keeps track of how many times an operation was performed and how much gas it consumed in total. Operations are grouped into categories so we can produce reports per category.

Note: Category mapping is done on access to keep gas metering as fast as possible by relying on a simple fixed-size array to store the counters.

5. Deduplicate tx info printing (07b6cf2)

This commit deduplicates the tx info printing that was duplicated in 3 different places. Now this part only calls the PrintTxInfo function from info.go.

6. Add verbose flag to gnokey (92b7b86)

This commit adds a new verbosity flag to gnokey to display 4 levels of gas reports.

-v 0     verbosity level for gas detail:
            - 0: no detail (default)
            - 1: gas by category
            - 2: gas by category + operations (excluding zero count)
            - 3: gas by category + all operations (including zero count)

Note: The default keep the current behavior so we don't break any existing tooling relying on gnokey output.

Examples

Level 0 output
$> gnokey maketx call -pkgpath "gno.land/r/demo/counter" -func "Increment" -gas-fee 10000000ugnot -gas-wanted 8000000 -broadcast test1 -v 0
Enter password.
(13 int)


OK!
GAS WANTED: 8000000
GAS USED:   134680
HEIGHT:     26
EVENTS:     []
INFO:
TX HASH:    oaxNsfsYJHOqIoZS0naoac6VRfmch7jBU9JvOKj5fsI=
Level 1 output
$> gnokey maketx call -pkgpath "gno.land/r/demo/counter" -func "Increment" -gas-fee 10000000ugnot -gas-wanted 8000000 -broadcast test1 -v 1
Enter password.
(14 int)


OK!
GAS WANTED: 8000000
GAS USED:           Operation: 73    Gas: 134680
├── CPU:            Operation: 14    Gas: 941
├── KVStore:        Operation: 8     Gas: 88384
├── Memory:         Operation: 5     Gas: 1192
├── Parsing:        Operation: 14    Gas: 23
├── Store:          Operation: 30    Gas: 40700
└── Transaction:    Operation: 2     Gas: 3440

HEIGHT:     28
EVENTS:     []
INFO:
TX HASH:    +oOuQjoLU67TDW8uwAkgzM/OM958bY/1eBdpF8ln66o=
Level 2 output
$> gnokey maketx call -pkgpath "gno.land/r/demo/counter" -func "Increment" -gas-fee 10000000ugnot -gas-wanted 8000000 -broadcast test1 -v 2
Enter password.
(15 int)


OK!
GAS WANTED: 8000000
GAS USED:                                 Operation: 73    Gas: 134680
├── CPU:                                  Operation: 14    Gas: 941
│   ├── CPUHalt:                          Operation: 1     Gas: 1
│   ├── CPUPrecall:                       Operation: 1     Gas: 207
│   ├── CPUEnterCrossing:                 Operation: 1     Gas: 100
│   ├── CPUCall:                          Operation: 1     Gas: 256
│   ├── CPUReturn:                        Operation: 1     Gas: 38
│   ├── CPUEval:                          Operation: 5     Gas: 145
│   ├── CPUSelector:                      Operation: 1     Gas: 32
│   ├── CPUInc:                           Operation: 1     Gas: 76
│   └── CPUBody:                          Operation: 2     Gas: 86
├── KVStore:                              Operation: 8     Gas: 88384
│   ├── KVStoreGetObjectPerByte:          Operation: 6     Gas: 45344
│   ├── KVStoreSetObjectPerByte:          Operation: 1     Gas: 3216
│   └── KVStoreGetPackageRealmPerByte:    Operation: 1     Gas: 39824
├── Memory:                               Operation: 5     Gas: 1192
│   └── MemoryAllocPerByte:               Operation: 5     Gas: 1192
├── Parsing:                              Operation: 14    Gas: 23
│   ├── ParsingToken:                     Operation: 8     Gas: 8
│   └── ParsingNesting:                   Operation: 6     Gas: 15
├── Store:                                Operation: 30    Gas: 40700
│   ├── StoreReadFlat:                    Operation: 10    Gas: 10000
│   ├── StoreReadPerByte:                 Operation: 10    Gas: 3450
│   ├── StoreWriteFlat:                   Operation: 5     Gas: 10000
│   └── StoreWritePerByte:                Operation: 5     Gas: 17250
└── Transaction:                          Operation: 2     Gas: 3440
    ├── TransactionPerByte:               Operation: 1     Gas: 2440
    └── TxansactionSigVerifySecp256k1:    Operation: 1     Gas: 1000

HEIGHT:     30
EVENTS:     []
INFO:
TX HASH:    mZpwriqYnXSPYBYUhsu6CrUI4NQovvIiwZ1vvvp2HnU=
Level 3 output
$> gnokey maketx call -pkgpath "gno.land/r/demo/counter" -func "Increment" -gas-fee 10000000ugnot -gas-wanted 8000000 -broadcast test1 -v 3
Enter password.
(16 int)


OK!
GAS WANTED: 8000000
GAS USED:                                 Operation: 73    Gas: 134680
├── BlockGas:                             Operation: 0     Gas: 0
│   └── BlockGasSum:                      Operation: 0     Gas: 0
├── CPU:                                  Operation: 14    Gas: 941
│   ├── CPUInvalid:                       Operation: 0     Gas: 0
│   ├── CPUHalt:                          Operation: 1     Gas: 1
│   ├── CPUNoop:                          Operation: 0     Gas: 0
│   ├── CPUExec:                          Operation: 0     Gas: 0
│   ├── CPUPrecall:                       Operation: 1     Gas: 207
│   ├── CPUEnterCrossing:                 Operation: 1     Gas: 100
│   ├── CPUCall:                          Operation: 1     Gas: 256
│   ├── CPUCallNativeBody:                Operation: 0     Gas: 0
│   ├── CPUDefer:                         Operation: 0     Gas: 0
│   ├── CPUCallDeferNativeBody:           Operation: 0     Gas: 0
│   ├── CPUGo:                            Operation: 0     Gas: 0
│   ├── CPUSelect:                        Operation: 0     Gas: 0
│   ├── CPUSwitchClause:                  Operation: 0     Gas: 0
│   ├── CPUSwitchClauseCase:              Operation: 0     Gas: 0
│   ├── CPUTypeSwitch:                    Operation: 0     Gas: 0
│   ├── CPUIfCond:                        Operation: 0     Gas: 0
│   ├── CPUPopValue:                      Operation: 0     Gas: 0
│   ├── CPUPopResults:                    Operation: 0     Gas: 0
│   ├── CPUPopBlock:                      Operation: 0     Gas: 0
│   ├── CPUPopFrameAndReset:              Operation: 0     Gas: 0
│   ├── CPUPanic1:                        Operation: 0     Gas: 0
│   ├── CPUPanic2:                        Operation: 0     Gas: 0
│   ├── CPUReturn:                        Operation: 1     Gas: 38
│   ├── CPUReturnAfterCopy:               Operation: 0     Gas: 0
│   ├── CPUReturnFromBlock:               Operation: 0     Gas: 0
│   ├── CPUReturnToBlock:                 Operation: 0     Gas: 0
│   ├── CPUUpos:                          Operation: 0     Gas: 0
│   ├── CPUUneg:                          Operation: 0     Gas: 0
│   ├── CPUUnot:                          Operation: 0     Gas: 0
│   ├── CPUUxor:                          Operation: 0     Gas: 0
│   ├── CPUUrecv:                         Operation: 0     Gas: 0
│   ├── CPULor:                           Operation: 0     Gas: 0
│   ├── CPULand:                          Operation: 0     Gas: 0
│   ├── CPUEql:                           Operation: 0     Gas: 0
│   ├── CPUNeq:                           Operation: 0     Gas: 0
│   ├── CPULss:                           Operation: 0     Gas: 0
│   ├── CPULeq:                           Operation: 0     Gas: 0
│   ├── CPUGtr:                           Operation: 0     Gas: 0
│   ├── CPUGeq:                           Operation: 0     Gas: 0
│   ├── CPUAdd:                           Operation: 0     Gas: 0
│   ├── CPUSub:                           Operation: 0     Gas: 0
│   ├── CPUBor:                           Operation: 0     Gas: 0
│   ├── CPUXor:                           Operation: 0     Gas: 0
│   ├── CPUMul:                           Operation: 0     Gas: 0
│   ├── CPUQuo:                           Operation: 0     Gas: 0
│   ├── CPURem:                           Operation: 0     Gas: 0
│   ├── CPUShl:                           Operation: 0     Gas: 0
│   ├── CPUShr:                           Operation: 0     Gas: 0
│   ├── CPUBand:                          Operation: 0     Gas: 0
│   ├── CPUBandn:                         Operation: 0     Gas: 0
│   ├── CPUEval:                          Operation: 5     Gas: 145
│   ├── CPUBinary1:                       Operation: 0     Gas: 0
│   ├── CPUIndex1:                        Operation: 0     Gas: 0
│   ├── CPUIndex2:                        Operation: 0     Gas: 0
│   ├── CPUSelector:                      Operation: 1     Gas: 32
│   ├── CPUSlice:                         Operation: 0     Gas: 0
│   ├── CPUStar:                          Operation: 0     Gas: 0
│   ├── CPURef:                           Operation: 0     Gas: 0
│   ├── CPUTypeAssert1:                   Operation: 0     Gas: 0
│   ├── CPUTypeAssert2:                   Operation: 0     Gas: 0
│   ├── CPUStaticTypeOf:                  Operation: 0     Gas: 0
│   ├── CPUCompositeLit:                  Operation: 0     Gas: 0
│   ├── CPUArrayLit:                      Operation: 0     Gas: 0
│   ├── CPUSliceLit:                      Operation: 0     Gas: 0
│   ├── CPUSliceLit2:                     Operation: 0     Gas: 0
│   ├── CPUMapLit:                        Operation: 0     Gas: 0
│   ├── CPUStructLit:                     Operation: 0     Gas: 0
│   ├── CPUFuncLit:                       Operation: 0     Gas: 0
│   ├── CPUConvert:                       Operation: 0     Gas: 0
│   ├── CPUFieldType:                     Operation: 0     Gas: 0
│   ├── CPUArrayType:                     Operation: 0     Gas: 0
│   ├── CPUSliceType:                     Operation: 0     Gas: 0
│   ├── CPUPointerType:                   Operation: 0     Gas: 0
│   ├── CPUInterfaceType:                 Operation: 0     Gas: 0
│   ├── CPUChanType:                      Operation: 0     Gas: 0
│   ├── CPUFuncType:                      Operation: 0     Gas: 0
│   ├── CPUMapType:                       Operation: 0     Gas: 0
│   ├── CPUStructType:                    Operation: 0     Gas: 0
│   ├── CPUAssign:                        Operation: 0     Gas: 0
│   ├── CPUAddAssign:                     Operation: 0     Gas: 0
│   ├── CPUSubAssign:                     Operation: 0     Gas: 0
│   ├── CPUMulAssign:                     Operation: 0     Gas: 0
│   ├── CPUQuoAssign:                     Operation: 0     Gas: 0
│   ├── CPURemAssign:                     Operation: 0     Gas: 0
│   ├── CPUBandAssign:                    Operation: 0     Gas: 0
│   ├── CPUBandnAssign:                   Operation: 0     Gas: 0
│   ├── CPUBorAssign:                     Operation: 0     Gas: 0
│   ├── CPUXorAssign:                     Operation: 0     Gas: 0
│   ├── CPUShlAssign:                     Operation: 0     Gas: 0
│   ├── CPUShrAssign:                     Operation: 0     Gas: 0
│   ├── CPUDefine:                        Operation: 0     Gas: 0
│   ├── CPUInc:                           Operation: 1     Gas: 76
│   ├── CPUDec:                           Operation: 0     Gas: 0
│   ├── CPUValueDecl:                     Operation: 0     Gas: 0
│   ├── CPUTypeDecl:                      Operation: 0     Gas: 0
│   ├── CPUSticky:                        Operation: 0     Gas: 0
│   ├── CPUBody:                          Operation: 2     Gas: 86
│   ├── CPUForLoop:                       Operation: 0     Gas: 0
│   ├── CPURangeIter:                     Operation: 0     Gas: 0
│   ├── CPURangeIterString:               Operation: 0     Gas: 0
│   ├── CPURangeIterMap:                  Operation: 0     Gas: 0
│   ├── CPURangeIterArrayPtr:             Operation: 0     Gas: 0
│   └── CPUReturnCallDefers:              Operation: 0     Gas: 0
├── KVStore:                              Operation: 8     Gas: 88384
│   ├── KVStoreGetObjectPerByte:          Operation: 6     Gas: 45344
│   ├── KVStoreSetObjectPerByte:          Operation: 1     Gas: 3216
│   ├── KVStoreGetTypePerByte:            Operation: 0     Gas: 0
│   ├── KVStoreSetTypePerByte:            Operation: 0     Gas: 0
│   ├── KVStoreGetPackageRealmPerByte:    Operation: 1     Gas: 39824
│   ├── KVStoreSetPackageRealmPerByte:    Operation: 0     Gas: 0
│   ├── KVStoreAddMemPackagePerByte:      Operation: 0     Gas: 0
│   ├── KVStoreGetMemPackagePerByte:      Operation: 0     Gas: 0
│   └── KVStoreDeleteObject:              Operation: 0     Gas: 0
├── Memory:                               Operation: 5     Gas: 1192
│   ├── MemoryAllocPerByte:               Operation: 5     Gas: 1192
│   └── MemoryGarbageCollect:             Operation: 0     Gas: 0
├── Native:                               Operation: 0     Gas: 0
│   ├── NativePrintFlat:                  Operation: 0     Gas: 0
│   └── NativePrintPerByte:               Operation: 0     Gas: 0
├── Parsing:                              Operation: 14    Gas: 23
│   ├── ParsingToken:                     Operation: 8     Gas: 8
│   └── ParsingNesting:                   Operation: 6     Gas: 15
├── Store:                                Operation: 30    Gas: 40700
│   ├── StoreReadFlat:                    Operation: 10    Gas: 10000
│   ├── StoreReadPerByte:                 Operation: 10    Gas: 3450
│   ├── StoreWriteFlat:                   Operation: 5     Gas: 10000
│   ├── StoreWritePerByte:                Operation: 5     Gas: 17250
│   ├── StoreHas:                         Operation: 0     Gas: 0
│   ├── StoreDelete:                      Operation: 0     Gas: 0
│   ├── StoreIterNextFlat:                Operation: 0     Gas: 0
│   └── StoreValuePerByte:                Operation: 0     Gas: 0
├── Testing:                              Operation: 0     Gas: 0
│   └── Testing:                          Operation: 0     Gas: 0
└── Transaction:                          Operation: 2     Gas: 3440
    ├── TransactionPerByte:               Operation: 1     Gas: 2440
    ├── TransactionSigVerifyEd25519:      Operation: 0     Gas: 0
    └── TxansactionSigVerifySecp256k1:    Operation: 1     Gas: 1000

HEIGHT:     32
EVENTS:     []
INFO:
TX HASH:    RQ9UomnOrU9bZQGiD/nmakDyJfd0hR8UllAcwFd7hcc=

Questions

  • Is it okay to have added float support to the overflow package? Or should we have created a new package for that? (Since float operations don't really overflow in the same way)
  • Should we remove the CPU Cycles metric in favor of a more relevant one or more detailed logging?
  • The only place where gas costs were not a constant hardcoded somewhere in the codebase is the auth package in tm2/pkg/sdk/auth/params.go. Except for the default values, these gas costs were set by the genesis file.
    Does it make sense to set some costs through the genesis while others are defined in a config struct somewhere in the codebase? Should we make all costs configurable via the genesis file, or remove all cost parameters from the genesis (as I did in this PR)?

@aeddi aeddi self-assigned this Dec 9, 2025
@github-actions github-actions bot added 📦 🤖 gnovm Issues or PRs gnovm related 📦 🌐 tendermint v2 Issues or PRs tm2 related 📦 ⛰️ gno.land Issues or PRs gno.land package related 🐹 golang Pull requests that update Go code 🛠️ gnodev labels Dec 9, 2025
@Gno2D2
Copy link
Collaborator

Gno2D2 commented Dec 9, 2025

🛠 PR Checks Summary

All Automated Checks passed. ✅

Manual Checks (for Reviewers):
  • IGNORE the bot requirements for this PR (force green CI check)
Read More

🤖 This bot helps streamline PR reviews by verifying automated checks and providing guidance for contributors and reviewers.

✅ Automated Checks (for Contributors):

🟢 Maintainers must be able to edit this pull request (more info)

☑️ Contributor Actions:
  1. Fix any issues flagged by automated checks.
  2. Follow the Contributor Checklist to ensure your PR is ready for review.
    • Add new tests, or document why they are unnecessary.
    • Provide clear examples/screenshots, if necessary.
    • Update documentation, if required.
    • Ensure no breaking changes, or include BREAKING CHANGE notes.
    • Link related issues/PRs, where applicable.
☑️ Reviewer Actions:
  1. Complete manual checks for the PR, including the guidelines and additional checks if applicable.
📚 Resources:
Debug
Automated Checks
Maintainers must be able to edit this pull request (more info)

If

🟢 Condition met
└── 🟢 And
    ├── 🟢 The base branch matches this pattern: ^master$
    └── 🟢 The pull request was created from a fork (head branch repo: aeddi/gno)

Then

🟢 Requirement satisfied
└── 🟢 Maintainer can modify this pull request

Manual Checks
**IGNORE** the bot requirements for this PR (force green CI check)

If

🟢 Condition met
└── 🟢 On every pull request

Can be checked by

  • Any user with comment edit permission

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐳 devops 🛠️ gnodev 🐹 golang Pull requests that update Go code 📦 🌐 tendermint v2 Issues or PRs tm2 related 📦 ⛰️ gno.land Issues or PRs gno.land package related 📦 🤖 gnovm Issues or PRs gnovm related

Projects

Status: No status
Status: In Review

Development

Successfully merging this pull request may close these issues.

2 participants