blob: abe1db3eb25d85773252e0e25b07c9a7c66c632e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.tuweni.evmdsl
import org.apache.tuweni.bytes.Bytes
/**
* EVM code represented as a set of domain-specific instructions.
*
* The code can be serialized into bytes to be deployed on a EVM-compatible chain.
*
* The code can also read bytecode and represent it in this DSL.
*/
class Code(val instructions: List<Instruction>) {
companion object {
fun read(codeBytes: Bytes): Code {
return Code(
buildList {
var index = 0
while (index < codeBytes.size()) {
val model = InstructionRegistry.opcodes.get(codeBytes.get(index))
if (model == null) {
throw IllegalArgumentException("Unknown opcode " + Bytes.of(codeBytes.get(index)) + " at index " + index)
}
index++
this.add(model.creator(codeBytes, index))
index += model.additionalBytesToRead
}
}
)
}
}
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() })
}
override fun toString(): String {
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)