Skip to content

Instantly share code, notes, and snippets.

@apemon
Created June 7, 2020 18:32
Show Gist options
  • Save apemon/4761200ad975a2927c2c8659d1fa50cd to your computer and use it in GitHub Desktop.
Save apemon/4761200ad975a2927c2c8659d1fa50cd to your computer and use it in GitHub Desktop.
ZenyCoin
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);
}
}
}
}
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