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 |