Dria

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

Unsafe Allowance Management in LLMOracleRegistry's Unregister Function

Summary

The unregister function in LLMOracleRegistry uses an unsafe pattern for managing token allowances by adding to the existing allowance rather than setting it directly or using safer alternatives.

Vulnerability Details

In the unregister function:

https://github.com/Cyfrin/2024-10-swan-dria/blob/main/contracts/llm/LLMOracleRegistry.sol#L116-L132

Impact

Allowance could potentially be set higher than intended if multiple transactions are processed simultaneously.

PoC

sample test case for a mock token:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MockERC20 is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
function mint(address to, uint256 amount) public {
_mint(to, amount);
}
}
import { expect } from "chai";
import { ethers, upgrades } from "hardhat";
import { LLMOracleRegistry, MockERC20 } from "../../typechain-types";
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
describe("LLMOracleRegistry Allowance Vulnerability", function () {
let registry: LLMOracleRegistry;
let token: MockERC20;
let user1: SignerWithAddress;
const generatorStakeAmount = ethers.parseEther("100");
const validatorStakeAmount = ethers.parseEther("50");
beforeEach(async function () {
[user1] = await ethers.getSigners();
// Deploy mock token
const MockToken = await ethers.getContractFactory("MockERC20");
token = await MockToken.deploy("Mock Token", "MTK");
await token.waitForDeployment();
// Deploy registry using proxy pattern
const Registry = await ethers.getContractFactory("LLMOracleRegistry");
registry = (await upgrades.deployProxy(
Registry,
[generatorStakeAmount, validatorStakeAmount, await token.getAddress()],
{ initializer: "initialize" }
)) as unknown as LLMOracleRegistry;
await registry.waitForDeployment();
// Mint tokens to user1
await token.mint(user1.address, ethers.parseEther("1000"));
});
it("demonstrates allowance accumulation", async function () {
const registryAsUser1 = registry.connect(user1);
const tokenAsUser1 = token.connect(user1);
// Initial approval and registration
await tokenAsUser1.approve(await registry.getAddress(), generatorStakeAmount * 2n);
await registryAsUser1.register(0); // Register as Generator
// Get initial allowance
const initialAllowance = await token.allowance(await registry.getAddress(), user1.address);
console.log("Initial allowance:", initialAllowance);
// Unregister
await registryAsUser1.unregister(0);
// Register again
await tokenAsUser1.approve(await registry.getAddress(), generatorStakeAmount);
await registryAsUser1.register(0);
// Unregister again
await registryAsUser1.unregister(0);
// Check final allowance
const finalAllowance = await token.allowance(await registry.getAddress(), user1.address);
console.log("Final allowance:", finalAllowance);
// The final allowance should be higher than the initial stake amount
expect(finalAllowance).to.be.gt(generatorStakeAmount);
});
});
Initial allowance: 0n
Final allowance: 200000000000000000000n
LLMOracleRegistry Allowance Vulnerability
✔ demonstrates allowance accumulation
1 passing (3s)
Initial allowance: 0n
// Step 1: First Registration Cycle
await tokenAsUser1.approve(await registry.getAddress(), generatorStakeAmount * 2n); // Approved 200 tokens
await registryAsUser1.register(0); // Registered as Generator with 100 tokens
// Step 2: First Unregister
await registryAsUser1.unregister(0); // This adds 100 tokens to allowance
// Step 3: Second Registration Cycle
await tokenAsUser1.approve(await registry.getAddress(), generatorStakeAmount); // Approved another 100 tokens
await registryAsUser1.register(0); // Registered again with 100 tokens
// Step 4: Second Unregister
await registryAsUser1.unregister(0); // This adds another 100 tokens to allowance
Final allowance: 200000000000000000000n // Shows 200 tokens total allowance

Tools Used

Manual review

Hardhat for PoC testing

Recommendations

Preferred: Transfer tokens directly instead of using approvals

Alternative: Use increaseAllowance if approval is necessary

Updates

Lead Judging Commences

inallhonesty Lead Judge 12 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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