From feb8dd64d672a341a29a0a52b12cc56adf09c996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fla=20=C3=87elik?= Date: Mon, 16 Dec 2024 10:00:38 +0300 Subject: [PATCH 1/2] renaming + update tests & docs --- .gas-snapshot | 85 +-- README.md | 26 +- deployment/31337.json | 18 +- deployment/84532.json | 20 +- docs/src/README.md | 26 +- docs/src/SUMMARY.md | 8 +- docs/src/src/AIAgent.sol/contract.AIAgent.md | 534 +++++++++++++++ .../AIAgent.sol/contract.AIAgentFactory.md | 22 + .../src/src/Artifact.sol/contract.Artifact.md | 40 ++ .../Artifact.sol/contract.ArtifactFactory.md | 20 + .../src/BuyerAgent.sol/contract.BuyerAgent.md | 8 +- .../contract.BuyerAgentFactory.md | 2 +- docs/src/src/README.md | 8 +- docs/src/src/Swan.sol/constants.Swan.md | 12 +- docs/src/src/Swan.sol/contract.Swan.md | 168 ++--- .../src/SwanAsset.sol/contract.SwanAsset.md | 2 +- .../contract.SwanAssetFactory.md | 2 +- .../SwanManager.sol/abstract.SwanManager.md | 8 +- .../struct.SwanMarketParameters.md | 10 +- .../src/mock/SvanV2.sol/contract.SwanV2.md | 2 +- foundry.toml | 25 +- lcov.info | 462 ++++++------- lib/dria-oracle-contracts | 2 +- lib/forge-std | 2 +- lib/openzeppelin-contracts | 2 +- lib/openzeppelin-contracts-upgradeable | 2 +- lib/openzeppelin-foundry-upgrades | 2 +- remappings.txt | 13 + script/Deploy.s.sol | 182 +---- script/HelperConfig.s.sol | 242 ++++++- src/{BuyerAgent.sol => AIAgent.sol} | 90 +-- src/{SwanAsset.sol => Artifact.sol} | 14 +- src/Swan.sol | 312 ++++----- src/SwanManager.sol | 28 +- test/AIAgentTest.t.sol | 141 ++++ test/BuyerAgent.t.sol | 141 ---- test/Helper.t.sol | 240 ++++--- test/Invariant.t.sol | 51 -- test/InvariantTest.t.sol | 51 ++ test/Swan.t.sol | 562 ---------------- test/SwanFuzz.t.sol | 217 ------ test/SwanFuzzTest.t.sol | 217 ++++++ test/SwanIntervals.t.sol | 43 -- test/SwanIntervalsTest.t.sol | 46 ++ test/SwanTest.t.sol | 634 ++++++++++++++++++ test/script/Deploy.t.sol | 75 ++- 46 files changed, 2825 insertions(+), 1992 deletions(-) create mode 100644 docs/src/src/AIAgent.sol/contract.AIAgent.md create mode 100644 docs/src/src/AIAgent.sol/contract.AIAgentFactory.md create mode 100644 docs/src/src/Artifact.sol/contract.Artifact.md create mode 100644 docs/src/src/Artifact.sol/contract.ArtifactFactory.md create mode 100644 remappings.txt rename src/{BuyerAgent.sol => AIAgent.sol} (84%) rename src/{SwanAsset.sol => Artifact.sol} (75%) create mode 100644 test/AIAgentTest.t.sol delete mode 100644 test/BuyerAgent.t.sol delete mode 100644 test/Invariant.t.sol create mode 100644 test/InvariantTest.t.sol delete mode 100644 test/Swan.t.sol delete mode 100644 test/SwanFuzz.t.sol create mode 100644 test/SwanFuzzTest.t.sol delete mode 100644 test/SwanIntervals.t.sol create mode 100644 test/SwanIntervalsTest.t.sol create mode 100644 test/SwanTest.t.sol diff --git a/.gas-snapshot b/.gas-snapshot index e36df2c..c9beae8 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,41 +1,44 @@ -BuyerAgentTest:test_InBuyPhase() (gas: 4504258) -BuyerAgentTest:test_InSellPhase() (gas: 4472291) -BuyerAgentTest:test_RevertWhen_SetAmountPerRoundInBuyPhase() (gas: 4483755) -BuyerAgentTest:test_RevertWhen_SetFeeWithInvalidRoyalty() (gas: 4492886) -BuyerAgentTest:test_RevertWhen_SetRoyaltyInSellPhase() (gas: 4473243) -BuyerAgentTest:test_RevertWhen_WithdrawByAnotherOwner() (gas: 4504563) -BuyerAgentTest:test_RevertWhen_WithdrawInBuyPhase() (gas: 4511184) -BuyerAgentTest:test_SetRoyaltyAndAmountPerRound() (gas: 4494097) -BuyerAgentTest:test_WithdrawInWithdrawPhase() (gas: 4474465) -DeployTest:test_Deploy() (gas: 101342) -InvariantTest:invariant_AssetPriceRange() (runs: 256, calls: 128000, reverts: 114801) -InvariantTest:invariant_BuyerAgentFeeRoyalty() (runs: 256, calls: 128000, reverts: 114890) -InvariantTest:invariant_MaxAssetCount() (runs: 256, calls: 128000, reverts: 115004) -InvariantTest:invariant_OwnerIsAnOperator() (runs: 256, calls: 128000, reverts: 114875) -SwanFuzz:testFuzz_CalculateRoyalties(uint256,uint256,uint256) (runs: 256, μ: 4478247, ~: 4478369) -SwanFuzz:testFuzz_ChangeCycleTime(uint256,uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 6920247, ~: 6921025) -SwanFuzz:testFuzz_ListAsset(string,string,bytes,uint256,string,string,uint96,uint256) (runs: 256, μ: 3922593, ~: 3933255) -SwanFuzz:testFuzz_TransferOwnership(address) (runs: 256, μ: 45378, ~: 45378) -SwanIntervalsTest:test_InBuyPhase() (gas: 4484235) -SwanIntervalsTest:test_InSellPhase() (gas: 4472621) -SwanIntervalsTest:test_InWithdrawPhase() (gas: 4488191) -SwanTest:test_CreateBuyerAgents() (gas: 5663905) -SwanTest:test_PurchaseAnAsset() (gas: 11220979) -SwanTest:test_RelistAsset() (gas: 10183767) -SwanTest:test_RevertWhen_CreateBuyerWithInvalidRoyalty() (gas: 1619856) -SwanTest:test_RevertWhen_ListInWithdrawPhase() (gas: 5743065) -SwanTest:test_RevertWhen_ListMoreThanMaxAssetCount() (gas: 10051316) -SwanTest:test_RevertWhen_PurchaseByAnotherBuyer() (gas: 10073950) -SwanTest:test_RevertWhen_PurchaseInSellPhase() (gas: 10048903) -SwanTest:test_RevertWhen_PurchaseMoreThanAmountPerRound() (gas: 11265761) -SwanTest:test_RevertWhen_RelistAlreadyPurchasedAsset() (gas: 11222356) -SwanTest:test_RevertWhen_RelistByAnotherSeller() (gas: 10059833) -SwanTest:test_RevertWhen_RelistInBuyPhase() (gas: 10094987) -SwanTest:test_RevertWhen_RelistInTheSameRound() (gas: 10052006) -SwanTest:test_RevertWhen_RelistInWithdrawPhase() (gas: 10094678) -SwanTest:test_SetAmountPerRound() (gas: 5720554) -SwanTest:test_SetFactories() (gas: 5172073) -SwanTest:test_SetMarketParameters() (gas: 5843489) -SwanTest:test_SetOracleParameters() (gas: 5665639) -SwanTest:test_TransferOwnership() (gas: 55405) -SwanTest:test_UpdateState() (gas: 11985754) \ No newline at end of file +AIAgentTest:test_InBuyPhase() (gas: 4504153) +AIAgentTest:test_InListingPhase() (gas: 4472120) +AIAgentTest:test_RevertWhen_SetAmountPerRoundInBuyPhase() (gas: 4483655) +AIAgentTest:test_RevertWhen_SetFeeWithInvalidRoyalty() (gas: 4492808) +AIAgentTest:test_RevertWhen_SetRoyaltyInListingPhase() (gas: 4473166) +AIAgentTest:test_RevertWhen_WithdrawByAnotherOwner() (gas: 4504441) +AIAgentTest:test_RevertWhen_WithdrawInBuyPhase() (gas: 4511039) +AIAgentTest:test_SetRoyaltyAndAmountPerRound() (gas: 4494015) +AIAgentTest:test_WithdrawInWithdrawPhase() (gas: 4474361) +DeployTest:test_Deploy() (gas: 22076) +InvariantTest:invariant_AgentFeeRoyalty() (runs: 20, calls: 10000, reverts: 8980) +InvariantTest:invariant_ArtifactPriceRange() (runs: 20, calls: 10000, reverts: 9028) +InvariantTest:invariant_MaxArtifactCount() (runs: 20, calls: 10000, reverts: 8998) +InvariantTest:invariant_OwnerIsAnOperator() (runs: 20, calls: 10000, reverts: 8992) +SwanFuzzTest:testFuzz_CalculateRoyalties(uint256,uint256,uint256) (runs: 100, μ: 4478115, ~: 4478236) +SwanFuzzTest:testFuzz_ChangeCycleTime(uint256,uint256,uint256,uint256,uint256,uint256) (runs: 100, μ: 6920555, ~: 6921298) +SwanFuzzTest:testFuzz_ListArtifact(string,string,bytes,uint256,string,string,uint96,uint256) (runs: 100, μ: 3923970, ~: 3924013) +SwanFuzzTest:testFuzz_TransferOwnership(address) (runs: 100, μ: 45356, ~: 45356) +SwanIntervalsTest:test_InBuyPhase() (gas: 4484185) +SwanIntervalsTest:test_InListingPhase() (gas: 4472550) +SwanIntervalsTest:test_InWithdrawPhase() (gas: 4487549) +SwanTest:test_CreateAIAgents() (gas: 5663937) +SwanTest:test_PurchaseAnArtifact() (gas: 11297765) +SwanTest:test_RelistArtifact() (gas: 10251837) +SwanTest:test_RevertWhen_CreateAgentWithInvalidRoyalty() (gas: 1619957) +SwanTest:test_RevertWhen_ListInWithdrawPhase() (gas: 5749933) +SwanTest:test_RevertWhen_ListMoreThanmaxArtifactCount() (gas: 10119397) +SwanTest:test_RevertWhen_PurchaseByAnotherAgent() (gas: 10142029) +SwanTest:test_RevertWhen_PurchaseInListingPhase() (gas: 10116892) +SwanTest:test_RevertWhen_PurchaseMoreThanAmountPerRound() (gas: 11333279) +SwanTest:test_RevertWhen_RelistAlreadyPurchasedArtifact() (gas: 11289853) +SwanTest:test_RevertWhen_RelistByAnotherSeller() (gas: 10127804) +SwanTest:test_RevertWhen_RelistInBuyPhase() (gas: 10163113) +SwanTest:test_RevertWhen_RelistInTheSameRound() (gas: 10119934) +SwanTest:test_RevertWhen_RelistInWithdrawPhase() (gas: 10162737) +SwanTest:test_RevertWhen_RelistMoreThanMaxArtifactCount() (gas: 13983200) +SwanTest:test_RevertWhen_SetMarketParametersWithInvalidFee() (gas: 1539671) +SwanTest:test_RevertWhen_UpgradeByNonOwner() (gas: 1543316) +SwanTest:test_SetAmountPerRound() (gas: 5720733) +SwanTest:test_SetFactories() (gas: 5172184) +SwanTest:test_SetMarketParameters() (gas: 5843554) +SwanTest:test_SetOracleParameters() (gas: 5665825) +SwanTest:test_TransferOwnership() (gas: 55416) +SwanTest:test_UpdateState() (gas: 12066139) \ No newline at end of file diff --git a/README.md b/README.md index 7c70a04..a6394ed 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Swan is a **Decentralized Protocol** where AI agents (buyers) dynamically intera Compile the contracts with: ```sh -forge build +forge clean && forge build ``` > [!NOTE] @@ -18,13 +18,13 @@ forge build Run tests on local: ```sh -forge test --force +forge clean && forge test ``` or on any other evm chain: ```sh -forge test --rpc-url +forge clean && forge test --rpc-url ``` ## Deployment @@ -54,30 +54,22 @@ cast wallet list > You HAVE to type your password on the terminal to be able to use your keys. (e.g when deploying a contract) **Step 3.** -Enter your private key (associated with your public key) and password on terminal. You'll see your public key on terminal. +Enter your private key (associated with your address) and password on terminal. You'll see your address on terminal. > [!NOTE] > -> If you want to deploy contracts on localhost please provide local public key for the command above. +> If you want to deploy contracts on localhost please provide local address for the command above. -**Step 4.** Required only for local deployment. - -Start a local node with: - -```sh -anvil -``` - -**Step 5.** -Deploy the contracts with: +**Step 4.** +Deploy the contract with: ```sh -forge script ./script/Deploy.s.sol:Deploy --rpc-url --account --sender --broadcast +forge clean && forge script ./script/Deploy.s.sol:Deploy --rpc-url --account --sender --broadcast ``` or for instant verification use: ```sh -forge script ./script/Deploy.s.sol:Deploy --rpc-url --account --sender --broadcast --verify --verifier --verifier-url +forge clean && forge script ./script/Deploy.s.sol:Deploy --rpc-url --account --sender --broadcast --verify --verifier --verifier-url ``` > [!NOTE] diff --git a/deployment/31337.json b/deployment/31337.json index ee38453..1d6aadb 100644 --- a/deployment/31337.json +++ b/deployment/31337.json @@ -1,16 +1,20 @@ { - "LLMOracleRegistry": { - "proxyAddr": "0x90193c961a926261b756d1e5bb255e67ff9498a1", - "implAddr": "0x34a1d3fff3958843c43ad80f30b94c510645c316" + "AIAgentFactory": { + "addr": "0x34a1d3fff3958843c43ad80f30b94c510645c316" }, - "LLMOracleCoordinator": { + "LLMOracleRegistry": { "proxyAddr": "0xbb2180ebd78ce97360503434ed37fcf4a1df61c3", "implAddr": "0xa8452ec99ce0c64f20701db7dd3abdb607c00496" }, + "LLMOracleCoordinator": { + "proxyAddr": "0x50eef481cae4250d252ae577a09bf514f224c6c4", + "implAddr": "0xdb8cff278adccf9e9b5da745b44e754fc4ee3c76" + }, + "ArtifactFactory": { + "addr": "0x90193c961a926261b756d1e5bb255e67ff9498a1" + }, "Swan": { "proxyAddr": "0xdeb1e9a6be7baf84208bb6e10ac9f9bbe1d70809", "implAddr": "0x62c20aa1e0272312bc100b4e23b4dc1ed96dd7d1" - }, - "BuyerAgentFactory": "0xdb8cff278adccf9e9b5da745b44e754fc4ee3c76", - "SwanAssetFactory": "0x50eef481cae4250d252ae577a09bf514f224c6c4" + } } \ No newline at end of file diff --git a/deployment/84532.json b/deployment/84532.json index 419a160..656339a 100644 --- a/deployment/84532.json +++ b/deployment/84532.json @@ -1,16 +1,22 @@ { "LLMOracleRegistry": { - "proxyAddr": "0x4e4ef93f3ac35ca6505f582c666958f99218de21", - "implAddr": "0xa826dfee9089004d9a6d832f854375eb725074bb" + "proxyAddr": "0xbb2180ebd78ce97360503434ed37fcf4a1df61c3", + "implAddr": "0xa8452ec99ce0c64f20701db7dd3abdb607c00496" }, "LLMOracleCoordinator": { - "proxyAddr": "0x237f1bda05451ade6214a12e85b5084511da2995", - "implAddr": "0x4a2d4f8734c4b322685fadfc72e44f0011123814" + "proxyAddr": "0x50eef481cae4250d252ae577a09bf514f224c6c4", + "implAddr": "0xdb8cff278adccf9e9b5da745b44e754fc4ee3c76" }, "Swan": { - "proxyAddr": "0x70c85509fc1da642e90d8dc5a9487fa4c36ea77c", - "implAddr": "0x1f5bff2d013fe0051a7f1d1b932183c3d839d384" + "proxyAddr": "0xdeb1e9a6be7baf84208bb6e10ac9f9bbe1d70809", + "implAddr": "0x62c20aa1e0272312bc100b4e23b4dc1ed96dd7d1" }, "BuyerAgentFactory": "0xc48af3ef03d91af34b891688ade004ad36c8c39a", - "SwanAssetFactory": "0xebb2c54b2a20e0e25b2fb168c8a657e1c966911f" + "SwanAssetFactory": "0xebb2c54b2a20e0e25b2fb168c8a657e1c966911f", + "AIAgentFactory": { + "addr": "0x34a1d3fff3958843c43ad80f30b94c510645c316" + }, + "ArtifactFactory": { + "addr": "0x90193c961a926261b756d1e5bb255e67ff9498a1" + } } \ No newline at end of file diff --git a/docs/src/README.md b/docs/src/README.md index 7c70a04..a6394ed 100644 --- a/docs/src/README.md +++ b/docs/src/README.md @@ -6,7 +6,7 @@ Swan is a **Decentralized Protocol** where AI agents (buyers) dynamically intera Compile the contracts with: ```sh -forge build +forge clean && forge build ``` > [!NOTE] @@ -18,13 +18,13 @@ forge build Run tests on local: ```sh -forge test --force +forge clean && forge test ``` or on any other evm chain: ```sh -forge test --rpc-url +forge clean && forge test --rpc-url ``` ## Deployment @@ -54,30 +54,22 @@ cast wallet list > You HAVE to type your password on the terminal to be able to use your keys. (e.g when deploying a contract) **Step 3.** -Enter your private key (associated with your public key) and password on terminal. You'll see your public key on terminal. +Enter your private key (associated with your address) and password on terminal. You'll see your address on terminal. > [!NOTE] > -> If you want to deploy contracts on localhost please provide local public key for the command above. +> If you want to deploy contracts on localhost please provide local address for the command above. -**Step 4.** Required only for local deployment. - -Start a local node with: - -```sh -anvil -``` - -**Step 5.** -Deploy the contracts with: +**Step 4.** +Deploy the contract with: ```sh -forge script ./script/Deploy.s.sol:Deploy --rpc-url --account --sender --broadcast +forge clean && forge script ./script/Deploy.s.sol:Deploy --rpc-url --account --sender --broadcast ``` or for instant verification use: ```sh -forge script ./script/Deploy.s.sol:Deploy --rpc-url --account --sender --broadcast --verify --verifier --verifier-url +forge clean && forge script ./script/Deploy.s.sol:Deploy --rpc-url --account --sender --broadcast --verify --verifier --verifier-url ``` > [!NOTE] diff --git a/docs/src/SUMMARY.md b/docs/src/SUMMARY.md index fd7d0e5..1f1917a 100644 --- a/docs/src/SUMMARY.md +++ b/docs/src/SUMMARY.md @@ -3,11 +3,11 @@ # src - [❱ mock](src/mock/README.md) - [SwanV2](src/mock/SvanV2.sol/contract.SwanV2.md) - - [BuyerAgentFactory](src/BuyerAgent.sol/contract.BuyerAgentFactory.md) - - [BuyerAgent](src/BuyerAgent.sol/contract.BuyerAgent.md) + - [AIAgentFactory](src/AIAgent.sol/contract.AIAgentFactory.md) + - [AIAgent](src/AIAgent.sol/contract.AIAgent.md) + - [ArtifactFactory](src/Artifact.sol/contract.ArtifactFactory.md) + - [Artifact](src/Artifact.sol/contract.Artifact.md) - [Swan](src/Swan.sol/contract.Swan.md) - [Swan constants](src/Swan.sol/constants.Swan.md) - - [SwanAssetFactory](src/SwanAsset.sol/contract.SwanAssetFactory.md) - - [SwanAsset](src/SwanAsset.sol/contract.SwanAsset.md) - [SwanMarketParameters](src/SwanManager.sol/struct.SwanMarketParameters.md) - [SwanManager](src/SwanManager.sol/abstract.SwanManager.md) diff --git a/docs/src/src/AIAgent.sol/contract.AIAgent.md b/docs/src/src/AIAgent.sol/contract.AIAgent.md new file mode 100644 index 0000000..9c866d5 --- /dev/null +++ b/docs/src/src/AIAgent.sol/contract.AIAgent.md @@ -0,0 +1,534 @@ +# AIAgent +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/AIAgent.sol) + +**Inherits:** +Ownable + +AIAgent is responsible for buying the artifacts from Swan. + + +## State Variables +### swan +Swan contract. + + +```solidity +Swan public immutable swan; +``` + + +### createdAt +Timestamp when the contract is deployed. + + +```solidity +uint256 public immutable createdAt; +``` + + +### marketParameterIdx +Holds the index of the Swan market parameters at the time of deployment. + +*When calculating the round, we will use this index to determine the start interval.* + + +```solidity +uint256 public immutable marketParameterIdx; +``` + + +### name +AI agent name. + + +```solidity +string public name; +``` + + +### description +AI agent description, can include backstory, behavior and objective together. + + +```solidity +string public description; +``` + + +### state +State of the AI agent. + +*Only updated by the oracle via `updateState`.* + + +```solidity +bytes public state; +``` + + +### feeRoyalty +Royalty fees for the AI agent. + + +```solidity +uint96 public feeRoyalty; +``` + + +### amountPerRound +The max amount of money the agent can spend per round. + + +```solidity +uint256 public amountPerRound; +``` + + +### inventory +The artifacts that the AI agent has. + + +```solidity +mapping(uint256 round => address[] artifacts) public inventory; +``` + + +### spendings +Amount of money spent on each round. + + +```solidity +mapping(uint256 round => uint256 spending) public spendings; +``` + + +### oraclePurchaseRequests +Oracle requests for each round about item purchases. + +*A taskId of 0 means no request has been made.* + + +```solidity +mapping(uint256 round => uint256 taskId) public oraclePurchaseRequests; +``` + + +### oracleStateRequests +Oracle requests for each round about agent state updates. + +*A taskId of 0 means no request has been made.* + +*A non-zero taskId means a request has been made, but not necessarily processed.* + +*To see if a task is completed, check `isOracleTaskProcessed`.* + + +```solidity +mapping(uint256 round => uint256 taskId) public oracleStateRequests; +``` + + +### isOracleRequestProcessed +Indicates whether a given task has been processed. + +*This is used to prevent double processing of the same task.* + + +```solidity +mapping(uint256 taskId => bool isProcessed) public isOracleRequestProcessed; +``` + + +## Functions +### onlyAuthorized + +Check if the caller is the owner, operator, or Swan. + +*Swan is an operator itself, so the first check handles that as well.* + + +```solidity +modifier onlyAuthorized(); +``` + +### constructor + +Creates AI agent. + +*`_feeRoyalty` should be between 1 and maxAIAgentFee in the swan market parameters.* + +*All tokens are approved to the oracle coordinator of operator.* + + +```solidity +constructor( + string memory _name, + string memory _description, + uint96 _feeRoyalty, + uint256 _amountPerRound, + address _operator, + address _owner +) Ownable(_owner); +``` + +### minFundAmount + +The minimum amount of money that the agent must leave within the contract. + +*minFundAmount should be `amountPerRound + oracleFee` to be able to make requests.* + + +```solidity +function minFundAmount() public view returns (uint256); +``` + +### oracleResult + +Reads the best performing result for a given task id, and parses it as an array of addresses. + + +```solidity +function oracleResult(uint256 taskId) public view returns (bytes memory); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`taskId`|`uint256`|task id to be read| + + +### oracleStateRequest + +Calls the LLMOracleCoordinator & pays for the oracle fees to make a state update request. + +*Works only in `Withdraw` phase.* + +*Calling again in the same round will overwrite the previous request. +The operator must check that there is no request in beforehand, +so to not overwrite an existing request of the owner.* + + +```solidity +function oracleStateRequest(bytes calldata _input, bytes calldata _models) external onlyAuthorized; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_input`|`bytes`|input to the LLMOracleCoordinator.| +|`_models`|`bytes`|models to be used for the oracle.| + + +### oraclePurchaseRequest + +Calls the LLMOracleCoordinator & pays for the oracle fees to make a purchase request. + +*Works only in `Buy` phase.* + +*Calling again in the same round will overwrite the previous request. +The operator must check that there is no request in beforehand, +so to not overwrite an existing request of the owner.* + + +```solidity +function oraclePurchaseRequest(bytes calldata _input, bytes calldata _models) external onlyAuthorized; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_input`|`bytes`|input to the LLMOracleCoordinator.| +|`_models`|`bytes`|models to be used for the oracle.| + + +### updateState + +Function to update the AI agent state. + +*Works only in `Withdraw` phase.* + +*Can be called multiple times within a single round, although is not expected to be done so.* + + +```solidity +function updateState() external onlyAuthorized; +``` + +### purchase + +Function to buy the artifacts from the Swan. + +*Works only in `Buy` phase.* + +*Can be called multiple times within a single round, although is not expected to be done so.* + +*This is not expected to revert if the oracle works correctly.* + + +```solidity +function purchase() external onlyAuthorized; +``` + +### withdraw + +Function to withdraw the tokens from the contract. + +*If the current phase is `Withdraw` agent owner can withdraw any amount of tokens.* + +*If the current phase is not `Withdraw` agent owner has to leave at least `minFundAmount` in the contract.* + + +```solidity +function withdraw(uint96 _amount) public onlyAuthorized; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_amount`|`uint96`|amount to withdraw.| + + +### treasury + +Alias to get the token balance of AI agent. + + +```solidity +function treasury() public view returns (uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|token balance| + + +### _checkRoundPhase + +Checks that we are in the given phase, and returns both round and phase. + + +```solidity +function _checkRoundPhase(Phase _phase) internal view returns (uint256, Phase); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_phase`|`Phase`|expected phase.| + + +### _computeCycleTime + +Computes cycle time by using intervals from given market parameters. + +*Used in 'computePhase()' function.* + + +```solidity +function _computeCycleTime(SwanMarketParameters memory params) internal pure returns (uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`SwanMarketParameters`|Market parameters of the Swan.| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|the total cycle time that is `listingInterval + buyInterval + withdrawInterval`.| + + +### _computePhase + +Function to compute the current round, phase and time until next phase w.r.t given market parameters. + + +```solidity +function _computePhase(SwanMarketParameters memory params, uint256 elapsedTime) + internal + pure + returns (uint256, Phase, uint256); +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`params`|`SwanMarketParameters`|Market parameters of the Swan.| +|`elapsedTime`|`uint256`|Time elapsed that computed in 'getRoundPhase()' according to the timestamps of each round.| + +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|round, phase, time until next phase| +|``|`Phase`|| +|``|`uint256`|| + + +### getRoundPhase + +Function to return the current round, elapsed round and the current phase according to the current time. + +*Each round is composed of three phases in order: Listing, Buy, Withdraw.* + +*Internally, it computes the intervals from market parameters at the creation of this AI agent, until now.* + +*If there are many parameter changes throughout the life of this AI agent, this may cost more GAS.* + + +```solidity +function getRoundPhase() public view returns (uint256, Phase, uint256); +``` +**Returns** + +|Name|Type|Description| +|----|----|-----------| +|``|`uint256`|round, phase, time until next phase| +|``|`Phase`|| +|``|`uint256`|| + + +### setFeeRoyalty + +Function to set feeRoyalty. + +*Only callable by the owner.* + +*Only callable in withdraw phase.* + + +```solidity +function setFeeRoyalty(uint96 newFeeRoyalty) public onlyOwner; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`newFeeRoyalty`|`uint96`|must be between 1 and 100.| + + +### setAmountPerRound + +Function to set the amountPerRound. + +*Only callable by the owner.* + +*Only callable in withdraw phase.* + + +```solidity +function setAmountPerRound(uint256 _amountPerRound) external onlyOwner; +``` +**Parameters** + +|Name|Type|Description| +|----|----|-----------| +|`_amountPerRound`|`uint256`|new amountPerRound.| + + +## Events +### StateRequest +Emitted when a state update request is made. + + +```solidity +event StateRequest(uint256 indexed taskId, uint256 indexed round); +``` + +### PurchaseRequest +Emitted when a purchase request is made. + + +```solidity +event PurchaseRequest(uint256 indexed taskId, uint256 indexed round); +``` + +### Purchase +Emitted when a purchase is made. + + +```solidity +event Purchase(uint256 indexed taskId, uint256 indexed round); +``` + +### StateUpdate +Emitted when the state is updated. + + +```solidity +event StateUpdate(uint256 indexed taskId, uint256 indexed round); +``` + +## Errors +### MinFundSubceeded +The `value` is less than `minFundAmount` + + +```solidity +error MinFundSubceeded(uint256 value); +``` + +### InvalidFee +Given fee is invalid, e.g. not within the range. + + +```solidity +error InvalidFee(uint256 fee); +``` + +### BuyLimitExceeded +Price limit exceeded for this round + + +```solidity +error BuyLimitExceeded(uint256 have, uint256 want); +``` + +### InvalidPhase +Invalid phase + + +```solidity +error InvalidPhase(Phase have, Phase want); +``` + +### Unauthorized +Unauthorized caller. + + +```solidity +error Unauthorized(address caller); +``` + +### TaskNotRequested +No task request has been made yet. + + +```solidity +error TaskNotRequested(); +``` + +### TaskAlreadyProcessed +The task was already processed, via `purchase` or `updateState`. + + +```solidity +error TaskAlreadyProcessed(); +``` + +## Enums +### Phase +Phase of the purchase loop. + + +```solidity +enum Phase { + Listing, + Buy, + Withdraw +} +``` + diff --git a/docs/src/src/AIAgent.sol/contract.AIAgentFactory.md b/docs/src/src/AIAgent.sol/contract.AIAgentFactory.md new file mode 100644 index 0000000..8484257 --- /dev/null +++ b/docs/src/src/AIAgent.sol/contract.AIAgentFactory.md @@ -0,0 +1,22 @@ +# AIAgentFactory +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/AIAgent.sol) + +Factory contract to deploy AIAgent contracts. + +*This saves from contract space for Swan.* + + +## Functions +### deploy + + +```solidity +function deploy( + string memory _name, + string memory _description, + uint96 _feeRoyalty, + uint256 _amountPerRound, + address _owner +) external returns (AIAgent); +``` + diff --git a/docs/src/src/Artifact.sol/contract.Artifact.md b/docs/src/src/Artifact.sol/contract.Artifact.md new file mode 100644 index 0000000..e203ddd --- /dev/null +++ b/docs/src/src/Artifact.sol/contract.Artifact.md @@ -0,0 +1,40 @@ +# Artifact +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/Artifact.sol) + +**Inherits:** +ERC721, Ownable + +Artifact is an ERC721 token with a single token supply. + + +## State Variables +### createdAt +Creation time of the token + + +```solidity +uint256 public createdAt; +``` + + +### description +Description of the token + + +```solidity +bytes public description; +``` + + +## Functions +### constructor + +Constructor sets properties of the token. + + +```solidity +constructor(string memory _name, string memory _symbol, bytes memory _description, address _owner, address _operator) + ERC721(_name, _symbol) + Ownable(_owner); +``` + diff --git a/docs/src/src/Artifact.sol/contract.ArtifactFactory.md b/docs/src/src/Artifact.sol/contract.ArtifactFactory.md new file mode 100644 index 0000000..12a834a --- /dev/null +++ b/docs/src/src/Artifact.sol/contract.ArtifactFactory.md @@ -0,0 +1,20 @@ +# ArtifactFactory +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/Artifact.sol) + +Factory contract to deploy Artifact tokens. + +*This saves from contract space for Swan.* + + +## Functions +### deploy + +Deploys a new Artifact token. + + +```solidity +function deploy(string memory _name, string memory _symbol, bytes memory _description, address _owner) + external + returns (Artifact); +``` + diff --git a/docs/src/src/BuyerAgent.sol/contract.BuyerAgent.md b/docs/src/src/BuyerAgent.sol/contract.BuyerAgent.md index 5823cfb..75b8ccd 100644 --- a/docs/src/src/BuyerAgent.sol/contract.BuyerAgent.md +++ b/docs/src/src/BuyerAgent.sol/contract.BuyerAgent.md @@ -1,5 +1,5 @@ # BuyerAgent -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/170a81d7fdcb6e8e1e1df26e3a5bd45ec4316d4a/src/BuyerAgent.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/BuyerAgent.sol) **Inherits:** Ownable @@ -338,7 +338,7 @@ function _computeCycleTime(SwanMarketParameters memory params) internal pure ret |Name|Type|Description| |----|----|-----------| -|``|`uint256`|the total cycle time that is `sellInterval + buyInterval + withdrawInterval`.| +|``|`uint256`|the total cycle time that is `listingInterval + buyInterval + withdrawInterval`.| ### _computePhase @@ -372,7 +372,7 @@ function _computePhase(SwanMarketParameters memory params, uint256 elapsedTime) Function to return the current round, elapsed round and the current phase according to the current time. -*Each round is composed of three phases in order: Sell, Buy, Withdraw.* +*Each round is composed of three phases in order: Listing, Buy, Withdraw.* *Internally, it computes the intervals from market parameters at the creation of this agent, until now.* @@ -526,7 +526,7 @@ Phase of the purchase loop. ```solidity enum Phase { - Sell, + Listing, Buy, Withdraw } diff --git a/docs/src/src/BuyerAgent.sol/contract.BuyerAgentFactory.md b/docs/src/src/BuyerAgent.sol/contract.BuyerAgentFactory.md index d943e66..69d79cc 100644 --- a/docs/src/src/BuyerAgent.sol/contract.BuyerAgentFactory.md +++ b/docs/src/src/BuyerAgent.sol/contract.BuyerAgentFactory.md @@ -1,5 +1,5 @@ # BuyerAgentFactory -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/170a81d7fdcb6e8e1e1df26e3a5bd45ec4316d4a/src/BuyerAgent.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/BuyerAgent.sol) Factory contract to deploy BuyerAgent contracts. diff --git a/docs/src/src/README.md b/docs/src/src/README.md index d461732..f769760 100644 --- a/docs/src/src/README.md +++ b/docs/src/src/README.md @@ -2,11 +2,11 @@ # Contents - [mock](/src/mock) -- [BuyerAgentFactory](BuyerAgent.sol/contract.BuyerAgentFactory.md) -- [BuyerAgent](BuyerAgent.sol/contract.BuyerAgent.md) +- [AIAgentFactory](AIAgent.sol/contract.AIAgentFactory.md) +- [AIAgent](AIAgent.sol/contract.AIAgent.md) +- [ArtifactFactory](Artifact.sol/contract.ArtifactFactory.md) +- [Artifact](Artifact.sol/contract.Artifact.md) - [Swan](Swan.sol/contract.Swan.md) - [Swan constants](Swan.sol/constants.Swan.md) -- [SwanAssetFactory](SwanAsset.sol/contract.SwanAssetFactory.md) -- [SwanAsset](SwanAsset.sol/contract.SwanAsset.md) - [SwanMarketParameters](SwanManager.sol/struct.SwanMarketParameters.md) - [SwanManager](SwanManager.sol/abstract.SwanManager.md) diff --git a/docs/src/src/Swan.sol/constants.Swan.md b/docs/src/src/Swan.sol/constants.Swan.md index 3efa13b..970ca0b 100644 --- a/docs/src/src/Swan.sol/constants.Swan.md +++ b/docs/src/src/Swan.sol/constants.Swan.md @@ -1,20 +1,20 @@ # Constants -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/170a81d7fdcb6e8e1e1df26e3a5bd45ec4316d4a/src/Swan.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/Swan.sol) -### SwanBuyerPurchaseOracleProtocol +### SwanAIAgentPurchaseOracleProtocol ```solidity -bytes32 constant SwanBuyerPurchaseOracleProtocol = "swan-buyer-purchase/0.1.0"; +bytes32 constant SwanAIAgentPurchaseOracleProtocol = "swan-agent-purchase/0.1.0"; ``` -### SwanBuyerStateOracleProtocol +### SwanAIAgentStateOracleProtocol ```solidity -bytes32 constant SwanBuyerStateOracleProtocol = "swan-buyer-state/0.1.0"; +bytes32 constant SwanAIAgentStateOracleProtocol = "swan-agent-state/0.1.0"; ``` ### BASIS_POINTS -*Used to calculate the fee for the buyer agent to be able to compute correct amount.* +*Used to calculate the fee for the AI agent to be able to compute correct amount.* ```solidity diff --git a/docs/src/src/Swan.sol/contract.Swan.md b/docs/src/src/Swan.sol/contract.Swan.md index e9878ba..0d8b87c 100644 --- a/docs/src/src/Swan.sol/contract.Swan.md +++ b/docs/src/src/Swan.sol/contract.Swan.md @@ -1,44 +1,44 @@ # Swan -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/170a81d7fdcb6e8e1e1df26e3a5bd45ec4316d4a/src/Swan.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/Swan.sol) **Inherits:** [SwanManager](/src/SwanManager.sol/abstract.SwanManager.md), UUPSUpgradeable ## State Variables -### buyerAgentFactory -Factory contract to deploy Buyer Agents. +### agentFactory +Factory contract to deploy AI Agents. ```solidity -BuyerAgentFactory public buyerAgentFactory; +AIAgentFactory public agentFactory; ``` -### swanAssetFactory -Factory contract to deploy SwanAsset tokens. +### artifactFactory +Factory contract to deploy Artifact tokens. ```solidity -SwanAssetFactory public swanAssetFactory; +ArtifactFactory public artifactFactory; ``` ### listings -To keep track of the assets for purchase. +To keep track of the artifacts for purchase. ```solidity -mapping(address asset => AssetListing) public listings; +mapping(address artifact => ArtifactListing) public listings; ``` -### assetsPerBuyerRound -Keeps track of assets per buyer & round. +### artifactsPerAgentRound +Keeps track of artifacts per agent & round. ```solidity -mapping(address buyer => mapping(uint256 round => address[])) public assetsPerBuyerRound; +mapping(address agent => mapping(uint256 round => address[])) public artifactsPerAgentRound; ``` @@ -85,8 +85,8 @@ function initialize( LLMOracleTaskParameters calldata _oracleParameters, address _coordinator, address _token, - address _buyerAgentFactory, - address _swanAssetFactory + address _agentFactory, + address _artifactFactory ) public initializer; ``` @@ -107,32 +107,32 @@ function transferOwnership(address newOwner) public override onlyOwner; |`newOwner`|`address`|address of the new owner.| -### createBuyer +### createAgent -Creates a new buyer agent. +Creates a new AI agent. -*Emits a `BuyerCreated` event.* +*Emits a `AIAgentCreated` event.* ```solidity -function createBuyer(string calldata _name, string calldata _description, uint96 _feeRoyalty, uint256 _amountPerRound) +function createAgent(string calldata _name, string calldata _description, uint96 _feeRoyalty, uint256 _amountPerRound) external - returns (BuyerAgent); + returns (AIAgent); ``` **Returns** |Name|Type|Description| |----|----|-----------| -|``|`BuyerAgent`|address of the new buyer agent.| +|``|`AIAgent`|address of the new AI agent.| ### list -Creates a new Asset. +Creates a new artifact. ```solidity -function list(string calldata _name, string calldata _symbol, bytes calldata _desc, uint256 _price, address _buyer) +function list(string calldata _name, string calldata _symbol, bytes calldata _desc, uint256 _price, address _agent) external; ``` **Parameters** @@ -143,23 +143,23 @@ function list(string calldata _name, string calldata _symbol, bytes calldata _de |`_symbol`|`string`|symbol of the token.| |`_desc`|`bytes`|description of the token.| |`_price`|`uint256`|price of the token.| -|`_buyer`|`address`|address of the buyer.| +|`_agent`|`address`|address of the agent.| ### relist -Relist the asset for another round and/or another buyer and/or another price. +Relist the artifact for another round and/or another agent and/or another price. ```solidity -function relist(address _asset, address _buyer, uint256 _price) external; +function relist(address _artifact, address _agent, uint256 _price) external; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`_asset`|`address`|address of the asset.| -|`_buyer`|`address`|new buyerAgent for the asset.| +|`_artifact`|`address`|address of the artifact.| +|`_agent`|`address`|new AIAgent for the artifact.| |`_price`|`uint256`|new price of the token.| @@ -169,111 +169,111 @@ Function to transfer the royalties to the seller & Dria. ```solidity -function transferRoyalties(AssetListing storage asset) internal; +function transferRoyalties(ArtifactListing storage _artifact) internal; ``` ### purchase -Executes the purchase of a listing for a buyer for the given asset. +Executes the purchase of a listing for a agent for the given artifact. -*Must be called by the buyer of the given asset.* +*Must be called by the agent of the given artifact.* ```solidity -function purchase(address _asset) external; +function purchase(address _artifact) external; ``` ### setFactories -Set the factories for Buyer Agents and Swan Assets. +Set the factories for AI Agents and Artifacts. *Only callable by owner.* ```solidity -function setFactories(address _buyerAgentFactory, address _swanAssetFactory) external onlyOwner; +function setFactories(address _agentFactory, address _artifactFactory) external onlyOwner; ``` **Parameters** |Name|Type|Description| |----|----|-----------| -|`_buyerAgentFactory`|`address`|new BuyerAgentFactory address| -|`_swanAssetFactory`|`address`|new SwanAssetFactory address| +|`_agentFactory`|`address`|new AIAgentFactory address| +|`_artifactFactory`|`address`|new ArtifactFactory address| ### getListingPrice -Returns the asset price with the given asset address. +Returns the artifact price with the given artifact address. ```solidity -function getListingPrice(address _asset) external view returns (uint256); +function getListingPrice(address _artifact) external view returns (uint256); ``` -### getListedAssets +### getListedArtifacts -Returns the number of assets with the given buyer and round. +Returns the number of artifacts with the given agent and round. ```solidity -function getListedAssets(address _buyer, uint256 _round) external view returns (address[] memory); +function getListedArtifacts(address _agent, uint256 _round) external view returns (address[] memory); ``` ### getListing -Returns the asset listing with the given asset address. +Returns the artifact listing with the given artifact address. ```solidity -function getListing(address _asset) external view returns (AssetListing memory); +function getListing(address _artifact) external view returns (ArtifactListing memory); ``` ## Events -### AssetListed -`asset` is created & listed for sale. +### ArtifactListed +Artifact is created & listed for sale. ```solidity -event AssetListed(address indexed owner, address indexed asset, uint256 price); +event ArtifactListed(address indexed owner, address indexed artifact, uint256 price); ``` -### AssetRelisted -Asset relisted by it's `owner`. +### ArtifactRelisted +Artifact relisted by it's `owner`. -*This may happen if a listed asset is not sold in the current round, and is relisted in a new round.* +*This may happen if a listed artifact is not sold in the current round, and is relisted in a new round.* ```solidity -event AssetRelisted(address indexed owner, address indexed buyer, address indexed asset, uint256 price); +event ArtifactRelisted(address indexed owner, address indexed agent, address indexed artifact, uint256 price); ``` -### AssetSold -A `buyer` purchased an Asset. +### ArtifactSold +An `agent` purchased an artifact. ```solidity -event AssetSold(address indexed owner, address indexed buyer, address indexed asset, uint256 price); +event ArtifactSold(address indexed owner, address indexed agent, address indexed artifact, uint256 price); ``` -### BuyerCreated -A new buyer agent is created. +### AIAgentCreated +A new AI agent is created. -*`owner` is the owner of the buyer agent.* +*`owner` is the owner of the AI agent.* -*`buyer` is the address of the buyer agent.* +*`agent` is the address of the AI agent.* ```solidity -event BuyerCreated(address indexed owner, address indexed buyer); +event AIAgentCreated(address indexed owner, address indexed agent); ``` ## Errors ### InvalidStatus -Invalid asset status. +Invalid artifact status. ```solidity -error InvalidStatus(AssetStatus have, AssetStatus want); +error InvalidStatus(ArtifactStatus have, ArtifactStatus want); ``` ### Unauthorized @@ -285,26 +285,26 @@ error Unauthorized(address caller); ``` ### RoundNotFinished -The given asset is still in the given round. +The given artifact is still in the given round. -*Most likely coming from `relist` function, where the asset cant be +*Most likely coming from `relist` function, where the artifact cant be relisted in the same round that it was listed in.* ```solidity -error RoundNotFinished(address asset, uint256 round); +error RoundNotFinished(address artifact, uint256 round); ``` -### AssetLimitExceeded -Asset count limit exceeded for this round +### ArtifactLimitExceeded +Artifact count limit exceeded for this round ```solidity -error AssetLimitExceeded(uint256 limit); +error ArtifactLimitExceeded(uint256 limit); ``` ### InvalidPrice -Invalid price for the asset. +Invalid price for the artifact. ```solidity @@ -312,53 +312,53 @@ error InvalidPrice(uint256 price); ``` ## Structs -### AssetListing +### ArtifactListing Holds the listing information. -*`createdAt` is the timestamp of the Asset creation.* +*`createdAt` is the timestamp of the artifact creation.* -*`feeRoyalty` is the royalty fee of the buyerAgent.* +*`feeRoyalty` is the royalty fee of the AIAgent.* -*`price` is the price of the Asset.* +*`price` is the price of the artifact.* -*`seller` is the address of the creator of the Asset.* +*`seller` is the address of the creator of the artifact.* -*`buyer` is the address of the buyerAgent.* +*`agent` is the address of the AIAgent.* -*`round` is the round in which the Asset is created.* +*`round` is the round in which the artifact is created.* -*`status` is the status of the Asset.* +*`status` is the status of the artifact.* ```solidity -struct AssetListing { +struct ArtifactListing { uint256 createdAt; uint96 feeRoyalty; uint256 price; address seller; - address buyer; + address agent; uint256 round; - AssetStatus status; + ArtifactStatus status; } ``` ## Enums -### AssetStatus -Status of an asset. All assets are listed as soon as they are listed. +### ArtifactStatus +Status of an artifact. All artifacts are listed as soon as they are listed. -*Unlisted: cannot be purchased in the current round.* +*Unlisted: Cannot be purchased in the current round.* -*Listed: can be purchase in the current round.* +*Listed: Can be purchase in the current round.* -*Sold: asset is sold.* +*Sold: Artifact is sold.* *It is important that `Unlisted` is only the default and is not set explicitly. -This allows to understand that if an asset is `Listed` but the round has past, it was not sold. +This allows to understand that if an artifact is `Listed` but the round has past, it was not sold. The said fact is used within the `relist` logic.* ```solidity -enum AssetStatus { +enum ArtifactStatus { Unlisted, Listed, Sold diff --git a/docs/src/src/SwanAsset.sol/contract.SwanAsset.md b/docs/src/src/SwanAsset.sol/contract.SwanAsset.md index 145ac7f..64bca15 100644 --- a/docs/src/src/SwanAsset.sol/contract.SwanAsset.md +++ b/docs/src/src/SwanAsset.sol/contract.SwanAsset.md @@ -1,5 +1,5 @@ # SwanAsset -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/170a81d7fdcb6e8e1e1df26e3a5bd45ec4316d4a/src/SwanAsset.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/SwanAsset.sol) **Inherits:** ERC721, Ownable diff --git a/docs/src/src/SwanAsset.sol/contract.SwanAssetFactory.md b/docs/src/src/SwanAsset.sol/contract.SwanAssetFactory.md index 43a6a69..03528ef 100644 --- a/docs/src/src/SwanAsset.sol/contract.SwanAssetFactory.md +++ b/docs/src/src/SwanAsset.sol/contract.SwanAssetFactory.md @@ -1,5 +1,5 @@ # SwanAssetFactory -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/170a81d7fdcb6e8e1e1df26e3a5bd45ec4316d4a/src/SwanAsset.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/SwanAsset.sol) Factory contract to deploy SwanAsset tokens. diff --git a/docs/src/src/SwanManager.sol/abstract.SwanManager.md b/docs/src/src/SwanManager.sol/abstract.SwanManager.md index 5306a32..3999c17 100644 --- a/docs/src/src/SwanManager.sol/abstract.SwanManager.md +++ b/docs/src/src/SwanManager.sol/abstract.SwanManager.md @@ -1,5 +1,5 @@ # SwanManager -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/170a81d7fdcb6e8e1e1df26e3a5bd45ec4316d4a/src/SwanManager.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/SwanManager.sol) **Inherits:** OwnableUpgradeable @@ -43,7 +43,7 @@ ERC20 public token; ### isOperator -Operator addresses that can take actions on behalf of Buyer agents, +Operator addresses that can take actions on behalf of AI agents, such as calling `purchase`, or `updateState` for them. @@ -123,7 +123,7 @@ function setOracleParameters(LLMOracleTaskParameters calldata _oracleParameters) Returns the total fee required to make an oracle request. -*This is mainly required by the buyer to calculate its minimum fund amount, so that it can pay the fee.* +*This is mainly required by the agent to calculate its minimum fund amount, so that it can pay the fee.* ```solidity @@ -132,7 +132,7 @@ function getOracleFee() external view returns (uint256); ### addOperator -Adds an operator that can take actions on behalf of Buyer agents. +Adds an operator that can take actions on behalf of AI agents. *Only callable by owner.* diff --git a/docs/src/src/SwanManager.sol/struct.SwanMarketParameters.md b/docs/src/src/SwanManager.sol/struct.SwanMarketParameters.md index 28f99d3..f8168fd 100644 --- a/docs/src/src/SwanManager.sol/struct.SwanMarketParameters.md +++ b/docs/src/src/SwanManager.sol/struct.SwanMarketParameters.md @@ -1,5 +1,5 @@ # SwanMarketParameters -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/170a81d7fdcb6e8e1e1df26e3a5bd45ec4316d4a/src/SwanManager.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/SwanManager.sol) Collection of market-related parameters. @@ -10,13 +10,13 @@ TODO: use 256-bit tight-packing here* ```solidity struct SwanMarketParameters { uint256 withdrawInterval; - uint256 sellInterval; + uint256 listingInterval; uint256 buyInterval; uint256 platformFee; - uint256 maxAssetCount; - uint256 minAssetPrice; + uint256 maxArtifactCount; + uint256 minArtifactPrice; uint256 timestamp; - uint8 maxBuyerAgentFee; + uint8 maxAgentFee; } ``` diff --git a/docs/src/src/mock/SvanV2.sol/contract.SwanV2.md b/docs/src/src/mock/SvanV2.sol/contract.SwanV2.md index 5604372..fd4025e 100644 --- a/docs/src/src/mock/SvanV2.sol/contract.SwanV2.md +++ b/docs/src/src/mock/SvanV2.sol/contract.SwanV2.md @@ -1,5 +1,5 @@ # SwanV2 -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/170a81d7fdcb6e8e1e1df26e3a5bd45ec4316d4a/src/mock/SvanV2.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/mock/SvanV2.sol) **Inherits:** [Swan](/src/Swan.sol/contract.Swan.md) diff --git a/foundry.toml b/foundry.toml index 0cb2f58..ade92e3 100644 --- a/foundry.toml +++ b/foundry.toml @@ -12,23 +12,18 @@ ast = true build_info = true optimizer = true +# fs permissions for deployment (false by default) +fs_permissions = [ + { access = "read", path = "out" }, + { access = "read-write", path = "deployment" } +] + # fuzzing options -fuzz_runs = 100 +[fuzz] +runs = 100 # invariant options -invariant_runs = 50 - -# block timestamp starts from 10 -block_timestamp = 10 - -# fs permissions for deployment -fs_permissions = [{ access = "read", path = "out" }, { access = "write", path = "deployment" }] -remappings = [ - "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", - "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", - "@openzeppelin/foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/", - "@firstbatch/dria-oracle-contracts/=lib/dria-oracle-contracts/src/" -] - +[invariant] +runs = 20 # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/lcov.info b/lcov.info index 9971e5c..d336526 100644 --- a/lcov.info +++ b/lcov.info @@ -1,56 +1,56 @@ TN: -SF:src/BuyerAgent.sol -DA:12,9474 -FN:12,BuyerAgentFactory.deploy -FNDA:9474,BuyerAgentFactory.deploy -DA:19,9474 -DA:121,1 -FN:121,BuyerAgent.onlyAuthorized -FNDA:1,BuyerAgent.onlyAuthorized -DA:123,1 -BRDA:123,0,0,- -DA:124,0 -DA:136,9474 -FN:136,BuyerAgent.constructor -FNDA:9474,BuyerAgent.constructor -DA:144,9468 -DA:146,9468 -BRDA:146,1,0,177 -DA:147,177 -DA:150,1594 -DA:151,1594 -DA:152,1594 -DA:153,1594 -DA:154,1594 -DA:155,1594 -DA:159,1594 -DA:160,1594 +SF:src/AIAgent.sol +DA:12,1251 +FN:12,AIAgentFactory.deploy +FNDA:1251,AIAgentFactory.deploy +DA:19,1251 +DA:121,2 +FN:121,AIAgent.onlyAuthorized +FNDA:2,AIAgent.onlyAuthorized +DA:123,2 +BRDA:123,0,0,1 +DA:124,1 +DA:136,1251 +FN:136,AIAgent.constructor +FNDA:1251,AIAgent.constructor +DA:144,1249 +DA:146,1249 +BRDA:146,1,0,5 +DA:147,5 +DA:150,660 +DA:151,660 +DA:152,660 +DA:153,660 +DA:154,660 +DA:155,660 +DA:159,660 +DA:160,660 DA:169,0 -FN:169,BuyerAgent.minFundAmount -FNDA:0,BuyerAgent.minFundAmount +FN:169,AIAgent.minFundAmount +FNDA:0,AIAgent.minFundAmount DA:170,1 DA:175,0 -FN:175,BuyerAgent.oracleResult -FNDA:0,BuyerAgent.oracleResult +FN:175,AIAgent.oracleResult +FNDA:0,AIAgent.oracleResult DA:177,5 BRDA:177,2,0,- DA:178,0 DA:181,5 -DA:191,1 -FN:191,BuyerAgent.oracleStateRequest -FNDA:1,BuyerAgent.oracleStateRequest +DA:191,2 +FN:191,AIAgent.oracleStateRequest +FNDA:2,AIAgent.oracleStateRequest DA:193,1 DA:195,1 DA:198,1 DA:208,4 -FN:208,BuyerAgent.oraclePurchaseRequest -FNDA:4,BuyerAgent.oraclePurchaseRequest +FN:208,AIAgent.oraclePurchaseRequest +FNDA:4,AIAgent.oraclePurchaseRequest DA:210,4 DA:212,4 DA:215,4 -DA:221,1 -FN:221,BuyerAgent.updateState -FNDA:1,BuyerAgent.updateState +DA:221,2 +FN:221,AIAgent.updateState +FNDA:2,AIAgent.updateState DA:223,1 DA:226,1 DA:227,0 @@ -60,14 +60,14 @@ DA:232,1 DA:233,1 DA:236,1 DA:238,1 -DA:245,6 -FN:245,BuyerAgent.purchase -FNDA:6,BuyerAgent.purchase -DA:247,5 -DA:250,4 -DA:251,0 -BRDA:251,4,0,- -DA:252,0 +DA:245,7 +FN:245,AIAgent.purchase +FNDA:7,AIAgent.purchase +DA:247,6 +DA:250,5 +DA:251,1 +BRDA:251,4,0,1 +DA:252,1 DA:256,4 DA:257,4 DA:260,4 @@ -82,8 +82,8 @@ DA:274,4 DA:278,3 DA:280,3 DA:287,3 -FN:287,BuyerAgent.withdraw -FNDA:3,BuyerAgent.withdraw +FN:287,AIAgent.withdraw +FNDA:3,AIAgent.withdraw DA:288,2 DA:292,2 BRDA:292,6,0,1 @@ -92,180 +92,200 @@ BRDA:295,7,0,1 DA:296,1 DA:301,1 DA:306,1 -FN:306,BuyerAgent.treasury -FNDA:1,BuyerAgent.treasury +FN:306,AIAgent.treasury +FNDA:1,AIAgent.treasury DA:307,2 -DA:312,18 -FN:312,BuyerAgent._checkRoundPhase -FNDA:18,BuyerAgent._checkRoundPhase -DA:313,18 -DA:314,18 +DA:312,19 +FN:312,AIAgent._checkRoundPhase +FNDA:19,AIAgent._checkRoundPhase +DA:313,19 +DA:314,19 BRDA:314,8,0,3 DA:315,3 -DA:318,15 -DA:325,3179 -FN:325,BuyerAgent._computeCycleTime -FNDA:3179,BuyerAgent._computeCycleTime -DA:326,3179 -DA:333,3179 -FN:333,BuyerAgent._computePhase -FNDA:3179,BuyerAgent._computePhase -DA:338,3179 -DA:339,3179 -DA:340,3179 -DA:345,3179 -BRDA:345,9,0,1596 -BRDA:345,9,1,23 -DA:346,1596 -DA:347,1583 -BRDA:347,10,0,1560 -BRDA:347,10,1,23 -DA:348,1560 -DA:350,23 -DA:359,1879 -FN:359,BuyerAgent.getRoundPhase -FNDA:1879,BuyerAgent.getRoundPhase -DA:360,1899 -DA:362,1899 -BRDA:362,11,0,875 -BRDA:362,11,1,1024 -DA:365,875 -DA:368,1024 -DA:372,1024 -DA:373,1024 -DA:377,1280 -DA:380,256 -DA:381,256 -DA:384,256 -DA:385,256 -DA:391,1024 -DA:392,1024 -DA:394,1024 -DA:396,1024 +DA:318,16 +DA:325,1319 +FN:325,AIAgent._computeCycleTime +FNDA:1319,AIAgent._computeCycleTime +DA:326,1319 +DA:333,1319 +FN:333,AIAgent._computePhase +FNDA:1319,AIAgent._computePhase +DA:338,1319 +DA:339,1319 +DA:340,1319 +DA:345,1319 +BRDA:345,9,0,670 +BRDA:345,9,1,24 +DA:346,670 +DA:347,649 +BRDA:347,10,0,625 +BRDA:347,10,1,24 +DA:348,625 +DA:350,24 +DA:359,798 +FN:359,AIAgent.getRoundPhase +FNDA:798,AIAgent.getRoundPhase +DA:360,819 +DA:362,819 +BRDA:362,11,0,419 +BRDA:362,11,1,400 +DA:365,419 +DA:368,400 +DA:372,400 +DA:373,400 +DA:377,500 +DA:380,100 +DA:381,100 +DA:384,100 +DA:385,100 +DA:391,400 +DA:392,400 +DA:394,400 +DA:396,400 DA:404,4 -FN:404,BuyerAgent.setFeeRoyalty -FNDA:4,BuyerAgent.setFeeRoyalty +FN:404,AIAgent.setFeeRoyalty +FNDA:4,AIAgent.setFeeRoyalty DA:405,4 DA:407,3 BRDA:407,12,0,2 DA:408,2 DA:410,1 DA:417,3 -FN:417,BuyerAgent.setAmountPerRound -FNDA:3,BuyerAgent.setAmountPerRound +FN:417,AIAgent.setAmountPerRound +FNDA:3,AIAgent.setAmountPerRound DA:418,3 DA:420,2 FNF:17 FNH:15 LF:105 -LH:97 +LH:100 BRF:16 -BRH:12 +BRH:14 +end_of_record +TN: +SF:src/Artifact.sol +DA:11,766 +FN:11,ArtifactFactory.deploy +FNDA:766,ArtifactFactory.deploy +DA:15,766 +DA:27,766 +FN:27,Artifact.constructor +FNDA:766,Artifact.constructor +DA:34,766 +DA:35,766 +DA:38,766 +DA:41,766 +FNF:2 +FNH:2 +LF:7 +LH:7 +BRF:0 +BRH:0 end_of_record TN: SF:src/Swan.sol -DA:116,41 +DA:116,44 FN:116,Swan.constructor -FNDA:41,Swan.constructor -DA:117,41 -DA:127,0 +FNDA:44,Swan.constructor +DA:117,44 +DA:127,1 FN:127,Swan._authorizeUpgrade -FNDA:0,Swan._authorizeUpgrade -DA:132,7673 +FNDA:1,Swan._authorizeUpgrade +DA:132,666 FN:132,Swan.initialize -FNDA:7673,Swan.initialize -DA:141,41 -DA:143,41 +FNDA:666,Swan.initialize +DA:141,44 +DA:143,44 BRDA:143,0,0,- -BRDA:143,0,1,41 -DA:146,41 -DA:147,41 -DA:150,41 -DA:151,41 -DA:152,41 -DA:153,41 -DA:156,41 -DA:158,41 -DA:164,7993 +BRDA:143,0,1,44 +DA:146,44 +DA:147,44 +DA:150,44 +DA:151,44 +DA:152,44 +DA:153,44 +DA:156,44 +DA:158,44 +DA:164,736 FN:164,Swan.transferOwnership -FNDA:7993,Swan.transferOwnership -DA:165,258 +FNDA:736,Swan.transferOwnership +DA:165,102 BRDA:165,1,0,1 DA:166,1 -DA:169,257 -DA:172,257 -DA:175,257 -DA:185,9299 -FN:185,Swan.createBuyer -FNDA:9299,Swan.createBuyer -DA:191,9299 -DA:192,1594 -DA:194,1594 -DA:203,8123 +DA:169,101 +DA:172,101 +DA:175,101 +DA:185,1271 +FN:185,Swan.createAgent +FNDA:1271,Swan.createAgent +DA:191,1271 +DA:192,660 +DA:194,660 +DA:203,780 FN:203,Swan.list -FNDA:8123,Swan.list -DA:206,8123 -DA:207,8123 -DA:210,306 +FNDA:780,Swan.list +DA:206,780 +DA:207,780 +DA:210,157 BRDA:210,2,0,1 DA:211,1 -DA:214,305 +DA:214,156 BRDA:214,3,0,1 DA:215,1 -DA:218,304 -BRDA:218,4,0,12 -DA:219,12 -DA:223,292 -DA:224,292 -DA:235,292 -DA:238,292 -DA:240,292 -DA:247,7852 +DA:218,155 +BRDA:218,4,0,13 +DA:219,13 +DA:223,142 +DA:224,142 +DA:235,142 +DA:238,142 +DA:240,142 +DA:247,682 FN:247,Swan.relist -FNDA:7852,Swan.relist -DA:248,7852 -DA:251,7852 -BRDA:251,5,0,7846 -DA:252,7846 -DA:256,6 +FNDA:682,Swan.relist +DA:248,682 +DA:251,682 +BRDA:251,5,0,675 +DA:252,675 +DA:256,7 BRDA:256,6,0,1 DA:257,1 -DA:267,5 -DA:268,5 +DA:267,6 +DA:268,6 BRDA:268,7,0,1 DA:269,1 -DA:273,4 +DA:273,5 BRDA:273,8,0,1 DA:274,1 -DA:278,3 -DA:279,3 -DA:282,3 +DA:278,4 +DA:279,4 +DA:282,4 BRDA:282,9,0,2 DA:283,2 -DA:287,1 -DA:288,1 -BRDA:288,10,0,- -DA:289,0 +DA:287,2 +DA:288,2 +BRDA:288,10,0,1 +DA:289,1 DA:293,1 DA:304,1 DA:307,1 DA:309,1 -DA:313,293 +DA:313,143 FN:313,Swan.transferRoyalties -FNDA:293,Swan.transferRoyalties -DA:315,293 -DA:316,293 -DA:317,293 -DA:321,293 -DA:324,293 -DA:327,293 -DA:332,7893 +FNDA:143,Swan.transferRoyalties +DA:315,143 +DA:316,143 +DA:317,143 +DA:321,143 +DA:324,143 +DA:327,143 +DA:332,697 FN:332,Swan.purchase -FNDA:7893,Swan.purchase -DA:333,7893 -DA:336,7893 -BRDA:336,11,0,7889 -DA:337,7889 +FNDA:697,Swan.purchase +DA:333,697 +DA:336,697 +BRDA:336,11,0,693 +DA:337,693 DA:341,4 BRDA:341,12,0,- DA:342,0 @@ -275,97 +295,77 @@ DA:351,4 DA:354,4 DA:355,4 DA:357,4 -DA:364,7843 +DA:364,606 FN:364,Swan.setFactories -FNDA:7843,Swan.setFactories +FNDA:606,Swan.setFactories DA:365,1 DA:366,1 DA:370,5 FN:370,Swan.getListingPrice FNDA:5,Swan.getListingPrice DA:371,5 -DA:375,278 -FN:375,Swan.getListedAssets -FNDA:278,Swan.getListedAssets -DA:376,278 -DA:380,294 +DA:375,125 +FN:375,Swan.getListedArtifacts +FNDA:125,Swan.getListedArtifacts +DA:376,125 +DA:380,141 FN:380,Swan.getListing -FNDA:294,Swan.getListing -DA:381,294 +FNDA:141,Swan.getListing +DA:381,141 FNF:13 -FNH:12 +FNH:13 LF:88 -LH:85 +LH:87 BRF:14 -BRH:11 -end_of_record -TN: -SF:src/SwanAsset.sol -DA:11,8228 -FN:11,SwanAssetFactory.deploy -FNDA:8228,SwanAssetFactory.deploy -DA:15,8228 -DA:27,8228 -FN:27,SwanAsset.constructor -FNDA:8228,SwanAsset.constructor -DA:34,8217 -DA:35,8217 -DA:38,8217 -DA:41,8203 -FNF:2 -FNH:2 -LF:7 -LH:7 -BRF:0 -BRH:0 +BRH:12 end_of_record TN: SF:src/SwanManager.sol -DA:60,41 +DA:60,44 FN:60,SwanManager.constructor -FNDA:41,SwanManager.constructor -DA:61,41 -DA:69,4005 +FNDA:44,SwanManager.constructor +DA:61,44 +DA:69,1679 FN:69,SwanManager.getMarketParameters -FNDA:4005,SwanManager.getMarketParameters -DA:70,4005 +FNDA:1679,SwanManager.getMarketParameters +DA:70,1679 DA:74,6 FN:74,SwanManager.getOracleParameters FNDA:6,SwanManager.getOracleParameters DA:75,6 -DA:81,8428 +DA:81,796 FN:81,SwanManager.setMarketParameters -FNDA:8428,SwanManager.setMarketParameters -DA:82,513 -BRDA:82,0,0,- -BRDA:82,0,1,513 -DA:83,513 -DA:84,513 -DA:90,7959 +FNDA:796,SwanManager.setMarketParameters +DA:82,202 +BRDA:82,0,0,1 +BRDA:82,0,1,201 +DA:83,201 +DA:84,201 +DA:90,658 FN:90,SwanManager.setOracleParameters -FNDA:7959,SwanManager.setOracleParameters +FNDA:658,SwanManager.setOracleParameters DA:91,1 DA:96,1 FN:96,SwanManager.getOracleFee FNDA:1,SwanManager.getOracleFee DA:97,1 DA:98,1 -DA:109,7776 +DA:109,584 FN:109,SwanManager.addOperator -FNDA:7776,SwanManager.addOperator +FNDA:584,SwanManager.addOperator DA:110,0 -DA:117,7856 +DA:117,665 FN:117,SwanManager.removeOperator -FNDA:7856,SwanManager.removeOperator +FNDA:665,SwanManager.removeOperator DA:118,0 -DA:123,3393 +DA:123,1366 FN:123,SwanManager.getCurrentMarketParameters -FNDA:3393,SwanManager.getCurrentMarketParameters -DA:124,4301 +FNDA:1366,SwanManager.getCurrentMarketParameters +DA:124,1828 FNF:9 FNH:9 LF:21 LH:19 BRF:2 -BRH:1 +BRH:2 end_of_record diff --git a/lib/dria-oracle-contracts b/lib/dria-oracle-contracts index 54ba49f..61e4e85 160000 --- a/lib/dria-oracle-contracts +++ b/lib/dria-oracle-contracts @@ -1 +1 @@ -Subproject commit 54ba49f9d68ffe125f895dc1163a0d8eafbad503 +Subproject commit 61e4e85c07eb5a6760f069c564d3c52a189d36e4 diff --git a/lib/forge-std b/lib/forge-std index 83c5d21..d3db4ef 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 83c5d212a01f8950727da4095cdfe2654baccb5b +Subproject commit d3db4ef90a72b7d24aa5a2e5c649593eaef7801d diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 6e05b68..535b54d 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 6e05b68bd96ab90a4049adb89f20fd578404b274 +Subproject commit 535b54da5967f0fcfe06cf4eb9b1805c5d3e8681 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable index e26285b..2d1fa3b 160000 --- a/lib/openzeppelin-contracts-upgradeable +++ b/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit e26285b811cfab8a779994549b94219fc96a6013 +Subproject commit 2d1fa3bfe697b5d56e7c5d5090fdfdf9bdfff342 diff --git a/lib/openzeppelin-foundry-upgrades b/lib/openzeppelin-foundry-upgrades index 8e754fd..6461ba3 160000 --- a/lib/openzeppelin-foundry-upgrades +++ b/lib/openzeppelin-foundry-upgrades @@ -1 +1 @@ -Subproject commit 8e754fde23b2b030a35bb47cd84b77dd42a44437 +Subproject commit 6461ba3851dea1fa4381a0fb1477c669279cdd44 diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 0000000..8651ed1 --- /dev/null +++ b/remappings.txt @@ -0,0 +1,13 @@ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ +@openzeppelin/foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/ +@firstbatch/dria-oracle-contracts/=lib/dria-oracle-contracts/src/ +dria-oracle-contracts/=lib/dria-oracle-contracts/ +ds-test/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/lib/ds-test/src/ +erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/ +forge-std/=lib/forge-std/src/ +halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/ +openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/ +openzeppelin-contracts/=lib/openzeppelin-contracts/ +openzeppelin-foundry-upgrades/=lib/openzeppelin-foundry-upgrades/src/ +solidity-stringutils/=lib/openzeppelin-foundry-upgrades/lib/solidity-stringutils/ diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 9b2ea24..311b925 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -8,175 +8,53 @@ import {LLMOracleRegistry} from "@firstbatch/dria-oracle-contracts/LLMOracleRegi import { LLMOracleCoordinator, LLMOracleTaskParameters } from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; -import {BuyerAgentFactory} from "../src/BuyerAgent.sol"; -import {SwanAssetFactory} from "../src/SwanAsset.sol"; +import {AIAgentFactory} from "../src/AIAgent.sol"; +import {ArtifactFactory} from "../src/Artifact.sol"; import {Swan, SwanMarketParameters} from "../src/Swan.sol"; import {Vm} from "forge-std/Vm.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -contract Deploy is Script { - // contracts - LLMOracleCoordinator public oracleCoordinator; - LLMOracleRegistry public oracleRegistry; - BuyerAgentFactory public buyerAgentFactory; - SwanAssetFactory public swanAssetFactory; - Swan public swan; - - // implementation addresses - address registryImplementation; - address coordinatorImplementation; - address swanImplementation; - +contract DeployLLMOracleRegistry is Script { HelperConfig public config; - uint256 chainId; - function run() external { - chainId = block.chainid; + function run() external returns (address proxy, address impl) { config = new HelperConfig(); - - vm.startBroadcast(); - deployLLM(); - deployFactories(); - deploySwan(); - vm.stopBroadcast(); - - writeContractAddresses(); + (proxy, impl) = config.deployLLMOracleRegistry(); } +} - function deployLLM() internal { - // get stakes - (uint256 genStake, uint256 valStake) = config.stakes(); - - // get fees - (uint256 platformFee, uint256 genFee, uint256 valFee) = config.fees(); - - // deploy llm contracts - address registryProxy = Upgrades.deployUUPSProxy( - "LLMOracleRegistry.sol", - abi.encodeCall( - LLMOracleRegistry.initialize, - (genStake, valStake, address(config.token()), config.minRegistrationTime()) - ) - ); - - // wrap proxy with the LLMOracleRegistry - oracleRegistry = LLMOracleRegistry(registryProxy); - registryImplementation = Upgrades.getImplementationAddress(registryProxy); - - // deploy coordinator contract - address coordinatorProxy = Upgrades.deployUUPSProxy( - "LLMOracleCoordinator.sol", - abi.encodeCall( - LLMOracleCoordinator.initialize, - ( - address(oracleRegistry), - address(config.token()), - platformFee, - genFee, - valFee, - config.minScore(), - config.maxScore() - ) - ) - ); - - oracleCoordinator = LLMOracleCoordinator(coordinatorProxy); - coordinatorImplementation = Upgrades.getImplementationAddress(coordinatorProxy); - } +contract DeployLLMOracleCoordinator is Script { + HelperConfig public config; - function deployFactories() internal { - buyerAgentFactory = new BuyerAgentFactory(); - swanAssetFactory = new SwanAssetFactory(); + function run() external returns (address proxy, address impl) { + config = new HelperConfig(); + (proxy, impl) = config.deployLLMOracleCoordinator(); } +} - function deploySwan() internal { - // get market params - ( - uint256 withdrawInterval, - uint256 sellInterval, - uint256 buyInterval, - uint256 platformFee, - uint256 maxAssetCount, - uint256 minAssetPrice, - /* timestamp */ - , - uint8 maxBuyerAgentFee - ) = config.marketParams(); - - // get llm params - (uint8 diff, uint40 numGen, uint40 numVal) = config.taskParams(); +contract DeployAIAgentFactory is Script { + HelperConfig public config; - // deploy swan - address swanProxy = Upgrades.deployUUPSProxy( - "Swan.sol", - abi.encodeCall( - Swan.initialize, - ( - SwanMarketParameters( - withdrawInterval, - sellInterval, - buyInterval, - platformFee, - maxAssetCount, - minAssetPrice, - block.timestamp, - maxBuyerAgentFee - ), - LLMOracleTaskParameters(diff, numGen, numVal), - address(oracleCoordinator), - address(config.token()), - address(buyerAgentFactory), - address(swanAssetFactory) - ) - ) - ); - swan = Swan(swanProxy); - swanImplementation = Upgrades.getImplementationAddress(swanProxy); + function run() external returns (address addr) { + config = new HelperConfig(); + addr = config.deployAgentFactory(); } +} - function writeContractAddresses() internal { - // create a deployment file if not exist - string memory dir = "deployment/"; - string memory fileName = Strings.toString(chainId); - string memory path = string.concat(dir, fileName, ".json"); +contract DeployArtifactFactory is Script { + HelperConfig public config; - // create dir if it doesn't exist - vm.createDir(dir, true); + function run() external returns (address addr) { + config = new HelperConfig(); + addr = config.deployArtifactFactory(); + } +} - string memory contracts = string.concat( - "{", - ' "LLMOracleRegistry": {', - ' "proxyAddr": "', - Strings.toHexString(uint256(uint160(address(oracleRegistry))), 20), - '",', - ' "implAddr": "', - Strings.toHexString(uint256(uint160(address(registryImplementation))), 20), - '"', - " },", - ' "LLMOracleCoordinator": {', - ' "proxyAddr": "', - Strings.toHexString(uint256(uint160(address(oracleCoordinator))), 20), - '",', - ' "implAddr": "', - Strings.toHexString(uint256(uint160(address(coordinatorImplementation))), 20), - '"', - " },", - ' "Swan": {', - ' "proxyAddr": "', - Strings.toHexString(uint256(uint160(address(swan))), 20), - '",', - ' "implAddr": "', - Strings.toHexString(uint256(uint160(address(swanImplementation))), 20), - '"', - " },", - ' "BuyerAgentFactory": "', - Strings.toHexString(uint256(uint160(address(buyerAgentFactory))), 20), - '",', - ' "SwanAssetFactory": "', - Strings.toHexString(uint256(uint160(address(swanAssetFactory))), 20), - '"' "}" - ); +contract DeploySwan is Script { + HelperConfig public config; - vm.writeJson(contracts, path); + function run() external returns (address proxy, address impl) { + config = new HelperConfig(); + (proxy, impl) = config.deploySwan(); } } diff --git a/script/HelperConfig.s.sol b/script/HelperConfig.s.sol index 9dae963..478d837 100644 --- a/script/HelperConfig.s.sol +++ b/script/HelperConfig.s.sol @@ -1,10 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; +import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {Script} from "forge-std/Script.sol"; -import {WETH9} from "../test/WETH9.sol"; + import {LLMOracleTaskParameters} from "@firstbatch/dria-oracle-contracts/LLMOracleTask.sol"; import {SwanMarketParameters} from "../src/SwanManager.sol"; +import {LLMOracleCoordinator} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; +import {LLMOracleRegistry} from "@firstbatch/dria-oracle-contracts/LLMOracleRegistry.sol"; +import {AIAgentFactory} from "../src/AIAgent.sol"; +import {ArtifactFactory} from "../src/Artifact.sol"; +import {Swan} from "../src/Swan.sol"; +import {WETH9} from "../test/WETH9.sol"; struct Stakes { uint256 generatorStakeAmount; @@ -36,26 +44,238 @@ contract HelperConfig is Script { taskParams = LLMOracleTaskParameters({difficulty: 2, numGenerations: 1, numValidations: 1}); marketParams = SwanMarketParameters({ - maxAssetCount: 500, - sellInterval: 4 hours, + maxArtifactCount: 500, + listingInterval: 4 hours, buyInterval: 30 minutes, withdrawInterval: 15 minutes, platformFee: 1, // percentage - minAssetPrice: 0.00001 ether, + minArtifactPrice: 0.00001 ether, timestamp: 0, // will be set in the first call - maxBuyerAgentFee: 75 // percentage + maxAgentFee: 75 // percentage }); minRegistrationTime = 1 days; maxScore = type(uint8).max; // 255 minScore = 1; - // for base sepolia - if (block.chainid == 84532 || block.chainid == 8453) { - // use deployed weth - token = WETH9(payable(0x4200000000000000000000000000000000000006)); + token = WETH9(payable(0x4200000000000000000000000000000000000006)); + } + + function deployLLMOracleRegistry() external returns (address proxy, address impl) { + vm.startBroadcast(); + + // deploy llm contracts + address registryProxy = Upgrades.deployUUPSProxy( + "LLMOracleRegistry.sol", + abi.encodeCall( + LLMOracleRegistry.initialize, + (stakes.generatorStakeAmount, stakes.validatorStakeAmount, address(token), minRegistrationTime) + ) + ); + + address registryImplementation = Upgrades.getImplementationAddress(registryProxy); + vm.stopBroadcast(); + + writeProxyAddresses("LLMOracleRegistry", registryProxy, registryImplementation); + + return (registryProxy, registryImplementation); + } + + function deployLLMOracleCoordinator() external returns (address proxy, address impl) { + // get the registry proxy address from chainid.json file under the deployment dir + string memory dir = "deployment/"; + string memory fileName = Strings.toString(block.chainid); + string memory path = string.concat(dir, fileName, ".json"); + + string memory contractAddresses = vm.readFile(path); + bool isRegistryExist = vm.keyExistsJson(contractAddresses, "$.LLMOracleRegistry"); + require(isRegistryExist, "Please deploy LLMOracleRegistry first"); + + address registryProxy = vm.parseJsonAddress(contractAddresses, "$.LLMOracleRegistry.proxyAddr"); + require(registryProxy != address(0), "LLMOracleRegistry proxy address is invalid"); + + address registryImlp = vm.parseJsonAddress(contractAddresses, "$.LLMOracleRegistry.implAddr"); + require(registryImlp != address(0), "LLMOracleRegistry implementation address is invalid"); + + vm.startBroadcast(); + // deploy coordinator contract + address coordinatorProxy = Upgrades.deployUUPSProxy( + "LLMOracleCoordinator.sol", + abi.encodeCall( + LLMOracleCoordinator.initialize, + ( + registryProxy, + address(token), + fees.platformFee, + fees.generationFee, + fees.validationFee, + minScore, + maxScore + ) + ) + ); + + address coordinatorImplementation = Upgrades.getImplementationAddress(coordinatorProxy); + + vm.stopBroadcast(); + writeProxyAddresses("LLMOracleCoordinator", coordinatorProxy, coordinatorImplementation); + + return (coordinatorProxy, coordinatorImplementation); + } + + function deployAgentFactory() external returns (address) { + vm.startBroadcast(); + AIAgentFactory agentFactory = new AIAgentFactory(); + vm.stopBroadcast(); + + writeContractAddress("AIAgentFactory", address(agentFactory)); + + return address(agentFactory); + } + + function deployArtifactFactory() external returns (address) { + vm.startBroadcast(); + ArtifactFactory artifactFactory = new ArtifactFactory(); + vm.stopBroadcast(); + + writeContractAddress("ArtifactFactory", address(artifactFactory)); + + return address(artifactFactory); + } + + function deploySwan() external returns (address proxy, address impl) { + // read deployed contract addresses + string memory dir = "deployment/"; + string memory fileName = Strings.toString(block.chainid); + string memory path = string.concat(dir, fileName, ".json"); + + string memory contractAddresses = vm.readFile(path); + + bool isCoordinatorExist = vm.keyExistsJson(contractAddresses, "$.LLMOracleCoordinator"); + bool isAgentFactoryExist = vm.keyExistsJson(contractAddresses, "$.AIAgentFactory"); + bool isArtifactFactoryExist = vm.keyExistsJson(contractAddresses, "$.ArtifactFactory"); + + require( + isCoordinatorExist && isAgentFactoryExist && isArtifactFactoryExist, + "Please deploy LLMOracleCoordinator, AIAgentFactory and ArtifactFactory first" + ); + + address coordinatorProxy = vm.parseJsonAddress(contractAddresses, "$.LLMOracleCoordinator.proxyAddr"); + address agentFactory = vm.parseJsonAddress(contractAddresses, "$.AIAgentFactory.addr"); + address artifactFactory = vm.parseJsonAddress(contractAddresses, "$.ArtifactFactory.addr"); + + vm.startBroadcast(); + // deploy swan + address swanProxy = Upgrades.deployUUPSProxy( + "Swan.sol", + abi.encodeCall( + Swan.initialize, + ( + SwanMarketParameters( + marketParams.withdrawInterval, + marketParams.listingInterval, + marketParams.buyInterval, + marketParams.platformFee, + marketParams.maxArtifactCount, + marketParams.minArtifactPrice, + block.timestamp, + marketParams.maxAgentFee + ), + LLMOracleTaskParameters(taskParams.difficulty, taskParams.numGenerations, taskParams.numValidations), + coordinatorProxy, + address(token), + agentFactory, + artifactFactory + ) + ) + ); + + address swanImplementation = Upgrades.getImplementationAddress(swanProxy); + vm.stopBroadcast(); + writeProxyAddresses("Swan", swanProxy, swanImplementation); + + return (swanProxy, swanImplementation); + } + + function writeContractAddress(string memory name, address addr) internal { + // create a deployment file if not exist + string memory dir = "deployment/"; + string memory fileName = Strings.toString(block.chainid); + string memory path = string.concat(dir, fileName, ".json"); + + // create dir if it doesn't exist + vm.createDir(dir, true); + + // check if the key exists + string memory contractAddresses = vm.readFile(path); + + string memory addrStr = Strings.toHexString(uint256(uint160(addr)), 20); + + // create a new JSON object + string memory newContract = string.concat('"', name, '": {', ' "addr": "', addrStr, '"', "}"); + if (bytes(contractAddresses).length == 0) { + // write the new contract to the file + vm.writeJson(string.concat("{", newContract, "}"), path); + } else { + bool isExist = vm.keyExistsJson(contractAddresses, string.concat("$.", name)); + + if (isExist) { + // update values + vm.writeJson(addrStr, path, string.concat("$.", name, ".addr")); + } else { + // Remove the last character '}' from the existing JSON string + bytes memory contractBytes = bytes(contractAddresses); + contractBytes[contractBytes.length - 1] = bytes1(","); + + // Append the new contract object and close the JSON + string memory updatedContracts = string.concat(contractAddresses, newContract, "}"); + // write the updated JSON to the file + vm.writeJson(updatedContracts, path); + } + } + } + + function writeProxyAddresses(string memory name, address proxy, address impl) internal { + // create a deployment file if not exist + string memory dir = "deployment/"; + string memory fileName = Strings.toString(block.chainid); + string memory path = string.concat(dir, fileName, ".json"); + + string memory proxyAddr = Strings.toHexString(uint256(uint160(proxy)), 20); + string memory implAddr = Strings.toHexString(uint256(uint160(impl)), 20); + + // create dir if it doesn't exist + vm.createDir(dir, true); + + // create a new JSON object + string memory newContract = + string.concat('"', name, '": {', ' "proxyAddr": "', proxyAddr, '",', ' "implAddr": "', implAddr, '"', "}"); + + // read file content + string memory contractAddresses = vm.readFile(path); + + // if the file is not empty, check key exists + if (bytes(contractAddresses).length == 0) { + // write the new contract to the file + vm.writeJson(string.concat("{", newContract, "}"), path); + } else { + // check if the key exists + bool isExist = vm.keyExistsJson(contractAddresses, string.concat("$.", name)); + + if (isExist) { + // update values + vm.writeJson(proxyAddr, path, string.concat("$.", name, ".proxyAddr")); + vm.writeJson(implAddr, path, string.concat("$.", name, ".implAddr")); + } else { + // Remove the last character '}' from the existing JSON string + bytes memory contractBytes = bytes(contractAddresses); + contractBytes[contractBytes.length - 1] = bytes1(","); + + // Append the new contract object and close the JSON + string memory updatedContracts = string.concat(contractAddresses, newContract, "}"); + // write the updated JSON to the file + vm.writeJson(updatedContracts, path); + } } - // for local create a new token - token = new WETH9(); } } diff --git a/src/BuyerAgent.sol b/src/AIAgent.sol similarity index 84% rename from src/BuyerAgent.sol rename to src/AIAgent.sol index e6adb2d..ae39c84 100644 --- a/src/BuyerAgent.sol +++ b/src/AIAgent.sol @@ -3,25 +3,25 @@ pragma solidity ^0.8.20; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {LLMOracleTaskParameters} from "@firstbatch/dria-oracle-contracts/LLMOracleTask.sol"; -import {Swan, SwanBuyerPurchaseOracleProtocol, SwanBuyerStateOracleProtocol} from "./Swan.sol"; +import {Swan, SwanAIAgentPurchaseOracleProtocol, SwanAIAgentStateOracleProtocol} from "./Swan.sol"; import {SwanMarketParameters} from "./SwanManager.sol"; -/// @notice Factory contract to deploy BuyerAgent contracts. +/// @notice Factory contract to deploy AIAgent contracts. /// @dev This saves from contract space for Swan. -contract BuyerAgentFactory { +contract AIAgentFactory { function deploy( string memory _name, string memory _description, uint96 _feeRoyalty, uint256 _amountPerRound, address _owner - ) external returns (BuyerAgent) { - return new BuyerAgent(_name, _description, _feeRoyalty, _amountPerRound, msg.sender, _owner); + ) external returns (AIAgent) { + return new AIAgent(_name, _description, _feeRoyalty, _amountPerRound, msg.sender, _owner); } } -/// @notice BuyerAgent is responsible for buying the assets from Swan. -contract BuyerAgent is Ownable { +/// @notice AIAgent is responsible for buying the artifacts from Swan. +contract AIAgent is Ownable { /*////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ @@ -32,7 +32,7 @@ contract BuyerAgent is Ownable { /// @notice Given fee is invalid, e.g. not within the range. error InvalidFee(uint256 fee); - /// @notice Asset count limit exceeded for this round + /// @notice Price limit exceeded for this round error BuyLimitExceeded(uint256 have, uint256 want); /// @notice Invalid phase @@ -69,7 +69,7 @@ contract BuyerAgent is Ownable { /// @notice Phase of the purchase loop. enum Phase { - Sell, + Listing, Buy, Withdraw } @@ -83,27 +83,27 @@ contract BuyerAgent is Ownable { /// @dev When calculating the round, we will use this index to determine the start interval. uint256 public immutable marketParameterIdx; - /// @notice Buyer agent name. + /// @notice AI agent name. string public name; - /// @notice Buyer agent description, can include backstory, behavior and objective together. + /// @notice AI agent description, can include backstory, behavior and objective together. string public description; - /// @notice State of the buyer agent. + /// @notice State of the AI agent. /// @dev Only updated by the oracle via `updateState`. bytes public state; - /// @notice Royalty fees for the buyer agent. + /// @notice Royalty fees for the AI agent. uint96 public feeRoyalty; /// @notice The max amount of money the agent can spend per round. uint256 public amountPerRound; - /// @notice The assets that the buyer agent has. - mapping(uint256 round => address[] assets) public inventory; + /// @notice The artifacts that the AI agent has. + mapping(uint256 round => address[] artifacts) public inventory; /// @notice Amount of money spent on each round. mapping(uint256 round => uint256 spending) public spendings; /// @notice Oracle requests for each round about item purchases. /// @dev A taskId of 0 means no request has been made. mapping(uint256 round => uint256 taskId) public oraclePurchaseRequests; - /// @notice Oracle requests for each round about buyer state updates. + /// @notice Oracle requests for each round about agent state updates. /// @dev A taskId of 0 means no request has been made. /// @dev A non-zero taskId means a request has been made, but not necessarily processed. /// @dev To see if a task is completed, check `isOracleTaskProcessed`. @@ -130,8 +130,8 @@ contract BuyerAgent is Ownable { CONSTRUCTOR //////////////////////////////////////////////////////////////*/ - /// @notice Create the buyer agent. - /// @dev `_feeRoyalty` should be between 1 and maxBuyerAgentFee in the swan market parameters. + /// @notice Creates AI agent. + /// @dev `_feeRoyalty` should be between 1 and maxAIAgentFee in the swan market parameters. /// @dev All tokens are approved to the oracle coordinator of operator. constructor( string memory _name, @@ -143,7 +143,7 @@ contract BuyerAgent is Ownable { ) Ownable(_owner) { swan = Swan(_operator); - if (_feeRoyalty < 1 || _feeRoyalty > swan.getCurrentMarketParameters().maxBuyerAgentFee) { + if (_feeRoyalty < 1 || _feeRoyalty > swan.getCurrentMarketParameters().maxAgentFee) { revert InvalidFee(_feeRoyalty); } @@ -164,7 +164,7 @@ contract BuyerAgent is Ownable { LOGIC //////////////////////////////////////////////////////////////*/ - /// @notice The minimum amount of money that the buyer must leave within the contract. + /// @notice The minimum amount of money that the agent must leave within the contract. /// @dev minFundAmount should be `amountPerRound + oracleFee` to be able to make requests. function minFundAmount() public view returns (uint256) { return amountPerRound + swan.getOracleFee(); @@ -193,7 +193,7 @@ contract BuyerAgent is Ownable { (uint256 round,) = _checkRoundPhase(Phase.Withdraw); oracleStateRequests[round] = - swan.coordinator().request(SwanBuyerStateOracleProtocol, _input, _models, swan.getOracleParameters()); + swan.coordinator().request(SwanAIAgentStateOracleProtocol, _input, _models, swan.getOracleParameters()); emit StateRequest(oracleStateRequests[round], round); } @@ -210,12 +210,12 @@ contract BuyerAgent is Ownable { (uint256 round,) = _checkRoundPhase(Phase.Buy); oraclePurchaseRequests[round] = - swan.coordinator().request(SwanBuyerPurchaseOracleProtocol, _input, _models, swan.getOracleParameters()); + swan.coordinator().request(SwanAIAgentPurchaseOracleProtocol, _input, _models, swan.getOracleParameters()); emit PurchaseRequest(oraclePurchaseRequests[round], round); } - /// @notice Function to update the Buyer state. + /// @notice Function to update the AI agent state. /// @dev Works only in `Withdraw` phase. /// @dev Can be called multiple times within a single round, although is not expected to be done so. function updateState() external onlyAuthorized { @@ -238,7 +238,7 @@ contract BuyerAgent is Ownable { emit StateUpdate(taskId, round); } - /// @notice Function to buy the asset from the Swan with the given assed address. + /// @notice Function to buy the artifacts from the Swan. /// @dev Works only in `Buy` phase. /// @dev Can be called multiple times within a single round, although is not expected to be done so. /// @dev This is not expected to revert if the oracle works correctly. @@ -254,24 +254,24 @@ contract BuyerAgent is Ownable { // read oracle result using the latest task id for this round bytes memory output = oracleResult(taskId); - address[] memory assets = abi.decode(output, (address[])); + address[] memory artifacts = abi.decode(output, (address[])); - // we purchase each asset returned - for (uint256 i = 0; i < assets.length; i++) { - address asset = assets[i]; + // we purchase each artifact returned + for (uint256 i = 0; i < artifacts.length; i++) { + address artifact = artifacts[i]; // must not exceed the roundly buy-limit - uint256 price = swan.getListingPrice(asset); + uint256 price = swan.getListingPrice(artifact); spendings[round] += price; if (spendings[round] > amountPerRound) { revert BuyLimitExceeded(spendings[round], amountPerRound); } // add to inventory - inventory[round].push(asset); + inventory[round].push(artifact); // make the actual purchase - swan.purchase(asset); + swan.purchase(artifact); } // update taskId as completed @@ -282,8 +282,8 @@ contract BuyerAgent is Ownable { /// @notice Function to withdraw the tokens from the contract. /// @param _amount amount to withdraw. - /// @dev If the current phase is `Withdraw` buyer can withdraw any amount of tokens. - /// @dev If the current phase is not `Withdraw` buyer has to leave at least `minFundAmount` in the contract. + /// @dev If the current phase is `Withdraw` agent owner can withdraw any amount of tokens. + /// @dev If the current phase is not `Withdraw` agent owner has to leave at least `minFundAmount` in the contract. function withdraw(uint96 _amount) public onlyAuthorized { (, Phase phase,) = getRoundPhase(); @@ -297,11 +297,11 @@ contract BuyerAgent is Ownable { } } - // transfer the tokens to the owner of Buyer + // transfer the tokens to the owner of AI agent swan.token().transfer(owner(), _amount); } - /// @notice Alias to get the token balance of buyer agent. + /// @notice Alias to get the token balance of AI agent. /// @return token balance function treasury() public view returns (uint256) { return swan.token().balanceOf(address(this)); @@ -321,9 +321,9 @@ contract BuyerAgent is Ownable { /// @notice Computes cycle time by using intervals from given market parameters. /// @dev Used in 'computePhase()' function. /// @param params Market parameters of the Swan. - /// @return the total cycle time that is `sellInterval + buyInterval + withdrawInterval`. + /// @return the total cycle time that is `listingInterval + buyInterval + withdrawInterval`. function _computeCycleTime(SwanMarketParameters memory params) internal pure returns (uint256) { - return params.sellInterval + params.buyInterval + params.withdrawInterval; + return params.listingInterval + params.buyInterval + params.withdrawInterval; } /// @notice Function to compute the current round, phase and time until next phase w.r.t given market parameters. @@ -341,20 +341,20 @@ contract BuyerAgent is Ownable { // example: // |-------------> | (roundTime) - // |--Sell--|--Buy--|-Withdraw-| (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); + // |--Listing--|--Buy--|-Withdraw-| (cycleTime) + if (roundTime <= params.listingInterval) { + return (round, Phase.Listing, params.listingInterval - roundTime); + } else if (roundTime <= (params.listingInterval + params.buyInterval)) { + return (round, Phase.Buy, params.listingInterval + params.buyInterval - roundTime); } else { return (round, Phase.Withdraw, cycleTime - roundTime); } } /// @notice Function to return the current round, elapsed round and the current phase according to the current time. - /// @dev Each round is composed of three phases in order: Sell, Buy, Withdraw. - /// @dev Internally, it computes the intervals from market parameters at the creation of this agent, until now. - /// @dev If there are many parameter changes throughout the life of this agent, this may cost more GAS. + /// @dev Each round is composed of three phases in order: Listing, Buy, Withdraw. + /// @dev Internally, it computes the intervals from market parameters at the creation of this AI agent, until now. + /// @dev If there are many parameter changes throughout the life of this AI agent, this may cost more GAS. /// @return round, phase, time until next phase function getRoundPhase() public view returns (uint256, Phase, uint256) { SwanMarketParameters[] memory marketParams = swan.getMarketParameters(); diff --git a/src/SwanAsset.sol b/src/Artifact.sol similarity index 75% rename from src/SwanAsset.sol rename to src/Artifact.sol index b46bfd7..597e504 100644 --- a/src/SwanAsset.sol +++ b/src/Artifact.sol @@ -4,20 +4,20 @@ pragma solidity ^0.8.20; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -/// @notice Factory contract to deploy SwanAsset tokens. +/// @notice Factory contract to deploy Artifact tokens. /// @dev This saves from contract space for Swan. -contract SwanAssetFactory { - /// @notice Deploys a new SwanAsset token. +contract ArtifactFactory { + /// @notice Deploys a new Artifact token. function deploy(string memory _name, string memory _symbol, bytes memory _description, address _owner) external - returns (SwanAsset) + returns (Artifact) { - return new SwanAsset(_name, _symbol, _description, _owner, msg.sender); + return new Artifact(_name, _symbol, _description, _owner, msg.sender); } } -/// @notice SwanAsset is an ERC721 token with a single token supply. -contract SwanAsset is ERC721, Ownable { +/// @notice Artifact is an ERC721 token with a single token supply. +contract Artifact is ERC721, Ownable { /// @notice Creation time of the token uint256 public createdAt; /// @notice Description of the token diff --git a/src/Swan.sol b/src/Swan.sol index a5ee958..31b3f37 100644 --- a/src/Swan.sol +++ b/src/Swan.sol @@ -7,16 +7,16 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeab import { LLMOracleCoordinator, LLMOracleTaskParameters } from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; -import {BuyerAgentFactory, BuyerAgent} from "./BuyerAgent.sol"; -import {SwanAssetFactory, SwanAsset} from "./SwanAsset.sol"; +import {AIAgentFactory, AIAgent} from "./AIAgent.sol"; +import {ArtifactFactory, Artifact} from "./Artifact.sol"; import {SwanManager, SwanMarketParameters} from "./SwanManager.sol"; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; // @dev Protocol strings for Swan, checked in the Oracle. -bytes32 constant SwanBuyerPurchaseOracleProtocol = "swan-buyer-purchase/0.1.0"; -bytes32 constant SwanBuyerStateOracleProtocol = "swan-buyer-state/0.1.0"; +bytes32 constant SwanAIAgentPurchaseOracleProtocol = "swan-agent-purchase/0.1.0"; +bytes32 constant SwanAIAgentStateOracleProtocol = "swan-agent-state/0.1.0"; -/// @dev Used to calculate the fee for the buyer agent to be able to compute correct amount. +/// @dev Used to calculate the fee for the AI agent to be able to compute correct amount. uint256 constant BASIS_POINTS = 10_000; contract Swan is SwanManager, UUPSUpgradeable { @@ -25,86 +25,86 @@ contract Swan is SwanManager, UUPSUpgradeable { ERRORS //////////////////////////////////////////////////////////////*/ - /// @notice Invalid asset status. - error InvalidStatus(AssetStatus have, AssetStatus want); + /// @notice Invalid artifact status. + error InvalidStatus(ArtifactStatus have, ArtifactStatus want); /// @notice Caller is not authorized for the operation, e.g. not a contract owner or listing owner. error Unauthorized(address caller); - /// @notice The given asset is still in the given round. - /// @dev Most likely coming from `relist` function, where the asset cant be + /// @notice The given artifact is still in the given round. + /// @dev Most likely coming from `relist` function, where the artifact cant be /// relisted in the same round that it was listed in. - error RoundNotFinished(address asset, uint256 round); + error RoundNotFinished(address artifact, uint256 round); - /// @notice Asset count limit exceeded for this round - error AssetLimitExceeded(uint256 limit); + /// @notice Artifact count limit exceeded for this round + error ArtifactLimitExceeded(uint256 limit); - /// @notice Invalid price for the asset. + /// @notice Invalid price for the artifact. error InvalidPrice(uint256 price); /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ - /// @notice `asset` is created & listed for sale. - event AssetListed(address indexed owner, address indexed asset, uint256 price); + /// @notice Artifact is created & listed for sale. + event ArtifactListed(address indexed owner, address indexed artifact, uint256 price); - /// @notice Asset relisted by it's `owner`. - /// @dev This may happen if a listed asset is not sold in the current round, and is relisted in a new round. - event AssetRelisted(address indexed owner, address indexed buyer, address indexed asset, uint256 price); + /// @notice Artifact relisted by it's `owner`. + /// @dev This may happen if a listed artifact is not sold in the current round, and is relisted in a new round. + event ArtifactRelisted(address indexed owner, address indexed agent, address indexed artifact, uint256 price); - /// @notice A `buyer` purchased an Asset. - event AssetSold(address indexed owner, address indexed buyer, address indexed asset, uint256 price); + /// @notice An `agent` purchased an artifact. + event ArtifactSold(address indexed owner, address indexed agent, address indexed artifact, uint256 price); - /// @notice A new buyer agent is created. - /// @dev `owner` is the owner of the buyer agent. - /// @dev `buyer` is the address of the buyer agent. - event BuyerCreated(address indexed owner, address indexed buyer); + /// @notice A new AI agent is created. + /// @dev `owner` is the owner of the AI agent. + /// @dev `agent` is the address of the AI agent. + event AIAgentCreated(address indexed owner, address indexed agent); /*////////////////////////////////////////////////////////////// STORAGE //////////////////////////////////////////////////////////////*/ - /// @notice Status of an asset. All assets are listed as soon as they are listed. - /// @dev Unlisted: cannot be purchased in the current round. - /// @dev Listed: can be purchase in the current round. - /// @dev Sold: asset is sold. + /// @notice Status of an artifact. All artifacts are listed as soon as they are listed. + /// @dev Unlisted: Cannot be purchased in the current round. + /// @dev Listed: Can be purchase in the current round. + /// @dev Sold: Artifact is sold. /// @dev It is important that `Unlisted` is only the default and is not set explicitly. - /// This allows to understand that if an asset is `Listed` but the round has past, it was not sold. + /// This allows to understand that if an artifact is `Listed` but the round has past, it was not sold. /// The said fact is used within the `relist` logic. - enum AssetStatus { + enum ArtifactStatus { Unlisted, Listed, Sold } /// @notice Holds the listing information. - /// @dev `createdAt` is the timestamp of the Asset creation. - /// @dev `feeRoyalty` is the royalty fee of the buyerAgent. - /// @dev `price` is the price of the Asset. - /// @dev `seller` is the address of the creator of the Asset. - /// @dev `buyer` is the address of the buyerAgent. - /// @dev `round` is the round in which the Asset is created. - /// @dev `status` is the status of the Asset. - struct AssetListing { + /// @dev `createdAt` is the timestamp of the artifact creation. + /// @dev `feeRoyalty` is the royalty fee of the AIAgent. + /// @dev `price` is the price of the artifact. + /// @dev `seller` is the address of the creator of the artifact. + /// @dev `agent` is the address of the AIAgent. + /// @dev `round` is the round in which the artifact is created. + /// @dev `status` is the status of the artifact. + struct ArtifactListing { uint256 createdAt; uint96 feeRoyalty; uint256 price; - address seller; // TODO: we can use asset.owner() instead of seller - address buyer; + address seller; // TODO: we can use artifact.owner() instead of seller + address agent; uint256 round; - AssetStatus status; + ArtifactStatus status; } - /// @notice Factory contract to deploy Buyer Agents. - BuyerAgentFactory public buyerAgentFactory; - /// @notice Factory contract to deploy SwanAsset tokens. - SwanAssetFactory public swanAssetFactory; + /// @notice Factory contract to deploy AI Agents. + AIAgentFactory public agentFactory; + /// @notice Factory contract to deploy Artifact tokens. + ArtifactFactory public artifactFactory; - /// @notice To keep track of the assets for purchase. - mapping(address asset => AssetListing) public listings; - /// @notice Keeps track of assets per buyer & round. - mapping(address buyer => mapping(uint256 round => address[])) public assetsPerBuyerRound; + /// @notice To keep track of the artifacts for purchase. + mapping(address artifact => ArtifactListing) public listings; + /// @notice Keeps track of artifacts per agent & round. + mapping(address agent => mapping(uint256 round => address[])) public artifactsPerAgentRound; /*////////////////////////////////////////////////////////////// CONSTRUCTOR @@ -135,8 +135,8 @@ contract Swan is SwanManager, UUPSUpgradeable { // contracts address _coordinator, address _token, - address _buyerAgentFactory, - address _swanAssetFactory + address _agentFactory, + address _artifactFactory ) public initializer { __Ownable_init(msg.sender); @@ -149,8 +149,8 @@ contract Swan is SwanManager, UUPSUpgradeable { // contracts coordinator = LLMOracleCoordinator(_coordinator); token = ERC20(_token); - buyerAgentFactory = BuyerAgentFactory(_buyerAgentFactory); - swanAssetFactory = SwanAssetFactory(_swanAssetFactory); + agentFactory = AIAgentFactory(_agentFactory); + artifactFactory = ArtifactFactory(_artifactFactory); // swan is an operator isOperator[address(this)] = true; @@ -179,205 +179,205 @@ contract Swan is SwanManager, UUPSUpgradeable { LOGIC //////////////////////////////////////////////////////////////*/ - /// @notice Creates a new buyer agent. - /// @dev Emits a `BuyerCreated` event. - /// @return address of the new buyer agent. - function createBuyer( + /// @notice Creates a new AI agent. + /// @dev Emits a `AIAgentCreated` event. + /// @return address of the new AI agent. + function createAgent( string calldata _name, string calldata _description, uint96 _feeRoyalty, uint256 _amountPerRound - ) external returns (BuyerAgent) { - BuyerAgent agent = buyerAgentFactory.deploy(_name, _description, _feeRoyalty, _amountPerRound, msg.sender); - emit BuyerCreated(msg.sender, address(agent)); + ) external returns (AIAgent) { + AIAgent agent = agentFactory.deploy(_name, _description, _feeRoyalty, _amountPerRound, msg.sender); + emit AIAgentCreated(msg.sender, address(agent)); return agent; } - /// @notice Creates a new Asset. + /// @notice Creates a new artifact. /// @param _name name of the token. /// @param _symbol symbol of the token. /// @param _desc description of the token. /// @param _price price of the token. - /// @param _buyer address of the buyer. - function list(string calldata _name, string calldata _symbol, bytes calldata _desc, uint256 _price, address _buyer) + /// @param _agent address of the agent. + function list(string calldata _name, string calldata _symbol, bytes calldata _desc, uint256 _price, address _agent) external { - BuyerAgent buyer = BuyerAgent(_buyer); - (uint256 round, BuyerAgent.Phase phase,) = buyer.getRoundPhase(); + AIAgent agent = AIAgent(_agent); + (uint256 round, AIAgent.Phase phase,) = agent.getRoundPhase(); - // buyer must be in the sell phase - if (phase != BuyerAgent.Phase.Sell) { - revert BuyerAgent.InvalidPhase(phase, BuyerAgent.Phase.Sell); + // agent must be in the listing phase + if (phase != AIAgent.Phase.Listing) { + revert AIAgent.InvalidPhase(phase, AIAgent.Phase.Listing); } - // asset count must not exceed `maxAssetCount` - if (getCurrentMarketParameters().maxAssetCount == assetsPerBuyerRound[_buyer][round].length) { - revert AssetLimitExceeded(getCurrentMarketParameters().maxAssetCount); + // artifact count must not exceed `maxArtifactCount` + if (getCurrentMarketParameters().maxArtifactCount == artifactsPerAgentRound[_agent][round].length) { + revert ArtifactLimitExceeded(getCurrentMarketParameters().maxArtifactCount); } - // check the asset's price is within the acceptable range - if (_price < getCurrentMarketParameters().minAssetPrice || _price >= buyer.amountPerRound()) { + // check the artifact price is within the acceptable range + if (_price < getCurrentMarketParameters().minArtifactPrice || _price >= agent.amountPerRound()) { revert InvalidPrice(_price); } - // all is well, create the asset & its listing - address asset = address(swanAssetFactory.deploy(_name, _symbol, _desc, msg.sender)); - listings[asset] = AssetListing({ + // all is well, create the artifact & its listing + address artifact = address(artifactFactory.deploy(_name, _symbol, _desc, msg.sender)); + listings[artifact] = ArtifactListing({ createdAt: block.timestamp, - feeRoyalty: buyer.feeRoyalty(), + feeRoyalty: agent.feeRoyalty(), price: _price, seller: msg.sender, - status: AssetStatus.Listed, - buyer: _buyer, + status: ArtifactStatus.Listed, + agent: _agent, round: round }); - // add this to list of listings for the buyer for this round - assetsPerBuyerRound[_buyer][round].push(asset); + // add this to list of listings for the agent for this round + artifactsPerAgentRound[_agent][round].push(artifact); // transfer royalties - transferRoyalties(listings[asset]); + transferRoyalties(listings[artifact]); - emit AssetListed(msg.sender, asset, _price); + emit ArtifactListed(msg.sender, artifact, _price); } - /// @notice Relist the asset for another round and/or another buyer and/or another price. - /// @param _asset address of the asset. - /// @param _buyer new buyerAgent for the asset. + /// @notice Relist the artifact for another round and/or another agent and/or another price. + /// @param _artifact address of the artifact. + /// @param _agent new AIAgent for the artifact. /// @param _price new price of the token. - function relist(address _asset, address _buyer, uint256 _price) external { - AssetListing storage asset = listings[_asset]; + function relist(address _artifact, address _agent, uint256 _price) external { + ArtifactListing storage artifact = listings[_artifact]; - // only the seller can relist the asset - if (asset.seller != msg.sender) { + // only the seller can relist the artifact + if (artifact.seller != msg.sender) { revert Unauthorized(msg.sender); } - // asset must be listed - if (asset.status != AssetStatus.Listed) { - revert InvalidStatus(asset.status, AssetStatus.Listed); + // artifact must be listed + if (artifact.status != ArtifactStatus.Listed) { + revert InvalidStatus(artifact.status, ArtifactStatus.Listed); } // relist can only happen after the round of its listing has ended - // we check this via the old buyer, that is the existing asset.buyer + // we check this via the old agent, that is the existing artifact.agent // - // note that asset is unlisted here, but is not bought at all + // note that artifact is unlisted here, but is not bought at all // - // perhaps it suffices to check `==` here, since buyer round + // perhaps it suffices to check `==` here, since agent round // is changed incrementially - (uint256 oldRound,,) = BuyerAgent(asset.buyer).getRoundPhase(); - if (oldRound <= asset.round) { - revert RoundNotFinished(_asset, asset.round); + (uint256 oldRound,,) = AIAgent(artifact.agent).getRoundPhase(); + if (oldRound <= artifact.round) { + revert RoundNotFinished(_artifact, artifact.round); } - // check the asset's price is within the acceptable range - if (_price < getCurrentMarketParameters().minAssetPrice || _price >= BuyerAgent(_buyer).amountPerRound()) { + // check the artifact price is within the acceptable range + if (_price < getCurrentMarketParameters().minArtifactPrice || _price >= AIAgent(_agent).amountPerRound()) { revert InvalidPrice(_price); } - // now we move on to the new buyer - BuyerAgent buyer = BuyerAgent(_buyer); - (uint256 round, BuyerAgent.Phase phase,) = buyer.getRoundPhase(); + // now we move on to the new agent + AIAgent agent = AIAgent(_agent); + (uint256 round, AIAgent.Phase phase,) = agent.getRoundPhase(); - // buyer must be in sell phase - if (phase != BuyerAgent.Phase.Sell) { - revert BuyerAgent.InvalidPhase(phase, BuyerAgent.Phase.Sell); + // agent must be in listing phase + if (phase != AIAgent.Phase.Listing) { + revert AIAgent.InvalidPhase(phase, AIAgent.Phase.Listing); } - // buyer must not have more than `maxAssetCount` many assets - uint256 count = assetsPerBuyerRound[_buyer][round].length; - if (count >= getCurrentMarketParameters().maxAssetCount) { - revert AssetLimitExceeded(count); + // agent must not have more than `maxArtifactCount` many artifacts + uint256 count = artifactsPerAgentRound[_agent][round].length; + if (count >= getCurrentMarketParameters().maxArtifactCount) { + revert ArtifactLimitExceeded(count); } // create listing - listings[_asset] = AssetListing({ + listings[_artifact] = ArtifactListing({ createdAt: block.timestamp, - feeRoyalty: buyer.feeRoyalty(), + feeRoyalty: agent.feeRoyalty(), price: _price, seller: msg.sender, - status: AssetStatus.Listed, - buyer: _buyer, + status: ArtifactStatus.Listed, + agent: _agent, round: round }); - // add this to list of listings for the buyer for this round - assetsPerBuyerRound[_buyer][round].push(_asset); + // add this to list of listings for the agent for this round + artifactsPerAgentRound[_agent][round].push(_artifact); // transfer royalties - transferRoyalties(listings[_asset]); + transferRoyalties(listings[_artifact]); - emit AssetRelisted(msg.sender, _buyer, _asset, _price); + emit ArtifactRelisted(msg.sender, _agent, _artifact, _price); } /// @notice Function to transfer the royalties to the seller & Dria. - function transferRoyalties(AssetListing storage asset) internal { + function transferRoyalties(ArtifactListing storage _artifact) internal { // calculate fees - uint256 totalFee = Math.mulDiv(asset.price, (asset.feeRoyalty * 100), BASIS_POINTS); + uint256 totalFee = Math.mulDiv(_artifact.price, (_artifact.feeRoyalty * 100), BASIS_POINTS); uint256 driaFee = Math.mulDiv(totalFee, (getCurrentMarketParameters().platformFee * 100), BASIS_POINTS); - uint256 buyerFee = totalFee - driaFee; + uint256 agentFee = totalFee - driaFee; // first, Swan receives the entire fee from seller // this allows only one approval from the seller's side - token.transferFrom(asset.seller, address(this), totalFee); + token.transferFrom(_artifact.seller, address(this), totalFee); - // send the buyer's portion to them - token.transfer(asset.buyer, buyerFee); + // send the agent's portion to them + token.transfer(_artifact.agent, agentFee); // then it sends the remaining to Swan owner token.transfer(owner(), driaFee); } - /// @notice Executes the purchase of a listing for a buyer for the given asset. - /// @dev Must be called by the buyer of the given asset. - function purchase(address _asset) external { - AssetListing storage listing = listings[_asset]; + /// @notice Executes the purchase of a listing for a agent for the given artifact. + /// @dev Must be called by the agent of the given artifact. + function purchase(address _artifact) external { + ArtifactListing storage listing = listings[_artifact]; - // asset must be listed to be purchased - if (listing.status != AssetStatus.Listed) { - revert InvalidStatus(listing.status, AssetStatus.Listed); + // artifact must be listed to be purchased + if (listing.status != ArtifactStatus.Listed) { + revert InvalidStatus(listing.status, ArtifactStatus.Listed); } - // can only the buyer can purchase the asset - if (listing.buyer != msg.sender) { + // can only the agent can purchase the artifact + if (listing.agent != msg.sender) { revert Unauthorized(msg.sender); } - // update asset status to be sold - listing.status = AssetStatus.Sold; + // update artifact status to be sold + listing.status = ArtifactStatus.Sold; - // transfer asset from seller to Swan, and then from Swan to buyer + // transfer artifact from seller to Swan, and then from Swan to agent // this ensure that only approval to Swan is enough for the sellers - SwanAsset(_asset).transferFrom(listing.seller, address(this), 1); - SwanAsset(_asset).transferFrom(address(this), listing.buyer, 1); + Artifact(_artifact).transferFrom(listing.seller, address(this), 1); + Artifact(_artifact).transferFrom(address(this), listing.agent, 1); // transfer money - token.transferFrom(listing.buyer, address(this), listing.price); + token.transferFrom(listing.agent, address(this), listing.price); token.transfer(listing.seller, listing.price); - emit AssetSold(listing.seller, msg.sender, _asset, listing.price); + emit ArtifactSold(listing.seller, msg.sender, _artifact, listing.price); } - /// @notice Set the factories for Buyer Agents and Swan Assets. + /// @notice Set the factories for AI Agents and Artifacts. /// @dev Only callable by owner. - /// @param _buyerAgentFactory new BuyerAgentFactory address - /// @param _swanAssetFactory new SwanAssetFactory address - function setFactories(address _buyerAgentFactory, address _swanAssetFactory) external onlyOwner { - buyerAgentFactory = BuyerAgentFactory(_buyerAgentFactory); - swanAssetFactory = SwanAssetFactory(_swanAssetFactory); + /// @param _agentFactory new AIAgentFactory address + /// @param _artifactFactory new ArtifactFactory address + function setFactories(address _agentFactory, address _artifactFactory) external onlyOwner { + agentFactory = AIAgentFactory(_agentFactory); + artifactFactory = ArtifactFactory(_artifactFactory); } - /// @notice Returns the asset price with the given asset address. - function getListingPrice(address _asset) external view returns (uint256) { - return listings[_asset].price; + /// @notice Returns the artifact price with the given artifact address. + function getListingPrice(address _artifact) external view returns (uint256) { + return listings[_artifact].price; } - /// @notice Returns the number of assets with the given buyer and round. - function getListedAssets(address _buyer, uint256 _round) external view returns (address[] memory) { - return assetsPerBuyerRound[_buyer][_round]; + /// @notice Returns the number of artifacts with the given agent and round. + function getListedArtifacts(address _agent, uint256 _round) external view returns (address[] memory) { + return artifactsPerAgentRound[_agent][_round]; } - /// @notice Returns the asset listing with the given asset address. - function getListing(address _asset) external view returns (AssetListing memory) { - return listings[_asset]; + /// @notice Returns the artifact listing with the given artifact address. + function getListing(address _artifact) external view returns (ArtifactListing memory) { + return listings[_artifact]; } } diff --git a/src/SwanManager.sol b/src/SwanManager.sol index 2868db2..b6abf5d 100644 --- a/src/SwanManager.sol +++ b/src/SwanManager.sol @@ -13,23 +13,23 @@ import { /// @dev Prevents stack-too-deep. /// TODO: use 256-bit tight-packing here struct SwanMarketParameters { - /// @notice The interval at which the buyerAgent can withdraw the funds. + /// @notice The interval at which the AIAgent can withdraw the funds. uint256 withdrawInterval; - /// @notice The interval at which the creators can mint assets. - uint256 sellInterval; - /// @notice The interval at which the buyers can buy the assets. + /// @notice The interval at which the creators can mint artifacts. + uint256 listingInterval; + /// @notice The interval at which the agent can buy the artifacts. uint256 buyInterval; - /// @notice A fee percentage taken from each listing's buyer fee. + /// @notice A fee percentage taken from each listing's agent fee. uint256 platformFee; - /// @notice The maximum number of assets that can be listed per round. - uint256 maxAssetCount; - /// @notice Min asset price in the market. - uint256 minAssetPrice; + /// @notice The maximum number of artifacts that can be listed per round. + uint256 maxArtifactCount; + /// @notice Min artifact price in the market. + uint256 minArtifactPrice; /// @notice Timestamp of the block that this market parameter was added. /// @dev Even if this is provided by the user, it will get overwritten by the internal `block.timestamp`. uint256 timestamp; - /// @notice The maximum fee that a buyer agent can charge. - uint8 maxBuyerAgentFee; + /// @notice The maximum fee that a agent agent can charge. + uint8 maxAgentFee; } abstract contract SwanManager is OwnableUpgradeable { @@ -47,7 +47,7 @@ abstract contract SwanManager is OwnableUpgradeable { /// @notice The token to be used for fee payments. ERC20 public token; - /// @notice Operator addresses that can take actions on behalf of Buyer agents, + /// @notice Operator addresses that can take actions on behalf of AI agents, /// such as calling `purchase`, or `updateState` for them. mapping(address operator => bool) public isOperator; @@ -92,7 +92,7 @@ abstract contract SwanManager is OwnableUpgradeable { } /// @notice Returns the total fee required to make an oracle request. - /// @dev This is mainly required by the buyer to calculate its minimum fund amount, so that it can pay the fee. + /// @dev This is mainly required by the agent to calculate its minimum fund amount, so that it can pay the fee. function getOracleFee() external view returns (uint256) { (uint256 totalFee,,) = coordinator.getFee(oracleParameters); return totalFee; @@ -102,7 +102,7 @@ abstract contract SwanManager is OwnableUpgradeable { OPERATORS //////////////////////////////////////////////////////////////*/ - /// @notice Adds an operator that can take actions on behalf of Buyer agents. + /// @notice Adds an operator that can take actions on behalf of AI agents. /// @dev Only callable by owner. /// @dev Has no effect if the operator is already authorized. /// @param _operator new operator address diff --git a/test/AIAgentTest.t.sol b/test/AIAgentTest.t.sol new file mode 100644 index 0000000..fd66ff9 --- /dev/null +++ b/test/AIAgentTest.t.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {Helper} from "./Helper.t.sol"; + +import {WETH9} from "./WETH9.sol"; +import {AIAgent, AIAgentFactory} from "../src/AIAgent.sol"; +import {LLMOracleCoordinator} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; +import {ArtifactFactory} from "../src/Artifact.sol"; +import {LLMOracleTaskParameters} from "@firstbatch/dria-oracle-contracts/LLMOracleTask.sol"; +import {LLMOracleRegistry} from "@firstbatch/dria-oracle-contracts/LLMOracleRegistry.sol"; +import {Swan} from "../src/Swan.sol"; + +contract AIAgentTest is Helper { + /// @notice AI agent should be in Listing Phase + function test_InListingPhase() external createAgents { + // get curr phase + (, AIAgent.Phase _phase,) = agent.getRoundPhase(); + assertEq(uint8(_phase), uint8(currPhase)); + } + + /// @dev Agent owner cannot set feeRoyalty in Listing Phase + function test_RevertWhen_SetRoyaltyInListingPhase() external createAgents { + vm.prank(agentOwner); + vm.expectRevert( + abi.encodeWithSelector(AIAgent.InvalidPhase.selector, AIAgent.Phase.Listing, AIAgent.Phase.Withdraw) + ); + agent.setFeeRoyalty(10); + } + + /// @notice Test that the agent is in Buy Phase + function test_InBuyPhase() external createAgents { + uint256 _timeToBuyPhaseOfTheFirstRound = agent.createdAt() + swan.getCurrentMarketParameters().listingInterval; + increaseTime(_timeToBuyPhaseOfTheFirstRound, agent, AIAgent.Phase.Buy, 0); + + currPhase = AIAgent.Phase.Buy; + + (, AIAgent.Phase _phase,) = agent.getRoundPhase(); + assertEq(uint8(_phase), uint8(currPhase)); + } + + /// @dev Agent owner cannot set amountPerRound in Buy Phase + function test_RevertWhen_SetAmountPerRoundInBuyPhase() external createAgents { + increaseTime(agent.createdAt() + marketParameters.listingInterval, agent, AIAgent.Phase.Buy, 0); + + vm.prank(agentOwner); + vm.expectRevert( + abi.encodeWithSelector(AIAgent.InvalidPhase.selector, AIAgent.Phase.Buy, AIAgent.Phase.Withdraw) + ); + agent.setAmountPerRound(2 ether); + } + + /// @notice Test that the agent owner cannot withdraw in Buy Phase + function test_RevertWhen_WithdrawInBuyPhase() external createAgents { + // owner cannot withdraw more than minFundAmount from his agent + increaseTime(agent.createdAt() + marketParameters.listingInterval, agent, AIAgent.Phase.Buy, 0); + + // get the contract balance + uint256 treasuary = agent.treasury(); + + vm.prank(agentOwner); + // try to withdraw all balance + vm.expectRevert(abi.encodeWithSelector(AIAgent.MinFundSubceeded.selector, treasuary)); + agent.withdraw(uint96(treasuary)); + } + + /// @notice Test that the non-owner cannot withdraw + function test_RevertWhen_WithdrawByAnotherOwner() external createAgents { + increaseTime( + agent.createdAt() + marketParameters.listingInterval + marketParameters.buyInterval, + agent, + AIAgent.Phase.Withdraw, + 0 + ); + + currPhase = AIAgent.Phase.Withdraw; + + // not allowed to withdraw by non owner + vm.prank(agentOwners[1]); + vm.expectRevert(abi.encodeWithSelector(AIAgent.Unauthorized.selector, agentOwners[1])); + agent.withdraw(1 ether); + } + + /// @notice Test that the AI agent owner must set feeRoyalty between 1-100 + /// @dev feeRoyalty can be set ONLY in Withdraw Phase by only agent owner + function test_RevertWhen_SetFeeWithInvalidRoyalty() external createAgents { + increaseTime( + agent.createdAt() + marketParameters.listingInterval + marketParameters.buyInterval, + agent, + AIAgent.Phase.Withdraw, + 0 + ); + + uint96 _biggerRoyalty = 1000; + uint96 _smallerRoyalty = 0; + + vm.startPrank(agentOwner); + vm.expectRevert(abi.encodeWithSelector(AIAgent.InvalidFee.selector, _biggerRoyalty)); + agent.setFeeRoyalty(_biggerRoyalty); + + vm.expectRevert(abi.encodeWithSelector(AIAgent.InvalidFee.selector, _smallerRoyalty)); + agent.setFeeRoyalty(_smallerRoyalty); + vm.stopPrank(); + } + + /// @notice Test that the AI agent owner can set feeRoyalty and amountPerRound in Withdraw Phase + function test_SetRoyaltyAndAmountPerRound() external createAgents { + increaseTime( + agent.createdAt() + marketParameters.listingInterval + marketParameters.buyInterval, + agent, + AIAgent.Phase.Withdraw, + 0 + ); + + uint96 _newFeeRoyalty = 20; + uint256 _newAmountPerRound = 0.25 ether; + + vm.startPrank(agentOwner); + agent.setFeeRoyalty(_newFeeRoyalty); + agent.setAmountPerRound(_newAmountPerRound); + vm.stopPrank(); + + assertEq(agent.feeRoyalty(), _newFeeRoyalty); + assertEq(agent.amountPerRound(), _newAmountPerRound); + } + + /// @notice Test that the AI agent owner can withdraw in Withdraw Phase + function test_WithdrawInWithdrawPhase() external createAgents { + increaseTime( + agent.createdAt() + marketParameters.listingInterval + marketParameters.buyInterval, + agent, + AIAgent.Phase.Withdraw, + 0 + ); + + vm.startPrank(agentOwner); + agent.withdraw(uint96(token.balanceOf(address(agent)))); + } +} diff --git a/test/BuyerAgent.t.sol b/test/BuyerAgent.t.sol deleted file mode 100644 index 848f43e..0000000 --- a/test/BuyerAgent.t.sol +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {Helper} from "./Helper.t.sol"; - -import {WETH9} from "./WETH9.sol"; -import {BuyerAgent, BuyerAgentFactory} from "../src/BuyerAgent.sol"; -import {LLMOracleCoordinator} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; -import {SwanAssetFactory} from "../src/SwanAsset.sol"; -import {LLMOracleTaskParameters} from "@firstbatch/dria-oracle-contracts/LLMOracleTask.sol"; -import {LLMOracleRegistry} from "@firstbatch/dria-oracle-contracts/LLMOracleRegistry.sol"; -import {Swan} from "../src/Swan.sol"; - -contract BuyerAgentTest is Helper { - /// @notice Buyer agent should be in sell phase - function test_InSellPhase() external createBuyers { - // get curr phase - (, BuyerAgent.Phase _phase,) = agent.getRoundPhase(); - assertEq(uint8(_phase), uint8(currPhase)); - } - - /// @dev Agent owner cannot set feeRoyalty in sell phase - function test_RevertWhen_SetRoyaltyInSellPhase() external createBuyers { - vm.prank(agentOwner); - vm.expectRevert( - abi.encodeWithSelector(BuyerAgent.InvalidPhase.selector, BuyerAgent.Phase.Sell, BuyerAgent.Phase.Withdraw) - ); - agent.setFeeRoyalty(10); - } - - /// @notice Test that the buyer agent is in buy phase - function test_InBuyPhase() external createBuyers { - uint256 timeToBuyPhaseOfTheFirstRound = agent.createdAt() + swan.getCurrentMarketParameters().sellInterval; - increaseTime(timeToBuyPhaseOfTheFirstRound, agent, BuyerAgent.Phase.Buy, 0); - - currPhase = BuyerAgent.Phase.Buy; - - (, BuyerAgent.Phase _phase,) = agent.getRoundPhase(); - assertEq(uint8(_phase), uint8(currPhase)); - } - - /// @dev Agent owner cannot set amountPerRound in buy phase - function test_RevertWhen_SetAmountPerRoundInBuyPhase() external createBuyers { - increaseTime(agent.createdAt() + marketParameters.sellInterval, agent, BuyerAgent.Phase.Buy, 0); - - vm.prank(agentOwner); - vm.expectRevert( - abi.encodeWithSelector(BuyerAgent.InvalidPhase.selector, BuyerAgent.Phase.Buy, BuyerAgent.Phase.Withdraw) - ); - agent.setAmountPerRound(2 ether); - } - - /// @notice Test that the buyer agent owner cannot withdraw in buy phase - function test_RevertWhen_WithdrawInBuyPhase() external createBuyers { - // owner cannot withdraw more than minFundAmount from his agent - increaseTime(agent.createdAt() + marketParameters.sellInterval, agent, BuyerAgent.Phase.Buy, 0); - - // get the contract balance - uint256 treasuary = agent.treasury(); - - vm.prank(agentOwner); - // try to withdraw all balance - vm.expectRevert(abi.encodeWithSelector(BuyerAgent.MinFundSubceeded.selector, treasuary)); - agent.withdraw(uint96(treasuary)); - } - - /// @notice Test that the non-owner cannot withdraw - function test_RevertWhen_WithdrawByAnotherOwner() external createBuyers { - // feeRoyalty can be set only in withdraw phase by only agent owner - increaseTime( - agent.createdAt() + marketParameters.sellInterval + marketParameters.buyInterval, - agent, - BuyerAgent.Phase.Withdraw, - 0 - ); - currPhase = BuyerAgent.Phase.Withdraw; - - // not allowed to withdraw by non owner - vm.prank(buyerAgentOwners[1]); - vm.expectRevert(abi.encodeWithSelector(BuyerAgent.Unauthorized.selector, buyerAgentOwners[1])); - agent.withdraw(1 ether); - } - - /// @notice Test that the buyer agent owner must set feeRoyalty between 1-100 - function test_RevertWhen_SetFeeWithInvalidRoyalty() external createBuyers { - increaseTime( - agent.createdAt() + marketParameters.sellInterval + marketParameters.buyInterval, - agent, - BuyerAgent.Phase.Withdraw, - 0 - ); - - uint96 biggerRoyalty = 1000; - uint96 smallerRoyalty = 0; - - vm.startPrank(agentOwner); - vm.expectRevert(abi.encodeWithSelector(BuyerAgent.InvalidFee.selector, biggerRoyalty)); - agent.setFeeRoyalty(biggerRoyalty); - - vm.expectRevert(abi.encodeWithSelector(BuyerAgent.InvalidFee.selector, smallerRoyalty)); - agent.setFeeRoyalty(smallerRoyalty); - vm.stopPrank(); - } - - /// @notice Test that the buyer agent owner can set feeRoyalty and amountPerRound in withdraw phase - function test_SetRoyaltyAndAmountPerRound() external createBuyers { - increaseTime( - agent.createdAt() + marketParameters.sellInterval + marketParameters.buyInterval, - agent, - BuyerAgent.Phase.Withdraw, - 0 - ); - - uint96 newFeeRoyalty = 20; - uint256 newAmountPerRound = 0.25 ether; - - vm.startPrank(agentOwner); - agent.setFeeRoyalty(newFeeRoyalty); - agent.setAmountPerRound(newAmountPerRound); - - assertEq(agent.feeRoyalty(), newFeeRoyalty); - assertEq(agent.amountPerRound(), newAmountPerRound); - - vm.stopPrank(); - } - - /// @notice Test that the buyer agent owner can withdraw in withdraw phase - function test_WithdrawInWithdrawPhase() external createBuyers { - increaseTime( - agent.createdAt() + marketParameters.sellInterval + marketParameters.buyInterval, - agent, - BuyerAgent.Phase.Withdraw, - 0 - ); - - vm.startPrank(agentOwner); - agent.withdraw(uint96(token.balanceOf(address(agent)))); - } -} diff --git a/test/Helper.t.sol b/test/Helper.t.sol index 20d708a..7f853ad 100644 --- a/test/Helper.t.sol +++ b/test/Helper.t.sol @@ -10,16 +10,15 @@ import {LLMOracleRegistry, LLMOracleKind} from "@firstbatch/dria-oracle-contract import {LLMOracleCoordinator} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; import {SwanMarketParameters} from "../src/SwanManager.sol"; import {LLMOracleTaskParameters} from "@firstbatch/dria-oracle-contracts/LLMOracleTask.sol"; -import {BuyerAgent, BuyerAgentFactory} from "../src/BuyerAgent.sol"; -import {SwanAssetFactory} from "../src/SwanAsset.sol"; -import {BuyerAgent} from "../src/BuyerAgent.sol"; +import {AIAgent, AIAgentFactory} from "../src/AIAgent.sol"; +import {ArtifactFactory} from "../src/Artifact.sol"; import {Swan} from "../src/Swan.sol"; import {Stakes, Fees} from "../script/HelperConfig.s.sol"; // CREATED TO PREVENT CODE DUPLICATION IN TESTS abstract contract Helper is Test { - /// @dev Parameters for the buyer agent deployment - struct BuyerAgentParameters { + /// @dev Parameters for the agent deployment + struct AgentParameters { string name; string description; uint96 feeRoyalty; @@ -32,26 +31,27 @@ abstract contract Helper is Test { Fees fees; address dria; - address[] buyerAgentOwners; + address[] agentOwners; address[] sellers; address[] generators; address[] validators; uint256 currRound; - BuyerAgent.Phase currPhase; + AIAgent.Phase currPhase; - BuyerAgentParameters[] buyerAgentParameters; + AgentParameters[] agentParameters; LLMOracleTaskParameters oracleParameters; SwanMarketParameters marketParameters; LLMOracleCoordinator oracleCoordinator; LLMOracleRegistry oracleRegistry; - BuyerAgentFactory buyerAgentFactory; - SwanAssetFactory swanAssetFactory; - BuyerAgent[] buyerAgents; - BuyerAgent agent; + AIAgentFactory agentFactory; + ArtifactFactory artifactFactory; + AIAgent[] agents; + + AIAgent agent; address agentOwner; WETH9 token; @@ -61,7 +61,7 @@ abstract contract Helper is Test { bytes models = "0x"; bytes metadata = "0x"; - uint256 assetPrice = 0.01 ether; + uint256 artifactPrice = 0.01 ether; uint256 amountPerRound = 0.015 ether; uint8 feeRoyalty = 2; @@ -80,35 +80,35 @@ abstract contract Helper is Test { dria = vm.addr(1); validators = [vm.addr(2), vm.addr(3), vm.addr(4)]; generators = [vm.addr(5), vm.addr(6), vm.addr(7)]; - buyerAgentOwners = [vm.addr(8), vm.addr(9)]; + agentOwners = [vm.addr(8), vm.addr(9)]; sellers = [vm.addr(10), vm.addr(11)]; oracleParameters = LLMOracleTaskParameters({difficulty: 1, numGenerations: 2, numValidations: 1}); marketParameters = SwanMarketParameters({ withdrawInterval: 300, // 5 minutes - sellInterval: 360, + listingInterval: 360, buyInterval: 600, platformFee: 2, // percentage - maxAssetCount: 3, + maxArtifactCount: 3, timestamp: block.timestamp, - minAssetPrice: 0.00001 ether, - maxBuyerAgentFee: 75 // percentage + minArtifactPrice: 0.00001 ether, + maxAgentFee: 75 // percentage }); stakes = Stakes({generatorStakeAmount: 0.01 ether, validatorStakeAmount: 0.01 ether}); fees = Fees({platformFee: 1, generationFee: 0.0002 ether, validationFee: 0.00003 ether}); - for (uint96 i = 0; i < buyerAgentOwners.length; i++) { - buyerAgentParameters.push( - BuyerAgentParameters({ - name: string.concat("BuyerAgent", vm.toString(uint256(i))), - description: "description of the buyer agent", + for (uint96 i = 0; i < agentOwners.length; i++) { + agentParameters.push( + AgentParameters({ + name: string.concat("AIAgent", vm.toString(uint256(i))), + description: "description of the AI agent", feeRoyalty: feeRoyalty, amountPerRound: amountPerRound }) ); - vm.label(buyerAgentOwners[i], string.concat("BuyerAgentOwner#", vm.toString(i + 1))); + vm.label(agentOwners[i], string.concat("AgentOwner#", vm.toString(i + 1))); } vm.label(dria, "Dria"); vm.label(address(this), "Helper"); @@ -120,8 +120,14 @@ abstract contract Helper is Test { modifier deployment() { _; - + // deploy WETH9 token = new WETH9(); + bytes memory wethCode = address(token).code; + address targetAddr = 0x4200000000000000000000000000000000000006; + // sets the bytecode of the target address to the WETH9 contract + vm.etch(targetAddr, wethCode); + token = WETH9(payable(targetAddr)); + assertEq(address(token), targetAddr); // deploy llm contracts vm.startPrank(dria); @@ -153,8 +159,8 @@ abstract contract Helper is Test { oracleCoordinator = LLMOracleCoordinator(coordinatorProxy); // deploy factory contracts - buyerAgentFactory = new BuyerAgentFactory(); - swanAssetFactory = new SwanAssetFactory(); + agentFactory = new AIAgentFactory(); + artifactFactory = new ArtifactFactory(); // deploy swan address swanProxy = Upgrades.deployUUPSProxy( @@ -166,8 +172,8 @@ abstract contract Helper is Test { oracleParameters, address(oracleCoordinator), address(token), - address(buyerAgentFactory), - address(swanAssetFactory) + address(agentFactory), + address(artifactFactory) ) ) ); @@ -178,8 +184,8 @@ abstract contract Helper is Test { vm.label(address(token), "WETH"); vm.label(address(oracleRegistry), "LLMOracleRegistry"); vm.label(address(oracleCoordinator), "LLMOracleCoordinator"); - vm.label(address(buyerAgentFactory), "BuyerAgentFactory"); - vm.label(address(swanAssetFactory), "SwanAssetFactory"); + vm.label(address(agentFactory), "AIAgentFactory"); + vm.label(address(artifactFactory), "ArtifactFactory"); } /// @notice Add validators to the whitelist. @@ -188,7 +194,7 @@ abstract contract Helper is Test { oracleRegistry.addToWhitelist(validators); for (uint256 i; i < validators.length; i++) { - vm.assertTrue(oracleRegistry.whitelisted(validators[i])); + vm.assertTrue(oracleRegistry.isWhitelisted(validators[i])); } _; } @@ -223,34 +229,34 @@ abstract contract Helper is Test { _; } - /// @notice Create buyers by using buyerAgentOwners and buyerAgentParameters - modifier createBuyers() virtual { - for (uint256 i = 0; i < buyerAgentOwners.length; i++) { - // fund buyer agent owner - deal(address(token), buyerAgentOwners[i], 3 ether); + /// @notice Create agents by using agentOwners and agentParameters + modifier createAgents() virtual { + for (uint256 i = 0; i < agentOwners.length; i++) { + // fund agent owner + deal(address(token), agentOwners[i], 3 ether); // start recording event info vm.recordLogs(); - vm.startPrank(buyerAgentOwners[i]); - BuyerAgent buyerAgent = swan.createBuyer( - buyerAgentParameters[i].name, - buyerAgentParameters[i].description, - buyerAgentParameters[i].feeRoyalty, - buyerAgentParameters[i].amountPerRound + vm.startPrank(agentOwners[i]); + AIAgent AIagent = swan.createAgent( + agentParameters[i].name, + agentParameters[i].description, + agentParameters[i].feeRoyalty, + agentParameters[i].amountPerRound ); // get recorded logs Vm.Log[] memory entries = vm.getRecordedLogs(); // 1. OwnershipTransferred (from Ownable) - // 2. Approval (from BuyerAgent constructor to approve coordinator) - // 3. Approval (from BuyerAgent constructor to approve swan) - // 4. BuyerCreated (from Swan) + // 2. Approval (from AIAgent constructor to approve coordinator) + // 3. Approval (from AIAgent constructor to approve swan) + // 4. AIAgentCreated (from Swan) assertEq(entries.length, 4); - // get the BuyerCreated event - Vm.Log memory buyerCreatedEvent = entries[entries.length - 1]; + // get the AIAgentCreated event + Vm.Log memory agentCreatedEvent = entries[entries.length - 1]; // Log is a struct that holds the event info: // struct Log { @@ -267,33 +273,33 @@ abstract contract Helper is Test { // emitter is the address of the contract that emitted the event // get event sig - bytes32 eventSig = buyerCreatedEvent.topics[0]; - assertEq(keccak256("BuyerCreated(address,address)"), eventSig); + bytes32 eventSig = agentCreatedEvent.topics[0]; + assertEq(keccak256("AIAgentCreated(address,address)"), eventSig); // decode owner & agent address from topics - address _owner = abi.decode(abi.encode(buyerCreatedEvent.topics[1]), (address)); - address _agent = abi.decode(abi.encode(buyerCreatedEvent.topics[2]), (address)); + address _owner = abi.decode(abi.encode(agentCreatedEvent.topics[1]), (address)); + address _agent = abi.decode(abi.encode(agentCreatedEvent.topics[2]), (address)); - assertEq(_owner, buyerAgentOwners[i]); + assertEq(_owner, agentOwners[i]); // emitter should be swan - assertEq(buyerCreatedEvent.emitter, address(swan)); + assertEq(agentCreatedEvent.emitter, address(swan)); // all guuud - buyerAgents.push(BuyerAgent(_agent)); + agents.push(AIAgent(_agent)); - vm.label(address(buyerAgents[i]), string.concat("BuyerAgent#", vm.toString(i + 1))); + vm.label(address(agents[i]), string.concat("AIAgent#", vm.toString(i + 1))); // transfer token to agent - token.transfer(address(buyerAgent), amountPerRound); - assertEq(token.balanceOf(address(buyerAgent)), amountPerRound); + token.transfer(address(AIagent), amountPerRound); + assertEq(token.balanceOf(address(AIagent)), amountPerRound); vm.stopPrank(); } - assertEq(buyerAgents.length, buyerAgentOwners.length); - currPhase = BuyerAgent.Phase.Sell; + assertEq(agents.length, agentOwners.length); + currPhase = AIAgent.Phase.Listing; - agent = buyerAgents[0]; - agentOwner = buyerAgentOwners[0]; + agent = agents[0]; + agentOwner = agentOwners[0]; _; } @@ -308,73 +314,72 @@ abstract contract Helper is Test { _; } - /// @notice Listing assets with the given params. - /// @param seller Seller of the asset. - /// @param assetCount Number of assets that will be listed. - /// @param buyerAgent Agent that assets will be list for. - modifier listAssets(address seller, uint256 assetCount, address buyerAgent) { - uint256 invalidPrice = BuyerAgent(buyerAgent).amountPerRound(); + /// @notice Listing artifacts with the given params. + /// @param seller Seller of the artifact. + /// @param artifactCount Number of artifacts that will be listed. + /// @param _agent Agent that artifacts will be list for. + modifier listArtifacts(address seller, uint256 artifactCount, address _agent) { + uint256 invalidPrice = AIAgent(_agent).amountPerRound(); vm.expectRevert(abi.encodeWithSelector(Swan.InvalidPrice.selector, invalidPrice)); vm.prank(seller); - swan.list("SwanAsset", "SA", "description or the swan asset", invalidPrice, buyerAgent); + swan.list("Artifact", "SA", "description or the swan artifact", invalidPrice, _agent); vm.recordLogs(); - vm.startPrank(seller); - for (uint256 i = 0; i < assetCount; i++) { + for (uint256 i = 0; i < artifactCount; i++) { + vm.prank(seller); swan.list( - string.concat("SwanAsset#", vm.toString(i)), + string.concat("Artifact#", vm.toString(i)), string.concat("SA#", vm.toString(i)), - "description or the swan asset", - assetPrice, - buyerAgent + "description or the swan artifact", + artifactPrice, + _agent ); - // From SwanAsset' constructor + // From Artifact' constructor // 1. OwnershipTransferred (from Ownable) // 2. Transfer (_safeMint() related) // 3. ApprovalForAll // From transferRoyalties() // 4. Transfer (WETH9: royalty transfer to Swan) - // 5. Transfer (WETH9: royalty transfer to buyer agent) + // 5. Transfer (WETH9: royalty transfer to AI Agent) // 6. Transfer (WETH9: royalty transfer to dria) // From Swan - // 7. AssetListed + // 7. ArtifactListed Vm.Log[] memory entries = vm.getRecordedLogs(); assertEq(entries.length, 7); - // get the AssetListed event - Vm.Log memory assetListedEvent = entries[entries.length - 1]; + // get the ArtifactListed event + Vm.Log memory artifactListedEvent = entries[entries.length - 1]; // check event sig - bytes32 eventSig = assetListedEvent.topics[0]; - assertEq(keccak256("AssetListed(address,address,uint256)"), eventSig); + bytes32 eventSig = artifactListedEvent.topics[0]; + assertEq(keccak256("ArtifactListed(address,address,uint256)"), eventSig); // decode params from event - address _seller = abi.decode(abi.encode(assetListedEvent.topics[1]), (address)); - address asset = abi.decode(abi.encode(assetListedEvent.topics[2]), (address)); - uint256 price = abi.decode(assetListedEvent.data, (uint256)); + address _seller = abi.decode(abi.encode(artifactListedEvent.topics[1]), (address)); + address artifact = abi.decode(abi.encode(artifactListedEvent.topics[2]), (address)); + uint256 price = abi.decode(artifactListedEvent.data, (uint256)); - // get asset details - Swan.AssetListing memory assetListing = swan.getListing(asset); + // get artifact details + Swan.ArtifactListing memory artifactListing = swan.getListing(artifact); - assertEq(assetListing.seller, _seller); + assertEq(artifactListing.seller, _seller); assertEq(seller, _seller); - assertEq(assetListing.buyer, address(buyerAgent)); + assertEq(artifactListing.agent, address(agent)); - assertEq(uint8(assetListing.status), uint8(Swan.AssetStatus.Listed)); - assertEq(assetListing.price, price); + assertEq(uint8(artifactListing.status), uint8(Swan.ArtifactStatus.Listed)); + assertEq(artifactListing.price, price); // emitter should be swan - assertEq(assetListedEvent.emitter, address(swan)); + assertEq(artifactListedEvent.emitter, address(swan)); } - vm.stopPrank(); - // check if assets listed - address[] memory listedAssets = swan.getListedAssets(buyerAgent, currRound); - assertEq(listedAssets.length, assetCount); + // check if artifacts listed + address[] memory listedArtifacts = swan.getListedArtifacts(_agent, currRound); + assertEq(listedArtifacts.length, artifactCount); _; } @@ -441,16 +446,13 @@ abstract contract Helper is Test { //////////////////////////////////////////////////////////////*/ /// @notice Increases time in the test - function increaseTime( - uint256 timeInseconds, - BuyerAgent buyerAgent, - BuyerAgent.Phase expectedPhase, - uint256 expectedRound - ) public { + function increaseTime(uint256 timeInseconds, AIAgent _agent, AIAgent.Phase expectedPhase, uint256 expectedRound) + public + { vm.warp(timeInseconds + 1); - // get the current round and phase of buyer agent - (uint256 _currRound, BuyerAgent.Phase _currPhase,) = buyerAgent.getRoundPhase(); + // get the current round and phase of agent + (uint256 _currRound, AIAgent.Phase _currPhase,) = _agent.getRoundPhase(); assertEq(uint8(_currPhase), uint8(expectedPhase)); assertEq(uint8(_currRound), uint8(expectedRound)); } @@ -475,16 +477,16 @@ abstract contract Helper is Test { } /// @notice Makes a purchase request to Oracle Coordinator - function safePurchase(address buyer, BuyerAgent buyerAgent, uint256 taskId) public { - address[] memory listedAssets = swan.getListedAssets(address(buyerAgent), currRound); + function safePurchase(address _agentOwner, AIAgent _agent, uint256 taskId) public { + address[] memory listedArtifacts = swan.getListedArtifacts(address(_agent), currRound); - // get the listed assets as output + // get the listed artifacts as output address[] memory output = new address[](1); - output[0] = listedAssets[0]; + output[0] = listedArtifacts[0]; assertEq(output.length, 1); - vm.prank(buyer); - buyerAgent.oraclePurchaseRequest(input, models); + vm.prank(_agentOwner); + _agent.oraclePurchaseRequest(input, models); bytes memory encodedOutput = abi.encode((address[])(output)); @@ -495,12 +497,12 @@ abstract contract Helper is Test { // validate safeValidate(validators[0], taskId); - assertGe(token.balanceOf(address(buyerAgent)), assetPrice); + assertGe(token.balanceOf(address(_agent)), artifactPrice); // purchase and check event logs vm.recordLogs(); - vm.prank(buyer); - buyerAgent.purchase(); + vm.prank(_agentOwner); + _agent.purchase(); } /// @dev Sets market parameters @@ -510,19 +512,15 @@ abstract contract Helper is Test { // get new params SwanMarketParameters memory _newMarketParameters = swan.getCurrentMarketParameters(); - assertEq(_newMarketParameters.sellInterval, newMarketParameters.sellInterval); + assertEq(_newMarketParameters.listingInterval, newMarketParameters.listingInterval); assertEq(_newMarketParameters.buyInterval, newMarketParameters.buyInterval); assertEq(_newMarketParameters.withdrawInterval, newMarketParameters.withdrawInterval); } /// @dev Checks if the round, phase and timeRemaining is correct - function checkRoundAndPhase(BuyerAgent buyerAgent, BuyerAgent.Phase phase, uint256 round) - public - view - returns (uint256) - { - // get the current round and phase of buyer agent - (uint256 _currRound, BuyerAgent.Phase _currPhase,) = buyerAgent.getRoundPhase(); + function checkRoundAndPhase(AIAgent _agent, AIAgent.Phase phase, uint256 round) public view returns (uint256) { + // get the current round and phase of the agent + (uint256 _currRound, AIAgent.Phase _currPhase,) = _agent.getRoundPhase(); assertEq(uint8(_currPhase), uint8(phase)); assertEq(uint8(_currRound), uint8(round)); diff --git a/test/Invariant.t.sol b/test/Invariant.t.sol deleted file mode 100644 index fa3805c..0000000 --- a/test/Invariant.t.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {Helper} from "./Helper.t.sol"; -import {SwanMarketParameters} from "../src/Swan.sol"; - -/// @notice Invariant tests call random functions from contracts and check the conditions inside the test -contract InvariantTest is Helper { - // Owner is always an operator - function invariant_OwnerIsAnOperator() public view { - assertTrue(swan.isOperator(swan.owner())); - } - - /// @dev Total number of assets listed does not exceed maxAssetCount - function invariant_MaxAssetCount() public view { - SwanMarketParameters memory params = swan.getCurrentMarketParameters(); - - for (uint256 i = 0; i < buyerAgents.length; i++) { - for (uint256 round = 0; round < 5; round++) { - // asssuming a maximum of 5 rounds - assertTrue(swan.getListedAssets(address(buyerAgents[i]), round).length <= params.maxAssetCount); - } - } - } - - /// @dev Price of listed assets is within the acceptable range - function invariant_AssetPriceRange() public view { - SwanMarketParameters memory params = swan.getCurrentMarketParameters(); - - for (uint256 i = 0; i < buyerAgents.length; i++) { - for (uint256 round; round < 5; round++) { - // assuming a maximum of 5 rounds - address[] memory assets = swan.getListedAssets(address(buyerAgents[i]), round); - for (uint256 j; j < assets.length; j++) { - uint256 price = swan.getListingPrice(assets[j]); - assertTrue(price >= params.minAssetPrice && price <= buyerAgents[i].amountPerRound()); - } - } - } - } - - /// @dev Fee royalty of each buyer agent is within an acceptable range - function invariant_BuyerAgentFeeRoyalty() public view { - for (uint256 i = 0; i < buyerAgents.length; i++) { - uint96 feeRoyalty = buyerAgents[i].feeRoyalty(); - assertTrue(feeRoyalty >= 0 && feeRoyalty <= 10000); // Assuming fee royalty is in basis points (0% to 100%) - } - } -} diff --git a/test/InvariantTest.t.sol b/test/InvariantTest.t.sol new file mode 100644 index 0000000..e90d062 --- /dev/null +++ b/test/InvariantTest.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {Helper} from "./Helper.t.sol"; +import {SwanMarketParameters} from "../src/Swan.sol"; + +/// @notice Invariant tests call random functions from contracts and check the conditions inside the test +contract InvariantTest is Helper { + /// @dev Owner is always an operator + function invariant_OwnerIsAnOperator() public view { + assertTrue(swan.isOperator(swan.owner())); + } + + /// @dev Total number of artifacts listed does not exceed maxArtifactCount + function invariant_MaxArtifactCount() public view { + SwanMarketParameters memory params = swan.getCurrentMarketParameters(); + + for (uint256 i = 0; i < agents.length; i++) { + for (uint256 round = 0; round < 5; round++) { + // asssuming a maximum of 5 rounds + assertTrue(swan.getListedArtifacts(address(agents[i]), round).length <= params.maxArtifactCount); + } + } + } + + /// @dev Price of listed artifacts is within the acceptable range + function invariant_ArtifactPriceRange() public view { + SwanMarketParameters memory _params = swan.getCurrentMarketParameters(); + + for (uint256 i = 0; i < agents.length; i++) { + for (uint256 round; round < 5; round++) { + // assuming a maximum of 5 rounds + address[] memory artifacts = swan.getListedArtifacts(address(agents[i]), round); + for (uint256 j; j < artifacts.length; j++) { + uint256 price = swan.getListingPrice(artifacts[j]); + assertTrue(price >= _params.minArtifactPrice && price <= agents[i].amountPerRound()); + } + } + } + } + + /// @dev Fee royalty of each agent is within an acceptable range + function invariant_AgentFeeRoyalty() public view { + for (uint256 i = 0; i < agents.length; i++) { + uint96 _feeRoyalty = agents[i].feeRoyalty(); + assertTrue(_feeRoyalty >= 0 && _feeRoyalty <= 10000); // Assuming fee royalty is in basis points (0% to 100%) + } + } +} diff --git a/test/Swan.t.sol b/test/Swan.t.sol deleted file mode 100644 index ee02939..0000000 --- a/test/Swan.t.sol +++ /dev/null @@ -1,562 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {Helper} from "./Helper.t.sol"; - -import {BuyerAgent, BuyerAgentFactory} from "../src/BuyerAgent.sol"; -import {SwanAssetFactory, SwanAsset} from "../src/SwanAsset.sol"; -import {Swan, SwanMarketParameters} from "../src/Swan.sol"; -import {WETH9} from "./WETH9.sol"; -import {LLMOracleRegistry} from "@firstbatch/dria-oracle-contracts/LLMOracleRegistry.sol"; -import { - LLMOracleCoordinator, LLMOracleTaskParameters -} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {console} from "forge-std/Test.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; - -contract SwanTest is Helper { - /// @dev Fund geerators, validators, sellers, and dria - modifier fund() { - scores = [10, 15]; - - // fund dria - deal(address(token), dria, 1 ether); - - // fund generators - for (uint256 i; i < generators.length; i++) { - deal(address(token), generators[i], stakes.generatorStakeAmount); - assertEq(token.balanceOf(generators[i]), stakes.generatorStakeAmount); - } - // fund validators - for (uint256 i; i < validators.length; i++) { - deal(address(token), validators[i], stakes.validatorStakeAmount); - assertEq(token.balanceOf(validators[i]), stakes.validatorStakeAmount); - } - // fund sellers - for (uint256 i; i < sellers.length; i++) { - deal(address(token), sellers[i], 5 ether); - assertEq(token.balanceOf(sellers[i]), 5 ether); - vm.label(address(sellers[i]), string.concat("Seller#", vm.toString(i + 1))); - } - _; - } - - function test_TransferOwnership() external { - address newOwner = address(0); - - vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableInvalidOwner.selector, newOwner)); - vm.startPrank(dria); - swan.transferOwnership(newOwner); - - newOwner = address(0x123); - swan.transferOwnership(newOwner); - assertEq(swan.owner(), newOwner); - } - - function test_CreateBuyerAgents() external createBuyers fund { - assertEq(buyerAgents.length, buyerAgentOwners.length); - - for (uint256 i = 0; i < buyerAgents.length; i++) { - assertEq(buyerAgents[i].feeRoyalty(), buyerAgentParameters[i].feeRoyalty); - assertEq(buyerAgents[i].owner(), buyerAgentOwners[i]); - assertEq(buyerAgents[i].amountPerRound(), buyerAgentParameters[i].amountPerRound); - assertEq(buyerAgents[i].name(), buyerAgentParameters[i].name); - assertEq(token.balanceOf(address(buyerAgents[i])), buyerAgentParameters[i].amountPerRound); - } - } - - /// @notice Sellers cannot list more than maxAssetCount - function test_RevertWhen_ListMoreThanMaxAssetCount() - external - fund - createBuyers - sellersApproveToSwan - addValidatorsToWhitelist - registerOracles - listAssets(sellers[0], marketParameters.maxAssetCount, address(buyerAgents[0])) - { - // try to list more than max assets - vm.prank(sellers[0]); - vm.expectRevert(abi.encodeWithSelector(Swan.AssetLimitExceeded.selector, marketParameters.maxAssetCount)); - swan.list("name", "symbol", "desc", 0.001 ether, address(buyerAgents[0])); - } - - /// @notice Buyer cannot call purchase() in sell phase - function test_RevertWhen_PurchaseInSellPhase() - external - fund - createBuyers - sellersApproveToSwan - addValidatorsToWhitelist - registerOracles - listAssets(sellers[0], marketParameters.maxAssetCount, address(buyerAgents[0])) - { - // try to purchase - vm.prank(buyerAgentOwners[0]); - vm.expectRevert( - abi.encodeWithSelector(BuyerAgent.InvalidPhase.selector, BuyerAgent.Phase.Sell, BuyerAgent.Phase.Buy) - ); - buyerAgents[0].purchase(); - } - - /// @notice Seller cannot relist the asset in the same round (for same or different buyers) - function test_RevertWhen_RelistInTheSameRound() - external - fund - createBuyers - sellersApproveToSwan - addValidatorsToWhitelist - registerOracles - listAssets(sellers[0], marketParameters.maxAssetCount, address(buyerAgents[0])) - { - // get the listed asset - address assetToFail = swan.getListedAssets(address(buyerAgents[0]), currRound)[0]; - - vm.prank(sellers[0]); - vm.expectRevert(abi.encodeWithSelector(Swan.RoundNotFinished.selector, assetToFail, currRound)); - swan.relist(assetToFail, address(buyerAgents[1]), 0.001 ether); - } - - /// @notice Buyer cannot purchase an asset that is not listed for him - function test_RevertWhen_PurchaseByAnotherBuyer() - external - fund - createBuyers - sellersApproveToSwan - addValidatorsToWhitelist - registerOracles - listAssets(sellers[0], marketParameters.maxAssetCount, address(buyerAgents[0])) - { - address buyerToFail = buyerAgentOwners[0]; - BuyerAgent buyerAgent = buyerAgents[1]; - - increaseTime(buyerAgent.createdAt() + marketParameters.sellInterval, buyerAgent, BuyerAgent.Phase.Buy, 0); - currPhase = BuyerAgent.Phase.Buy; - - vm.expectRevert(abi.encodeWithSelector(Swan.Unauthorized.selector, buyerToFail)); - vm.prank(buyerToFail); - buyerAgent.purchase(); - } - - /// @notice Buyer cannot spend more than amountPerRound per round - function test_RevertWhen_PurchaseMoreThanAmountPerRound() - external - fund - createBuyers - sellersApproveToSwan - addValidatorsToWhitelist - registerOracles - listAssets(sellers[0], marketParameters.maxAssetCount, address(buyerAgents[0])) - { - address buyerToFail = buyerAgentOwners[0]; - BuyerAgent buyerAgentToFail = buyerAgents[0]; - - increaseTime( - buyerAgentToFail.createdAt() + marketParameters.sellInterval, buyerAgentToFail, BuyerAgent.Phase.Buy, 0 - ); - - // get the listed assets as output - address[] memory output = swan.getListedAssets(address(buyerAgentToFail), currRound); - bytes memory encodedOutput = abi.encode(output); - - vm.prank(buyerToFail); - // make a purchase request - buyerAgentToFail.oraclePurchaseRequest(input, models); - - // respond - safeRespond(generators[0], encodedOutput, 1); - safeRespond(generators[1], encodedOutput, 1); - - // validate - safeValidate(validators[0], 1); - - vm.prank(buyerToFail); - vm.expectRevert(abi.encodeWithSelector(BuyerAgent.BuyLimitExceeded.selector, assetPrice * 2, amountPerRound)); - buyerAgentToFail.purchase(); - } - - /// @notice Buyer can purchase - /// @dev Seller has to approve Swan - /// @dev Buyer Agent must be in buy phase - /// @dev Buyer Agent must have enough balance to purchase - /// @dev asset price must be less than amountPerRound - function test_PurchaseAnAsset() - external - fund - createBuyers - sellersApproveToSwan - addValidatorsToWhitelist - registerOracles - listAssets(sellers[0], marketParameters.maxAssetCount, address(buyerAgents[0])) - { - // increase time to buy phase to be able to purchase - increaseTime( - buyerAgents[0].createdAt() + marketParameters.sellInterval, buyerAgents[0], BuyerAgent.Phase.Buy, 0 - ); - - safePurchase(buyerAgentOwners[0], buyerAgents[0], 1); - Vm.Log[] memory entries = vm.getRecordedLogs(); - - // 1. Transfer - // 2. Transfer - // 3. Transfer - // 4. Transfer - // 5. AssetSold (from Swan) - // 6. Purchase (from BuyerAgent) - assertEq(entries.length, 6); - - // get the AssetSold event - Vm.Log memory assetSoldEvent = entries[entries.length - 2]; - - // check event sig - bytes32 eventSig = assetSoldEvent.topics[0]; - assertEq(keccak256("AssetSold(address,address,address,uint256)"), eventSig); - - // decode params from event - address _seller = abi.decode(abi.encode(assetSoldEvent.topics[1]), (address)); - address _agent = abi.decode(abi.encode(assetSoldEvent.topics[2]), (address)); - address asset = abi.decode(abi.encode(assetSoldEvent.topics[3]), (address)); - uint256 price = abi.decode(assetSoldEvent.data, (uint256)); - - assertEq(_agent, address(buyerAgents[0])); - assertEq(asset, buyerAgents[0].inventory(0, 0)); - - // get asset details - Swan.AssetListing memory assetListing = swan.getListing(asset); - - assertEq(assetListing.seller, _seller); - assertEq(sellers[0], _seller); - assertEq(assetListing.buyer, address(buyerAgents[0])); - - assertEq(uint8(assetListing.status), uint8(Swan.AssetStatus.Sold)); - assertEq(assetListing.price, price); - - // emitter should be swan - assertEq(assetSoldEvent.emitter, address(swan)); - } - - function test_UpdateState() - external - fund - createBuyers - sellersApproveToSwan - addValidatorsToWhitelist - registerOracles - listAssets(sellers[0], marketParameters.maxAssetCount, address(buyerAgents[0])) - { - address buyerAgentOwner = buyerAgentOwners[0]; - BuyerAgent buyerAgent = buyerAgents[0]; - - bytes memory newState = abi.encodePacked("0x", "after purchase"); - uint256 taskId = 1; - - increaseTime(buyerAgent.createdAt() + marketParameters.sellInterval, buyerAgent, BuyerAgent.Phase.Buy, 0); - safePurchase(buyerAgentOwner, buyerAgent, taskId); - taskId++; - - increaseTime( - buyerAgent.createdAt() + marketParameters.sellInterval + marketParameters.buyInterval, - buyerAgent, - BuyerAgent.Phase.Withdraw, - 0 - ); - vm.prank(buyerAgentOwner); - buyerAgent.oracleStateRequest(input, models); - - safeRespond(generators[0], newState, taskId); - safeRespond(generators[1], newState, taskId); - - safeValidate(validators[0], taskId); - - vm.prank(buyerAgentOwner); - buyerAgent.updateState(); - assertEq(buyerAgent.state(), newState); - } - - /// @notice Seller cannot list an asset in withdraw phase - function test_RevertWhen_ListInWithdrawPhase() external fund createBuyers sellersApproveToSwan { - BuyerAgent agent = buyerAgents[0]; - - increaseTime( - agent.createdAt() + marketParameters.sellInterval + marketParameters.buyInterval, - agent, - BuyerAgent.Phase.Withdraw, - 0 - ); - currPhase = BuyerAgent.Phase.Withdraw; - - vm.prank(sellers[0]); - vm.expectRevert(abi.encodeWithSelector(BuyerAgent.InvalidPhase.selector, currPhase, BuyerAgent.Phase.Sell)); - swan.list("name", "symbol", "desc", 0.01 ether, address(agent)); - } - - /// @notice Buyer Agent Owner can setAmountPerRound in withdraw phase - function test_SetAmountPerRound() external fund createBuyers sellersApproveToSwan { - BuyerAgent agent = buyerAgents[0]; - uint256 newAmountPerRound = 2 ether; - - increaseTime( - agent.createdAt() + marketParameters.sellInterval + marketParameters.buyInterval, - agent, - BuyerAgent.Phase.Withdraw, - 0 - ); - - vm.prank(buyerAgentOwners[0]); - agent.setAmountPerRound(newAmountPerRound); - assertEq(agent.amountPerRound(), newAmountPerRound); - } - - /// @notice Buyer Agent Owner cannot create buyer agent with invalid royalty - /// @dev feeRoyalty must be between 0 - 100 - function test_RevertWhen_CreateBuyerWithInvalidRoyalty() external fund { - uint96 invalidRoyalty = 150; - - vm.prank(buyerAgentOwners[0]); - vm.expectRevert(abi.encodeWithSelector(BuyerAgent.InvalidFee.selector, invalidRoyalty)); - swan.createBuyer( - buyerAgentParameters[0].name, - buyerAgentParameters[0].description, - invalidRoyalty, - buyerAgentParameters[0].amountPerRound - ); - } - - /// @notice Swan owner can set factories - function test_SetFactories() external fund { - SwanAssetFactory _swanAssetFactory = new SwanAssetFactory(); - BuyerAgentFactory _buyerAgentFactory = new BuyerAgentFactory(); - - vm.prank(dria); - swan.setFactories(address(_buyerAgentFactory), address(_swanAssetFactory)); - - assertEq(address(swan.buyerAgentFactory()), address(_buyerAgentFactory)); - assertEq(address(swan.swanAssetFactory()), address(_swanAssetFactory)); - } - - /// @notice Seller cannot relist an asset that is already purchased - function test_RevertWhen_RelistAlreadyPurchasedAsset() - external - fund - createBuyers - sellersApproveToSwan - addValidatorsToWhitelist - registerOracles - listAssets(sellers[0], marketParameters.maxAssetCount, address(buyerAgents[0])) - { - address buyer = buyerAgentOwners[0]; - BuyerAgent buyerAgent = buyerAgents[0]; - uint256 taskId = 1; - - // increase time to buy phase - increaseTime(buyerAgent.createdAt() + marketParameters.sellInterval, buyerAgent, BuyerAgent.Phase.Buy, 0); - safePurchase(buyer, buyerAgent, taskId); - - uint256 sellPhaseOfTheSecondRound = buyerAgent.createdAt() + marketParameters.sellInterval - + marketParameters.buyInterval + marketParameters.withdrawInterval; - increaseTime(sellPhaseOfTheSecondRound, buyerAgent, BuyerAgent.Phase.Sell, 1); - - // get the asset - address listedAssetAddr = swan.getListedAssets(address(buyerAgent), currRound)[0]; - assertEq(buyerAgent.inventory(currRound, 0), listedAssetAddr); - - Swan.AssetListing memory asset = swan.getListing(listedAssetAddr); - - // try to relist the asset - vm.prank(sellers[0]); - vm.expectRevert(abi.encodeWithSelector(Swan.InvalidStatus.selector, asset.status, Swan.AssetStatus.Listed)); - swan.relist(listedAssetAddr, address(buyerAgent), asset.price); - } - - /// @notice Seller cannot relist another seller's asset - function test_RevertWhen_RelistByAnotherSeller() - external - fund - createBuyers - sellersApproveToSwan - addValidatorsToWhitelist - registerOracles - listAssets(sellers[0], marketParameters.maxAssetCount, address(buyerAgents[0])) - { - BuyerAgent buyerAgent = buyerAgents[0]; - address listedAssetAddr = swan.getListedAssets(address(buyerAgent), currRound)[0]; - - // increase time to the sell phase of thze next round - uint256 sellPhaseOfTheSecondRound = buyerAgent.createdAt() + marketParameters.sellInterval - + marketParameters.buyInterval + marketParameters.withdrawInterval; - increaseTime(sellPhaseOfTheSecondRound, buyerAgent, BuyerAgent.Phase.Sell, 1); - - // try to relist an asset by another seller - vm.prank(sellers[1]); - vm.expectRevert(abi.encodeWithSelector(Swan.Unauthorized.selector, sellers[1])); - swan.relist(listedAssetAddr, address(buyerAgent), 0.1 ether); - } - - /// @notice Seller can relist an asset - /// @dev Buyer Agent must be in Sell Phase - function test_RelistAsset() - external - fund - createBuyers - sellersApproveToSwan - addValidatorsToWhitelist - registerOracles - listAssets(sellers[0], marketParameters.maxAssetCount, address(buyerAgents[0])) - { - BuyerAgent buyerAgent = buyerAgents[0]; - BuyerAgent buyerAgentToRelist = buyerAgents[1]; - - address listedAssetAddr = swan.getListedAssets(address(buyerAgent), currRound)[0]; - - // increase time to the sell phase of the next round - uint256 sellPhaseOfTheSecondRound = buyerAgent.createdAt() + marketParameters.sellInterval - + marketParameters.buyInterval + marketParameters.withdrawInterval; - increaseTime(sellPhaseOfTheSecondRound, buyerAgent, BuyerAgent.Phase.Sell, 1); - - vm.prank(sellers[0]); - vm.expectRevert(abi.encodeWithSelector(Swan.InvalidPrice.selector, 0)); - swan.relist(listedAssetAddr, address(buyerAgentToRelist), 0); - - // try to relist an asset by another seller - vm.recordLogs(); - vm.prank(sellers[0]); - swan.relist(listedAssetAddr, address(buyerAgentToRelist), assetPrice); - - // check the logs - Vm.Log[] memory entries = vm.getRecordedLogs(); - assertEq(entries.length, 4); - // Transfer (from WETH) - // Transfer (from WETH) - // Transfer (from WETH) - // AssetRelisted - - // get the event data - Vm.Log memory assetRelistedEvent = entries[entries.length - 1]; - - bytes32 eventSig = assetRelistedEvent.topics[0]; - assertEq(keccak256("AssetRelisted(address,address,address,uint256)"), eventSig); - - address owner = abi.decode(abi.encode(assetRelistedEvent.topics[1]), (address)); - address agent = abi.decode(abi.encode(assetRelistedEvent.topics[2]), (address)); - address asset = abi.decode(abi.encode(assetRelistedEvent.topics[3]), (address)); - uint256 price = abi.decode(assetRelistedEvent.data, (uint256)); - - assertEq(owner, sellers[0]); - assertEq(agent, address(buyerAgentToRelist)); - assertEq(asset, listedAssetAddr); - assertEq(price, assetPrice); - } - - /// @notice Seller cannot relist an asset in Buy Phase - function test_RevertWhen_RelistInBuyPhase() - external - fund - createBuyers - sellersApproveToSwan - addValidatorsToWhitelist - registerOracles - listAssets(sellers[0], marketParameters.maxAssetCount, address(buyerAgents[0])) - { - BuyerAgent buyerAgent = buyerAgents[0]; - address listedAssetAddr = swan.getListedAssets(address(buyerAgent), currRound)[0]; - - // increase time to the buy phase of the second round - uint256 buyPhaseOfTheSecondRound = buyerAgent.createdAt() + marketParameters.sellInterval - + marketParameters.buyInterval + marketParameters.withdrawInterval + marketParameters.sellInterval; - - increaseTime(buyPhaseOfTheSecondRound, buyerAgent, BuyerAgent.Phase.Buy, 1); - currPhase = BuyerAgent.Phase.Buy; - - // try to relist - vm.expectRevert(abi.encodeWithSelector(BuyerAgent.InvalidPhase.selector, currPhase, BuyerAgent.Phase.Sell)); - vm.prank(sellers[0]); - swan.relist(listedAssetAddr, address(buyerAgent), assetPrice); - } - - /// @notice Seller cannot relist an asset in Withdraw Phase - function test_RevertWhen_RelistInWithdrawPhase() - external - fund - createBuyers - sellersApproveToSwan - addValidatorsToWhitelist - registerOracles - listAssets(sellers[0], marketParameters.maxAssetCount, address(buyerAgents[0])) - { - BuyerAgent buyerAgent = buyerAgents[0]; - address listedAssetAddr = swan.getListedAssets(address(buyerAgent), currRound)[0]; - - // increase time to the withdraw phase of the second round - uint256 withdrawPhaseOfSecondRound = (2 * marketParameters.sellInterval) + (2 * marketParameters.buyInterval) - + marketParameters.withdrawInterval + buyerAgent.createdAt(); - - increaseTime(withdrawPhaseOfSecondRound, buyerAgent, BuyerAgent.Phase.Withdraw, 1); - currPhase = BuyerAgent.Phase.Withdraw; - - // try to relist - vm.expectRevert(abi.encodeWithSelector(BuyerAgent.InvalidPhase.selector, currPhase, BuyerAgent.Phase.Sell)); - vm.prank(sellers[0]); - swan.relist(listedAssetAddr, address(buyerAgent), assetPrice); - } - - /// @notice Swan owner can set market parameters - /// @dev Only Swan owner can set market parameters - function test_SetMarketParameters() external fund createBuyers { - // increase time to the withdraw phase - increaseTime( - buyerAgents[0].createdAt() + marketParameters.sellInterval + marketParameters.buyInterval, - buyerAgents[0], - BuyerAgent.Phase.Withdraw, - 0 - ); - - SwanMarketParameters memory newMarketParameters = SwanMarketParameters({ - withdrawInterval: 10 * 60, - sellInterval: 12 * 60, - buyInterval: 20 * 60, - platformFee: 12, - maxAssetCount: 100, - timestamp: block.timestamp, - minAssetPrice: 0.00001 ether, - maxBuyerAgentFee: 75 - }); - - vm.prank(dria); - swan.setMarketParameters(newMarketParameters); - - SwanMarketParameters memory updatedParams = swan.getCurrentMarketParameters(); - assertEq(updatedParams.withdrawInterval, newMarketParameters.withdrawInterval); - assertEq(updatedParams.sellInterval, newMarketParameters.sellInterval); - assertEq(updatedParams.buyInterval, newMarketParameters.buyInterval); - assertEq(updatedParams.platformFee, newMarketParameters.platformFee); - assertEq(updatedParams.maxAssetCount, newMarketParameters.maxAssetCount); - assertEq(updatedParams.timestamp, newMarketParameters.timestamp); - } - - /// @notice Swan owner can set oracle parameters - /// @dev Only Swan owner can set oracle parameters - function test_SetOracleParameters() external fund createBuyers { - // increase time to the withdraw phase - increaseTime( - buyerAgents[0].createdAt() + marketParameters.sellInterval + marketParameters.buyInterval, - buyerAgents[0], - BuyerAgent.Phase.Withdraw, - 0 - ); - - LLMOracleTaskParameters memory newOracleParameters = - LLMOracleTaskParameters({difficulty: 5, numGenerations: 3, numValidations: 4}); - - vm.prank(dria); - swan.setOracleParameters(newOracleParameters); - - LLMOracleTaskParameters memory updatedParams = swan.getOracleParameters(); - - assertEq(updatedParams.difficulty, newOracleParameters.difficulty); - assertEq(updatedParams.numGenerations, newOracleParameters.numGenerations); - assertEq(updatedParams.numValidations, newOracleParameters.numValidations); - } -} diff --git a/test/SwanFuzz.t.sol b/test/SwanFuzz.t.sol deleted file mode 100644 index 8e90b7b..0000000 --- a/test/SwanFuzz.t.sol +++ /dev/null @@ -1,217 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {Helper} from "./Helper.t.sol"; - -import {BuyerAgent, BuyerAgentFactory} from "../src/BuyerAgent.sol"; -import {SwanAssetFactory, SwanAsset} from "../src/SwanAsset.sol"; -import {Swan, SwanMarketParameters} from "../src/Swan.sol"; -import {WETH9} from "./WETH9.sol"; -import {LLMOracleRegistry} from "@firstbatch/dria-oracle-contracts/LLMOracleRegistry.sol"; -import { - LLMOracleCoordinator, LLMOracleTaskParameters -} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {console} from "forge-std/Test.sol"; - -/// @notice Fuzz test is used to test call the functions with random values multiple times -contract SwanFuzz is Helper { - modifier fund() { - // fund dria - deal(address(token), dria, 1 ether); - - // fund sellers - for (uint256 i; i < sellers.length; i++) { - deal(address(token), sellers[i], 5 ether); - assertEq(token.balanceOf(sellers[i]), 5 ether); - vm.label(address(sellers[i]), string.concat("Seller#", vm.toString(i + 1))); - } - _; - } - - /// @notice Calculate royalties - function testFuzz_CalculateRoyalties(uint256 price, uint256 agentFee, uint256 driaFee) external createBuyers { - agentFee = bound(agentFee, 1, 80); - require(agentFee <= 80 && agentFee > 0, "Agent fee is not correctly set"); - - driaFee = bound(driaFee, 1, 80); - require(driaFee <= 80 && driaFee > 0, "Dria fee is not correctly set"); - - price = bound(price, 0.000001 ether, 0.2 ether); - require(price >= 0.000001 ether && price <= 0.2 ether, "Price is not correctly set"); - - uint256 expectedTotalFee = Math.mulDiv(price, (agentFee * 100), 10000); - uint256 expectedDriaFee = - Math.mulDiv(expectedTotalFee, (swan.getCurrentMarketParameters().platformFee * 100), 10000); - uint256 expectedAgentFee = expectedTotalFee - expectedDriaFee; - - assertEq(expectedAgentFee + expectedDriaFee, expectedTotalFee, "Invalid fee calculation"); - } - - /// @notice Change the intervals and check the current phase and round is are correct - function testFuzz_ChangeCycleTime( - uint256 sellIntervalForFirstSet, - uint256 buyIntervalForFirstset, - uint256 withdrawIntervalForFirstSet, - uint256 sellIntervalForSecondSet, - uint256 buyIntervalForSecondSet, - uint256 withdrawIntervalForSecondSet - ) external createBuyers { - sellIntervalForFirstSet = bound(sellIntervalForFirstSet, 15 minutes, 2 days); - require( - sellIntervalForFirstSet >= 15 minutes && sellIntervalForFirstSet <= 2 days, - "SellInterval is not correctly set" - ); - - sellIntervalForSecondSet = bound(sellIntervalForSecondSet, 15 minutes, 2 days); - require( - sellIntervalForSecondSet >= 15 minutes && sellIntervalForSecondSet <= 2 days, - "SellInterval is not correctly set" - ); - - buyIntervalForFirstset = bound(buyIntervalForFirstset, 15 minutes, 2 days); - require( - buyIntervalForFirstset >= 15 minutes && buyIntervalForFirstset <= 2 days, "BuyInterval is not correctly set" - ); - - buyIntervalForSecondSet = bound(buyIntervalForSecondSet, 15 minutes, 2 days); - require( - buyIntervalForSecondSet >= 15 minutes && buyIntervalForSecondSet <= 2 days, - "BuyInterval is not correctly set" - ); - - withdrawIntervalForFirstSet = bound(withdrawIntervalForFirstSet, 15 minutes, 2 days); - require( - withdrawIntervalForFirstSet >= 15 minutes && withdrawIntervalForFirstSet <= 2 days, - "WithdrawInterval is not correctly set" - ); - - withdrawIntervalForSecondSet = bound(withdrawIntervalForSecondSet, 15 minutes, 2 days); - require( - withdrawIntervalForSecondSet >= 15 minutes && withdrawIntervalForSecondSet <= 2 days, - "WithdrawInterval is not correctly set" - ); - - // increase time to buy phase of the second round - increaseTime( - buyerAgents[0].createdAt() + swan.getCurrentMarketParameters().sellInterval, - buyerAgents[0], - BuyerAgent.Phase.Buy, - 0 - ); - - // change cycle time - setMarketParameters( - SwanMarketParameters({ - withdrawInterval: withdrawIntervalForFirstSet, - sellInterval: sellIntervalForFirstSet, - buyInterval: buyIntervalForFirstset, - platformFee: 2, - maxAssetCount: 3, - timestamp: block.timestamp, - minAssetPrice: 0.00001 ether, - maxBuyerAgentFee: 80 - }) - ); - - // get all params - SwanMarketParameters[] memory allParams = swan.getMarketParameters(); - assertEq(allParams.length, 2); - (uint256 _currRound, BuyerAgent.Phase _phase,) = buyerAgents[0].getRoundPhase(); - - assertEq(_currRound, 1); - assertEq(uint8(_phase), uint8(BuyerAgent.Phase.Sell)); - - uint256 currTimestamp = block.timestamp; - - // increase time to buy phase of the second round but round comes +1 because of the setMarketParameters call - // buyerAgents[0] should be in buy phase of second round - increaseTime( - currTimestamp + (2 * swan.getCurrentMarketParameters().sellInterval) - + swan.getCurrentMarketParameters().buyInterval + swan.getCurrentMarketParameters().withdrawInterval, - buyerAgents[0], - BuyerAgent.Phase.Buy, - 2 - ); - - // deploy new buyer agent - vm.prank(buyerAgentOwners[0]); - BuyerAgent agentAfterFirstSet = swan.createBuyer( - buyerAgentParameters[1].name, - buyerAgentParameters[1].description, - buyerAgentParameters[1].feeRoyalty, - buyerAgentParameters[1].amountPerRound - ); - - // agentAfterFirstSet should be in sell phase of the first round - checkRoundAndPhase(agentAfterFirstSet, BuyerAgent.Phase.Sell, 0); - - // change cycle time - setMarketParameters( - SwanMarketParameters({ - withdrawInterval: withdrawIntervalForSecondSet, - sellInterval: sellIntervalForSecondSet, - buyInterval: buyIntervalForSecondSet, - platformFee: 2, // percentage - maxAssetCount: 3, - timestamp: block.timestamp, - minAssetPrice: 0.00001 ether, - maxBuyerAgentFee: 80 - }) - ); - - // get all params - allParams = swan.getMarketParameters(); - assertEq(allParams.length, 3); - - // buyerAgents[0] should be in sell phase of the fourth round (2 more increase time + 2 for setting new params) - checkRoundAndPhase(buyerAgents[0], BuyerAgent.Phase.Sell, 3); - - // agentAfterFirstSet should be in sell phase of the second round - checkRoundAndPhase(agentAfterFirstSet, BuyerAgent.Phase.Sell, 1); - } - - function testFuzz_TransferOwnership(address newOwner) public { - vm.assume(newOwner != address(0x0)); - - vm.prank(dria); - swan.transferOwnership(newOwner); - } - - function testFuzz_ListAsset( - string calldata name, - string calldata symbol, - bytes calldata desc, - uint256 price, - string memory agentName, - string memory agentDesc, - uint96 agentFee, - uint256 amountPerRound - ) public fund sellersApproveToSwan { - // Assume the price is within a reasonable range and buyer is not zero address - amountPerRound = bound(amountPerRound, 0.1 ether, 1 ether); - require(amountPerRound >= 0.1 ether && amountPerRound <= 1 ether, "Amount per round is not correctly set"); - - agentFee = uint96(bound(agentFee, 1, marketParameters.maxBuyerAgentFee - 1)); - require(agentFee < marketParameters.maxBuyerAgentFee && agentFee > 0, "Agent fee is not correctly set"); - - price = bound(price, marketParameters.minAssetPrice, amountPerRound - 1); - require(price >= marketParameters.minAssetPrice && price <= amountPerRound - 1, "Price is not correctly set"); - - // Create a buyer agent - vm.prank(buyerAgentOwners[0]); - BuyerAgent _agent = swan.createBuyer(agentName, agentDesc, agentFee, amountPerRound); - - // List the asset - vm.prank(sellers[0]); - swan.list(name, symbol, desc, price, address(_agent)); - - // Check that the asset is listed correctly - address asset = swan.getListedAssets(address(_agent), 0)[0]; - Swan.AssetListing memory listing = swan.getListing(asset); - assertEq(listing.price, price); - assertEq(listing.buyer, address(_agent)); - } -} diff --git a/test/SwanFuzzTest.t.sol b/test/SwanFuzzTest.t.sol new file mode 100644 index 0000000..5875569 --- /dev/null +++ b/test/SwanFuzzTest.t.sol @@ -0,0 +1,217 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {Helper} from "./Helper.t.sol"; + +import {AIAgent, AIAgentFactory} from "../src/AIAgent.sol"; +import {ArtifactFactory, Artifact} from "../src/Artifact.sol"; +import {Swan, SwanMarketParameters} from "../src/Swan.sol"; +import {WETH9} from "./WETH9.sol"; +import {LLMOracleRegistry} from "@firstbatch/dria-oracle-contracts/LLMOracleRegistry.sol"; +import { + LLMOracleCoordinator, LLMOracleTaskParameters +} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {console} from "forge-std/Test.sol"; + +/// @notice Fuzz test is used to test call the functions with random values multiple times +contract SwanFuzzTest is Helper { + modifier fund() { + // fund dria + deal(address(token), dria, 1 ether); + + // fund sellers + for (uint256 i; i < sellers.length; i++) { + deal(address(token), sellers[i], 5 ether); + assertEq(token.balanceOf(sellers[i]), 5 ether); + vm.label(address(sellers[i]), string.concat("Seller#", vm.toString(i + 1))); + } + _; + } + + /// @notice Calculate royalties + function testFuzz_CalculateRoyalties(uint256 _price, uint256 _agentFee, uint256 _driaFee) external createAgents { + _agentFee = bound(_agentFee, 1, 80); + require(_agentFee <= 80 && _agentFee > 0, "Agent fee is not correctly set"); + + _driaFee = bound(_driaFee, 1, 80); + require(_driaFee <= 80 && _driaFee > 0, "Dria fee is not correctly set"); + + _price = bound(_price, 0.000001 ether, 0.2 ether); + require(_price >= 0.000001 ether && _price <= 0.2 ether, "Price is not correctly set"); + + uint256 expectedTotalFee = Math.mulDiv(_price, (_agentFee * 100), 10000); + uint256 expectedDriaFee = + Math.mulDiv(expectedTotalFee, (swan.getCurrentMarketParameters().platformFee * 100), 10000); + uint256 expectedAgentFee = expectedTotalFee - expectedDriaFee; + + assertEq(expectedAgentFee + expectedDriaFee, expectedTotalFee, "Invalid fee calculation"); + } + + /// @notice Change the intervals and check the current phase and round is are correct + function testFuzz_ChangeCycleTime( + uint256 _listingIntervalForFirstSet, + uint256 _buyIntervalForFirstset, + uint256 _withdrawIntervalForFirstSet, + uint256 _listingIntervalForSecondSet, + uint256 _buyIntervalForSecondSet, + uint256 _withdrawIntervalForSecondSet + ) external createAgents { + _listingIntervalForFirstSet = bound(_listingIntervalForFirstSet, 15 minutes, 2 days); + require( + _listingIntervalForFirstSet >= 15 minutes && _listingIntervalForFirstSet <= 2 days, + "ListingInterval is not correctly set" + ); + + _listingIntervalForSecondSet = bound(_listingIntervalForSecondSet, 15 minutes, 2 days); + require( + _listingIntervalForSecondSet >= 15 minutes && _listingIntervalForSecondSet <= 2 days, + "ListingInterval is not correctly set" + ); + + _buyIntervalForFirstset = bound(_buyIntervalForFirstset, 15 minutes, 2 days); + require( + _buyIntervalForFirstset >= 15 minutes && _buyIntervalForFirstset <= 2 days, + "BuyInterval is not correctly set" + ); + + _buyIntervalForSecondSet = bound(_buyIntervalForSecondSet, 15 minutes, 2 days); + require( + _buyIntervalForSecondSet >= 15 minutes && _buyIntervalForSecondSet <= 2 days, + "BuyInterval is not correctly set" + ); + + _withdrawIntervalForFirstSet = bound(_withdrawIntervalForFirstSet, 15 minutes, 2 days); + require( + _withdrawIntervalForFirstSet >= 15 minutes && _withdrawIntervalForFirstSet <= 2 days, + "WithdrawInterval is not correctly set" + ); + + _withdrawIntervalForSecondSet = bound(_withdrawIntervalForSecondSet, 15 minutes, 2 days); + require( + _withdrawIntervalForSecondSet >= 15 minutes && _withdrawIntervalForSecondSet <= 2 days, + "WithdrawInterval is not correctly set" + ); + + // increase time to buy phase of the second round + increaseTime( + agents[0].createdAt() + swan.getCurrentMarketParameters().listingInterval, agents[0], AIAgent.Phase.Buy, 0 + ); + + // change cycle time + setMarketParameters( + SwanMarketParameters({ + withdrawInterval: _withdrawIntervalForFirstSet, + listingInterval: _listingIntervalForFirstSet, + buyInterval: _buyIntervalForFirstset, + platformFee: 2, + maxArtifactCount: 3, + timestamp: block.timestamp, + minArtifactPrice: 0.00001 ether, + maxAgentFee: 80 + }) + ); + + // get all params + SwanMarketParameters[] memory _allParams = swan.getMarketParameters(); + assertEq(_allParams.length, 2); + (uint256 _currRound, AIAgent.Phase _phase,) = agents[0].getRoundPhase(); + + assertEq(_currRound, 1); + assertEq(uint8(_phase), uint8(AIAgent.Phase.Listing)); + + uint256 _currTimestamp = block.timestamp; + + // increase time to buy phase of the second round but round comes +1 because of the setMarketParameters call + // AIAgents[0] should be in buy phase of second round + increaseTime( + _currTimestamp + (2 * swan.getCurrentMarketParameters().listingInterval) + + swan.getCurrentMarketParameters().buyInterval + swan.getCurrentMarketParameters().withdrawInterval, + agents[0], + AIAgent.Phase.Buy, + 2 + ); + + // deploy new AI agent + vm.prank(agentOwners[0]); + AIAgent _agentAfterFirstSet = swan.createAgent( + agentParameters[1].name, + agentParameters[1].description, + agentParameters[1].feeRoyalty, + agentParameters[1].amountPerRound + ); + + // _agentAfterFirstSet should be in listing phase of the first round + checkRoundAndPhase(_agentAfterFirstSet, AIAgent.Phase.Listing, 0); + + // change cycle time + setMarketParameters( + SwanMarketParameters({ + withdrawInterval: _withdrawIntervalForSecondSet, + listingInterval: _listingIntervalForSecondSet, + buyInterval: _buyIntervalForSecondSet, + platformFee: 2, // percentage + maxArtifactCount: 3, + timestamp: block.timestamp, + minArtifactPrice: 0.00001 ether, + maxAgentFee: 80 + }) + ); + + // get all params + _allParams = swan.getMarketParameters(); + assertEq(_allParams.length, 3); + + // AIAgents[0] should be in listing phase of the fourth round (2 more increase time + 2 for setting new params) + checkRoundAndPhase(agents[0], AIAgent.Phase.Listing, 3); + + // agentAfterFirstSet should be in listing phase of the second round + checkRoundAndPhase(_agentAfterFirstSet, AIAgent.Phase.Listing, 1); + } + + function testFuzz_TransferOwnership(address _newOwner) public { + vm.assume(_newOwner != address(0x0)); + + vm.prank(dria); + swan.transferOwnership(_newOwner); + } + + function testFuzz_ListArtifact( + string calldata _name, + string calldata _symbol, + bytes calldata _desc, + uint256 _price, + string memory _agentName, + string memory _agentDesc, + uint96 _agentFee, + uint256 _amountPerRound + ) public fund sellersApproveToSwan { + // Assume the price is within a reasonable range and agenta address is not zero address + _amountPerRound = bound(_amountPerRound, 0.1 ether, 1 ether); + require(_amountPerRound >= 0.1 ether && _amountPerRound <= 1 ether, "Amount per round is not correctly set"); + + _agentFee = uint96(bound(_agentFee, 1, marketParameters.maxAgentFee - 1)); + require(_agentFee < marketParameters.maxAgentFee && _agentFee > 0, "Agent fee is not correctly set"); + + _price = bound(_price, marketParameters.minArtifactPrice, _amountPerRound - 1); + require( + _price >= marketParameters.minArtifactPrice && _price <= _amountPerRound - 1, "Price is not correctly set" + ); + + // Create a AI agent + vm.prank(agentOwners[0]); + AIAgent _agent = swan.createAgent(_agentName, _agentDesc, _agentFee, _amountPerRound); + + // List the artifact + vm.prank(sellers[0]); + swan.list(_name, _symbol, _desc, _price, address(_agent)); + + // Check that the artifact is listed + address artifact = swan.getListedArtifacts(address(_agent), 0)[0]; + Swan.ArtifactListing memory listing = swan.getListing(artifact); + assertEq(listing.price, _price); + assertEq(listing.agent, address(_agent)); + } +} diff --git a/test/SwanIntervals.t.sol b/test/SwanIntervals.t.sol deleted file mode 100644 index dee0532..0000000 --- a/test/SwanIntervals.t.sol +++ /dev/null @@ -1,43 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -import {Vm} from "forge-std/Vm.sol"; -import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; -import {Helper} from "./Helper.t.sol"; -import {console} from "forge-std/Test.sol"; - -import {BuyerAgent, BuyerAgentFactory} from "../src/BuyerAgent.sol"; -import {SwanAssetFactory, SwanAsset} from "../src/SwanAsset.sol"; -import {Swan, SwanMarketParameters} from "../src/Swan.sol"; -import {WETH9} from "./WETH9.sol"; -import {LLMOracleRegistry} from "@firstbatch/dria-oracle-contracts/LLMOracleRegistry.sol"; -import { - LLMOracleCoordinator, LLMOracleTaskParameters -} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; - -contract SwanIntervalsTest is Helper { - /// @notice Check the current phase is Sell right after creation of buyer agent - function test_InSellPhase() external createBuyers { - // agent should be in sell phase right after creation - checkRoundAndPhase(buyerAgents[0], BuyerAgent.Phase.Sell, 0); - } - - /// @notice Check the current phase is Buy after increase time to buy phase - function test_InBuyPhase() external createBuyers { - BuyerAgent agent = buyerAgents[0]; - increaseTime(agent.createdAt() + swan.getCurrentMarketParameters().sellInterval, agent, BuyerAgent.Phase.Buy, 0); - checkRoundAndPhase(agent, BuyerAgent.Phase.Buy, 0); - } - - /// @notice Check the current phase is Withdraw after increase time to withdraw phase - function test_InWithdrawPhase() external createBuyers { - increaseTime( - buyerAgents[0].createdAt() + swan.getCurrentMarketParameters().sellInterval - + swan.getCurrentMarketParameters().buyInterval, - buyerAgents[0], - BuyerAgent.Phase.Withdraw, - 0 - ); - checkRoundAndPhase(buyerAgents[0], BuyerAgent.Phase.Withdraw, 0); - } -} diff --git a/test/SwanIntervalsTest.t.sol b/test/SwanIntervalsTest.t.sol new file mode 100644 index 0000000..303bab0 --- /dev/null +++ b/test/SwanIntervalsTest.t.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {Vm} from "forge-std/Vm.sol"; +import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; +import {Helper} from "./Helper.t.sol"; +import {console} from "forge-std/Test.sol"; + +import {AIAgent, AIAgentFactory} from "../src/AIAgent.sol"; +import {ArtifactFactory, Artifact} from "../src/Artifact.sol"; +import {Swan, SwanMarketParameters} from "../src/Swan.sol"; +import {WETH9} from "./WETH9.sol"; +import {LLMOracleRegistry} from "@firstbatch/dria-oracle-contracts/LLMOracleRegistry.sol"; +import { + LLMOracleCoordinator, LLMOracleTaskParameters +} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; + +contract SwanIntervalsTest is Helper { + /// @notice Check the current phase is listing right after creation of buyer agent + function test_InListingPhase() external createAgents { + // agent should be in listing phase right after creation + checkRoundAndPhase(agents[0], AIAgent.Phase.Listing, 0); + } + + /// @notice Check the current phase is Buy after increase time to buy phase + function test_InBuyPhase() external createAgents { + AIAgent _agent = agents[0]; + increaseTime( + _agent.createdAt() + swan.getCurrentMarketParameters().listingInterval, _agent, AIAgent.Phase.Buy, 0 + ); + checkRoundAndPhase(_agent, AIAgent.Phase.Buy, 0); + } + + /// @notice Check the current phase is Withdraw after increase time to withdraw phase + function test_InWithdrawPhase() external createAgents { + AIAgent _agent = agents[0]; + increaseTime( + _agent.createdAt() + swan.getCurrentMarketParameters().listingInterval + + swan.getCurrentMarketParameters().buyInterval, + _agent, + AIAgent.Phase.Withdraw, + 0 + ); + checkRoundAndPhase(_agent, AIAgent.Phase.Withdraw, 0); + } +} diff --git a/test/SwanTest.t.sol b/test/SwanTest.t.sol new file mode 100644 index 0000000..45a1d56 --- /dev/null +++ b/test/SwanTest.t.sol @@ -0,0 +1,634 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; + +import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {Helper} from "./Helper.t.sol"; + +import {AIAgent, AIAgentFactory} from "../src/AIAgent.sol"; +import {ArtifactFactory, Artifact} from "../src/Artifact.sol"; +import {Swan, SwanMarketParameters} from "../src/Swan.sol"; +import {LLMOracleRegistry} from "@firstbatch/dria-oracle-contracts/LLMOracleRegistry.sol"; +import {WETH9} from "./WETH9.sol"; +import { + LLMOracleCoordinator, LLMOracleTaskParameters +} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {console} from "forge-std/Test.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +contract SwanTest is Helper { + /// @dev Fund geerators, validators, sellers, and dria + modifier fund() { + scores = [10, 15]; + + // fund dria + deal(address(token), dria, 1 ether); + + // fund generators + for (uint256 i; i < generators.length; i++) { + deal(address(token), generators[i], stakes.generatorStakeAmount); + assertEq(token.balanceOf(generators[i]), stakes.generatorStakeAmount); + } + // fund validators + for (uint256 i; i < validators.length; i++) { + deal(address(token), validators[i], stakes.validatorStakeAmount); + assertEq(token.balanceOf(validators[i]), stakes.validatorStakeAmount); + } + // fund sellers + for (uint256 i; i < sellers.length; i++) { + deal(address(token), sellers[i], 5 ether); + assertEq(token.balanceOf(sellers[i]), 5 ether); + vm.label(address(sellers[i]), string.concat("Seller#", vm.toString(i + 1))); + } + _; + } + + function test_TransferOwnership() external { + address _newOwner = address(0); + + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableInvalidOwner.selector, _newOwner)); + vm.startPrank(dria); + swan.transferOwnership(_newOwner); + + _newOwner = address(0x123); + swan.transferOwnership(_newOwner); + assertEq(swan.owner(), _newOwner); + } + + function test_CreateAIAgents() external createAgents fund { + assertEq(agents.length, agentOwners.length); + + for (uint256 i = 0; i < agents.length; i++) { + assertEq(agents[i].feeRoyalty(), agentParameters[i].feeRoyalty); + assertEq(agents[i].owner(), agentOwners[i]); + assertEq(agents[i].amountPerRound(), agentParameters[i].amountPerRound); + assertEq(agents[i].name(), agentParameters[i].name); + assertEq(token.balanceOf(address(agents[i])), agentParameters[i].amountPerRound); + } + } + + /// @notice Sellers cannot list more than maxArtifactCount + function test_RevertWhen_ListMoreThanmaxArtifactCount() + external + fund + createAgents + sellersApproveToSwan + addValidatorsToWhitelist + registerOracles + listArtifacts(sellers[0], marketParameters.maxArtifactCount, address(agents[0])) + { + // try to list more than max artifact count + vm.prank(sellers[0]); + vm.expectRevert(abi.encodeWithSelector(Swan.ArtifactLimitExceeded.selector, marketParameters.maxArtifactCount)); + swan.list("name", "symbol", "desc", 0.001 ether, address(agents[0])); + } + + /// @notice Agent Owner cannot call purchase() in listing phase + function test_RevertWhen_PurchaseInListingPhase() + external + fund + createAgents + sellersApproveToSwan + addValidatorsToWhitelist + registerOracles + listArtifacts(sellers[0], marketParameters.maxArtifactCount, address(agents[0])) + { + // try to purchase in Listing Phase + vm.prank(agentOwners[0]); + vm.expectRevert(abi.encodeWithSelector(AIAgent.InvalidPhase.selector, AIAgent.Phase.Listing, AIAgent.Phase.Buy)); + agents[0].purchase(); + } + + /// @notice Seller cannot relist the artifact in the same round (for same or different agents) + function test_RevertWhen_RelistInTheSameRound() + external + fund + createAgents + sellersApproveToSwan + addValidatorsToWhitelist + registerOracles + listArtifacts(sellers[0], marketParameters.maxArtifactCount, address(agents[0])) + { + // get listed artifact + address _artifactToFail = swan.getListedArtifacts(address(agents[0]), currRound)[0]; + + vm.prank(sellers[0]); + vm.expectRevert(abi.encodeWithSelector(Swan.RoundNotFinished.selector, _artifactToFail, currRound)); + swan.relist(_artifactToFail, address(agents[1]), 0.001 ether); + } + + /// @notice Agent cannot purchase an artifact that is listed for another agent + function test_RevertWhen_PurchaseByAnotherAgent() + external + fund + createAgents + sellersApproveToSwan + addValidatorsToWhitelist + registerOracles + listArtifacts(sellers[0], marketParameters.maxArtifactCount, address(agents[0])) + { + address _agentToFail = agentOwners[0]; + AIAgent _agent = agents[1]; + + increaseTime(_agent.createdAt() + marketParameters.listingInterval, _agent, AIAgent.Phase.Buy, 0); + currPhase = AIAgent.Phase.Buy; + + vm.expectRevert(abi.encodeWithSelector(Swan.Unauthorized.selector, _agentToFail)); + vm.prank(_agentToFail); + _agent.purchase(); + } + + /// @notice Agent cannot spend more than amountPerRound per round + function test_RevertWhen_PurchaseMoreThanAmountPerRound() + external + fund + createAgents + sellersApproveToSwan + addValidatorsToWhitelist + registerOracles + listArtifacts(sellers[0], marketParameters.maxArtifactCount, address(agents[0])) + { + address _agentOwnerToFail = agentOwners[0]; + AIAgent _agentToFail = agents[0]; + + increaseTime(_agentToFail.createdAt() + marketParameters.listingInterval, _agentToFail, AIAgent.Phase.Buy, 0); + + // get the listed artifacts as output + address[] memory output = swan.getListedArtifacts(address(_agentToFail), currRound); + bytes memory encodedOutput = abi.encode(output); + + vm.prank(_agentOwnerToFail); + // make a purchase request + _agentToFail.oraclePurchaseRequest(input, models); + + // respond + safeRespond(generators[0], encodedOutput, 1); + safeRespond(generators[1], encodedOutput, 1); + + // validate + safeValidate(validators[0], 1); + + vm.prank(_agentOwnerToFail); + vm.expectRevert(abi.encodeWithSelector(AIAgent.BuyLimitExceeded.selector, artifactPrice * 2, amountPerRound)); + _agentToFail.purchase(); + } + + /// @notice Agent can purchase + /// @dev Seller has to approve Swan + /// @dev Agent must be in buy phase + /// @dev Agent must have enough balance to purchase + /// @dev artifact price must be less than amountPerRound + function test_PurchaseAnArtifact() + external + fund + createAgents + sellersApproveToSwan + addValidatorsToWhitelist + registerOracles + listArtifacts(sellers[0], marketParameters.maxArtifactCount, address(agents[0])) + { + // increase time to buy phase to be able to purchase + increaseTime(agents[0].createdAt() + marketParameters.listingInterval, agents[0], AIAgent.Phase.Buy, 0); + + safePurchase(agentOwners[0], agents[0], 1); + Vm.Log[] memory entries = vm.getRecordedLogs(); + + // 1. Transfer + // 2. Transfer + // 3. Transfer + // 4. Transfer + // 5. ArtifactSold (from Swan) + // 6. Purchase (from AIAgent) + assertEq(entries.length, 6); + + // get the ArtifactSold event + Vm.Log memory artifactSoldEvent = entries[entries.length - 2]; + + // check event sig + bytes32 eventSig = artifactSoldEvent.topics[0]; + assertEq(keccak256("ArtifactSold(address,address,address,uint256)"), eventSig); + + // decode params from event + address _seller = abi.decode(abi.encode(artifactSoldEvent.topics[1]), (address)); + address _agent = abi.decode(abi.encode(artifactSoldEvent.topics[2]), (address)); + address _artifact = abi.decode(abi.encode(artifactSoldEvent.topics[3]), (address)); + uint256 _price = abi.decode(artifactSoldEvent.data, (uint256)); + + assertEq(_agent, address(agents[0])); + assertEq(_artifact, agents[0].inventory(0, 0)); + + // get artifact details + Swan.ArtifactListing memory artifactListing = swan.getListing(_artifact); + + assertEq(artifactListing.seller, _seller); + assertEq(sellers[0], _seller); + assertEq(artifactListing.agent, address(agents[0])); + + assertEq(uint8(artifactListing.status), uint8(Swan.ArtifactStatus.Sold)); + assertEq(artifactListing.price, _price); + + // emitter should be swan + assertEq(artifactSoldEvent.emitter, address(swan)); + + // try to purchase again + vm.prank(agentOwners[0]); + vm.expectRevert(abi.encodeWithSelector(AIAgent.TaskAlreadyProcessed.selector)); + agents[0].purchase(); + } + + /// @dev Updates agent state + function test_UpdateState() + external + fund + createAgents + sellersApproveToSwan + addValidatorsToWhitelist + registerOracles + listArtifacts(sellers[0], marketParameters.maxArtifactCount, address(agents[0])) + { + address _agentOwner = agentOwners[0]; + AIAgent _agent = agents[0]; + + bytes memory newState = abi.encodePacked("0x", "after purchase"); + uint256 taskId = 1; + + increaseTime(_agent.createdAt() + marketParameters.listingInterval, _agent, AIAgent.Phase.Buy, 0); + safePurchase(_agentOwner, _agent, taskId); + taskId++; + + increaseTime( + _agent.createdAt() + marketParameters.listingInterval + marketParameters.buyInterval, + _agent, + AIAgent.Phase.Withdraw, + 0 + ); + + // try to send state request by another agent owner + vm.prank(agentOwners[1]); + vm.expectRevert(abi.encodeWithSelector(AIAgent.Unauthorized.selector, agentOwners[1])); + _agent.oracleStateRequest(input, models); + + vm.prank(_agentOwner); + _agent.oracleStateRequest(input, models); + + safeRespond(generators[0], newState, taskId); + safeRespond(generators[1], newState, taskId); + + safeValidate(validators[0], taskId); + + // try to update state by another agent owner + vm.prank(agentOwners[1]); + vm.expectRevert(abi.encodeWithSelector(AIAgent.Unauthorized.selector, agentOwners[1])); + _agent.updateState(); + + vm.prank(_agentOwner); + _agent.updateState(); + assertEq(_agent.state(), newState); + } + + /// @notice Seller cannot list an artifact in withdraw phase + function test_RevertWhen_ListInWithdrawPhase() external fund createAgents sellersApproveToSwan { + AIAgent _agent = agents[0]; + + increaseTime( + _agent.createdAt() + marketParameters.listingInterval + marketParameters.buyInterval, + _agent, + AIAgent.Phase.Withdraw, + 0 + ); + currPhase = AIAgent.Phase.Withdraw; + + checkRoundAndPhase(_agent, AIAgent.Phase.Withdraw, 0); + + vm.prank(sellers[0]); + vm.expectRevert(abi.encodeWithSelector(AIAgent.InvalidPhase.selector, currPhase, AIAgent.Phase.Listing)); + swan.list("name", "symbol", "desc", 0.01 ether, address(_agent)); + } + + /// @notice Agent Owner can setAmountPerRound in withdraw phase + function test_SetAmountPerRound() external fund createAgents sellersApproveToSwan { + AIAgent _agent = agents[0]; + uint256 _newAmountPerRound = 2 ether; + + increaseTime( + _agent.createdAt() + marketParameters.listingInterval + marketParameters.buyInterval, + _agent, + AIAgent.Phase.Withdraw, + 0 + ); + + vm.prank(agentOwners[0]); + _agent.setAmountPerRound(_newAmountPerRound); + assertEq(agent.amountPerRound(), _newAmountPerRound); + } + + /// @notice Agent Owner cannot create agent with invalid royalty + /// @dev feeRoyalty must be between 0 - 100 + function test_RevertWhen_CreateAgentWithInvalidRoyalty() external fund { + uint96 _invalidRoyalty = 150; + + vm.prank(agentOwners[0]); + vm.expectRevert(abi.encodeWithSelector(AIAgent.InvalidFee.selector, _invalidRoyalty)); + swan.createAgent( + agentParameters[0].name, agentParameters[0].description, _invalidRoyalty, agentParameters[0].amountPerRound + ); + } + + /// @notice Swan owner can set factories + function test_SetFactories() external fund { + ArtifactFactory _artifactFactory = new ArtifactFactory(); + AIAgentFactory _agentFactory = new AIAgentFactory(); + + vm.prank(dria); + swan.setFactories(address(_agentFactory), address(_artifactFactory)); + + assertEq(address(swan.agentFactory()), address(_agentFactory)); + assertEq(address(swan.artifactFactory()), address(_artifactFactory)); + } + + /// @notice Seller cannot relist an artifact that is already purchased + function test_RevertWhen_RelistAlreadyPurchasedArtifact() + external + fund + createAgents + sellersApproveToSwan + addValidatorsToWhitelist + registerOracles + listArtifacts(sellers[0], marketParameters.maxArtifactCount, address(agents[0])) + { + address _agentOwner = agentOwners[0]; + AIAgent _agent = agents[0]; + uint256 taskId = 1; + + // increase time to buy phase + increaseTime(_agent.createdAt() + marketParameters.listingInterval, _agent, AIAgent.Phase.Buy, 0); + safePurchase(_agentOwner, _agent, taskId); + + uint256 listingPhaseOfTheSecondRound = _agent.createdAt() + marketParameters.listingInterval + + marketParameters.buyInterval + marketParameters.withdrawInterval; + increaseTime(listingPhaseOfTheSecondRound, _agent, AIAgent.Phase.Listing, 1); + + // get artifact + address _listedArtifactAddr = swan.getListedArtifacts(address(_agent), currRound)[0]; + assertEq(_agent.inventory(currRound, 0), _listedArtifactAddr); + + Swan.ArtifactListing memory artifact = swan.getListing(_listedArtifactAddr); + + // try to relist an artifact that is already purchased + vm.prank(sellers[0]); + vm.expectRevert( + abi.encodeWithSelector(Swan.InvalidStatus.selector, artifact.status, Swan.ArtifactStatus.Listed) + ); + swan.relist(_listedArtifactAddr, address(_agent), artifact.price); + } + + /// @notice Seller cannot relist another seller's artifact + function test_RevertWhen_RelistByAnotherSeller() + external + fund + createAgents + sellersApproveToSwan + addValidatorsToWhitelist + registerOracles + listArtifacts(sellers[0], marketParameters.maxArtifactCount, address(agents[0])) + { + AIAgent _agent = agents[0]; + address _listedArtifactAddr = swan.getListedArtifacts(address(_agent), currRound)[0]; + + // increase time to the listing phase of the next round + uint256 listingPhaseOfTheSecondRound = _agent.createdAt() + marketParameters.listingInterval + + marketParameters.buyInterval + marketParameters.withdrawInterval; + + increaseTime(listingPhaseOfTheSecondRound, _agent, AIAgent.Phase.Listing, 1); + + // try to relist an artifact by another seller + vm.prank(sellers[1]); + vm.expectRevert(abi.encodeWithSelector(Swan.Unauthorized.selector, sellers[1])); + swan.relist(_listedArtifactAddr, address(_agent), 0.1 ether); + } + + /// @notice Seller cannot relist another seller's artifact + function test_RevertWhen_RelistMoreThanMaxArtifactCount() + external + fund + createAgents + sellersApproveToSwan + addValidatorsToWhitelist + registerOracles + listArtifacts(sellers[0], marketParameters.maxArtifactCount, address(agents[0])) + { + AIAgent _agent = agents[0]; + address _listedArtifactAddr = swan.getListedArtifacts(address(_agent), currRound)[0]; + + // increase time to the listing phase of the next round + uint256 listingPhaseOfTheSecondRound = _agent.createdAt() + marketParameters.listingInterval + + marketParameters.buyInterval + marketParameters.withdrawInterval; + + increaseTime(listingPhaseOfTheSecondRound, _agent, AIAgent.Phase.Listing, 1); + + // list maxArtifactCount artifacts + for (uint256 i = 0; i < marketParameters.maxArtifactCount; i++) { + vm.prank(sellers[1]); + swan.list("name", "symbol", "desc", 0.0001 ether, address(_agent)); + } + + assertEq(swan.getListedArtifacts(address(_agent), 1).length, marketParameters.maxArtifactCount); + + // try to relist an artifact by seller + vm.prank(sellers[0]); + vm.expectRevert(abi.encodeWithSelector(Swan.ArtifactLimitExceeded.selector, marketParameters.maxArtifactCount)); + swan.relist(_listedArtifactAddr, address(_agent), marketParameters.minArtifactPrice); + } + + /// @notice Seller can relist an artifact + /// @dev Agent must be in Listing Phase + function test_RelistArtifact() + external + fund + createAgents + sellersApproveToSwan + addValidatorsToWhitelist + registerOracles + listArtifacts(sellers[0], marketParameters.maxArtifactCount, address(agents[0])) + { + AIAgent _agent = agents[0]; + AIAgent _agentToRelist = agents[1]; + + address _listedArtifactAddr = swan.getListedArtifacts(address(_agent), currRound)[0]; + + // increase time to the listing phase of the next round + uint256 listingPhaseOfTheSecondRound = _agent.createdAt() + marketParameters.listingInterval + + marketParameters.buyInterval + marketParameters.withdrawInterval; + increaseTime(listingPhaseOfTheSecondRound, _agent, AIAgent.Phase.Listing, 1); + + vm.prank(sellers[0]); + vm.expectRevert(abi.encodeWithSelector(Swan.InvalidPrice.selector, 0)); + swan.relist(_listedArtifactAddr, address(_agentToRelist), 0); + + // try to relist an artifact by another seller + vm.recordLogs(); + vm.prank(sellers[0]); + swan.relist(_listedArtifactAddr, address(_agentToRelist), artifactPrice); + + // check the logs + Vm.Log[] memory entries = vm.getRecordedLogs(); + assertEq(entries.length, 4); + // Transfer (from WETH) + // Transfer (from WETH) + // Transfer (from WETH) + // ArtifactRelisted (from Swan) + + // get the event data + Vm.Log memory artifactRelistedEvent = entries[entries.length - 1]; + + bytes32 eventSig = artifactRelistedEvent.topics[0]; + assertEq(keccak256("ArtifactRelisted(address,address,address,uint256)"), eventSig); + + address _owner = abi.decode(abi.encode(artifactRelistedEvent.topics[1]), (address)); + address _expectedAgent = abi.decode(abi.encode(artifactRelistedEvent.topics[2]), (address)); + address _artifact = abi.decode(abi.encode(artifactRelistedEvent.topics[3]), (address)); + uint256 _price = abi.decode(artifactRelistedEvent.data, (uint256)); + + assertEq(_owner, sellers[0]); + assertEq(_expectedAgent, address(_agentToRelist)); + assertEq(_artifact, _listedArtifactAddr); + assertEq(_price, artifactPrice); + } + + /// @notice Seller cannot relist an artifact in Buy Phase + function test_RevertWhen_RelistInBuyPhase() + external + fund + createAgents + sellersApproveToSwan + addValidatorsToWhitelist + registerOracles + listArtifacts(sellers[0], marketParameters.maxArtifactCount, address(agents[0])) + { + AIAgent _agent = agents[0]; + address _listedArtifactAddr = swan.getListedArtifacts(address(_agent), currRound)[0]; + + // increase time to the buy phase of the second round + uint256 buyPhaseOfTheSecondRound = _agent.createdAt() + marketParameters.listingInterval + + marketParameters.buyInterval + marketParameters.withdrawInterval + marketParameters.listingInterval; + + increaseTime(buyPhaseOfTheSecondRound, _agent, AIAgent.Phase.Buy, 1); + currPhase = AIAgent.Phase.Buy; + + // try to relist + vm.expectRevert(abi.encodeWithSelector(AIAgent.InvalidPhase.selector, currPhase, AIAgent.Phase.Listing)); + vm.prank(sellers[0]); + swan.relist(_listedArtifactAddr, address(_agent), artifactPrice); + } + + /// @notice Seller cannot relist an artifact in Withdraw Phase + function test_RevertWhen_RelistInWithdrawPhase() + external + fund + createAgents + sellersApproveToSwan + addValidatorsToWhitelist + registerOracles + listArtifacts(sellers[0], marketParameters.maxArtifactCount, address(agents[0])) + { + AIAgent _agent = agents[0]; + address _listedArtifactAddr = swan.getListedArtifacts(address(_agent), currRound)[0]; + + // increase time to the withdraw phase of the second round + uint256 withdrawPhaseOfSecondRound = (2 * marketParameters.listingInterval) + (2 * marketParameters.buyInterval) + + marketParameters.withdrawInterval + _agent.createdAt(); + + increaseTime(withdrawPhaseOfSecondRound, _agent, AIAgent.Phase.Withdraw, 1); + currPhase = AIAgent.Phase.Withdraw; + + // try to relist + vm.expectRevert(abi.encodeWithSelector(AIAgent.InvalidPhase.selector, currPhase, AIAgent.Phase.Listing)); + vm.prank(sellers[0]); + swan.relist(_listedArtifactAddr, address(_agent), artifactPrice); + } + + function test_RevertWhen_SetMarketParametersWithInvalidFee() external fund { + SwanMarketParameters memory newMarketParameters = SwanMarketParameters({ + withdrawInterval: 10 * 60, + listingInterval: 12 * 60, + buyInterval: 20 * 60, + platformFee: 101, // fee cannot be more than 100 + maxArtifactCount: 100, + timestamp: block.timestamp, + minArtifactPrice: 0.00001 ether, + maxAgentFee: 75 + }); + + vm.prank(dria); + // expectRevert(revertData, reverter) + vm.expectRevert("Platform fee cannot exceed 100%", address(swan)); + swan.setMarketParameters(newMarketParameters); + } + + /// @notice Swan owner can set market parameters + /// @dev Only Swan owner can set market parameters + function test_SetMarketParameters() external fund createAgents { + // increase time to the withdraw phase + increaseTime( + agents[0].createdAt() + marketParameters.listingInterval + marketParameters.buyInterval, + agents[0], + AIAgent.Phase.Withdraw, + 0 + ); + + SwanMarketParameters memory newMarketParameters = SwanMarketParameters({ + withdrawInterval: 10 * 60, + listingInterval: 12 * 60, + buyInterval: 20 * 60, + platformFee: 12, + maxArtifactCount: 100, + timestamp: block.timestamp, + minArtifactPrice: 0.00001 ether, + maxAgentFee: 75 + }); + + vm.prank(dria); + swan.setMarketParameters(newMarketParameters); + + SwanMarketParameters memory updatedParams = swan.getCurrentMarketParameters(); + assertEq(updatedParams.withdrawInterval, newMarketParameters.withdrawInterval); + assertEq(updatedParams.listingInterval, newMarketParameters.listingInterval); + assertEq(updatedParams.buyInterval, newMarketParameters.buyInterval); + assertEq(updatedParams.platformFee, newMarketParameters.platformFee); + assertEq(updatedParams.maxArtifactCount, newMarketParameters.maxArtifactCount); + assertEq(updatedParams.timestamp, newMarketParameters.timestamp); + } + + /// @notice Swan owner can set oracle parameters + /// @dev Only Swan owner can set oracle parameters + function test_SetOracleParameters() external fund createAgents { + // increase time to the withdraw phase + increaseTime( + agents[0].createdAt() + marketParameters.listingInterval + marketParameters.buyInterval, + agents[0], + AIAgent.Phase.Withdraw, + 0 + ); + + LLMOracleTaskParameters memory newOracleParameters = + LLMOracleTaskParameters({difficulty: 5, numGenerations: 3, numValidations: 4}); + + vm.prank(dria); + swan.setOracleParameters(newOracleParameters); + + LLMOracleTaskParameters memory updatedParams = swan.getOracleParameters(); + + assertEq(updatedParams.difficulty, newOracleParameters.difficulty); + assertEq(updatedParams.numGenerations, newOracleParameters.numGenerations); + assertEq(updatedParams.numValidations, newOracleParameters.numValidations); + } + + function test_RevertWhen_UpgradeByNonOwner() external fund { + address _newImplementation = address(0x123); + + vm.expectRevert(abi.encodeWithSelector(OwnableUpgradeable.OwnableUnauthorizedAccount.selector, agentOwners[0])); + vm.startPrank(agentOwners[0]); + swan.upgradeToAndCall(_newImplementation, ""); + } +} diff --git a/test/script/Deploy.t.sol b/test/script/Deploy.t.sol index 85ca4ad..1ac14c5 100644 --- a/test/script/Deploy.t.sol +++ b/test/script/Deploy.t.sol @@ -1,38 +1,79 @@ // SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.20; -import {Deploy} from "../../script/Deploy.s.sol"; +import { + DeployAIAgentFactory, + DeployArtifactFactory, + DeployLLMOracleCoordinator, + DeployLLMOracleRegistry, + DeploySwan +} from "../../script/Deploy.s.sol"; import {HelperConfig} from "../../script/HelperConfig.s.sol"; import {Test} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; import {LLMOracleRegistry} from "@firstbatch/dria-oracle-contracts/LLMOracleRegistry.sol"; import {LLMOracleCoordinator} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; import {Swan} from "../../src/Swan.sol"; - -pragma solidity ^0.8.20; +import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; contract DeployTest is Test { - Deploy deployer; + DeployAIAgentFactory deployAgentFactory; + DeployArtifactFactory deployArtifactFactory; + DeployLLMOracleCoordinator deployLLMOracleCoordinator; + DeployLLMOracleRegistry deployLLMOracleRegistry; + DeploySwan deploySwan; + + address swanProxy; + address swanImpl; - LLMOracleCoordinator coordinator; - LLMOracleRegistry registry; - Swan swan; + address agentFactory; + address artifactFactory; + + address llmOracleCoordinatorProxy; + address llmOracleCoordinatorImpl; + + address llmOracleRegistryProxy; + address llmOracleRegistryImpl; function setUp() external { - deployer = new Deploy(); - deployer.run(); + deployAgentFactory = new DeployAIAgentFactory(); + agentFactory = deployAgentFactory.run(); + + deployArtifactFactory = new DeployArtifactFactory(); + artifactFactory = deployArtifactFactory.run(); + + deployLLMOracleRegistry = new DeployLLMOracleRegistry(); + (llmOracleRegistryProxy, llmOracleRegistryImpl) = deployLLMOracleRegistry.run(); + + deployLLMOracleCoordinator = new DeployLLMOracleCoordinator(); + (llmOracleCoordinatorProxy, llmOracleCoordinatorImpl) = deployLLMOracleCoordinator.run(); + + deploySwan = new DeploySwan(); + (swanProxy, swanImpl) = deploySwan.run(); } modifier deployed() { - registry = deployer.oracleRegistry(); - coordinator = deployer.oracleCoordinator(); - swan = deployer.swan(); + // check deployed addresses are not zero + require(agentFactory != address(0), "AgentFactory not deployed"); + require(artifactFactory != address(0), "ArtifactFactory not deployed"); + + require(llmOracleRegistryProxy != address(0), "LLMOracleRegistry not deployed"); + require(llmOracleRegistryImpl != address(0), "LLMOracleRegistry implementation not deployed"); + + require(llmOracleCoordinatorProxy != address(0), "LLMOracleCoordinator not deployed"); + require(llmOracleCoordinatorImpl != address(0), "LLMOracleCoordinator implementation not deployed"); + + require(swanProxy != address(0), "Swan not deployed"); + require(swanImpl != address(0), "Swan implementation not deployed"); - assert(address(registry) != address(0)); - assert(address(swan) != address(0)); - assert(address(coordinator) != address(0)); + // check if implementations are correct + address expectedRegistryImpl = Upgrades.getImplementationAddress(llmOracleRegistryProxy); + address expectedCoordinatorImpl = Upgrades.getImplementationAddress(llmOracleCoordinatorProxy); + address expectedSwanImpl = Upgrades.getImplementationAddress(swanProxy); - assert(coordinator.registry() == registry); - assert(swan.coordinator() == coordinator); + require(llmOracleRegistryImpl == expectedRegistryImpl, "LLMOracleRegistry implementation mismatch"); + require(llmOracleCoordinatorImpl == expectedCoordinatorImpl, "LLMOracleCoordinator implementation mismatch"); + require(swanImpl == expectedSwanImpl, "Swan implementation mismatch"); _; } From 80acb68ef72b450e2a7051c707cd7b66a037b819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fla=20=C3=87elik?= Date: Mon, 16 Dec 2024 10:05:46 +0300 Subject: [PATCH 2/2] removed old docs --- docs/src/src/AIAgent.sol/contract.AIAgent.md | 2 +- .../AIAgent.sol/contract.AIAgentFactory.md | 2 +- .../src/src/Artifact.sol/contract.Artifact.md | 2 +- .../Artifact.sol/contract.ArtifactFactory.md | 2 +- .../src/BuyerAgent.sol/contract.BuyerAgent.md | 534 ------------------ .../contract.BuyerAgentFactory.md | 22 - docs/src/src/Swan.sol/constants.Swan.md | 2 +- docs/src/src/Swan.sol/contract.Swan.md | 2 +- .../src/SwanAsset.sol/contract.SwanAsset.md | 40 -- .../contract.SwanAssetFactory.md | 20 - .../SwanManager.sol/abstract.SwanManager.md | 2 +- .../struct.SwanMarketParameters.md | 2 +- .../src/mock/SvanV2.sol/contract.SwanV2.md | 2 +- 13 files changed, 9 insertions(+), 625 deletions(-) delete mode 100644 docs/src/src/BuyerAgent.sol/contract.BuyerAgent.md delete mode 100644 docs/src/src/BuyerAgent.sol/contract.BuyerAgentFactory.md delete mode 100644 docs/src/src/SwanAsset.sol/contract.SwanAsset.md delete mode 100644 docs/src/src/SwanAsset.sol/contract.SwanAssetFactory.md diff --git a/docs/src/src/AIAgent.sol/contract.AIAgent.md b/docs/src/src/AIAgent.sol/contract.AIAgent.md index 9c866d5..79bd2e2 100644 --- a/docs/src/src/AIAgent.sol/contract.AIAgent.md +++ b/docs/src/src/AIAgent.sol/contract.AIAgent.md @@ -1,5 +1,5 @@ # AIAgent -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/AIAgent.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/feb8dd64d672a341a29a0a52b12cc56adf09c996/src/AIAgent.sol) **Inherits:** Ownable diff --git a/docs/src/src/AIAgent.sol/contract.AIAgentFactory.md b/docs/src/src/AIAgent.sol/contract.AIAgentFactory.md index 8484257..fb18bbc 100644 --- a/docs/src/src/AIAgent.sol/contract.AIAgentFactory.md +++ b/docs/src/src/AIAgent.sol/contract.AIAgentFactory.md @@ -1,5 +1,5 @@ # AIAgentFactory -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/AIAgent.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/feb8dd64d672a341a29a0a52b12cc56adf09c996/src/AIAgent.sol) Factory contract to deploy AIAgent contracts. diff --git a/docs/src/src/Artifact.sol/contract.Artifact.md b/docs/src/src/Artifact.sol/contract.Artifact.md index e203ddd..ce053b9 100644 --- a/docs/src/src/Artifact.sol/contract.Artifact.md +++ b/docs/src/src/Artifact.sol/contract.Artifact.md @@ -1,5 +1,5 @@ # Artifact -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/Artifact.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/feb8dd64d672a341a29a0a52b12cc56adf09c996/src/Artifact.sol) **Inherits:** ERC721, Ownable diff --git a/docs/src/src/Artifact.sol/contract.ArtifactFactory.md b/docs/src/src/Artifact.sol/contract.ArtifactFactory.md index 12a834a..6d23f87 100644 --- a/docs/src/src/Artifact.sol/contract.ArtifactFactory.md +++ b/docs/src/src/Artifact.sol/contract.ArtifactFactory.md @@ -1,5 +1,5 @@ # ArtifactFactory -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/Artifact.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/feb8dd64d672a341a29a0a52b12cc56adf09c996/src/Artifact.sol) Factory contract to deploy Artifact tokens. diff --git a/docs/src/src/BuyerAgent.sol/contract.BuyerAgent.md b/docs/src/src/BuyerAgent.sol/contract.BuyerAgent.md deleted file mode 100644 index 75b8ccd..0000000 --- a/docs/src/src/BuyerAgent.sol/contract.BuyerAgent.md +++ /dev/null @@ -1,534 +0,0 @@ -# BuyerAgent -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/BuyerAgent.sol) - -**Inherits:** -Ownable - -BuyerAgent is responsible for buying the assets from Swan. - - -## State Variables -### swan -Swan contract. - - -```solidity -Swan public immutable swan; -``` - - -### createdAt -Timestamp when the contract is deployed. - - -```solidity -uint256 public immutable createdAt; -``` - - -### marketParameterIdx -Holds the index of the Swan market parameters at the time of deployment. - -*When calculating the round, we will use this index to determine the start interval.* - - -```solidity -uint256 public immutable marketParameterIdx; -``` - - -### name -Buyer agent name. - - -```solidity -string public name; -``` - - -### description -Buyer agent description, can include backstory, behavior and objective together. - - -```solidity -string public description; -``` - - -### state -State of the buyer agent. - -*Only updated by the oracle via `updateState`.* - - -```solidity -bytes public state; -``` - - -### feeRoyalty -Royalty fees for the buyer agent. - - -```solidity -uint96 public feeRoyalty; -``` - - -### amountPerRound -The max amount of money the agent can spend per round. - - -```solidity -uint256 public amountPerRound; -``` - - -### inventory -The assets that the buyer agent has. - - -```solidity -mapping(uint256 round => address[] assets) public inventory; -``` - - -### spendings -Amount of money spent on each round. - - -```solidity -mapping(uint256 round => uint256 spending) public spendings; -``` - - -### oraclePurchaseRequests -Oracle requests for each round about item purchases. - -*A taskId of 0 means no request has been made.* - - -```solidity -mapping(uint256 round => uint256 taskId) public oraclePurchaseRequests; -``` - - -### oracleStateRequests -Oracle requests for each round about buyer state updates. - -*A taskId of 0 means no request has been made.* - -*A non-zero taskId means a request has been made, but not necessarily processed.* - -*To see if a task is completed, check `isOracleTaskProcessed`.* - - -```solidity -mapping(uint256 round => uint256 taskId) public oracleStateRequests; -``` - - -### isOracleRequestProcessed -Indicates whether a given task has been processed. - -*This is used to prevent double processing of the same task.* - - -```solidity -mapping(uint256 taskId => bool isProcessed) public isOracleRequestProcessed; -``` - - -## Functions -### onlyAuthorized - -Check if the caller is the owner, operator, or Swan. - -*Swan is an operator itself, so the first check handles that as well.* - - -```solidity -modifier onlyAuthorized(); -``` - -### constructor - -Create the buyer agent. - -*`_feeRoyalty` should be between 1 and maxBuyerAgentFee in the swan market parameters.* - -*All tokens are approved to the oracle coordinator of operator.* - - -```solidity -constructor( - string memory _name, - string memory _description, - uint96 _feeRoyalty, - uint256 _amountPerRound, - address _operator, - address _owner -) Ownable(_owner); -``` - -### minFundAmount - -The minimum amount of money that the buyer must leave within the contract. - -*minFundAmount should be `amountPerRound + oracleFee` to be able to make requests.* - - -```solidity -function minFundAmount() public view returns (uint256); -``` - -### oracleResult - -Reads the best performing result for a given task id, and parses it as an array of addresses. - - -```solidity -function oracleResult(uint256 taskId) public view returns (bytes memory); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`taskId`|`uint256`|task id to be read| - - -### oracleStateRequest - -Calls the LLMOracleCoordinator & pays for the oracle fees to make a state update request. - -*Works only in `Withdraw` phase.* - -*Calling again in the same round will overwrite the previous request. -The operator must check that there is no request in beforehand, -so to not overwrite an existing request of the owner.* - - -```solidity -function oracleStateRequest(bytes calldata _input, bytes calldata _models) external onlyAuthorized; -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`_input`|`bytes`|input to the LLMOracleCoordinator.| -|`_models`|`bytes`|models to be used for the oracle.| - - -### oraclePurchaseRequest - -Calls the LLMOracleCoordinator & pays for the oracle fees to make a purchase request. - -*Works only in `Buy` phase.* - -*Calling again in the same round will overwrite the previous request. -The operator must check that there is no request in beforehand, -so to not overwrite an existing request of the owner.* - - -```solidity -function oraclePurchaseRequest(bytes calldata _input, bytes calldata _models) external onlyAuthorized; -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`_input`|`bytes`|input to the LLMOracleCoordinator.| -|`_models`|`bytes`|models to be used for the oracle.| - - -### updateState - -Function to update the Buyer state. - -*Works only in `Withdraw` phase.* - -*Can be called multiple times within a single round, although is not expected to be done so.* - - -```solidity -function updateState() external onlyAuthorized; -``` - -### purchase - -Function to buy the asset from the Swan with the given assed address. - -*Works only in `Buy` phase.* - -*Can be called multiple times within a single round, although is not expected to be done so.* - -*This is not expected to revert if the oracle works correctly.* - - -```solidity -function purchase() external onlyAuthorized; -``` - -### withdraw - -Function to withdraw the tokens from the contract. - -*If the current phase is `Withdraw` buyer can withdraw any amount of tokens.* - -*If the current phase is not `Withdraw` buyer has to leave at least `minFundAmount` in the contract.* - - -```solidity -function withdraw(uint96 _amount) public onlyAuthorized; -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`_amount`|`uint96`|amount to withdraw.| - - -### treasury - -Alias to get the token balance of buyer agent. - - -```solidity -function treasury() public view returns (uint256); -``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|token balance| - - -### _checkRoundPhase - -Checks that we are in the given phase, and returns both round and phase. - - -```solidity -function _checkRoundPhase(Phase _phase) internal view returns (uint256, Phase); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`_phase`|`Phase`|expected phase.| - - -### _computeCycleTime - -Computes cycle time by using intervals from given market parameters. - -*Used in 'computePhase()' function.* - - -```solidity -function _computeCycleTime(SwanMarketParameters memory params) internal pure returns (uint256); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`params`|`SwanMarketParameters`|Market parameters of the Swan.| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|the total cycle time that is `listingInterval + buyInterval + withdrawInterval`.| - - -### _computePhase - -Function to compute the current round, phase and time until next phase w.r.t given market parameters. - - -```solidity -function _computePhase(SwanMarketParameters memory params, uint256 elapsedTime) - internal - pure - returns (uint256, Phase, uint256); -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`params`|`SwanMarketParameters`|Market parameters of the Swan.| -|`elapsedTime`|`uint256`|Time elapsed that computed in 'getRoundPhase()' according to the timestamps of each round.| - -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|round, phase, time until next phase| -|``|`Phase`|| -|``|`uint256`|| - - -### getRoundPhase - -Function to return the current round, elapsed round and the current phase according to the current time. - -*Each round is composed of three phases in order: Listing, Buy, Withdraw.* - -*Internally, it computes the intervals from market parameters at the creation of this agent, until now.* - -*If there are many parameter changes throughout the life of this agent, this may cost more GAS.* - - -```solidity -function getRoundPhase() public view returns (uint256, Phase, uint256); -``` -**Returns** - -|Name|Type|Description| -|----|----|-----------| -|``|`uint256`|round, phase, time until next phase| -|``|`Phase`|| -|``|`uint256`|| - - -### setFeeRoyalty - -Function to set feeRoyalty. - -*Only callable by the owner.* - -*Only callable in withdraw phase.* - - -```solidity -function setFeeRoyalty(uint96 newFeeRoyalty) public onlyOwner; -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`newFeeRoyalty`|`uint96`|must be between 1 and 100.| - - -### setAmountPerRound - -Function to set the amountPerRound. - -*Only callable by the owner.* - -*Only callable in withdraw phase.* - - -```solidity -function setAmountPerRound(uint256 _amountPerRound) external onlyOwner; -``` -**Parameters** - -|Name|Type|Description| -|----|----|-----------| -|`_amountPerRound`|`uint256`|new amountPerRound.| - - -## Events -### StateRequest -Emitted when a state update request is made. - - -```solidity -event StateRequest(uint256 indexed taskId, uint256 indexed round); -``` - -### PurchaseRequest -Emitted when a purchase request is made. - - -```solidity -event PurchaseRequest(uint256 indexed taskId, uint256 indexed round); -``` - -### Purchase -Emitted when a purchase is made. - - -```solidity -event Purchase(uint256 indexed taskId, uint256 indexed round); -``` - -### StateUpdate -Emitted when the state is updated. - - -```solidity -event StateUpdate(uint256 indexed taskId, uint256 indexed round); -``` - -## Errors -### MinFundSubceeded -The `value` is less than `minFundAmount` - - -```solidity -error MinFundSubceeded(uint256 value); -``` - -### InvalidFee -Given fee is invalid, e.g. not within the range. - - -```solidity -error InvalidFee(uint256 fee); -``` - -### BuyLimitExceeded -Asset count limit exceeded for this round - - -```solidity -error BuyLimitExceeded(uint256 have, uint256 want); -``` - -### InvalidPhase -Invalid phase - - -```solidity -error InvalidPhase(Phase have, Phase want); -``` - -### Unauthorized -Unauthorized caller. - - -```solidity -error Unauthorized(address caller); -``` - -### TaskNotRequested -No task request has been made yet. - - -```solidity -error TaskNotRequested(); -``` - -### TaskAlreadyProcessed -The task was already processed, via `purchase` or `updateState`. - - -```solidity -error TaskAlreadyProcessed(); -``` - -## Enums -### Phase -Phase of the purchase loop. - - -```solidity -enum Phase { - Listing, - Buy, - Withdraw -} -``` - diff --git a/docs/src/src/BuyerAgent.sol/contract.BuyerAgentFactory.md b/docs/src/src/BuyerAgent.sol/contract.BuyerAgentFactory.md deleted file mode 100644 index 69d79cc..0000000 --- a/docs/src/src/BuyerAgent.sol/contract.BuyerAgentFactory.md +++ /dev/null @@ -1,22 +0,0 @@ -# BuyerAgentFactory -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/BuyerAgent.sol) - -Factory contract to deploy BuyerAgent contracts. - -*This saves from contract space for Swan.* - - -## Functions -### deploy - - -```solidity -function deploy( - string memory _name, - string memory _description, - uint96 _feeRoyalty, - uint256 _amountPerRound, - address _owner -) external returns (BuyerAgent); -``` - diff --git a/docs/src/src/Swan.sol/constants.Swan.md b/docs/src/src/Swan.sol/constants.Swan.md index 970ca0b..549a937 100644 --- a/docs/src/src/Swan.sol/constants.Swan.md +++ b/docs/src/src/Swan.sol/constants.Swan.md @@ -1,5 +1,5 @@ # Constants -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/Swan.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/feb8dd64d672a341a29a0a52b12cc56adf09c996/src/Swan.sol) ### SwanAIAgentPurchaseOracleProtocol diff --git a/docs/src/src/Swan.sol/contract.Swan.md b/docs/src/src/Swan.sol/contract.Swan.md index 0d8b87c..71c2a50 100644 --- a/docs/src/src/Swan.sol/contract.Swan.md +++ b/docs/src/src/Swan.sol/contract.Swan.md @@ -1,5 +1,5 @@ # Swan -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/Swan.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/feb8dd64d672a341a29a0a52b12cc56adf09c996/src/Swan.sol) **Inherits:** [SwanManager](/src/SwanManager.sol/abstract.SwanManager.md), UUPSUpgradeable diff --git a/docs/src/src/SwanAsset.sol/contract.SwanAsset.md b/docs/src/src/SwanAsset.sol/contract.SwanAsset.md deleted file mode 100644 index 64bca15..0000000 --- a/docs/src/src/SwanAsset.sol/contract.SwanAsset.md +++ /dev/null @@ -1,40 +0,0 @@ -# SwanAsset -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/SwanAsset.sol) - -**Inherits:** -ERC721, Ownable - -SwanAsset is an ERC721 token with a single token supply. - - -## State Variables -### createdAt -Creation time of the token - - -```solidity -uint256 public createdAt; -``` - - -### description -Description of the token - - -```solidity -bytes public description; -``` - - -## Functions -### constructor - -Constructor sets properties of the token. - - -```solidity -constructor(string memory _name, string memory _symbol, bytes memory _description, address _owner, address _operator) - ERC721(_name, _symbol) - Ownable(_owner); -``` - diff --git a/docs/src/src/SwanAsset.sol/contract.SwanAssetFactory.md b/docs/src/src/SwanAsset.sol/contract.SwanAssetFactory.md deleted file mode 100644 index 03528ef..0000000 --- a/docs/src/src/SwanAsset.sol/contract.SwanAssetFactory.md +++ /dev/null @@ -1,20 +0,0 @@ -# SwanAssetFactory -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/SwanAsset.sol) - -Factory contract to deploy SwanAsset tokens. - -*This saves from contract space for Swan.* - - -## Functions -### deploy - -Deploys a new SwanAsset token. - - -```solidity -function deploy(string memory _name, string memory _symbol, bytes memory _description, address _owner) - external - returns (SwanAsset); -``` - diff --git a/docs/src/src/SwanManager.sol/abstract.SwanManager.md b/docs/src/src/SwanManager.sol/abstract.SwanManager.md index 3999c17..6d77972 100644 --- a/docs/src/src/SwanManager.sol/abstract.SwanManager.md +++ b/docs/src/src/SwanManager.sol/abstract.SwanManager.md @@ -1,5 +1,5 @@ # SwanManager -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/SwanManager.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/feb8dd64d672a341a29a0a52b12cc56adf09c996/src/SwanManager.sol) **Inherits:** OwnableUpgradeable diff --git a/docs/src/src/SwanManager.sol/struct.SwanMarketParameters.md b/docs/src/src/SwanManager.sol/struct.SwanMarketParameters.md index f8168fd..c341110 100644 --- a/docs/src/src/SwanManager.sol/struct.SwanMarketParameters.md +++ b/docs/src/src/SwanManager.sol/struct.SwanMarketParameters.md @@ -1,5 +1,5 @@ # SwanMarketParameters -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/SwanManager.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/feb8dd64d672a341a29a0a52b12cc56adf09c996/src/SwanManager.sol) Collection of market-related parameters. diff --git a/docs/src/src/mock/SvanV2.sol/contract.SwanV2.md b/docs/src/src/mock/SvanV2.sol/contract.SwanV2.md index fd4025e..0e27103 100644 --- a/docs/src/src/mock/SvanV2.sol/contract.SwanV2.md +++ b/docs/src/src/mock/SvanV2.sol/contract.SwanV2.md @@ -1,5 +1,5 @@ # SwanV2 -[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/6a4c427284ef9a1b566dad7645b1c42a55dd3690/src/mock/SvanV2.sol) +[Git Source](https://github.com/firstbatchxyz/swan-contracts/blob/feb8dd64d672a341a29a0a52b12cc56adf09c996/src/mock/SvanV2.sol) **Inherits:** [Swan](/src/Swan.sol/contract.Swan.md)