feat: natspec for staking pool DAO contract (#12)
This commit is contained in:
parent
bd4a85db01
commit
4e3037b141
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue