diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index f3a9808251..bfab34d45a 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -25,12 +25,19 @@ import ( "github.com/ethereum/go-ethereum/params" ) -// memoryGasCost calculates the quadratic gas for memory expansion. It does so -// only for the memory region that is expanded, not the total memory. -func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { - if newMemSize == 0 { +func memoryGasCost(pc uint64, scope *ScopeContext, mem *Memory, newMemSize uint64) (uint64, error) { + return evmmaxMemoryGasCost(pc, scope, mem, newMemSize, scope.modExtState.AllocSize()) +} + +// evmmaxMemory calculates the quadratic gas for memory expansion. It does so +// only for the memory region that is expanded, not the total memory. It uses +// the modified EVMMAX memory expansion rule: consider the size of memory to +// include EVM memory and the memory allocated by all active field contexts. +func evmmaxMemoryGasCost(pc uint64, scope *ScopeContext, mem *Memory, newMemSize uint64, newEVMMAXMemSize uint64) (uint64, error) { + if newMemSize == 0 && newEVMMAXMemSize == 0 { return 0, nil } + // The maximum that will fit in a uint64 is max_word_count - 1. Anything above // that will result in an overflow. Additionally, a newMemSize which results in // a newMemSizeWords larger than 0xFFFFFFFF will cause the square operation to @@ -40,11 +47,24 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { return 0, ErrGasUintOverflow } newMemSizeWords := toWordSize(newMemSize) - newMemSize = newMemSizeWords * 32 + newMemSizePadded := newMemSizeWords * 32 - if newMemSize > uint64(mem.Len()) { - square := newMemSizeWords * newMemSizeWords - linCoef := newMemSizeWords * params.MemoryGas + curEVMMAXMemSize := scope.modExtState.AllocSize() + curEVMMAXMemSizePadded := toWordSize(curEVMMAXMemSize) * 32 + newEVMMAXMemSizePadded := toWordSize(newEVMMAXMemSize) * 32 + + // if newEVMMAXMemSize + newEVMMemSize > curEVMMAXMemSize + curEVMMemSize + if newMemSizePadded > uint64(mem.Len()) || newEVMMAXMemSizePadded > curEVMMAXMemSizePadded { + // if this is called by the invocation of SETUPX, the new evm memory is + // 0, but we still need it to compute the fee + if newMemSize <= uint64(mem.Len()) { + newMemSize = uint64(mem.Len()) + } + // new effective mem size for the purpose of gas charging is the sum of + // evmmax memory and evm memory padded to a multiple of 32 bytes. + newEffectiveMemSizeWords := toWordSize(newEVMMAXMemSize + newMemSize) + square := newEffectiveMemSizeWords * newEffectiveMemSizeWords + linCoef := newEffectiveMemSizeWords * params.MemoryGas quadCoef := square / params.QuadCoeffDiv newTotalFee := linCoef + quadCoef @@ -65,9 +85,9 @@ func memoryGasCost(mem *Memory, newMemSize uint64) (uint64, error) { // EXTCODECOPY (stack position 3) // RETURNDATACOPY (stack position 2) func memoryCopierGas(stackpos int) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return func(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // Gas for expanding the memory - gas, err := memoryGasCost(mem, memorySize) + gas, err := memoryGasCost(pc, scope, mem, memorySize) if err != nil { return 0, err } @@ -96,10 +116,10 @@ var ( gasReturnDataCopy = memoryCopierGas(2) ) -func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasSStore(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) + current = evm.StateDB.GetState(scope.Contract.Address(), x.Bytes32()) ) // The legacy gas metering only takes into consideration the current state // Legacy rules should be applied if we are in Petersburg (removal of EIP-1283) @@ -139,7 +159,7 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi if current == value { // noop (1) return params.NetSstoreNoopGas, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + original := evm.StateDB.GetCommittedState(scope.Contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.NetSstoreInitGas, nil @@ -181,22 +201,22 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi // (2.2.2.) If original value equals new value (this storage slot is reset): // (2.2.2.1.) If original value is 0, add SSTORE_SET_GAS - SLOAD_GAS to refund counter. // (2.2.2.2.) Otherwise, add SSTORE_RESET_GAS - SLOAD_GAS gas to refund counter. -func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasSStoreEIP2200(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // If we fail the minimum gas availability invariant, fail (0) - if contract.Gas <= params.SstoreSentryGasEIP2200 { + if scope.Contract.Gas <= params.SstoreSentryGasEIP2200 { return 0, errors.New("not enough gas for reentrancy sentry") } // Gas sentry honoured, do the actual gas calculation based on the stored value var ( y, x = stack.Back(1), stack.Back(0) - current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) + current = evm.StateDB.GetState(scope.Contract.Address(), x.Bytes32()) ) value := common.Hash(y.Bytes32()) if current == value { // noop (1) return params.SloadGasEIP2200, nil } - original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + original := evm.StateDB.GetCommittedState(scope.Contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return params.SstoreSetGasEIP2200, nil @@ -224,13 +244,13 @@ func gasSStoreEIP2200(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m } func makeGasLog(n uint64) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return func(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { requestedSize, overflow := stack.Back(1).Uint64WithOverflow() if overflow { return 0, ErrGasUintOverflow } - gas, err := memoryGasCost(mem, memorySize) + gas, err := memoryGasCost(pc, scope, mem, memorySize) if err != nil { return 0, err } @@ -253,8 +273,8 @@ func makeGasLog(n uint64) gasFunc { } } -func gasKeccak256(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) +func gasKeccak256(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(pc, scope, mem, memorySize) if err != nil { return 0, err } @@ -274,8 +294,8 @@ func gasKeccak256(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor // pureMemoryGascost is used by several operations, which aside from their // static cost have a dynamic cost which is solely based on the memory // expansion -func pureMemoryGascost(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - return memoryGasCost(mem, memorySize) +func pureMemoryGascost(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return memoryGasCost(pc, scope, mem, memorySize) } var ( @@ -287,8 +307,8 @@ var ( gasCreate = pureMemoryGascost ) -func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) +func gasCreate2(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(pc, scope, mem, memorySize) if err != nil { return 0, err } @@ -305,8 +325,8 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS return gas, nil } -func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) +func gasCreateEip3860(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(pc, scope, mem, memorySize) if err != nil { return 0, err } @@ -324,8 +344,8 @@ func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m } return gas, nil } -func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) +func gasCreate2Eip3860(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(pc, scope, mem, memorySize) if err != nil { return 0, err } @@ -344,7 +364,7 @@ func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, return gas, nil } -func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExpFrontier(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8) var ( @@ -357,7 +377,7 @@ func gasExpFrontier(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem return gas, nil } -func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExpEIP158(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { expByteLen := uint64((stack.data[stack.len()-2].BitLen() + 7) / 8) var ( @@ -370,7 +390,7 @@ func gasExpEIP158(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memor return gas, nil } -func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasCall(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( gas uint64 transfersValue = !stack.Back(2).IsZero() @@ -386,7 +406,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize if transfersValue && !evm.chainRules.IsEIP4762 { gas += params.CallValueTransferGas } - memoryGas, err := memoryGasCost(mem, memorySize) + memoryGas, err := memoryGasCost(pc, scope, mem, memorySize) if err != nil { return 0, err } @@ -396,13 +416,13 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize } if evm.chainRules.IsEIP4762 { if transfersValue { - gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address)) + gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(scope.Contract.Address(), address)) if overflow { return 0, ErrGasUintOverflow } } } - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, scope.Contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } @@ -413,8 +433,8 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize return gas, nil } -func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - memoryGas, err := memoryGasCost(mem, memorySize) +func gasCallCode(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + memoryGas, err := memoryGasCost(pc, scope, mem, memorySize) if err != nil { return 0, err } @@ -432,13 +452,13 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory address := common.Address(stack.Back(1).Bytes20()) transfersValue := !stack.Back(2).IsZero() if transfersValue { - gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address)) + gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(scope.Contract.Address(), address)) if overflow { return 0, ErrGasUintOverflow } } } - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, scope.Contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } @@ -448,12 +468,12 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory return gas, nil } -func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) +func gasDelegateCall(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(pc, scope, mem, memorySize) if err != nil { return 0, err } - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, scope.Contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } @@ -464,12 +484,12 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me return gas, nil } -func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := memoryGasCost(mem, memorySize) +func gasStaticCall(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := memoryGasCost(pc, scope, mem, memorySize) if err != nil { return 0, err } - evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0)) + evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, scope.Contract.Gas, gas, stack.Back(0)) if err != nil { return 0, err } @@ -480,7 +500,7 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo return gas, nil } -func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasSelfdestruct(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var gas uint64 // EIP150 homestead gas reprice fork: if evm.chainRules.IsEIP150 { @@ -489,7 +509,7 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me if evm.chainRules.IsEIP158 { // if empty and transfers value - if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { + if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(scope.Contract.Address()).Sign() != 0 { gas += params.CreateBySelfdestructGas } } else if !evm.StateDB.Exist(address) { @@ -497,25 +517,149 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me } } - if !evm.StateDB.HasSelfDestructed(contract.Address()) { + if !evm.StateDB.HasSelfDestructed(scope.Contract.Address()) { evm.StateDB.AddRefund(params.SelfdestructRefundGas) } return gas, nil } -func gasExtCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExtCall(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { panic("not implemented") } -func gasExtDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExtDelegateCall(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { panic("not implemented") } -func gasExtStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExtStaticCall(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { panic("not implemented") } // gasEOFCreate returns the gas-cost for EOF-Create. Hashing charge needs to be // deducted in the opcode itself, since it depends on the immediate -func gasEOFCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasEOFCreate(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { panic("not implemented") } + +func gasSetupx(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + if !stack.Back(0).IsUint64() || !stack.Back(2).IsUint64() || !stack.Back(3).IsUint64() { + return 0, errors.New("one or more parameters overflows 64 bits") + } + + modId := uint(stack.Back(0).Uint64()) + if scope.modExtState.alloced[modId] != nil { + return 0, nil + } + + modSize := stack.Back(2).Uint64() + if modSize > 96 { + // TODO: ensure returning error here consumes all evm call context gas + return 0, fmt.Errorf("modulus cannot exceed 768 bits in width") + } + + feAllocCount := stack.Back(3).Uint64() + if feAllocCount > 256 { + return 0, fmt.Errorf("cannot allocate more than 256 field elements per modulus id") + } + paddedModSize := (modSize + 7) / 8 + precompCost := uint64(params.SetupxPrecompCost[paddedModSize]) + + // the size in bytes of the field element heap that this call to SETUPX is + // allocating. + allocSize := paddedModSize * feAllocCount + + // if the new evmmax memory alloc would exceed the maximum allowed, return an error + if scope.modExtState.AllocSize()+allocSize > uint64(params.MaxFEAllocSize) { + return 0, fmt.Errorf("call context evmmax allocation threshold exceeded") + } + + // overflow error unchecked because we do not expand evm memory here, + // and the maximum call-context allocatable memory + reasonable evm memory limit + // will not overflow a uint64. + memCost, _ := evmmaxMemoryGasCost(pc, scope, mem, memorySize, allocSize) + return precompCost + memCost, nil +} + +func gasStorex(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + if scope.modExtState.active == nil { + return 0, errors.New("no active mod state") + } + dst := stack.Back(0) + src := stack.Back(1) + count := stack.Back(2) + + if !src.IsUint64() || int(src.Uint64()) >= mem.Len() { + return 0, errors.New("source index is out of bounds") + } + if !dst.IsUint64() || dst.Uint64() >= uint64(scope.modExtState.active.NumElems()) { + return 0, errors.New("destination of copy out of bounds") + } + if !count.IsUint64() || count.Uint64() > uint64(scope.modExtState.active.NumElems()) { + return 0, errors.New("count must be less than number of field elements in the active space") + } + storeSize := count.Uint64() * uint64(scope.modExtState.active.NumElems()) + if src.Uint64()+storeSize > uint64(mem.Len()) { + return 0, errors.New("source of copy out of bounds of EVM memory") + } + + if scope.modExtState.active.IsModulusBinary() { + return toWordSize(storeSize) * params.CopyGas, nil + } else { + return count.Uint64() * uint64(params.MulmodxCost[int(scope.modExtState.active.ElemSize()/8)-1]), nil + } +} + +func gasLoadx(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + if scope.modExtState.active == nil { + return 0, errors.New("no active mod state") + } + dst := stack.Back(0) + src := stack.Back(1) + count := stack.Back(2) + + if !src.IsUint64() || uint(src.Uint64()) >= scope.modExtState.active.NumElems() { + return 0, errors.New("out of bounds copy source") + } + if !count.IsUint64() || uint(count.Uint64()) > scope.modExtState.active.NumElems() { + return 0, errors.New("count must be less than number of field elements") + } + if last, overflow := math.SafeAdd(src.Uint64(), count.Uint64()); overflow || last > uint64(scope.modExtState.active.NumElems()) { + return 0, errors.New("out of bounds copy source") + } + if !dst.IsUint64() { + return 0, errors.New("out of bounds destination") + } + + loadSize := count.Uint64() * uint64(scope.modExtState.active.ElemSize()) + last, overflow := math.SafeAdd(dst.Uint64(), loadSize) + if overflow || last > uint64(mem.Len()) { + return 0, errors.New("out of bounds destination") + } + + if scope.modExtState.active.IsModulusBinary() { + return toWordSize(loadSize) * params.CopyGas, nil + } else { + return count.Uint64() * uint64(params.MulmodxCost[int(scope.modExtState.active.ElemSize()/8)-1]), nil + } +} + +func gasEVMMAXArithOp(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + if scope.modExtState.active == nil { + return 0, errors.New("no active mod state") + } + _ = scope.Contract.Code[pc+7] + out := uint(scope.Contract.Code[pc+1]) + out_stride := uint(scope.Contract.Code[pc+2]) + x := uint(scope.Contract.Code[pc+3]) + x_stride := uint(scope.Contract.Code[pc+4]) + y := uint(scope.Contract.Code[pc+5]) + y_stride := uint(scope.Contract.Code[pc+6]) + count := uint(scope.Contract.Code[pc+7]) + + maxOffset := max(x+x_stride*count, y+y_stride*count, out+out_stride*count) + // TODO: might not need to assert count == 0 ? + if count == 0 || out_stride == 0 || maxOffset > scope.modExtState.active.NumElems() { + return 0, errors.New("bad parameters") + } + // TODO: fill in gas costs with table lookup multiplied by count... + return 1, nil +} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 47eb62be08..9d70cf30c2 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -954,6 +954,62 @@ func makeLog(size int) executionFunc { } } +func opSetupx(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + id, modOffset, modSize, allocSize := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.pop(), scope.Stack.pop() + modulus := scope.Memory.GetCopy(modOffset.Uint64(), modSize.Uint64()) + + if err := scope.modExtState.AllocAndSetActive(uint(id.Uint64()), modulus, int(allocSize.Uint64())); err != nil { + return nil, err + } + return nil, nil +} + +func opLoadx(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + dest, source, count := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.pop() + destBuf := scope.Memory.GetPtr(dest.Uint64(), count.Uint64()*uint64(scope.modExtState.active.ElemSize())) + scope.modExtState.active.Load(destBuf, int(source.Uint64()), int(count.Uint64())) + return nil, nil +} + +func opStorex(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + dest, source, count := scope.Stack.pop(), scope.Stack.pop(), scope.Stack.pop() + srcBuf := scope.Memory.GetPtr(source.Uint64(), count.Uint64()*uint64(scope.modExtState.active.ElemSize())) + return nil, scope.modExtState.active.Store(uint(dest.Uint64()), uint(count.Uint64()), srcBuf) +} + +func extractEVMMAXImmediateInputs(pc uint64, code []byte) (out, outStride, x, xStride, y, yStride, count uint) { + _ = code[pc+7] + out = uint(code[pc+1]) + outStride = uint(code[pc+2]) + x = uint(code[pc+3]) + xStride = uint(code[pc+4]) + y = uint(code[pc+5]) + yStride = uint(code[pc+6]) + count = uint(code[pc+7]) + return out, outStride, x, xStride, y, yStride, count +} + +func opAddmodx(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + out, outStride, x, xStride, y, yStride, count := extractEVMMAXImmediateInputs(*pc, scope.Contract.Code) + *pc += 7 + scope.modExtState.active.AddMod(out, outStride, x, xStride, y, yStride, count) + return nil, nil +} + +func opSubmodx(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + out, outStride, x, xStride, y, yStride, count := extractEVMMAXImmediateInputs(*pc, scope.Contract.Code) + *pc += 7 + scope.modExtState.active.SubMod(out, outStride, x, xStride, y, yStride, count) + return nil, nil +} + +func opMulmodx(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { + out, outStride, x, xStride, y, yStride, count := extractEVMMAXImmediateInputs(*pc, scope.Contract.Code) + *pc += 7 + scope.modExtState.active.MulMod(out, outStride, x, xStride, y, yStride, count) + return nil, nil +} + // opPush1 is a specialized version of pushN func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { var ( diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index c408994401..ff6e5b5910 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -18,13 +18,13 @@ package vm import ( "fmt" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/holiman/uint256" + evmmax_arith "github.com/jwasinger/evmmax-arith" ) // Config are the configuration options for the Interpreter @@ -40,9 +40,49 @@ type Config struct { // ScopeContext contains the things that are per-call, such as stack and memory, // but not transients like pc and gas type ScopeContext struct { - Memory *Memory - Stack *Stack - Contract *Contract + Memory *Memory + Stack *Stack + Contract *Contract + modExtState fieldAllocs +} + +// fieldAllocs represents the current set of field contexts that have been +// allocated in the current EVM call frame. It keeps track of an active +// context and the total allocated size in bytes of all field elements +// in all contexts in the current EVM call frame. +type fieldAllocs struct { + alloced map[uint]*evmmax_arith.FieldContext + active *evmmax_arith.FieldContext + allocedSize uint64 +} + +// AllocAndSetActive takes an id (number between 0 and 255 inclusive), a +// big-endian modulus, and the number of field elements to allocate. Each +// field element occupies memory equivalent to the size of the modulus padded +// to the nearest multiple of 8 bytes. +func (f *fieldAllocs) AllocAndSetActive(id uint, modulus []byte, allocCount int) error { + fieldContext, err := evmmax_arith.NewFieldContext(modulus, allocCount) + if err != nil { + return err + } + f.alloced[id] = fieldContext + f.active = fieldContext + f.allocedSize += uint64(fieldContext.AllocedSize()) + return nil +} + +// AllocSize returns the amount of EVMMAX-allocated memory (in bytes) in the current EVM call context +func (f *fieldAllocs) AllocSize() uint64 { + return f.allocedSize +} + +// SetActive sets a modulus as active in the current EVM call context. The +// modulus associated with id is assumed to have already been instantiated by +// a previous call to AllocAndSetActive +func (f *fieldAllocs) SetActive(id uint) error { + fieldContext := f.alloced[id] + f.active = fieldContext + return nil } // MemoryData returns the underlying memory slice. Callers must not modify the contents @@ -109,6 +149,8 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter { case evm.chainRules.IsVerkle: // TODO replace with proper instruction set when fork is specified table = &verkleInstructionSet + case evm.chainRules.IsEVMMAX: + table = &evmmaxInstructionSet case evm.chainRules.IsCancun: table = &cancunInstructionSet case evm.chainRules.IsShanghai: @@ -183,9 +225,10 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( mem = NewMemory() // bound memory stack = newstack() // local stack callContext = &ScopeContext{ - Memory: mem, - Stack: stack, - Contract: contract, + Memory: mem, + Stack: stack, + Contract: contract, + modExtState: fieldAllocs{alloced: make(map[uint]*evmmax_arith.FieldContext)}, } // For optimisation reason we're using uint64 as the program counter. // It's theoretically possible to go above 2^64. The YP defines the PC @@ -277,7 +320,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( // Consume the gas and return an error if not enough gas is available. // cost is explicitly set so that the capture state defer method can get the proper cost var dynamicCost uint64 - dynamicCost, err = operation.dynamicGas(in.evm, contract, stack, mem, memorySize) + dynamicCost, err = operation.dynamicGas(pc, in.evm, callContext, stack, mem, memorySize) cost += dynamicCost // for tracing if err != nil { return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 658014f24c..dfcb659087 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -24,9 +24,9 @@ import ( type ( executionFunc func(pc *uint64, interpreter *EVMInterpreter, callContext *ScopeContext) ([]byte, error) - gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64 + gasFunc func(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, memory *Memory, evmMemorySize uint64) (uint64, error) // last parameters are the requested memory sizes as uint64 // memorySizeFunc returns the required size, and whether the operation overflowed a uint64 - memorySizeFunc func(*Stack) (size uint64, overflow bool) + memorySizeFunc func(*Stack) (memSize uint64, overflow bool) ) type operation struct { @@ -59,6 +59,7 @@ var ( londonInstructionSet = newLondonInstructionSet() mergeInstructionSet = newMergeInstructionSet() shanghaiInstructionSet = newShanghaiInstructionSet() + evmmaxInstructionSet = newEVMMAXInstructionSet() cancunInstructionSet = newCancunInstructionSet() verkleInstructionSet = newVerkleInstructionSet() pragueEOFInstructionSet = newPragueEOFInstructionSet() @@ -101,6 +102,49 @@ func newPragueEOFInstructionSet() JumpTable { return validate(instructionSet) } +func newEVMMAXInstructionSet() JumpTable { + instructionSet := newCancunInstructionSet() + instructionSet[SETUPX] = &operation{ + execute: opSetupx, + dynamicGas: gasSetupx, + minStack: minStack(4, 0), + maxStack: maxStack(4, 0), + } + instructionSet[LOADX] = &operation{ + execute: opLoadx, + constantGas: GasQuickStep, + dynamicGas: gasLoadx, + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + } + instructionSet[STOREX] = &operation{ + execute: opStorex, + constantGas: GasQuickStep, + dynamicGas: gasStorex, + minStack: minStack(3, 0), + maxStack: maxStack(3, 0), + } + instructionSet[ADDMODX] = &operation{ + execute: opAddmodx, + dynamicGas: gasEVMMAXArithOp, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + } + instructionSet[SUBMODX] = &operation{ + execute: opSubmodx, + dynamicGas: gasEVMMAXArithOp, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + } + instructionSet[MULMODX] = &operation{ + execute: opMulmodx, + dynamicGas: gasEVMMAXArithOp, + minStack: minStack(0, 0), + maxStack: maxStack(0, 0), + } + return instructionSet +} + func newCancunInstructionSet() JumpTable { instructionSet := newShanghaiInstructionSet() enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode) diff --git a/core/vm/jump_table_export.go b/core/vm/jump_table_export.go index b74109da0a..047eefc65e 100644 --- a/core/vm/jump_table_export.go +++ b/core/vm/jump_table_export.go @@ -18,7 +18,6 @@ package vm import ( "errors" - "github.com/ethereum/go-ethereum/params" ) @@ -30,6 +29,8 @@ func LookupInstructionSet(rules params.Rules) (JumpTable, error) { return newCancunInstructionSet(), errors.New("verkle-fork not defined yet") case rules.IsPrague: return newCancunInstructionSet(), errors.New("prague-fork not defined yet") + case rules.IsEVMMAX: + return newEVMMAXInstructionSet(), nil case rules.IsCancun: return newCancunInstructionSet(), nil case rules.IsShanghai: diff --git a/core/vm/opcodes.go b/core/vm/opcodes.go index 162be8f6c8..5e1a97df43 100644 --- a/core/vm/opcodes.go +++ b/core/vm/opcodes.go @@ -202,6 +202,16 @@ const ( SWAP16 ) +// 0xc0 range - extended-width modular arithmetic ops. +const ( + SETUPX OpCode = 0xc0 + iota + LOADX + STOREX + ADDMODX + SUBMODX + MULMODX +) + // 0xa0 range - logging ops. const ( LOG0 OpCode = 0xa0 + iota @@ -407,6 +417,14 @@ var opCodeToString = [256]string{ SWAP15: "SWAP15", SWAP16: "SWAP16", + // 0xc0 range - extended-range modular arithmetic ops + SETUPX: "SETUPX", + LOADX: "LOADX", + STOREX: "STOREX", + ADDMODX: "ADDMODX", + SUBMODX: "SUBMODX", + MULMODX: "MULMODX", + // 0xa0 range - logging ops. LOG0: "LOG0", LOG1: "LOG1", diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index b993b651ff..5083f29fc3 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -26,23 +26,23 @@ import ( ) func makeGasSStoreFunc(clearingRefund uint64) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + return func(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // If we fail the minimum gas availability invariant, fail (0) - if contract.Gas <= params.SstoreSentryGasEIP2200 { + if scope.Contract.Gas <= params.SstoreSentryGasEIP2200 { return 0, errors.New("not enough gas for reentrancy sentry") } // Gas sentry honoured, do the actual gas calculation based on the stored value var ( y, x = stack.Back(1), stack.peek() slot = common.Hash(x.Bytes32()) - current = evm.StateDB.GetState(contract.Address(), slot) + current = evm.StateDB.GetState(scope.Contract.Address(), slot) cost = uint64(0) ) // Check slot presence in the access list - if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { + if _, slotPresent := evm.StateDB.SlotInAccessList(scope.Contract.Address(), slot); !slotPresent { cost = params.ColdSloadCostEIP2929 // If the caller cannot afford the cost, this change will be rolled back - evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + evm.StateDB.AddSlotToAccessList(scope.Contract.Address(), slot) } value := common.Hash(y.Bytes32()) @@ -51,7 +51,7 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { // return params.SloadGasEIP2200, nil return cost + params.WarmStorageReadCostEIP2929, nil // SLOAD_GAS } - original := evm.StateDB.GetCommittedState(contract.Address(), x.Bytes32()) + original := evm.StateDB.GetCommittedState(scope.Contract.Address(), x.Bytes32()) if original == current { if original == (common.Hash{}) { // create slot (2.1.1) return cost + params.SstoreSetGasEIP2200, nil @@ -95,14 +95,14 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc { // whose storage is being read) is not yet in accessed_storage_keys, // charge 2100 gas and add the pair to accessed_storage_keys. // If the pair is already in accessed_storage_keys, charge 100 gas. -func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasSLoadEIP2929(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { loc := stack.peek() slot := common.Hash(loc.Bytes32()) // Check slot presence in the access list - if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { + if _, slotPresent := evm.StateDB.SlotInAccessList(scope.Contract.Address(), slot); !slotPresent { // If the caller cannot afford the cost, this change will be rolled back // If he does afford it, we can skip checking the same thing later on, during execution - evm.StateDB.AddSlotToAccessList(contract.Address(), slot) + evm.StateDB.AddSlotToAccessList(scope.Contract.Address(), slot) return params.ColdSloadCostEIP2929, nil } return params.WarmStorageReadCostEIP2929, nil @@ -113,9 +113,9 @@ func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me // > If the target is not in accessed_addresses, // > charge COLD_ACCOUNT_ACCESS_COST gas, and add the address to accessed_addresses. // > Otherwise, charge WARM_STORAGE_READ_COST gas. -func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExtCodeCopyEIP2929(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // memory expansion first (dynamic part of pre-2929 implementation) - gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) + gas, err := gasExtCodeCopy(pc, evm, scope, stack, mem, memorySize) if err != nil { return 0, err } @@ -140,7 +140,7 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo // - extcodehash, // - extcodesize, // - (ext) balance -func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasEip2929AccountCheck(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { addr := common.Address(stack.peek().Bytes20()) // Check slot presence in the access list if !evm.StateDB.AddressInAccessList(addr) { @@ -153,8 +153,8 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem } func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - addr := common.Address(stack.Back(addressPosition).Bytes20()) + return func(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + addr := common.Address(scope.Stack.Back(addressPosition).Bytes20()) // Check slot presence in the access list warmAccess := evm.StateDB.AddressInAccessList(addr) // The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost, so @@ -164,7 +164,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g evm.StateDB.AddAddressToAccessList(addr) // Charge the remaining difference here already, to correctly calculate available // gas for call - if !contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { + if !scope.Contract.UseGas(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return 0, ErrOutOfGas } } @@ -173,7 +173,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g // - transfer value // - memory expansion // - 63/64ths rule - gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + gas, err := oldCalculator(pc, evm, scope, stack, mem, memorySize) if warmAccess || err != nil { return gas, err } @@ -181,7 +181,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g // add it to the returned gas. By adding it to the return, it will be charged // outside of this function, as part of the dynamic gas, and that will make it // also become correctly reported to tracers. - contract.Gas += coldCost + scope.Contract.Gas += coldCost var overflow bool if gas, overflow = math.SafeAdd(gas, coldCost); overflow { @@ -221,7 +221,7 @@ var ( // makeSelfdestructGasFn can create the selfdestruct dynamic gas function for EIP-2929 and EIP-3529 func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { - gasFunc := func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gasFunc := func(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( gas uint64 address = common.Address(stack.peek().Bytes20()) @@ -232,10 +232,10 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { gas = params.ColdAccountAccessCostEIP2929 } // if empty and transfers value - if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { + if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(scope.Contract.Address()).Sign() != 0 { gas += params.CreateBySelfdestructGas } - if refundsEnabled && !evm.StateDB.HasSelfDestructed(contract.Address()) { + if refundsEnabled && !evm.StateDB.HasSelfDestructed(scope.Contract.Address()) { evm.StateDB.AddRefund(params.SelfdestructRefundGas) } return gas, nil diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go index 3492994778..66eabd0991 100644 --- a/core/vm/operations_verkle.go +++ b/core/vm/operations_verkle.go @@ -24,23 +24,23 @@ import ( "github.com/ethereum/go-ethereum/params" ) -func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true) +func gasSStore4762(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas := evm.AccessEvents.SlotGas(scope.Contract.Address(), stack.peek().Bytes32(), true) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } return gas, nil } -func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false) +func gasSLoad4762(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas := evm.AccessEvents.SlotGas(scope.Contract.Address(), stack.peek().Bytes32(), false) if gas == 0 { gas = params.WarmStorageReadCostEIP2929 } return gas, nil } -func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasBalance4762(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() gas := evm.AccessEvents.BasicDataGas(address, false) if gas == 0 { @@ -49,7 +49,7 @@ func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem return gas, nil } -func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExtCodeSize4762(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil @@ -61,7 +61,7 @@ func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, return gas, nil } -func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExtCodeHash4762(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { address := stack.peek().Bytes20() if _, isPrecompile := evm.precompile(address); isPrecompile { return 0, nil @@ -74,15 +74,15 @@ func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, } func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc { - return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := oldCalculator(evm, contract, stack, mem, memorySize) + return func(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := oldCalculator(pc, evm, scope, stack, mem, memorySize) if err != nil { return 0, err } - if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile { + if _, isPrecompile := evm.precompile(scope.Contract.Address()); isPrecompile { return gas, nil } - witnessGas := evm.AccessEvents.MessageCallGas(contract.Address()) + witnessGas := evm.AccessEvents.MessageCallGas(scope.Contract.Address()) if witnessGas == 0 { witnessGas = params.WarmStorageReadCostEIP2929 } @@ -97,12 +97,12 @@ var ( gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall) ) -func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasSelfdestructEIP4762(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { beneficiaryAddr := common.Address(stack.peek().Bytes20()) if _, isPrecompile := evm.precompile(beneficiaryAddr); isPrecompile { return 0, nil } - contractAddr := contract.Address() + contractAddr := scope.Contract.Address() statelessGas := evm.AccessEvents.BasicDataGas(contractAddr, false) if contractAddr != beneficiaryAddr { statelessGas += evm.AccessEvents.BasicDataGas(beneficiaryAddr, false) @@ -117,8 +117,8 @@ func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Mem return statelessGas, nil } -func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - gas, err := gasCodeCopy(evm, contract, stack, mem, memorySize) +func gasCodeCopyEip4762(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + gas, err := gasCodeCopy(pc, evm, scope, stack, mem, memorySize) if err != nil { return 0, err } @@ -130,16 +130,16 @@ func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, if overflow { uint64CodeOffset = gomath.MaxUint64 } - _, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64()) - if !contract.IsDeployment { - gas += evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false) + _, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) + if !scope.Contract.IsDeployment { + gas += evm.AccessEvents.CodeChunksRangeGas(scope.Contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code)), false) } return gas, nil } -func gasExtCodeCopyEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { +func gasExtCodeCopyEIP4762(pc uint64, evm *EVM, scope *ScopeContext, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { // memory expansion first (dynamic part of pre-2929 implementation) - gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize) + gas, err := gasExtCodeCopy(pc, evm, scope, stack, mem, memorySize) if err != nil { return 0, err } diff --git a/go.mod b/go.mod index c467cef169..bea6d1cc15 100644 --- a/go.mod +++ b/go.mod @@ -46,6 +46,7 @@ require ( github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c github.com/jackpal/go-nat-pmp v1.0.2 github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 + github.com/jwasinger/evmmax-arith v0.0.0-20241121162715-6824561e983f github.com/karalabe/hid v1.0.1-0.20240306101548-573246063e52 github.com/kylelemons/godebug v1.1.0 github.com/mattn/go-colorable v0.1.13 diff --git a/go.sum b/go.sum index 19a7b9d25c..b2e7a8101c 100644 --- a/go.sum +++ b/go.sum @@ -336,6 +336,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jwasinger/evmmax-arith v0.0.0-20241121162715-6824561e983f h1:o9wHIWVnSTqno+E6KoCes5bOgl8JU+S3YDk1jhH7If8= +github.com/jwasinger/evmmax-arith v0.0.0-20241121162715-6824561e983f/go.mod h1:nz1RE8DNCfQycJI4AqfOYbEC1+Uugxvv1ywAvJr5sOs= github.com/karalabe/hid v1.0.1-0.20240306101548-573246063e52 h1:msKODTL1m0wigztaqILOtla9HeW1ciscYG4xjLtvk5I= github.com/karalabe/hid v1.0.1-0.20240306101548-573246063e52/go.mod h1:qk1sX/IBgppQNcGCRoj90u6EGC056EBoIc1oEjCWla8= github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= diff --git a/params/config.go b/params/config.go index 9b3b92484a..2c420546bc 100644 --- a/params/config.go +++ b/params/config.go @@ -319,6 +319,7 @@ type ChainConfig struct { CancunTime *uint64 `json:"cancunTime,omitempty"` // Cancun switch time (nil = no fork, 0 = already on cancun) PragueTime *uint64 `json:"pragueTime,omitempty"` // Prague switch time (nil = no fork, 0 = already on prague) VerkleTime *uint64 `json:"verkleTime,omitempty"` // Verkle switch time (nil = no fork, 0 = already on verkle) + EVMMAXTime *uint64 `json:"evmmaxTime,omitempty"` // EVMMAX switch time (nil = no fork, 0 = already on verkle) // TerminalTotalDifficulty is the amount of total difficulty reached by // the network that triggers the consensus upgrade. @@ -520,6 +521,11 @@ func (c *ChainConfig) IsPrague(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.PragueTime, time) } +func (c *ChainConfig) IsEVMMAX(num *big.Int, time uint64) bool { + res := c.IsLondon(num) && isTimestampForked(c.EVMMAXTime, time) + return res +} + // IsVerkle returns whether time is either equal to the Verkle fork time or greater. func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool { return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time) @@ -863,6 +869,7 @@ type Rules struct { IsBerlin, IsLondon bool IsMerge, IsShanghai, IsCancun, IsPrague bool IsVerkle bool + IsEVMMAX bool } // Rules ensures c's ChainID is not nil. @@ -891,6 +898,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsShanghai: isMerge && c.IsShanghai(num, timestamp), IsCancun: isMerge && c.IsCancun(num, timestamp), IsPrague: isMerge && c.IsPrague(num, timestamp), + IsEVMMAX: isMerge && c.IsEVMMAX(num, timestamp), IsVerkle: isVerkle, IsEIP4762: isVerkle, } diff --git a/params/protocol_params.go b/params/protocol_params.go index 90e7487cff..55ce702380 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -210,3 +210,17 @@ var ( ConsolidationQueueAddress = common.HexToAddress("0x01aBEa29659e5e97C95107F20bb753cD3e09bBBb") ConsolidationQueueCode = common.FromHex("3373fffffffffffffffffffffffffffffffffffffffe1460cf573615156028575f545f5260205ff35b366060141561019a5760115f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1461019a57600182026001905f5b5f821115608057810190830284830290049160010191906065565b90939004341061019a57600154600101600155600354806004026004013381556001015f358155600101602035815560010160403590553360601b5f5260605f60143760745fa0600101600355005b6003546002548082038060011160e3575060015b5f5b8181146101295780607402838201600402600401805490600101805490600101805490600101549260601b84529083601401528260340152906054015260010160e5565b910180921461013b5790600255610146565b90505f6002555f6003555b5f54807fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff141561017357505f5b6001546001828201116101885750505f61018e565b01600190035b5f555f6001556074025ff35b5f5ffd") ) + +// EVMMAX constants +var ( + SetupxPrecompCost = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} + // mulmodx cost lookup table + MulmodxCost = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} + // addmodx/submodx cost lookup table + AddOrSubCost = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12} + // the maximum size (in bytes) that can be allocated by all field contexts + // per EVM call frame + MaxFEAllocSize = 96 * 256 + StorexBaseCost = 1 + LoadxBaseCost = 1 +)