Skip to content

Commit

Permalink
Welcome to Humanity
Browse files Browse the repository at this point in the history
  • Loading branch information
richmcateer committed May 9, 2019
0 parents commit f58f173
Show file tree
Hide file tree
Showing 39 changed files with 11,122 additions and 0 deletions.
34 changes: 34 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2
jobs:
build:
docker:
# specify the version you desire here
- image: circleci/node:10.9.0

# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
# - image: circleci/mongo:3.4.4

working_directory: ~/humanity

steps:
- checkout

# Download and cache dependencies
- restore_cache:
key: module-cache-{{ checksum "yarn.lock" }}
- run:
name: Fetch Dependencies
command: yarn
- save_cache:
key: module-cache-{{ checksum "yarn.lock" }}
paths:
- node_modules
- run:
name: Run Tests
command: yarn rebuild_and_test
31 changes: 31 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.DS_Store

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/.changelog

# Dependency directories
node_modules/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Output of 'npm pack'
*.tgz

# dotenv environment variables file
.env

# build directories
lib/
build/

# VSCode file
.vscode
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
[![CircleCI](https://circleci.com/gh/marbleprotocol/polaris/tree/master.svg?style=svg)](https://circleci.com/gh/marbleprotocol/humanity/tree/master)

# Humanity

Humanity is a Decentralized Autonomous Organization (DAO) that governs a registry of unique humans.

## Query the Registry

Create Sybil-resistant smart contract protocols by restricting permission to Ethereum addresses that are on the registry.

**HumanityRegistry.sol**
```
function isHuman(address who) public view returns (bool)
```

See **UniversalBasicIncome.sol** for an example.

## Apply to the Registry

First, submit social verification in the form of a Twitter post with your Ethereum address. Then, apply to the registry with your Twitter username and a refundable proposal fee.

**TwitterHumanityApplicant.sol**
```
function applyWithTwitter(string memory username) public returns (uint)
```

## Vote on Applicants

Vote on applicants to the registry using Humanity tokens.

**Governance.sol**
```
function voteYes(uint proposalId) public
```

```
function voteNo(uint proposalId) public
```
35 changes: 35 additions & 0 deletions contracts/Faucet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
pragma solidity 0.5.7;
pragma experimental ABIEncoderV2;

import { IERC20 } from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol";

/**
* @title Faucet
* @dev Mine Humanity tokens into Uniswap.
*/
contract Faucet {
using SafeMath for uint;

uint public constant BLOCK_REWARD = 1e18;
uint public START_BLOCK = block.number;
uint public END_BLOCK = block.number + 5000000;

IERC20 public humanity;
address public auction;

uint public lastMined = block.number;

constructor(IERC20 _humanity, address _auction) public {
humanity = _humanity;
auction = _auction;
}

function mine() public {
uint rewardBlock = block.number < END_BLOCK ? block.number : END_BLOCK;
uint reward = rewardBlock.sub(lastMined).mul(BLOCK_REWARD);
humanity.transfer(auction, reward);
lastMined = block.number;
}

}
195 changes: 195 additions & 0 deletions contracts/Governance.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
pragma solidity 0.5.7;
pragma experimental ABIEncoderV2;

import { IERC20 } from "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import { SafeMath } from "openzeppelin-solidity/contracts/math/SafeMath.sol";

import { Void } from "./Void.sol";

/**
* @title Governance
* @dev Plutocratic voting system.
*/
contract Governance {
using SafeMath for uint;

event Execute(uint indexed proposalId);
event Propose(uint indexed proposalId, address indexed proposer, address indexed target, bytes data);
event RemoveVote(uint indexed proposalId, address indexed voter);
event Terminate(uint indexed proposalId);
event Vote(uint indexed proposalId, address indexed voter, bool approve, uint weight);

enum Result { Pending, Yes, No }

struct Proposal {
Result result;
address target;
bytes data;
address proposer;
address feeRecipient;
uint fee;
uint startTime;
uint yesCount;
uint noCount;
}

uint public constant OPEN_VOTE_PERIOD = 2 days;
uint public constant VETO_PERIOD = 2 days;
uint public constant TOTAL_VOTE_PERIOD = OPEN_VOTE_PERIOD + VETO_PERIOD;

uint public proposalFee;
IERC20 public token;
Void public void;

Proposal[] public proposals;

// Proposal Id => Voter => Yes Votes
mapping(uint => mapping(address => uint)) public yesVotes;

// Proposal Id => Voter => No Votes
mapping(uint => mapping(address => uint)) public noVotes;

// Voter => Deposit
mapping (address => uint) public deposits;

// Voter => Withdraw timestamp
mapping (address => uint) public withdrawTimes;

constructor(IERC20 _token, uint _initialProposalFee) public {
token = _token;
proposalFee = _initialProposalFee;
void = new Void();
}

function deposit(uint amount) public {
require(token.transferFrom(msg.sender, address(this), amount), "Governance::deposit: Transfer failed");
deposits[msg.sender] = deposits[msg.sender].add(amount);
}

function withdraw(uint amount) public {
require(time() > withdrawTimes[msg.sender], "Governance::withdraw: Voters with an active proposal cannot withdraw");
deposits[msg.sender] = deposits[msg.sender].sub(amount);
require(token.transfer(msg.sender, amount), "Governance::withdraw: Transfer failed");
}

function propose(address target, bytes memory data) public returns (uint) {
return proposeWithFeeRecipient(msg.sender, target, data);
}

function proposeWithFeeRecipient(address feeRecipient, address target, bytes memory data) public returns (uint) {
require(msg.sender != address(this) && target != address(token), "Governance::proposeWithFeeRecipient: Invalid proposal");
require(token.transferFrom(msg.sender, address(this), proposalFee), "Governance::proposeWithFeeRecipient: Transfer failed");

uint proposalId = proposals.length;

// Create a new proposal and vote yes
Proposal memory proposal;
proposal.target = target;
proposal.data = data;
proposal.proposer = msg.sender;
proposal.feeRecipient = feeRecipient;
proposal.fee = proposalFee;
proposal.startTime = time();
proposal.yesCount = proposalFee;

proposals.push(proposal);

emit Propose(proposalId, msg.sender, target, data);

return proposalId;
}

function voteYes(uint proposalId) public {
Proposal storage proposal = proposals[proposalId];
require(time() <= proposal.startTime.add(OPEN_VOTE_PERIOD), "Governance::voteYes: Proposal is no longer accepting yes votes");

uint proposalEndTime = proposal.startTime.add(TOTAL_VOTE_PERIOD);
if (proposalEndTime > withdrawTimes[msg.sender]) withdrawTimes[msg.sender] = proposalEndTime;

uint weight = deposits[msg.sender].sub(yesVotes[proposalId][msg.sender]);
proposal.yesCount = proposal.yesCount.add(weight);
yesVotes[proposalId][msg.sender] = deposits[msg.sender];

emit Vote(proposalId, msg.sender, true, weight);
}

function voteNo(uint proposalId) public {
Proposal storage proposal = proposals[proposalId];
require(proposal.result == Result.Pending, "Governance::voteNo: Proposal is already finalized");

uint proposalEndTime = proposal.startTime.add(TOTAL_VOTE_PERIOD);
uint _time = time();
require(_time <= proposalEndTime, "Governance::voteNo: Proposal is no longer in voting period");

uint _deposit = deposits[msg.sender];
uint weight = _deposit.sub(noVotes[proposalId][msg.sender]);
proposal.noCount = proposal.noCount.add(weight);
noVotes[proposalId][msg.sender] = _deposit;

emit Vote(proposalId, msg.sender, false, weight);

// Finalize the vote and burn the proposal fee if no votes outnumber yes votes and open voting has ended
if (_time > proposal.startTime.add(OPEN_VOTE_PERIOD) && proposal.noCount >= proposal.yesCount) {
proposal.result = Result.No;
require(token.transfer(address(void), proposal.fee), "Governance::voteNo: Transfer to void failed");
emit Terminate(proposalId);
} else if (proposalEndTime > withdrawTimes[msg.sender]) {
withdrawTimes[msg.sender] = proposalEndTime;
}

}

function removeVote(uint proposalId) public {
Proposal storage proposal = proposals[proposalId];
require(proposal.result == Result.Pending, "Governance::removeVote: Proposal is already finalized");
require(time() <= proposal.startTime.add(TOTAL_VOTE_PERIOD), "Governance::removeVote: Proposal is no longer in voting period");

proposal.yesCount = proposal.yesCount.sub(yesVotes[proposalId][msg.sender]);
proposal.noCount = proposal.noCount.sub(noVotes[proposalId][msg.sender]);
delete yesVotes[proposalId][msg.sender];
delete noVotes[proposalId][msg.sender];

emit RemoveVote(proposalId, msg.sender);
}

function finalize(uint proposalId) public {
Proposal storage proposal = proposals[proposalId];
require(proposal.result == Result.Pending, "Governance::finalize: Proposal is already finalized");
uint _time = time();

if (proposal.yesCount > proposal.noCount) {
require(_time > proposal.startTime.add(TOTAL_VOTE_PERIOD), "Governance::finalize: Proposal cannot be executed until end of veto period");

proposal.result = Result.Yes;
require(token.transfer(proposal.feeRecipient, proposal.fee), "Governance::finalize: Return proposal fee failed");
proposal.target.call(proposal.data);

emit Execute(proposalId);
} else {
require(_time > proposal.startTime.add(OPEN_VOTE_PERIOD), "Governance::finalize: Proposal cannot be terminated until end of yes vote period");

proposal.result = Result.No;
require(token.transfer(address(void), proposal.fee), "Governance::finalize: Transfer to void failed");

emit Terminate(proposalId);
}
}

function setProposalFee(uint fee) public {
require(msg.sender == address(this), "Governance::setProposalFee: Proposal fee can only be set via governance");
proposalFee = fee;
}

function time() public view returns (uint) {
return block.timestamp;
}

function getProposal(uint proposalId) external view returns (Proposal memory) {
return proposals[proposalId];
}

function getProposalsCount() external view returns (uint) {
return proposals.length;
}

}
35 changes: 35 additions & 0 deletions contracts/Humanity.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
pragma solidity 0.5.7;
pragma experimental ABIEncoderV2;

import { ERC20 } from "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";


/**
* @title Humanity
* @dev ERC20 token that can be used to vote on applications to the Humanity registry.
*/
contract Humanity is ERC20 {

string public constant name = "Humanity";
string public constant symbol = "HUM";
uint8 public constant decimals = 18;
string public version = "1.0.0";

uint public constant INITIAL_SUPPLY = 25000000e18; // 25 million
uint public constant FINAL_SUPPLY = 100000000e18; // 100 million

address public registry;

constructor(address _registry) public {
registry = _registry;
_mint(msg.sender, INITIAL_SUPPLY);
}

function mint(address account, uint256 value) public {
require(msg.sender == registry, "Humanity::mint: Only the registry can mint new tokens");
require(totalSupply().add(value) <= FINAL_SUPPLY, "Humanity::mint: Exceeds final supply");

_mint(account, value);
}

}
Loading

0 comments on commit f58f173

Please sign in to comment.