Last active
May 1, 2023 23:14
-
-
Save Graeme22/e008239b4ba569039d183b82dc53031f 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.17; | |
| import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | |
| contract DecentralizedStockWithAMM is ERC20 { | |
| uint public reserveToken; | |
| uint public reserveEther; | |
| uint public totalFees; | |
| uint public totalUnclaimed; | |
| address[] liquidityProviders; | |
| mapping(address => uint) public individualLiquidity; | |
| mapping(address => uint) public individualFees; | |
| // represents amount of liquidity provided | |
| uint public liquiditySupply; | |
| // used for IPO | |
| uint public ipoDeadline; | |
| uint public ipoPricePerShare; | |
| // the IPO funds will be sent here | |
| address public ipoAddress; | |
| constructor(string memory _name, string memory _symbol, uint256 initialSupply, | |
| uint _ipoPricePerShare, uint _ipoDeadline, address payable _ipoAddress) ERC20(_name, _symbol) { | |
| _mint(msg.sender, initialSupply); | |
| ipoDeadline = _ipoDeadline; | |
| ipoPricePerShare = _ipoPricePerShare; | |
| ipoAddress = _ipoAddress; | |
| } | |
| function _realEtherBalance() private view returns(uint) { | |
| return address(this).balance - totalFees - totalUnclaimed; | |
| } | |
| function _mintLiquidity(address _to, uint _amount) private { | |
| if(individualLiquidity[_to] == 0) | |
| liquidityProviders.push(_to); | |
| individualLiquidity[_to] += _amount; | |
| liquiditySupply += _amount; | |
| } | |
| function _burnLiquidity(address _from, uint _amount) private { | |
| individualLiquidity[_from] -= _amount; | |
| liquiditySupply -= _amount; | |
| } | |
| function _update(uint _reserveEther, uint _reserveToken) private { | |
| reserveEther = _reserveEther; | |
| reserveToken = _reserveToken; | |
| } | |
| function _updateFees() private { | |
| for(uint i = 0; i < liquidityProviders.length; i++) { | |
| address lp = liquidityProviders[i]; | |
| individualFees[lp] += individualLiquidity[lp] * totalFees / liquiditySupply; | |
| } | |
| totalUnclaimed += totalFees; | |
| totalFees = 0; | |
| } | |
| function vote(string[] memory _proposal, uint _deadline) public { | |
| // todo: implement | |
| // staked shares should still be able to vote | |
| } | |
| function getCurrentBlockTimestamp() public view returns(uint) { | |
| return block.timestamp; | |
| } | |
| function IPO() public payable { | |
| require(block.timestamp < ipoDeadline, "IPO has ended"); | |
| _mint(msg.sender, msg.value / ipoPricePerShare); | |
| (bool sent,) = ipoAddress.call{value: msg.value}(""); | |
| } | |
| function sharesForEther(uint _sharesIn) external returns (uint etherOut) { | |
| require(_sharesIn > 0, "amount in must greater than zero"); | |
| transfer(address(this), _sharesIn); | |
| uint _etherOut = (reserveEther * _sharesIn) / (reserveToken + _sharesIn); | |
| // 0.5% fee | |
| uint fee = _etherOut * 5 / 1000; | |
| totalFees += fee; | |
| etherOut = _etherOut - fee; | |
| (bool sent,) = msg.sender.call{value: etherOut}(""); | |
| _update(_realEtherBalance(), balanceOf(address(this))); | |
| } | |
| function etherForShares() external payable returns (uint sharesOut) { | |
| require(msg.value > 0, "amount in must greater than zero"); | |
| // 0.5% fee | |
| uint fee = msg.value * 5 / 1000; | |
| totalFees += fee; | |
| uint etherIn = msg.value - fee; | |
| sharesOut = (reserveToken * etherIn) / (reserveEther + etherIn); | |
| _transfer(address(this), msg.sender, sharesOut); | |
| _update(_realEtherBalance(), balanceOf(address(this))); | |
| } | |
| function addLiquidity(uint _amountToken) external payable returns (uint liquidity) { | |
| transfer(address(this), _amountToken); | |
| _updateFees(); | |
| if (reserveEther > 0 || reserveToken > 0) { | |
| require(reserveEther * _amountToken == reserveToken * msg.value, "x / y != dx / dy"); | |
| } | |
| if (liquiditySupply == 0) { | |
| liquidity = _sqrt(_amountToken * msg.value); | |
| } else { | |
| liquidity = _min( | |
| (_amountToken * liquiditySupply) / reserveToken, | |
| (msg.value * liquiditySupply) / reserveEther | |
| ); | |
| } | |
| require(liquidity > 0, "liquidity nonexistent"); | |
| _mintLiquidity(msg.sender, liquidity); | |
| _update(_realEtherBalance(), balanceOf(address(this))); | |
| } | |
| function removeLiquidity(uint _liquidity) external | |
| returns (uint amountEther, uint amountToken) { | |
| _updateFees(); | |
| // bal0 >= reserve0 | |
| // bal1 >= reserve1 | |
| uint balEther = _realEtherBalance(); | |
| uint balToken = balanceOf(address(this)); | |
| amountEther = (_liquidity * balEther) / liquiditySupply; | |
| amountToken = (_liquidity * balToken) / liquiditySupply; | |
| require(amountEther > 0 && amountToken > 0, "amount of ether or amount of token is zero"); | |
| // update fees and find out how much belongs to caller | |
| _update(balEther - amountEther, balToken - amountToken); | |
| _burnLiquidity(msg.sender, _liquidity); | |
| // fees | |
| amountEther += individualFees[msg.sender]; | |
| totalUnclaimed -= individualFees[msg.sender]; | |
| _transfer(address(this), msg.sender, amountToken); | |
| (bool sent,) = msg.sender.call{value: amountEther}(""); | |
| } | |
| function _sqrt(uint y) private pure returns (uint z) { | |
| if (y > 3) { | |
| z = y; | |
| uint x = y / 2 + 1; | |
| while (x < z) { | |
| z = x; | |
| x = (y / x + x) / 2; | |
| } | |
| } else if (y != 0) { | |
| z = 1; | |
| } | |
| } | |
| function _min(uint x, uint y) private pure returns (uint) { | |
| return x <= y ? x : y; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment