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 --- contracts/arc20.sol | 150 ++++++++++++++++++++++++++++++++++++++ contracts/nativeAssets.sol | 20 +++++ core/vm/contracts.go | 86 +++++++++++----------- core/vm/contracts_stateful.go | 165 ++++++++++++++++++++++++++++++++++++++++++ core/vm/evm.go | 22 +++--- core/vm/interpreter.go | 12 +++ core/vm/jump_table.go | 11 +++ core/vm/opcodes.go | 1 + eth/backend.go | 5 +- examples/arc20/main.go | 93 ++++++++++++++++++++++++ examples/multicoin/main.go | 17 +++-- params/config.go | 67 ++++++++++++++++- params/protocol_params.go | 2 + plugin/evm/export_tx.go | 3 +- plugin/evm/import_tx.go | 3 +- plugin/evm/vm.go | 9 +++ 16 files changed, 597 insertions(+), 69 deletions(-) create mode 100644 contracts/arc20.sol create mode 100644 contracts/nativeAssets.sol create mode 100644 core/vm/contracts_stateful.go create mode 100644 examples/arc20/main.go diff --git a/contracts/arc20.sol b/contracts/arc20.sol new file mode 100644 index 0000000..a7fc997 --- /dev/null +++ b/contracts/arc20.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +import {NativeAssets} from "./NativeAssets.sol"; + +contract ARC20 { + + mapping (address => uint256) private _balances; + mapping(address => mapping(address => uint256)) private _allowances; + + uint256 private _assetID; + + uint256 private _totalSupply; + + string private _name; + string private _symbol; + uint8 private _decimals; + + constructor(string memory name_, string memory symbol_, uint8 decimals_, uint256 assetID_) public { + _name = name_; + _symbol = symbol_; + _decimals = decimals_; + _assetID = assetID_; + } + + /** + * @dev Returns the name of the token. + */ + function name() public view returns (string memory) { + return _name; + } + + /** + * @dev Returns the symbol of the token, usually a shorter version of the + * name. + */ + function symbol() public view returns (string memory) { + return _symbol; + } + + /** + * @dev Returns the number of decimals used to represent the token. + */ + function decimals() public view returns (uint8) { + return _decimals; + } + + /** + * @dev Returns the total supply of `assetID` currently held by + * this contract. + */ + function totalSupply() public view returns (uint256) { + return _totalSupply; + } + + /** + * @dev Returns the balance of `account` held in this contract. + */ + function balanceOf(address account) public view returns (uint256) { + return _balances[account]; + } + + // Withdrawal/Deposit functionality + + /** + * @dev Acknowledges the receipt of some amount of an Avalanche Native Token + * into the contract implementing this interface. + */ + function deposit() public { + uint256 updatedBalance = NativeAssets.assetBalance(address(this), _assetID); + uint256 depositAmount = updatedBalance - _totalSupply; + assert(depositAmount >= 0); + + _balances[msg.sender] += depositAmount; + _totalSupply = updatedBalance; + emit Deposit(msg.sender, depositAmount); + } + + /** + * @dev Emitted when `value` tokens are deposited from `depositor` + */ + event Deposit(address indexed depositor, uint256 value); + + /** + * @dev Withdraws `value` of the underlying asset to the contract + * caller. + */ + function withdraw(uint256 value) public { + require(_balances[msg.sender] >= value, "Insufficient funds for withdrawal"); + + _balances[msg.sender] -= value; + _totalSupply -= value; + + NativeAssets.assetCall(msg.sender, _assetID, value, ""); + emit Withdrawal(msg.sender, value); + } + + /** + * @dev Emitted when `value` tokens are withdrawn to `withdrawer` + */ + event Withdrawal(address indexed withdrawer, uint256 value); + + /** + * @dev Returns the `assetID` of the underlying asset this contract handles. + */ + function assetID() external view returns (uint256) { + return _assetID; + } + + event Transfer(address indexed from, address indexed to, uint256 value); + + function transfer(address to, uint256 value) public returns (bool success) { + require(_balances[msg.sender] >= value, "insufficient balance for transfer"); + + _balances[msg.sender] -= value; // deduct from sender's balance + _balances[to] += value; // add to recipient's balance + emit Transfer(msg.sender, to, value); + return true; + } + + event Approval(address indexed owner, address indexed spender, uint256 value); + + function approve(address spender, uint256 value) + public + returns (bool success) + { + _allowances[msg.sender][spender] = value; + emit Approval(msg.sender, spender, value); + return true; + } + + function transferFrom(address from, address to, uint256 value) + public + returns (bool success) + { + require(value <= _balances[from], "From address has insufficient balance to transfer"); + require(value <= _allowances[from][msg.sender], "Insufficient allowance granted to sender"); + + _balances[from] -= value; + _balances[to] += value; + _allowances[from][msg.sender] -= value; + emit Transfer(from, to, value); + return true; + } + + function allowance(address owner, address spender) public view returns (uint256) { + return _allowances[owner][spender]; + } +} diff --git a/contracts/nativeAssets.sol b/contracts/nativeAssets.sol new file mode 100644 index 0000000..0656271 --- /dev/null +++ b/contracts/nativeAssets.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.6.0 <0.8.0; + +library NativeAssets { + address constant balanceAddr = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF; + address constant transferAddr = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE; + + function assetBalance(address addr, uint256 assetID) public returns (uint256) { + (bool success, bytes memory data) = balanceAddr.call(abi.encodePacked(addr, assetID)); + require(success, "assetBalance failed"); + return abi.decode(data, (uint256)); + } + + function assetCall(address addr, uint256 assetID, uint256 assetAmount, bytes memory callData) public returns (bytes memory) { + (bool success, bytes memory data) = transferAddr.call(abi.encodePacked(addr, assetID, assetAmount, callData)); + require(success, "assetCall failed"); + return data; + } +} 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..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) +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 54c9c7f..ab04790 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: @@ -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..fc12f93 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() @@ -1043,3 +1044,13 @@ func newFrontierInstructionSet() JumpTable { }, } } + +// newApricotInstructionSet returns a new instruction set +// compatible with the Apricot release, which deprecates +// added instructions: CALLEX and BALANCEMC +func newApricotInstructionSet() JumpTable { + instructionSet := newFrontierInstructionSet() + instructionSet[CALLEX] = nil + instructionSet[BALANCEMC] = nil + return instructionSet +} 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 diff --git a/eth/backend.go b/eth/backend.go index 9fb8f38..e26cbb3 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -28,7 +28,6 @@ import ( "github.com/ava-labs/coreth/consensus" "github.com/ava-labs/coreth/consensus/clique" "github.com/ava-labs/coreth/consensus/dummy" - "github.com/ava-labs/coreth/consensus/ethash" "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/bloombits" "github.com/ava-labs/coreth/core/rawdb" @@ -152,7 +151,7 @@ func New(stack *node.Node, config *Config, chainDb: chainDb, eventMux: stack.EventMux(), accountManager: stack.AccountManager(), - engine: CreateConsensusEngine(stack, chainConfig, &config.Ethash, config.Miner.Notify, config.Miner.Noverify, chainDb, cb), + engine: CreateConsensusEngine(cb), closeBloomHandler: make(chan struct{}), networkID: config.NetworkId, gasPrice: config.Miner.GasPrice, @@ -265,7 +264,7 @@ func makeExtraData(extra []byte) []byte { } // CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service -func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database, cb *dummy.ConsensusCallbacks) consensus.Engine { +func CreateConsensusEngine(cb *dummy.ConsensusCallbacks) consensus.Engine { return dummy.NewDummyEngine(cb) } diff --git a/examples/arc20/main.go b/examples/arc20/main.go new file mode 100644 index 0000000..6bac290 --- /dev/null +++ b/examples/arc20/main.go @@ -0,0 +1,93 @@ +package main + +import ( + "context" + "crypto/ecdsa" + "fmt" + "math/big" + + "github.com/ava-labs/coreth/params" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" +) + +var ( + uri = "http://127.0.0.1:9650/ext/bc/C/rpc" + nativeAssetCallAddr = common.BytesToAddress([]byte{11}) + nativeAssetBalanceAddr = common.BytesToAddress([]byte{10}) + chainID = new(big.Int).SetUint64(43112) + privateKey *ecdsa.PrivateKey + address common.Address + erc20 common.Address + assetID *big.Int + amount *big.Int + gasLimit = uint64(700000) + gasPrice = params.MinGasPrice +) + +func init() { + pk, err := crypto.HexToECDSA("da777cd656c8760a7d378ae04d7dd0cd7a703c450c84e6c2faa886ca97517df7") + if err != nil { + panic(err) + } + privateKey = pk + address = crypto.PubkeyToAddress(privateKey.PublicKey) + erc20 = common.HexToAddress("0xea75d59faF258F1fdf2b94F158e54D7ad44359B6") + aID, success := new(big.Int).SetString("21556002304173297996004673107070813226981923092188601867247360128943231977558", 10) + if !success { + panic("Failed to read assetID") + } + assetID = aID + amount = new(big.Int).SetUint64(100) +} + +// createDepositCallData creates the callData argument to nativeAssetTransfer to move [amount] +// of [assetID] to [erc20] address and call the deposit function with signature "deposit()" +func createDepositCallData(erc20 common.Address, assetID, amount *big.Int) []byte { + // erc20 addr, assetID, assetAmount, callData + signatureHash := crypto.Keccak256([]byte("deposit()")) + fmt.Printf("signatureHash: 0x%x\n", signatureHash) + functionSignature := signatureHash[:4] + data := make([]byte, 0, 84) + data = append(data, erc20.Bytes()...) + data = append(data, common.LeftPadBytes(assetID.Bytes(), 32)...) + data = append(data, common.LeftPadBytes(amount.Bytes(), 32)...) + data = append(data, functionSignature...) // Add this back in to trigger call to deposit + fmt.Printf("deposit callData: 0x%x\n", data) + return data +} + +// createDepositTransaction creates a transaction to deposit native asset funds in [erc20] +func createDepositTransaction(nonce uint64, erc20 common.Address, assetID, amount *big.Int, gasLimit uint64, gasPrice *big.Int) *types.Transaction { + callData := createDepositCallData(erc20, assetID, amount) + return types.NewTransaction(nonce, nativeAssetCallAddr, new(big.Int), gasLimit, gasPrice, callData) +} + +func main() { + client, err := ethclient.Dial(uri) + if err != nil { + panic(err) + } + ctx := context.Background() + nonce, err := client.NonceAt(ctx, address, nil) + if err != nil { + panic(err) + } + fmt.Printf("Creating deposit transaction from: %s, erc20 address: %s, assetID: %d, amount: %d, nonce: %d\n", address.Hex(), erc20.Hex(), assetID, amount, nonce) + // Create and sign deposit transaction from account that has been funded with sufficient AVAX to + // pay gas costs and sufficient amount of the native asset to make the deposit + tx := createDepositTransaction(nonce, erc20, assetID, amount, gasLimit, gasPrice) + signer := types.NewEIP155Signer(chainID) + signedTx, err := types.SignTx(tx, signer, privateKey) + if err != nil { + panic(err) + } + // Send the signed transaction to the client + if err := client.SendTransaction(ctx, signedTx); err != nil { + panic(err) + } + txHash := signedTx.Hash() + fmt.Printf("txHash: %s\n", txHash.Hex()) +} diff --git a/examples/multicoin/main.go b/examples/multicoin/main.go index 35158a8..c2257e3 100644 --- a/examples/multicoin/main.go +++ b/examples/multicoin/main.go @@ -5,6 +5,15 @@ import ( "encoding/json" "flag" "fmt" + "go/build" + "math/big" + "os" + "os/signal" + "path/filepath" + "strings" + "syscall" + "time" + "github.com/ava-labs/coreth" "github.com/ava-labs/coreth/core" "github.com/ava-labs/coreth/core/types" @@ -18,14 +27,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" - "go/build" - "math/big" - "os" - "os/signal" - "path/filepath" - "strings" - "syscall" - "time" ) func checkError(err error) { diff --git a/params/config.go b/params/config.go index e5ec64b..1e14c40 100644 --- a/params/config.go +++ b/params/config.go @@ -53,6 +53,49 @@ var CheckpointOracles = map[common.Hash]*CheckpointOracleConfig{ } var ( + // AvalancheMainnetChainID ... + AvalancheMainnetChainID = big.NewInt(43114) + // AvalancheFujiChainID ... + AvalancheFujiChainID = big.NewInt(43113) +) + +var ( + // AvalancheApricotMainnetChainConfig is the configuration for Avalanche Main Network + AvalancheApricotMainnetChainConfig = &ChainConfig{ + ChainID: AvalancheMainnetChainID, + HomesteadBlock: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), + EIP155Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + ApricotBlock: big.NewInt(0), // TODO specify correct block height + } + + // AvalancheApricotFujiChainConfig is the configuration for the Fuji Test Network + AvalancheApricotFujiChainConfig = &ChainConfig{ + ChainID: AvalancheFujiChainID, + HomesteadBlock: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), + EIP155Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + ApricotBlock: big.NewInt(0), // TODO specify correct block height + } + + // TODO update local network genesis to enable Apricot + // MainnetChainConfig is the chain parameters to run a node on the main network. MainnetChainConfig = &ChainConfig{ ChainID: big.NewInt(1), @@ -239,16 +282,16 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil} + AllEthashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, new(EthashConfig), nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ethereum core developers into the Clique consensus. // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} + AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, new(EthashConfig), nil} + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, new(EthashConfig), nil} TestRules = TestChainConfig.Rules(new(big.Int)) ) @@ -323,6 +366,8 @@ type ChainConfig struct { YoloV1Block *big.Int `json:"yoloV1Block,omitempty"` // YOLO v1: https://github.com/ethereum/EIPs/pull/2657 (Ephemeral testnet) EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) + ApricotBlock *big.Int `json:"apricotBlock,omitempty"` // Apricot switch block (nil = no fork, 0 = already activated) + // Various consensus engines Ethash *EthashConfig `json:"ethash,omitempty"` Clique *CliqueConfig `json:"clique,omitempty"` @@ -358,7 +403,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v1: %v, Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v1: %v, Apricot: %v, Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -372,6 +417,7 @@ func (c *ChainConfig) String() string { c.IstanbulBlock, c.MuirGlacierBlock, c.YoloV1Block, + c.ApricotBlock, engine, ) } @@ -438,6 +484,12 @@ func (c *ChainConfig) IsEWASM(num *big.Int) bool { return isForked(c.EWASMBlock, num) } +// IsApricot returns whether num represents a block number after the Apricot fork +func (c *ChainConfig) IsApricot(num *big.Int) bool { + return isForked(c.ApricotBlock, num) +} + +// TODO review how this works and see if it will work for a live transition // CheckCompatible checks whether scheduled fork transitions have been imported // with a mismatching chain configuration. func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64) *ConfigCompatError { @@ -477,6 +529,10 @@ func (c *ChainConfig) CheckConfigForkOrder() error { {name: "istanbulBlock", block: c.IstanbulBlock}, {name: "muirGlacierBlock", block: c.MuirGlacierBlock, optional: true}, {name: "yoloV1Block", block: c.YoloV1Block}, + // TODO figure out how Apricot fits in here. + // Precompiled contracts start from other end so they are compatible + // but it may be incompatible (and it was before Apricot as well) due + // to how the instruction sets are created } { if lastFork.name != "" { // Next one must be higher number @@ -610,6 +666,8 @@ type Rules struct { IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsYoloV1 bool + // Avalanche Releases + IsApricot bool } // Rules ensures c's ChainID is not nil. @@ -629,5 +687,6 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), IsYoloV1: c.IsYoloV1(num), + IsApricot: c.IsApricot(num), } } diff --git a/params/protocol_params.go b/params/protocol_params.go index 3536c83..fdfb9a5 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -95,6 +95,8 @@ const ( ExtcodeHashGasConstantinople uint64 = 400 // Cost of EXTCODEHASH (introduced in Constantinople) ExtcodeHashGasEIP1884 uint64 = 700 // Cost of EXTCODEHASH after EIP 1884 (part in Istanbul) SelfdestructGasEIP150 uint64 = 5000 // Cost of SELFDESTRUCT post EIP 150 (Tangerine) + AssetBalanceApricot uint64 = 20 // Cost of calling PrecompiledContract AssetBalance (Apricot) + AssetCallApricot uint64 = 40 // Cost of calling PrecompiledContract AssetCall (Apricot) // EXP has a dynamic portion depending on the size of the exponent ExpByteFrontier uint64 = 10 // was set to 10 in Frontier diff --git a/plugin/evm/export_tx.go b/plugin/evm/export_tx.go index 2735573..2ebfeff 100644 --- a/plugin/evm/export_tx.go +++ b/plugin/evm/export_tx.go @@ -240,8 +240,8 @@ func (vm *VM) newExportTx( func (tx *UnsignedExportTx) EVMStateTransfer(vm *VM, state *state.StateDB) error { addrs := map[[20]byte]uint64{} for _, from := range tx.Ins { - log.Info("crosschain C->X", "addr", from.Address, "amount", from.Amount) if from.AssetID == vm.ctx.AVAXAssetID { + log.Info("crosschain C->X", "from", from.Address, "amount", from.Amount, "assetID", "AVAX") amount := new(big.Int).Mul( new(big.Int).SetUint64(from.Amount), x2cRate) if state.GetBalance(from.Address).Cmp(amount) < 0 { @@ -250,6 +250,7 @@ func (tx *UnsignedExportTx) EVMStateTransfer(vm *VM, state *state.StateDB) error state.SubBalance(from.Address, amount) } else { amount := new(big.Int).SetUint64(from.Amount) + log.Info("crosschain C->X", "from", from.Address, "amount", from.Amount, "assetID", from.AssetID) if state.GetBalanceMultiCoin(from.Address, common.Hash(from.AssetID)).Cmp(amount) < 0 { return errInsufficientFunds } diff --git a/plugin/evm/import_tx.go b/plugin/evm/import_tx.go index 1ec394c..d6b4b7d 100644 --- a/plugin/evm/import_tx.go +++ b/plugin/evm/import_tx.go @@ -270,12 +270,13 @@ func (vm *VM) newImportTx( // accounts accordingly with the imported EVMOutputs func (tx *UnsignedImportTx) EVMStateTransfer(vm *VM, state *state.StateDB) error { for _, to := range tx.Outs { - log.Info("crosschain X->C", "addr", to.Address, "amount", to.Amount) if to.AssetID == vm.ctx.AVAXAssetID { + log.Info("crosschain X->C", "to", to.Address, "amount", to.Amount, "assetID", "AVAX") amount := new(big.Int).Mul( new(big.Int).SetUint64(to.Amount), x2cRate) state.AddBalance(to.Address, amount) } else { + log.Info("crosschain X->C", "to", to.Address, "amount", to.Amount, "assetID", to.AssetID) amount := new(big.Int).SetUint64(to.Amount) state.AddBalanceMultiCoin(to.Address, common.Hash(to.AssetID), amount) } diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index c9bdad9..fc571b9 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -239,6 +239,15 @@ func (vm *VM) Initialize( return err } + // Set the Apricot Fork Block on the ChainConfig + // TODO switch to correct block number of the fork + switch { + case g.Config.ChainID.Cmp(params.AvalancheMainnetChainID) == 0: + g.Config.ApricotBlock = nil + case g.Config.ChainID.Cmp(params.AvalancheFujiChainID) == 0: + g.Config.ApricotBlock = nil + } + vm.acceptedDB = prefixdb.New([]byte(acceptedPrefix), db) vm.chainID = g.Config.ChainID -- cgit v1.2.3 From 3bf6e094864fde72484b824749019c64411239c3 Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Tue, 15 Dec 2020 17:05:31 -0500 Subject: Improve examples script --- examples/arc20/main.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/examples/arc20/main.go b/examples/arc20/main.go index 6bac290..25f66bf 100644 --- a/examples/arc20/main.go +++ b/examples/arc20/main.go @@ -15,8 +15,8 @@ import ( var ( uri = "http://127.0.0.1:9650/ext/bc/C/rpc" - nativeAssetCallAddr = common.BytesToAddress([]byte{11}) - nativeAssetBalanceAddr = common.BytesToAddress([]byte{10}) + nativeAssetBalanceAddr = common.HexToAddress("0x0100000000000000000000000000000000000001") + nativeAssetCallAddr = common.HexToAddress("0x0100000000000000000000000000000000000002") chainID = new(big.Int).SetUint64(43112) privateKey *ecdsa.PrivateKey address common.Address @@ -28,14 +28,23 @@ var ( ) func init() { + // Private Key to sign deposit transaction + // To export from MetaMask: + // 1) Click ellipsis on right hand side of account page + // 2) View Account Details + // 3) Export Private Key pk, err := crypto.HexToECDSA("da777cd656c8760a7d378ae04d7dd0cd7a703c450c84e6c2faa886ca97517df7") if err != nil { panic(err) } privateKey = pk + // Extract From Address from the private key address = crypto.PubkeyToAddress(privateKey.PublicKey) - erc20 = common.HexToAddress("0xea75d59faF258F1fdf2b94F158e54D7ad44359B6") - aID, success := new(big.Int).SetString("21556002304173297996004673107070813226981923092188601867247360128943231977558", 10) + // erc20 = common.HexToAddress("0xea75d59faF258F1fdf2b94F158e54D7ad44359B6") + // Address of ARC-20 to deposit funds in + erc20 = common.HexToAddress("0xC67353d203d748FC6D5E50bf44c7813C39EADea3") + // AssetID as a uint256 integer as displayed in Remix of the ARC-20 + aID, success := new(big.Int).SetString("49381164258359268658146204715775898207620442222804199785202784815303309497754", 10) if !success { panic("Failed to read assetID") } -- cgit v1.2.3 From 34da2a9bbc1cad12e5d1ab14799290e4b4987f99 Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Tue, 15 Dec 2020 17:05:51 -0500 Subject: Fix apricot instruction set --- core/vm/contracts_stateful.go | 4 ++-- core/vm/jump_table.go | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/vm/contracts_stateful.go b/core/vm/contracts_stateful.go index 3ef7a9e..b91acfb 100644 --- a/core/vm/contracts_stateful.go +++ b/core/vm/contracts_stateful.go @@ -27,8 +27,8 @@ var PrecompiledContractsApricot = map[common.Address]StatefulPrecompiledContract 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}, + common.HexToAddress("0x0100000000000000000000000000000000000001"): &nativeAssetBalance{gasCost: params.AssetBalanceApricot}, + common.HexToAddress("0x0100000000000000000000000000000000000002"): &nativeAssetCall{gasCost: params.AssetCallApricot}, } // StatefulPrecompiledContract is the interface for executing a precompiled contract diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index fc12f93..44e2ebc 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -63,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() @@ -1044,13 +1054,3 @@ func newFrontierInstructionSet() JumpTable { }, } } - -// newApricotInstructionSet returns a new instruction set -// compatible with the Apricot release, which deprecates -// added instructions: CALLEX and BALANCEMC -func newApricotInstructionSet() JumpTable { - instructionSet := newFrontierInstructionSet() - instructionSet[CALLEX] = nil - instructionSet[BALANCEMC] = nil - return instructionSet -} -- cgit v1.2.3 From b892b31f7f9bc9a63eb9774e7fbe0b5cd82891ae Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Tue, 15 Dec 2020 17:06:05 -0500 Subject: Update precompile addresses --- contracts/nativeAssets.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/nativeAssets.sol b/contracts/nativeAssets.sol index 0656271..836c4d8 100644 --- a/contracts/nativeAssets.sol +++ b/contracts/nativeAssets.sol @@ -3,8 +3,8 @@ pragma solidity >=0.6.0 <0.8.0; library NativeAssets { - address constant balanceAddr = 0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF; - address constant transferAddr = 0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE; + address constant balanceAddr = 0x0100000000000000000000000000000000000001; + address constant transferAddr = 0x0100000000000000000000000000000000000002; function assetBalance(address addr, uint256 assetID) public returns (uint256) { (bool success, bytes memory data) = balanceAddr.call(abi.encodePacked(addr, assetID)); -- cgit v1.2.3 From cc2a13f91f95bd377f5c3efb86ebbcc7096f6d97 Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Tue, 15 Dec 2020 17:30:03 -0500 Subject: Set chain rules based on block times for mainnet/fuji --- contracts/arc20.sol | 2 +- core/vm/evm.go | 2 +- params/config.go | 84 +++++++++++++++++++++++++++++++---------------------- plugin/evm/vm.go | 5 ++-- 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/contracts/arc20.sol b/contracts/arc20.sol index a7fc997..d1af6a3 100644 --- a/contracts/arc20.sol +++ b/contracts/arc20.sol @@ -2,7 +2,7 @@ pragma solidity >=0.6.0 <0.8.0; -import {NativeAssets} from "./NativeAssets.sol"; +import {NativeAssets} from "./nativeAssets.sol"; contract ARC20 { diff --git a/core/vm/evm.go b/core/vm/evm.go index ab04790..fe840da 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -152,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), } diff --git a/params/config.go b/params/config.go index 1e14c40..d5db94f 100644 --- a/params/config.go +++ b/params/config.go @@ -20,6 +20,7 @@ import ( "encoding/binary" "fmt" "math/big" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -62,40 +63,38 @@ var ( var ( // AvalancheApricotMainnetChainConfig is the configuration for Avalanche Main Network AvalancheApricotMainnetChainConfig = &ChainConfig{ - ChainID: AvalancheMainnetChainID, - HomesteadBlock: big.NewInt(0), - DAOForkBlock: big.NewInt(0), - DAOForkSupport: true, - EIP150Block: big.NewInt(0), - EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), - EIP155Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - ApricotBlock: big.NewInt(0), // TODO specify correct block height + ChainID: AvalancheMainnetChainID, + HomesteadBlock: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), + EIP155Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + ApricotBlockTimestamp: new(big.Int).SetUint64(uint64(time.Date(2021, 1, 7, 5, 00, 0, 0, time.UTC).Unix())), } // AvalancheApricotFujiChainConfig is the configuration for the Fuji Test Network AvalancheApricotFujiChainConfig = &ChainConfig{ - ChainID: AvalancheFujiChainID, - HomesteadBlock: big.NewInt(0), - DAOForkBlock: big.NewInt(0), - DAOForkSupport: true, - EIP150Block: big.NewInt(0), - EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), - EIP155Block: big.NewInt(0), - ByzantiumBlock: big.NewInt(0), - ConstantinopleBlock: big.NewInt(0), - PetersburgBlock: big.NewInt(0), - IstanbulBlock: big.NewInt(0), - MuirGlacierBlock: big.NewInt(0), - ApricotBlock: big.NewInt(0), // TODO specify correct block height + ChainID: AvalancheFujiChainID, + HomesteadBlock: big.NewInt(0), + DAOForkBlock: big.NewInt(0), + DAOForkSupport: true, + EIP150Block: big.NewInt(0), + EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), + EIP155Block: big.NewInt(0), + ByzantiumBlock: big.NewInt(0), + ConstantinopleBlock: big.NewInt(0), + PetersburgBlock: big.NewInt(0), + IstanbulBlock: big.NewInt(0), + MuirGlacierBlock: big.NewInt(0), + ApricotBlockTimestamp: new(big.Int).SetUint64(uint64(time.Date(2020, 12, 23, 5, 00, 0, 0, time.UTC).Unix())), } - // TODO update local network genesis to enable Apricot - // MainnetChainConfig is the chain parameters to run a node on the main network. MainnetChainConfig = &ChainConfig{ ChainID: big.NewInt(1), @@ -366,7 +365,11 @@ type ChainConfig struct { YoloV1Block *big.Int `json:"yoloV1Block,omitempty"` // YOLO v1: https://github.com/ethereum/EIPs/pull/2657 (Ephemeral testnet) EWASMBlock *big.Int `json:"ewasmBlock,omitempty"` // EWASM switch block (nil = no fork, 0 = already activated) - ApricotBlock *big.Int `json:"apricotBlock,omitempty"` // Apricot switch block (nil = no fork, 0 = already activated) + // Avalanche Network Upgrade Block Timestamps: + + // Apricot switch block timestamp (nil = no fork, 0 = already activated) + // Apricot rules go into effect for blocks with a timestamp >= ApricotBlockTimestamp + ApricotBlockTimestamp *big.Int `json:"apricotBlock,omitempty"` // Various consensus engines Ethash *EthashConfig `json:"ethash,omitempty"` @@ -403,7 +406,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v1: %v, Apricot: %v, Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, YOLO v1: %v, ApricotTimestamp: %v, Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -417,7 +420,7 @@ func (c *ChainConfig) String() string { c.IstanbulBlock, c.MuirGlacierBlock, c.YoloV1Block, - c.ApricotBlock, + c.ApricotBlockTimestamp, engine, ) } @@ -484,9 +487,12 @@ func (c *ChainConfig) IsEWASM(num *big.Int) bool { return isForked(c.EWASMBlock, num) } -// IsApricot returns whether num represents a block number after the Apricot fork -func (c *ChainConfig) IsApricot(num *big.Int) bool { - return isForked(c.ApricotBlock, num) +// Avalanche Forks: + +// IsApricot returns whether num represents a block with a timestamp +// after the Apricot fork time. +func (c *ChainConfig) IsApricot(blockTimestamp *big.Int) bool { + return isForked(c.ApricotBlockTimestamp, blockTimestamp) } // TODO review how this works and see if it will work for a live transition @@ -687,6 +693,14 @@ func (c *ChainConfig) Rules(num *big.Int) Rules { IsPetersburg: c.IsPetersburg(num), IsIstanbul: c.IsIstanbul(num), IsYoloV1: c.IsYoloV1(num), - IsApricot: c.IsApricot(num), } } + +// AvalancheRules returns the Avalanche modified rules to support Avalanche +// network upgrades +func (c *ChainConfig) AvalancheRules(blockNum, blockTimestamp *big.Int) Rules { + rules := c.Rules(blockNum) + + rules.IsApricot = c.IsApricot(blockTimestamp) + return rules +} diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index fc571b9..2732a79 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -243,11 +243,10 @@ func (vm *VM) Initialize( // TODO switch to correct block number of the fork switch { case g.Config.ChainID.Cmp(params.AvalancheMainnetChainID) == 0: - g.Config.ApricotBlock = nil + g.Config = params.AvalancheApricotMainnetChainConfig case g.Config.ChainID.Cmp(params.AvalancheFujiChainID) == 0: - g.Config.ApricotBlock = nil + g.Config = params.AvalancheApricotFujiChainConfig } - vm.acceptedDB = prefixdb.New([]byte(acceptedPrefix), db) vm.chainID = g.Config.ChainID -- cgit v1.2.3 From faf845717a170e1c4baebaafef051d32058e2f99 Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Tue, 15 Dec 2020 17:34:48 -0500 Subject: Move params for network upgrade --- params/config.go | 11 +++++++++-- plugin/evm/vm.go | 8 ++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/params/config.go b/params/config.go index d5db94f..a0b4a51 100644 --- a/params/config.go +++ b/params/config.go @@ -53,6 +53,7 @@ var CheckpointOracles = map[common.Hash]*CheckpointOracleConfig{ GoerliGenesisHash: GoerliCheckpointOracle, } +// Avalanche ChainIDs var ( // AvalancheMainnetChainID ... AvalancheMainnetChainID = big.NewInt(43114) @@ -60,6 +61,12 @@ var ( AvalancheFujiChainID = big.NewInt(43113) ) +// Network upgrade block timestamps +var ( + AvalancheMainnetApricotTimestamp = new(big.Int).SetUint64(uint64(time.Date(2021, 1, 7, 5, 00, 0, 0, time.UTC).Unix())) + AvalancheFujiApricotTimestamp = new(big.Int).SetUint64(uint64(time.Date(2020, 12, 23, 5, 00, 0, 0, time.UTC).Unix())) +) + var ( // AvalancheApricotMainnetChainConfig is the configuration for Avalanche Main Network AvalancheApricotMainnetChainConfig = &ChainConfig{ @@ -75,7 +82,7 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), - ApricotBlockTimestamp: new(big.Int).SetUint64(uint64(time.Date(2021, 1, 7, 5, 00, 0, 0, time.UTC).Unix())), + ApricotBlockTimestamp: AvalancheMainnetApricotTimestamp, } // AvalancheApricotFujiChainConfig is the configuration for the Fuji Test Network @@ -92,7 +99,7 @@ var ( PetersburgBlock: big.NewInt(0), IstanbulBlock: big.NewInt(0), MuirGlacierBlock: big.NewInt(0), - ApricotBlockTimestamp: new(big.Int).SetUint64(uint64(time.Date(2020, 12, 23, 5, 00, 0, 0, time.UTC).Unix())), + ApricotBlockTimestamp: AvalancheFujiApricotTimestamp, } // MainnetChainConfig is the chain parameters to run a node on the main network. diff --git a/plugin/evm/vm.go b/plugin/evm/vm.go index 2732a79..9f5607a 100644 --- a/plugin/evm/vm.go +++ b/plugin/evm/vm.go @@ -239,13 +239,13 @@ func (vm *VM) Initialize( return err } - // Set the Apricot Fork Block on the ChainConfig - // TODO switch to correct block number of the fork + // If Mainnet or Fuji ChainID is present then switch + // manually set the config switch { case g.Config.ChainID.Cmp(params.AvalancheMainnetChainID) == 0: - g.Config = params.AvalancheApricotMainnetChainConfig + g.Config.ApricotBlockTimestamp = params.AvalancheMainnetApricotTimestamp case g.Config.ChainID.Cmp(params.AvalancheFujiChainID) == 0: - g.Config = params.AvalancheApricotFujiChainConfig + g.Config.ApricotBlockTimestamp = params.AvalancheFujiApricotTimestamp } vm.acceptedDB = prefixdb.New([]byte(acceptedPrefix), db) -- cgit v1.2.3 From 898c6841f72ba2343682aebe34004d90a4cf3466 Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Tue, 15 Dec 2020 21:22:42 -0500 Subject: modify arc20 example script --- examples/arc20/main.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/arc20/main.go b/examples/arc20/main.go index 25f66bf..303d620 100644 --- a/examples/arc20/main.go +++ b/examples/arc20/main.go @@ -42,13 +42,9 @@ func init() { address = crypto.PubkeyToAddress(privateKey.PublicKey) // erc20 = common.HexToAddress("0xea75d59faF258F1fdf2b94F158e54D7ad44359B6") // Address of ARC-20 to deposit funds in - erc20 = common.HexToAddress("0xC67353d203d748FC6D5E50bf44c7813C39EADea3") + erc20 = common.HexToAddress("0x721F9Af7631605713133ccc502E32eA4d43CDfec") // AssetID as a uint256 integer as displayed in Remix of the ARC-20 - aID, success := new(big.Int).SetString("49381164258359268658146204715775898207620442222804199785202784815303309497754", 10) - if !success { - panic("Failed to read assetID") - } - assetID = aID + assetID = common.HexToHash("0x42f0d24c970eb6c567ea63a68b97d8e357a7fc8d9f73489c7761f1382b295db4").Big() amount = new(big.Int).SetUint64(100) } @@ -74,6 +70,7 @@ func createDepositTransaction(nonce uint64, erc20 common.Address, assetID, amoun return types.NewTransaction(nonce, nativeAssetCallAddr, new(big.Int), gasLimit, gasPrice, callData) } +// deploy ARC-20 contract with specific assetID func main() { client, err := ethclient.Dial(uri) if err != nil { -- cgit v1.2.3 From 8026c5f6c44a02ff44bb25cb941915f53cdc9e1c Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Tue, 15 Dec 2020 21:24:34 -0500 Subject: Change name of apricot block in config --- params/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/params/config.go b/params/config.go index a0b4a51..2f59170 100644 --- a/params/config.go +++ b/params/config.go @@ -376,7 +376,7 @@ type ChainConfig struct { // Apricot switch block timestamp (nil = no fork, 0 = already activated) // Apricot rules go into effect for blocks with a timestamp >= ApricotBlockTimestamp - ApricotBlockTimestamp *big.Int `json:"apricotBlock,omitempty"` + ApricotBlockTimestamp *big.Int `json:"apricotBlockTimestamp,omitempty"` // Various consensus engines Ethash *EthashConfig `json:"ethash,omitempty"` -- cgit v1.2.3 From 7c758da302baf80775876008958ec77055ab953a Mon Sep 17 00:00:00 2001 From: Aaron Buchwald Date: Tue, 15 Dec 2020 21:30:16 -0500 Subject: Remove contracts --- contracts/arc20.sol | 150 --------------------------------------------- contracts/nativeAssets.sol | 20 ------ 2 files changed, 170 deletions(-) delete mode 100644 contracts/arc20.sol delete mode 100644 contracts/nativeAssets.sol diff --git a/contracts/arc20.sol b/contracts/arc20.sol deleted file mode 100644 index d1af6a3..0000000 --- a/contracts/arc20.sol +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity >=0.6.0 <0.8.0; - -import {NativeAssets} from "./nativeAssets.sol"; - -contract ARC20 { - - mapping (address => uint256) private _balances; - mapping(address => mapping(address => uint256)) pr