Skip to content

Commit f88dd77

Browse files
committed
dispatch: move selector computation to runtime
1 parent b2c9d2e commit f88dd77

File tree

2 files changed

+119
-23
lines changed

2 files changed

+119
-23
lines changed

std/dispatch.solc

Lines changed: 93 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,85 @@ data Fallback(payability, args, rets, fn) = Fallback(payability, args, rets, fn)
2121

2222
// --- Method Selectors ---
2323

24-
// For each method in a contract the compiler generates a unique type and
25-
// produces a `Selector` instance for that type that returns the selector hash
26-
forall nm . class nm:Selector {
27-
function hash(prx: Proxy(nm)) -> word;
24+
// The string representation of abi types used during selector computation
25+
forall ty . class ty:ABIString {
26+
function append(head : word, tail : word, prx : Proxy(ty)) -> word;
2827
}
2928

30-
// Method has a Selector if its name has a Selector
31-
forall name payability args rets fn . name:Selector => instance Method(name,payability,args,rets,fn):Selector {
32-
function hash(prx: Proxy(Method(name,payability,args,rets,fn))) -> word {
33-
return Selector.hash(Proxy : Proxy(name));
34-
}
29+
instance uint256:ABIString {
30+
function append(head : word, tail : word, prx : Proxy(uint256)) -> word {
31+
let size : word = 7;
32+
assembly {
33+
mstore(head, add(mload(head), size))
34+
mstore(tail, 0x75696e7432353600000000000000000000000000000000000000000000000000)
35+
}
36+
return Add.add(tail, size);
37+
}
38+
}
39+
40+
instance ():ABIString {
41+
function append(head : word, tail : word, prx : Proxy(())) -> word {
42+
return(tail);
43+
}
44+
}
45+
46+
function append_left_bracket(head : word, tail : word) -> word {
47+
let size = 1;
48+
assembly {
49+
mstore(head, add(mload(head), size));
50+
mstore(tail, 0x2800000000000000000000000000000000000000000000000000000000000000);
51+
}
52+
return Add.add(tail, size);
53+
}
54+
55+
function append_right_bracket(head : word, tail : word) -> word {
56+
let size = 1;
57+
assembly {
58+
mstore(head, add(mload(head), size));
59+
mstore(tail, 0x2900000000000000000000000000000000000000000000000000000000000000);
60+
}
61+
return Add.add(tail, size);
62+
}
63+
64+
function append_comma(head : word, tail : word) -> word {
65+
let size = 1;
66+
assembly {
67+
mstore(head, add(mload(head), size));
68+
mstore(tail, 0x2c00000000000000000000000000000000000000000000000000000000000000);
69+
}
70+
return Add.add(tail, size);
71+
}
72+
73+
forall l r . l:ABIString, r:ABIString => instance (l,r):ABIString {
74+
function append(head : word, tail : word, prx : Proxy((l,r))) -> word {
75+
let with_l = ABIString.append(head, tail, Proxy : Proxy(l));
76+
let with_comma = append_comma(head, with_l);
77+
return ABIString.append(head, with_comma, Proxy : Proxy(r));
78+
}
79+
}
80+
81+
forall ty . class ty:Selector {
82+
function compute(prx : Proxy(ty)) -> bytes4;
83+
}
84+
85+
// Computes the selector hash for a given method
86+
// this is a class with a single instance since it made some of the downstream definitions a bit cleaner to define
87+
forall name payability args rets fn
88+
. name:ABIString
89+
, args:ABIString
90+
=> instance Method(name,payability,Proxy(args),Proxy(rets),fn):Selector {
91+
function compute(prx : Proxy(Method(name,payability,Proxy(args),Proxy(rets),fn))) -> bytes4 {
92+
let head = get_free_memory();
93+
let tail = Add.add(head, 32);
94+
tail = ABIString.append(head, tail, Proxy : Proxy(name));
95+
tail = append_left_bracket(head, tail);
96+
tail = ABIString.append(head, tail, Proxy : Proxy(args));
97+
tail = append_right_bracket(head, tail);
98+
99+
let res : word;
100+
assembly { res := shr(224, keccak256(add(head, 32), mload(head))) }
101+
return bytes4(res);
102+
}
35103
}
36104

37105
// --- Method Execution ---
@@ -162,13 +230,13 @@ forall n m . n:ExecMethod, n:Selector, m:RunDispatch => instance (n,m):RunDispat
162230
}
163231

164232
// TODO: we only wanna do the calldataload once
165-
// Given evidence of a name with a known selector, we can check if it matches the selector in the first four bytes of calldata
166-
forall name . name:Selector => function selector_matches(prx : Proxy(name)) -> Bool {
167-
let hash = Selector.hash(prx);
233+
// Given evidence of a type with a known selector, we can check if it matches the selector in the first four bytes of calldata
234+
forall ty . ty:Selector => function selector_matches(prx : Proxy(ty)) -> Bool {
235+
let candidate = Typedef.rep(Selector.compute(prx));
168236
let res : word;
169237
assembly {
170238
let sel := shr(224, calldataload(0));
171-
res := eq(sel, hash);
239+
res := eq(sel, candidate);
172240
}
173241
match res {
174242
| 0 => return False;
@@ -264,14 +332,17 @@ function revert_handler() -> () {
264332
assembly { revert(0,0) }
265333
}
266334

267-
data C_Add2_Selector = C_Add2_Selector;
335+
data C_Add2_Name = C_Add2_Name;
268336

269-
instance C_Add2_Selector:Selector {
270-
function hash(prx: Proxy(C_Add2_Selector)) -> word {
271-
// This would be keccak256("add2(uint256,uint256)") >> 224
272-
// Compiler computes this at compile time
273-
return 0x29fcda33; // placeholder value
274-
}
337+
instance C_Add2_Name:ABIString {
338+
function append(head : word, tail : word, prx : Proxy(C_Add2_Name)) -> word {
339+
let size = 4;
340+
assembly {
341+
mstore(head, add(mload(head), size));
342+
mstore(tail, 0x6164643200000000000000000000000000000000000000000000000000000000);
343+
}
344+
return Add.add(tail, size);
345+
}
275346
}
276347

277348
// transform
@@ -281,13 +352,12 @@ contract C {
281352
return Add.add(x,y);
282353
}
283354

284-
function main() -> word {
355+
function main() -> () {
285356
let c = Contract(
286-
Method(C_Add2_Selector, Proxy : Proxy(NonPayable), Proxy : Proxy((uint256,uint256)), Proxy : Proxy(uint256), add2),
357+
Method(C_Add2_Name, Proxy : Proxy(NonPayable), Proxy : Proxy((uint256,uint256)), Proxy : Proxy(uint256), add2),
287358
Fallback(Proxy : Proxy(NonPayable), Proxy : Proxy(()), Proxy : Proxy(()),revert_handler)
288359
);
289360

290361
RunContract.exec(c);
291-
return 0;
292362
}
293363
}

std/std.solc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,21 @@ instance byte:Typedef(word) {
174174
}
175175
}
176176

177+
// --- Bytes4 ---
178+
179+
data bytes4 = bytes4(word);
180+
181+
instance bytes4:Typedef(word) {
182+
function rep(b : bytes4) -> word {
183+
match b {
184+
| bytes4(w) => return w;
185+
}
186+
}
187+
function abs(w : word) -> bytes4 {
188+
return bytes4(w);
189+
}
190+
}
191+
177192
// --- Pointers ---
178193

179194
data memory(t) = memory(word);
@@ -245,6 +260,12 @@ function set_free_memory(loc : word) {
245260
assembly { mstore(0x40, loc) }
246261
}
247262

263+
function allocate_memory(size : word) -> word {
264+
let ptr = get_free_memory();
265+
set_free_memory(Add.add(ptr,size));
266+
return ptr;
267+
}
268+
248269
// --- Indexable Types ---
249270

250271
// types that can be written to and read from at a uint256 index
@@ -313,6 +334,11 @@ forall t . function allocateDynamicArray(prx : Proxy(t), length : word) -> memor
313334
// TODO: IndexAccess for storage(bytes)
314335
data bytes;
315336

337+
// --- strings ---
338+
339+
// TODO: should this be a typedef over `bytes`?
340+
data string;
341+
316342
// --- slices (sized pointers) ---
317343

318344
// A slice is a wrapper around an existing pointer type that extends the

0 commit comments

Comments
 (0)