Merge pull request #2596 from vzotova/pool-refinements

Refinements for pool staking contract
pull/2631/head
Victoria 2021-04-07 17:39:44 +03:00 committed by GitHub
commit c817b619a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 188 additions and 88 deletions

View File

@ -0,0 +1 @@
Refinements for pool staking contract

View File

@ -25,7 +25,7 @@ contract PoolingStakingContractV2 is InitializableStakingContract, Ownable {
uint256 depositedTokens
);
event ETHWithdrawn(address indexed sender, uint256 value);
event DepositSet(address indexed sender, bool value);
event WorkerOwnerSet(address indexed sender, address indexed workerOwner);
struct Delegator {
uint256 depositedTokens;
@ -33,8 +33,11 @@ contract PoolingStakingContractV2 is InitializableStakingContract, Ownable {
uint256 withdrawnETH;
}
/// Defines base fraction and precision of worker fraction. Value 100 defines 1 worker fraction is 1% of reward
uint256 public constant BASIS_FRACTION = 100;
/**
* Defines base fraction and precision of worker fraction.
* E.g., for a value of 10000, a worker fraction of 100 represents 1% of reward (100/10000)
*/
uint256 public constant BASIS_FRACTION = 10000;
StakingEscrow public escrow;
address public workerOwner;
@ -47,12 +50,12 @@ contract PoolingStakingContractV2 is InitializableStakingContract, Ownable {
uint256 public workerWithdrawnReward;
mapping(address => Delegator) public delegators;
bool public depositIsEnabled = true;
/**
* @notice Initialize function for using with OpenZeppelin proxy
* @param _workerFraction Share of token reward that worker node owner will get.
* Use value up to BASIS_FRACTION, if _workerFraction = BASIS_FRACTION -> means 100% reward as commission
* Use value up to BASIS_FRACTION (10000), if _workerFraction = BASIS_FRACTION -> means 100% reward as commission.
* For example, 100 worker fraction is 1% of reward
* @param _router StakingInterfaceRouter address
* @param _workerOwner Owner of worker node, only this address can withdraw worker commission
*/
@ -60,35 +63,48 @@ contract PoolingStakingContractV2 is InitializableStakingContract, Ownable {
uint256 _workerFraction,
StakingInterfaceRouter _router,
address _workerOwner
) public initializer {
) external initializer {
require(_workerOwner != address(0) && _workerFraction <= BASIS_FRACTION);
InitializableStakingContract.initialize(_router);
_transferOwnership(msg.sender);
escrow = _router.target().escrow();
workerFraction = _workerFraction;
workerOwner = _workerOwner;
emit WorkerOwnerSet(msg.sender, _workerOwner);
}
/**
* @notice Enabled deposit
* @notice withdrawAll() is allowed
*/
function enableDeposit() external onlyOwner {
depositIsEnabled = true;
emit DepositSet(msg.sender, depositIsEnabled);
function isWithdrawAllAllowed() public view returns (bool) {
// no tokens in StakingEscrow contract which belong to pool
return escrow.getAllTokens(address(this)) == 0;
}
/**
* @notice Disable deposit
* @notice deposit() is allowed
*/
function disableDeposit() external onlyOwner {
depositIsEnabled = false;
emit DepositSet(msg.sender, depositIsEnabled);
function isDepositAllowed() public view returns (bool) {
// tokens which directly belong to pool
uint256 freeTokens = token.balanceOf(address(this));
// no sub-stakes and no earned reward
return isWithdrawAllAllowed() && freeTokens == totalDepositedTokens;
}
/**
* @notice Set worker owner address
*/
function setWorkerOwner(address _workerOwner) external onlyOwner {
workerOwner = _workerOwner;
emit WorkerOwnerSet(msg.sender, _workerOwner);
}
/**
* @notice Calculate worker's fraction depending on deposited tokens
* Override to implement dynamic worker fraction.
*/
function getWorkerFraction() public view returns (uint256) {
function getWorkerFraction() public view virtual returns (uint256) {
return workerFraction;
}
@ -97,7 +113,7 @@ contract PoolingStakingContractV2 is InitializableStakingContract, Ownable {
* @param _value Amount of tokens to transfer
*/
function depositTokens(uint256 _value) external {
require(depositIsEnabled, "Deposit must be enabled");
require(isDepositAllowed(), "Deposit must be enabled");
require(_value > 0, "Value must be not empty");
totalDepositedTokens = totalDepositedTokens.add(_value);
Delegator storage delegator = delegators[msg.sender];
@ -110,9 +126,13 @@ contract PoolingStakingContractV2 is InitializableStakingContract, Ownable {
* @notice Get available reward for all delegators and owner
*/
function getAvailableReward() public view returns (uint256) {
// locked + unlocked tokens in StakingEscrow contract which belong to pool
uint256 stakedTokens = escrow.getAllTokens(address(this));
// tokens which directly belong to pool
uint256 freeTokens = token.balanceOf(address(this));
// tokens in excess of the initially deposited
uint256 reward = stakedTokens.add(freeTokens).sub(totalDepositedTokens);
// check how many of reward tokens belong directly to pool
if (reward > freeTokens) {
return freeTokens;
}
@ -120,7 +140,8 @@ contract PoolingStakingContractV2 is InitializableStakingContract, Ownable {
}
/**
* @notice Get cumulative reward
* @notice Get cumulative reward.
* Available and withdrawn reward together to use in delegator/owner reward calculations
*/
function getCumulativeReward() public view returns (uint256) {
return getAvailableReward().add(totalWithdrawnReward);
@ -130,16 +151,21 @@ contract PoolingStakingContractV2 is InitializableStakingContract, Ownable {
* @notice Get available reward in tokens for worker node owner
*/
function getAvailableWorkerReward() public view returns (uint256) {
// total current and historical reward
uint256 reward = getCumulativeReward();
// calculate total reward for worker including historical reward
uint256 maxAllowableReward;
// usual case
if (totalDepositedTokens != 0) {
uint256 fraction = getWorkerFraction();
maxAllowableReward = reward.mul(fraction).div(BASIS_FRACTION);
// special case when there are no delegators
} else {
maxAllowableReward = reward;
}
// check that worker has any new reward
if (maxAllowableReward > workerWithdrawnReward) {
return maxAllowableReward - workerWithdrawnReward;
}
@ -149,26 +175,28 @@ contract PoolingStakingContractV2 is InitializableStakingContract, Ownable {
/**
* @notice Get available reward in tokens for delegator
*/
function getAvailableReward(address _delegator)
public
view
returns (uint256)
{
function getAvailableDelegatorReward(address _delegator) public view returns (uint256) {
// special case when there are no delegators
if (totalDepositedTokens == 0) {
return 0;
}
// total current and historical reward
uint256 reward = getCumulativeReward();
Delegator storage delegator = delegators[_delegator];
uint256 fraction = getWorkerFraction();
// calculate total reward for delegator including historical reward
// excluding worker share
uint256 maxAllowableReward = reward.mul(delegator.depositedTokens).mul(BASIS_FRACTION - fraction).div(
totalDepositedTokens.mul(BASIS_FRACTION)
);
return
maxAllowableReward > delegator.withdrawnReward
? maxAllowableReward - delegator.withdrawnReward
: 0;
// check that worker has any new reward
if (maxAllowableReward > delegator.withdrawnReward) {
return maxAllowableReward - delegator.withdrawnReward;
}
return 0;
}
/**
@ -202,7 +230,7 @@ contract PoolingStakingContractV2 is InitializableStakingContract, Ownable {
require(_value <= balance, "Not enough tokens in the contract");
Delegator storage delegator = delegators[msg.sender];
uint256 availableReward = getAvailableReward(msg.sender);
uint256 availableReward = getAvailableDelegatorReward(msg.sender);
require( _value <= availableReward, "Requested amount of tokens exceeded allowed portion");
delegator.withdrawnReward = delegator.withdrawnReward.add(_value);
@ -216,18 +244,19 @@ contract PoolingStakingContractV2 is InitializableStakingContract, Ownable {
* @notice Withdraw reward, deposit and fee to delegator
*/
function withdrawAll() public {
require(isWithdrawAllAllowed(), "Withdraw deposit and reward must be enabled");
uint256 balance = token.balanceOf(address(this));
Delegator storage delegator = delegators[msg.sender];
uint256 availableReward = getAvailableReward(msg.sender);
uint256 availableReward = getAvailableDelegatorReward(msg.sender);
uint256 value = availableReward.add(delegator.depositedTokens);
require(value <= balance, "Not enough tokens in the contract");
// TODO remove double reading
// TODO remove double reading: availableReward and availableWorkerReward use same calls to external contracts
uint256 availableWorkerReward = getAvailableWorkerReward();
// potentially could be less then due reward
uint256 availableETH = getAvailableETH(msg.sender);
uint256 availableETH = getAvailableDelegatorETH(msg.sender);
// prevent losing reward for worker after calculations
uint256 workerReward = availableWorkerReward.mul(delegator.depositedTokens).div(totalDepositedTokens);
@ -252,15 +281,15 @@ contract PoolingStakingContractV2 is InitializableStakingContract, Ownable {
totalWithdrawnETH = totalWithdrawnETH.sub(delegator.withdrawnETH);
delegator.withdrawnETH = 0;
if (availableETH > 0) {
msg.sender.sendValue(availableETH);
emit ETHWithdrawn(msg.sender, availableETH);
msg.sender.sendValue(availableETH);
}
}
/**
* @notice Get available ether for delegator
*/
function getAvailableETH(address _delegator) public view returns (uint256) {
function getAvailableDelegatorETH(address _delegator) public view returns (uint256) {
Delegator storage delegator = delegators[_delegator];
uint256 balance = address(this).balance;
// ETH balance + already withdrawn
@ -279,13 +308,13 @@ contract PoolingStakingContractV2 is InitializableStakingContract, Ownable {
*/
function withdrawETH() public override {
Delegator storage delegator = delegators[msg.sender];
uint256 availableETH = getAvailableETH(msg.sender);
uint256 availableETH = getAvailableDelegatorETH(msg.sender);
require(availableETH > 0, "There is no available ETH to withdraw");
delegator.withdrawnETH = delegator.withdrawnETH.add(availableETH);
totalWithdrawnETH = totalWithdrawnETH.add(availableETH);
msg.sender.sendValue(availableETH);
emit ETHWithdrawn(msg.sender, availableETH);
msg.sender.sendValue(availableETH);
}
/**

View File

@ -24,8 +24,8 @@ from web3.contract import Contract
from nucypher.blockchain.eth.token import NU
WORKER_FRACTION = 10
BASIS_FRACTION = 100
WORKER_FRACTION = 1000
BASIS_FRACTION = 10000
@pytest.fixture()
@ -56,6 +56,7 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
delegators = testerchain.client.accounts[3:6]
deposit_log = pooling_contract.events.TokensDeposited.createFilter(fromBlock='latest')
withdraw_log = pooling_contract.events.TokensWithdrawn.createFilter(fromBlock='latest')
workers_log = pooling_contract.events.WorkerOwnerSet.createFilter(fromBlock=0)
assert pooling_contract.functions.owner().call() == owner
assert pooling_contract.functions.workerOwner().call() == worker_owner
@ -66,6 +67,8 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
assert token.functions.balanceOf(pooling_contract.address).call() == 0
assert pooling_contract.functions.getAvailableWorkerReward().call() == 0
assert pooling_contract.functions.getAvailableReward().call() == 0
assert pooling_contract.functions.isDepositAllowed().call()
assert pooling_contract.functions.isWithdrawAllAllowed().call()
# Give some tokens to delegators
for index, delegator in enumerate(delegators):
@ -79,14 +82,14 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
assert pooling_contract.functions.getAvailableReward().call() == 0
for index, delegator in enumerate(delegators):
assert pooling_contract.functions.delegators(delegator).call() == [0, 0, 0]
assert pooling_contract.functions.getAvailableReward(delegator).call() == 0
assert pooling_contract.functions.getAvailableDelegatorReward(delegator).call() == 0
tokens = token.functions.balanceOf(delegator).call() // 2
tx = token.functions.approve(pooling_contract.address, tokens).transact({'from': delegator})
testerchain.wait_for_receipt(tx)
tx = pooling_contract.functions.depositTokens(tokens).transact({'from': delegator})
testerchain.wait_for_receipt(tx)
assert pooling_contract.functions.delegators(delegator).call() == [tokens, 0, 0]
assert pooling_contract.functions.getAvailableReward(delegator).call() == 0
assert pooling_contract.functions.getAvailableDelegatorReward(delegator).call() == 0
total_deposited_tokens += tokens
tokens_supply += tokens
assert pooling_contract.functions.totalDepositedTokens().call() == total_deposited_tokens
@ -104,42 +107,22 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
assert pooling_contract.functions.totalWithdrawnReward().call() == 0
assert pooling_contract.functions.getAvailableWorkerReward().call() == 0
assert pooling_contract.functions.getAvailableReward().call() == 0
assert pooling_contract.functions.isDepositAllowed().call()
assert pooling_contract.functions.isWithdrawAllAllowed().call()
# Disable deposit
log = pooling_contract.events.DepositSet.createFilter(fromBlock='latest')
with pytest.raises((TransactionFailed, ValueError)):
tx = pooling_contract.functions.disableDeposit().transact({'from': delegators[0]})
testerchain.wait_for_receipt(tx)
tx = pooling_contract.functions.disableDeposit().transact({'from': owner})
testerchain.wait_for_receipt(tx)
events = log.get_all_entries()
assert len(events) == 1
event_args = events[-1]['args']
assert event_args['sender'] == owner
assert not event_args['value']
# Delegator can cancel deposit before stake will be created
delegator = delegators[0]
tokens = token.functions.balanceOf(delegator).call()
tx = pooling_contract.functions.withdrawAll().transact({'from': delegator})
testerchain.wait_for_receipt(tx)
assert token.functions.balanceOf(delegator).call() == 2 * tokens
assert pooling_contract.functions.delegators(delegator).call() == [0, 0, 0]
# Return back
tx = token.functions.approve(pooling_contract.address, tokens).transact({'from': delegator})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = pooling_contract.functions.depositTokens(tokens).transact({'from': delegator})
testerchain.wait_for_receipt(tx)
tx = token.functions.approve(pooling_contract.address, 0).transact({'from': delegator})
tx = pooling_contract.functions.depositTokens(tokens).transact({'from': delegator})
testerchain.wait_for_receipt(tx)
# Enable deposit
with pytest.raises((TransactionFailed, ValueError)):
tx = pooling_contract.functions.enableDeposit().transact({'from': delegators[0]})
testerchain.wait_for_receipt(tx)
tx = pooling_contract.functions.enableDeposit().transact({'from': owner})
testerchain.wait_for_receipt(tx)
events = log.get_all_entries()
assert len(events) == 2
event_args = events[-1]['args']
assert event_args['sender'] == owner
assert event_args['value']
# Delegators deposit tokens to the pooling contract again
for index, delegator in enumerate(delegators):
tokens = token.functions.balanceOf(delegator).call()
@ -154,7 +137,7 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
assert token.functions.balanceOf(pooling_contract.address).call() == tokens_supply
events = deposit_log.get_all_entries()
assert len(events) == len(delegators) + index + 1
assert len(events) == len(delegators) + index + 2
event_args = events[-1]['args']
assert event_args['sender'] == delegator
assert event_args['value'] == tokens
@ -172,6 +155,18 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
testerchain.wait_for_receipt(tx)
assert pooling_contract.functions.totalDepositedTokens().call() == total_deposited_tokens
assert token.functions.balanceOf(pooling_contract.address).call() == 0
assert not pooling_contract.functions.isDepositAllowed().call()
assert not pooling_contract.functions.isWithdrawAllAllowed().call()
# Can't deposit after stake was created
tokens = token_economics.minimum_allowed_locked
tx = token.functions.approve(pooling_contract.address, tokens).transact({'from': creator})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = pooling_contract.functions.depositTokens(tokens).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = token.functions.approve(pooling_contract.address, 0).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Give some tokens as a reward
assert pooling_contract.functions.getAvailableReward().call() == 0
@ -180,23 +175,32 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
testerchain.wait_for_receipt(tx)
tx = escrow.functions.deposit(pooling_contract.address, reward, 0).transact()
testerchain.wait_for_receipt(tx)
assert not pooling_contract.functions.isDepositAllowed().call()
assert not pooling_contract.functions.isWithdrawAllAllowed().call()
# Only owner can withdraw tokens from the staking escrow
with pytest.raises((TransactionFailed, ValueError)):
tx = pooling_contract_interface.functions.withdrawAsStaker(reward).transact({'from': delegators[0]})
testerchain.wait_for_receipt(tx)
withdrawn_stake = reward + stake
assert pooling_contract.functions.getAvailableReward().call() == 0
tx = pooling_contract_interface.functions.withdrawAsStaker(withdrawn_stake).transact({'from': owner})
tx = pooling_contract_interface.functions.withdrawAsStaker(reward).transact({'from': owner})
testerchain.wait_for_receipt(tx)
assert pooling_contract.functions.getAvailableReward().call() == reward
worker_reward = reward * WORKER_FRACTION // BASIS_FRACTION
assert pooling_contract.functions.getAvailableWorkerReward().call() == worker_reward
assert pooling_contract.functions.totalDepositedTokens().call() == total_deposited_tokens
assert token.functions.balanceOf(pooling_contract.address).call() == withdrawn_stake
tokens_supply = withdrawn_stake
assert token.functions.balanceOf(pooling_contract.address).call() == reward
tokens_supply = reward
total_withdrawn_tokens = 0
assert not pooling_contract.functions.isDepositAllowed().call()
assert not pooling_contract.functions.isWithdrawAllAllowed().call()
# Can't withdraw initial deposit before stake will be unlocked
for delegator in delegators:
with pytest.raises((TransactionFailed, ValueError)):
tx = pooling_contract.functions.withdrawAll().transact({'from': delegator})
testerchain.wait_for_receipt(tx)
# Each delegator can withdraw some portion of tokens
available_reward = reward
@ -211,11 +215,11 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
testerchain.wait_for_receipt(tx)
portion = max_portion // 2
assert pooling_contract.functions.getAvailableReward(delegator).call() == max_portion
assert pooling_contract.functions.getAvailableDelegatorReward(delegator).call() == max_portion
tx = pooling_contract.functions.withdrawTokens(portion).transact({'from': delegator})
testerchain.wait_for_receipt(tx)
assert pooling_contract.functions.delegators(delegator).call() == [deposited_tokens, portion, 0]
assert pooling_contract.functions.getAvailableReward(delegator).call() == max_portion - portion
assert pooling_contract.functions.getAvailableDelegatorReward(delegator).call() == max_portion - portion
tokens_supply -= portion
total_withdrawn_tokens += portion
available_reward -= portion
@ -225,7 +229,7 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
assert pooling_contract.functions.totalWithdrawnReward().call() == total_withdrawn_tokens
events = withdraw_log.get_all_entries()
assert len(events) == index + 1
assert len(events) == index + 2
event_args = events[-1]['args']
assert event_args['sender'] == delegator
assert event_args['value'] == portion
@ -258,7 +262,7 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
assert pooling_contract.functions.getAvailableReward().call() == available_reward - worker_reward
events = withdraw_log.get_all_entries()
assert len(events) == len(delegators) + 1
assert len(events) == len(delegators) + 2
event_args = events[-1]['args']
assert event_args['sender'] == worker_owner
assert event_args['value'] == worker_reward
@ -269,6 +273,16 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
tx = pooling_contract.functions.withdrawWorkerReward().transact({'from': worker_owner})
testerchain.wait_for_receipt(tx)
# Withdraw stake
tx = pooling_contract_interface.functions.withdrawAsStaker(stake - 1).transact({'from': owner})
testerchain.wait_for_receipt(tx)
assert not pooling_contract.functions.isWithdrawAllAllowed().call()
tx = pooling_contract_interface.functions.withdrawAsStaker(1).transact({'from': owner})
testerchain.wait_for_receipt(tx)
tokens_supply += stake
assert not pooling_contract.functions.isDepositAllowed().call()
assert pooling_contract.functions.isWithdrawAllAllowed().call()
# Each delegator can withdraw rest of reward and deposit
previous_total_deposited_tokens = total_deposited_tokens
withdrawn_worker_reward = worker_reward
@ -281,7 +295,7 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
max_portion = reward * deposited_tokens * (BASIS_FRACTION - WORKER_FRACTION) // \
(previous_total_deposited_tokens * BASIS_FRACTION)
supposed_portion = max_portion // 2
reward_portion = pooling_contract.functions.getAvailableReward(delegator).call()
reward_portion = pooling_contract.functions.getAvailableDelegatorReward(delegator).call()
# could be some rounding errors
assert abs(supposed_portion - reward_portion) <= 10
@ -296,7 +310,7 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
assert token.functions.balanceOf(pooling_contract.address).call() == tokens_supply
assert token.functions.balanceOf(delegator).call() == previous_portion + new_portion
assert token.functions.balanceOf(worker_owner).call() == worker_reward
assert pooling_contract.functions.getAvailableReward(delegator).call() == 0
assert pooling_contract.functions.getAvailableDelegatorReward(delegator).call() == 0
withdraw_to_decrease = withdrawn_worker_reward * deposited_tokens // previous_total_deposited_tokens
total_withdrawn_tokens -= withdraw_to_decrease
@ -306,12 +320,15 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
assert abs(pooling_contract.functions.workerWithdrawnReward().call() - withdrawn_worker_reward) <= 1
events = withdraw_log.get_all_entries()
assert len(events) == len(delegators) + 2
assert len(events) == len(delegators) + 3
event_args = events[-1]['args']
assert event_args['sender'] == delegator
assert event_args['value'] == new_portion
assert event_args['depositedTokens'] == 0
assert not pooling_contract.functions.isDepositAllowed().call()
assert pooling_contract.functions.isWithdrawAllAllowed().call()
# Check worker's reward, still zero
assert pooling_contract.functions.getAvailableWorkerReward().call() == 0
@ -321,7 +338,7 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
max_portion = reward * deposited_tokens * (BASIS_FRACTION - WORKER_FRACTION) // \
(previous_total_deposited_tokens * BASIS_FRACTION)
supposed_portion = max_portion // 2
reward_portion = pooling_contract.functions.getAvailableReward(delegator).call()
reward_portion = pooling_contract.functions.getAvailableDelegatorReward(delegator).call()
# could be some rounding errors
assert abs(supposed_portion - reward_portion) <= 10
@ -340,8 +357,8 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
delegator = delegators[1]
deposited_tokens = pooling_contract.functions.delegators(delegator).call()[0]
withdrawn_tokens = pooling_contract.functions.delegators(delegator).call()[1]
reward_portion = pooling_contract.functions.getAvailableReward(delegator).call()
other_reward_portion = pooling_contract.functions.getAvailableReward(delegators[2]).call()
reward_portion = pooling_contract.functions.getAvailableDelegatorReward(delegator).call()
other_reward_portion = pooling_contract.functions.getAvailableDelegatorReward(delegators[2]).call()
new_portion = deposited_tokens + reward_portion
previous_portion = token.functions.balanceOf(delegator).call()
@ -356,7 +373,7 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
assert token.functions.balanceOf(pooling_contract.address).call() == tokens_supply
assert token.functions.balanceOf(delegator).call() == previous_portion + new_portion
assert token.functions.balanceOf(worker_owner).call() == worker_reward + new_worker_transfer
assert pooling_contract.functions.getAvailableReward(delegator).call() == 0
assert pooling_contract.functions.getAvailableDelegatorReward(delegator).call() == 0
withdraw_to_decrease = withdrawn_worker_reward * deposited_tokens // previous_total_deposited_tokens
total_withdrawn_tokens -= withdraw_to_decrease
@ -366,7 +383,7 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
assert abs(pooling_contract.functions.workerWithdrawnReward().call() - withdrawn_worker_reward) <= 1
events = withdraw_log.get_all_entries()
assert len(events) == len(delegators) + 4
assert len(events) == len(delegators) + 5
event_args = events[-2]['args']
assert event_args['sender'] == worker_owner
assert event_args['value'] == new_worker_transfer
@ -382,12 +399,15 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
assert pooling_contract.functions.getAvailableWorkerReward().call() == new_worker_reward
# Check others rewards
assert abs(pooling_contract.functions.getAvailableReward(delegators[2]).call() - other_reward_portion) <= 10
assert abs(pooling_contract.functions.getAvailableDelegatorReward(delegators[2]).call() - other_reward_portion) <= 10
assert not pooling_contract.functions.isDepositAllowed().call()
assert pooling_contract.functions.isWithdrawAllAllowed().call()
# Withdraw last portion for last delegator
delegator = delegators[2]
deposited_tokens = pooling_contract.functions.delegators(delegator).call()[0]
reward_portion = pooling_contract.functions.getAvailableReward(delegator).call()
reward_portion = pooling_contract.functions.getAvailableDelegatorReward(delegator).call()
new_portion = deposited_tokens + reward_portion
previous_portion = token.functions.balanceOf(delegator).call()
@ -401,7 +421,7 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
assert pooling_contract.functions.getAvailableReward().call() <= 1
events = withdraw_log.get_all_entries()
assert len(events) == len(delegators) + 6
assert len(events) == len(delegators) + 7
event_args = events[-2]['args']
assert event_args['sender'] == worker_owner
assert event_args['value'] == new_worker_reward
@ -412,6 +432,56 @@ def test_staking(testerchain, token_economics, token, escrow, pooling_contract,
assert event_args['value'] == new_portion
assert event_args['depositedTokens'] == 0
assert not pooling_contract.functions.isDepositAllowed().call()
assert pooling_contract.functions.isWithdrawAllAllowed().call()
#
# Change worker owner
#
# Prepare reward
tx = token.functions.transfer(pooling_contract.address, new_reward).transact({'from': creator})
testerchain.wait_for_receipt(tx)
assert not pooling_contract.functions.isDepositAllowed().call()
assert pooling_contract.functions.isWithdrawAllAllowed().call()
# Only pool owner can change value
new_worker_owner = delegators[0]
with pytest.raises((TransactionFailed, ValueError)):
tx = pooling_contract.functions.setWorkerOwner(new_worker_owner).transact({'from': worker_owner})
testerchain.wait_for_receipt(tx)
tx = pooling_contract.functions.setWorkerOwner(new_worker_owner).transact({'from': owner})
testerchain.wait_for_receipt(tx)
# Now old worker owner can't withdraw reward
with pytest.raises((TransactionFailed, ValueError)):
tx = pooling_contract.functions.withdrawWorkerReward().transact({'from': worker_owner})
testerchain.wait_for_receipt(tx)
# But new can
tx = pooling_contract.functions.withdrawWorkerReward().transact({'from': new_worker_owner})
testerchain.wait_for_receipt(tx)
events = workers_log.get_all_entries()
assert len(events) == 2
event_args = events[0]['args']
assert event_args['sender'] == owner
assert event_args['workerOwner'] == worker_owner
event_args = events[1]['args']
assert event_args['sender'] == owner
assert event_args['workerOwner'] == new_worker_owner
# There are no reward or locked tokens, deposit is allowed again
assert pooling_contract.functions.isDepositAllowed().call()
assert pooling_contract.functions.isWithdrawAllAllowed().call()
delegator = delegators[0]
tokens = token_economics.minimum_allowed_locked
tx = token.functions.approve(pooling_contract.address, tokens).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = pooling_contract.functions.depositTokens(tokens).transact({'from': creator})
testerchain.wait_for_receipt(tx)
def test_fee(testerchain, token_economics, token, policy_manager, pooling_contract, pooling_contract_interface):
creator = testerchain.client.accounts[0]
@ -463,7 +533,7 @@ def test_fee(testerchain, token_economics, token, policy_manager, pooling_contra
deposited_tokens = pooling_contract.functions.delegators(delegator).call()[0]
max_portion = value * deposited_tokens // total_deposited_tokens
balance = testerchain.client.get_balance(delegator)
assert pooling_contract.functions.getAvailableETH(delegator).call() == max_portion
assert pooling_contract.functions.getAvailableDelegatorETH(delegator).call() == max_portion
tx = pooling_contract.functions.withdrawETH().transact({'from': delegator, 'gas_price': 0})
testerchain.wait_for_receipt(tx)