Skip to content

Commit

Permalink
Add gov powers; prevent full slashing
Browse files Browse the repository at this point in the history
  • Loading branch information
def-main committed Jul 14, 2021
1 parent e849611 commit f816626
Show file tree
Hide file tree
Showing 18 changed files with 1,040 additions and 264 deletions.
89 changes: 89 additions & 0 deletions contracts/interfaces/IGovernancePowerDelegationERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// SPDX-License-Identifier: agpl-3.0
pragma solidity 0.7.5;

interface IGovernancePowerDelegationERC20 {

enum DelegationType {
VOTING_POWER,
PROPOSITION_POWER
}

/**
* @dev Emitted when a user delegates governance power to another user.
*
* @param delegator The delegator.
* @param delegatee The delegatee.
* @param delegationType The type of delegation (VOTING_POWER, PROPOSITION_POWER).
*/
event DelegateChanged(
address indexed delegator,
address indexed delegatee,
DelegationType delegationType
);

/**
* @dev Emitted when an action changes the delegated power of a user.
*
* @param user The user whose delegated power has changed.
* @param amount The new amount of delegated power for the user.
* @param delegationType The type of delegation (VOTING_POWER, PROPOSITION_POWER).
*/
event DelegatedPowerChanged(address indexed user, uint256 amount, DelegationType delegationType);

/**
* @dev Delegates a specific governance power to a delegatee.
*
* @param delegatee The address to delegate power to.
* @param delegationType The type of delegation (VOTING_POWER, PROPOSITION_POWER).
*/
function delegateByType(address delegatee, DelegationType delegationType) external virtual;

/**
* @dev Delegates all governance powers to a delegatee.
*
* @param delegatee The user to which the power will be delegated.
*/
function delegate(address delegatee) external virtual;

/**
* @dev Returns the delegatee of an user.
*
* @param delegator The address of the delegator.
* @param delegationType The type of delegation (VOTING_POWER, PROPOSITION_POWER).
*/
function getDelegateeByType(address delegator, DelegationType delegationType)
external
view
virtual
returns (address);

/**
* @dev Returns the current delegated power of a user. The current power is the power delegated
* at the time of the last snapshot.
*
* @param user The user whose power to query.
* @param delegationType The type of power (VOTING_POWER, PROPOSITION_POWER).
*/
function getPowerCurrent(address user, DelegationType delegationType)
external
view
virtual
returns (uint256);

/**
* @dev Returns the delegated power of a user at a certain block.
*
* @param user The user whose power to query.
* @param blockNumber The block number at which to get the user's power.
* @param delegationType The type of power (VOTING_POWER, PROPOSITION_POWER).
*/
function getPowerAtBlock(
address user,
uint256 blockNumber,
DelegationType delegationType
)
external
view
virtual
returns (uint256);
}
9 changes: 9 additions & 0 deletions contracts/lib/SafeCast.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,13 @@ library SafeCast {
require(uint256(b) == a, 'SafeCast: toUint128 overflow');
return b;
}

/**
* @dev Downcase to a uint240, reverting on overflow.
*/
function toUint240(uint256 a) internal pure returns (uint240) {
uint240 b = uint240(a);
require(uint256(b) == a, 'SafeCast: toUint240 overflow');
return b;
}
}
30 changes: 27 additions & 3 deletions contracts/safety/v1/SafetyModuleV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ pragma experimental ABIEncoderV2;

import {IERC20} from '../../interfaces/IERC20.sol';
import {SM1Admin} from './impl/SM1Admin.sol';
import {SM1ERC20} from './impl/SM1ERC20.sol';
import {SM1Getters} from './impl/SM1Getters.sol';
import {SM1Operators} from './impl/SM1Operators.sol';
import {SM1Slashing} from './impl/SM1Slashing.sol';
Expand All @@ -21,9 +20,18 @@ contract SafetyModuleV1 is
SM1Slashing,
SM1Operators,
SM1Admin,
SM1ERC20,
SM1Getters
{
// ============ Constants ============

string public constant EIP712_DOMAIN_NAME = 'dYdX Safety Module';

string public constant EIP712_DOMAIN_VERSION = '1';

bytes32 public constant EIP712_DOMAIN_SCHEMA_HASH = keccak256(
'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'
);

// ============ Constructor ============

constructor(
Expand All @@ -41,10 +49,26 @@ contract SafetyModuleV1 is
uint256 offset,
uint256 blackoutWindow
) external initializer {
__SM1ExchangeRate_init();
__SM1Roles_init();
__SM1EpochSchedule_init(interval, offset, blackoutWindow);
__SM1Rewards_init();
_EXCHANGE_RATE_ = EXCHANGE_RATE_BASE;

// Store the domain separator for EIP-712 signatures.
uint256 chainId;
// solium-disable-next-line
assembly {
chainId := chainid()
}
_DOMAIN_SEPARATOR_ = keccak256(
abi.encode(
EIP712_DOMAIN_SCHEMA_HASH,
keccak256(bytes(EIP712_DOMAIN_NAME)),
keccak256(bytes(EIP712_DOMAIN_VERSION)),
chainId,
address(this)
)
);
}

// ============ Internal Functions ============
Expand Down
8 changes: 6 additions & 2 deletions contracts/safety/v1/impl/SM1Admin.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
pragma solidity 0.7.5;
pragma experimental ABIEncoderV2;

import {SafeMath} from '../../../dependencies/open-zeppelin/SafeMath.sol';
import {SafeCast} from '../../../lib/SafeCast.sol';
import {SafeMath} from '../../../dependencies/open-zeppelin/SafeMath.sol';
import {SM1Types} from '../lib/SM1Types.sol';
import {SM1Roles} from './SM1Roles.sol';
import {SM1StakedBalances} from './SM1StakedBalances.sol';

/**
Expand All @@ -12,7 +13,10 @@ import {SM1StakedBalances} from './SM1StakedBalances.sol';
*
* @dev Admin-only functions.
*/
abstract contract SM1Admin is SM1StakedBalances {
abstract contract SM1Admin is
SM1StakedBalances,
SM1Roles
{
using SafeCast for uint256;
using SafeMath for uint256;

Expand Down
74 changes: 68 additions & 6 deletions contracts/safety/v1/impl/SM1ERC20.sol
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
pragma solidity 0.7.5;
pragma experimental ABIEncoderV2;

import {IERC20Detailed} from '../../../interfaces/IERC20Detailed.sol';
import {SafeMath} from '../../../dependencies/open-zeppelin/SafeMath.sol';
import {IERC20} from '../../../interfaces/IERC20.sol';
import {IERC20Detailed} from '../../../interfaces/IERC20Detailed.sol';
import {SM1Types} from '../lib/SM1Types.sol';
import {SM1GovernancePowerDelegation} from './SM1GovernancePowerDelegation.sol';
import {SM1StakedBalances} from './SM1StakedBalances.sol';

/**
* @title SM1ERC20
* @author dYdX
*
* @dev ERC20 interface for staked tokens. Allows a user with an active stake to transfer their
* staked tokens to another user, even if they would otherwise be restricted from withdrawing.
* @dev ERC20 interface for staked tokens. Implements governance functionality for the tokens.
*
* Also allows a user with an active stake to transfer their staked tokens to another user,
* even if they would otherwise be restricted from withdrawing.
*/
abstract contract SM1ERC20 is SM1StakedBalances, IERC20Detailed {
abstract contract SM1ERC20 is
SM1StakedBalances,
SM1GovernancePowerDelegation,
IERC20Detailed
{
using SafeMath for uint256;

// ============ Constants ============

/// @notice EIP-712 typehash for token approval via EIP-2612 permit.
bytes32 public constant PERMIT_TYPEHASH = keccak256(
'Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'
);

// ============ External Functions ============

function name() external pure override returns (string memory) {
Expand Down Expand Up @@ -53,7 +68,12 @@ abstract contract SM1ERC20 is SM1StakedBalances, IERC20Detailed {
*
* @return The user's staked balance.
*/
function balanceOf(address account) external view override returns (uint256) {
function balanceOf(address account)
public
view
override(SM1GovernancePowerDelegation, IERC20)
returns (uint256)
{
return getActiveBalanceCurrentEpoch(account) + getInactiveBalanceCurrentEpoch(account);
}

Expand Down Expand Up @@ -107,21 +127,63 @@ abstract contract SM1ERC20 is SM1StakedBalances, IERC20Detailed {
return true;
}

/**
* @notice Implements the permit function as specified in EIP-2612.
*
* @param owner Address of the token owner.
* @param spender Address of the spender.
* @param value Amount of allowance.
* @param deadline Expiration timestamp for the signature.
* @param v Signature param.
* @param r Signature param.
* @param s Signature param.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
)
external
{
require(owner != address(0), 'SM1ERC20: INVALID_OWNER');
require(block.timestamp <= deadline, 'SM1ERC20: INVALID_EXPIRATION');
uint256 currentValidNonce = _NONCES_[owner];
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
_DOMAIN_SEPARATOR_,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, currentValidNonce, deadline))
)
);
require(owner == ecrecover(digest, v, r, s), 'SM1ERC20: INVALID_SIGNATURE');
_NONCES_[owner] = currentValidNonce.add(1);
_approve(owner, spender, value);
}

// ============ Internal Functions ============

function _transfer(
address sender,
address recipient,
uint256 amount
) internal {
)
internal
{
require(sender != address(0), 'SM1ERC20: Transfer from address(0)');
require(recipient != address(0), 'SM1ERC20: Transfer to address(0)');
require(
getTransferableBalance(sender) >= amount,
'SM1ERC20: Transfer amount exceeds user next epoch active balance'
);

// Update staked balances and delegate snapshots.
_transferCurrentAndNextActiveBalance(sender, recipient, amount);
_moveDelegatesForTransfer(sender, recipient, amount);

emit Transfer(sender, recipient, amount);
}

Expand Down
8 changes: 5 additions & 3 deletions contracts/safety/v1/impl/SM1EpochSchedule.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
pragma solidity 0.7.5;
pragma experimental ABIEncoderV2;

import {SafeMath} from '../../../dependencies/open-zeppelin/SafeMath.sol';
import {SafeCast} from '../../../lib/SafeCast.sol';
import {SafeMath} from '../../../dependencies/open-zeppelin/SafeMath.sol';
import {SM1Types} from '../lib/SM1Types.sol';
import {SM1Roles} from './SM1Roles.sol';
import {SM1Storage} from './SM1Storage.sol';

/**
* @title SM1EpochSchedule
Expand All @@ -24,7 +24,9 @@ import {SM1Roles} from './SM1Roles.sol';
* The recommended epoch length and blackout window are 28 and 7 days respectively; however, these
* are modifiable by the admin, within the specified bounds.
*/
abstract contract SM1EpochSchedule is SM1Roles {
abstract contract SM1EpochSchedule is
SM1Storage
{
using SafeCast for uint256;
using SafeMath for uint256;

Expand Down
Loading

0 comments on commit f816626

Please sign in to comment.