Skip to content

Commit 096b488

Browse files
authored
Merge pull request #56 from refcell/nox/internal-return-type
fix(ir): derive internal function return types from declarations
2 parents efb95c4 + e66016d commit 096b488

6 files changed

Lines changed: 201 additions & 1 deletion

File tree

crates/e2e/tests/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ mod generics_exec;
2525
mod generics_negative;
2626
#[path = "suites/inline_asm_exec.rs"]
2727
mod inline_asm_exec;
28+
#[path = "suites/internal_fn_types.rs"]
29+
mod internal_fn_types;
2830
#[path = "suites/packed_exec.rs"]
2931
mod packed_exec;
3032
#[path = "suites/packed_storage_exec.rs"]
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
#![allow(missing_docs)]
2+
3+
//! Tests that internal (non-pub) functions get correct return type annotations
4+
//! in the IR, regardless of their declared return type.
5+
6+
use std::path::PathBuf;
7+
8+
use edge_driver::{
9+
compiler::Compiler,
10+
config::{CompilerConfig, EmitKind},
11+
};
12+
use revm::{
13+
context::{Context, TxEnv},
14+
database::{CacheDB, EmptyDB},
15+
handler::{MainBuilder, MainnetContext},
16+
primitives::{Address, Bytes, TxKind, U256},
17+
state::AccountInfo,
18+
ExecuteCommitEvm, MainContext, MainnetEvm,
19+
};
20+
use tiny_keccak::{Hasher, Keccak};
21+
22+
fn workspace_root() -> PathBuf {
23+
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../..")
24+
}
25+
26+
fn compile_contract(relative_path: &str) -> Vec<u8> {
27+
let path = workspace_root().join(relative_path);
28+
let mut config = CompilerConfig::new(path);
29+
config.emit = EmitKind::Bytecode;
30+
let mut compiler = Compiler::new(config).expect("compiler init failed");
31+
let output = compiler.compile().expect("compile failed");
32+
output.bytecode.expect("no bytecode produced")
33+
}
34+
35+
fn selector(sig: &str) -> [u8; 4] {
36+
let mut h = Keccak::v256();
37+
h.update(sig.as_bytes());
38+
let mut out = [0u8; 32];
39+
h.finalize(&mut out);
40+
[out[0], out[1], out[2], out[3]]
41+
}
42+
43+
fn encode_u256(val: u64) -> [u8; 32] {
44+
let mut out = [0u8; 32];
45+
out[24..].copy_from_slice(&val.to_be_bytes());
46+
out
47+
}
48+
49+
fn decode_u256(output: &[u8]) -> u64 {
50+
assert!(
51+
output.len() >= 32,
52+
"return value too short: {} bytes",
53+
output.len()
54+
);
55+
assert_eq!(&output[0..24], &[0u8; 24], "u256 too large for u64");
56+
u64::from_be_bytes(output[24..32].try_into().unwrap())
57+
}
58+
59+
fn calldata(sig: &str, args: &[[u8; 32]]) -> Vec<u8> {
60+
let mut cd = selector(sig).to_vec();
61+
for arg in args {
62+
cd.extend_from_slice(arg);
63+
}
64+
cd
65+
}
66+
67+
const CALLER: Address = Address::ZERO;
68+
69+
struct EvmHandle {
70+
evm: MainnetEvm<MainnetContext<CacheDB<EmptyDB>>>,
71+
contract: Address,
72+
nonce: u64,
73+
}
74+
75+
impl EvmHandle {
76+
fn new(deploy_bytecode: Vec<u8>) -> Self {
77+
let mut db = CacheDB::<EmptyDB>::default();
78+
db.insert_account_info(
79+
CALLER,
80+
AccountInfo {
81+
balance: U256::from(u64::MAX),
82+
nonce: 0,
83+
..Default::default()
84+
},
85+
);
86+
let mut evm = Context::mainnet().with_db(db).build_mainnet();
87+
let tx = TxEnv::builder()
88+
.caller(CALLER)
89+
.kind(TxKind::Create)
90+
.data(Bytes::from(deploy_bytecode))
91+
.gas_limit(10_000_000)
92+
.nonce(0)
93+
.build()
94+
.unwrap();
95+
let result = evm.transact_commit(tx).unwrap();
96+
assert!(result.is_success(), "Deployment failed: {result:#?}");
97+
let contract = CALLER.create(0);
98+
Self {
99+
evm,
100+
contract,
101+
nonce: 1,
102+
}
103+
}
104+
105+
fn call(&mut self, cd: Vec<u8>) -> (bool, Vec<u8>) {
106+
let tx = TxEnv::builder()
107+
.caller(CALLER)
108+
.kind(TxKind::Call(self.contract))
109+
.data(Bytes::from(cd))
110+
.nonce(self.nonce)
111+
.gas_limit(10_000_000)
112+
.build()
113+
.unwrap();
114+
let result = self.evm.transact_commit(tx).unwrap();
115+
self.nonce += 1;
116+
let success = result.is_success();
117+
let output = result.output().map(|b| b.to_vec()).unwrap_or_default();
118+
(success, output)
119+
}
120+
}
121+
122+
// =============================================================================
123+
// Test 1: Internal function returning bool
124+
// =============================================================================
125+
126+
#[test]
127+
fn internal_fn_returning_bool() {
128+
let bc = compile_contract("examples/tests/internal_bool.edge");
129+
let mut evm = EvmHandle::new(bc);
130+
131+
let (ok, out) = evm.call(calldata("is_positive(uint256)", &[encode_u256(42)]));
132+
assert!(ok, "call failed");
133+
assert_eq!(decode_u256(&out), 1, "is_positive(42) should be true (1)");
134+
135+
let (ok, out) = evm.call(calldata("is_positive(uint256)", &[encode_u256(0)]));
136+
assert!(ok, "call failed");
137+
assert_eq!(decode_u256(&out), 0, "is_positive(0) should be false (0)");
138+
}
139+
140+
// =============================================================================
141+
// Test 2: Internal function returning u256 (regression)
142+
// =============================================================================
143+
144+
#[test]
145+
fn internal_fn_returning_u256() {
146+
let bc = compile_contract("examples/tests/internal_math.edge");
147+
let mut evm = EvmHandle::new(bc);
148+
149+
let (ok, out) = evm.call(calldata("get_double(uint256)", &[encode_u256(21)]));
150+
assert!(ok, "call failed");
151+
assert_eq!(decode_u256(&out), 42, "get_double(21) should be 42");
152+
}
153+
154+
// =============================================================================
155+
// Test 3: Storage set/get (regression, no internal fn)
156+
// =============================================================================
157+
158+
#[test]
159+
fn internal_fn_storage_regression() {
160+
let bc = compile_contract("examples/tests/internal_void.edge");
161+
let mut evm = EvmHandle::new(bc);
162+
163+
let (ok, _) = evm.call(calldata("set_value(uint256)", &[encode_u256(99)]));
164+
assert!(ok, "set_value call failed");
165+
166+
let (ok, out) = evm.call(calldata("get_value()", &[]));
167+
assert!(ok, "get_value call failed");
168+
assert_eq!(decode_u256(&out), 99, "get_value() should be 99");
169+
}

crates/ir/src/to_egglog/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ impl Scope {
110110
}
111111
}
112112

113-
/// A contract function: (name, params, body).
113+
/// A contract function: (name, params, returns, body).
114114
pub(crate) type ContractFunction = (
115115
String,
116116
Vec<(String, edge_ast::ty::TypeSig)>,

examples/tests/internal_bool.edge

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
contract BoolTest {
2+
fn check_positive(x: u256) -> (bool) {
3+
return x > 0;
4+
}
5+
6+
pub fn is_positive(x: u256) -> (bool) {
7+
return check_positive(x);
8+
}
9+
}

examples/tests/internal_math.edge

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
contract MathTest {
2+
fn double(x: u256) -> (u256) {
3+
return x * 2;
4+
}
5+
6+
pub fn get_double(x: u256) -> (u256) {
7+
return double(x);
8+
}
9+
}

examples/tests/internal_void.edge

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
contract VoidTest {
2+
let value: &s u256;
3+
4+
pub fn set_value(x: u256) {
5+
value = x;
6+
}
7+
8+
pub fn get_value() -> (u256) {
9+
return value;
10+
}
11+
}

0 commit comments

Comments
 (0)