Skip to content

[ETCM-912] Magneto gas changes in EXT* codes family #1074

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jul 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/main/scala/io/iohk/ethereum/extvm/VMServer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ class VMServer(messageHandler: MessageHandler) extends Logger {
aghartaBlockNumber = BigInt(9573000), //TODO include agharta block number in protobuf
petersburgBlockNumber = BigInt(10000000), //TODO include petersburg block number in protobuf
phoenixBlockNumber = BigInt(10500839), //TODO include phoenix block number in protobuf
magnetoBlockNumber = BigInt(13189133), //TODO include magneto block number in protobuf
chainId = 0x3d.toByte //TODO include chainId in protobuf
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.Agharta
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.Atlantis
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.BeforeAtlantis
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.EtcFork
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.Magneto
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.Phoenix
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks.BeforeByzantium
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks.Byzantium
Expand Down Expand Up @@ -35,13 +36,15 @@ case class BlockchainConfigForEvm(
aghartaBlockNumber: BigInt,
petersburgBlockNumber: BigInt,
phoenixBlockNumber: BigInt,
magnetoBlockNumber: BigInt,
chainId: Byte
) {
def etcForkForBlockNumber(blockNumber: BigInt): EtcFork = blockNumber match {
case _ if blockNumber < atlantisBlockNumber => BeforeAtlantis
case _ if blockNumber < aghartaBlockNumber => Atlantis
case _ if blockNumber < phoenixBlockNumber => Agharta
case _ if blockNumber >= phoenixBlockNumber => Phoenix
case _ if blockNumber < magnetoBlockNumber => Phoenix
case _ if blockNumber >= magnetoBlockNumber => Magneto
}

def ethForkForBlockNumber(blockNumber: BigInt): BlockchainConfigForEvm.EthForks.Value = blockNumber match {
Expand All @@ -65,6 +68,8 @@ object BlockchainConfigForEvm {
val BeforeByzantium, Byzantium, Constantinople, Petersburg, Istanbul, Berlin = Value
}

def isEip2929Enabled(etcFork: EtcFork): Boolean = etcFork >= EtcForks.Magneto

def apply(blockchainConfig: BlockchainConfig): BlockchainConfigForEvm = {
import blockchainConfig._
BlockchainConfigForEvm(
Expand All @@ -82,6 +87,7 @@ object BlockchainConfigForEvm {
aghartaBlockNumber = forkBlockNumbers.aghartaBlockNumber,
petersburgBlockNumber = forkBlockNumbers.petersburgBlockNumber,
phoenixBlockNumber = forkBlockNumbers.phoenixBlockNumber,
magnetoBlockNumber = forkBlockNumbers.magnetoBlockNumber,
chainId = chainId
)
}
Expand Down
20 changes: 19 additions & 1 deletion src/main/scala/io/iohk/ethereum/vm/EvmConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ object EvmConfig {
(blockchainConfig.aghartaBlockNumber, 7, AghartaConfigBuilder),
(blockchainConfig.petersburgBlockNumber, 8, PetersburgConfigBuilder),
(blockchainConfig.istanbulBlockNumber, 9, IstanbulConfigBuilder),
(blockchainConfig.phoenixBlockNumber, 9, PhoenixConfigBuilder)
(blockchainConfig.phoenixBlockNumber, 9, PhoenixConfigBuilder),
(blockchainConfig.magnetoBlockNumber, 10, MagnetoConfigBuilder)
)

// highest transition block that is less/equal to `blockNumber`
Expand All @@ -61,6 +62,7 @@ object EvmConfig {
val ConstantinopleOpCodes: OpCodeList = OpCodeList(OpCodes.ConstantinopleOpCodes)
val AghartaOpCodes = ConstantinopleOpCodes
val PhoenixOpCodes: OpCodeList = OpCodeList(OpCodes.PhoenixOpCodes)
val MagnetoOpCodes: OpCodeList = PhoenixOpCodes

val FrontierConfigBuilder: EvmConfigBuilder = config =>
EvmConfig(
Expand Down Expand Up @@ -132,6 +134,12 @@ object EvmConfig {
opCodeList = PhoenixOpCodes
)

val MagnetoConfigBuilder: EvmConfigBuilder = config =>
PhoenixConfigBuilder(config).copy(
feeSchedule = new ethereum.vm.FeeSchedule.MagnetoFeeSchedule,
opCodeList = MagnetoOpCodes
)

case class OpCodeList(opCodes: List[OpCode]) {
val byteToOpCode: Map[Byte, OpCode] =
opCodes.map(op => op.code -> op).toMap
Expand Down Expand Up @@ -251,6 +259,9 @@ object FeeSchedule {
override val G_copy = 3
override val G_blockhash = 20
override val G_extcode = 20
override val G_cold_sload = 2100
override val G_cold_account_access = 2600
override val G_warm_storage_read = 100
}

class HomesteadFeeSchedule extends FrontierFeeSchedule {
Expand Down Expand Up @@ -283,6 +294,10 @@ object FeeSchedule {
override val G_txdatanonzero = 16
}

class MagnetoFeeSchedule extends PhoenixFeeSchedule {
override val G_sload: BigInt = G_warm_storage_read
override val G_sreset: BigInt = 5000 - G_cold_sload
}
}

trait FeeSchedule {
Expand Down Expand Up @@ -321,4 +336,7 @@ trait FeeSchedule {
val G_copy: BigInt
val G_blockhash: BigInt
val G_extcode: BigInt
val G_cold_sload: BigInt
val G_cold_account_access: BigInt
val G_warm_storage_read: BigInt
}
93 changes: 72 additions & 21 deletions src/main/scala/io/iohk/ethereum/vm/OpCode.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EtcForks.EtcFork
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks
import io.iohk.ethereum.vm.BlockchainConfigForEvm.EthForks.EthFork
import io.iohk.ethereum.vm.BlockchainConfigForEvm._

// scalastyle:off magic.number
// scalastyle:off number.of.types
Expand Down Expand Up @@ -171,7 +172,7 @@ object OpCode {
* @param delta number of words to be popped from stack
* @param alpha number of words to be pushed to stack
*/
abstract class OpCode(val code: Byte, val delta: Int, val alpha: Int, val constGasFn: FeeSchedule => BigInt)
abstract class OpCode(val code: Byte, val delta: Int, val alpha: Int, val baseGasFn: FeeSchedule => BigInt)
extends Product
with Serializable {
def this(code: Int, pop: Int, push: Int, constGasFn: FeeSchedule => BigInt) = this(code.toByte, pop, push, constGasFn)
Expand All @@ -184,21 +185,50 @@ abstract class OpCode(val code: Byte, val delta: Int, val alpha: Int, val constG
else if (state.stack.size - delta + alpha > state.stack.maxSize)
state.withError(StackOverflow)
else {
val constGas: BigInt = constGasFn(state.config.feeSchedule)

val gas: BigInt = constGas + varGas(state)
val gas: BigInt = calcGas(state)
if (gas > state.gas)
state.copy(gas = 0).withError(OutOfGas)
else
exec(state).spendGas(gas)
}

protected def calcGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt =
baseGas(state) + varGas(state)

protected def baseGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = baseGasFn(
state.config.feeSchedule
)

protected def varGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt

protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S]

protected def availableInContext[W <: WorldStateProxy[W, S], S <: Storage[S]]: ProgramState[W, S] => Boolean = _ =>
true

}

trait AddrAccessGas { self: OpCode =>

private def coldGasFn: FeeSchedule => BigInt = _.G_cold_account_access
private def warmGasFn: FeeSchedule => BigInt = _.G_warm_storage_read

override protected def baseGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = {
val currentBlockNumber = state.env.blockHeader.number
val etcFork = state.config.blockchainConfig.etcForkForBlockNumber(currentBlockNumber)
val eip2929Enabled = isEip2929Enabled(etcFork)
if (eip2929Enabled) {
val addr = address(state)
if (state.accessedAddresses.contains(addr))
warmGasFn(state.config.feeSchedule)
else
coldGasFn(state.config.feeSchedule)
} else
baseGasFn(state.config.feeSchedule)
}

protected def address[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): Address

}

sealed trait ConstGas { self: OpCode =>
Expand All @@ -210,8 +240,8 @@ case object STOP extends OpCode(0x00, 0, 0, _.G_zero) with ConstGas {
state.withReturnData(ByteString.empty).halt
}

sealed abstract class UnaryOp(code: Int, constGasFn: FeeSchedule => BigInt)(val f: UInt256 => UInt256)
extends OpCode(code, 1, 1, constGasFn)
sealed abstract class UnaryOp(code: Int, baseGasFn: FeeSchedule => BigInt)(val f: UInt256 => UInt256)
extends OpCode(code, 1, 1, baseGasFn)
with ConstGas {

protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = {
Expand All @@ -222,8 +252,8 @@ sealed abstract class UnaryOp(code: Int, constGasFn: FeeSchedule => BigInt)(val
}
}

sealed abstract class BinaryOp(code: Int, constGasFn: FeeSchedule => BigInt)(val f: (UInt256, UInt256) => UInt256)
extends OpCode(code.toByte, 2, 1, constGasFn) {
sealed abstract class BinaryOp(code: Int, baseGasFn: FeeSchedule => BigInt)(val f: (UInt256, UInt256) => UInt256)
extends OpCode(code.toByte, 2, 1, baseGasFn) {

protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = {
val (Seq(a, b), stack1) = state.stack.pop(2)
Expand All @@ -233,9 +263,9 @@ sealed abstract class BinaryOp(code: Int, constGasFn: FeeSchedule => BigInt)(val
}
}

sealed abstract class TernaryOp(code: Int, constGasFn: FeeSchedule => BigInt)(
sealed abstract class TernaryOp(code: Int, baseGasFn: FeeSchedule => BigInt)(
val f: (UInt256, UInt256, UInt256) => UInt256
) extends OpCode(code.toByte, 3, 1, constGasFn) {
) extends OpCode(code.toByte, 3, 1, baseGasFn) {

protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = {
val (Seq(a, b, c), stack1) = state.stack.pop(3)
Expand Down Expand Up @@ -365,7 +395,8 @@ case object BALANCE extends OpCode(0x31, 1, 1, _.G_balance) with ConstGas {
}
}

case object EXTCODEHASH extends OpCode(0x3f, 1, 1, _.G_balance) with ConstGas {
case object EXTCODEHASH extends OpCode(0x3f, 1, 1, _.G_balance) with AddrAccessGas with ConstGas {

protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = {
val (accountAddress, stack1) = state.stack.pop
val address = Address(accountAddress)
Expand Down Expand Up @@ -395,7 +426,12 @@ case object EXTCODEHASH extends OpCode(0x3f, 1, 1, _.G_balance) with ConstGas {
}

val stack2 = stack1.push(codeHash)
state.withStack(stack2).step()
state.withStack(stack2).addAccessedAddress(address).step()
}

protected def address[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): Address = {
val (accountAddress, _) = state.stack.pop
Address(accountAddress)
}
}

Expand Down Expand Up @@ -452,29 +488,42 @@ case object CODECOPY extends OpCode(0x39, 3, 0, _.G_verylow) {

case object GASPRICE extends ConstOp(0x3a)(_.env.gasPrice)

case object EXTCODESIZE extends OpCode(0x3b, 1, 1, _.G_extcode) with ConstGas {
case object EXTCODESIZE extends OpCode(0x3b, 1, 1, _.G_extcode) with AddrAccessGas with ConstGas {
protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = {
val (addr, stack1) = state.stack.pop
val codeSize = state.world.getCode(Address(addr)).size
val (addrUint, stack1) = state.stack.pop
val addr = Address(addrUint)
val codeSize = state.world.getCode(addr).size
val stack2 = stack1.push(UInt256(codeSize))
state.withStack(stack2).step()
state.withStack(stack2).addAccessedAddress(addr).step()
}

protected def address[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): Address = {
val (accountAddress, _) = state.stack.pop
Address(accountAddress)
}
}

case object EXTCODECOPY extends OpCode(0x3c, 4, 0, _.G_extcode) {
case object EXTCODECOPY extends OpCode(0x3c, 4, 0, _.G_extcode) with AddrAccessGas {

protected def exec[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): ProgramState[W, S] = {
val (Seq(address, memOffset, codeOffset, size), stack1) = state.stack.pop(4)
val codeCopy = OpCode.sliceBytes(state.world.getCode(Address(address)), codeOffset, size)
val addr = Address(address)
val codeCopy = OpCode.sliceBytes(state.world.getCode(addr), codeOffset, size)
val mem1 = state.memory.store(memOffset, codeCopy)
state.withStack(stack1).withMemory(mem1).step()
state.withStack(stack1).withMemory(mem1).addAccessedAddress(addr).step()
}

protected def varGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = {
override protected def varGas[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): BigInt = {
val (Seq(_, memOffset, _, size), _) = state.stack.pop(4)
val memCost = state.config.calcMemCost(state.memory.size, memOffset, size)
val copyCost = state.config.feeSchedule.G_copy * wordsForBytes(size)
memCost + copyCost
}

protected def address[W <: WorldStateProxy[W, S], S <: Storage[S]](state: ProgramState[W, S]): Address = {
val (Seq(accountAddress, _, _, _), _) = state.stack.pop(4)
Address(accountAddress)
}
}

case object RETURNDATASIZE extends ConstOp(0x3d)(_.returnData.size)
Expand Down Expand Up @@ -845,7 +894,7 @@ abstract class CreateOp(code: Int, delta: Int) extends OpCode(code, delta, 1, _.

//FIXME: to avoid calculating this twice, we could adjust state.gas prior to execution in OpCode#execute
//not sure how this would affect other opcodes [EC-243]
val availableGas = state.gas - (constGasFn(state.config.feeSchedule) + varGas(state))
val availableGas = state.gas - (baseGasFn(state.config.feeSchedule) + varGas(state))
val startGas = state.config.gasCap(availableGas)
val (initCode, memory1) = state.memory.load(inOffset, inSize)
val world1 = state.world.increaseNonce(state.ownAddress)
Expand Down Expand Up @@ -885,6 +934,7 @@ abstract class CreateOp(code: Int, delta: Int) extends OpCode(code, delta, 1, _.
.withWorld(world2)
.withStack(resultStack)
.withReturnData(returnData)
.addAccessedAddress(newAddress)
.step()

case None =>
Expand All @@ -902,6 +952,7 @@ abstract class CreateOp(code: Int, delta: Int) extends OpCode(code, delta, 1, _.
.withMemory(memory1)
.withInternalTxs(internalTx +: result.internalTxs)
.withReturnData(ByteString.empty)
.addAccessedAddress(newAddress)
.step()
}
}
Expand Down
24 changes: 14 additions & 10 deletions src/main/scala/io/iohk/ethereum/vm/PrecompiledContracts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,22 @@ object PrecompiledContracts {

private def getContract(context: ProgramContext[_, _]): Option[PrecompiledContract] =
context.recipientAddr.flatMap { addr =>
val ethFork = context.evmConfig.blockchainConfig.ethForkForBlockNumber(context.blockHeader.number)
val etcFork = context.evmConfig.blockchainConfig.etcForkForBlockNumber(context.blockHeader.number)

if (ethFork >= EthForks.Istanbul || etcFork >= EtcForks.Phoenix) {
istanbulPhoenixContracts.get(addr)
} else if (ethFork >= EthForks.Byzantium || etcFork >= EtcForks.Atlantis) {
// byzantium and atlantis hard fork introduce the same set of precompiled contracts
byzantiumAtlantisContracts.get(addr)
} else
contracts.get(addr)
getContracts(context).get(addr)
}

def getContracts(context: ProgramContext[_, _]): Map[Address, PrecompiledContract] = {
val ethFork = context.evmConfig.blockchainConfig.ethForkForBlockNumber(context.blockHeader.number)
val etcFork = context.evmConfig.blockchainConfig.etcForkForBlockNumber(context.blockHeader.number)

if (ethFork >= EthForks.Istanbul || etcFork >= EtcForks.Phoenix) {
istanbulPhoenixContracts
} else if (ethFork >= EthForks.Byzantium || etcFork >= EtcForks.Atlantis) {
// byzantium and atlantis hard fork introduce the same set of precompiled contracts
byzantiumAtlantisContracts
} else
contracts
}

sealed trait PrecompiledContract {
protected def exec(inputData: ByteString): Option[ByteString]
protected def gas(inputData: ByteString, etcFork: EtcFork, ethFork: EthFork): BigInt
Expand Down
19 changes: 17 additions & 2 deletions src/main/scala/io/iohk/ethereum/vm/ProgramState.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ object ProgramState {
world = context.world,
staticCtx = context.staticCtx,
addressesToDelete = context.initialAddressesToDelete,
originalWorld = context.originalWorld
originalWorld = context.originalWorld,
accessedAddresses = PrecompiledContracts.getContracts(context).keySet ++ Set(
context.originAddr,
context.recipientAddr.getOrElse(context.callerAddr)
),
accessedStorageKeys = Set.empty
)
}

Expand All @@ -40,6 +45,8 @@ object ProgramState {
* @param staticCtx a flag to indicate static context (EIP-214)
* @param error indicates whether the program terminated abnormally
* @param originalWorld state of the world at the beginning og the current transaction, read-only,
* @param accessedAddresses set of addresses which have already been accessed in this transaction (EIP-2929)
* @param accessedStorageKeys set of storage slots which have already been accessed in this transaction (EIP-2929)
* needed for https://eips.ethereum.org/EIPS/eip-1283
*/
case class ProgramState[W <: WorldStateProxy[W, S], S <: Storage[S]](
Expand All @@ -58,7 +65,9 @@ case class ProgramState[W <: WorldStateProxy[W, S], S <: Storage[S]](
halted: Boolean = false,
staticCtx: Boolean = false,
error: Option[ProgramError] = None,
originalWorld: W
originalWorld: W,
accessedAddresses: Set[Address],
accessedStorageKeys: Set[(Address, BigInt)]
) {

def config: EvmConfig = env.evmConfig
Expand Down Expand Up @@ -126,6 +135,12 @@ case class ProgramState[W <: WorldStateProxy[W, S], S <: Storage[S]](
def revert(data: ByteString): ProgramState[W, S] =
copy(error = Some(RevertOccurs), returnData = data, halted = true)

def addAccessedAddress(addr: Address): ProgramState[W, S] =
copy(accessedAddresses = accessedAddresses + addr)

def addAccessedStorageKey(addr: Address, storageKey: BigInt): ProgramState[W, S] =
copy(accessedStorageKeys = accessedStorageKeys + ((addr, storageKey)))

def toResult: ProgramResult[W, S] =
ProgramResult[W, S](
returnData,
Expand Down
Loading