Dria

Swan
NFTHardhat
21,000 USDC
View results
Submission Details
Severity: medium
Valid

Vulnerability in Oracle Staking: Immediate Withdrawal Exploitation

Summary

The LLMOracleRegistry contract contains a flaw that allows a malicious actor to bypass the staking mechanism designed to ensure commitment from oracles before responding to or validating tasks. Using flash loans, an attacker can register, perform the required actions, and withdraw the staked funds all within the same transaction, thereby circumventing the intended security and stability of the staking system. This finding highlights the need for a mechanism that enforces a time delay before withdrawal of stake after registration.

Vulnerability Details

  • Location: The issue exists within the register and unregister functions of the LLMOracleRegistry contract.

https://github.com/Cyfrin/2024-10-swan-dria/blob/c8686b199daadcef3161980022e12b66a5304f8e/contracts/llm/LLMOracleRegistry.sol#L94C1-L131C6

function register(LLMOracleKind kind) public {
uint256 amount = getStakeAmount(kind);
// ensure the user is not already registered
if (isRegistered(msg.sender, kind)) {
revert AlreadyRegistered(msg.sender);
}
// ensure the user has enough allowance to stake
if (token.allowance(msg.sender, address(this)) < amount) {
revert InsufficientFunds();
}
token.transferFrom(msg.sender, address(this), amount);
// register the user
registrations[msg.sender][kind] = amount;
emit Registered(msg.sender, kind);
}
/// @notice Remove registration of an Oracle.
/// @dev Reverts if the user is not registered.
/// @param kind The kind of Oracle to unregister.
/// @return amount Amount of stake approved back.
function unregister(LLMOracleKind kind) public returns (uint256 amount) {
amount = registrations[msg.sender][kind];
// ensure the user is registered
if (amount == 0) {
revert NotRegistered(msg.sender);
}
// unregister the user
delete registrations[msg.sender][kind];
emit Unregistered(msg.sender, kind);
// approve its stake back
token.approve(msg.sender, token.allowance(address(this), msg.sender) + amount);
}
  • Description: The contract allows oracles to register by staking tokens, which are meant to serve as a security deposit. However, an attacker can exploit this system by using a flash loan to obtain the necessary tokens temporarily, register, perform the required connection, and withdraw immediately.

  • Exploitation Scenario:

    • An attacker obtains a flash loan to cover the staking requirement.

    • Registers as an oracle by staking the borrowed tokens.

    • Executes actions such as responding to task requests or validating tasks.

    • Unregisters to withdraw the staked tokens.

    • Repays the flash loan within the same transaction block.

By executing these operations in quick succession within a single transaction, the attacker effectively nullifies the staking requirement, collapsing its protective utility.

Impact

The flash loan exploitation undermines the financial security expected to be guaranteed by the staking requirement, leading to increased vulnerability of the system to potentially malicious activities by non-committed oracles.

Tools Used

foundry

Recommendations

Introduce a mandatory waiting period between registration and withdrawal. This can be realized by setting a minimum time that must elapse after registration before an oracle can unregister and withdraw their stake.

function register(LLMOracleKind kind) public {
uint256 amount = getStakeAmount(kind);
if (isRegistered(msg.sender, kind)) {
revert AlreadyRegistered(msg.sender);
}
if (token.allowance(msg.sender, address(this)) < amount) {
revert InsufficientFunds();
}
token.transferFrom(msg.sender, address(this), amount);
registrations[msg.sender][kind] = amount;
++ registrationTimestamps[msg.sender][kind] = block.timestamp;
emit Registered(msg.sender, kind);
}
function unregister(LLMOracleKind kind) public returns (uint256 amount) {
if (!isRegistered(msg.sender, kind)) {
revert NotRegistered(msg.sender);
}
// Check if the user has waited long enough to withdraw
++ if (block.timestamp < registrationTimestamps[msg.sender][kind] + withdrawalDelay) {
++ revert WithdrawalTooSoon(msg.sender);
++ }
amount = registrations[msg.sender][kind];
delete registrations[msg.sender][kind];
emit Unregistered(msg.sender, kind);
token.transfer(msg.sender, amount); // Transfer the staked tokens back to the user
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

There is no oracle whitelisting

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.