> ## Documentation Index
> Fetch the complete documentation index at: https://docs.phylax.systems/llms.txt
> Use this file to discover all available pages before exploring further.

# Radiant Capital Hack

> Ownership change of lending pools lead to drain

This case study explains how the Radiant Capital hack worked and which assertion pattern could have constrained the invalid state transition.

## What Happened

In October 2024, Radiant Capital was hacked. You can read more about the hack on [Rekt](https://rekt.news/radiant-capital-rekt2/).
In short, the attacker managed to gain control over 3 signers of the Radiant Capital multisig, which allowed the attacker to change ownership of the lending pools and ultimately drain the pools.
Had there been an assertion in place that checked that the ownership of the lending pools didn't change, the hack would have been prevented.

## Why This Matters

This use case is a good example of how to use assertions to detect ownership changes. A lot of DeFi protocols have the concept of owners and admins that can change the protocol's behavior.
Usually these are controlled by a multisig, which is best practice, but it is not always enough. Especially if the multisig setup is not done in an optimal way.

The assertion shown below is easy to generalize and use in any protocol that wants to make sure that the ownership of critical contracts don't change.
It would also be possible to define a whitelist of contracts that the ownership can be changed to. By default there is a cooldown period before an assertion can be paused or removed, so protocols need to plan ahead if they don't have a whitelist defined.

## Assertion Pattern

This assertions checks if the owner, emergency admin and pool admin of the lending pool have changed.
It's a good example of how a simple assertion can be used to prevent disastrous hacks.

```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {Assertion} from "../lib/credible-std/Assertion.sol";
import {AssertionSpec} from "../lib/credible-std/SpecRecorder.sol";

interface ILendingPoolAddressesProvider {
    function owner() external view returns (address);
    function getEmergencyAdmin() external view returns (address);
    function getPoolAdmin() external view returns (address);
}

// Radiant Lending Pool on Arbitrum that got hacked and drained
contract LendingPoolAddressesProviderAssertions is Assertion {
    constructor() {
        registerAssertionSpec(AssertionSpec.Reshiram);
    }

    ILendingPoolAddressesProvider public lendingPoolAddressesProvider =
        ILendingPoolAddressesProvider(0x091d52CacE1edc5527C99cDCFA6937C1635330E4); //arbitrum

    function triggers() external view override {
        // Trigger on any storage change to catch ownership modifications
        registerTxEndTrigger(this.assertionOwnerChange.selector);
        registerTxEndTrigger(this.assertionEmergencyAdminChange.selector);
        registerTxEndTrigger(this.assertionPoolAdminChange.selector);
    }

    // Check if the owner has changed
    function assertionOwnerChange() external view {
        PhEvm.ForkId memory preFork = _preTx();
        address prevOwner = lendingPoolAddressesProvider.owner();
        PhEvm.ForkId memory postFork = _postTx();
        address newOwner = lendingPoolAddressesProvider.owner();
        require(prevOwner == newOwner, "Owner has changed");
    }

    // Check if the emergency admin has changed
    function assertionEmergencyAdminChange() external view {
        PhEvm.ForkId memory preFork = _preTx();
        address prevEmergencyAdmin = lendingPoolAddressesProvider.getEmergencyAdmin();
        PhEvm.ForkId memory postFork = _postTx();
        address newEmergencyAdmin = lendingPoolAddressesProvider.getEmergencyAdmin();
        require(prevEmergencyAdmin == newEmergencyAdmin, "Emergency admin has changed");
    }

    // Check if the pool admin has changed
    function assertionPoolAdminChange() external view {
        PhEvm.ForkId memory preFork = _preTx();
        address prevPoolAdmin = lendingPoolAddressesProvider.getPoolAdmin();
        PhEvm.ForkId memory postFork = _postTx();
        address newPoolAdmin = lendingPoolAddressesProvider.getPoolAdmin();
        require(prevPoolAdmin == newPoolAdmin, "Pool admin has changed");
    }
}
```
