vyper
vyper copied to clipboard
Request for Comment: `safe` and `unsafe` math operations
Simple Summary
This VIP introduces an alternative syntax for unsafe math operations. The goal is to improve the readability of complex mathematical expressions without compromising auditability. To that extent we propose safe and unsafe builtin methods for safe as well as unsafe operations.
Motivation
Simple expressions like a: uint256 = unsafe_add(b, c) are straightforward in Vyper. However, readability decreases when complex expressions involve multiple unsafe operations (code snippet taken from CurveCryptoMathOptimized3.vy):
K0 = unsafe_div(
unsafe_mul(
unsafe_mul(
unsafe_div(
unsafe_mul(
unsafe_mul(
unsafe_div(
unsafe_mul(
unsafe_mul(10**18, x[0]), N_COINS
),
D,
),
x[1],
),
N_COINS,
),
D,
),
x[2],
),
N_COINS,
),
D,
)
In such situations, Solidity's inline assembly blocks can be more readable than Vyper's unsafe_ops(...).
Finally, expressions with a mix of safe and unsafe operations are particularly challenging to read (code snippet from CurveCryptoMathOptimized3.vy):
GK0: uint256 = (
unsafe_div(unsafe_div(2 * K0 * K0, 10**36) * K0, 10**36)
+ pow_mod256(unsafe_add(_A_gamma[1], 10**18), 2)
- unsafe_div(
unsafe_div(pow_mod256(K0, 2), 10**36) * unsafe_add(unsafe_mul(2, _A_gamma[1]), 3 * 10**18),
10**18
)
)
While long-term compiler optimizations can eventually address these readability issues, we propose a short-term solution here.
Specification
Readable Expressions with Unsafe Operations
For straightforward expressions involving unsafe operations, we retain the existing unsafe_ops syntax for its readability:
a: uint256 = unsafe_add(b, c)
but unsafe can also be used here:
a: uint256 = unsafe(b + c)
Entirely Unsafe Expression
When an entire expression is unsafe, we suggest borrowing Solidity's approach:
K0 = unsafe(10**18 * x[0] * N_COINS / D * x[1] * N_COINS / D * x[2] * N_COINS / D)
such that it is clear the entire expression is evaluated using unsafe math operations.
This would then introduce unsafe builtin that does only unsafe operations with the expression inside it.
Expressions Combining Safe and Unsafe Operations
We propose a safe builtin method that can be used inside unsafe expressions.
GK0: uint256 = (
unsafe(
safe(unsafe(safe(2 * K0 * K0) / 10**36) * K0) / 10**36
)
+ unsafe((_A_gamma[1] + 10**18) ** 2)
- unsafe(
unsafe(K0 ** 2 / 10**36) * unsafe(2 * _A_gamma[1] + safe(3 * 10**18)) / 10**18
)
)
Backwards Compatibility
Syntactically, this would not be a breaking change since it does not change how unsafe_mul etc. are handled, and only introduces unsafe(...expression * safe (...expression)).
Yes ser, bring it!
Not sure how it will translate to Python syntax.
Alternatively:
with unsafe_math():
K0 = 10**18 * x[0] * N_COINS / D * x[1] * N_COINS / D * x[2] * N_COINS / D
The __enter__() and __exit__() method for the unsafe_math() object may be used to turn off the safe math checks for the operator overloads.
It can also allow for optional arguments:
with unsafe_math(allow_zero_denominator=True):
K0 = 10**18 * x[0] * N_COINS / D * x[1] * N_COINS / D * x[2] * N_COINS / D
what about +!, -!, /!, and *! as aliases for the unsafe version of each of these operators
so that
GK0: uint256 = (
unsafe(
safe(unsafe(safe(2 * K0 * K0) / 10**36) * K0) / 10**36
)
+ unsafe((_A_gamma[1] + 10**18) ** 2)
- unsafe(
unsafe(K0 ** 2 / 10**36) * unsafe(2 * _A_gamma[1] + safe(3 * 10**18)) / 10**18
)
)
becomes
GK0: uint256 = (((2 * K0 * K0) /! 10**36) * K0) /! 10**36)
+ ((_A_gamma[1] +! 10**18) ** 2)
- ((K0 ** 2 /! 10 ** 36) * (2 *! _A_gamma[1] +! (3 * 10**18) /! 10**18)
@z80dev Not bad! This looks more readable! Question: why would the ! come before or after the operator? Does that matter?