diff options
Diffstat (limited to 'core/vm')
-rw-r--r-- | core/vm/contracts.go | 86 | ||||
-rw-r--r-- | core/vm/contracts_stateful.go | 165 | ||||
-rw-r--r-- | core/vm/evm.go | 24 | ||||
-rw-r--r-- | core/vm/interpreter.go | 12 | ||||
-rw-r--r-- | core/vm/jump_table.go | 11 | ||||
-rw-r--r-- | core/vm/opcodes.go | 1 |
6 files changed, 246 insertions, 53 deletions
diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 9827bac..6fc6334 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -44,61 +44,61 @@ type PrecompiledContract interface { // PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum // contracts used in the Frontier and Homestead releases. -var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, +var PrecompiledContractsHomestead = 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{}), } // PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum // contracts used in the Byzantium release. -var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{}, - common.BytesToAddress([]byte{6}): &bn256AddByzantium{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{}, - common.BytesToAddress([]byte{8}): &bn256PairingByzantium{}, +var PrecompiledContractsByzantium = 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(&bn256AddByzantium{}), + common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulByzantium{}), + common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingByzantium{}), } // PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum // contracts used in the Istanbul release. -var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, +var PrecompiledContractsIstanbul = 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{}), } // PrecompiledContractsYoloV1 contains the default set of pre-compiled Ethereum // contracts used in the Yolo v1 test release. -var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{ - common.BytesToAddress([]byte{1}): &ecrecover{}, - common.BytesToAddress([]byte{2}): &sha256hash{}, - common.BytesToAddress([]byte{3}): &ripemd160hash{}, - common.BytesToAddress([]byte{4}): &dataCopy{}, - common.BytesToAddress([]byte{5}): &bigModExp{}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, - common.BytesToAddress([]byte{10}): &bls12381G1Add{}, - common.BytesToAddress([]byte{11}): &bls12381G1Mul{}, - common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{}, - common.BytesToAddress([]byte{13}): &bls12381G2Add{}, - common.BytesToAddress([]byte{14}): &bls12381G2Mul{}, - common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{}, - common.BytesToAddress([]byte{16}): &bls12381Pairing{}, - common.BytesToAddress([]byte{17}): &bls12381MapG1{}, - common.BytesToAddress([]byte{18}): &bls12381MapG2{}, +var PrecompiledContractsYoloV1 = 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.BytesToAddress([]byte{10}): newWrappedPrecompiledContract(&bls12381G1Add{}), + common.BytesToAddress([]byte{11}): newWrappedPrecompiledContract(&bls12381G1Mul{}), + common.BytesToAddress([]byte{12}): newWrappedPrecompiledContract(&bls12381G1MultiExp{}), + common.BytesToAddress([]byte{13}): newWrappedPrecompiledContract(&bls12381G2Add{}), + common.BytesToAddress([]byte{14}): newWrappedPrecompiledContract(&bls12381G2Mul{}), + common.BytesToAddress([]byte{15}): newWrappedPrecompiledContract(&bls12381G2MultiExp{}), + common.BytesToAddress([]byte{16}): newWrappedPrecompiledContract(&bls12381Pairing{}), + common.BytesToAddress([]byte{17}): newWrappedPrecompiledContract(&bls12381MapG1{}), + common.BytesToAddress([]byte{18}): newWrappedPrecompiledContract(&bls12381MapG2{}), } // RunPrecompiledContract runs and evaluates the output of a precompiled contract. diff --git a/core/vm/contracts_stateful.go b/core/vm/contracts_stateful.go new file mode 100644 index 0000000..b91acfb --- /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("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) { + 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) +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 54c9c7f..fe840da 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -34,19 +34,23 @@ var emptyCodeHash = crypto.Keccak256Hash(nil) type ( // CanTransferFunc is the signature of a transfer guard function - CanTransferFunc func(StateDB, common.Address, *big.Int) bool + CanTransferFunc func(StateDB, common.Address, *big.Int) bool + // CanTransferMCFunc is the signature of a transfer native asset function CanTransferMCFunc func(StateDB, common.Address, common.Address, *common.Hash, *big.Int) int // TransferFunc is the signature of a transfer function - TransferFunc func(StateDB, common.Address, common.Address, *big.Int) + TransferFunc func(StateDB, common.Address, common.Address, *big.Int) + // TransferMCFunc is the signature of a transfer native asset function TransferMCFunc func(StateDB, common.Address, common.Address, *common.Hash, *big.Int) // GetHashFunc returns the n'th block hash in the blockchain // and is used by the BLOCKHASH EVM op code. GetHashFunc func(uint64) common.Hash ) -func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { - var precompiles map[common.Address]PrecompiledContract +func (evm *EVM) precompile(addr common.Address) (StatefulPrecompiledContract, bool) { + var precompiles map[common.Address]StatefulPrecompiledContract switch { + case evm.chainRules.IsApricot: + precompiles = PrecompiledContractsApricot case evm.chainRules.IsYoloV1: precompiles = PrecompiledContractsYoloV1 case evm.chainRules.IsIstanbul: @@ -148,7 +152,7 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon StateDB: statedb, vmConfig: vmConfig, chainConfig: chainConfig, - chainRules: chainConfig.Rules(ctx.BlockNumber), + chainRules: chainConfig.AvalancheRules(ctx.BlockNumber, ctx.Time), interpreters: make([]Interpreter, 0, 1), } @@ -222,7 +226,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } evm.StateDB.CreateAccount(addr) } - evm.Transfer(evm.StateDB, caller.Address(), addr, value) // Capture the tracer start/end events in debug mode if evm.vmConfig.Debug && evm.depth == 0 { @@ -233,8 +236,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } if isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + ret, gas, err = p.Run(evm, caller, addr, value, input, gas, false) } else { + evm.Transfer(evm.StateDB, caller.Address(), addr, value) // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. code := evm.StateDB.GetCode(addr) @@ -371,7 +375,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + ret, gas, err = p.Run(evm, caller, addr, value, input, gas, false) } else { addrCopy := addr // Initialise a new contract and set the code that is to be used by the EVM. @@ -407,7 +411,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + ret, gas, err = p.Run(evm, caller, addr, big0, input, gas, false) } else { addrCopy := addr // Initialise a new contract and make initialise the delegate values @@ -451,7 +455,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte evm.StateDB.AddBalance(addr, big0) if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + ret, gas, err = p.Run(evm, caller, addr, big0, input, gas, true) } else { // At this point, we use a copy of address. If we don't, the go compiler will // leak the 'contract' to the outer scope, and make allocation for 'contract' diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index a114a8d..1292650 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -26,6 +26,7 @@ import ( ) var ( + // BuiltinAddr ... BuiltinAddr = common.Address{ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -106,6 +107,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { if cfg.JumpTable[STOP] == nil { var jt JumpTable switch { + case evm.chainRules.IsApricot: + jt = apricotInstructionSet case evm.chainRules.IsYoloV1: jt = yoloV1InstructionSet case evm.chainRules.IsIstanbul: @@ -146,6 +149,15 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter { // considered a revert-and-consume-all-gas operation except for // ErrExecutionReverted which means revert-and-keep-gas-left. func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) { + // TODO remove after Apricot network upgrade. + // [BuiltinAddr] is overridden by a precompile in Apricot. + // This makes any call to [BuiltinAddr] a no-op once the network + // upgrade occurs. Then in the upgrade after the hard fork, this + // code can be safely removed. + // This can't occur before the hard fork because it would change + // the behavior of upgraded nodes before the hard fork occurs + // since there is no mechanism to change this code based on the + // block height. if contract.Address() == BuiltinAddr { self := AccountRef(contract.Caller()) if _, ok := contract.caller.(*Contract); ok { diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 9b538d4..44e2ebc 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -49,6 +49,7 @@ type operation struct { } var ( + apricotInstructionSet = newApricotInstructionSet() frontierInstructionSet = newFrontierInstructionSet() homesteadInstructionSet = newHomesteadInstructionSet() tangerineWhistleInstructionSet = newTangerineWhistleInstructionSet() @@ -62,6 +63,16 @@ var ( // JumpTable contains the EVM opcodes supported at a given fork. type JumpTable [256]*operation +// newApricotInstructionSet returns a new instruction set +// compatible with the Apricot release, which deprecates +// added instructions: CALLEX and BALANCEMC +func newApricotInstructionSet() JumpTable { + instructionSet := newIstanbulInstructionSet() + instructionSet[CALLEX] = nil + instructionSet[BALANCEMC] = nil + return instructionSet +} + func newYoloV1InstructionSet() JumpTable { instructionSet := newIstanbulInstructionSet() diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index c5097f8..cc9518e 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -208,6 +208,7 @@ const ( SWAP ) +// Constants supporting native asset operations const ( BALANCEMC = 0xcd //EMC = 0xce |