Merge pull request #1562 from vzotova/removing-pe

Removing PE from WorkLock
pull/1572/head
K Prasch 2020-01-14 11:10:08 -08:00 committed by GitHub
commit 7a60009d63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 356 additions and 451 deletions

View File

@ -6,8 +6,6 @@ import "zeppelin/token/ERC20/SafeERC20.sol";
import "zeppelin/utils/Address.sol";
import "contracts/NuCypherToken.sol";
import "contracts/StakingEscrow.sol";
import "contracts/staking_contracts/PreallocationEscrow.sol";
import "contracts/staking_contracts/AbstractStakingContract.sol";
import "contracts/lib/AdditionalMath.sol";
@ -23,20 +21,19 @@ contract WorkLock {
event Deposited(address indexed sender, uint256 value);
event Bid(address indexed sender, uint256 depositedETH);
event Claimed(address indexed sender, address preallocationEscrow, uint256 claimedTokens);
event Refund(address indexed sender, address preallocationEscrow, uint256 refundETH, uint256 completedWork);
event Claimed(address indexed sender, uint256 claimedTokens);
event Refund(address indexed sender, uint256 refundETH, uint256 completedWork);
event Burnt(address indexed sender, uint256 value);
event Canceled(address indexed sender, uint256 value);
struct WorkInfo {
uint256 depositedETH;
uint256 completedWork;
PreallocationEscrow preallocationEscrow;
bool claimed;
}
NuCypherToken public token;
StakingEscrow public escrow;
StakingInterfaceRouter public router;
uint256 public startBidDate;
uint256 public endBidDate;
@ -55,38 +52,34 @@ contract WorkLock {
uint256 public tokenSupply;
uint256 public ethSupply;
uint256 public unclaimedTokens;
uint256 public lockingDuration;
uint16 public stakingPeriods;
mapping(address => WorkInfo) public workInfo;
mapping(address => address) public depositors;
/**
* @param _token Token contract
* @param _escrow Escrow contract
* @param _router Router contract
* @param _startBidDate Timestamp when bidding starts
* @param _endBidDate Timestamp when bidding will end
* @param _boostingRefund Coefficient to boost refund ETH
* @param _lockingDuration Duration of tokens locking
* @param _stakingPeriods Amount of periods during which tokens will be locked after claiming
*/
constructor(
NuCypherToken _token,
StakingEscrow _escrow,
StakingInterfaceRouter _router,
uint256 _startBidDate,
uint256 _endBidDate,
uint256 _boostingRefund,
uint256 _lockingDuration
uint16 _stakingPeriods
)
public
{
uint256 totalSupply = _token.totalSupply();
require(totalSupply > 0 &&
_escrow.secondsPerPeriod() > 0 &&
_router.target().isContract() &&
_endBidDate > _startBidDate &&
_endBidDate > block.timestamp &&
_boostingRefund > 0 &&
_lockingDuration > 0);
_stakingPeriods >= _escrow.minLockedPeriods());
// worst case for `ethToWork()` and `workToETH()`,
// when ethSupply == MAX_ETH_SUPPLY and tokenSupply == totalSupply
require(MAX_ETH_SUPPLY * totalSupply * SLOWING_REFUND / MAX_ETH_SUPPLY / totalSupply == SLOWING_REFUND &&
@ -94,17 +87,16 @@ contract WorkLock {
token = _token;
escrow = _escrow;
router = _router;
startBidDate = _startBidDate;
endBidDate = _endBidDate;
boostingRefund = _boostingRefund;
lockingDuration = _lockingDuration;
stakingPeriods = _stakingPeriods;
}
/**
* @notice Deposit tokens to contract
* @param _value Amount of tokens to transfer
**/
*/
function tokenDeposit(uint256 _value) external {
require(block.timestamp <= endBidDate, "Can't deposit more tokens after end of bidding");
token.safeTransferFrom(msg.sender, address(this), _value);
@ -115,7 +107,7 @@ contract WorkLock {
/**
* @notice Calculate amount of tokens that will be get for specified amount of ETH
* @dev This value will be fixed only after end of bidding
**/
*/
function ethToTokens(uint256 _ethAmount) public view returns (uint256) {
return _ethAmount.mul(tokenSupply).div(ethSupply);
}
@ -123,7 +115,7 @@ contract WorkLock {
/**
* @notice Calculate amount of work that need to be done to refund specified amount of ETH
* @dev This value will be fixed only after end of bidding
**/
*/
function ethToWork(uint256 _ethAmount) public view returns (uint256) {
return _ethAmount.mul(tokenSupply).mul(SLOWING_REFUND).divCeil(ethSupply.mul(boostingRefund));
}
@ -131,7 +123,7 @@ contract WorkLock {
/**
* @notice Calculate amount of ETH that will be refund for completing specified amount of work
* @dev This value will be fixed only after end of bidding
**/
*/
function workToETH(uint256 _completedWork) public view returns (uint256) {
return _completedWork.mul(ethSupply).mul(boostingRefund).div(tokenSupply.mul(SLOWING_REFUND));
}
@ -139,10 +131,9 @@ contract WorkLock {
/**
* @notice Get remaining work to full refund
*/
function getRemainingWork(address _preallocationEscrow) public view returns (uint256) {
address depositor = depositors[_preallocationEscrow];
WorkInfo storage info = workInfo[depositor];
uint256 completedWork = escrow.getCompletedWork(_preallocationEscrow).sub(info.completedWork);
function getRemainingWork(address _depositor) public view returns (uint256) {
WorkInfo storage info = workInfo[_depositor];
uint256 completedWork = escrow.getCompletedWork(_depositor).sub(info.completedWork);
uint256 remainingWork = ethToWork(info.depositedETH);
if (remainingWork <= completedWork) {
return 0;
@ -169,7 +160,7 @@ contract WorkLock {
// TODO check date? check minimum amount of tokens? (#1508)
WorkInfo storage info = workInfo[msg.sender];
require(info.depositedETH > 0, "No bid to cancel");
require(address(info.preallocationEscrow) == address(0), "Tokens are already claimed");
require(!info.claimed, "Tokens are already claimed");
uint256 refundETH = info.depositedETH;
info.depositedETH = 0;
if (block.timestamp <= endBidDate) {
@ -184,34 +175,27 @@ contract WorkLock {
/**
* @notice Claimed tokens will be deposited and locked as stake in the StakingEscrow contract.
*/
function claim() external returns (PreallocationEscrow preallocationEscrow, uint256 claimedTokens) {
function claim() external returns (uint256 claimedTokens) {
require(block.timestamp >= endBidDate, "Claiming tokens allowed after bidding is over");
WorkInfo storage info = workInfo[msg.sender];
require(address(info.preallocationEscrow) == address(0), "Tokens are already claimed");
require(!info.claimed, "Tokens are already claimed");
claimedTokens = ethToTokens(info.depositedETH);
require(claimedTokens > 0, "Nothing to claim");
preallocationEscrow = new PreallocationEscrow(router, token, StakingEscrowInterface(address(escrow)));
token.approve(address(preallocationEscrow), claimedTokens);
preallocationEscrow.initialDeposit(claimedTokens, lockingDuration);
preallocationEscrow.transferOwnership(msg.sender);
depositors[address(preallocationEscrow)] = msg.sender;
info.preallocationEscrow = preallocationEscrow;
info.completedWork = escrow.setWorkMeasurement(address(preallocationEscrow), true);
emit Claimed(msg.sender, address(preallocationEscrow), claimedTokens);
info.claimed = true;
token.approve(address(escrow), claimedTokens);
escrow.deposit(msg.sender, claimedTokens, stakingPeriods);
info.completedWork = escrow.setWorkMeasurement(msg.sender, true);
emit Claimed(msg.sender, claimedTokens);
}
/**
* @notice Refund ETH for the completed work
*/
function refund(PreallocationEscrow _preallocationEscrow) public returns (uint256 refundETH) {
address depositor = depositors[address(_preallocationEscrow)];
require(depositor != address(0), "Untrusted contract");
WorkInfo storage info = workInfo[depositor];
function refund() public returns (uint256 refundETH) {
WorkInfo storage info = workInfo[msg.sender];
require(info.depositedETH > 0, "Nothing deposited");
require(_preallocationEscrow.owner() == msg.sender, "Only the owner of specified contract can request a refund");
assert(_preallocationEscrow == info.preallocationEscrow);
uint256 currentWork = escrow.getCompletedWork(address(_preallocationEscrow));
uint256 currentWork = escrow.getCompletedWork(msg.sender);
uint256 completedWork = currentWork.sub(info.completedWork);
require(completedWork > 0, "No work that has been completed.");
@ -221,19 +205,19 @@ contract WorkLock {
refundETH = info.depositedETH;
}
if (refundETH == info.depositedETH) {
escrow.setWorkMeasurement(address(_preallocationEscrow), false);
escrow.setWorkMeasurement(msg.sender, false);
}
info.depositedETH = info.depositedETH.sub(refundETH);
completedWork = ethToWork(refundETH);
info.completedWork = info.completedWork.add(completedWork);
emit Refund(msg.sender, address(_preallocationEscrow), refundETH, completedWork);
emit Refund(msg.sender, refundETH, completedWork);
msg.sender.sendValue(refundETH);
}
/**
* @notice Burn unclaimed tokens
**/
*/
function burnUnclaimed() public {
require(block.timestamp >= endBidDate, "Burning tokens allowed when bidding is over");
require(unclaimedTokens > 0, "There are no tokens that can be burned");

View File

@ -61,9 +61,3 @@ contract StakingEscrowForWorkLockMock {
}
}
/**
* @notice Contract for using in WorkLock tests
**/
contract StakingInterfaceMock {}

View File

@ -14,13 +14,10 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with nucypher. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import pytest
import rlp
from eth_tester.exceptions import TransactionFailed
from eth_utils import to_wei, keccak, to_canonical_address, to_checksum_address
from web3.contract import Contract
from eth_utils import to_wei
@pytest.fixture()
@ -29,22 +26,6 @@ def token(testerchain, token_economics, deploy_contract):
return contract
@pytest.fixture()
def router(testerchain, deploy_contract):
staking_interface, _ = deploy_contract('StakingInterfaceMock')
secret = os.urandom(32)
secret_hash = keccak(secret)
contract, _ = deploy_contract('StakingInterfaceRouter', staking_interface.address, secret_hash)
return contract
def next_address(testerchain, worklock):
# https://github.com/ethereum/wiki/wiki/Subtleties#nonces
nonce = testerchain.w3.eth.getTransactionCount(worklock.address)
data_to_encode = [to_canonical_address(worklock.address), nonce]
return to_checksum_address(keccak(rlp.codec.encode(data_to_encode))[12:])
@pytest.fixture()
def escrow(testerchain, token_economics, deploy_contract, token):
contract, _ = deploy_contract(
@ -58,36 +39,30 @@ def escrow(testerchain, token_economics, deploy_contract, token):
@pytest.mark.slow
def test_worklock(testerchain, token_economics, deploy_contract, token, escrow, router):
def test_worklock(testerchain, token_economics, deploy_contract, token, escrow):
creator, staker1, staker2, staker3, *everyone_else = testerchain.w3.eth.accounts
# Deploy fake preallocation escrow
preallocation_escrow_fake, _ = deploy_contract('PreallocationEscrow', router.address, token.address, escrow.address)
tx = preallocation_escrow_fake.functions.transferOwnership(staker1).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Deploy WorkLock
now = testerchain.w3.eth.getBlock(block_identifier='latest').timestamp
start_bid_date = now + (60 * 60) # 1 Hour
end_bid_date = start_bid_date + (60 * 60)
boosting_refund = 50
slowing_refund = 100
locking_duration = 60 * 60
staking_periods = 2 * token_economics.minimum_locked_periods
worklock, _ = deploy_contract(
contract_name='WorkLock',
_token=token.address,
_escrow=escrow.address,
_router=router.address,
_startBidDate=start_bid_date,
_endBidDate=end_bid_date,
_boostingRefund=boosting_refund,
_lockingDuration=locking_duration
_stakingPeriods=staking_periods
)
assert worklock.functions.startBidDate().call() == start_bid_date
assert worklock.functions.endBidDate().call() == end_bid_date
assert worklock.functions.boostingRefund().call() == boosting_refund
assert worklock.functions.SLOWING_REFUND().call() == slowing_refund
assert worklock.functions.lockingDuration().call() == locking_duration
assert worklock.functions.stakingPeriods().call() == staking_periods
deposit_log = worklock.events.Deposited.createFilter(fromBlock='latest')
bidding_log = worklock.events.Bid.createFilter(fromBlock='latest')
@ -118,16 +93,16 @@ def test_worklock(testerchain, token_economics, deploy_contract, token, escrow,
assert event_args['sender'] == creator
assert event_args['value'] == worklock_supply_2
# Give Ursulas some ETH
# Give stakers some ETH
deposit_eth_1 = to_wei(4, 'ether')
deposit_eth_2 = deposit_eth_1 // 4
staker1_balance = 10 * deposit_eth_1
tx = testerchain.w3.eth.sendTransaction(
{'from': testerchain.etherbase_account, 'to': staker1, 'value': staker1_balance})
testerchain.wait_for_receipt(tx)
ursula2_balance = staker1_balance
staker2_balance = staker1_balance
tx = testerchain.w3.eth.sendTransaction(
{'from': testerchain.etherbase_account, 'to': staker2, 'value': ursula2_balance})
{'from': testerchain.etherbase_account, 'to': staker2, 'value': staker2_balance})
testerchain.wait_for_receipt(tx)
staker3_balance = staker1_balance
tx = testerchain.w3.eth.sendTransaction(
@ -142,7 +117,7 @@ def test_worklock(testerchain, token_economics, deploy_contract, token, escrow,
tx = worklock.functions.claim().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.refund(preallocation_escrow_fake.address).transact({'from': staker1, 'gas_price': 0})
tx = worklock.functions.refund().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.burnUnclaimed().transact({'from': staker1, 'gas_price': 0})
@ -154,12 +129,13 @@ def test_worklock(testerchain, token_economics, deploy_contract, token, escrow,
# Wait for the start of bidding
testerchain.time_travel(seconds=3600) # Wait exactly 1 hour
# Ursula does first bid
# Staker does first bid
assert worklock.functions.workInfo(staker1).call()[0] == 0
assert testerchain.w3.eth.getBalance(worklock.address) == 0
tx = worklock.functions.bid().transact({'from': staker1, 'value': deposit_eth_1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.workInfo(staker1).call()[0] == deposit_eth_1
assert not worklock.functions.workInfo(staker1).call()[2]
assert testerchain.w3.eth.getBalance(worklock.address) == deposit_eth_1
assert worklock.functions.ethToTokens(deposit_eth_1).call() == worklock_supply
@ -169,11 +145,12 @@ def test_worklock(testerchain, token_economics, deploy_contract, token, escrow,
assert event_args['sender'] == staker1
assert event_args['depositedETH'] == deposit_eth_1
# Second Ursula does first bid
# Second staker does first bid
assert worklock.functions.workInfo(staker2).call()[0] == 0
tx = worklock.functions.bid().transact({'from': staker2, 'value': deposit_eth_2, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.workInfo(staker2).call()[0] == deposit_eth_2
assert not worklock.functions.workInfo(staker2).call()[2]
assert testerchain.w3.eth.getBalance(worklock.address) == deposit_eth_1 + deposit_eth_2
assert worklock.functions.ethToTokens(deposit_eth_2).call() == worklock_supply // 5
@ -183,7 +160,7 @@ def test_worklock(testerchain, token_economics, deploy_contract, token, escrow,
assert event_args['sender'] == staker2
assert event_args['depositedETH'] == deposit_eth_2
# Third Ursula does first bid
# Third staker does first bid
assert worklock.functions.workInfo(staker3).call()[0] == 0
tx = worklock.functions.bid().transact({'from': staker3, 'value': deposit_eth_2, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
@ -197,7 +174,7 @@ def test_worklock(testerchain, token_economics, deploy_contract, token, escrow,
assert event_args['sender'] == staker3
assert event_args['depositedETH'] == deposit_eth_2
# Ursula does second bid
# Staker does second bid
tx = worklock.functions.bid().transact({'from': staker1, 'value': deposit_eth_1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.workInfo(staker1).call()[0] == 2 * deposit_eth_1
@ -215,7 +192,7 @@ def test_worklock(testerchain, token_economics, deploy_contract, token, escrow,
tx = worklock.functions.claim().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.refund(preallocation_escrow_fake.address).transact({'from': staker1, 'gas_price': 0})
tx = worklock.functions.refund().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.burnUnclaimed().transact({'from': staker1, 'gas_price': 0})
@ -241,7 +218,7 @@ def test_worklock(testerchain, token_economics, deploy_contract, token, escrow,
tx = worklock.functions.cancelBid().transact({'from': staker3, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Third Ursula does second bid
# Third staker does second bid
assert worklock.functions.workInfo(staker3).call()[0] == 0
tx = worklock.functions.bid().transact({'from': staker3, 'value': deposit_eth_2, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
@ -264,43 +241,35 @@ def test_worklock(testerchain, token_economics, deploy_contract, token, escrow,
testerchain.wait_for_receipt(tx)
# Can't refund without claim
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.refund(preallocation_escrow_fake.address).transact({'from': staker1, 'gas_price': 0})
tx = worklock.functions.refund().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Ursula claims tokens
preallocation_escrow_1_address = next_address(testerchain, worklock)
_value, measure_work, _completed_work, _periods = escrow.functions.stakerInfo(preallocation_escrow_1_address).call()
# Staker claims tokens
value, measure_work, _completed_work, periods = escrow.functions.stakerInfo(staker1).call()
assert not measure_work
assert value == 0
assert periods == 0
assert not worklock.functions.workInfo(staker1).call()[2]
tx = worklock.functions.claim().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.workInfo(staker1).call()[2]
staker1_tokens = 8 * worklock_supply // 10
preallocation_escrow_1 = testerchain.client.get_contract(
abi=preallocation_escrow_fake.abi,
address=worklock.functions.workInfo(staker1).call()[2],
ContractFactoryClass=Contract)
assert preallocation_escrow_1.address == preallocation_escrow_1_address
assert token.functions.balanceOf(staker1).call() == 0
assert token.functions.balanceOf(preallocation_escrow_1.address).call() == staker1_tokens
assert preallocation_escrow_1.functions.owner().call() == staker1
assert preallocation_escrow_1.functions.router().call() == router.address
assert preallocation_escrow_1.functions.lockedValue().call() == staker1_tokens
assert preallocation_escrow_1.functions.getLockedTokens().call() == staker1_tokens
assert preallocation_escrow_1.functions.endLockTimestamp().call() == \
testerchain.w3.eth.getBlock(block_identifier='latest').timestamp + locking_duration
staker1_remaining_work = int(-(-8 * worklock_supply * slowing_refund // (boosting_refund * 10))) # div ceil
assert worklock.functions.ethToWork(2 * deposit_eth_1).call() == staker1_remaining_work
assert worklock.functions.workToETH(staker1_remaining_work).call() == 2 * deposit_eth_1
assert worklock.functions.getRemainingWork(preallocation_escrow_1_address).call() == staker1_remaining_work
assert worklock.functions.getRemainingWork(staker1).call() == staker1_remaining_work
assert token.functions.balanceOf(worklock.address).call() == worklock_supply - staker1_tokens
_value, measure_work, _completed_work, _periods = escrow.functions.stakerInfo(preallocation_escrow_1_address).call()
value, measure_work, _completed_work, periods = escrow.functions.stakerInfo(staker1).call()
assert measure_work
assert value == staker1_tokens
assert periods == staking_periods
events = claim_log.get_all_entries()
assert 1 == len(events)
event_args = events[0]['args']
assert event_args['sender'] == staker1
assert event_args['claimedTokens'] == staker1_tokens
assert event_args['preallocationEscrow'] == preallocation_escrow_1_address
# Can't claim more than once
with pytest.raises((TransactionFailed, ValueError)):
@ -308,14 +277,14 @@ def test_worklock(testerchain, token_economics, deploy_contract, token, escrow,
testerchain.wait_for_receipt(tx)
# Can't refund without work
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.refund(preallocation_escrow_1.address).transact({'from': staker1, 'gas_price': 0})
tx = worklock.functions.refund().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Can't cancel after claim
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.cancelBid().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# One of Ursulas cancel bid
# One of stakers cancel bid
staker3_balance = testerchain.w3.eth.getBalance(staker3)
staker3_tokens = worklock_supply // 10
assert worklock.functions.ethToTokens(deposit_eth_2).call() == staker3_tokens
@ -328,107 +297,80 @@ def test_worklock(testerchain, token_economics, deploy_contract, token, escrow,
assert worklock.functions.unclaimedTokens().call() == staker3_tokens
assert token.functions.balanceOf(worklock.address).call() == worklock_supply - staker1_tokens
# Second Ursula claims tokens
preallocation_escrow_2_address = next_address(testerchain, worklock)
_value, measure_work, _completed_work, _periods = escrow.functions.stakerInfo(preallocation_escrow_2_address).call()
# Second staker claims tokens
value, measure_work, _completed_work, periods = escrow.functions.stakerInfo(staker2).call()
assert not measure_work
assert value == 0
assert periods == 0
staker2_tokens = staker3_tokens
# staker2_tokens * slowing_refund / boosting_refund
staker2_remaining_work = int(-(-worklock_supply * slowing_refund // (boosting_refund * 10))) # div ceil
assert worklock.functions.ethToWork(deposit_eth_2).call() == staker2_remaining_work
assert worklock.functions.workToETH(staker2_remaining_work).call() == deposit_eth_2
tx = escrow.functions.setCompletedWork(preallocation_escrow_2_address, staker2_remaining_work // 2).transact()
tx = escrow.functions.setCompletedWork(staker2, staker2_remaining_work // 2).transact()
testerchain.wait_for_receipt(tx)
assert not worklock.functions.workInfo(staker2).call()[2]
tx = worklock.functions.claim().transact({'from': staker2, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.getRemainingWork(preallocation_escrow_2_address).call() == staker2_remaining_work
assert worklock.functions.workInfo(staker2).call()[2]
assert worklock.functions.getRemainingWork(staker2).call() == staker2_remaining_work
assert token.functions.balanceOf(worklock.address).call() == worklock_supply - staker1_tokens - staker2_tokens
assert token.functions.balanceOf(staker2).call() == 0
preallocation_escrow_2 = testerchain.client.get_contract(
abi=preallocation_escrow_fake.abi,
address=worklock.functions.workInfo(staker2).call()[2],
ContractFactoryClass=Contract)
assert preallocation_escrow_2.address == preallocation_escrow_2_address
assert token.functions.balanceOf(preallocation_escrow_2.address).call() == staker2_tokens
assert preallocation_escrow_2.functions.owner().call() == staker2
assert preallocation_escrow_2.functions.getLockedTokens().call() == staker2_tokens
_value, measure_work, _completed_work, _periods = escrow.functions.stakerInfo(preallocation_escrow_2.address).call()
value, measure_work, _completed_work, periods = escrow.functions.stakerInfo(staker2).call()
assert measure_work
assert value == staker2_tokens
assert periods == staking_periods
events = claim_log.get_all_entries()
assert 2 == len(events)
event_args = events[1]['args']
assert event_args['sender'] == staker2
assert event_args['claimedTokens'] == staker2_tokens
assert event_args['preallocationEscrow'] == preallocation_escrow_2_address
# "Do" some work and partial refund
staker1_balance = testerchain.w3.eth.getBalance(staker1)
completed_work = staker1_remaining_work // 2 + 1
remaining_work = staker1_remaining_work - completed_work
tx = escrow.functions.setCompletedWork(preallocation_escrow_1_address, completed_work).transact()
tx = escrow.functions.setCompletedWork(staker1, completed_work).transact()
testerchain.wait_for_receipt(tx)
assert worklock.functions.getRemainingWork(preallocation_escrow_1_address).call() == remaining_work
assert worklock.functions.getRemainingWork(staker1).call() == remaining_work
# Can't refund using wrong escrow address
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.refund(preallocation_escrow_fake.address).transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Only owner of escrow can call refund
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.refund(preallocation_escrow_1_address).transact({'from': staker2, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
tx = worklock.functions.refund(preallocation_escrow_1_address).transact({'from': staker1, 'gas_price': 0})
tx = worklock.functions.refund().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.workInfo(staker1).call()[0] == deposit_eth_1
assert worklock.functions.getRemainingWork(preallocation_escrow_1_address).call() == remaining_work
assert worklock.functions.getRemainingWork(staker1).call() == remaining_work
assert testerchain.w3.eth.getBalance(staker1) == staker1_balance + deposit_eth_1
assert testerchain.w3.eth.getBalance(worklock.address) == deposit_eth_1 + deposit_eth_2
_value, measure_work, _completed_work, _periods = escrow.functions.stakerInfo(preallocation_escrow_1_address).call()
_value, measure_work, _completed_work, _periods = escrow.functions.stakerInfo(staker1).call()
assert measure_work
events = refund_log.get_all_entries()
assert 1 == len(events)
event_args = events[0]['args']
assert event_args['sender'] == staker1
assert event_args['preallocationEscrow'] == preallocation_escrow_1_address
assert event_args['refundETH'] == deposit_eth_1
assert event_args['completedWork'] == staker1_remaining_work // 2
# Transfer ownership of preallocation escrow to the new staker
tx = preallocation_escrow_1.functions.transferOwnership(staker2).transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# "Do" more work and full refund
staker1_balance = testerchain.w3.eth.getBalance(staker1)
staker2_balance = testerchain.w3.eth.getBalance(staker2)
completed_work = staker1_remaining_work
tx = escrow.functions.setCompletedWork(preallocation_escrow_1_address, completed_work).transact()
tx = escrow.functions.setCompletedWork(staker1, completed_work).transact()
testerchain.wait_for_receipt(tx)
assert worklock.functions.getRemainingWork(preallocation_escrow_1_address).call() == 0
assert worklock.functions.getRemainingWork(staker1).call() == 0
# Only ??? owner of escrow can call refund
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.refund(preallocation_escrow_1_address).transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
tx = worklock.functions.refund(preallocation_escrow_1_address).transact({'from': staker2, 'gas_price': 0})
tx = worklock.functions.refund().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.workInfo(staker1).call()[0] == 0
assert worklock.functions.workInfo(staker2).call()[0] == deposit_eth_2
assert worklock.functions.getRemainingWork(preallocation_escrow_1_address).call() == 0
assert testerchain.w3.eth.getBalance(staker2) == staker2_balance + deposit_eth_1
assert testerchain.w3.eth.getBalance(staker1) == staker1_balance
assert worklock.functions.getRemainingWork(staker1).call() == 0
assert testerchain.w3.eth.getBalance(staker1) == staker1_balance + deposit_eth_1
assert testerchain.w3.eth.getBalance(worklock.address) == deposit_eth_2
_value, measure_work, _completed_work, _periods = escrow.functions.stakerInfo(preallocation_escrow_1_address).call()
_value, measure_work, _completed_work, _periods = escrow.functions.stakerInfo(staker1).call()
assert not measure_work
events = refund_log.get_all_entries()
assert 2 == len(events)
event_args = events[1]['args']
assert event_args['sender'] == staker2
assert event_args['preallocationEscrow'] == preallocation_escrow_1_address
assert event_args['sender'] == staker1
assert event_args['refundETH'] == deposit_eth_1
assert event_args['completedWork'] == staker1_remaining_work // 2
@ -436,16 +378,17 @@ def test_worklock(testerchain, token_economics, deploy_contract, token, escrow,
tx = escrow.functions.setCompletedWork(staker1, 2 * completed_work).transact()
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.refund(preallocation_escrow_1_address).transact({'from': staker2, 'gas_price': 0})
tx = worklock.functions.refund().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Now burn remaining tokens
escrow_tokens = token.functions.balanceOf(escrow.address).call()
assert worklock.functions.unclaimedTokens().call() == staker3_tokens
tx = worklock.functions.burnUnclaimed().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.unclaimedTokens().call() == 0
assert token.functions.balanceOf(worklock.address).call() == 0
assert token.functions.balanceOf(escrow.address).call() == staker3_tokens
assert token.functions.balanceOf(escrow.address).call() == escrow_tokens + staker3_tokens
# Can't burn twice
with pytest.raises((TransactionFailed, ValueError)):
@ -460,22 +403,21 @@ def test_worklock(testerchain, token_economics, deploy_contract, token, escrow,
@pytest.mark.slow
def test_reentrancy(testerchain, token_economics, deploy_contract, token, escrow, router):
def test_reentrancy(testerchain, token_economics, deploy_contract, token, escrow):
# Deploy WorkLock
now = testerchain.w3.eth.getBlock(block_identifier='latest').timestamp
start_bid_date = now
end_bid_date = start_bid_date + (60 * 60)
boosting_refund = 100
locking_duration = 60 * 60
staking_periods = token_economics.minimum_locked_periods
worklock, _ = deploy_contract(
contract_name='WorkLock',
_token=token.address,
_escrow=escrow.address,
_router=router.address,
_startBidDate=start_bid_date,
_endBidDate=end_bid_date,
_boostingRefund=boosting_refund,
_lockingDuration=locking_duration
_stakingPeriods=staking_periods
)
refund_log = worklock.events.Refund.createFilter(fromBlock='latest')
canceling_log = worklock.events.Canceled.createFilter(fromBlock='latest')
@ -522,15 +464,14 @@ def test_reentrancy(testerchain, token_economics, deploy_contract, token, escrow
testerchain.wait_for_receipt(tx)
tx = testerchain.client.send_transaction({'to': contract_address})
testerchain.wait_for_receipt(tx)
preallocation_escrow = worklock.functions.workInfo(contract_address).call()[2]
assert worklock.functions.getRemainingWork(preallocation_escrow).call() == worklock_supply // 2
assert worklock.functions.getRemainingWork(contract_address).call() == worklock_supply // 2
# Prepare for refund and check reentrancy protection
balance = testerchain.w3.eth.getBalance(contract_address)
completed_work = worklock_supply // 6
tx = escrow.functions.setCompletedWork(preallocation_escrow, completed_work).transact()
tx = escrow.functions.setCompletedWork(contract_address, completed_work).transact()
testerchain.wait_for_receipt(tx)
transaction = worklock.functions.refund(preallocation_escrow).buildTransaction({'gas': 0})
transaction = worklock.functions.refund().buildTransaction({'gas': 0})
tx = reentrancy_contract.functions.setData(2, transaction['to'], 0, transaction['data']).transact()
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
@ -538,5 +479,5 @@ def test_reentrancy(testerchain, token_economics, deploy_contract, token, escrow
testerchain.wait_for_receipt(tx)
assert testerchain.w3.eth.getBalance(contract_address) == balance
assert worklock.functions.workInfo(contract_address).call()[0] == deposit_eth
assert worklock.functions.getRemainingWork(preallocation_escrow).call() == 2 * worklock_supply // 6
assert worklock.functions.getRemainingWork(contract_address).call() == 2 * worklock_supply // 6
assert len(refund_log.get_all_entries()) == 0