aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAaron Buchwald <aaron.buchwald56@gmail.com>2020-11-23 20:46:35 -0500
committerAaron Buchwald <aaron.buchwald56@gmail.com>2020-12-15 10:46:26 -0500
commit368844ad2a28ec07848e3c0169cf2b83b579a2e8 (patch)
tree0b3956351add28aed944824eb45ffb2723809dba
parent2d0a37c6490dc9a4ec36ee4ebbed01c790f0426a (diff)
Add native asset precompiled contracts for apricot release
-rw-r--r--contracts/arc20.sol150
-rw-r--r--contracts/nativeAssets.sol20
-rw-r--r--core/vm/contracts.go86
-rw-r--r--core/vm/contracts_stateful.go165
-rw-r--r--core/vm/evm.go22
-rw-r--r--core/vm/interpreter.go12
-rw-r--r--core/vm/jump_table.go11
-rw-r--r--core/vm/opcodes.go1
-rw-r--r--eth/backend.go5
-rw-r--r--examples/arc20/main.go93
-rw-r--r--examples/multicoin/main.go17
-rw-r--r--params/config.go67
-rw-r--r--params/protocol_params.go2
-rw-r--r--plugin/evm/export_tx.go3
-rw-r--r--plugin/evm/import_tx.go3
-rw-r--r--plugin/evm/vm.go9
16 files changed, 597 insertions, 69 deletions
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/si