mirror of https://github.com/nucypher/nucypher.git
commit
7a60009d63
|
@ -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");
|
||||
|
|
|
@ -61,9 +61,3 @@ contract StakingEscrowForWorkLockMock {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @notice Contract for using in WorkLock tests
|
||||
**/
|
||||
contract StakingInterfaceMock {}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue