import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {LLMOracleTask, LLMOracleTaskParameters} from "../llm/LLMOracleTask.sol";
import {Swan, SwanBuyerPurchaseOracleProtocol, SwanBuyerStateOracleProtocol} from "../swan/Swan.sol";
import {SwanMarketParameters} from "../swan/SwanManager.sol";
contract MaliciousBuyerAgentFactory {
function deploy(
string memory _name,
string memory _description,
uint96 _royaltyFee,
uint256 _amountPerRound,
address _owner
) external returns (MaliciousBuyerAgent) {
return new MaliciousBuyerAgent(_name, _description, _royaltyFee, _amountPerRound, msg.sender, _owner);
}
}
contract MaliciousBuyerAgent is Ownable {
ERRORS
error MinFundSubceeded(uint256 value);
error InvalidFee(uint256 fee);
error BuyLimitExceeded(uint256 have, uint256 want);
error InvalidPhase(Phase have, Phase want);
error Unauthorized(address caller);
error TaskNotRequested();
error TaskAlreadyProcessed();
STORAGE
enum Phase {
Sell,
Buy,
Withdraw
}
Swan public immutable swan;
uint256 public immutable createdAt;
uint256 public immutable marketParameterIdx;
string public name;
string public description;
bytes public state;
uint96 public royaltyFee;
uint256 public amountPerRound;
mapping(uint256 round => address[] assets) public inventory;
mapping(uint256 round => uint256 spending) public spendings;
mapping(uint256 round => uint256 taskId) public oraclePurchaseRequests;
mapping(uint256 round => uint256 taskId) public oracleStateRequests;
mapping(uint256 taskId => bool isProcessed) public isOracleRequestProcessed;
MODIFIERS
modifier onlyAuthorized() {
if (!swan.isOperator(msg.sender) && msg.sender != owner()) {
revert Unauthorized(msg.sender);
}
_;
}
CONSTRUCTOR
constructor(
string memory _name,
string memory _description,
uint96 _royaltyFee,
uint256 _amountPerRound,
address _operator,
address _owner
) Ownable(_owner) {
if (_royaltyFee < 1 || _royaltyFee > 100) {
revert InvalidFee(_royaltyFee);
}
royaltyFee = _royaltyFee;
swan = Swan(_operator);
amountPerRound = _amountPerRound;
name = _name;
description = _description;
createdAt = block.timestamp;
marketParameterIdx = swan.getMarketParameters().length - 1;
swan.token().approve(address(swan.coordinator()), type(uint256).max);
swan.token().approve(address(swan), type(uint256).max);
}
LOGIC
function minFundAmount() public view returns (uint256) {
return amountPerRound + swan.getOracleFee();
}
function oracleResult(uint256 taskId) public view returns (bytes memory) {
if (taskId == 0) {
revert TaskNotRequested();
}
return swan.coordinator().getBestResponse(taskId).output;
}
function oracleStateRequest(bytes calldata _input, bytes calldata _models) external onlyAuthorized {
(uint256 round,) = _checkRoundPhase(Phase.Withdraw);
oracleStateRequests[round] =
swan.coordinator().request(SwanBuyerStateOracleProtocol, _input, _models, swan.getOracleParameters());
}
function oraclePurchaseRequest(bytes calldata _input, bytes calldata _models) external onlyAuthorized {
(uint256 round,) = _checkRoundPhase(Phase.Buy);
oraclePurchaseRequests[round] =
swan.coordinator().request(SwanBuyerPurchaseOracleProtocol, _input, _models, swan.getOracleParameters());
}
function updateState() external onlyAuthorized {
(uint256 round,) = _checkRoundPhase(Phase.Withdraw);
uint256 taskId = oracleStateRequests[round];
if (isOracleRequestProcessed[taskId]) {
revert TaskAlreadyProcessed();
}
bytes memory newState = oracleResult(taskId);
state = newState;
isOracleRequestProcessed[taskId] = true;
}
function purchase() external onlyAuthorized {
(uint256 round,) = _checkRoundPhase(Phase.Buy);
uint256 taskId = oraclePurchaseRequests[round];
if (isOracleRequestProcessed[taskId]) {
revert TaskAlreadyProcessed();
}
bytes memory output = oracleResult(taskId);
address[] memory assets = abi.decode(output, (address[]));
for (uint256 i = 0; i < assets.length; i++) {
address asset = assets[i];
uint256 price = swan.getListingPrice(asset);
spendings[round] += price;
if (spendings[round] > amountPerRound) {
revert BuyLimitExceeded(spendings[round], amountPerRound);
}
inventory[round].push(asset);
swan.purchase(asset);
}
isOracleRequestProcessed[taskId] = true;
}
function withdraw(uint96 _amount) public onlyAuthorized {
(, Phase phase,) = getRoundPhase();
swan.token().transfer(owner(), _amount);
}
function treasury() public view returns (uint256) {
return swan.token().balanceOf(address(this));
}
function _checkRoundPhase(Phase _phase) internal view returns (uint256, Phase) {
(uint256 round, Phase phase,) = getRoundPhase();
if (phase != _phase) {
revert InvalidPhase(phase, _phase);
}
return (round, phase);
}
function _computeCycleTime(SwanMarketParameters memory params) internal pure returns (uint256) {
return params.sellInterval + params.buyInterval + params.withdrawInterval;
}
function _computePhase(SwanMarketParameters memory params, uint256 elapsedTime)
internal
pure
returns (uint256, Phase, uint256)
{
uint256 cycleTime = _computeCycleTime(params);
uint256 round = elapsedTime / cycleTime;
uint256 roundTime = elapsedTime % cycleTime;
if (roundTime <= params.sellInterval) {
return (round, Phase.Sell, params.sellInterval - roundTime);
} else if (roundTime <= params.sellInterval + params.buyInterval) {
return (round, Phase.Buy, params.sellInterval + params.buyInterval - roundTime);
} else {
return (round, Phase.Withdraw, cycleTime - roundTime);
}
}
function getRoundPhase() public view returns (uint256, Phase, uint256) {
SwanMarketParameters[] memory marketParams = swan.getMarketParameters();
if (marketParams.length == marketParameterIdx + 1) {
return _computePhase(marketParams[marketParameterIdx], block.timestamp - createdAt);
} else {
uint256 idx = marketParameterIdx;
(uint256 round,,) = _computePhase(marketParams[idx], marketParams[idx + 1].timestamp - createdAt);
idx++;
while (idx < marketParams.length - 1) {
(uint256 innerRound,,) =
_computePhase(marketParams[idx], marketParams[idx + 1].timestamp - marketParams[idx].timestamp);
round += innerRound + 1;
idx++;
}
(uint256 lastRound, Phase phase, uint256 timeRemaining) =
_computePhase(marketParams[idx], block.timestamp - marketParams[idx].timestamp);
round += lastRound + 1;
return (round, phase, timeRemaining);
}
}
function setFeeRoyalty(uint96 _fee) public onlyOwner {
_checkRoundPhase(Phase.Withdraw);
if (_fee < 1 || _fee > 100) {
revert InvalidFee(_fee);
}
royaltyFee = _fee;
}
function setAmountPerRound(uint256 _amountPerRound) external onlyOwner {
_checkRoundPhase(Phase.Withdraw);
amountPerRound = _amountPerRound;
}
}