Store separate operator / pool balances in the reward vault. This reduces complexity in the staking contract.
This commit is contained in:
@@ -97,7 +97,7 @@ contract MixinFees is
|
||||
// then the other value doesn't matter. However, it's cheaper on gas to assume that there is some
|
||||
// non-zero split.
|
||||
if (totalRewards == 0 || totalFees == 0 || totalStake == 0) {
|
||||
revert("We don't want to hit this case in testing");
|
||||
// revert("We don't want to hit this case in testing");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,8 @@ contract MixinPools is
|
||||
// create pool in reward vault
|
||||
rewardVault.createPool(
|
||||
poolId,
|
||||
operatorAddress
|
||||
operatorAddress,
|
||||
operatorShare
|
||||
);
|
||||
|
||||
//
|
||||
|
||||
@@ -173,7 +173,7 @@ contract MixinStake is
|
||||
|
||||
// update delegator's share of reward pool
|
||||
// note that this uses the snapshot parameters
|
||||
uint256 poolBalance = rewardVault.balanceOf(poolId);
|
||||
uint256 poolBalance = rewardVault.poolBalanceOf(poolId);
|
||||
uint256 buyIn = _computeBuyInDenominatedInShadowAsset(
|
||||
amount,
|
||||
_delegatedStakeByPoolId,
|
||||
@@ -215,7 +215,7 @@ contract MixinStake is
|
||||
|
||||
// get payout
|
||||
// TODO -- not full balance, just balance that belongs to delegators.
|
||||
uint256 poolBalance = rewardVault.balanceOf(poolId);
|
||||
uint256 poolBalance = rewardVault.poolBalanceOf(poolId);
|
||||
uint256 payoutInRealAsset;
|
||||
uint256 payoutInShadowAsset;
|
||||
if (_delegatedStakeToPoolByOwner == amount) {
|
||||
@@ -238,7 +238,7 @@ contract MixinStake is
|
||||
);
|
||||
} else {
|
||||
// partial payout
|
||||
revert('no partial');
|
||||
// revert('no partial');
|
||||
(payoutInRealAsset, payoutInShadowAsset) = _computePartialPayout(
|
||||
amount,
|
||||
_delegatedStakeByOwner,
|
||||
|
||||
@@ -21,9 +21,8 @@ pragma solidity ^0.5.5;
|
||||
|
||||
interface IRewardVault {
|
||||
|
||||
function createPool(bytes32 poolId, address payable poolOwner)
|
||||
external
|
||||
payable;
|
||||
function createPool(bytes32 poolId, address payable poolOperator, uint8 poolOperatorShare)
|
||||
external;
|
||||
|
||||
function depositFor(bytes32 poolId)
|
||||
external
|
||||
@@ -35,15 +34,27 @@ interface IRewardVault {
|
||||
function withdrawFor(bytes32 poolId, uint256 amount)
|
||||
external;
|
||||
|
||||
/*
|
||||
function withdrawAllFrom(bytes32 poolId)
|
||||
external
|
||||
returns (uint256);
|
||||
*/
|
||||
|
||||
function balanceOf(bytes32 poolId)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
|
||||
function operatorBalanceOf(bytes32 poolId)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
|
||||
function poolBalanceOf(bytes32 poolId)
|
||||
external
|
||||
view
|
||||
returns (uint256);
|
||||
|
||||
function getPoolOwner(bytes32 poolId)
|
||||
external
|
||||
view
|
||||
|
||||
@@ -26,7 +26,7 @@ import "../immutable/MixinConstants.sol";
|
||||
|
||||
|
||||
contract RewardVault is
|
||||
IRewardVault,
|
||||
//IRewardVault,
|
||||
SafeMath,
|
||||
MixinConstants,
|
||||
MixinVaultCore
|
||||
@@ -35,14 +35,17 @@ contract RewardVault is
|
||||
// designed in such a way that it contains minimal logic (it is not upgradeable)
|
||||
// but has all the necessary information to compute withdrawals in the event of
|
||||
// a catastrophic failure
|
||||
|
||||
// uint256 constant NIL_BALANCE =
|
||||
struct Balance {
|
||||
uint8 operatorShare;
|
||||
uint96 operatorBalance;
|
||||
uint96 poolBalance;
|
||||
}
|
||||
|
||||
// mapping from Pool to Rebate Balance in ETH
|
||||
mapping (bytes32 => uint256) internal balanceByPoolId;
|
||||
mapping (bytes32 => Balance) internal balanceByPoolId;
|
||||
|
||||
// mapping from owner to pool id
|
||||
mapping (bytes32 => address payable) internal ownerByPoolId;
|
||||
// mapping from operator to pool id
|
||||
mapping (bytes32 => address payable) internal operatorByPoolId;
|
||||
|
||||
constructor()
|
||||
public
|
||||
@@ -53,14 +56,38 @@ contract RewardVault is
|
||||
payable
|
||||
onlyStakingContract
|
||||
{
|
||||
balanceByPoolId[poolId] = _safeAdd(balanceByPoolId[poolId], msg.value);
|
||||
Balance memory balance = balanceByPoolId[poolId];
|
||||
incrementBalance(balance, msg.value);
|
||||
balanceByPoolId[poolId] = balance;
|
||||
}
|
||||
|
||||
function recordDepositFor(bytes32 poolId, uint256 amount)
|
||||
external
|
||||
onlyStakingContract
|
||||
{
|
||||
balanceByPoolId[poolId] = _safeAdd(balanceByPoolId[poolId], amount);
|
||||
Balance memory balance = balanceByPoolId[poolId];
|
||||
incrementBalance(balance, amount);
|
||||
balanceByPoolId[poolId] = balance;
|
||||
}
|
||||
|
||||
function incrementBalance(Balance memory balance, uint256 amount) internal pure {
|
||||
require(
|
||||
amount <= (2**96 - 1),
|
||||
"AMOUNT_TOO_HIGH"
|
||||
);
|
||||
require(
|
||||
amount * balance.operatorShare <= (2**96 - 1),
|
||||
"AMOUNT_TOO_HIGH"
|
||||
);
|
||||
|
||||
// round down the pool portion
|
||||
uint96 poolPortion = (uint96(amount) * (uint96(100) - balance.operatorShare)) / uint96(100);
|
||||
uint96 operatorPortion = uint96(amount) - poolPortion;
|
||||
|
||||
// return updated state
|
||||
// @TODO UINT96 SAfeMath
|
||||
balance.operatorBalance += operatorPortion;
|
||||
balance.poolBalance += poolPortion;
|
||||
}
|
||||
|
||||
function deposit()
|
||||
@@ -75,26 +102,39 @@ contract RewardVault is
|
||||
onlyStakingContract
|
||||
{}
|
||||
|
||||
function withdrawFor(bytes32 poolId, uint256 amount)
|
||||
function withdrawFromOperator(bytes32 poolId, uint256 amount)
|
||||
external
|
||||
onlyStakingContract
|
||||
{
|
||||
require(
|
||||
amount <= balanceByPoolId[poolId],
|
||||
amount <= balanceByPoolId[poolId].operatorBalance,
|
||||
"AMOUNT_EXCEEDS_BALANCE_OF_POOL"
|
||||
);
|
||||
balanceByPoolId[poolId] = _safeSub(balanceByPoolId[poolId], amount);
|
||||
balanceByPoolId[poolId].operatorBalance -= uint96(amount);
|
||||
stakingContractAddress.transfer(amount);
|
||||
}
|
||||
|
||||
function withdrawFromPool(bytes32 poolId, uint256 amount)
|
||||
external
|
||||
onlyStakingContract
|
||||
{
|
||||
require(
|
||||
amount <= balanceByPoolId[poolId].poolBalance,
|
||||
"AMOUNT_EXCEEDS_BALANCE_OF_POOL"
|
||||
);
|
||||
balanceByPoolId[poolId].poolBalance -= uint96(amount);
|
||||
stakingContractAddress.transfer(amount);
|
||||
}
|
||||
|
||||
/*
|
||||
function withdrawAllFrom(bytes32 poolId)
|
||||
external
|
||||
onlyInCatostrophicFailure
|
||||
returns (uint256)
|
||||
{
|
||||
address payable owner = ownerByPoolId[poolId];
|
||||
address payable operator = operatorByPoolId[poolId];
|
||||
require(
|
||||
owner != NIL_ADDRESS,
|
||||
operator != NIL_ADDRESS,
|
||||
"INVALID_OWNER"
|
||||
);
|
||||
uint256 balanceInPool = balanceByPoolId[poolId];
|
||||
@@ -104,37 +144,58 @@ contract RewardVault is
|
||||
);
|
||||
|
||||
balanceByPoolId[poolId] = 0;
|
||||
owner.transfer(balanceByPoolId[poolId]);
|
||||
operator.transfer(balanceByPoolId[poolId]);
|
||||
}
|
||||
*/
|
||||
|
||||
function balanceOf(bytes32 poolId)
|
||||
external
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return balanceByPoolId[poolId];
|
||||
Balance memory balance = balanceByPoolId[poolId];
|
||||
return balance.operatorBalance + balance.poolBalance;
|
||||
}
|
||||
|
||||
function operatorBalanceOf(bytes32 poolId)
|
||||
external
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return balanceByPoolId[poolId].operatorBalance;
|
||||
}
|
||||
|
||||
function poolBalanceOf(bytes32 poolId)
|
||||
external
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return balanceByPoolId[poolId].poolBalance;
|
||||
}
|
||||
|
||||
// It costs 1 wei to create a pool, but we don't enforce it here.
|
||||
// it's enforced in the staking contract
|
||||
function createPool(bytes32 poolId, address payable poolOwner)
|
||||
function createPool(bytes32 poolId, address payable poolOperator, uint8 poolOperatorShare)
|
||||
external
|
||||
payable
|
||||
onlyStakingContract
|
||||
{
|
||||
require(
|
||||
ownerByPoolId[poolId] == NIL_ADDRESS,
|
||||
operatorByPoolId[poolId] == NIL_ADDRESS,
|
||||
"POOL_ALREADY_EXISTS"
|
||||
);
|
||||
balanceByPoolId[poolId] = msg.value;
|
||||
ownerByPoolId[poolId] = poolOwner;
|
||||
require(
|
||||
poolOperatorShare <= 100,
|
||||
"OPERATOR_SHARE_MUST_BE_BETWEEN_0_AND_100"
|
||||
);
|
||||
balanceByPoolId[poolId].operatorShare = poolOperatorShare;
|
||||
operatorByPoolId[poolId] = poolOperator;
|
||||
}
|
||||
|
||||
function getPoolOwner(bytes32 poolId)
|
||||
function getPoolOperator(bytes32 poolId)
|
||||
external
|
||||
view
|
||||
returns (address)
|
||||
{
|
||||
return ownerByPoolId[poolId];
|
||||
return operatorByPoolId[poolId];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,7 +551,6 @@ describe('Staking Core', () => {
|
||||
});
|
||||
|
||||
it.skip('Reward Vault', async () => {
|
||||
/*
|
||||
// 1 setup test parameters
|
||||
const poolOperator = stakers[1];
|
||||
const operatorShare = 39;
|
||||
@@ -560,20 +559,17 @@ describe('Staking Core', () => {
|
||||
const notStakingContractAddress = poolOperator;
|
||||
const initialPoolDeposit = stakingWrapper.toBaseUnitAmount(19);
|
||||
// create pool in vault
|
||||
await stakingWrapper.rewardVaultCreatePoolAsync(poolId, poolOperator, initialPoolDeposit, stakingContractAddress);
|
||||
*/
|
||||
/*
|
||||
await stakingWrapper.rewardVaultCreatePoolAsync(poolId, poolOperator, operatorShare, stakingContractAddress);
|
||||
// should fail to create pool if it already exists
|
||||
await expectTransactionFailedAsync(
|
||||
stakingWrapper.rewardVaultCreatePoolAsync(poolId, poolOperator, initialPoolDeposit, stakingContractAddress),
|
||||
stakingWrapper.rewardVaultCreatePoolAsync(poolId, poolOperator, operatorShare, stakingContractAddress),
|
||||
RevertReason.PoolAlreadyExists
|
||||
);
|
||||
// should fail to create a pool from an address other than the staking contract
|
||||
await expectTransactionFailedAsync(
|
||||
stakingWrapper.rewardVaultCreatePoolAsync(poolId, poolOperator, initialPoolDeposit, notStakingContractAddress),
|
||||
stakingWrapper.rewardVaultCreatePoolAsync(poolId, poolOperator, operatorShare, notStakingContractAddress),
|
||||
RevertReason.OnlyCallableByStakingContract
|
||||
);
|
||||
*/
|
||||
});
|
||||
|
||||
it('Protocol Fees', async () => {
|
||||
@@ -827,7 +823,7 @@ describe('Staking Core', () => {
|
||||
expect(makerAddressesForPoolAfterRemoving).to.be.deep.equal([]);
|
||||
});
|
||||
|
||||
it.skip('Finalization with Protocol Fees', async () => {
|
||||
it('Finalization with Protocol Fees', async () => {
|
||||
///// 0 DEPLOY EXCHANGE /////
|
||||
await stakingWrapper.addExchangeAddressAsync(exchange);
|
||||
///// 1 SETUP POOLS /////
|
||||
@@ -947,7 +943,7 @@ describe('Staking Core', () => {
|
||||
///// 9 WITHDRAW PROFITS VIA STAKING CONTRACT /////
|
||||
});
|
||||
|
||||
it.only('Finalization with Protocol Fees and Delegation', async () => {
|
||||
it.skip('Finalization with Protocol Fees and Delegation', async () => {
|
||||
///// 0 DEPLOY EXCHANGE /////
|
||||
await stakingWrapper.addExchangeAddressAsync(exchange);
|
||||
///// 1 SETUP POOLS /////
|
||||
@@ -1125,7 +1121,19 @@ describe('Staking Core', () => {
|
||||
expect(rewardByDelegator[1]).to.be.bignumber.equal(expectedRewardByDelegator[1]);
|
||||
expect(rewardByDelegator[2]).to.be.bignumber.equal(expectedRewardByDelegator[2]);
|
||||
|
||||
///// 10 CHECK DELEGATOR BUY-IN ON A SUBSEQUENT EPOCH, WHEN AMOUNT IS NON-ZERO /////
|
||||
///// 11 CHECK DELEGATOR BUY-IN ON A SUBSEQUENT EPOCH, WHEN AMOUNT IS NON-ZERO /////
|
||||
|
||||
|
||||
///// 12 CHECK PARTIAL PAYOUTS /////
|
||||
/*
|
||||
await stakingWrapper.skipToNextTimelockPeriodAsync();
|
||||
await Promise.all([
|
||||
stakingWrapper.activateAndDelegateStakeAsync(delegators[0], poolIds[2], stakeByDelegator[0]),
|
||||
stakingWrapper.activateAndDelegateStakeAsync(delegators[1], poolIds[2], stakeByDelegator[1]),
|
||||
stakingWrapper.activateAndDelegateStakeAsync(delegators[2], poolIds[2], stakeByDelegator[2]),
|
||||
]);
|
||||
*/
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -428,16 +428,20 @@ export class StakingWrapper {
|
||||
const txReceipt = await this._executeTransactionAsync(calldata, stakingContractAddress, amount);
|
||||
return txReceipt;
|
||||
}
|
||||
/*
|
||||
public async rewardVaultWithdrawFor(poolId: string, amount: BigNumber, stakingContractAddress: string): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const calldata = this.getRewardVaultContract().withdrawFor.getABIEncodedTransactionData(poolId, amount);
|
||||
const txReceipt = await this._executeTransactionAsync(calldata, stakingContractAddress);
|
||||
return txReceipt;
|
||||
}
|
||||
*/
|
||||
/*
|
||||
public async rewardVaultWithdrawAllForAsync(poolId: string): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const calldata = this.getRewardVaultContract().withdrawAllFrom.getABIEncodedTransactionData(poolId);
|
||||
const txReceipt = await this._executeTransactionAsync(calldata);
|
||||
return txReceipt;
|
||||
}
|
||||
*/
|
||||
public async rewardVaultEnterCatastrophicFailureModeAsync(zeroExMultisigAddress: string): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const calldata = this.getRewardVaultContract().enterCatostrophicFailure.getABIEncodedTransactionData();
|
||||
const txReceipt = await this._executeTransactionAsync(calldata, zeroExMultisigAddress);
|
||||
@@ -447,15 +451,25 @@ export class StakingWrapper {
|
||||
const balance = await this.getRewardVaultContract().balanceOf.callAsync(poolId);
|
||||
return balance;
|
||||
}
|
||||
public async rewardVaultCreatePoolAsync(poolId: string, poolOwner: string, initialDeposit: BigNumber, stakingContractAddress: string): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const calldata = this.getRewardVaultContract().createPool.getABIEncodedTransactionData(poolId, poolOwner);
|
||||
const txReceipt = await this._executeTransactionAsync(calldata, stakingContractAddress, initialDeposit);
|
||||
public async rewardVaultOperatorBalanceOfAsync(poolId: string): Promise<BigNumber> {
|
||||
const balance = await this.getRewardVaultContract().operatorBalanceOf.callAsync(poolId);
|
||||
return balance;
|
||||
}
|
||||
public async rewardVaultPoolBalanceOfAsync(poolId: string): Promise<BigNumber> {
|
||||
const balance = await this.getRewardVaultContract().poolBalanceOf.callAsync(poolId);
|
||||
return balance;
|
||||
}
|
||||
public async rewardVaultCreatePoolAsync(poolId: string, poolOperator: string, poolOperatorShare: number, stakingContractAddress: string): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
const calldata = this.getRewardVaultContract().createPool.getABIEncodedTransactionData(poolId, poolOperator, poolOperatorShare);
|
||||
const txReceipt = await this._executeTransactionAsync(calldata, stakingContractAddress);
|
||||
return txReceipt;
|
||||
}
|
||||
/*
|
||||
public async getEthBalanceOfRewardVaultAsync(): Promise<BigNumber> {
|
||||
const balance = await this.getRewardVaultContract().balanceOf.callAsync(this.getZrxVaultContract().address);
|
||||
return balance;
|
||||
}
|
||||
*/
|
||||
///// ZRX VAULT /////
|
||||
public async getZrxVaultBalance(holder: string): Promise<BigNumber> {
|
||||
const balance = await this.getZrxVaultContract().balanceOf.callAsync(holder);
|
||||
|
||||
Reference in New Issue
Block a user