From c03d1a6de62989b61d24eab03d2b7f79d9d44b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fla=20=C3=87elik?= Date: Thu, 21 Nov 2024 15:26:50 +0300 Subject: [PATCH 1/3] used llm contracts as module --- .gitignore | 2 - .gitmodules | 3 + .vscode | 5 + LICENSE | 201 +++++++++ Makefile | 2 +- contracts/libraries/Statistics.sol | 48 -- contracts/llm/LLMOracleCoordinator.sol | 423 ------------------ contracts/llm/LLMOracleManager.sol | 153 ------- contracts/llm/LLMOracleRegistry.sol | 149 ------ contracts/llm/LLMOracleTask.sol | 81 ---- contracts/mock/LLMOracleCoordinatorV2.sol | 10 - contracts/mock/LLMOracleRegistryV2.sol | 10 - foundry.toml | 8 +- lcov.info | 71 +-- lib/dria-oracle-contracts | 1 + lib/forge-std | 2 +- lib/openzeppelin-contracts | 2 +- lib/openzeppelin-contracts-upgradeable | 2 +- script/Deploy.s.sol | 69 +-- script/HelperConfig.s.sol | 8 +- {contracts/swan => src}/BuyerAgent.sol | 6 +- {contracts/swan => src}/Swan.sol | 53 ++- {contracts/swan => src}/SwanAsset.sol | 0 {contracts/swan => src}/SwanManager.sol | 18 +- .../mock/SwanV2.sol => src/mock/SvanV2.sol | 2 +- test/BuyerAgent.t.sol | 18 +- test/Deploy.t.sol | 10 +- test/Helper.t.sol | 24 +- test/LLMOracleCoordinator.t.sol | 239 ---------- test/LLMOracleRegistry.t.sol | 156 ------- test/Swan.t.sol | 19 +- test/SwanIntervals.t.sol | 19 +- {contracts/token => test}/WETH9.sol | 1 + 33 files changed, 392 insertions(+), 1423 deletions(-) create mode 100644 .vscode create mode 100644 LICENSE delete mode 100644 contracts/libraries/Statistics.sol delete mode 100644 contracts/llm/LLMOracleCoordinator.sol delete mode 100644 contracts/llm/LLMOracleManager.sol delete mode 100644 contracts/llm/LLMOracleRegistry.sol delete mode 100644 contracts/llm/LLMOracleTask.sol delete mode 100644 contracts/mock/LLMOracleCoordinatorV2.sol delete mode 100644 contracts/mock/LLMOracleRegistryV2.sol create mode 160000 lib/dria-oracle-contracts rename {contracts/swan => src}/BuyerAgent.sol (98%) rename {contracts/swan => src}/Swan.sol (94%) rename {contracts/swan => src}/SwanAsset.sol (100%) rename {contracts/swan => src}/SwanManager.sol (85%) rename contracts/mock/SwanV2.sol => src/mock/SvanV2.sol (82%) delete mode 100644 test/LLMOracleCoordinator.t.sol delete mode 100644 test/LLMOracleRegistry.t.sol rename {contracts/token => test}/WETH9.sol (99%) diff --git a/.gitignore b/.gitignore index 9128398..0afabf1 100644 --- a/.gitignore +++ b/.gitignore @@ -20,9 +20,7 @@ docs/ # Forge files cache/ out/ -lib/ storage/ -lcov.info/ # gas snapshot .gas-snapshot/ diff --git a/.gitmodules b/.gitmodules index 56bfede..8257fef 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/dria-oracle-contracts"] + path = lib/dria-oracle-contracts + url = https://github.com/firstbatchxyz/dria-oracle-contracts diff --git a/.vscode b/.vscode new file mode 100644 index 0000000..2ce1d21 --- /dev/null +++ b/.vscode @@ -0,0 +1,5 @@ +{ + "solidity.packageDefaultDependenciesContractsDirectory": "src", + "solidity.packageDefaultDependenciesDirectory": "lib", + "editor.formatOnSave": true +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0589540 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2023 FirstBatch + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/Makefile b/Makefile index 873b741..a3bcd12 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ endif # Install Dependencies install: - forge install foundry-rs/forge-std --no-commit && forge install OpenZeppelin/openzeppelin-contracts --no-commit && forge install OpenZeppelin/openzeppelin-foundry-upgrades --no-commit && forge install OpenZeppelin/openzeppelin-contracts-upgradeable --no-commit + forge install foundry-rs/forge-std --no-commit && forge install firstbatchxyz/dria-oracle-contracts --no-commit && forge install OpenZeppelin/openzeppelin-contracts --no-commit && forge install OpenZeppelin/openzeppelin-foundry-upgrades --no-commit && forge install OpenZeppelin/openzeppelin-contracts-upgradeable --no-commit # Build the contracts build: diff --git a/contracts/libraries/Statistics.sol b/contracts/libraries/Statistics.sol deleted file mode 100644 index 8c53643..0000000 --- a/contracts/libraries/Statistics.sol +++ /dev/null @@ -1,48 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -/// @notice Simple statistic library for uint256 arrays, numbers are treat as fixed-precision floats. -library Statistics { - /// @notice Compute the mean of the data. - /// @param data The data to compute the mean for. - function avg(uint256[] memory data) internal pure returns (uint256 ans) { - uint256 sum = 0; - for (uint256 i = 0; i < data.length; i++) { - sum += data[i]; - } - ans = sum / data.length; - } - - /// @notice Compute the variance of the data. - /// @param data The data to compute the variance for. - function variance(uint256[] memory data) internal pure returns (uint256 ans, uint256 mean) { - mean = avg(data); - uint256 sum = 0; - for (uint256 i = 0; i < data.length; i++) { - uint256 diff = data[i] - mean; - sum += diff * diff; - } - ans = sum / data.length; - } - - /// @notice Compute the standard deviation of the data. - /// @dev Computes variance, and takes the square root. - /// @param data The data to compute the standard deviation for. - function stddev(uint256[] memory data) internal pure returns (uint256 ans, uint256 mean) { - (uint256 _variance, uint256 _mean) = variance(data); - mean = _mean; - ans = sqrt(_variance); - } - - /// @notice Compute the square root of a number. - /// @dev Uses Babylonian method. - /// @param x The number to compute the square root for. - function sqrt(uint256 x) internal pure returns (uint256 y) { - uint256 z = (x + 1) / 2; - y = x; - while (z < y) { - y = z; - z = (x / z + z) / 2; - } - } -} diff --git a/contracts/llm/LLMOracleCoordinator.sol b/contracts/llm/LLMOracleCoordinator.sol deleted file mode 100644 index fd24731..0000000 --- a/contracts/llm/LLMOracleCoordinator.sol +++ /dev/null @@ -1,423 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -import {LLMOracleRegistry, LLMOracleKind} from "./LLMOracleRegistry.sol"; -import {LLMOracleTask, LLMOracleTaskParameters} from "./LLMOracleTask.sol"; -import {LLMOracleManager} from "./LLMOracleManager.sol"; -import {Statistics} from "../libraries/Statistics.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; - -/// @title LLM Oracle Coordinator -/// @notice Responsible for coordinating the Oracle responses to LLM generation requests. -contract LLMOracleCoordinator is LLMOracleTask, LLMOracleManager, UUPSUpgradeable { - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - /// @notice Indicates a generation request for LLM. - /// @dev `protocol` is a short 32-byte string (e.g., "dria/1.0.0"). - /// @dev Using the protocol topic, listeners can filter by protocol. - event Request(uint256 indexed taskId, address indexed requester, bytes32 indexed protocol); - - /// @notice Indicates a single Oracle response for a request. - event Response(uint256 indexed taskId, address indexed responder); - - /// @notice Indicates a single Oracle response for a request. - event Validation(uint256 indexed taskId, address indexed validator); - - /// @notice Indicates the status change of an LLM generation request. - event StatusUpdate( - uint256 indexed taskId, bytes32 indexed protocol, TaskStatus statusBefore, TaskStatus statusAfter - ); - - /*////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - - /// @notice Not enough funds were provided for the task. - error InsufficientFees(uint256 have, uint256 want); - - /// @notice Unexpected status for this task. - error InvalidTaskStatus(uint256 taskId, TaskStatus have, TaskStatus want); - - /// @notice The given nonce is not a valid proof-of-work. - error InvalidNonce(uint256 taskId, uint256 nonce); - - /// @notice The provided validation does not have a score for all responses. - error InvalidValidation(uint256 taskId, address validator); - - /// @notice The oracle is not registered. - error NotRegistered(address oracle); - - /// @notice The oracle has already responded to this task. - error AlreadyResponded(uint256 taskId, address oracle); - - /*////////////////////////////////////////////////////////////// - STORAGE - //////////////////////////////////////////////////////////////*/ - - /// @notice The Oracle Registry. - LLMOracleRegistry public registry; - /// @notice The token to be used for fee payments. - ERC20 public feeToken; - - /// @notice The task ID counter. - /// @dev TaskId starts from 1, as 0 is reserved. - /// @dev 0 can be used in to check that a request/response/validation has not been made. - uint256 public nextTaskId; - /// @notice LLM generation requests. - mapping(uint256 taskId => TaskRequest) public requests; - /// @notice LLM generation responses. - mapping(uint256 taskId => TaskResponse[]) public responses; - /// @notice LLM generation response validations. - mapping(uint256 taskId => TaskValidation[]) public validations; - - /*////////////////////////////////////////////////////////////// - MODIFIERS - //////////////////////////////////////////////////////////////*/ - - /// @notice Reverts if `msg.sender` is not a registered oracle. - modifier onlyRegistered(LLMOracleKind kind) { - if (!registry.isRegistered(msg.sender, kind)) { - revert NotRegistered(msg.sender); - } - _; - } - - /// @notice Reverts if the task status is not `status`. - modifier onlyAtStatus(uint256 taskId, TaskStatus status) { - if (requests[taskId].status != status) { - revert InvalidTaskStatus(taskId, requests[taskId].status, status); - } - _; - } - - /*////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////*/ - - /// @notice Locks the contract, preventing any future re-initialization. - /// @dev [See more](https://docs.openzeppelin.com/contracts/5.x/api/proxy#Initializable-_disableInitializers--). - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - - /*////////////////////////////////////////////////////////////// - UPGRADABLE - //////////////////////////////////////////////////////////////*/ - - /// @notice Function that should revert when `msg.sender` is not authorized to upgrade the contract. - /// @dev Called by and upgradeToAndCall. - function _authorizeUpgrade(address newImplementation) internal override onlyOwner { - // `onlyOwner` modifier does the auth here - } - - /// @notice Initialize the contract. - /// @notice Sets the Oracle Registry & Oracle Fee Manager. - /// @param _oracleRegistry The Oracle Registry contract address. - /// @param _feeToken The token (ERC20) to be used for fee payments (usually $BATCH). - /// @param _platformFee The initial platform fee for each LLM generation. - /// @param _generationFee The initial base fee for LLM generation. - /// @param _validationFee The initial base fee for response validation. - function initialize( - address _oracleRegistry, - address _feeToken, - uint256 _platformFee, - uint256 _generationFee, - uint256 _validationFee - ) public initializer { - __LLMOracleManager_init(_platformFee, _generationFee, _validationFee); - registry = LLMOracleRegistry(_oracleRegistry); - feeToken = ERC20(_feeToken); - nextTaskId = 1; - } - - /*////////////////////////////////////////////////////////////// - LOGIC - //////////////////////////////////////////////////////////////*/ - - /// @notice Request LLM generation. - /// @dev Input must be non-empty. - /// @dev Reverts if contract has not enough allowance for the fee. - /// @dev Reverts if difficulty is out of range. - /// @param protocol The protocol string, should be a short 32-byte string (e.g., "dria/1.0.0"). - /// @param input The input data for the LLM generation. - /// @param parameters The task parameters - /// @return task id - function request( - bytes32 protocol, - bytes memory input, - bytes memory models, - LLMOracleTaskParameters calldata parameters - ) public onlyValidParameters(parameters) returns (uint256) { - (uint256 totalfee, uint256 generatorFee, uint256 validatorFee) = getFee(parameters); - - // check allowance requirements - uint256 allowance = feeToken.allowance(msg.sender, address(this)); - if (allowance < totalfee) { - revert InsufficientFees(allowance, totalfee); - } - - // ensure there is enough balance - uint256 balance = feeToken.balanceOf(msg.sender); - if (balance < totalfee) { - revert InsufficientFees(balance, totalfee); - } - - // transfer tokens - feeToken.transferFrom(msg.sender, address(this), totalfee); - - // increment the task id for later tasks & emit task request event - uint256 taskId = nextTaskId; - unchecked { - ++nextTaskId; - } - emit Request(taskId, msg.sender, protocol); - - // push request & emit status update for the task - requests[taskId] = TaskRequest({ - requester: msg.sender, - protocol: protocol, - input: input, - parameters: parameters, - status: TaskStatus.PendingGeneration, - generatorFee: generatorFee, - validatorFee: validatorFee, - platformFee: platformFee, - models: models - }); - emit StatusUpdate(taskId, protocol, TaskStatus.None, TaskStatus.PendingGeneration); - - return taskId; - } - - /// @notice Respond to an LLM generation. - /// @dev Output must be non-empty. - /// @dev Reverts if the task is not pending generation. - /// @dev Reverts if the responder is not registered. - /// @dev Reverts if the responder has already responded to this task. - /// @dev Reverts if the nonce is not a valid proof-of-work. - /// @param taskId The task ID to respond to. - /// @param nonce The proof-of-work nonce. - /// @param output The output data for the LLM generation. - /// @param metadata Optional metadata for this output. - function respond(uint256 taskId, uint256 nonce, bytes calldata output, bytes calldata metadata) - public - onlyRegistered(LLMOracleKind.Generator) - onlyAtStatus(taskId, TaskStatus.PendingGeneration) - { - TaskRequest storage task = requests[taskId]; - - // ensure responder to be unique for this task - for (uint256 i = 0; i < responses[taskId].length; i++) { - if (responses[taskId][i].responder == msg.sender) { - revert AlreadyResponded(taskId, msg.sender); - } - } - - // check nonce (proof-of-work) - assertValidNonce(taskId, task, nonce); - - // push response - TaskResponse memory response = - TaskResponse({responder: msg.sender, nonce: nonce, output: output, metadata: metadata, score: 0}); - responses[taskId].push(response); - - // emit response events - emit Response(taskId, msg.sender); - - // send rewards to the generator if there is no validation - if (task.parameters.numValidations == 0) { - _increaseAllowance(msg.sender, task.generatorFee); - } - - // check if we have received enough responses & update task status - bool isCompleted = responses[taskId].length == uint256(task.parameters.numGenerations); - if (isCompleted) { - if (task.parameters.numValidations == 0) { - // no validations required, task is completed - task.status = TaskStatus.Completed; - emit StatusUpdate(taskId, task.protocol, TaskStatus.PendingGeneration, TaskStatus.Completed); - } else { - // now we are waiting for validations - task.status = TaskStatus.PendingValidation; - emit StatusUpdate(taskId, task.protocol, TaskStatus.PendingGeneration, TaskStatus.PendingValidation); - } - } - } - - /// @notice Validate requests for a given taskId. - /// @dev Reverts if the task is not pending validation. - /// @dev Reverts if the number of scores is not equal to the number of generations. - /// @dev Reverts if any score is greater than the maximum score. - /// @param taskId The ID of the task to validate. - /// @param nonce The proof-of-work nonce. - /// @param scores The validation scores for each generation. - /// @param metadata Optional metadata for this validation. - function validate(uint256 taskId, uint256 nonce, uint256[] calldata scores, bytes calldata metadata) - public - onlyRegistered(LLMOracleKind.Validator) - onlyAtStatus(taskId, TaskStatus.PendingValidation) - { - TaskRequest storage task = requests[taskId]; - - // ensure there is a score for each generation - if (scores.length != task.parameters.numGenerations) { - revert InvalidValidation(taskId, msg.sender); - } - - // ensure validator did not participate in generation - for (uint256 i = 0; i < task.parameters.numGenerations; i++) { - if (responses[taskId][i].responder == msg.sender) { - revert AlreadyResponded(taskId, msg.sender); - } - } - - // ensure validator to be unique for this task - for (uint256 i = 0; i < validations[taskId].length; i++) { - if (validations[taskId][i].validator == msg.sender) { - revert AlreadyResponded(taskId, msg.sender); - } - } - - // check nonce (proof-of-work) - assertValidNonce(taskId, task, nonce); - - // update validation scores - validations[taskId].push( - TaskValidation({scores: scores, nonce: nonce, metadata: metadata, validator: msg.sender}) - ); - - // emit validation event - emit Validation(taskId, msg.sender); - - // update completion status - bool isCompleted = validations[taskId].length == task.parameters.numValidations; - if (isCompleted) { - task.status = TaskStatus.Completed; - emit StatusUpdate(taskId, task.protocol, TaskStatus.PendingValidation, TaskStatus.Completed); - - // finalize validation scores - finalizeValidation(taskId); - } - } - - /// @notice Checks that proof-of-work is valid for a given task with taskId and nonce. - /// @dev Reverts if the nonce is not a valid proof-of-work. - /// @param taskId The ID of the task to check proof-of-work. - /// @param task The task (in storage) to validate. - /// @param nonce The candidate proof-of-work nonce. - function assertValidNonce(uint256 taskId, TaskRequest storage task, uint256 nonce) internal view { - bytes memory message = abi.encodePacked(taskId, task.input, task.requester, msg.sender, nonce); - if (uint256(keccak256(message)) > type(uint256).max >> uint256(task.parameters.difficulty)) { - revert InvalidNonce(taskId, nonce); - } - } - - /// @notice Compute the validation scores for a given task. - /// @dev Reverts if the task has no validations. - /// @param taskId The ID of the task to compute scores for. - function finalizeValidation(uint256 taskId) private { - TaskRequest storage task = requests[taskId]; - - // compute score for each generation - for (uint256 g_i = 0; g_i < task.parameters.numGenerations; g_i++) { - // get the scores for this generation, i.e. the g_i-th element of each validation - uint256[] memory scores = new uint256[](task.parameters.numValidations); - for (uint256 v_i = 0; v_i < task.parameters.numValidations; v_i++) { - scores[v_i] = validations[taskId][v_i].scores[g_i]; - } - - // compute the mean and standard deviation - (uint256 _stddev, uint256 _mean) = Statistics.stddev(scores); - - // compute the score for this generation as the "inner-mean" - // and send rewards to validators that are within the range - uint256 innerSum = 0; - uint256 innerCount = 0; - for (uint256 v_i = 0; v_i < task.parameters.numValidations; ++v_i) { - uint256 score = scores[v_i]; - if ((score >= _mean - _stddev) && (score <= _mean + _stddev)) { - innerSum += score; - innerCount++; - - // send validation fee to the validator - _increaseAllowance(validations[taskId][v_i].validator, task.validatorFee); - } - } - - // set score for this generation as the average of inner scores - uint256 inner_score = innerCount == 0 ? 0 : innerSum / innerCount; - responses[taskId][g_i].score = inner_score; - } - - // now, we have the scores for each generation - // compute stddev for these and pick the ones above a threshold - uint256[] memory generationScores = new uint256[](task.parameters.numGenerations); - for (uint256 g_i = 0; g_i < task.parameters.numGenerations; g_i++) { - generationScores[g_i] = responses[taskId][g_i].score; - } - - // compute the mean and standard deviation - (uint256 stddev, uint256 mean) = Statistics.stddev(generationScores); - for (uint256 g_i = 0; g_i < task.parameters.numGenerations; g_i++) { - // ignore lower outliers - if (generationScores[g_i] >= mean - generationDeviationFactor * stddev) { - _increaseAllowance(responses[taskId][g_i].responder, task.generatorFee); - } - } - } - - /// @notice Withdraw the platform fees & along with remaining fees within the contract. - function withdrawPlatformFees() public onlyOwner { - feeToken.transfer(owner(), feeToken.balanceOf(address(this))); - } - - /// @notice Returns the responses to a given taskId. - /// @param taskId The ID of the task to get responses for. - /// @return The responses for the given taskId. - function getResponses(uint256 taskId) public view returns (TaskResponse[] memory) { - return responses[taskId]; - } - - /// @notice Returns the validations to a given taskId. - /// @param taskId The ID of the task to get validations for. - /// @return The validations for the given taskId. - function getValidations(uint256 taskId) public view returns (TaskValidation[] memory) { - return validations[taskId]; - } - - /// Increases the allowance by setting the approval to the sum of the current allowance and the additional amount. - /// @param spender spender address - /// @param amount additional amount of allowance - function _increaseAllowance(address spender, uint256 amount) internal { - feeToken.approve(spender, feeToken.allowance(address(this), spender) + amount); - } - - /// @notice Returns the best performing result of the given task. - /// @dev For invalid task IDs, the status check will fail. - /// @param taskId The ID of the task to get the result for. - /// @return The best performing response w.r.t validation scores. - function getBestResponse(uint256 taskId) external view returns (TaskResponse memory) { - TaskResponse[] storage taskResponses = responses[taskId]; - - // ensure that task is completed - if (requests[taskId].status != LLMOracleTask.TaskStatus.Completed) { - revert InvalidTaskStatus(taskId, requests[taskId].status, LLMOracleTask.TaskStatus.Completed); - } - - // pick the result with the highest validation score - TaskResponse storage result = taskResponses[0]; - uint256 highestScore = result.score; - for (uint256 i = 1; i < taskResponses.length; i++) { - if (taskResponses[i].score > highestScore) { - highestScore = taskResponses[i].score; - result = taskResponses[i]; - } - } - - return result; - } -} diff --git a/contracts/llm/LLMOracleManager.sol b/contracts/llm/LLMOracleManager.sol deleted file mode 100644 index bfbdcf8..0000000 --- a/contracts/llm/LLMOracleManager.sol +++ /dev/null @@ -1,153 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {LLMOracleTaskParameters} from "./LLMOracleTask.sol"; - -/// @title LLM Oracle Manager -/// @notice Holds the configuration for the LLM Oracle, such as allowed bounds on difficulty, -/// number of generations & validations, and fee settings. - -contract LLMOracleManager is OwnableUpgradeable { - /*////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - - /// @notice Given parameter is out of range. - error InvalidParameterRange(uint256 have, uint256 min, uint256 max); - - /*////////////////////////////////////////////////////////////// - STORAGE - //////////////////////////////////////////////////////////////*/ - - /// @notice A fixed fee paid for the platform. - uint256 public platformFee; - /// @notice The base fee factor for a generation of LLM generation. - /// @dev When scaled with difficulty & number of generations, we denote it as `generatorFee`. - uint256 public generationFee; - /// @notice The base fee factor for a generation of LLM validation. - /// @dev When scaled with difficulty & number of validations, we denote it as `validatorFee`. - uint256 public validationFee; - - /// @notice The deviation factor for the validation scores. - uint64 public validationDeviationFactor; - /// @notice The deviation factor for the generation scores. - uint64 public generationDeviationFactor; - - /// @notice Minimums for oracle parameters. - LLMOracleTaskParameters minimumParameters; - /// @notice Maximums for oracle parameters. - LLMOracleTaskParameters maximumParameters; - - /*////////////////////////////////////////////////////////////// - UPGRADABLE - //////////////////////////////////////////////////////////////*/ - - /// @notice Initialize the contract. - function __LLMOracleManager_init(uint256 _platformFee, uint256 _generationFee, uint256 _validationFee) - internal - onlyInitializing - { - __Ownable_init(msg.sender); - __LLMOracleManager_init_unchained(_platformFee, _generationFee, _validationFee); - } - - function __LLMOracleManager_init_unchained(uint256 _platformFee, uint256 _generationFee, uint256 _validationFee) - internal - onlyInitializing - { - minimumParameters = LLMOracleTaskParameters({difficulty: 1, numGenerations: 1, numValidations: 0}); - maximumParameters = LLMOracleTaskParameters({difficulty: 10, numGenerations: 10, numValidations: 10}); - - validationDeviationFactor = 2; - generationDeviationFactor = 1; - - setFees(_platformFee, _generationFee, _validationFee); - } - - /*////////////////////////////////////////////////////////////// - LOGIC - //////////////////////////////////////////////////////////////*/ - - /// @notice Modifier to check if the given parameters are within the allowed range. - modifier onlyValidParameters(LLMOracleTaskParameters calldata parameters) { - if ( - parameters.difficulty < minimumParameters.difficulty || parameters.difficulty > maximumParameters.difficulty - ) { - revert InvalidParameterRange( - parameters.difficulty, minimumParameters.difficulty, maximumParameters.difficulty - ); - } - - if ( - parameters.numGenerations < minimumParameters.numGenerations - || parameters.numGenerations > maximumParameters.numGenerations - ) { - revert InvalidParameterRange( - parameters.numGenerations, minimumParameters.numGenerations, maximumParameters.numGenerations - ); - } - - if ( - parameters.numValidations < minimumParameters.numValidations - || parameters.numValidations > maximumParameters.numValidations - ) { - revert InvalidParameterRange( - parameters.numValidations, minimumParameters.numValidations, maximumParameters.numValidations - ); - } - _; - } - - /// @notice Update Oracle fees. - /// @dev To keep a fee unchanged, provide the same value. - /// @param _platformFee The new platform fee - /// @param _generationFee The new generation fee - /// @param _validationFee The new validation fee - function setFees(uint256 _platformFee, uint256 _generationFee, uint256 _validationFee) public onlyOwner { - platformFee = _platformFee; - generationFee = _generationFee; - validationFee = _validationFee; - } - - /// @notice Get the total fee for a given task setting. - /// @param parameters The task parameters. - /// @return totalFee The total fee for the task. - /// @return generatorFee The fee paid to each generator per generation. - /// @return validatorFee The fee paid to each validator per validated generation. - function getFee(LLMOracleTaskParameters calldata parameters) - public - view - returns (uint256 totalFee, uint256 generatorFee, uint256 validatorFee) - { - uint256 diff = (2 << uint256(parameters.difficulty)); - generatorFee = diff * generationFee; - validatorFee = diff * validationFee; - totalFee = - platformFee + (parameters.numGenerations * (generatorFee + (parameters.numValidations * validatorFee))); - } - - /// @notice Update Oracle parameters bounds. - /// @dev Provide the same value to keep it unchanged. - /// @param minimums The new minimum parameters. - /// @param maximums The new maximum parameters. - function setParameters(LLMOracleTaskParameters calldata minimums, LLMOracleTaskParameters calldata maximums) - public - onlyOwner - { - minimumParameters = minimums; - maximumParameters = maximums; - } - - /// @notice Update deviation factors. - /// @dev Provide the same value to keep it unchanged. - /// @param _generationDeviationFactor The new generation deviation factor. - /// @param _validationDeviationFactor The new validation deviation factor. - function setDeviationFactors(uint64 _generationDeviationFactor, uint64 _validationDeviationFactor) - public - onlyOwner - { - generationDeviationFactor = _generationDeviationFactor; - validationDeviationFactor = _validationDeviationFactor; - } -} diff --git a/contracts/llm/LLMOracleRegistry.sol b/contracts/llm/LLMOracleRegistry.sol deleted file mode 100644 index a585d06..0000000 --- a/contracts/llm/LLMOracleRegistry.sol +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; - -/// @notice The type of Oracle. -enum LLMOracleKind { - Generator, - Validator -} - -/// @title LLM Oracle Registry -/// @notice Holds the addresses that are eligible to respond to LLM requests. -/// @dev There may be several types of oracle kinds, and each require their own stake. -contract LLMOracleRegistry is OwnableUpgradeable, UUPSUpgradeable { - /*////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - - /// @notice The Oracle response to an LLM generation request. - event Registered(address indexed, LLMOracleKind kind); - - /// @notice The Oracle response to an LLM generation request. - event Unregistered(address indexed, LLMOracleKind kind); - - /*////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - - /// @notice The user is not registered. - error NotRegistered(address); - - /// @notice The user is already registered. - error AlreadyRegistered(address); - - /// @notice Insufficient stake amount during registration. - error InsufficientFunds(); - - /*////////////////////////////////////////////////////////////// - STORAGE - //////////////////////////////////////////////////////////////*/ - - /// @notice Stake amount to be registered as an Oracle that can serve generation requests. - uint256 public generatorStakeAmount; - - /// @notice Stake amount to be registered as an Oracle that can serve validation requests. - uint256 public validatorStakeAmount; - - /// @notice Registrations per address & kind. If amount is 0, it is not registered. - mapping(address oracle => mapping(LLMOracleKind => uint256 amount)) public registrations; - - /// @notice Token used for staking. - ERC20 public token; - - /*////////////////////////////////////////////////////////////// - CONSTRUCTOR - //////////////////////////////////////////////////////////////*/ - - /// @notice Locks the contract, preventing any future re-initialization. - /// @dev [See more](https://docs.openzeppelin.com/contracts/5.x/api/proxy#Initializable-_disableInitializers--). - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - - /*////////////////////////////////////////////////////////////// - UPGRADABLE - //////////////////////////////////////////////////////////////*/ - - /// @notice Function that should revert when `msg.sender` is not authorized to upgrade the contract. - /// @dev Called by and upgradeToAndCall. - function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} - - /// @dev Sets the owner to be the deployer, sets initial stake amount. - function initialize(uint256 _generatorStakeAmount, uint256 _validatorStakeAmount, address _token) - public - initializer - { - __Ownable_init(msg.sender); - generatorStakeAmount = _generatorStakeAmount; - validatorStakeAmount = _validatorStakeAmount; - token = ERC20(_token); - } - - /*////////////////////////////////////////////////////////////// - LOGIC - //////////////////////////////////////////////////////////////*/ - - /// @notice Register an Oracle. - /// @dev Reverts if the user is already registered or has insufficient funds. - /// @param kind The kind of Oracle to unregister. - function register(LLMOracleKind kind) public { - uint256 amount = getStakeAmount(kind); - - // ensure the user is not already registered - if (isRegistered(msg.sender, kind)) { - revert AlreadyRegistered(msg.sender); - } - - // ensure the user has enough allowance to stake - if (token.allowance(msg.sender, address(this)) < amount) { - revert InsufficientFunds(); - } - token.transferFrom(msg.sender, address(this), amount); - - // register the user - registrations[msg.sender][kind] = amount; - emit Registered(msg.sender, kind); - } - - /// @notice Remove registration of an Oracle. - /// @dev Reverts if the user is not registered. - /// @param kind The kind of Oracle to unregister. - /// @return amount Amount of stake approved back. - function unregister(LLMOracleKind kind) public returns (uint256 amount) { - amount = registrations[msg.sender][kind]; - - // ensure the user is registered - if (amount == 0) { - revert NotRegistered(msg.sender); - } - - // unregister the user - delete registrations[msg.sender][kind]; - emit Unregistered(msg.sender, kind); - - // approve its stake back - token.approve(msg.sender, token.allowance(address(this), msg.sender) + amount); - } - - /// @notice Set the stake amount required to register as an Oracle. - /// @dev Only allowed by the owner. - function setStakeAmounts(uint256 _generatorStakeAmount, uint256 _validatorStakeAmount) public onlyOwner { - generatorStakeAmount = _generatorStakeAmount; - validatorStakeAmount = _validatorStakeAmount; - } - - /// @notice Returns the stake amount required to register as an Oracle w.r.t given kind. - function getStakeAmount(LLMOracleKind kind) public view returns (uint256) { - return kind == LLMOracleKind.Generator ? generatorStakeAmount : validatorStakeAmount; - } - - /// @notice Check if an Oracle is registered. - function isRegistered(address user, LLMOracleKind kind) public view returns (bool) { - return registrations[user][kind] != 0; - } -} diff --git a/contracts/llm/LLMOracleTask.sol b/contracts/llm/LLMOracleTask.sol deleted file mode 100644 index 3519429..0000000 --- a/contracts/llm/LLMOracleTask.sol +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -/// @notice Collection of oracle task-related parameters. -/// @dev Prevents stack-too-deep with tight-packing. -/// TODO: use 256-bit tight-packing here -struct LLMOracleTaskParameters { - /// @notice Difficulty of the task. - uint8 difficulty; - /// @notice Number of generations. - uint40 numGenerations; - /// @notice Number of validations. - uint40 numValidations; -} - -/// @title LLM Oracle Task Interface -/// @notice An umbrella interface that captures task-related structs and enums. -interface LLMOracleTask { - /// @notice Task status. - /// @dev `None`: Task has not been created yet. (default) - /// @dev `PendingGeneration`: Task is waiting for Oracle generation responses. - /// @dev `PendingValidation`: Task is waiting for validation by validator Oracles. - /// @dev `Completed`: The task has been completed. - /// @dev With validation, the flow is `None -> PendingGeneration -> PendingValidation -> Completed`. - /// @dev Without validation, the flow is `None -> PendingGeneration -> Completed`. - enum TaskStatus { - None, - PendingGeneration, - PendingValidation, - Completed - } - - /// @notice A task request for LLM generation. - /// @dev Fees are stored here as well in case fee changes occur within the duration of a task. - struct TaskRequest { - /// @dev Requesting address, also responsible of the fee payment. - address requester; - /// @dev Protocol string, such as `dria/0.1.0`. - bytes32 protocol; - /// @dev Task parameters, e.g. difficulty and number of generations & validations. - LLMOracleTaskParameters parameters; - /// @dev Task status. - TaskStatus status; - /// @dev Fee paid to each generator per generation. - uint256 generatorFee; - /// @dev Fee paid to each validator per validated generation. - uint256 validatorFee; - /// @dev Fee paid to the platform - uint256 platformFee; - /// @dev Input data for the task, usually a human-readable string. - bytes input; - /// @dev Allowed model names for the task. - bytes models; - } - - /// @notice A task response to an LLM generation request. - struct TaskResponse { - /// @dev Responding Oracle address. - address responder; - /// @dev Proof-of-Work nonce for SHA3(taskId, input, requester, responder, nonce) < difficulty. - uint256 nonce; - /// @dev Final validation score assigned by validators, stays 0 if there is no validation. - uint256 score; - /// @dev Output data for the task, usually the direct output of LLM. - bytes output; - /// @dev Optional metadata for this generation. - bytes metadata; - } - - /// @notice A task validation for a response. - struct TaskValidation { - /// @dev Responding validator address. - address validator; - /// @dev Proof-of-Work nonce for SHA3(taskId, input, requester, responder, nonce) < difficulty. - uint256 nonce; - /// @dev Validation scores - uint256[] scores; - /// @dev Optional metadata for this validation. - bytes metadata; - } -} diff --git a/contracts/mock/LLMOracleCoordinatorV2.sol b/contracts/mock/LLMOracleCoordinatorV2.sol deleted file mode 100644 index 8060a68..0000000 --- a/contracts/mock/LLMOracleCoordinatorV2.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -import {LLMOracleCoordinator} from "../llm/LLMOracleCoordinator.sol"; - -contract LLMOracleCoordinatorV2 is LLMOracleCoordinator { - function upgraded() public view virtual returns (bool) { - return true; - } -} diff --git a/contracts/mock/LLMOracleRegistryV2.sol b/contracts/mock/LLMOracleRegistryV2.sol deleted file mode 100644 index dcba1b6..0000000 --- a/contracts/mock/LLMOracleRegistryV2.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -import {LLMOracleRegistry} from "../llm/LLMOracleRegistry.sol"; - -contract LLMOracleRegistryV2 is LLMOracleRegistry { - function upgraded() public view virtual returns (bool) { - return true; - } -} diff --git a/foundry.toml b/foundry.toml index a9f71ce..ade0cbb 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,7 +2,7 @@ src = 'contracts' lib = 'lib' test = 'test' -script = 'scripts' +script = 'script' out = 'out' cache_path = 'cache' ffi = true @@ -12,8 +12,10 @@ optimizer = true extra_output = ['storageLayout'] 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/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/" ] # 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 7257553..b5cb06e 100644 --- a/lcov.info +++ b/lcov.info @@ -618,52 +618,63 @@ BRH:1 end_of_record TN: SF:script/Deploy.s.sol -FN:23,Deploy.run +FN:31,Deploy.run FNDA:1,Deploy.run -DA:24,1 -DA:26,1 -DA:27,1 -DA:28,1 -DA:29,1 -DA:30,1 -FN:33,Deploy.deployLLM -FNDA:1,Deploy.deployLLM +DA:32,1 +DA:33,1 DA:35,1 +DA:36,1 +DA:37,1 DA:38,1 +DA:39,1 DA:41,1 -DA:47,1 -DA:50,1 +FN:44,Deploy.deployLLM +FNDA:1,Deploy.deployLLM +DA:46,1 +DA:49,1 +DA:52,1 DA:58,1 -FN:61,Deploy.deployFactories -FNDA:1,Deploy.deployFactories +DA:59,1 DA:62,1 -DA:63,1 -FN:66,Deploy.deploySwan -FNDA:1,Deploy.deploySwan -DA:68,1 +DA:70,1 +DA:71,1 +FN:74,Deploy.deployFactories +FNDA:1,Deploy.deployFactories DA:75,1 -DA:78,1 +DA:76,1 +FN:79,Deploy.deploySwan +FNDA:1,Deploy.deploySwan DA:81,1 -DA:103,1 -FNF:4 -FNH:4 -LF:19 -LH:19 +DA:88,1 +DA:91,1 +DA:94,1 +DA:116,1 +DA:117,1 +FN:120,Deploy.writeContractAddresses +FNDA:1,Deploy.writeContractAddresses +DA:122,1 +DA:123,1 +DA:125,1 +DA:144,1 +FNF:5 +FNH:5 +LF:28 +LH:28 BRF:0 BRH:0 end_of_record TN: SF:script/HelperConfig.s.sol -FN:32,HelperConfig. +FN:31,HelperConfig. FNDA:1,HelperConfig. +DA:33,1 DA:34,1 DA:35,1 -DA:36,1 -DA:38,1 -DA:49,1 -BRDA:49,0,0,- -DA:51,0 -DA:54,1 +DA:37,1 +DA:48,1 +BRDA:48,0,0,- +DA:50,0 +DA:53,1 FNF:1 FNH:1 LF:7 diff --git a/lib/dria-oracle-contracts b/lib/dria-oracle-contracts new file mode 160000 index 0000000..f64bfc9 --- /dev/null +++ b/lib/dria-oracle-contracts @@ -0,0 +1 @@ +Subproject commit f64bfc930ac19dac447a8e6ab6bd69830d808c87 diff --git a/lib/forge-std b/lib/forge-std index 1eea5ba..2b59872 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 1eea5bae12ae557d589f9f0f0edae2faa47cb262 +Subproject commit 2b59872eee0b8088ddcade39fe8c041e17bb79c0 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 69c8def..ccb5f2d 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 69c8def5f222ff96f2b5beff05dfba996368aa79 +Subproject commit ccb5f2d8ca9d194623cf3cff7f010ab92715826b diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable index fa52531..16f3f81 160000 --- a/lib/openzeppelin-contracts-upgradeable +++ b/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit fa525310e45f91eb20a6d3baa2644be8e0adba31 +Subproject commit 16f3f81635da15454e2ddfa87d98ced437621c3c diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index c399975..1b41b67 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -1,16 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import {Script} from "../lib/forge-std/src/Script.sol"; -import {HelperConfig} from "../script/HelperConfig.s.sol"; -import {Upgrades} from "../lib/openzeppelin-foundry-upgrades/src/Upgrades.sol"; -import {LLMOracleRegistry} from "../contracts/llm/LLMOracleRegistry.sol"; -import {LLMOracleCoordinator, LLMOracleTaskParameters} from "../contracts/llm/LLMOracleCoordinator.sol"; -import {BuyerAgentFactory} from "../contracts/swan/BuyerAgent.sol"; -import {SwanAssetFactory} from "../contracts/swan/SwanAsset.sol"; -import {Swan, SwanMarketParameters} from "../contracts/swan/Swan.sol"; -import {Vm} from "../lib/forge-std/src/Vm.sol"; -import {Strings} from "../lib/openzeppelin-contracts/contracts/utils/Strings.sol"; +import {Script} from "forge-std/Script.sol"; +import {HelperConfig} from "./HelperConfig.s.sol"; +import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; +import {LLMOracleRegistry} from "@firstbatch/dria-oracle-contracts/LLMOracleRegistry.sol"; +import { + LLMOracleCoordinator, LLMOracleTaskParameters +} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; +import {BuyerAgentFactory} from "../src/BuyerAgent.sol"; +import {SwanAssetFactory} from "../src/SwanAsset.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 @@ -123,22 +125,37 @@ contract Deploy is Script { string memory path = string.concat("deployment/", fileName, ".json"); 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), '"' - "}" + "{", + ' "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), + '"' "}" ); vm.writeJson(contracts, path); diff --git a/script/HelperConfig.s.sol b/script/HelperConfig.s.sol index f6488a0..887f6af 100644 --- a/script/HelperConfig.s.sol +++ b/script/HelperConfig.s.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import {Script} from "../lib/forge-std/src/Script.sol"; -import {WETH9} from "../contracts/token/WETH9.sol"; -import {LLMOracleTaskParameters} from "../contracts/llm/LLMOracleTask.sol"; -import {SwanMarketParameters} from "../contracts/swan/SwanManager.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"; struct Stakes { uint256 generatorStakeAmount; diff --git a/contracts/swan/BuyerAgent.sol b/src/BuyerAgent.sol similarity index 98% rename from contracts/swan/BuyerAgent.sol rename to src/BuyerAgent.sol index fc70cd4..88657d7 100644 --- a/contracts/swan/BuyerAgent.sol +++ b/src/BuyerAgent.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.20; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {LLMOracleTask, LLMOracleTaskParameters} from "../llm/LLMOracleTask.sol"; -import {Swan, SwanBuyerPurchaseOracleProtocol, SwanBuyerStateOracleProtocol} from "../swan/Swan.sol"; -import {SwanMarketParameters} from "../swan/SwanManager.sol"; +import {LLMOracleTask, LLMOracleTaskParameters} from "@firstbatch/dria-oracle-contracts/LLMOracleTask.sol"; +import {Swan, SwanBuyerPurchaseOracleProtocol, SwanBuyerStateOracleProtocol} from "./Swan.sol"; +import {SwanMarketParameters} from "./SwanManager.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; /// @notice Factory contract to deploy BuyerAgent contracts. diff --git a/contracts/swan/Swan.sol b/src/Swan.sol similarity index 94% rename from contracts/swan/Swan.sol rename to src/Swan.sol index 5fdee95..fd2956c 100644 --- a/contracts/swan/Swan.sol +++ b/src/Swan.sol @@ -2,15 +2,13 @@ pragma solidity ^0.8.20; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import {LLMOracleCoordinator} from "../llm/LLMOracleCoordinator.sol"; -import {LLMOracleTaskParameters} from "../llm/LLMOracleTask.sol"; -import {BuyerAgentFactory, BuyerAgent} from "./BuyerAgent.sol"; -import {SwanAssetFactory, SwanAsset} from "./SwanAsset.sol"; +import {LLMOracleTask, LLMOracleTaskParameters, LLMOracleCoordinator} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; import {SwanManager, SwanMarketParameters} from "./SwanManager.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import {BuyerAgentFactory, BuyerAgent} from "./BuyerAgent.sol"; +import {SwanAssetFactory, SwanAsset} from "./SwanAsset.sol"; // Protocol strings for Swan, checked in the Oracle. bytes32 constant SwanBuyerPurchaseOracleProtocol = "swan-buyer-purchase/0.1.0"; @@ -93,11 +91,17 @@ contract Swan is SwanManager, UUPSUpgradeable, IERC721Receiver { AssetStatus status; } + /// @notice Factory contract to deploy Buyer Agents. + BuyerAgentFactory public buyerAgentFactory; + /// @notice Factory contract to deploy SwanAsset tokens. + SwanAssetFactory public swanAssetFactory; + /// @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; + /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ @@ -160,6 +164,21 @@ contract Swan is SwanManager, UUPSUpgradeable, IERC721Receiver { LOGIC //////////////////////////////////////////////////////////////*/ + /// @notice Creates a new buyer agent. + /// @dev Emits a `BuyerCreated` event. + /// @return address of the new buyer agent. + function createBuyer( + 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)); + + return agent; + } + /// @notice Creates a new Asset. /// @param _name name of the token. /// @param _symbol symbol of the token. @@ -317,6 +336,15 @@ contract Swan is SwanManager, UUPSUpgradeable, IERC721Receiver { emit AssetSold(listing.seller, msg.sender, _asset, listing.price); } + /// @notice Set the factories for Buyer Agents and Swan Assets. + /// @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); + } + /// @notice Returns the asset status with the given asset address. /// @dev Active: If the asset has not been purchased or the next round has not started. /// @dev Inactive: If the assets's purchaseRound has passed or delisted by the creator of the asset. @@ -335,19 +363,4 @@ contract Swan is SwanManager, UUPSUpgradeable, IERC721Receiver { function getListing(address _asset) external view returns (AssetListing memory) { return listings[_asset]; } - - /// @notice Creates a new buyer agent. - /// @dev Emits a `BuyerCreated` event. - /// @return address of the new buyer agent. - function createBuyer( - 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)); - - return agent; - } } diff --git a/contracts/swan/SwanAsset.sol b/src/SwanAsset.sol similarity index 100% rename from contracts/swan/SwanAsset.sol rename to src/SwanAsset.sol diff --git a/contracts/swan/SwanManager.sol b/src/SwanManager.sol similarity index 85% rename from contracts/swan/SwanManager.sol rename to src/SwanManager.sol index 018e59f..6f909b4 100644 --- a/contracts/swan/SwanManager.sol +++ b/src/SwanManager.sol @@ -3,10 +3,7 @@ pragma solidity ^0.8.20; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; -import {LLMOracleCoordinator} from "../llm/LLMOracleCoordinator.sol"; -import {LLMOracleTaskParameters} from "../llm/LLMOracleTask.sol"; -import {BuyerAgentFactory, BuyerAgent} from "./BuyerAgent.sol"; -import {SwanAssetFactory, SwanAsset} from "./SwanAsset.sol"; +import {LLMOracleTask, LLMOracleTaskParameters, LLMOracleCoordinator} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; /// @notice Collection of market-related parameters. /// @dev Prevents stack-too-deep. @@ -39,10 +36,6 @@ abstract contract SwanManager is OwnableUpgradeable { /// @notice Oracle parameters such as fees. LLMOracleTaskParameters oracleParameters; - /// @notice Factory contract to deploy Buyer Agents. - BuyerAgentFactory public buyerAgentFactory; - /// @notice Factory contract to deploy SwanAsset tokens. - SwanAssetFactory public swanAssetFactory; /// @notice LLM Oracle Coordinator. LLMOracleCoordinator public coordinator; /// @notice The token to be used for fee payments. @@ -102,15 +95,6 @@ abstract contract SwanManager is OwnableUpgradeable { (uint256 totalFee,,) = coordinator.getFee(oracleParameters); return totalFee; } - /// @notice Set the factories for Buyer Agents and Swan Assets. - /// @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); - } /*////////////////////////////////////////////////////////////// OPERATORS diff --git a/contracts/mock/SwanV2.sol b/src/mock/SvanV2.sol similarity index 82% rename from contracts/mock/SwanV2.sol rename to src/mock/SvanV2.sol index a75dfe8..a914b76 100644 --- a/contracts/mock/SwanV2.sol +++ b/src/mock/SvanV2.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import {Swan} from "../swan/Swan.sol"; +import {Swan} from "../Swan.sol"; contract SwanV2 is Swan { function upgraded() public view virtual returns (bool) { diff --git a/test/BuyerAgent.t.sol b/test/BuyerAgent.t.sol index 1584bb3..240c327 100644 --- a/test/BuyerAgent.t.sol +++ b/test/BuyerAgent.t.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import {Vm} from "../lib/forge-std/src/Vm.sol"; +import {Vm} from "forge-std/Vm.sol"; import {Helper} from "./Helper.t.sol"; -import {WETH9} from "../contracts/token/WETH9.sol"; -import {Upgrades} from "../lib/openzeppelin-foundry-upgrades/src/Upgrades.sol"; -import {BuyerAgent, BuyerAgentFactory} from "../contracts/swan/BuyerAgent.sol"; -import {LLMOracleCoordinator} from "../contracts/llm/LLMOracleCoordinator.sol"; -import {SwanAssetFactory} from "../contracts/swan/SwanAsset.sol"; -import {LLMOracleTaskParameters} from "../contracts/llm/LLMOracleTask.sol"; -import {LLMOracleRegistry} from "../contracts/llm/LLMOracleRegistry.sol"; -import {Swan} from "../contracts/swan/Swan.sol"; +import {WETH9} from "./WETH9.sol"; +import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.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"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract BuyerAgentTest is Helper { diff --git a/test/Deploy.t.sol b/test/Deploy.t.sol index 91d43c8..2c322db 100644 --- a/test/Deploy.t.sol +++ b/test/Deploy.t.sol @@ -2,11 +2,11 @@ import {Deploy} from "../script/Deploy.s.sol"; import {HelperConfig} from "../script/HelperConfig.s.sol"; -import {Test} from "../lib/forge-std/src/Test.sol"; -import {Vm} from "../lib/forge-std/src/Vm.sol"; -import {LLMOracleRegistry} from "../contracts/llm/LLMOracleRegistry.sol"; -import {LLMOracleCoordinator} from "../contracts/llm/LLMOracleCoordinator.sol"; -import {Swan} from "../contracts/swan/Swan.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; diff --git a/test/Helper.t.sol b/test/Helper.t.sol index 4c6277f..9326217 100644 --- a/test/Helper.t.sol +++ b/test/Helper.t.sol @@ -1,18 +1,18 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import {Vm} from "../lib/forge-std/src/Vm.sol"; -import {Upgrades} from "../lib/openzeppelin-foundry-upgrades/src/Upgrades.sol"; -import {WETH9} from "../contracts/token/WETH9.sol"; -import {LLMOracleRegistry, LLMOracleKind} from "../contracts/llm/LLMOracleRegistry.sol"; -import {LLMOracleCoordinator} from "../contracts/llm/LLMOracleCoordinator.sol"; -import {Test, console} from "../lib/forge-std/src/Test.sol"; -import {SwanMarketParameters} from "../contracts/swan/SwanManager.sol"; -import {LLMOracleTaskParameters} from "../contracts/llm/LLMOracleTask.sol"; -import {BuyerAgent} from "../contracts/swan/BuyerAgent.sol"; -import {Swan} from "../contracts/swan/Swan.sol"; -import {BuyerAgent, BuyerAgentFactory} from "../contracts/swan/BuyerAgent.sol"; -import {SwanAssetFactory, SwanAsset} from "../contracts/swan/SwanAsset.sol"; +import {Vm} from "forge-std/Vm.sol"; +import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; +import {WETH9} from "./WETH9.sol"; +import {LLMOracleRegistry, LLMOracleKind} from "@firstbatch/dria-oracle-contracts/LLMOracleRegistry.sol"; +import {LLMOracleCoordinator} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.sol"; +import {Test, console} from "forge-std/Test.sol"; +import {SwanMarketParameters} from "../src/SwanManager.sol"; +import {LLMOracleTaskParameters} from "@firstbatch/dria-oracle-contracts/LLMOracleTask.sol"; +import {BuyerAgent} from "../src/BuyerAgent.sol"; +import {Swan} from "../src/Swan.sol"; +import {BuyerAgent, BuyerAgentFactory} from "../src/BuyerAgent.sol"; +import {SwanAssetFactory, SwanAsset} from "../src/SwanAsset.sol"; abstract contract Helper is Test { struct Stakes { diff --git a/test/LLMOracleCoordinator.t.sol b/test/LLMOracleCoordinator.t.sol deleted file mode 100644 index ce826f9..0000000 --- a/test/LLMOracleCoordinator.t.sol +++ /dev/null @@ -1,239 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -import {LLMOracleTask, LLMOracleTaskParameters} from "../contracts/llm/LLMOracleTask.sol"; -import {LLMOracleRegistry, LLMOracleKind} from "../contracts/llm/LLMOracleRegistry.sol"; -import {Upgrades} from "../lib/openzeppelin-foundry-upgrades/src/Upgrades.sol"; -import {LLMOracleCoordinator} from "../contracts/llm/LLMOracleCoordinator.sol"; -import {WETH9} from "../contracts/token/WETH9.sol"; -import {Vm} from "../lib/forge-std/src/Vm.sol"; -import {Helper} from "./Helper.t.sol"; - -contract LLMOracleCoordinatorTest is Helper { - address dummy = vm.addr(20); - address requester = vm.addr(21); - - bytes output = "0x"; - - modifier deployment() { - vm.startPrank(dria); - address registryProxy = Upgrades.deployUUPSProxy( - "LLMOracleRegistry.sol", - abi.encodeCall( - LLMOracleRegistry.initialize, (stakes.generatorStakeAmount, stakes.validatorStakeAmount, address(token)) - ) - ); - - // wrap proxy with the LLMOracleRegistry contract to use in tests easily - oracleRegistry = LLMOracleRegistry(registryProxy); - - // deploy coordinator contract - address coordinatorProxy = Upgrades.deployUUPSProxy( - "LLMOracleCoordinator.sol", - abi.encodeCall( - LLMOracleCoordinator.initialize, - (address(oracleRegistry), address(token), fees.platformFee, fees.generationFee, fees.validationFee) - ) - ); - oracleCoordinator = LLMOracleCoordinator(coordinatorProxy); - vm.stopPrank(); - - vm.label(dummy, "Dummy"); - vm.label(requester, "Requester"); - vm.label(address(this), "LLMOracleCoordinatorTest"); - vm.label(address(oracleRegistry), "LLMOracleRegistry"); - vm.label(address(oracleCoordinator), "LLMOracleCoordinator"); - _; - } - - modifier fund() { - // deploy weth - token = new WETH9(); - - // fund dria & requester - deal(address(token), dria, 1 ether); - deal(address(token), requester, 1 ether); - - // fund generators and validators - for (uint256 i = 0; i < generators.length; i++) { - deal(address(token), generators[i], stakes.generatorStakeAmount + stakes.validatorStakeAmount); - assertEq(token.balanceOf(generators[i]), stakes.generatorStakeAmount + stakes.validatorStakeAmount); - } - for (uint256 i = 0; i < validators.length; i++) { - deal(address(token), validators[i], stakes.validatorStakeAmount); - assertEq(token.balanceOf(validators[i]), stakes.validatorStakeAmount); - } - _; - } - - function test_Deployment() external fund deployment { - assertEq(oracleRegistry.generatorStakeAmount(), stakes.generatorStakeAmount); - assertEq(oracleRegistry.validatorStakeAmount(), stakes.validatorStakeAmount); - - assertEq(address(oracleRegistry.token()), address(token)); - assertEq(oracleRegistry.owner(), dria); - - // check the coordinator variables - assertEq(address(oracleCoordinator.feeToken()), address(token)); - assertEq(address(oracleCoordinator.registry()), address(oracleRegistry)); - assertEq(oracleCoordinator.platformFee(), fees.platformFee); - assertEq(oracleCoordinator.generationFee(), fees.generationFee); - assertEq(oracleCoordinator.validationFee(), fees.validationFee); - } - - /// @notice Test the registerOracles modifier to check if the oracles are registered - function test_RegisterOracles() external fund deployment registerOracles { - for (uint256 i; i < generators.length; i++) { - assertTrue(oracleRegistry.isRegistered(generators[i], LLMOracleKind.Generator)); - } - - for (uint256 i; i < validators.length; i++) { - assertTrue(oracleRegistry.isRegistered(validators[i], LLMOracleKind.Validator)); - } - } - - // @notice Test without validation - function test_WithoutValidation() - external - fund - setOracleParameters(1, 2, 0) - deployment - registerOracles - safeRequest(requester, 1) - checkAllowances - { - uint256 responseId; - - // try to respond as an outsider (should fail) - uint256 dummyNonce = mineNonce(dummy, 1); - vm.expectRevert(abi.encodeWithSelector(LLMOracleRegistry.NotRegistered.selector, dummy)); - vm.prank(dummy); - oracleCoordinator.respond(1, dummyNonce, output, metadata); - - // respond as the first generator - safeRespond(generators[0], output, 1); - - // verify the response - (address _responder,,, bytes memory _output,) = oracleCoordinator.responses(1, responseId); - assertEq(_responder, generators[0]); - assertEq(output, _output); - responseId++; - - // try responding again (should fail) - uint256 genNonce0 = mineNonce(generators[0], 1); - vm.expectRevert(abi.encodeWithSelector(LLMOracleCoordinator.AlreadyResponded.selector, 1, generators[0])); - vm.prank(generators[0]); - oracleCoordinator.respond(1, genNonce0, output, metadata); - - // second responder responds - safeRespond(generators[1], output, 1); - responseId++; - - // try to respond after task completion (should fail) - uint256 genNonce1 = mineNonce(generators[1], 1); - vm.expectRevert( - abi.encodeWithSelector( - LLMOracleCoordinator.InvalidTaskStatus.selector, - 1, - uint8(LLMOracleTask.TaskStatus.Completed), - uint8(LLMOracleTask.TaskStatus.PendingGeneration) - ) - ); - vm.prank(generators[1]); - oracleCoordinator.respond(1, genNonce1, output, metadata); - - // try to respond to a non-existent task (should fail) - vm.expectRevert( - abi.encodeWithSelector( - LLMOracleCoordinator.InvalidTaskStatus.selector, - 900, - uint8(LLMOracleTask.TaskStatus.None), - uint8(LLMOracleTask.TaskStatus.PendingGeneration) - ) - ); - vm.prank(generators[0]); - oracleCoordinator.respond(900, genNonce0, output, metadata); - } - - // @notice Test with single validation - function test_WithValidation() - external - fund - setOracleParameters(1, 2, 2) - deployment - registerOracles - safeRequest(requester, 1) - checkAllowances - { - // generators respond - for (uint256 i = 0; i < oracleParameters.numGenerations; i++) { - safeRespond(generators[i], output, 1); - } - - // set scores - scores = [1 ether, 1 ether]; - - uint256 genNonce = mineNonce(generators[2], 1); - // ensure third generator can't respond after completion - vm.expectRevert( - abi.encodeWithSelector( - LLMOracleCoordinator.InvalidTaskStatus.selector, - 1, - uint8(LLMOracleTask.TaskStatus.PendingValidation), - uint8(LLMOracleTask.TaskStatus.PendingGeneration) - ) - ); - vm.prank(generators[2]); - oracleCoordinator.respond(1, genNonce, output, metadata); - - // validator validate - safeValidate(validators[0], 1); - - uint256 valNonce = mineNonce(validators[0], 1); - // ensure first validator can't validate twice - vm.expectRevert(abi.encodeWithSelector(LLMOracleCoordinator.AlreadyResponded.selector, 1, validators[0])); - vm.prank(validators[0]); - oracleCoordinator.validate(1, valNonce, scores, metadata); - - // second validator validates and completes the task - safeValidate(validators[1], 1); - - // check the task's status is Completed - (,,, LLMOracleTask.TaskStatus status,,,,,) = oracleCoordinator.requests(1); - assertEq(uint8(status), uint8(LLMOracleTask.TaskStatus.Completed)); - - // should see generation scores - for (uint256 i = 0; i < oracleParameters.numGenerations; i++) { - (,, uint256 responseScore,,) = oracleCoordinator.responses(1, i); - assertEq(responseScore, 1 ether); - } - } - - /// @dev Oracle cannot validate if already participated as generator - function test_ValidatorIsGenerator() - external - fund - setOracleParameters(1, 1, 1) - deployment - registerOracles - safeRequest(requester, 1) - { - // register generators[0] as a validator as well - vm.prank(generators[0]); - oracleRegistry.register(LLMOracleKind.Validator); - - // respond as generator - for (uint256 i = 0; i < oracleParameters.numGenerations; i++) { - safeRespond(generators[i], output, 1); - } - - // set scores for (setOracleParameters(1, 1, 1)) - scores = [1 ether]; - - // try to validate after responding as generator - uint256 nonce = mineNonce(generators[0], 1); - vm.prank(generators[0]); - vm.expectRevert(abi.encodeWithSelector(LLMOracleCoordinator.AlreadyResponded.selector, 1, generators[0])); - oracleCoordinator.validate(1, nonce, scores, metadata); - } -} diff --git a/test/LLMOracleRegistry.t.sol b/test/LLMOracleRegistry.t.sol deleted file mode 100644 index 4ec0a74..0000000 --- a/test/LLMOracleRegistry.t.sol +++ /dev/null @@ -1,156 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.20; - -import {Vm} from "../lib/forge-std/src/Vm.sol"; -import {Upgrades} from "../lib/openzeppelin-foundry-upgrades/src/Upgrades.sol"; -import {WETH9} from "../contracts/token/WETH9.sol"; -import {LLMOracleRegistry, LLMOracleKind} from "../contracts/llm/LLMOracleRegistry.sol"; -import {Helper} from "./Helper.t.sol"; - -contract LLMOracleRegistryTest is Helper { - uint256 totalStakeAmount; - address oracle; - - modifier deployment() { - oracle = generators[0]; - totalStakeAmount = stakes.generatorStakeAmount + stakes.validatorStakeAmount; - - token = new WETH9(); - - vm.startPrank(dria); - address registryProxy = Upgrades.deployUUPSProxy( - "LLMOracleRegistry.sol", - abi.encodeCall( - LLMOracleRegistry.initialize, (stakes.generatorStakeAmount, stakes.validatorStakeAmount, address(token)) - ) - ); - - // wrap proxy with the LLMOracleRegistry contract to use in tests easily - oracleRegistry = LLMOracleRegistry(registryProxy); - vm.stopPrank(); - - vm.label(oracle, "Oracle"); - vm.label(address(this), "LLMOracleRegistryTest"); - vm.label(address(oracleRegistry), "LLMOracleRegistry"); - vm.label(address(oracleCoordinator), "LLMOracleCoordinator"); - _; - } - - /// @notice fund the oracle and dria - modifier fund() { - deal(address(token), dria, 1 ether); - deal(address(token), oracle, totalStakeAmount); - - assertEq(token.balanceOf(dria), 1 ether); - assertEq(token.balanceOf(oracle), totalStakeAmount); - _; - } - - // move to helper ? - modifier registerOracle(LLMOracleKind kind) { - // register oracle - vm.startPrank(oracle); - token.approve(address(oracleRegistry), totalStakeAmount); - - // Register the generator oracle - oracleRegistry.register(kind); - vm.stopPrank(); - _; - } - - // move to helper ? - modifier unregisterOracle(LLMOracleKind kind) { - // Simulate the oracle account - vm.startPrank(oracle); - token.approve(address(oracleRegistry), stakes.generatorStakeAmount); - oracleRegistry.unregister(kind); - vm.stopPrank(); - - assertFalse(oracleRegistry.isRegistered(oracle, LLMOracleKind.Generator)); - _; - } - - function test_Deployment() external deployment { - assertEq(oracleRegistry.generatorStakeAmount(), stakes.generatorStakeAmount); - assertEq(oracleRegistry.validatorStakeAmount(), stakes.validatorStakeAmount); - - assertEq(address(oracleRegistry.token()), address(token)); - assertEq(oracleRegistry.owner(), dria); - } - - /// @notice Registry has not approved by oracle - function test_RevertWhen_RegistryHasNotApprovedByOracle() external deployment { - // oracle has the funds but has not approved yet - deal(address(token), oracle, totalStakeAmount); - - vm.expectRevert(abi.encodeWithSelector(LLMOracleRegistry.InsufficientFunds.selector)); - oracleRegistry.register(LLMOracleKind.Generator); - } - - /// @notice Oracle has enough funds and approve registry - function test_RegisterGeneratorOracle() external deployment fund registerOracle(LLMOracleKind.Generator) {} - - /// @notice Same oracle try to register twice - function test_RevertWhen_RegisterSameGeneratorTwice() - external - deployment - fund - registerOracle(LLMOracleKind.Generator) - { - vm.prank(oracle); - vm.expectRevert(abi.encodeWithSelector(LLMOracleRegistry.AlreadyRegistered.selector, oracle)); - - oracleRegistry.register(LLMOracleKind.Generator); - } - - /// @notice Oracle registers as validator - function test_RegisterValidatorOracle() external deployment fund registerOracle(LLMOracleKind.Validator) {} - - /// @notice Oracle unregisters as generator - function test_UnregisterOracle() - external - deployment - fund - registerOracle(LLMOracleKind.Generator) - unregisterOracle(LLMOracleKind.Generator) - {} - - /// @notice Oracle try to unregisters as generator twice - function test_RevertWhen_UnregisterSameGeneratorTwice() - external - deployment - fund - registerOracle(LLMOracleKind.Generator) - unregisterOracle(LLMOracleKind.Generator) - { - vm.prank(oracle); - vm.expectRevert(abi.encodeWithSelector(LLMOracleRegistry.NotRegistered.selector, oracle)); - oracleRegistry.unregister(LLMOracleKind.Generator); - } - - /// @notice Oracle can withdraw stakes after unregistering - /// @dev 1. Register as generator - /// @dev 2. Register as validator - /// @dev 3. Unregister as generator - /// @dev 4. Unregister as validator - /// @dev 5. withdraw stakes - function test_WithdrawStakesAfterUnregistering() - external - deployment - fund - registerOracle(LLMOracleKind.Generator) - registerOracle(LLMOracleKind.Validator) - unregisterOracle(LLMOracleKind.Generator) - unregisterOracle(LLMOracleKind.Validator) - { - uint256 balanceBefore = token.balanceOf(oracle); - token.approve(address(oracleRegistry), totalStakeAmount); - - // withdraw stakes - vm.startPrank(oracle); - token.transferFrom(address(oracleRegistry), oracle, (totalStakeAmount)); - - uint256 balanceAfter = token.balanceOf(oracle); - assertEq(balanceAfter - balanceBefore, totalStakeAmount); - } -} diff --git a/test/Swan.t.sol b/test/Swan.t.sol index 01cd9ab..999ba34 100644 --- a/test/Swan.t.sol +++ b/test/Swan.t.sol @@ -1,15 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import {LLMOracleTaskParameters} from "../contracts/llm/LLMOracleTask.sol"; -import {LLMOracleRegistry} from "../contracts/llm/LLMOracleRegistry.sol"; -import {Upgrades} from "../lib/openzeppelin-foundry-upgrades/src/Upgrades.sol"; -import {LLMOracleCoordinator} from "../contracts/llm/LLMOracleCoordinator.sol"; -import {BuyerAgent, BuyerAgentFactory} from "../contracts/swan/BuyerAgent.sol"; -import {SwanAssetFactory, SwanAsset} from "../contracts/swan/SwanAsset.sol"; -import {Swan, SwanMarketParameters} from "../contracts/swan/Swan.sol"; -import {WETH9} from "../contracts/token/WETH9.sol"; -import {Vm} from "../lib/forge-std/src/Vm.sol"; +import {LLMOracleRegistry} from "@firstbatch/dria-oracle-contracts/LLMOracleRegistry.sol"; +import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; +import { + LLMOracleCoordinator, LLMOracleTaskParameters +} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.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 {Vm} from "forge-std/Vm.sol"; import {Helper} from "./Helper.t.sol"; contract SwanTest is Helper { diff --git a/test/SwanIntervals.t.sol b/test/SwanIntervals.t.sol index 451e84f..588bf79 100644 --- a/test/SwanIntervals.t.sol +++ b/test/SwanIntervals.t.sol @@ -1,15 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; -import {LLMOracleTaskParameters} from "../contracts/llm/LLMOracleTask.sol"; -import {LLMOracleRegistry} from "../contracts/llm/LLMOracleRegistry.sol"; -import {Upgrades} from "../lib/openzeppelin-foundry-upgrades/src/Upgrades.sol"; -import {LLMOracleCoordinator} from "../contracts/llm/LLMOracleCoordinator.sol"; -import {BuyerAgent, BuyerAgentFactory} from "../contracts/swan/BuyerAgent.sol"; -import {SwanAssetFactory, SwanAsset} from "../contracts/swan/SwanAsset.sol"; -import {Swan, SwanMarketParameters} from "../contracts/swan/Swan.sol"; -import {WETH9} from "../contracts/token/WETH9.sol"; -import {Vm} from "../lib/forge-std/src/Vm.sol"; +import {LLMOracleRegistry} from "@firstbatch/dria-oracle-contracts/LLMOracleRegistry.sol"; +import {Upgrades} from "@openzeppelin/foundry-upgrades/Upgrades.sol"; +import { + LLMOracleCoordinator, LLMOracleTaskParameters +} from "@firstbatch/dria-oracle-contracts/LLMOracleCoordinator.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 {Vm} from "forge-std/Vm.sol"; import {Helper} from "./Helper.t.sol"; contract SwanIntervalsTest is Helper { diff --git a/contracts/token/WETH9.sol b/test/WETH9.sol similarity index 99% rename from contracts/token/WETH9.sol rename to test/WETH9.sol index 20def46..c729534 100644 --- a/contracts/token/WETH9.sol +++ b/test/WETH9.sol @@ -15,6 +15,7 @@ // pragma solidity >=0.4.22 <0.6; // For not getting an "Bad CPU type in executable" error for solc-0.5.17 compiler +pragma solidity ^0.8.20; contract WETH9 { string public name = "Wrapped Ether"; From 1658ef7f6e17f33219a5198053f040d6b6cdf7d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fla=20=C3=87elik?= Date: Thu, 21 Nov 2024 15:30:53 +0300 Subject: [PATCH 2/3] update makefile --- Makefile | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Makefile b/Makefile index a3bcd12..156e8af 100644 --- a/Makefile +++ b/Makefile @@ -26,10 +26,6 @@ build: snapshot: forge snapshot -# Generate the documentation under docs directory -docs: - forge doc - # Test the contracts on forked base-sepolia network test: forge clean && forge test --fork-url $(BASE_TEST_RPC_URL) From 9da89710f62a76d05b9c2693cd1422d64ae7d12c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Fla=20=C3=87elik?= Date: Thu, 21 Nov 2024 16:34:38 +0300 Subject: [PATCH 3/3] refactor --- script/Deploy.s.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 1b41b67..70bb2b9 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -121,9 +121,13 @@ contract Deploy is Script { 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("deployment/", fileName, ".json"); + string memory path = string.concat(dir, fileName, ".json"); + // create dir if it doesn't exist + vm.createDir(dir, true); + string memory contracts = string.concat( "{", ' "LLMOracleRegistry": {',