From 7d85e61cc565993670caa16095ca2091f8ff9674 Mon Sep 17 00:00:00 2001 From: Greg Hysen Date: Tue, 11 Jun 2019 16:56:12 -0700 Subject: [PATCH] Basic payouts to delegators when the pool is empty and they receive 100% of the reward. --- contracts/staking/contracts/src/Staking.sol | 3 ++ .../staking/contracts/src/core/MixinStake.sol | 35 +++++++++++++++--- .../staking/contracts/src/libs/LibRewards.sol | 3 ++ contracts/staking/test/core_test.ts | 37 ++++++++++++++++++- .../staking/test/utils/staking_wrapper.ts | 14 ++++--- 5 files changed, 80 insertions(+), 12 deletions(-) diff --git a/contracts/staking/contracts/src/Staking.sol b/contracts/staking/contracts/src/Staking.sol index 508c12fa0b..82246e11a1 100644 --- a/contracts/staking/contracts/src/Staking.sol +++ b/contracts/staking/contracts/src/Staking.sol @@ -420,4 +420,7 @@ contract Staking is { rewardVault = IRewardVault(_rewardVault); } + + ///// CAN RECEIVE FUNDS ///// + function () external payable {} } diff --git a/contracts/staking/contracts/src/core/MixinStake.sol b/contracts/staking/contracts/src/core/MixinStake.sol index e1299f1064..9e42494b21 100644 --- a/contracts/staking/contracts/src/core/MixinStake.sol +++ b/contracts/staking/contracts/src/core/MixinStake.sol @@ -174,16 +174,27 @@ contract MixinStake is // update delegator's share of reward pool // note that this uses the snapshot parameters uint256 poolBalance = rewardVault.balanceOf(poolId); - /*uint256 buyIn = _computeBuyInDenominatedInShadowAsset( + uint256 buyIn = _computeBuyInDenominatedInShadowAsset( amount, _delegatedStakeByPoolId, shadowRewardsByPoolId[poolId], poolBalance - );*/ - //shadowRewardsInPoolByOwner[owner][poolId] = _safeAdd(shadowRewardsInPoolByOwner[owner][poolId], buyIn); - //shadowRewardsByPoolId[poolId] = _safeAdd(shadowRewardsByPoolId[poolId], buyIn); + ); + if (buyIn > 0) { + shadowRewardsInPoolByOwner[owner][poolId] = _safeAdd(shadowRewardsInPoolByOwner[owner][poolId], buyIn); + shadowRewardsByPoolId[poolId] = _safeAdd(shadowRewardsByPoolId[poolId], buyIn); + } } + event K( + uint256 amountDelegatedByOwner, + uint256 totalAmountDelegated, + uint256 amountOfShadowAssetHeldByOwner, + uint256 totalAmountOfShadowAsset, + uint256 totalAmountOfRealAsset, + uint256 payoutInRealAsset + ); + // question - should we then return the amount withdrawn? function _undelegateStake(address payable owner, bytes32 poolId, uint256 amount) private @@ -203,6 +214,7 @@ contract MixinStake is delegatedStakeByPoolId[poolId] = _safeSub(_delegatedStakeByPoolId, amount); // get payout + // TODO -- not full balance, just balance that belongs to delegators. uint256 poolBalance = rewardVault.balanceOf(poolId); uint256 payoutInRealAsset; uint256 payoutInShadowAsset; @@ -216,8 +228,17 @@ contract MixinStake is shadowRewardsByPoolId[poolId], poolBalance ); + emit K( + amount, + _delegatedStakeByPoolId, + payoutInShadowAsset, + shadowRewardsByPoolId[poolId], + poolBalance, + payoutInRealAsset + ); } else { // partial payout + revert('no partial'); (payoutInRealAsset, payoutInShadowAsset) = _computePartialPayout( amount, _delegatedStakeByOwner, @@ -231,8 +252,10 @@ contract MixinStake is shadowRewardsByPoolId[poolId] = _safeSub(shadowRewardsByPoolId[poolId], payoutInShadowAsset); // withdraw payout for delegator - rewardVault.withdrawFor(poolId, payoutInRealAsset); - owner.transfer(payoutInRealAsset); + if (payoutInRealAsset > 0) { + rewardVault.withdrawFor(poolId, payoutInRealAsset); + owner.transfer(payoutInRealAsset); + } } // Epoch | lockedAt | total | pending | deactivated | timelock() | withdraw() | available() diff --git a/contracts/staking/contracts/src/libs/LibRewards.sol b/contracts/staking/contracts/src/libs/LibRewards.sol index 9afd3b18e9..5a1246165e 100644 --- a/contracts/staking/contracts/src/libs/LibRewards.sol +++ b/contracts/staking/contracts/src/libs/LibRewards.sol @@ -31,6 +31,7 @@ contract LibRewards is SafeMath { uint256 totalAmountOfRealAsset ) internal + pure returns (uint256) { return _safeSub( @@ -54,6 +55,7 @@ contract LibRewards is SafeMath { uint256 totalAmountOfRealAsset ) internal + pure returns ( uint256 payoutInRealAsset, uint256 payoutInShadowAsset @@ -77,6 +79,7 @@ contract LibRewards is SafeMath { uint256 totalAmountOfRealAsset ) internal + pure returns (uint256) { if (totalAmountDelegated == 0) { diff --git a/contracts/staking/test/core_test.ts b/contracts/staking/test/core_test.ts index e30b1b7432..d75643bace 100644 --- a/contracts/staking/test/core_test.ts +++ b/contracts/staking/test/core_test.ts @@ -1090,7 +1090,42 @@ describe('Staking Core', () => { ///// 8 CHECK PROFITS VIA STAKING CONTRACT ///// ///// 9 WITHDRAW PROFITS VIA STAKING CONTRACT ///// - ///// + ///// 10 CHECK DELEGATOR BY UNDELEGATING ///// + const ethBalancesByDelegatorInit = await Promise.all([ + stakingWrapper.getEthBalanceAsync(delegators[0]), + stakingWrapper.getEthBalanceAsync(delegators[1]), + stakingWrapper.getEthBalanceAsync(delegators[2]), + ]); + await Promise.all([ + stakingWrapper.deactivateAndTimelockDelegatedStakeAsync(delegators[0], poolIds[2], stakeByDelegator[0]), + stakingWrapper.deactivateAndTimelockDelegatedStakeAsync(delegators[1], poolIds[2], stakeByDelegator[1]), + stakingWrapper.deactivateAndTimelockDelegatedStakeAsync(delegators[2], poolIds[2], stakeByDelegator[2]), + ]); + const ethBalancesByDelegatorFinal = await Promise.all([ + stakingWrapper.getEthBalanceAsync(delegators[0]), + stakingWrapper.getEthBalanceAsync(delegators[1]), + stakingWrapper.getEthBalanceAsync(delegators[2]), + ]); + const rewardByDelegator = [ + ethBalancesByDelegatorFinal[0].minus(ethBalancesByDelegatorInit[0]), + ethBalancesByDelegatorFinal[1].minus(ethBalancesByDelegatorInit[1]), + ethBalancesByDelegatorFinal[2].minus(ethBalancesByDelegatorInit[2]), + ]; + + // note that these may be slightly off due to rounding down on each entry + // there is a carry over between calls, which we account for here. + // if the last person to leave rounded down, then there is some trace amount left in the pool. + // carry-over here is 00000000000000000002 + const expectedRewardByDelegator = [ + payoutByPoolOperator[2].times(stakeByDelegator[0]).dividedToIntegerBy(totalStakeByDelegators), + payoutByPoolOperator[2].times(stakeByDelegator[1]).dividedToIntegerBy(totalStakeByDelegators), + new BigNumber('13927564703644768540'), // computed by hand to account for carry-over + ]; + expect(rewardByDelegator[0]).to.be.bignumber.equal(expectedRewardByDelegator[0]); + 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 ///// }); }); }); diff --git a/contracts/staking/test/utils/staking_wrapper.ts b/contracts/staking/test/utils/staking_wrapper.ts index 07ff8292a8..3b6ecd5f1a 100644 --- a/contracts/staking/test/utils/staking_wrapper.ts +++ b/contracts/staking/test/utils/staking_wrapper.ts @@ -122,12 +122,10 @@ export class StakingWrapper { to: this.getStakingProxyContract().address, data: calldata, gas: 3000000, + gasPrice: 0, value } const txHash = await this._web3Wrapper.sendTransactionAsync(txData); - if (includeLogs) { - - } const txReceipt = await (includeLogs ? this._logDecoder.getTxWithDecodedLogsAsync(txHash) : this._web3Wrapper.awaitTransactionSuccessAsync(txHash)); return txReceipt; } @@ -141,6 +139,10 @@ export class StakingWrapper { const returnValue = await this._web3Wrapper.callAsync(txData); return returnValue; } + public async getEthBalanceAsync(owner: string): Promise { + const balance = this._web3Wrapper.getBalanceInWeiAsync(owner); + return balance; + } ///// STAKE ///// public async depositAsync(owner: string, amount: BigNumber): Promise { const calldata = this.getStakingContract().deposit.getABIEncodedTransactionData(amount); @@ -154,7 +156,8 @@ export class StakingWrapper { } public async depositAndDelegateAsync(owner: string, poolId: string, amount: BigNumber): Promise { const calldata = this.getStakingContract().depositAndDelegate.getABIEncodedTransactionData(poolId, amount); - const txReceipt = await this._executeTransactionAsync(calldata, owner); + const txReceipt = await this._executeTransactionAsync(calldata, owner);//, new BigNumber(0), true); + //console.log(JSON.stringify(txReceipt, null, 4)); return txReceipt; } public async activateStakeAsync(owner: string, amount: BigNumber): Promise { @@ -174,7 +177,8 @@ export class StakingWrapper { } public async deactivateAndTimelockDelegatedStakeAsync(owner: string, poolId: string, amount: BigNumber): Promise { const calldata = this.getStakingContract().deactivateAndTimelockDelegatedStake.getABIEncodedTransactionData(poolId, amount); - const txReceipt = await this._executeTransactionAsync(calldata, owner); + const txReceipt = await this._executeTransactionAsync(calldata, owner, new BigNumber(0), true); + console.log(JSON.stringify(txReceipt, null, 4)); return txReceipt; } public async withdrawAsync(owner: string, amount: BigNumber): Promise {