mirror of https://github.com/nucypher/nucypher.git
Merge pull request #2596 from vzotova/pool-refinements
Refinements for pool staking contractpull/2631/head
commit
c817b619a5
|
@ -0,0 +1 @@
|
|||
Refinements for pool staking contract
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue