Pool Playground
- 1. Overview
- 2. Installation
- 3. Testing
- 4. Deployment
- 5. Interactions
- 6. Bugs and Feature Requests
- 7. Authors
- 8. License
1. Overview
An interactive educational playground for visualizing and learning Uniswap V2 mechanics by swapping testnet ERC20 tokens.
Learn all about Pool Playground here.
Live on https://pool.eridian.xyz
2. Installation
2.1. Clone repository
git clone https://github.com/EridianAlpha/pool-playground.git
2.2. Install Dependencies
This should happen automatically when first running a command, but the installation can be manually triggered with the following commands:
git submodule init
git submodule update
make install
2.3. Create the .env file
Use the .env.example file as a template to create a .env file.
3. Testing
3.1. Tests
make test
make test-fork
make test-v
make test-v-fork
make test-summary
make test-summary-fork
3.2. Coverage
make coverage
make coverage-fork
make coverage-report
make coverage-report-fork
4. Deployment
4.1. Deploying Uniswap V2 Contracts (optional)
This step is optional and only necessary if you need to deploy the Uniswap V2 core contracts to a new chain. The Uniswap V2 core contracts have already been deployed on Mainnet (used for forks only), Holesky, Sepolia, Base Sepolia, Arbitrum Sepolia, and Optimism Sepolia chains. The addresses of these existing deployments can be found in the .env.example file.
Fork the Uniswap v2-periphery repository and modify the v2-periphery/contracts/libraries/UniswapV2Library.sol contract with the correct init code hash on line 24. Without changing this init code hash, the contract will not be able to calculate the correct pair address. There are a number of reasons why the init code hash changes, but the most likely reason is that the contract was deployed with a different compiler version or with different compiler settings.
You can try to configure your complier to match the settings of the original deployment, but this is not always possible. The best way to get the correct init code hash is to inspect the creation bytecode of the contract that you are using and manually change the init code hash.
# Get the creation bytecode of the UniswapV2Pair contract
CREATION_BYTECODE=$(forge inspect lib/v2-core/contracts/UniswapV2Pair.sol:UniswapV2Pair bytecode)
# Calculate the init code hash of the UniswapV2Pair contract
cast keccak "$CREATION_BYTECODE"
0x0be7e5aaf721ce53efd2148867ffca974ca93687937cd12b66ab2acef87168ed
# Remove the 0x from the start and use that as the "init code hash"
# in `v2-periphery/contracts/libraries/UniswapV2Library.sol` line 24
0be7e5aaf721ce53efd2148867ffca974ca93687937cd12b66ab2acef87168ed
Once you have the correct init code hash updated in your own fork of the Uniswap v2-periphery repository, you can install it to your local environment.
# Example of installing the EridianAlpha/v2-periphery repository with the modified init code hash
forge install EridianAlpha/v2-periphery --no-commit
After installing the modified Uniswap v2-periphery repository, you can deploy the Uniswap V2 Factory and Router02 contracts to the specified chain.
| Chain | Command |
|---|---|
| Anvil | make deploy-uniswapV2-anvil |
| Holesky | make deploy-uniswapV2-ethereum-holesky |
| Sepolia | make deploy-uniswapV2-ethereum-sepolia |
| Base Sepolia | make deploy-uniswapV2-base-sepolia |
| Arbitrum Sepolia | make deploy-uniswapV2-arbitrum-sepolia |
| Optimism Sepolia | make deploy-uniswapV2-optimism-sepolia |
4.2. Deploying PoolPlayground Contract
Deploys PoolPlayground to the specified chain.
| Chain | Command |
|---|---|
| Anvil | make deploy anvil |
| Holesky | make deploy ethereum-holesky |
| Sepolia | make deploy ethereum-sepolia |
| Base Sepolia | make deploy base-sepolia |
| Arbitrum Sepolia | make deploy arbitrum-sepolia |
| Optimism Sepolia | make deploy optimism-sepolia |
5. Interactions
Interactions are defined in ./script/Interactions.s.sol
If DEPLOYED_CONTRACT_ADDRESS is set in the .env file, that contract address will be used for interactions.
If that variable is not set, the latest deployment on the specified chain will be used.
5.1. Deploy Playground Instance
Call the deploy() function on the PoolPlayground contract.
The input parameters are defined in the Interactions.s.sol script.
| Chain | Command |
|---|---|
| Anvil | make deployPlaygroundInstance anvil |
| Holesky | make deployPlaygroundInstance ethereum-holesky |
| Sepolia | make deployPlaygroundInstance ethereum-sepolia |
| Base Sepolia | make deployPlaygroundInstance base-sepolia |
| Arbitrum Sepolia | make deployPlaygroundInstance arbitrum-sepolia |
| Optimism Sepolia | make deployPlaygroundInstance optimism-sepolia |
6. Bugs and Feature Requests
If you encounter any bugs or have a feature request, please open an issue on GitHub. To help us resolve the issue, please provide the following information:
6.1. Bugs
- A detailed description of the bug.
- Steps to reproduce the bug.
- Expected behavior and actual behavior.
- Screenshots, if possible, and any additional context or information that may help us resolve the bug.
- If you have a solution, suggestion, or code change, please submit a pull request.
6.2. Feature Requests
- For feature requests, questions, or feedback, please open an issue.
- For security issues or general inquiries, please contact Eridian privately.
7. Authors
8. License
PoolPlayground
Author: EridianAlpha
An interactive educational playground for visualizing and learning Uniswap V2 mechanics by swapping testnet ERC20 tokens.
State Variables
TOKEN_DECIMALS
uint256 public constant TOKEN_DECIMALS = 10 ** 18;
MARKET_PRICE_USD
TokenAmounts public MARKET_PRICE_USD = TokenAmounts({diamond: 100, wood: 20, stone: 2});
s_userTokens
mapping(address => TokenAddresses) internal s_userTokens;
s_userInitialTokenBalances
mapping(address => TokenAmounts) internal s_userInitialTokenBalances;
s_contractAddresses
mapping(string => address) internal s_contractAddresses;
Functions
constructor
Constructor to set the Uniswap contract addresses for the network.
constructor(ContractAddress[] memory _contractAddresses);
Parameters
| Name | Type | Description |
|---|---|---|
_contractAddresses | ContractAddress[] | An array of ContractAddress structs. |
deploy
Deploy a new playground instance.
Overwrites any existing tokens and pools for the user.
function deploy(TokenAmounts calldata _userTokenAmounts, TokenAmounts[] calldata _poolTokenAmounts) public;
Parameters
| Name | Type | Description |
|---|---|---|
_userTokenAmounts | TokenAmounts | The amount of tokens to mint for the user. |
_poolTokenAmounts | TokenAmounts[] | The amount of tokens to add to the Uniswap pools. |
createTokens
Create tokens.
function createTokens(TokenAmounts memory _mintTokenAmounts) internal returns (TokenAddresses memory tokenAddresses);
Parameters
| Name | Type | Description |
|---|---|---|
_mintTokenAmounts | TokenAmounts | The total amount of tokens to mint. |
createUniswapV2Pools
Create UniswapV2 pools.
function createUniswapV2Pools(TokenAddresses memory _tokenAddresses, TokenAmounts[] memory _poolTokenAmounts)
internal;
Parameters
| Name | Type | Description |
|---|---|---|
_tokenAddresses | TokenAddresses | The token addresses for the user. |
_poolTokenAmounts | TokenAmounts[] | The amount of tokens to add to the Uniswap pools. |
createPairAndAddLiquidity
Create UniSwapV2 pair and add liquidity.
function createPairAndAddLiquidity(
IUniswapV2Router02 uniswapV2Router,
address tokenA,
address tokenB,
uint256 amountA,
uint256 amountB
) internal;
Parameters
| Name | Type | Description |
|---|---|---|
uniswapV2Router | IUniswapV2Router02 | |
tokenA | address | The address of the first token. |
tokenB | address | The address of the second token. |
amountA | uint256 | The amount of tokenA to add to the pool. |
amountB | uint256 | The amount of tokenB to add to the pool. |
sendRemainingTokens
Send remaining tokens to the user.
function sendRemainingTokens(TokenAddresses memory _tokenAddresses) internal;
Parameters
| Name | Type | Description |
|---|---|---|
_tokenAddresses | TokenAddresses | The token addresses for the user. |
getContractAddress
Get the contract address for a given identifier.
function getContractAddress(string memory _identifier) public view returns (address);
Parameters
| Name | Type | Description |
|---|---|---|
_identifier | string | The identifier of the contract. |
getUserTokens
Get the deployed tokens for a user.
function getUserTokens(address _user) public view returns (TokenAddresses memory);
Parameters
| Name | Type | Description |
|---|---|---|
_user | address | The address of the user. |
getUserTokenBalances
Get the all the token balances for a user.
function getUserTokenBalances(address _user) public view returns (TokenAmounts memory userTokenBalances);
Parameters
| Name | Type | Description |
|---|---|---|
_user | address | The address of the user. |
Returns
| Name | Type | Description |
|---|---|---|
userTokenBalances | TokenAmounts | The token balances for the user. |
getUserInitialTokenBalances
Get the initial token balances for a user.
function getUserInitialTokenBalances(address _user)
public
view
returns (TokenAmounts memory userInitialTokenBalances);
Parameters
| Name | Type | Description |
|---|---|---|
_user | address | The address of the user. |
Returns
| Name | Type | Description |
|---|---|---|
userInitialTokenBalances | TokenAmounts | The initial token balances for the user. |
Events
TokensAndPoolsCreated
event TokensAndPoolsCreated(address indexed user);
Structs
ContractAddress
struct ContractAddress {
string identifier;
address contractAddress;
}
TokenAddresses
struct TokenAddresses {
address diamond;
address wood;
address stone;
}
TokenAmounts
struct TokenAmounts {
uint256 diamond;
uint256 wood;
uint256 stone;
}
Token
Inherits: ERC20, ERC20Permit
Author: EridianAlpha
An ERC20 token contract used in the Pool Playground project.
State Variables
preApprovedAddresses
address[] public preApprovedAddresses;
Functions
constructor
Constructor for the Token contract
constructor(uint256 initialMintAmount, string memory name, string memory symbol, address[] memory _preApprovedAddresses)
ERC20(name, symbol)
ERC20Permit(name);
Parameters
| Name | Type | Description |
|---|---|---|
initialMintAmount | uint256 | The amount of tokens to mint to the deployer |
name | string | The name of the token |
symbol | string | The symbol of the token |
_preApprovedAddresses | address[] | An array of addresses that are pre-approved to spend an unlimited amount of tokens |
isPreApproved
Checks if an address is pre-approved to spend an unlimited amount of tokens
function isPreApproved(address spender) public view returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
spender | address | The address to check |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | true if the address is pre-approved, false otherwise |
allowance
Overrides the allowance function to return type(uint256).max for pre-approved addresses
function allowance(address owner, address spender) public view override returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | The owner of the tokens |
spender | address | The spender of the tokens |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | The allowance of the spender |
approve
Overrides the approve function to return true for pre-approved addresses
function approve(address spender, uint256 amount) public override returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
spender | address | The spender of the tokens |
amount | uint256 | The amount of tokens to approve |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | true if the approval was successful, false otherwise |