aboutsummaryrefslogblamecommitdiff
path: root/core/vm/contracts_stateful.go
blob: a7caf14b160c8614d7e819219806e8839f49c1bc (plain) (tree)




























                                                                                                                                                      

                                                                                                                                    























                                                                                                                                                                                                           


                                                       













































































































                                                                                                                                                                                                   
package vm

import (
	"errors"
	"fmt"
	"math/big"

	"github.com/ava-labs/coreth/params"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/log"
	"github.com/holiman/uint256"
)

// PrecompiledContractsApricot contains the default set of pre-compiled Ethereum
// contracts used in the Istanbul release and the stateful precompiled contracts
// added for the Avalanche Apricot release.
// Apricot is incompatible with the YoloV1 Release since it does not include the
// BLS12-381 Curve Operations added to the set of precompiled contracts
var PrecompiledContractsApricot = map[common.Address]StatefulPrecompiledContract{
	common.BytesToAddress([]byte{1}):                                  newWrappedPrecompiledContract(&ecrecover{}),
	common.BytesToAddress([]byte{2}):                                  newWrappedPrecompiledContract(&sha256hash{}),
	common.BytesToAddress([]byte{3}):                                  newWrappedPrecompiledContract(&ripemd160hash{}),
	common.BytesToAddress([]byte{4}):                                  newWrappedPrecompiledContract(&dataCopy{}),
	common.BytesToAddress([]byte{5}):                                  newWrappedPrecompiledContract(&bigModExp{}),
	common.BytesToAddress([]byte{6}):                                  newWrappedPrecompiledContract(&bn256AddIstanbul{}),
	common.BytesToAddress([]byte{7}):                                  newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}),
	common.BytesToAddress([]byte{8}):                                  newWrappedPrecompiledContract(&bn256PairingIstanbul{}),
	common.BytesToAddress([]byte{9}):                                  newWrappedPrecompiledContract(&blake2F{}),
	common.HexToAddress("0x0100000000000000000000000000000000000000"): &deprecatedContract{msg: "hardcoded genesis contract has been deprecated"},
	common.HexToAddress("0x0100000000000000000000000000000000000001"): &nativeAssetBalance{gasCost: params.AssetBalanceApricot},
	common.HexToAddress("0x0100000000000000000000000000000000000002"): &nativeAssetCall{gasCost: params.AssetCallApricot},
}

// StatefulPrecompiledContract is the interface for executing a precompiled contract
// This wraps the PrecompiledContracts native to Ethereum and allows adding in stateful
// precompiled contracts to support native Avalanche asset transfers.
type StatefulPrecompiledContract interface {
	// Run executes a precompiled contract in the current state
	// assumes that it has already been verified that [caller] can
	// transfer [value].
	Run(evm *EVM, caller ContractRef, addr common.Address, value *big.Int, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error)
}

// wrappedPrecompiledContract implements StatefulPrecompiledContract by wrapping stateless native precompiled contracts
// in Ethereum.
type wrappedPrecompiledContract struct {
	p PrecompiledContract
}

func newWrappedPrecompiledContract(p PrecompiledContract) StatefulPrecompiledContract {
	return &wrappedPrecompiledContract{p: p}
}

// Run ...
func (w *wrappedPrecompiledContract) Run(evm *EVM, caller ContractRef, addr common.Address, value *big.Int, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
	// [caller.Address()] has already been verified
	// as having a sufficient balance before the
	// precompiled contract runs.
	evm.Transfer(evm.StateDB, caller.Address(), addr, value)
	return RunPrecompiledContract(w.p, input, suppliedGas)
}

// nativeAssetBalance is a precompiled contract used to retrieve the native asset balance
type nativeAssetBalance struct {
	gasCost uint64
}

// Run implements StatefulPrecompiledContract
func (b *nativeAssetBalance) Run(evm *EVM, caller ContractRef, addr common.Address, value *big.Int, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
	// input: encodePacked(address 20 bytes, assetID 32 bytes)
	if value.Sign() != 0 {
		return nil, suppliedGas, errors.New("cannot transfer value to native asset balance pre-compiled contract")
	}

	if suppliedGas < b.gasCost {
		return nil, 0, ErrOutOfGas
	}
	remainingGas = suppliedGas - b.gasCost

	if len(input) != 52 {
		return nil, remainingGas, fmt.Errorf("input to native asset balance must be 52 bytes, containing address [20 bytes] and assetID [32 bytes], but found length: %d", len(input))
	}
	address := common.BytesToAddress(input[:20])
	assetID := new(common.Hash)
	assetID.SetBytes(input[20:52])

	res, overflow := uint256.FromBig(evm.StateDB.GetBalanceMultiCoin(address, *assetID))
	log.Info("nativeAssetBalance", "address", address, "assetID", assetID.Hex(), "res", res, "overflow", overflow)
	if overflow {
		return nil, remainingGas, errors.New("balance overflow")
	}
	return common.LeftPadBytes(res.Bytes(), 32), remainingGas, nil
}

// nativeAssetCall atomically transfers a native asset to a recipient address as well as calling that
// address
type nativeAssetCall struct {
	gasCost uint64
}

// Run implements StatefulPrecompiledContract
func (c *nativeAssetCall) Run(evm *EVM, caller ContractRef, addr common.Address, value *big.Int, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
	// input: encodePacked(address 20 bytes, assetID 32 bytes, assetAmount 32 bytes, callData variable length bytes)
	if suppliedGas < c.gasCost {
		return nil, 0, ErrOutOfGas
	}
	remainingGas = suppliedGas - c.gasCost

	if readOnly {
		return nil, remainingGas, errors.New("cannot execute native asset transfer within read only call")
	}

	if len(input) < 84 {
		return nil, remainingGas, fmt.Errorf("missing inputs to native asset transfer, expected input of length at least 84, but found %d", len(input))
	}
	to := common.BytesToAddress(input[:20])
	assetID := new(common.Hash)
	assetID.SetBytes(input[20:52])
	assetAmount := new(big.Int).SetBytes(input[52:84])
	callData := input[84:]
	log.Info("nativeAssetCall", "input", fmt.Sprintf("0x%x", input), "to", to.Hex(), "assetID", assetID.Hex(), "assetAmount", assetAmount, "callData", fmt.Sprintf("0x%x", callData))

	mcerr := evm.Context.CanTransferMC(evm.StateDB, caller.Address(), to, assetID, assetAmount)
	if mcerr == 1 {
		log.Info("Insufficient balance for mc transfer")
		return nil, remainingGas, ErrInsufficientBalance
	} else if mcerr != 0 {
		log.Info("incompatible account for mc transfer")
		return nil, remainingGas, ErrIncompatibleAccount
	}

	snapshot := evm.StateDB.Snapshot()

	if !evm.StateDB.Exist(to) {
		log.Info("Creating account entry", "address", to.Hex())
		remainingGas -= params.CallNewAccountGas
		evm.StateDB.CreateAccount(to)
	}

	// Send [value] to [to] address
	evm.Transfer(evm.StateDB, caller.Address(), to, value)
	evm.TransferMultiCoin(evm.StateDB, caller.Address(), to, assetID, assetAmount)
	ret, remainingGas, err = evm.Call(caller, to, callData, remainingGas, big.NewInt(0))
	log.Info("Finished TransferMultiCoin and call", "return", ret, "remainingGas", remainingGas, "error", err)

	// When an error was returned by the EVM or when setting the creation code
	// above we revert to the snapshot and consume any gas remaining. Additionally
	// when we're in homestead this also counts for code storage gas errors.
	if err != nil {
		evm.StateDB.RevertToSnapshot(snapshot)
		if err != ErrExecutionReverted {
			remainingGas = 0
		}
		// TODO: consider clearing up unused snapshots:
		//} else {
		//	evm.StateDB.DiscardSnapshot(snapshot)
	}
	return ret, remainingGas, err
}

type deprecatedContract struct {
	msg string
}

// Run ...
func (d *deprecatedContract) Run(evm *EVM, caller ContractRef, addr common.Address, value *big.Int, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
	return nil, suppliedGas, errors.New(d.msg)
}