-
-
Save 0xc0de4c0ffee/53edaf6414a98be9da96c78b4bb5d06e to your computer and use it in GitHub Desktop.
Stream prototype for UBI https://github.com/DemocracyEarth/ubi/blob/master/contracts/UBI.sol
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
// 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