Store separate operator / pool balances in the reward vault. This reduces complexity in the staking contract.

This commit is contained in:
Greg Hysen
2019-06-13 17:13:55 -07:00
parent 7d85e61cc5
commit b3d1b6c499
7 changed files with 139 additions and 44 deletions

View File

@@ -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;
}

View File

@@ -61,9 +61,10 @@ contract MixinPools is
// create pool in reward vault
rewardVault.createPool(
poolId,
operatorAddress
operatorAddress,
operatorShare
);
//
emit PoolCreated(poolId, operatorAddress, operatorShare);
return poolId;

View File

@@ -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,

View File

@@ -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

View File

@@ -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];
}
}

View File

@@ -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]),
]);
*/
});
});
});

View File

@@ -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);