From 368844ad2a28ec07848e3c0169cf2b83b579a2e8 Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Mon, 23 Nov 2020 20:46:35 -0500 Subject: Add native asset precompiled contracts for apricot release --- core/vm/contracts_stateful.go | 165 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 core/vm/contracts_stateful.go (limited to 'core/vm/contracts_stateful.go') diff --git a/core/vm/contracts_stateful.go b/core/vm/contracts_stateful.go new file mode 100644 index 0000000..3ef7a9e --- /dev/null +++ b/core/vm/contracts_stateful.go @@ -0,0 +1,165 @@ +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("0xffffffffffffffffffffffffffffffffffffff"): &nativeAssetBalance{gasCost: params.AssetBalanceApricot}, + common.HexToAddress("0xfffffffffffffffffffffffffffffffffffffe"): &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) { + 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) +} -- cgit v1.2.3