Merge pull request #1407 from vzotova/new-worklock

WorkLock updates
pull/1542/head
David Núñez 2019-12-26 10:18:57 +00:00 committed by GitHub
commit 6269ad8619
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 855 additions and 442 deletions

View File

@ -182,7 +182,8 @@ class ContractAdministrator(NucypherTokenActor):
registry: BaseContractRegistry,
deployer_address: str = None,
client_password: str = None,
economics: TokenEconomics = None):
economics: TokenEconomics = None,
staking_escrow_test_mode: bool = False):
"""
Note: super() is not called here to avoid setting the token agent.
TODO: Review this logic ^^ "bare mode". #1510
@ -199,6 +200,7 @@ class ContractAdministrator(NucypherTokenActor):
self.transacting_power = TransactingPower(password=client_password, account=deployer_address, cache=True)
self.transacting_power.activate()
self.staking_escrow_test_mode = staking_escrow_test_mode
def __repr__(self):
r = '{name} - {deployer_address})'.format(name=self.__class__.__name__, deployer_address=self.deployer_address)
@ -235,6 +237,9 @@ class ContractAdministrator(NucypherTokenActor):
) -> Tuple[dict, BaseContractDeployer]:
Deployer = self.__get_deployer(contract_name=contract_name)
if Deployer is StakingEscrowDeployer:
kwargs.update({"test_mode": self.staking_escrow_test_mode})
deployer = Deployer(registry=self.registry,
deployer_address=self.deployer_address,
economics=self.economics,

View File

@ -483,16 +483,17 @@ class StakingEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin, Owna
agency = StakingEscrowAgent
contract_name = agency.registry_contract_name
deployment_steps = ('contract_deployment', 'dispatcher_deployment', 'reward_transfer', 'initialize')
deployment_steps = ('contract_deployment', 'dispatcher_deployment', 'approve_reward_transfer', 'initialize')
_proxy_deployer = DispatcherDeployer
def __init__(self, *args, **kwargs):
def __init__(self, test_mode: bool = False, *args, **kwargs):
super().__init__(*args, **kwargs)
self.__dispatcher_contract = None
token_contract_name = NucypherTokenDeployer.contract_name
self.token_contract = self.blockchain.get_contract_by_name(registry=self.registry,
contract_name=token_contract_name)
self.test_mode = test_mode
def __check_policy_manager(self):
result = self.contract.functions.policyManager().call()
@ -509,7 +510,8 @@ class StakingEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin, Owna
"_minLockedPeriods": args[4],
"_minAllowableLockedTokens": args[5],
"_maxAllowableLockedTokens": args[6],
"_minWorkerPeriods": args[7]
"_minWorkerPeriods": args[7],
"_isTestContract": self.test_mode
}
constructor_kwargs.update(overrides)
constructor_kwargs = {k: v for k, v in constructor_kwargs.items() if v is not None}
@ -597,19 +599,19 @@ class StakingEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin, Owna
# Switch the contract for the wrapped one
the_escrow_contract = wrapped_escrow_contract
# 3 - Transfer the reward supply tokens to StakingEscrow #
reward_function = self.token_contract.functions.transfer(the_escrow_contract.address,
self.economics.erc20_reward_supply)
# 3 - Approve transfer the reward supply tokens to StakingEscrow #
approve_reward_function = self.token_contract.functions.approve(the_escrow_contract.address,
self.economics.erc20_reward_supply)
# TODO: Confirmations / Successful Transaction Indicator / Events ?? - #1193, #1194
reward_receipt = self.blockchain.send_transaction(contract_function=reward_function,
sender_address=self.deployer_address,
payload=origin_args)
approve_reward_receipt = self.blockchain.send_transaction(contract_function=approve_reward_function,
sender_address=self.deployer_address,
payload=origin_args)
if progress:
progress.update(1)
# 4 - Initialize the StakingEscrow contract
init_function = the_escrow_contract.functions.initialize()
init_function = the_escrow_contract.functions.initialize(self.economics.erc20_reward_supply)
init_receipt = self.blockchain.send_transaction(contract_function=init_function,
sender_address=self.deployer_address,
@ -618,7 +620,7 @@ class StakingEscrowDeployer(BaseContractDeployer, UpgradeableContractMixin, Owna
progress.update(1)
# Gather the transaction receipts
ordered_receipts = (deploy_receipt, dispatcher_deploy_receipt, reward_receipt, init_receipt)
ordered_receipts = (deploy_receipt, dispatcher_deploy_receipt, approve_reward_receipt, init_receipt)
deployment_receipts = dict(zip(self.deployment_steps, ordered_receipts))
# Set the contract and transaction receipts #

View File

@ -238,6 +238,7 @@ class BaseContractRegistry(ABC):
def id(self) -> str:
"""Returns a hexstr of the registry contents."""
blake = hashlib.blake2b()
blake.update(self.__class__.__name__.encode())
blake.update(json.dumps(self.read()).encode())
digest = blake.digest().hex()
return digest

View File

@ -6,6 +6,7 @@ import "zeppelin/math/SafeMath.sol";
import "zeppelin/math/Math.sol";
import "contracts/proxy/Upgradeable.sol";
import "contracts/lib/AdditionalMath.sol";
import "zeppelin/token/ERC20/SafeERC20.sol";
/**
@ -13,9 +14,11 @@ import "contracts/lib/AdditionalMath.sol";
* @dev |v1.1.2|
*/
contract Issuer is Upgradeable {
using SafeERC20 for NuCypherToken;
using SafeMath for uint256;
using AdditionalMath for uint32;
event Burnt(address indexed sender, uint256 value);
/// Issuer is initialized with a reserved reward
event Initialized(uint256 reservedReward);
@ -97,14 +100,14 @@ contract Issuer is Upgradeable {
/**
* @notice Initialize reserved tokens for reward
*/
function initialize() public onlyOwner {
function initialize(uint256 _reservedReward) public onlyOwner {
require(currentSupply1 == 0);
token.safeTransferFrom(msg.sender, address(this), _reservedReward);
currentMintingPeriod = getCurrentPeriod();
uint256 reservedReward = token.balanceOf(address(this));
uint256 currentTotalSupply = totalSupply - reservedReward;
uint256 currentTotalSupply = totalSupply - _reservedReward;
currentSupply1 = currentTotalSupply;
currentSupply2 = currentTotalSupply;
emit Initialized(reservedReward);
emit Initialized(_reservedReward);
}
/**
@ -169,6 +172,16 @@ contract Issuer is Upgradeable {
currentSupply2 = currentSupply2 - _amount;
}
/**
* @notice Burn sender's tokens. Amount of tokens will be returned for future minting
* @param _value Amount to burn
*/
function burn(uint256 _value) public {
token.safeTransferFrom(msg.sender, address(this), _value);
unMint(_value);
emit Burnt(msg.sender, _value);
}
/**
* @notice Returns the number of tokens that can be mined
*/

View File

@ -1,7 +1,6 @@
pragma solidity ^0.5.3;
import "zeppelin/token/ERC20/SafeERC20.sol";
import "contracts/Issuer.sol";
@ -34,10 +33,9 @@ contract WorkLockInterface {
/**
* @notice Contract holds and locks stakers tokens.
* Each staker that locks their tokens will receive some compensation
* @dev |v1.4.1|
* @dev |v1.5.1|
*/
contract StakingEscrow is Issuer {
using SafeERC20 for NuCypherToken;
using AdditionalMath for uint256;
using AdditionalMath for uint16;
@ -118,6 +116,7 @@ contract StakingEscrow is Issuer {
PolicyManagerInterface public policyManager;
AdjudicatorInterface public adjudicator;
WorkLockInterface public workLock;
bool public isTestContract;
/**
* @notice Constructor sets address of token contract and coefficients for mining
@ -130,6 +129,7 @@ contract StakingEscrow is Issuer {
* @param _minAllowableLockedTokens Min amount of tokens that can be locked
* @param _maxAllowableLockedTokens Max amount of tokens that can be locked
* @param _minWorkerPeriods Min amount of periods while a worker can't be changed
* @param _isTestContract True if contract is only for tests
*/
constructor(
NuCypherToken _token,
@ -140,7 +140,8 @@ contract StakingEscrow is Issuer {
uint16 _minLockedPeriods,
uint256 _minAllowableLockedTokens,
uint256 _maxAllowableLockedTokens,
uint16 _minWorkerPeriods
uint16 _minWorkerPeriods,
bool _isTestContract
)
public
Issuer(
@ -157,6 +158,7 @@ contract StakingEscrow is Issuer {
minAllowableLockedTokens = _minAllowableLockedTokens;
maxAllowableLockedTokens = _maxAllowableLockedTokens;
minWorkerPeriods = _minWorkerPeriods;
isTestContract = _isTestContract;
}
/**
@ -196,7 +198,7 @@ contract StakingEscrow is Issuer {
*/
function setWorkLock(WorkLockInterface _workLock) external onlyOwner {
// WorkLock can be set only once
require(address(workLock) == address(0));
require(address(workLock) == address(0) || isTestContract);
// This escrow must be the escrow for the new worklock
require(_workLock.escrow() == address(this));
workLock = _workLock;
@ -1213,6 +1215,7 @@ contract StakingEscrow is Issuer {
/// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `verifyState`
function verifyState(address _testTarget) public {
super.verifyState(_testTarget);
require((delegateGet(_testTarget, "isTestContract()") == 0) == !isTestContract);
require(uint16(delegateGet(_testTarget, "minWorkerPeriods()")) == minWorkerPeriods);
require(delegateGet(_testTarget, "minAllowableLockedTokens()") == minAllowableLockedTokens);
require(delegateGet(_testTarget, "maxAllowableLockedTokens()") == maxAllowableLockedTokens);
@ -1277,6 +1280,7 @@ contract StakingEscrow is Issuer {
minAllowableLockedTokens = escrow.minAllowableLockedTokens();
maxAllowableLockedTokens = escrow.maxAllowableLockedTokens();
minWorkerPeriods = escrow.minWorkerPeriods();
isTestContract = escrow.isTestContract();
// Create fake period
lockedPerPeriod[RESERVED_PERIOD] = 111;

View File

@ -2,148 +2,245 @@ pragma solidity ^0.5.3;
import "zeppelin/math/SafeMath.sol";
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";
/**
* @notice The WorkLock distribution contract
*/
contract WorkLock {
using SafeERC20 for NuCypherToken;
using SafeMath for uint256;
using AdditionalMath for uint256;
using Address for address payable;
using Address for address;
event Bid(address indexed staker, uint256 depositedETH, uint256 claimedTokens);
event Claimed(address indexed staker, uint256 claimedTokens);
event Refund(address indexed staker, uint256 refundETH, uint256 completedWork);
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 Burnt(address indexed sender, uint256 value);
event Canceled(address indexed sender, uint256 value);
struct WorkInfo {
uint256 depositedETH;
uint256 completedWork;
bool claimed;
PreallocationEscrow preallocationEscrow;
}
NuCypherToken public token;
StakingEscrow public escrow;
StakingInterfaceRouter public router;
uint256 public startBidDate;
uint256 public endBidDate;
// ETH -> NU
uint256 public depositRate;
// Work (reward in NU) -> ETH
uint256 public refundRate;
uint256 public minAllowableLockedTokens;
uint256 public maxAllowableLockedTokens;
uint256 public allClaimedTokens;
uint16 public lockedPeriods;
/*
* @dev WorkLock calculations:
* depositRate = tokenSupply / ethSupply
* claimedTokens = depositedETH * depositRate
* refundRate = depositRate * SLOWING_REFUND / boostingRefund
* refundETH = completedWork / refundRate
*/
uint256 public boostingRefund;
uint16 public constant SLOWING_REFUND = 100;
uint256 private constant MAX_ETH_SUPPLY = 2e10 ether;
uint256 public tokenSupply;
uint256 public ethSupply;
uint256 public unclaimedTokens;
uint256 public lockingDuration;
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 _depositRate ETH -> NU rate
* @param _refundRate Work -> ETH rate
* @param _lockedPeriods Number of periods during which claimed tokens will be locked
* @param _boostingRefund Coefficient to boost refund ETH
* @param _lockingDuration Duration of tokens locking
*/
constructor(
NuCypherToken _token,
StakingEscrow _escrow,
StakingInterfaceRouter _router,
uint256 _startBidDate,
uint256 _endBidDate,
uint256 _depositRate,
uint256 _refundRate,
uint16 _lockedPeriods
uint256 _boostingRefund,
uint256 _lockingDuration
)
public
{
require(_token.totalSupply() > 0 &&
uint256 totalSupply = _token.totalSupply();
require(totalSupply > 0 &&
_escrow.secondsPerPeriod() > 0 &&
_router.target().isContract() &&
_endBidDate > _startBidDate &&
_endBidDate > block.timestamp &&
_depositRate > 0 &&
_refundRate > 0 &&
_lockedPeriods >= _escrow.minLockedPeriods());
_boostingRefund > 0 &&
_lockingDuration > 0);
// 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 &&
MAX_ETH_SUPPLY * totalSupply * _boostingRefund / MAX_ETH_SUPPLY / totalSupply == _boostingRefund);
token = _token;
escrow = _escrow;
router = _router;
startBidDate = _startBidDate;
endBidDate = _endBidDate;
minAllowableLockedTokens = _escrow.minAllowableLockedTokens();
maxAllowableLockedTokens = _escrow.maxAllowableLockedTokens();
depositRate = _depositRate;
refundRate = _refundRate;
lockedPeriods = _lockedPeriods;
boostingRefund = _boostingRefund;
lockingDuration = _lockingDuration;
}
/**
* @notice Bid for tokens by transferring ETH
*/
function bid() public payable returns (uint256 newClaimedTokens) {
require(block.timestamp >= startBidDate && block.timestamp <= endBidDate,
"Bid is open during a certain period");
WorkInfo storage info = workInfo[msg.sender];
info.depositedETH = info.depositedETH.add(msg.value);
uint256 claimedTokens = info.depositedETH.mul(depositRate);
require(claimedTokens >= minAllowableLockedTokens && claimedTokens <= maxAllowableLockedTokens,
"Claimed tokens must be within the allowed limits");
newClaimedTokens = msg.value.mul(depositRate);
allClaimedTokens = allClaimedTokens.add(newClaimedTokens);
require(allClaimedTokens <= token.balanceOf(address(this)),
"Not enough tokens in the contract");
emit Bid(msg.sender, msg.value, newClaimedTokens);
* @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);
tokenSupply += _value;
emit Deposited(msg.sender, _value);
}
/**
* @notice Claimed tokens will be deposited and locked as stake in the StakingEscrow contract.
*/
function claim() public returns (uint256 claimedTokens) {
require(block.timestamp >= endBidDate, "Claiming tokens allowed after bidding is over");
WorkInfo storage info = workInfo[msg.sender];
require(!info.claimed, "Tokens are already claimed");
info.claimed = true;
claimedTokens = info.depositedETH.mul(depositRate);
info.completedWork = escrow.setWorkMeasurement(msg.sender, true);
token.approve(address(escrow), claimedTokens);
escrow.deposit(msg.sender, claimedTokens, lockedPeriods);
emit Claimed(msg.sender, claimedTokens);
* @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);
}
/**
* @notice Refund ETH for the completed work
*/
function refund() public returns (uint256 refundETH) {
WorkInfo storage info = workInfo[msg.sender];
require(info.claimed, "Tokens are not claimed");
require(info.depositedETH > 0, "Nothing deposited");
uint256 currentWork = escrow.getCompletedWork(msg.sender);
uint256 completedWork = currentWork.sub(info.completedWork);
require(completedWork > 0, "No work that has been completed.");
refundETH = completedWork.div(refundRate);
if (refundETH > info.depositedETH) {
refundETH = info.depositedETH;
}
if (refundETH == info.depositedETH) {
escrow.setWorkMeasurement(msg.sender, false);
}
info.depositedETH = info.depositedETH.sub(refundETH);
completedWork = refundETH.mul(refundRate);
info.completedWork = info.completedWork.add(completedWork);
emit Refund(msg.sender, refundETH, completedWork);
msg.sender.sendValue(refundETH);
* @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));
}
/**
* @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));
}
/**
* @notice Get remaining work to full refund
*/
function getRemainingWork(address _staker) public view returns (uint256) {
WorkInfo storage info = workInfo[_staker];
uint256 completedWork = escrow.getCompletedWork(_staker).sub(info.completedWork);
uint256 remainingWork = info.depositedETH.mul(refundRate);
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);
uint256 remainingWork = ethToWork(info.depositedETH);
if (remainingWork <= completedWork) {
return 0;
}
return remainingWork.sub(completedWork);
}
/**
* @notice Bid for tokens by transferring ETH
*/
function bid() external payable {
require(block.timestamp >= startBidDate && block.timestamp <= endBidDate,
"Bid is open during a certain period");
WorkInfo storage info = workInfo[msg.sender];
info.depositedETH = info.depositedETH.add(msg.value);
ethSupply = ethSupply.add(msg.value);
emit Bid(msg.sender, msg.value);
}
/**
* @notice Cancel bid and refund deposited ETH
*/
function cancelBid() external {
// 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");
uint256 refundETH = info.depositedETH;
info.depositedETH = 0;
if (block.timestamp <= endBidDate) {
ethSupply = ethSupply.sub(refundETH);
} else {
unclaimedTokens = unclaimedTokens.add(ethToTokens(refundETH));
}
msg.sender.sendValue(refundETH);
emit Canceled(msg.sender, refundETH);
}
/**
* @notice Claimed tokens will be deposited and locked as stake in the StakingEscrow contract.
*/
function claim() external returns (PreallocationEscrow preallocationEscrow, 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");
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);
}
/**
* @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];
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 completedWork = currentWork.sub(info.completedWork);
require(completedWork > 0, "No work that has been completed.");
refundETH = workToETH(completedWork);
if (refundETH > info.depositedETH) {
refundETH = info.depositedETH;
}
if (refundETH == info.depositedETH) {
escrow.setWorkMeasurement(address(_preallocationEscrow), false);
}
info.depositedETH = info.depositedETH.sub(refundETH);
completedWork = ethToWork(refundETH);
info.completedWork = info.completedWork.add(completedWork);
emit Refund(msg.sender, address(_preallocationEscrow), 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");
token.approve(address(escrow), unclaimedTokens);
escrow.burn(unclaimedTokens);
emit Burnt(msg.sender, unclaimedTokens);
unclaimedTokens = 0;
}
}

View File

@ -61,6 +61,7 @@ def _admin_actor_options(func):
@click.option('--registry-infile', help="Input path for contract registry file", type=EXISTING_READABLE_FILE)
@click.option('--registry-outfile', help="Output path for contract registry file", type=click.Path(file_okay=True))
@click.option('--dev', '-d', help="Forcibly use the development registry filepath.", is_flag=True)
@click.option('--se-test-mode', help="Enable test mode for StakingEscrow in deployment.", is_flag=True)
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
@ -138,7 +139,7 @@ def inspect(provider_uri, config_root, registry_infile, deployer_address, poa):
@click.option('--ignore-deployed', help="Ignore already deployed contracts if exist.", is_flag=True)
def upgrade(# Admin Actor Options
provider_uri, contract_name, config_root, poa, force, etherscan, hw_wallet, deployer_address,
registry_infile, registry_outfile, dev,
registry_infile, registry_outfile, dev, se_test_mode,
# Other
retarget, target_address, ignore_deployed):
@ -165,7 +166,8 @@ def upgrade(# Admin Actor Options
registry_outfile,
hw_wallet,
dev,
force)
force,
se_test_mode)
if not contract_name:
raise click.BadArgumentUsage(message="--contract-name is required when using --upgrade")
@ -200,7 +202,7 @@ def upgrade(# Admin Actor Options
@_admin_actor_options
def rollback(# Admin Actor Options
provider_uri, contract_name, config_root, poa, force, etherscan, hw_wallet, deployer_address,
registry_infile, registry_outfile, dev):
registry_infile, registry_outfile, dev, se_test_mode):
"""
Rollback a proxy contract's target.
"""
@ -224,7 +226,8 @@ def rollback(# Admin Actor Options
registry_outfile,
hw_wallet,
dev,
force)
force,
se_test_mode)
if not contract_name:
raise click.BadArgumentUsage(message="--contract-name is required when using --rollback")
@ -242,7 +245,7 @@ def rollback(# Admin Actor Options
@click.option('--ignore-deployed', help="Ignore already deployed contracts if exist.", is_flag=True)
def contracts(# Admin Actor Options
provider_uri, contract_name, config_root, poa, force, etherscan, hw_wallet, deployer_address,
registry_infile, registry_outfile, dev,
registry_infile, registry_outfile, dev, se_test_mode,
# Other
bare, gas, ignore_deployed):
@ -269,7 +272,8 @@ def contracts(# Admin Actor Options
registry_outfile,
hw_wallet,
dev,
force)
force,
se_test_mode)
#
# Deploy Single Contract (Amend Registry)
@ -354,7 +358,7 @@ def contracts(# Admin Actor Options
type=click.Path(exists=False, file_okay=True))
def allocations(# Admin Actor Options
provider_uri, contract_name, config_root, poa, force, etherscan, hw_wallet, deployer_address,
registry_infile, registry_outfile, dev,
registry_infile, registry_outfile, dev, se_test_mode,
# Other
allocation_infile, allocation_outfile):
@ -381,7 +385,8 @@ def allocations(# Admin Actor Options
registry_outfile,
hw_wallet,
dev,
force)
force,
se_test_mode)
if not allocation_infile:
allocation_infile = click.prompt("Enter allocation data filepath")
@ -398,7 +403,7 @@ def allocations(# Admin Actor Options
@click.option('--value', help="Amount of tokens to transfer in the smallest denomination", type=click.INT)
def transfer_tokens(# Admin Actor Options
provider_uri, contract_name, config_root, poa, force, etherscan, hw_wallet, deployer_address,
registry_infile, registry_outfile, dev,
registry_infile, registry_outfile, dev, se_test_mode,
# Other
target_address, value):
@ -425,7 +430,8 @@ def transfer_tokens(# Admin Actor Options
registry_outfile,
hw_wallet,
dev,
force)
force,
se_test_mode)
token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=local_registry)
if not target_address:
@ -448,7 +454,7 @@ def transfer_tokens(# Admin Actor Options
@click.option('--gas', help="Operate with a specified gas per-transaction limit", type=click.IntRange(min=1))
def transfer_ownership(# Admin Actor Options
provider_uri, contract_name, config_root, poa, force, etherscan, hw_wallet, deployer_address,
registry_infile, registry_outfile, dev,
registry_infile, registry_outfile, dev, se_test_mode,
# Other
target_address, gas):
@ -475,7 +481,8 @@ def transfer_ownership(# Admin Actor Options
registry_outfile,
hw_wallet,
dev,
force)
force,
se_test_mode)
if not target_address:
target_address = click.prompt("Enter new owner's checksum address", type=EIP55_CHECKSUM_ADDRESS)
@ -498,7 +505,7 @@ def transfer_ownership(# Admin Actor Options
def _make_authenticated_deployment_actor(emitter, provider_uri, deployer_address, deployer_interface, contract_name,
registry_infile, registry_outfile, hw_wallet, dev, force):
registry_infile, registry_outfile, hw_wallet, dev, force, se_test_mode):
#
# Establish Registry
#
@ -525,7 +532,8 @@ def _make_authenticated_deployment_actor(emitter, provider_uri, deployer_address
# Produce Actor
ADMINISTRATOR = ContractAdministrator(registry=local_registry,
client_password=password,
deployer_address=deployer_address)
deployer_address=deployer_address,
staking_escrow_test_mode=se_test_mode)
# Verify ETH Balance
emitter.echo(f"\n\nDeployer ETH balance: {ADMINISTRATOR.eth_balance}")
if ADMINISTRATOR.eth_balance == 0:

View File

@ -220,7 +220,8 @@ class TesterBlockchain(BlockchainDeployerInterface):
origin = testerchain.client.etherbase
deployer = ContractAdministrator(deployer_address=origin,
registry=registry,
economics=economics or cls._default_token_economics)
economics=economics or cls._default_token_economics,
staking_escrow_test_mode=True)
secrets = dict()
for deployer_class in deployer.upgradeable_deployer_classes:
secrets[deployer_class.contract_name] = INSECURE_DEVELOPMENT_PASSWORD

View File

@ -75,14 +75,14 @@ def test_issuer(testerchain, token, deploy_contract):
events = issuer.events.Initialized.createFilter(fromBlock='latest')
# Give staker tokens for reward and initialize contract
tx = token.functions.transfer(issuer.address, economics.erc20_reward_supply).transact({'from': creator})
tx = token.functions.approve(issuer.address, economics.erc20_reward_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Only owner can initialize
with pytest.raises((TransactionFailed, ValueError)):
tx = issuer.functions.initialize().transact({'from': ursula})
tx = issuer.functions.initialize(0).transact({'from': ursula})
testerchain.wait_for_receipt(tx)
tx = issuer.functions.initialize().transact({'from': creator})
tx = issuer.functions.initialize(economics.erc20_reward_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
events = events.get_all_entries()
@ -92,7 +92,7 @@ def test_issuer(testerchain, token, deploy_contract):
# Can't initialize second time
with pytest.raises((TransactionFailed, ValueError)):
tx = issuer.functions.initialize().transact({'from': creator})
tx = issuer.functions.initialize(0).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Check result of minting tokens
@ -152,9 +152,9 @@ def test_inflation_rate(testerchain, token, deploy_contract):
)
# Give staker tokens for reward and initialize contract
tx = token.functions.transfer(issuer.address, economics.erc20_reward_supply).transact({'from': creator})
tx = token.functions.approve(issuer.address, economics.erc20_reward_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = issuer.functions.initialize().transact({'from': creator})
tx = issuer.functions.initialize(economics.erc20_reward_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
reward = issuer.functions.getReservedReward().call()
@ -193,9 +193,20 @@ def test_inflation_rate(testerchain, token, deploy_contract):
# Return some tokens as a reward
balance = token.functions.balanceOf(ursula).call()
reward = issuer.functions.getReservedReward().call()
tx = issuer.functions.testUnMint(2 * one_period + 2 * minted_amount).transact()
amount_to_burn = 2 * one_period + 2 * minted_amount
tx = token.functions.transfer(ursula, amount_to_burn).transact({'from': creator})
testerchain.wait_for_receipt(tx)
assert reward + 2 * one_period + 2 * minted_amount == issuer.functions.getReservedReward().call()
tx = token.functions.approve(issuer.address, amount_to_burn).transact({'from': ursula})
testerchain.wait_for_receipt(tx)
tx = issuer.functions.testBurn(amount_to_burn).transact({'from': ursula})
testerchain.wait_for_receipt(tx)
assert reward + amount_to_burn == issuer.functions.getReservedReward().call()
events = issuer.events.Burnt.createFilter(fromBlock=0).get_all_entries()
assert 1 == len(events)
event_args = events[0]['args']
assert ursula == event_args['sender']
assert amount_to_burn == event_args['value']
# Rate will be increased because some tokens were returned
tx = issuer.functions.testMint(period + 3, 1, 1, 0).transact({'from': ursula})
@ -247,9 +258,9 @@ def test_upgrading(testerchain, token, deploy_contract):
testerchain.wait_for_receipt(tx)
# Give tokens for reward and initialize contract
tx = token.functions.transfer(contract.address, 10000).transact({'from': creator})
tx = token.functions.approve(contract.address, 10000).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = contract.functions.initialize().transact({'from': creator})
tx = contract.functions.initialize(10000).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Upgrade to the second version, check new and old values of variables

View File

@ -45,8 +45,8 @@ contract IssuerMock is Issuer {
token.transfer(msg.sender, amount);
}
function testUnMint(uint256 _amount) public {
unMint(_amount);
function testBurn(uint256 _value) public {
burn(_value);
}
}

View File

@ -19,7 +19,8 @@ contract StakingEscrowBad is StakingEscrow {
uint16 _minLockedPeriods,
uint256 _minAllowableLockedTokens,
uint256 _maxAllowableLockedTokens,
uint16 _minWorkerPeriods
uint16 _minWorkerPeriods,
bool _isTestContract
)
public
StakingEscrow(
@ -31,7 +32,8 @@ contract StakingEscrowBad is StakingEscrow {
_minLockedPeriods,
_minAllowableLockedTokens,
_maxAllowableLockedTokens,
_minWorkerPeriods
_minWorkerPeriods,
_isTestContract
)
{
}
@ -60,6 +62,7 @@ contract StakingEscrowV2Mock is StakingEscrow {
uint256 _minAllowableLockedTokens,
uint256 _maxAllowableLockedTokens,
uint16 _minWorkerPeriods,
bool _isTestContract,
uint256 _valueToCheck
)
public
@ -72,7 +75,8 @@ contract StakingEscrowV2Mock is StakingEscrow {
_minLockedPeriods,
_minAllowableLockedTokens,
_maxAllowableLockedTokens,
_minWorkerPeriods
_minWorkerPeriods,
_isTestContract
)
{
valueToCheck = _valueToCheck;
@ -90,6 +94,7 @@ contract StakingEscrowV2Mock is StakingEscrow {
function finishUpgrade(address _target) public onlyWhileUpgrading {
StakingEscrowV2Mock escrow = StakingEscrowV2Mock(_target);
valueToCheck = escrow.valueToCheck();
isTestContract = escrow.isTestContract();
emit UpgradeFinished(_target, msg.sender);
}
}

View File

@ -56,4 +56,14 @@ contract StakingEscrowForWorkLockMock {
stakerInfo[_staker].completedWork = _completedWork;
}
function burn(uint256 _value) public {
token.transferFrom(msg.sender, address(this), _value);
}
}
/**
* @notice Contract for using in WorkLock tests
**/
contract StakingInterfaceMock {}

View File

@ -20,7 +20,7 @@ import os
import pytest
from eth_tester.exceptions import TransactionFailed
from eth_utils import to_canonical_address
from eth_utils import to_canonical_address, to_wei
from web3.contract import Contract
from nucypher.blockchain.economics import TokenEconomics
@ -70,7 +70,7 @@ def token(token_economics, deploy_contract):
def escrow(testerchain, token, token_economics, deploy_contract):
# Creator deploys the escrow
contract, _ = deploy_contract(
'StakingEscrow', token.address, *token_economics.staking_deployment_parameters
'StakingEscrow', token.address, *token_economics.staking_deployment_parameters, True
)
secret_hash = testerchain.w3.keccak(escrow_secret)
@ -134,34 +134,6 @@ def adjudicator(testerchain, escrow, token_economics, deploy_contract):
return contract, dispatcher
@pytest.fixture()
def worklock(testerchain, token, escrow, deploy_contract):
escrow, _ = escrow
creator = testerchain.w3.eth.accounts[0]
# Creator deploys the worklock using test values
now = testerchain.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, _ = 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=6
)
tx = escrow.functions.setWorkLock(contract.address).transact({'from': creator})
testerchain.wait_for_receipt(tx)
return contract
def mock_ursula(testerchain, account, mocker):
ursula_privkey = UmbralPrivateKey.gen_key()
ursula_stamp = SignatureStamp(verifying_key=ursula_privkey.pubkey,
@ -195,6 +167,35 @@ def staking_interface(testerchain, token, escrow, policy_manager, deploy_contrac
return staking_interface, router
@pytest.fixture()
def worklock(testerchain, token, escrow, staking_interface, deploy_contract):
escrow, _ = escrow
creator = testerchain.w3.eth.accounts[0]
_, router = staking_interface
# Creator deploys the worklock using test values
now = testerchain.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
boosting_refund = 100
locking_duration = 20 * 60 * 60
contract, _ = 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
)
tx = escrow.functions.setWorkLock(contract.address).transact({'from': creator})
testerchain.wait_for_receipt(tx)
return contract
@pytest.fixture()
def multisig(testerchain, escrow, policy_manager, adjudicator, staking_interface, deploy_contract):
escrow, escrow_dispatcher = escrow
@ -268,6 +269,14 @@ def test_all(testerchain,
testerchain.client.accounts
contracts_owners = sorted(contracts_owners)
# Create the first preallocation escrow
preallocation_escrow_1, _ = deploy_contract(
'PreallocationEscrow', staking_interface_router.address, token.address, escrow.address)
preallocation_escrow_interface_1 = testerchain.client.get_contract(
abi=staking_interface.abi,
address=preallocation_escrow_1.address,
ContractFactoryClass=Contract)
# We'll need this later for slashing these Ursulas
ursula1_with_stamp = mock_ursula(testerchain, ursula1, mocker=mocker)
ursula2_with_stamp = mock_ursula(testerchain, ursula2, mocker=mocker)
@ -307,50 +316,61 @@ def test_all(testerchain,
testerchain.wait_for_receipt(tx)
# Initialize escrow
tx = token.functions.transfer(escrow.address, token_economics.erc20_reward_supply).transact({'from': creator})
tx = token.functions.transfer(multisig.address, token_economics.erc20_reward_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.initialize().buildTransaction({'from': multisig.address, 'gasPrice': 0})
tx = token.functions.approve(escrow.address, token_economics.erc20_reward_supply)\
.buildTransaction({'from': multisig.address, 'gasPrice': 0})
execute_multisig_transaction(testerchain, multisig, [contracts_owners[0], contracts_owners[1]], tx)
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)
# Initialize worklock
initial_supply = 1000
tx = token.functions.transfer(worklock.address, initial_supply).transact({'from': creator})
worklock_supply = 1980
tx = token.functions.approve(worklock.address, worklock_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = worklock.functions.tokenDeposit(worklock_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
deposited_eth_1 = to_wei(18, 'ether')
deposited_eth_2 = to_wei(1, 'ether')
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.bid().transact({'from': ursula2, 'value': deposited_eth, 'gas_price': 0})
tx = worklock.functions.bid().transact({'from': ursula2, 'value': deposited_eth_1, '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.w3.eth.getBalance(worklock.address) == 0
tx = worklock.functions.bid().transact({'from': ursula2, 'value': deposited_eth, 'gas_price': 0})
tx = worklock.functions.bid().transact({'from': ursula2, 'value': deposited_eth_1, '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.w3.eth.getBalance(worklock.address) == deposited_eth
assert worklock.functions.workInfo(ursula2).call()[0] == deposited_eth_1
assert testerchain.w3.eth.getBalance(worklock.address) == deposited_eth_1
assert worklock.functions.ethToTokens(deposited_eth_1).call() == worklock_supply
# 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)
# Other Ursula do bid
assert worklock.functions.workInfo(ursula1).call()[0] == 0
tx = worklock.functions.bid().transact({'from': ursula1, 'value': deposited_eth_2, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.workInfo(ursula1).call()[0] == deposited_eth_2
assert testerchain.w3.eth.getBalance(worklock.address) == deposited_eth_1 + deposited_eth_2
assert worklock.functions.ethToTokens(deposited_eth_2).call() == worklock_supply // 19
assert worklock.functions.workInfo(ursula4).call()[0] == 0
tx = worklock.functions.bid().transact({'from': ursula4, 'value': deposited_eth_2, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.workInfo(ursula4).call()[0] == deposited_eth_2
assert testerchain.w3.eth.getBalance(worklock.address) == deposited_eth_1 + 2 * deposited_eth_2
assert worklock.functions.ethToTokens(deposited_eth_2).call() == worklock_supply // 20
# Wait for the end of the bidding
testerchain.time_travel(hours=1)
@ -359,29 +379,81 @@ def test_all(testerchain,
tx = worklock.functions.bid().transact({'from': ursula2, 'value': 1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# One of Ursulas cancels bid
tx = worklock.functions.cancelBid().transact({'from': ursula1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.workInfo(ursula1).call()[0] == 0
assert testerchain.w3.eth.getBalance(worklock.address) == deposited_eth_1 + deposited_eth_2
assert worklock.functions.ethToTokens(deposited_eth_2).call() == worklock_supply // 20
assert worklock.functions.unclaimedTokens().call() == worklock_supply // 20
# 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 token_economics.erc20_reward_supply + 1000 == token.functions.balanceOf(escrow.address).call()
assert 1000 == escrow.functions.getAllTokens(ursula2).call()
assert 0 == escrow.functions.getLockedTokens(ursula2, 0).call()
assert 1000 == escrow.functions.getLockedTokens(ursula2, 1).call()
assert 1000 == escrow.functions.getLockedTokens(ursula2, 6).call()
assert 0 == escrow.functions.getLockedTokens(ursula2, 7).call()
assert 0 == escrow.functions.getCompletedWork(ursula2).call()
preallocation_escrow_2 = testerchain.client.get_contract(
abi=preallocation_escrow_1.abi,
address=worklock.functions.workInfo(ursula2).call()[2],
ContractFactoryClass=Contract)
tx = escrow.functions.setWorker(ursula2).transact({'from': ursula2})
ursula2_tokens = worklock_supply * 9 // 10
assert token.functions.balanceOf(ursula2).call() == 0
assert token.functions.balanceOf(preallocation_escrow_2.address).call() == ursula2_tokens
assert preallocation_escrow_2.functions.owner().call() == ursula2
assert preallocation_escrow_2.functions.getLockedTokens().call() == ursula2_tokens
ursula2_remaining_work = ursula2_tokens
assert worklock.functions.ethToWork(deposited_eth_1).call() == ursula2_remaining_work
assert worklock.functions.workToETH(ursula2_remaining_work).call() == deposited_eth_1
assert worklock.functions.getRemainingWork(preallocation_escrow_2.address).call() == ursula2_remaining_work
assert token.functions.balanceOf(worklock.address).call() == worklock_supply - ursula2_tokens
preallocation_escrow_interface_2 = testerchain.client.get_contract(
abi=staking_interface.abi,
address=preallocation_escrow_2.address,
ContractFactoryClass=Contract)
tx = preallocation_escrow_interface_2.functions.depositAsStaker(1000, 6).transact({'from': ursula2})
testerchain.wait_for_receipt(tx)
tx = preallocation_escrow_interface_2.functions.setWorker(ursula2).transact({'from': ursula2})
testerchain.wait_for_receipt(tx)
escrow_balance = token_economics.erc20_reward_supply + 1000
assert 1000 == escrow.functions.getAllTokens(preallocation_escrow_2.address).call()
assert 0 == escrow.functions.getLockedTokens(preallocation_escrow_2.address, 0).call()
assert 1000 == escrow.functions.getLockedTokens(preallocation_escrow_2.address, 1).call()
assert 1000 == escrow.functions.getLockedTokens(preallocation_escrow_2.address, 6).call()
assert 0 == escrow.functions.getLockedTokens(preallocation_escrow_2.address, 7).call()
assert 0 == escrow.functions.getCompletedWork(preallocation_escrow_2.address).call()
# Another Ursula claims tokens
tx = worklock.functions.claim().transact({'from': ursula4, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
preallocation_escrow_3 = testerchain.client.get_contract(
abi=preallocation_escrow_1.abi,
address=worklock.functions.workInfo(ursula4).call()[2],
ContractFactoryClass=Contract)
ursula4_tokens = worklock_supply // 20
assert token.functions.balanceOf(ursula4).call() == 0
assert token.functions.balanceOf(preallocation_escrow_3.address).call() == ursula4_tokens
assert preallocation_escrow_3.functions.owner().call() == ursula4
assert preallocation_escrow_3.functions.getLockedTokens().call() == ursula4_tokens
assert token.functions.balanceOf(worklock.address).call() == worklock_supply - ursula2_tokens - ursula4_tokens
# Burn remaining tokens in WorkLock
tx = worklock.functions.burnUnclaimed().transact({'from': creator})
testerchain.wait_for_receipt(tx)
escrow_balance += worklock_supply // 20
assert 0 == worklock.functions.unclaimedTokens().call()
assert 0 == token.functions.balanceOf(worklock.address).call()
assert escrow_balance == token.functions.balanceOf(escrow.address).call()
assert token_economics.erc20_reward_supply + worklock_supply // 20 == escrow.functions.getReservedReward().call()
# Ursula prolongs lock duration
tx = escrow.functions.prolongStake(0, 3).transact({'from': ursula2, 'gas_price': 0})
tx = preallocation_escrow_interface_2.functions.prolongStake(0, 3).transact({'from': ursula2, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert 0 == escrow.functions.getLockedTokens(ursula2, 0).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.getCompletedWork(ursula2).call()
assert 0 == escrow.functions.getLockedTokens(preallocation_escrow_2.address, 0).call()
assert 1000 == escrow.functions.getLockedTokens(preallocation_escrow_2.address, 1).call()
assert 1000 == escrow.functions.getLockedTokens(preallocation_escrow_2.address, 9).call()
assert 0 == escrow.functions.getLockedTokens(preallocation_escrow_2.address, 10).call()
assert 0 == escrow.functions.getCompletedWork(preallocation_escrow_2.address).call()
# Can't claim more than once
with pytest.raises((TransactionFailed, ValueError)):
@ -389,16 +461,10 @@ def test_all(testerchain,
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})
tx = worklock.functions.refund(preallocation_escrow_2.address).transact({'from': ursula2, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Create the first preallocation escrow, set and lock re-stake parameter
preallocation_escrow_1, _ = deploy_contract(
'PreallocationEscrow', staking_interface_router.address, token.address, escrow.address)
preallocation_escrow_interface_1 = testerchain.client.get_contract(
abi=staking_interface.abi,
address=preallocation_escrow_1.address,
ContractFactoryClass=Contract)
# Set and lock re-stake parameter in first preallocation escrow
tx = preallocation_escrow_1.functions.transferOwnership(ursula3).transact({'from': creator})
testerchain.wait_for_receipt(tx)
assert not escrow.functions.stakerInfo(preallocation_escrow_1.address).call()[RE_STAKE_FIELD]
@ -419,22 +485,10 @@ def test_all(testerchain,
tx = preallocation_escrow_1.functions.initialDeposit(10000, 20 * 60 * 60).transact({'from': creator})
testerchain.wait_for_receipt(tx)
preallocation_escrow_2, _ = deploy_contract(
'PreallocationEscrow', staking_interface_router.address, token.address, escrow.address)
tx = preallocation_escrow_2.functions.transferOwnership(ursula4).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = token.functions.approve(preallocation_escrow_2.address, 10000).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = preallocation_escrow_2.functions.initialDeposit(10000, 20 * 60 * 60).transact({'from': creator})
testerchain.wait_for_receipt(tx)
assert 10000 == token.functions.balanceOf(preallocation_escrow_1.address).call()
assert ursula3 == preallocation_escrow_1.functions.owner().call()
assert 10000 >= preallocation_escrow_1.functions.getLockedTokens().call()
assert 9500 <= preallocation_escrow_1.functions.getLockedTokens().call()
assert 10000 == token.functions.balanceOf(preallocation_escrow_2.address).call()
assert ursula4 == preallocation_escrow_2.functions.owner().call()
assert 10000 >= preallocation_escrow_2.functions.getLockedTokens().call()
assert 9500 <= preallocation_escrow_2.functions.getLockedTokens().call()
# Ursula's withdrawal attempt won't succeed because nothing to withdraw
with pytest.raises((TransactionFailed, ValueError)):
@ -453,6 +507,7 @@ def test_all(testerchain,
assert 0 == escrow.functions.getLockedTokens(ursula4, 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(preallocation_escrow_3.address, 0).call()
assert 0 == escrow.functions.getLockedTokens(contracts_owners[0], 0).call()
# Ursula can't deposit and lock too low value
@ -476,7 +531,8 @@ def test_all(testerchain,
testerchain.wait_for_receipt(tx)
tx = escrow.functions.confirmActivity().transact({'from': ursula1})
testerchain.wait_for_receipt(tx)
assert token_economics.erc20_reward_supply + 2000 == token.functions.balanceOf(escrow.address).call()
escrow_balance += 1000
assert escrow_balance == token.functions.balanceOf(escrow.address).call()
assert 9000 == token.functions.balanceOf(ursula1).call()
assert 0 == escrow.functions.getLockedTokens(ursula1, 0).call()
assert 1000 == escrow.functions.getLockedTokens(ursula1, 1).call()
@ -491,12 +547,13 @@ def test_all(testerchain,
testerchain.wait_for_receipt(tx)
tx = escrow.functions.confirmActivity().transact({'from': ursula3})
testerchain.wait_for_receipt(tx)
escrow_balance += 1000
assert 1000 == escrow.functions.getAllTokens(preallocation_escrow_1.address).call()
assert 0 == escrow.functions.getLockedTokens(preallocation_escrow_1.address, 0).call()
assert 1000 == escrow.functions.getLockedTokens(preallocation_escrow_1.address, 1).call()
assert 1000 == escrow.functions.getLockedTokens(preallocation_escrow_1.address, 10).call()
assert 0 == escrow.functions.getLockedTokens(preallocation_escrow_1.address, 11).call()
assert token_economics.erc20_reward_supply + 3000 == token.functions.balanceOf(escrow.address).call()
assert escrow_balance == token.functions.balanceOf(escrow.address).call()
assert 9000 == token.functions.balanceOf(preallocation_escrow_1.address).call()
# Only owner can deposit tokens to the staking escrow
@ -509,7 +566,7 @@ def test_all(testerchain,
testerchain.wait_for_receipt(tx)
# Divide stakes
tx = escrow.functions.divideStake(0, 500, 6).transact({'from': ursula2})
tx = preallocation_escrow_interface_2.functions.divideStake(0, 500, 6).transact({'from': ursula2})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.divideStake(0, 500, 9).transact({'from': ursula1})
testerchain.wait_for_receipt(tx)
@ -544,12 +601,13 @@ def test_all(testerchain,
# Create policies
policy_id_1 = os.urandom(16)
tx = policy_manager.functions.createPolicy(policy_id_1, 5, 44, [ursula1, ursula2]) \
tx = policy_manager.functions.createPolicy(policy_id_1, 5, 44, [ursula1, preallocation_escrow_2.address]) \
.transact({'from': alice1, 'value': 2 * 1000 + 2 * 44, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
policy_id_2 = os.urandom(16)
tx = policy_manager.functions.createPolicy(policy_id_2, 5, 44, [ursula2, preallocation_escrow_1.address]) \
tx = policy_manager.functions\
.createPolicy(policy_id_2, 5, 44, [preallocation_escrow_2.address, preallocation_escrow_1.address]) \
.transact({'from': alice1, 'value': 2 * 1000 + 2 * 44, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
@ -559,12 +617,13 @@ def test_all(testerchain,
testerchain.wait_for_receipt(tx)
policy_id_4 = os.urandom(16)
tx = policy_manager.functions.createPolicy(policy_id_4, 5, 44, [ursula2, preallocation_escrow_1.address]) \
tx = policy_manager.functions\
.createPolicy(policy_id_4, 5, 44, [preallocation_escrow_2.address, preallocation_escrow_1.address]) \
.transact({'from': alice2, 'value': 2 * 1000 + 2 * 44, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
policy_id_5 = os.urandom(16)
tx = policy_manager.functions.createPolicy(policy_id_5, 5, 44, [ursula1, ursula2]) \
tx = policy_manager.functions.createPolicy(policy_id_5, 5, 44, [ursula1, preallocation_escrow_2.address]) \
.transact({'from': alice2, 'value': 2 * 1000 + 2 * 44, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
@ -589,8 +648,8 @@ def test_all(testerchain,
testerchain.wait_for_receipt(tx)
alice1_balance = testerchain.client.get_balance(alice1)
tx = policy_manager.functions.revokeArrangement(policy_id_2, ursula2).transact({'from': alice1, 'gas_price': 0})
tx = policy_manager.functions.revokeArrangement(policy_id_2, preallocation_escrow_2.address)\
.transact({'from': alice1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert 7440 == testerchain.client.get_balance(policy_manager.address)
assert alice1_balance + 1000 == testerchain.client.get_balance(alice1)
@ -598,7 +657,8 @@ def test_all(testerchain,
# Can't revoke again
with pytest.raises((TransactionFailed, ValueError)):
tx = policy_manager.functions.revokeArrangement(policy_id_2, ursula2).transact({'from': alice1})
tx = policy_manager.functions.revokeArrangement(policy_id_2, preallocation_escrow_2.address)\
.transact({'from': alice1})
testerchain.wait_for_receipt(tx)
# Wait, confirm activity, mint
@ -611,8 +671,8 @@ def test_all(testerchain,
testerchain.wait_for_receipt(tx)
# Check work measurement
work_done = escrow.functions.getCompletedWork(ursula2).call()
assert 0 < work_done
completed_work = escrow.functions.getCompletedWork(preallocation_escrow_2.address).call()
assert 0 < completed_work
assert 0 == escrow.functions.getCompletedWork(preallocation_escrow_1.address).call()
assert 0 == escrow.functions.getCompletedWork(ursula1).call()
@ -649,7 +709,7 @@ def test_all(testerchain,
testerchain.wait_for_receipt(tx)
assert ursula1_balance < testerchain.client.get_balance(ursula1)
ursula2_balance = testerchain.client.get_balance(ursula2)
tx = policy_manager.functions.withdraw().transact({'from': ursula2, 'gas_price': 0})
tx = preallocation_escrow_interface_2.functions.withdrawPolicyReward(ursula2).transact({'from': ursula2, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert ursula2_balance < testerchain.client.get_balance(ursula2)
ursula3_balance = testerchain.client.get_balance(ursula3)
@ -682,7 +742,7 @@ def test_all(testerchain,
policy_manager_v1 = policy_manager.functions.target().call()
# Creator deploys the contracts as the second versions
escrow_v2, _ = deploy_contract(
'StakingEscrow', token.address, *token_economics.staking_deployment_parameters
'StakingEscrow', token.address, *token_economics.staking_deployment_parameters, False
)
policy_manager_v2, _ = deploy_contract('PolicyManager', escrow.address)
# Ursula and Alice can't upgrade contracts, only owner can
@ -850,22 +910,22 @@ def test_all(testerchain,
assert alice1_balance + base_penalty / reward_coefficient == token.functions.balanceOf(alice1).call()
# Slash part of the one sub stake
tokens_amount = escrow.functions.getAllTokens(ursula2).call()
unlocked_amount = tokens_amount - escrow.functions.getLockedTokens(ursula2, 0).call()
tx = escrow.functions.withdraw(unlocked_amount).transact({'from': ursula2})
tokens_amount = escrow.functions.getAllTokens(preallocation_escrow_2.address).call()
unlocked_amount = tokens_amount - escrow.functions.getLockedTokens(preallocation_escrow_2.address, 0).call()
tx = preallocation_escrow_interface_2.functions.withdrawAsStaker(unlocked_amount).transact({'from': ursula2})
testerchain.wait_for_receipt(tx)
previous_lock = escrow.functions.getLockedTokensInPast(ursula2, 1).call()
lock = escrow.functions.getLockedTokens(ursula2, 0).call()
next_lock = escrow.functions.getLockedTokens(ursula2, 1).call()
previous_lock = escrow.functions.getLockedTokensInPast(preallocation_escrow_2.address, 1).call()
lock = escrow.functions.getLockedTokens(preallocation_escrow_2.address, 0).call()
next_lock = escrow.functions.getLockedTokens(preallocation_escrow_2.address, 1).call()
data_hash, slashing_args = generate_args_for_slashing(mock_ursula_reencrypts, ursula2_with_stamp)
assert not adjudicator.functions.evaluatedCFrags(data_hash).call()
tx = adjudicator.functions.evaluateCFrag(*slashing_args).transact({'from': alice1})
testerchain.wait_for_receipt(tx)
assert adjudicator.functions.evaluatedCFrags(data_hash).call()
assert lock - base_penalty == escrow.functions.getAllTokens(ursula2).call()
assert previous_lock == escrow.functions.getLockedTokensInPast(ursula2, 1).call()
assert lock - base_penalty == escrow.functions.getLockedTokens(ursula2, 0).call()
assert next_lock - base_penalty == escrow.functions.getLockedTokens(ursula2, 1).call()
assert lock - base_penalty == escrow.functions.getAllTokens(preallocation_escrow_2.address).call()
assert previous_lock == escrow.functions.getLockedTokensInPast(preallocation_escrow_2.address, 1).call()
assert lock - base_penalty == escrow.functions.getLockedTokens(preallocation_escrow_2.address, 0).call()
assert next_lock - base_penalty == escrow.functions.getLockedTokens(preallocation_escrow_2.address, 1).call()
assert total_previous_lock == escrow.functions.lockedPerPeriod(current_period - 1).call()
assert total_lock - base_penalty == escrow.functions.lockedPerPeriod(current_period).call()
assert 0 == escrow.functions.lockedPerPeriod(current_period + 1).call()
@ -986,7 +1046,7 @@ def test_all(testerchain,
# Can't prolong stake by too low duration
with pytest.raises((TransactionFailed, ValueError)):
tx = escrow.functions.prolongStake(0, 1).transact({'from': ursula2, 'gas_price': 0})
tx = preallocation_escrow_interface_2.functions.prolongStake(0, 1).transact({'from': ursula2, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Unlock and withdraw all tokens
@ -1012,7 +1072,7 @@ def test_all(testerchain,
tx = escrow.functions.mint().transact({'from': ursula1})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.mint().transact({'from': ursula2})
tx = preallocation_escrow_interface_2.functions.mint().transact({'from': ursula2})
testerchain.wait_for_receipt(tx)
tx = preallocation_escrow_interface_1.functions.mint().transact({'from': ursula3})
testerchain.wait_for_receipt(tx)
@ -1023,51 +1083,51 @@ def test_all(testerchain,
assert 0 == escrow.functions.getLockedTokens(ursula4, 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(preallocation_escrow_3.address, 0).call()
ursula1_balance = token.functions.balanceOf(ursula1).call()
ursula2_balance = token.functions.balanceOf(ursula2).call()
ursula2_balance = token.functions.balanceOf(preallocation_escrow_2.address).call()
preallocation_escrow_1_balance = token.functions.balanceOf(preallocation_escrow_1.address).call()
tokens_amount = escrow.functions.getAllTokens(ursula1).call()
tx = escrow.functions.withdraw(tokens_amount).transact({'from': ursula1})
testerchain.wait_for_receipt(tx)
tokens_amount = escrow.functions.getAllTokens(ursula2).call()
tx = escrow.functions.withdraw(tokens_amount).transact({'from': ursula2})
tokens_amount = escrow.functions.getAllTokens(preallocation_escrow_2.address).call()
tx = preallocation_escrow_interface_2.functions.withdrawAsStaker(tokens_amount).transact({'from': ursula2})
testerchain.wait_for_receipt(tx)
tokens_amount = escrow.functions.getAllTokens(preallocation_escrow_1.address).call()
tx = preallocation_escrow_interface_1.functions.withdrawAsStaker(tokens_amount).transact({'from': ursula3})
testerchain.wait_for_receipt(tx)
assert ursula1_balance < token.functions.balanceOf(ursula1).call()
assert ursula2_balance < token.functions.balanceOf(ursula2).call()
assert ursula2_balance < token.functions.balanceOf(preallocation_escrow_2.address).call()
assert preallocation_escrow_1_balance < token.functions.balanceOf(preallocation_escrow_1.address).call()
# Unlock and withdraw all tokens in PreallocationEscrow
testerchain.time_travel(hours=1)
assert 0 == preallocation_escrow_1.functions.getLockedTokens().call()
assert 0 == preallocation_escrow_2.functions.getLockedTokens().call()
assert 0 == preallocation_escrow_3.functions.getLockedTokens().call()
ursula3_balance = token.functions.balanceOf(ursula3).call()
ursula4_balance = token.functions.balanceOf(ursula4).call()
tokens_amount = token.functions.balanceOf(preallocation_escrow_1.address).call()
tx = preallocation_escrow_1.functions.withdrawTokens(tokens_amount).transact({'from': ursula3})
testerchain.wait_for_receipt(tx)
tokens_amount = token.functions.balanceOf(preallocation_escrow_2.address).call()
tx = preallocation_escrow_2.functions.withdrawTokens(tokens_amount).transact({'from': ursula4})
tokens_amount = token.functions.balanceOf(preallocation_escrow_3.address).call()
tx = preallocation_escrow_3.functions.withdrawTokens(tokens_amount).transact({'from': ursula4})
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
new_work_done = escrow.functions.getCompletedWork(ursula2).call()
assert work_done < new_work_done
remaining_work = worklock.functions.getRemainingWork(ursula2).call()
new_completed_work = escrow.functions.getCompletedWork(preallocation_escrow_2.address).call()
assert completed_work < new_completed_work
remaining_work = worklock.functions.getRemainingWork(preallocation_escrow_2.address).call()
assert 0 < remaining_work
assert deposited_eth == worklock.functions.workInfo(ursula2).call()[0]
assert deposited_eth_1 == worklock.functions.workInfo(ursula2).call()[0]
ursula2_balance = testerchain.w3.eth.getBalance(ursula2)
tx = worklock.functions.refund().transact({'from': ursula2, 'gas_price': 0})
tx = worklock.functions.refund(preallocation_escrow_2.address).transact({'from': ursula2, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
refund = new_work_done // refund_rate
assert deposited_eth - refund == worklock.functions.workInfo(ursula2).call()[0]
refund = worklock.functions.workToETH(new_completed_work).call()
assert deposited_eth_1 - refund == worklock.functions.workInfo(ursula2).call()[0]
assert refund + ursula2_balance == testerchain.w3.eth.getBalance(ursula2)
assert remaining_work == worklock.functions.getRemainingWork(ursula2).call()
assert deposited_eth - refund == testerchain.w3.eth.getBalance(worklock.address)
assert deposited_eth_1 + deposited_eth_2 - refund == testerchain.w3.eth.getBalance(worklock.address)
assert 0 == escrow.functions.getCompletedWork(ursula1).call()
assert 0 == escrow.functions.getCompletedWork(preallocation_escrow_1.address).call()

View File

@ -55,6 +55,7 @@ def escrow_contract(testerchain, token, token_economics, request, deploy_contrac
# Creator deploys the escrow
deploy_parameters = list(token_economics.staking_deployment_parameters)
deploy_parameters[-2] = max_allowed_locked_tokens
deploy_parameters.append(True)
contract, _ = deploy_contract('StakingEscrow', token.address, *deploy_parameters)
if request.param:

View File

@ -49,9 +49,9 @@ def test_mining(testerchain, token, escrow_contract, token_economics):
withdraw_log = escrow.events.Withdrawn.createFilter(fromBlock='latest')
# Give Escrow tokens for reward and initialize contract
tx = token.functions.transfer(escrow.address, token_economics.erc20_reward_supply).transact({'from': creator})
tx = token.functions.approve(escrow.address, token_economics.erc20_reward_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.initialize().transact({'from': creator})
tx = escrow.functions.initialize(token_economics.erc20_reward_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Give Ursula and Ursula(2) some coins
@ -366,9 +366,9 @@ def test_slashing(testerchain, token, escrow_contract, token_economics, deploy_c
slashing_log = escrow.events.Slashed.createFilter(fromBlock='latest')
# Give Escrow tokens for reward and initialize contract
tx = token.functions.transfer(escrow.address, token_economics.erc20_reward_supply).transact({'from': creator})
tx = token.functions.approve(escrow.address, token_economics.erc20_reward_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.initialize().transact({'from': creator})
tx = escrow.functions.initialize(token_economics.erc20_reward_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Give Ursula deposit some tokens

View File

@ -77,7 +77,7 @@ def test_staking(testerchain, token, escrow_contract):
testerchain.wait_for_receipt(tx)
# Initialize Escrow contract
tx = escrow.functions.initialize().transact({'from': creator})
tx = escrow.functions.initialize(0).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Ursula can't deposit and lock too low value (less than _minAllowableLockedTokens coefficient)
@ -550,7 +550,7 @@ def test_max_sub_stakes(testerchain, token, escrow_contract):
ursula = testerchain.client.accounts[1]
# Initialize Escrow contract
tx = escrow.functions.initialize().transact({'from': creator})
tx = escrow.functions.initialize(0).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Prepare before deposit

View File

@ -42,7 +42,7 @@ def test_upgrading(testerchain, token, token_economics, deploy_contract):
# Deploy contract
contract_library_v1, _ = deploy_contract(
'StakingEscrow', token.address, *token_economics.staking_deployment_parameters
'StakingEscrow', token.address, *token_economics.staking_deployment_parameters, True
)
dispatcher, _ = deploy_contract('Dispatcher', contract_library_v1.address, secret_hash)
@ -58,6 +58,7 @@ def test_upgrading(testerchain, token, token_economics, deploy_contract):
_minAllowableLockedTokens=2,
_maxAllowableLockedTokens=2,
_minWorkerPeriods=2,
_isTestContract=False,
_valueToCheck=2
)
@ -66,6 +67,7 @@ def test_upgrading(testerchain, token, token_economics, deploy_contract):
address=dispatcher.address,
ContractFactoryClass=Contract)
assert token_economics.maximum_allowed_locked == contract.functions.maxAllowableLockedTokens().call()
assert contract.functions.isTestContract().call()
# Can't call `finishUpgrade` and `verifyState` methods outside upgrade lifecycle
with pytest.raises((TransactionFailed, ValueError)):
@ -94,9 +96,9 @@ def test_upgrading(testerchain, token, token_economics, deploy_contract):
tx = contract.functions.setWorkLock(worklock.address).transact()
testerchain.wait_for_receipt(tx)
tx = token.functions.transfer(contract.address, token_economics.erc20_reward_supply).transact({'from': creator})
tx = token.functions.approve(contract.address, token_economics.erc20_reward_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = contract.functions.initialize().transact({'from': creator})
tx = contract.functions.initialize(token_economics.erc20_reward_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = token.functions.transfer(staker, 1000).transact({'from': creator})
testerchain.wait_for_receipt(tx)
@ -119,6 +121,10 @@ def test_upgrading(testerchain, token, token_economics, deploy_contract):
tx = contract.functions.confirmActivity().transact({'from': worker})
testerchain.wait_for_receipt(tx)
# Can set WorkLock twice, because isTestContract == True
tx = contract.functions.setWorkLock(worklock.address).transact()
testerchain.wait_for_receipt(tx)
# Upgrade to the second version
tx = dispatcher.functions.upgrade(contract_library_v2.address, secret, secret2_hash).transact({'from': creator})
testerchain.wait_for_receipt(tx)
@ -127,6 +133,10 @@ def test_upgrading(testerchain, token, token_economics, deploy_contract):
assert token_economics.maximum_allowed_locked == contract.functions.maxAllowableLockedTokens().call()
assert policy_manager.address == contract.functions.policyManager().call()
assert 2 == contract.functions.valueToCheck().call()
assert not contract.functions.isTestContract().call()
with pytest.raises((TransactionFailed, ValueError)):
tx = contract.functions.setWorkLock(worklock.address).transact()
testerchain.wait_for_receipt(tx)
# Check new ABI
tx = contract.functions.setValueToCheck(3).transact({'from': creator})
testerchain.wait_for_receipt(tx)
@ -143,7 +153,8 @@ def test_upgrading(testerchain, token, token_economics, deploy_contract):
_minLockedPeriods=2,
_minAllowableLockedTokens=2,
_maxAllowableLockedTokens=2,
_minWorkerPeriods=2
_minWorkerPeriods=2,
_isTestContract=False
)
with pytest.raises((TransactionFailed, ValueError)):
tx = dispatcher.functions.upgrade(contract_library_v1.address, secret2, secret_hash)\
@ -159,6 +170,9 @@ def test_upgrading(testerchain, token, token_economics, deploy_contract):
testerchain.wait_for_receipt(tx)
assert contract_library_v1.address == dispatcher.functions.target().call()
assert policy_manager.address == contract.functions.policyManager().call()
assert contract.functions.isTestContract().call()
tx = contract.functions.setWorkLock(worklock.address).transact()
testerchain.wait_for_receipt(tx)
# After rollback new ABI is unavailable
with pytest.raises((TransactionFailed, ValueError)):
tx = contract.functions.setValueToCheck(2).transact({'from': creator})
@ -207,9 +221,10 @@ def test_re_stake(testerchain, token, escrow_contract):
re_stake_lock_log = escrow.events.ReStakeLocked.createFilter(fromBlock='latest')
# Give Escrow tokens for reward and initialize contract
tx = token.functions.transfer(escrow.address, 10 ** 9).transact({'from': creator})
reward = 10 ** 9
tx = token.functions.approve(escrow.address, reward).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.initialize().transact({'from': creator})
tx = escrow.functions.initialize(reward).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Set re-stake parameter even before initialization
@ -469,7 +484,7 @@ def test_worker(testerchain, token, escrow_contract, deploy_contract):
worker_log = escrow.events.WorkerSet.createFilter(fromBlock='latest')
# Initialize escrow contract
tx = escrow.functions.initialize().transact({'from': creator})
tx = escrow.functions.initialize(0).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Deploy intermediary contracts
@ -710,9 +725,10 @@ def test_measure_work(testerchain, token, escrow_contract, deploy_contract):
work_measurement_log = escrow.events.WorkMeasurementSet.createFilter(fromBlock='latest')
# Initialize escrow contract
tx = token.functions.transfer(escrow.address, int(NU(10 ** 9, 'NuNit'))).transact({'from': creator})
reward = 10 ** 9
tx = token.functions.approve(escrow.address, int(NU(reward, 'NuNit'))).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = escrow.functions.initialize().transact({'from': creator})
tx = escrow.functions.initialize(reward).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# Deploy WorkLock mock

View File

@ -14,9 +14,13 @@ 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
@pytest.fixture()
@ -25,6 +29,22 @@ 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(
@ -38,306 +58,462 @@ def escrow(testerchain, token_economics, deploy_contract, token):
@pytest.mark.slow
def test_worklock(testerchain, token_economics, deploy_contract, token, escrow):
creator, ursula1, ursula2, *everyone_else = testerchain.w3.eth.accounts
def test_worklock(testerchain, token_economics, deploy_contract, token, escrow, router):
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)
deposit_rate = 100
refund_rate = 200
boosting_refund = 50
slowing_refund = 100
locking_duration = 60 * 60
worklock, _ = deploy_contract(
contract_name='WorkLock',
_token=token.address,
_escrow=escrow.address,
_router=router.address,
_startBidDate=start_bid_date,
_endBidDate=end_bid_date,
_depositRate=deposit_rate,
_refundRate=refund_rate,
_lockedPeriods=2 * token_economics.minimum_locked_periods
_boostingRefund=boosting_refund,
_lockingDuration=locking_duration
)
assert worklock.functions.startBidDate().call() == start_bid_date
assert worklock.functions.endBidDate().call() == end_bid_date
assert worklock.functions.minAllowableLockedTokens().call() == token_economics.minimum_allowed_locked
assert worklock.functions.maxAllowableLockedTokens().call() == token_economics.maximum_allowed_locked
assert worklock.functions.depositRate().call() == deposit_rate
assert worklock.functions.refundRate().call() == refund_rate
assert worklock.functions.boostingRefund().call() == boosting_refund
assert worklock.functions.SLOWING_REFUND().call() == slowing_refund
assert worklock.functions.lockingDuration().call() == locking_duration
deposit_log = worklock.events.Deposited.createFilter(fromBlock='latest')
bidding_log = worklock.events.Bid.createFilter(fromBlock='latest')
claim_log = worklock.events.Claimed.createFilter(fromBlock='latest')
refund_log = worklock.events.Refund.createFilter(fromBlock='latest')
burning_log = worklock.events.Burnt.createFilter(fromBlock='latest')
canceling_log = worklock.events.Canceled.createFilter(fromBlock='latest')
# Transfer tokens to WorkLock
worklock_supply = 2 * token_economics.maximum_allowed_locked - 1
tx = token.functions.transfer(worklock.address, worklock_supply).transact({'from': creator})
worklock_supply_1 = 2 * token_economics.maximum_allowed_locked + 1
worklock_supply_2 = token_economics.maximum_allowed_locked - 1
worklock_supply = worklock_supply_1 + worklock_supply_2
tx = token.functions.approve(worklock.address, worklock_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = worklock.functions.tokenDeposit(worklock_supply_1).transact({'from': creator})
testerchain.wait_for_receipt(tx)
assert worklock.functions.tokenSupply().call() == worklock_supply_1
tx = worklock.functions.tokenDeposit(worklock_supply_2).transact({'from': creator})
testerchain.wait_for_receipt(tx)
assert worklock.functions.tokenSupply().call() == worklock_supply
events = deposit_log.get_all_entries()
assert 2 == len(events)
event_args = events[0]['args']
assert event_args['sender'] == creator
assert event_args['value'] == worklock_supply_1
event_args = events[1]['args']
assert event_args['sender'] == creator
assert event_args['value'] == worklock_supply_2
# Give Ursulas some ETH
minimum_deposit_eth = token_economics.minimum_allowed_locked // deposit_rate
maximum_deposit_eth = token_economics.maximum_allowed_locked // deposit_rate
ursula1_balance = 2 * maximum_deposit_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': ursula1, 'value': ursula1_balance})
{'from': testerchain.etherbase_account, 'to': staker1, 'value': staker1_balance})
testerchain.wait_for_receipt(tx)
ursula2_balance = 2 * maximum_deposit_eth
ursula2_balance = staker1_balance
tx = testerchain.w3.eth.sendTransaction(
{'from': testerchain.etherbase_account, 'to': ursula2, 'value': ursula2_balance})
{'from': testerchain.etherbase_account, 'to': staker2, 'value': ursula2_balance})
testerchain.wait_for_receipt(tx)
staker3_balance = staker1_balance
tx = testerchain.w3.eth.sendTransaction(
{'from': testerchain.etherbase_account, 'to': staker3, 'value': staker3_balance})
testerchain.wait_for_receipt(tx)
# Can't do anything before start date
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.bid().transact({'from': ursula1, 'value': minimum_deposit_eth, 'gas_price': 0})
tx = worklock.functions.bid().transact({'from': staker1, 'value': deposit_eth_1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.claim().transact({'from': ursula1, 'gas_price': 0})
tx = worklock.functions.claim().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.refund().transact({'from': ursula1, 'gas_price': 0})
tx = worklock.functions.refund(preallocation_escrow_fake.address).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})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.cancelBid().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Wait for the start of bidding
testerchain.time_travel(seconds=3600) # Wait exactly 1 hour
# Can't bid with too low or too high ETH
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.bid().transact({'from': ursula1, 'value': minimum_deposit_eth - 1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.bid().transact({'from': ursula1, 'value': maximum_deposit_eth + 1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Ursula does first bid
assert worklock.functions.allClaimedTokens().call() == 0
assert worklock.functions.workInfo(ursula1).call()[0] == 0
assert worklock.functions.workInfo(staker1).call()[0] == 0
assert testerchain.w3.eth.getBalance(worklock.address) == 0
tx = worklock.functions.bid().transact({'from': ursula1, 'value': minimum_deposit_eth, 'gas_price': 0})
tx = worklock.functions.bid().transact({'from': staker1, 'value': deposit_eth_1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.allClaimedTokens().call() == token_economics.minimum_allowed_locked
assert worklock.functions.workInfo(ursula1).call()[0] == minimum_deposit_eth
assert testerchain.w3.eth.getBalance(worklock.address) == minimum_deposit_eth
assert worklock.functions.workInfo(staker1).call()[0] == deposit_eth_1
assert testerchain.w3.eth.getBalance(worklock.address) == deposit_eth_1
assert worklock.functions.ethToTokens(deposit_eth_1).call() == worklock_supply
events = bidding_log.get_all_entries()
assert 1 == len(events)
event_args = events[0]['args']
assert event_args['staker'] == ursula1
assert event_args['depositedETH'] == minimum_deposit_eth
assert event_args['claimedTokens'] == token_economics.minimum_allowed_locked
assert event_args['sender'] == staker1
assert event_args['depositedETH'] == deposit_eth_1
# Second Ursula does first bid
assert worklock.functions.workInfo(ursula2).call()[0] == 0
tx = worklock.functions.bid().transact({'from': ursula2, 'value': maximum_deposit_eth, 'gas_price': 0})
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.allClaimedTokens().call() == \
token_economics.minimum_allowed_locked + token_economics.maximum_allowed_locked
assert worklock.functions.workInfo(ursula2).call()[0] == maximum_deposit_eth
assert testerchain.w3.eth.getBalance(worklock.address) == maximum_deposit_eth + minimum_deposit_eth
assert worklock.functions.workInfo(staker2).call()[0] == deposit_eth_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
events = bidding_log.get_all_entries()
assert 2 == len(events)
event_args = events[1]['args']
assert event_args['staker'] == ursula2
assert event_args['depositedETH'] == maximum_deposit_eth
assert event_args['claimedTokens'] == token_economics.maximum_allowed_locked
assert event_args['sender'] == staker2
assert event_args['depositedETH'] == deposit_eth_2
# Can't bid again with too high ETH
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.bid().transact(
{'from': ursula1, 'value': maximum_deposit_eth-minimum_deposit_eth+1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.bid().transact({'from': ursula2, 'value': 1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Ursula does second bid
tx = worklock.functions.bid().transact({'from': ursula1, 'value': minimum_deposit_eth, 'gas_price': 0})
# Third Ursula 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)
assert worklock.functions.allClaimedTokens().call() == \
2 * token_economics.minimum_allowed_locked + token_economics.maximum_allowed_locked
assert worklock.functions.workInfo(ursula1).call()[0] == 2 * minimum_deposit_eth
assert testerchain.w3.eth.getBalance(worklock.address) == maximum_deposit_eth + 2 * minimum_deposit_eth
assert worklock.functions.workInfo(staker3).call()[0] == deposit_eth_2
assert testerchain.w3.eth.getBalance(worklock.address) == deposit_eth_1 + 2 * deposit_eth_2
assert worklock.functions.ethToTokens(deposit_eth_2).call() == worklock_supply // 6
events = bidding_log.get_all_entries()
assert 3 == len(events)
event_args = events[2]['args']
assert event_args['staker'] == ursula1
assert event_args['depositedETH'] == minimum_deposit_eth
assert event_args['claimedTokens'] == token_economics.minimum_allowed_locked
assert event_args['sender'] == staker3
assert event_args['depositedETH'] == deposit_eth_2
# Can't bid again: not enough tokens in worklock
# Ursula 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
assert testerchain.w3.eth.getBalance(worklock.address) == 2 * deposit_eth_1 + 2 * deposit_eth_2
assert worklock.functions.ethToTokens(deposit_eth_2).call() == worklock_supply // 10
events = bidding_log.get_all_entries()
assert 4 == len(events)
event_args = events[3]['args']
assert event_args['sender'] == staker1
assert event_args['depositedETH'] == deposit_eth_1
# Can't claim, refund or burn while bidding phase
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.bid().transact(
{'from': ursula1, 'value': maximum_deposit_eth - 2 * minimum_deposit_eth, 'gas_price': 0})
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})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.burnUnclaimed().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Can't claim or refund while bidding phase
# But can cancel bid
staker3_balance = testerchain.w3.eth.getBalance(staker3)
tx = worklock.functions.cancelBid().transact({'from': staker3, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.workInfo(staker3).call()[0] == 0
assert testerchain.w3.eth.getBalance(worklock.address) == 2 * deposit_eth_1 + deposit_eth_2
assert worklock.functions.ethToTokens(deposit_eth_2).call() == worklock_supply // 9
assert testerchain.w3.eth.getBalance(staker3) == staker3_balance + deposit_eth_2
events = canceling_log.get_all_entries()
assert 1 == len(events)
event_args = events[0]['args']
assert event_args['sender'] == staker3
assert event_args['value'] == deposit_eth_2
# Can't cancel twice in a row
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.claim().transact({'from': ursula1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.refund().transact({'from': ursula1, 'gas_price': 0})
tx = worklock.functions.cancelBid().transact({'from': staker3, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Third Ursula 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)
assert worklock.functions.workInfo(staker3).call()[0] == deposit_eth_2
assert testerchain.w3.eth.getBalance(worklock.address) == 2 * deposit_eth_1 + 2 * deposit_eth_2
assert worklock.functions.ethToTokens(deposit_eth_2).call() == worklock_supply // 10
events = bidding_log.get_all_entries()
assert 5 == len(events)
event_args = events[4]['args']
assert event_args['sender'] == staker3
assert event_args['depositedETH'] == deposit_eth_2
# Wait for the end of bidding
testerchain.time_travel(seconds=3600) # Wait exactly 1 hour
# Can't bid after the enf of bidding
# Can't bid after the end of bidding
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.bid().transact({'from': ursula1, 'value': minimum_deposit_eth, 'gas_price': 0})
tx = worklock.functions.bid().transact({'from': staker1, 'value': deposit_eth_1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Can't refund without claim
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.refund().transact({'from': ursula1, 'gas_price': 0})
tx = worklock.functions.refund(preallocation_escrow_fake.address).transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Ursula claims tokens
value, measure_work, _completed_work, periods = escrow.functions.stakerInfo(ursula1).call()
assert value == 0
preallocation_escrow_1_address = next_address(testerchain, worklock)
_value, measure_work, _completed_work, _periods = escrow.functions.stakerInfo(preallocation_escrow_1_address).call()
assert not measure_work
assert periods == 0
tx = worklock.functions.claim().transact({'from': ursula1, 'gas_price': 0})
tx = worklock.functions.claim().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.getRemainingWork(ursula1).call() == 2 * minimum_deposit_eth * refund_rate
value, measure_work, completed_work, periods = escrow.functions.stakerInfo(ursula1).call()
assert value == 2 * token_economics.minimum_allowed_locked
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 token.functions.balanceOf(worklock.address).call() == worklock_supply - staker1_tokens
_value, measure_work, _completed_work, _periods = escrow.functions.stakerInfo(preallocation_escrow_1_address).call()
assert measure_work
assert periods == 2 * token_economics.minimum_locked_periods
assert token.functions.balanceOf(worklock.address).call() == \
worklock_supply - 2 * token_economics.minimum_allowed_locked
assert token.functions.balanceOf(escrow.address).call() == 2 * token_economics.minimum_allowed_locked
events = claim_log.get_all_entries()
assert 1 == len(events)
event_args = events[0]['args']
assert event_args['staker'] == ursula1
assert event_args['claimedTokens'] == 2 * token_economics.minimum_allowed_locked
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)):
tx = worklock.functions.claim().transact({'from': ursula1, 'gas_price': 0})
tx = worklock.functions.claim().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Can't refund without work
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.refund().transact({'from': ursula1, 'gas_price': 0})
tx = worklock.functions.refund(preallocation_escrow_1.address).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
staker3_balance = testerchain.w3.eth.getBalance(staker3)
staker3_tokens = worklock_supply // 10
assert worklock.functions.ethToTokens(deposit_eth_2).call() == staker3_tokens
tx = worklock.functions.cancelBid().transact({'from': staker3, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.ethToTokens(deposit_eth_2).call() == staker3_tokens
assert worklock.functions.workInfo(staker3).call()[0] == 0
assert testerchain.w3.eth.getBalance(worklock.address) == 2 * deposit_eth_1 + deposit_eth_2
assert testerchain.w3.eth.getBalance(staker3) == staker3_balance + deposit_eth_2
assert worklock.functions.unclaimedTokens().call() == staker3_tokens
assert token.functions.balanceOf(worklock.address).call() == worklock_supply - staker1_tokens
# Second Ursula claims tokens
value, measure_work, _completed_work, periods = escrow.functions.stakerInfo(ursula2).call()
assert value == 0
preallocation_escrow_2_address = next_address(testerchain, worklock)
_value, measure_work, _completed_work, _periods = escrow.functions.stakerInfo(preallocation_escrow_2_address).call()
assert not measure_work
assert periods == 0
tx = escrow.functions.setCompletedWork(ursula2, refund_rate * minimum_deposit_eth).transact()
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()
testerchain.wait_for_receipt(tx)
tx = worklock.functions.claim().transact({'from': ursula2, 'gas_price': 0})
tx = worklock.functions.claim().transact({'from': staker2, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
assert worklock.functions.getRemainingWork(ursula2).call() == maximum_deposit_eth * refund_rate
value, measure_work, completed_work, periods = escrow.functions.stakerInfo(ursula2).call()
assert value == token_economics.maximum_allowed_locked
assert worklock.functions.getRemainingWork(preallocation_escrow_2_address).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()
assert measure_work
assert periods == 2 * token_economics.minimum_locked_periods
assert token.functions.balanceOf(worklock.address).call() == \
worklock_supply - 2 * token_economics.minimum_allowed_locked - token_economics.maximum_allowed_locked
assert token.functions.balanceOf(escrow.address).call() == \
2 * token_economics.minimum_allowed_locked + token_economics.maximum_allowed_locked
events = claim_log.get_all_entries()
assert 2 == len(events)
event_args = events[1]['args']
assert event_args['staker'] == ursula2
assert event_args['claimedTokens'] == token_economics.maximum_allowed_locked
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
ursula1_balance = testerchain.w3.eth.getBalance(ursula1)
completed_work = refund_rate * minimum_deposit_eth + refund_rate // 2
tx = escrow.functions.setCompletedWork(ursula1, completed_work).transact()
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()
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})
assert worklock.functions.getRemainingWork(preallocation_escrow_1_address).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})
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 - refund_rate // 2
assert testerchain.w3.eth.getBalance(ursula1) == ursula1_balance + minimum_deposit_eth
assert testerchain.w3.eth.getBalance(worklock.address) == maximum_deposit_eth + minimum_deposit_eth
_value, measure_work, _completed_work, _periods = escrow.functions.stakerInfo(ursula1).call()
assert worklock.functions.workInfo(staker1).call()[0] == deposit_eth_1
assert worklock.functions.getRemainingWork(preallocation_escrow_1_address).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()
assert measure_work
events = refund_log.get_all_entries()
assert 1 == len(events)
event_args = events[0]['args']
assert event_args['staker'] == ursula1
assert event_args['refundETH'] == minimum_deposit_eth
assert event_args['completedWork'] == minimum_deposit_eth * refund_rate
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
ursula1_balance = testerchain.w3.eth.getBalance(ursula1)
completed_work = refund_rate * 2 * minimum_deposit_eth
tx = escrow.functions.setCompletedWork(ursula1, completed_work).transact()
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()
testerchain.wait_for_receipt(tx)
assert worklock.functions.getRemainingWork(ursula1).call() == 0
tx = worklock.functions.refund().transact({'from': ursula1, 'gas_price': 0})
assert worklock.functions.getRemainingWork(preallocation_escrow_1_address).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})
testerchain.wait_for_receipt(tx)
assert worklock.functions.workInfo(ursula1).call()[0] == 0
assert worklock.functions.getRemainingWork(ursula1).call() == 0
assert testerchain.w3.eth.getBalance(ursula1) == ursula1_balance + minimum_deposit_eth
assert testerchain.w3.eth.getBalance(worklock.address) == maximum_deposit_eth
_value, measure_work, _completed_work, _periods = escrow.functions.stakerInfo(ursula1).call()
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 testerchain.w3.eth.getBalance(worklock.address) == deposit_eth_2
_value, measure_work, _completed_work, _periods = escrow.functions.stakerInfo(preallocation_escrow_1_address).call()
assert not measure_work
events = refund_log.get_all_entries()
assert 2 == len(events)
event_args = events[1]['args']
assert event_args['staker'] == ursula1
assert event_args['refundETH'] == minimum_deposit_eth
assert event_args['completedWork'] == minimum_deposit_eth * refund_rate
assert event_args['sender'] == staker2
assert event_args['preallocationEscrow'] == preallocation_escrow_1_address
assert event_args['refundETH'] == deposit_eth_1
assert event_args['completedWork'] == staker1_remaining_work // 2
# Can't refund more tokens
tx = escrow.functions.setCompletedWork(ursula1, 2 * completed_work).transact()
tx = escrow.functions.setCompletedWork(staker1, 2 * completed_work).transact()
testerchain.wait_for_receipt(tx)
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.refund().transact({'from': ursula1, 'gas_price': 0})
tx = worklock.functions.refund(preallocation_escrow_1_address).transact({'from': staker2, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
# Now burn remaining tokens
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
# Can't burn twice
with pytest.raises((TransactionFailed, ValueError)):
tx = worklock.functions.burnUnclaimed().transact({'from': staker1, 'gas_price': 0})
testerchain.wait_for_receipt(tx)
events = burning_log.get_all_entries()
assert 1 == len(events)
event_args = events[0]['args']
assert event_args['sender'] == staker1
assert event_args['value'] == staker3_tokens
@pytest.mark.slow
def test_reentrancy(testerchain, token_economics, deploy_contract, token, escrow):
def test_reentrancy(testerchain, token_economics, deploy_contract, token, escrow, router):
# Deploy WorkLock
now = testerchain.w3.eth.getBlock(block_identifier='latest').timestamp
start_bid_date = now
end_bid_date = start_bid_date + (60 * 60)
deposit_rate = 1
refund_rate = 1
boosting_refund = 100
locking_duration = 60 * 60
worklock, _ = deploy_contract(
contract_name='WorkLock',
_token=token.address,
_escrow=escrow.address,
_router=router.address,
_startBidDate=start_bid_date,
_endBidDate=end_bid_date,
_depositRate=deposit_rate,
_refundRate=refund_rate,
_lockedPeriods=2 * token_economics.minimum_locked_periods
_boostingRefund=boosting_refund,
_lockingDuration=locking_duration
)
refund_log = worklock.events.Refund.createFilter(fromBlock='latest')
worklock_supply = 2 * token_economics.maximum_allowed_locked - 1
tx = token.functions.transfer(worklock.address, worklock_supply).transact()
canceling_log = worklock.events.Canceled.createFilter(fromBlock='latest')
worklock_supply = 3 * token_economics.maximum_allowed_locked
tx = token.functions.approve(worklock.address, worklock_supply).transact()
testerchain.wait_for_receipt(tx)
tx = worklock.functions.tokenDeposit(worklock_supply).transact()
testerchain.wait_for_receipt(tx)
reentrancy_contract, _ = deploy_contract('ReentrancyTest')
contract_address = reentrancy_contract.address
minimum_deposit_eth = token_economics.minimum_allowed_locked // deposit_rate
deposit_eth = to_wei(3, 'ether')
tx = testerchain.client.send_transaction(
{'from': testerchain.etherbase_account, 'to': contract_address, 'value': minimum_deposit_eth})
{'from': testerchain.etherbase_account, 'to': contract_address, 'value': deposit_eth})
testerchain.wait_for_receipt(tx)
# Bid
transaction = worklock.functions.bid().buildTransaction({'gas': 0})
tx = reentrancy_contract.functions.setData(1, transaction['to'], minimum_deposit_eth, transaction['data']).transact()
tx = reentrancy_contract.functions.setData(1, transaction['to'], deposit_eth, transaction['data']).transact()
testerchain.wait_for_receipt(tx)
tx = testerchain.client.send_transaction({'to': contract_address})
testerchain.wait_for_receipt(tx)
assert worklock.functions.workInfo(contract_address).call()[0] == minimum_deposit_eth
assert testerchain.w3.eth.getBalance(worklock.address) == minimum_deposit_eth
assert worklock.functions.workInfo(contract_address).call()[0] == deposit_eth
assert testerchain.w3.eth.getBalance(worklock.address) == deposit_eth
tx = worklock.functions.bid().transact({'from': testerchain.etherbase_account, 'value': deposit_eth})
testerchain.wait_for_receipt(tx)
# Check reentrancy protection when cancelling a bid
balance = testerchain.w3.eth.getBalance(contract_address)
transaction = worklock.functions.cancelBid().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)):
tx = testerchain.client.send_transaction({'to': contract_address})
testerchain.wait_for_receipt(tx)
assert testerchain.w3.eth.getBalance(contract_address) == balance
assert worklock.functions.workInfo(contract_address).call()[0] == deposit_eth
assert len(canceling_log.get_all_entries()) == 0
# Claim
testerchain.time_travel(seconds=3600) # Wait exactly 1 hour
@ -346,20 +522,21 @@ 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)
assert worklock.functions.getRemainingWork(contract_address).call() == minimum_deposit_eth * refund_rate
preallocation_escrow = worklock.functions.workInfo(contract_address).call()[2]
assert worklock.functions.getRemainingWork(preallocation_escrow).call() == worklock_supply // 2
# Prepare for refund and check reentrancy protection
balance = testerchain.w3.eth.getBalance(contract_address)
completed_work = refund_rate * minimum_deposit_eth // 3
tx = escrow.functions.setCompletedWork(contract_address, completed_work).transact()
completed_work = worklock_supply // 6
tx = escrow.functions.setCompletedWork(preallocation_escrow, completed_work).transact()
testerchain.wait_for_receipt(tx)
transaction = worklock.functions.refund().buildTransaction({'gas': 0})
transaction = worklock.functions.refund(preallocation_escrow).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)):
tx = testerchain.client.send_transaction({'to': contract_address})
testerchain.wait_for_receipt(tx)
assert testerchain.w3.eth.getBalance(contract_address) == balance
assert worklock.functions.workInfo(contract_address).call()[0] == minimum_deposit_eth
assert worklock.functions.getRemainingWork(contract_address).call() == 2 * minimum_deposit_eth * refund_rate // 3
assert worklock.functions.workInfo(contract_address).call()[0] == deposit_eth
assert worklock.functions.getRemainingWork(preallocation_escrow).call() == 2 * worklock_supply // 6
assert len(refund_log.get_all_entries()) == 0

View File

@ -19,14 +19,14 @@ import os
import pytest
import requests
from eth_utils import keccak
from web3.exceptions import ValidationError
from nucypher.blockchain.eth.deployers import NucypherTokenDeployer, StakingEscrowDeployer, PolicyManagerDeployer, \
AdjudicatorDeployer, BaseContractDeployer, UpgradeableContractMixin, DispatcherDeployer
AdjudicatorDeployer, BaseContractDeployer
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory, BlockchainDeployerInterface
from nucypher.blockchain.eth.registry import InMemoryContractRegistry, BaseContractRegistry
from nucypher.blockchain.eth.registry import InMemoryContractRegistry
from nucypher.blockchain.eth.sol.compile import SolidityCompiler, SourceDirs
from nucypher.crypto.powers import TransactingPower
from nucypher.utilities.sandbox.blockchain import TesterBlockchain
from nucypher.utilities.sandbox.constants import INSECURE_DEVELOPMENT_PASSWORD, STAKING_ESCROW_DEPLOYMENT_SECRET, \
POLICY_MANAGER_DEPLOYMENT_SECRET, ADJUDICATOR_DEPLOYMENT_SECRET
@ -65,10 +65,10 @@ def download_github_file(source_link: str, target_folder: str):
# Constructor parameters overrides for previous versions if needed
# All versions below the specified version must use these overrides
# 'None' value removes arg from list of constructor parameters
CONSTRUCTOR_OVERRIDES = {
# Test example
StakingEscrowDeployer.contract_name: {"v0.0.0": {"_hoursPerPeriod": 1}}
StakingEscrowDeployer.contract_name: {"v1.5.1": {"_isTestContract": None}}
}
@ -76,13 +76,15 @@ def deploy_earliest_contract(blockchain_interface: BlockchainDeployerInterface,
deployer: BaseContractDeployer,
secret: str):
contract_name = deployer.contract_name
earliest_version, _data = blockchain_interface.find_raw_contract_data(contract_name, "earliest")
latest_version, _data = blockchain_interface.find_raw_contract_data(contract_name, "latest")
try:
overrides = CONSTRUCTOR_OVERRIDES[contract_name][earliest_version]
overrides = CONSTRUCTOR_OVERRIDES[contract_name][latest_version]
except KeyError:
overrides = dict()
deployer.deploy(secret_hash=keccak(text=secret), contract_version=earliest_version, **overrides)
try:
deployer.deploy(secret_hash=keccak(text=secret), contract_version="earliest", **overrides)
except ValidationError:
pass # Skip errors related to initialization
def upgrade_to_latest_contract(deployer, secret: str):
@ -128,10 +130,8 @@ def test_upgradeability(temp_dir_path, token_economics):
staking_escrow_deployer = StakingEscrowDeployer(registry=registry, deployer_address=origin)
deploy_earliest_contract(blockchain_interface, staking_escrow_deployer, secret=STAKING_ESCROW_DEPLOYMENT_SECRET)
assert staking_escrow_deployer.contract.functions.secondsPerPeriod().call() == 3600
if test_staking_escrow:
upgrade_to_latest_contract(staking_escrow_deployer, secret=STAKING_ESCROW_DEPLOYMENT_SECRET)
assert staking_escrow_deployer.contract.functions.secondsPerPeriod().call() == token_economics.seconds_per_period
if test_policy_manager:
policy_manager_deployer = PolicyManagerDeployer(registry=registry, deployer_address=origin)

View File

@ -55,7 +55,8 @@ def test_nucypher_deploy_contracts(click_runner,
command = ['contracts',
'--registry-outfile', registry_filepath,
'--provider', TEST_PROVIDER_URI,
'--poa']
'--poa',
'--se-test-mode']
user_input = '0\n' + 'Y\n' + (f'{INSECURE_SECRETS[1]}\n' * 8) + 'DEPLOY'
result = click_runner.invoke(deploy, command, input=user_input, catch_exceptions=False)
@ -101,6 +102,7 @@ def test_nucypher_deploy_contracts(click_runner,
assert token_agent.get_balance() == 0
staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=registry)
assert staking_agent.get_current_period()
assert staking_agent.contract.functions.isTestContract().call()
# and at least the others can be instantiated
assert PolicyManagerAgent(registry=registry)

View File

@ -457,7 +457,7 @@ def _make_agency(testerchain, test_registry):
token_deployer = NucypherTokenDeployer(deployer_address=origin, registry=test_registry)
token_deployer.deploy()
staking_escrow_deployer = StakingEscrowDeployer(deployer_address=origin, registry=test_registry)
staking_escrow_deployer = StakingEscrowDeployer(deployer_address=origin, registry=test_registry, test_mode=True)
staking_escrow_deployer.deploy(secret_hash=INSECURE_DEPLOYMENT_SECRET_HASH)
policy_manager_deployer = PolicyManagerDeployer(deployer_address=origin, registry=test_registry)