Skip to content

Commit

Permalink
fix(EVM): Add stipends for calls to zkVM contracts (#1126)
Browse files Browse the repository at this point in the history
  • Loading branch information
0xVolosnikov committed Dec 10, 2024
1 parent 5102173 commit b050221
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 15 deletions.
64 changes: 54 additions & 10 deletions system-contracts/contracts/EvmEmulator.yul
Original file line number Diff line number Diff line change
Expand Up @@ -863,14 +863,15 @@ object "EvmEmulator" {
}

function _genericCall(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft {
switch isHashOfConstructedEvmContract(getRawCodeHash(addr))
let rawCodeHash := getRawCodeHash(addr)
switch isHashOfConstructedEvmContract(rawCodeHash)
case 0 {
// zkEVM native call
let precompileCost := getGasForPrecompiles(addr, argsSize)
switch precompileCost
case 0 {
// just smart contract
success, frameGasLeft := callZkVmNative(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic)
success, frameGasLeft := callZkVmNative(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic, rawCodeHash)
}
default {
// precompile
Expand Down Expand Up @@ -919,9 +920,20 @@ object "EvmEmulator" {
}

// Call native ZkVm contract from EVM context
function callZkVmNative(addr, evmGasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft {
function callZkVmNative(addr, evmGasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic, rawCodeHash) -> success, frameGasLeft {
let zkEvmGasToPass := mul(evmGasToPass, GAS_DIVISOR()) // convert EVM gas -> ZkVM gas

let additionalStipend
if iszero(and(shr(224, rawCodeHash), 0xffff)) { // if codelen is zero
additionalStipend := 6000 // should cover first access to empty account
}

if value {
additionalStipend := 27000 // Stipend for MsgValueSimulator. Covered by positive_value_cost
}

zkEvmGasToPass := add(zkEvmGasToPass, additionalStipend)

if gt(zkEvmGasToPass, UINT32_MAX()) { // just in case
zkEvmGasToPass := UINT32_MAX()
}
Expand All @@ -937,11 +949,21 @@ object "EvmEmulator" {
let zkEvmGasUsed := sub(zkEvmGasBefore, gas())

_saveReturndataAfterZkEVMCall()

if gt(zkEvmGasUsed, zkEvmGasBefore) { // overflow case
zkEvmGasUsed := zkEvmGasToPass // should never happen
zkEvmGasUsed := 0 // should never happen
}

switch gt(zkEvmGasUsed, additionalStipend)
case 0 {
zkEvmGasUsed := 0
}
default {
zkEvmGasUsed := sub(zkEvmGasUsed, additionalStipend)
}

zkEvmGasToPass := sub(zkEvmGasToPass, additionalStipend)

// refund gas
if gt(zkEvmGasToPass, zkEvmGasUsed) {
frameGasLeft := div(sub(zkEvmGasToPass, zkEvmGasUsed), GAS_DIVISOR())
Expand Down Expand Up @@ -3999,14 +4021,15 @@ object "EvmEmulator" {
}

function _genericCall(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft {
switch isHashOfConstructedEvmContract(getRawCodeHash(addr))
let rawCodeHash := getRawCodeHash(addr)
switch isHashOfConstructedEvmContract(rawCodeHash)
case 0 {
// zkEVM native call
let precompileCost := getGasForPrecompiles(addr, argsSize)
switch precompileCost
case 0 {
// just smart contract
success, frameGasLeft := callZkVmNative(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic)
success, frameGasLeft := callZkVmNative(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic, rawCodeHash)
}
default {
// precompile
Expand Down Expand Up @@ -4055,9 +4078,20 @@ object "EvmEmulator" {
}

// Call native ZkVm contract from EVM context
function callZkVmNative(addr, evmGasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft {
function callZkVmNative(addr, evmGasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic, rawCodeHash) -> success, frameGasLeft {
let zkEvmGasToPass := mul(evmGasToPass, GAS_DIVISOR()) // convert EVM gas -> ZkVM gas

let additionalStipend
if iszero(and(shr(224, rawCodeHash), 0xffff)) { // if codelen is zero
additionalStipend := 6000 // should cover first access to empty account
}

if value {
additionalStipend := 27000 // Stipend for MsgValueSimulator. Covered by positive_value_cost
}

zkEvmGasToPass := add(zkEvmGasToPass, additionalStipend)

if gt(zkEvmGasToPass, UINT32_MAX()) { // just in case
zkEvmGasToPass := UINT32_MAX()
}
Expand All @@ -4073,11 +4107,21 @@ object "EvmEmulator" {
let zkEvmGasUsed := sub(zkEvmGasBefore, gas())

_saveReturndataAfterZkEVMCall()

if gt(zkEvmGasUsed, zkEvmGasBefore) { // overflow case
zkEvmGasUsed := zkEvmGasToPass // should never happen
zkEvmGasUsed := 0 // should never happen
}

switch gt(zkEvmGasUsed, additionalStipend)
case 0 {
zkEvmGasUsed := 0
}
default {
zkEvmGasUsed := sub(zkEvmGasUsed, additionalStipend)
}

zkEvmGasToPass := sub(zkEvmGasToPass, additionalStipend)

// refund gas
if gt(zkEvmGasToPass, zkEvmGasUsed) {
frameGasLeft := div(sub(zkEvmGasToPass, zkEvmGasUsed), GAS_DIVISOR())
Expand Down
32 changes: 27 additions & 5 deletions system-contracts/evm-emulator/EvmEmulatorFunctions.template.yul
Original file line number Diff line number Diff line change
Expand Up @@ -801,14 +801,15 @@ function _genericPrecallLogic(rawAddr, argsOffset, argsSize, retOffset, retSize)
}

function _genericCall(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft {
switch isHashOfConstructedEvmContract(getRawCodeHash(addr))
let rawCodeHash := getRawCodeHash(addr)
switch isHashOfConstructedEvmContract(rawCodeHash)
case 0 {
// zkEVM native call
let precompileCost := getGasForPrecompiles(addr, argsSize)
switch precompileCost
case 0 {
// just smart contract
success, frameGasLeft := callZkVmNative(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic)
success, frameGasLeft := callZkVmNative(addr, gasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic, rawCodeHash)
}
default {
// precompile
Expand Down Expand Up @@ -857,9 +858,20 @@ function callPrecompile(addr, precompileCost, gasToPass, value, argsOffset, args
}

// Call native ZkVm contract from EVM context
function callZkVmNative(addr, evmGasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic) -> success, frameGasLeft {
function callZkVmNative(addr, evmGasToPass, value, argsOffset, argsSize, retOffset, retSize, isStatic, rawCodeHash) -> success, frameGasLeft {
let zkEvmGasToPass := mul(evmGasToPass, GAS_DIVISOR()) // convert EVM gas -> ZkVM gas

let additionalStipend
if iszero(and(shr(224, rawCodeHash), 0xffff)) { // if codelen is zero
additionalStipend := 6000 // should cover first access to empty account
}

if value {
additionalStipend := 27000 // Stipend for MsgValueSimulator. Covered by positive_value_cost
}

zkEvmGasToPass := add(zkEvmGasToPass, additionalStipend)

if gt(zkEvmGasToPass, UINT32_MAX()) { // just in case
zkEvmGasToPass := UINT32_MAX()
}
Expand All @@ -875,11 +887,21 @@ function callZkVmNative(addr, evmGasToPass, value, argsOffset, argsSize, retOffs
let zkEvmGasUsed := sub(zkEvmGasBefore, gas())

_saveReturndataAfterZkEVMCall()

if gt(zkEvmGasUsed, zkEvmGasBefore) { // overflow case
zkEvmGasUsed := zkEvmGasToPass // should never happen
zkEvmGasUsed := 0 // should never happen
}

switch gt(zkEvmGasUsed, additionalStipend)
case 0 {
zkEvmGasUsed := 0
}
default {
zkEvmGasUsed := sub(zkEvmGasUsed, additionalStipend)
}

zkEvmGasToPass := sub(zkEvmGasToPass, additionalStipend)

// refund gas
if gt(zkEvmGasToPass, zkEvmGasUsed) {
frameGasLeft := div(sub(zkEvmGasToPass, zkEvmGasUsed), GAS_DIVISOR())
Expand Down

0 comments on commit b050221

Please sign in to comment.