Skip to content

Commit cedf708

Browse files
fix[codegen]: fix non-memory reason strings (#3877)
fix code generation for non-memory reason strings. previously, the byte array copier would fail trying to produce code for the buf, which was an int rather than an IRnode. this commit cleans up the revert generation code, allocates a fresh buffer to avoid memory cleanliness issues, and refactors the code to use abi_encoder code instead of handrolling the ABI encoding routine. --------- Co-authored-by: Charles Cooper <cooper.charles.m@gmail.com>
1 parent e48ff32 commit cedf708

File tree

3 files changed

+44
-38
lines changed

3 files changed

+44
-38
lines changed

.github/workflows/pull-request.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ jobs:
3434
# ux: language changes (UX)
3535
# tool: integration
3636
# ir: (old) IR/codegen changes
37+
# codegen: lowering from vyper AST to codegen
3738
# venom: venom changes
3839
scopes: |
3940
ci
@@ -43,6 +44,7 @@ jobs:
4344
ux
4445
tool
4546
ir
47+
codegen
4648
venom
4749
requireScope: true
4850
subjectPattern: '^(?![A-Z]).+$'

tests/functional/codegen/features/test_assert.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ def foo():
2828

2929
def test_assert_reason(w3, get_contract_with_gas_estimation, tx_failed, memory_mocker):
3030
code = """
31+
err: String[32]
32+
3133
@external
3234
def test(a: int128) -> int128:
3335
assert a > 1, "larger than one please"
@@ -43,6 +45,17 @@ def test2(a: int128, b: int128, extra_reason: String[32]) -> int128:
4345
@external
4446
def test3(reason_str: String[32]):
4547
raise reason_str
48+
49+
@external
50+
def test4(a: int128, reason_str: String[32]) -> int128:
51+
self.err = reason_str
52+
assert a > 1, self.err
53+
return 1 + a
54+
55+
@external
56+
def test5(reason_str: String[32]):
57+
self.err = reason_str
58+
raise self.err
4659
"""
4760
c = get_contract_with_gas_estimation(code)
4861

@@ -66,6 +79,15 @@ def test3(reason_str: String[32]):
6679
c.test3("An exception")
6780
assert _fixup_err_str(e_info.value.args[0]) == "An exception"
6881

82+
assert c.test4(2, "msg") == 3
83+
with pytest.raises(TransactionFailed) as e_info:
84+
c.test4(0, "larger than one again please")
85+
assert _fixup_err_str(e_info.value.args[0]) == "larger than one again please"
86+
87+
with pytest.raises(TransactionFailed) as e_info:
88+
c.test5("A storage exception")
89+
assert _fixup_err_str(e_info.value.args[0]) == "A storage exception"
90+
6991

7092
invalid_code = [
7193
"""

vyper/codegen/stmt.py

Lines changed: 20 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import vyper.codegen.events as events
22
import vyper.utils as util
33
from vyper import ast as vy_ast
4+
from vyper.codegen.abi_encoder import abi_encode
45
from vyper.codegen.context import Constancy, Context
56
from vyper.codegen.core import (
67
LOAD,
@@ -9,20 +10,14 @@
910
clamp_le,
1011
get_dyn_array_count,
1112
get_element_ptr,
12-
make_byte_array_copier,
13+
get_type_for_exact_size,
1314
make_setter,
14-
zero_pad,
15+
wrap_value_for_external_return,
1516
)
1617
from vyper.codegen.expr import Expr
1718
from vyper.codegen.return_ import make_return_stmt
1819
from vyper.evm.address_space import MEMORY, STORAGE
19-
from vyper.exceptions import (
20-
CodegenPanic,
21-
CompilerPanic,
22-
StructureException,
23-
TypeCheckFailure,
24-
tag_exceptions,
25-
)
20+
from vyper.exceptions import CodegenPanic, StructureException, TypeCheckFailure, tag_exceptions
2621
from vyper.semantics.types import DArrayT
2722
from vyper.semantics.types.shortcuts import UINT256_T
2823

@@ -132,39 +127,26 @@ def _assert_reason(self, test_expr, msg):
132127
finally:
133128
self.context.constancy = tmp
134129

135-
# TODO this is probably useful in codegen.core
136-
# compare with eval_seq.
137-
def _get_last(ir):
138-
if len(ir.args) == 0:
139-
return ir.value
140-
return _get_last(ir.args[-1])
141-
142-
# TODO maybe use ensure_in_memory
143-
if msg_ir.location != MEMORY:
144-
buf = self.context.new_internal_variable(msg_ir.typ)
145-
instantiate_msg = make_byte_array_copier(buf, msg_ir)
146-
else:
147-
buf = _get_last(msg_ir)
148-
if not isinstance(buf, int): # pragma: nocover
149-
raise CompilerPanic(f"invalid bytestring {buf}\n{self}")
150-
instantiate_msg = msg_ir
130+
msg_ir = wrap_value_for_external_return(msg_ir)
131+
bufsz = 64 + msg_ir.typ.memory_bytes_required
132+
buf = self.context.new_internal_variable(get_type_for_exact_size(bufsz))
151133

152134
# offset of bytes in (bytes,)
153135
method_id = util.method_id_int("Error(string)")
154136

155-
# abi encode method_id + bytestring
156-
assert buf >= 36, "invalid buffer"
157-
# we don't mind overwriting other memory because we are
158-
# getting out of here anyway.
159-
_runtime_length = ["mload", buf]
160-
revert_seq = [
161-
"seq",
162-
instantiate_msg,
163-
zero_pad(buf),
164-
["mstore", buf - 64, method_id],
165-
["mstore", buf - 32, 0x20],
166-
["revert", buf - 36, ["add", 4 + 32 + 32, ["ceil32", _runtime_length]]],
167-
]
137+
# abi encode method_id + bytestring to `buf+32`, then
138+
# write method_id to `buf` and get out of here
139+
payload_buf = buf + 32
140+
bufsz -= 32 # reduce buffer by size of `method_id` slot
141+
encoded_length = abi_encode(payload_buf, msg_ir, self.context, bufsz, returns_len=True)
142+
with encoded_length.cache_when_complex("encoded_len") as (b1, encoded_length):
143+
revert_seq = [
144+
"seq",
145+
["mstore", buf, method_id],
146+
["revert", buf + 28, ["add", 4, encoded_length]],
147+
]
148+
revert_seq = b1.resolve(revert_seq)
149+
168150
if is_raise:
169151
ir_node = revert_seq
170152
else:

0 commit comments

Comments
 (0)