Created
June 7, 2020 18:32
-
-
Save apemon/4761200ad975a2927c2c8659d1fa50cd to your computer and use it in GitHub Desktop.
ZenyCoin
This file contains 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
pragma solidity ^0.6.2; | |
pragma experimental ABIEncoderV2; | |
import {SafeMath} from "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/math/SafeMath.sol"; | |
library BalanceManager { | |
using SafeMath for uint256; | |
// Storage | |
struct AccountBalance { | |
uint256 _currentBlock; // for debugging | |
uint256 _expiryPeriod; | |
// global state | |
uint256 _totalSupply; | |
uint256 _totalRedeemable; | |
uint256 _totalUnderlying; | |
BalanceInfo[] _globalBalances; | |
// balance per account | |
uint256 _counter; | |
mapping (uint256 => BalanceInfo) _balances; | |
mapping (address => uint256) _balanceHead; | |
mapping (address => uint256) _balanceTail; | |
mapping (address => uint256) _balanceLength; | |
} | |
struct BalanceInfo { | |
uint256 _blockNumber; | |
uint256 _amount; | |
uint256 _next; | |
} | |
function init(AccountBalance storage self) public { | |
self._counter = 1; | |
self._expiryPeriod = 100; | |
} | |
function setBlockNumber(AccountBalance storage self, uint256 blockNumber) public { | |
self._currentBlock = blockNumber; | |
} | |
function isExpiry(AccountBalance storage self, uint256 blockNumber) public view returns (bool) { | |
return currentBlock(self).sub(blockNumber) > self._expiryPeriod; | |
} | |
function totalSupply(AccountBalance storage self) public view returns (uint256) { | |
return self._totalSupply; | |
} | |
function totalUnderlying(AccountBalance storage self) public view returns (uint256) { | |
return self._totalUnderlying; | |
} | |
function totalRedeemable(AccountBalance storage self) public view returns (uint256) { | |
return self._totalRedeemable; | |
} | |
function currentBlock(AccountBalance storage self) public view returns (uint256) { | |
//return block.number; | |
return self._currentBlock; | |
} | |
function addBalance(AccountBalance storage self, address account, uint256 amount) public { | |
_addBalance(self, account, currentBlock(self), amount); | |
} | |
function addUnderlying(AccountBalance storage self, uint256 underlyingAmount) public { | |
self._totalUnderlying = self._totalUnderlying.add(underlyingAmount); | |
} | |
function removeUnderlying(AccountBalance storage self, uint256 underlyingAmount) public { | |
self._totalUnderlying = self._totalUnderlying.sub(underlyingAmount); | |
} | |
function addRedeemable(AccountBalance storage self, uint256 underlyingAmount) public { | |
self._totalRedeemable = self._totalRedeemable.add(underlyingAmount); | |
} | |
function removeRedeemable(AccountBalance storage self, uint256 underlyingAmount) public { | |
self._totalRedeemable = self._totalRedeemable.sub(underlyingAmount); | |
} | |
function addTotalSupply(AccountBalance storage self, uint256 amount) public { | |
self._totalSupply = self._totalSupply.add(amount); | |
} | |
function removeTotalSupply(AccountBalance storage self, uint256 amount) public { | |
self._totalSupply = self._totalSupply.sub(amount); | |
} | |
function _addBalance(AccountBalance storage self, address account, uint256 blockNumber, uint256 amount) private { | |
BalanceInfo memory bal = BalanceInfo({ | |
_blockNumber: blockNumber, | |
_amount: amount, | |
_next: 0 | |
}); | |
self._balances[self._counter] = bal; | |
if(self._balanceLength[account] == 0) { | |
self._balanceHead[account] = self._counter; | |
} else { | |
// retrieve tail | |
uint256 index = self._balanceTail[account]; | |
BalanceInfo storage lastestBalance = self._balances[index]; | |
lastestBalance._next = self._counter; | |
} | |
self._balanceTail[account] = self._counter; | |
self._balanceLength[account] = self._balanceLength[account].add(1); | |
// update global stat | |
self._counter = self._counter.add(1); | |
} | |
function balanceOf(AccountBalance storage self, address account) public view returns (uint256) { | |
uint256 length = self._balanceLength[account]; | |
if(length == 0) | |
return 0; | |
uint256 balance; | |
uint256 index = self._balanceHead[account]; | |
for(uint256 i=0;i<length;i++) { | |
BalanceInfo memory bal = self._balances[index]; | |
if(!isExpiry(self, bal._blockNumber)) | |
balance = balance.add(bal._amount); | |
index = bal._next; | |
} | |
return balance; | |
} | |
function removeBalance(AccountBalance storage self, address account, uint256 amount) public { | |
adjustAccountBalance(self, account); | |
require(balanceOf(self, account) >= amount, 'ERC20: not enough balance'); | |
uint256 remaining = amount; | |
uint256 delIndex = 0; | |
uint256 length = self._balanceLength[account]; | |
uint256 index = self._balanceHead[account]; | |
uint256 i; | |
for(i=0;i<length;i++) { | |
BalanceInfo storage bal = self._balances[index]; | |
if(remaining >= bal._amount) { | |
remaining = remaining.sub(bal._amount); | |
delIndex = index; | |
index = bal._next; | |
} else { | |
bal._amount = bal._amount.sub(remaining); | |
break; | |
} | |
} | |
_deleteBalanceNode(self, account, i, length, delIndex, index); | |
} | |
// remove expiry node | |
function adjustAccountBalance(AccountBalance storage self, address account) public returns (uint256) { | |
uint256 length = self._balanceLength[account]; | |
uint256 index = self._balanceHead[account]; | |
uint256 expiredBalance = 0; | |
uint256 delIndex = 0; | |
uint256 i; | |
for(i=0;i<length;i++) { | |
BalanceInfo memory bal = self._balances[index]; | |
if(!isExpiry(self, bal._blockNumber)) { | |
break; | |
} | |
delIndex = index; | |
index = bal._next; | |
expiredBalance = expiredBalance.add(bal._amount); | |
} | |
_deleteBalanceNode(self, account, i, length, delIndex, index); | |
return expiredBalance; | |
} | |
function _deleteBalanceNode(AccountBalance storage self, address account, uint256 i, uint256 length, uint256 delIndex, uint256 index) private { | |
if(delIndex == 0) | |
return; | |
if(delIndex > 0) { | |
// delete all | |
if(i == length) { | |
self._balanceHead[account] = 0; | |
self._balanceTail[account] = 0; | |
self._balanceLength[account] = 0; | |
} else { | |
self._balanceHead[account] = index; | |
self._balanceLength[account] = length.sub(i); | |
} | |
} | |
} | |
} |
This file contains 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
pragma solidity ^0.6.2; | |
pragma experimental ABIEncoderV2; | |
import {ERC20} from "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/token/ERC20/ERC20.sol"; | |
import {Ownable} from "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/access/Ownable.sol"; | |
import {SafeMath} from "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/math/SafeMath.sol"; | |
import {IERC20} from "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/token/ERC20/IERC20.sol"; | |
import {Context} from "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.1/contracts/GSN/Context.sol"; | |
import {BalanceManager} from "./BalanceManager.sol"; | |
contract ZenyToken is IERC20, Ownable { | |
using SafeMath for uint256; | |
using BalanceManager for BalanceManager.AccountBalance; | |
string public _name; | |
string public _symbol; | |
uint8 public _decimals; | |
mapping (address => mapping (address => uint256)) private _allowances; | |
address public _underlying; | |
uint256 public _rate = 1000; | |
BalanceManager.AccountBalance accountBalance; | |
event Expire(uint256 blockNumber, uint256 amount); | |
constructor(string memory name, string memory symbol, uint8 decimals, address underlying) public { | |
_name = name; | |
_symbol = symbol; | |
_decimals = decimals; | |
_underlying = underlying; | |
accountBalance.init(); | |
} | |
function mint(uint256 underylyingAmount) public returns (bool) { | |
require(_transferUnderlyingFrom(underylyingAmount), 'ZNY: Can not transfer from this address'); | |
uint256 amount = calculateZeny(underylyingAmount); | |
require(amount > 0, 'ZNY: Amount is zero'); | |
accountBalance.addUnderlying(underylyingAmount); | |
_mint(_msgSender(), amount); | |
return true; | |
} | |
function adjustGlobalBalance() public { | |
uint256 expiredAmount = accountBalance.adjustAccountBalance(address(this)); | |
accountBalance.removeTotalSupply(expiredAmount); | |
uint256 underlyingAmount = calculateUnderlying(expiredAmount); | |
accountBalance.addRedeemable(underlyingAmount); | |
emit Expire(accountBalance.currentBlock(), expiredAmount); | |
} | |
function setUnderlying(address underlying) public onlyOwner { | |
_underlying = underlying; | |
} | |
function setRate(uint256 rate) public onlyOwner { | |
_rate = rate; | |
} | |
function calculateZeny(uint256 underylyingAmount) public view returns(uint256){ | |
ERC20 underlyingInterface = ERC20(_underlying); | |
uint256 underlyingDecimal = 10; | |
underlyingDecimal = underlyingDecimal ** underlyingInterface.decimals(); | |
uint256 currentDecimal = 10; | |
currentDecimal = currentDecimal ** _decimals; | |
return underylyingAmount.mul(1e18).mul(_rate).mul(currentDecimal).div(underlyingDecimal).div(1e18); | |
} | |
function calculateUnderlying(uint256 amount) public view returns (uint256) { | |
ERC20 underlyingInterface = ERC20(_underlying); | |
uint256 underlyingDecimal = 10; | |
underlyingDecimal = underlyingDecimal ** underlyingInterface.decimals(); | |
uint256 currentDecimal = 10; | |
currentDecimal = currentDecimal ** _decimals; | |
return amount.mul(1e18).mul(underlyingDecimal).div(_rate).div(currentDecimal).div(1e18); | |
} | |
function _approve(address owner, address spender, uint256 amount) internal { | |
require(owner != address(0), "ERC20: approve from the zero address"); | |
require(spender != address(0), "ERC20: approve to the zero address"); | |
_allowances[owner][spender] = amount; | |
emit Approval(owner, spender, amount); | |
} | |
function _transferUnderlyingFrom(uint256 amount) internal returns (bool) { | |
IERC20 underlyingInterface = IERC20(_underlying); | |
underlyingInterface.transferFrom(_msgSender(), address(this), amount); | |
return true; | |
} | |
function _burn(address account, uint256 amount) internal { | |
require(account != address(0), "ERC20: burn from the zero address"); | |
// adjust global | |
adjustGlobalBalance(); | |
accountBalance.removeBalance(address(this), amount); | |
accountBalance.removeTotalSupply(amount); | |
// remove account balance | |
accountBalance.adjustAccountBalance(account); | |
accountBalance.removeBalance(account, amount); | |
emit Transfer(account, address(0), amount); | |
} | |
function burn(uint256 amount) public returns (bool) { | |
_burn(_msgSender(), amount); | |
// return underlying | |
uint256 underlyingAmount = calculateUnderlying(amount); | |
_redeem(_msgSender(), underlyingAmount); | |
return true; | |
} | |
function _redeem(address account, uint256 underlyingAmount) internal { | |
ERC20 underlyingInterface = ERC20(_underlying); | |
underlyingInterface.transfer(account, underlyingAmount); | |
accountBalance.removeUnderlying(underlyingAmount); | |
} | |
function redeem(uint256 amount) public onlyOwner returns (bool) { | |
adjustGlobalBalance(); | |
uint256 underlyingAmount = calculateUnderlying(amount); | |
_redeem(_msgSender(), underlyingAmount); | |
accountBalance.removeRedeemable(underlyingAmount); | |
} | |
// getter | |
function totalRedeemable() public view returns (uint256) { | |
return accountBalance.totalRedeemable(); | |
} | |
function totalUnderlying() public view returns (uint256) { | |
return accountBalance.totalUnderlying(); | |
} | |
// for debugging | |
function setBlockNumber(uint256 blockNumber) public { | |
accountBalance.setBlockNumber(blockNumber); | |
} | |
function getBlockNumber() public view returns (uint256) { | |
return accountBalance.currentBlock(); | |
} | |
function getBalance(uint256 index) public view returns (BalanceManager.BalanceInfo memory) { | |
return accountBalance._balances[index]; | |
} | |
function getAccountBalance(address account) public view returns (uint256, uint256, uint256) { | |
return (accountBalance._balanceLength[account], accountBalance._balanceHead[account],accountBalance._balanceTail[account]); | |
} | |
// for debugging | |
function _mint(address account, uint256 amount) internal { | |
require(account != address(0), "ERC20: mint to the zero address"); | |
// update global stat | |
adjustGlobalBalance(); | |
accountBalance.addBalance(address(this), amount); | |
accountBalance.addTotalSupply(amount); | |
// update account balance | |
accountBalance.adjustAccountBalance(account); | |
accountBalance.addBalance(account, amount); | |
emit Transfer(address(0), account, amount); | |
} | |
function _transfer(address sender, address recipient, uint256 amount) internal { | |
require(sender != address(0), "ERC20: transfer from the zero address"); | |
require(recipient != address(0), "ERC20: transfer to the zero address"); | |
require(recipient != address(this), "ERC20: transfer to this contract address"); | |
// adjust global balance | |
adjustGlobalBalance(); | |
accountBalance.removeBalance(address(this), amount); | |
accountBalance.addBalance(address(this), amount); | |
// transfer | |
accountBalance.adjustAccountBalance(sender); | |
accountBalance.removeBalance(sender, amount); | |
accountBalance.adjustAccountBalance(recipient); | |
accountBalance.addBalance(recipient, amount); | |
emit Transfer(sender, recipient, amount); | |
} | |
// implemented function | |
function totalSupply() public view override returns (uint256) { | |
return accountBalance.totalSupply(); | |
} | |
function balanceOf(address account) public view override returns (uint256) { | |
return accountBalance.balanceOf(account); | |
} | |
function transfer(address recipient, uint256 amount) public override returns (bool) { | |
_transfer(_msgSender(), recipient, amount); | |
return true; | |
} | |
function allowance(address owner, address spender) public view override returns (uint256) { | |
return _allowances[owner][spender]; | |
} | |
function approve(address spender, uint256 amount) public override returns (bool) { | |
_approve(_msgSender(), spender, amount); | |
return true; | |
} | |
function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) { | |
_transfer(sender, recipient, amount); | |
_approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance")); | |
return true; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment