Skip to content

Commit 9f02805

Browse files
committed
selectors: fix FN on some vyper contracts, issue #7
1 parent 577725f commit 9f02805

File tree

3 files changed

+65
-52
lines changed

3 files changed

+65
-52
lines changed

evmole/selectors.py

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -42,26 +42,30 @@ def process(vm: Vm, gas_limit: int) -> tuple[set[bytes], int]:
4242
case (Op.MUL, _, Element('signature'), _) | (Op.MUL, _, _, Element('signature')) | (Op.SHR, _, _, Element('mulsig')):
4343
vm.stack.peek().label = 'mulsig'
4444

45-
# Vyper _selector_section_dense()
46-
case (Op.MOD, _, Element('mulsig') | Element('signature'), Element() as s1):
47-
ma = int.from_bytes(s1.data, 'big')
48-
if ma < 128:
49-
for m in range(1, ma):
50-
cloned_vm = copy.copy(vm)
51-
cloned_vm.stack.peek().data = m.to_bytes(32, 'big')
52-
s, g = process(cloned_vm, (gas_limit - gas_used) // ma)
53-
selectors.update(s)
54-
gas_used += g
55-
if gas_used > gas_limit:
56-
break
57-
vm.stack.peek().data = (0).to_bytes(32, 'big')
58-
45+
# Vyper _selector_section_dense()/_selector_section_sparse()
46+
# (sig MOD n_buckets) or (sig AND (n_buckets-1))
5947
case (
60-
(Op.SHR, _, _, Element('calldata'))
61-
| (Op.AND, _, Element('signature'), _)
62-
| (Op.AND, _, _, Element('signature'))
63-
| (Op.DIV, _, Element('calldata'), _)
48+
(Op.MOD as op, _, Element('mulsig') | Element('signature'), Element() as s1)
49+
| (Op.AND as op, _, Element('signature'), Element() as s1)
50+
| (Op.AND as op, _, Element() as s1, Element('signature'))
6451
):
52+
if op == Op.AND and s1.data == b'\x00' * 28 + b'\xff\xff\xff\xff':
53+
vm.stack.peek().label = 'signature'
54+
else:
55+
ma = int.from_bytes(s1.data, 'big')
56+
if ma < 256:
57+
to = ma if op == Op.MOD else ma + 1
58+
for m in range(1, to):
59+
cloned_vm = copy.copy(vm)
60+
cloned_vm.stack.peek().data = m.to_bytes(32, 'big')
61+
s, g = process(cloned_vm, (gas_limit - gas_used) // ma)
62+
selectors.update(s)
63+
gas_used += g
64+
if gas_used > gas_limit:
65+
break
66+
vm.stack.peek().data = (0).to_bytes(32, 'big')
67+
68+
case (Op.SHR, _, _, Element('calldata')) | (Op.DIV, _, Element('calldata'), _):
6569
v = vm.stack.peek()
6670
if v.data[-4:] == vm.calldata.data[:4]:
6771
vm.stack.peek().label = 'signature'

js/src/selectors.js

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,37 @@ function process(vm, gasLimit) {
5959
}
6060
break
6161

62-
// Vyper _selector_section_dense()
62+
// Vyper _selector_section_dense()/_selector_section_sparse()
63+
// (sig MOD n_buckets) or (sig AND (n_buckets-1))
6364
case Op.MOD:
64-
if (r0.label === 'mulsig' || r0.label === 'signature') {
65-
const rawMa = uint8ArrayToBigInt(r1.data)
66-
if (rawMa < 128n) {
67-
const ma = Number(rawMa)
68-
vm.stack.pop()
69-
for (let m = 1; m < ma && gasUsed < gasLimit; m++) {
70-
const clonedVm = vm.clone()
71-
clonedVm.stack.push_uint(BigInt(m))
72-
const [newSelectors, gas] = process(clonedVm, Math.trunc((gasLimit - gasUsed) / ma))
73-
newSelectors.forEach((v) => selectors.add(v))
74-
gasUsed += gas
65+
case Op.AND:
66+
{
67+
const p = vm.stack.peek()
68+
if (
69+
(op === Op.AND && (r0.label === 'signature' || r1.label === 'signature')) ||
70+
(op === Op.MOD && (r0.label === 'mulsig' || r0.label === 'signature'))
71+
) {
72+
const otd = op === Op.AND && r1.label == 'signature' ? r0.data : r1.data
73+
const rawMa = uint8ArrayToBigInt(otd)
74+
if (op === Op.AND && rawMa === 0xffffffffn) {
75+
p.label = 'signature'
76+
} else {
77+
if (rawMa < 256n) {
78+
const ma = Number(rawMa)
79+
vm.stack.pop()
80+
const to = op === Op.MOD ? ma : ma + 1
81+
for (let m = 1; m < to && gasUsed < gasLimit; m++) {
82+
const clonedVm = vm.clone()
83+
clonedVm.stack.push_uint(BigInt(m))
84+
const [newSelectors, gas] = process(clonedVm, Math.trunc((gasLimit - gasUsed) / ma))
85+
newSelectors.forEach((v) => selectors.add(v))
86+
gasUsed += gas
87+
}
88+
vm.stack.push_uint(0n)
89+
}
7590
}
76-
vm.stack.push_uint(0n)
91+
} else if (op === Op.AND && (r0.label === 'calldata' || r1.label === 'calldata')) {
92+
p.label = 'calldata'
7793
}
7894
}
7995
break
@@ -92,19 +108,6 @@ function process(vm, gasLimit) {
92108
}
93109
break
94110

95-
case Op.AND:
96-
{
97-
const p = vm.stack.peek()
98-
if (r0.label === 'signature' || r1.label === 'signature') {
99-
if (p.data.slice(-4).every((v, i) => v === vm.calldata.data[i])) {
100-
p.label = 'signature'
101-
}
102-
} else if (r0.label === 'calldata' || r1.label === 'calldata') {
103-
p.label = 'calldata'
104-
}
105-
}
106-
break
107-
108111
case Op.DIV:
109112
if (r0.label === 'calldata') {
110113
const p = vm.stack.peek()

rust/src/selectors.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ enum Label {
1414
MulSig,
1515
}
1616

17+
const VAL_FFFFFFFF_B: [u8; 32] = ruint::uint!(0xffffffff_U256).to_be_bytes();
18+
1719
fn analyze(
1820
vm: &mut Vm<Label>,
1921
selectors: &mut BTreeSet<Selector>,
@@ -45,13 +47,19 @@ fn analyze(
4547
vm.stack.peek_mut()?.label = Some(Label::MulSig);
4648
}
4749

48-
// Vyper _selector_section_dense()
49-
StepResult{op: op::MOD, fa: Some(Element{label: Some(Label::MulSig | Label::Signature), ..}), sa: Some(ot), ..} =>
50+
// Vyper _selector_section_dense()/_selector_section_sparse()
51+
// (sig MOD n_buckets) or (sig AND (n_buckets-1))
52+
StepResult{op: op @ op::MOD, fa: Some(Element{label: Some(Label::MulSig | Label::Signature), ..}), sa: Some(ot), ..}
53+
| StepResult{op: op @ op::AND, fa: Some(Element{label: Some(Label::Signature), ..}), sa: Some(ot), ..}
54+
| StepResult{op: op @ op::AND, sa: Some(Element{label: Some(Label::Signature), ..}), fa: Some(ot), ..} =>
5055
{
51-
let t: Result<u8, _> = U256::from_be_bytes(ot.data).try_into();
52-
if let Ok(ma) = t {
53-
if ma < 128 {
54-
for m in 1..ma {
56+
if op == op::AND && ot.data == VAL_FFFFFFFF_B {
57+
vm.stack.peek_mut()?.label = Some(Label::Signature);
58+
} else {
59+
let ot8: Result<u8, _> = U256::from_be_bytes(ot.data).try_into();
60+
if let Ok(ma) = ot8 {
61+
let to = if op == op::MOD { ma } else { ma + 1 };
62+
for m in 1..to {
5563
let mut vm_clone = vm.clone();
5664
vm_clone.stack.peek_mut()?.data = U256::from(m).to_be_bytes();
5765
*gas_used += process(vm_clone, selectors, (gas_limit - *gas_used) / (ma as u32));
@@ -65,8 +73,6 @@ fn analyze(
6573
}
6674

6775
StepResult{op: op::SHR, sa: Some(Element{label: Some(Label::CallData), ..}), ..}
68-
| StepResult{op: op::AND, fa: Some(Element{label: Some(Label::Signature), ..}), ..}
69-
| StepResult{op: op::AND, sa: Some(Element{label: Some(Label::Signature), ..}), ..}
7076
| StepResult{op: op::DIV, fa: Some(Element{label: Some(Label::CallData), ..}), ..} =>
7177
{
7278
let v = vm.stack.peek_mut()?;

0 commit comments

Comments
 (0)