diff --git a/core/vm/contracts.go b/core/vm/contracts.go index b45f14724b..a976688c0e 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -26,64 +26,45 @@ import ( "github.com/ethereum/go-ethereum/params" ) -// PrecompiledAccount represents a native ethereum contract -type PrecompiledAccount struct { - Gas func(l int) *big.Int - fn func(in []byte) []byte -} - -// Call calls the native function -func (self PrecompiledAccount) Call(in []byte) []byte { - return self.fn(in) +// Precompiled contract is the basic interface for native Go contracts. The implementation +// requires a deterministic gas count based on the input size of the Run method of the +// contract. +type PrecompiledContract interface { + RequiredGas(inputSize int) *big.Int // RequiredPrice calculates the contract gas use + Run(input []byte) []byte // Run runs the precompiled contract } // Precompiled contains the default set of ethereum contracts -var Precompiled = PrecompiledContracts() +var PrecompiledContracts = map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256{}, + common.BytesToAddress([]byte{3}): &ripemd160{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, +} -// PrecompiledContracts returns the default set of precompiled ethereum -// contracts defined by the ethereum yellow paper. -func PrecompiledContracts() map[string]*PrecompiledAccount { - return map[string]*PrecompiledAccount{ - // ECRECOVER - string(common.LeftPadBytes([]byte{1}, 20)): &PrecompiledAccount{func(l int) *big.Int { - return params.EcrecoverGas - }, ecrecoverFunc}, +// RunPrecompile runs and evaluate the output of a precompiled contract defined in contracts.go +func RunPrecompiledContract(p PrecompiledContract, input []byte, contract *Contract) (ret []byte, err error) { + gas := p.RequiredGas(len(input)) + if contract.UseGas(gas) { + ret = p.Run(input) - // SHA256 - string(common.LeftPadBytes([]byte{2}, 20)): &PrecompiledAccount{func(l int) *big.Int { - n := big.NewInt(int64(l+31) / 32) - n.Mul(n, params.Sha256WordGas) - return n.Add(n, params.Sha256Gas) - }, sha256Func}, - - // RIPEMD160 - string(common.LeftPadBytes([]byte{3}, 20)): &PrecompiledAccount{func(l int) *big.Int { - n := big.NewInt(int64(l+31) / 32) - n.Mul(n, params.Ripemd160WordGas) - return n.Add(n, params.Ripemd160Gas) - }, ripemd160Func}, - - string(common.LeftPadBytes([]byte{4}, 20)): &PrecompiledAccount{func(l int) *big.Int { - n := big.NewInt(int64(l+31) / 32) - n.Mul(n, params.IdentityWordGas) - - return n.Add(n, params.IdentityGas) - }, memCpy}, + return ret, nil + } else { + return nil, OutOfGasError } } -func sha256Func(in []byte) []byte { - return crypto.Sha256(in) +// ECRECOVER implemented as a native contract +type ecrecover struct{} + +func (c *ecrecover) RequiredGas(inputSize int) *big.Int { + return params.EcrecoverGas } -func ripemd160Func(in []byte) []byte { - return common.LeftPadBytes(crypto.Ripemd160(in), 32) -} +func (c *ecrecover) Run(in []byte) []byte { + const ecRecoverInputLength = 128 -const ecRecoverInputLength = 128 - -func ecrecoverFunc(in []byte) []byte { - in = common.RightPadBytes(in, 128) + in = common.RightPadBytes(in, ecRecoverInputLength) // "in" is (hash, v, r, s), each 32 bytes // but for ecrecover we want (r, s, v) @@ -114,6 +95,39 @@ func ecrecoverFunc(in []byte) []byte { return common.LeftPadBytes(crypto.Keccak256(pubKey[1:])[12:], 32) } -func memCpy(in []byte) []byte { +// SHA256 implemented as a native contract +type sha256 struct{} + +func (c *sha256) RequiredGas(inputSize int) *big.Int { + n := big.NewInt(int64(inputSize+31) / 32) + n.Mul(n, params.Sha256WordGas) + return n.Add(n, params.Sha256Gas) +} +func (c *sha256) Run(in []byte) []byte { + return crypto.Sha256(in) +} + +// RIPMED160 implemented as a native contract +type ripemd160 struct{} + +func (c *ripemd160) RequiredGas(inputSize int) *big.Int { + n := big.NewInt(int64(inputSize+31) / 32) + n.Mul(n, params.Ripemd160WordGas) + return n.Add(n, params.Ripemd160Gas) +} +func (c *ripemd160) Run(in []byte) []byte { + return common.LeftPadBytes(crypto.Ripemd160(in), 32) +} + +// data copy implemented as a native contract +type dataCopy struct{} + +func (c *dataCopy) RequiredGas(inputSize int) *big.Int { + n := big.NewInt(int64(inputSize+31) / 32) + n.Mul(n, params.IdentityWordGas) + + return n.Add(n, params.IdentityGas) +} +func (c *dataCopy) Run(in []byte) []byte { return in } diff --git a/core/vm/environment.go b/core/vm/environment.go index 50a09d4444..ca39dec589 100644 --- a/core/vm/environment.go +++ b/core/vm/environment.go @@ -113,7 +113,7 @@ func (env *Environment) Call(caller ContractRef, addr common.Address, input []by snapshotPreTransfer = env.StateDB.Snapshot() ) if !env.StateDB.Exist(addr) { - if Precompiled[addr.Str()] == nil && env.ChainConfig().IsEIP158(env.BlockNumber) && value.BitLen() == 0 { + if PrecompiledContracts[addr] == nil && env.ChainConfig().IsEIP158(env.BlockNumber) && value.BitLen() == 0 { caller.ReturnGas(gas) return nil, nil } diff --git a/core/vm/vm.go b/core/vm/vm.go index cb9fabbdf5..4df8457cd5 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -90,8 +90,8 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) { defer func() { evm.env.Depth-- }() if contract.CodeAddr != nil { - if p := Precompiled[contract.CodeAddr.Str()]; p != nil { - return evm.RunPrecompiled(p, input, contract) + if p := PrecompiledContracts[*contract.CodeAddr]; p != nil { + return RunPrecompiledContract(p, input, contract) } } @@ -192,15 +192,3 @@ func (evm *EVM) Run(contract *Contract, input []byte) (ret []byte, err error) { } return nil, nil } - -// RunPrecompile runs and evaluate the output of a precompiled contract defined in contracts.go -func (evm *EVM) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Contract) (ret []byte, err error) { - gas := p.Gas(len(input)) - if contract.UseGas(gas) { - ret = p.Call(input) - - return ret, nil - } else { - return nil, OutOfGasError - } -} diff --git a/tests/state_test_util.go b/tests/state_test_util.go index df0adab4cd..f47f5f7a1b 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -205,8 +205,6 @@ func runStateTest(chainConfig *params.ChainConfig, test VmTest) error { func RunState(chainConfig *params.ChainConfig, statedb *state.StateDB, env, tx map[string]string) ([]byte, vm.Logs, *big.Int, error) { environment, msg := NewEVMEnvironment(false, chainConfig, statedb, env, tx) - // Set pre compiled contracts - vm.Precompiled = vm.PrecompiledContracts() gaspool := new(core.GasPool).AddGas(common.Big(env["currentGasLimit"])) root, _ := statedb.Commit(false) diff --git a/tests/vm_test_util.go b/tests/vm_test_util.go index 9b2e10cff0..dc9f1d62ce 100644 --- a/tests/vm_test_util.go +++ b/tests/vm_test_util.go @@ -225,7 +225,7 @@ func RunVm(statedb *state.StateDB, env, exec map[string]string) ([]byte, vm.Logs value = common.Big(exec["value"]) ) caller := statedb.GetOrNewStateObject(from) - vm.Precompiled = make(map[string]*vm.PrecompiledAccount) + vm.PrecompiledContracts = make(map[common.Address]vm.PrecompiledContract) environment, _ := NewEVMEnvironment(true, chainConfig, statedb, env, exec) ret, err := environment.Call(caller, to, data, gas, value)