initial commit

Signed-off-by: Cyrill Leutwiler <bigcyrill@hotmail.com>
This commit is contained in:
Cyrill Leutwiler
2023-12-07 20:37:23 +01:00
commit 426ab4b095
16 changed files with 2289 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
/target
*.dot
Generated
+1483
View File
File diff suppressed because it is too large Load Diff
+14
View File
@@ -0,0 +1,14 @@
[workspace]
resolver = "2"
members = [
"crates/ir-tac",
"crates/cli",
]
[workspace.dependencies]
evmil = "0.4"
hex = "0.4"
petgraph = "0.6"
primitive-types = "0.12"
indexmap = "2.1.0"
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "revive"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hex = { workspace = true }
evmil = { workspace = true }
ir-tac = { path = "../ir-tac" }
+14
View File
@@ -0,0 +1,14 @@
use evmil::bytecode::Disassemble;
use ir_tac::cfg::{BasicBlockFormatOption, Program};
fn main() {
let hexcode = std::fs::read_to_string(std::env::args().nth(1).unwrap()).unwrap();
let bytecode = hex::decode(hexcode.trim()).unwrap();
let instructions = bytecode.disassemble();
//for instruction in instructions.iter() {
// println!("{instruction:?}");
//}
Program::new(instructions).dot(BasicBlockFormatOption::ByteCode);
}
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "ir-tac"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
evmil = { workspace = true }
petgraph = { workspace = true }
primitive-types = { workspace = true }
indexmap = { workspace = true }
+58
View File
@@ -0,0 +1,58 @@
use primitive_types::U256;
#[derive(Clone, Copy)]
pub enum Kind {
Constant(U256),
Temporary(usize),
Stack,
}
#[derive(Clone, Copy)]
pub struct Address {
pub kind: Kind,
pub type_hint: Option<Type>,
}
impl From<(Kind, Option<Type>)> for Address {
fn from(value: (Kind, Option<Type>)) -> Self {
Self {
kind: value.0,
type_hint: value.1,
}
}
}
impl Address {
pub fn new(kind: Kind, type_hint: Option<Type>) -> Self {
Self { kind, type_hint }
}
}
#[derive(Clone, Copy)]
pub enum Type {
Int { size: u8 },
Bytes { size: u8 },
Bool,
}
impl Type {
pub fn int(size: u8) -> Self {
Self::Int { size }
}
fn bytes(size: u8) -> Self {
Self::Bytes { size }
}
}
impl Default for Type {
fn default() -> Self {
Type::Int { size: 32 }
}
}
pub enum LinearMemory {
CallData,
Memory,
ReturnData,
}
+216
View File
@@ -0,0 +1,216 @@
use std::fmt::Write;
use std::ops::Range;
use evmil::bytecode;
use petgraph::{
dot::{Config, Dot},
graph::DiGraph,
stable_graph::NodeIndex,
};
use crate::{
instruction::{self, Instruction},
symbol::SymbolTable,
};
#[derive(Clone, Debug)]
pub struct EvmInstruction {
pub bytecode_offset: usize,
pub instruction: bytecode::Instruction,
}
#[derive(Debug, Default)]
pub struct BasicBlock {
pub entry: Option<Entry>,
pub opcodes: Range<usize>,
pub instructions: Vec<Instruction>,
}
#[derive(Clone, Copy, Default)]
pub enum BasicBlockFormatOption {
ByteCode,
Ir,
#[default]
None,
}
impl BasicBlock {
fn format(&self, evm_bytecode: &[EvmInstruction], options: BasicBlockFormatOption) -> String {
let offset = evm_bytecode[self.opcodes.start].bytecode_offset;
let start = if let Some(Entry::Start) = self.entry {
"Start\n".to_string()
} else {
String::new()
};
let instructions = match options {
BasicBlockFormatOption::ByteCode => evm_bytecode[self.opcodes.start..self.opcodes.end]
.iter()
.fold(String::new(), |mut acc, opcode| {
writeln!(&mut acc, "{:?}", opcode.instruction).unwrap();
acc
}),
_ => String::new(),
};
format!("{start}Offset: 0x{offset:02x}\n---\n{instructions}")
}
}
#[derive(Clone, Debug)]
pub enum Entry {
Start,
Jumpdest(NodeIndex),
Else(NodeIndex),
}
#[derive(Debug)]
pub enum Jump {
Direct,
Indirect,
}
pub struct Program {
pub evm_instructions: Vec<EvmInstruction>,
pub cfg: DiGraph<BasicBlock, Jump>,
pub symbol_table: SymbolTable,
}
impl Program {
pub fn new(bytecode: Vec<bytecode::Instruction>) -> Self {
let mut cfg = DiGraph::new();
let mut symbol_table = SymbolTable::default();
let mut evm_instructions = Vec::with_capacity(bytecode.len());
let mut current_block = Some(BasicBlock {
entry: Some(Entry::Start),
..Default::default()
});
let mut bytecode_offset = 0;
for (index, opcode) in bytecode.iter().enumerate() {
evm_instructions.push(EvmInstruction {
bytecode_offset,
instruction: opcode.clone(),
});
bytecode_offset += opcode.length();
let instructions = instruction::translate(opcode, &mut symbol_table);
use bytecode::Instruction::*;
match opcode {
JUMPDEST => {
// If we are already in a bb, conclude it
let entry = current_block.take().map(|mut node| {
node.opcodes.end = index + 1;
let entry = node.entry.clone();
let node_index = cfg.add_node(node);
// If the block had an entry, add an edge from the previous block to it
if let Some(Entry::Else(incoming)) | Some(Entry::Jumpdest(incoming)) = entry
{
cfg.add_edge(incoming, node_index, Jump::Direct);
}
node_index
});
// JUMPDEST implicitly starts a new bb
current_block = Some(BasicBlock {
entry: entry.map(Entry::Jumpdest),
opcodes: Range {
start: index + 1,
end: index + 1,
},
..Default::default()
});
}
JUMP | STOP | RETURN | REVERT | INVALID => {
// Conclude this bb; if we are not already in a bb we must create a new one
let mut node = current_block.take().unwrap_or_else(|| BasicBlock {
opcodes: Range {
start: index,
end: index + 1,
},
..Default::default()
});
node.instructions.extend(instructions);
node.opcodes.end = index + 1;
let entry = node.entry.clone();
let node_index = cfg.add_node(node);
// If the block had an entry, add an edge from the previous block to it
if let Some(Entry::Else(incoming)) | Some(Entry::Jumpdest(incoming)) = entry {
cfg.add_edge(incoming, node_index, Jump::Direct);
}
}
JUMPI => {
// Conclude this bb; if we are not already in a bb we must create a new one
let mut node = current_block.take().unwrap_or_else(|| BasicBlock {
opcodes: Range {
start: index,
end: index + 1,
},
..Default::default()
});
node.instructions.extend(instructions);
node.opcodes.end = index + 1;
let entry = node.entry.clone();
let node_index = cfg.add_node(node);
// If the block had an entry, add an edge from the previous block to it
if let Some(Entry::Else(incoming)) | Some(Entry::Jumpdest(incoming)) = entry {
cfg.add_edge(incoming, node_index, Jump::Direct);
}
// JUMPI implicitly starts a new bb for the else branch
current_block = Some(BasicBlock {
entry: Some(Entry::Else(node_index)),
opcodes: Range {
start: index + 1,
end: index + 1,
},
..Default::default()
});
}
_ => current_block
.get_or_insert(BasicBlock {
opcodes: Range {
start: index,
end: index + 1,
},
..Default::default()
})
.instructions
.extend(instructions),
}
}
Self {
evm_instructions,
cfg,
symbol_table,
}
}
pub fn dot(&self, format_options: BasicBlockFormatOption) {
let get_node_attrs = move |_, (_, node): (_, &BasicBlock)| {
format!(
"label = \"{}\"",
node.format(&self.evm_instructions, format_options)
)
};
let dot = Dot::with_attr_getters(
&self.cfg,
&[Config::EdgeNoLabel, Config::NodeNoLabel],
&|_, edge| format!("label = \"{:?}\"", edge.weight()),
&get_node_attrs,
);
println!("{dot:?}");
}
}
+321
View File
@@ -0,0 +1,321 @@
use evmil::bytecode::Instruction as EvmInstruction;
use primitive_types::U256;
use crate::symbol::{Global, Symbol, SymbolTable, Type};
#[derive(PartialEq, Debug)]
pub enum Instruction {
/// `x = y op z`
BinaryAssign {
x: Symbol,
y: Symbol,
operator: Operator,
z: Symbol,
},
/// `x = op y`
UnaryAssign {
x: Symbol,
y: Symbol,
operator: Operator,
},
/// `branch target`
UncoditionalBranch { target: Symbol },
/// `branch target if condition`
ConditionalBranch { condition: Symbol, target: Symbol },
/// `call(label, n)`
Procedure {
symbol: Global,
parameters: Vec<Symbol>,
},
/// `x = call(label, n)`
Function {
symbol: Global,
x: Symbol,
args: Vec<Symbol>,
},
/// `x = y`
Copy { x: Symbol, y: Symbol },
/// `x[index] = y`
IndexedAssign { x: Symbol, index: Symbol, y: Symbol },
/// `x = y[index]`
IndexedCopy { x: Symbol, y: Symbol, index: Symbol },
}
#[derive(PartialEq, Debug)]
pub enum Operator {
Add,
Mul,
Sub,
Div,
SDiv,
Mod,
SMod,
AddMod,
MulMod,
Exp,
SignExtend,
LessThat,
GreaterThan,
SignedLessThan,
SignedGreaterThan,
Eq,
IsZero,
And,
Or,
Xor,
Not,
Byte,
ShiftLeft,
ShiftRight,
ShiftArithmeticRight,
}
struct StackPop {
decrement: Instruction,
load: Instruction,
}
/// Pop a value from the stack.
///
/// Returns 2 `Instruction`: Decrementing the stack pointer and the value copy.
fn stack_pop(symbol_table: &mut SymbolTable) -> StackPop {
let decrement = decrement_stack_height(symbol_table);
let load = Instruction::IndexedCopy {
x: symbol_table.temporary(None),
y: symbol_table.global(Global::Stack),
index: symbol_table.global(Global::StackHeight),
};
StackPop { decrement, load }
}
/// Decrease the stack height by one.
fn decrement_stack_height(symbol_table: &mut SymbolTable) -> Instruction {
Instruction::BinaryAssign {
x: symbol_table.global(Global::StackHeight),
y: symbol_table.global(Global::StackHeight),
operator: Operator::Sub,
z: symbol_table.constant(U256::one(), Some(Type::Int(4))),
}
}
struct StackPush {
assign: Instruction,
increment: Instruction,
}
/// Push a `value` to the stack.
///
/// Returns 2 `Instruction`: the value assign and the stack height increase.
fn stack_push(symbol_table: &mut SymbolTable, value: Symbol) -> StackPush {
let assign = Instruction::IndexedAssign {
x: symbol_table.global(Global::Stack),
index: symbol_table.global(Global::StackHeight),
y: value,
};
let increment = increment_stack_height(symbol_table);
StackPush { assign, increment }
}
/// Increment the stack height by one.
fn increment_stack_height(symbol_table: &mut SymbolTable) -> Instruction {
Instruction::BinaryAssign {
x: symbol_table.global(Global::StackHeight),
y: symbol_table.global(Global::StackHeight),
operator: Operator::Add,
z: symbol_table.constant(U256::one(), Some(Type::Int(4))),
}
}
impl Instruction {
fn target_address(&self) -> Symbol {
match self {
Instruction::Copy { x, .. } => *x,
Instruction::IndexedAssign { x, .. } => *x,
Instruction::IndexedCopy { x, .. } => *x,
_ => unreachable!(),
}
}
}
/// Lower an EVM instruction into corresponding 3AC instructions.
pub fn translate(opcode: &EvmInstruction, symbol_table: &mut SymbolTable) -> Vec<Instruction> {
use EvmInstruction::*;
match opcode {
JUMPDEST => Vec::new(),
PUSH(bytes) => {
let type_hint = Some(Type::Bytes(bytes.len()));
let value = symbol_table.constant(U256::from_big_endian(bytes), type_hint);
let push = stack_push(symbol_table, value);
vec![push.assign, push.increment]
}
POP => vec![decrement_stack_height(symbol_table)],
MSTORE => {
let offset = stack_pop(symbol_table);
let value = stack_pop(symbol_table);
let store = Instruction::IndexedAssign {
x: symbol_table.global(Global::Memory),
index: offset.load.target_address(),
y: value.load.target_address(),
};
vec![
offset.decrement,
offset.load,
value.decrement,
value.load,
store,
]
}
JUMP => {
let target = stack_pop(symbol_table);
let jump = Instruction::UncoditionalBranch {
target: target.load.target_address(),
};
vec![target.decrement, target.load, jump]
}
RETURN => {
let offset = stack_pop(symbol_table);
let size = stack_pop(symbol_table);
let procedure = Instruction::Procedure {
symbol: Global::Return,
parameters: vec![offset.load.target_address(), size.load.target_address()],
};
vec![
offset.decrement,
offset.load,
size.decrement,
size.load,
procedure,
]
}
CALLDATACOPY => {
let destination_offset = stack_pop(symbol_table);
let offset = stack_pop(symbol_table);
let size = stack_pop(symbol_table);
let parameters = vec![
destination_offset.load.target_address(),
offset.load.target_address(),
size.load.target_address(),
];
let procedure = Instruction::Procedure {
symbol: Global::MemoryCopy,
parameters,
};
vec![
destination_offset.decrement,
destination_offset.load,
offset.decrement,
offset.load,
size.decrement,
size.load,
procedure,
]
}
CALLDATALOAD => {
let index = stack_pop(symbol_table);
let value = Instruction::IndexedCopy {
x: symbol_table.temporary(None),
y: symbol_table.global(Global::CallData),
index: index.load.target_address(),
};
let push = stack_push(symbol_table, value.target_address());
vec![
index.decrement,
index.load,
value,
push.assign,
push.increment,
]
}
//_ => todo!("{opcode}"),
_ => Vec::new(),
}
}
#[cfg(test)]
mod tests {
use evmil::bytecode;
use primitive_types::U256;
use crate::{
instruction::Operator,
symbol::{Global, Kind, Symbol, Type},
};
use super::Instruction;
#[test]
fn lower_push_works() {
let mut symbol_table = Default::default();
let opcode = bytecode::Instruction::PUSH(vec![0x01]);
let result = super::translate(&opcode, &mut symbol_table);
let expected = vec![
Instruction::IndexedAssign {
x: Symbol {
kind: Kind::Label(Global::Stack),
type_hint: Type::Word,
},
index: Symbol {
kind: Kind::Label(Global::StackHeight),
type_hint: Type::Int(4),
},
y: Symbol {
kind: Kind::Constant(U256::one()),
type_hint: Type::Bytes(1),
},
},
Instruction::BinaryAssign {
x: Symbol {
kind: Kind::Label(Global::StackHeight),
type_hint: Type::Int(4),
},
y: Symbol {
kind: Kind::Label(Global::StackHeight),
type_hint: Type::Int(4),
},
operator: Operator::Add,
z: Symbol {
kind: Kind::Constant(U256::one()),
type_hint: Type::Int(4),
},
},
];
assert_eq!(expected, result);
}
}
+10
View File
@@ -0,0 +1,10 @@
pub mod cfg;
pub mod instruction;
pub mod symbol;
#[cfg(test)]
mod tests {
#[test]
fn it_works() {}
}
+143
View File
@@ -0,0 +1,143 @@
use indexmap::IndexSet;
use primitive_types::U256;
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Kind {
Constant(U256),
Temporary(usize),
Label(Global),
}
impl Kind {
pub fn from_be_bytes(bytes: &[u8]) -> Self {
Self::Constant(U256::from_big_endian(bytes))
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub struct Symbol {
pub kind: Kind,
pub type_hint: Type,
}
impl Symbol {
fn global(symbol: Global) -> Self {
let type_hint = match symbol {
Global::StackHeight => Type::Int(4),
_ => Default::default(),
};
Self::new(Kind::Label(symbol), type_hint)
}
fn new(kind: Kind, type_hint: Type) -> Self {
Self { kind, type_hint }
}
}
#[derive(Debug, PartialEq, Eq, Hash, Default, Clone, Copy)]
pub enum Type {
#[default]
Word,
Int(usize),
Bytes(usize),
Bool,
}
impl Type {
pub fn pointer() -> Self {
Self::Int(4)
}
}
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Global {
/// Pointer
Stack,
/// Stack height variable
StackHeight,
/// Pointer
CallData,
/// Pointer
Memory,
/// Pointer
ReturnData,
/// Low level `memcpy` like function
MemoryCopy,
// EVM
Sha3,
Address,
CallDataLoad,
CallDataSize,
CallDataCopy,
CodeSize,
CodeCopy,
GasPrice,
ExtCodeSize,
ExtCodeCopy,
ReturnDataSize,
ReturnDataCopy,
ExtCodeHash,
BlockHash,
Coinbase,
Timestamp,
BlockNumber,
PrevRanDao,
GasLimit,
ChainId,
SelfBalance,
BaseFee,
SLoad,
SStore,
Gas,
Create,
Create2,
Call,
StaticCall,
DelegateCall,
CallCode,
Return,
Stop,
Revert,
SelfDestruct,
Event,
}
#[derive(Debug, Default)]
pub struct SymbolTable {
symbols: IndexSet<Symbol>,
nonce: usize,
}
impl SymbolTable {
fn next(&mut self) -> usize {
let current = self.nonce;
self.nonce += 1;
current
}
pub fn temporary(&mut self, type_hint: Option<Type>) -> Symbol {
let id = self.next();
let symbol = Symbol::new(Kind::Temporary(id), type_hint.unwrap_or_default());
assert!(self.symbols.insert(symbol));
symbol
}
pub fn constant(&mut self, value: U256, type_hint: Option<Type>) -> Symbol {
let symbol = Symbol::new(Kind::Constant(value), type_hint.unwrap_or_default());
self.symbols.insert(symbol);
symbol
}
pub fn global(&mut self, label: Global) -> Symbol {
let symbol = Symbol::global(label);
self.symbols.insert(symbol);
symbol
}
}
+1
View File
@@ -0,0 +1 @@
600035565b607f600052610014565b60ff6000525b60206000f3
+1
View File
@@ -0,0 +1 @@
608060405234801561000f575f80fd5b50600436106100f0575f3560e01c806370a082311161009357806395d89b411161006357806395d89b41146101ef578063a9059cbb146101f7578063dd62ed3e1461020a578063f2fde38b14610242575f80fd5b806370a0823114610191578063715018a6146101b957806379cc6790146101c15780638da5cb5b146101d4575f80fd5b806323b872dd116100ce57806323b872dd14610147578063313ce5671461015a57806340c10f191461016957806342966c681461017e575f80fd5b806306fdde03146100f4578063095ea7b31461011257806318160ddd14610135575b5f80fd5b6100fc610255565b604051610109919061078f565b60405180910390f35b6101256101203660046107f6565b6102e5565b6040519015158152602001610109565b6002545b604051908152602001610109565b61012561015536600461081e565b6102fe565b60405160128152602001610109565b61017c6101773660046107f6565b610321565b005b61017c61018c366004610857565b610337565b61013961019f36600461086e565b6001600160a01b03165f9081526020819052604090205490565b61017c610344565b61017c6101cf3660046107f6565b610357565b6005546040516001600160a01b039091168152602001610109565b6100fc61036c565b6101256102053660046107f6565b61037b565b61013961021836600461088e565b6001600160a01b039182165f90815260016020908152604080832093909416825291909152205490565b61017c61025036600461086e565b610388565b606060038054610264906108bf565b80601f0160208091040260200160405190810160405280929190818152602001828054610290906108bf565b80156102db5780601f106102b2576101008083540402835291602001916102db565b820191905f5260205f20905b8154815290600101906020018083116102be57829003601f168201915b5050505050905090565b5f336102f28185856103c7565b60019150505b92915050565b5f3361030b8582856103d9565b610316858585610454565b506001949350505050565b6103296104b1565b61033382826104de565b5050565b6103413382610512565b50565b61034c6104b1565b6103555f610546565b565b6103628233836103d9565b6103338282610512565b606060048054610264906108bf565b5f336102f2818585610454565b6103906104b1565b6001600160a01b0381166103be57604051631e4fbdf760e01b81525f60048201526024015b60405180910390fd5b61034181610546565b6103d48383836001610597565b505050565b6001600160a01b038381165f908152600160209081526040808320938616835292905220545f19811461044e578181101561044057604051637dc7a0d960e11b81526001600160a01b038416600482015260248101829052604481018390526064016103b5565b61044e84848484035f610597565b50505050565b6001600160a01b03831661047d57604051634b637e8f60e11b81525f60048201526024016103b5565b6001600160a01b0382166104a65760405163ec442f0560e01b81525f60048201526024016103b5565b6103d4838383610669565b6005546001600160a01b031633146103555760405163118cdaa760e01b81523360048201526024016103b5565b6001600160a01b0382166105075760405163ec442f0560e01b81525f60048201526024016103b5565b6103335f8383610669565b6001600160a01b03821661053b57604051634b637e8f60e11b81525f60048201526024016103b5565b610333825f83610669565b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0905f90a35050565b6001600160a01b0384166105c05760405163e602df0560e01b81525f60048201526024016103b5565b6001600160a01b0383166105e957604051634a1406b160e11b81525f60048201526024016103b5565b6001600160a01b038085165f908152600160209081526040808320938716835292905220829055801561044e57826001600160a01b0316846001600160a01b03167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b9258460405161065b91815260200190565b60405180910390a350505050565b6001600160a01b038316610693578060025f82825461068891906108f7565b909155506107039050565b6001600160a01b0383165f90815260208190526040902054818110156106e55760405163391434e360e21b81526001600160a01b038516600482015260248101829052604481018390526064016103b5565b6001600160a01b0384165f9081526020819052604090209082900390555b6001600160a01b03821661071f5760028054829003905561073d565b6001600160a01b0382165f9081526020819052604090208054820190555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161078291815260200190565b60405180910390a3505050565b5f602080835283518060208501525f5b818110156107bb5785810183015185820160400152820161079f565b505f604082860101526040601f19601f8301168501019250505092915050565b80356001600160a01b03811681146107f1575f80fd5b919050565b5f8060408385031215610807575f80fd5b610810836107db565b946020939093013593505050565b5f805f60608486031215610830575f80fd5b610839846107db565b9250610847602085016107db565b9150604084013590509250925092565b5f60208284031215610867575f80fd5b5035919050565b5f6020828403121561087e575f80fd5b610887826107db565b9392505050565b5f806040838503121561089f575f80fd5b6108a8836107db565b91506108b6602084016107db565b90509250929050565b600181811c908216806108d357607f821691505b6020821081036108f157634e487b7160e01b5f52602260045260245ffd5b50919050565b808201808211156102f857634e487b7160e01b5f52601160045260245ffdfea26469706673582212203436d2f76da96888f84c631a86a77acc02d0494c4ed226857c2872074984910064736f6c63430008170033
+1
View File
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
600035600160009160025b818111601c576001019180930191600a565b505060005260206000f350
+1
View File
@@ -0,0 +1 @@
600035600160009160025b818111601c576001019180930191600a565b505060005260206000f350