Web3 Beginner Series: Tips for Contract Development I Learned from Uniswap Code

CN
3 hours ago

Didn't expect contracts could be written like this? This is the most frequent sentiment expressed by the author recently~

Written by: Fisher, ZAN Team

Didn't expect contracts could be written like this? This is the most frequent sentiment expressed by the author recently~

Recently, while writing a tutorial on decentralized exchange development https://github.com/WTFAcademy/WTF-Dapp, I referred to the code implementation of Uniswap V3 and learned a lot of knowledge points. The author has previously developed simple NFT contracts, and this is the first time attempting to develop a DeFi contract. I believe these small tips will be very helpful for beginners who want to learn contract development.

Contract development experts can head directly to https://github.com/WTFAcademy/WTF-Dapp to contribute code and help build Web3~

Next, let's take a look at these small tips, some of which can even be considered clever tricks.

Predictable Contract Addresses on Deployment

Generally, the addresses we get when deploying contracts appear random because they are related to the "nonce," making contract addresses hard to predict. However, in Uniswap, we have a requirement: to infer the contract address from the trading pair and related information. This is useful in many cases, such as determining transaction permissions or obtaining pool addresses.

In Uniswap, contracts are created using code like pool = address(new UniswapV3Pool{salt: keccak256(abi.encode(token0, token1, fee))}());. By adding a "salt" and using CREATE2 (https://github.com/AmazingAng/WTF-Solidity/blob/main/25_Create2/readme.md), the benefit is that the created contract address is predictable. The logic for address generation is new address = hash("0xFF", creator address, salt, initcode).

You can check out the https://github.com/WTFAcademy/WTF-Dapp/blob/main/P103_Factory/readme.md chapter of the WTF-DApp course for more information.

Make Good Use of Callback Functions

In Solidity, contracts can call each other. One scenario is when A calls a method on B, and B calls back to A in the method being invoked. This can be very useful in certain situations.

In Uniswap, when you call the swap method of the UniswapV3Pool contract, it will call back swapCallback, passing in the calculated "Token" needed for the transaction. The caller needs to transfer the required Token to UniswapV3Pool in the callback, rather than splitting the swap method into two parts for the caller to invoke. This ensures the security of the swap method and guarantees that the entire logic is executed completely without cumbersome variable tracking to ensure safety.

The code snippet is as follows:

You can learn more about the trading section in the course https://github.com/WTFAcademy/WTF-Dapp/blob/main/P106_PoolSwap/readme.md.

Use Exceptions to Convey Information and Implement Transaction Estimation with Try-Catch

While referencing Uniswap's code, we found that in its https://github.com/Uniswap/v3-periphery/blob/main/contracts/lens/Quoter.sol contract, the swap method of UniswapV3Pool is executed within a try-catch block:

Why is this done? Because we need to simulate the swap method to estimate the Tokens needed for the transaction, but since no actual Token exchange occurs during estimation, it will throw an error. In Uniswap, it throws a special error in the transaction callback function and captures this error to parse the needed information from the error message.

It looks quite hacky, but it's very practical. This way, there's no need to modify the swap method for estimation needs, and the logic is simpler. In our course, we also referenced this logic to implement the contract https://github.com/WTFAcademy/WTF-Dapp/blob/main/demo-contract/contracts/wtfswap/SwapRouter.sol.

Use Big Numbers to Solve Precision Issues

In Uniswap's code, there are many calculation logics, such as calculating the exchanged Tokens based on the current price and liquidity. During this process, we need to avoid losing precision during division operations. In Uniswap, the calculation process often uses FixedPoint96.RESOLUTION, which represents a left shift of 96 bits, equivalent to multiplying by 2^96. After left shifting, division operations are performed, ensuring precision while normal transactions do not overflow (generally using uint256 for calculations, which is sufficient).

The code is as follows (calculating the number of Tokens needed for the transaction based on price and liquidity):

As you can see, in Uniswap, prices are represented by the square root multiplied by 2^96 (corresponding to sqrtRatioAX96 and sqrtRatioBX96 in the above code), and then liquidity liquidity is left-shifted to calculate numerator1. In the subsequent calculations, 2^96 will be canceled out during the calculation process, yielding the final result.

Of course, theoretically, there will still be some loss of precision, but this situation involves only the smallest unit loss, which is acceptable.

You can learn more in the course https://github.com/WTFAcademy/WTF-Dapp/blob/main/P106_PoolSwap/readme.md.

Calculate Earnings Using Shares

In Uniswap, we need to record the fee earnings of LPs (liquidity providers). Clearly, we cannot record each LP's fees during every transaction, as this would consume a lot of Gas. So how do we handle this?

In Uniswap, we can see that the Position defines the following structure:

It includes feeGrowthInside0LastX128 and feeGrowthInside1LastX128, which record the fees each liquidity position should receive during the last fee withdrawal.

In simple terms, I only need to record the total fees and how much fee each liquidity should be allocated. This way, when LPs withdraw fees, they can calculate how much they can withdraw based on their liquidity. It's like holding shares in a company; when you want to withdraw stock earnings, you only need to know the company's historical earnings per share and your last withdrawal earnings.

Previously, we also introduced the earnings calculation method of stETH in the article “Clever Contract Design: How Does stETH Automatically Distribute Earnings Daily? Let Your ETH Participate in Staking for Stable Interest”, which follows a similar principle.

Not All Information Needs to Be Retrieved from the Chain

On-chain storage is relatively expensive, so not all information needs to be stored on-chain or retrieved from the chain. For example, many of the interfaces called by the Uniswap frontend are traditional Web2 interfaces.

The list of trading pools, information about trading pools, etc., can be stored in a regular database. Some of this information may need to be periodically synchronized from the chain, but we do not need to call the RPC interfaces provided by the chain or node services in real-time to obtain relevant data.

Of course, many blockchain RPC providers now offer advanced interfaces that allow you to obtain some data more quickly and cheaply, which follows a similar principle. For example, ZAN provides an interface to retrieve all NFTs under a specific user, and this information can clearly be cached to improve performance and efficiency. You can visit https://zan.top/service/advance-api for more information.

Of course, key transactions must be conducted on-chain.

Learn to Split Contracts and Utilize Existing Standard Contracts Like ERC721

A project may consist of multiple actual deployed contracts. Even if there is only one contract deployed, we 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, as shown in the code below:

Moreover, when you look at the implementation of the "ERC721Permit" contract, you will find that it directly uses the "@openzeppelin/contracts/token/ERC721/ERC721.sol" contract. This not only facilitates the management of positions through NFTs but also improves the development efficiency of contracts by using existing standard contracts.

In our course, you can learn to develop a simple ERC721 contract to manage positions by visiting https://github.com/WTFAcademy/WTF-Dapp/blob/main/P108_PositionManager/readme.md.

Summary

Reading more articles is not as practical as hands-on development. Attempting to implement a simplified version of a decentralized exchange will give you a deeper understanding of Uniswap's code implementation and allow you to learn more practical knowledge that you will encounter in real 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 to complete a simplified version of an exchange step by step. I believe it will be helpful to you~

免责声明:本文章仅代表作者个人观点,不代表本平台的立场和观点。本文章仅供信息分享,不构成对任何人的任何投资建议。用户与作者之间的任何争议,与本平台无关。如网页中刊载的文章或图片涉及侵权,请提供相关的权利证明和身份证明发送邮件到support@aicoin.com,本平台相关工作人员将会进行核查。

Share To
APP

X

Telegram

Facebook

Reddit

CopyLink