User-Defined Value Types & Operators
UDVTs create a zero-cost, type-safe alias over an elementary value type:
22.1 User-defined value types (0.8.8+)
UDVTs create a zero-cost, type-safe alias over an elementary value type:
T.wrap(x) and T.unwrap(t) convert between the UDVT and its underlying type.
No implicit conversions — mixing UFixed18 with uint256 is a compile error, preventing unit bugs (e.g., mixing token amounts with prices).
Operators that can be defined: & | ^ ~ + - * / % == != < <= > >= (unary - and ~ too). Operator functions must be pure free or library functions, and binding must be global in the same file as the type.
Real-world use: Uniswap v4 uses UDVTs such as Currency, BalanceDelta, PoolId.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
type UFixed18 is uint256; // 18-decimal fixed-point number
using {add as +, sub as -, mul as *, lt as <} for UFixed18 global; // user-defined operators (0.8.19+)
function add(UFixed18 a, UFixed18 b) pure returns (UFixed18) {
return UFixed18.wrap(UFixed18.unwrap(a) + UFixed18.unwrap(b));
}
function sub(UFixed18 a, UFixed18 b) pure returns (UFixed18) {
return UFixed18.wrap(UFixed18.unwrap(a) - UFixed18.unwrap(b));
}
function mul(UFixed18 a, UFixed18 b) pure returns (UFixed18) {
return UFixed18.wrap(UFixed18.unwrap(a) * UFixed18.unwrap(b) / 1e18);
}
function lt(UFixed18 a, UFixed18 b) pure returns (bool) {
return UFixed18.unwrap(a) < UFixed18.unwrap(b);
}
library FixedLib {
UFixed18 internal constant ONE = UFixed18.wrap(1e18);
function fromUint(uint256 x) internal pure returns (UFixed18) { return UFixed18.wrap(x * 1e18); }
}
contract Pricing {
function total(uint256 qty, UFixed18 unitPrice) external pure returns (UFixed18) {
UFixed18 q = FixedLib.fromUint(qty);
UFixed18 fee = UFixed18.wrap(0.01e18); // 1%
return q * unitPrice * (FixedLib.ONE + fee); // oper
…