diff --git a/contracts/contracts/Test.sol b/contracts/contracts/Test.sol new file mode 100644 index 0000000..c8e8414 --- /dev/null +++ b/contracts/contracts/Test.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +// Uncomment this line to use console.log +// import "hardhat/console.sol"; + + +interface IOracle { + function createFunctionCall( + uint functionCallbackId, + string memory functionType, + string memory functionInput + ) external returns (uint i); + + function createKnowledgeBaseQuery( + uint kbQueryCallbackId, + string memory cid, + string memory query, + uint32 num_documents + ) external returns (uint i); +} + +contract Test { + address private owner; + address public oracleAddress; + string public lastResponse; + string public lastError; + uint private callsCount; + + event OracleAddressUpdated(address indexed newOracleAddress); + + constructor( + address initialOracleAddress + ) { + owner = msg.sender; + oracleAddress = initialOracleAddress; + } + + modifier onlyOwner() { + require(msg.sender == owner, "Caller is not owner"); + _; + } + + modifier onlyOracle() { + require(msg.sender == oracleAddress, "Caller is not oracle"); + _; + } + + function setOracleAddress(address newOracleAddress) public onlyOwner { + oracleAddress = newOracleAddress; + emit OracleAddressUpdated(newOracleAddress); + } + + function callFunction(string memory name, string memory message) public returns (uint i) { + uint currentId = callsCount; + callsCount = currentId + 1; + + lastResponse = ""; + lastError = ""; + + IOracle(oracleAddress).createFunctionCall( + currentId, + name, + message + ); + + return currentId; + } + + function queryKnowledgeBase(string memory cid, string memory query) public returns (uint i) { + uint currentId = callsCount; + callsCount = currentId + 1; + + lastResponse = ""; + lastError = ""; + + IOracle(oracleAddress).createKnowledgeBaseQuery( + currentId, + cid, + query, + 3 + ); + return currentId; + } + + function onOracleFunctionResponse( + uint runId, + string memory response, + string memory errorMessage + ) public onlyOracle { + lastResponse = response; + lastError = errorMessage; + } + + function onOracleKnowledgeBaseQueryResponse( + uint runId, + string [] memory documents, + string memory errorMessage + ) public onlyOracle { + string memory newContent = ""; + for (uint i = 0; i < documents.length; i++) { + newContent = string(abi.encodePacked(newContent, documents[i], "\n")); + } + lastResponse = newContent; + lastError = errorMessage; + } + } diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 8c6981c..3ee7782 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -1,7 +1,8 @@ import {HardhatUserConfig} from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox"; import "./tasks/whitelist"; -import "./tasks/deployChatWithRAG"; +import "./tasks/deployments"; +import "./tasks/functions"; require('dotenv').config() diff --git a/contracts/package.json b/contracts/package.json index c8a15b4..865cc7e 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -11,6 +11,8 @@ "deployAll:localhost": "npx hardhat run scripts/deployAll.ts --network localhost", "deployAll:galadriel": "npx hardhat run scripts/deployAll.ts --network galadriel", "deployQuickstart": "npx hardhat run scripts/deployQuickstart.ts --network galadriel", + "deployTest:localhost": "npx hardhat run scripts/deployTest.ts --network localhost", + "deployTest:galadriel": "npx hardhat run scripts/deployTest.ts --network galadriel", "callQuickstart": "npx hardhat run scripts/callQuickstart.ts --network galadriel" }, "devDependencies": { diff --git a/contracts/scripts/deployTest.ts b/contracts/scripts/deployTest.ts new file mode 100644 index 0000000..4ec4ae1 --- /dev/null +++ b/contracts/scripts/deployTest.ts @@ -0,0 +1,28 @@ +import {ethers} from "hardhat"; + + +async function main() { + if (!process.env.ORACLE_ADDRESS) { + throw new Error("ORACLE_ADDRESS env variable is not set."); + } + const oracleAddress: string = process.env.ORACLE_ADDRESS; + await deployTest(oracleAddress); +} + + +async function deployTest(oracleAddress: string) { + const contract = await ethers.deployContract("Test", [oracleAddress], {}); + + await contract.waitForDeployment(); + + console.log( + `Test contract deployed to ${contract.target}` + ); +} + +// We recommend this pattern to be able to use async/await everywhere +// and properly handle errors. +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/contracts/scripts/start_chat.ts b/contracts/scripts/start_chat.ts deleted file mode 100644 index 2fdf789..0000000 --- a/contracts/scripts/start_chat.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Import ethers from Hardhat package -const { ethers } = require("hardhat"); - -async function main() { - const contractABI = [ - "function startChat(string memory message) public returns (uint)" - ]; - const contractAddress = "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"; - const [signer] = await ethers.getSigners(); - - // Create a contract instance - const contract = new ethers.Contract(contractAddress, contractABI, signer); - - // The message you want to start the chat with - const message = "Say 3 dog names!"; - - // Call the startChat function - const transactionResponse = await contract.startChat(message); - await transactionResponse.wait(); - - console.log(`Chat started with message: "${message}"`); -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); \ No newline at end of file diff --git a/contracts/tasks/deployChatWithRAG.ts b/contracts/tasks/deployments.ts similarity index 99% rename from contracts/tasks/deployChatWithRAG.ts rename to contracts/tasks/deployments.ts index 05b3853..eebb355 100644 --- a/contracts/tasks/deployChatWithRAG.ts +++ b/contracts/tasks/deployments.ts @@ -10,5 +10,3 @@ task("deployChatWithRAG", "Deploys the chat contract with knowledge base") await contract.waitForDeployment(); console.log(`RAG deployed to: ${contract.target}`); }); - - diff --git a/contracts/tasks/functions.ts b/contracts/tasks/functions.ts new file mode 100644 index 0000000..6a8ae2b --- /dev/null +++ b/contracts/tasks/functions.ts @@ -0,0 +1,133 @@ +import { task } from "hardhat/config"; +import { Contract, TransactionReceipt } from "ethers"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { Func } from "mocha"; + +interface FunctionResponse { + response: string, + error: string, + } + +task("web_search", "Calls the web search function") + .addParam("contractAddress", "The address of the Test contract") + .addParam("query", "The query to ask the contract") + .setAction(async (taskArgs, hre) => { + const contractAddress = taskArgs.contractAddress; + const query = taskArgs.query; + + const contract = await getContract("Test", contractAddress, hre); + const response = await queryContractFunction(contract, "web_search", query, hre); + console.log(response) + if (response.error.length > 0) { + process.exit(1); + } +}); + +task("image_generation", "Calls the image generation function") + .addParam("contractAddress", "The address of the Test contract") + .addParam("query", "The query to ask the contract") + .setAction(async (taskArgs, hre) => { + const contractAddress = taskArgs.contractAddress; + const query = taskArgs.query; + + const contract = await getContract("Test", contractAddress, hre); + const response = await queryContractFunction(contract, "image_generation", query, hre); + console.log(response) + if (response.error.length > 0) { + process.exit(1); + } + }); + + task("code_interpreter", "Calls the code interpreter function") + .addParam("contractAddress", "The address of the Test contract") + .addParam("query", "The query to ask the contract") + .setAction(async (taskArgs, hre) => { + const contractAddress = taskArgs.contractAddress; + const query = taskArgs.query; + + const contract = await getContract("Test", contractAddress, hre); + const response = await queryContractFunction(contract, "code_interpreter", query, hre); + console.log(response) + if (response.error.length > 0) { + process.exit(1); + } + }); + + task("knowledge_base", "Queries a knowledge base") + .addParam("contractAddress", "The address of the Test contract") + .addParam("cid", "The CID of the knowledge base") + .addParam("query", "The query to ask the knowledge base") + .setAction(async (taskArgs, hre) => { + const contractAddress = taskArgs.contractAddress; + const cid = taskArgs.cid; + const query = taskArgs.query; + + const contract = await getContract("Test", contractAddress, hre); + const response = await queryContractKnowledgeBase(contract, cid, query, hre); + console.log(response) + if (response.error.length > 0) { + process.exit(1); + } + }); + + async function getContract( + name: string, + contractAddress: string, + hre: HardhatRuntimeEnvironment + ): Promise { + const signer = (await hre.ethers.getSigners())[0]; + const ContractArtifact = await hre.artifacts.readArtifact(name); + return new hre.ethers.Contract(contractAddress, ContractArtifact.abi, signer); + } + +async function queryContractFunction( + contract: Contract, + tool: string, + query: string, + hre: HardhatRuntimeEnvironment +): Promise { + try { + const txResponse = await contract.callFunction(tool, query); + await txResponse.wait(); + process.stdout.write("Waiting for response"); + let response = await contract.lastResponse(); + let error = await contract.lastError(); + while (response.length === 0 && error.length === 0) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + response = await contract.lastResponse(); + error = await contract.lastError(); + process.stdout.write("."); + } + console.log(""); + return { response: response, error: error }; + } catch (error) { + console.error(`Error calling contract function: ${error}`); + } + return { response: "", error: "Failed XX"}; +} + +async function queryContractKnowledgeBase( + contract: Contract, + cid: string, + query: string, + hre: HardhatRuntimeEnvironment +): Promise { + try { + const txResponse = await contract.queryKnowledgeBase(cid, query); + await txResponse.wait(); + process.stdout.write("Waiting for response"); + let response = await contract.lastResponse(); + let error = await contract.lastError(); + while (response.length === 0 && error.length === 0) { + await new Promise((resolve) => setTimeout(resolve, 1000)); + response = await contract.lastResponse(); + error = await contract.lastError(); + process.stdout.write("."); + } + console.log(""); + return { response: response, error: error }; + } catch (error) { + console.error(`Error calling contract function: ${error}`); + } + return { response: "", error: "Failed XX"}; +} \ No newline at end of file