3333METADATA_MAX_ARG_VALUE_LEN = 44
3434
3535
36+ def _require (condition , message ):
37+ """Precondition check that survives `python -O`.
38+
39+ These serializers build the bytes that get SIGNED. `assert` is stripped
40+ under -O, so an optimized process would serialize and sign an over-long
41+ method name, a 19-byte address or a 9th argument instead of refusing it.
42+ A signing precondition must not be an assertion.
43+ """
44+ if not condition :
45+ raise ValueError (message )
46+
47+
3648def token_amount_value (amount , decimals , symbol ):
3749 """Build an ARG_FORMAT_TOKEN_AMOUNT value: decimals + symbol + amount.
3850
3951 amount: non-negative int (raw on-chain units). decimals: int 0..36.
4052 symbol: short ticker, [A-Za-z0-9], <=10 chars.
4153 """
4254 sym = symbol .encode ('ascii' )
43- assert 0 < len (sym ) <= 10 and sym .isalnum ()
44- assert 0 <= decimals <= 36
55+ _require (0 < len (sym ) <= 10 and sym .isalnum (),
56+ 'symbol must be 1-10 alphanumeric characters, got %r' % symbol )
57+ _require (0 <= decimals <= 36 , 'decimals must be 0..36, got %r' % decimals )
4558 # Minimal big-endian amount, at least 1 byte, at most 32.
4659 n = amount .to_bytes (32 , 'big' ).lstrip (b'\x00 ' ) or b'\x00 '
47- assert len (n ) <= 32
60+ _require ( len (n ) <= 32 , 'amount does not fit in 32 bytes' )
4861 return bytes ([decimals , len (sym )]) + sym + n
4962
5063CLASSIFICATION_OPAQUE = 0
@@ -186,11 +199,14 @@ def serialize_metadata(
186199 if timestamp is None :
187200 timestamp = int (time .time ())
188201
189- assert len (contract_address ) == 20
190- assert len (selector ) == 4
191- assert len (tx_hash ) == 32
192- assert len (method_name .encode ('utf-8' )) <= 64
193- assert len (args ) <= 8
202+ _require (len (contract_address ) == 20 ,
203+ 'contract_address must be 20 bytes, got %d' % len (contract_address ))
204+ _require (len (selector ) == 4 , 'selector must be 4 bytes, got %d' % len (selector ))
205+ _require (len (tx_hash ) == 32 , 'tx_hash must be 32 bytes, got %d' % len (tx_hash ))
206+ _require (len (method_name .encode ('utf-8' )) <= 64 ,
207+ 'method_name must be <=64 UTF-8 bytes, got %d'
208+ % len (method_name .encode ('utf-8' )))
209+ _require (len (args ) <= 8 , 'at most 8 args, got %d' % len (args ))
194210
195211 buf = bytearray ()
196212
@@ -221,7 +237,8 @@ def serialize_metadata(
221237 for arg in args :
222238 # name (1-byte length prefix + UTF-8)
223239 arg_name = arg ['name' ].encode ('utf-8' )
224- assert len (arg_name ) <= 32
240+ _require (len (arg_name ) <= 32 ,
241+ 'arg name must be <=32 UTF-8 bytes, got %d' % len (arg_name ))
225242 buf .append (len (arg_name ))
226243 buf .extend (arg_name )
227244
@@ -230,7 +247,9 @@ def serialize_metadata(
230247
231248 # value (2-byte length prefix + raw bytes)
232249 val = arg ['value' ]
233- assert len (val ) <= METADATA_MAX_ARG_VALUE_LEN
250+ _require (len (val ) <= METADATA_MAX_ARG_VALUE_LEN ,
251+ 'arg value must be <=%d bytes, got %d'
252+ % (METADATA_MAX_ARG_VALUE_LEN , len (val )))
234253 buf .extend (struct .pack ('>H' , len (val )))
235254 buf .extend (val )
236255
@@ -286,10 +305,13 @@ def serialize_schema_metadata(
286305 if timestamp is None :
287306 timestamp = int (time .time ())
288307
289- assert len (contract_address ) == 20
290- assert len (selector ) == 4
291- assert len (method_name .encode ('utf-8' )) <= 64
292- assert len (args ) <= 8
308+ _require (len (contract_address ) == 20 ,
309+ 'contract_address must be 20 bytes, got %d' % len (contract_address ))
310+ _require (len (selector ) == 4 , 'selector must be 4 bytes, got %d' % len (selector ))
311+ _require (len (method_name .encode ('utf-8' )) <= 64 ,
312+ 'method_name must be <=64 UTF-8 bytes, got %d'
313+ % len (method_name .encode ('utf-8' )))
314+ _require (len (args ) <= 8 , 'at most 8 args, got %d' % len (args ))
293315
294316 buf = bytearray ()
295317 buf .append (METADATA_VERSION_SCHEMA )
@@ -304,19 +326,24 @@ def serialize_schema_metadata(
304326 buf .append (len (args ))
305327 for arg in args :
306328 arg_name = arg ['name' ].encode ('utf-8' )
307- assert len (arg_name ) <= 32
329+ _require (len (arg_name ) <= 32 ,
330+ 'arg name must be <=32 UTF-8 bytes, got %d' % len (arg_name ))
308331 buf .append (len (arg_name ))
309332 buf .extend (arg_name )
310333
311334 fmt = arg ['format' ]
312- assert fmt in (ARG_FORMAT_ADDRESS , ARG_FORMAT_AMOUNT ,
313- ARG_FORMAT_TOKEN_AMOUNT ), \
314- 'v2 supports only fixed-word ADDRESS/AMOUNT/TOKEN_AMOUNT'
335+ _require (fmt in (ARG_FORMAT_ADDRESS , ARG_FORMAT_AMOUNT ,
336+ ARG_FORMAT_TOKEN_AMOUNT ),
337+ 'v2 supports only fixed-word ADDRESS/AMOUNT/TOKEN_AMOUNT, '
338+ 'got %r' % fmt )
315339 buf .append (fmt )
316340 if fmt == ARG_FORMAT_TOKEN_AMOUNT :
317341 sym = arg ['symbol' ].encode ('ascii' )
318- assert 0 < len (sym ) <= 10 and sym .isalnum ()
319- assert 0 <= arg ['decimals' ] <= 36
342+ _require (0 < len (sym ) <= 10 and sym .isalnum (),
343+ 'symbol must be 1-10 alphanumeric characters, got %r'
344+ % arg ['symbol' ])
345+ _require (0 <= arg ['decimals' ] <= 36 ,
346+ 'decimals must be 0..36, got %r' % arg ['decimals' ])
320347 buf .append (arg ['decimals' ])
321348 buf .append (len (sym ))
322349 buf .extend (sym )
@@ -342,12 +369,13 @@ def schema_calldata(selector: bytes, args: list) -> bytes:
342369 fmt = arg ['format' ]
343370 if fmt == ARG_FORMAT_ADDRESS :
344371 addr = arg ['address' ]
345- assert len (addr ) == 20
372+ _require (len (addr ) == 20 ,
373+ 'ADDRESS arg must be 20 bytes, got %d' % len (addr ))
346374 data .extend (b'\x00 ' * 12 + addr )
347375 elif fmt in (ARG_FORMAT_AMOUNT , ARG_FORMAT_TOKEN_AMOUNT ):
348376 data .extend (int (arg ['amount' ]).to_bytes (32 , 'big' ))
349377 else :
350- raise AssertionError ('unsupported v2 arg format %r' % fmt )
378+ raise ValueError ('unsupported v2 arg format %r' % fmt )
351379 return bytes (data )
352380
353381
0 commit comments