diff --git a/nucypher/blockchain/eth/sol/source/contracts/WorkLock.sol b/nucypher/blockchain/eth/sol/source/contracts/WorkLock.sol index f3eec94f9..24826524f 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/WorkLock.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/WorkLock.sol @@ -136,7 +136,12 @@ contract WorkLock { **/ function getRemainingWork(address _miner) public view returns (uint256) { WorkInfo storage info = workInfo[_miner]; - return info.depositedETH.mul(refundRate); + uint256 workDone = escrow.getWorkDone(_miner).sub(info.workDone); + uint256 remainingWork = info.depositedETH.mul(refundRate); + if (remainingWork <= workDone) { + return 0; + } + return remainingWork.sub(workDone); } } diff --git a/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py b/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py index 1d7a822a9..2e49d0982 100644 --- a/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py +++ b/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py @@ -132,6 +132,34 @@ def adjudicator(testerchain, escrow, slashing_economics): return contract, dispatcher +@pytest.fixture() +def worklock(testerchain, token, escrow): + escrow, _ = escrow + creator = testerchain.interface.w3.eth.accounts[0] + + # Creator deploys the worklock using test values + now = testerchain.interface.w3.eth.getBlock(block_identifier='latest').timestamp + start_bid_date = ((now + 3600) // 3600 + 1) * 3600 # beginning of the next hour plus 1 hour + end_bid_date = start_bid_date + 3600 + deposit_rate = 2 + refund_rate = deposit_rate + contract, _ = testerchain.interface.deploy_contract( + contract_name='WorkLock', + _token=token.address, + _escrow=escrow.address, + _startBidDate=start_bid_date, + _endBidDate=end_bid_date, + _depositRate=deposit_rate, + _refundRate=refund_rate, + _lockedPeriods=9 + ) + + tx = escrow.functions.setWorkLock(contract.address).transact({'from': creator}) + testerchain.wait_for_receipt(tx) + + return contract + + def mock_ursula(testerchain, account): ursula_privkey = UmbralPrivateKey.gen_key() ursula_stamp = SignatureStamp(verifying_key=ursula_privkey.pubkey, @@ -219,6 +247,7 @@ def test_all(testerchain, escrow, policy_manager, adjudicator, + worklock, user_escrow_proxy, multisig, slashing_economics, @@ -247,6 +276,9 @@ def test_all(testerchain, tx = testerchain.client.send_transaction( {'from': testerchain.client.coinbase, 'to': alice2, 'value': 10 ** 10}) testerchain.wait_for_receipt(tx) + tx = testerchain.interface.w3.eth.sendTransaction( + {'from': testerchain.interface.w3.eth.coinbase, 'to': ursula2, 'value': 10 ** 10}) + testerchain.wait_for_receipt(tx) # Give Ursula and Alice some coins tx = token.functions.transfer(ursula1, 10000).transact({'from': creator}) @@ -277,6 +309,74 @@ def test_all(testerchain, tx = escrow.functions.initialize().buildTransaction({'from': multisig.address, 'gasPrice': 0}) execute_multisig_transaction(testerchain, multisig, [contracts_owners[0], contracts_owners[1]], tx) + # Initialize worklock + initial_supply = 1000 + tx = token.functions.transfer(worklock.address, initial_supply).transact({'from': creator}) + testerchain.wait_for_receipt(tx) + + # Can't do anything before start date + deposit_rate = 2 + refund_rate = 2 + deposited_eth = 1000 // deposit_rate + with pytest.raises((TransactionFailed, ValueError)): + tx = worklock.functions.bid().transact({'from': ursula2, 'value': deposited_eth, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + + # Wait for the start of the bidding + testerchain.time_travel(hours=1) + + # Can't bid with too low or too high ETH + with pytest.raises((TransactionFailed, ValueError)): + tx = worklock.functions.bid().transact({'from': ursula2, 'value': 1, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + with pytest.raises((TransactionFailed, ValueError)): + tx = worklock.functions.bid().transact({'from': ursula2, 'value': 10**10, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + + # Ursula does bid + assert worklock.functions.allClaimedTokens().call() == 0 + assert worklock.functions.workInfo(ursula2).call()[0] == 0 + assert testerchain.interface.w3.eth.getBalance(worklock.address) == 0 + tx = worklock.functions.bid().transact({'from': ursula2, 'value': deposited_eth, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + assert worklock.functions.allClaimedTokens().call() == 1000 + assert worklock.functions.workInfo(ursula2).call()[0] == deposited_eth + assert testerchain.interface.w3.eth.getBalance(worklock.address) == deposited_eth + + # Can't claim while bidding phase + with pytest.raises((TransactionFailed, ValueError)): + tx = worklock.functions.claim().transact({'from': ursula2, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + + # Wait for the end of the bidding + testerchain.time_travel(hours=1) + + # Can't bid after the enf of bidding + with pytest.raises((TransactionFailed, ValueError)): + tx = worklock.functions.bid().transact({'from': ursula2, 'value': 1, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + + # Ursula claims tokens + tx = worklock.functions.claim().transact({'from': ursula2, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + assert worklock.functions.getRemainingWork(ursula2).call() == deposit_rate * deposited_eth + assert reward + 1000 == token.functions.balanceOf(escrow.address).call() + assert 1000 == escrow.functions.getAllTokens(ursula2).call() + assert 0 == escrow.functions.getLockedTokens(ursula2).call() + assert 1000 == escrow.functions.getLockedTokens(ursula2, 1).call() + assert 1000 == escrow.functions.getLockedTokens(ursula2, 9).call() + assert 0 == escrow.functions.getLockedTokens(ursula2, 10).call() + assert 0 == escrow.functions.getWorkDone(ursula2).call() + + # Can't claim more than once + with pytest.raises((TransactionFailed, ValueError)): + tx = worklock.functions.claim().transact({'from': ursula2, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + # Can't refund without work + with pytest.raises((TransactionFailed, ValueError)): + tx = worklock.functions.refund().transact({'from': ursula2, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + # Create the first user escrow, set and lock re-stake parameter user_escrow_1, _ = testerchain.deploy_contract( 'UserEscrow', user_escrow_linker.address, token.address) @@ -354,34 +454,6 @@ def test_all(testerchain, tx = token.functions.approve(escrow.address, 10000).transact({'from': creator}) testerchain.wait_for_receipt(tx) - # Deposit tokens for 1 owner - tx = escrow.functions.preDeposit([ursula2], [1000], [9]).transact({'from': creator}) - testerchain.wait_for_receipt(tx) - tx = escrow.functions.setWorker(ursula2).transact({'from': ursula2}) - testerchain.wait_for_receipt(tx) - assert reward + 1000 == token.functions.balanceOf(escrow.address).call() - assert 1000 == escrow.functions.getAllTokens(ursula2).call() - assert 0 == escrow.functions.getLockedTokens(ursula2).call() - assert 1000 == escrow.functions.getLockedTokens(ursula2, 1).call() - assert 1000 == escrow.functions.getLockedTokens(ursula2, 9).call() - assert 0 == escrow.functions.getLockedTokens(ursula2, 10).call() - - # Can't pre-deposit tokens again for the same owner - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.preDeposit([ursula2], [1000], [9]).transact({'from': creator}) - testerchain.wait_for_receipt(tx) - - # Can't pre-deposit tokens with too low or too high value - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.preDeposit([ursula3], [1], [10]).transact({'from': creator}) - testerchain.wait_for_receipt(tx) - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.preDeposit([ursula3], [10 ** 6], [10]).transact({'from': creator}) - testerchain.wait_for_receipt(tx) - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.preDeposit([ursula3], [500], [1]).transact({'from': creator}) - testerchain.wait_for_receipt(tx) - # Ursula transfer some tokens to the escrow and lock them tx = escrow.functions.deposit(1000, 10).transact({'from': ursula1}) testerchain.wait_for_receipt(tx) @@ -970,3 +1042,18 @@ def test_all(testerchain, testerchain.wait_for_receipt(tx) assert ursula3_balance < token.functions.balanceOf(ursula3).call() assert ursula4_balance < token.functions.balanceOf(ursula4).call() + + # Partial refund for Ursula + work_done = escrow.functions.getWorkDone(ursula2).call() + remaining_work = worklock.functions.getRemainingWork(ursula2).call() + assert 0 < remaining_work + assert 0 < work_done + ursula2_balance = testerchain.interface.w3.eth.getBalance(ursula2) + tx = worklock.functions.refund().transact({'from': ursula2, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + refund = work_done // refund_rate + assert refund + ursula2_balance == testerchain.interface.w3.eth.getBalance(ursula2) + assert remaining_work - work_done == worklock.functions.getRemainingWork(ursula2).call() + assert deposited_eth - refund == testerchain.interface.w3.eth.getBalance(worklock.address) + assert 0 == escrow.functions.getWorkDone(ursula1).call() + assert 0 == escrow.functions.getWorkDone(user_escrow_1.address).call() diff --git a/tests/blockchain/eth/contracts/main/worklock/test_worklock.py b/tests/blockchain/eth/contracts/main/worklock/test_worklock.py index 8aa579ccd..999eed3d0 100644 --- a/tests/blockchain/eth/contracts/main/worklock/test_worklock.py +++ b/tests/blockchain/eth/contracts/main/worklock/test_worklock.py @@ -247,10 +247,11 @@ def test_worklock(testerchain, token_economics): work_done = refund_rate * minimum_deposit_eth + refund_rate // 2 tx = escrow.functions.setWorkDone(ursula1, work_done).transact() testerchain.wait_for_receipt(tx) + assert worklock.functions.getRemainingWork(ursula1).call() == minimum_deposit_eth * refund_rate - refund_rate // 2 tx = worklock.functions.refund().transact({'from': ursula1, 'gas_price': 0}) testerchain.wait_for_receipt(tx) assert worklock.functions.workInfo(ursula1).call()[0] == minimum_deposit_eth - assert worklock.functions.getRemainingWork(ursula1).call() == minimum_deposit_eth * refund_rate + assert worklock.functions.getRemainingWork(ursula1).call() == minimum_deposit_eth * refund_rate - refund_rate // 2 assert testerchain.interface.w3.eth.getBalance(ursula1) == ursula1_balance + minimum_deposit_eth assert testerchain.interface.w3.eth.getBalance(worklock.address) == maximum_deposit_eth + minimum_deposit_eth _value, measure_work, _work_done, _periods = escrow.functions.minerInfo(ursula1).call() @@ -268,6 +269,7 @@ def test_worklock(testerchain, token_economics): work_done = refund_rate * 2 * minimum_deposit_eth tx = escrow.functions.setWorkDone(ursula1, work_done).transact() testerchain.wait_for_receipt(tx) + assert worklock.functions.getRemainingWork(ursula1).call() == 0 tx = worklock.functions.refund().transact({'from': ursula1, 'gas_price': 0}) testerchain.wait_for_receipt(tx) assert worklock.functions.workInfo(ursula1).call()[0] == 0