aboutsummaryrefslogtreecommitdiff
path: root/core/vm/contracts_stateful.go
diff options
context:
space:
mode:
Diffstat (limited to 'core/vm/contracts_stateful.go')
-rw-r--r--core/vm/contracts_stateful.go165
1 files changed, 165 insertions, 0 deletions
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)
+}