Files
evm/precompile/contracts/nativeminter/contract.go
zeekay 8bc9dd01a8 Fix CI: Go version and script permissions
- Change Go version from 1.24.6 to 1.22.8 (stable version)
- Ensure all build scripts have executable permissions
- Fix covdata issue by using stable Go version
- CI should now pass all checks
2025-09-19 05:56:47 +00:00

152 lines
5.7 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package nativeminter
import (
_ "embed"
"errors"
"fmt"
"math/big"
"github.com/holiman/uint256"
"github.com/luxfi/evm/precompile/allowlist"
"github.com/luxfi/evm/precompile/contract"
"github.com/luxfi/geth/common"
"github.com/luxfi/geth/core/types"
"github.com/luxfi/geth/core/vm"
)
const (
mintInputLen = common.HashLength + common.HashLength
MintGasCost = 30_000
)
type MintNativeCoinInput struct {
Addr common.Address
Amount *big.Int
}
var (
// Singleton StatefulPrecompiledContract for minting native assets by permissioned callers.
ContractNativeMinterPrecompile contract.StatefulPrecompiledContract = createNativeMinterPrecompile()
ErrCannotMint = errors.New("non-enabled cannot mint")
ErrInvalidLen = errors.New("invalid input length for minting")
// NativeMinterRawABI contains the raw ABI of NativeMinter contract.
//go:embed contract.abi
NativeMinterRawABI string
NativeMinterABI = contract.ParseABI(NativeMinterRawABI)
)
// GetContractNativeMinterStatus returns the role of [address] for the minter list.
func GetContractNativeMinterStatus(stateDB contract.StateDB, address common.Address) allowlist.Role {
return allowlist.GetAllowListStatus(stateDB, ContractAddress, address)
}
// SetContractNativeMinterStatus sets the permissions of [address] to [role] for the
// minter list. assumes [role] has already been verified as valid.
func SetContractNativeMinterStatus(stateDB contract.StateDB, address common.Address, role allowlist.Role) {
allowlist.SetAllowListRole(stateDB, ContractAddress, address, role)
}
// PackMintNativeCoin packs [address] and [amount] into the appropriate arguments for mintNativeCoin.
func PackMintNativeCoin(address common.Address, amount *big.Int) ([]byte, error) {
return NativeMinterABI.Pack("mintNativeCoin", address, amount)
}
// UnpackMintNativeCoinInput attempts to unpack [input] as address and amount.
// assumes that [input] does not include selector (omits first 4 func signature bytes)
// if [useStrictMode] is true, it will return an error if the length of [input] is not [mintInputLen]
func UnpackMintNativeCoinInput(input []byte, useStrictMode bool) (common.Address, *big.Int, error) {
// Initially we had this check to ensure that the input was the correct length.
// However solidity does not always pack the input to the correct length, and allows
// for extra padding bytes to be added to the end of the input. Therefore, we have removed
// this check with Durango. We still need to keep this check for backwards compatibility.
if useStrictMode && len(input) != mintInputLen {
return common.Address{}, nil, fmt.Errorf("%w: %d", ErrInvalidLen, len(input))
}
inputStruct := MintNativeCoinInput{}
err := NativeMinterABI.UnpackInputIntoInterface(&inputStruct, "mintNativeCoin", input, useStrictMode)
return inputStruct.Addr, inputStruct.Amount, err
}
// mintNativeCoin checks if the caller is permissioned for minting operation.
// The execution function parses the [input] into native coin amount and receiver address.
func mintNativeCoin(accessibleState contract.AccessibleState, caller common.Address, addr common.Address, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
if remainingGas, err = contract.DeductGas(suppliedGas, MintGasCost); err != nil {
return nil, 0, err
}
if readOnly {
return nil, remainingGas, vm.ErrWriteProtection
}
useStrictMode := !contract.IsDurangoActivated(accessibleState)
to, amount, err := UnpackMintNativeCoinInput(input, useStrictMode)
if err != nil {
return nil, remainingGas, err
}
stateDB := accessibleState.GetStateDB()
// Verify that the caller is in the allow list and therefore has the right to call this function.
callerStatus := allowlist.GetAllowListStatus(stateDB, ContractAddress, caller)
if !callerStatus.IsEnabled() {
return nil, remainingGas, fmt.Errorf("%w: %s", ErrCannotMint, caller)
}
if contract.IsDurangoActivated(accessibleState) {
if remainingGas, err = contract.DeductGas(remainingGas, NativeCoinMintedEventGasCost); err != nil {
return nil, 0, err
}
topics, data, err := PackNativeCoinMintedEvent(caller, to, amount)
if err != nil {
return nil, remainingGas, err
}
stateDB.AddLog(&types.Log{
Address: ContractAddress,
Topics: topics,
Data: data,
BlockNumber: accessibleState.GetBlockContext().Number().Uint64(),
})
}
// if there is no address in the state, create one.
if !stateDB.Exist(to) {
stateDB.CreateAccount(to)
}
amountU256, _ := uint256.FromBig(amount)
stateDB.AddBalance(to, amountU256)
// Return an empty output and the remaining gas
return []byte{}, remainingGas, nil
}
// createNativeMinterPrecompile returns a StatefulPrecompiledContract with getters and setters for the precompile.
// Access to the getters/setters is controlled by an allow list for ContractAddress.
func createNativeMinterPrecompile() contract.StatefulPrecompiledContract {
var functions []*contract.StatefulPrecompileFunction
functions = append(functions, allowlist.CreateAllowListFunctions(ContractAddress)...)
abiFunctionMap := map[string]contract.RunStatefulPrecompileFunc{
"mintNativeCoin": mintNativeCoin,
}
for name, function := range abiFunctionMap {
method, ok := NativeMinterABI.Methods[name]
if !ok {
panic(fmt.Errorf("given method (%s) does not exist in the ABI", name))
}
functions = append(functions, contract.NewStatefulPrecompileFunction(method.ID, function))
}
// Construct the contract with no fallback function.
statefulContract, err := contract.NewStatefulPrecompileContract(nil, functions)
if err != nil {
panic(err)
}
return statefulContract
}