pub(crate) fn test_protocol_upgrade_is_first<VM: TestedVm>() {
    let mut storage = get_empty_storage();
    let bytecode_hash = BytecodeHash::for_bytecode(TestContract::counter().bytecode).value();
    storage.set_value(get_known_code_key(&bytecode_hash), u256_to_h256(1.into()));

    let mut vm = VmTesterBuilder::new()
        .with_storage(storage)
        .with_execution_mode(TxExecutionMode::VerifyExecute)
        .with_rich_accounts(1)
        .build::<VM>();

    // Here we just use some random transaction of protocol upgrade type:
    let protocol_upgrade_transaction = get_forced_deploy_tx(&[ForceDeployment {
        // The bytecode hash to put on an address
        bytecode_hash,
        // The address on which to deploy the bytecode hash to
        address: Address::repeat_byte(1),
        // Whether to run the constructor on the force deployment
        call_constructor: false,
        // The value with which to initialize a contract
        value: U256::zero(),
        // The constructor calldata
        input: vec![],
    }]);

    // Another random upgrade transaction
    let another_protocol_upgrade_transaction = get_forced_deploy_tx(&[ForceDeployment {
        // The bytecode hash to put on an address
        bytecode_hash,
        // The address on which to deploy the bytecode hash to
        address: Address::repeat_byte(2),
        // Whether to run the constructor on the force deployment
        call_constructor: false,
        // The value with which to initialize a contract
        value: U256::zero(),
        // The constructor calldata
        input: vec![],
    }]);

    let normal_l1_transaction = vm.rich_accounts[0]
        .get_deploy_tx(
            TestContract::counter().bytecode,
            None,
            TxType::L1 { serial_id: 0 },
        )
        .tx;

    let expected_error =
        Halt::UnexpectedVMBehavior("Assertion error: Protocol upgrade tx not first".to_string());

    vm.vm.make_snapshot();
    // Test 1: there must be only one system transaction in block
    vm.vm.push_transaction(protocol_upgrade_transaction.clone());
    vm.vm.push_transaction(normal_l1_transaction.clone());
    vm.vm.push_transaction(another_protocol_upgrade_transaction);

    vm.vm.execute(InspectExecutionMode::OneTx);
    vm.vm.execute(InspectExecutionMode::OneTx);
    let result = vm.vm.execute(InspectExecutionMode::OneTx);
    assert_eq!(
        result.result,
        ExecutionResult::Halt {
            reason: expected_error.clone()
        }
    );

    // Test 2: the protocol upgrade tx must be the first one in block
    vm.vm.rollback_to_the_latest_snapshot();
    vm.vm.make_snapshot();
    vm.vm.push_transaction(normal_l1_transaction.clone());
    vm.vm.push_transaction(protocol_upgrade_transaction.clone());

    vm.vm.execute(InspectExecutionMode::OneTx);
    let result = vm.vm.execute(InspectExecutionMode::OneTx);
    assert_eq!(
        result.result,
        ExecutionResult::Halt {
            reason: expected_error
        }
    );

    vm.vm.rollback_to_the_latest_snapshot();
    vm.vm.make_snapshot();
    vm.vm.push_transaction(protocol_upgrade_transaction);
    vm.vm.push_transaction(normal_l1_transaction);

    vm.vm.execute(InspectExecutionMode::OneTx);
    let result = vm.vm.execute(InspectExecutionMode::OneTx);
    assert!(!result.result.is_failed());
}
In this test we ensure that the requirements for protocol upgrade transactions are enforced by the bootloader:
- This transaction must be the only one in block
- If present, this transaction must be the first one in block
core/lib/multivm/src/versions/testonly/upgrade.rs