Solidity v0.8.8 introduces user defined value types as a means to create zero-cost abstractions over an elementary value type that also increases type safety and improves readability.

## Motivation

A problem with primitive value types is that they are not very descriptive: they only specify how
the data is stored and not how it should be interpreted. For example, one may want to use `uint128`

to store the price of some object as well as the quantity available. It is quite useful to have
stricter type rules to avoid intermingling of the two different concepts. For example, one may want
to disallow assigning a quantity to a price or vice versa.

One option for solving this issue is by using structs. For example, price and quantity can be abstracted as structs as follows:

```
struct Price { uint128 price; }
struct Quantity { uint128 quantity; }
function toPrice(uint128 price) returns(Price memory) {
return Price(price);
}
function fromPrice(Price memory price) returns(uint128) {
return price.price;
}
function toQuantity(uint128 quantity) returns(Quantity memory) {
return Quantity(quantity);
}
function fromQuantity(Quantity memory quantity) returns(uint128) {
return quantity.quantity;
}
```

However, a struct is a reference type
and therefore always points to a value in `memory`

, `calldata`

or `storage`

. This means that the
above abstraction has a runtime overhead, i.e., additional gas when compared to using just `uint128`

to represent the underlying value. In particular, the functions `toPrice`

and `toQuantity`

involve
storing the value in memory. Similarly, the functions `fromPrice`

and `fromQuantity`

read the
respective value from memory. Together, these functions pass the value from ```
stack -> memory ->
stack
```

which wastes memory and incurs a runtime cost. This issue is solved by user defined value
types, which are abstractions of elementary value types (such as `uint8`

or `address`

), without any
additional runtime overhead.

## Syntax for User Defined Value Types

A user defined value type is defined using `type C is V;`

, where `C`

is the name of the newly
introduced type and `V`

has to be a built-in value type (the “underlying type”). They can be defined
inside or outside contracts (including libraries and interfaces). The function `C.wrap`

is used to
convert from the underlying type to the custom type. Similarly, the function `C.unwrap`

is used to
convert from the custom type to the underlying type.

Going back to the problem from the motivation section, one can replace the structs by:

```
pragma solidity ^0.8.8;
type Price is uint128;
type Quantity is uint128;
```

The functions `toPrice`

and `toQuantity`

can be replaced by `Price.wrap`

and `Quantity.wrap`

respectively. Similarly, the functions `fromPrice`

and `fromQuantity`

can be replaced by
`Price.unwrap`

and `Quantity.unwrap`

respectively.

The data-representation of the values of such types are inherited from the underlying type and the
underlying type is also used in the ABI. This means that the following two `transfer`

functions
would be identical, i.e., they have the same function
selector as well as the
same ABI encoding and
decoding. This
allows using user defined value types in a backwards compatible way.

```
pragma solidity ^0.8.8;
type Decimal18 is uint256;
interface MinimalERC20 {
function transfer(address to, Decimal18 value) external;
}
interface AnotherMinimalERC20 {
function transfer(address to, uint256 value) external;
}
```

Notice in the above example, how the user defined type `Decimal18`

makes it clear that a value is
supposed to represent a number with 18 decimals.

## Example

The following example illustrates a custom type `UFixed`

representing a decimal fixed point type
with 18 decimals and a minimal library to do arithmetic operations on the type.

```
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
// Represent a 18 decimal, 256 bit wide fixed point type
// using a user defined value type.
type UFixed is uint256;
/// A minimal library to do fixed point operations on UFixed.
library FixedMath {
uint constant multiplier = 10**18;
/// Adds two UFixed numbers. Reverts on overflow,
/// relying on checked arithmetic on uint256.
function add(UFixed a, UFixed b) internal pure returns (UFixed) {
return UFixed.wrap(UFixed.unwrap(a) + UFixed.unwrap(b));
}
/// Multiplies UFixed and uint256. Reverts on overflow,
/// relying on checked arithmetic on uint256.
function mul(UFixed a, uint256 b) internal pure returns (UFixed) {
return UFixed.wrap(UFixed.unwrap(a) * b);
}
/// Take the floor of a UFixed number.
/// @return the largest integer that does not exceed `a`.
function floor(UFixed a) internal pure returns (uint256) {
return UFixed.unwrap(a) / multiplier;
}
/// Turns a uint256 into a UFixed of the same value.
/// Reverts if the integer is too large.
function toUFixed(uint256 a) internal pure returns (UFixed) {
return UFixed.wrap(a * multiplier);
}
}
```

Notice how `UFixed.wrap`

and `FixedMath.toUFixed`

have the same signature but perform two very
different operations: The `UFixed.wrap`

function returns a `UFixed`

that has the same data
representation as the input, whereas `toUFixed`

returns a `UFixed`

that has the same numerical
value. One can allow some form of type-encapsulation by only using the `wrap`

and `unwrap`

functions
in the file that defines the type.

## Operators and Type Rules

Explicit and implicit conversions to and from other types are disallowed.

Currently, no operators are defined for user defined value types. In particular, even the operator
`==`

is not defined. However, allowing operators is currently being discussed. To give a short
outlook on the applications, one may want to introduce a new integer type that always does wrapping
arithmetic as follows:

```
/// Proposal on defining operators on user defined value types
/// Note: this does not fully compile on Solidity 0.8.8; only a concept.
type UncheckedInt8 is int8;
function add(UncheckedInt8 a, UncheckedInt8 b) pure returns(UncheckedInt8) {
unchecked {
return UncheckedInt8.wrap(UncheckedInt8.unwrap(a) + UncheckedInt8.unwrap(b));
}
}
function addInt(UncheckedInt8 a, uint b) pure returns(UncheckedInt8) {
unchecked {
return UncheckedInt8.wrap(UncheckedInt8.unwrap(a) + b);
}
}
using {add as +, addInt as +} for UncheckedInt8;
contract MockOperator {
UncheckedInt8 x;
function increment() external {
// This would not revert on overflow when x = 127
x = x + 1;
}
function add(UncheckedInt8 y) external {
// Similarly, this would also not revert on overflow.
x = x + y;
}
}
```

You can join or follow this discussion in the Solidity forum and issue #11969. Also, you can join or follow the discussion about allowing the constructor syntax for user defined value types in the issue #11953.