Skip to content

Instantly share code, notes, and snippets.

@AugustoL
Created July 1, 2020 01:43
Show Gist options
  • Save AugustoL/4cc0e656d782559dc7125c792b47fded to your computer and use it in GitHub Desktop.
Save AugustoL/4cc0e656d782559dc7125c792b47fded to your computer and use it in GitHub Desktop.
// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.6.10;
pragma experimental ABIEncoderV2;
import "https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/master/contracts/math/SafeMath.sol";
/// @title ERC20 Guild
/// @author github:AugustoL
/// @notice This smart contract has not be audited.
/// @dev Extends an ERC20 funcionality into a Guild.
/// An ERC20Guild can make decisions by creating proposals
/// and vote on the token balance as voting power.
contract ERC20Guild {
using SafeMath for uint256;
address public token;
bool public initialized = false;
uint256 public nonce = 0;
uint256 public minimumProposalTime;
uint256 public tokensForExecution;
uint256 public tokensForCreation;
bytes4 constant balanceOfFuncSignature = bytes4(keccak256(bytes('balanceOf(address)')));
struct Proposal {
address creator;
uint256 startTime;
uint256 endTime;
address[] to;
bytes[] data;
uint256[] value;
string description;
bytes contentHash;
uint256 totalTokens;
bool executed;
mapping(address => uint256) tokens;
}
mapping(bytes32 => Proposal) public proposals;
event ProposalCreated(bytes32 indexed proposalId);
event ProposalExecuted(bytes32 indexed proposalId);
event VoteAdded(bytes32 indexed proposalId, address voter, uint256 tokens);
event VoteRemoved(bytes32 indexed proposalId, address voter, uint256 tokens);
/// @dev Initilizer
/// @param _token The address of the token to be used, it is immutable and ca
/// @param _minimumProposalTime The minimun time for a proposal to be under votation
/// @param _tokensForExecution The token votes needed for a proposal to be executed
/// @param _tokensForCreation The minimum balance of tokens needed to create a proposal
function initilize(
address _token,
uint256 _minimumProposalTime,
uint256 _tokensForExecution,
uint256 _tokensForCreation
) public {
require(address(_token) != address(0), "ERC20Guild: token is the zero address");
token = _token;
setConfig(_minimumProposalTime, _tokensForExecution, _tokensForCreation);
}
/// @dev Set the ERC20Guild configuration, can be callable only executing a proposal or when it is initilized
/// @param _minimumProposalTime The minimun time for a proposal to be under votation
/// @param _tokensForExecution The token votes needed for a proposal to be executed
/// @param _tokensForCreation The minimum balance of tokens needed to create a proposal
function setConfig(uint256 _minimumProposalTime, uint256 _tokensForExecution, uint256 _tokensForCreation) public {
require(
!initialized || (msg.sender == address(this)),
"ERC20Guild: Only callable by ERC20guild itself when initialized"
);
initialized = true;
minimumProposalTime = _minimumProposalTime;
tokensForExecution = _tokensForExecution;
tokensForCreation = _tokensForCreation;
}
/// @dev Create a proposal with an static call data and extra information
/// @param _to The receiver addresses of each call to be executed
/// @param _data The data to be executed on each call to be executed
/// @param _value The ETH value to be sent on each call to be executed
/// @param _description A short description of the proposal
/// @param _contentHash The content hash of the content reference of the proposal
/// @param _extraTime The extra time to be added to the minimumProposalTime
/// for teh proposal to be executed
function createProposal(
address[] memory _to,
bytes[] memory _data,
uint256[] memory _value,
string memory _description,
bytes memory _contentHash,
uint256 _extraTime
) public {
require(balanceOf(msg.sender) > tokensForCreation, "ERC20Guild: Not enough tokens to create proposal");
require(
(_to.length == _data.length) && (_to.length == _value.length),
"ERC20Guild: Wrong length of to, data or value arrays"
);
bytes32 proposalId = keccak256(abi.encodePacked(msg.sender, now, nonce));
proposals[proposalId] = Proposal(
msg.sender,
now,
now.add(minimumProposalTime).add(_extraTime),
_to,
_data,
_value,
_description,
_contentHash,
balanceOf(msg.sender),
false
);
nonce ++;
emit ProposalCreated(proposalId);
}
/// @dev Execute a proposal that has already passed the votation time and has enough votes
/// @param proposalId The id of the proposal to be executed
function executeProposal(bytes32 proposalId) public {
require(!proposals[proposalId].executed, "ERC20Guild: Proposal already executed");
require(proposals[proposalId].endTime < now, "ERC20Guild: Proposal hasnt ended yet");
require(proposals[proposalId].totalTokens >= tokensForExecution, "ERC20Guild: Not enough tokens to execute proposal");
for (uint i = 0; i < proposals[proposalId].to.length; i ++) {
(bool success,) = proposals[proposalId].to[i]
.call{value: proposals[proposalId].value[i]}(proposals[proposalId].data[i]);
require(success, "ERC20Guild: Proposal call failed");
}
emit ProposalExecuted(proposalId);
}
/// @dev Set the amount of tokens to vote in a proposal
/// @param proposalId The id of the proposal to set the vote
/// @param tokens The amount of tokens to use as voting for the proposal
function setVote(bytes32 proposalId, uint256 tokens) public {
require(!proposals[proposalId].executed, "ERC20Guild: Proposal already executed");
require(balanceOf(msg.sender) <= tokens, "ERC20Guild: Invalid tokens amount");
if (tokens > proposals[proposalId].tokens[msg.sender]) {
proposals[proposalId].totalTokens.add(
proposals[proposalId].tokens[msg.sender].sub(tokens)
);
emit VoteAdded(proposalId, msg.sender, proposals[proposalId].tokens[msg.sender].sub(tokens));
proposals[proposalId].tokens[msg.sender] = tokens.sub(proposals[proposalId].tokens[msg.sender]);
} else {
proposals[proposalId].totalTokens.sub(
proposals[proposalId].tokens[msg.sender].sub(tokens)
);
emit VoteRemoved(proposalId, msg.sender, proposals[proposalId].tokens[msg.sender].sub(tokens));
proposals[proposalId].tokens[msg.sender] = tokens;
}
}
/// @dev Set the amount of tokens to vote in multiple proposals
/// @param proposalIds The ids of the proposals to set the vote
/// @param tokens The amounts of tokens to use as voting for each proposals
function setVotes(bytes32[] memory proposalIds, uint256[] memory tokens) public {
require(proposalIds.length == tokens.length, "ERC20Guild: Wrong length of proposalIds or tokens");
for(uint i = 0; i < proposalIds.length; i ++)
setVote(proposalIds[i], tokens[i]);
}
/// @dev Get the ERC20 token balance of an address
/// @param holder The address of the token holder
function balanceOf(address holder) internal view returns(uint256) {
(bool success, bytes memory data) = token.staticcall(
abi.encodeWithSelector(balanceOfFuncSignature, holder)
);
require(success, 'ERC20Guild: balanceOf failded');
return abi.decode(data, (uint256));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment