aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/vm/contracts.go86
-rw-r--r--core/vm/contracts_stateful.go165
-rw-r--r--core/vm/evm.go24
-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.go99
-rw-r--r--examples/multicoin/main.go17
-rw-r--r--params/config.go88
-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.go8
14 files changed, 454 insertions, 70 deletions
diff --git a/core/vm/contracts.go b/core/vm/contracts.go
index 9827bac..6fc6334 100644
--- a/core/vm/contracts.go
+++ b/core/vm/contracts.go
@@ -44,61 +44,61 @@ type PrecompiledContract interface {
// PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum
// contracts used in the Frontier and Homestead releases.
-var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{
- common.BytesToAddress([]byte{1}): &ecrecover{},
- common.BytesToAddress([]byte{2}): &sha256hash{},
- common.BytesToAddress([]byte{3}): &ripemd160hash{},
- common.BytesToAddress([]byte{4}): &dataCopy{},
+var PrecompiledContractsHomestead = map[common.Address]StatefulPrecompiledContract{
+ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}),
+ common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}),
+ common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}),
+ common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}),
}
// PrecompiledContractsByzantium contains the default set of pre-compiled Ethereum
// contracts used in the Byzantium release.
-var PrecompiledContractsByzantium = map[common.Address]PrecompiledContract{
- common.BytesToAddress([]byte{1}): &ecrecover{},
- common.BytesToAddress([]byte{2}): &sha256hash{},
- common.BytesToAddress([]byte{3}): &ripemd160hash{},
- common.BytesToAddress([]byte{4}): &dataCopy{},
- common.BytesToAddress([]byte{5}): &bigModExp{},
- common.BytesToAddress([]byte{6}): &bn256AddByzantium{},
- common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{},
- common.BytesToAddress([]byte{8}): &bn256PairingByzantium{},
+var PrecompiledContractsByzantium = map[common.Address]StatefulPrecompiledContract{
+ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}),
+ common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}),
+ common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}),
+ common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}),
+ common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{}),
+ common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddByzantium{}),
+ common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulByzantium{}),
+ common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingByzantium{}),
}
// PrecompiledContractsIstanbul contains the default set of pre-compiled Ethereum
// contracts used in the Istanbul release.
-var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
- common.BytesToAddress([]byte{1}): &ecrecover{},
- common.BytesToAddress([]byte{2}): &sha256hash{},
- common.BytesToAddress([]byte{3}): &ripemd160hash{},
- common.BytesToAddress([]byte{4}): &dataCopy{},
- common.BytesToAddress([]byte{5}): &bigModExp{},
- common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
- common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
- common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
- common.BytesToAddress([]byte{9}): &blake2F{},
+var PrecompiledContractsIstanbul = map[common.Address]StatefulPrecompiledContract{
+ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}),
+ common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}),
+ common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}),
+ common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}),
+ common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{}),
+ common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}),
+ common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}),
+ common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}),
+ common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}),
}
// PrecompiledContractsYoloV1 contains the default set of pre-compiled Ethereum
// contracts used in the Yolo v1 test release.
-var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{
- common.BytesToAddress([]byte{1}): &ecrecover{},
- common.BytesToAddress([]byte{2}): &sha256hash{},
- common.BytesToAddress([]byte{3}): &ripemd160hash{},
- common.BytesToAddress([]byte{4}): &dataCopy{},
- common.BytesToAddress([]byte{5}): &bigModExp{},
- common.BytesToAddress([]byte{6}): &bn256AddIstanbul{},
- common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{},
- common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{},
- common.BytesToAddress([]byte{9}): &blake2F{},
- common.BytesToAddress([]byte{10}): &bls12381G1Add{},
- common.BytesToAddress([]byte{11}): &bls12381G1Mul{},
- common.BytesToAddress([]byte{12}): &bls12381G1MultiExp{},
- common.BytesToAddress([]byte{13}): &bls12381G2Add{},
- common.BytesToAddress([]byte{14}): &bls12381G2Mul{},
- common.BytesToAddress([]byte{15}): &bls12381G2MultiExp{},
- common.BytesToAddress([]byte{16}): &bls12381Pairing{},
- common.BytesToAddress([]byte{17}): &bls12381MapG1{},
- common.BytesToAddress([]byte{18}): &bls12381MapG2{},
+var PrecompiledContractsYoloV1 = map[common.Address]StatefulPrecompiledContract{
+ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}),
+ common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}),
+ common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}),
+ common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}),
+ common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{}),
+ common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}),
+ common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}),
+ common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}),
+ common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}),
+ common.BytesToAddress([]byte{10}): newWrappedPrecompiledContract(&bls12381G1Add{}),
+ common.BytesToAddress([]byte{11}): newWrappedPrecompiledContract(&bls12381G1Mul{}),
+ common.BytesToAddress([]byte{12}): newWrappedPrecompiledContract(&bls12381G1MultiExp{}),
+ common.BytesToAddress([]byte{13}): newWrappedPrecompiledContract(&bls12381G2Add{}),
+ common.BytesToAddress([]byte{14}): newWrappedPrecompiledContract(&bls12381G2Mul{}),
+ common.BytesToAddress([]byte{15}): newWrappedPrecompiledContract(&bls12381G2MultiExp{}),
+ common.BytesToAddress([]byte{16}): newWrappedPrecompiledContract(&bls12381Pairing{}),
+ common.BytesToAddress([]byte{17}): newWrappedPrecompiledContract(&bls12381MapG1{}),
+ common.BytesToAddress([]byte{18}): newWrappedPrecompiledContract(&bls12381MapG2{}),
}
// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
diff --git a/core/vm/contracts_stateful.go b/core/vm/contracts_stateful.go
new file mode 100644
index 0000000..b91acfb
--- /dev/null
+++ b/core/vm/contracts_stateful.go
@@ -0,0 +1,165 @@
+package vm
+
+import (
+ "errors"
+ "fmt"
+ "math/big"
+
+ "github.com/ava-labs/coreth/params"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/holiman/uint256"
+)
+
+// PrecompiledContractsApricot contains the default set of pre-compiled Ethereum
+// contracts used in the Istanbul release and the stateful precompiled contracts
+// added for the Avalanche Apricot release.
+// Apricot is incompatible with the YoloV1 Release since it does not include the
+// BLS12-381 Curve Operations added to the set of precompiled contracts
+var PrecompiledContractsApricot = map[common.Address]StatefulPrecompiledContract{
+ common.BytesToAddress([]byte{1}): newWrappedPrecompiledContract(&ecrecover{}),
+ common.BytesToAddress([]byte{2}): newWrappedPrecompiledContract(&sha256hash{}),
+ common.BytesToAddress([]byte{3}): newWrappedPrecompiledContract(&ripemd160hash{}),
+ common.BytesToAddress([]byte{4}): newWrappedPrecompiledContract(&dataCopy{}),
+ common.BytesToAddress([]byte{5}): newWrappedPrecompiledContract(&bigModExp{}),
+ common.BytesToAddress([]byte{6}): newWrappedPrecompiledContract(&bn256AddIstanbul{}),
+ common.BytesToAddress([]byte{7}): newWrappedPrecompiledContract(&bn256ScalarMulIstanbul{}),
+ common.BytesToAddress([]byte{8}): newWrappedPrecompiledContract(&bn256PairingIstanbul{}),
+ common.BytesToAddress([]byte{9}): newWrappedPrecompiledContract(&blake2F{}),
+ common.HexToAddress("0x0100000000000000000000000000000000000000"): &deprecatedContract{msg: "hardcoded genesis contract has been deprecated"},
+ common.HexToAddress("0x0100000000000000000000000000000000000001"): &nativeAssetBalance{gasCost: params.AssetBalanceApricot},
+ common.HexToAddress("0x0100000000000000000000000000000000000002"): &nativeAssetCall{gasCost: params.AssetCallApricot},
+}
+
+// StatefulPrecompiledContract is the interface for executing a precompiled contract
+// This wraps the PrecompiledContracts native to Ethereum and allows adding in stateful
+// precompiled contracts to support native Avalanche asset transfers.
+type StatefulPrecompiledContract interface {
+ // Run executes a precompiled contract in the current state
+ // assumes that it has already been verified that [caller] can
+ // transfer [value].
+ Run(evm *EVM, caller ContractRef, addr common.Address, value *big.Int, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error)
+}
+
+// wrappedPrecompiledContract implements StatefulPrecompiledContract by wrapping stateless native precompiled contracts
+// in Ethereum.
+type wrappedPrecompiledContract struct {
+ p PrecompiledContract
+}
+
+func newWrappedPrecompiledContract(p PrecompiledContract) StatefulPrecompiledContract {
+ return &wrappedPrecompiledContract{p: p}
+}
+
+// Run ...
+func (w *wrappedPrecompiledContract) Run(evm *EVM, caller ContractRef, addr common.Address, value *big.Int, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
+ evm.Transfer(evm.StateDB, caller.Address(), addr, value)
+ return RunPrecompiledContract(w.p, input, suppliedGas)
+}
+
+// nativeAssetBalance is a precompiled contract used to retrieve the native asset balance
+type nativeAssetBalance struct {
+ gasCost uint64
+}
+
+// Run implements StatefulPrecompiledContract
+func (b *nativeAssetBalance) Run(evm *EVM, caller ContractRef, addr common.Address, value *big.Int, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
+ // input: encodePacked(address 20 bytes, assetID 32 bytes)
+ if value.Sign() != 0 {
+ return nil, suppliedGas, errors.New("cannot transfer value to native asset balance pre-compiled contract")
+ }
+
+ if suppliedGas < b.gasCost {
+ return nil, 0, ErrOutOfGas
+ }
+ remainingGas = suppliedGas - b.gasCost
+
+ if len(input) != 52 {
+ return nil, remainingGas, fmt.Errorf("input to native asset balance must be 52 bytes, containing address [20 bytes] and assetID [32 bytes], but found length: %d", len(input))
+ }
+ address := common.BytesToAddress(input[:20])
+ assetID := new(common.Hash)
+ assetID.SetBytes(input[20:52])
+
+ res, overflow := uint256.FromBig(evm.StateDB.GetBalanceMultiCoin(address, *assetID))
+ log.Info("nativeAssetBalance", "address", address, "assetID", assetID.Hex(), "res", res, "overflow", overflow)
+ if overflow {
+ return nil, remainingGas, errors.New("balance overflow")
+ }
+ return common.LeftPadBytes(res.Bytes(), 32), remainingGas, nil
+}
+
+// nativeAssetCall atomically transfers a native asset to a recipient address as well as calling that
+// address
+type nativeAssetCall struct {
+ gasCost uint64
+}
+
+// Run implements StatefulPrecompiledContract
+func (c *nativeAssetCall) Run(evm *EVM, caller ContractRef, addr common.Address, value *big.Int, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
+ // input: encodePacked(address 20 bytes, assetID 32 bytes, assetAmount 32 bytes, callData variable length bytes)
+ if suppliedGas < c.gasCost {
+ return nil, 0, ErrOutOfGas
+ }
+ remainingGas = suppliedGas - c.gasCost
+
+ if readOnly {
+ return nil, remainingGas, errors.New("cannot execute native asset transfer within read only call")
+ }
+
+ if len(input) < 84 {
+ return nil, remainingGas, fmt.Errorf("missing inputs to native asset transfer, expected input of length at least 84, but found %d", len(input))
+ }
+ to := common.BytesToAddress(input[:20])
+ assetID := new(common.Hash)
+ assetID.SetBytes(input[20:52])
+ assetAmount := new(big.Int).SetBytes(input[52:84])
+ callData := input[84:]
+ log.Info("nativeAssetCall", "input", fmt.Sprintf("0x%x", input), "to", to.Hex(), "assetID", assetID.Hex(), "assetAmount", assetAmount, "callData", fmt.Sprintf("0x%x", callData))
+
+ mcerr := evm.Context.CanTransferMC(evm.StateDB, caller.Address(), to, assetID, assetAmount)
+ if mcerr == 1 {
+ log.Info("Insufficient balance for mc transfer")
+ return nil, remainingGas, ErrInsufficientBalance
+ } else if mcerr != 0 {
+ log.Info("incompatible account for mc transfer")
+ return nil, remainingGas, ErrIncompatibleAccount
+ }
+
+ snapshot := evm.StateDB.Snapshot()
+
+ if !evm.StateDB.Exist(to) {
+ log.Info("Creating account entry", "address", to.Hex())
+ remainingGas -= params.CallNewAccountGas
+ evm.StateDB.CreateAccount(to)
+ }
+
+ // Send [value] to [to] address
+ evm.Transfer(evm.StateDB, caller.Address(), to, value)
+ evm.TransferMultiCoin(evm.StateDB, caller.Address(), to, assetID, assetAmount)
+ ret, remainingGas, err = evm.Call(caller, to, callData, remainingGas, big.NewInt(0))
+ log.Info("Finished TransferMultiCoin and call", "return", ret, "remainingGas", remainingGas, "error", err)
+
+ // When an error was returned by the EVM or when setting the creation code
+ // above we revert to the snapshot and consume any gas remaining. Additionally
+ // when we're in homestead this also counts for code storage gas errors.
+ if err != nil {
+ evm.StateDB.RevertToSnapshot(snapshot)
+ if err != ErrExecutionReverted {
+ remainingGas = 0
+ }
+ // TODO: consider clearing up unused snapshots:
+ //} else {
+ // evm.StateDB.DiscardSnapshot(snapshot)
+ }
+ return ret, remainingGas, err
+}
+
+type deprecatedContract struct {
+ msg string
+}
+
+// Run ...
+func (d *deprecatedContract) Run(evm *EVM, caller ContractRef, addr common.Address, value *big.Int, input []byte, suppliedGas uint64, readOnly bool) (ret []byte, remainingGas uint64, err error) {
+ return nil, suppliedGas, errors.New(d.msg)
+}
diff --git a/core/vm/evm.go b/core/vm/evm.go
index 54c9c7f..fe840da 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -34,19 +34,23 @@ var emptyCodeHash = crypto.Keccak256Hash(nil)
type (
// CanTransferFunc is the signature of a transfer guard function
- CanTransferFunc func(StateDB, common.Address, *big.Int) bool
+ CanTransferFunc func(StateDB, common.Address, *big.Int) bool
+ // CanTransferMCFunc is the signature of a transfer native asset function
CanTransferMCFunc func(StateDB, common.Address, common.Address, *common.Hash, *big.Int) int
// TransferFunc is the signature of a transfer function
- TransferFunc func(StateDB, common.Address, common.Address, *big.Int)
+ TransferFunc func(StateDB, common.Address, common.Address, *big.Int)
+ // TransferMCFunc is the signature of a transfer native asset function
TransferMCFunc func(StateDB, common.Address, common.Address, *common.Hash, *big.Int)
// GetHashFunc returns the n'th block hash in the blockchain
// and is used by the BLOCKHASH EVM op code.
GetHashFunc func(uint64) common.Hash
)
-func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
- var precompiles map[common.Address]PrecompiledContract
+func (evm *EVM) precompile(addr common.Address) (StatefulPrecompiledContract, bool) {
+ var precompiles map[common.Address]StatefulPrecompiledContract
switch {
+ case evm.chainRules.IsApricot:
+ precompiles = PrecompiledContractsApricot
case evm.chainRules.IsYoloV1:
precompiles = PrecompiledContractsYoloV1
case evm.chainRules.IsIstanbul:
@@ -148,7 +152,7 @@ func NewEVM(ctx Context, statedb StateDB, chainConfig *params.ChainConfig, vmCon
StateDB: statedb,
vmConfig: vmConfig,
chainConfig: chainConfig,
- chainRules: chainConfig.Rules(ctx.BlockNumber),
+ chainRules: chainConfig.AvalancheRules(ctx.BlockNumber, ctx.Time),
interpreters: make([]Interpreter, 0, 1),
}
@@ -222,7 +226,6 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
}
evm.StateDB.CreateAccount(addr)
}
- evm.Transfer(evm.StateDB, caller.Address(), addr, value)
// Capture the tracer start/end events in debug mode
if evm.vmConfig.Debug && evm.depth == 0 {
@@ -233,8 +236,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
}
if isPrecompile {
- ret, gas, err = RunPrecompiledContract(p, input, gas)
+ ret, gas, err = p.Run(evm, caller, addr, value, input, gas, false)
} else {
+ evm.Transfer(evm.StateDB, caller.Address(), addr, value)
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
code := evm.StateDB.GetCode(addr)
@@ -371,7 +375,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
- ret, gas, err = RunPrecompiledContract(p, input, gas)
+ ret, gas, err = p.Run(evm, caller, addr, value, input, gas, false)
} else {
addrCopy := addr
// Initialise a new contract and set the code that is to be used by the EVM.
@@ -407,7 +411,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
// It is allowed to call precompiles, even via delegatecall
if p, isPrecompile := evm.precompile(addr); isPrecompile {
- ret, gas, err = RunPrecompiledContract(p, input, gas)
+ ret, gas, err = p.Run(evm, caller, addr, big0, input, gas, false)
} else {
addrCopy := addr
// Initialise a new contract and make initialise the delegate values
@@ -451,7 +455,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte
evm.StateDB.AddBalance(addr, big0)
if p, isPrecompile := evm.precompile(addr); isPrecompile {
- ret, gas, err = RunPrecompiledContract(p, input, gas)
+ ret, gas, err = p.Run(evm, caller, addr, big0, input, gas, true)
} else {
// At this point, we use a copy of address. If we don't, the go compiler will
// leak the 'contract' to the outer scope, and make allocation for 'contract'
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index a114a8d..1292650 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -26,6 +26,7 @@ import (
)
var (
+ // BuiltinAddr ...
BuiltinAddr = common.Address{
1, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
@@ -106,6 +107,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
if cfg.JumpTable[STOP] == nil {
var jt JumpTable
switch {
+ case evm.chainRules.IsApricot:
+ jt = apricotInstructionSet
case evm.chainRules.IsYoloV1:
jt = yoloV1InstructionSet
case evm.chainRules.IsIstanbul:
@@ -146,6 +149,15 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
// considered a revert-and-consume-all-gas operation except for
// ErrExecutionReverted which means revert-and-keep-gas-left.
func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (ret []byte, err error) {
+ // TODO remove after Apricot network upgrade.
+ // [BuiltinAddr] is overridden by a precompile in Apricot.
+ // This makes any call to [BuiltinAddr] a no-op once the network
+ // upgrade occurs. Then in the upgrade after the hard fork, this
+ // code can be safely removed.
+ // This can't occur before the hard fork because it would change
+ // the behavior of upgraded nodes before the hard fork occurs
+ // since there is no mechanism to change this code based on the
+ // block height.
if contract.Address() == BuiltinAddr {
self := AccountRef(contract.Caller())
if _, ok := contract.caller.(*Contract); ok {
diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go
index 9b538d4..44e2ebc 100644
--- a/core/vm/jump_table.go
+++ b/core/vm/jump_table.go
@@ -49,6 +49,7 @@ type operation struct {
}
var (
+ apricotInstructionSet = newApricotInstructionSet()
frontierInstructionSet = newFrontierInstructionSet()
homesteadInstructionSet = newHomesteadInstructionSet()
tangerineWhistleInstructionSet = newTangerineWhistleInstructionSet()
@@ -62,6 +63,16 @@ var (
// JumpTable contains the EVM opcodes supported at a given fork.
type JumpTable [256]*operation
+// newApricotInstructionSet returns a new instruction set
+// compatible with the Apricot release, which deprecates
+// added instructions: CALLEX and BALANCEMC
+func newApricotInstructionSet() JumpTable {
+ instructionSet := newIstanbulInstructionSet()
+ instructionSet[CALLEX] = nil
+ instructionSet[BALANCEMC] = nil
+ return instructionSet
+}
+
func newYoloV1InstructionSet() JumpTable {
instructionSet := newIstanbulInstructionSet()
diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go
index c5097f8..cc9518e 100644
--- a/core/vm/opcodes.go
+++ b/core/vm/opcodes.go
@@ -208,6 +208,7 @@ const (
SWAP
)
+// Constants supporting native asset operations
const (
BALANCEMC = 0xcd
//EMC = 0xce
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..303d620
--- /dev/null
+++ b/examples/arc20/main.go
@@ -0,0 +1,99 @@
+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"
+ nativeAssetBalanceAddr = common.HexToAddress("0x0100000000000000000000000000000000000001")
+ nativeAssetCallAddr = common.HexToAddress("0x0100000000000000000000000000000000000002")
+ 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() {
+ // 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")
+ // Address of ARC-20 to deposit funds in
+ erc20 = common.HexToAddress("0x721F9Af7631605713133ccc502E32eA4d43CDfec")
+ // AssetID as a uint256 integer as displayed in Remix of the ARC-20
+ assetID = common.HexToHash("0x42f0d24c970eb6c567ea63a68b97d8e357a7fc8d9f73489c7761f1382b295db4").Big()
+ 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)
+}
+
+// deploy ARC-20 contract with specific assetID
+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..2f59170 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"
@@ -52,7 +53,55 @@ var CheckpointOracles = map[common.Hash]*CheckpointOracleConfig{
GoerliGenesisHash: GoerliCheckpointOracle,
}
+// Avalanche ChainIDs
var (
+ // AvalancheMainnetChainID ...
+ AvalancheMainnetChainID = big.NewInt(43114)
+ // AvalancheFujiChainID ...
+ 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{
+ 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: AvalancheMainnetApricotTimestamp,
+ }
+
+ // 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),
+ ApricotBlockTimestamp: AvalancheFujiApricotTimestamp,
+ }
+
// MainnetChainConfig is the chain parameters to run a node on the main network.
MainnetChainConfig = &ChainConfig{
ChainID: big.NewInt(1),
@@ -239,16 +288,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 +372,12 @@ 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)
+ // 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:"apricotBlockTimestamp,omitempty"`
+
// Various consensus engines
Ethash *EthashConfig `json:"ethash,omitempty"`
Clique *CliqueConfig `json:"clique,omitempty"`
@@ -358,7 +413,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, ApricotTimestamp: %v, Engine: %v}",
c.ChainID,
c.HomesteadBlock,
c.DAOForkBlock,
@@ -372,6 +427,7 @@ func (c *ChainConfig) String() string {
c.IstanbulBlock,
c.MuirGlacierBlock,
c.YoloV1Block,
+ c.ApricotBlockTimestamp,
engine,
)
}
@@ -438,6 +494,15 @@ func (c *ChainConfig) IsEWASM(num *big.Int) bool {
return isForked(c.EWASMBlock, 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
// 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 +542,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 +679,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.
@@ -631,3 +702,12 @@ func (c *ChainConfig) Rules(num *big.Int) Rules {
IsYoloV1: c.IsYoloV1(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/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..9f5607a 100644
--- a/plugin/evm/vm.go
+++ b/plugin/evm/vm.go
@@ -239,6 +239,14 @@ func (vm *VM) Initialize(
return err
}
+ // If Mainnet or Fuji ChainID is present then switch
+ // manually set the config
+ switch {
+ case g.Config.ChainID.Cmp(params.AvalancheMainnetChainID) == 0:
+ g.Config.ApricotBlockTimestamp = params.AvalancheMainnetApricotTimestamp
+ case g.Config.ChainID.Cmp(params.AvalancheFujiChainID) == 0:
+ g.Config.ApricotBlockTimestamp = params.AvalancheFujiApricotTimestamp
+ }
vm.acceptedDB = prefixdb.New([]byte(acceptedPrefix), db)
vm.chainID = g.Config.ChainID