Created
August 17, 2022 05:01
-
-
Save casweeney/d569cbd261bc9b6e081729804b328199 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // SPDX-License-Identifier: MIT | |
| pragma solidity ^0.8.4; | |
| import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | |
| contract Exchange is ERC20 { | |
| address public cryptoDevTokenAddress; | |
| // Exchange is inheriting ERC20, becase our exchange would keep track of Crypto Dev LP tokens | |
| constructor(address _CryptoDevtoken) ERC20("CryptoDev LP Token", "CDLP") { | |
| require(_CryptoDevtoken != address(0), "Token address passed is a null address"); | |
| cryptoDevTokenAddress = _CryptoDevtoken; | |
| } | |
| /** | |
| * @dev Returns the amount of `Crypto Dev Tokens` held by the contract | |
| */ | |
| function getReserve() public view returns (uint) { | |
| return ERC20(cryptoDevTokenAddress).balanceOf(address(this)); | |
| } | |
| /** | |
| * @dev Adds liquidity to the exchange. | |
| */ | |
| function addLiquidity(uint _amount) public payable returns (uint) { | |
| uint liquidity; | |
| uint ethBalance = address(this).balance; | |
| uint cryptoDevTokenReserve = getReserve(); | |
| ERC20 cryptoDevToken = ERC20(cryptoDevTokenAddress); | |
| /* | |
| If the reserve is empty, intake any user supplied value for | |
| `Ether` and `Crypto Dev` tokens because there is no ratio currently | |
| */ | |
| if(cryptoDevTokenReserve == 0) { | |
| // Transfer the `cryptoDevToken` from the user's account to the contract | |
| cryptoDevToken.transferFrom(msg.sender, address(this), _amount); | |
| // Take the current ethBalance and mint `ethBalance` amount of LP tokens to the user. | |
| // `liquidity` provided is equal to `ethBalance` because this is the first time user | |
| // is adding `Eth` to the contract, so whatever `Eth` contract has is equal to the one supplied | |
| // by the user in the current `addLiquidity` call | |
| // `liquidity` tokens that need to be minted to the user on `addLiquidity` call should always be propotional | |
| // to the eth specified by the user | |
| liquidity = ethBalance; | |
| _mint(msg.sender, liquidity); | |
| // _mint is ERC20.sol smart contract function to mint ERC20 tokens | |
| } else { | |
| /* | |
| If the reserve is not empty, intake any user supplied value for | |
| `Ether` and determine according to the ratio how many `Crypto Dev` tokens | |
| need to be supplied to prevent any large price impacts because of the additional | |
| liquidity | |
| */ | |
| // EthReserve should be the current ethBalance subtracted by the value of ether sent by the user | |
| // in the current `addLiquidity` call | |
| uint ethReserve = ethBalance - msg.value; | |
| // Ratio should always be maintained so that there are no major price impacts when adding liquidity | |
| // Ratio here is -> (cryptoDevTokenAmount user can add/cryptoDevTokenReserve in the contract) = (Eth Sent by the user/Eth Reserve in the contract); | |
| // So doing some maths, (cryptoDevTokenAmount user can add) = (Eth Sent by the user * cryptoDevTokenReserve /Eth Reserve); | |
| uint cryptoDevTokenAmount = (msg.value * cryptoDevTokenReserve)/(ethReserve); | |
| require(_amount >= cryptoDevTokenAmount, "Amount of tokens sent is less than the minimum tokens required"); | |
| // transfer only (cryptoDevTokenAmount user can add) amount of `Crypto Dev tokens` from users account | |
| // to the contract | |
| cryptoDevToken.transferFrom(msg.sender, address(this), cryptoDevTokenAmount); | |
| // The amount of LP tokens that would be sent to the user should be propotional to the liquidity of | |
| // ether added by the user | |
| // Ratio here to be maintained is -> | |
| // (LP tokens to be sent to the user(liquidity)/ totalSupply of LP tokens in contract) = (eth sent by the user)/(eth reserve in the contract) | |
| // by some maths -> liquidity = (totalSupply of LP tokens in contract * (eth sent by the user))/(eth reserve in the contract) | |
| liquidity = (totalSupply() * msg.value)/ ethReserve; | |
| _mint(msg.sender, liquidity); | |
| } | |
| return liquidity; | |
| } | |
| /** | |
| @dev Returns the amount Eth/Crypto Dev tokens that would be returned to the user | |
| * in the swap | |
| */ | |
| function removeLiquidity(uint _amount) public returns (uint , uint) { | |
| require(_amount > 0, "_amount should be greater than zero"); | |
| uint ethReserve = address(this).balance; | |
| uint _totalSupply = totalSupply(); | |
| // The amount of Eth that would be sent back to the user is based | |
| // on a ratio | |
| // Ratio is -> (Eth sent back to the user/ Current Eth reserve) | |
| // = (amount of LP tokens that user wants to withdraw)/ Total supply of `LP` tokens | |
| // Then by some maths -> (Eth sent back to the user) | |
| // = (Current Eth reserve * amount of LP tokens that user wants to withdraw)/Total supply of `LP` tokens | |
| uint ethAmount = (ethReserve * _amount)/ _totalSupply; | |
| // The amount of Crypto Dev token that would be sent back to the user is based | |
| // on a ratio | |
| // Ratio is -> (Crypto Dev sent back to the user/ Current Crypto Dev token reserve) | |
| // = (amount of LP tokens that user wants to withdraw)/ Total supply of `LP` tokens | |
| // Then by some maths -> (Crypto Dev sent back to the user/) | |
| // = (Current Crypto Dev token reserve * amount of LP tokens that user wants to withdraw)/Total supply of `LP` tokens | |
| uint cryptoDevTokenAmount = (getReserve() * _amount)/ _totalSupply; | |
| // Burn the sent `LP` tokens from the user'a wallet because they are already sent to | |
| // remove liquidity | |
| _burn(msg.sender, _amount); | |
| // Transfer `ethAmount` of Eth from user's wallet to the contract | |
| payable(msg.sender).transfer(ethAmount); | |
| // Transfer `cryptoDevTokenAmount` of `Crypto Dev` tokens from the user's wallet to the contract | |
| ERC20(cryptoDevTokenAddress).transfer(msg.sender, cryptoDevTokenAmount); | |
| return (ethAmount, cryptoDevTokenAmount); | |
| } | |
| /** | |
| @dev Returns the amount Eth/Crypto Dev tokens that would be returned to the user | |
| * in the swap | |
| */ | |
| function getAmountOfTokens( | |
| uint256 inputAmount, | |
| uint256 inputReserve, | |
| uint256 outputReserve | |
| ) public pure returns (uint256) { | |
| require(inputReserve > 0 && outputReserve > 0, "invalid reserves"); | |
| // We are charging a fees of `1%` | |
| // Input amount with fees = (input amount - (1*(input amount)/100)) = ((input amount)*99)/100 | |
| uint256 inputAmountWithFee = inputAmount * 99; | |
| // Because we need to follow the concept of `XY = K` curve | |
| // We need to make sure (x + Δx)*(y - Δy) = (x)*(y) | |
| // so the final formulae is Δy = (y*Δx)/(x + Δx); | |
| // Δy in our case is `tokens to be recieved` | |
| // Δx = ((input amount)*99)/100, x = inputReserve, y = outputReserve | |
| // So by putting the values in the formulae you can get the numerator and denominator | |
| uint256 numerator = inputAmountWithFee * outputReserve; | |
| uint256 denominator = (inputReserve * 100) + inputAmountWithFee; | |
| return numerator / denominator; | |
| } | |
| /** | |
| @dev Swaps Ether for CryptoDev Tokens | |
| */ | |
| function ethToCryptoDevToken(uint _minTokens) public payable { | |
| uint256 tokenReserve = getReserve(); | |
| // call the `getAmountOfTokens` to get the amount of crypto dev tokens | |
| // that would be returned to the user after the swap | |
| // Notice that the `inputReserve` we are sending is equal to | |
| // `address(this).balance - msg.value` instead of just `address(this).balance` | |
| // because `address(this).balance` already contains the `msg.value` user has sent in the given call | |
| // so we need to subtract it to get the actual input reserve | |
| uint256 tokensBought = getAmountOfTokens( | |
| msg.value, | |
| address(this).balance - msg.value, | |
| tokenReserve | |
| ); | |
| require(tokensBought >= _minTokens, "insufficient output amount"); | |
| // Transfer the `Crypto Dev` tokens to the user | |
| ERC20(cryptoDevTokenAddress).transfer(msg.sender, tokensBought); | |
| } | |
| /** | |
| @dev Swaps CryptoDev Tokens for Ether | |
| */ | |
| function cryptoDevTokenToEth(uint _tokensSold, uint _minEth) public { | |
| uint256 tokenReserve = getReserve(); | |
| // call the `getAmountOfTokens` to get the amount of ether | |
| // that would be returned to the user after the swap | |
| uint256 ethBought = getAmountOfTokens( | |
| _tokensSold, | |
| tokenReserve, | |
| address(this).balance | |
| ); | |
| require(ethBought >= _minEth, "insufficient output amount"); | |
| // Transfer `Crypto Dev` tokens from the user's address to the contract | |
| ERC20(cryptoDevTokenAddress).transferFrom( | |
| msg.sender, | |
| address(this), | |
| _tokensSold | |
| ); | |
| // send the `ethBought` to the user from the contract | |
| payable(msg.sender).transfer(ethBought); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment