Skip to content

Instantly share code, notes, and snippets.

@0xc0de4c0ffee
Last active April 29, 2021 18:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 0xc0de4c0ffee/53edaf6414a98be9da96c78b4bb5d06e to your computer and use it in GitHub Desktop.
Save 0xc0de4c0ffee/53edaf6414a98be9da96c78b4bb5d06e to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: MIT
pragma solidity <0.8.0;
/**
* This code contains elements of ERC20BurnableUpgradeable.sol https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/token/ERC20/ERC20BurnableUpgradeable.sol
* Those have been inlined for the purpose of gas optimization.
*/
//import "@openzeppelin/contracts-upgradeable/proxy/Initializable.sol";
//import "@openzeppelin/contracts/math/SafeMath.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/math/SafeMath.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.4.0/contracts/proxy/Initializable.sol";
/**
* @title ProofOfHumanity Interface
* @dev See https://github.com/Proof-Of-Humanity/Proof-Of-Humanity.
*/
interface IProofOfHumanity {
function isRegistered(address _submissionID)
external
view
returns (
bool registered
);
}
/**
* @title Universal Basic Income
* @dev UBI is an ERC20 compatible token that is connected to a Proof of Humanity registry.
*
* Tokens are issued and drip over time for every verified submission on a Proof of Humanity registry.
* The accrued tokens are updated directly on every wallet using the `balanceOf` function.
* The tokens get effectively minted and persisted in memory when someone interacts with the contract doing a `transfer` or `burn`.
*/
contract UBI is Initializable {
/* Events */
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to another (`to`).
*
* Note that `value` may be zero.
* Also note that due to continuous minting we cannot emit transfer events from the address 0 when tokens are created.
* In order to keep consistency, we decided not to emit those events from the address 0 even when minting is done within a transaction.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
using SafeMath for uint256;
/* Storage */
mapping (address => uint256) private balance;
mapping (address => mapping (address => uint256)) public allowance;
/**@dev M0 supply marker
* M0 should be updated after..
* a) adding new PoH
* b) revoking active PoH
*
* If accrued per sec rate is changed,
* It'll fluctuate +- "real" total supply
*/
uint256 public supplyMarker; // same slot/position as old totalSupply to match upgradable pattern
/// @dev Name of the token.
string public name;
/// @dev Symbol of the token.
string public symbol;
/// @dev Number of decimals of the token.
uint8 public decimals;
/// @dev How many tokens per second will be minted for every valid human.
uint256 public accruedPerSecond;
/// @dev The contract's governor.
address public governor;
/// @dev The Proof Of Humanity registry to reference.
IProofOfHumanity public proofOfHumanity;
/// @dev Timestamp since human started accruing.
mapping(address => uint256) public accruedSince;
/// @dev Timestamp of PoH activation, used as internal PoH Marker
mapping(address => bool) public activated;
/// @dev Number of active Humans accruing UBI, Updated after adding / revoking PoH
uint256 public activeVerifiedHumans;
/// @dev Timestamp of last M0 update
uint256 public lastSupplyUpdate; // same as accruedSince but for M0 supply
struct Stream {
uint128 inflow; // incoming per second
uint128 outflow; // outgoing per second
// uint64 accruedSince; // accrued Since
uint8 outCount; // number of outgoing streams
mapping(address => uint128) flow;
}
mapping(address => Stream) internal stream;
/**@dev Total Supply of UBI
* @notice
*/
function totalSupply() public view returns(uint){
// NOT using SafeMath for internal is OK?
return supplyMarker + ((block.timestamp - lastSupplyUpdate) * (activeVerifiedHumans * accruedPerSecond));
}
/* Modifiers */
/// @dev Verifies that the sender has ability to modify governed parameters.
modifier onlyByGovernor() {
require(governor == msg.sender, "The caller is not the governor.");
_;
}
/* Initializer */
/** @dev Constructor.
* @param _initialSupply for the UBI coin including all decimals.
* @param _name for UBI coin.
* @param _symbol for UBI coin ticker.
* @param _accruedPerSecond How much of the token is accrued per block.
* @param _proofOfHumanity The Proof Of Humanity registry to reference.
*/
function initialize(uint256 _initialSupply, string memory _name, string memory _symbol, uint256 _accruedPerSecond, IProofOfHumanity _proofOfHumanity) public initializer {
name = _name;
symbol = _symbol;
decimals = 18;
accruedPerSecond = _accruedPerSecond;
proofOfHumanity = _proofOfHumanity;
governor = msg.sender;
balance[msg.sender] = _initialSupply;
supplyMarker = _initialSupply;
}
/* External */
/** @dev Starts accruing UBI for a registered submission.
* @param _human The submission ID.
*/
function startAccruing(address _human) external {
require(proofOfHumanity.isRegistered(_human), "The submission is not registered in Proof Of Humanity.");
require(!activated[_human], "The submission is already accruing UBI.");
Stream storage _stream = stream[_human];
if(_stream.inflow != 0) { // check incoming streams
balance[_human] += (_stream.inflow * (block.timestamp - accruedSince[_human]));
}
activated[_human] = true;
accruedSince[_human] = block.timestamp;
emit NewStream(address(0), _human, accruedPerSecond); // emit stream event
// update M0 total supply marker
supplyMarker += ((activeVerifiedHumans * accruedPerSecond) * (block.timestamp - lastSupplyUpdate));
activeVerifiedHumans++;
lastSupplyUpdate = block.timestamp;
}
/** @dev Allows anyone to report a submission that
* should no longer receive UBI due to removal from the
* Proof Of Humanity registry. The reporter receives any
* leftover accrued UBI.
* @param _notHuman The submission ID.
* @param _outFlow Array of outgoing stream addresses to revoke
*/
function reportRemoval(address _notHuman, address[] calldata _outFlow) external {
require(activated[_notHuman], "Stream : Already Removed");
require(!proofOfHumanity.isRegistered(_notHuman), "The submission is still registered in Proof Of Humanity.");
Stream storage _stream = stream[_notHuman];
//require(_stream.outCount == _outFlow.length, "Stream : Outgoing stream counter length isn't valid"); // check if outgoing counter = revoke list length
uint256 _slash; // total value recovered
uint256 _out; // sum of UBI units
for (uint256 i = 0; i < _outFlow.length; i++) {
address _addr = _outFlow[i];
uint128 _drip = _stream.flow[_addr]; // units in this stream >=0
stream[_addr].inflow -= _drip;
_slash += _drip * (block.timestamp - accruedSince[_addr]); // add total recovered
_stream.flow[_addr] = 0;
_out += _drip; // for final check
emit Revoked(_notHuman, _addr, _drip); // emit revoked event
}
require(_stream.outflow == _out, "Stream: Unable to close all outgoing stream.");
_slash += (accruedPerSecond - _out) * (block.timestamp - accruedSince[_notHuman]);
balance[msg.sender] = balance[msg.sender].add(_slash); // reward msg sender
activated[_notHuman] = false; // deactivate stream
_stream.outflow = 0; // reset outflow
_stream.outCount = 0; // reset outcoing counter
emit Revoked(address(0), _notHuman, _slash); // emit revoked event
// update M0 for total supply marker
supplyMarker += ((activeVerifiedHumans * accruedPerSecond) * (block.timestamp - lastSupplyUpdate));
activeVerifiedHumans--; // remove 1 PoH
lastSupplyUpdate = block.timestamp; // last updated
}
/** @dev Changes `governor` to `_governor`.
* @param _governor The address of the new governor.
*/
function changeGovernor(address _governor) external onlyByGovernor {
governor = _governor;
}
/* @ dev Changes `accruedPerSecond`
* @ param _accruedPerSecond.
* IF changed totalSupply will be +- of real total supply
function changeAccruedRate(uint256 _accruedPerSecond) external onlyByGovernor {
supplyMarker += ((activeVerifiedHumans * accruedPerSecond) * (block.timestamp - lastSupplyUpdate));
lastSupplyUpdate = block.timestamp;
accruedPerSecond = _accruedPerSecond;
}
*/
/** @dev Changes `proofOfHumanity` to `_proofOfHumanity`.
* @param _proofOfHumanity Registry that meets interface of Proof of Humanity.
*/
function changeProofOfHumanity(IProofOfHumanity _proofOfHumanity) external onlyByGovernor {
proofOfHumanity = _proofOfHumanity;
}
/** @dev Transfers `_amount` to `_recipient` and withdraws accrued tokens.
* @param _recipient The entity receiving the funds.
* @param _amount The amount to transfer in base units.
*/
function transfer(address _recipient, uint256 _amount) public returns (bool) {
Stream storage _stream = stream[msg.sender];
if(activated[msg.sender] && proofOfHumanity.isRegistered(msg.sender)) {
balance[msg.sender] += ((block.timestamp - accruedSince[msg.sender]) * ((accruedPerSecond + _stream.inflow) - _stream.outflow));
} else if(_stream.inflow != 0) {
balance[msg.sender] += ((block.timestamp - accruedSince[msg.sender]) * _stream.inflow);
}
balance[msg.sender] = (balance[msg.sender]).sub(_amount, "ERC20: transfer amount exceeds balance");
balance[_recipient] += _amount;
accruedSince[msg.sender] = block.timestamp;
emit Transfer(msg.sender, _recipient, _amount);
return true;
}
/** @dev Transfers `_amount` from `_sender` to `_recipient` and withdraws accrued tokens.
* @param _sender The entity to take the funds from.
* @param _recipient The entity receiving the funds.
* @param _amount The amount to transfer in base units.
*/
function transferFrom(address _sender, address _recipient, uint256 _amount) public returns (bool) {
if(allowance[_sender][msg.sender] != type(uint256).max){
allowance[_sender][msg.sender] = allowance[_sender][msg.sender].sub(_amount, "ERC20: transfer amount exceeds allowance");
}
balance[_sender] = (balance[_sender] + getAccruedValue(_sender)).sub(_amount, "ERC20: transfer amount exceeds balance");
balance[_recipient] += _amount;
accruedSince[_sender] = block.timestamp;
emit Transfer(_sender, _recipient, _amount);
return true;
}
/** @dev Approves `_spender` to spend `_amount`.
* @param _spender The entity allowed to spend funds.
* @param _amount The amount of base units the entity will be allowed to spend.
*/
function approve(address _spender, uint256 _amount) public returns (bool) {
allowance[msg.sender][_spender] = _amount;
emit Approval(msg.sender, _spender, _amount);
return true;
}
/** @dev Increases the `_spender` allowance by `_addedValue`.
* @param _spender The entity allowed to spend funds.
* @param _addedValue The amount of extra base units the entity will be allowed to spend.
*/
function increaseAllowance(address _spender, uint256 _addedValue) public returns (bool) {
uint256 newAllowance = allowance[msg.sender][_spender].add(_addedValue);
allowance[msg.sender][_spender] = newAllowance;
emit Approval(msg.sender, _spender, newAllowance);
return true;
}
/** @dev Decreases the `_spender` allowance by `_subtractedValue`.
* @param _spender The entity whose spending allocation will be reduced.
* @param _subtractedValue The reduction of spending allocation in base units.
*/
function decreaseAllowance(address _spender, uint256 _subtractedValue) public returns (bool) {
uint256 newAllowance = allowance[msg.sender][_spender].sub(_subtractedValue, "ERC20: decreased allowance below zero");
allowance[msg.sender][_spender] = newAllowance;
emit Approval(msg.sender, _spender, newAllowance);
return true;
}
/** @dev Burns `_amount` of tokens and withdraws accrued tokens.
* @param _amount The quantity of tokens to burn in base units.
*/
function burn(uint256 _amount) public {
balance[msg.sender] = (balance[msg.sender] + getAccruedValue(msg.sender)).sub(_amount, "ERC20: Burn amount exceeds balance");
accruedSince[msg.sender] = block.timestamp;
supplyMarker = supplyMarker.sub(_amount);
emit Transfer(msg.sender, address(0), _amount);
}
/** @dev Burns `_amount` of tokens from `_account` and withdraws accrued tokens.
* @param _account The entity to burn tokens from.
* @param _amount The quantity of tokens to burn in base units.
*/
function burnFrom(address _account, uint256 _amount) public {
if(allowance[_account][msg.sender] != type(uint256).max){
allowance[_account][msg.sender] = allowance[_account][msg.sender].sub(_amount, "ERC20: Burn amount exceeds allowance");
}
balance[_account] = (balance[_account] + getAccruedValue(_account)).sub(_amount, "ERC20: Burn amount exceeds balance");
supplyMarker = supplyMarker.sub(_amount);
emit Transfer(_account, address(0), _amount);
}
/* Getters */
/** @dev Calculates how much UBI an address has available for withdrawal.
* @param _human The submission ID.
* @return accrued The available UBI for withdrawal.
*/
function getAccruedValue(address _human) public view returns (uint256 accrued) {
Stream storage _stream = stream[_human];
if(activated[_human] && proofOfHumanity.isRegistered(_human)) {
return (block.timestamp - accruedSince[_human]) * ((accruedPerSecond + _stream.inflow) - _stream.outflow);
} else if(_stream.inflow != 0) {
return ((block.timestamp - accruedSince[_human]) * _stream.inflow);
}
/*
accrued = (activated[_human] && proofOfHumanity.isRegistered(_human)) ? // check if Active PoH
(block.timestamp - accruedSince[_human]) * ((accruedPerSecond + _stream.inflow) - _stream.outflow) :
(_stream.inflow != 0) ? //check incoming stream
((block.timestamp - accruedSince[_human]) * _stream.inflow) : 0;
*/
}
/**
* @dev Calculates the current user accrued balance.
* @param _human The submission ID.
* @return The current balance including accrued Universal Basic Income of the user.
**/
function balanceOf(address _human) public view returns (uint256) {
return (balance[_human] + getAccruedValue(_human));
}
/* Stream Functions */
/* Stream Events */
/**
* @dev Emitted when the `_src` creates new stream for `_dst`
* `_src` is address(0) for Primary stream
* `_drip` are UBI units moved into`_dst` stream.
*/
event NewStream(address indexed _src, address indexed _dst, uint256 _drip);
/**
* @dev Emitted when the `_src` ~ `_dst` is stopped
* `_src` is address(0) for Primary stream
* `_drip` are UBI units moved out of `_dst` stream.
*/
event EndStream(address indexed _src, address indexed _dst, uint256 _drip);
/**
* @dev Emitted when the `_src`'s PoH is revoked `_dst`.
* `_src` is address(0) for Primary stream
* `_drip` UBI units per second revoked from `_dst` stream.
*/
event Revoked(address indexed _src, address indexed _dst, uint256 _drip);
/** @dev Start secondary UBI stream to any address
* @param _dst destination address
* @param _drip UBI units per second moved in stream.
* <10% >1% of accruedPerSecond
*/
function startStream(address _dst, uint128 _drip) external {
require(_dst != address(0), "Stream: Zero address as destination.");
require(activated[msg.sender], "Stream: Start accruing before streaming out.");
require(proofOfHumanity.isRegistered(msg.sender), "The submission is not registered in Proof Of Humanity.");
Stream storage _stream = stream[msg.sender];
require(_stream.outCount < 8, "Stream: Max 8 outgoing streams"); // max 80% total
uint256 _rate = accruedPerSecond;
require(_drip <= _rate / 10 && _drip >= _rate / 100, "Stream: limit Max 10%, Min 1%");
require(_stream.flow[_dst] == 0, "Stream: Already Active outgoing to same destination");
balance[_dst] += getAccruedValue(_dst); // settle dst balance
accruedSince[_dst] = block.timestamp; // update dst timer
balance[msg.sender] += ((block.timestamp - accruedSince[msg.sender]) * ((_rate + _stream.inflow) - _stream.outflow));
accruedSince[msg.sender] = block.timestamp; // update src timer
_stream.flow[_dst] = _drip; // record of new stream
_stream.outflow += _drip; // add outgoing stream for src
stream[_dst].inflow += _drip; // add incoming stream for dst
_stream.outCount++; // increase active stream counter
emit NewStream(msg.sender, _dst, _drip);
}
/** @dev Stop secondary UBI stream
* @param _dst destination address to stop stream
*/
function stopStream(address _dst) external {
require(proofOfHumanity.isRegistered(msg.sender), "Your address is not registered in Proof Of Humanity.");
Stream storage _stream = stream[msg.sender];
uint128 _drip = _stream.flow[_dst];
require(_drip != 0, "Stream: Not active");
balance[_dst] += getAccruedValue(_dst); // settle dst balance
accruedSince[_dst] = block.timestamp; // update dst timer
balance[msg.sender] += ((block.timestamp - accruedSince[msg.sender]) * ((accruedPerSecond + _stream.inflow) - _stream.outflow));
accruedSince[msg.sender] = block.timestamp;
_stream.flow[_dst] = 0; // reset stream to zero
_stream.outflow -= _drip; // subtract outgoing stream for src
stream[_dst].inflow -= _drip; // subtract incoming stream for dst
_stream.outCount--; // decrease active outgoing stream counter
emit EndStream(msg.sender, _dst, _drip); // close stream
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment