As decentralized finance (DeFi) has matured, standardization has emerged as a critical factor in its evolution, enhancing and safeguarding interoperability—a feature widely regarded as one of the most unique and appealing aspects of DeFi. A prime example of this is the ERC-20 token standard, which has become the backbone of programmable money, defining how market participants can interact with digital currencies at both high and low levels. Standards like ERC-20 benefit all actors in the ecosystem, undeniably making DeFi a more robust and accessible field.
However, as the DeFi space evolves, so does the complexity of its protocols. Standardized building blocks, while foundational, cannot address the nuanced needs of increasingly sophisticated financial instruments.
Some evolutions in DeFi cannot be realistically standardized due to the protocol specificity required by their code. When universal standards fall short, protocols must develop their own internal components, representing the unique financial instruments they aim to create. Therefore, these components require detailed scrutiny and validation through testing, auditing, formal verification, and other methodologies to meet the rigorous quality demands necessary for the safe handling of user funds.
Most well-known protocols excel at creating and testing new components, yet there's still potential for enhancement, particularly regarding data types.
Take, for example, a perpetual futures market. It's a complex instrument, often simplified in code to the detriment of clarity and safety. This simplification results in a loss of critical information about the instrument's dimensions, such as the base asset unit, the asset unit for profit and loss (PnL) settlement, and the funding rate mechanism to balance market demand (just to name a few). These elements are crucial for understanding and managing the instrument but are frequently represented by overly simplified data types, like uint256
, leading to significant information loss.
The nuances of financial instruments like the perpetual futures market are usually grossly under-defined within the code. This shortcoming forces developers to be the sole enforcers of proper arithmetic and logic between components, making it challenging for future developers, readers, or auditors to understand or test the logic as intended by the original developer. Without explicit information defining an instrument's dimensions, proper testing and validation can become fraught with mistakes.
🍋 Imagine you're running a lemonade stand and decide to add fruit punch to your menu. A supplier offers to send you a batch of fruits, but you don't know the difference between lemons and strawberries. Assuming all fruits are as sour as lemons, you end up making your punch with only strawberries, expecting it to taste just right. But strawberries aren't as sour, and your punch is too sweet, disappointing customers who were excited for a tangy treat. This mix-up costs you sales and leaves you with a bunch of unsold, overly sweet punch. Similarly, in DeFi, if you mix up financial units without understanding their specific properties—like treating all tokens as if they have the same precision or transferability—you could make a costly mistake. It's like assuming all fruits can make good lemonade when not knowing the unique qualities of each can lead to unexpected and often unwanted results.
User-defined value types in Solidity offer an alternative to representing dimensioned information, allowing for zero-cost abstractions of elementary data types (1). These types, combined with libraries that define and enforce interactions with the underlying type, offer a robust way to ensure that complex financial instruments are represented accurately and safely. This approach significantly benefits developers and auditors by making the code more intuitive and easier to reason about, compared to the use of basic data types for representing detailed financial instruments.
Adopting a user-defined type system in smart contract development offers numerous and substantial benefits, laying the groundwork for code that is more secure, reliable, and transparent in its intentions.
At the core, user-defined types ensure that a developer's intent is preserved and enforced throughout the lifecycle of the code, transcending the limitations of traditional documentation, which may become outdated as the code evolves (4). This enduring accuracy is pivotal in smart contract development, where the stakes of maintaining integrity and security are high. User-defined types act as a constant source of truth, resistant to the inevitable changes that occur within a codebase.
Moreover, user-defined types are instrumental in establishing and maintaining invariants—conditions that are designed to remain true throughout the execution of a contract. These invariants play a critical role in enhancing the security and robustness of smart contracts. As the complexity of these types increases, so does their ability to assert and safeguard more of these crucial invariants, offering a systematic way to capture and enforce the contract's intended behaviors and constraints (4).
One of the most compelling advantages of user-defined types is their role in guiding refactoring efforts (4). Refactoring, the process of restructuring existing code without changing its external behavior, is essential for maintaining code quality and adaptability. User-defined types provide a clear framework for these efforts by clearly delineating how different parts of the system interact with each other. When a developer modifies one part of the system, the type system helps identify which other parts may be affected, ensuring that changes are consistent and that the system's integrity is maintained. This guidance is invaluable, especially in complex systems where interdependencies may not be immediately apparent, facilitating a more deliberate and informed approach to modifying code.
Despite these advantages, the implementation of user-defined value types introduces challenges, including an increase in verbosity, larger contract sizes, a steeper learning curve for developers, and potentially slower development processes. However, these challenges can often be mitigated. For example, the issue of increased contract size can be addressed through strategic router-proxy architecture solutions like Alice in Proxyland, which is used by Synthetix. Additionally, Solidity's support for user-defined operators can help reduce verbosity, making the code more intuitive and easier to interact with.
While these hurdles are noteworthy, the trade-offs are generally considered worthwhile for the benefits provided by a robust type system, particularly in terms of enhanced reliability, security, and code maintainability.
Below is a snippet of code demonstrating how operator overloading can be implemented to simplify interactions with user-defined types (2):
pragma solidity ^0.8.19;
type Int is int;
using {add as +} for Int global;
using {sub as -, sub} for Int global;
function add(Int a, Int b) pure returns (Int) {
return Int.wrap(Int.unwrap(a) + Int.unwrap(b));
}
function sub(Int a, Int b) pure returns (Int) {
return Int.wrap(Int.unwrap(a) - Int.unwrap(b));
}
function test(Int x, Int y) pure {
x + y;
// ERROR: Member "add" not found or not visible
// after argument-dependent lookup in Int.
x.add(y);
x - y;
// OK -- "sub" was also attached in "using for"
x.sub(y);
}
At Kwenta, we're developing a new quantity-adjusting (“Quanto”) perpetual futures derivative, inspired by and based on a fork of Synthetix v3 perpetual futures markets. This market introduces a quanto asset to settle PnL, a significant innovation that was engineered simply by reimagining some of the underlying market units to achieve the desired payoff for our traders (3). Implementing and integrating a robust user-defined type system (quanto dimensions) into our new market has allowed us to enforce all logic and arithmetic involving dimensioned components accurately, significantly enhancing the predictability and security of our product.
See the full spec for quanto perpetual futures here
The decision to leverage user-defined value types and to enhance the robustness of our financial instruments was heavily inspired by the advantages of a strong type system found in OCaml, as highlighted by Jane Street (4). The insights gained from exploring the benefits of types in programming have been crucial in shaping our approach to developing secure and reliable DeFi protocols, ensuring that we process significant sums of money responsibly.
User-Defined Value Types: Solidity Documentation ↩
Feature Deep-Dive - User-Defined Operators: Solidity Blog ↩
Quanto Units: GitHub Repository ↩
Jane Street - Types, and Why You Should Care: YouTube Video ↩