Skip to content
This repository has been archived by the owner on Jul 9, 2021. It is now read-only.

Feature/staking/protocol fees #2130

Merged
merged 4 commits into from
Sep 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions contracts/erc20/contracts/src/interfaces/IERC20Token.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ contract IERC20Token {
)
external
returns (bool);

/// @dev `msg.sender` approves `_spender` to spend `_value` tokens
/// @param _spender The address of the account able to transfer the tokens
/// @param _value The amount of wei to be approved for transfer
Expand All @@ -69,7 +69,7 @@ contract IERC20Token {
external
view
returns (uint256);

/// @param _owner The address from which the balance will be retrieved
/// @return Balance of owner
function balanceOf(address _owner)
Expand Down
5 changes: 4 additions & 1 deletion contracts/staking/contracts/src/StakingProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,16 @@ contract StakingProxy is

/// @dev Constructor.
/// @param _stakingContract Staking contract to delegate calls to.
constructor(address _stakingContract, address _readOnlyProxy)
/// @param _readOnlyProxy The address of the read only proxy.
/// @param _wethProxyAddress The address that can transfer WETH for fees.
constructor(address _stakingContract, address _readOnlyProxy, address _wethProxyAddress)
public
MixinStorage()
{
stakingContract = _stakingContract;
readOnlyProxyCallee = _stakingContract;
readOnlyProxy = _readOnlyProxy;
wethAssetProxy = IAssetProxy(_wethProxyAddress);
}

/// @dev Delegates calls to the staking contract, if it is set.
Expand Down
71 changes: 59 additions & 12 deletions contracts/staking/contracts/src/fees/MixinExchangeFees.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
pragma solidity ^0.5.9;
pragma experimental ABIEncoderV2;

import "@0x/contracts-erc20/contracts/src/interfaces/IEtherToken.sol";
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/LibSafeMath.sol";
import "../libs/LibStakingRichErrors.sol";
Expand Down Expand Up @@ -80,10 +81,11 @@ contract MixinExchangeFees is
emit CobbDouglasAlphaChanged(numerator, denominator);
}

/// TODO(jalextowle): Add WETH to protocol fees. Should this be unwrapped?
/// @dev Pays a protocol fee in ETH.
/// @dev Pays a protocol fee in ETH or WETH.
/// Only a known 0x exchange can call this method. See (MixinExchangeManager).
/// @param makerAddress The address of the order's maker.
/// @param payerAddress The address of the protocol fee payer.
/// @param protocolFeePaid The protocol fee that should be paid.
function payProtocolFee(
address makerAddress,
// solhint-disable-next-line
Expand All @@ -95,15 +97,42 @@ contract MixinExchangeFees is
payable
onlyExchange
{
uint256 amount = msg.value;
// If the protocol fee payment is invalid, revert with a rich error.
if (
protocolFeePaid == 0 ||
(msg.value != protocolFeePaid && msg.value != 0)
) {
LibRichErrors.rrevert(LibStakingRichErrors.InvalidProtocolFeePaymentError(
jalextowle marked this conversation as resolved.
Show resolved Hide resolved
protocolFeePaid == 0 ?
LibStakingRichErrors.ProtocolFeePaymentErrorCodes.ZeroProtocolFeePaid :
LibStakingRichErrors.ProtocolFeePaymentErrorCodes.MismatchedFeeAndPayment,
protocolFeePaid,
msg.value
));
}

// Transfer the protocol fee to this address if it should be paid in WETH.
if (msg.value == 0) {
wethAssetProxy.transferFrom(
WETH_ASSET_DATA,
payerAddress,
address(this),
protocolFeePaid
);
}

// Get the pool id of the maker address.
bytes32 poolId = getStakingPoolIdOfMaker(makerAddress);

// Only attribute the protocol fee payment to a pool if the maker is registered to a pool.
if (poolId != NIL_POOL_ID) {
// There is a pool associated with `makerAddress`.
// TODO(dorothy-zbornak): When we have epoch locks on delegating, we could
// preclude pools that have no delegated stake, since they will never have
// stake in this epoch and are therefore not entitled to rewards.
// Use the maker pool id to get the amount of fees collected during this epoch in the pool.
uint256 _feesCollectedThisEpoch = protocolFeesThisEpochByPool[poolId];
protocolFeesThisEpochByPool[poolId] = _feesCollectedThisEpoch.safeAdd(amount);

// Update the amount of protocol fees paid to this pool this epoch.
protocolFeesThisEpochByPool[poolId] = _feesCollectedThisEpoch.safeAdd(protocolFeePaid);

// If there were no fees collected prior to this payment, activate the pool that is being paid.
if (_feesCollectedThisEpoch == 0) {
activePoolsThisEpoch.push(poolId);
}
Expand Down Expand Up @@ -156,6 +185,18 @@ contract MixinExchangeFees is
return protocolFeesThisEpochByPool[poolId];
}

/// @dev Withdraws the entire WETH balance of the contract.
function _unwrapWETH()
internal
{
uint256 wethBalance = IEtherToken(WETH_ADDRESS).balanceOf(address(this));

// Don't withdraw WETH if the WETH balance is zero as a gas optimization.
if (wethBalance != 0) {
IEtherToken(WETH_ADDRESS).withdraw(wethBalance);
}
}

/// @dev Pays rewards to market making pools that were active this epoch.
/// Each pool receives a portion of the fees generated this epoch (see _cobbDouglas) that is
/// proportional to (i) the fee volume attributed to their pool over the epoch, and
Expand All @@ -181,7 +222,12 @@ contract MixinExchangeFees is
uint256 finalContractBalance
)
{
// initialize return values
// step 1/4 - withdraw the entire wrapped ether balance into this contract. WETH
// is unwrapped here to keep `payProtocolFee()` calls relatively cheap,
// and WETH is only withdrawn if this contract's WETH balance is nonzero.
_unwrapWETH();

// Initialize initial values
totalActivePools = activePoolsThisEpoch.length;
totalFeesCollected = 0;
totalWeightedStake = 0;
Expand All @@ -201,7 +247,7 @@ contract MixinExchangeFees is
);
}

// step 1/3 - compute stats for active maker pools
// step 2/4 - compute stats for active maker pools
IStructs.ActivePool[] memory activePools = new IStructs.ActivePool[](totalActivePools);
for (uint256 i = 0; i != totalActivePools; i++) {
bytes32 poolId = activePoolsThisEpoch[i];
Expand Down Expand Up @@ -240,7 +286,7 @@ contract MixinExchangeFees is
);
}

// step 2/3 - record reward for each pool
// step 3/4 - record reward for each pool
for (uint256 i = 0; i != totalActivePools; i++) {
// compute reward using cobb-douglas formula
uint256 reward = _cobbDouglas(
Expand Down Expand Up @@ -277,7 +323,7 @@ contract MixinExchangeFees is
}
activePoolsThisEpoch.length = 0;

// step 3/3 send total payout to vault
// step 4/4 send total payout to vault

// Sanity check rewards calculation
if (totalRewardsPaid > initialContractBalance) {
Expand All @@ -289,6 +335,7 @@ contract MixinExchangeFees is
if (totalRewardsPaid > 0) {
_depositIntoStakingPoolRewardVault(totalRewardsPaid);
}

finalContractBalance = address(this).balance;

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,22 @@ contract MixinDeploymentConstants {
uint256 constant internal CHAIN_ID = 1;

uint256 constant internal MAX_MAKERS_IN_POOL = 10;

// Mainnet WETH9 Address
address constant internal WETH_ADDRESS = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

// Kovan WETH9 Address
// address constant internal WETH_ADDRESS = address(0xd0a1e359811322d97991e03f863a0c30c2cf029c);

// Ropsten & Rinkeby WETH9 Address
// address constant internal WETH_ADDRESS = address(0xc778417e063141139fce010982780140aa0cd5ab);

// Mainnet Weth Asset Data
bytes constant internal WETH_ASSET_DATA = hex"f47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2";

// Kovan Weth Asset Data
// bytes constant internal WETH_ASSET_DATA = hex"f47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c";

// Ropsten & Rinkeby Weth Asset Data
// bytes constant internal WETH_ASSET_DATA = hex"f47261b0000000000000000000000000c778417e063141139fce010982780140aa0cd5ab";
}
20 changes: 19 additions & 1 deletion contracts/staking/contracts/src/immutable/MixinStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,17 @@

pragma solidity ^0.5.9;

import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetData.sol";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import isn't being used at the moment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will actually be used now, since the check is in the constructor.

import "@0x/contracts-asset-proxy/contracts/src/interfaces/IAssetProxy.sol";
import "@0x/contracts-utils/contracts/src/LibBytes.sol";
import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";
import "@0x/contracts-utils/contracts/src/Ownable.sol";
import "./MixinConstants.sol";
import "../interfaces/IZrxVault.sol";
import "../interfaces/IEthVault.sol";
import "../interfaces/IStakingPoolRewardVault.sol";
import "../interfaces/IStructs.sol";
import "../libs/LibStakingRichErrors.sol";


// solhint-disable max-states-count, no-empty-blocks
Expand All @@ -32,10 +37,23 @@ contract MixinStorage is
Ownable,
MixinConstants
{
using LibBytes for bytes;

/// @dev Ensures that the WETH_ASSET_DATA is correct.
constructor()
public
Ownable()
{}
{
// Ensure that the WETH_ASSET_DATA from MixinDeploymentConstants is correct.
if (!WETH_ASSET_DATA.equals(
abi.encodeWithSelector(IAssetData(address(0)).ERC20Token.selector, WETH_ADDRESS)
)) {
LibRichErrors.rrevert(LibStakingRichErrors.InvalidWethAssetDataError());
}
}

// WETH Asset Proxy
IAssetProxy internal wethAssetProxy;

// address of staking contract
address internal stakingContract;
Expand Down
14 changes: 7 additions & 7 deletions contracts/staking/contracts/src/interfaces/IZrxVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,17 @@ interface IZrxVault {
uint256 amount
);

/// @dev Emitted when the ERC20 Proxy is changed.
/// @param erc20ProxyAddress Address of the new ERC20 proxy.
event Erc20ProxyChanged(
address erc20ProxyAddress
/// @dev Emitted when the Zrx Proxy is changed.
/// @param zrxProxyAddress Address of the new ERC20 proxy.
event ZrxProxyChanged(
address zrxProxyAddress
);

/// @dev Sets the ERC20 proxy.
/// @dev Sets the Zrx proxy.
/// Note that only the contract owner can call this.
/// Note that this can only be called when *not* in Catastrophic Failure mode.
/// @param erc20ProxyAddress Address of the 0x ERC20 Proxy.
function setErc20Proxy(address erc20ProxyAddress)
/// @param zrxProxyAddress Address of the 0x Zrx Asset Proxy.
function setZrxProxy(address zrxProxyAddress)
external;

/// @dev Deposit an `amount` of Zrx Tokens from `owner` into the vault.
Expand Down
38 changes: 38 additions & 0 deletions contracts/staking/contracts/src/libs/LibStakingRichErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ import "@0x/contracts-utils/contracts/src/LibRichErrors.sol";


library LibStakingRichErrors {

enum ProtocolFeePaymentErrorCodes {
ZeroProtocolFeePaid,
MismatchedFeeAndPayment
}

// bytes4(keccak256("MiscalculatedRewardsError(uint256,uint256)"))
bytes4 internal constant MISCALCULATED_REWARDS_ERROR_SELECTOR =
0xf7806c4e;
Expand Down Expand Up @@ -113,6 +119,14 @@ library LibStakingRichErrors {
POOL_IS_FULL
}

// bytes4(keccak256("InvalidProtocolFeePaymentError(uint8,uint256,uint256)"))
bytes4 internal constant INVALID_PROTOCOL_FEE_PAYMENT_ERROR_SELECTOR =
0xefd6cb33;

// bytes4(keccak256("InvalidWethAssetDataError()"))
bytes internal constant INVALID_WETH_ASSET_DATA_ERROR =
hex"24bf322c";

// solhint-disable func-name-mixedcase
function MiscalculatedRewardsError(
uint256 totalRewardsPaid,
Expand Down Expand Up @@ -359,6 +373,23 @@ library LibStakingRichErrors {
);
}

function InvalidProtocolFeePaymentError(
ProtocolFeePaymentErrorCodes errorCode,
uint256 expectedProtocolFeePaid,
uint256 actualProtocolFeePaid
)
internal
pure
returns (bytes memory)
{
return abi.encodeWithSelector(
INVALID_PROTOCOL_FEE_PAYMENT_ERROR_SELECTOR,
errorCode,
expectedProtocolFeePaid,
actualProtocolFeePaid
);
}

function RewardVaultNotSetError()
internal
pure
Expand Down Expand Up @@ -388,4 +419,11 @@ library LibStakingRichErrors {
return PROXY_DESTINATION_CANNOT_BE_NIL;
}

function InvalidWethAssetDataError()
internal
pure
returns (bytes memory)
{
return INVALID_WETH_ASSET_DATA_ERROR;
}
}
22 changes: 11 additions & 11 deletions contracts/staking/contracts/src/vaults/ZrxVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ contract ZrxVault is
// mapping from Owner to ZRX balance
mapping (address => uint256) internal balances;

// 0x ERC20 Proxy
IAssetProxy internal erc20Proxy;
// Zrx Asset Proxy
IAssetProxy internal zrxAssetProxy;

// Zrx Token
IERC20Token internal zrxToken;
Expand All @@ -52,33 +52,33 @@ contract ZrxVault is
bytes internal zrxAssetData;

/// @dev Constructor.
/// @param erc20ProxyAddress Address of the 0x ERC20 Proxy.
/// @param zrxProxyAddress Address of the 0x Zrx Proxy.
/// @param zrxTokenAddress Address of the Zrx Token.
constructor(
address erc20ProxyAddress,
address zrxProxyAddress,
address zrxTokenAddress
)
public
{
erc20Proxy = IAssetProxy(erc20ProxyAddress);
zrxAssetProxy = IAssetProxy(zrxProxyAddress);
zrxToken = IERC20Token(zrxTokenAddress);
zrxAssetData = abi.encodeWithSelector(
IAssetData(address(0)).ERC20Token.selector,
zrxTokenAddress
);
}

/// @dev Sets the ERC20 proxy.
/// @dev Sets the Zrx proxy.
/// Note that only the contract owner can call this.
/// Note that this can only be called when *not* in Catastrophic Failure mode.
/// @param erc20ProxyAddress Address of the 0x ERC20 Proxy.
function setErc20Proxy(address erc20ProxyAddress)
/// @param zrxProxyAddress Address of the 0x Zrx Proxy.
function setZrxProxy(address zrxProxyAddress)
external
onlyOwner
onlyNotInCatastrophicFailure
{
erc20Proxy = IAssetProxy(erc20ProxyAddress);
emit Erc20ProxyChanged(erc20ProxyAddress);
zrxAssetProxy = IAssetProxy(zrxProxyAddress);
emit ZrxProxyChanged(zrxProxyAddress);
}

/// @dev Deposit an `amount` of Zrx Tokens from `owner` into the vault.
Expand All @@ -98,7 +98,7 @@ contract ZrxVault is
emit ZrxDepositedIntoVault(msg.sender, owner, amount);

// deposit ZRX from owner
erc20Proxy.transferFrom(
zrxAssetProxy.transferFrom(
zrxAssetData,
owner,
address(this),
Expand Down
Loading