I recently wrote a tutorial on decentralized exchange development https://github.com/WTFAcademy/WTF-Dapp , referring to the code implementation of Uniswap V3, and learned a lot of knowledge points. I have developed simple NFT contracts before, and this is the first time I have tried to develop a Defi contract. I believe these tips will be very helpful for novices who want to learn contract development.
Contract developers can go directly to https://github.com/WTFAcademy/WTF-Dapp to contribute code and make a difference for Web3~
Next, let’s take a look at these little tricks, some of which can even be called gimmicky tricks.
The contract address of the contract deployment can be made predictable
When we deploy a contract, we usually get a seemingly random address. Because it is related to nonce, the contract address is difficult to predict. However, in Uniswap, we have such a requirement: we need to infer the contract address through the transaction pair and related information. This is very useful in many cases, such as determining the transaction authority or obtaining the address of the pool.
In Uniswap, contracts are created by using code like pool = address(new Uniswap V3 Pool{salt: keccak 256(abi.encode(token 0, token 1, fee))}());. By adding salt to create a contract using CREATE 2 ( https://github.com/AmazingAng/WTF-Solidity/blob/main/25_Create2/readme.md ), the advantage is that the created contract address is predictable. The logic of address generation is new address = hash(0x FF, creator address, salt, initcode).
You can learn more about this in the WTF-DApp course at https://github.com/WTFAcademy/WTF-Dapp/blob/main/P103_Factory/readme.md .
Make good use of callback functions
In Solidity, contracts can call each other. There is a scenario where A calls B in a certain method, and B calls back A in the called method, which is also very useful in some scenarios.
In Uniswap, when you call the swap method of the Uniswap V3 Pool contract, it will call back the swapCallback, and the callback will pass in the calculated Token actually required for this transaction. The caller needs to transfer the Token required for the transaction to the Uniswap V3 Pool in the callback, instead of splitting the swap method into two parts for the caller to call. This ensures the security of the swap method and ensures that the entire logic is fully executed without the need for cumbersome variable records to ensure security.
The code snippet is as follows:
You can learn more about the trading part of the course at https://github.com/WTFAcademy/WTF-Dapp/blob/main/P106_PoolSwap/readme.md .
Use exceptions to pass information and try catch to implement transaction estimates
When referring to the Uniswap code, we found that in its https://github.com/Uniswap/v3-periphery/blob/main/contracts/lens/Quoter.sol contract, the swap method of Uniswap V3 Pool was wrapped in try catch and executed:
Why is this? Because we need to simulate the swap method to estimate the tokens needed for the transaction, but since the token exchange will not actually occur during the estimation, an error will be reported. In Uniswap, it throws a special error in the callback function of the transaction, then captures the error and parses the required information from the error message.
It looks like a hack, but it is also very practical. In this way, there is no need to modify the swap method according to the needs of estimated transactions, and the logic is simpler. In our course, we also refer to this logic to implement the contract https://github.com/WTFAcademy/WTF-Dapp/blob/main/demo-contract/contracts/wtfswap/SwapRouter.sol .
Using large numbers to solve precision problems
There are a lot of calculation logics in the Uniswap code, such as calculating the exchanged tokens according to the current price and liquidity. In this process, we must avoid losing precision during division operations. In Uniswap, the calculation process often uses the << FixedPoint 96.RESOLUTION operation, which represents a left shift of 96 bits, equivalent to multiplying by 2 ^ 96. After the left shift, the division operation is performed, so that the accuracy can be guaranteed without overflow in normal transactions (generally uint 256 is used for calculation, which is sufficient).
The code is as follows (calculating the number of tokens required for the transaction through price and liquidity):
As you can see, first of all, the price in Uniswap is multiplied by the square root of 2 ^ 96 (corresponding to sqrtRatioAX 96 and sqrtRatioBX 96 in the code above), and then the liquidity liquidity will be shifted left to calculate numerator 1. In the following calculation, 2 ^ 96 will be eliminated in the calculation process to get the final result.
Of course, no matter what, there will still be a loss of precision in theory, but in this case the smallest unit is lost, which is acceptable.
For more information, you can learn more from this course at https://github.com/WTFAcademy/WTF-Dapp/blob/main/P106_PoolSwap/readme.md .
Calculate income using Share
In Uniswap, we need to record the fee income of LP (liquidity providers). Obviously, we cannot record the fee for each LP in each transaction, which will consume a lot of Gas. So how to deal with it?
In Uniswap, we can see that the following structure is defined in Position:
It includes feeGrowthInside0LastX128 and feeGrowthInside1LastX128, which record the fee that each liquidity should receive when each position (Position) withdrew the fee last time.
To put it simply, I only need to record the total fee and how much fee should be allocated to each liquidity, so that when LP withdraws the fee, he can calculate how much fee he can withdraw according to the liquidity in his hand. Its like if you hold a companys stock, when you want to withdraw the stock income, you only need to know the companys historical earnings per share and the income when you last withdrew.
Previously, we introduced the stETH profit calculation method in the article Ingenious contract design, see how stETH automatically distributes profits on a daily basis? Let your ETH participate in staking to obtain stable interest , and the principle is similar.
Not all information needs to be obtained from the chain
On-chain storage is relatively expensive, so we don’t need to store all information on-chain or obtain it from the chain. For example, many of the interfaces called by the Uniswap front-end website are traditional Web2 interfaces.
The list of transaction pools, information about transaction pools, etc. can all be stored in ordinary databases. Some may need to be synchronized from the chain regularly, but we do not need to call the RPC interface provided by the chain or node service in real time to obtain relevant data.
Of course, many blockchain PRC suppliers now provide some advanced interfaces, you can get some data in a faster and cheaper way, which is similar. For example, ZAN provides an interface similar to getting all NFTs under a certain user. This information can obviously be cached to improve performance and efficiency. You can visit https://zan.top/service/advance-api to get more.
Of course, key transactions must be conducted on-chain.
Learn how to split contracts and use existing standard contracts like ERC 721
A project may contain multiple actually deployed contracts. Even if there is only one contract actually deployed, our code can split the contract into multiple contracts for maintenance through inheritance.
For example, in Uniswap, the https://github.com/Uniswap/v3-periphery/blob/main/contracts/NonfungiblePositionManager.sol contract inherits many contracts, and the code is as follows:
And when you look at the implementation of the ERC 721 Permit contract, you will find that it directly uses the @openzeppelin/contracts/token/ERC 721/ERC 721.sol contract. This makes it convenient to manage positions through NFT, and on the other hand, it can also use existing standard contracts to improve the efficiency of contract development.
In our course, you can learn https://github.com/WTFAcademy/WTF-Dapp/blob/main/P 108 _PositionManager /readme.md and try to develop a simple ERC 721 contract to manage positions.
Summarize
No matter how many articles you read, it is not as practical as developing it yourself. In the process of trying to implement a simplified version of a decentralized exchange yourself, you can have a deeper understanding of the Uniswap code implementation and learn more knowledge points that you will experience in actual projects.
The WTF-DApp course is an open source course jointly completed by the ZAN developer community and the WTF Academy developer community. If you are also interested in Web3 and Defi project development, you can refer to our practical course https://github.com/WTFAcademy/WTF-Dapp and complete a simple version of the exchange step by step. I believe it will definitely help you~
This article was written by Fisher (X account @yudao 1024 ) of ZAN Team (X account @zan_team ).