At OpenZeppelin, our mission is to protect the open economy. Decentralized finance applications are still the greatest trend in the blockchain industry with promising projects holding and managing enormous amounts of capital.
In our previous post, we gave an introduction to important related concepts, and now, we are going to progress this further.
In this article, we want to identify what the technical building blocks of several DeFi projects are, with the purpose of detecting their patterns and structures and revealing common pieces of the entire puzzle.
This preliminary work can be then used to actually forge a new set of contracts that anyone can reuse and plug in their projects.
Let’s start our exploration with the main architecture supporting the majority of DeFi protocols in the space.
DeFi projects, use pooling systems to collect, manage, and exchange assets.
Lending protocols are systems where lenders become liquidity providers, by depositing cryptocurrencies in a pool, from which borrowers take out loans after having placed enough collateral. We can find examples of such pooling strategies in Aave or Compound. However, lending platforms are not the only kind of DeFi projects using pools.
Protocols like MAI V2 are using pools to track protocol liquidity and are letting the users trade LONG or SHORT positions on the price of the underlying asset. Lastly, Automated Market Makers (like Uniswap or Balancer) use pools to provide liquidity while swapping or trading two or more different tokens.
In general, pools are needed to collect funds from providers. These funds are then used as fuel in the system, whether fuel represents liquidity for loans or swaps, votes for an oracle price, etc. Deposited funds are tracked in the contracts, and it is not uncommon to see the use of a secondary ERC20 token to represent them (see the cToken). Liquidity providers can earn interest or rewards in the form of tokens to incentivize their deposits. Moreover, the true consumers of such liquidity are paying fees for the use of it. How these dynamics are modeled heavily depends on the project purpose and its design choices.
We can summarize by saying that pools are just collections of funds where one or more tokens can be deposited and withdrawn but also minted and burned, while the underlying system is using those pool deposits to calculate loans, interest, rewards, fees, or votes and execute the protocol rules.
From a technical perspective, we can abstract pools as an independent mechanism of in/out multi-tokens transfers, where users’ balances are tracked and available for the system rules to be executed.
While pools are just conceptual structures independent from the rest of the system, most of the time they are mixed together with all the set of rules and constraints that apply differently on each protocol. Trying to separate the object (the pool) from how it’s used, we can talk now about another piece of the puzzle: the pool’s management system.
Depending on the goals defined by the project, pools can parametrized in different ways:
- Some DeFi projects use state variables as pool parameters:
Multi-pool systems, like The Graph protocol, use Solidity structs for each subgraph’s pool, accounting for accumulated fees and rewards.
Automated Market Makers (like in Uniswap or Montecarlo) can use such parameters inside their mathematical formulas. On AMM-based decentralized exchanges, the traditional order book is replaced by liquidity pools that are pre-funded on-chain for both assets of the trading pair. The liquidity is provided by other users who also earn passive income on their deposit through trading fees based on the percentage of the liquidity pool that they provide. Depending on the chosen value for the pools’ parameters, those fees and the consequent liquidity provider incomes can be highly affected.
- Others may also rely on external integrations and oracles to provide a price feed for the assets as we will see in a moment.
Since pool management is a critical system component, most of the time, any core change is performed by appropriate governance.
Normally, a typical implementation would probably rely on a set of state variables, some financial tools (like AMM formulas, interest calculations,etc.), a governance system, and eventually, a set of external integrations and auxiliary contracts.
Abstracting such complex schemes can be challenging and it’s worth exploring it. Without entering into the details of such a complex task, let’s try to focus now on just two parts which are easy to model.
Governance systems are crucial pieces of every protocol. They make it possible for the system to operate guaranteeing that no malicious actors are playing against the rules and that the health of the protocol is maintained.
Ideally, a governance is a decentralized system that lets the users choose protocol changes or special interventions in a democratic way.
Practically, in terms of smart contracts, most of the DeFi projects out there, have a set of addresses with their particular role and access level to operate in the platform. In this way we usually have a fleet of different names for these special users like governors, guardians, administrators, enforcers, etc. They all are addresses set to special state variables to be later queried whenever a restricted function is called.
An outstanding example is the governance designed by the Compound protocol.
Whenever a sensible state changing function must be called, this function call is queued in a waiting list and a timelock period must be waited before executing it.
Moreover, not everyone is able to propose a function call to be queued. A governance contract lets the user propose changes, vote those proposals by staking COMP tokens, and accepted proposals are then queued for a later execution.
This governance system is a decentralized solution that doesn’t require third actors with special powers to administrate the platform.
Whether the choice is to have a set of centralized special users or a decentralized orchestration, both solutions can be modeled in terms of inheritable contracts.
External integrations and auxiliary libraries
In order to manage pools, apart from a governance system, external integrations and libraries are sometimes needed.
Many DeFi mechanics need to know asset prices in order to work. Thanks to price feeds, undercollateralized loans and system insolvencies can be detected and controlled or accrued interest can be calculated. Asset prices are, in general, retrieved by oracles or other existing protocols.
There are different examples of oracles out there, the most famous probably being Chainlink.
Other protocols, like Compound or UMA, are integrating their own oracles to feed their systems with asset prices.
While oracles are mainly needed to just retrieve prices, external integrations can also have other purposes. Arbitrage can be done programmatically by the use of two different decentralized exchanges. Within a protocol, deposits can be forwarded to a Compound pool and earn interests over time. All those actions translate in deposits and withdrawals in/from external protocols, something more complex than just retrieving the price of an asset.
This kind of arithmetic requires fine decimals management, something that Solidity is not able to provide out of the box. For this, Fixed-Point libraries able to manage calculations and precisions up to several decimals are common pieces of many DeFi applications.
One can undoubtedly create such a library contributing in creating standards for the ecosystem. In the same way, oracle and protocol integrations can be abstracted and provided as template contracts to ease the developer tasks of building modular DeFi systems.