Solidity 0.8.19 came up with quite a few changes and upgrades. In this post, I’ll talk about user-defined operators for user-defined value types. (I’ll use UDO and UDVT ). Before learning about UDOs, we need to look into UDVTs. What are User-Defined Value Types UDVTs are gasless abstractions of value types over the pre-defined value types in solidity. This was . In simpler terms, we can call them aliases for other value types. The core reason behind introducing this was to facilitate more strict definitions of variables. introduced in solidity 0.8.8 Quick Example For example, our contract stores two types of addresses, one for buyers and the other for sellers. We already know that under the hood, both of these variables will be of type address; however, what if we want to make them different to avoid any confusion or intermingling of different concepts? What if we want the data types to be more descriptive about the value it’s storing? Before UDVTs were introduced, this goal was attained using structs. Below is an example. The four functions in this code are used to wrap or unwrap the given data type into or from the defined data type. I am sure you have seen this type of usage in some contracts. However, as we know, structs are reference types; they’ll use memory, costing more gas than just using uint. This is where UDVTs are being used. They are simple extracts that don’t cost gas. Now, as far as the syntax is concerned, we can define UDVTs like where is the extracted value type, can also call an alias for which is the underlying type that can be uint, address, etc. Once we declare these value types, we get two attached methods with them, and , can be used to convert an underlying type to the newly created value type, is used for the vice versa. type A is B A B wrap unwrap A.wrap(value) and Let’s see this in the code. This was a demonstration related to the previous code snippet however, you might have realized that we don’t need to define the wrapping/unwrapping functions explicitly because . So, if we talk about the usage of these functions, below is another code snippet. solidity gives wrap/unwrap functions // SPDX-License-Identifier: GPL-3.0 pragma solidity ^0.8.8; // Represent an 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. uint256 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); } } We can conclude that UDVTs are not something that affects the logic of the contract but a kind of syntactical sugar that makes our code clearer and readable. math library, for example, which is a library used to tackle the floating number issue in solidity, uses UDVTs to define different types of floating numbers. PRB Now, the problem arises when we try to use arithmetic operations on UDVTs. As you can see in the above example, we have to unwrap the passed variables to use built-in operators. This is where UDOs are introduced. What are User Defined Operators? UDOs are built using the two built-in features of solidity, i.e., built-in operators and . using … for … UDOs are an extended version of . To recall, is used to: using for using for Attaching all Library functions to a data type. using LibrayName for TypeName Attaching library functions to all the data types. using LibrayName for * Attaching specific library functions or free functions to any specific type. using {LibrayName.FunctionName, FreeFunctionName} for TypeName Now, UDO comes into play and defines the fourth type of using for statement. Something like this: using {FunctionName as OperatorSign} for UDVTName global; This syntax facilitates attaching a function as an operator sign, the same as attaching a library function. The difference is we can use an operator sign instead of using the default way of calling the function. This will be clearer by looking at the code example below. There are some rules while using User Defined Operators. They can only be defined as functions(functions defined at the file level). free The free functions should be pure. Can only be defined at global directive, i.e., it can not be inside a contract but at a file level. using for User-defined Operator can only be attached to the UDVTs and not to the underlying default value types. They can only be defined for a particular type, i.e., they won’t work with different UDVTs in the same function. At last, while attaching the operators to any UDVT, only these operator signs can be used: &, |, ^, ~, +, -, *, /, %, ==, !=, <, <=, >, >= . Let’s now look at a code example. // SPDX-License-Identifier: MIT pragma solidity 0.8.19; //The types are defined at the file level type Float is uint256; type unFloat is uint256; //"using for" statements are written at the global directive //not inside a contract using {add as +} for Float global; using {multiply as *} for Float global; using {divide as /} for Float global; //These are pure free functions, which is a requirement ///@notice Each function works with only one type. function add(Float a, Float b) pure returns (Float) { return Float.wrap(Float.unwrap(a) + Float.unwrap(b)); } function multiply(Float a, Float b) pure returns (Float) { return Float.wrap(Float.unwrap(a) * Float.unwrap(b)); } function divide(Float a, Float b) pure returns (Float) { return Float.wrap(Float.unwrap(a) / Float.unwrap(b)); } //Using the attached operators inside a contract contract UDO { Float cent = Float.wrap(100); Float decimal = Float.wrap(1e18); //The multiplication and division using operators is only possible // because we attached these particular operators' sign to the relevant functions function takePercent(Float _amount, Float totalAmount) external view returns (Float) { return (_amount * cent * decimal)/(totalAmount); } } Attaching any function to any UDVT is independent of binding the same function to it. This means that we can’t call the free function with UDVTs in a similar fashion as a library function like or when we are binding them as an operator, but obviously, we can do so when they are attached as functions. Let’s look at the example below. add(a,b) a.add(b) // SPDX-License-Identifier: MIT pragma solidity 0.8.19; type Float is uint256; type unFloat is uint256; // binding and attaching using {multiply as *,multiply} for Float global; using {divide as /} for Float global; function multiply(Float a, Float b) pure returns (Float) { return Float.wrap(Float.unwrap(a) * Float.unwrap(b)); } function divide(Float a, Float b) pure returns (Float) { return Float.wrap(Float.unwrap(a) / Float.unwrap(b)); } contract UDO { Float cent = Float.wrap(100); Float decimal = Float.wrap(1e18) function takePercent(Float _amount, Float totalAmount) external view returns (Float) { // return (_amount * cent * decimal)/(totalAmount); return (_amount.multiply(decimal.multiply(cent))).divide(totalAmount); //In this line Multiply will work but divide will throw an error, because // we haven't bound the divide function to Float. } } The signs we used for particular functions are not strictly needed; we could’ve used instead of with the add function, and the would work as the add function. However, that will cause unwanted confusion, so we won’t do that. / + / What are the use cases? As discussed earlier, the UDVTs can be used to prevent any kind of type mistakes and provide a better understanding of the logic. UDOs increase the usability of the same by making it more accessible and familiar. The best usage of these are math libraries, where we can have multiple types of floating numbers and much more. That’s it for this article. Join us with all your solidity questions/confusions . HERE Also published . here