Skip to content

Commit 7b4c746

Browse files
committed
Proof-of-concept for evaluator.
1 parent d0b7bb3 commit 7b4c746

File tree

4 files changed

+218
-15
lines changed

4 files changed

+218
-15
lines changed

src/encoder.rs

+18-4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::evaluator::Evaluator;
12
use crate::evm_context;
23
use crate::execution_position::ExecutionPositionManager;
34
use crate::smt::{self, SMTExpr, SMTFormat, SMTSort, SMTStatement, SMTVariable};
@@ -29,6 +30,7 @@ pub struct Encoder<InstructionsType> {
2930
ssa_tracker: SSATracker,
3031
output: Vec<SMTStatement>,
3132
interpreter: InstructionsType,
33+
evaluator: Evaluator,
3234
loop_unroll: u64,
3335
path_conditions: Vec<SMTExpr>,
3436
execution_position: ExecutionPositionManager,
@@ -155,11 +157,18 @@ impl<InstructionsType: Instructions> Encoder<InstructionsType> {
155157
/// Encodes the given function, potentially re-creating copies of all local variables
156158
/// if called multiple times.
157159
/// @returns the names of the function parameters and return variables.
158-
pub fn encode_function(&mut self, function: &FunctionDefinition) -> FunctionVariables {
159-
for param in &function.parameters {
160+
pub fn encode_function(
161+
&mut self,
162+
function: &FunctionDefinition,
163+
arguments: &Vec<SMTVariable>,
164+
) -> FunctionVariables {
165+
// TODO also equate at this point for SMT, not after the call to encode_function
166+
for (i, param) in function.parameters.iter().enumerate() {
160167
let var = self.ssa_tracker.introduce_variable(param);
168+
self.evaluator.define_from_variable(&var, &arguments[i]);
161169
self.out(smt::declare_const(var))
162170
}
171+
163172
let parameters = self.ssa_tracker.to_smt_variables(&function.parameters);
164173
self.encode_variable_declaration(&VariableDeclaration {
165174
variables: function.returns.clone(),
@@ -344,6 +353,7 @@ impl<InstructionsType: Instructions> Encoder<InstructionsType> {
344353
fn encode_literal(&mut self, literal: &Literal) -> SMTVariable {
345354
let sort = SMTSort::BV(256);
346355
let var = self.new_temporary_variable(sort);
356+
self.evaluator.define_from_literal(&var, literal);
347357
self.out(smt::define_const(
348358
var.clone(),
349359
self.encode_literal_value(literal),
@@ -356,7 +366,7 @@ impl<InstructionsType: Instructions> Encoder<InstructionsType> {
356366
}
357367

358368
fn encode_function_call(&mut self, call: &FunctionCall) -> Vec<SMTVariable> {
359-
let arguments = call
369+
let arguments: Vec<SMTVariable> = call
360370
.arguments
361371
.iter()
362372
.rev()
@@ -375,6 +385,9 @@ impl<InstructionsType: Instructions> Encoder<InstructionsType> {
375385
.map(|_i| self.new_temporary_variable(SMTSort::BV(256)))
376386
.collect();
377387

388+
// TODO call evaluator first or interpreter first?
389+
self.evaluator
390+
.builtin_call(builtin, &arguments, &return_vars);
378391
let result = self.interpreter.encode_builtin_call(
379392
builtin,
380393
arguments,
@@ -389,7 +402,7 @@ impl<InstructionsType: Instructions> Encoder<InstructionsType> {
389402
IdentifierID::Reference(id) => {
390403
let fun_def = self.function_definitions[&id].clone();
391404

392-
let function_vars = self.encode_function(&fun_def);
405+
let function_vars = self.encode_function(&fun_def, &arguments);
393406
assert!(arguments.len() == function_vars.parameters.len());
394407
arguments
395408
.into_iter()
@@ -422,6 +435,7 @@ impl<T> Encoder<T> {
422435

423436
for (v, val) in variables.iter().zip(values.into_iter()) {
424437
let var = self.ssa_tracker.allocate_new_ssa_index(v);
438+
self.evaluator.define_from_variable(&var, &val);
425439
self.out(smt::define_const(var, val.into()));
426440
}
427441
}

src/evaluator.rs

+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use std::collections::HashMap;
2+
3+
use num_bigint::BigUint;
4+
use num_traits::{Num, ToPrimitive};
5+
use yultsur::{dialect::Builtin, yul::Literal};
6+
7+
use crate::smt::SMTVariable;
8+
9+
#[derive(Default)]
10+
pub struct Evaluator {
11+
ssa_values: HashMap<String, BigUint>,
12+
storage: HashMap<BigUint, BigUint>,
13+
}
14+
15+
// TODO
16+
// As soon as we handle side-effecty things, we might have to include
17+
// "executing regularly":
18+
// sstore(0, 1)
19+
// stop()
20+
// sload(0)
21+
//
22+
// Also, storage needs to take branches into account.
23+
// if x { sstore(0, 1) }
24+
// let t := sload(0)
25+
//
26+
// If storage is not changed in a branch, its ssa variable is not updated.
27+
// If all storage writes are to compile-time constant variables,
28+
// then the Evaluator can deal with the branch joins.
29+
// Which means we have to have a mechanism to craete a snapshot of the state (like the SSATracker does)
30+
// and then combine snapshots.
31+
//
32+
// Because of packed storage, the values we store should ideally be byte-wise.
33+
// For now, the following could be OK:
34+
// It's one of the following:
35+
// - unknown
36+
// - a symbolic value in the lower 20 bytes (the rest of the bytes are unknown)
37+
// - a concrete value
38+
39+
impl Evaluator {
40+
pub fn define_from_literal(&mut self, var: &SMTVariable, literal: &Literal) {
41+
let value = &literal.literal;
42+
//println!("{} := {literal}", var.name);
43+
self.ssa_values.insert(
44+
var.name.clone(),
45+
if let Some(hex) = value.strip_prefix("0x") {
46+
BigUint::from_str_radix(hex, 16).unwrap()
47+
} else {
48+
BigUint::from_str_radix(value, 10).unwrap()
49+
},
50+
);
51+
}
52+
pub fn define_from_variable(&mut self, var: &SMTVariable, value: &SMTVariable) {
53+
//println!("{} := {}", var.name, value.name);
54+
if let Some(value) = self.ssa_values.get(&value.name).cloned() {
55+
self.ssa_values.insert(var.name.clone(), value);
56+
}
57+
}
58+
pub fn builtin_call(
59+
&mut self,
60+
builtin: &Builtin,
61+
arguments: &Vec<SMTVariable>,
62+
return_vars: &[SMTVariable],
63+
) {
64+
if builtin.name == "create" {
65+
self.ssa_values
66+
.insert(return_vars[0].name.clone(), BigUint::from(1234u64));
67+
}
68+
if builtin.name == "sstore" {
69+
if let (Some(key), Some(value)) = (
70+
self.ssa_values.get(&arguments[0].name).cloned(),
71+
self.ssa_values.get(&arguments[1].name).cloned(),
72+
) {
73+
self.storage.insert(key, value);
74+
}
75+
}
76+
if builtin.name == "sload" {
77+
if let Some(key) = self.ssa_values.get(&arguments[0].name).cloned() {
78+
if let Some(value) = self.storage.get(&key).cloned() {
79+
self.ssa_values.insert(return_vars[0].name.clone(), value);
80+
} else {
81+
// TODO assume unknown storage is some weird value - should use unknown bits later
82+
self.ssa_values.insert(
83+
return_vars[0].name.clone(),
84+
BigUint::from(0x1234567812345678u64),
85+
);
86+
}
87+
}
88+
}
89+
if matches!(
90+
builtin.name,
91+
"add" | "sub" | "mul" | "div" | "shl" | "shr" | "and" | "or"
92+
) {
93+
if let (Some(left), Some(right)) = (
94+
self.ssa_values.get(&arguments[0].name).cloned(),
95+
self.ssa_values.get(&arguments[1].name).cloned(),
96+
) {
97+
let two256 = BigUint::from(1u64) << 256;
98+
let result = match builtin.name {
99+
"add" => wrap(left + right),
100+
"sub" => wrap(left + two256 - right),
101+
"shl" => {
102+
if left >= 256u64.into() {
103+
BigUint::from(0u64)
104+
} else {
105+
wrap(right << left.to_u64().unwrap())
106+
}
107+
}
108+
"shr" => {
109+
if left >= 256u64.into() {
110+
BigUint::from(0u64)
111+
} else {
112+
wrap(right >> left.to_u64().unwrap())
113+
}
114+
}
115+
"and" => left & right,
116+
"or" => left | right,
117+
_ => panic!(),
118+
};
119+
self.ssa_values.insert(return_vars[0].name.clone(), result);
120+
}
121+
}
122+
if matches!(builtin.name, "not" | "iszero") {
123+
if let Some(arg) = self.ssa_values.get(&arguments[0].name).cloned() {
124+
let mask = (BigUint::from(1u64) << 256) - BigUint::from(1u64);
125+
let result = match builtin.name {
126+
"not" => arg ^ mask,
127+
"iszero" => {
128+
if arg == BigUint::from(0u64) {
129+
BigUint::from(1u64)
130+
} else {
131+
BigUint::from(0u64)
132+
}
133+
}
134+
_ => panic!(),
135+
};
136+
self.ssa_values.insert(return_vars[0].name.clone(), result);
137+
}
138+
}
139+
match builtin.name {
140+
"create" | "sstore" | "sload" | "call" => {
141+
println!(
142+
"{}{}({})",
143+
if return_vars.is_empty() {
144+
String::new()
145+
} else {
146+
format!(
147+
"{} := ",
148+
return_vars
149+
.iter()
150+
.map(|v| v.name.to_owned())
151+
.collect::<Vec<_>>()
152+
.join(", ")
153+
)
154+
},
155+
builtin.name,
156+
arguments
157+
.iter()
158+
.map(|v| v.name.to_owned())
159+
.collect::<Vec<_>>()
160+
.join(", ")
161+
);
162+
for (name, v) in arguments
163+
.iter()
164+
.map(|v| (v.name.clone(), self.ssa_values.get(&v.name)))
165+
{
166+
if let Some(v) = v {
167+
println!(" - {name} = {v}");
168+
}
169+
}
170+
for (name, v) in return_vars
171+
.iter()
172+
.map(|v| (v.name.clone(), self.ssa_values.get(&v.name)))
173+
{
174+
if let Some(v) = v {
175+
println!(" - {name} = {v}");
176+
}
177+
}
178+
}
179+
_ => {}
180+
}
181+
}
182+
}
183+
184+
fn wrap(mut x: BigUint) -> BigUint {
185+
let mask = (BigUint::from(1u64) << 256) - BigUint::from(1u64);
186+
// TODO optimization: work directly on limbs
187+
x & mask
188+
}

src/evm_builtins.rs

+11-11
Original file line numberDiff line numberDiff line change
@@ -99,14 +99,14 @@ impl Instructions for EVMInstructions {
9999
"calldatasize" => single_return(evm_context::calldatasize(ssa).into()),
100100
"calldatacopy" => panic!("Builtin {} not implemented", builtin.name), // TODO
101101
"codesize" => single_return(evm_context::codesize(ssa).into()),
102-
"codecopy" => panic!("Builtin {} not implemented", builtin.name), // TODO
102+
"codecopy" => vec![], //panic!("Builtin {} not implemented", builtin.name), // TODO
103103
"gasprice" => single_return(evm_context::gasprice(ssa).into()),
104-
"extcodesize" => panic!("Builtin {} not implemented", builtin.name), // TODO
104+
"extcodesize" => vec![smt::declare_const(return_vars[0].clone())], //panic!("Builtin {} not implemented", builtin.name), // TODO
105105
"extcodecopy" => panic!("Builtin {} not implemented", builtin.name), // TODO
106-
"returndatasize" => panic!("Builtin {} not implemented", builtin.name), // TODO
107-
"returndatacopy" => panic!("Builtin {} not implemented", builtin.name), // TODO
106+
"returndatasize" => vec![smt::declare_const(return_vars[0].clone())], //panic!("Builtin {} not implemented", builtin.name), // TODO
107+
"returndatacopy" => vec![], //panic!("Builtin {} not implemented", builtin.name), // TODO
108108
"extcodehash" => panic!("Builtin {} not implemented", builtin.name), // TODO
109-
"blockhash" => panic!("Builtin {} not implemented", builtin.name), // TODO
109+
"blockhash" => panic!("Builtin {} not implemented", builtin.name), // TODO
110110
"coinbase" => single_return(evm_context::coinbase(ssa).into()),
111111
"timestamp" => single_return(evm_context::timestamp(ssa).into()),
112112
"number" => single_return(evm_context::number(ssa).into()),
@@ -134,12 +134,12 @@ impl Instructions for EVMInstructions {
134134
ssa,
135135
)],
136136
"msize" => panic!("Builtin {} not implemented", builtin.name), // TODO
137-
"gas" => panic!("Builtin {} not implemented", builtin.name), // TODO
138-
"log0" => panic!("Builtin {} not implemented", builtin.name), // TODO
139-
"log1" => panic!("Builtin {} not implemented", builtin.name), // TODO
140-
"log2" => panic!("Builtin {} not implemented", builtin.name), // TODO
141-
"log3" => panic!("Builtin {} not implemented", builtin.name), // TODO
142-
"log4" => panic!("Builtin {} not implemented", builtin.name), // TODO
137+
"gas" => vec![smt::declare_const(return_vars[0].clone())], //panic!("Builtin {} not implemented", builtin.name), // TODO
138+
"log0" => panic!("Builtin {} not implemented", builtin.name), // TODO
139+
"log1" => panic!("Builtin {} not implemented", builtin.name), // TODO
140+
"log2" => panic!("Builtin {} not implemented", builtin.name), // TODO
141+
"log3" => panic!("Builtin {} not implemented", builtin.name), // TODO
142+
"log4" => panic!("Builtin {} not implemented", builtin.name), // TODO
143143
"create" => evm_context::create(
144144
arg_0.unwrap().into(),
145145
MemoryRange::new(arg_1.unwrap(), arg_2.unwrap()),

src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod cfg;
22
pub mod encoder;
3+
pub mod evaluator;
34
pub mod evm_builtins;
45
pub mod evm_context;
56
pub mod execution_position;

0 commit comments

Comments
 (0)