diff --git a/nucypher/blockchain/eth/deployers.py b/nucypher/blockchain/eth/deployers.py index c8720b2f0..ad1b09b36 100644 --- a/nucypher/blockchain/eth/deployers.py +++ b/nucypher/blockchain/eth/deployers.py @@ -48,8 +48,8 @@ from nucypher.blockchain.eth.decorators import validate_secret, validate_checksu from nucypher.blockchain.eth.interfaces import ( BlockchainDeployerInterface, BlockchainInterfaceFactory, - VersionedContract -) + VersionedContract, + BlockchainInterface) from nucypher.blockchain.eth.registry import AllocationRegistry from nucypher.blockchain.eth.registry import BaseContractRegistry @@ -870,11 +870,20 @@ class StakingInterfaceDeployer(BaseContractDeployer, UpgradeableContractMixin): contract_name=policy_contract_name, proxy_name=policy_proxy_name) + worklock_name = WorklockDeployer.contract_name + try: + self.worklock_contract = self.blockchain.get_contract_by_name(registry=self.registry, + contract_name=worklock_name) + except BaseContractRegistry.UnknownContract: + self.worklock_contract = None + def _deploy_essential(self, contract_version: str, gas_limit: int = None, confirmations: int = 0): """Note: These parameters are order-sensitive""" + worklock_address = self.worklock_contract.address if self.worklock_contract else BlockchainInterface.NULL_ADDRESS constructor_args = (self.token_contract.address, self.staking_contract.address, - self.policy_contract.address) + self.policy_contract.address, + worklock_address) contract, deployment_receipt = self.blockchain.deploy_contract(self.deployer_address, self.registry, diff --git a/nucypher/blockchain/eth/sol/source/contracts/PolicyManager.sol b/nucypher/blockchain/eth/sol/source/contracts/PolicyManager.sol index 0450d3851..b6923ea14 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/PolicyManager.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/PolicyManager.sol @@ -194,7 +194,7 @@ contract PolicyManager is Upgradeable { /** * @notice Get the minimum reward rate acceptable by node */ - function getMinRewardRate(NodeInfo storage _nodeInfo) internal returns (uint256) { + function getMinRewardRate(NodeInfo storage _nodeInfo) internal view returns (uint256) { // if minRewardRate has not been set or is outside the acceptable range if (_nodeInfo.minRewardRate == 0 || _nodeInfo.minRewardRate < minRewardRateRange.min || @@ -208,7 +208,7 @@ contract PolicyManager is Upgradeable { /** * @notice Get the minimum reward rate acceptable by node */ - function getMinRewardRate(address _node) public returns (uint256) { + function getMinRewardRate(address _node) public view returns (uint256) { NodeInfo storage nodeInfo = nodes[_node]; return getMinRewardRate(nodeInfo); } diff --git a/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/AbstractStakingContract.sol b/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/AbstractStakingContract.sol index ae8ce60cc..78af961d6 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/AbstractStakingContract.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/AbstractStakingContract.sol @@ -62,7 +62,7 @@ contract AbstractStakingContract { * @dev Checks permission for calling fallback function */ function isFallbackAllowed() public returns (bool); - + /** * @dev Withdraw tokens from staking contract */ diff --git a/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/StakingInterface.sol b/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/StakingInterface.sol index 3b1b60190..7d621bcb5 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/StakingInterface.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/staking_contracts/StakingInterface.sol @@ -5,13 +5,14 @@ import "contracts/staking_contracts/AbstractStakingContract.sol"; import "contracts/NuCypherToken.sol"; import "contracts/StakingEscrow.sol"; import "contracts/PolicyManager.sol"; +import "contracts/WorkLock.sol"; /** * @notice Interface for accessing main contracts from a staking contract * @dev All methods must be stateless because this code will be executed by delegatecall call. * If state is needed - use getStateContract() method to access state of this contract. -* @dev |v1.3.1| +* @dev |v1.4.1| */ contract StakingInterface { @@ -27,30 +28,41 @@ contract StakingInterface { event WorkerSet(address indexed sender, address worker); event Prolonged(address indexed sender, uint256 index, uint16 periods); event WindDownSet(address indexed sender, bool windDown); + event Bid(address indexed sender, uint256 depositedETH); + event Claimed(address indexed sender, uint256 claimedTokens); + event Refund(address indexed sender, uint256 refundETH); + event BidCanceled(address indexed sender); + event CompensationWithdrawn(address indexed sender); NuCypherToken public token; StakingEscrow public escrow; PolicyManager public policyManager; + WorkLock public workLock; /** * @notice Constructor sets addresses of the contracts * @param _token Token contract * @param _escrow Escrow contract * @param _policyManager PolicyManager contract + * @param _workLock WorkLock contract */ constructor( NuCypherToken _token, StakingEscrow _escrow, - PolicyManager _policyManager + PolicyManager _policyManager, + WorkLock _workLock ) public { require(_token.totalSupply() > 0 && _escrow.secondsPerPeriod() > 0 && - _policyManager.secondsPerPeriod() > 0); + _policyManager.secondsPerPeriod() > 0 && + // in case there is no worklock contract + (address(_workLock) == address(0) || _workLock.startBidDate() > 0)); token = _token; escrow = _escrow; policyManager = _policyManager; + workLock = _workLock; } /** @@ -185,4 +197,54 @@ contract StakingInterface { emit WindDownSet(msg.sender, _windDown); } + /** + * @notice Bid for tokens by transferring ETH + */ + function bid(uint256 _value) public payable { + WorkLock workLockFromState = getStateContract().workLock(); + require(address(workLockFromState) != address(0)); + workLockFromState.bid.value(_value)(); + emit Bid(msg.sender, _value); + } + + /** + * @notice Cancel bid and refund deposited ETH + */ + function cancelBid() public { + WorkLock workLockFromState = getStateContract().workLock(); + require(address(workLockFromState) != address(0)); + workLockFromState.cancelBid(); + emit BidCanceled(msg.sender); + } + + /** + * @notice Withdraw compensation after force refund + */ + function withdrawCompensation() public { + WorkLock workLockFromState = getStateContract().workLock(); + require(address(workLockFromState) != address(0)); + workLockFromState.withdrawCompensation(); + emit CompensationWithdrawn(msg.sender); + } + + /** + * @notice Claimed tokens will be deposited and locked as stake in the StakingEscrow contract. + */ + function claim() public { + WorkLock workLockFromState = getStateContract().workLock(); + require(address(workLockFromState) != address(0)); + uint256 claimedTokens = workLockFromState.claim(); + emit Claimed(msg.sender, claimedTokens); + } + + /** + * @notice Refund ETH for the completed work + */ + function refund() public { + WorkLock workLockFromState = getStateContract().workLock(); + require(address(workLockFromState) != address(0)); + uint256 refundETH = workLockFromState.refund(); + emit Refund(msg.sender, refundETH); + } + } diff --git a/tests/blockchain/eth/contracts/contracts/StakingContractsTestSet.sol b/tests/blockchain/eth/contracts/contracts/StakingContractsTestSet.sol index c406d37e0..a143321b5 100644 --- a/tests/blockchain/eth/contracts/contracts/StakingContractsTestSet.sol +++ b/tests/blockchain/eth/contracts/contracts/StakingContractsTestSet.sol @@ -113,6 +113,56 @@ contract PolicyManagerForStakingContractMock { } +/** +* @notice Contract for staking contract tests +*/ +contract WorkLockForStakingContractMock { + + uint256 public startBidDate = 1; + uint256 public claimed; + uint256 public depositedETH; + uint256 public compensation; + uint256 public refundETH; + + function bid() external payable { + depositedETH = msg.value; + } + + function cancelBid() external { + uint256 value = depositedETH; + depositedETH = 0; + msg.sender.transfer(value); + } + + function sendCompensation() external payable { + compensation = msg.value; + } + + function withdrawCompensation() external { + uint256 value = compensation; + compensation = 0; + msg.sender.transfer(value); + } + + function claim() external returns (uint256) { + claimed += 1; + return claimed; + } + + function sendRefund() external payable { + refundETH = msg.value; + } + + function refund() external returns (uint256) { + uint256 value = refundETH; + refundETH = 0; + msg.sender.transfer(value); + return value; + } + +} + + /** * @notice Contract for staking contract tests */ diff --git a/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py b/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py index 802b3796a..933c4cc41 100644 --- a/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py +++ b/tests/blockchain/eth/contracts/integration/test_intercontract_integration.py @@ -156,13 +156,13 @@ def generate_args_for_slashing(mock_ursula_reencrypts, ursula): @pytest.fixture() -def staking_interface(testerchain, token, escrow, policy_manager, deploy_contract): +def staking_interface(testerchain, token, escrow, policy_manager, worklock, deploy_contract): escrow, _ = escrow policy_manager, _ = policy_manager secret_hash = testerchain.w3.keccak(router_secret) # Creator deploys the staking interface staking_interface, _ = deploy_contract( - 'StakingInterface', token.address, escrow.address, policy_manager.address) + 'StakingInterface', token.address, escrow.address, policy_manager.address, worklock.address) router, _ = deploy_contract( 'StakingInterfaceRouter', staking_interface.address, secret_hash) return staking_interface, router @@ -331,6 +331,16 @@ def test_all(testerchain, tx = token.functions.approve(escrow.address, 0).transact({'from': creator}) testerchain.wait_for_receipt(tx) + # 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) + tx = preallocation_escrow_1.functions.transferOwnership(staker3).transact({'from': creator}) + testerchain.wait_for_receipt(tx) + # Initialize worklock worklock_supply = 3 * token_economics.minimum_allowed_locked + token_economics.maximum_allowed_locked tx = token.functions.approve(worklock.address, worklock_supply).transact({'from': creator}) @@ -366,10 +376,15 @@ def test_all(testerchain, testerchain.wait_for_receipt(tx) # Other stakers do bid - assert worklock.functions.workInfo(staker4).call()[0] == 0 - tx = worklock.functions.bid().transact({'from': staker4, 'value': deposited_eth_2, 'gas_price': 0}) + assert worklock.functions.workInfo(preallocation_escrow_1.address).call()[0] == 0 + tx = testerchain.client.send_transaction( + {'from': testerchain.client.coinbase, 'to': preallocation_escrow_1.address, 'value': deposited_eth_2}) testerchain.wait_for_receipt(tx) - assert worklock.functions.workInfo(staker4).call()[0] == deposited_eth_2 + assert testerchain.w3.eth.getBalance(preallocation_escrow_1.address) == deposited_eth_2 + tx = preallocation_escrow_interface_1.functions.bid(deposited_eth_2).transact({'from': staker3, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + assert testerchain.w3.eth.getBalance(preallocation_escrow_1.address) == 0 + assert worklock.functions.workInfo(preallocation_escrow_1.address).call()[0] == deposited_eth_2 worklock_balance += deposited_eth_2 bonus_worklock_supply -= min_stake assert testerchain.w3.eth.getBalance(worklock.address) == worklock_balance @@ -396,11 +411,12 @@ def test_all(testerchain, # One of stakers cancels bid assert worklock.functions.getBiddersLength().call() == 3 - tx = worklock.functions.cancelBid().transact({'from': staker4, 'gas_price': 0}) + tx = preallocation_escrow_interface_1.functions.cancelBid().transact({'from': staker3, 'gas_price': 0}) testerchain.wait_for_receipt(tx) - assert worklock.functions.workInfo(staker4).call()[0] == 0 + assert worklock.functions.workInfo(preallocation_escrow_1.address).call()[0] == 0 worklock_balance -= deposited_eth_2 bonus_worklock_supply += min_stake + assert testerchain.w3.eth.getBalance(preallocation_escrow_1.address) == deposited_eth_2 assert testerchain.w3.eth.getBalance(worklock.address) == worklock_balance assert worklock.functions.ethToTokens(deposited_eth_2).call() == min_stake assert worklock.functions.ethToTokens(2 * deposited_eth_2).call() == min_stake + bonus_worklock_supply // 18 @@ -499,17 +515,7 @@ def test_all(testerchain, tx = worklock.functions.refund().transact({'from': staker2, 'gas_price': 0}) testerchain.wait_for_receipt(tx) - # 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) - # Set and lock re-stake parameter in first preallocation escrow - tx = preallocation_escrow_1.functions.transferOwnership(staker3).transact({'from': creator}) - testerchain.wait_for_receipt(tx) assert not escrow.functions.stakerInfo(preallocation_escrow_1.address).call()[DISABLE_RE_STAKE_FIELD] current_period = escrow.functions.getCurrentPeriod().call() tx = preallocation_escrow_interface_1.functions.lockReStake(current_period + 22).transact({'from': staker3}) @@ -948,7 +954,7 @@ def test_all(testerchain, # Upgrade the preallocation escrow library # Deploy the same contract as the second version staking_interface_v2, _ = deploy_contract( - 'StakingInterface', token.address, escrow.address, policy_manager.address) + 'StakingInterface', token.address, escrow.address, policy_manager.address, worklock.address) router_secret2 = os.urandom(SECRET_LENGTH) router_secret2_hash = testerchain.w3.keccak(router_secret2) # Staker and Alice can't upgrade library, only owner can diff --git a/tests/blockchain/eth/contracts/main/staking_contracts/conftest.py b/tests/blockchain/eth/contracts/main/staking_contracts/conftest.py index 614a0e0cc..f63bdb506 100644 --- a/tests/blockchain/eth/contracts/main/staking_contracts/conftest.py +++ b/tests/blockchain/eth/contracts/main/staking_contracts/conftest.py @@ -50,10 +50,16 @@ def policy_manager(testerchain, deploy_contract): @pytest.fixture() -def staking_interface(testerchain, token, escrow, policy_manager, deploy_contract): +def worklock(testerchain, deploy_contract): + contract, _ = deploy_contract('WorkLockForStakingContractMock') + return contract + + +@pytest.fixture() +def staking_interface(testerchain, token, escrow, policy_manager, worklock, deploy_contract): # Creator deploys the staking interface contract, _ = deploy_contract( - 'StakingInterface', token.address, escrow.address, policy_manager.address) + 'StakingInterface', token.address, escrow.address, policy_manager.address, worklock.address) return contract diff --git a/tests/blockchain/eth/contracts/main/staking_contracts/test_preallocation_escrow.py b/tests/blockchain/eth/contracts/main/staking_contracts/test_preallocation_escrow.py index 9ed411b9a..b13f22378 100644 --- a/tests/blockchain/eth/contracts/main/staking_contracts/test_preallocation_escrow.py +++ b/tests/blockchain/eth/contracts/main/staking_contracts/test_preallocation_escrow.py @@ -20,6 +20,9 @@ import os import pytest from eth_utils import keccak from eth_tester.exceptions import TransactionFailed +from web3.contract import Contract + +from nucypher.blockchain.eth.interfaces import BlockchainInterface @pytest.mark.slow @@ -407,17 +410,13 @@ def test_policy(testerchain, policy_manager, preallocation_escrow, preallocation @pytest.mark.slow -def test_reentrancy(testerchain, deploy_contract, token, escrow, policy_manager): - proxy, _ = deploy_contract('StakingInterfaceMockV2', token.address, escrow.address, policy_manager.address) - secret = os.urandom(32) - secret_hash = keccak(secret) - router, _ = deploy_contract('StakingInterfaceRouter', proxy.address, secret_hash) +def test_reentrancy(testerchain, preallocation_escrow, deploy_contract): + owner = testerchain.client.accounts[1] # Prepare contracts reentrancy_contract, _ = deploy_contract('ReentrancyTest') contract_address = reentrancy_contract.address - preallocation_escrow, _ = deploy_contract('PreallocationEscrow', router.address, token.address, escrow.address) - tx = preallocation_escrow.functions.transferOwnership(contract_address).transact() + tx = preallocation_escrow.functions.transferOwnership(contract_address).transact({'from': owner}) testerchain.wait_for_receipt(tx) # Transfer ETH to user escrow @@ -438,3 +437,168 @@ def test_reentrancy(testerchain, deploy_contract, token, escrow, policy_manager) tx = testerchain.client.send_transaction({'to': contract_address}) testerchain.wait_for_receipt(tx) assert testerchain.w3.eth.getBalance(contract_address) == balance + + +@pytest.mark.slow +def test_worklock(testerchain, worklock, preallocation_escrow, preallocation_escrow_interface, staking_interface): + """ + Test worklock functions in the preallocation escrow + """ + creator = testerchain.client.accounts[0] + owner = testerchain.client.accounts[1] + + bids = preallocation_escrow_interface.events.Bid.createFilter(fromBlock='latest') + claims = preallocation_escrow_interface.events.Claimed.createFilter(fromBlock='latest') + refunds = preallocation_escrow_interface.events.Refund.createFilter(fromBlock='latest') + cancellations = preallocation_escrow_interface.events.BidCanceled.createFilter(fromBlock='latest') + compensations = preallocation_escrow_interface.events.CompensationWithdrawn.createFilter(fromBlock='latest') + + # Owner can't use the staking interface directly + with pytest.raises((TransactionFailed, ValueError)): + tx = staking_interface.functions.bid(0).transact({'from': owner}) + testerchain.wait_for_receipt(tx) + with pytest.raises((TransactionFailed, ValueError)): + tx = staking_interface.functions.cancelBid().transact({'from': owner}) + testerchain.wait_for_receipt(tx) + with pytest.raises((TransactionFailed, ValueError)): + tx = staking_interface.functions.withdrawCompensation().transact({'from': owner}) + testerchain.wait_for_receipt(tx) + with pytest.raises((TransactionFailed, ValueError)): + tx = staking_interface.functions.claim().transact({'from': owner}) + testerchain.wait_for_receipt(tx) + with pytest.raises((TransactionFailed, ValueError)): + tx = staking_interface.functions.refund().transact({'from': owner}) + testerchain.wait_for_receipt(tx) + + # Send ETH to to the escrow + bid = 10000 + tx = testerchain.client.send_transaction( + {'from': testerchain.client.coinbase, 'to': preallocation_escrow.address, 'value': 2 * bid}) + testerchain.wait_for_receipt(tx) + + # Bid + assert worklock.functions.depositedETH().call() == 0 + assert testerchain.client.get_balance(preallocation_escrow.address) == 2 * bid + tx = preallocation_escrow_interface.functions.bid(bid).transact({'from': owner, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + assert worklock.functions.depositedETH().call() == bid + assert testerchain.client.get_balance(preallocation_escrow.address) == bid + + events = bids.get_all_entries() + assert len(events) == 1 + event_args = events[0]['args'] + assert event_args['sender'] == owner + assert event_args['depositedETH'] == bid + + # Cancel bid + tx = preallocation_escrow_interface.functions.cancelBid().transact({'from': owner, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + assert worklock.functions.depositedETH().call() == 0 + assert testerchain.client.get_balance(preallocation_escrow.address) == 2 * bid + + events = cancellations.get_all_entries() + assert len(events) == 1 + event_args = events[0]['args'] + assert event_args['sender'] == owner + + # Withdraw compensation + compensation = 11000 + tx = worklock.functions.sendCompensation().transact({'from': creator, 'value': compensation, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + assert worklock.functions.compensation().call() == compensation + tx = preallocation_escrow_interface.functions.withdrawCompensation().transact({'from': owner, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + assert worklock.functions.compensation().call() == 0 + assert testerchain.client.get_balance(preallocation_escrow.address) == 2 * bid + compensation + + events = compensations.get_all_entries() + assert len(events) == 1 + event_args = events[0]['args'] + assert event_args['sender'] == owner + + # Claim + assert worklock.functions.claimed().call() == 0 + tx = preallocation_escrow_interface.functions.claim().transact({'from': owner, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + assert worklock.functions.claimed().call() == 1 + + events = claims.get_all_entries() + assert len(events) == 1 + event_args = events[0]['args'] + assert event_args['sender'] == owner + assert event_args['claimedTokens'] == 1 + + # Withdraw refund + refund = 12000 + tx = worklock.functions.sendRefund().transact({'from': creator, 'value': refund, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + assert worklock.functions.refundETH().call() == refund + tx = preallocation_escrow_interface.functions.refund().transact({'from': owner, 'gas_price': 0}) + testerchain.wait_for_receipt(tx) + assert worklock.functions.refundETH().call() == 0 + assert testerchain.client.get_balance(preallocation_escrow.address) == 2 * bid + compensation + refund + + events = refunds.get_all_entries() + assert len(events) == 1 + event_args = events[0]['args'] + assert event_args['sender'] == owner + assert event_args['refundETH'] == refund + + +@pytest.mark.slow +def test_interface_without_worklock(testerchain, deploy_contract, token, escrow, policy_manager, worklock): + creator = testerchain.client.accounts[0] + owner = testerchain.client.accounts[1] + + staking_interface, _ = deploy_contract( + 'StakingInterface', token.address, escrow.address, policy_manager.address, worklock.address) + secret = os.urandom(32) + secret_hash = keccak(secret) + router, _ = deploy_contract('StakingInterfaceRouter', staking_interface.address, secret_hash) + + preallocation_escrow, _ = deploy_contract('PreallocationEscrow', router.address, token.address, escrow.address) + # Transfer ownership + tx = preallocation_escrow.functions.transferOwnership(owner).transact({'from': creator}) + testerchain.wait_for_receipt(tx) + + preallocation_escrow_interface = testerchain.client.get_contract( + abi=staking_interface.abi, + address=preallocation_escrow.address, + ContractFactoryClass=Contract) + + # All worklock methods work + tx = preallocation_escrow_interface.functions.bid(0).transact({'from': owner}) + testerchain.wait_for_receipt(tx) + tx = preallocation_escrow_interface.functions.cancelBid().transact({'from': owner}) + testerchain.wait_for_receipt(tx) + tx = preallocation_escrow_interface.functions.withdrawCompensation().transact({'from': owner}) + testerchain.wait_for_receipt(tx) + tx = preallocation_escrow_interface.functions.claim().transact({'from': owner}) + testerchain.wait_for_receipt(tx) + tx = preallocation_escrow_interface.functions.refund().transact({'from': owner}) + testerchain.wait_for_receipt(tx) + + # Test interface without worklock + secret2 = os.urandom(32) + secret2_hash = keccak(secret2) + staking_interface, _ = deploy_contract( + 'StakingInterface', token.address, escrow.address, policy_manager.address, BlockchainInterface.NULL_ADDRESS) + tx = router.functions.upgrade(staking_interface.address, secret, secret2_hash).transact({'from': creator}) + testerchain.wait_for_receipt(tx) + + # Current version of interface doesn't have worklock contract + with pytest.raises((TransactionFailed, ValueError)): + tx = preallocation_escrow_interface.functions.bid(0).transact({'from': owner}) + testerchain.wait_for_receipt(tx) + with pytest.raises((TransactionFailed, ValueError)): + tx = preallocation_escrow_interface.functions.cancelBid().transact({'from': owner}) + testerchain.wait_for_receipt(tx) + with pytest.raises((TransactionFailed, ValueError)): + tx = preallocation_escrow_interface.functions.withdrawCompensation().transact({'from': owner}) + testerchain.wait_for_receipt(tx) + with pytest.raises((TransactionFailed, ValueError)): + tx = preallocation_escrow_interface.functions.claim().transact({'from': owner}) + testerchain.wait_for_receipt(tx) + with pytest.raises((TransactionFailed, ValueError)): + tx = preallocation_escrow_interface.functions.refund().transact({'from': owner}) + testerchain.wait_for_receipt(tx)