Transaction Hash:
Block:
12525699 at May-28-2021 11:08:55 PM +UTC
Transaction Fee:
0.000734514048711633 ETH
$1.78
Gas Used:
33,387 Gas / 22.000001459 Gwei
Emitted Events:
266 |
DSToken.Transfer( _from=[Sender] 0x7d8486e35460d8aca8ed2d34af810c8b39b88b4b, _to=[Receiver] BancorGovernance, _value=4162000000000000000000 )
|
267 |
BancorGovernance.Staked( _user=[Sender] 0x7d8486e35460d8aca8ed2d34af810c8b39b88b4b, _amount=4162000000000000000000 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x48Fb2534...d9d387f94 | |||||
0x7D8486e3...B39B88b4B |
0.308565016421272315 Eth
Nonce: 28
|
0.307830502372560682 Eth
Nonce: 29
| 0.000734514048711633 | ||
0x892f481B...75d5D00e4 | (Bancor: Governance) | ||||
0x99C85bb6...993Cb89E3
Miner
| (BeePool) | 2,291.977708787258719726 Eth | 2,291.978443301307431359 Eth | 0.000734514048711633 |
Execution Trace
BancorGovernance.stake( _amount=4162000000000000000000 )
-
DSToken.transferFrom( _from=0x7D8486e35460D8aca8ed2d34af810C8B39B88b4B, _to=0x892f481BD6E9d7D26aE365211D9B45175d5D00e4, _value=4162000000000000000000 ) => ( True )
File 1 of 2: BancorGovernance
File 2 of 2: DSToken
// File: @bancor/contracts-solidity/solidity/contracts/utility/interfaces/IOwned.sol // SPDX-License-Identifier: SEE LICENSE IN LICENSE pragma solidity 0.6.12; /* Owned contract interface */ interface IOwned { // this function isn't since the compiler emits automatically generated getter functions as external function owner() external view returns (address); function transferOwnership(address _newOwner) external; function acceptOwnership() external; } // File: @bancor/contracts-solidity/solidity/contracts/utility/Owned.sol pragma solidity 0.6.12; /** * @dev Provides support and utilities for contract ownership */ contract Owned is IOwned { address public override owner; address public newOwner; /** * @dev triggered when the owner is updated * * @param _prevOwner previous owner * @param _newOwner new owner */ event OwnerUpdate(address indexed _prevOwner, address indexed _newOwner); /** * @dev initializes a new Owned instance */ constructor() public { owner = msg.sender; } // allows execution by the owner only modifier ownerOnly { _ownerOnly(); _; } // error message binary size optimization function _ownerOnly() internal view { require(msg.sender == owner, "ERR_ACCESS_DENIED"); } /** * @dev allows transferring the contract ownership * the new owner still needs to accept the transfer * can only be called by the contract owner * * @param _newOwner new contract owner */ function transferOwnership(address _newOwner) public override ownerOnly { require(_newOwner != owner, "ERR_SAME_OWNER"); newOwner = _newOwner; } /** * @dev used by a new owner to accept an ownership transfer */ function acceptOwnership() override public { require(msg.sender == newOwner, "ERR_ACCESS_DENIED"); emit OwnerUpdate(owner, newOwner); owner = newOwner; newOwner = address(0); } } // File: @openzeppelin/contracts/math/Math.sol pragma solidity ^0.6.0; /** * @dev Standard math utilities missing in the Solidity language. */ library Math { /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return a >= b ? a : b; } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } /** * @dev Returns the average of two numbers. The result is rounded towards * zero. */ function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow, so we distribute return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2); } } // File: @openzeppelin/contracts/math/SafeMath.sol pragma solidity ^0.6.0; /** * @dev Wrappers over Solidity's arithmetic operations with added overflow * checks. * * Arithmetic operations in Solidity wrap on overflow. This can easily result * in bugs, because programmers usually assume that an overflow raises an * error, which is the standard behavior in high level programming languages. * `SafeMath` restores this intuition by reverting the transaction when an * operation overflows. * * Using this library instead of the unchecked operations eliminates an entire * class of bugs, so it's recommended to use it always. */ library SafeMath { /** * @dev Returns the addition of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `+` operator. * * Requirements: * * - Addition cannot overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; } /** * @dev Returns the subtraction of two unsigned integers, reverting on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, "SafeMath: subtraction overflow"); } /** * @dev Returns the subtraction of two unsigned integers, reverting with custom message on * overflow (when the result is negative). * * Counterpart to Solidity's `-` operator. * * Requirements: * * - Subtraction cannot overflow. */ function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); uint256 c = a - b; return c; } /** * @dev Returns the multiplication of two unsigned integers, reverting on * overflow. * * Counterpart to Solidity's `*` operator. * * Requirements: * * - Multiplication cannot overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; } /** * @dev Returns the integer division of two unsigned integers. Reverts on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { return div(a, b, "SafeMath: division by zero"); } /** * @dev Returns the integer division of two unsigned integers. Reverts with custom message on * division by zero. The result is rounded towards zero. * * Counterpart to Solidity's `/` operator. Note: this function uses a * `revert` opcode (which leaves remaining gas untouched) while Solidity * uses an invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { return mod(a, b, "SafeMath: modulo by zero"); } /** * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo), * Reverts with custom message when dividing by zero. * * Counterpart to Solidity's `%` operator. This function uses a `revert` * opcode (which leaves remaining gas untouched) while Solidity uses an * invalid opcode to revert (consuming all remaining gas). * * Requirements: * * - The divisor cannot be zero. */ function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; } } // File: @openzeppelin/contracts/token/ERC20/IERC20.sol pragma solidity ^0.6.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `recipient`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address recipient, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `sender` to `recipient` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ 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); } // File: @openzeppelin/contracts/utils/Address.sol pragma solidity ^0.6.2; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * ==== */ function isContract(address account) internal view returns (bool) { // This method relies in extcodesize, which returns 0 for contracts in // construction, since the code is only stored at the end of the // constructor execution. uint256 size; // solhint-disable-next-line no-inline-assembly assembly { size := extcodesize(account) } return size > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); // solhint-disable-next-line avoid-low-level-calls, avoid-call-value (bool success, ) = recipient.call{ value: amount }(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain`call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCall(target, data, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data, string memory errorMessage) internal returns (bytes memory) { return _functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value, string memory errorMessage) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); return _functionCallWithValue(target, data, value, errorMessage); } function _functionCallWithValue(address target, bytes memory data, uint256 weiValue, string memory errorMessage) private returns (bytes memory) { require(isContract(target), "Address: call to non-contract"); // solhint-disable-next-line avoid-low-level-calls (bool success, bytes memory returndata) = target.call{ value: weiValue }(data); if (success) { return returndata; } else { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly // solhint-disable-next-line no-inline-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } } } // File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol pragma solidity ^0.6.0; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { using SafeMath for uint256; using Address for address; function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } /** * @dev Deprecated. This function has issues similar to the ones found in * {IERC20-approve}, and its usage is discouraged. * * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ function safeApprove(IERC20 token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' // solhint-disable-next-line max-line-length require((value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance" ); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender).add(value); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero"); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance)); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20 token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); if (returndata.length > 0) { // Return data is optional // solhint-disable-next-line max-line-length require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } } } // File: contracts/interfaces/IExecutor.sol pragma solidity 0.6.12; interface IExecutor { function execute( uint256 _id, uint256 _for, uint256 _against, uint256 _quorum ) external; } // File: contracts/BancorGovernance.sol /* ____ __ __ __ _ / __/__ __ ___ / /_ / / ___ / /_ (_)__ __ _\ \ / // // _ \/ __// _ \/ -_)/ __// / \ \ / /___/ \_, //_//_/\__//_//_/\__/ \__//_/ /_\_\ /___/ * Synthetix: YFIRewards.sol * * Docs: https://docs.synthetix.io/ * * * MIT License * =========== * * Copyright (c) 2020 Synthetix * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE */ pragma solidity 0.6.12; /** * @title The Bancor Governance Contract * * Big thanks to synthetix / yearn.finance for the initial version! */ contract BancorGovernance is Owned { using SafeMath for uint256; using SafeERC20 for IERC20; uint32 internal constant PPM_RESOLUTION = 1000000; struct Proposal { uint256 id; mapping(address => uint256) votesFor; mapping(address => uint256) votesAgainst; uint256 totalVotesFor; uint256 totalVotesAgainst; uint256 start; // start timestmp; uint256 end; // start + voteDuration uint256 totalAvailableVotes; uint256 quorum; uint256 quorumRequired; bool open; bool executed; address proposer; address executor; string hash; } /** * @notice triggered when a new proposal is created * * @param _id proposal id * @param _start voting start timestamp * @param _duration voting duration * @param _proposer proposal creator * @param _executor contract that will exeecute the proposal once it passes */ event NewProposal( uint256 indexed _id, uint256 _start, uint256 _duration, address _proposer, address _executor ); /** * @notice triggered when voting on a proposal has ended * * @param _id proposal id * @param _for number of votes for the proposal * @param _against number of votes against the proposal * @param _quorumReached true if quorum was reached, false otherwise */ event ProposalFinished( uint256 indexed _id, uint256 _for, uint256 _against, bool _quorumReached ); /** * @notice triggered when a proposal was successfully executed * * @param _id proposal id * @param _executor contract that will execute the proposal once it passes */ event ProposalExecuted(uint256 indexed _id, address indexed _executor); /** * @notice triggered when a stake has been added to the contract * * @param _user staker address * @param _amount staked amount */ event Staked(address indexed _user, uint256 _amount); /** * @notice triggered when a stake has been removed from the contract * * @param _user staker address * @param _amount unstaked amount */ event Unstaked(address indexed _user, uint256 _amount); /** * @notice triggered when a user votes on a proposal * * @param _id proposal id * @param _voter voter addrerss * @param _vote true if the vote is for the proposal, false otherwise * @param _weight number of votes */ event Vote(uint256 indexed _id, address indexed _voter, bool _vote, uint256 _weight); /** * @notice triggered when the quorum is updated * * @param _quorum new quorum */ event QuorumUpdated(uint256 _quorum); /** * @notice triggered when the minimum stake required to create a new proposal is updated * * @param _minimum new minimum */ event NewProposalMinimumUpdated(uint256 _minimum); /** * @notice triggered when the vote duration is updated * * @param _voteDuration new vote duration */ event VoteDurationUpdated(uint256 _voteDuration); /** * @notice triggered when the vote lock duration is updated * * @param _duration new vote lock duration */ event VoteLockDurationUpdated(uint256 _duration); // PROPOSALS // voting duration in seconds uint256 public voteDuration = 3 days; // vote lock in seconds uint256 public voteLockDuration = 3 days; // the fraction of vote lock used to lock voter to avoid rapid unstaking uint256 public constant voteLockFraction = 10; // minimum stake required to propose uint256 public newProposalMinimum = 1e18; // quorum needed for a proposal to pass, default = 20% uint256 public quorum = 200000; // sum of current total votes uint256 public totalVotes; // number of proposals uint256 public proposalCount; // proposals by id mapping(uint256 => Proposal) public proposals; // VOTES // governance token used for votes IERC20 public immutable govToken; // lock duration for each voter stake by voter address mapping(address => uint256) public voteLocks; // number of votes for each user mapping(address => uint256) private votes; /** * @notice used to initialize a new BancorGovernance contract * * @param _govToken token used to represents votes */ constructor(IERC20 _govToken) public { require(address(_govToken) != address(0), "ERR_NO_TOKEN"); govToken = _govToken; } /** * @notice allows execution by staker only */ modifier onlyStaker() { require(votes[msg.sender] > 0, "ERR_NOT_STAKER"); _; } /** * @notice allows execution only when the proposal exists * * @param _id proposal id */ modifier proposalExists(uint256 _id) { Proposal memory proposal = proposals[_id]; require(proposal.start > 0 && proposal.start < block.timestamp, "ERR_INVALID_ID"); _; } /** * @notice allows execution only when the proposal is still open * * @param _id proposal id */ modifier proposalOpen(uint256 _id) { Proposal memory proposal = proposals[_id]; require(proposal.open, "ERR_NOT_OPEN"); _; } /** * @notice allows execution only when the proposal with given id is open * * @param _id proposal id */ modifier proposalNotEnded(uint256 _id) { Proposal memory proposal = proposals[_id]; require(proposal.end >= block.timestamp, "ERR_ENDED"); _; } /** * @notice allows execution only when the proposal with given id has ended * * @param _id proposal id */ modifier proposalEnded(uint256 _id) { Proposal memory proposal = proposals[_id]; require(proposal.end <= block.timestamp, "ERR_NOT_ENDED"); _; } /** * @notice verifies that a value is greater than zero * * @param _value value to check for zero */ modifier greaterThanZero(uint256 _value) { require(_value > 0, "ERR_ZERO_VALUE"); _; } /** * @notice Updates the vote lock on the sender * * @param _proposalEnd proposal end time */ function updateVoteLock(uint256 _proposalEnd) private onlyStaker { voteLocks[msg.sender] = Math.max( voteLocks[msg.sender], Math.max(_proposalEnd, voteLockDuration.add(block.timestamp)) ); } /** * @notice does the common vote finalization * * @param _id the id of the proposal to vote * @param _for is this vote for or against the proposal */ function vote(uint256 _id, bool _for) private onlyStaker proposalExists(_id) proposalOpen(_id) proposalNotEnded(_id) { Proposal storage proposal = proposals[_id]; if (_for) { uint256 votesAgainst = proposal.votesAgainst[msg.sender]; // do we have against votes for this sender? if (votesAgainst > 0) { // yes, remove the against votes first proposal.totalVotesAgainst = proposal.totalVotesAgainst.sub(votesAgainst); proposal.votesAgainst[msg.sender] = 0; } } else { // get against votes for this sender uint256 votesFor = proposal.votesFor[msg.sender]; // do we have for votes for this sender? if (votesFor > 0) { proposal.totalVotesFor = proposal.totalVotesFor.sub(votesFor); proposal.votesFor[msg.sender] = 0; } } // calculate voting power in case voting against twice uint256 voteAmount = votesOf(msg.sender).sub( _for ? proposal.votesFor[msg.sender] : proposal.votesAgainst[msg.sender] ); if (_for) { // increase total for votes of the proposal proposal.totalVotesFor = proposal.totalVotesFor.add(voteAmount); // set for votes to the votes of the sender proposal.votesFor[msg.sender] = votesOf(msg.sender); } else { // increase total against votes of the proposal proposal.totalVotesAgainst = proposal.totalVotesAgainst.add(voteAmount); // set against votes to the votes of the sender proposal.votesAgainst[msg.sender] = votesOf(msg.sender); } // update total votes available on the proposal proposal.totalAvailableVotes = totalVotes; // recalculate quorum based on overall votes proposal.quorum = calculateQuorumRatio(proposal); // update vote lock updateVoteLock(proposal.end); // emit vote event emit Vote(proposal.id, msg.sender, _for, voteAmount); } /** * @notice returns the quorum ratio of a proposal * * @param _proposal proposal * @return quorum ratio */ function calculateQuorumRatio(Proposal memory _proposal) internal view returns (uint256) { // calculate overall votes uint256 totalProposalVotes = _proposal.totalVotesFor.add(_proposal.totalVotesAgainst); return totalProposalVotes.mul(PPM_RESOLUTION).div(totalVotes); } /** * @notice removes the caller's entire stake */ function exit() external { unstake(votesOf(msg.sender)); } /** * @notice returns the voting stats of a proposal * * @param _id proposal id * @return votes for ratio * @return votes against ratio * @return quorum ratio */ function proposalStats(uint256 _id) public view returns ( uint256, uint256, uint256 ) { Proposal memory proposal = proposals[_id]; uint256 forRatio = proposal.totalVotesFor; uint256 againstRatio = proposal.totalVotesAgainst; // calculate overall total votes uint256 totalProposalVotes = forRatio.add(againstRatio); // calculate for votes ratio forRatio = forRatio.mul(PPM_RESOLUTION).div(totalProposalVotes); // calculate against votes ratio againstRatio = againstRatio.mul(PPM_RESOLUTION).div(totalProposalVotes); // calculate quorum ratio uint256 quorumRatio = totalProposalVotes.mul(PPM_RESOLUTION).div( proposal.totalAvailableVotes ); return (forRatio, againstRatio, quorumRatio); } /** * @notice returns the voting power of a given address * * @param _voter voter address * @return votes of given address */ function votesOf(address _voter) public view returns (uint256) { return votes[_voter]; } /** * @notice returns the voting power of a given address against a given proposal * * @param _voter voter address * @param _id proposal id * @return votes of given address against given proposal */ function votesAgainstOf(address _voter, uint256 _id) public view returns (uint256) { return proposals[_id].votesAgainst[_voter]; } /** * @notice returns the voting power of a given address for a given proposal * * @param _voter voter address * @param _id proposal id * @return votes of given address for given proposal */ function votesForOf(address _voter, uint256 _id) public view returns (uint256) { return proposals[_id].votesFor[_voter]; } /** * @notice updates the quorum needed for proposals to pass * * @param _quorum required quorum */ function setQuorum(uint256 _quorum) public ownerOnly greaterThanZero(_quorum) { // check quorum for not being above 100 require(_quorum <= PPM_RESOLUTION, "ERR_QUORUM_TOO_HIGH"); quorum = _quorum; emit QuorumUpdated(_quorum); } /** * @notice updates the minimum stake required to create a new proposal * * @param _minimum minimum stake */ function setNewProposalMinimum(uint256 _minimum) public ownerOnly greaterThanZero(_minimum) { require(_minimum <= govToken.totalSupply(), "ERR_EXCEEDS_TOTAL_SUPPLY"); newProposalMinimum = _minimum; emit NewProposalMinimumUpdated(_minimum); } /** * @notice updates the proposals voting duration * * @param _voteDuration vote duration */ function setVoteDuration(uint256 _voteDuration) public ownerOnly greaterThanZero(_voteDuration) { voteDuration = _voteDuration; emit VoteDurationUpdated(_voteDuration); } /** * @notice updates the post vote lock duration * * @param _duration new vote lock duration */ function setVoteLockDuration(uint256 _duration) public ownerOnly greaterThanZero(_duration) { voteLockDuration = _duration; emit VoteLockDurationUpdated(_duration); } /** * @notice creates a new proposal * * @param _executor the address of the contract that will execute the proposal after it passes * @param _hash ipfs hash of the proposal description */ function propose(address _executor, string memory _hash) public { require(votesOf(msg.sender) > newProposalMinimum, "ERR_INSUFFICIENT_STAKE"); uint256 id = proposalCount; // increment proposal count so next proposal gets the next higher id proposalCount = proposalCount.add(1); // create new proposal Proposal memory proposal = Proposal({ id: id, proposer: msg.sender, totalVotesFor: 0, totalVotesAgainst: 0, start: block.timestamp, end: voteDuration.add(block.timestamp), executor: _executor, hash: _hash, totalAvailableVotes: totalVotes, quorum: 0, quorumRequired: quorum, open: true, executed: false }); proposals[id] = proposal; // lock proposer updateVoteLock(proposal.end); // emit proposal event emit NewProposal(id, proposal.start, voteDuration, proposal.proposer, proposal.executor); } /** * @notice executes a proposal * * @param _id id of the proposal to execute */ function execute(uint256 _id) public proposalExists(_id) proposalEnded(_id) { // check for executed status require(!proposals[_id].executed, "ERR_ALREADY_EXECUTED"); // get voting info of proposal (uint256 forRatio, uint256 againstRatio, uint256 quorumRatio) = proposalStats(_id); // check proposal state require(quorumRatio >= proposals[_id].quorumRequired, "ERR_NO_QUORUM"); // if the proposal is still open if (proposals[_id].open) { // tally votes tallyVotes(_id); } // set executed proposals[_id].executed = true; // do execution on the contract to be executed // note that this is a safe call as it was part of the proposal that was voted on IExecutor(proposals[_id].executor).execute(_id, forRatio, againstRatio, quorumRatio); // emit proposal executed event emit ProposalExecuted(_id, proposals[_id].executor); } /** * @notice tallies votes of proposal with given id * * @param _id id of the proposal to tally votes for */ function tallyVotes(uint256 _id) public proposalExists(_id) proposalOpen(_id) proposalEnded(_id) { // get voting info of proposal (uint256 forRatio, uint256 againstRatio, ) = proposalStats(_id); // do we have a quorum? bool quorumReached = proposals[_id].quorum >= proposals[_id].quorumRequired; // close proposal proposals[_id].open = false; // emit proposal finished event emit ProposalFinished(_id, forRatio, againstRatio, quorumReached); } /** * @notice stakes vote tokens * * @param _amount amount of vote tokens to stake */ function stake(uint256 _amount) public greaterThanZero(_amount) { // increase vote power votes[msg.sender] = votesOf(msg.sender).add(_amount); // increase total votes totalVotes = totalVotes.add(_amount); // transfer tokens to this contract govToken.safeTransferFrom(msg.sender, address(this), _amount); // lock staker to avoid flashloans messing around with total votes voteLocks[msg.sender] = Math.max( voteLocks[msg.sender], Math.max(voteLockDuration.div(voteLockFraction), 10 minutes).add(block.timestamp) ); // emit staked event emit Staked(msg.sender, _amount); } /** * @notice unstakes vote tokens * * @param _amount amount of vote tokens to unstake */ function unstake(uint256 _amount) public greaterThanZero(_amount) { require(voteLocks[msg.sender] < block.timestamp, "ERR_LOCKED"); // reduce votes for user votes[msg.sender] = votesOf(msg.sender).sub(_amount); // reduce total votes totalVotes = totalVotes.sub(_amount); // transfer tokens back govToken.safeTransfer(msg.sender, _amount); // emit unstaked event emit Unstaked(msg.sender, _amount); } /** * @notice votes for a proposal * * @param _id id of the proposal to vote for */ function voteFor(uint256 _id) public { vote(_id, true); } /** * @notice votes against a proposal * * @param _id id of the proposal to vote against */ function voteAgainst(uint256 _id) public { vote(_id, false); } }
File 2 of 2: DSToken
// File: solidity/contracts/token/interfaces/IERC20Token.sol // SPDX-License-Identifier: SEE LICENSE IN LICENSE pragma solidity 0.6.12; /* ERC20 Standard Token interface */ interface IERC20Token { function name() external view returns (string memory); function symbol() external view returns (string memory); function decimals() external view returns (uint8); function totalSupply() external view returns (uint256); function balanceOf(address _owner) external view returns (uint256); function allowance(address _owner, address _spender) external view returns (uint256); function transfer(address _to, uint256 _value) external returns (bool); function transferFrom(address _from, address _to, uint256 _value) external returns (bool); function approve(address _spender, uint256 _value) external returns (bool); } // File: solidity/contracts/utility/Utils.sol pragma solidity 0.6.12; /** * @dev Utilities & Common Modifiers */ contract Utils { // verifies that a value is greater than zero modifier greaterThanZero(uint256 _value) { _greaterThanZero(_value); _; } // error message binary size optimization function _greaterThanZero(uint256 _value) internal pure { require(_value > 0, "ERR_ZERO_VALUE"); } // validates an address - currently only checks that it isn't null modifier validAddress(address _address) { _validAddress(_address); _; } // error message binary size optimization function _validAddress(address _address) internal pure { require(_address != address(0), "ERR_INVALID_ADDRESS"); } // verifies that the address is different than this contract address modifier notThis(address _address) { _notThis(_address); _; } // error message binary size optimization function _notThis(address _address) internal view { require(_address != address(this), "ERR_ADDRESS_IS_SELF"); } } // File: solidity/contracts/utility/SafeMath.sol pragma solidity 0.6.12; /** * @dev Library for basic math operations with overflow/underflow protection */ library SafeMath { /** * @dev returns the sum of _x and _y, reverts if the calculation overflows * * @param _x value 1 * @param _y value 2 * * @return sum */ function add(uint256 _x, uint256 _y) internal pure returns (uint256) { uint256 z = _x + _y; require(z >= _x, "ERR_OVERFLOW"); return z; } /** * @dev returns the difference of _x minus _y, reverts if the calculation underflows * * @param _x minuend * @param _y subtrahend * * @return difference */ function sub(uint256 _x, uint256 _y) internal pure returns (uint256) { require(_x >= _y, "ERR_UNDERFLOW"); return _x - _y; } /** * @dev returns the product of multiplying _x by _y, reverts if the calculation overflows * * @param _x factor 1 * @param _y factor 2 * * @return product */ function mul(uint256 _x, uint256 _y) internal pure returns (uint256) { // gas optimization if (_x == 0) return 0; uint256 z = _x * _y; require(z / _x == _y, "ERR_OVERFLOW"); return z; } /** * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. * * @param _x dividend * @param _y divisor * * @return quotient */ function div(uint256 _x, uint256 _y) internal pure returns (uint256) { require(_y > 0, "ERR_DIVIDE_BY_ZERO"); uint256 c = _x / _y; return c; } } // File: solidity/contracts/token/ERC20Token.sol pragma solidity 0.6.12; /** * @dev ERC20 Standard Token implementation */ contract ERC20Token is IERC20Token, Utils { using SafeMath for uint256; string public override name; string public override symbol; uint8 public override decimals; uint256 public override totalSupply; mapping (address => uint256) public override balanceOf; mapping (address => mapping (address => uint256)) public override allowance; /** * @dev triggered when tokens are transferred between wallets * * @param _from source address * @param _to target address * @param _value transfer amount */ event Transfer(address indexed _from, address indexed _to, uint256 _value); /** * @dev triggered when a wallet allows another wallet to transfer tokens from on its behalf * * @param _owner wallet that approves the allowance * @param _spender wallet that receives the allowance * @param _value allowance amount */ event Approval(address indexed _owner, address indexed _spender, uint256 _value); /** * @dev initializes a new ERC20Token instance * * @param _name token name * @param _symbol token symbol * @param _decimals decimal points, for display purposes * @param _totalSupply total supply of token units */ constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _totalSupply) public { // validate input require(bytes(_name).length > 0, "ERR_INVALID_NAME"); require(bytes(_symbol).length > 0, "ERR_INVALID_SYMBOL"); name = _name; symbol = _symbol; decimals = _decimals; totalSupply = _totalSupply; balanceOf[msg.sender] = _totalSupply; } /** * @dev transfers tokens to a given address * throws on any error rather then return a false flag to minimize user errors * * @param _to target address * @param _value transfer amount * * @return true if the transfer was successful, false if it wasn't */ function transfer(address _to, uint256 _value) public virtual override validAddress(_to) returns (bool) { balanceOf[msg.sender] = balanceOf[msg.sender].sub(_value); balanceOf[_to] = balanceOf[_to].add(_value); emit Transfer(msg.sender, _to, _value); return true; } /** * @dev transfers tokens to a given address on behalf of another address * throws on any error rather then return a false flag to minimize user errors * * @param _from source address * @param _to target address * @param _value transfer amount * * @return true if the transfer was successful, false if it wasn't */ function transferFrom(address _from, address _to, uint256 _value) public virtual override validAddress(_from) validAddress(_to) returns (bool) { allowance[_from][msg.sender] = allowance[_from][msg.sender].sub(_value); balanceOf[_from] = balanceOf[_from].sub(_value); balanceOf[_to] = balanceOf[_to].add(_value); emit Transfer(_from, _to, _value); return true; } /** * @dev allows another account/contract to transfers tokens on behalf of the caller * throws on any error rather then return a false flag to minimize user errors * * @param _spender approved address * @param _value allowance amount * * @return true if the approval was successful, false if it wasn't */ function approve(address _spender, uint256 _value) public virtual override validAddress(_spender) returns (bool) { allowance[msg.sender][_spender] = _value; emit Approval(msg.sender, _spender, _value); return true; } } // File: solidity/contracts/utility/interfaces/IOwned.sol pragma solidity 0.6.12; /* Owned contract interface */ interface IOwned { // this function isn't since the compiler emits automatically generated getter functions as external function owner() external view returns (address); function transferOwnership(address _newOwner) external; function acceptOwnership() external; } // File: solidity/contracts/converter/interfaces/IConverterAnchor.sol pragma solidity 0.6.12; /* Converter Anchor interface */ interface IConverterAnchor is IOwned { } // File: solidity/contracts/token/interfaces/IDSToken.sol pragma solidity 0.6.12; /* DSToken interface */ interface IDSToken is IConverterAnchor, IERC20Token { function issue(address _to, uint256 _amount) external; function destroy(address _from, uint256 _amount) external; } // File: solidity/contracts/utility/Owned.sol pragma solidity 0.6.12; /** * @dev Provides support and utilities for contract ownership */ contract Owned is IOwned { address public override owner; address public newOwner; /** * @dev triggered when the owner is updated * * @param _prevOwner previous owner * @param _newOwner new owner */ event OwnerUpdate(address indexed _prevOwner, address indexed _newOwner); /** * @dev initializes a new Owned instance */ constructor() public { owner = msg.sender; } // allows execution by the owner only modifier ownerOnly { _ownerOnly(); _; } // error message binary size optimization function _ownerOnly() internal view { require(msg.sender == owner, "ERR_ACCESS_DENIED"); } /** * @dev allows transferring the contract ownership * the new owner still needs to accept the transfer * can only be called by the contract owner * * @param _newOwner new contract owner */ function transferOwnership(address _newOwner) public override ownerOnly { require(_newOwner != owner, "ERR_SAME_OWNER"); newOwner = _newOwner; } /** * @dev used by a new owner to accept an ownership transfer */ function acceptOwnership() override public { require(msg.sender == newOwner, "ERR_ACCESS_DENIED"); emit OwnerUpdate(owner, newOwner); owner = newOwner; newOwner = address(0); } } // File: solidity/contracts/token/DSToken.sol pragma solidity 0.6.12; /** * @dev DSToken represents a token with dynamic supply. * The owner of the token can mint/burn tokens to/from any account. * */ contract DSToken is IDSToken, ERC20Token, Owned { using SafeMath for uint256; /** * @dev triggered when the total supply is increased * * @param _amount amount that gets added to the supply */ event Issuance(uint256 _amount); /** * @dev triggered when the total supply is decreased * * @param _amount amount that gets removed from the supply */ event Destruction(uint256 _amount); /** * @dev initializes a new DSToken instance * * @param _name token name * @param _symbol token short symbol, minimum 1 character * @param _decimals for display purposes only */ constructor(string memory _name, string memory _symbol, uint8 _decimals) public ERC20Token(_name, _symbol, _decimals, 0) { } /** * @dev increases the token supply and sends the new tokens to the given account * can only be called by the contract owner * * @param _to account to receive the new amount * @param _amount amount to increase the supply by */ function issue(address _to, uint256 _amount) public override ownerOnly validAddress(_to) notThis(_to) { totalSupply = totalSupply.add(_amount); balanceOf[_to] = balanceOf[_to].add(_amount); emit Issuance(_amount); emit Transfer(address(0), _to, _amount); } /** * @dev removes tokens from the given account and decreases the token supply * can only be called by the contract owner * * @param _from account to remove the amount from * @param _amount amount to decrease the supply by */ function destroy(address _from, uint256 _amount) public override ownerOnly { balanceOf[_from] = balanceOf[_from].sub(_amount); totalSupply = totalSupply.sub(_amount); emit Transfer(_from, address(0), _amount); emit Destruction(_amount); } // ERC20 standard method overrides with some extra functionality /** * @dev send coins * throws on any error rather then return a false flag to minimize user errors * in addition to the standard checks, the function throws if transfers are disabled * * @param _to target address * @param _value transfer amount * * @return true if the transfer was successful, false if it wasn't */ function transfer(address _to, uint256 _value) public override(IERC20Token, ERC20Token) returns (bool) { return super.transfer(_to, _value); } /** * @dev an account/contract attempts to get the coins * throws on any error rather then return a false flag to minimize user errors * in addition to the standard checks, the function throws if transfers are disabled * * @param _from source address * @param _to target address * @param _value transfer amount * * @return true if the transfer was successful, false if it wasn't */ function transferFrom(address _from, address _to, uint256 _value) public override(IERC20Token, ERC20Token) returns (bool) { return super.transferFrom(_from, _to, _value); } }