This is an automated email from the ASF dual-hosted git repository.
toulmean pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-tuweni.git
The following commit(s) were added to refs/heads/main by this push:
new 69581922 Add code to validate EVM code
new 7a25e760 Merge pull request #407 from atoulme/validate_code
69581922 is described below
commit 695819226130becfda4cdca428baf070436d22a6
Author: Antoine Toulme <[email protected]>
AuthorDate: Sat May 28 23:15:25 2022 -0700
Add code to validate EVM code
---
.../main/kotlin/org/apache/tuweni/evmdsl/Code.kt | 34 +++
.../org/apache/tuweni/evmdsl/Instructions.kt | 239 ++++++++++++++-------
.../kotlin/org/apache/tuweni/evmdsl/CodeTest.kt | 45 ++++
3 files changed, 243 insertions(+), 75 deletions(-)
diff --git a/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Code.kt
b/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Code.kt
index c1b740d7..abe1db3e 100644
--- a/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Code.kt
+++ b/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Code.kt
@@ -47,6 +47,32 @@ class Code(val instructions: List<Instruction>) {
}
}
+ fun validate(): CodeValidationError? {
+ var stackSize = 0
+ val visited = mutableSetOf<Int>()
+ var index = 0
+ while (visited.add(index)) {
+ val currentInstruction = instructions.getOrNull(index) ?: break
+ if (currentInstruction.stackItemsNeeded() > stackSize) {
+ return CodeValidationError(currentInstruction, index,
Error.STACK_UNDERFLOW)
+ }
+ stackSize += currentInstruction.stackItemsProduced()
+ if (stackSize > 1024) {
+ return CodeValidationError(currentInstruction, index,
Error.STACK_OVERFLOW)
+ }
+ if (currentInstruction is Invalid) {
+ return CodeValidationError(currentInstruction, index,
Error.HIT_INVALID_OPCODE)
+ }
+ // TODO cannot follow jumps right now.
+ if (currentInstruction == Jump || currentInstruction == Jumpi) {
+ break
+ }
+
+ index++
+ }
+ return null
+ }
+
fun toBytes(): Bytes {
return Bytes.wrap(instructions.map { it.toBytes() })
}
@@ -55,3 +81,11 @@ class Code(val instructions: List<Instruction>) {
return instructions.map { it.toString() }.joinToString("\n")
}
}
+
+enum class Error {
+ STACK_UNDERFLOW,
+ STACK_OVERFLOW,
+ HIT_INVALID_OPCODE
+}
+
+data class CodeValidationError(val instruction: Instruction, val index: Int,
val error: Error)
diff --git a/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Instructions.kt
b/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Instructions.kt
index 7a6287cd..04be3e10 100644
--- a/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Instructions.kt
+++ b/evm-dsl/src/main/kotlin/org/apache/tuweni/evmdsl/Instructions.kt
@@ -25,6 +25,12 @@ import org.apache.tuweni.bytes.Bytes
interface Instruction {
fun toBytes(): Bytes
+
+ fun stackItemsNeeded() = stackItemsConsumed()
+
+ fun stackItemsConsumed(): Int
+
+ fun stackItemsProduced(): Int
}
data class InstructionModel(val opcode: Byte, val additionalBytesToRead: Int =
0, val creator: (code: Bytes, index: Int) -> Instruction)
@@ -173,469 +179,552 @@ class Push(val bytesToPush: Bytes) : Instruction {
}
override fun toBytes(): Bytes = Bytes.wrap(Bytes.of((0x60 +
bytesToPush.size() - 1).toByte()), bytesToPush)
-
override fun toString(): String = "PUSH ${bytesToPush.toHexString()}"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
-class Invalid(val invalidByte: kotlin.Byte) : Instruction {
-
+data class Invalid(val invalidByte: Byte) : Instruction {
override fun toBytes(): Bytes = Bytes.of(invalidByte)
-
- override fun toString(): String = "INVALID 0x${invalidByte.toString(16)}"
+ override fun toString(): String = "INVALID ${Bytes.of(invalidByte)}"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 0
}
object Stop : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x00)
-
override fun toString(): String = "STOP"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 0
}
object Add : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x01)
-
override fun toString(): String = "ADD"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Mul : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x02)
-
override fun toString(): String = "MUL"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Sub : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x03)
-
override fun toString(): String = "SUB"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Div : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x04)
override fun toString(): String = "DIV"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object SDiv : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x05)
override fun toString(): String = "SDIV"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Mod : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x06)
override fun toString(): String = "MOD"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object SMod : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x07)
override fun toString(): String = "SMOD"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object AddMod : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x08)
override fun toString(): String = "ADDMOD"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object MulMod : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x09)
override fun toString(): String = "MULMOD"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Lt : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x10)
override fun toString(): String = "LT"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Gt : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x11)
override fun toString(): String = "GT"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Slt : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x12)
override fun toString(): String = "SLT"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Sgt : Instruction {
override fun toBytes(): Bytes = Bytes.of(0x13)
override fun toString(): String = "SGT"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Exp : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x0a)
override fun toString(): String = "EXP"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object SignExtend : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x0b)
-
override fun toString(): String = "SIGNEXTEND"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Eq : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x14)
override fun toString(): String = "EQ"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object IsZero : Instruction {
override fun toBytes(): Bytes = Bytes.of(0x15)
override fun toString(): String = "ISZERO"
+ override fun stackItemsConsumed() = 1
+ override fun stackItemsProduced() = 1
}
object And : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x16)
override fun toString(): String = "AND"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Or : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x17)
override fun toString(): String = "OR"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Xor : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x18)
override fun toString(): String = "XOR"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Not : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x19)
override fun toString(): String = "NOT"
+ override fun stackItemsConsumed() = 1
+ override fun stackItemsProduced() = 1
}
object Byte : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x1a)
override fun toString(): String = "BYTE"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Shl : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x1b)
override fun toString(): String = "SHL"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Shr : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x1c)
override fun toString(): String = "SHR"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Sar : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x1d)
-
override fun toString(): String = "SAR"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Sha3 : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x20)
override fun toString(): String = "SHA3"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Address : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x30)
override fun toString(): String = "ADDRESS"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object Balance : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x31)
override fun toString(): String = "BALANCE"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object Origin : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x32)
override fun toString(): String = "ORIGIN"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object Caller : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x33)
override fun toString(): String = "CALLER"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object CallValue : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x34)
override fun toString(): String = "CALLVALUE"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object CallDataLoad : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x35)
override fun toString(): String = "CALLDATALOAD"
+ override fun stackItemsConsumed() = 1
+ override fun stackItemsProduced() = 1
}
object CallDataSize : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x36)
override fun toString(): String = "CALLDATASIZE"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object CallDataCopy : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x37)
override fun toString(): String = "CALLDATACOPY"
+ override fun stackItemsConsumed() = 3
+ override fun stackItemsProduced() = 0
}
object CodeSize : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x38)
override fun toString(): String = "CODESIZE"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object CodeCopy : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x39)
override fun toString(): String = "CODECOPY"
+ override fun stackItemsConsumed() = 3
+ override fun stackItemsProduced() = 0
}
object GasPrice : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x3a)
override fun toString(): String = "GASPRICE"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object ExtCodeSize : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x3b)
override fun toString(): String = "EXTCODESIZE"
+ override fun stackItemsConsumed() = 1
+ override fun stackItemsProduced() = 1
}
object ExtCodeCopy : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x3c)
override fun toString(): String = "EXTCODECOPY"
+ override fun stackItemsConsumed() = 4
+ override fun stackItemsProduced() = 0
}
object ReturnDataSize : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x3d)
override fun toString(): String = "RETURNDATASIZE"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object ReturnDataCopy : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x3e)
override fun toString(): String = "RETURNDATACOPY"
+ override fun stackItemsConsumed() = 3
+ override fun stackItemsProduced() = 0
}
object ExtCodeHash : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x3f)
override fun toString(): String = "EXTCODEHASH"
+ override fun stackItemsConsumed() = 1
+ override fun stackItemsProduced() = 1
}
object BlockHash : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x40)
override fun toString(): String = "BLOCKHASH"
+ override fun stackItemsConsumed() = 1
+ override fun stackItemsProduced() = 1
}
object Coinbase : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x41)
override fun toString(): String = "COINBASE"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object Timestamp : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x42)
override fun toString(): String = "TIMESTAMP"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object Number : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x43)
override fun toString(): String = "NUMBER"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object Difficulty : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x44)
override fun toString(): String = "DIFFICULTY"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object GasLimit : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x45)
override fun toString(): String = "GASLIMIT"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object ChainId : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x46)
override fun toString(): String = "CHAINID"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object SelfBalance : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x47)
override fun toString(): String = "SELFBALANCE"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object Pop : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x50)
override fun toString(): String = "POP"
+ override fun stackItemsConsumed() = 1
+ override fun stackItemsProduced() = 0
}
object Mload : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x51)
override fun toString(): String = "MLOAD"
+ override fun stackItemsConsumed() = 1
+ override fun stackItemsProduced() = 1
}
object Mstore : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x52)
override fun toString(): String = "MSTORE"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 0
}
object Mstore8 : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x53)
override fun toString(): String = "MSTORE8"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Sload : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x54)
override fun toString(): String = "SLOAD"
+ override fun stackItemsConsumed() = 1
+ override fun stackItemsProduced() = 1
}
object Sstore : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x55)
override fun toString(): String = "SSTORE"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 0
}
object Jump : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x56)
override fun toString(): String = "JUMP"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 0
}
object Jumpi : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x57)
override fun toString(): String = "JUMPI"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 1
}
object Pc : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x58)
override fun toString(): String = "PC"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object Msize : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x59)
override fun toString(): String = "MSIZE"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object Gas : Instruction {
override fun toBytes(): Bytes = Bytes.of(0x5a)
override fun toString(): String = "GAS"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 1
}
object JumpDest : Instruction {
override fun toBytes(): Bytes = Bytes.of(0x5b)
override fun toString(): String = "JUMPDEST"
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 0
}
object Create : Instruction {
override fun toBytes(): Bytes = Bytes.of(0xf0)
override fun toString(): String = "CREATE"
+ override fun stackItemsConsumed() = 3
+ override fun stackItemsProduced() = 1
}
object Call : Instruction {
override fun toBytes(): Bytes = Bytes.of(0xf1)
override fun toString(): String = "CALL"
+ override fun stackItemsConsumed() = 7
+ override fun stackItemsProduced() = 1
}
object CallCode : Instruction {
override fun toBytes(): Bytes = Bytes.of(0xf2)
override fun toString(): String = "CALLCODE"
+ override fun stackItemsConsumed() = 7
+ override fun stackItemsProduced() = 1
}
object Return : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0xf3)
override fun toString(): String = "RETURN"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 0
}
object DelegateCall : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0xf4)
override fun toString(): String = "DELEGATECALL"
+ override fun stackItemsConsumed() = 7
+ override fun stackItemsProduced() = 1
}
object Create2 : Instruction {
override fun toBytes(): Bytes = Bytes.of(0xf5)
override fun toString(): String = "CREATE2"
+ override fun stackItemsConsumed() = 4
+ override fun stackItemsProduced() = 1
}
object StaticCall : Instruction {
override fun toBytes(): Bytes = Bytes.of(0xfa)
override fun toString(): String = "STATICCALL"
+ override fun stackItemsConsumed() = 7
+ override fun stackItemsProduced() = 1
}
object Revert : Instruction {
override fun toBytes(): Bytes = Bytes.of(0xfd)
override fun toString(): String = "REVERT"
+ override fun stackItemsConsumed() = 2
+ override fun stackItemsProduced() = 0
}
object SelfDestruct : Instruction {
override fun toBytes(): Bytes = Bytes.of(0xff)
override fun toString(): String = "SELFDESTRUCT"
+ override fun stackItemsConsumed() = 1
+ override fun stackItemsProduced() = 0
}
class Dup(val dupIndex: Int) : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x80 + dupIndex - 1)
override fun toString(): String = "DUP$dupIndex"
+ override fun stackItemsNeeded() = dupIndex
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = dupIndex
}
class Swap(val swapIndex: Int) : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0x90 + swapIndex - 1)
override fun toString(): String = "SWAP$swapIndex"
+ override fun stackItemsNeeded() = swapIndex
+ override fun stackItemsConsumed() = 0
+ override fun stackItemsProduced() = 0
}
class Log(val logIndex: Int) : Instruction {
-
override fun toBytes(): Bytes = Bytes.of(0xa0 + logIndex)
override fun toString(): String = "LOG$logIndex"
+ override fun stackItemsConsumed() = logIndex + 2
+ override fun stackItemsProduced() = 0
}
diff --git a/evm-dsl/src/test/kotlin/org/apache/tuweni/evmdsl/CodeTest.kt
b/evm-dsl/src/test/kotlin/org/apache/tuweni/evmdsl/CodeTest.kt
index 086c1434..31f5dfeb 100644
--- a/evm-dsl/src/test/kotlin/org/apache/tuweni/evmdsl/CodeTest.kt
+++ b/evm-dsl/src/test/kotlin/org/apache/tuweni/evmdsl/CodeTest.kt
@@ -53,4 +53,49 @@ class CodeTest {
val reread = Code.read(code.toBytes())
assertEquals(codeStr, reread.toString())
}
+
+ @Test
+ fun testValidateUnderFlow() {
+ val code = Code(
+ buildList {
+ this.add(Push(Bytes.fromHexString("0x4567")))
+ this.add(Push(Bytes.fromHexString("0x456778")))
+ this.add(Call)
+ }
+ )
+ val err = code.validate()!!
+ assertEquals(2, err.index)
+ assertEquals(Error.STACK_UNDERFLOW, err.error)
+ assertEquals(Call, err.instruction)
+ }
+
+ @Test
+ fun testValidateInvalid() {
+ val code = Code(
+ buildList {
+ this.add(Push(Bytes.fromHexString("0x4567")))
+ this.add(Push(Bytes.fromHexString("0x456778")))
+ this.add(Invalid(0xfe.toByte()))
+ this.add(Push(Bytes.fromHexString("0x456778")))
+ }
+ )
+ val err = code.validate()!!
+ assertEquals(2, err.index)
+ assertEquals(Error.HIT_INVALID_OPCODE, err.error)
+ assertEquals(Invalid(0xfe.toByte()), err.instruction)
+ }
+
+ @Test
+ fun testValidateOverFlow() {
+ val code = Code(
+ buildList {
+ for (i in 0..1024) {
+ this.add(Push(Bytes.fromHexString("0x4567")))
+ }
+ }
+ )
+ val err = code.validate()!!
+ assertEquals(1024, err.index)
+ assertEquals(Error.STACK_OVERFLOW, err.error)
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]