StakingEscrow: batch deposit with one sub-stake

pull/1764/head
vzotova 2020-03-09 15:48:53 +03:00
parent 272764e5dc
commit 3449c2f873
4 changed files with 197 additions and 13 deletions

View File

@ -34,7 +34,7 @@ contract WorkLockInterface {
/**
* @notice Contract holds and locks stakers tokens.
* Each staker that locks their tokens will receive some compensation
* @dev |v2.2.4|
* @dev |v2.3.1|
*/
contract StakingEscrow is Issuer {
using AdditionalMath for uint256;
@ -542,6 +542,49 @@ contract StakingEscrow is Issuer {
}
}
/**
* @notice Batch deposit. Allowed only initial deposit for each staker
* @param _stakers Stakers
* @param _values Amount of tokens to deposit for each staker
* @param _periods Amount of periods during which tokens will be locked for each staker
*/
function batchDeposit(
address[] calldata _stakers,
uint256[] calldata _values,
uint16[] calldata _periods
)
external
{
require(_stakers.length != 0 &&
_stakers.length == _values.length &&
_stakers.length == _periods.length);
uint16 previousPeriod = getCurrentPeriod() - 1;
uint16 nextPeriod = previousPeriod + 2;
uint256 sumValue = 0;
for (uint256 i = 0; i < _stakers.length; i++) {
address staker = _stakers[i];
uint256 value = _values[i];
uint16 periods = _periods[i];
StakerInfo storage info = stakerInfo[staker];
require(info.subStakes.length == 0 &&
value >= minAllowableLockedTokens &&
value <= maxAllowableLockedTokens &&
periods >= minLockedPeriods);
require(workerToStaker[staker] == address(0) || workerToStaker[staker] == info.worker,
"A staker can't be a worker for another staker");
stakers.push(staker);
policyManager.register(staker, previousPeriod);
info.value = value;
info.subStakes.push(SubStakeInfo(nextPeriod, 0, periods, value));
sumValue = sumValue.add(value);
emit Deposited(staker, value, periods);
emit Locked(staker, value, nextPeriod, periods);
}
token.safeTransferFrom(msg.sender, address(this), sumValue);
}
/**
* @notice Implementation of the receiveApproval(address,uint256,address,bytes) method
* (see NuCypherToken contract). Deposit all tokens that were approved to transfer

View File

@ -303,10 +303,33 @@ def test_all(testerchain,
tx = token.functions.approve(escrow.address, 10000).transact({'from': staker1})
testerchain.wait_for_receipt(tx)
# Staker can't deposit tokens before Escrow initialization
# Check that nothing is locked
assert 0 == escrow.functions.getLockedTokens(staker1, 0).call()
assert 0 == escrow.functions.getLockedTokens(staker2, 0).call()
assert 0 == escrow.functions.getLockedTokens(staker3, 0).call()
assert 0 == escrow.functions.getLockedTokens(staker4, 0).call()
# Deposit tokens for 1 staker
staker1_tokens = token_economics.minimum_allowed_locked
duration = token_economics.minimum_locked_periods
tx = token.functions.approve(escrow.address, 2 * staker1_tokens).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.batchDeposit([staker1], [staker1_tokens], [duration]).transact({'from': creator})
testerchain.wait_for_receipt(tx)
escrow_supply = token_economics.minimum_allowed_locked
assert token.functions.balanceOf(escrow.address).call() == escrow_supply
assert escrow.functions.getAllTokens(staker1).call() == staker1_tokens
assert escrow.functions.getLockedTokens(staker1, 0).call() == 0
assert escrow.functions.getLockedTokens(staker1, 1).call() == staker1_tokens
assert escrow.functions.getLockedTokens(staker1, duration).call() == staker1_tokens
assert escrow.functions.getLockedTokens(staker1, duration + 1).call() == 0
# Can't deposit tokens again for the same staker
with pytest.raises((TransactionFailed, ValueError)):
tx = escrow.functions.deposit(1, 1).transact({'from': staker1})
tx = escrow.functions.batchDeposit([staker1], [staker1_tokens], [duration]).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = token.functions.approve(escrow.address, 0).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Initialize worklock
worklock_supply = 3 * token_economics.minimum_allowed_locked + token_economics.maximum_allowed_locked
@ -443,7 +466,7 @@ def test_all(testerchain,
assert token.functions.balanceOf(worklock.address).call() == worklock_supply - staker2_tokens
tx = escrow.functions.setWorker(staker2).transact({'from': staker2})
testerchain.wait_for_receipt(tx)
escrow_supply = token_economics.erc20_reward_supply + staker2_tokens
escrow_supply += staker2_tokens
assert escrow.functions.getAllTokens(staker2).call() == staker2_tokens
assert escrow.functions.getCompletedWork(staker2).call() == 0
tx = escrow.functions.setWindDown(True).transact({'from': staker2})
@ -453,9 +476,10 @@ def test_all(testerchain,
tx = worklock.functions.claim().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.workInfo(staker1).call()[2]
staker1_tokens = worklock.functions.ethToTokens(2 * deposited_eth_2).call()
staker1_claims = worklock.functions.ethToTokens(2 * deposited_eth_2).call()
staker1_tokens += staker1_claims
assert escrow.functions.getLockedTokens(staker1, 1).call() == staker1_tokens
escrow_supply += staker1_tokens
escrow_supply += staker1_claims
# Staker prolongs lock duration
tx = escrow.functions.prolongStake(0, 3).transact({'from': staker2, 'gas_price': 0})
@ -534,10 +558,6 @@ def test_all(testerchain,
testerchain.wait_for_receipt(tx)
# Check that nothing is locked
assert 0 == escrow.functions.getLockedTokens(staker1, 0).call()
assert 0 == escrow.functions.getLockedTokens(staker2, 0).call()
assert 0 == escrow.functions.getLockedTokens(staker3, 0).call()
assert 0 == escrow.functions.getLockedTokens(staker4, 0).call()
assert 0 == escrow.functions.getLockedTokens(preallocation_escrow_1.address, 0).call()
assert 0 == escrow.functions.getLockedTokens(preallocation_escrow_2.address, 0).call()
assert 0 == escrow.functions.getLockedTokens(contracts_owners[0], 0).call()
@ -572,6 +592,7 @@ def test_all(testerchain,
tx = escrow.functions.initialize(token_economics.erc20_reward_supply) \
.buildTransaction({'from': multisig.address, 'gasPrice': 0})
execute_multisig_transaction(testerchain, multisig, [contracts_owners[0], contracts_owners[1]], tx)
escrow_supply += token_economics.erc20_reward_supply
# Grant access to transfer tokens
tx = token.functions.approve(escrow.address, 10000).transact({'from': creator})
@ -590,12 +611,11 @@ def test_all(testerchain,
tx = escrow.functions.confirmActivity().transact({'from': staker1})
testerchain.wait_for_receipt(tx)
escrow_supply += 1000
first_sub_stake = staker1_tokens
second_sub_stake = 1000
staker1_tokens += second_sub_stake
assert token.functions.balanceOf(escrow.address).call() == escrow_supply
assert token.functions.balanceOf(staker1).call() == 9000
assert escrow.functions.getLockedTokens(staker1, 0).call() == 0
assert escrow.functions.getLockedTokens(staker1, 0).call() == token_economics.minimum_allowed_locked
assert escrow.functions.getLockedTokens(staker1, 1).call() == staker1_tokens
assert escrow.functions.getLockedTokens(staker1, token_economics.minimum_locked_periods).call() == staker1_tokens
assert escrow.functions.getLockedTokens(staker1, token_economics.minimum_locked_periods + 1).call() == second_sub_stake
@ -649,7 +669,7 @@ def test_all(testerchain,
# Divide stakes
tx = escrow.functions.divideStake(0, 500, 6).transact({'from': staker2})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.divideStake(1, 500, 9).transact({'from': staker1})
tx = escrow.functions.divideStake(2, 500, 9).transact({'from': staker1})
testerchain.wait_for_receipt(tx)
tx = preallocation_escrow_interface_1.functions.divideStake(0, 500, 6).transact({'from': staker3})
testerchain.wait_for_receipt(tx)

View File

@ -19,6 +19,7 @@ along with nucypher. If not, see <https://www.gnu.org/licenses/>.
import pytest
from eth_tester.exceptions import TransactionFailed
from eth_utils import to_checksum_address
from web3.contract import Contract
MAX_SUB_STAKES = 30
MAX_UINT16 = 65535
@ -712,3 +713,113 @@ def test_allowable_locked_tokens(testerchain, token_economics, token, escrow_con
staker1_lock += minimum_allowed
tx = escrow.functions.lock(maximum_allowed - staker1_lock, duration).transact({'from': staker1})
testerchain.wait_for_receipt(tx)
@pytest.mark.slow
def test_batch_deposit(testerchain, token, escrow_contract, deploy_contract):
escrow = escrow_contract(1500)
policy_manager_interface = testerchain.get_contract_factory('PolicyManagerForStakingEscrowMock')
policy_manager = testerchain.client.get_contract(
abi=policy_manager_interface.abi,
address=escrow.functions.policyManager().call(),
ContractFactoryClass=Contract)
creator = testerchain.client.accounts[0]
deposit_log = escrow.events.Deposited.createFilter(fromBlock='latest')
lock_log = escrow.events.Locked.createFilter(fromBlock='latest')
# Grant access to transfer tokens
tx = token.functions.approve(escrow.address, 10000).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Deposit tokens for 1 staker
staker = testerchain.client.accounts[1]
tx = escrow.functions.batchDeposit([staker], [1000], [10]).transact({'from': creator})
testerchain.wait_for_receipt(tx)
assert token.functions.balanceOf(escrow.address).call() == 1000
assert escrow.functions.getAllTokens(staker).call() == 1000
assert escrow.functions.getLockedTokens(staker, 0).call() == 0
assert escrow.functions.getLockedTokens(staker, 1).call() == 1000
assert escrow.functions.getLockedTokens(staker, 10).call() == 1000
assert escrow.functions.getLockedTokens(staker, 11).call() == 0
current_period = escrow.functions.getCurrentPeriod().call()
assert policy_manager.functions.getPeriodsLength(staker).call() == 1
assert policy_manager.functions.getPeriod(staker, 0).call() == current_period - 1
assert escrow.functions.getPastDowntimeLength(staker).call() == 0
assert escrow.functions.getLastActivePeriod(staker).call() == 0
deposit_events = deposit_log.get_all_entries()
assert len(deposit_events) == 1
event_args = deposit_events[-1]['args']
assert event_args['staker'] == staker
assert event_args['value'] == 1000
assert event_args['periods'] == 10
lock_events = lock_log.get_all_entries()
assert len(lock_events) == 1
event_args = lock_events[-1]['args']
assert event_args['staker'] == staker
assert event_args['value'] == 1000
assert event_args['firstPeriod'] == current_period + 1
assert event_args['periods'] == 10
# Can't deposit tokens again for the same staker twice
with pytest.raises((TransactionFailed, ValueError)):
tx = escrow.functions.batchDeposit([testerchain.client.accounts[1]], [1000], [10])\
.transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Can't deposit tokens with too low or too high value
with pytest.raises((TransactionFailed, ValueError)):
tx = escrow.functions.batchDeposit([testerchain.client.accounts[2]], [1], [10])\
.transact({'from': creator})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = escrow.functions.batchDeposit([testerchain.client.accounts[2]], [1501], [10])\
.transact({'from': creator})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = escrow.functions.batchDeposit([testerchain.client.accounts[2]], [500], [1])\
.transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Initialize Escrow contract
tx = escrow.functions.initialize(0).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Deposit tokens for multiple stakers
stakers = testerchain.client.accounts[2:7]
tx = escrow.functions.batchDeposit(
stakers, [100, 200, 300, 400, 500], [50, 100, 150, 200, 250]).transact({'from': creator})
testerchain.wait_for_receipt(tx)
assert 2500 == token.functions.balanceOf(escrow.address).call()
current_period = escrow.functions.getCurrentPeriod().call()
deposit_events = deposit_log.get_all_entries()
lock_events = lock_log.get_all_entries()
assert len(deposit_events) == 6
assert len(lock_events) == 6
for index, staker in enumerate(stakers):
value = 100 * (index + 1)
duration = 50 * (index + 1)
assert escrow.functions.getAllTokens(staker).call() == value
assert escrow.functions.getLockedTokens(staker, 1).call() == value
assert escrow.functions.getLockedTokens(staker, duration).call() == value
assert escrow.functions.getLockedTokens(staker, duration + 1).call() == 0
assert policy_manager.functions.getPeriodsLength(staker).call() == 1
assert policy_manager.functions.getPeriod(staker, 0).call() == current_period - 1
assert escrow.functions.getPastDowntimeLength(staker).call() == 0
assert escrow.functions.getLastActivePeriod(staker).call() == 0
event_args = deposit_events[index + 1]['args']
assert event_args['staker'] == staker
assert event_args['value'] == value
assert event_args['periods'] == duration
event_args = lock_events[index + 1]['args']
assert event_args['staker'] == staker
assert event_args['value'] == value
assert event_args['firstPeriod'] == current_period + 1
assert event_args['periods'] == duration

View File

@ -206,6 +206,16 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None:
transact(token_functions.transfer(ursula2, MIN_ALLOWED_LOCKED * 10), {'from': origin})
transact(token_functions.transfer(ursula3, MIN_ALLOWED_LOCKED * 10), {'from': origin})
#
# Batch deposit tokens
#
transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 5), {'from': origin})
transact_and_log("Batch deposit tokens for 5 owners",
staker_functions.batchDeposit(everyone_else[0:5],
[MIN_ALLOWED_LOCKED] * 5,
[MIN_LOCKED_PERIODS] * 5),
{'from': origin})
#
# Ursula and Alice give Escrow rights to transfer
#