In this article, we will show how to build and deploy Uniswap V2 contracts, including the
uniswap-v2-periphery, into Phalcon Fork. We also cover how to create a Uniswap v2 pool, add liquidity and perform a swap in the pool.
Read this blog in a more user-friendly format with the following link:
Before diving into this document, you must understand the following background knowledge.
- How Uniswap works
- The Uniswap V2 Smart Contracts
- Foundry: Foundry manages your dependencies, compiles your project, runs tests, deploys, and lets you interact with the chain from the command-line and via Solidity scripts.
- Deploy and verify contract
- Forge create command
Phalcon Fork is a specialized tool designed for Web3 developers and security researchers to conduct collaborative testing with private mainnet states. It allows users to create a Fork from any mainnet state and send transactions to the Fork via an RPC endpoint. This innovative tool has two key features that set it apart from other platforms.
- Firstly, it offers the ability to browse all transactions and, more crucially, debug them using the Phalcon Explorer.
- Secondly, it boasts an internal block browser named Phalcon Scan, akin to Etherscan, facilitating easier viewing of transactions and accounts within the Fork.
Compile Uniswap V2
Clone necessary source code
We want to use Foundry to compile the contract. First, we can create an empty foundry project.
# forge init hello_foundry
This will create a foundry project. Then we add
v2-periphery project as submodules.
# git submodule add https://github.com/Uniswap/v2-core.git contracts/v2-core
# git submodule add https://github.com/Uniswap/v2-periphery.git contracts/v2-periphery
We also need to add
uniswap-lib as a submodule since the smart contracts in
v2-periphery relies on this library.
# git submodule add https://github.com/Uniswap/uniswap-lib lib/uniswap-lib
This will clone the corresponding GitHub repository into corresponding locations.
Change the source code
We need to change the source code of
contracts/v2-core/contracts/UniswapV2Factory.sol to add a global variable to record the init_code_hash of the UniswapV2Pair contract. This code hash is used by the
v2-Periphery contract to compute the contract address of each dex pool, e.g., WETH and USDC.
bytes32 public constant INIT_CODE_HASH = keccak256(abi.encodePacked(type(UniswapV2Pair).creationCode));
Then we create and edit the
remappings.txt to make the compiler find corresponding libraries.
# cat remappings.txt
Change the default source code directory (to “contracts”) in
src = “contracts”
out = “out”
libs = [“lib”]
# See more config options https://github.com/foundry-rs/foundry/tree/master/config
After that, we can compile the project.
# forge build
This will download the needed version of
solc compiler and build the
v2-periphery contracts. The generated source code is under
Deploy into Phalcon Fork
Create a Fork
We need to create a Fork first. Go to the dashboard of Phalcon Fork, and then create a Fork inside a project. We can name this Fork as
UniswapV2 or any other name you want. Note the RPC endpoint for this Fork.
Prepare Ether for the deployer
Before deploying the contract, the deployer should have Ether. If the deployer does not have enough Ether, we can use the
Faucet to add Ether to the deployer address, or directly transfer Ether from another account.
In this article, the deployer address is
0xbb8De73B06A0fF10e5ae9b65AaaeAEa22eB2C041.I used the second way to directly transfer Ether from the Binance Hot wallet to our deployer address. You can view this transaction.
We can use the
forge-create command to deploy and verify the
UniswapV2Factory contract. This contract is responsible for generating new dex pool.
forge create — rpc-url [RPC_URL] — private-key [DEPLOYER_PRIVATE_KEY] contracts/v2-core/contracts/UniswapV2Factory.sol:UniswapV2Factory — constructor-args [DEPLOYER_ADDRESS] — verify — verifier-url [API_URL] — etherscan-api-key [ACCESS_KEY]
The needed information in the command can be fetched from the configuration template. Click
Configuration in the Fork to get the information.
The [DEPLOYER_PRIVATE_KEY] is the private key of the contract deployer address.
The above command will deploy and verify the contract. If you only want to deploy (but do not want to verify) the contract, do not add
--verify --verifier  --etherscan-api-key  into the command.
The UniswapV2Factory contract is deployed to 0x24dd8cbe81075b16cf70666ac225113e9a57e8d9
Before deploying the router contract, we need to get the INIT_CODE_HASH of a pair. We can read the INIT_CODE_HASH of the deployed
# cast call — rpc-url [RPC_URL] 0x24Dd8CbE81075b16Cf70666AC225113E9a57e8d9 “INIT_CODE_HASH()”
Remember to change
0x24Dd8CbE81075b16Cf70666AC225113E9a57e8d9 to the deployed address of the
UniswapV2Factory contract in your Fork.
In our Fork, this invocation returns
0x015238e5df4461ceff35c64639ad0883e13effba2231011ef724ef164254cc68 as the
Change the line 24 of
contracts/v2-periphery/contracts/libraries/UniswapV2Library.sol to the returned
Since we have changed the source code, we need to compile the contract again.
# forge build
Deploy the Contract
# forge create — rpc-url [RPC_URL] — private-key [DEPLOYER_PRIVATE_KEY] contracts/v2-periphery/contracts/UniswapV2Router02.sol:UniswapV2Router02 — constructor-args [ADDRESS_OF_DEPLOYED_FACTORY_CONTRACT] 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 — verify — verifier-url [API_URL] — etherscan-api-key [ACCESS_KEY]
The two constructor args are the deployed factory contract address and the WETH contract address (0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2).
This will deploy and verify the router contract.
Use the script for this purpose
For easy use, we created a Python script to deploy the contract.
Before using this script, three environment variables need to be set.
export PRIVATE_KEY=[DEPLOYER PRIVATE KEY]
Use Uniswap V2
In the following section, I will show how to use the deployed contracts inside the Fork, including how to create a pool, add liquidity, and perform a swap.
Create a pair
The first step is to create a pair with two tokens. This is through the createPair function inside the factory contract we just deployed.
function createPair(address tokenA, address tokenB) external returns (address pair);
This function takes two token address, and then create a pair contract if the pool of these two tokens do not exist.
We can use
cast command to issue the transaction to create a pair inside the Phalcon Fork.
Cast is a command to perform Ethereum RPC calls. In particular,
cast send can be used to sign and publish a transaction, while
cast call can be used to perform a call on an account without publishing a transaction (not broadcasting to the blockchain).
cast send to publish a transaction, the
to address is needed, which is the destination of this transaction. The
args are needed if the transaction is a function call. Foundry supports different types of function signatures, like
cast send — rpc-url [RPC_URL] 0x24dd8cbe81075b16cf70666ac225113e9a57e8d9 “createPair(address,address)” 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 — from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 — unlocked
- 0x24dd8cbe81075b16cf70666ac225113e9a57e8d9: the address of the deployed factory contract.
- 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2: WETH
- 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48: USDC
We use the address
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 as the sender of this transaction. Note that this is a testing account, whose private key is public. DO NOT use this address in real cases!
We can get the created pair addresses from the transaction inside the Phalcon Fork. We can use the Phalcon Explorer to view this transaction. The created pair address is
The Phalcon Scan of a Fork
The transaction to create a pair — viewed using Phalcon Explorer
The pair contract is created using the factory contract. We can verify the created pair contract.
forge verify-contract — verifier-url [API_URL] 0x6951da28b9751b864bd15f6ed9a6b2b25cb10723 contracts/v2-core/contracts/UniswapV2Pair.sol:UniswapV2Pair — etherscan-api-key [ACCESS_KEY]
Note that, the address in the command is
0x6951da28b9751b864bd15f6ed9a6b2b25cb10723 , which is the newly created pair address.
A common error is using [FORK_URL] in the verifier. Please use [API_URL] instead (begins with https://api.phalcon.xyz/api/xxxx).
Get WETH and USDC
After creating the pair, we need to add liquidity into the pair. This means the LPs can deposit WETH and USDC into the pair, and let LP token as a certificate of the share inside this pool.
For WETH, we can invoke the
deposit function to deposit ETH into the contract and get WETH. For USDC, we can directly transfer from USDC from another address.
cast send — rpc-url [RPC_URL] 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 “deposit()” — from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 — unlocked — value 10ether
We deposit 10 Ether into the WETH contract, and get 10 WETH.
cast send — rpc-url[RPC_URL] 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 “transfer(address,uint256)” 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 2000000000000 — from 0x51eDF02152EBfb338e03E30d65C15fBf06cc9ECC — unlocked
- 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48: to address. The USDC contract.
- “transfer(address,uint256)” : invoked function signature
- 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 2000000000000: args of transfer function.
We transfer 2M USDC from
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 to our address
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266. The decimal of USDC is 6, so 2000000000000 means 2,000,000 USDC.
Now our address
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266has 10 WETH and 2M USDC.
Approve WETH/USDC to Router
Next step is to approve WETH/USDC to the router contract. That’s because when interacting with the router contract, it will directly transfer user’s token to the pool on behalf of the user.
Though the approval mechanism has some security loopholes, it still is commonly used in many contracts.
- Approve USDC and WETH
cast send — rpc-url [RPC_URL] 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 “approve(address,uint256)” 0xa20bf9733e1011C944D6334316456c52Df5C09A5 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff — from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 — unlocked
cast send — rpc-url [RPC_URL] 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 “approve(address,uint256)” 0xa20bf9733e1011C944D6334316456c52Df5C09A5 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff — from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 — unlocked
In these two commands, we approve max WETH and USDC to
0xa20bf9733e1011C944D6334316456c52Df5C09A5 -- the deployed router contract.
Approving max value is NOT a good security practice (see our research)! We use this for demo purposes!
addLiquidity function in the UniswapRouter contract is used to add two tokens into the pool, and get the LP tokens as a certificate of the share in the pool. Read more about this function on this document.
We can add 1 WETH and 2,000 USDC into the pool.
cast send — rpc-url [RPC_URL] 0xa20bf9733e1011C944D6334316456c52Df5C09A5 “addLiquidity(address,address, uint, uint, uint, uint, address,uint)” 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 1000000000000000000 2000000000 0 0 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 1991501602 — from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 — unlocked
- 0xa20bf9733e1011C944D6334316456c52Df5C09A5: to address, deployed router contract.
- “addLiquidity(address,address, uint, uint, uint, uint, address,uint)”: function sig
- 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2: tokenA 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48: Token B
- 1000000000000000000: AmountADesired
- 2000000000: AmountBDesired
- 0: AmountAMin
- 0: AmountBMin
- 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266: the receipt of the LP token
- 1991501602: deadline
From the Phalcon Explorer of this transaction, we can see the balance changes and the invocation flow.
When adding liquidity into the pool, you need to consider the actual value of the token. We add 1 WETH and 2000 USDC into the pool only for demo purposes!
Now we have liquidity in the pool, and anyone can perform a swap in this pool. Uniswap provides a couple of methods to swap the tokens, and we use
swapETHForExactTokens as an example. There are other functions can server the same purpose.
Receive an exact amount of tokens for as little ETH as possible, along the route determined by the path. The first element of path must be WETH, the last is the output token and any intermediate elements represent intermediate pairs to trade through (if, for example, a direct pair does not exist). Leftover ETH, if any, is returned to
This function swaps an exact amount of tokens using as little Ether as possible. The exact number of Ether needed is determined by the constant product formula. See the document for more information.
cast send — rpc-url [RPC_URL] 0xa20bf9733e1011C944D6334316456c52Df5C09A5 “swapETHForExactTokens(uint,address, address, uint)” 100000000 “[0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2,0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48]” 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC 1991501602 — from 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 — unlocked — value 5ether
This command tries to swap 100 USDC by sending 5 Ether. As shown in the Phalcon Explorer, the actual used Ether is around 0.05 Ether, and the remaining one is returned to the caller.
Again, we can swap 1000 USDC more. This time, around 1.17 Ether was used.
Debug a transaction
One may wonder how may Ether is needed to swap the token. We can use the
Debug functionality to debug a transaction -- the second one to swap 1000 USDC.
You can use
Next to navigate the source to see the core logic of
_swap function. Refer the Phalcon Explorer manual of how to use the
Debug functionality to dive into a transaction.
In this blog, we describe how to deploy Uniswap V2 contracts into Phalcon Fork step by step and how to interact with the deployed contracts inside the Fork. More importantly, we also illustrate how to use the Phalcon Explorer to view and debug a transaction and Phalcon Scan to view the transactions/addresses/contracts inside a Fork.
All the transactions this blog illustrates can be found inside the following Phalcon Scan (for a Fork).
Take a look and have fun with Phalcon Fork.