feat(evm): add MNT support to EVM execution layer#29
Conversation
| case rules.IsByzantium: | ||
| return PrecompiledAddressesByzantium | ||
| default: | ||
| return PrecompiledAddressesHomestead |
There was a problem hiding this comment.
MNT precompiles are executable through activePrecompiledContracts, but ActivePrecompiles still returns only the base address slices. StateDB.Prepare warms vm.ActivePrecompiles(rules), so under EIP-2929 the MNT precompiles are charged as cold accounts on first access. Please include the MNT addresses when rules.IsQKCMNT. (but need to check the existing behavior to avoid forking)
| if isPrecompile { | ||
| ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules) | ||
| if mntP, ok := p.(PrecompiledContractWithEVM); ok { | ||
| ret, gas, err = runMNTPrecompiledContract(evm, mntP, addr, input, gas, caller, value) |
There was a problem hiding this comment.
Call dispatches MNT precompiles through RunWithEVM, but CallCode, DelegateCall, and StaticCall still use RunPrecompiledContract, which calls the plain Run method. The MNT precompile Run methods intentionally return errMNTNotDispatchedDirectly, so these opcodes fail for MNT precompiles. This especially breaks read-only precompiles such as balanceMNT/currentMntID, which should be callable via STATICCALL.
| return nil, err | ||
| } | ||
| contract.Gas = leftOver | ||
| return targetAddr.Bytes(), nil |
There was a problem hiding this comment.
deploySystemContract selects a fixed targetAddr, but then calls normal evm.Create, which deploys to CreateAddress(caller, nonce) instead. The precompile returns targetAddr without putting code there, so the system contracts remain undeployed and later calls/mints cannot work.
1f8eff7 to
c4a732c
Compare
Integrate Multi-Native Token (MNT) support into the EVM execution layer, building on the foundational types/state layer from feature/mnt-types-state. **Core execution:** - core/evm.go: Add tokenID params to CanTransfer/Transfer, route to GetMntBalance/SubMntBalance/AddMntBalance for non-QKC tokens - core/state_transition.go: Add Message.GasTokenID/TransferTokenID fields, check MNT balance in buyGas(), debit correct token in state transition **VM layer:** - core/vm/interface.go: Add GetMntBalance/AddMntBalance/SubMntBalance to StateDB interface - core/vm/evm.go: Add TxContext.GasTokenID/TransferTokenID, enforce MNT token acknowledgement check, route through runMNTPrecompiledContract - core/vm/contract.go: Add Contract.TokenIDQueried flag for MNT acknowledgement - core/vm/instructions.go: Add ModifyTokenIDQueried() to propagate flag from CALL/CALLCODE/DELEGATECALL/STATICCALL **Precompiles:** - core/vm/contracts.go: Register QKC precompiles (currentMntID, nativeMntTransfer) - core/vm/contracts_qkc.go: Implement QKC-specific precompiles with EVM context - core/vm/contracts_qkc_test.go: Add MNT transfer and token ID propagation tests **Tests:** - core/eth_transfer_logs_test.go: Update for QKC fork (Transfer() no longer emits EthTransferLog, only SELFDESTRUCT does) - eth/tracers/js/tracer_test.go: Add MNT stubs to dummyStatedb Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Without timestamp gating, replaying blocks before MNT activation would
incorrectly execute the MNT precompile/system-contract addresses and
fork from the canonical chain.
Add ChainConfig.QKCMNTTime *uint64 (json:"qkcMNTTime") following the
same pattern as ShanghaiTime/CancunTime. Add Rules.IsQKCMNT bool and
ChainConfig.IsQKCMNT(time uint64) bool, populated in Rules().
In activePrecompiledContracts (core/vm/contracts.go) gate the MNT
precompile map merge behind rules.IsQKCMNT so that prior to activation
the addresses are ordinary accounts, mirroring goquarkchain's per-
contract enableTime check in core/vm/evm.go run().
Set QKCMNTTime=0 in TestChainConfig and MergedTestChainConfig so
existing MNT precompile tests continue to pass.
Ref: goquarkchain cmd/cluster/config.go SetEnableTime calls
goquarkchain core/vm/evm.go run() enableTime gating
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
TokenIDQueriedflag mechanism, requiring contracts to acknowledge non-default token transfers by calling thecurrentMntIDprecompilecurrentMntID,transferMnt,mintMNT,balanceMNT, anddeploySystemContractBackground
This PR builds on
feature/mnt-state. The EVM layer needs to support:Message.GasTokenIDallows gas to be paid with a specified token (currently only the QKC default token is supported; non-default tokens revert).Message.TransferTokenIDspecifies the token used for the transfer. The EVMTransfer()routes to ETH balance or MNT balance based on the token ID.currentMntIDprecompile during execution, otherwise the EVM rolls back the transfer. This mirrors pyquarkchain'sis_token_id_queriedlogic, preventing MNT-unaware contracts from silently receiving tokens they cannot handle.Key Design Decisions
TokenIDQueried propagation:
DELEGATECALL/CALLCODEpropagateTokenIDQueriedback to the caller viaModifyTokenIDQueried(), ensuring correct semantics under proxy contract patterns:CanTransfer/Transfersignature change: Both functions gain atokenID uint64parameter to route value transfers to the correct token balance. All call sites (including tests) are updated to pass the token ID.QKC Precompile Contracts
0x...514b430001currentMntIDTokenIDQueried=true0x...514b430002transferMnt0x...514b430003deploySystemContract0x...514b430004mintMNT0x...514b430005balanceMNTMNT System Contracts
Two Solidity system contracts are deployed on-chain via the
deploySystemContractprecompile. Their bytecode is ported directly from goquarkchain and embedded incontracts_qkc.go.0x514b430000000000000000000000000000000002NonReservedNativeToken0x514b430000000000000000000000000000000003GeneralNativeTokenChanged Files
Core execution
core/evm.goCanTransfer/Transfertake atokenIDparam and route to MNT balance methodscore/state_transition.goGasTokenID/TransferTokenIDtoMessage; validate non-default gas token inbuyGas()core/vm/interface.goGetMntBalance/AddMntBalance/SubMntBalancetoStateDBinterfacecore/vm/evm.goGasTokenID/TransferTokenIDtoTxContext; addTokenIDQueriedcheck inCall(); addrunMNTPrecompiledContractdispatchcore/vm/contract.goTokenIDQueried boolfield toContractcore/vm/instructions.goCALL/CALLCODE/DELEGATECALL/STATICCALLpropagate the flag viaModifyTokenIDQueried()core/vm/contracts.goPrecompiledContractWithEVMinterfacecore/vm/contracts_qkc.goTests — adapter updates (safe to ignore during review)
These files only update
CanTransfer/Transfercall sites to pass the newtokenIDparameter, or add MNT stubs to satisfy the updatedStateDBinterface. No test logic changed.core/vm/gas_table_test.goCanTransfer/Transferstubs to includetokenIDparamcore/vm/interpreter_test.goTransferstub to includetokenIDparameth/tracers/js/tracer_test.godummyStatedbTests — new and updated logic
core/vm/contracts_qkc_test.gocore/eth_transfer_logs_test.goTransfer()no longer emitsEthTransferLog(only SELFDESTRUCT retains it)Test Plan
go test -run=Mnt ./core/vm— all passgo test ./core/vm— all passgo build ./...— build successful