Skip to content

Commit d684599

Browse files
chore: update ERC721 to use bytes4 (vyperlang#2709)
update ERC721 to use bytes4, and fix some issues with bytes literals notably, hex literals are always interpreted as bytesN now (not Bytes), and vice versa - bytes literals are always Bytes, not bytesN also adds ERC165 interface.
1 parent 5b7ccbe commit d684599

File tree

7 files changed

+47
-34
lines changed

7 files changed

+47
-34
lines changed

examples/tokens/ERC721.vy

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
# @author Ryuya Nakamura (@nrryuya)
33
# Modified from: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy
44

5+
from vyper.interfaces import ERC165
56
from vyper.interfaces import ERC721
67

78
implements: ERC721
9+
implements: ERC165
810

911
# Interface for the contract called by safeTransferFrom()
1012
interface ERC721Receiver:
@@ -67,11 +69,11 @@ ownerToOperators: HashMap[address, HashMap[address, bool]]
6769
minter: address
6870

6971
# @dev Static list of supported ERC165 interface ids
70-
SUPPORTED_INTERFACES: constant(bytes32[2]) = [
72+
SUPPORTED_INTERFACES: constant(bytes4[2]) = [
7173
# ERC165 interface ID of ERC165
72-
0x01ffc9a700000000000000000000000000000000000000000000000000000000,
74+
0x01ffc9a7,
7375
# ERC165 interface ID of ERC721
74-
0x80ac58cd00000000000000000000000000000000000000000000000000000000,
76+
0x80ac58cd,
7577
]
7678

7779
@external
@@ -84,14 +86,11 @@ def __init__():
8486

8587
@pure
8688
@external
87-
#def supportsInterface(interface_id: bytes4) -> bool:
88-
def pizza_mandate_apology(interface_id_int: uint256) -> bool:
89+
def supportsInterface(interface_id: bytes4) -> bool:
8990
"""
9091
@dev Interface identification is specified in ERC-165.
9192
@param interface_id Id of the interface
9293
"""
93-
# NOTE: Signature is a hack until Vyper adds `bytes4` type
94-
interface_id: bytes32 = convert(interface_id_int, bytes32)
9594
return interface_id in SUPPORTED_INTERFACES
9695

9796

tests/examples/tokens/test_erc721.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import pytest
2-
from eth_utils import to_int
32

43
SOMEONE_TOKEN_IDS = [1, 2, 3]
54
OPERATOR_TOKEN_ID = 10
65
NEW_TOKEN_ID = 20
76
INVALID_TOKEN_ID = 99
87
ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
9-
ERC165_CHECK_CALL = "0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000"
10-
ERC165_INVALID_CALL = "0x01ffc9a7ffffffff00000000000000000000000000000000000000000000000000000000"
11-
ERC721_CHECK_CALL = "0x01ffc9a780ac58cd00000000000000000000000000000000000000000000000000000000"
8+
ERC165_SIG = "0x01ffc9a7"
9+
ERC165_INVALID_SIG = "0xffffffff"
10+
ERC721_SIG = "0x80ac58cd"
1211

1312

1413
@pytest.fixture
@@ -30,16 +29,15 @@ def test_erc165(w3, c):
3029
# The source contract makes a STATICCALL to the destination address with input data:
3130
# 0x01ffc9a701ffc9a700000000000000000000000000000000000000000000000000000000
3231
# and gas 30,000. This corresponds to `contract.supportsInterface(0x01ffc9a7)`
33-
assert to_int(w3.eth.call({"to": c.address, "data": ERC165_CHECK_CALL, "gas": 30000})) == 1
32+
assert c.supportsInterface(ERC165_SIG)
3433
# If the call fails or return false, the destination contract does not implement ERC-165.
3534
# If the call returns true, a second call is made with input data:
3635
# 0x01ffc9a7ffffffff00000000000000000000000000000000000000000000000000000000.
37-
assert to_int(w3.eth.call({"to": c.address, "data": ERC165_INVALID_CALL})) == 0
36+
assert not c.supportsInterface(ERC165_INVALID_SIG)
3837
# If the second call fails or returns true, the destination contract does not implement
3938
# ERC-165. Otherwise it implements ERC-165.
4039

41-
# NOTE: Just to check for ERC721 calls
42-
assert to_int(w3.eth.call({"to": c.address, "data": ERC721_CHECK_CALL})) == 1
40+
assert c.supportsInterface(ERC721_SIG)
4341

4442

4543
def test_balanceOf(c, w3, assert_tx_failed):

tests/parser/exceptions/test_type_mismatch_exception.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"""
99
@external
1010
def foo():
11-
b: Bytes[1] = 0x05
11+
b: Bytes[1] = b"\x05"
1212
x: uint256 = as_wei_value(b, "babbage")
1313
""",
1414
]

vyper/ast/annotation.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,8 @@ def visit_Num(self, node):
195195
node.lineno,
196196
node.col_offset,
197197
)
198-
if len(value) in (42, 66):
199-
node.ast_type = "Hex"
200-
node.n = value
201-
else:
202-
node.ast_type = "Bytes"
203-
node.value = int(value, 16).to_bytes(len(value) // 2 - 1, "big")
198+
node.ast_type = "Hex"
199+
node.n = value
204200

205201
elif value.lower()[:2] == "0b":
206202
node.ast_type = "Bytes"

vyper/builtin_functions/functions.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,8 +1962,9 @@ def _check(cond):
19621962
return fourbytes_to_int(method_id.value)
19631963

19641964
if isinstance(method_id, vy_ast.Hex):
1965-
_check(method_id.value <= 2 ** 32)
1966-
return method_id.value
1965+
hexstr = method_id.value # e.g. 0xdeadbeef
1966+
_check(len(hexstr) // 2 - 1 <= 4)
1967+
return int(hexstr, 16)
19671968

19681969
_check(False)
19691970

vyper/builtin_interfaces/ERC165.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
interface_code = """
2+
@view
3+
@external
4+
def supportsInterface(interface_id: bytes4) -> bool:
5+
pass
6+
"""

vyper/codegen/expr.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -230,20 +230,33 @@ def parse_Decimal(self):
230230
)
231231

232232
def parse_Hex(self):
233-
orignum = self.expr.value
234-
if len(orignum) == 42 and checksum_encode(orignum) == orignum:
235-
return LLLnode.from_list(
236-
int(self.expr.value, 16),
237-
typ=BaseType("address", is_literal=True),
238-
pos=getpos(self.expr),
239-
)
240-
elif len(orignum) == 66:
233+
pos = getpos(self.expr)
234+
235+
hexstr = self.expr.value
236+
237+
if len(hexstr) == 42:
238+
# sanity check typechecker did its job
239+
assert checksum_encode(hexstr) == hexstr
240+
typ = BaseType("address")
241+
# TODO allow non-checksum encoded bytes20
241242
return LLLnode.from_list(
242243
int(self.expr.value, 16),
243-
typ=BaseType("bytes32", is_literal=True),
244-
pos=getpos(self.expr),
244+
typ=typ,
245+
pos=pos,
245246
)
246247

248+
else:
249+
n_bytes = (len(hexstr) - 2) // 2 # e.g. "0x1234" is 2 bytes
250+
# TODO: typ = new_type_to_old_type(self.expr._metadata["type"])
251+
# assert n_bytes == typ._bytes_info.m
252+
253+
# bytes_m types are left padded with zeros
254+
val = int(hexstr, 16) << 8 * (32 - n_bytes)
255+
256+
typ = BaseType(f"bytes{n_bytes}", is_literal=True)
257+
typ.is_literal = True
258+
return LLLnode.from_list(val, typ=typ, pos=pos)
259+
247260
# String literals
248261
def parse_Str(self):
249262
bytez, bytez_length = string_to_bytes(self.expr.value)

0 commit comments

Comments
 (0)