diff --git a/specs/electra/beacon-chain.md b/specs/electra/beacon-chain.md index 0c56491907..1bf78851c7 100644 --- a/specs/electra/beacon-chain.md +++ b/specs/electra/beacon-chain.md @@ -1056,7 +1056,7 @@ def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], withdrawal_index = state.next_withdrawal_index validator_index = state.next_withdrawal_validator_index withdrawals: List[Withdrawal] = [] - partial_withdrawals_count = 0 + processed_partial_withdrawals_count = 0 # [New in Electra:EIP7251] Consume pending partial withdrawals for withdrawal in state.pending_partial_withdrawals: @@ -1076,13 +1076,16 @@ def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], )) withdrawal_index += WithdrawalIndex(1) - partial_withdrawals_count += 1 + processed_partial_withdrawals_count += 1 # Sweep for remaining. bound = min(len(state.validators), MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) for _ in range(bound): validator = state.validators[validator_index] - balance = state.balances[validator_index] + # [Modified in Electra:EIP7251] + partially_withdrawn_balance = sum( + withdrawal.amount for withdrawal in withdrawals if withdrawal.validator_index == validator_index) + balance = state.balances[validator_index] - partially_withdrawn_balance if is_fully_withdrawable_validator(validator, balance, epoch): withdrawals.append(Withdrawal( index=withdrawal_index, @@ -1102,7 +1105,7 @@ def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD: break validator_index = ValidatorIndex((validator_index + 1) % len(state.validators)) - return withdrawals, partial_withdrawals_count + return withdrawals, processed_partial_withdrawals_count ``` ##### Modified `process_withdrawals` @@ -1111,7 +1114,8 @@ def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], ```python def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None: - expected_withdrawals, partial_withdrawals_count = get_expected_withdrawals(state) # [Modified in Electra:EIP7251] + # [Modified in Electra:EIP7251] + expected_withdrawals, processed_partial_withdrawals_count = get_expected_withdrawals(state) assert payload.withdrawals == expected_withdrawals @@ -1119,7 +1123,7 @@ def process_withdrawals(state: BeaconState, payload: ExecutionPayload) -> None: decrease_balance(state, withdrawal.validator_index, withdrawal.amount) # Update pending partial withdrawals [New in Electra:EIP7251] - state.pending_partial_withdrawals = state.pending_partial_withdrawals[partial_withdrawals_count:] + state.pending_partial_withdrawals = state.pending_partial_withdrawals[processed_partial_withdrawals_count:] # Update the next withdrawal index if this block contained withdrawals if len(expected_withdrawals) != 0: diff --git a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawals.py b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawals.py index 555eae85b5..1757c79994 100644 --- a/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawals.py +++ b/tests/core/pyspec/eth2spec/test/electra/block_processing/test_process_withdrawals.py @@ -11,7 +11,7 @@ next_slot, ) from eth2spec.test.helpers.withdrawals import ( - prepare_expected_withdrawals_compounding, + prepare_expected_withdrawals, run_withdrawals_processing, set_compounding_withdrawal_credential_with_balance, prepare_pending_withdrawal, @@ -23,11 +23,11 @@ def test_success_mixed_fully_and_partial_withdrawable_compounding(spec, state): num_full_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 2 num_partial_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD - num_full_withdrawals - fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals_compounding( + fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals( spec, state, rng=random.Random(42), - num_full_withdrawals=num_full_withdrawals, - num_partial_withdrawals_sweep=num_partial_withdrawals, + num_full_withdrawals_comp=num_full_withdrawals, + num_partial_withdrawals_comp=num_partial_withdrawals, ) next_slot(spec, state) @@ -94,14 +94,351 @@ def test_pending_withdrawals_one_skipped_one_effective(spec, state): index_0 = 3 index_1 = 5 - withdrawal_0 = prepare_pending_withdrawal(spec, state, index_0) - withdrawal_1 = prepare_pending_withdrawal(spec, state, index_1) + pending_withdrawal_0 = prepare_pending_withdrawal(spec, state, index_0) + pending_withdrawal_1 = prepare_pending_withdrawal(spec, state, index_1) # If validator doesn't have an excess balance pending withdrawal is skipped state.balances[index_0] = spec.MIN_ACTIVATION_BALANCE execution_payload = build_empty_execution_payload(spec, state) - assert state.pending_partial_withdrawals == [withdrawal_0, withdrawal_1] - yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=1) + assert state.pending_partial_withdrawals == [pending_withdrawal_0, pending_withdrawal_1] + yield from run_withdrawals_processing( + spec, state, + execution_payload, + num_expected_withdrawals=1, + pending_withdrawal_requests=[pending_withdrawal_1] + ) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_next_epoch(spec, state): + validator_index = len(state.validators) // 2 + next_epoch = spec.get_current_epoch(state) + 1 + + pending_withdrawal = prepare_pending_withdrawal(spec, state, validator_index, withdrawable_epoch=next_epoch) + + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0) + + assert state.pending_partial_withdrawals == [pending_withdrawal] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_at_max(spec, state): + pending_withdrawal_requests = [] + # Create spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + 1 partial withdrawals + for i in range(0, spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + 1): + pending_withdrawal = prepare_pending_withdrawal(spec, state, i) + pending_withdrawal_requests.append(pending_withdrawal) + + assert len(state.pending_partial_withdrawals) == spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + 1 + + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + num_expected_withdrawals=spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP, + pending_withdrawal_requests=pending_withdrawal_requests[:spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP] + ) + + withdrawals_exceeding_max = pending_withdrawal_requests[spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP:] + assert state.pending_partial_withdrawals == withdrawals_exceeding_max + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_exiting_validator(spec, state): + validator_index = len(state.validators) // 2 + + pending_withdrawal = prepare_pending_withdrawal(spec, state, validator_index) + spec.initiate_validator_exit(state, pending_withdrawal.index) + + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_low_effective_balance(spec, state): + validator_index = len(state.validators) // 2 + + pending_withdrawal = prepare_pending_withdrawal(spec, state, validator_index) + state.validators[pending_withdrawal.index].effective_balance = ( + spec.MIN_ACTIVATION_BALANCE - spec.EFFECTIVE_BALANCE_INCREMENT + ) + + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_no_excess_balance(spec, state): + validator_index = len(state.validators) // 2 + + pending_withdrawal = prepare_pending_withdrawal(spec, state, validator_index) + state.balances[pending_withdrawal.index] = spec.MIN_ACTIVATION_BALANCE + + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=0) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_with_ineffective_sweep_on_top(spec, state): + # Ensure validator will be processed by the sweep + validator_index = min(len(state.validators), spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) // 2 + + pending_withdrawal = prepare_pending_withdrawal( + spec, state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA, + ) + + # Check that validator is partially withdrawable before pending withdrawal is processed + assert spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] + ) + # And is not partially withdrawable thereafter + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] - pending_withdrawal.amount + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + num_expected_withdrawals=1, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[], + pending_withdrawal_requests=[pending_withdrawal] + ) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_with_ineffective_sweep_on_top_2(spec, state): + # Ensure validator will be processed by the sweep + validator_index = min(len(state.validators), spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) // 2 + + pending_withdrawal_0 = prepare_pending_withdrawal( + spec, state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA, + amount=spec.EFFECTIVE_BALANCE_INCREMENT // 2 + ) + + pending_withdrawal_1 = prepare_pending_withdrawal( + spec, state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA, + amount=spec.EFFECTIVE_BALANCE_INCREMENT + ) + + # Set excess balance in a way that validator + # becomes not partially withdrawable only after the second pending withdrawal is processed + state.balances[validator_index] = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + spec.EFFECTIVE_BALANCE_INCREMENT + assert spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] - pending_withdrawal_0.amount + ) + assert not spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] - pending_withdrawal_0.amount - pending_withdrawal_1.amount + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + num_expected_withdrawals=2, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[], + pending_withdrawal_requests=[pending_withdrawal_0, pending_withdrawal_1] + ) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_with_effective_sweep_on_top(spec, state): + # Ensure validator will be processed by the sweep + validator_index = min(len(state.validators), spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) // 2 + + pending_withdrawal_0 = prepare_pending_withdrawal( + spec, state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA, + amount=spec.EFFECTIVE_BALANCE_INCREMENT // 2 + ) + + pending_withdrawal_1 = prepare_pending_withdrawal( + spec, state, + validator_index, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA, + amount=spec.EFFECTIVE_BALANCE_INCREMENT + ) + + # Set excess balance to requested amount times three, + # so the validator is partially withdrawable after pending withdrawal is processed + state.balances[validator_index] = spec.MAX_EFFECTIVE_BALANCE_ELECTRA + spec.EFFECTIVE_BALANCE_INCREMENT * 2 + assert spec.is_partially_withdrawable_validator( + state.validators[validator_index], + state.balances[validator_index] - pending_withdrawal_0.amount - pending_withdrawal_1.amount + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + num_expected_withdrawals=3, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[validator_index], + pending_withdrawal_requests=[pending_withdrawal_0, pending_withdrawal_1] + ) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_with_sweep_different_validator(spec, state): + # Ensure validator will be processed by the sweep + validator_index_0 = min(len(state.validators), spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) // 2 - 1 + validator_index_1 = min(len(state.validators), spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) // 2 + + # Initiate pending withdrawal for the first validator + pending_withdrawal_0 = prepare_pending_withdrawal( + spec, state, + validator_index_0, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA, + amount=spec.EFFECTIVE_BALANCE_INCREMENT + ) + + # Make the second validator partially withdrawable by the sweep + set_compounding_withdrawal_credential_with_balance( + spec, state, validator_index_1, + effective_balance=spec.MAX_EFFECTIVE_BALANCE_ELECTRA, + balance=(spec.MAX_EFFECTIVE_BALANCE_ELECTRA + spec.EFFECTIVE_BALANCE_INCREMENT) + ) + + assert spec.is_partially_withdrawable_validator( + state.validators[validator_index_1], + state.balances[validator_index_1] + ) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + num_expected_withdrawals=2, + fully_withdrawable_indices=[], + partial_withdrawals_indices=[validator_index_1], + pending_withdrawal_requests=[pending_withdrawal_0] + ) + + assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_mixed_with_sweep_and_fully_withdrawable(spec, state): + num_full_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_partial_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_full_withdrawals_comp = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_partial_withdrawals_comp = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_pending_withdrawal_requests = spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP // 2 + + fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals( + spec, state, + rng=random.Random(42), + num_full_withdrawals=num_full_withdrawals, + num_partial_withdrawals=num_partial_withdrawals, + num_full_withdrawals_comp=num_full_withdrawals_comp, + num_partial_withdrawals_comp=num_partial_withdrawals_comp, + ) + + pending_withdrawal_requests = [] + for index in range(0, len(state.validators)): + if len(pending_withdrawal_requests) >= num_pending_withdrawal_requests: + break + if index in (fully_withdrawable_indices + partial_withdrawals_indices): + continue + + pending_withdrawal = prepare_pending_withdrawal(spec, state, index) + pending_withdrawal_requests.append(pending_withdrawal) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + num_expected_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD, + fully_withdrawable_indices=fully_withdrawable_indices, + partial_withdrawals_indices=partial_withdrawals_indices, + pending_withdrawal_requests=pending_withdrawal_requests + ) assert state.pending_partial_withdrawals == [] + + +@with_electra_and_later +@spec_state_test +def test_pending_withdrawals_at_max_mixed_with_sweep_and_fully_withdrawable(spec, state): + num_full_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_partial_withdrawals = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_full_withdrawals_comp = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_partial_withdrawals_comp = spec.MAX_WITHDRAWALS_PER_PAYLOAD // 4 + num_pending_withdrawal_requests = spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP + 1 + + fully_withdrawable_indices, partial_withdrawals_indices = prepare_expected_withdrawals( + spec, state, + rng=random.Random(42), + num_full_withdrawals=num_full_withdrawals, + num_partial_withdrawals=num_partial_withdrawals, + num_full_withdrawals_comp=num_full_withdrawals_comp, + num_partial_withdrawals_comp=num_partial_withdrawals_comp, + ) + + pending_withdrawal_requests = [] + for index in range(0, len(state.validators)): + if len(pending_withdrawal_requests) >= num_pending_withdrawal_requests: + break + if index in (fully_withdrawable_indices + partial_withdrawals_indices): + continue + + pending_withdrawal = prepare_pending_withdrawal(spec, state, index) + pending_withdrawal_requests.append(pending_withdrawal) + + next_slot(spec, state) + execution_payload = build_empty_execution_payload(spec, state) + yield from run_withdrawals_processing( + spec, state, + execution_payload, + num_expected_withdrawals=spec.MAX_WITHDRAWALS_PER_PAYLOAD, + fully_withdrawable_indices=fully_withdrawable_indices, + partial_withdrawals_indices=partial_withdrawals_indices, + pending_withdrawal_requests=pending_withdrawal_requests[:spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP] + ) + + withdrawals_exceeding_max = pending_withdrawal_requests[spec.MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP:] + assert state.pending_partial_withdrawals == withdrawals_exceeding_max diff --git a/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py b/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py index 518920aeb2..b1cfaf869d 100644 --- a/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py +++ b/tests/core/pyspec/eth2spec/test/helpers/withdrawals.py @@ -63,11 +63,21 @@ def sample_withdrawal_indices(spec, state, rng, num_full_withdrawals, num_partia def prepare_expected_withdrawals(spec, state, rng, - num_full_withdrawals=0, num_partial_withdrawals=0): + num_full_withdrawals=0, num_partial_withdrawals=0, + num_full_withdrawals_comp=0, num_partial_withdrawals_comp=0): fully_withdrawable_indices, partial_withdrawals_indices = sample_withdrawal_indices( - spec, state, rng, num_full_withdrawals, num_partial_withdrawals + spec, state, rng, + num_full_withdrawals + num_full_withdrawals_comp, + num_partial_withdrawals + num_partial_withdrawals_comp ) + fully_withdrawable_indices_comp = rng.sample(fully_withdrawable_indices, num_full_withdrawals_comp) + partial_withdrawals_indices_comp = rng.sample(partial_withdrawals_indices, num_partial_withdrawals_comp) + + for index in (fully_withdrawable_indices_comp + partial_withdrawals_indices_comp): + address = state.validators[index].withdrawal_credentials[12:] + set_compounding_withdrawal_credential_with_balance(spec, state, index, address=address) + for index in fully_withdrawable_indices: set_validator_fully_withdrawable(spec, state, index) for index in partial_withdrawals_indices: @@ -97,32 +107,13 @@ def set_compounding_withdrawal_credential_with_balance(spec, state, index, state.balances[index] = balance -def prepare_expected_withdrawals_compounding(spec, state, rng, - num_full_withdrawals=0, - num_partial_withdrawals_sweep=0, - excess_balance=1000000000): - assert is_post_electra(spec) - - fully_withdrawable_indices, partial_withdrawals_sweep_indices = sample_withdrawal_indices( - spec, state, rng, num_full_withdrawals, num_partial_withdrawals_sweep - ) - - for index in fully_withdrawable_indices + partial_withdrawals_sweep_indices: - address = state.validators[index].withdrawal_credentials[12:] - set_compounding_withdrawal_credential_with_balance(spec, state, index, address=address) - - for index in fully_withdrawable_indices: - set_validator_fully_withdrawable(spec, state, index) - for index in partial_withdrawals_sweep_indices: - set_validator_partially_withdrawable(spec, state, index) - - return fully_withdrawable_indices, partial_withdrawals_sweep_indices - - def prepare_pending_withdrawal(spec, state, validator_index, - effective_balance=32_000_000_000, amount=1_000_000_000): + effective_balance=32_000_000_000, amount=1_000_000_000, withdrawable_epoch=None): assert is_post_electra(spec) + if withdrawable_epoch is None: + withdrawable_epoch = spec.get_current_epoch(state) + balance = effective_balance + amount set_compounding_withdrawal_credential_with_balance( spec, state, validator_index, effective_balance, balance @@ -131,7 +122,7 @@ def prepare_pending_withdrawal(spec, state, validator_index, withdrawal = spec.PendingPartialWithdrawal( index=validator_index, amount=amount, - withdrawable_epoch=spec.get_current_epoch(state), + withdrawable_epoch=withdrawable_epoch, ) state.pending_partial_withdrawals.append(withdrawal) @@ -175,7 +166,8 @@ def verify_post_state(state, spec, expected_withdrawals, def run_withdrawals_processing(spec, state, execution_payload, num_expected_withdrawals=None, - fully_withdrawable_indices=None, partial_withdrawals_indices=None, valid=True): + fully_withdrawable_indices=None, partial_withdrawals_indices=None, + pending_withdrawal_requests=None, valid=True): """ Run ``process_withdrawals``, yielding: - pre-state ('pre') @@ -206,6 +198,11 @@ def run_withdrawals_processing(spec, state, execution_payload, num_expected_with yield 'post', state + # Check withdrawal indices + assert state.next_withdrawal_index == pre_state.next_withdrawal_index + len(expected_withdrawals) + for index, withdrawal in enumerate(execution_payload.withdrawals): + assert withdrawal.index == pre_state.next_withdrawal_index + index + if len(expected_withdrawals) == 0: next_withdrawal_validator_index = ( pre_state.next_withdrawal_validator_index + spec.MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP @@ -220,4 +217,12 @@ def run_withdrawals_processing(spec, state, execution_payload, num_expected_with if fully_withdrawable_indices is not None or partial_withdrawals_indices is not None: verify_post_state(state, spec, expected_withdrawals, fully_withdrawable_indices, partial_withdrawals_indices) + # Check withdrawal requests + if pending_withdrawal_requests is not None: + assert len(pending_withdrawal_requests) <= len(execution_payload.withdrawals) + for index, request in enumerate(pending_withdrawal_requests): + withdrawal = execution_payload.withdrawals[index] + assert withdrawal.validator_index == request.index + assert withdrawal.amount == request.amount + return expected_withdrawals