diff --git a/contracts/TinyHops.sol b/contracts/TinyHops.sol index d324764..62a1dfa 100644 --- a/contracts/TinyHops.sol +++ b/contracts/TinyHops.sol @@ -10,10 +10,30 @@ interface ModicumContract { ) external payable returns (uint256); } +struct WorkflowEntry { + uint256 stepId; + string cmd; + string params; + uint256[] deps; +} + +struct Workflow { + WorkflowEntry[][] entries; +} + contract TinyHops { address public contractAddress; ModicumContract remoteContractInstance; + // simple ownership mapping + mapping(uint256 => address) workflowIdToOwner; + mapping(uint256 => uint256) workflowIdToStatus; + mapping(uint256 => Workflow) workflowIdToWorkflow; + mapping(uint256 => uint256) workflowIdToBalance; + + // auto increment workflow id + uint256 public workflowIdCounter; + // See the latest result. uint256 public resultJobId1; @@ -35,6 +55,26 @@ contract TinyHops { contractAddress = _modicumContract; //make a connection instance to the remote contract remoteContractInstance = ModicumContract(_modicumContract); + workflowIdCounter = 1; + } + + function startWorkflow( + Workflow memory workflow + ) public payable returns (uint256) { + require( + msg.value >= 5 ether, + "Payment of at least 5 Ether per step is required maybe more" + ); + + workflowIdToOwner[workflowIdCounter] = msg.sender; + workflowIdToWorkflow[workflowIdCounter] = workflow; + workflowIdToBalance[workflowIdCounter] = + msg.value + + workflowIdToBalance[workflowIdCounter]; + + // kick off the first workflow steps + + return ++workflowIdCounter; } function runBatchSDXL( diff --git a/contracts/library/LibTinyHopsTemplateResolver.sol b/contracts/library/LibTinyHopsTemplateResolver.sol new file mode 100644 index 0000000..e637a35 --- /dev/null +++ b/contracts/library/LibTinyHopsTemplateResolver.sol @@ -0,0 +1,126 @@ +pragma solidity ^0.8.15; + +// SPDX-License-Identifier: MIT +/* This specifies the format for workflows to be stored in the contract as +a byte stream. The format allows for the representation of template variables in cmds to be run +This allows us to simply store the workflow in the contract, and then apply the variables where needed +for the command prompt arguments. This is used to transform the templated workflow into a concrete workflow +The format is as follows: +- 2-byte segment type +- 8-byte segment length +- segment data +- repeat +The segment type is either 0 or 1. 0 indicates a text segment, 1 indicates a stepId segment. +The segment length is the length of the segment data in bytes. +The segment data is either the text data or the stepId. If the sgement is the stepId a uint256 is used. +The uint64 is the index of the step in the workflow. It represents the output of that specific step. +This allows us at construction of the workflow to know which steps are dependent on which other steps. +Allow us to block the execution of a step until the step it depends on has completed. + +NOTE - its probably more efficient ot simply store more data and align things in 32byte increments, +additionally using efficient copy operation with pre allocation of the result string would also +be more efficient. +example: + +*/ +library LibTinyHopsTemplateResolver { + function applyVariables( + bytes memory byteStream, + mapping(uint256 => string) storage replacements + ) public view returns (string memory) { + string memory result = ""; + uint256 ptr = 0; + uint256 startptr = 0; + uint256 endptr = 0; + + uint256 byteStreamLen = byteStream.length; + assembly { + ptr := add(byteStream, 0x20) + endptr := add(ptr, byteStreamLen) + startptr := ptr + } + while (ptr < endptr) { + uint256 segmentType; + uint256 rawType; + assembly { + rawType := shr(240, mload(ptr)) + segmentType := shr(240, mload(ptr)) + } + ptr += 2; // Move past the 2-byte prefix + + if (segmentType == 0) { + // Text segment + uint64 textLength; + assembly { + // we have to adjust the pointer to align the values + textLength := shr(192, mload(ptr)) + } + ptr += 8; // Move past the 8-byte length + + bytes memory textBytes = new bytes(textLength); + + uint256 currentPlace = ptr - startptr; + for (uint64 i = 0; i < textLength; i++) { + textBytes[i] = byteStream[currentPlace + i]; + } + ptr += textLength; // Move past the text data + result = string(abi.encodePacked(result, string(textBytes))); + } else if (segmentType == 1) { + // stepId segment + uint64 stepId; + assembly { + stepId := shr(192, mload(ptr)) + } + ptr += 8; // Move past the 8-byte stepId + + result = string(abi.encodePacked(result, replacements[stepId])); + } + } + + return result; + } + + // this allows anyone to validate a byte stream to ensure it is valid prior to a workflow call + function validateByteStream(bytes memory byteStream) public pure { + uint256 ptr = 0; + uint256 endPtr = 0; + uint256 byteStreamLen = byteStream.length; + assembly { + ptr := add(byteStream, 0x20) + endPtr := add(ptr, byteStreamLen) + } + while (ptr < endPtr) { + // Ensure there's enough data for the 2-byte prefix + require(ptr + 2 <= endPtr, "Incomplete prefix"); + + uint16 segmentType; + assembly { + segmentType := shr(240, mload(ptr)) + } + ptr += 2; // Move past the 2-byte prefix + + if (segmentType == 0) { + // Text segment + // Ensure there's enough data for the 8-byte length + require(ptr + 8 <= endPtr, "Incomplete text length"); + + uint64 textLength; + assembly { + textLength := shr(192, mload(ptr)) + } + ptr += 8; // Move past the 8-byte length + + // Ensure there's enough data for the text + require(ptr + textLength <= endPtr, "Incomplete text data"); + ptr += textLength; // Move past the text data + } else if (segmentType == 1) { + // stepId segment + // Ensure there's enough data for the 8-byte stepId + require(ptr + 8 <= endPtr, "Incomplete stepId"); + ptr += 8; // Move past the 8-byte stepId + } else { + revert("Invalid segment type"); + } + } + } +} diff --git a/example.thops.json b/example.thops.json index 09d515a..30cd48c 100644 --- a/example.thops.json +++ b/example.thops.json @@ -3,12 +3,40 @@ "steps": [ { "stepDesc": "First cow say", + "stepName": "cows say", "stepId": "1", "stepModule": { "cmd": "cowsay:v0.0.1", - "params": "helloworld {{stepId}}" + "params": "{{stepName:2}} helloworld" + } + }, + [{ + "stepDesc": "First cow say", + "stepName": "cows say", + "stepId": "2", + "stepModule": { + "cmd": "cowsay:v0.0.1", + "params": "{{stepName:2}} hello parallel world {stepId:1}" + } + }, + { + "stepDesc": "First cow say", + "stepName": "cows say", + "stepId": "3", + "stepModule": { + "cmd": "cowsay:v0.0.1", + "params": "{{stepName:2}} hello parallel world {stepId:1}" + } + }], + { + "stepDesc": "last cow say", + "stepName": "lastcowsay", + "stepId": "4", + "stepModule": { + "cmd": "cowsay:v0.0.1", + "params": "{{stepName:4}} hello parallel world {stepId:2} {stepId:3}" } } - ] -} \ No newline at end of file +} + diff --git a/package-lock.json b/package-lock.json index 6ecebb5..6962503 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0-development", "license": "Apache-2.0", "dependencies": { + "chalk": "^4.0.0", "dotenv": "^16.3.1", "typescript": "^5.2.2" }, @@ -17,6 +18,7 @@ "@nomicfoundation/hardhat-foundry": "^1.1.1", "@nomicfoundation/hardhat-toolbox": "^3.0.0", "@typechain/ethers-v6": "^0.4.0", + "@types/commander": "^2.12.2", "ethers": "^6.7.1", "ts-node": "^10.9.1" } @@ -1617,6 +1619,20 @@ "hardhat": "^2.17.2" } }, + "node_modules/@nomicfoundation/hardhat-foundry/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@nomicfoundation/hardhat-network-helpers": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.9.tgz", @@ -1717,6 +1733,21 @@ "hardhat": "^2.0.4" } }, + "node_modules/@nomicfoundation/hardhat-verify/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@nomicfoundation/solidity-analyzer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.1.tgz", @@ -2262,6 +2293,16 @@ "@types/chai": "*" } }, + "node_modules/@types/commander": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz", + "integrity": "sha512-0QEFiR8ljcHp9bAbWxecjVRuAMr16ivPiGOw6KFQBVrVd0RQIcM3xKdRisH2EDWgVWujiYtHwhSkSUoAAGzH7Q==", + "deprecated": "This is a stub types definition for commander (https://github.com/tj/commander.js). commander provides its own type definitions, so you don't need @types/commander installed!", + "dev": true, + "dependencies": { + "commander": "*" + } + }, "node_modules/@types/concat-stream": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", @@ -3108,17 +3149,67 @@ } }, "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/chalk/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/chalk/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/charenc": { @@ -3378,6 +3469,21 @@ "node": ">=8" } }, + "node_modules/command-line-usage/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/command-line-usage/node_modules/typical": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", @@ -3389,11 +3495,13 @@ } }, "node_modules/commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz", + "integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==", "dev": true, - "peer": true + "engines": { + "node": ">=16" + } }, "node_modules/concat-map": { "version": "0.0.1", @@ -4068,6 +4176,34 @@ "node": ">=6" } }, + "node_modules/eth-gas-reporter/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/eth-gas-reporter/node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/eth-gas-reporter/node_modules/chokidar": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", @@ -5108,6 +5244,21 @@ "testrpc-sc": "index.js" } }, + "node_modules/ghost-testrpc/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -5370,6 +5521,21 @@ "hardhat": "^2.0.2" } }, + "node_modules/hardhat/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/hardhat/node_modules/ws": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", @@ -6343,82 +6509,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true - }, - "node_modules/log-symbols/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/loupe": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", @@ -8108,6 +8198,13 @@ "node": ">=8.0.0" } }, + "node_modules/solc/node_modules/commander": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true, + "peer": true + }, "node_modules/solc/node_modules/fs-extra": { "version": "0.30.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", @@ -8187,6 +8284,21 @@ "antlr4ts": "^0.5.0-alpha.4" } }, + "node_modules/solidity-coverage/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/solidity-coverage/node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -8703,82 +8815,6 @@ "write-markdown": "dist/write-markdown.js" } }, - "node_modules/ts-command-line-args/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ts-command-line-args/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ts-command-line-args/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ts-command-line-args/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "peer": true - }, - "node_modules/ts-command-line-args/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-command-line-args/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ts-essentials": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz", diff --git a/package.json b/package.json index 4d65733..8d6452e 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,16 @@ "name": "tiny-hops", "version": "0.0.0-development", "description": "A workflow executor for lilypad", - "main": "build/src/cli/index.ts", + "main": "build/src/cli/index.js", + "module": "esm", "directories": { "lib": "lib", "test": "test" }, "scripts": { - "gen": "npx hardhat compile && rm src/artifacts/contracts/**/*.dbg.json; typechain --target ethers-v6 --out-dir src/generated/typechain src/artifacts/contracts/facets/**/*.json", - "test": "echo \"Error: no test specified\" && exit 1" + "compile": "tsc", + "compile-contracts": "npx hardhat compile", + "test": "forge test" }, "author": "", "license": "Apache-2.0", @@ -17,11 +19,13 @@ "@nomicfoundation/hardhat-ethers": "^3.0.4", "@nomicfoundation/hardhat-foundry": "^1.1.1", "@nomicfoundation/hardhat-toolbox": "^3.0.0", - "@typechain/ethers-v6": "^0.5.0", + "@typechain/ethers-v6": "^0.4.0", + "@types/commander": "^2.12.2", "ethers": "^6.7.1", "ts-node": "^10.9.1" }, "dependencies": { + "chalk": "^4.0.0", "dotenv": "^16.3.1", "typescript": "^5.2.2" } diff --git a/schema.md b/schema.md index 9a653fe..101d27f 100644 --- a/schema.md +++ b/schema.md @@ -1,17 +1,15 @@ # Tiny-Hops Schema +#### Supported Template Variables +``` +{{stepId:integer}} - Specifies the step resultCID to process +``` + #### Example ```json { steps : [{ stepName: "foobar", - stepInput: - [ { - cid: "xxxxx" - } - ] - stepOutput: [ { - cid: - }] + }] } diff --git a/src/cli/index.ts b/src/cli/index.ts new file mode 100644 index 0000000..b2090b9 --- /dev/null +++ b/src/cli/index.ts @@ -0,0 +1,81 @@ +import { Command } from 'commander'; +import chalk from 'chalk'; + +const program = new Command(); + +program + .version('1.0.0') + .description('Tiny Hops CLI for managing and monitoring workflows'); + +program + .command('generate-template') + .description('Generate a template configuration file') + .action(() => { + console.log(chalk.green('Generating template...')); + // Implementation here... + }); + +program + .command('run ') + .option('-d, --deposit ', 'Deposit amount in ETH') + .description('Run a workflow with a specified configuration file') + .action((config, options) => { + console.log(chalk.green(`Running workflow with config: ${chalk.cyan(config)} and deposit: ${chalk.yellow(options.deposit)}`)); + // Implementation here... + }); + +program + .command('monitor ') + .description('Monitor a running workflow') + .action((workflowId, config) => { + console.log(chalk.green(`Monitoring workflow: ${chalk.cyan(workflowId)} with config: ${chalk.cyan(config)}`)); + // Implementation here... + }); + +program + .command('pause ') + .description('Pause a running workflow') + .action((workflowId) => { + console.log(chalk.green(`Pausing workflow: ${chalk.cyan(workflowId)}`)); + // Implementation here... + }); + +// Override the helpInformation method to colorize the arguments +program.configureHelp({ + // Sort commands alphabetically + sortSubcommands: true, + // Colorize the output + formatHelp: (cmd, helper) => { + const colorize = (str: string) => chalk.cyanBright(str); + const format = (str: string) => chalk.bold(str); + const formatOption = (str: string) => chalk.yellow(str); + const formatCommand = (str: string) => chalk.green(str); + + const desc = cmd.description() ? `\n${cmd.description()}\n` : ''; + const usage = `${format('Usage:')} ${formatCommand(cmd.name())} ${colorize(cmd.usage())}\n`; + + let options = ''; + cmd.options.forEach((option) => { + const optionFlags = option.flags; + const optionDescription = option.description; + options += ` ${formatOption(optionFlags)}\t${optionDescription}\n`; + }); + if (options) { + options = `\n${format('Options:')}\n${options}`; + } + + let commands = ''; + cmd.commands.forEach((command) => { + const commandName = command.name(); + const commandDescription = command.description(); + commands += ` ${formatCommand(commandName)}\t${commandDescription}\n`; + }); + if (commands) { + commands = `\n${format('Commands:')}\n${commands}`; + } + + return `${usage}${desc}${options}${commands}`; + }, +}); + +program.parseAsync(process.argv); diff --git a/src/generated/typechain-types/ModicumContract.ts b/src/generated/typechain-types/ModicumContract.ts new file mode 100644 index 0000000..f083784 --- /dev/null +++ b/src/generated/typechain-types/ModicumContract.ts @@ -0,0 +1,96 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BytesLike, + FunctionFragment, + Result, + Interface, + ContractRunner, + ContractMethod, + Listener, +} from "ethers"; +import type { + TypedContractEvent, + TypedDeferredTopicFilter, + TypedEventLog, + TypedListener, + TypedContractMethod, +} from "./common"; + +export interface ModicumContractInterface extends Interface { + getFunction( + nameOrSignature: "runModuleWithDefaultMediators" + ): FunctionFragment; + + encodeFunctionData( + functionFragment: "runModuleWithDefaultMediators", + values: [string, string] + ): string; + + decodeFunctionResult( + functionFragment: "runModuleWithDefaultMediators", + data: BytesLike + ): Result; +} + +export interface ModicumContract extends BaseContract { + connect(runner?: ContractRunner | null): ModicumContract; + waitForDeployment(): Promise; + + interface: ModicumContractInterface; + + queryFilter( + event: TCEvent, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + queryFilter( + filter: TypedDeferredTopicFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + + on( + event: TCEvent, + listener: TypedListener + ): Promise; + on( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + once( + event: TCEvent, + listener: TypedListener + ): Promise; + once( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + listeners( + event: TCEvent + ): Promise>>; + listeners(eventName?: string): Promise>; + removeAllListeners( + event?: TCEvent + ): Promise; + + runModuleWithDefaultMediators: TypedContractMethod< + [name: string, params: string], + [bigint], + "payable" + >; + + getFunction( + key: string | FunctionFragment + ): T; + + getFunction( + nameOrSignature: "runModuleWithDefaultMediators" + ): TypedContractMethod<[name: string, params: string], [bigint], "payable">; + + filters: {}; +} diff --git a/src/generated/typechain-types/TinyHops.ts b/src/generated/typechain-types/TinyHops.ts new file mode 100644 index 0000000..f85f65f --- /dev/null +++ b/src/generated/typechain-types/TinyHops.ts @@ -0,0 +1,214 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + BaseContract, + BigNumberish, + BytesLike, + FunctionFragment, + Result, + Interface, + ContractRunner, + ContractMethod, + Listener, +} from "ethers"; +import type { + TypedContractEvent, + TypedDeferredTopicFilter, + TypedEventLog, + TypedListener, + TypedContractMethod, +} from "./common"; + +export interface TinyHopsInterface extends Interface { + getFunction( + nameOrSignature: + | "contractAddress" + | "job1" + | "job2" + | "receiveJobResults" + | "resultCIDs1" + | "resultCIDs2" + | "resultJobId1" + | "resultJobId2" + | "runBatchSDXL" + | "runSDXL" + ): FunctionFragment; + + encodeFunctionData( + functionFragment: "contractAddress", + values?: undefined + ): string; + encodeFunctionData(functionFragment: "job1", values?: undefined): string; + encodeFunctionData(functionFragment: "job2", values?: undefined): string; + encodeFunctionData( + functionFragment: "receiveJobResults", + values: [BigNumberish, string] + ): string; + encodeFunctionData( + functionFragment: "resultCIDs1", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "resultCIDs2", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "resultJobId1", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "resultJobId2", + values?: undefined + ): string; + encodeFunctionData( + functionFragment: "runBatchSDXL", + values: [string, string] + ): string; + encodeFunctionData(functionFragment: "runSDXL", values: [string]): string; + + decodeFunctionResult( + functionFragment: "contractAddress", + data: BytesLike + ): Result; + decodeFunctionResult(functionFragment: "job1", data: BytesLike): Result; + decodeFunctionResult(functionFragment: "job2", data: BytesLike): Result; + decodeFunctionResult( + functionFragment: "receiveJobResults", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "resultCIDs1", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "resultCIDs2", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "resultJobId1", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "resultJobId2", + data: BytesLike + ): Result; + decodeFunctionResult( + functionFragment: "runBatchSDXL", + data: BytesLike + ): Result; + decodeFunctionResult(functionFragment: "runSDXL", data: BytesLike): Result; +} + +export interface TinyHops extends BaseContract { + connect(runner?: ContractRunner | null): TinyHops; + waitForDeployment(): Promise; + + interface: TinyHopsInterface; + + queryFilter( + event: TCEvent, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + queryFilter( + filter: TypedDeferredTopicFilter, + fromBlockOrBlockhash?: string | number | undefined, + toBlock?: string | number | undefined + ): Promise>>; + + on( + event: TCEvent, + listener: TypedListener + ): Promise; + on( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + once( + event: TCEvent, + listener: TypedListener + ): Promise; + once( + filter: TypedDeferredTopicFilter, + listener: TypedListener + ): Promise; + + listeners( + event: TCEvent + ): Promise>>; + listeners(eventName?: string): Promise>; + removeAllListeners( + event?: TCEvent + ): Promise; + + contractAddress: TypedContractMethod<[], [string], "view">; + + job1: TypedContractMethod<[], [bigint], "view">; + + job2: TypedContractMethod<[], [bigint], "view">; + + receiveJobResults: TypedContractMethod< + [_jobID: BigNumberish, _cid: string], + [void], + "nonpayable" + >; + + resultCIDs1: TypedContractMethod<[], [string], "view">; + + resultCIDs2: TypedContractMethod<[], [string], "view">; + + resultJobId1: TypedContractMethod<[], [bigint], "view">; + + resultJobId2: TypedContractMethod<[], [bigint], "view">; + + runBatchSDXL: TypedContractMethod< + [prompt1: string, prompt2: string], + [void], + "payable" + >; + + runSDXL: TypedContractMethod<[prompt: string], [bigint], "payable">; + + getFunction( + key: string | FunctionFragment + ): T; + + getFunction( + nameOrSignature: "contractAddress" + ): TypedContractMethod<[], [string], "view">; + getFunction( + nameOrSignature: "job1" + ): TypedContractMethod<[], [bigint], "view">; + getFunction( + nameOrSignature: "job2" + ): TypedContractMethod<[], [bigint], "view">; + getFunction( + nameOrSignature: "receiveJobResults" + ): TypedContractMethod< + [_jobID: BigNumberish, _cid: string], + [void], + "nonpayable" + >; + getFunction( + nameOrSignature: "resultCIDs1" + ): TypedContractMethod<[], [string], "view">; + getFunction( + nameOrSignature: "resultCIDs2" + ): TypedContractMethod<[], [string], "view">; + getFunction( + nameOrSignature: "resultJobId1" + ): TypedContractMethod<[], [bigint], "view">; + getFunction( + nameOrSignature: "resultJobId2" + ): TypedContractMethod<[], [bigint], "view">; + getFunction( + nameOrSignature: "runBatchSDXL" + ): TypedContractMethod<[prompt1: string, prompt2: string], [void], "payable">; + getFunction( + nameOrSignature: "runSDXL" + ): TypedContractMethod<[prompt: string], [bigint], "payable">; + + filters: {}; +} diff --git a/src/generated/typechain-types/common.ts b/src/generated/typechain-types/common.ts new file mode 100644 index 0000000..192c895 --- /dev/null +++ b/src/generated/typechain-types/common.ts @@ -0,0 +1,129 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { + FunctionFragment, + Typed, + EventFragment, + ContractTransaction, + ContractTransactionResponse, + DeferredTopicFilter, + EventLog, + TransactionRequest, + LogDescription, +} from "ethers"; + +export interface TypedDeferredTopicFilter<_TCEvent extends TypedContractEvent> + extends DeferredTopicFilter {} + +export interface TypedContractEvent< + InputTuple extends Array = any, + OutputTuple extends Array = any, + OutputObject = any +> { + (...args: Partial): TypedDeferredTopicFilter< + TypedContractEvent + >; + name: string; + fragment: EventFragment; + getFragment(...args: Partial): EventFragment; +} + +type __TypechainAOutputTuple = T extends TypedContractEvent< + infer _U, + infer W +> + ? W + : never; +type __TypechainOutputObject = T extends TypedContractEvent< + infer _U, + infer _W, + infer V +> + ? V + : never; + +export interface TypedEventLog + extends Omit { + args: __TypechainAOutputTuple & __TypechainOutputObject; +} + +export interface TypedLogDescription + extends Omit { + args: __TypechainAOutputTuple & __TypechainOutputObject; +} + +export type TypedListener = ( + ...listenerArg: [ + ...__TypechainAOutputTuple, + TypedEventLog, + ...undefined[] + ] +) => void; + +export type MinEthersFactory = { + deploy(...a: ARGS[]): Promise; +}; + +export type GetContractTypeFromFactory = F extends MinEthersFactory< + infer C, + any +> + ? C + : never; +export type GetARGsTypeFromFactory = F extends MinEthersFactory + ? Parameters + : never; + +export type StateMutability = "nonpayable" | "payable" | "view"; + +export type BaseOverrides = Omit; +export type NonPayableOverrides = Omit< + BaseOverrides, + "value" | "blockTag" | "enableCcipRead" +>; +export type PayableOverrides = Omit< + BaseOverrides, + "blockTag" | "enableCcipRead" +>; +export type ViewOverrides = Omit; +export type Overrides = S extends "nonpayable" + ? NonPayableOverrides + : S extends "payable" + ? PayableOverrides + : ViewOverrides; + +export type PostfixOverrides, S extends StateMutability> = + | A + | [...A, Overrides]; +export type ContractMethodArgs< + A extends Array, + S extends StateMutability +> = PostfixOverrides<{ [I in keyof A]-?: A[I] | Typed }, S>; + +export type DefaultReturnType = R extends Array ? R[0] : R; + +// export interface ContractMethod = Array, R = any, D extends R | ContractTransactionResponse = R | ContractTransactionResponse> { +export interface TypedContractMethod< + A extends Array = Array, + R = any, + S extends StateMutability = "payable" +> { + (...args: ContractMethodArgs): S extends "view" + ? Promise> + : Promise; + + name: string; + + fragment: FunctionFragment; + + getFragment(...args: ContractMethodArgs): FunctionFragment; + + populateTransaction( + ...args: ContractMethodArgs + ): Promise; + staticCall(...args: ContractMethodArgs): Promise>; + send(...args: ContractMethodArgs): Promise; + estimateGas(...args: ContractMethodArgs): Promise; + staticCallResult(...args: ContractMethodArgs): Promise; +} diff --git a/src/generated/typechain-types/factories/ModicumContract__factory.ts b/src/generated/typechain-types/factories/ModicumContract__factory.ts new file mode 100644 index 0000000..c64a32d --- /dev/null +++ b/src/generated/typechain-types/factories/ModicumContract__factory.ts @@ -0,0 +1,49 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { Contract, Interface, type ContractRunner } from "ethers"; +import type { + ModicumContract, + ModicumContractInterface, +} from "../ModicumContract"; + +const _abi = [ + { + inputs: [ + { + internalType: "string", + name: "name", + type: "string", + }, + { + internalType: "string", + name: "params", + type: "string", + }, + ], + name: "runModuleWithDefaultMediators", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "payable", + type: "function", + }, +] as const; + +export class ModicumContract__factory { + static readonly abi = _abi; + static createInterface(): ModicumContractInterface { + return new Interface(_abi) as ModicumContractInterface; + } + static connect( + address: string, + runner?: ContractRunner | null + ): ModicumContract { + return new Contract(address, _abi, runner) as unknown as ModicumContract; + } +} diff --git a/src/generated/typechain-types/factories/TinyHops__factory.ts b/src/generated/typechain-types/factories/TinyHops__factory.ts new file mode 100644 index 0000000..3c70b6b --- /dev/null +++ b/src/generated/typechain-types/factories/TinyHops__factory.ts @@ -0,0 +1,227 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import { + Contract, + ContractFactory, + ContractTransactionResponse, + Interface, +} from "ethers"; +import type { + Signer, + AddressLike, + ContractDeployTransaction, + ContractRunner, +} from "ethers"; +import type { NonPayableOverrides } from "../common"; +import type { TinyHops, TinyHopsInterface } from "../TinyHops"; + +const _abi = [ + { + inputs: [ + { + internalType: "address", + name: "_modicumContract", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + inputs: [], + name: "contractAddress", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "job1", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "job2", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "uint256", + name: "_jobID", + type: "uint256", + }, + { + internalType: "string", + name: "_cid", + type: "string", + }, + ], + name: "receiveJobResults", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "resultCIDs1", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "resultCIDs2", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "resultJobId1", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "resultJobId2", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "prompt1", + type: "string", + }, + { + internalType: "string", + name: "prompt2", + type: "string", + }, + ], + name: "runBatchSDXL", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "prompt", + type: "string", + }, + ], + name: "runSDXL", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "payable", + type: "function", + }, +] as const; + +const _bytecode = + "0x608060405234801561001057600080fd5b5060405161096c38038061096c83398101604081905261002f916100b8565b6001600160a01b0381166100895760405162461bcd60e51b815260206004820152601f60248201527f436f6e74726163742063616e6e6f74206265207a65726f206164647265737300604482015260640160405180910390fd5b600080546001600160a01b039092166001600160a01b03199283168117909155600180549092161790556100e8565b6000602082840312156100ca57600080fd5b81516001600160a01b03811681146100e157600080fd5b9392505050565b610875806100f76000396000f3fe6080604052600436106100b15760003560e01c80636cfac9e211610069578063b061f8dc1161004e578063b061f8dc14610179578063f227a3e41461018f578063f6b4dfb4146101a457600080fd5b80636cfac9e2146101435780637a4943621461015957600080fd5b80634c8f44681161009a5780634c8f4468146100f65780635b8f66c1146101175780636c0ff76b1461012d57600080fd5b806306ab218c146100b65780632aceef78146100e1575b600080fd5b3480156100c257600080fd5b506100cb6101dc565b6040516100d8919061049a565b60405180910390f35b6100f46100ef366004610557565b61026a565b005b6101096101043660046105bb565b61036c565b6040519081526020016100d8565b34801561012357600080fd5b5061010960075481565b34801561013957600080fd5b5061010960025481565b34801561014f57600080fd5b5061010960035481565b34801561016557600080fd5b506100f46101743660046105f8565b610401565b34801561018557600080fd5b5061010960065481565b34801561019b57600080fd5b506100cb610440565b3480156101b057600080fd5b506000546101c4906001600160a01b031681565b6040516001600160a01b0390911681526020016100d8565b600480546101e990610674565b80601f016020809104026020016040519081016040528092919081815260200182805461021590610674565b80156102625780601f1061023757610100808354040283529160200191610262565b820191906000526020600020905b81548152906001019060200180831161024557829003601f168201915b505050505081565b6001546001600160a01b0316637ce7b1796102866002346106ae565b846040518363ffffffff1660e01b81526004016102a391906106d0565b60206040518083038185885af11580156102c1573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906102e69190610717565b6006556001546001600160a01b0316637ce7b1796103056002346106ae565b836040518363ffffffff1660e01b815260040161032291906106d0565b60206040518083038185885af1158015610340573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906103659190610717565b6007555050565b6001546040517f7ce7b1790000000000000000000000000000000000000000000000000000000081526000916001600160a01b031690637ce7b1799034906103b89086906004016106d0565b60206040518083038185885af11580156103d6573d6000803e3d6000fd5b50505050506040513d601f19601f820116820180604052508101906103fb9190610717565b92915050565b600654830361041e576002839055600461041c82848361077e565b505b600754830361043b576003839055600561043982848361077e565b505b505050565b600580546101e990610674565b6000815180845260005b8181101561047357602081850181015186830182015201610457565b81811115610485576000602083870101525b50601f01601f19169290920160200192915050565b6020815260006104ad602083018461044d565b9392505050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126104db57600080fd5b813567ffffffffffffffff808211156104f6576104f66104b4565b604051601f8301601f19908116603f0116810190828211818310171561051e5761051e6104b4565b8160405283815286602085880101111561053757600080fd5b836020870160208301376000602085830101528094505050505092915050565b6000806040838503121561056a57600080fd5b823567ffffffffffffffff8082111561058257600080fd5b61058e868387016104ca565b935060208501359150808211156105a457600080fd5b506105b1858286016104ca565b9150509250929050565b6000602082840312156105cd57600080fd5b813567ffffffffffffffff8111156105e457600080fd5b6105f0848285016104ca565b949350505050565b60008060006040848603121561060d57600080fd5b83359250602084013567ffffffffffffffff8082111561062c57600080fd5b818601915086601f83011261064057600080fd5b81358181111561064f57600080fd5b87602082850101111561066157600080fd5b6020830194508093505050509250925092565b600181811c9082168061068857607f821691505b6020821081036106a857634e487b7160e01b600052602260045260246000fd5b50919050565b6000826106cb57634e487b7160e01b600052601260045260246000fd5b500490565b60408152601260408201527f7364786c3a76302e392d6c696c7970616431000000000000000000000000000060608201526080602082015260006104ad608083018461044d565b60006020828403121561072957600080fd5b5051919050565b601f82111561043b57600081815260208120601f850160051c810160208610156107575750805b601f850160051c820191505b8181101561077657828155600101610763565b505050505050565b67ffffffffffffffff831115610796576107966104b4565b6107aa836107a48354610674565b83610730565b6000601f8411600181146107de57600085156107c65750838201355b600019600387901b1c1916600186901b178355610838565b600083815260209020601f19861690835b8281101561080f57868501358255602094850194600190920191016107ef565b508682101561082c5760001960f88860031b161c19848701351681555b505060018560011b0183555b505050505056fea2646970667358221220f535e4084aa45f9994c80781fdd3db14394faad2a21f6f3a00282ec22618efe564736f6c634300080f0033"; + +type TinyHopsConstructorParams = + | [signer?: Signer] + | ConstructorParameters; + +const isSuperArgs = ( + xs: TinyHopsConstructorParams +): xs is ConstructorParameters => xs.length > 1; + +export class TinyHops__factory extends ContractFactory { + constructor(...args: TinyHopsConstructorParams) { + if (isSuperArgs(args)) { + super(...args); + } else { + super(_abi, _bytecode, args[0]); + } + } + + override getDeployTransaction( + _modicumContract: AddressLike, + overrides?: NonPayableOverrides & { from?: string } + ): Promise { + return super.getDeployTransaction(_modicumContract, overrides || {}); + } + override deploy( + _modicumContract: AddressLike, + overrides?: NonPayableOverrides & { from?: string } + ) { + return super.deploy(_modicumContract, overrides || {}) as Promise< + TinyHops & { + deploymentTransaction(): ContractTransactionResponse; + } + >; + } + override connect(runner: ContractRunner | null): TinyHops__factory { + return super.connect(runner) as TinyHops__factory; + } + + static readonly bytecode = _bytecode; + static readonly abi = _abi; + static createInterface(): TinyHopsInterface { + return new Interface(_abi) as TinyHopsInterface; + } + static connect(address: string, runner?: ContractRunner | null): TinyHops { + return new Contract(address, _abi, runner) as unknown as TinyHops; + } +} diff --git a/src/generated/typechain-types/factories/index.ts b/src/generated/typechain-types/factories/index.ts new file mode 100644 index 0000000..66d0746 --- /dev/null +++ b/src/generated/typechain-types/factories/index.ts @@ -0,0 +1,5 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export { ModicumContract__factory } from "./ModicumContract__factory"; +export { TinyHops__factory } from "./TinyHops__factory"; diff --git a/src/generated/typechain-types/hardhat.d.ts b/src/generated/typechain-types/hardhat.d.ts new file mode 100644 index 0000000..ea4a7d0 --- /dev/null +++ b/src/generated/typechain-types/hardhat.d.ts @@ -0,0 +1,81 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import { ethers } from "ethers"; +import { + DeployContractOptions, + FactoryOptions, + HardhatEthersHelpers as HardhatEthersHelpersBase, +} from "@nomicfoundation/hardhat-ethers/types"; + +import * as Contracts from "."; + +declare module "hardhat/types/runtime" { + interface HardhatEthersHelpers extends HardhatEthersHelpersBase { + getContractFactory( + name: "ModicumContract", + signerOrOptions?: ethers.Signer | FactoryOptions + ): Promise; + getContractFactory( + name: "TinyHops", + signerOrOptions?: ethers.Signer | FactoryOptions + ): Promise; + + getContractAt( + name: "ModicumContract", + address: string | ethers.Addressable, + signer?: ethers.Signer + ): Promise; + getContractAt( + name: "TinyHops", + address: string | ethers.Addressable, + signer?: ethers.Signer + ): Promise; + + deployContract( + name: "ModicumContract", + signerOrOptions?: ethers.Signer | DeployContractOptions + ): Promise; + deployContract( + name: "TinyHops", + signerOrOptions?: ethers.Signer | DeployContractOptions + ): Promise; + + deployContract( + name: "ModicumContract", + args: any[], + signerOrOptions?: ethers.Signer | DeployContractOptions + ): Promise; + deployContract( + name: "TinyHops", + args: any[], + signerOrOptions?: ethers.Signer | DeployContractOptions + ): Promise; + + // default types + getContractFactory( + name: string, + signerOrOptions?: ethers.Signer | FactoryOptions + ): Promise; + getContractFactory( + abi: any[], + bytecode: ethers.BytesLike, + signer?: ethers.Signer + ): Promise; + getContractAt( + nameOrAbi: string | any[], + address: string | ethers.Addressable, + signer?: ethers.Signer + ): Promise; + deployContract( + name: string, + signerOrOptions?: ethers.Signer | DeployContractOptions + ): Promise; + deployContract( + name: string, + args: any[], + signerOrOptions?: ethers.Signer | DeployContractOptions + ): Promise; + } +} diff --git a/src/generated/typechain-types/index.ts b/src/generated/typechain-types/index.ts new file mode 100644 index 0000000..748d91b --- /dev/null +++ b/src/generated/typechain-types/index.ts @@ -0,0 +1,8 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export type { ModicumContract } from "./ModicumContract"; +export type { TinyHops } from "./TinyHops"; +export * as factories from "./factories"; +export { ModicumContract__factory } from "./factories/ModicumContract__factory"; +export { TinyHops__factory } from "./factories/TinyHops__factory"; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/test/foundry/ModicumMock.t.sol b/test/foundry/ModicumMock.t.sol new file mode 100644 index 0000000..81bdefa --- /dev/null +++ b/test/foundry/ModicumMock.t.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; + +contract ModicumMockTest is Test { + uint256 public currentJob; + + function clearJobIds() public { + currentJob = 0; + } + + function runModuleWithDefaultMediators( + string calldata name, + string calldata params + ) external payable returns (uint256) { + // use sha256 to also generate a uint256 number + return getJobId(name, params, ++currentJob); + } + + function getJobId( + string calldata name, + string calldata params, + uint256 jobNo + ) public pure returns (uint256) { + return uint256(keccak256(abi.encodePacked(name, params, jobNo))); + } +} diff --git a/test/foundry/TinyHops.t.sol b/test/foundry/TinyHops.t.sol new file mode 100644 index 0000000..f73347d --- /dev/null +++ b/test/foundry/TinyHops.t.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "./ModicumMock.t.sol"; +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import "../../contracts/TinyHops.sol"; +import "../../contracts/library/LibTinyHopsTemplateResolver.sol"; + +contract TinyHopsTest is Test { + TinyHops public tinyHops; + ModicumMockTest public modicumMock; + address user1 = makeAddr("user1"); + address user2 = makeAddr("user2"); + address modicumNode = makeAddr("modicumNode"); + mapping(uint256 => string) replacements; + + function setUp() public { + modicumMock = new ModicumMockTest(); + tinyHops = new TinyHops(address(modicumMock)); + } + + function getTestStream() public view returns (bytes memory testStream) { + bytes memory hello = abi.encodePacked( + uint16(0), + uint64(6), + bytes("hello ") + ); + bytes memory space = abi.encodePacked(uint16(0), uint64(1), bytes(" ")); + bytes memory world = abi.encodePacked( + uint16(0), + uint64(7), + bytes(" world ") + ); + bytes memory stepId0 = abi.encodePacked(uint16(1), uint64(0)); + bytes memory stepId1 = abi.encodePacked(uint16(1), uint64(1)); + + testStream = abi.encodePacked(hello, stepId0, world, stepId1); + } + + function testValidateWorkflow() public { + bytes memory testStream = getTestStream(); + LibTinyHopsTemplateResolver.validateByteStream(testStream); + + replacements[0] = "QmNjJUyFZpSg7HC9akujZ6KHWvJbCEytre3NRSMHzCA6NR"; + replacements[1] = "QmNjJUyFZpSg7HC9akujZ6KHWvJbCEytre3NRSMHzCA6N1"; + string memory resolved = LibTinyHopsTemplateResolver.applyVariables( + testStream, + replacements + ); + console.log("resolved", resolved); + assertEq( + resolved, + "hello QmNjJUyFZpSg7HC9akujZ6KHWvJbCEytre3NRSMHzCA6NR world QmNjJUyFZpSg7HC9akujZ6KHWvJbCEytre3NRSMHzCA6N1" + ); + } + + function testSingleJob() public { + vm.startPrank(user1); + // uint256 workflowId = tinyHops.startWorkflow(workflow); + // tinyHops.getWorkflow(workflowId); + } + + function testSequentialJob() public {} + + function testParallelJob() public {} + + function testPauseAndResumeJob() public {} + + function testJobStatus() public {} +}