Skip to content

Instantly share code, notes, and snippets.

@casweeney
Created August 17, 2022 05:01
Show Gist options
  • Select an option

  • Save casweeney/d569cbd261bc9b6e081729804b328199 to your computer and use it in GitHub Desktop.

Select an option

Save casweeney/d569cbd261bc9b6e081729804b328199 to your computer and use it in GitHub Desktop.
// 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