Skip to content

Instantly share code, notes, and snippets.

@Graeme22
Last active May 1, 2023 23:14
Show Gist options
  • Select an option

  • Save Graeme22/e008239b4ba569039d183b82dc53031f to your computer and use it in GitHub Desktop.

Select an option

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