diff --git a/docs/source/architecture/contracts.rst b/docs/source/architecture/contracts.rst index 967cdbd7a..1bd81edd5 100644 --- a/docs/source/architecture/contracts.rst +++ b/docs/source/architecture/contracts.rst @@ -36,7 +36,6 @@ For a guide of how to deploy these contracts automatically, see the :doc:`Deploy #. Approve tokens transfer to the ``StakingEscrow`` contract. These tokens are future staking rewards #. Run the ``initialize(uint256)`` method to initialize the ``StakingEscrow`` contract #. Approve tokens transfer for distribution to the ``WorkLock`` contract and call ``tokenDeposit(uint256)`` method -#. Pre-deposit tokens to the ``StakingEscrow`` using ``batchDeposit(address[], uint256[], uint256[], uint16[])`` Alice's Contract Interaction ---------------------------- diff --git a/newsfragments/2518.removal.rst b/newsfragments/2518.removal.rst new file mode 100644 index 000000000..296e7376e --- /dev/null +++ b/newsfragments/2518.removal.rst @@ -0,0 +1 @@ +Deprecated StakingEscrow features to reduce code size: batch deposits, testContract flag, separate setters for related contracts. diff --git a/nucypher/blockchain/eth/actors.py b/nucypher/blockchain/eth/actors.py index 869e7b2df..a2f54b9e7 100644 --- a/nucypher/blockchain/eth/actors.py +++ b/nucypher/blockchain/eth/actors.py @@ -352,92 +352,6 @@ class ContractAdministrator(NucypherTokenActor): receipts = deployer.rollback() return receipts - def batch_deposits(self, - allocation_data_filepath: str, - interactive: bool = True, - emitter: StdoutEmitter = None, - gas_limit: int = None - ) -> Dict[str, dict]: - """ - The allocations file is a JSON or CSV file containing a list of substakes. - Each substake is comprised of a staker address, an amount of tokens locked (in NuNits), - and a lock duration (in periods). - - It accepts both CSV and JSON formats. Example allocation file in CSV format: - - "checksum_address","amount","lock_periods" - "0xFABADA",123456,30 - "0xFABADA",789,45 - - Example allocation file in JSON format: - - [ {"checksum_address": "0xFABADA", "amount": 123456, "lock_periods": 30}, - {"checksum_address": "0xFABADA", "amount": 789, "lock_periods": 45}] - """ - - if interactive and not emitter: - raise ValueError("'emitter' is a required keyword argument when interactive is True.") - - allocator = Allocator(allocation_data_filepath, self.registry, self.deployer_address) - - # TODO: Check validity of input address (check TX) - - if emitter: - blockchain = BlockchainInterfaceFactory.get_interface() - chain_name = blockchain.client.chain_name - paint_input_allocation_file(emitter, allocator.allocations) - - if interactive: - click.confirm("Continue with the allocations process?", abort=True) - - batch_deposit_receipts, failed = dict(), False - with click.progressbar(length=len(allocator.allocations), - label="Allocation progress", - show_eta=False) as bar: - - while allocator.pending_deposits and not failed: - - self.activate_deployer(refresh=True) - - try: - deposited_stakers, receipt = allocator.deposit_next_batch(sender_address=self.deployer_address, - gas_limit=gas_limit) - except (TestTransactionFailed, ValidationError, ValueError): # TODO: 1950 - if emitter: - emitter.echo(f"\nFailed to deploy next batch. These addresses weren't funded:", color="yellow") - for staker in allocator.pending_deposits: - emitter.echo(f"\t{staker}", color="yellow") - emitter.echo(f"\nThe failure is caused by the following exception:") - - for line in traceback.format_exception(*sys.exc_info()): - emitter.echo(line, color='red') - failed = True - else: - number_of_deposits = len(deposited_stakers) - if emitter: - emitter.echo(f"\nDeployed allocations for {number_of_deposits} stakers:") - for staker in deposited_stakers: - emitter.echo(f"\t{staker}") - emitter.echo() - bar._last_line = None - bar.render_progress() - - bar.update(number_of_deposits) - - if emitter: - emitter.echo() - paint_receipt_summary(emitter=emitter, - receipt=receipt, - chain_name=chain_name, # TODO: this variable might be unused - transaction_type=f'batch_deposit_{number_of_deposits}_stakers') - - batch_deposit_receipts.update({staker: {'batch_deposit': receipt} for staker in deposited_stakers}) - - if interactive: - click.pause(info=f"\nPress any key to continue with next batch of allocations") - - return batch_deposit_receipts - def save_deployment_receipts(self, receipts: dict, filename_prefix: str = 'deployment') -> str: config_root = DEFAULT_CONFIG_ROOT # We force the use of the default here. filename = f'{filename_prefix}-receipts-{self.deployer_address[:6]}-{maya.now().epoch}.json' @@ -472,142 +386,6 @@ class ContractAdministrator(NucypherTokenActor): return receipt -class Allocator: - class AllocationInputError(Exception): - """Raised when the allocation data file doesn't have the correct format""" - - OCCUPATION_RATIO = 0.9 # When there's no explicit gas limit, we'll try to use the block limit up to this ratio - - def __init__(self, filepath: str, registry, deployer_address): - - self.log = Logger("allocator") - self.staking_agent = ContractAgency.get_agent(StakingEscrowAgent, - registry=registry) # type: StakingEscrowAgent - self.max_substakes = self.staking_agent.contract.functions.MAX_SUB_STAKES().call() - self.allocations = dict() - self.deposited = set() - self.economics = EconomicsFactory.get_economics(registry) - - self.__total_to_allocate = 0 - self.__process_allocation_data(str(filepath)) - self.__approve_token_transfer(registry, deployer_address) - - def __process_allocation_data(self, filepath: str): - try: - with open(filepath, 'r') as allocation_file: - if filepath.endswith(".csv"): - reader = csv.DictReader(allocation_file) - allocation_data = list(reader) - else: # Assume it's JSON by default - allocation_data = json.load(allocation_file) - except FileNotFoundError: - raise self.AllocationInputError(f"No allocation data file found at {filepath}") - - # Pre-process allocations data - for entry in allocation_data: - try: - staker = to_checksum_address(entry['checksum_address']) - amount = int(entry['amount']) - lock_periods = int(entry['lock_periods']) - except (KeyError, ValueError) as e: - raise self.AllocationInputError(f"Invalid allocation data: {str(e)}") - else: - self._add_substake(staker, amount, lock_periods) - self.__total_to_allocate += amount - - def __approve_token_transfer(self, registry, deployer_address): - token_agent = ContractAgency.get_agent(NucypherTokenAgent, registry=registry) # type: NucypherTokenAgent - - balance = token_agent.get_balance(deployer_address) - if balance < self.__total_to_allocate: - raise ValueError(f"Not enough tokens to allocate." - f"We need at least {NU.from_nunits(self.__total_to_allocate)}.") - - allowance = token_agent.get_allowance(owner=deployer_address, spender=self.staking_agent.contract_address) - if allowance < self.__total_to_allocate: - self.log.debug(f"Allocating a total of {NU.from_nunits(self.__total_to_allocate)}") - _allowance_receipt = token_agent.increase_allowance(sender_address=deployer_address, - spender_address=self.staking_agent.contract_address, - increase=NuNits(self.__total_to_allocate - allowance)) - - def _add_substake(self, staker, amount, lock_periods): - try: - substakes = self.allocations[staker] - if len(substakes) >= self.max_substakes: - raise ValueError(f"Number of sub-stakes, {len(substakes)}, must be ≤ {self.max_substakes}") - except KeyError: - if list(self.staking_agent.get_all_stakes(staker_address=staker)): - raise ValueError(f"{staker} is already a staker. It cannot be included in a batch deposit") - substakes = list() - self.allocations[staker] = substakes - - message = f"Invalid substake for {staker}: " - if amount < self.economics.minimum_allowed_locked: - message += f"Amount ({amount}) is below the min allowed ({self.economics.minimum_allowed_locked})" - raise ValueError(message) - overall_amount = sum([amount for amount, periods in substakes]) - if overall_amount + amount > self.economics.maximum_allowed_locked: - message += f"Total amount is above the max allowed ({self.economics.maximum_allowed_locked})" - raise ValueError(message) - if lock_periods < self.economics.minimum_locked_periods: - message += f"Lock periods ({lock_periods}) are below the min ({self.economics.minimum_locked_periods})" - raise ValueError(message) - - substakes.append((amount, lock_periods)) - - def deposit_next_batch(self, - sender_address: str, - gas_limit: int = None) -> Tuple[List[str], dict]: - - pending_stakers = self.pending_deposits - - self.log.debug(f"Constructing next batch. " - f"Currently, {len(pending_stakers)} stakers pending, {len(self.deposited)} deposited.") - - batch_size = 1 - if not gas_limit: - block_limit = self.staking_agent.blockchain.client.w3.eth.getBlock('latest').gasLimit - gas_limit = int(self.OCCUPATION_RATIO * block_limit) - self.log.debug(f"Gas limit for this batch is {gas_limit}") - - # Execute a dry-run of the batch deposit method, incrementing the batch size, until it's too big and fails. - last_good_batch = None - while batch_size <= len(pending_stakers): - test_batch = {staker: self.allocations[staker] for staker in pending_stakers[:batch_size]} - batch_parameters = self.staking_agent.construct_batch_deposit_parameters(deposits=test_batch) - try: - estimated_gas = self.staking_agent.batch_deposit(*batch_parameters, - sender_address=sender_address, - dry_run=True, - gas_limit=gas_limit) - except (TestTransactionFailed, ValidationError, ValueError): # TODO: 1950 - self.log.debug(f"Batch of {len(test_batch)} is too big. Let's stick to {len(test_batch) - 1} then") - break - else: - self.log.debug(f"Batch of {len(test_batch)} stakers fits in single TX ({estimated_gas} gas). " - f"Trying to squeeze one more staker...") - last_good_batch = test_batch - batch_size += 1 - - if not last_good_batch: - message = "It was not possible to find a new batch of deposits. " - raise ValueError(message) - - batch_parameters = self.staking_agent.construct_batch_deposit_parameters(deposits=last_good_batch) - receipt = self.staking_agent.batch_deposit(*batch_parameters, - sender_address=sender_address, - gas_limit=gas_limit) - - deposited_stakers = list(last_good_batch.keys()) - self.deposited.update(deposited_stakers) - return deposited_stakers, receipt - - @property - def pending_deposits(self) -> List[str]: - pending_deposits = [staker for staker in self.allocations.keys() if staker not in self.deposited] - return pending_deposits - - class MultiSigActor(BaseActor): class UnknownExecutive(Exception): """ diff --git a/nucypher/blockchain/eth/agents.py b/nucypher/blockchain/eth/agents.py index 787d1affe..766070447 100644 --- a/nucypher/blockchain/eth/agents.py +++ b/nucypher/blockchain/eth/agents.py @@ -461,58 +461,6 @@ class StakingEscrowAgent(EthereumContractAgent): sender_address=staker_address) return receipt - @contract_api(CONTRACT_CALL) - def construct_batch_deposit_parameters(self, deposits: Dict[ChecksumAddress, List[Tuple[int, int]]]) -> Tuple[list, list, list, list]: - max_substakes: int = self.contract.functions.MAX_SUB_STAKES().call() - stakers: List[ChecksumAddress] = list() - number_of_substakes: List[int] = list() - amounts: List[NuNits] = list() - lock_periods: List[int] = list() - for staker, substakes in deposits.items(): - if not 0 < len(substakes) <= max_substakes: - raise self.RequirementError(f"Number of substakes for staker {staker} must be >0 and ≤{max_substakes}") - # require(value >= minAllowableLockedTokens & & periods >= minLockedPeriods); - # require(info.value <= maxAllowableLockedTokens); - # require(info.subStakes.length == 0); - stakers.append(staker) - number_of_substakes.append(len(substakes)) - staker_amounts, staker_periods = zip(*substakes) - amounts.extend(staker_amounts) - lock_periods.extend(staker_periods) - - return stakers, number_of_substakes, amounts, lock_periods - - @contract_api(TRANSACTION) - def batch_deposit(self, - stakers: List[ChecksumAddress], - number_of_substakes: List[int], - amounts: List[NuNits], - lock_periods: List[PeriodDelta], - sender_address: ChecksumAddress, - dry_run: bool = False, - gas_limit: Optional[Wei] = None - ) -> Union[TxReceipt, Wei]: - - min_gas_batch_deposit: Wei = Wei(250_000) # TODO: move elsewhere? - if gas_limit and gas_limit < min_gas_batch_deposit: - raise ValueError(f"{gas_limit} is not enough gas for any batch deposit") - - contract_function: ContractFunction = self.contract.functions.batchDeposit( - stakers, number_of_substakes, amounts, lock_periods) - if dry_run: - payload: TxParams = {'from': sender_address} - if gas_limit: - payload['gas'] = gas_limit - estimated_gas: Wei = Wei(contract_function.estimateGas(payload)) # If TX is not correct, or there's not enough gas, this will fail. - if gas_limit and estimated_gas > gas_limit: - raise ValueError(f"Estimated gas for transaction exceeds gas limit {gas_limit}") - return estimated_gas - else: - receipt = self.blockchain.send_transaction(contract_function=contract_function, - sender_address=sender_address, - transaction_gas_limit=gas_limit) - return receipt - @contract_api(TRANSACTION) def divide_stake(self, staker_address: ChecksumAddress, diff --git a/nucypher/blockchain/eth/sol/source/contracts/StakingEscrow.sol b/nucypher/blockchain/eth/sol/source/contracts/StakingEscrow.sol index 2eb51f807..1dc1430c9 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/StakingEscrow.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/StakingEscrow.sol @@ -46,7 +46,7 @@ interface WorkLockInterface { * @title StakingEscrow * @notice Contract holds and locks stakers tokens. * Each staker that locks their tokens will receive some compensation -* @dev |v5.5.1| +* @dev |v5.6.1| */ contract StakingEscrow is Issuer, IERC900History { @@ -633,63 +633,6 @@ contract StakingEscrow is Issuer, IERC900History { } } - - /** - * @notice Batch deposit. Allowed only initial deposit for each staker - * @param _stakers Stakers - * @param _numberOfSubStakes Number of sub-stakes which belong to staker in _values and _periods arrays - * @param _values Amount of tokens to deposit for each staker - * @param _periods Amount of periods during which tokens will be locked for each staker - */ - function batchDeposit( - address[] calldata _stakers, - uint256[] calldata _numberOfSubStakes, - uint256[] calldata _values, - uint16[] calldata _periods - ) - external - { - uint256 subStakesLength = _values.length; - require(_stakers.length != 0 && - _stakers.length == _numberOfSubStakes.length && - subStakesLength >= _stakers.length && - _periods.length == subStakesLength); - uint16 previousPeriod = getCurrentPeriod() - 1; - uint16 nextPeriod = previousPeriod + 2; - uint256 sumValue = 0; - - uint256 j = 0; - for (uint256 i = 0; i < _stakers.length; i++) { - address staker = _stakers[i]; - uint256 numberOfSubStakes = _numberOfSubStakes[i]; - uint256 endIndex = j + numberOfSubStakes; - require(numberOfSubStakes > 0 && subStakesLength >= endIndex); - StakerInfo storage info = stakerInfo[staker]; - require(info.subStakes.length == 0 && !info.flags.bitSet(SNAPSHOTS_DISABLED_INDEX)); - // A staker can't be a worker for another staker - require(stakerFromWorker[staker] == address(0)); - stakers.push(staker); - policyManager.register(staker, previousPeriod); - - for (; j < endIndex; j++) { - uint256 value = _values[j]; - uint16 periods = _periods[j]; - require(value >= minAllowableLockedTokens && periods >= minLockedPeriods); - info.value = info.value.add(value); - info.subStakes.push(SubStakeInfo(nextPeriod, 0, periods, uint128(value))); - sumValue = sumValue.add(value); - emit Deposited(staker, value, periods); - emit Locked(staker, value, nextPeriod, periods); - } - require(info.value <= maxAllowableLockedTokens); - info.history.addSnapshot(info.value); - } - require(j == subStakesLength); - uint256 lastGlobalBalance = uint256(balanceHistory.lastValue()); - balanceHistory.addSnapshot(lastGlobalBalance + sumValue); - token.safeTransferFrom(msg.sender, address(this), sumValue); - } - /** * @notice Implementation of the receiveApproval(address,uint256,address,bytes) method * (see NuCypherToken contract). Deposit all tokens that were approved to transfer diff --git a/nucypher/cli/commands/deploy.py b/nucypher/cli/commands/deploy.py index 8c0825fcd..de7953805 100644 --- a/nucypher/cli/commands/deploy.py +++ b/nucypher/cli/commands/deploy.py @@ -66,8 +66,6 @@ from nucypher.cli.literature import ( SUCCESSFUL_REGISTRY_DOWNLOAD, SUCCESSFUL_RETARGET, SUCCESSFUL_RETARGET_TX_BUILT, - SUCCESSFUL_SAVE_BATCH_DEPOSIT_RECEIPTS, - SUCCESSFUL_SAVE_DEPLOY_RECEIPTS, SUCCESSFUL_SAVE_MULTISIG_TX_PROPOSAL, SUCCESSFUL_UPGRADE, UNKNOWN_CONTRACT_NAME, @@ -542,26 +540,6 @@ def contracts(general_config, actor_options, mode, activate, gas, ignore_deploye # emitter.echo(SUCCESSFUL_SAVE_DEPLOY_RECEIPTS.format(receipts_filepath=receipts_filepath), color='blue', bold=True) -@deploy.command() -@group_general_config -@group_actor_options -@click.option('--allocation-infile', help="Input path for token allocation JSON file", type=EXISTING_READABLE_FILE) -@option_gas -def allocations(general_config, actor_options, allocation_infile, gas): - """Deposit stake allocations in batches""" - emitter = general_config.emitter - ADMINISTRATOR, _, deployer_interface, local_registry = actor_options.create_actor(emitter) - if not allocation_infile: - allocation_infile = click.prompt(PROMPT_FOR_ALLOCATION_DATA_FILEPATH) - receipts = ADMINISTRATOR.batch_deposits(allocation_data_filepath=allocation_infile, - emitter=emitter, - gas_limit=gas, - interactive=not actor_options.force) - receipts_filepath = ADMINISTRATOR.save_deployment_receipts(receipts=receipts, filename_prefix='batch_deposits') - if emitter: - emitter.echo(SUCCESSFUL_SAVE_BATCH_DEPOSIT_RECEIPTS.format(receipts_filepath=receipts_filepath), color='blue', bold=True) - - @deploy.command("transfer-ownership") @group_general_config @group_actor_options diff --git a/nucypher/cli/literature.py b/nucypher/cli/literature.py index 7d47e9691..ea4cb326d 100644 --- a/nucypher/cli/literature.py +++ b/nucypher/cli/literature.py @@ -425,8 +425,6 @@ DISPLAY_SENDER_TOKEN_BALANCE_BEFORE_TRANSFER = "Deployer NU balance: {token_bala PROMPT_FOR_ALLOCATION_DATA_FILEPATH = "Enter allocations data filepath" -SUCCESSFUL_SAVE_BATCH_DEPOSIT_RECEIPTS = "Saved batch deposits receipts to {receipts_filepath}" - SUCCESSFUL_SAVE_DEPLOY_RECEIPTS = "Saved deployment receipts to {receipts_filepath}" SUCCESSFUL_REGISTRY_CREATION = 'Wrote to registry {registry_outfile}' diff --git a/tests/acceptance/blockchain/actors/test_deployer.py b/tests/acceptance/blockchain/actors/test_deployer.py index 1c1d49548..661f1d8a2 100644 --- a/tests/acceptance/blockchain/actors/test_deployer.py +++ b/tests/acceptance/blockchain/actors/test_deployer.py @@ -72,7 +72,5 @@ def test_rapid_deployment(token_economics, test_registry, tmpdir, get_random_che with open(filepath, 'w') as f: json.dump(allocation_data, f) - administrator.batch_deposits(allocation_data_filepath=str(filepath), interactive=False) - minimum, default, maximum = 10, 20, 30 administrator.set_fee_rate_range(minimum, default, maximum) diff --git a/tests/acceptance/blockchain/agents/test_staking_escrow_agent.py b/tests/acceptance/blockchain/agents/test_staking_escrow_agent.py index 79966e463..1656eb673 100644 --- a/tests/acceptance/blockchain/agents/test_staking_escrow_agent.py +++ b/tests/acceptance/blockchain/agents/test_staking_escrow_agent.py @@ -452,59 +452,3 @@ def test_remove_unused_stake(agency, testerchain, test_registry): assert stakes[3] == original_stakes[3] assert staking_agent.get_locked_tokens(staker_account, 1) == next_locked_tokens assert staking_agent.get_locked_tokens(staker_account, 0) == current_locked_tokens - - -def test_batch_deposit(testerchain, - agency, - token_economics, - mock_transacting_power_activation, - get_random_checksum_address): - - token_agent, staking_agent, _policy_agent = agency - - amount = token_economics.minimum_allowed_locked - lock_periods = token_economics.minimum_locked_periods - - stakers = [get_random_checksum_address() for _ in range(4)] - - N = 5 - substakes = [(amount, lock_periods)] * N - deposits = {staker: substakes for staker in stakers} - - batch_parameters = staking_agent.construct_batch_deposit_parameters(deposits=deposits) - - assert batch_parameters[0] == stakers - assert batch_parameters[1] == [N] * len(stakers) - assert batch_parameters[2] == [amount] * (N * len(stakers)) - assert batch_parameters[3] == [lock_periods] * (N * len(stakers)) - - mock_transacting_power_activation(account=testerchain.etherbase_account, password=INSECURE_DEVELOPMENT_PASSWORD) - - tokens_in_batch = sum(batch_parameters[2]) - - _receipt = token_agent.approve_transfer(amount=tokens_in_batch, - spender_address=staking_agent.contract_address, - sender_address=testerchain.etherbase_account) - - not_enough_gas = 800_000 - with pytest.raises((TransactionFailed, ValueError)): - staking_agent.batch_deposit(*batch_parameters, - sender_address=testerchain.etherbase_account, - dry_run=True, - gas_limit=not_enough_gas) - - staking_agent.batch_deposit(*batch_parameters, - sender_address=testerchain.etherbase_account, - dry_run=True) - - staking_agent.batch_deposit(*batch_parameters, - sender_address=testerchain.etherbase_account) - - for staker in stakers: - assert staking_agent.owned_tokens(staker_address=staker) == amount * N - staker_substakes = list(staking_agent.get_all_stakes(staker_address=staker)) - assert N == len(staker_substakes) - for substake in staker_substakes: - first_period, last_period, locked_value = substake - assert last_period == first_period + lock_periods - 1 - assert locked_value == amount diff --git a/tests/acceptance/cli/deploy/test_deploy_cli_commands.py b/tests/acceptance/cli/deploy/test_deploy_cli_commands.py index e0418ad0b..2a0dde773 100644 --- a/tests/acceptance/cli/deploy/test_deploy_cli_commands.py +++ b/tests/acceptance/cli/deploy/test_deploy_cli_commands.py @@ -260,33 +260,6 @@ def test_manual_proxy_retargeting(monkeypatch, testerchain, click_runner, token_ assert proxy_deployer.target_contract.address == untargeted_deployment.address -def test_batch_deposits(click_runner, - testerchain, - agency_local_registry, - mock_allocation_infile, - token_economics): - # - # Main - # - - deploy_command = ('allocations', - '--registry-infile', agency_local_registry.filepath, - '--allocation-infile', mock_allocation_infile, - '--network', TEMPORARY_DOMAIN, - '--provider', TEST_PROVIDER_URI) - - account_index = '0\n' - user_input = account_index + YES_ENTER + YES_ENTER - - result = click_runner.invoke(deploy, - deploy_command, - input=user_input, - catch_exceptions=False) - assert result.exit_code == 0, result.output - for allocation_address in testerchain.unassigned_accounts: - assert allocation_address in result.output - - def test_manual_deployment_of_idle_network(click_runner): if os.path.exists(ALTERNATE_REGISTRY_FILEPATH_2): diff --git a/tests/contracts/integration/test_intercontract_integration.py b/tests/contracts/integration/test_intercontract_integration.py index 228cf5d39..0dcdc44b5 100644 --- a/tests/contracts/integration/test_intercontract_integration.py +++ b/tests/contracts/integration/test_intercontract_integration.py @@ -294,56 +294,6 @@ def preallocation_escrow_2(testerchain, token, staking_interface, staking_interf return contract - -def test_batch_deposit(testerchain, token_economics, token, escrow, multisig): - creator, staker1, staker2, staker3, staker4, _alice1, _alice2, *contracts_owners =\ - testerchain.client.accounts - contracts_owners = sorted(contracts_owners) - - # Travel to the start of the next period to prevent problems with unexpected overflow first period - testerchain.time_travel(hours=1) - - # Staker gives Escrow rights to transfer - tx = token.functions.approve(escrow.address, 10000).transact({'from': staker1}) - testerchain.wait_for_receipt(tx) - - # Check that nothing is locked - assert 0 == escrow.functions.getLockedTokens(staker1, 0).call() - assert 0 == escrow.functions.getLockedTokens(staker2, 0).call() - assert 0 == escrow.functions.getLockedTokens(staker3, 0).call() - assert 0 == escrow.functions.getLockedTokens(staker4, 0).call() - - # Deposit tokens for 1 staker - pytest.staker1_tokens = token_economics.minimum_allowed_locked - staker1_tokens = pytest.staker1_tokens - duration = token_economics.minimum_locked_periods - current_period = escrow.functions.getCurrentPeriod().call() - - tx = token.functions.transfer(multisig.address, staker1_tokens).transact({'from': creator}) - testerchain.wait_for_receipt(tx) - tx = token.functions.approve(escrow.address, staker1_tokens) \ - .buildTransaction({'from': multisig.address, 'gasPrice': 0}) - execute_multisig_transaction(testerchain, multisig, [contracts_owners[0], contracts_owners[1]], tx) - tx = escrow.functions.batchDeposit([staker1], [1], [staker1_tokens], [duration])\ - .buildTransaction({'from': multisig.address, 'gasPrice': 0}) - execute_multisig_transaction(testerchain, multisig, [contracts_owners[0], contracts_owners[1]], tx) - - pytest.escrow_supply = token_economics.minimum_allowed_locked - assert token.functions.balanceOf(escrow.address).call() == pytest.escrow_supply - assert escrow.functions.getAllTokens(staker1).call() == staker1_tokens - assert escrow.functions.getLockedTokens(staker1, 0).call() == 0 - assert escrow.functions.getLockedTokens(staker1, 1).call() == staker1_tokens - assert escrow.functions.getLockedTokens(staker1, duration).call() == staker1_tokens - assert escrow.functions.getLockedTokens(staker1, duration + 1).call() == 0 - - # Can't deposit tokens again for the same staker - with pytest.raises((TransactionFailed, ValueError)): - execute_multisig_transaction(testerchain, multisig, [contracts_owners[0], contracts_owners[1]], tx) - - tx = token.functions.approve(escrow.address, 0).transact({'from': creator}) - testerchain.wait_for_receipt(tx) - - def test_staking_before_initialization(testerchain, token_economics, token, @@ -356,11 +306,40 @@ def test_staking_before_initialization(testerchain, testerchain.client.accounts contracts_owners = sorted(contracts_owners) + # Travel to the start of the next period to prevent problems with unexpected overflow first period + testerchain.time_travel(hours=1) + # Give staker some coins tx = token.functions.transfer(staker1, 10000).transact({'from': creator}) testerchain.wait_for_receipt(tx) assert 10000 == token.functions.balanceOf(staker1).call() + # Check that nothing is locked + assert 0 == escrow.functions.getLockedTokens(staker1, 0).call() + assert 0 == escrow.functions.getLockedTokens(staker2, 0).call() + assert 0 == escrow.functions.getLockedTokens(staker3, 0).call() + assert 0 == escrow.functions.getLockedTokens(staker4, 0).call() + + # Deposit tokens for 1 staker + tx = token.functions.approve(escrow.address, 10000).transact({'from': creator}) + testerchain.wait_for_receipt(tx) + pytest.staker1_tokens = token_economics.minimum_allowed_locked + staker1_tokens = pytest.staker1_tokens + duration = token_economics.minimum_locked_periods + tx = escrow.functions.deposit(staker1, staker1_tokens, duration).transact({'from': creator}) + testerchain.wait_for_receipt(tx) + + pytest.escrow_supply = token_economics.minimum_allowed_locked + assert token.functions.balanceOf(escrow.address).call() == pytest.escrow_supply + assert escrow.functions.getAllTokens(staker1).call() == staker1_tokens + assert escrow.functions.getLockedTokens(staker1, 0).call() == 0 + assert escrow.functions.getLockedTokens(staker1, 1).call() == staker1_tokens + assert escrow.functions.getLockedTokens(staker1, duration).call() == staker1_tokens + assert escrow.functions.getLockedTokens(staker1, duration + 1).call() == 0 + + tx = token.functions.approve(escrow.address, 0).transact({'from': creator}) + testerchain.wait_for_receipt(tx) + # Set and lock re-stake parameter in first preallocation escrow _wind_down, re_stake, _measure_work, _snapshots = escrow.functions.getFlags(preallocation_escrow_1.address).call() assert re_stake @@ -652,6 +631,8 @@ def test_staking_after_worklock(testerchain, testerchain.wait_for_receipt(tx) # Staker transfers some tokens to the escrow and lock them + tx = token.functions.approve(escrow.address, 1000).transact({'from': staker1}) + testerchain.wait_for_receipt(tx) tx = escrow.functions.deposit(staker1, 1000, 10).transact({'from': staker1}) testerchain.wait_for_receipt(tx) tx = escrow.functions.bondWorker(staker1).transact({'from': staker1}) diff --git a/tests/contracts/main/staking_escrow/test_staking_escrow.py b/tests/contracts/main/staking_escrow/test_staking_escrow.py index f7389876a..24f7a6625 100644 --- a/tests/contracts/main/staking_escrow/test_staking_escrow.py +++ b/tests/contracts/main/staking_escrow/test_staking_escrow.py @@ -1256,302 +1256,6 @@ def test_allowable_locked_tokens(testerchain, token_economics, token, escrow_con testerchain.wait_for_receipt(tx) -def test_batch_deposit(testerchain, token, escrow_contract, deploy_contract): - escrow = escrow_contract(1500, disable_reward=True) - policy_manager_interface = testerchain.get_contract_factory('PolicyManagerForStakingEscrowMock') - policy_manager = testerchain.client.get_contract( - abi=policy_manager_interface.abi, - address=escrow.functions.policyManager().call(), - ContractFactoryClass=Contract) - - creator = testerchain.client.accounts[0] - deposit_log = escrow.events.Deposited.createFilter(fromBlock='latest') - lock_log = escrow.events.Locked.createFilter(fromBlock='latest') - - # Grant access to transfer tokens - tx = token.functions.approve(escrow.address, 10000).transact({'from': creator}) - testerchain.wait_for_receipt(tx) - - # Can't deposit tokens if taking snapshots was disabled - staker = testerchain.client.accounts[1] - tx = escrow.functions.setSnapshots(False).transact({'from': staker}) - testerchain.wait_for_receipt(tx) - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.batchDeposit([staker], [1], [1000], [10]).transact({'from': creator}) - testerchain.wait_for_receipt(tx) - - # Deposit tokens for 1 staker - tx = escrow.functions.setSnapshots(True).transact({'from': staker}) - testerchain.wait_for_receipt(tx) - tx = escrow.functions.batchDeposit([staker], [1], [1000], [10]).transact({'from': creator}) - testerchain.wait_for_receipt(tx) - escrow_balance = 1000 - assert token.functions.balanceOf(escrow.address).call() == escrow_balance - assert escrow.functions.getAllTokens(staker).call() == 1000 - assert escrow.functions.getLockedTokens(staker, 0).call() == 0 - assert escrow.functions.getLockedTokens(staker, 1).call() == 1000 - assert escrow.functions.getLockedTokens(staker, 10).call() == 1000 - assert escrow.functions.getLockedTokens(staker, 11).call() == 0 - current_period = escrow.functions.getCurrentPeriod().call() - assert policy_manager.functions.getPeriodsLength(staker).call() == 1 - assert policy_manager.functions.getPeriod(staker, 0).call() == current_period - 1 - assert escrow.functions.getPastDowntimeLength(staker).call() == 0 - assert escrow.functions.getLastCommittedPeriod(staker).call() == 0 - - deposit_events = deposit_log.get_all_entries() - assert len(deposit_events) == 1 - event_args = deposit_events[-1]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == 1000 - assert event_args['periods'] == 10 - - lock_events = lock_log.get_all_entries() - assert len(lock_events) == 1 - event_args = lock_events[-1]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == 1000 - assert event_args['firstPeriod'] == current_period + 1 - assert event_args['periods'] == 10 - - # Can't deposit tokens again for the same staker twice - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.batchDeposit([staker], [1], [1000], [10])\ - .transact({'from': creator}) - testerchain.wait_for_receipt(tx) - - # Can't deposit tokens with too low or too high value - staker = testerchain.client.accounts[2] - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.batchDeposit([staker], [1], [1], [10])\ - .transact({'from': creator}) - testerchain.wait_for_receipt(tx) - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.batchDeposit([staker], [1], [1501], [10])\ - .transact({'from': creator}) - testerchain.wait_for_receipt(tx) - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.batchDeposit([staker], [1], [500], [1])\ - .transact({'from': creator}) - testerchain.wait_for_receipt(tx) - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.batchDeposit([staker], [2], [1000, 501], [10, 10])\ - .transact({'from': creator}) - testerchain.wait_for_receipt(tx) - - # Inconsistent input - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.batchDeposit([staker], [0], [500], [10])\ - .transact({'from': creator}) - testerchain.wait_for_receipt(tx) - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.batchDeposit([staker], [2], [500], [10])\ - .transact({'from': creator}) - testerchain.wait_for_receipt(tx) - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.batchDeposit([staker], [1, 1], [500], [10])\ - .transact({'from': creator}) - testerchain.wait_for_receipt(tx) - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.batchDeposit([staker], [1, 1], [500, 500], [10])\ - .transact({'from': creator}) - testerchain.wait_for_receipt(tx) - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.batchDeposit([staker], [1, 1], [500, 500], [10, 10])\ - .transact({'from': creator}) - testerchain.wait_for_receipt(tx) - stakers = testerchain.client.accounts[2:4] - with pytest.raises((TransactionFailed, ValueError)): - tx = escrow.functions.batchDeposit(stakers, [1, 1], [500, 500, 500], [10, 10, 10])\ - .transact({'from': creator}) - testerchain.wait_for_receipt(tx) - - # Initialize Escrow contract - tx = escrow.functions.initialize(0, creator).transact({'from': creator}) - testerchain.wait_for_receipt(tx) - - # Deposit tokens for multiple stakers - tx = token.functions.transfer(staker, 1500).transact({'from': creator}) - testerchain.wait_for_receipt(tx) - tx = token.functions.approve(escrow.address, 1500).transact({'from': staker}) - testerchain.wait_for_receipt(tx) - stakers = testerchain.client.accounts[2:7] - tx = escrow.functions.setWindDown(True).transact({'from': stakers[0]}) - testerchain.wait_for_receipt(tx) - current_period = escrow.functions.getCurrentPeriod().call() - tx = escrow.functions.batchDeposit( - stakers, [1, 1, 1, 1, 1], [100, 200, 300, 400, 500], [50, 100, 150, 200, 250] - ).transact({'from': staker}) - testerchain.wait_for_receipt(tx) - - escrow_balance += 1500 - assert token.functions.balanceOf(escrow.address).call() == escrow_balance - deposit_events = deposit_log.get_all_entries() - lock_events = lock_log.get_all_entries() - - assert len(deposit_events) == 6 - assert len(lock_events) == 6 - - for index, staker in enumerate(stakers): - value = 100 * (index + 1) - duration = 50 * (index + 1) - assert escrow.functions.getAllTokens(staker).call() == value - assert escrow.functions.getLockedTokens(staker, 0).call() == 0 - assert escrow.functions.getLockedTokens(staker, 1).call() == value - assert escrow.functions.getLockedTokens(staker, duration).call() == value - assert escrow.functions.getLockedTokens(staker, duration + 1).call() == 0 - assert policy_manager.functions.getPeriodsLength(staker).call() == 1 - assert policy_manager.functions.getPeriod(staker, 0).call() == current_period - 1 - assert escrow.functions.getPastDowntimeLength(staker).call() == 0 - assert escrow.functions.getLastCommittedPeriod(staker).call() == 0 - - event_args = deposit_events[index + 1]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == value - assert event_args['periods'] == duration - - event_args = lock_events[index + 1]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == value - assert event_args['firstPeriod'] == current_period + 1 - assert event_args['periods'] == duration - - # Deposit tokens for multiple stakers with multiple sub-stakes - stakers = testerchain.client.accounts[7:10] - current_period = escrow.functions.getCurrentPeriod().call() - - tx = escrow.functions.batchDeposit( - stakers, [1, 2, 3], [100, 200, 300, 400, 500, 600], [50, 100, 150, 200, 250, 300])\ - .transact({'from': creator}) - testerchain.wait_for_receipt(tx) - - escrow_balance += 2100 - assert token.functions.balanceOf(escrow.address).call() == escrow_balance - deposit_events = deposit_log.get_all_entries() - lock_events = lock_log.get_all_entries() - - assert len(deposit_events) == 12 - assert len(lock_events) == 12 - - staker = stakers[0] - duration = 50 - value = 100 - assert escrow.functions.getAllTokens(staker).call() == value - assert escrow.functions.getLockedTokens(staker, 1).call() == value - assert escrow.functions.getLockedTokens(staker, duration).call() == value - assert escrow.functions.getLockedTokens(staker, duration + 1).call() == 0 - assert policy_manager.functions.getPeriodsLength(staker).call() == 1 - assert policy_manager.functions.getPeriod(staker, 0).call() == current_period - 1 - assert escrow.functions.getPastDowntimeLength(staker).call() == 0 - assert escrow.functions.getLastCommittedPeriod(staker).call() == 0 - assert escrow.functions.getSubStakesLength(staker).call() == 1 - - event_args = deposit_events[6]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == value - assert event_args['periods'] == duration - - event_args = lock_events[6]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == value - assert event_args['firstPeriod'] == current_period + 1 - assert event_args['periods'] == duration - - staker = stakers[1] - duration1 = 100 - duration2 = 150 - value1 = 200 - value2 = 300 - assert escrow.functions.getAllTokens(staker).call() == value1 + value2 - assert escrow.functions.getLockedTokens(staker, 0).call() == 0 - assert escrow.functions.getLockedTokens(staker, 1).call() == value1 + value2 - assert escrow.functions.getLockedTokens(staker, duration1).call() == value1 + value2 - assert escrow.functions.getLockedTokens(staker, duration1 + 1).call() == value2 - assert escrow.functions.getLockedTokens(staker, duration2).call() == value2 - assert escrow.functions.getLockedTokens(staker, duration2 + 1).call() == 0 - assert policy_manager.functions.getPeriodsLength(staker).call() == 1 - assert policy_manager.functions.getPeriod(staker, 0).call() == current_period - 1 - assert escrow.functions.getPastDowntimeLength(staker).call() == 0 - assert escrow.functions.getLastCommittedPeriod(staker).call() == 0 - assert escrow.functions.getSubStakesLength(staker).call() == 2 - - event_args = deposit_events[7]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == value1 - assert event_args['periods'] == duration1 - - event_args = lock_events[7]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == value1 - assert event_args['firstPeriod'] == current_period + 1 - assert event_args['periods'] == duration1 - - event_args = deposit_events[8]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == value2 - assert event_args['periods'] == duration2 - - event_args = lock_events[8]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == value2 - assert event_args['firstPeriod'] == current_period + 1 - assert event_args['periods'] == duration2 - - staker = stakers[2] - duration1 = 200 - duration2 = 250 - duration3 = 300 - value1 = 400 - value2 = 500 - value3 = 600 - assert escrow.functions.getAllTokens(staker).call() == value1 + value2 + value3 - assert escrow.functions.getLockedTokens(staker, 0).call() == 0 - assert escrow.functions.getLockedTokens(staker, 1).call() == value1 + value2 + value3 - assert escrow.functions.getLockedTokens(staker, duration1).call() == value1 + value2 + value3 - assert escrow.functions.getLockedTokens(staker, duration1 + 1).call() == value2 + value3 - assert escrow.functions.getLockedTokens(staker, duration2).call() == value2 + value3 - assert escrow.functions.getLockedTokens(staker, duration2 + 1).call() == value3 - assert escrow.functions.getLockedTokens(staker, duration3).call() == value3 - assert escrow.functions.getLockedTokens(staker, duration3 + 1).call() == 0 - assert policy_manager.functions.getPeriodsLength(staker).call() == 1 - assert policy_manager.functions.getPeriod(staker, 0).call() == current_period - 1 - assert escrow.functions.getPastDowntimeLength(staker).call() == 0 - assert escrow.functions.getLastCommittedPeriod(staker).call() == 0 - assert escrow.functions.getSubStakesLength(staker).call() == 3 - - event_args = deposit_events[9]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == value1 - assert event_args['periods'] == duration1 - - event_args = lock_events[9]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == value1 - assert event_args['firstPeriod'] == current_period + 1 - assert event_args['periods'] == duration1 - - event_args = deposit_events[10]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == value2 - assert event_args['periods'] == duration2 - - event_args = lock_events[10]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == value2 - assert event_args['firstPeriod'] == current_period + 1 - assert event_args['periods'] == duration2 - - event_args = deposit_events[11]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == value3 - assert event_args['periods'] == duration3 - - event_args = lock_events[11]['args'] - assert event_args['staker'] == staker - assert event_args['value'] == value3 - assert event_args['firstPeriod'] == current_period + 1 - assert event_args['periods'] == duration3 - - def test_staking_from_worklock(testerchain, token, escrow_contract, token_economics, deploy_contract): """ Tests for staking method: depositFromWorkLock diff --git a/tests/metrics/estimate_gas.py b/tests/metrics/estimate_gas.py index 8887007bd..c97167b69 100755 --- a/tests/metrics/estimate_gas.py +++ b/tests/metrics/estimate_gas.py @@ -183,6 +183,7 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None: compiled_contract = testerchain._raw_contract_cache[contract_name] version = list(compiled_contract).pop() + # FIXME this value includes constructor code size but should not bin_runtime = compiled_contract[version]['evm']['bytecode']['object'] bin_length_in_bytes = len(bin_runtime) // 2 percentage = int(100 * bin_length_in_bytes / MAX_SIZE) @@ -224,6 +225,11 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None: tx = function.transact(transaction) testerchain.wait_for_receipt(tx) + # First deposit ever is the most expensive, make it to remove unusual gas spending + transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 10), {'from': origin}) + transact(staker_functions.deposit(everyone_else[0], MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS), {'from': origin}) + testerchain.time_travel(periods=1) + # # Give Ursula and Alice some coins # @@ -240,34 +246,6 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None: transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6), {'from': staker2}) transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 6), {'from': staker3}) - # - # Batch deposit tokens - # - current_period = staking_agent.get_current_period() - transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 10), {'from': origin}) - transact_and_log("Batch deposit tokens for 5 owners x 2 sub-stakes", - staker_functions.batchDeposit(everyone_else[0:5], - [2] * 5, - [MIN_ALLOWED_LOCKED] * 10, - [MIN_LOCKED_PERIODS] * 10), - {'from': origin}) - - transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 24), {'from': origin}) - transact_and_log("Batch deposit tokens for 1 owners x 24 sub-stakes", - staker_functions.batchDeposit([everyone_else[6]], - [24], - [MIN_ALLOWED_LOCKED] * 24, - [MIN_LOCKED_PERIODS] * 24), - {'from': origin}) - - transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * 24 * 5), {'from': origin}) - transact_and_log("Batch deposit tokens for 5 owners x 24 sub-stakes", - staker_functions.batchDeposit(everyone_else[7:12], - [24]*5, - [MIN_ALLOWED_LOCKED] * (24 * 5), - [MIN_LOCKED_PERIODS] * (24 * 5)), - {'from': origin}) - # # Ursula and Alice transfer some tokens to the escrow and lock them # @@ -587,13 +565,12 @@ def estimate_gas(analyzer: AnalyzeGas = None) -> None: # Large number of sub-stakes number_of_sub_stakes = 24 + transact(token_functions.approve(staking_agent.contract_address, 0), {'from': origin}) transact(token_functions.approve(staking_agent.contract_address, MIN_ALLOWED_LOCKED * number_of_sub_stakes), {'from': origin}) - transact(staker_functions.batchDeposit([staker4], - [number_of_sub_stakes], - [MIN_ALLOWED_LOCKED] * number_of_sub_stakes, - [MIN_LOCKED_PERIODS] * number_of_sub_stakes), - {'from': origin}) + for i in range(number_of_sub_stakes): + transact(staker_functions.deposit(staker4, MIN_ALLOWED_LOCKED, MIN_LOCKED_PERIODS), + {'from': origin}) transact(staker_functions.bondWorker(staker4), {'from': staker4}) transact(staker_functions.setWindDown(True), {'from': staker4})