#35511 RFC: consensus: Make `CAmount` a class

ReviewabilityReviewsAgreementSize
#35511 hodlinator RFC: consensus: Make `CAmount` a classReady0Mild589 + 927 tests
  • Prevent silent bugs from accidental conversions, uninitialized amounts, and invalid monetary math
  • Catch invalid operations and mismatched comparisons on monetary values at compile time
  • Ready for review
  • Concept approval because the design is clean and improves safety (ryanofsky)
  • Concept approval without stated reasons (sedited)
  • Objection that converting the entire codebase at once is too risky to review (l0rinc)
  • 589 lines added or modified outside tests
  • 927 lines added or modified in tests
  • 1,282 lines removed in total
  • 194 files, 12 commits
Validation#47 of 53
P3 · cleanup
  • P3 because strong monetary typing prevents hazards like uninitialized amounts and invalid arithmetic
  • It catches historical bugs like boolean assignments and mismatched comparisons at compile time
  • It lays groundwork for enforcing valid monetary bounds across consensus code
Reviewability: ReadyStalePaused
Agreement: StrongPositivePositive w/ caveatsNeutralMildDisputedBlockedCrickets
Reviews: current code-review ACKs, then (+stale ACKs) and -NACKs; greener = more ACKs.
Size: lines added or modified outside tests, then in tests; greener = smaller.

Goal

Replaces the CAmount type alias for int64_t with a dedicated class type requiring explicit initialization and construction. Arithmetic operations on amounts are restricted to domain-appropriate operations, implicit conversions from booleans or floats are banned, and a sats unit operator is introduced for literals. Call sites and tests across consensus, mempool, wallet, GUI, and IPC are updated to conform to the new type.

Problem: Because CAmount is a raw integer typedef, the compiler permits uninitialized amount variables, implicit conversions from boolean and floating-point types, comparisons across mismatched integer widths, and nonsensical operations such as multiplying two monetary values together.

Evidence of importance:

  • The PR author notes this prevents bugs previously fixed in commit 0026b330c4abbbbdb96e4f0c4d380d70d8e592ab (assigning from bool) and commit 890a09b1e49925315a5636f29cc5af36928fe092 (mismatched integral comparisons, which fail to compile if reverted on top of this PR).
  • References prior attempts in #4067 and #5430.

Scope: Very large refactor across 194 files; several commits are mechanical scripted diffs converting uninitialized declarations, explicit constructors, and literals with the `sats` operator.

Risk of a mistake: high (consensus, validation, mempool_policy, wallet). A flaw in custom arithmetic operators, type conversions, or serializations for monetary amounts could alter consensus validation, cause accounting errors, or fork the network.

Changed since the previous assessment: Rebased with minor diff updates across test and utility files while maintaining the 12-commit structure.

Reviewability

Ready

Nothing known that would invalidate a review now.

Agreement

Mild

objection that needs a change was answered, no reply from the objector (l0rinc: Transitioning the entire codebase to the new CAmount class in a single monolithic PR is too risky and onerous to review )

Thread notes: Initial record for the PR. l0rinc filed a Changes Requested review objecting to replacing CAmount everywhere in one massive PR rather than introducing the class incrementally; this remains open. Objections regarding IPC serialization, type encapsulation in serialize.h, and the proposed _sats literal were resolved.

Objections, suggestions, and questions:

ReviewerTypeHarm, request, or questionStatusMust beAuthor repliedQuote
janb84 (member)objection, correctnessIPC Cap'n Proto serialization only overwrote a local copy, failing to write CAmount values into the message and resulting in zeroed output.fixed · fix pushed · v1, not yet re-checkedansweredyes2026-06-11: 'Current code gets a local copy, overrides the local copy and local copy gets destroyed at the end of the function. Nothing is written back to the Cap'n Proto message.'
Status from: 2026-06-12: janb84: 'yeah test was for proving the bug not the intention to add it so i'm glad you altered it somewhat !'
optout21 (contributor)objection, approachRestricting construction to literals and forbidding direct integer comparison causes friction and hinders migration across the codebase.fixed · fix pushed · v1, not yet re-checkedansweredyes2026-06-18: 'However, to be constructible only from `_sats` literals, and to be comparable only against other `CAmount` seems a bit of an overkill to me, and it hinders introduction (large change).'
Status from: 2026-06-18: hodlinator: 'I briefly experimented with having an operator int64_t() method and allowing comparisons against integers. What I ran into a lot was that the compiler couldn't resolve the resulting ambiguities.'
purpleKarrot (member)objection, interfaceUser-defined literals for amount units have known pitfalls and deviate from emerging C++ quantity standards that prefer unit multiplication syntax.fixed · fix pushed · v1, not yet re-checkedansweredyes2026-06-22: 'Which is absolutely true and convinces me to vote against a `_sat` UDL.'
Status from: 2026-08-25: hodlinator: 'Latest push removes the `_sats` UDL. Instead the commits get by on `CAmount{<literal>}` until the end, when a `sats` unit is defined and applied as `<literal>*sats`.'
l0rinc (member)objection, scopeTransitioning the entire codebase to the new CAmount class in a single monolithic PR is too risky and onerous to review compared to an incremental rollout.answered · reply on record; standing not assessed yet · v1, not yet re-checkedchangedyes2026-06-29: 'As mentioned by others, I am also not comfortable with taking such a large change in one step. I would prefer introducing a new amount type first, with a few focused examples, and then converting call sites gradually.'
l0rinc (member)objection, interfacePlacing CAmount serialization outside the class in serialize.h breaks type encapsulation.fixed · fix pushed · v1, not yet re-checkedansweredyes2026-06-29: 'Could we rather encapsulate the serialization inside the new type?'
Status from: 2026-07-01: hodlinator: 'Agreed, thanks for pushing back on this. (Edit: Fixed in later push).'

Support:

  • sedited (member) (Concept ACK, 2026-06-11): (no reason given) [not substantive]
  • ryanofsky (member) (Concept ACK, 2026-06-15): New CAmount implementation seems simple and clean and adds safety benefits.

Participants:

  • janb84 (member, objection, 3 statements 2026-06-11 to 2026-06-12): found and provided a regression test for an IPC Cap'n Proto serialization bug where CAmount fields were lost
  • l0rinc (member, objection, 11 statements 2026-06-11 to 2026-06-29): requested changes over the monolithic PR size and raised concerns on serialization encapsulation and value semantics
  • sedited (member, support, 1 statements 2026-06-11 to 2026-06-11): Concept ACK
  • ryanofsky (member, support, 4 statements 2026-06-15 to 2026-06-29): Concept ACK; suggested using std::quantity multiplication syntax and exploring splitting up the commits
  • maflcko (member, neutral, 4 statements 2026-06-16 to 2026-06-17): asked about MAX_MONEY bounds and suggested how commits could be split or formatted as scripted diffs
  • Sjors (member, neutral, 1 statements 2026-06-17 to 2026-06-17): suggested introducing the literal via a scripted diff or standalone PR
  • optout21 (contributor, objection, 1 statements 2026-06-18 to 2026-06-18): supported the class concept but objected to literal-only construction and disallowing integer comparisons
  • purpleKarrot (member, objection, 2 statements 2026-06-22 to 2026-06-23): opposed user-defined literals in favor of multiplication syntax and outlined consensus invariant restrictions

Dependencies

Enables:

  • A future constrained amount type restricted to the valid range [0, MAX_MONEY]

Files

1800 lines under test/bench/ci.

  • src/wallet/test/coinselector_tests.cpp +134/-109
  • src/test/feefrac_tests.cpp +79/-78
  • src/consensus/amount.h +128/-2
  • src/test/transaction_tests.cpp +53/-53
  • src/test/rpc_tests.cpp +51/-51
  • src/test/script_tests.cpp +50/-50
  • src/test/amount_tests.cpp +46/-46
  • src/test/miner_tests.cpp +39/-39
  • src/test/rbf_tests.cpp +34/-34
  • src/test/mempool_tests.cpp +33/-33
  • src/wallet/coinselection.cpp +25/-25
  • src/wallet/test/fuzz/coinselection.cpp +24/-24
  • src/rpc/blockchain.cpp +22/-22
  • src/wallet/spend.cpp +22/-22
  • src/wallet/test/coinselection_tests.cpp +22/-21
  • src/qt/coincontroldialog.cpp +20/-20
  • src/policy/fees/block_policy_estimator.cpp +19/-20
  • src/test/miniminer_tests.cpp +19/-19
  • src/txmempool.cpp +22/-16
  • src/test/script_p2sh_tests.cpp +18/-18
  • src/bench/coin_selection.cpp +18/-17
  • src/test/txpackage_tests.cpp +17/-17
  • src/test/fuzz/package_eval.cpp +15/-15
  • src/test/util_tests.cpp +15/-15
  • src/util/feefrac.h +16/-14
  • src/wallet/receive.cpp +16/-12
  • src/test/txgraph_tests.cpp +13/-13
  • src/test/coins_tests.cpp +11/-11
  • src/test/fuzz/feefrac.cpp +11/-11
  • src/test/blockfilter_tests.cpp +10/-10
  • src/node/mempool_persist.cpp +8/-11
  • src/test/compress_tests.cpp +11/-8
  • src/qt/bitcoinamountfield.cpp +9/-9
  • src/validation.cpp +9/-9
  • src/interfaces/wallet.h +8/-8
  • src/ipc/capnp/common-types.h +16/-0
  • src/qt/test/uritests.cpp +8/-8
  • src/test/fuzz/util.cpp +13/-3
  • src/wallet/test/group_outputs_tests.cpp +8/-8
  • src/bench/mempool_eviction.cpp +7/-7
  • src/test/blockpolicyestimator_tests.cpp +7/-7
  • src/test/cluster_linearize_tests.cpp +7/-7
  • src/test/feerounder_tests.cpp +7/-7
  • src/test/fuzz/integer.cpp +7/-7
  • src/test/fuzz/txorphan.cpp +7/-7
  • src/txgraph.cpp +7/-7
  • src/rpc/mempool.cpp +7/-6
  • src/test/bip352_tests.cpp +7/-6
  • src/util/moneystr.cpp +7/-6
  • src/test/mempool_fee_estimator_tests.cpp +6/-6
  • src/test/psbt_tests.cpp +6/-6
  • src/txmempool.h +6/-6
  • src/wallet/rpc/spend.cpp +6/-6
  • src/compressor.h +7/-4
  • src/net_processing.cpp +6/-5
  • src/primitives/transaction.h +4/-7
  • src/qt/bitcoinamountfield.h +10/-1
  • src/core_io.cpp +5/-5
  • src/index/coinstatsindex.cpp +5/-5
  • src/node/txorphanage.cpp +5/-5
  • src/qt/transactiondesc.cpp +5/-5
  • src/rpc/mining.cpp +5/-5
  • src/wallet/coinselection.h +5/-5
  • src/wallet/rpc/coins.cpp +5/-5
  • src/wallet/test/fuzz/fees.cpp +5/-5
  • src/wallet/test/spend_tests.cpp +5/-5
  • src/wallet/wallet.h +5/-5
  • src/rpc/rawtransaction.cpp +5/-4
  • src/node/block_template_manager.cpp +4/-4
  • src/policy/feerate.cpp +4/-4
  • src/policy/policy.h +4/-4
  • src/qt/overviewpage.cpp +4/-4
  • src/qt/walletmodel.cpp +4/-4
  • src/test/fuzz/txgraph.cpp +4/-4
  • src/test/multisig_tests.cpp +4/-4
  • src/test/txdownload_tests.cpp +4/-4
  • src/test/txvalidation_tests.cpp +4/-4
  • src/test/txvalidationcache_tests.cpp +4/-4
  • src/test/validation_tests.cpp +4/-4
  • src/wallet/feebumper.cpp +4/-4
  • src/wallet/fees.cpp +4/-4
  • src/wallet/wallet.cpp +4/-4
  • src/bitcoin-tx.cpp +4/-3
  • src/kernel/bitcoinkernel.cpp +4/-3
  • src/coins.cpp +3/-3
  • src/ipc/test/ipc_tests.cpp +5/-1
  • src/node/mini_miner.cpp +3/-3
  • src/qt/bitcoinunits.cpp +3/-3
  • src/qt/sendcoinsdialog.cpp +3/-3
  • src/qt/test/wallettests.cpp +3/-3
  • src/qt/transactionrecord.cpp +3/-3
  • src/qt/transactiontablemodel.cpp +3/-3
  • src/rpc/util.cpp +4/-2
  • src/test/fuzz/cluster_linearize.cpp +4/-2
  • src/test/fuzz/feeratediagram.cpp +3/-3
  • src/test/fuzz/mini_miner.cpp +3/-3
  • src/test/fuzz/tx_pool.cpp +3/-3
  • src/test/sighash_tests.cpp +3/-3
  • src/test/util/setup_common.cpp +3/-3
  • src/wallet/rpc/addresses.cpp +4/-2
  • src/wallet/rpc/transactions.cpp +3/-3
  • src/bench/txgraph.cpp +3/-2
  • src/consensus/tx_check.cpp +3/-2
  • src/primitives/transaction.cpp +2/-3
  • src/signet.cpp +3/-2
  • src/bench/mempool_ephemeral_spends.cpp +2/-2
  • src/bench/rpc_mempool.cpp +2/-2
  • src/bench/wallet_balance.cpp +3/-1
  • src/node/transaction.cpp +2/-2
  • src/qt/guiutil.cpp +2/-2
  • src/qt/psbtoperationsdialog.cpp +2/-2
  • src/qt/transactionrecord.h +2/-2
  • src/qt/transactionview.cpp +2/-2
  • src/rpc/fees.cpp +2/-2
  • src/test/coinsviewoverlay_tests.cpp +2/-2
  • src/test/fuzz/fee_rate.cpp +2/-2
  • src/test/fuzz/rbf.cpp +2/-2
  • src/test/fuzz/util.h +3/-1
  • src/test/sigopcount_tests.cpp +2/-2
  • src/test/util/cluster_linearize.h +2/-2
  • src/test/validation_block_tests.cpp +2/-2
  • src/wallet/test/rescan_tests.cpp +2/-2
  • src/wallet/transaction.h +2/-2
  • src/bench/block_assemble.cpp +2/-1
  • src/bench/cluster_linearize.cpp +2/-1
  • src/bench/duplicate_inputs.cpp +2/-1
  • src/bench/sign_transaction.cpp +2/-1
  • src/bench/verify_script.cpp +2/-1
  • src/consensus/tx_verify.cpp +2/-1
  • src/kernel/coinstats.cpp +2/-1
  • src/policy/feerate.h +1/-2
  • src/qt/bitcoin.cpp +1/-2
  • src/util/fees.h +2/-1
  • src/bench/mempool_stress.cpp +1/-1
  • src/bench/wallet_create_tx.cpp +1/-1
  • src/consensus/tx_verify.h +1/-1
  • src/core_io.h +1/-1
  • src/interfaces/chain.h +1/-1
  • src/interfaces/node.h +1/-1
  • src/kernel/mempool_entry.h +1/-1
  • src/net_processing.h +1/-1
  • src/node/interfaces.cpp +1/-1
  • src/node/miner.cpp +1/-1
  • src/node/miner.h +1/-1
  • src/node/mini_miner.h +1/-1
  • src/node/mining_types.h +1/-1
  • src/node/psbt.cpp +1/-1
  • src/node/transaction.h +1/-1
  • src/policy/ephemeral_policy.cpp +1/-1
  • src/policy/policy.cpp +1/-1
  • src/psbt.h +1/-1
  • src/qt/bitcoingui.cpp +1/-1
  • src/qt/receivecoinsdialog.cpp +1/-1
  • src/qt/receiverequestdialog.cpp +1/-1
  • src/qt/recentrequeststablemodel.cpp +1/-1
  • src/qt/sendcoinsentry.cpp +1/-1
  • src/qt/sendcoinsrecipient.h +1/-1
  • src/qt/transactionfilterproxy.cpp +1/-1
  • src/qt/walletmodeltransaction.cpp +1/-1
  • src/qt/walletview.cpp +1/-1
  • src/rpc/blockchain.h +1/-1
  • src/script/interpreter.cpp +1/-1
  • src/script/sigcache.h +1/-1
  • src/script/sign.h +1/-1
  • src/test/blockencodings_tests.cpp +1/-1
  • src/test/descriptor_tests.cpp +1/-1
  • src/test/fuzz/cmpctblock.cpp +1/-1
  • src/test/fuzz/coins_view.cpp +1/-1
  • src/test/fuzz/coinscache_sim.cpp +1/-1
  • src/test/fuzz/connect_block.cpp +1/-1
  • src/test/fuzz/deserialize.cpp +1/-1
  • src/test/fuzz/p2p_headers_presync.cpp +1/-1
  • src/test/fuzz/script_flags.cpp +1/-1
  • src/test/fuzz/util/mempool.cpp +1/-1
  • src/test/testnet4_miner_tests.cpp +1/-1
  • src/test/txospenderindex_tests.cpp +1/-1
  • src/test/util/random.h +1/-1
  • src/test/util/transaction_utils.cpp +1/-1
  • src/test/util/transaction_utils.h +1/-1
  • src/test/util/txmempool.cpp +1/-1
  • src/test/validation_chainstatemanager_tests.cpp +1/-1
  • src/txgraph.h +1/-1
  • src/util/feefrac.cpp +1/-1
  • src/wallet/interfaces.cpp +1/-1
  • src/wallet/receive.h +1/-1
  • src/wallet/rpc/wallet.cpp +1/-1
  • src/wallet/test/fuzz/spend.cpp +1/-1
  • src/wallet/test/wallet_tests.cpp +1/-1
  • src/wallet/types.h +1/-1
  • src/init.cpp +1/-0
  • src/ipc/test/ipc_test.capnp +1/-0
  • src/ipc/test/ipc_test.h +1/-0
  • src/rest.cpp +1/-0
  • src/rpc/net.cpp +1/-0

Card

The short text the ranking pass compares across PRs.

This draft pull request turns CAmount from a plain int64_t typedef into a class type requiring explicit construction and initialization. The change enforces compile-time type safety against uninitialized monetary amounts, implicit conversions from booleans or floats, and invalid operations like multiplying two amounts together. As evidence, the author points to historical bugs (such as 0026b330c4 and 890a09b1e4) that now produce compile errors instead of runtime errors. The refactor touches 194 files across consensus, mempool, and wallet components, largely via scripted diffs.

Data

dossier JSON · extract JSON · model ledger, generated 2026-11-02T01:10, confidence high, input hash c890c31ceeeedf70
thread read through 2026-08-25 (43 statements), head ec4ad1fbe2
Discussion read under schema 1 rules: suggestions and questions may be missing, and an objection may combine several harms (schema history).
labels: none · draft

Categories

Validation

P3 · cleanup

Strong typing for CAmount removes class-level type hazards such as uninitialized values and illegal arithmetic across consensus primitives. The PR replaces the int64_t typedef with a class requiring explicit initialization, banning implicit conversions from booleans or floats, and restricting domain arithmetic, referencing historical consensus bugs like assigning from bool (0026b330c4a) and mismatched integral comparisons (890a09b1e49).

Membership: Modifies src/consensus/amount.h, transforming the core consensus monetary type CAmount from a raw typedef into a strong class type.

Factors: security/stability 1, bug 0, performance 0, user value 0, leverage 1