Summary
Accounts registering with the LLMOracleRegistry can pay excess fee due to lack of slippage control on the fee
Vulnerability Details
Accounts that are eligible to respond to LLM requests are registered with LLMOracleRegistry and pay a fee:
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;
emit Registered(msg.sender, kind);
}
The vulnerability arises from the fact that this fee can be changed by admin at anytime:
function setStakeAmounts(uint256 _generatorStakeAmount, uint256 _validatorStakeAmount) public onlyOwner {
generatorStakeAmount = _generatorStakeAmount;
validatorStakeAmount = _validatorStakeAmount;
}
Consider a case where an Oracle wishes to register both as a generator and validator. In order to save on gas fee, the account approves tokens to the LLMOracleRegistry
in one transaction with the total amount required to register as generator and validator. The account then sends a register
transaction. At the same time, admin wishes to increase the stake fee. He therefore also sends a setStakeAmounts
transaction, but with a higher gas fee than the Oracle. The block validator choses to prioritize the admin's transaction ahead of the register
transaction. The end result is that the Oracle's registration will be processed at a higher fee than anticipated.
For a POC, consider the case when admin changes the fee after Oracle has already approved tokens:
it("register generator oracle can pay more fee", async function () {
const amount = GENERATOR_STAKE + VALIDATOR_STAKE;
await token.connect(oracle).approve(oracleRegistryAddress, amount);
const allowance = await token.allowance(oracle, oracleRegistryAddress);
expect(allowance).to.equal(amount);
const NEW_GENERATOR_STAKE = ethers.parseEther("0.2");
await oracleRegistry.connect(dria).setStakeAmounts(NEW_GENERATOR_STAKE,VALIDATOR_STAKE);
await expect(oracleRegistry.connect(oracle).register(OracleKind.Generator))
.to.emit(oracleRegistry, "Registered")
.withArgs(oracle.address, OracleKind.Generator);
expect(await oracleRegistry.isRegistered(oracle.address, OracleKind.Generator)).to.equal(true);
await expect(oracleRegistry.connect(oracle).register(OracleKind.Validator)).to.revertedWithCustomError(
oracleRegistry,
"InsufficientFunds"
);
expect(await oracleRegistry.isRegistered(oracle.address, OracleKind.Validator)).to.equal(false);
});
Impact
Oracles can pay higher fee than anticipated.
Tools Used
Manual review
Recommendations
Consider introducing a maxFee input parameter for the register
function.