![Logo](/Aboudoc/Constant-Product-AMM/raw/main/images/logo.png)
A basic Constant Product AMM
Explore the docs »
View Demo
·
Report Bug
·
Request Feature
Table of Contents
- About The Project
- Getting Started
- Usage
- Constant Product AMM
- Time Weighted Average Price
- Uniswap V2 Price Oracle
- Constant Product AMM Spot Price Examples
- Uniswap V3 Price Oracle
- Geometric Mean
- Uniswap V3 TWAP and Geometric Mean
- Uniswap V3 TWAP Inverse Price
- Roadmap
- Contributing
- License
- Contact
- Acknowledgments
This project shows a basic Constant Product Automated Market Maker.
You will also find all the Maths used in Uniswap V2 and Uniswap V3
To get a local copy up and running follow these simple example steps.
-
npm
npm install npm@latest -g
-
hardhat
npm install --save-dev hardhat
npm install @nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle
run:
npx hardhat
verify:
npx hardhat verify --network goerli "contract address" "pair address"
-
Clone the repo
git clone https://github.com/Aboudoc/Constant-Product-AMM.git
-
Install NPM packages
npm install
-
Dependencies
npm i @uniswap/v2-core @uniswap/v2-periphery
If you need testnet funds, use the Alchemy testnet faucet.
This project shows a basic example of Constant Product AMM
Constant product AMM (automated market maker) is a decentralized exchange where 2 tokens are traded.
Uniswap trading fee = 0.3%
Motivation:
What is L0 and L1
Simplify the equation:
Conclusion
We saw the math to calculate TWAP, you can also calculate it in Uniswap V2 using Solidity.
This is the purpose of UniswapV2Twap
contract
In Uniswap V2, the numbers are represented in decimals, but Solidity does not have any decimal datatypes, so we'ell be using FixedPoint.sol
library
The datatype FixedPoint.uq112x112
represents a decimal number as follows:
range: [0, 2e112 - 1] resolution: 1 / 2e112
The IUniswapV2Pair
contract is the pair contract that holds the two tokens and does the swaps
Since we're using Solidity 0.6, constructor must be declared as public
- We set the pair first
- We set
token0
andtoken1
frompair
- We record
price0CumulativeLast
andprice1CumulativeLast
and the last time that these variables were updated:blockTimestampLast
usinggetReserves()
, it returns 3 outputs but we only need the third
-
To get the current
price0Cumulative
, currentprice1Cumulative
andblockTimestamp
, we callcurrentCumulativePrices()
onUniswapV2OracleLibrary
by passing in the address of the pair -
Calculate how much time has elapsed since the last time we called
update()
. This time elapsed must be greather than theperiod
defined as state variable -
Calculate the price averages (
price0Average
andprice1Average
) by taking the current price cumulative, substracting it from the last price cumulative, and dividing it over the time elapsed. Cast it into aFixedPoint
=> Note thatup112x112
is a struct and the input for this struct must be auint224
=> cast the calcul expressionThe expression below, used to calculate the price averages, is the exact same equation that we derived from
How do we compute the Time Weighted Average Price from Tk to Tn?
price0Average = FixedPoint.uq112x112( uint224((price0Cumulative - price0CumulativeLast) / timeElapsed) ); price1Average = FixedPoint.uq112x112( uint224((price1Cumulative - price1CumulativeLast) / timeElapsed) );
Keep in mind that we don't care if the numbers overflows This is why we are not using
SafeMath
-
Finally, update the state variables:
price0CumulativeLast
,price1CumulativeLast
andblockTimestampLast
Giving the token and the amount of token put in, this function will calculate the amount out using the price0Average
and price1Average
- The token must be token0 or token1
- Compute
amountOut
usingprice0Average
andamountIn
argument (.mul). We have to put it back to uint using a function calleddecode144()
You can find more in the following article Using the new Uniswap v2 as oracle
However, virtual reserves are not tracked in Uni V3
The Tick
that is used to compute the current price is tracked in Uni V3
- Set
token0
andtoken1
- get the pool by calling
getPool()
on,IUniswapV3Factory
and set the state variable - To call
getPool()
we need to pass in 3 parameters: address of the tokens, and the fee => set factory address and fee (uint24) - Check that the pool is a valid pool (not address(0)) and finally set it (state variable)
- Define
tokenIn
andtokenOut
- Get the average tick: Call
consult()
onOracleLibrary
like so:
(int24 tick, ) = OracleLibrary.consult(pool, secondsAgo);
We'll copy paste the code to compute tick
directly from the library to save some gas
- Call
GetQuoteAtTick()
onOracleLibrary
to set theamountOut
hardhat.config.js
networks: {
hardhat: {
forking: {
url: `https://eth-mainnet.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`,
},
},
}
.env
ALCHEMY_API_KEY=...
terminal1
ALCHEMY_API_KEY=...
npx hardhat node --fork https://eth-mainnet.g.alchemy.com/v2/$ALCHEMY_API_KEY
terminal2
npx hardhat test --network localhost
This contract assumes that token0 and token1 both have same decimals
Consider fees = 0.3%
(...soon)
- Uniswap V3 TWAP
- Further reading
- Deploy script
- Unit test
See the open issues for a full list of proposed features (and known issues).
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
- Fork the Project
- Create your Feature Branch (
git checkout -b feature/AmazingFeature
) - Commit your Changes (
git commit -m 'Add some AmazingFeature'
) - Push to the Branch (
git push origin feature/AmazingFeature
) - Open a Pull Request
Distributed under the MIT License. See LICENSE.txt
for more information.
Reda Aboutika - @twitter - reda.aboutika@gmail.com
Project Link: https://github.com/Aboudoc/Constant-Product-AMM.git