core, internal: support various eth_call invocations post 1559

上级 7a00378e
...@@ -221,23 +221,26 @@ func (st *StateTransition) preCheck() error { ...@@ -221,23 +221,26 @@ func (st *StateTransition) preCheck() error {
} }
// Make sure that transaction gasFeeCap is greater than the baseFee (post london) // Make sure that transaction gasFeeCap is greater than the baseFee (post london)
if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
if l := st.gasFeeCap.BitLen(); l > 256 { // Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh, if !st.evm.Config.NoBaseFee || st.gasFeeCap.BitLen() > 0 || st.gasTipCap.BitLen() > 0 {
st.msg.From().Hex(), l) if l := st.gasFeeCap.BitLen(); l > 256 {
} return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", ErrFeeCapVeryHigh,
if l := st.gasTipCap.BitLen(); l > 256 { st.msg.From().Hex(), l)
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh, }
st.msg.From().Hex(), l) if l := st.gasTipCap.BitLen(); l > 256 {
} return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", ErrTipVeryHigh,
if st.gasFeeCap.Cmp(st.gasTipCap) < 0 { st.msg.From().Hex(), l)
return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap, }
st.msg.From().Hex(), st.gasTipCap, st.gasFeeCap) if st.gasFeeCap.Cmp(st.gasTipCap) < 0 {
} return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", ErrTipAboveFeeCap,
// This will panic if baseFee is nil, but basefee presence is verified st.msg.From().Hex(), st.gasTipCap, st.gasFeeCap)
// as part of header validation. }
if st.gasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 { // This will panic if baseFee is nil, but basefee presence is verified
return fmt.Errorf("%w: address %v, maxFeePerGas: %s baseFee: %s", ErrFeeCapTooLow, // as part of header validation.
st.msg.From().Hex(), st.gasFeeCap, st.evm.Context.BaseFee) if st.gasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 {
return fmt.Errorf("%w: address %v, maxFeePerGas: %s baseFee: %s", ErrFeeCapTooLow,
st.msg.From().Hex(), st.gasFeeCap, st.evm.Context.BaseFee)
}
} }
} }
return st.buyGas() return st.buyGas()
...@@ -275,7 +278,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { ...@@ -275,7 +278,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
sender := vm.AccountRef(msg.From()) sender := vm.AccountRef(msg.From())
homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber) homestead := st.evm.ChainConfig().IsHomestead(st.evm.Context.BlockNumber)
istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.BlockNumber) istanbul := st.evm.ChainConfig().IsIstanbul(st.evm.Context.BlockNumber)
eip3529 := st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber)
contractCreation := msg.To() == nil contractCreation := msg.To() == nil
// Check clauses 4-5, subtract intrinsic gas if everything is correct // Check clauses 4-5, subtract intrinsic gas if everything is correct
...@@ -308,7 +310,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { ...@@ -308,7 +310,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
} }
if !eip3529 { if !st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
// Before EIP-3529: refunds were capped to gasUsed / 2 // Before EIP-3529: refunds were capped to gasUsed / 2
st.refundGas(params.RefundQuotient) st.refundGas(params.RefundQuotient)
} else { } else {
......
...@@ -128,7 +128,7 @@ type EVM struct { ...@@ -128,7 +128,7 @@ type EVM struct {
chainRules params.Rules chainRules params.Rules
// virtual machine configuration options used to initialise the // virtual machine configuration options used to initialise the
// evm. // evm.
vmConfig Config Config Config
// global (to this context) ethereum virtual machine // global (to this context) ethereum virtual machine
// used throughout the execution of the tx. // used throughout the execution of the tx.
interpreters []Interpreter interpreters []Interpreter
...@@ -144,12 +144,12 @@ type EVM struct { ...@@ -144,12 +144,12 @@ type EVM struct {
// NewEVM returns a new EVM. The returned EVM is not thread safe and should // NewEVM returns a new EVM. The returned EVM is not thread safe and should
// only ever be used *once*. // only ever be used *once*.
func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM { func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, config Config) *EVM {
evm := &EVM{ evm := &EVM{
Context: blockCtx, Context: blockCtx,
TxContext: txCtx, TxContext: txCtx,
StateDB: statedb, StateDB: statedb,
vmConfig: vmConfig, Config: config,
chainConfig: chainConfig, chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber), chainRules: chainConfig.Rules(blockCtx.BlockNumber),
interpreters: make([]Interpreter, 0, 1), interpreters: make([]Interpreter, 0, 1),
...@@ -173,7 +173,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig ...@@ -173,7 +173,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
// vmConfig.EVMInterpreter will be used by EVM-C, it won't be checked here // vmConfig.EVMInterpreter will be used by EVM-C, it won't be checked here
// as we always want to have the built-in EVM as the failover option. // as we always want to have the built-in EVM as the failover option.
evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, vmConfig)) evm.interpreters = append(evm.interpreters, NewEVMInterpreter(evm, config))
evm.interpreter = evm.interpreters[0] evm.interpreter = evm.interpreters[0]
return evm return evm
...@@ -207,7 +207,7 @@ func (evm *EVM) Interpreter() Interpreter { ...@@ -207,7 +207,7 @@ func (evm *EVM) Interpreter() Interpreter {
// the necessary steps to create accounts and reverses the state in case of an // the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer. // execution error or failed value transfer.
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
if evm.vmConfig.NoRecursion && evm.depth > 0 { if evm.Config.NoRecursion && evm.depth > 0 {
return nil, gas, nil return nil, gas, nil
} }
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
...@@ -224,9 +224,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas ...@@ -224,9 +224,9 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
if !evm.StateDB.Exist(addr) { if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer // Calling a non existing account, don't do anything, but ping the tracer
if evm.vmConfig.Debug && evm.depth == 0 { if evm.Config.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
evm.vmConfig.Tracer.CaptureEnd(ret, 0, 0, nil) evm.Config.Tracer.CaptureEnd(ret, 0, 0, nil)
} }
return nil, gas, nil return nil, gas, nil
} }
...@@ -235,10 +235,10 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas ...@@ -235,10 +235,10 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
// Capture the tracer start/end events in debug mode // Capture the tracer start/end events in debug mode
if evm.vmConfig.Debug && evm.depth == 0 { if evm.Config.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value) evm.Config.Tracer.CaptureStart(evm, caller.Address(), addr, false, input, gas, value)
defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters defer func(startGas uint64, startTime time.Time) { // Lazy evaluation of the parameters
evm.vmConfig.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err) evm.Config.Tracer.CaptureEnd(ret, startGas-gas, time.Since(startTime), err)
}(gas, time.Now()) }(gas, time.Now())
} }
...@@ -283,7 +283,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas ...@@ -283,7 +283,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// CallCode differs from Call in the sense that it executes the given address' // CallCode differs from Call in the sense that it executes the given address'
// code with the caller as context. // code with the caller as context.
func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, gas uint64, value *big.Int) (ret []byte, leftOverGas uint64, err error) {
if evm.vmConfig.NoRecursion && evm.depth > 0 { if evm.Config.NoRecursion && evm.depth > 0 {
return nil, gas, nil return nil, gas, nil
} }
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
...@@ -326,7 +326,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, ...@@ -326,7 +326,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte,
// DelegateCall differs from CallCode in the sense that it executes the given address' // DelegateCall differs from CallCode in the sense that it executes the given address'
// code with the caller as context and the caller is set to the caller of the caller. // code with the caller as context and the caller is set to the caller of the caller.
func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
if evm.vmConfig.NoRecursion && evm.depth > 0 { if evm.Config.NoRecursion && evm.depth > 0 {
return nil, gas, nil return nil, gas, nil
} }
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
...@@ -360,7 +360,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by ...@@ -360,7 +360,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by
// Opcodes that attempt to perform such modifications will result in exceptions // Opcodes that attempt to perform such modifications will result in exceptions
// instead of performing the modifications. // instead of performing the modifications.
func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) { func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte, gas uint64) (ret []byte, leftOverGas uint64, err error) {
if evm.vmConfig.NoRecursion && evm.depth > 0 { if evm.Config.NoRecursion && evm.depth > 0 {
return nil, gas, nil return nil, gas, nil
} }
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
...@@ -453,12 +453,12 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, ...@@ -453,12 +453,12 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
contract := NewContract(caller, AccountRef(address), value, gas) contract := NewContract(caller, AccountRef(address), value, gas)
contract.SetCodeOptionalHash(&address, codeAndHash) contract.SetCodeOptionalHash(&address, codeAndHash)
if evm.vmConfig.NoRecursion && evm.depth > 0 { if evm.Config.NoRecursion && evm.depth > 0 {
return nil, address, gas, nil return nil, address, gas, nil
} }
if evm.vmConfig.Debug && evm.depth == 0 { if evm.Config.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value) evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
} }
start := time.Now() start := time.Now()
...@@ -497,8 +497,8 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, ...@@ -497,8 +497,8 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
} }
} }
if evm.vmConfig.Debug && evm.depth == 0 { if evm.Config.Debug && evm.depth == 0 {
evm.vmConfig.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err)
} }
return ret, address, contract.Gas, err return ret, address, contract.Gas, err
} }
......
...@@ -244,7 +244,7 @@ func opSha3(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt ...@@ -244,7 +244,7 @@ func opSha3(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
interpreter.hasher.Read(interpreter.hasherBuf[:]) interpreter.hasher.Read(interpreter.hasherBuf[:])
evm := interpreter.evm evm := interpreter.evm
if evm.vmConfig.EnablePreimageRecording { if evm.Config.EnablePreimageRecording {
evm.StateDB.AddPreimage(interpreter.hasherBuf, data) evm.StateDB.AddPreimage(interpreter.hasherBuf, data)
} }
......
...@@ -194,7 +194,7 @@ func TestAddMod(t *testing.T) { ...@@ -194,7 +194,7 @@ func TestAddMod(t *testing.T) {
var ( var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig) evmInterpreter = NewEVMInterpreter(env, env.Config)
pc = uint64(0) pc = uint64(0)
) )
tests := []struct { tests := []struct {
...@@ -283,7 +283,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) { ...@@ -283,7 +283,7 @@ func opBenchmark(bench *testing.B, op executionFunc, args ...string) {
var ( var (
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig) evmInterpreter = NewEVMInterpreter(env, env.Config)
) )
env.interpreter = evmInterpreter env.interpreter = evmInterpreter
...@@ -518,7 +518,7 @@ func TestOpMstore(t *testing.T) { ...@@ -518,7 +518,7 @@ func TestOpMstore(t *testing.T) {
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig) evmInterpreter = NewEVMInterpreter(env, env.Config)
) )
env.interpreter = evmInterpreter env.interpreter = evmInterpreter
...@@ -542,7 +542,7 @@ func BenchmarkOpMstore(bench *testing.B) { ...@@ -542,7 +542,7 @@ func BenchmarkOpMstore(bench *testing.B) {
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig) evmInterpreter = NewEVMInterpreter(env, env.Config)
) )
env.interpreter = evmInterpreter env.interpreter = evmInterpreter
...@@ -563,7 +563,7 @@ func BenchmarkOpSHA3(bench *testing.B) { ...@@ -563,7 +563,7 @@ func BenchmarkOpSHA3(bench *testing.B) {
env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{}) env = NewEVM(BlockContext{}, TxContext{}, nil, params.TestChainConfig, Config{})
stack = newstack() stack = newstack()
mem = NewMemory() mem = NewMemory()
evmInterpreter = NewEVMInterpreter(env, env.vmConfig) evmInterpreter = NewEVMInterpreter(env, env.Config)
) )
env.interpreter = evmInterpreter env.interpreter = evmInterpreter
mem.Resize(32) mem.Resize(32)
......
...@@ -30,6 +30,7 @@ type Config struct { ...@@ -30,6 +30,7 @@ type Config struct {
Debug bool // Enables debugging Debug bool // Enables debugging
Tracer Tracer // Opcode logger Tracer Tracer // Opcode logger
NoRecursion bool // Disables call, callcode, delegate call and create NoRecursion bool // Disables call, callcode, delegate call and create
NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls)
EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages
JumpTable [256]*operation // EVM instruction table, automatically populated if unset JumpTable [256]*operation // EVM instruction table, automatically populated if unset
......
...@@ -849,7 +849,7 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash ...@@ -849,7 +849,7 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
if err != nil { if err != nil {
return nil, err return nil, err
} }
evm, vmError, err := b.GetEVM(ctx, msg, state, header, nil) evm, vmError, err := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true})
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -158,7 +158,9 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error { ...@@ -158,7 +158,9 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
return nil return nil
} }
// ToMessage converts TransactionArgs to the Message type used by the core evm // ToMessage converts th transaction arguments to the Message type used by the
// core evm. This method is used in calls and traces that do not require a real
// live transaction.
func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (types.Message, error) { func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (types.Message, error) {
// Reject invalid combinations of pre- and post-1559 fee styles // Reject invalid combinations of pre- and post-1559 fee styles
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) { if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
...@@ -194,9 +196,11 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (t ...@@ -194,9 +196,11 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (t
} else { } else {
// A basefee is provided, necessitating 1559-type execution // A basefee is provided, necessitating 1559-type execution
if args.GasPrice != nil { if args.GasPrice != nil {
// User specified the legacy gas field, convert to 1559 gas typing
gasPrice = args.GasPrice.ToInt() gasPrice = args.GasPrice.ToInt()
gasFeeCap, gasTipCap = gasPrice, gasPrice gasFeeCap, gasTipCap = gasPrice, gasPrice
} else { } else {
// User specified 1559 gas feilds (or none), use those
gasFeeCap = new(big.Int) gasFeeCap = new(big.Int)
if args.MaxFeePerGas != nil { if args.MaxFeePerGas != nil {
gasFeeCap = args.MaxFeePerGas.ToInt() gasFeeCap = args.MaxFeePerGas.ToInt()
...@@ -205,7 +209,11 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (t ...@@ -205,7 +209,11 @@ func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (t
if args.MaxPriorityFeePerGas != nil { if args.MaxPriorityFeePerGas != nil {
gasTipCap = args.MaxPriorityFeePerGas.ToInt() gasTipCap = args.MaxPriorityFeePerGas.ToInt()
} }
gasPrice = math.BigMin(new(big.Int).Add(gasTipCap, baseFee), gasFeeCap) // Backfill the legacy gasPrice for EVM execution, unless we're all zeroes
gasPrice = new(big.Int)
if gasFeeCap.BitLen() > 0 || gasTipCap.BitLen() > 0 {
gasPrice = math.BigMin(new(big.Int).Add(gasTipCap, baseFee), gasFeeCap)
}
} }
} }
value := new(big.Int) value := new(big.Int)
......
此差异已折叠。
...@@ -3734,7 +3734,7 @@ var inputCallFormatter = function (options){ ...@@ -3734,7 +3734,7 @@ var inputCallFormatter = function (options){
options.to = inputAddressFormatter(options.to); options.to = inputAddressFormatter(options.to);
} }
['gasPrice', 'gas', 'value', 'nonce'].filter(function (key) { ['maxFeePerGas', 'maxPriorityFeePerGas', 'gasPrice', 'gas', 'value', 'nonce'].filter(function (key) {
return options[key] !== undefined; return options[key] !== undefined;
}).forEach(function(key){ }).forEach(function(key){
options[key] = utils.fromDecimal(options[key]); options[key] = utils.fromDecimal(options[key]);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册