diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 574bb9bef6..d850c3854c 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -44,81 +44,81 @@ 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]PrecompiledContractWithCtx{ + common.BytesToAddress([]byte{1}): precompileWrapper{&ecrecover{}}, + common.BytesToAddress([]byte{2}): precompileWrapper{&sha256hash{}}, + common.BytesToAddress([]byte{3}): precompileWrapper{&ripemd160hash{}}, + common.BytesToAddress([]byte{4}): precompileWrapper{&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{eip2565: false}, - common.BytesToAddress([]byte{6}): &bn256AddByzantium{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulByzantium{}, - common.BytesToAddress([]byte{8}): &bn256PairingByzantium{}, +var PrecompiledContractsByzantium = map[common.Address]PrecompiledContractWithCtx{ + common.BytesToAddress([]byte{1}): precompileWrapper{&ecrecover{}}, + common.BytesToAddress([]byte{2}): precompileWrapper{&sha256hash{}}, + common.BytesToAddress([]byte{3}): precompileWrapper{&ripemd160hash{}}, + common.BytesToAddress([]byte{4}): precompileWrapper{&dataCopy{}}, + common.BytesToAddress([]byte{5}): precompileWrapper{&bigModExp{eip2565: false}}, + common.BytesToAddress([]byte{6}): precompileWrapper{&bn256AddByzantium{}}, + common.BytesToAddress([]byte{7}): precompileWrapper{&bn256ScalarMulByzantium{}}, + common.BytesToAddress([]byte{8}): precompileWrapper{&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{eip2565: false}, - 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]PrecompiledContractWithCtx{ + common.BytesToAddress([]byte{1}): precompileWrapper{&ecrecover{}}, + common.BytesToAddress([]byte{2}): precompileWrapper{&sha256hash{}}, + common.BytesToAddress([]byte{3}): precompileWrapper{&ripemd160hash{}}, + common.BytesToAddress([]byte{4}): precompileWrapper{&dataCopy{}}, + common.BytesToAddress([]byte{5}): precompileWrapper{&bigModExp{eip2565: false}}, + common.BytesToAddress([]byte{6}): precompileWrapper{&bn256AddIstanbul{}}, + common.BytesToAddress([]byte{7}): precompileWrapper{&bn256ScalarMulIstanbul{}}, + common.BytesToAddress([]byte{8}): precompileWrapper{&bn256PairingIstanbul{}}, + common.BytesToAddress([]byte{9}): precompileWrapper{&blake2F{}}, } // PrecompiledContractsBerlin contains the default set of pre-compiled Ethereum // contracts used in the Berlin release. -var PrecompiledContractsBerlin = 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{eip2565: true}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, +var PrecompiledContractsBerlin = map[common.Address]PrecompiledContractWithCtx{ + common.BytesToAddress([]byte{1}): precompileWrapper{&ecrecover{}}, + common.BytesToAddress([]byte{2}): precompileWrapper{&sha256hash{}}, + common.BytesToAddress([]byte{3}): precompileWrapper{&ripemd160hash{}}, + common.BytesToAddress([]byte{4}): precompileWrapper{&dataCopy{}}, + common.BytesToAddress([]byte{5}): precompileWrapper{&bigModExp{eip2565: true}}, + common.BytesToAddress([]byte{6}): precompileWrapper{&bn256AddIstanbul{}}, + common.BytesToAddress([]byte{7}): precompileWrapper{&bn256ScalarMulIstanbul{}}, + common.BytesToAddress([]byte{8}): precompileWrapper{&bn256PairingIstanbul{}}, + common.BytesToAddress([]byte{9}): precompileWrapper{&blake2F{}}, } // PrecompiledContractsCancun contains the default set of pre-compiled Ethereum // contracts used in the Cancun release. -var PrecompiledContractsCancun = 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{eip2565: true}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, - common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{}, +var PrecompiledContractsCancun = map[common.Address]PrecompiledContractWithCtx{ + common.BytesToAddress([]byte{1}): precompileWrapper{&ecrecover{}}, + common.BytesToAddress([]byte{2}): precompileWrapper{&sha256hash{}}, + common.BytesToAddress([]byte{3}): precompileWrapper{&ripemd160hash{}}, + common.BytesToAddress([]byte{4}): precompileWrapper{&dataCopy{}}, + common.BytesToAddress([]byte{5}): precompileWrapper{&bigModExp{eip2565: true}}, + common.BytesToAddress([]byte{6}): precompileWrapper{&bn256AddIstanbul{}}, + common.BytesToAddress([]byte{7}): precompileWrapper{&bn256ScalarMulIstanbul{}}, + common.BytesToAddress([]byte{8}): precompileWrapper{&bn256PairingIstanbul{}}, + common.BytesToAddress([]byte{9}): precompileWrapper{&blake2F{}}, + common.BytesToAddress([]byte{0x0a}): precompileWrapper{&kzgPointEvaluation{}}, } // PrecompiledContractsBLS contains the set of pre-compiled Ethereum // contracts specified in EIP-2537. These are exported for testing purposes. -var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ - 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 PrecompiledContractsBLS = map[common.Address]PrecompiledContractWithCtx{ + common.BytesToAddress([]byte{10}): precompileWrapper{&bls12381G1Add{}}, + common.BytesToAddress([]byte{11}): precompileWrapper{&bls12381G1Mul{}}, + common.BytesToAddress([]byte{12}): precompileWrapper{&bls12381G1MultiExp{}}, + common.BytesToAddress([]byte{13}): precompileWrapper{&bls12381G2Add{}}, + common.BytesToAddress([]byte{14}): precompileWrapper{&bls12381G2Mul{}}, + common.BytesToAddress([]byte{15}): precompileWrapper{&bls12381G2MultiExp{}}, + common.BytesToAddress([]byte{16}): precompileWrapper{&bls12381Pairing{}}, + common.BytesToAddress([]byte{17}): precompileWrapper{&bls12381MapG1{}}, + common.BytesToAddress([]byte{18}): precompileWrapper{&bls12381MapG2{}}, } var ( @@ -168,14 +168,14 @@ func ActivePrecompiles(rules params.Rules) []common.Address { // - the returned bytes, // - the _remaining_ gas, // - any error that occurred -func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { +func RunPrecompiledContract(p PrecompiledContractWithCtx, input []byte, suppliedGas uint64, ctx *precompileContext) (ret []byte, remainingGas uint64, err error) { gasCost := p.RequiredGas(input) if suppliedGas < gasCost { return nil, 0, ErrOutOfGas } - suppliedGas -= gasCost - output, err := p.Run(input) - return output, suppliedGas, err + gasLeft := suppliedGas - gasCost + output, err := p.Run(input, ctx) + return output, gasLeft, err } // ECRECOVER implemented as a native contract. diff --git a/core/vm/contracts_fuzz_test.go b/core/vm/contracts_fuzz_test.go index 87c1fff7cc..7bc30af544 100644 --- a/core/vm/contracts_fuzz_test.go +++ b/core/vm/contracts_fuzz_test.go @@ -36,7 +36,8 @@ func FuzzPrecompiledContracts(f *testing.F) { return } inWant := string(input) - RunPrecompiledContract(p, input, gas) + + RunPrecompiledContract(p, input, gas, NewContext(common.HexToAddress("1337"), mockEVM)) if inHave := string(input); inWant != inHave { t.Errorf("Precompiled %v modified input data", a) } diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index f40e2c8f9e..b24957c6b0 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -45,28 +45,28 @@ type precompiledFailureTest struct { // allPrecompiles does not map to the actual set of precompiles, as it also contains // repriced versions of precompiles at certain slots -var allPrecompiles = 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{eip2565: false}, - common.BytesToAddress([]byte{0xf5}): &bigModExp{eip2565: true}, - common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, - common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, - common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, - common.BytesToAddress([]byte{9}): &blake2F{}, - common.BytesToAddress([]byte{0x0a}): &kzgPointEvaluation{}, +var allPrecompiles = map[common.Address]PrecompiledContractWithCtx{ + common.BytesToAddress([]byte{1}): precompileWrapper{&ecrecover{}}, + common.BytesToAddress([]byte{2}): precompileWrapper{&sha256hash{}}, + common.BytesToAddress([]byte{3}): precompileWrapper{&ripemd160hash{}}, + common.BytesToAddress([]byte{4}): precompileWrapper{&dataCopy{}}, + common.BytesToAddress([]byte{5}): precompileWrapper{&bigModExp{eip2565: false}}, + common.BytesToAddress([]byte{0xf5}): precompileWrapper{&bigModExp{eip2565: true}}, + common.BytesToAddress([]byte{6}): precompileWrapper{&bn256AddIstanbul{}}, + common.BytesToAddress([]byte{7}): precompileWrapper{&bn256ScalarMulIstanbul{}}, + common.BytesToAddress([]byte{8}): precompileWrapper{&bn256PairingIstanbul{}}, + common.BytesToAddress([]byte{9}): precompileWrapper{&blake2F{}}, + common.BytesToAddress([]byte{0x0a}): precompileWrapper{&kzgPointEvaluation{}}, - common.BytesToAddress([]byte{0x0f, 0x0a}): &bls12381G1Add{}, - common.BytesToAddress([]byte{0x0f, 0x0b}): &bls12381G1Mul{}, - common.BytesToAddress([]byte{0x0f, 0x0c}): &bls12381G1MultiExp{}, - common.BytesToAddress([]byte{0x0f, 0x0d}): &bls12381G2Add{}, - common.BytesToAddress([]byte{0x0f, 0x0e}): &bls12381G2Mul{}, - common.BytesToAddress([]byte{0x0f, 0x0f}): &bls12381G2MultiExp{}, - common.BytesToAddress([]byte{0x0f, 0x10}): &bls12381Pairing{}, - common.BytesToAddress([]byte{0x0f, 0x11}): &bls12381MapG1{}, - common.BytesToAddress([]byte{0x0f, 0x12}): &bls12381MapG2{}, + common.BytesToAddress([]byte{0x0f, 0x0a}): precompileWrapper{&bls12381G1Add{}}, + common.BytesToAddress([]byte{0x0f, 0x0b}): precompileWrapper{&bls12381G1Mul{}}, + common.BytesToAddress([]byte{0x0f, 0x0c}): precompileWrapper{&bls12381G1MultiExp{}}, + common.BytesToAddress([]byte{0x0f, 0x0d}): precompileWrapper{&bls12381G2Add{}}, + common.BytesToAddress([]byte{0x0f, 0x0e}): precompileWrapper{&bls12381G2Mul{}}, + common.BytesToAddress([]byte{0x0f, 0x0f}): precompileWrapper{&bls12381G2MultiExp{}}, + common.BytesToAddress([]byte{0x0f, 0x10}): precompileWrapper{&bls12381Pairing{}}, + common.BytesToAddress([]byte{0x0f, 0x11}): precompileWrapper{&bls12381MapG1{}}, + common.BytesToAddress([]byte{0x0f, 0x12}): precompileWrapper{&bls12381MapG2{}}, } // EIP-152 test vectors @@ -98,7 +98,8 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - if res, _, err := RunPrecompiledContract(p, in, gas); err != nil { + res, _, err := RunPrecompiledContract(p, in, gas, NewContext(common.HexToAddress("1337"), mockEVM)) + if err != nil { t.Error(err) } else if common.Bytes2Hex(res) != test.Expected { t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) @@ -120,7 +121,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { gas := p.RequiredGas(in) - 1 t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas) + _, _, err := RunPrecompiledContract(p, in, gas, NewContext(common.HexToAddress("1337"), mockEVM)) if err.Error() != "out of gas" { t.Errorf("Expected error [out of gas], got [%v]", err) } @@ -137,7 +138,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(test.Name, func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas) + _, _, err := RunPrecompiledContract(p, in, gas, NewContext(common.HexToAddress("1337"), mockEVM)) if err.Error() != test.ExpectedError { t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) } @@ -169,7 +170,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { copy(data, in) - res, _, err = RunPrecompiledContract(p, data, reqGas) + res, _, err = RunPrecompiledContract(p, data, reqGas, NewContext(common.HexToAddress("1337"), mockEVM)) } bench.StopTimer() elapsed := uint64(time.Since(start)) diff --git a/core/vm/contracts_with_ctx.go b/core/vm/contracts_with_ctx.go new file mode 100644 index 0000000000..01d17ce01b --- /dev/null +++ b/core/vm/contracts_with_ctx.go @@ -0,0 +1,104 @@ +package vm + +import ( + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/params" +) + +// Wrapper type which allows PrecompiledContract to be used as PrecompiledContractWithCtx +type precompileWrapper struct { + PrecompiledContract +} + +func (pw precompileWrapper) Run(input []byte, ctx *precompileContext) ([]byte, error) { + return pw.PrecompiledContract.Run(input) +} + +// Interface for precompiled contract with ctx object allowing for writes to state. +type PrecompiledContractWithCtx interface { + RequiredGas(input []byte) uint64 + Run(input []byte, ctx *precompileContext) ([]byte, error) +} + +type precompileContext struct { + *BlockContext + *params.Rules + + caller common.Address + evm *EVM +} + +func NewContext(caller common.Address, evm *EVM) *precompileContext { + return &precompileContext{ + BlockContext: &evm.Context, + Rules: &evm.chainRules, + caller: caller, + evm: evm, + } +} + +var vmBlockCtx = BlockContext{ + CanTransfer: func(db StateDB, addr common.Address, amount *big.Int) bool { + return db.GetBalance(addr).Cmp(amount) >= 0 + }, + Transfer: func(StateDB, common.Address, common.Address, *big.Int) { + panic("transfer: not implemented") + }, + GetHash: func(u uint64) common.Hash { + panic("getHash: not implemented") + }, + Coinbase: common.Address{}, + BlockNumber: new(big.Int).SetUint64(10), + Time: uint64(time.Now().Unix()), +} + +var vmTxCtx = TxContext{ + GasPrice: common.Big1, + Origin: common.HexToAddress("a11ce"), +} + +// Create a global mock EVM for use in the following tests. +var mockEVM = &EVM{ + Context: vmBlockCtx, + TxContext: vmTxCtx, +} + +// Native transfer precompile to make bridging to native token possible. +type transfer struct{} + +func (c *transfer) RequiredGas(input []byte) uint64 { + // TODO: determine appropriate gas cost + return 100 +} + +func Run(input []byte, ctx *precompileContext) ([]byte, error) { + + // TODO: filter out non-allowed callers + + // From + _ = common.BytesToAddress(input[0:32]) + + to := common.BytesToAddress(input[32:64]) + + var parsed bool + value, parsed := math.ParseBig256(hexutil.Encode(input[64:96])) + if !parsed { + return nil, fmt.Errorf("Error parsing transfer: unable to parse value from " + hexutil.Encode(input[64:96])) + } + + // Mint case: Create native token out of thin air + ctx.evm.StateDB.AddBalance(to, value) + + // if !ctx.CanTransfer(ctx.evm.StateDB, from, value) { + // return nil, ErrInsufficientBalance + // } + // ctx.Transfer(ctx.evm, from, to, value) + + return input, nil +} diff --git a/core/vm/evm.go b/core/vm/evm.go index 088b18aaa4..6a6660aa29 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -37,8 +37,8 @@ type ( 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) (PrecompiledContractWithCtx, bool) { + var precompiles map[common.Address]PrecompiledContractWithCtx switch { case evm.chainRules.IsCancun: precompiles = PrecompiledContractsCancun @@ -224,7 +224,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } if isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + ret, gas, err = RunPrecompiledContract(p, input, gas, NewContext(caller.Address(), evm)) } else { // 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. @@ -287,7 +287,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 = RunPrecompiledContract(p, input, gas, NewContext(caller.Address(), evm)) } else { addrCopy := addr // Initialise a new contract and set the code that is to be used by the EVM. @@ -332,7 +332,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 = RunPrecompiledContract(p, input, gas, NewContext(caller.Address(), evm)) } else { addrCopy := addr // Initialise a new contract and make initialise the delegate values @@ -381,7 +381,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte } if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + ret, gas, err = RunPrecompiledContract(p, input, gas, NewContext(caller.Address(), evm)) } 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'