feat: natspec for staking pool DAO contract (#12)

This commit is contained in:
RichΛrd 2020-03-30 12:04:47 -04:00 committed by GitHub
parent bd4a85db01
commit 4e3037b141
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 134 additions and 50 deletions

View File

@ -39,47 +39,73 @@ contract StakingPoolDAO is StakingPool, GSNRecipient, ERC20Snapshot, Controlled
event Execution(uint indexed proposalId); event Execution(uint indexed proposalId);
event ExecutionFailure(uint indexed proposalId); event ExecutionFailure(uint indexed proposalId);
constructor (address _tokenAddress, uint _stakingPeriodLen, uint _proposalVoteLength, uint _proposalExpirationLength, uint _minimumParticipation) public /**
* @dev constructor
* @param _tokenAddress SNT token address
* @param _stakingPeriodLen Length in blocks for the period where user will be able to stake SNT
* @param _proposalVoteLength Length in blocks for the period where voting will be available for proposals
* @param _proposalExpirationLength Length in blocks where a proposal must be executed after voting before it is considered expired
* @param _minimumParticipation Percentage of participation required for a proposal to be considered valid
*/
constructor (
address _tokenAddress,
uint _stakingPeriodLen,
uint _proposalVoteLength,
uint _proposalExpirationLength,
uint _minimumParticipation
) public
StakingPool(_tokenAddress, _stakingPeriodLen) { StakingPool(_tokenAddress, _stakingPeriodLen) {
proposalVoteLength = _proposalVoteLength; proposalVoteLength = _proposalVoteLength;
proposalExpirationLength = _proposalExpirationLength; proposalExpirationLength = _proposalExpirationLength;
minimumParticipation = _minimumParticipation; minimumParticipation = _minimumParticipation;
} }
/**
* @dev Set voting period length in blocks. Can only be executed by the contract's controller
* @param _newProposalVoteLength Length in blocks for the period where voting will be available for proposals
*/
function setProposalVoteLength(uint _newProposalVoteLength) public onlyController { function setProposalVoteLength(uint _newProposalVoteLength) public onlyController {
proposalVoteLength = _newProposalVoteLength; proposalVoteLength = _newProposalVoteLength;
} }
/**
* @dev Set length in blocks where a proposal must be executed before it is considered as expired. Can only be executed by the contract's controller
* @param _newProposalExpirationLength Length in blocks where a proposal must be executed after voting before it is considered expired
*/
function setProposalExpirationLength(uint _newProposalExpirationLength) public onlyController { function setProposalExpirationLength(uint _newProposalExpirationLength) public onlyController {
proposalExpirationLength = _newProposalExpirationLength; proposalExpirationLength = _newProposalExpirationLength;
} }
/**
* @dev Set minimum participation percentage for proposals to be considered valid. Can only be executed by the contract's controller
* @param _newMinimumParticipation Percentage of participation required for a proposal to be considered valid
*/
function setMinimumParticipation(uint _newMinimumParticipation) public onlyController { function setMinimumParticipation(uint _newMinimumParticipation) public onlyController {
minimumParticipation = _newMinimumParticipation; minimumParticipation = _newMinimumParticipation;
} }
/// @dev Adds a new proposal /**
/// @param destination Transaction target address. * @notice Adds a new proposal
/// @param value Transaction ether value. * @param _destination Transaction target address
/// @param data Transaction data payload. * @param _value Transaction ether value
/// @param details Proposal details * @param _data Transaction data payload
/// @return Returns proposal ID. * @param _details Proposal details
function addProposal(address destination, uint value, bytes calldata data, bytes calldata details) external returns (uint proposalId) * @return Returns proposal ID
*/
function addProposal(address _destination, uint _value, bytes calldata _data, bytes calldata _details) external returns (uint proposalId)
{ {
require(balanceOf(msg.sender) > 0, "Token balance is required to perform this operation"); require(balanceOf(msg.sender) > 0, "Token balance is required to perform this operation");
// TODO: should proposals have a cost? or require a minimum amount of tokens? assert(_destination != address(0));
assert(destination != address(0));
proposalId = proposalCount; proposalId = proposalCount;
proposals[proposalId] = Proposal({ proposals[proposalId] = Proposal({
destination: destination, destination: _destination,
value: value, value: _value,
data: data, data: _data,
executed: false, executed: false,
snapshotId: snapshot(), snapshotId: snapshot(),
details: details, details: _details,
voteEndingBlock: block.number + proposalVoteLength voteEndingBlock: block.number + proposalVoteLength
}); });
@ -88,8 +114,13 @@ contract StakingPoolDAO is StakingPool, GSNRecipient, ERC20Snapshot, Controlled
emit NewProposal(proposalId); emit NewProposal(proposalId);
} }
function vote(uint proposalId, bool choice) external { /**
Proposal storage proposal = proposals[proposalId]; * @notice Vote for a proposal
* @param _proposalId Id of the proposal to vote
* @param _choice True for voting yes, False for no
*/
function vote(uint _proposalId, bool _choice) external {
Proposal storage proposal = proposals[_proposalId];
require(proposal.voteEndingBlock > block.number, "Proposal has already ended"); require(proposal.voteEndingBlock > block.number, "Proposal has already ended");
@ -105,31 +136,36 @@ contract StakingPoolDAO is StakingPool, GSNRecipient, ERC20Snapshot, Controlled
proposal.votes[oldChoice] -= voterBalance; proposal.votes[oldChoice] -= voterBalance;
} }
VoteStatus enumVote = choice ? VoteStatus.YES : VoteStatus.NO; VoteStatus enumVote = _choice ? VoteStatus.YES : VoteStatus.NO;
proposal.votes[choice] += voterBalance; proposal.votes[_choice] += voterBalance;
proposal.voters[sender] = enumVote; proposal.voters[sender] = enumVote;
lastActivity[sender] = block.timestamp; lastActivity[sender] = block.timestamp;
emit Vote(proposalId, sender, enumVote); emit Vote(_proposalId, sender, enumVote);
} }
// call has been separated into its own function in order to take advantage /**
// of the Solidity's code generator to produce a loop that copies tx.data into memory. * @dev Execute a transaction
function external_call(address destination, uint value, uint dataLength, bytes memory data) internal returns (bool) { * @param _destination Transaction target address.
* @param _value Transaction ether value.
* @param _dataLength Transaction data payload length
* @param _data Transaction data payload.
*/
function external_call(address _destination, uint _value, uint _dataLength, bytes memory _data) internal returns (bool) {
bool result; bool result;
assembly { assembly {
let x := mload(0x40) // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention) let x := mload(0x40) // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention)
let d := add(data, 32) // First 32 bytes are the padded length of data, so exclude that let d := add(_data, 32) // First 32 bytes are the padded length of data, so exclude that
result := call( result := call(
sub(gas, 34710), // 34710 is the value that solidity is currently emitting sub(gas, 34710), // 34710 is the value that solidity is currently emitting
// It includes callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) + // It includes callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) +
// callNewAccountGas (25000, in case the destination address does not exist and needs creating) // callNewAccountGas (25000, in case the destination address does not exist and needs creating)
destination, _destination,
value, _value,
d, d,
dataLength, // Size of the input (in bytes) - this is what fixes the padding problem _dataLength, // Size of the input (in bytes) - this is what fixes the padding problem
x, x,
0 // Output is ignored, therefore the output size is zero 0 // Output is ignored, therefore the output size is zero
) )
@ -137,10 +173,12 @@ contract StakingPoolDAO is StakingPool, GSNRecipient, ERC20Snapshot, Controlled
return result; return result;
} }
/// @dev Allows anyone to execute an approved non-expired proposal /**
/// @param proposalId Proposal ID. * @notice Execute an approved non-expired proposal
function executeTransaction(uint proposalId) public { * @param _proposalId Proposal ID.
Proposal storage proposal = proposals[proposalId]; */
function executeTransaction(uint _proposalId) public {
Proposal storage proposal = proposals[_proposalId];
require(proposal.executed == false, "Proposal already executed"); require(proposal.executed == false, "Proposal already executed");
require(block.number > proposal.voteEndingBlock, "Voting is still active"); require(block.number > proposal.voteEndingBlock, "Voting is still active");
@ -150,25 +188,43 @@ contract StakingPoolDAO is StakingPool, GSNRecipient, ERC20Snapshot, Controlled
uint totalParticipation = ((proposal.votes[true] + proposal.votes[false]) * 10000) / totalSupply(); uint totalParticipation = ((proposal.votes[true] + proposal.votes[false]) * 10000) / totalSupply();
require(totalParticipation >= minimumParticipation, "Did not meet the minimum required participation"); require(totalParticipation >= minimumParticipation, "Did not meet the minimum required participation");
proposal.executed = true; proposal.executed = true;
bool result = external_call(proposal.destination, proposal.value, proposal.data.length, proposal.data); bool result = external_call(proposal.destination, proposal.value, proposal.data.length, proposal.data);
require(result, "Execution Failed"); require(result, "Execution Failed");
emit Execution(proposalId); emit Execution(_proposalId);
} }
function votes(uint proposalId, bool choice) public view returns (uint) { /**
return proposals[proposalId].votes[choice]; * @notice Get the number of votes for a proposal choice
* @param _proposalId Proposal ID
* @param _choice True for voting yes, False for no
* @return Number of votes for the selected choice
*/
function votes(uint _proposalId, bool _choice) public view returns (uint) {
return proposals[_proposalId].votes[_choice];
} }
function voteOf(address account, uint proposalId) public view returns (VoteStatus) { /**
return proposals[proposalId].voters[account]; * @notice Get the vote of an account
* @param _account Account to obtain the vote from
* @param _proposalId Proposal ID
* @return Vote cast by an account
*/
function voteOf(address _account, uint _proposalId) public view returns (VoteStatus) {
return proposals[_proposalId].voters[_account];
} }
function isProposalApproved(uint proposalId) public view returns (bool approved, bool executed){ /**
Proposal storage proposal = proposals[proposalId]; * @notice Check if a proposal is approved or not
if(block.number <= proposal.voteEndingBlock) { * @param _proposalId Proposal ID
* @return approved Indicates if the proposal was approved or not
* @return executed Indicates if the proposal was executed or not
*/
function isProposalApproved(uint _proposalId) public view returns (bool approved, bool executed){
Proposal storage proposal = proposals[_proposalId];
uint totalParticipation = ((proposal.votes[true] + proposal.votes[false]) * 10000) / totalSupply();
if(block.number <= proposal.voteEndingBlock || totalParticipation < minimumParticipation) {
approved = false; approved = false;
} else { } else {
approved = proposal.votes[true] > proposal.votes[false]; approved = proposal.votes[true] > proposal.votes[false];
@ -180,6 +236,9 @@ contract StakingPoolDAO is StakingPool, GSNRecipient, ERC20Snapshot, Controlled
// //
} }
// ========================================================================
// Gas station network
enum GSNErrorCodes { enum GSNErrorCodes {
FUNCTION_NOT_AVAILABLE, FUNCTION_NOT_AVAILABLE,
HAS_ETH_BALANCE, HAS_ETH_BALANCE,
@ -191,16 +250,23 @@ contract StakingPoolDAO is StakingPool, GSNRecipient, ERC20Snapshot, Controlled
bytes4 constant VOTE_SIGNATURE = bytes4(keccak256("vote(uint256,bool)")); bytes4 constant VOTE_SIGNATURE = bytes4(keccak256("vote(uint256,bool)"));
/**
* @dev Function returning if we accept or not the relayed call (do we pay or not for the gas)
* @param from Address of the buyer getting a free transaction
* @param encodedFunction Function that will be called on the Escrow contract
* @param gasPrice Gas price
* @dev relay and transaction_fee are useless in our relay workflow
*/
function acceptRelayedCall( function acceptRelayedCall(
address relay, address /*relay*/,
address from, address from,
bytes calldata encodedFunction, bytes calldata encodedFunction,
uint256 transactionFee, uint256 /*transactionFee*/,
uint256 gasPrice, uint256 gasPrice,
uint256 gasLimit, uint256 /*gasLimit*/,
uint256 nonce, uint256 /*nonce*/,
bytes calldata approvalData, bytes calldata /*approvalData*/,
uint256 maxPossibleCharge uint256 /*maxPossibleCharge*/
) external view returns (uint256, bytes memory) { ) external view returns (uint256, bytes memory) {
bytes memory abiEncodedFunc = encodedFunction; // Call data elements cannot be accessed directly bytes memory abiEncodedFunc = encodedFunction; // Call data elements cannot be accessed directly
@ -215,6 +281,13 @@ contract StakingPoolDAO is StakingPool, GSNRecipient, ERC20Snapshot, Controlled
return _evaluateConditions(from, functionSignature, proposalId, gasPrice); return _evaluateConditions(from, functionSignature, proposalId, gasPrice);
} }
/**
* @dev Evaluates if the sender conditions are valid for relaying a escrow transaction
* @param _from Sender
* @param _gasPrice Gas Price
* @param _functionSignature Function Signature
* @param _proposalId Proposal ID
*/
function _evaluateConditions( function _evaluateConditions(
address _from, address _from,
bytes4 _functionSignature, bytes4 _functionSignature,
@ -227,10 +300,6 @@ contract StakingPoolDAO is StakingPool, GSNRecipient, ERC20Snapshot, Controlled
if(balanceOfAt(_from, proposal.snapshotId) == 0) return _rejectRelayedCall(uint256(GSNErrorCodes.NO_TOKEN_BALANCE)); if(balanceOfAt(_from, proposal.snapshotId) == 0) return _rejectRelayedCall(uint256(GSNErrorCodes.NO_TOKEN_BALANCE));
/* ?
if(from.balance > 600000 * gasPrice) return _rejectRelayedCall(uint256(GSNErrorCodes.HAS_ETH_BALANCE));
*/
if(_gasPrice > 20000000000) return _rejectRelayedCall(uint256(GSNErrorCodes.GAS_PRICE)); // 20 gwei if(_gasPrice > 20000000000) return _rejectRelayedCall(uint256(GSNErrorCodes.GAS_PRICE)); // 20 gwei
if((lastActivity[_from] + 15 minutes) > block.timestamp) return _rejectRelayedCall(uint256(GSNErrorCodes.TRX_TOO_SOON)); if((lastActivity[_from] + 15 minutes) > block.timestamp) return _rejectRelayedCall(uint256(GSNErrorCodes.TRX_TOO_SOON));
@ -242,18 +311,33 @@ contract StakingPoolDAO is StakingPool, GSNRecipient, ERC20Snapshot, Controlled
mapping(address => uint) public lastActivity; mapping(address => uint) public lastActivity;
/**
* @dev Function executed before the relay. Unused by us
*/
function _preRelayedCall(bytes memory context) internal returns (bytes32) { function _preRelayedCall(bytes memory context) internal returns (bytes32) {
} }
/**
* @dev Function executed after the relay. Unused by us
*/
function _postRelayedCall(bytes memory context, bool, uint256 actualCharge, bytes32) internal { function _postRelayedCall(bytes memory context, bool, uint256 actualCharge, bytes32) internal {
} }
/**
* @notice Withdraw the ETH used for relay trxs
* @dev Only contract owner can execute this function
*/
function withdraw() external onlyController { function withdraw() external onlyController {
IRelayHub rh = IRelayHub(getHubAddr()); IRelayHub rh = IRelayHub(getHubAddr());
uint balance = rh.balanceOf(address(this)); uint balance = rh.balanceOf(address(this));
_withdrawDeposits(balance, msg.sender); _withdrawDeposits(balance, msg.sender);
} }
/**
* @notice Set gas station network hub address
* @dev Only contract owner can execute this function
* @param _relayHub New relay hub address
*/
function setRelayHubAddress(address _relayHub) external onlyController { function setRelayHubAddress(address _relayHub) external onlyController {
_upgradeRelayHub(_relayHub); _upgradeRelayHub(_relayHub);
} }