Thanos Development Story — 5
Predeploy Contracts: UniswapV3
As part of our ongoing advancements in Layer 2 solutions, the new Thanos network is being developed with cutting-edge rollup technology to deliver enhanced services. In this development story, we will discuss the predeployment of UniswapV3 contracts, following the predeployment of the USDC Bridge contracts previously covered.
Thanos Development Story Series
Key Advantages of Predeployment:
- Improved Network Stability: By deploying critical contracts in advance, we minimize potential issues during initial network operations.
- Immediate Service Provision: Essential features are swiftly and reliably implemented before the mainnet launch, allowing users to access services immediately upon deployment without additional steps, providing enhanced convenience.
Background Information:
Unlike the previous USDC Bridged, UniswapV3 involves multiple repositories importing various contracts. Instead of adding all contracts to the repository, we built the contracts locally using Hardhat and added only the artifacts to the repository. Uniswap V3 Artifacts.
Both op-bindings and op-chain-ops packages are used in UniswapV3. Op-bindings facilitate contract deployment in a Go environment, dynamically setting bytecode and storage slots, while op-chain-ops configures the network state, generating Layer 1 and Layer 2 genesis blocks, deploying contracts, calculating implementation addresses, and creating immutable values and storage for the initial genesis state.
1. Predeployed Smart Contracts
Deploying UniswapV3 contracts on Layer 2 solutions, such as Optimism, offers significant advantages. For instance, UniswapV3 deployed on Optimism confirms transactions instantly, reduces costs by up to 10 times compared to L1 Ethereum, and improves scalability by supporting up to 0.6 transactions per second. These benefits also apply to the Thanos Network, enhancing user experience and leveraging high scalability and low-cost structure to activate DeFi services.
UniswapV3 is a binary smart contract system composed of several libraries, and we will examine the 11 primary contracts predeployed in the L2 network from a library-centric perspective.
1. Core_[link]
The Core library ensures the safety of assets for all Uniswap users. It consists of a single factory, pool deployer, and multiple pools generated by the factory, defining the pool creation logic and asset interaction.
- UniswapV3Factory.sol: Defines the logic for creating pools, comprising two tokens and fees. Multiple pools can exist for the same asset pair, distinguished by fees.
2. Periphery_[link]
The Periphery library supports domain-specific interactions with the Core contracts.
- SwapRouter.sol: Executes stateless swaps in UniswapV3, with functions like
exactInputSingle
andexactInput
for token exchanges, andexactOutputSingle
andexactOutput
for specific token exchanges. TheuniswapV3SwapCallback
function handles token payments post-swap. - NonfungiblePositionManager.sol: Manages transactions related to creating, adjusting, or terminating positions.
- NonfungibleTokenPositionDescriptor.sol: The only proxy contract in UniswapV3, generates JSON metadata URI for specific token IDs, returning ERC721-compliant metadata URI using the
tokenURI
function. - TickLens.sol: Queries populated ticks in a specific bitmap index within a Uniswap V3 pool.
- QuoterV2.sol: Provides expected amounts for a given swap without executing it.
- NFTDescriptor.sol: Generates metadata and images dynamically for NFTs, including token URI creation, string conversion, and SVG image generation.
- UniswapInterfaceMulticall.sol: Provides multicall functionality for the Uniswap interface, executing multiple smart contract calls in one transaction and returning results and gas used.
3. UniversalRouter_[link]
Supports ETH, ERC20, and NFT swaps across eight marketplaces, offering flexible and customized transactions by integrating multiple protocols.
- UniversalRouter.sol: Executes efficient and secure ERC20 and NFT swap commands, validating transaction deadlines and processing results through repeated commands across various media.
- UnsupportedProtocol.sol: A dummy contract that always reverts, preventing attempts to call unsupported protocols on specific chains.
4. Permit2_[link]
A library used in UniversalRouter to allow users to avoid direct token approvals each time.
- Permit2.sol: Allows transactions through a one-time signature with
SignatureTransfer
and sets specific token usage rights for a defined period withAllowanceTransfer
. This flexibility and security enhance token transaction management, eliminating repetitive approvals through the Permit2 contract.
2. Development Process
The predeployment process for UniswapV3 contracts follows the same steps as those used in the previous USDC Bridge deployment. A brief summary of these steps is outlined below:
- addresses.go: This file pre-defines and initializes the addresses of each contract, and determines whether a proxy pattern will be used for the contracts.
- config.go: This file initializes each contract’s constructor and storage through
NewL2ImmutableConfig
andNewL2StorageConfig
. TheNewL2ImmutableConfig
file defines the constructor, whileNewL2StorageConfig
sets the addresses and values necessary for storage initialization. In a standard deployment, constructor values or storage information (such as addresses or permissions for interacting with other contracts) would be automatically set according to the contract's logic. However, in the predeployment process, this setup is done manually. - immutables.go: This file inputs immutable settings for contracts that include immutable variables. The
PredeploysImmutableConfig
structure is defined for this purpose, and thel2ImmutableDeployer
function ensures that each array contains the appropriate types before deploying the contract. - setter.go: This file manages the admin settings for specific proxy contracts. For Uniswap, only the
NonfungibleTokenPositionDescriptor
is a proxy contract, so the configurations for this contract need to be set accordingly. - layer_two.go: This file defines functions related to constructing the genesis block for Layer 2, ultimately setting the initial state of the Layer 2 network and storing the necessary contracts and addresses.
3. Development Differences
The main difference between predeploying USDC Bridge and UniswapV3 lies in the build tools used. USDC Bridge used Forge, while UniswapV3 used Hardhat. Each tool has its pros and cons, necessitating different workflows.
First, when we clone each library and build it into hardhat, we need to add a storage layout so that we can check that it fits properly later. To do this, we added the storageLayout option to hardhat.config.ts so that it can be generated with the contract when it is built.
When building each library with Hardhat, adding the storageLayout option to the hardhat.config.ts file ensures proper verification. By including storageLayout in the outputSelection property, storage layouts are generated alongside contract builds. Unlike Foundry, which creates storageLayout within artifact files, Hardhat generates it within buildinfo files, requiring careful consideration.
Here is the storageLayout
for the UniswapV3Factory
contract, illustrating variable roles and storage slots:
parameters
: Stores theParameters
struct at slot 0.owner
: Stores the contract owner’s address at slot 3.feeAmountTickSpacing
: Maps fee amounts and tick spacing at slot 4.getPool
: Maps pool addresses based on token pairs and fee amounts at slot 5.
Accurately defining storage slots is crucial to avoid initialization errors or storage allocation issues. The storageLayout
ensures precise variable allocation during predeploy, facilitating error detection and correction.
Binding files must also specify storageLayout positions for each contract, ensuring accurate interactions and verification throughout the predeploy process. The following is an example of a template for generating binding files:
The processHardhatArtifacts
function generates binding files for smart contracts, enabling Solidity contracts to be used in Go programs. Key logic includes:
for _, contractName := range contractNames {
artifactPath := filepath.Join(f.HardhatArtifacts, contractName+".json")
log.Printf("Processing artifact at path: %s\n", artifactPath)
log.Printf("generating code for : %s", contractName)
- Loop through
contractNames
to process each contract. - Set the path to the JSON artifact file for each smart contract in the
artifactPath
variable.f.HardhatArtifacts
is the directory path where the artifact files are located.
art, err := hh.GetArtifact(contractName)
if err != nil {
log.Fatalf("error reading artifact %s: %v\n", contractName, err)
}
storage, err := hh.GetStorageLayout(contractName)
if err != nil {
log.Fatalf("error reading storage layout %s: %v\n", contractName, err)
}
- Fetching Artifacts: Call
hh.GetArtifact(contractName)
to retrieve the artifact of the smart contract from Hardhat. This artifact includes the contract's ABI and bytecode information. - Retrieving Storage Layout: Call
hh.GetStorageLayout(contractName)
to obtain the storage layout information of the smart contract from Hardhat.
ser, err := json.Marshal(storage)
if err != nil {
log.Fatalf("error marshaling storage: %v\n", err)
}
serStr := strings.Replace(string(ser), "\"", "\\\"", -1)
- Serializing Storage Layout: Call
json.Marshal(storage)
to serialize the storage layout object into a JSON byte array format. - Error Handling: If an error occurs during the serialization process, the program terminates and logs the error message.
- String Conversion: Convert the serialized byte array into a string and replace all double quotes (
"
) with escaped double quotes (\"
). This ensures the JSON string is correctly processed according to the template requirements.
// Prepare data for template execution.
d := data{
Name: contractName,
StorageLayout: serStr,
DeployedBin: art.DeployedBytecode.(string),
Package: f.Package,
}
Initializing Data Structure: Initialize the data
structure containing the necessary information for template execution.
Name
: The current smart contract’s name.StorageLayout
: The serialized storage layout string.DeployedBin
: The deployed bytecode of the smart contract.Package
: The package name of the generated Go file.
By retrieving each smart contract’s artifact and storage layout, and subsequently generating the corresponding binding files in Go, the following outcome can be achieved.
This document outlines the advantages of utilizing UniswapV3-related contracts on Layer 2 solutions, the reasons for predeployment, and the steps involved in the predeployment process. The predeployment process involves clearly defining the composition and functionality of each contract and deploying them in accordance with the Layer 2 network requirements. This process includes configuring the storage layout and generating binding files for the contracts, thereby clearly defining the initial state.