From 060a9e01714307e480297252475beabbff1a790a Mon Sep 17 00:00:00 2001 From: Chibey-max Date: Mon, 16 Feb 2026 09:44:10 +0100 Subject: [PATCH 1/4] feat: submitting todo assignment --- assignments/Solidity/Todo/.gitignore | 20 +++ assignments/Solidity/Todo/README.md | 117 ++++++++++++++++++ .../Solidity/Todo/contracts/Counter.sol | 19 +++ .../Solidity/Todo/contracts/Counter.t.sol | 32 +++++ assignments/Solidity/Todo/contracts/Todo.sol | 60 +++++++++ assignments/Solidity/Todo/hardhat.config.ts | 44 +++++++ .../artifacts/TodoModule#Todo.json | 109 ++++++++++++++++ ...40f654bf38476b2528d38a332effe6a398f85.json | 39 ++++++ .../chain-11155111/deployed_addresses.json | 3 + .../deployments/chain-11155111/journal.jsonl | 8 ++ .../Solidity/Todo/ignition/modules/Todo.ts | 7 ++ assignments/Solidity/Todo/package.json | 25 ++++ .../Solidity/Todo/scripts/GenerateKey.js | 13 ++ .../Solidity/Todo/scripts/send-op-tx.ts | 22 ++++ assignments/Solidity/Todo/test/Counter.ts | 36 ++++++ assignments/Solidity/Todo/test/Todo.ts | 0 assignments/Solidity/Todo/tsconfig.json | 13 ++ 17 files changed, 567 insertions(+) create mode 100644 assignments/Solidity/Todo/.gitignore create mode 100644 assignments/Solidity/Todo/README.md create mode 100644 assignments/Solidity/Todo/contracts/Counter.sol create mode 100644 assignments/Solidity/Todo/contracts/Counter.t.sol create mode 100644 assignments/Solidity/Todo/contracts/Todo.sol create mode 100644 assignments/Solidity/Todo/hardhat.config.ts create mode 100644 assignments/Solidity/Todo/ignition/deployments/chain-11155111/artifacts/TodoModule#Todo.json create mode 100644 assignments/Solidity/Todo/ignition/deployments/chain-11155111/build-info/solc-0_8_28-33b40f654bf38476b2528d38a332effe6a398f85.json create mode 100644 assignments/Solidity/Todo/ignition/deployments/chain-11155111/deployed_addresses.json create mode 100644 assignments/Solidity/Todo/ignition/deployments/chain-11155111/journal.jsonl create mode 100644 assignments/Solidity/Todo/ignition/modules/Todo.ts create mode 100644 assignments/Solidity/Todo/package.json create mode 100644 assignments/Solidity/Todo/scripts/GenerateKey.js create mode 100644 assignments/Solidity/Todo/scripts/send-op-tx.ts create mode 100644 assignments/Solidity/Todo/test/Counter.ts create mode 100644 assignments/Solidity/Todo/test/Todo.ts create mode 100644 assignments/Solidity/Todo/tsconfig.json diff --git a/assignments/Solidity/Todo/.gitignore b/assignments/Solidity/Todo/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/Solidity/Todo/.gitignore @@ -0,0 +1,20 @@ +# Node modules +/node_modules + +# Compilation output +/dist + +# pnpm deploy output +/bundle + +# Hardhat Build Artifacts +/artifacts + +# Hardhat compilation (v2) support directory +/cache + +# Typechain output +/types + +# Hardhat coverage reports +/coverage diff --git a/assignments/Solidity/Todo/README.md b/assignments/Solidity/Todo/README.md new file mode 100644 index 00000000..22affb27 --- /dev/null +++ b/assignments/Solidity/Todo/README.md @@ -0,0 +1,117 @@ +# Todo Smart Contract + +A Solidity smart contract for managing personal TODO tasks with deadlines and status tracking. + +--- + +## Overview + +This contract allows users to: + +* Create TODO tasks with a text description and deadline +* Track the status of tasks: Pending, Done, Cancelled, or Defaulted +* Mark tasks as completed, automatically setting them to Done or Defaulted based on deadline + +--- + +## Contract Details + +* **License**: MIT +* **Solidity Version**: ^0.8.28 + +--- + +## State Variables + +| Variable | Type | Description | +| ------------- | ------------------------- | --------------------------------- | +| `todoCounter` | uint256 | Tracks the total number of TODOs | +| `todos` | mapping(uint => TodoList) | Stores all TODO items by their ID | + +### TodoList Struct + +| Variable | Type | Description | +| ---------- | ------- | ------------------------------------------------ | +| `id` | uint | Unique ID of the task | +| `owner` | address | Task creator | +| `text` | string | Task description | +| `status` | enum | Task status: Pending, Done, Cancelled, Defaulted | +| `deadline` | uint256 | Timestamp when the task is due | + +--- + +## Functions + +### createTodo(string _text, uint _deadline) + +Creates a new TODO task. + +**Access:** Public + +**Requirements:** + +* `_text` cannot be empty +* `_deadline` must be at least 10 minutes in the future +* Caller cannot be zero address + +**Notes:** + +* Increments `todoCounter` +* Saves the new TODO in `todos` mapping +* Emits `TodoCreated` event + +--- + +### completedTodo(uint _id) + +Marks a TODO as completed. + +**Access:** Task owner only + +**Requirements:** + +* `_id` must exist +* TODO must be `Pending` +* Caller must be the task owner + +**Notes:** + +* If the deadline has passed, sets status to `Defaulted` +* Otherwise, sets status to `Done` + +--- + +## Events + +| Event | Description | +| ----------------------------------------- | ---------------------------------- | +| `TodoCreated(string text, uint deadline)` | Emitted when a new TODO is created | + +--- + +## Important Notes + +1. **Task Ownership**: Only the task creator can mark a TODO as completed. +2. **Deadline Handling**: If a task is completed after its deadline, its status is automatically set to `Defaulted`. +3. **Pending Tasks**: Only tasks with `Pending` status can be completed. + +--- + +## Error Messages + +| Message | Meaning | +| --------------------- | ----------------------------------------- | +| "Empty text" | TODO text is empty | +| "Invalid Deadline" | Deadline is less than 10 minutes from now | +| "Zero address" | Caller is the zero address | +| "Invalid id" | TODO ID does not exist | +| "Not pending" | TODO is not in Pending status | +| "Unauthorized Caller" | Caller is not the task owner | + +--- + +## License + +MIT + +--- diff --git a/assignments/Solidity/Todo/contracts/Counter.sol b/assignments/Solidity/Todo/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/Solidity/Todo/contracts/Counter.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +contract Counter { + uint public x; + + event Increment(uint by); + + function inc() public { + x++; + emit Increment(1); + } + + function incBy(uint by) public { + require(by > 0, "incBy: increment should be positive"); + x += by; + emit Increment(by); + } +} diff --git a/assignments/Solidity/Todo/contracts/Counter.t.sol b/assignments/Solidity/Todo/contracts/Counter.t.sol new file mode 100644 index 00000000..ac71d5b8 --- /dev/null +++ b/assignments/Solidity/Todo/contracts/Counter.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import {Counter} from "./Counter.sol"; +import {Test} from "forge-std/Test.sol"; + +// Solidity tests are compatible with foundry, so they +// use the same syntax and offer the same functionality. + +contract CounterTest is Test { + Counter counter; + + function setUp() public { + counter = new Counter(); + } + + function test_InitialValue() public view { + require(counter.x() == 0, "Initial value should be 0"); + } + + function testFuzz_Inc(uint8 x) public { + for (uint8 i = 0; i < x; i++) { + counter.inc(); + } + require(counter.x() == x, "Value after calling inc x times should be x"); + } + + function test_IncByZero() public { + vm.expectRevert(); + counter.incBy(0); + } +} diff --git a/assignments/Solidity/Todo/contracts/Todo.sol b/assignments/Solidity/Todo/contracts/Todo.sol new file mode 100644 index 00000000..686a8296 --- /dev/null +++ b/assignments/Solidity/Todo/contracts/Todo.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +contract Todo { + uint256 todoCounter; + + enum Status { + Pending, + Done, + Cancelled, + Defaulted + } + + struct TodoList { + uint id; + address owner; + string text; + Status status; + uint256 deadline; + } + mapping(uint => TodoList) public todos; + event TodoCreated(string text, uint deadline); + + function createTodo( + string memory _text, + uint _deadline + ) external returns (uint) { + require(bytes(_text).length > 0, "Empty text"); + require(_deadline > (block.timestamp + 600), "Invalid Deadline"); + require(msg.sender != address(0), "Zero address"); + + todoCounter++; + + todos[todoCounter] = TodoList( + todoCounter, + msg.sender, + _text, + Status.Pending, + _deadline + ); + + emit TodoCreated(_text, _deadline); + return todoCounter; + } + + function completedTodo(uint _id) external { + // TodoList memory t = todos[todoCounter] + require((_id > 0) && (_id <= todoCounter), "Invalid id"); + + TodoList storage todo = todos[_id]; + require(todo.status == Status.Pending, "Not pending"); + require(msg.sender == todo.owner, "Unauthorized Caller"); + + if (block.timestamp > todo.deadline) { + todo.status = Status.Defaulted; + } else { + todo.status = Status.Done; + } + } +} diff --git a/assignments/Solidity/Todo/hardhat.config.ts b/assignments/Solidity/Todo/hardhat.config.ts new file mode 100644 index 00000000..1b645c9b --- /dev/null +++ b/assignments/Solidity/Todo/hardhat.config.ts @@ -0,0 +1,44 @@ +import "dotenv/config"; +import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers"; +import { configVariable, defineConfig } from "hardhat/config"; + +export default defineConfig({ + plugins: [hardhatToolboxMochaEthersPlugin], + solidity: { + profiles: { + default: { + version: "0.8.28", + }, + production: { + version: "0.8.28", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + }, + }, + networks: { + hardhatMainnet: { + type: "edr-simulated", + chainType: "l1", + }, + hardhatOp: { + type: "edr-simulated", + chainType: "op", + }, + sepolia: { + type: "http", + chainType: "l1", + url: configVariable("SEPOLIA_RPC_URL"), + accounts: [configVariable("SEPOLIA_PRIVATE_KEY")], + }, + }, + verify: { + etherscan: { + apiKey: configVariable("ETHERSCAN_API_KEY"), + }, + }, +}); diff --git a/assignments/Solidity/Todo/ignition/deployments/chain-11155111/artifacts/TodoModule#Todo.json b/assignments/Solidity/Todo/ignition/deployments/chain-11155111/artifacts/TodoModule#Todo.json new file mode 100644 index 00000000..87b1043e --- /dev/null +++ b/assignments/Solidity/Todo/ignition/deployments/chain-11155111/artifacts/TodoModule#Todo.json @@ -0,0 +1,109 @@ +{ + "_format": "hh3-artifact-1", + "contractName": "Todo", + "sourceName": "contracts/Todo.sol", + "abi": [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "text", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "TodoCreated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_id", + "type": "uint256" + } + ], + "name": "completedTodo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_text", + "type": "string" + }, + { + "internalType": "uint256", + "name": "_deadline", + "type": "uint256" + } + ], + "name": "createTodo", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "todos", + "outputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "string", + "name": "text", + "type": "string" + }, + { + "internalType": "enum Todo.Status", + "name": "status", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "bytecode": "0x6080604052348015600e575f5ffd5b506107ba8061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c8063bc8bc2b414610043578063ece28c6c14610070578063ff19f95c14610091575b5f5ffd5b610056610051366004610469565b6100a6565b6040516100679594939291906104c2565b60405180910390f35b61008361007e366004610530565b610169565b604051908152602001610067565b6100a461009f366004610469565b61033c565b005b600160208190525f91825260409091208054918101546002820180546001600160a01b0390921692916100d8906105e5565b80601f0160208091040260200160405190810160405280929190818152602001828054610104906105e5565b801561014f5780601f106101265761010080835404028352916020019161014f565b820191905f5260205f20905b81548152906001019060200180831161013257829003601f168201915b505050506003830154600490930154919260ff1691905085565b5f5f8351116101ac5760405162461bcd60e51b815260206004820152600a602482015269115b5c1d1e481d195e1d60b21b60448201526064015b60405180910390fd5b6101b842610258610631565b82116101f95760405162461bcd60e51b815260206004820152601060248201526f496e76616c696420446561646c696e6560801b60448201526064016101a3565b336102355760405162461bcd60e51b815260206004820152600c60248201526b5a65726f206164647265737360a01b60448201526064016101a3565b5f8054908061024383610644565b90915550506040805160a0810182525f80548252336020830152918101859052906060820190815260209081018490525f805481526001808352604091829020845181559284015190830180546001600160a01b0319166001600160a01b0390921691909117905582015160028201906102bd90826106a8565b506060820151816003015f6101000a81548160ff021916908360038111156102e7576102e76104ae565b0217905550608082015181600401559050507f022c3fffe570decf2328dbf4f2af598df5112da7e31568af8b927c48276ccae3838360405161032a929190610763565b60405180910390a1505f545b92915050565b5f8111801561034c57505f548111155b6103855760405162461bcd60e51b815260206004820152600a602482015269125b9d985b1a59081a5960b21b60448201526064016101a3565b5f8181526001602052604081209060038083015460ff16908111156103ac576103ac6104ae565b146103e75760405162461bcd60e51b815260206004820152600b60248201526a4e6f742070656e64696e6760a81b60448201526064016101a3565b60018101546001600160a01b031633146104395760405162461bcd60e51b81526020600482015260136024820152722ab730baba3437b934bd32b21021b0b63632b960691b60448201526064016101a3565b8060040154421115610458576003908101805460ff1916909117905550565b600301805460ff1916600117905550565b5f60208284031215610479575f5ffd5b5035919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b634e487b7160e01b5f52602160045260245ffd5b8581526001600160a01b038516602082015260a0604082018190525f906104eb90830186610480565b90506004841061050957634e487b7160e01b5f52602160045260245ffd5b6060820193909352608001529392505050565b634e487b7160e01b5f52604160045260245ffd5b5f5f60408385031215610541575f5ffd5b823567ffffffffffffffff811115610557575f5ffd5b8301601f81018513610567575f5ffd5b803567ffffffffffffffff8111156105815761058161051c565b604051601f8201601f19908116603f0116810167ffffffffffffffff811182821017156105b0576105b061051c565b6040528181528282016020018710156105c7575f5ffd5b816020840160208301375f6020928201830152969401359450505050565b600181811c908216806105f957607f821691505b60208210810361061757634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52601160045260245ffd5b808201808211156103365761033661061d565b5f600182016106555761065561061d565b5060010190565b601f8211156106a357805f5260205f20601f840160051c810160208510156106815750805b601f840160051c820191505b818110156106a0575f815560010161068d565b50505b505050565b815167ffffffffffffffff8111156106c2576106c261051c565b6106d6816106d084546105e5565b8461065c565b6020601f821160018114610708575f83156106f15750848201515b5f19600385901b1c1916600184901b1784556106a0565b5f84815260208120601f198516915b828110156107375787850151825560209485019460019092019101610717565b508482101561075457868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b604081525f6107756040830185610480565b9050826020830152939250505056fea26469706673582212200e5593d19828d3400eaf86132982eac8c01bcfc98d78233acf73ef0042055a1064736f6c634300081c0033", + "deployedBytecode": "0x608060405234801561000f575f5ffd5b506004361061003f575f3560e01c8063bc8bc2b414610043578063ece28c6c14610070578063ff19f95c14610091575b5f5ffd5b610056610051366004610469565b6100a6565b6040516100679594939291906104c2565b60405180910390f35b61008361007e366004610530565b610169565b604051908152602001610067565b6100a461009f366004610469565b61033c565b005b600160208190525f91825260409091208054918101546002820180546001600160a01b0390921692916100d8906105e5565b80601f0160208091040260200160405190810160405280929190818152602001828054610104906105e5565b801561014f5780601f106101265761010080835404028352916020019161014f565b820191905f5260205f20905b81548152906001019060200180831161013257829003601f168201915b505050506003830154600490930154919260ff1691905085565b5f5f8351116101ac5760405162461bcd60e51b815260206004820152600a602482015269115b5c1d1e481d195e1d60b21b60448201526064015b60405180910390fd5b6101b842610258610631565b82116101f95760405162461bcd60e51b815260206004820152601060248201526f496e76616c696420446561646c696e6560801b60448201526064016101a3565b336102355760405162461bcd60e51b815260206004820152600c60248201526b5a65726f206164647265737360a01b60448201526064016101a3565b5f8054908061024383610644565b90915550506040805160a0810182525f80548252336020830152918101859052906060820190815260209081018490525f805481526001808352604091829020845181559284015190830180546001600160a01b0319166001600160a01b0390921691909117905582015160028201906102bd90826106a8565b506060820151816003015f6101000a81548160ff021916908360038111156102e7576102e76104ae565b0217905550608082015181600401559050507f022c3fffe570decf2328dbf4f2af598df5112da7e31568af8b927c48276ccae3838360405161032a929190610763565b60405180910390a1505f545b92915050565b5f8111801561034c57505f548111155b6103855760405162461bcd60e51b815260206004820152600a602482015269125b9d985b1a59081a5960b21b60448201526064016101a3565b5f8181526001602052604081209060038083015460ff16908111156103ac576103ac6104ae565b146103e75760405162461bcd60e51b815260206004820152600b60248201526a4e6f742070656e64696e6760a81b60448201526064016101a3565b60018101546001600160a01b031633146104395760405162461bcd60e51b81526020600482015260136024820152722ab730baba3437b934bd32b21021b0b63632b960691b60448201526064016101a3565b8060040154421115610458576003908101805460ff1916909117905550565b600301805460ff1916600117905550565b5f60208284031215610479575f5ffd5b5035919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b634e487b7160e01b5f52602160045260245ffd5b8581526001600160a01b038516602082015260a0604082018190525f906104eb90830186610480565b90506004841061050957634e487b7160e01b5f52602160045260245ffd5b6060820193909352608001529392505050565b634e487b7160e01b5f52604160045260245ffd5b5f5f60408385031215610541575f5ffd5b823567ffffffffffffffff811115610557575f5ffd5b8301601f81018513610567575f5ffd5b803567ffffffffffffffff8111156105815761058161051c565b604051601f8201601f19908116603f0116810167ffffffffffffffff811182821017156105b0576105b061051c565b6040528181528282016020018710156105c7575f5ffd5b816020840160208301375f6020928201830152969401359450505050565b600181811c908216806105f957607f821691505b60208210810361061757634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52601160045260245ffd5b808201808211156103365761033661061d565b5f600182016106555761065561061d565b5060010190565b601f8211156106a357805f5260205f20601f840160051c810160208510156106815750805b601f840160051c820191505b818110156106a0575f815560010161068d565b50505b505050565b815167ffffffffffffffff8111156106c2576106c261051c565b6106d6816106d084546105e5565b8461065c565b6020601f821160018114610708575f83156106f15750848201515b5f19600385901b1c1916600184901b1784556106a0565b5f84815260208120601f198516915b828110156107375787850151825560209485019460019092019101610717565b508482101561075457868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b604081525f6107756040830185610480565b9050826020830152939250505056fea26469706673582212200e5593d19828d3400eaf86132982eac8c01bcfc98d78233acf73ef0042055a1064736f6c634300081c0033", + "linkReferences": {}, + "deployedLinkReferences": {}, + "immutableReferences": {}, + "inputSourceName": "project/contracts/Todo.sol", + "buildInfoId": "solc-0_8_28-33b40f654bf38476b2528d38a332effe6a398f85" +} \ No newline at end of file diff --git a/assignments/Solidity/Todo/ignition/deployments/chain-11155111/build-info/solc-0_8_28-33b40f654bf38476b2528d38a332effe6a398f85.json b/assignments/Solidity/Todo/ignition/deployments/chain-11155111/build-info/solc-0_8_28-33b40f654bf38476b2528d38a332effe6a398f85.json new file mode 100644 index 00000000..c79dc2c1 --- /dev/null +++ b/assignments/Solidity/Todo/ignition/deployments/chain-11155111/build-info/solc-0_8_28-33b40f654bf38476b2528d38a332effe6a398f85.json @@ -0,0 +1,39 @@ +{ + "_format": "hh3-sol-build-info-1", + "id": "solc-0_8_28-33b40f654bf38476b2528d38a332effe6a398f85", + "solcVersion": "0.8.28", + "solcLongVersion": "0.8.28+commit.7893614a", + "userSourceNameMap": { + "contracts/Todo.sol": "project/contracts/Todo.sol" + }, + "input": { + "language": "Solidity", + "settings": { + "evmVersion": "cancun", + "optimizer": { + "enabled": true, + "runs": 200 + }, + "outputSelection": { + "*": { + "": [ + "ast" + ], + "*": [ + "abi", + "evm.bytecode", + "evm.deployedBytecode", + "evm.methodIdentifiers", + "metadata" + ] + } + }, + "remappings": [] + }, + "sources": { + "project/contracts/Todo.sol": { + "content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.28;\n\ncontract Todo {\n uint256 todoCounter;\n\n enum Status {\n Pending,\n Done,\n Cancelled,\n Defaulted\n }\n\n struct TodoList {\n uint id;\n address owner;\n string text;\n Status status;\n uint256 deadline;\n }\n mapping(uint => TodoList) public todos;\n event TodoCreated(string text, uint deadline);\n\n function createTodo(\n string memory _text,\n uint _deadline\n ) external returns (uint) {\n require(bytes(_text).length > 0, \"Empty text\");\n require(_deadline > (block.timestamp + 600), \"Invalid Deadline\");\n require(msg.sender != address(0), \"Zero address\");\n\n todoCounter++;\n\n todos[todoCounter] = TodoList(\n todoCounter,\n msg.sender,\n _text,\n Status.Pending,\n _deadline\n );\n\n emit TodoCreated(_text, _deadline);\n return todoCounter;\n }\n\n function completedTodo(uint _id) external {\n // TodoList memory t = todos[todoCounter]\n require((_id > 0) && (_id <= todoCounter), \"Invalid id\");\n\n TodoList storage todo = todos[_id];\n require(todo.status == Status.Pending, \"Not pending\");\n require(msg.sender == todo.owner, \"Unauthorized Caller\");\n\n if (block.timestamp > todo.deadline) {\n todo.status = Status.Defaulted;\n } else {\n todo.status = Status.Done;\n }\n }\n}\n" + } + } + } +} \ No newline at end of file diff --git a/assignments/Solidity/Todo/ignition/deployments/chain-11155111/deployed_addresses.json b/assignments/Solidity/Todo/ignition/deployments/chain-11155111/deployed_addresses.json new file mode 100644 index 00000000..b7a6c5c7 --- /dev/null +++ b/assignments/Solidity/Todo/ignition/deployments/chain-11155111/deployed_addresses.json @@ -0,0 +1,3 @@ +{ + "TodoModule#Todo": "0xcC5EbE802c2A04a98e6eD6Ad863c937E689042E5" +} \ No newline at end of file diff --git a/assignments/Solidity/Todo/ignition/deployments/chain-11155111/journal.jsonl b/assignments/Solidity/Todo/ignition/deployments/chain-11155111/journal.jsonl new file mode 100644 index 00000000..10c28cb7 --- /dev/null +++ b/assignments/Solidity/Todo/ignition/deployments/chain-11155111/journal.jsonl @@ -0,0 +1,8 @@ + +{"chainId":11155111,"type":"DEPLOYMENT_INITIALIZE"} +{"artifactId":"TodoModule#Todo","constructorArgs":[],"contractName":"Todo","dependencies":[],"from":"0xce70fb173b9f737e4cc3129af3d90b65a7ac1698","futureId":"TodoModule#Todo","futureType":"NAMED_ARTIFACT_CONTRACT_DEPLOYMENT","libraries":{},"strategy":"basic","strategyConfig":{},"type":"DEPLOYMENT_EXECUTION_STATE_INITIALIZE","value":{"_kind":"bigint","value":"0"}} +{"futureId":"TodoModule#Todo","networkInteraction":{"data":"0x6080604052348015600e575f5ffd5b506107ba8061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c8063bc8bc2b414610043578063ece28c6c14610070578063ff19f95c14610091575b5f5ffd5b610056610051366004610469565b6100a6565b6040516100679594939291906104c2565b60405180910390f35b61008361007e366004610530565b610169565b604051908152602001610067565b6100a461009f366004610469565b61033c565b005b600160208190525f91825260409091208054918101546002820180546001600160a01b0390921692916100d8906105e5565b80601f0160208091040260200160405190810160405280929190818152602001828054610104906105e5565b801561014f5780601f106101265761010080835404028352916020019161014f565b820191905f5260205f20905b81548152906001019060200180831161013257829003601f168201915b505050506003830154600490930154919260ff1691905085565b5f5f8351116101ac5760405162461bcd60e51b815260206004820152600a602482015269115b5c1d1e481d195e1d60b21b60448201526064015b60405180910390fd5b6101b842610258610631565b82116101f95760405162461bcd60e51b815260206004820152601060248201526f496e76616c696420446561646c696e6560801b60448201526064016101a3565b336102355760405162461bcd60e51b815260206004820152600c60248201526b5a65726f206164647265737360a01b60448201526064016101a3565b5f8054908061024383610644565b90915550506040805160a0810182525f80548252336020830152918101859052906060820190815260209081018490525f805481526001808352604091829020845181559284015190830180546001600160a01b0319166001600160a01b0390921691909117905582015160028201906102bd90826106a8565b506060820151816003015f6101000a81548160ff021916908360038111156102e7576102e76104ae565b0217905550608082015181600401559050507f022c3fffe570decf2328dbf4f2af598df5112da7e31568af8b927c48276ccae3838360405161032a929190610763565b60405180910390a1505f545b92915050565b5f8111801561034c57505f548111155b6103855760405162461bcd60e51b815260206004820152600a602482015269125b9d985b1a59081a5960b21b60448201526064016101a3565b5f8181526001602052604081209060038083015460ff16908111156103ac576103ac6104ae565b146103e75760405162461bcd60e51b815260206004820152600b60248201526a4e6f742070656e64696e6760a81b60448201526064016101a3565b60018101546001600160a01b031633146104395760405162461bcd60e51b81526020600482015260136024820152722ab730baba3437b934bd32b21021b0b63632b960691b60448201526064016101a3565b8060040154421115610458576003908101805460ff1916909117905550565b600301805460ff1916600117905550565b5f60208284031215610479575f5ffd5b5035919050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b634e487b7160e01b5f52602160045260245ffd5b8581526001600160a01b038516602082015260a0604082018190525f906104eb90830186610480565b90506004841061050957634e487b7160e01b5f52602160045260245ffd5b6060820193909352608001529392505050565b634e487b7160e01b5f52604160045260245ffd5b5f5f60408385031215610541575f5ffd5b823567ffffffffffffffff811115610557575f5ffd5b8301601f81018513610567575f5ffd5b803567ffffffffffffffff8111156105815761058161051c565b604051601f8201601f19908116603f0116810167ffffffffffffffff811182821017156105b0576105b061051c565b6040528181528282016020018710156105c7575f5ffd5b816020840160208301375f6020928201830152969401359450505050565b600181811c908216806105f957607f821691505b60208210810361061757634e487b7160e01b5f52602260045260245ffd5b50919050565b634e487b7160e01b5f52601160045260245ffd5b808201808211156103365761033661061d565b5f600182016106555761065561061d565b5060010190565b601f8211156106a357805f5260205f20601f840160051c810160208510156106815750805b601f840160051c820191505b818110156106a0575f815560010161068d565b50505b505050565b815167ffffffffffffffff8111156106c2576106c261051c565b6106d6816106d084546105e5565b8461065c565b6020601f821160018114610708575f83156106f15750848201515b5f19600385901b1c1916600184901b1784556106a0565b5f84815260208120601f198516915b828110156107375787850151825560209485019460019092019101610717565b508482101561075457868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b604081525f6107756040830185610480565b9050826020830152939250505056fea26469706673582212200e5593d19828d3400eaf86132982eac8c01bcfc98d78233acf73ef0042055a1064736f6c634300081c0033","id":1,"type":"ONCHAIN_INTERACTION","value":{"_kind":"bigint","value":"0"}},"type":"NETWORK_INTERACTION_REQUEST"} +{"futureId":"TodoModule#Todo","networkInteractionId":1,"nonce":0,"type":"TRANSACTION_PREPARE_SEND"} +{"futureId":"TodoModule#Todo","networkInteractionId":1,"nonce":0,"transaction":{"fees":{"maxFeePerGas":{"_kind":"bigint","value":"1908316393"},"maxPriorityFeePerGas":{"_kind":"bigint","value":"1439189"}},"hash":"0x72146127aedbad7a048746994d1330f6c7a8c75803b36fbefa2e28bd4b8432d5"},"type":"TRANSACTION_SEND"} +{"futureId":"TodoModule#Todo","hash":"0x72146127aedbad7a048746994d1330f6c7a8c75803b36fbefa2e28bd4b8432d5","networkInteractionId":1,"receipt":{"blockHash":"0xf9bbc216f3571474e5f32e31e5e23886a699975543af64665edbf88b58cc9610","blockNumber":10238165,"contractAddress":"0xcC5EbE802c2A04a98e6eD6Ad863c937E689042E5","logs":[],"status":"SUCCESS"},"type":"TRANSACTION_CONFIRM"} +{"futureId":"TodoModule#Todo","result":{"address":"0xcC5EbE802c2A04a98e6eD6Ad863c937E689042E5","type":"SUCCESS"},"type":"DEPLOYMENT_EXECUTION_STATE_COMPLETE"} \ No newline at end of file diff --git a/assignments/Solidity/Todo/ignition/modules/Todo.ts b/assignments/Solidity/Todo/ignition/modules/Todo.ts new file mode 100644 index 00000000..e2e76357 --- /dev/null +++ b/assignments/Solidity/Todo/ignition/modules/Todo.ts @@ -0,0 +1,7 @@ +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; + +export default buildModule("TodoModule", (m) => { + const counter = m.contract("Todo"); + + return { counter }; +}); diff --git a/assignments/Solidity/Todo/package.json b/assignments/Solidity/Todo/package.json new file mode 100644 index 00000000..61ebe59c --- /dev/null +++ b/assignments/Solidity/Todo/package.json @@ -0,0 +1,25 @@ +{ + "name": "Todo", + "version": "1.0.0", + "type": "module", + "devDependencies": { + "@nomicfoundation/hardhat-ethers": "^4.0.4", + "@nomicfoundation/hardhat-ignition": "^3.0.7", + "@nomicfoundation/hardhat-toolbox-mocha-ethers": "^3.0.2", + "@types/chai": "^4.3.20", + "@types/chai-as-promised": "^8.0.2", + "@types/mocha": "^10.0.10", + "@types/node": "^22.19.8", + "chai": "^5.3.3", + "ethers": "^6.16.0", + "forge-std": "github:foundry-rs/forge-std#v1.9.4", + "hardhat": "^3.1.6", + "mocha": "^11.7.5", + "typescript": "~5.8.0" + }, + "dependencies": { + "22": "^0.0.0", + "dotenv": "^17.2.4", + "viem": "^2.45.3" + } +} diff --git a/assignments/Solidity/Todo/scripts/GenerateKey.js b/assignments/Solidity/Todo/scripts/GenerateKey.js new file mode 100644 index 00000000..c84174dc --- /dev/null +++ b/assignments/Solidity/Todo/scripts/GenerateKey.js @@ -0,0 +1,13 @@ +#!/usr/bin/env node +/** + * One-off script: generates 8 EVM private keys for AI players. + * Run: node scripts/generate-ai-keys.js + * Copy the output into your .envaccounts (server-side only). Do not commit .env. + */ +import { generatePrivateKey } from "viem/accounts"; +import { privateKeyToAccount } from "viem/accounts"; + +const pk = generatePrivateKey(); +const account = privateKeyToAccount(pk); +console.log(`Private key: ${pk}`); +console.log(`Address : ${account.address}\n`); diff --git a/assignments/Solidity/Todo/scripts/send-op-tx.ts b/assignments/Solidity/Todo/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/Solidity/Todo/scripts/send-op-tx.ts @@ -0,0 +1,22 @@ +import { network } from "hardhat"; + +const { ethers } = await network.connect({ + network: "hardhatOp", + chainType: "op", +}); + +console.log("Sending transaction using the OP chain type"); + +const [sender] = await ethers.getSigners(); + +console.log("Sending 1 wei from", sender.address, "to itself"); + +console.log("Sending L2 transaction"); +const tx = await sender.sendTransaction({ + to: sender.address, + value: 1n, +}); + +await tx.wait(); + +console.log("Transaction sent successfully"); diff --git a/assignments/Solidity/Todo/test/Counter.ts b/assignments/Solidity/Todo/test/Counter.ts new file mode 100644 index 00000000..f8c38986 --- /dev/null +++ b/assignments/Solidity/Todo/test/Counter.ts @@ -0,0 +1,36 @@ +import { expect } from "chai"; +import { network } from "hardhat"; + +const { ethers } = await network.connect(); + +describe("Counter", function () { + it("Should emit the Increment event when calling the inc() function", async function () { + const counter = await ethers.deployContract("Counter"); + + await expect(counter.inc()).to.emit(counter, "Increment").withArgs(1n); + }); + + it("The sum of the Increment events should match the current value", async function () { + const counter = await ethers.deployContract("Counter"); + const deploymentBlockNumber = await ethers.provider.getBlockNumber(); + + // run a series of increments + for (let i = 1; i <= 10; i++) { + await counter.incBy(i); + } + + const events = await counter.queryFilter( + counter.filters.Increment(), + deploymentBlockNumber, + "latest", + ); + + // check that the aggregated events match the current value + let total = 0n; + for (const event of events) { + total += event.args.by; + } + + expect(await counter.x()).to.equal(total); + }); +}); diff --git a/assignments/Solidity/Todo/test/Todo.ts b/assignments/Solidity/Todo/test/Todo.ts new file mode 100644 index 00000000..e69de29b diff --git a/assignments/Solidity/Todo/tsconfig.json b/assignments/Solidity/Todo/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/Solidity/Todo/tsconfig.json @@ -0,0 +1,13 @@ +/* Based on https://github.com/tsconfig/bases/blob/501da2bcd640cf95c95805783e1012b992338f28/bases/node22.json */ +{ + "compilerOptions": { + "lib": ["es2023"], + "module": "node16", + "target": "es2022", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "moduleResolution": "node16", + "outDir": "dist" + } +} From 14a3da5a44a51d00bdbdef37c8a4b31f5c103d43 Mon Sep 17 00:00:00 2001 From: Chibey-max Date: Mon, 16 Feb 2026 09:51:01 +0100 Subject: [PATCH 2/4] feat: submitting escrow-v1 assignment --- assignments/Solidity/Escrow/.gitignore | 20 + assignments/Solidity/Escrow/README.md | 124 +++++++ .../Solidity/Escrow/contracts/Escrow.sol | 82 ++++ .../Escrow/contracts/EscrowFactory.sol | 35 ++ assignments/Solidity/Escrow/hardhat.config.ts | 38 ++ .../Escrow/ignition/modules/Counter.ts | 9 + assignments/Solidity/Escrow/package.json | 20 + .../Solidity/Escrow/scripts/send-op-tx.ts | 22 ++ assignments/Solidity/Escrow/test/Escrow.ts | 351 ++++++++++++++++++ assignments/Solidity/Escrow/tsconfig.json | 13 + 10 files changed, 714 insertions(+) create mode 100644 assignments/Solidity/Escrow/.gitignore create mode 100644 assignments/Solidity/Escrow/README.md create mode 100644 assignments/Solidity/Escrow/contracts/Escrow.sol create mode 100644 assignments/Solidity/Escrow/contracts/EscrowFactory.sol create mode 100644 assignments/Solidity/Escrow/hardhat.config.ts create mode 100644 assignments/Solidity/Escrow/ignition/modules/Counter.ts create mode 100644 assignments/Solidity/Escrow/package.json create mode 100644 assignments/Solidity/Escrow/scripts/send-op-tx.ts create mode 100644 assignments/Solidity/Escrow/test/Escrow.ts create mode 100644 assignments/Solidity/Escrow/tsconfig.json diff --git a/assignments/Solidity/Escrow/.gitignore b/assignments/Solidity/Escrow/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/Solidity/Escrow/.gitignore @@ -0,0 +1,20 @@ +# Node modules +/node_modules + +# Compilation output +/dist + +# pnpm deploy output +/bundle + +# Hardhat Build Artifacts +/artifacts + +# Hardhat compilation (v2) support directory +/cache + +# Typechain output +/types + +# Hardhat coverage reports +/coverage diff --git a/assignments/Solidity/Escrow/README.md b/assignments/Solidity/Escrow/README.md new file mode 100644 index 00000000..8e14e4b2 --- /dev/null +++ b/assignments/Solidity/Escrow/README.md @@ -0,0 +1,124 @@ +# Escrow Smart Contract System + +A Solidity smart contract system for secure ETH escrow transactions between a buyer and seller, overseen by an agent. + +--- + +## Overview + +* **Escrow Contract** – Handles a single escrow transaction: + + * Seller deposits ETH + * Buyer confirms delivery + * Agent releases funds or refunds +* **EscrowFactory Contract** – Deploys and manages multiple Escrow contracts: + + * Buyers create new escrow agreements with sellers + * Tracks deployed escrows in an array + * Provides helper functions to retrieve escrow count and addresses + +--- + +## Example Usage (TypeScript) + +### Deploy EscrowFactory + +```ts +import { ethers } from "hardhat"; +import { EscrowFactory, Escrow } from "../typechain-types"; + +async function main() { + const [deployer, buyer, seller] = await ethers.getSigners(); + + // Deploy Factory + const factoryFactory = await ethers.getContractFactory("EscrowFactory"); + const factory = (await factoryFactory.deploy()) as EscrowFactory; + await factory.deployed(); + + console.log("Factory deployed at:", factory.target); + + // Buyer creates a new escrow + const tx = await factory.connect(buyer).createEscrow(seller.address); + await tx.wait(); + + // Retrieve deployed escrow address + const escrowAddress = await factory.getEscrow(0); + const escrow = (await ethers.getContractAt("Escrow", escrowAddress)) as Escrow; + + console.log("Escrow deployed at:", escrowAddress); +} +``` + +--- + +### Seller Funds Escrow + +```ts +const fundTx = await escrow.connect(seller).fundEscrow({ + value: ethers.parseEther("5") +}); +await fundTx.wait(); +console.log("Escrow funded by seller with 5 ETH"); +``` + +--- + +### Buyer Confirms Delivery + +```ts +const confirmTx = await escrow.connect(buyer).confirmDelivery(); +await confirmTx.wait(); +console.log("Buyer confirmed delivery"); +``` + +--- + +### Agent Releases Funds to Seller + +```ts +const releaseTx = await escrow.connect(deployer).releaseFunds(); // deployer is agent +await releaseTx.wait(); +console.log("Agent released funds to seller"); +``` + +--- + +### Agent Refunds Buyer + +```ts +const refundTx = await escrow.connect(deployer).refundBuyer(); +await refundTx.wait(); +console.log("Agent refunded buyer"); +``` + +--- + +### Getting Escrow Details + +```ts +const amount = await escrow.amountReceived(); +const status = await escrow.status(); +const currentBuyer = await escrow.buyer(); +const currentSeller = await escrow.seller(); + +console.log("Amount in escrow:", ethers.formatEther(amount), "ETH"); +console.log("Escrow status:", status); +console.log("Buyer:", currentBuyer); +console.log("Seller:", currentSeller); +``` + +--- + +### Factory Helper Functions + +```ts +// Total escrows +const totalEscrows = await factory.getEscrowCount(); +console.log("Total Escrows:", totalEscrows); + +// Get escrow address by index +const firstEscrowAddr = await factory.getEscrow(0); +console.log("Escrow[0] address:", firstEscrowAddr); +``` + +--- diff --git a/assignments/Solidity/Escrow/contracts/Escrow.sol b/assignments/Solidity/Escrow/contracts/Escrow.sol new file mode 100644 index 00000000..e67cd4f7 --- /dev/null +++ b/assignments/Solidity/Escrow/contracts/Escrow.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +contract Escrow { + address public agent; + address payable public buyer; + address payable public seller; + uint public amountReceived; + + enum Status { + PENDING, + PAID, + AWAITING_CONFIRM, + COMPLETE + } + + Status public status; + + event EscrowFunded(address indexed seller, uint amount); + event DeliveryConfirmed(address indexed buyer); + event FundsReleased(address indexed seller, uint amount); + event FundsRefunded(address indexed buyer, uint amount); + + modifier onlyBuyer() { + require(msg.sender == buyer, "Only buyer allowed"); + _; + } + + modifier onlySeller() { + require(msg.sender == seller, "Only seller allowed"); + _; + } + + modifier onlyAgent() { + require(msg.sender == agent, "Only agent allowed"); + _; + } + + constructor(address payable _buyer, address payable _seller) { + agent = msg.sender; // factory deployer is agent + buyer = _buyer; + seller = _seller; + status = Status.PENDING; + } + + /// Seller deposits ETH into escrow + function fundEscrow() external payable onlySeller { + require(status == Status.PENDING, "Already funded"); + require(msg.value > 0, "Must send ETH"); + + amountReceived = msg.value; + status = Status.PAID; + + emit EscrowFunded(msg.sender, msg.value); + } + + /// Buyer confirms delivery of goods + function confirmDelivery() external onlyBuyer { + require(status == Status.PAID, "Funds not deposited yet"); + status = Status.AWAITING_CONFIRM; + + emit DeliveryConfirmed(msg.sender); + } + + /// Agent releases funds to seller + function releaseFunds() external onlyAgent { + require(status == Status.AWAITING_CONFIRM, "Not ready to release"); + seller.transfer(amountReceived); + status = Status.COMPLETE; + + emit FundsReleased(seller, amountReceived); + } + + /// Agent refunds buyer + function refundBuyer() external onlyAgent { + require(status == Status.AWAITING_CONFIRM, "Not ready to refund"); + buyer.transfer(amountReceived); + status = Status.COMPLETE; + + emit FundsRefunded(buyer, amountReceived); + } +} diff --git a/assignments/Solidity/Escrow/contracts/EscrowFactory.sol b/assignments/Solidity/Escrow/contracts/EscrowFactory.sol new file mode 100644 index 00000000..8be8a974 --- /dev/null +++ b/assignments/Solidity/Escrow/contracts/EscrowFactory.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.28; + +import "./Escrow.sol"; + +contract EscrowFactory { +Escrow [] public escrows; + +event EscrowCreated( + address escrowAddress, + address buyer, + address seller +); +// buyer creates new escrow order with the seller and stores in escorws array + +function createEscrow (address payable seller) external { + Escrow escrow = new Escrow (payable(msg.sender), seller); + escrows.push (escrow); + + emit EscrowCreated (address(escrow), msg.sender, seller); +} + +/// i added it so it will return the total num of escrows +function getEscrowCount () external view returns (uint256){ + return escrows.length; +} +// retrieve the address by index and check if d index is valid then returns the escrows contract address + +function getEscrow (uint index) external view returns(address) { + require (index < escrows.length, "Index is out of range"); + return address (escrows[index]); +} +} + diff --git a/assignments/Solidity/Escrow/hardhat.config.ts b/assignments/Solidity/Escrow/hardhat.config.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/Solidity/Escrow/hardhat.config.ts @@ -0,0 +1,38 @@ +import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers"; +import { configVariable, defineConfig } from "hardhat/config"; + +export default defineConfig({ + plugins: [hardhatToolboxMochaEthersPlugin], + solidity: { + profiles: { + default: { + version: "0.8.28", + }, + production: { + version: "0.8.28", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + }, + }, + networks: { + hardhatMainnet: { + type: "edr-simulated", + chainType: "l1", + }, + hardhatOp: { + type: "edr-simulated", + chainType: "op", + }, + sepolia: { + type: "http", + chainType: "l1", + url: configVariable("SEPOLIA_RPC_URL"), + accounts: [configVariable("SEPOLIA_PRIVATE_KEY")], + }, + }, +}); diff --git a/assignments/Solidity/Escrow/ignition/modules/Counter.ts b/assignments/Solidity/Escrow/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/Solidity/Escrow/ignition/modules/Counter.ts @@ -0,0 +1,9 @@ +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; + +export default buildModule("CounterModule", (m) => { + const counter = m.contract("Counter"); + + m.call(counter, "incBy", [5n]); + + return { counter }; +}); diff --git a/assignments/Solidity/Escrow/package.json b/assignments/Solidity/Escrow/package.json new file mode 100644 index 00000000..820ad412 --- /dev/null +++ b/assignments/Solidity/Escrow/package.json @@ -0,0 +1,20 @@ +{ + "name": "Test", + "version": "1.0.0", + "type": "module", + "devDependencies": { + "@nomicfoundation/hardhat-ethers": "^4.0.4", + "@nomicfoundation/hardhat-ignition": "^3.0.7", + "@nomicfoundation/hardhat-toolbox-mocha-ethers": "^3.0.2", + "@types/chai": "^4.3.20", + "@types/chai-as-promised": "^8.0.2", + "@types/mocha": "^10.0.10", + "@types/node": "^22.19.10", + "chai": "^5.3.3", + "ethers": "^6.16.0", + "forge-std": "github:foundry-rs/forge-std#v1.9.4", + "hardhat": "^3.1.7", + "mocha": "^11.7.5", + "typescript": "~5.8.0" + } +} diff --git a/assignments/Solidity/Escrow/scripts/send-op-tx.ts b/assignments/Solidity/Escrow/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/Solidity/Escrow/scripts/send-op-tx.ts @@ -0,0 +1,22 @@ +import { network } from "hardhat"; + +const { ethers } = await network.connect({ + network: "hardhatOp", + chainType: "op", +}); + +console.log("Sending transaction using the OP chain type"); + +const [sender] = await ethers.getSigners(); + +console.log("Sending 1 wei from", sender.address, "to itself"); + +console.log("Sending L2 transaction"); +const tx = await sender.sendTransaction({ + to: sender.address, + value: 1n, +}); + +await tx.wait(); + +console.log("Transaction sent successfully"); diff --git a/assignments/Solidity/Escrow/test/Escrow.ts b/assignments/Solidity/Escrow/test/Escrow.ts new file mode 100644 index 00000000..c57bca43 --- /dev/null +++ b/assignments/Solidity/Escrow/test/Escrow.ts @@ -0,0 +1,351 @@ +import { expect } from "chai"; +import { network } from "hardhat"; + +describe("Escrow + EscrowFactory", function () { + let ethers: any; + let networkHelpers: any; + + before(async () => { + const connection = await network.connect(); + ethers = connection.ethers; + networkHelpers = connection.networkHelpers; + }); + + async function deployFactoryFixture() { + const [agent, buyer, seller, other] = await ethers.getSigners(); + + const Factory = await ethers.getContractFactory("EscrowFactory"); + const factory = await Factory.deploy(); + + return { factory, agent, buyer, seller, other }; + } + + describe("Factory", function () { + it("Should create an escrow and store it", async () => { + const { factory, buyer, seller } = await networkHelpers.loadFixture( + deployFactoryFixture, + ); + + await expect(factory.connect(buyer).createEscrow(seller.address)).to.emit( + factory, + "EscrowCreated", + ); + + expect(await factory.getEscrowCount()).to.equal(1); + + const escrowAddress = await factory.getEscrow(0); + expect(escrowAddress).to.properAddress; + }); + + it("Should revert for invalid index", async () => { + const { factory } = await networkHelpers.loadFixture( + deployFactoryFixture, + ); + + await expect(factory.getEscrow(0)).to.be.revertedWith( + "Index is out of range", + ); + }); + }); + + describe("Escrow Workflow", function () { + async function deployEscrowFixture() { + // const { factory, agent, buyer, seller, other } = + // await deployFactoryFixture(); + const [factory, agent, buyer, seller, other] = await ethers.getSigners(); + + await factory.connect(buyer).createEscrow(seller.address); + + const escrowAddress = await factory.getEscrow(0); + const escrow = await ethers.getContractAt("Escrow", escrowAddress); + // console.log(`The address for escrow`,escrow.runner.address); + // console.log(`The address for agent`, agent.address); + // console.log(`The address for buyer`, buyer.address); + // console.log(`The address for seller`,seller.address); + + return { escrow, agent, buyer, seller, other }; + } + + it("Should initialize correctly", async () => { + const { escrow, agent, buyer, seller } = await networkHelpers.loadFixture( + deployEscrowFixture, + ); + + expect(await escrow.agent()).to.equal(agent.address); + console.log("agent.address is: ", agent.address); + + expect(await escrow.buyer()).to.equal(buyer.address); + console.log("buyer.address is: ", buyer.address); + + expect(await escrow.seller()).to.equal(seller.address); + console.log("seller.address is: ", seller.address); + + expect(await escrow.status()).to.equal(0); // PENDING + }); + + it("Seller should fund escrow", async () => { + const { escrow, seller } = await networkHelpers.loadFixture( + deployEscrowFixture, + ); + + const amount = ethers.parseEther("1"); + + await expect(escrow.connect(seller).fundEscrow({ value: amount })) + .to.emit(escrow, "EscrowFunded") + .withArgs(seller.address, amount); + + expect(await escrow.amountReceived()).to.equal(amount); + expect(await escrow.status()).to.equal(1); // PAID + }); + + it("Buyer confirms delivery", async () => { + const { escrow, seller, buyer } = await networkHelpers.loadFixture( + deployEscrowFixture, + ); + + const amount = ethers.parseEther("1"); + + await escrow.connect(seller).fundEscrow({ value: amount }); + + await expect(escrow.connect(buyer).confirmDelivery()) + .to.emit(escrow, "DeliveryConfirmed") + .withArgs(buyer.address); + + expect(await escrow.status()).to.equal(2); // AWAITING_CONFIRM + }); + + it("Agent releases funds to seller", async () => { + const { escrow, seller, buyer, agent } = await networkHelpers.loadFixture( + deployEscrowFixture, + ); + + const amount = ethers.parseEther("1"); + + await escrow.connect(seller).fundEscrow({ value: amount }); + await escrow.connect(buyer).confirmDelivery(); + + const sellerBalanceBefore = await ethers.provider.getBalance(seller); + + await expect(escrow.connect(agent).releaseFunds()) + .to.emit(escrow, "FundsReleased") + .withArgs(seller.address, amount); + + const sellerBalanceAfter = await ethers.provider.getBalance(seller); + + expect(sellerBalanceAfter).to.be.gt(sellerBalanceBefore); + expect(await escrow.status()).to.equal(3); // COMPLETE + }); + + it("Agent can refund buyer instead", async () => { + const { escrow, seller, buyer, agent } = await networkHelpers.loadFixture( + deployEscrowFixture, + ); + + const amount = ethers.parseEther("1"); + + await escrow.connect(seller).fundEscrow({ value: amount }); + await escrow.connect(buyer).confirmDelivery(); + + await expect(escrow.connect(agent).refundBuyer()) + .to.emit(escrow, "FundsRefunded") + .withArgs(buyer.address, amount); + + expect(await escrow.status()).to.equal(3); + }); + }); + + describe("Access Control", function () { + it("Non-seller cannot fund", async () => { + const { escrow, buyer } = await networkHelpers.loadFixture(async () => { + const base = await deployFactoryFixture(); + await base.factory + .connect(base.buyer) + .createEscrow(base.seller.address); + const escrowAddress = await base.factory.getEscrow(0); + const escrow = await base.factory.runner!.provider!.getContractAt?.( + "Escrow", + escrowAddress, + ); + return { escrow, ...base }; + }); + + await expect( + escrow.connect(buyer).fundEscrow({ value: ethers.parseEther("1") }), + ).to.be.revertedWith("Only seller allowed"); + }); + }); +}); + +/* +import { expect } from "chai"; +import { network } from "hardhat"; + +describe("Escrow + EscrowFactory", function () { + let ethers: any; + let networkHelpers: any; + + before(async () => { + const connection = await network.connect(); + ethers = connection.ethers; + networkHelpers = connection.networkHelpers; + }); + + async function deployFixture() { + const [buyer, seller, other] = await ethers.getSigners(); + + const Factory = await ethers.getContractFactory("EscrowFactory"); + const factory = await Factory.deploy(); + + await factory.connect(buyer).createEscrow(seller.address); + + const escrowAddress = await factory.getEscrow(0); + const escrow = await ethers.getContractAt("Escrow", escrowAddress); + + return { factory, escrow, buyer, seller, other }; + } + + describe("Factory", function () { + it("Should create escrow and store it", async function () { + const [buyer, seller] = await ethers.getSigners(); + + const Factory = await ethers.getContractFactory("EscrowFactory"); + const factory = await Factory.deploy(); + + await expect(factory.connect(buyer).createEscrow(seller.address)).to.emit( + factory, + "EscrowCreated", + ); + + expect(await factory.getEscrowCount()).to.equal(1); + + const escrowAddress = await factory.getEscrow(0); + expect(escrowAddress).to.properAddress; + }); + + it("Should revert for invalid index", async function () { + const Factory = await ethers.getContractFactory("EscrowFactory"); + const factory = await Factory.deploy(); + + await expect(factory.getEscrow(0)).to.be.revertedWith( + "Index is out of range", + ); + }); + }); + + describe("Escrow Initialization", function () { + it("Should initialize correctly", async function () { + const { escrow, factory, buyer, seller } = await loadFixture( + deployFixture, + ); + + // IMPORTANT: agent = factory address (per your constructor) + expect(await escrow.agent()).to.equal(await factory.getAddress()); + + expect(await escrow.buyer()).to.equal(buyer.address); + expect(await escrow.seller()).to.equal(seller.address); + expect(await escrow.status()).to.equal(0); // PENDING + }); + }); + + describe("Escrow Workflow", function () { + it("Seller should fund escrow", async function () { + const { escrow, seller } = await loadFixture(deployFixture); + + const amount = ethers.parseEther("1"); + + await expect(escrow.connect(seller).fundEscrow({ value: amount })) + .to.emit(escrow, "EscrowFunded") + .withArgs(seller.address, amount); + + expect(await escrow.amountReceived()).to.equal(amount); + expect(await escrow.status()).to.equal(1); // PAID + }); + + it("Buyer confirms delivery", async function () { + const { escrow, seller, buyer } = await loadFixture(deployFixture); + + const amount = ethers.parseEther("1"); + + await escrow.connect(seller).fundEscrow({ value: amount }); + + await expect(escrow.connect(buyer).confirmDelivery()) + .to.emit(escrow, "DeliveryConfirmed") + .withArgs(buyer.address); + + expect(await escrow.status()).to.equal(2); // AWAITING_CONFIRM + }); + + it("Agent releases funds to seller", async function () { + const { escrow, seller, buyer, factory } = await loadFixture( + deployFixture, + ); + + const amount = ethers.parseEther("1"); + + await escrow.connect(seller).fundEscrow({ value: amount }); + await escrow.connect(buyer).confirmDelivery(); + + const sellerBalanceBefore = await ethers.provider.getBalance( + seller.address, + ); + + // Agent = factory address + await expect( + escrow + .connect(await ethers.getSigner(await factory.getAddress())) + .releaseFunds(), + ) + .to.emit(escrow, "FundsReleased") + .withArgs(seller.address, amount); + + const sellerBalanceAfter = await ethers.provider.getBalance( + seller.address, + ); + + expect(sellerBalanceAfter).to.be.gt(sellerBalanceBefore); + expect(await escrow.status()).to.equal(3); // COMPLETE + }); + + it("Agent refunds buyer instead", async function () { + const { escrow, seller, buyer, factory } = await loadFixture( + deployFixture, + ); + + const amount = ethers.parseEther("1"); + + await escrow.connect(seller).fundEscrow({ value: amount }); + await escrow.connect(buyer).confirmDelivery(); + + await expect( + escrow + .connect(await ethers.getSigner(await factory.getAddress())) + .refundBuyer(), + ) + .to.emit(escrow, "FundsRefunded") + .withArgs(buyer.address, amount); + + expect(await escrow.status()).to.equal(3); + }); + }); + + describe("Access Control", function () { + it("Non-seller cannot fund", async function () { + const { escrow, buyer } = await loadFixture(deployFixture); + + await expect( + escrow.connect(buyer).fundEscrow({ + value: ethers.parseEther("1"), + }), + ).to.be.revertedWith("Only seller allowed"); + }); + + it("Non-buyer cannot confirm delivery", async function () { + const { escrow, seller } = await loadFixture(deployFixture); + + await expect(escrow.connect(seller).confirmDelivery()).to.be.revertedWith( + "Only buyer allowed", + ); + }); + }); +}); +*/ diff --git a/assignments/Solidity/Escrow/tsconfig.json b/assignments/Solidity/Escrow/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/Solidity/Escrow/tsconfig.json @@ -0,0 +1,13 @@ +/* Based on https://github.com/tsconfig/bases/blob/501da2bcd640cf95c95805783e1012b992338f28/bases/node22.json */ +{ + "compilerOptions": { + "lib": ["es2023"], + "module": "node16", + "target": "es2022", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "moduleResolution": "node16", + "outDir": "dist" + } +} From 149b0325f72f5e2490b05273a46cc07cd5fc9543 Mon Sep 17 00:00:00 2001 From: Chibey-max Date: Mon, 16 Feb 2026 09:54:34 +0100 Subject: [PATCH 3/4] feat: submitting escrow-v2 assignment --- assignments/Solidity/Milestone/.gitignore | 20 ++ assignments/Solidity/Milestone/README.md | 173 ++++++++++++++++++ .../Solidity/Milestone/contracts/Counter.sol | 19 ++ .../Milestone/contracts/Counter.t.sol | 32 ++++ .../Milestone/contracts/milestone.sol | 85 +++++++++ .../Solidity/Milestone/hardhat.config.ts | 38 ++++ .../Milestone/ignition/modules/Counter.ts | 9 + assignments/Solidity/Milestone/package.json | 20 ++ .../Solidity/Milestone/scripts/send-op-tx.ts | 22 +++ .../Solidity/Milestone/test/Counter.ts | 36 ++++ assignments/Solidity/Milestone/test/miles.ts | 122 ++++++++++++ assignments/Solidity/Milestone/tsconfig.json | 13 ++ 12 files changed, 589 insertions(+) create mode 100644 assignments/Solidity/Milestone/.gitignore create mode 100644 assignments/Solidity/Milestone/README.md create mode 100644 assignments/Solidity/Milestone/contracts/Counter.sol create mode 100644 assignments/Solidity/Milestone/contracts/Counter.t.sol create mode 100644 assignments/Solidity/Milestone/contracts/milestone.sol create mode 100644 assignments/Solidity/Milestone/hardhat.config.ts create mode 100644 assignments/Solidity/Milestone/ignition/modules/Counter.ts create mode 100644 assignments/Solidity/Milestone/package.json create mode 100644 assignments/Solidity/Milestone/scripts/send-op-tx.ts create mode 100644 assignments/Solidity/Milestone/test/Counter.ts create mode 100644 assignments/Solidity/Milestone/test/miles.ts create mode 100644 assignments/Solidity/Milestone/tsconfig.json diff --git a/assignments/Solidity/Milestone/.gitignore b/assignments/Solidity/Milestone/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/Solidity/Milestone/.gitignore @@ -0,0 +1,20 @@ +# Node modules +/node_modules + +# Compilation output +/dist + +# pnpm deploy output +/bundle + +# Hardhat Build Artifacts +/artifacts + +# Hardhat compilation (v2) support directory +/cache + +# Typechain output +/types + +# Hardhat coverage reports +/coverage diff --git a/assignments/Solidity/Milestone/README.md b/assignments/Solidity/Milestone/README.md new file mode 100644 index 00000000..a8f15ea8 --- /dev/null +++ b/assignments/Solidity/Milestone/README.md @@ -0,0 +1,173 @@ +# Milestone Smart Contract + +A Solidity smart contract for managing ETH-based milestone payments between a client and freelancer. + +--- + +## Overview + +This contract allows: + +* A client to fund a milestone contract with ETH +* The client to confirm funding +* The client to approve the milestone +* Funds to be released to the freelancer after approval +* Refunds to the client if milestone is not approved + +--- + +## Contract Details + +* **License**: MIT +* **Solidity Version**: ^0.8.28 + +--- + +## State Variables + +| Variable | Type | Description | +| ------------ | --------------- | ----------------------------------------------------------------------------------------------------- | +| `client` | address | Address of the client (deployer) | +| `freelancer` | address payable | Address of the freelancer | +| `amount` | uint | Amount of ETH deposited for milestone | +| `status` | enum | Current milestone status: CONTRACT_FUNDED, FUNDING_CONFIRMED, MILESTONE_APPROVED, COMPLETED, REFUNDED | + +--- + +## Functions + +### fundContract() + +Client funds the contract with ETH. + +**Access:** Client only + +**Requirements:** + +* ETH must be > 0 +* Contract must not be funded already + +**Notes:** + +* Updates `amount` and sets `status` to `CONTRACT_FUNDED` +* Emits `JobCreated` event + +--- + +### confirmFunding() + +Client confirms that funding is correct. + +**Access:** Client only + +**Requirements:** + +* Contract must be funded + +**Notes:** + +* Updates `status` to `FUNDING_CONFIRMED` +* Emits `FundingConfirmed` event + +--- + +### approveMilestone() + +Client approves the milestone work. + +**Access:** Client only + +**Requirements:** + +* Funding must be confirmed + +**Notes:** + +* Updates `status` to `MILESTONE_APPROVED` +* Emits `MilestoneApproved` event + +--- + +### releaseFunds() + +Client releases milestone funds to freelancer. + +**Access:** Client only + +**Requirements:** + +* Milestone must be approved +* Amount must be > 0 + +**Notes:** + +* Sends ETH to freelancer +* Updates `status` to `COMPLETED` +* Emits `FundsReleased` event + +--- + +### refundClient() + +Client refunds their ETH if milestone is not approved yet. + +**Access:** Client only + +**Requirements:** + +* Status must be `CONTRACT_FUNDED` or `FUNDING_CONFIRMED` +* Amount must be > 0 + +**Notes:** + +* Sends ETH back to client +* Updates `status` to `REFUNDED` +* Emits `FundsRefunded` event + +--- + +## Events + +| Event | Description | +| ------------------------------------------------ | --------------------------------------------- | +| `JobCreated(address freelancer, uint amount)` | Emitted when contract is funded | +| `FundingConfirmed(address client)` | Emitted when funding is confirmed | +| `MilestoneApproved(address client)` | Emitted when milestone is approved | +| `FundsReleased(address freelancer, uint amount)` | Emitted when funds are released to freelancer | +| `FundsRefunded(address client, uint amount)` | Emitted when client receives refund | + + +--- + +## Important Notes + +1. **Client Only**: All functions are restricted to client (the deployer). +2. **Status Flow**: + `CONTRACT_FUNDED` → `FUNDING_CONFIRMED` → `MILESTONE_APPROVED` → `COMPLETED` or `REFUNDED` +3. **Funds Safety**: ETH is only sent after approval or refund. +4. **Refund Protection**: Refund can only occur before milestone approval. + +--- + +## Error Messages + +| Message | Meaning | +| ------------------------------ | ------------------------------------------- | +| "Only client allowed" | Function restricted to client | +| "Must send ETH" | No ETH sent to fund contract | +| "Already funded" | Contract already funded | +| "Contract not funded yet" | Funding not confirmed yet | +| "Funding not confirmed yet" | Milestone cannot be approved yet | +| "Cannot refund after approval" | Refund not allowed after milestone approval | +| "No funds to refund" | No ETH to refund | +| "Milestone not approved yet" | Cannot release funds before approval | +| "No funds to release" | No ETH to release | +| "Transfer failed" | ETH transfer failed | + +--- + +## License + +MIT + +--- diff --git a/assignments/Solidity/Milestone/contracts/Counter.sol b/assignments/Solidity/Milestone/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/Solidity/Milestone/contracts/Counter.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +contract Counter { + uint public x; + + event Increment(uint by); + + function inc() public { + x++; + emit Increment(1); + } + + function incBy(uint by) public { + require(by > 0, "incBy: increment should be positive"); + x += by; + emit Increment(by); + } +} diff --git a/assignments/Solidity/Milestone/contracts/Counter.t.sol b/assignments/Solidity/Milestone/contracts/Counter.t.sol new file mode 100644 index 00000000..ac71d5b8 --- /dev/null +++ b/assignments/Solidity/Milestone/contracts/Counter.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import {Counter} from "./Counter.sol"; +import {Test} from "forge-std/Test.sol"; + +// Solidity tests are compatible with foundry, so they +// use the same syntax and offer the same functionality. + +contract CounterTest is Test { + Counter counter; + + function setUp() public { + counter = new Counter(); + } + + function test_InitialValue() public view { + require(counter.x() == 0, "Initial value should be 0"); + } + + function testFuzz_Inc(uint8 x) public { + for (uint8 i = 0; i < x; i++) { + counter.inc(); + } + require(counter.x() == x, "Value after calling inc x times should be x"); + } + + function test_IncByZero() public { + vm.expectRevert(); + counter.incBy(0); + } +} diff --git a/assignments/Solidity/Milestone/contracts/milestone.sol b/assignments/Solidity/Milestone/contracts/milestone.sol new file mode 100644 index 00000000..abcf2db2 --- /dev/null +++ b/assignments/Solidity/Milestone/contracts/milestone.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +contract Milestone { + address public client; + address payable public freelancer; + uint public amount; + + enum Status { + CONTRACT_FUNDED, + FUNDING_CONFIRMED, + MILESTONE_APPROVED, + COMPLETED, + REFUNDED + } + + Status public status; + + event JobCreated(address freelancer, uint amount); + event FundingConfirmed(address client); + event MilestoneApproved(address client); + event FundsReleased(address freelancer, uint amount); + event FundsRefunded (address client, uint amount); + + modifier onlyClient() { + require(msg.sender == client, "Only client allowed"); + _; + } + + constructor(address payable _freelancer) { + client = msg.sender; // deployer is client + freelancer = _freelancer; + } + + function fundContract() external payable onlyClient { + require(msg.value > 0, "Must send ETH"); + require(status == Status(0), "Already funded"); + + amount = msg.value; + status = Status.CONTRACT_FUNDED; + + emit JobCreated(freelancer, msg.value); + } + + function confirmFunding() external onlyClient { + require(status == Status.CONTRACT_FUNDED, "Contract not funded yet"); + status = Status.FUNDING_CONFIRMED; + + emit FundingConfirmed(msg.sender); + } + + function approveMilestone() external onlyClient { + require(status == Status.FUNDING_CONFIRMED, "Funding not confirmed yet"); + status = Status.MILESTONE_APPROVED; + + emit MilestoneApproved(msg.sender); + } +function refundClient() external onlyClient { + require(status == Status.CONTRACT_FUNDED || status == Status.FUNDING_CONFIRMED, "cannot refund after approval"); + require (amount > 0, "No funds to refund"); + + uint refundAmount = amount; + amount = 0; + status = Status.REFUNDED; + + (bool success,) = payable (client).call{value: refundAmount}(""); + require(success, "Refund failed"); + + emit FundsRefunded(client, refundAmount); +} + // Release funds to freelancer + function releaseFunds() external onlyClient { + require(status == Status.MILESTONE_APPROVED, "Milestone not approved yet"); + require(amount > 0, "No funds to release"); + + uint payout = amount; + // amount = 0; + status = Status.COMPLETED; + + (bool success,) = freelancer.call{value: payout}(""); + require(success, "Transfer failed"); + + emit FundsReleased(freelancer, payout); + } +} diff --git a/assignments/Solidity/Milestone/hardhat.config.ts b/assignments/Solidity/Milestone/hardhat.config.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/Solidity/Milestone/hardhat.config.ts @@ -0,0 +1,38 @@ +import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers"; +import { configVariable, defineConfig } from "hardhat/config"; + +export default defineConfig({ + plugins: [hardhatToolboxMochaEthersPlugin], + solidity: { + profiles: { + default: { + version: "0.8.28", + }, + production: { + version: "0.8.28", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + }, + }, + networks: { + hardhatMainnet: { + type: "edr-simulated", + chainType: "l1", + }, + hardhatOp: { + type: "edr-simulated", + chainType: "op", + }, + sepolia: { + type: "http", + chainType: "l1", + url: configVariable("SEPOLIA_RPC_URL"), + accounts: [configVariable("SEPOLIA_PRIVATE_KEY")], + }, + }, +}); diff --git a/assignments/Solidity/Milestone/ignition/modules/Counter.ts b/assignments/Solidity/Milestone/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/Solidity/Milestone/ignition/modules/Counter.ts @@ -0,0 +1,9 @@ +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; + +export default buildModule("CounterModule", (m) => { + const counter = m.contract("Counter"); + + m.call(counter, "incBy", [5n]); + + return { counter }; +}); diff --git a/assignments/Solidity/Milestone/package.json b/assignments/Solidity/Milestone/package.json new file mode 100644 index 00000000..1d47b5bb --- /dev/null +++ b/assignments/Solidity/Milestone/package.json @@ -0,0 +1,20 @@ +{ + "name": "Milestone", + "version": "1.0.0", + "type": "module", + "devDependencies": { + "@nomicfoundation/hardhat-ethers": "^4.0.4", + "@nomicfoundation/hardhat-ignition": "^3.0.7", + "@nomicfoundation/hardhat-toolbox-mocha-ethers": "^3.0.2", + "@types/chai": "^4.3.20", + "@types/chai-as-promised": "^8.0.2", + "@types/mocha": "^10.0.10", + "@types/node": "^22.19.11", + "chai": "^5.3.3", + "ethers": "^6.16.0", + "forge-std": "github:foundry-rs/forge-std#v1.9.4", + "hardhat": "^3.1.8", + "mocha": "^11.7.5", + "typescript": "~5.8.0" + } +} diff --git a/assignments/Solidity/Milestone/scripts/send-op-tx.ts b/assignments/Solidity/Milestone/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/Solidity/Milestone/scripts/send-op-tx.ts @@ -0,0 +1,22 @@ +import { network } from "hardhat"; + +const { ethers } = await network.connect({ + network: "hardhatOp", + chainType: "op", +}); + +console.log("Sending transaction using the OP chain type"); + +const [sender] = await ethers.getSigners(); + +console.log("Sending 1 wei from", sender.address, "to itself"); + +console.log("Sending L2 transaction"); +const tx = await sender.sendTransaction({ + to: sender.address, + value: 1n, +}); + +await tx.wait(); + +console.log("Transaction sent successfully"); diff --git a/assignments/Solidity/Milestone/test/Counter.ts b/assignments/Solidity/Milestone/test/Counter.ts new file mode 100644 index 00000000..f8c38986 --- /dev/null +++ b/assignments/Solidity/Milestone/test/Counter.ts @@ -0,0 +1,36 @@ +import { expect } from "chai"; +import { network } from "hardhat"; + +const { ethers } = await network.connect(); + +describe("Counter", function () { + it("Should emit the Increment event when calling the inc() function", async function () { + const counter = await ethers.deployContract("Counter"); + + await expect(counter.inc()).to.emit(counter, "Increment").withArgs(1n); + }); + + it("The sum of the Increment events should match the current value", async function () { + const counter = await ethers.deployContract("Counter"); + const deploymentBlockNumber = await ethers.provider.getBlockNumber(); + + // run a series of increments + for (let i = 1; i <= 10; i++) { + await counter.incBy(i); + } + + const events = await counter.queryFilter( + counter.filters.Increment(), + deploymentBlockNumber, + "latest", + ); + + // check that the aggregated events match the current value + let total = 0n; + for (const event of events) { + total += event.args.by; + } + + expect(await counter.x()).to.equal(total); + }); +}); diff --git a/assignments/Solidity/Milestone/test/miles.ts b/assignments/Solidity/Milestone/test/miles.ts new file mode 100644 index 00000000..c448087c --- /dev/null +++ b/assignments/Solidity/Milestone/test/miles.ts @@ -0,0 +1,122 @@ +import { expect } from "chai"; +import { network } from "hardhat"; + +describe("Milestone Contract", function () { + let ethers: any; + let networkHelpers: any; + + before(async () => { + // Connect to Hardhat 3 network + const connection = await network.connect(); + ethers = connection.ethers; + networkHelpers = connection.networkHelpers; + }); + + // Fixture to deploy the Milestone contract + async function deployMilestoneFixture() { + const [client, freelancer, otherAccount] = await ethers.getSigners(); + const amount = ethers.parseEther("1"); // 1 ETH + + const Milestone = await ethers.getContractFactory("Milestone"); + const milestone = await Milestone.deploy(freelancer.address); + + return { milestone, client, freelancer, otherAccount, amount }; + } + + describe("Workflow", function () { + it("Should complete the full happy path", async function () { + const { milestone, client, freelancer, amount } = + await networkHelpers.loadFixture(deployMilestoneFixture); + + // 1. Fund the contract + await expect(milestone.connect(client).fundContract({ value: amount })) + .to.emit(milestone, "JobCreated") + .withArgs(freelancer.address, amount); + + // 2. Confirm funding + await milestone.connect(client).confirmFunding(); + expect(await milestone.status()).to.equal(1); // FUNDING_CONFIRMED + + // 3. Approve milestone + await milestone.connect(client).approveMilestone(); + expect(await milestone.status()).to.equal(2); // MILESTONE_APPROVED + + // 4. Release funds to freelancer + const initialBalance = await ethers.provider.getBalance( + freelancer.address, + ); // bigint + + const tx = await milestone.connect(client).releaseFunds(); + await expect(tx) + .to.emit(milestone, "FundsReleased") + .withArgs(freelancer.address, amount); + + const finalBalance = await ethers.provider.getBalance(freelancer.address); + + expect(finalBalance).to.equal(initialBalance + amount); // bigint arithmetic + expect(await milestone.status()).to.equal(3); // COMPLETED + }); + + it("Should allow client to refund before approval", async function () { + const { milestone, client, amount } = await networkHelpers.loadFixture( + deployMilestoneFixture, + ); + + await milestone.connect(client).fundContract({ value: amount }); + }); + }); + + describe.only("Security & Constraints", function () { + it("Should revert if non-client tries to release funds", async function () { + const { milestone, freelancer, amount, client } = + await networkHelpers.loadFixture(deployMilestoneFixture); + + await milestone.connect(client).fundContract({ value: amount }); + await milestone.connect(client).confirmFunding(); + await milestone.connect(client).approveMilestone(); + + await expect( + milestone.connect(freelancer).releaseFunds(), + ).to.be.revertedWith("Only client allowed"); + }); + + it("Should not allow refund after milestone is approved", async function () { + const { milestone, client, amount } = await networkHelpers.loadFixture( + deployMilestoneFixture, + ); + + await milestone.connect(client).fundContract({ value: amount }); + await milestone.connect(client).confirmFunding(); + await milestone.connect(client).approveMilestone(); + + await expect(milestone.connect(client).refundClient()).to.be.revertedWith( + "cannot refund after approval", + ); + }); + + it("Should revert if trying to approve milestone before confirming funding", async function () { + const { milestone, client, amount } = await networkHelpers.loadFixture( + deployMilestoneFixture, + ); + + await milestone.connect(client).fundContract({ value: amount }); + + await expect( + milestone.connect(client).approveMilestone(), + ).to.be.revertedWith("Funding not confirmed yet"); + }); + + it("Should revert if trying to release funds before milestone approval", async function () { + const { milestone, client, amount } = await networkHelpers.loadFixture( + deployMilestoneFixture, + ); + + await milestone.connect(client).fundContract({ value: amount }); + await milestone.connect(client).confirmFunding(); + + await expect(milestone.connect(client).releaseFunds()).to.be.revertedWith( + "Milestone not approved yet", + ); + }); + }); +}); diff --git a/assignments/Solidity/Milestone/tsconfig.json b/assignments/Solidity/Milestone/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/Solidity/Milestone/tsconfig.json @@ -0,0 +1,13 @@ +/* Based on https://github.com/tsconfig/bases/blob/501da2bcd640cf95c95805783e1012b992338f28/bases/node22.json */ +{ + "compilerOptions": { + "lib": ["es2023"], + "module": "node16", + "target": "es2022", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "moduleResolution": "node16", + "outDir": "dist" + } +} From 9efaea3bb4d38239121bfb2affe5dce3d0de8ed7 Mon Sep 17 00:00:00 2001 From: Chibey-max Date: Mon, 16 Feb 2026 09:58:02 +0100 Subject: [PATCH 4/4] feat: submitting transaction-vault assignment --- assignments/Solidity/Timevaults/.gitignore | 22 ++ assignments/Solidity/Timevaults/README.md | 117 ++++++++++ .../Solidity/Timevaults/contracts/Counter.sol | 19 ++ .../Timevaults/contracts/Counter.t.sol | 32 +++ .../Timevaults/contracts/Timevalut.sol | 56 +++++ .../Timevaults/ethers-contracts/Counter.ts | 119 ++++++++++ .../CrowdFunding.sol/CrowdFunding.ts | 213 ++++++++++++++++++ .../CrowdFunding.sol/index.ts | 4 + .../Timevalut.sol/TimelockedVault.ts | 136 +++++++++++ .../ethers-contracts/Timevalut.sol/index.ts | 4 + .../Timevaults/ethers-contracts/common.ts | 92 ++++++++ .../factories/Counter__factory.ts | 107 +++++++++ .../CrowdFunding.sol/CrowdFunding__factory.ts | 213 ++++++++++++++++++ .../factories/CrowdFunding.sol/index.ts | 4 + .../Timevalut.sol/TimelockedVault__factory.ts | 154 +++++++++++++ .../factories/Timevalut.sol/index.ts | 4 + .../ethers-contracts/factories/index.ts | 7 + .../Timevaults/ethers-contracts/hardhat.d.ts | 59 +++++ .../Timevaults/ethers-contracts/index.ts | 16 ++ .../Solidity/Timevaults/hardhat.config.ts | 38 ++++ .../Timevaults/ignition/modules/Counter.ts | 9 + assignments/Solidity/Timevaults/package.json | 20 ++ .../Solidity/Timevaults/scripts/send-op-tx.ts | 22 ++ .../Solidity/Timevaults/test/Counter.ts | 36 +++ .../Solidity/Timevaults/test/TimeVault.ts | 0 assignments/Solidity/Timevaults/tsconfig.json | 13 ++ 26 files changed, 1516 insertions(+) create mode 100644 assignments/Solidity/Timevaults/.gitignore create mode 100644 assignments/Solidity/Timevaults/README.md create mode 100644 assignments/Solidity/Timevaults/contracts/Counter.sol create mode 100644 assignments/Solidity/Timevaults/contracts/Counter.t.sol create mode 100644 assignments/Solidity/Timevaults/contracts/Timevalut.sol create mode 100644 assignments/Solidity/Timevaults/ethers-contracts/Counter.ts create mode 100644 assignments/Solidity/Timevaults/ethers-contracts/CrowdFunding.sol/CrowdFunding.ts create mode 100644 assignments/Solidity/Timevaults/ethers-contracts/CrowdFunding.sol/index.ts create mode 100644 assignments/Solidity/Timevaults/ethers-contracts/Timevalut.sol/TimelockedVault.ts create mode 100644 assignments/Solidity/Timevaults/ethers-contracts/Timevalut.sol/index.ts create mode 100644 assignments/Solidity/Timevaults/ethers-contracts/common.ts create mode 100644 assignments/Solidity/Timevaults/ethers-contracts/factories/Counter__factory.ts create mode 100644 assignments/Solidity/Timevaults/ethers-contracts/factories/CrowdFunding.sol/CrowdFunding__factory.ts create mode 100644 assignments/Solidity/Timevaults/ethers-contracts/factories/CrowdFunding.sol/index.ts create mode 100644 assignments/Solidity/Timevaults/ethers-contracts/factories/Timevalut.sol/TimelockedVault__factory.ts create mode 100644 assignments/Solidity/Timevaults/ethers-contracts/factories/Timevalut.sol/index.ts create mode 100644 assignments/Solidity/Timevaults/ethers-contracts/factories/index.ts create mode 100644 assignments/Solidity/Timevaults/ethers-contracts/hardhat.d.ts create mode 100644 assignments/Solidity/Timevaults/ethers-contracts/index.ts create mode 100644 assignments/Solidity/Timevaults/hardhat.config.ts create mode 100644 assignments/Solidity/Timevaults/ignition/modules/Counter.ts create mode 100644 assignments/Solidity/Timevaults/package.json create mode 100644 assignments/Solidity/Timevaults/scripts/send-op-tx.ts create mode 100644 assignments/Solidity/Timevaults/test/Counter.ts create mode 100644 assignments/Solidity/Timevaults/test/TimeVault.ts create mode 100644 assignments/Solidity/Timevaults/tsconfig.json diff --git a/assignments/Solidity/Timevaults/.gitignore b/assignments/Solidity/Timevaults/.gitignore new file mode 100644 index 00000000..bf26e97d --- /dev/null +++ b/assignments/Solidity/Timevaults/.gitignore @@ -0,0 +1,22 @@ +# Node modules +/node_modules + +# Compilation output +/dist + +# pnpm deploy output +/bundle + +# Hardhat Build Artifacts +/artifacts + +# Hardhat compilation (v2) support directory +/cache + +# Typechain output +/types + +# Hardhat coverage reports +/coverage + +de.sol diff --git a/assignments/Solidity/Timevaults/README.md b/assignments/Solidity/Timevaults/README.md new file mode 100644 index 00000000..48d3c6e9 --- /dev/null +++ b/assignments/Solidity/Timevaults/README.md @@ -0,0 +1,117 @@ +# TimelockedVault Smart Contract + +A Solidity smart contract that allows users to deposit ETH into a vault with a time lock. Users can only withdraw their funds after a specified unlock time. + +--- + +## Overview + +This contract allows users to: + +* Deposit ETH into a personal vault with a specified unlock time +* Withdraw ETH only after the unlock time has passed +* Ensure each user can only have one active vault at a time + +--- + +## Contract Details + +* **License**: MIT +* **Solidity Version**: ^0.8.28 + +--- + +## State Variables + +| Variable | Type | Description | +| -------- | ------------------------- | ------------------------------------ | +| `vaults` | mapping(address => Vault) | Stores each user's vault information | + +### Vault Struct + +| Variable | Type | Description | +| ------------ | ---- | ---------------------------------------- | +| `amount` | uint | Amount of ETH deposited in wei | +| `unlockTime` | uint | Timestamp when funds become withdrawable | +| `active` | bool | Whether the vault is active | + +--- + +## Functions + +### deposit(uint _unlockTime) + +Deposits ETH into the user's vault with a time lock. + +**Access:** Public + +**Requirements:** + +* ETH must be greater than 0 +* `_unlockTime` must be in the future and within 365 days from now +* User must not already have an active vault + +**Notes:** + +* Creates a new vault for the user +* Emits `Deposited` event + +--- + +### withdraw() + +Withdraws ETH from the vault after unlock time. + +**Access:** Public + +**Requirements:** + +* User must have an active vault +* Current timestamp must be >= `unlockTime` +* Vault amount must be greater than 0 + +**Notes:** + +* Sends all ETH from the vault to the user +* Marks vault as inactive +* Emits `Withdrawn` event + +--- + +## Events + +| Event | Description | +| ------------------------------------------------------- | ----------------------------------------------- | +| `Deposited(address user, uint amount, uint unlockTime)` | Emitted when a user deposits ETH into the vault | +| `Withdrawn(address user, uint amount)` | Emitted when a user withdraws funds | + +--- + +## Important Notes + +1. **One Active Vault**: Each user can only have one active vault at a time. +2. **Time Restriction**: Unlock time must be in the future and within 365 days. +3. **Funds Safety**: ETH is held in contract until unlock time. +4. **ETH Transfers**: Uses `.call()` for safe ETH transfer. + +--- + +## Error Messages + +| Message | Meaning | +| ---------------------- | ---------------------------------------------- | +| "Must send ETH" | User tried to deposit 0 ETH | +| "Invalid unlock time" | Unlock time is in the past or exceeds 365 days | +| "Vault already active" | User already has an active vault | +| "No active vault" | User tried to withdraw without an active vault | +| "Funds are locked" | Unlock time has not passed yet | +| "No funds" | Vault contains 0 ETH | +| "Transfer failed" | ETH transfer failed | + +--- + +## License + +MIT + +--- diff --git a/assignments/Solidity/Timevaults/contracts/Counter.sol b/assignments/Solidity/Timevaults/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/Solidity/Timevaults/contracts/Counter.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +contract Counter { + uint public x; + + event Increment(uint by); + + function inc() public { + x++; + emit Increment(1); + } + + function incBy(uint by) public { + require(by > 0, "incBy: increment should be positive"); + x += by; + emit Increment(by); + } +} diff --git a/assignments/Solidity/Timevaults/contracts/Counter.t.sol b/assignments/Solidity/Timevaults/contracts/Counter.t.sol new file mode 100644 index 00000000..ac71d5b8 --- /dev/null +++ b/assignments/Solidity/Timevaults/contracts/Counter.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import {Counter} from "./Counter.sol"; +import {Test} from "forge-std/Test.sol"; + +// Solidity tests are compatible with foundry, so they +// use the same syntax and offer the same functionality. + +contract CounterTest is Test { + Counter counter; + + function setUp() public { + counter = new Counter(); + } + + function test_InitialValue() public view { + require(counter.x() == 0, "Initial value should be 0"); + } + + function testFuzz_Inc(uint8 x) public { + for (uint8 i = 0; i < x; i++) { + counter.inc(); + } + require(counter.x() == x, "Value after calling inc x times should be x"); + } + + function test_IncByZero() public { + vm.expectRevert(); + counter.incBy(0); + } +} diff --git a/assignments/Solidity/Timevaults/contracts/Timevalut.sol b/assignments/Solidity/Timevaults/contracts/Timevalut.sol new file mode 100644 index 00000000..8820c59a --- /dev/null +++ b/assignments/Solidity/Timevaults/contracts/Timevalut.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +contract TimelockedVault { + + struct Vault { + uint amount; + uint unlockTime; + bool active; + } + + mapping(address => Vault) public vaults; + + event Deposited(address indexed user, uint amount, uint unlockTime); + event Withdrawn(address indexed user, uint amount); + + // Deposit ETH with a chosen unlock time + function deposit(uint _unlockTime) external payable { + require(msg.value > 0, "Must send ETH"); + require( + _unlockTime > block.timestamp && + _unlockTime <= block.timestamp + 365 days, + "Invalid unlock time" + ); + + // User cannot deposit again if a vault is active + require(!vaults[msg.sender].active, "Vault already active"); + + vaults[msg.sender] = Vault( + msg.value, + _unlockTime, + true + ); + + emit Deposited(msg.sender, msg.value, _unlockTime); + } + + // Withdraw ETH after unlock time + function withdraw() external { + Vault storage vault = vaults[msg.sender]; + + require(vault.active, "No active vault"); + require(block.timestamp >= vault.unlockTime, "Funds are locked"); + require(vault.amount > 0, "No funds"); + + uint amount = vault.amount; + + vault.amount = 0; + vault.active = false; + + (bool success, ) = payable(msg.sender).call{value: amount}(""); + require(success, "Transfer failed"); + + emit Withdrawn(msg.sender, amount); + } +} diff --git a/assignments/Solidity/Timevaults/ethers-contracts/Counter.ts b/assignments/Solidity/Timevaults/ethers-contracts/Counter.ts new file mode 100644 index 00000000..b34567ad --- /dev/null +++ b/assignments/Solidity/Timevaults/ethers-contracts/Counter.ts @@ -0,0 +1,119 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { BaseContract, BigNumberish, BytesLike, FunctionFragment, Result, Interface, EventFragment, ContractRunner, ContractMethod, Listener } from "ethers" +import type { TypedContractEvent, TypedDeferredTopicFilter, TypedEventLog, TypedLogDescription, TypedListener, TypedContractMethod } from "./common.js" + + + export interface CounterInterface extends Interface { + getFunction(nameOrSignature: "inc" | "incBy" | "x"): FunctionFragment; + + getEvent(nameOrSignatureOrTopic: "Increment"): EventFragment; + + encodeFunctionData(functionFragment: 'inc', values?: undefined): string; +encodeFunctionData(functionFragment: 'incBy', values: [BigNumberish]): string; +encodeFunctionData(functionFragment: 'x', values?: undefined): string; + + decodeFunctionResult(functionFragment: 'inc', data: BytesLike): Result; +decodeFunctionResult(functionFragment: 'incBy', data: BytesLike): Result; +decodeFunctionResult(functionFragment: 'x', data: BytesLike): Result; + } + + + export namespace IncrementEvent { + export type InputTuple = [by: BigNumberish]; + export type OutputTuple = [by: bigint]; + export interface OutputObject {by: bigint }; + export type Event = TypedContractEvent + export type Filter = TypedDeferredTopicFilter + export type Log = TypedEventLog + export type LogDescription = TypedLogDescription + } + + + + export interface Counter extends BaseContract { + + connect(runner?: ContractRunner | null): Counter; + waitForDeployment(): Promise; + + interface: CounterInterface; + + + 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 + + + + + inc: TypedContractMethod< + [], + [void], + 'nonpayable' + > + + + + incBy: TypedContractMethod< + [by: BigNumberish, ], + [void], + 'nonpayable' + > + + + + x: TypedContractMethod< + [], + [bigint], + 'view' + > + + + + getFunction(key: string | FunctionFragment): T; + + getFunction(nameOrSignature: 'inc'): TypedContractMethod< + [], + [void], + 'nonpayable' + >; +getFunction(nameOrSignature: 'incBy'): TypedContractMethod< + [by: BigNumberish, ], + [void], + 'nonpayable' + >; +getFunction(nameOrSignature: 'x'): TypedContractMethod< + [], + [bigint], + 'view' + >; + + getEvent(key: 'Increment'): TypedContractEvent; + + filters: { + + 'Increment(uint256)': TypedContractEvent; + Increment: TypedContractEvent; + + }; + } \ No newline at end of file diff --git a/assignments/Solidity/Timevaults/ethers-contracts/CrowdFunding.sol/CrowdFunding.ts b/assignments/Solidity/Timevaults/ethers-contracts/CrowdFunding.sol/CrowdFunding.ts new file mode 100644 index 00000000..7dcdfae7 --- /dev/null +++ b/assignments/Solidity/Timevaults/ethers-contracts/CrowdFunding.sol/CrowdFunding.ts @@ -0,0 +1,213 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { BaseContract, BigNumberish, BytesLike, FunctionFragment, Result, Interface, EventFragment, AddressLike, ContractRunner, ContractMethod, Listener } from "ethers" +import type { TypedContractEvent, TypedDeferredTopicFilter, TypedEventLog, TypedLogDescription, TypedListener, TypedContractMethod } from "../common.js" + + + export interface CrowdFundingInterface extends Interface { + getFunction(nameOrSignature: "contribute" | "contributions" | "deadline" | "goal" | "owner" | "totalFunds" | "withdrawFunds"): FunctionFragment; + + getEvent(nameOrSignatureOrTopic: "ContributionReceived" | "FundsWithdrawn" | "GoalReached"): EventFragment; + + encodeFunctionData(functionFragment: 'contribute', values?: undefined): string; +encodeFunctionData(functionFragment: 'contributions', values: [AddressLike]): string; +encodeFunctionData(functionFragment: 'deadline', values?: undefined): string; +encodeFunctionData(functionFragment: 'goal', values?: undefined): string; +encodeFunctionData(functionFragment: 'owner', values?: undefined): string; +encodeFunctionData(functionFragment: 'totalFunds', values?: undefined): string; +encodeFunctionData(functionFragment: 'withdrawFunds', values?: undefined): string; + + decodeFunctionResult(functionFragment: 'contribute', data: BytesLike): Result; +decodeFunctionResult(functionFragment: 'contributions', data: BytesLike): Result; +decodeFunctionResult(functionFragment: 'deadline', data: BytesLike): Result; +decodeFunctionResult(functionFragment: 'goal', data: BytesLike): Result; +decodeFunctionResult(functionFragment: 'owner', data: BytesLike): Result; +decodeFunctionResult(functionFragment: 'totalFunds', data: BytesLike): Result; +decodeFunctionResult(functionFragment: 'withdrawFunds', data: BytesLike): Result; + } + + + export namespace ContributionReceivedEvent { + export type InputTuple = [contributor: AddressLike, amount: BigNumberish]; + export type OutputTuple = [contributor: string, amount: bigint]; + export interface OutputObject {contributor: string, amount: bigint }; + export type Event = TypedContractEvent + export type Filter = TypedDeferredTopicFilter + export type Log = TypedEventLog + export type LogDescription = TypedLogDescription + } + + + + export namespace FundsWithdrawnEvent { + export type InputTuple = [owner: AddressLike, amount: BigNumberish]; + export type OutputTuple = [owner: string, amount: bigint]; + export interface OutputObject {owner: string, amount: bigint }; + export type Event = TypedContractEvent + export type Filter = TypedDeferredTopicFilter + export type Log = TypedEventLog + export type LogDescription = TypedLogDescription + } + + + + export namespace GoalReachedEvent { + export type InputTuple = [totalFunds: BigNumberish]; + export type OutputTuple = [totalFunds: bigint]; + export interface OutputObject {totalFunds: bigint }; + export type Event = TypedContractEvent + export type Filter = TypedDeferredTopicFilter + export type Log = TypedEventLog + export type LogDescription = TypedLogDescription + } + + + + export interface CrowdFunding extends BaseContract { + + connect(runner?: ContractRunner | null): CrowdFunding; + waitForDeployment(): Promise; + + interface: CrowdFundingInterface; + + + 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 + + + + + contribute: TypedContractMethod< + [], + [void], + 'payable' + > + + + + contributions: TypedContractMethod< + [arg0: AddressLike, ], + [bigint], + 'view' + > + + + + deadline: TypedContractMethod< + [], + [bigint], + 'view' + > + + + + goal: TypedContractMethod< + [], + [bigint], + 'view' + > + + + + owner: TypedContractMethod< + [], + [string], + 'view' + > + + + + totalFunds: TypedContractMethod< + [], + [bigint], + 'view' + > + + + + withdrawFunds: TypedContractMethod< + [], + [void], + 'nonpayable' + > + + + + getFunction(key: string | FunctionFragment): T; + + getFunction(nameOrSignature: 'contribute'): TypedContractMethod< + [], + [void], + 'payable' + >; +getFunction(nameOrSignature: 'contributions'): TypedContractMethod< + [arg0: AddressLike, ], + [bigint], + 'view' + >; +getFunction(nameOrSignature: 'deadline'): TypedContractMethod< + [], + [bigint], + 'view' + >; +getFunction(nameOrSignature: 'goal'): TypedContractMethod< + [], + [bigint], + 'view' + >; +getFunction(nameOrSignature: 'owner'): TypedContractMethod< + [], + [string], + 'view' + >; +getFunction(nameOrSignature: 'totalFunds'): TypedContractMethod< + [], + [bigint], + 'view' + >; +getFunction(nameOrSignature: 'withdrawFunds'): TypedContractMethod< + [], + [void], + 'nonpayable' + >; + + getEvent(key: 'ContributionReceived'): TypedContractEvent; +getEvent(key: 'FundsWithdrawn'): TypedContractEvent; +getEvent(key: 'GoalReached'): TypedContractEvent; + + filters: { + + 'ContributionReceived(address,uint256)': TypedContractEvent; + ContributionReceived: TypedContractEvent; + + + 'FundsWithdrawn(address,uint256)': TypedContractEvent; + FundsWithdrawn: TypedContractEvent; + + + 'GoalReached(uint256)': TypedContractEvent; + GoalReached: TypedContractEvent; + + }; + } \ No newline at end of file diff --git a/assignments/Solidity/Timevaults/ethers-contracts/CrowdFunding.sol/index.ts b/assignments/Solidity/Timevaults/ethers-contracts/CrowdFunding.sol/index.ts new file mode 100644 index 00000000..cb555ab5 --- /dev/null +++ b/assignments/Solidity/Timevaults/ethers-contracts/CrowdFunding.sol/index.ts @@ -0,0 +1,4 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export type { CrowdFunding } from './CrowdFunding.js'; \ No newline at end of file diff --git a/assignments/Solidity/Timevaults/ethers-contracts/Timevalut.sol/TimelockedVault.ts b/assignments/Solidity/Timevaults/ethers-contracts/Timevalut.sol/TimelockedVault.ts new file mode 100644 index 00000000..94f2898a --- /dev/null +++ b/assignments/Solidity/Timevaults/ethers-contracts/Timevalut.sol/TimelockedVault.ts @@ -0,0 +1,136 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { BaseContract, BigNumberish, BytesLike, FunctionFragment, Result, Interface, EventFragment, AddressLike, ContractRunner, ContractMethod, Listener } from "ethers" +import type { TypedContractEvent, TypedDeferredTopicFilter, TypedEventLog, TypedLogDescription, TypedListener, TypedContractMethod } from "../common.js" + + + export interface TimelockedVaultInterface extends Interface { + getFunction(nameOrSignature: "deposit" | "vaults" | "withdraw"): FunctionFragment; + + getEvent(nameOrSignatureOrTopic: "Deposited" | "Withdrawn"): EventFragment; + + encodeFunctionData(functionFragment: 'deposit', values: [BigNumberish]): string; +encodeFunctionData(functionFragment: 'vaults', values: [AddressLike]): string; +encodeFunctionData(functionFragment: 'withdraw', values?: undefined): string; + + decodeFunctionResult(functionFragment: 'deposit', data: BytesLike): Result; +decodeFunctionResult(functionFragment: 'vaults', data: BytesLike): Result; +decodeFunctionResult(functionFragment: 'withdraw', data: BytesLike): Result; + } + + + export namespace DepositedEvent { + export type InputTuple = [user: AddressLike, amount: BigNumberish, unlockTime: BigNumberish]; + export type OutputTuple = [user: string, amount: bigint, unlockTime: bigint]; + export interface OutputObject {user: string, amount: bigint, unlockTime: bigint }; + export type Event = TypedContractEvent + export type Filter = TypedDeferredTopicFilter + export type Log = TypedEventLog + export type LogDescription = TypedLogDescription + } + + + + export namespace WithdrawnEvent { + export type InputTuple = [user: AddressLike, amount: BigNumberish]; + export type OutputTuple = [user: string, amount: bigint]; + export interface OutputObject {user: string, amount: bigint }; + export type Event = TypedContractEvent + export type Filter = TypedDeferredTopicFilter + export type Log = TypedEventLog + export type LogDescription = TypedLogDescription + } + + + + export interface TimelockedVault extends BaseContract { + + connect(runner?: ContractRunner | null): TimelockedVault; + waitForDeployment(): Promise; + + interface: TimelockedVaultInterface; + + + 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 + + + + + deposit: TypedContractMethod< + [_unlockTime: BigNumberish, ], + [void], + 'payable' + > + + + + vaults: TypedContractMethod< + [arg0: AddressLike, ], + [[bigint, bigint, boolean] & {amount: bigint, unlockTime: bigint, active: boolean }], + 'view' + > + + + + withdraw: TypedContractMethod< + [], + [void], + 'nonpayable' + > + + + + getFunction(key: string | FunctionFragment): T; + + getFunction(nameOrSignature: 'deposit'): TypedContractMethod< + [_unlockTime: BigNumberish, ], + [void], + 'payable' + >; +getFunction(nameOrSignature: 'vaults'): TypedContractMethod< + [arg0: AddressLike, ], + [[bigint, bigint, boolean] & {amount: bigint, unlockTime: bigint, active: boolean }], + 'view' + >; +getFunction(nameOrSignature: 'withdraw'): TypedContractMethod< + [], + [void], + 'nonpayable' + >; + + getEvent(key: 'Deposited'): TypedContractEvent; +getEvent(key: 'Withdrawn'): TypedContractEvent; + + filters: { + + 'Deposited(address,uint256,uint256)': TypedContractEvent; + Deposited: TypedContractEvent; + + + 'Withdrawn(address,uint256)': TypedContractEvent; + Withdrawn: TypedContractEvent; + + }; + } \ No newline at end of file diff --git a/assignments/Solidity/Timevaults/ethers-contracts/Timevalut.sol/index.ts b/assignments/Solidity/Timevaults/ethers-contracts/Timevalut.sol/index.ts new file mode 100644 index 00000000..4b923536 --- /dev/null +++ b/assignments/Solidity/Timevaults/ethers-contracts/Timevalut.sol/index.ts @@ -0,0 +1,4 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export type { TimelockedVault } from './TimelockedVault.js'; \ No newline at end of file diff --git a/assignments/Solidity/Timevaults/ethers-contracts/common.ts b/assignments/Solidity/Timevaults/ethers-contracts/common.ts new file mode 100644 index 00000000..1b4cfcbe --- /dev/null +++ b/assignments/Solidity/Timevaults/ethers-contracts/common.ts @@ -0,0 +1,92 @@ +/* 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> + name: string + fragment: EventFragment + getFragment(...args: Partial): EventFragment +} + +type __TypechainAOutputTuple = T extends TypedContractEvent ? W : never +type __TypechainOutputObject = T extends TypedContractEvent ? 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 ? C : never +export type GetARGsTypeFromFactory = F extends MinEthersFactory ? Parameters : never + +export type StateMutability = 'nonpayable' | 'payable' | 'view' + +export type BaseOverrides = Omit +export type NonPayableOverrides = Omit +export type PayableOverrides = Omit +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, 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/assignments/Solidity/Timevaults/ethers-contracts/factories/Counter__factory.ts b/assignments/Solidity/Timevaults/ethers-contracts/factories/Counter__factory.ts new file mode 100644 index 00000000..00caab04 --- /dev/null +++ b/assignments/Solidity/Timevaults/ethers-contracts/factories/Counter__factory.ts @@ -0,0 +1,107 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { Addressable } from "ethers"; +import { Contract, ContractFactory, ContractTransactionResponse, Interface } from "ethers" +import type { Signer, ContractDeployTransaction, ContractRunner } from "ethers" +import type { NonPayableOverrides } from "../common.js" + import type { Counter, CounterInterface } from "../Counter.js"; + + const _abi = [ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "by", + "type": "uint256" + } + ], + "name": "Increment", + "type": "event" + }, + { + "inputs": [], + "name": "inc", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "by", + "type": "uint256" + } + ], + "name": "incBy", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "x", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] as const; + + const _bytecode = "0x6080604052348015600e575f5ffd5b506103cf8061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80630c55699c14610043578063371303c01461006157806370119d061461006b575b5f5ffd5b61004b610087565b6040516100589190610187565b60405180910390f35b61006961008c565b005b610085600480360381019061008091906101ce565b6100dc565b005b5f5481565b5f5f81548092919061009d90610226565b91905055507f51af157c2eee40f68107a47a49c32fbbeb0a3c9e5cd37aa56e88e6be92368a8160016040516100d291906102af565b60405180910390a1565b5f811161011e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161011590610348565b60405180910390fd5b805f5f82825461012e9190610366565b925050819055507f51af157c2eee40f68107a47a49c32fbbeb0a3c9e5cd37aa56e88e6be92368a81816040516101649190610187565b60405180910390a150565b5f819050919050565b6101818161016f565b82525050565b5f60208201905061019a5f830184610178565b92915050565b5f5ffd5b6101ad8161016f565b81146101b7575f5ffd5b50565b5f813590506101c8816101a4565b92915050565b5f602082840312156101e3576101e26101a0565b5b5f6101f0848285016101ba565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6102308261016f565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203610262576102616101f9565b5b600182019050919050565b5f819050919050565b5f819050919050565b5f61029961029461028f8461026d565b610276565b61016f565b9050919050565b6102a98161027f565b82525050565b5f6020820190506102c25f8301846102a0565b92915050565b5f82825260208201905092915050565b7f696e6342793a20696e6372656d656e742073686f756c6420626520706f7369745f8201527f6976650000000000000000000000000000000000000000000000000000000000602082015250565b5f6103326023836102c8565b915061033d826102d8565b604082019050919050565b5f6020820190508181035f83015261035f81610326565b9050919050565b5f6103708261016f565b915061037b8361016f565b9250828201905080821115610393576103926101f9565b5b9291505056fea26469706673582212208964bf78f883ff62c92c3c09416a09fa5a731da0068f08bc3384c0fdbe22f74f64736f6c634300081c0033"; + + + type CounterConstructorParams = [signer?: Signer] | ConstructorParameters; + + const isSuperArgs = (xs: CounterConstructorParams): xs is ConstructorParameters => + xs.length > 1 + + + export class Counter__factory extends ContractFactory { + + constructor(...args: CounterConstructorParams) { + if (isSuperArgs(args)) { + super(...args); + } else { + super(_abi, _bytecode, args[0]); + } + + } + + override getDeployTransaction(overrides?: NonPayableOverrides & { from?: string }): Promise { + return super.getDeployTransaction(overrides || {}); + }; + override deploy(overrides?: NonPayableOverrides & { from?: string }) { + return super.deploy(overrides || {}) as Promise; + } + override connect(runner: ContractRunner | null): Counter__factory { + return super.connect(runner) as Counter__factory; + } + + + static readonly bytecode = _bytecode; + static readonly abi = _abi; + static createInterface(): CounterInterface { + return new Interface(_abi) as CounterInterface; + } + + override attach(address: string | Addressable): Counter { + return super.attach(address) as Counter; + } + static connect(address: string, runner?: ContractRunner | null): Counter { + return new Contract(address, _abi, runner) as unknown as Counter; + } + } + + + \ No newline at end of file diff --git a/assignments/Solidity/Timevaults/ethers-contracts/factories/CrowdFunding.sol/CrowdFunding__factory.ts b/assignments/Solidity/Timevaults/ethers-contracts/factories/CrowdFunding.sol/CrowdFunding__factory.ts new file mode 100644 index 00000000..690cfc71 --- /dev/null +++ b/assignments/Solidity/Timevaults/ethers-contracts/factories/CrowdFunding.sol/CrowdFunding__factory.ts @@ -0,0 +1,213 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { Addressable } from "ethers"; +import { Contract, ContractFactory, ContractTransactionResponse, Interface } from "ethers" +import type { Signer, BigNumberish, ContractDeployTransaction, ContractRunner } from "ethers" +import type { NonPayableOverrides } from "../../common.js" + import type { CrowdFunding, CrowdFundingInterface } from "../../CrowdFunding.sol/CrowdFunding.js"; + + const _abi = [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_goal", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_duration", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "contributor", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ContributionReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "FundsWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "totalFunds", + "type": "uint256" + } + ], + "name": "GoalReached", + "type": "event" + }, + { + "inputs": [], + "name": "contribute", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "contributions", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "deadline", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "goal", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalFunds", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] as const; + + const _bytecode = "0x608060405234801561000f575f5ffd5b50604051610a46380380610a46833981810160405281019061003191906100c7565b335f5f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160018190555080426100839190610132565b6002819055505050610165565b5f5ffd5b5f819050919050565b6100a681610094565b81146100b0575f5ffd5b50565b5f815190506100c18161009d565b92915050565b5f5f604083850312156100dd576100dc610090565b5b5f6100ea858286016100b3565b92505060206100fb858286016100b3565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61013c82610094565b915061014783610094565b925082820190508082111561015f5761015e610105565b5b92915050565b6108d4806101725f395ff3fe60806040526004361061006f575f3560e01c806342e94c901161004d57806342e94c90146100dd5780638da5cb5b14610119578063968ed60014610143578063d7bb99ba1461016d5761006f565b806324600fc31461007357806329dcb0cf1461008957806340193883146100b3575b5f5ffd5b34801561007e575f5ffd5b50610087610177565b005b348015610094575f5ffd5b5061009d610373565b6040516100aa919061055c565b60405180910390f35b3480156100be575f5ffd5b506100c7610379565b6040516100d4919061055c565b60405180910390f35b3480156100e8575f5ffd5b5061010360048036038101906100fe91906105d3565b61037f565b604051610110919061055c565b60405180910390f35b348015610124575f5ffd5b5061012d610394565b60405161013a919061060d565b60405180910390f35b34801561014e575f5ffd5b506101576103b8565b604051610164919061055c565b60405180910390f35b6101756103be565b005b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610205576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101fc90610680565b60405180910390fd5b60025442101561024a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610241906106e8565b60405180910390fd5b6001546003541015610291576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161028890610750565b60405180910390fd5b5f60035490505f6003819055505f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc8290811502906040515f60405180830381858888f19350505050158015610301573d5f5f3e3d5ffd5b505f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167feaff4b37086828766ad3268786972c0cd24259d4c87a80f9d3963a3c3d999b0d82604051610368919061055c565b60405180910390a250565b60025481565b60015481565b6004602052805f5260405f205f915090505481565b5f5f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60035481565b6002544210610402576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103f9906107b8565b60405180910390fd5b5f3411610444576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161043b90610820565b60405180910390fd5b3460045f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f828254610490919061086b565b925050819055503460035f8282546104a8919061086b565b925050819055503373ffffffffffffffffffffffffffffffffffffffff167f1bb460ccaaf70fbacfec17a376f8acbd278c1405590ffcc8ebe4b88daf4f64ad346040516104f5919061055c565b60405180910390a260015460035410610542577ffbfd8ab7c24300fa9888cd721c8565a7da56759384781283684dcf7c7c4a846b600354604051610539919061055c565b60405180910390a15b565b5f819050919050565b61055681610544565b82525050565b5f60208201905061056f5f83018461054d565b92915050565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6105a282610579565b9050919050565b6105b281610598565b81146105bc575f5ffd5b50565b5f813590506105cd816105a9565b92915050565b5f602082840312156105e8576105e7610575565b5b5f6105f5848285016105bf565b91505092915050565b61060781610598565b82525050565b5f6020820190506106205f8301846105fe565b92915050565b5f82825260208201905092915050565b7f4f6e6c79206f776e657220616c6c6f77656400000000000000000000000000005f82015250565b5f61066a601283610626565b915061067582610636565b602082019050919050565b5f6020820190508181035f8301526106978161065e565b9050919050565b7f43616d706169676e207374696c6c2061637469766500000000000000000000005f82015250565b5f6106d2601583610626565b91506106dd8261069e565b602082019050919050565b5f6020820190508181035f8301526106ff816106c6565b9050919050565b7f476f616c206e6f742072656163686564000000000000000000000000000000005f82015250565b5f61073a601083610626565b915061074582610706565b602082019050919050565b5f6020820190508181035f8301526107678161072e565b9050919050565b7f43616d706169676e20656e6465640000000000000000000000000000000000005f82015250565b5f6107a2600e83610626565b91506107ad8261076e565b602082019050919050565b5f6020820190508181035f8301526107cf81610796565b9050919050565b7f4d7573742073656e6420455448000000000000000000000000000000000000005f82015250565b5f61080a600d83610626565b9150610815826107d6565b602082019050919050565b5f6020820190508181035f830152610837816107fe565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61087582610544565b915061088083610544565b92508282019050808211156108985761089761083e565b5b9291505056fea26469706673582212200cc699557f3a6c78532b17d21bc1765bc6276714c3a9066bf4fda6d2b93076fa64736f6c634300081c0033"; + + + type CrowdFundingConstructorParams = [signer?: Signer] | ConstructorParameters; + + const isSuperArgs = (xs: CrowdFundingConstructorParams): xs is ConstructorParameters => + xs.length > 1 + + + export class CrowdFunding__factory extends ContractFactory { + + constructor(...args: CrowdFundingConstructorParams) { + if (isSuperArgs(args)) { + super(...args); + } else { + super(_abi, _bytecode, args[0]); + } + + } + + override getDeployTransaction(_goal: BigNumberish, _duration: BigNumberish, overrides?: NonPayableOverrides & { from?: string }): Promise { + return super.getDeployTransaction(_goal, _duration, overrides || {}); + }; + override deploy(_goal: BigNumberish, _duration: BigNumberish, overrides?: NonPayableOverrides & { from?: string }) { + return super.deploy(_goal, _duration, overrides || {}) as Promise; + } + override connect(runner: ContractRunner | null): CrowdFunding__factory { + return super.connect(runner) as CrowdFunding__factory; + } + + + static readonly bytecode = _bytecode; + static readonly abi = _abi; + static createInterface(): CrowdFundingInterface { + return new Interface(_abi) as CrowdFundingInterface; + } + + override attach(address: string | Addressable): CrowdFunding { + return super.attach(address) as CrowdFunding; + } + static connect(address: string, runner?: ContractRunner | null): CrowdFunding { + return new Contract(address, _abi, runner) as unknown as CrowdFunding; + } + } + + + \ No newline at end of file diff --git a/assignments/Solidity/Timevaults/ethers-contracts/factories/CrowdFunding.sol/index.ts b/assignments/Solidity/Timevaults/ethers-contracts/factories/CrowdFunding.sol/index.ts new file mode 100644 index 00000000..e12ae264 --- /dev/null +++ b/assignments/Solidity/Timevaults/ethers-contracts/factories/CrowdFunding.sol/index.ts @@ -0,0 +1,4 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export { CrowdFunding__factory } from './CrowdFunding__factory.js'; \ No newline at end of file diff --git a/assignments/Solidity/Timevaults/ethers-contracts/factories/Timevalut.sol/TimelockedVault__factory.ts b/assignments/Solidity/Timevaults/ethers-contracts/factories/Timevalut.sol/TimelockedVault__factory.ts new file mode 100644 index 00000000..80f6a506 --- /dev/null +++ b/assignments/Solidity/Timevaults/ethers-contracts/factories/Timevalut.sol/TimelockedVault__factory.ts @@ -0,0 +1,154 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type { Addressable } from "ethers"; +import { Contract, ContractFactory, ContractTransactionResponse, Interface } from "ethers" +import type { Signer, ContractDeployTransaction, ContractRunner } from "ethers" +import type { NonPayableOverrides } from "../../common.js" + import type { TimelockedVault, TimelockedVaultInterface } from "../../Timevalut.sol/TimelockedVault.js"; + + const _abi = [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "unlockTime", + "type": "uint256" + } + ], + "name": "Deposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Withdrawn", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_unlockTime", + "type": "uint256" + } + ], + "name": "deposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "vaults", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "unlockTime", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "active", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] as const; + + const _bytecode = "0x6080604052348015600e575f5ffd5b50610a688061001c5f395ff3fe608060405260043610610033575f3560e01c80633ccfd60b14610037578063a622ee7c1461004d578063b6b55f251461008b575b5f5ffd5b348015610042575f5ffd5b5061004b6100a7565b005b348015610058575f5ffd5b50610073600480360381019061006e9190610582565b6102e7565b604051610082939291906105df565b60405180910390f35b6100a560048036038101906100a0919061063e565b610318565b005b5f5f5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f209050806002015f9054906101000a900460ff16610136576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161012d906106c3565b60405180910390fd5b806001015442101561017d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101749061072b565b60405180910390fd5b5f815f0154116101c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101b990610793565b60405180910390fd5b5f815f015490505f825f01819055505f826002015f6101000a81548160ff0219169083151502179055505f3373ffffffffffffffffffffffffffffffffffffffff1682604051610211906107de565b5f6040518083038185875af1925050503d805f811461024b576040519150601f19603f3d011682016040523d82523d5f602084013e610250565b606091505b5050905080610294576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161028b9061083c565b60405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d5836040516102da919061085a565b60405180910390a2505050565b5f602052805f5260405f205f91509050805f015490806001015490806002015f9054906101000a900460ff16905083565b5f341161035a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610351906108bd565b60405180910390fd5b428111801561037857506301e13380426103749190610908565b8111155b6103b7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103ae90610985565b60405180910390fd5b5f5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f206002015f9054906101000a900460ff1615610443576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161043a906109ed565b60405180910390fd5b6040518060600160405280348152602001828152602001600115158152505f5f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f820151815f0155602082015181600101556040820151816002015f6101000a81548160ff0219169083151502179055509050503373ffffffffffffffffffffffffffffffffffffffff167f73a19dd210f1a7f902193214c0ee91dd35ee5b4d920cba8d519eca65a7b488ca3483604051610519929190610a0b565b60405180910390a250565b5f5ffd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61055182610528565b9050919050565b61056181610547565b811461056b575f5ffd5b50565b5f8135905061057c81610558565b92915050565b5f6020828403121561059757610596610524565b5b5f6105a48482850161056e565b91505092915050565b5f819050919050565b6105bf816105ad565b82525050565b5f8115159050919050565b6105d9816105c5565b82525050565b5f6060820190506105f25f8301866105b6565b6105ff60208301856105b6565b61060c60408301846105d0565b949350505050565b61061d816105ad565b8114610627575f5ffd5b50565b5f8135905061063881610614565b92915050565b5f6020828403121561065357610652610524565b5b5f6106608482850161062a565b91505092915050565b5f82825260208201905092915050565b7f4e6f20616374697665207661756c7400000000000000000000000000000000005f82015250565b5f6106ad600f83610669565b91506106b882610679565b602082019050919050565b5f6020820190508181035f8301526106da816106a1565b9050919050565b7f46756e647320617265206c6f636b6564000000000000000000000000000000005f82015250565b5f610715601083610669565b9150610720826106e1565b602082019050919050565b5f6020820190508181035f83015261074281610709565b9050919050565b7f4e6f2066756e64730000000000000000000000000000000000000000000000005f82015250565b5f61077d600883610669565b915061078882610749565b602082019050919050565b5f6020820190508181035f8301526107aa81610771565b9050919050565b5f81905092915050565b50565b5f6107c95f836107b1565b91506107d4826107bb565b5f82019050919050565b5f6107e8826107be565b9150819050919050565b7f5472616e73666572206661696c656400000000000000000000000000000000005f82015250565b5f610826600f83610669565b9150610831826107f2565b602082019050919050565b5f6020820190508181035f8301526108538161081a565b9050919050565b5f60208201905061086d5f8301846105b6565b92915050565b7f4d7573742073656e6420455448000000000000000000000000000000000000005f82015250565b5f6108a7600d83610669565b91506108b282610873565b602082019050919050565b5f6020820190508181035f8301526108d48161089b565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610912826105ad565b915061091d836105ad565b9250828201905080821115610935576109346108db565b5b92915050565b7f496e76616c696420756e6c6f636b2074696d65000000000000000000000000005f82015250565b5f61096f601383610669565b915061097a8261093b565b602082019050919050565b5f6020820190508181035f83015261099c81610963565b9050919050565b7f5661756c7420616c7265616479206163746976650000000000000000000000005f82015250565b5f6109d7601483610669565b91506109e2826109a3565b602082019050919050565b5f6020820190508181035f830152610a04816109cb565b9050919050565b5f604082019050610a1e5f8301856105b6565b610a2b60208301846105b6565b939250505056fea26469706673582212201969f2fd32e539a4aadfdc85b9caad81878d25dd84e454fb9ee4035cb9093f4464736f6c634300081c0033"; + + + type TimelockedVaultConstructorParams = [signer?: Signer] | ConstructorParameters; + + const isSuperArgs = (xs: TimelockedVaultConstructorParams): xs is ConstructorParameters => + xs.length > 1 + + + export class TimelockedVault__factory extends ContractFactory { + + constructor(...args: TimelockedVaultConstructorParams) { + if (isSuperArgs(args)) { + super(...args); + } else { + super(_abi, _bytecode, args[0]); + } + + } + + override getDeployTransaction(overrides?: NonPayableOverrides & { from?: string }): Promise { + return super.getDeployTransaction(overrides || {}); + }; + override deploy(overrides?: NonPayableOverrides & { from?: string }) { + return super.deploy(overrides || {}) as Promise; + } + override connect(runner: ContractRunner | null): TimelockedVault__factory { + return super.connect(runner) as TimelockedVault__factory; + } + + + static readonly bytecode = _bytecode; + static readonly abi = _abi; + static createInterface(): TimelockedVaultInterface { + return new Interface(_abi) as TimelockedVaultInterface; + } + + override attach(address: string | Addressable): TimelockedVault { + return super.attach(address) as TimelockedVault; + } + static connect(address: string, runner?: ContractRunner | null): TimelockedVault { + return new Contract(address, _abi, runner) as unknown as TimelockedVault; + } + } + + + \ No newline at end of file diff --git a/assignments/Solidity/Timevaults/ethers-contracts/factories/Timevalut.sol/index.ts b/assignments/Solidity/Timevaults/ethers-contracts/factories/Timevalut.sol/index.ts new file mode 100644 index 00000000..f49294c3 --- /dev/null +++ b/assignments/Solidity/Timevaults/ethers-contracts/factories/Timevalut.sol/index.ts @@ -0,0 +1,4 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export { TimelockedVault__factory } from './TimelockedVault__factory.js'; \ No newline at end of file diff --git a/assignments/Solidity/Timevaults/ethers-contracts/factories/index.ts b/assignments/Solidity/Timevaults/ethers-contracts/factories/index.ts new file mode 100644 index 00000000..104ceb44 --- /dev/null +++ b/assignments/Solidity/Timevaults/ethers-contracts/factories/index.ts @@ -0,0 +1,7 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +export * as crowdFundingSol from './CrowdFunding.sol/index.js'; +export * as timevalutSol from './Timevalut.sol/index.js'; +export * as deSol from './de.sol/index.js'; +export { Counter__factory } from './Counter__factory.js'; \ No newline at end of file diff --git a/assignments/Solidity/Timevaults/ethers-contracts/hardhat.d.ts b/assignments/Solidity/Timevaults/ethers-contracts/hardhat.d.ts new file mode 100644 index 00000000..59df0cb0 --- /dev/null +++ b/assignments/Solidity/Timevaults/ethers-contracts/hardhat.d.ts @@ -0,0 +1,59 @@ +/* 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 "./index.js"; + +declare module "@nomicfoundation/hardhat-ethers/types" { + interface HardhatEthersHelpers extends HardhatEthersHelpersBase { + getContractFactory(name: 'Counter', signerOrOptions?: ethers.Signer | FactoryOptions): Promise +getContractFactory(name: 'CrowdFunding', signerOrOptions?: ethers.Signer | FactoryOptions): Promise +getContractFactory(name: 'TimelockedVault', signerOrOptions?: ethers.Signer | FactoryOptions): Promise +getContractFactory(name: 'TimelockedVault', signerOrOptions?: ethers.Signer | FactoryOptions): Promise + + getContractAt(name: 'Counter', address: string | ethers.Addressable, signer?: ethers.Signer): Promise +getContractAt(name: 'CrowdFunding', address: string | ethers.Addressable, signer?: ethers.Signer): Promise +getContractAt(name: 'TimelockedVault', address: string | ethers.Addressable, signer?: ethers.Signer): Promise +getContractAt(name: 'TimelockedVault', address: string | ethers.Addressable, signer?: ethers.Signer): Promise + + deployContract(name: 'Counter', signerOrOptions?: ethers.Signer | DeployContractOptions): Promise +deployContract(name: 'CrowdFunding', signerOrOptions?: ethers.Signer | DeployContractOptions): Promise +deployContract(name: 'TimelockedVault', signerOrOptions?: ethers.Signer | DeployContractOptions): Promise +deployContract(name: 'TimelockedVault', signerOrOptions?: ethers.Signer | DeployContractOptions): Promise + + deployContract(name: 'Counter', args: any[], signerOrOptions?: ethers.Signer | DeployContractOptions): Promise +deployContract(name: 'CrowdFunding', args: any[], signerOrOptions?: ethers.Signer | DeployContractOptions): Promise +deployContract(name: 'TimelockedVault', args: any[], signerOrOptions?: ethers.Signer | DeployContractOptions): Promise +deployContract(name: 'TimelockedVault', 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; + } +} + \ No newline at end of file diff --git a/assignments/Solidity/Timevaults/ethers-contracts/index.ts b/assignments/Solidity/Timevaults/ethers-contracts/index.ts new file mode 100644 index 00000000..b7f52683 --- /dev/null +++ b/assignments/Solidity/Timevaults/ethers-contracts/index.ts @@ -0,0 +1,16 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ +import type * as crowdFundingSol from './CrowdFunding.sol/index.js'; +export type { crowdFundingSol }; +import type * as timevalutSol from './Timevalut.sol/index.js'; +export type { timevalutSol }; +import type * as deSol from './de.sol/index.js'; +export type { deSol }; +export type { Counter } from './Counter.js'; +export * as factories from './factories/index.js'; +export { Counter__factory } from './factories/Counter__factory.js'; +export type { CrowdFunding } from './CrowdFunding.sol/CrowdFunding.js'; +export { CrowdFunding__factory } from './factories/CrowdFunding.sol/CrowdFunding__factory.js'; +export type { TimelockedVault } from './Timevalut.sol/TimelockedVault.js'; +export { TimelockedVault__factory } from './factories/Timevalut.sol/TimelockedVault__factory.js'; \ No newline at end of file diff --git a/assignments/Solidity/Timevaults/hardhat.config.ts b/assignments/Solidity/Timevaults/hardhat.config.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/Solidity/Timevaults/hardhat.config.ts @@ -0,0 +1,38 @@ +import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers"; +import { configVariable, defineConfig } from "hardhat/config"; + +export default defineConfig({ + plugins: [hardhatToolboxMochaEthersPlugin], + solidity: { + profiles: { + default: { + version: "0.8.28", + }, + production: { + version: "0.8.28", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + }, + }, + networks: { + hardhatMainnet: { + type: "edr-simulated", + chainType: "l1", + }, + hardhatOp: { + type: "edr-simulated", + chainType: "op", + }, + sepolia: { + type: "http", + chainType: "l1", + url: configVariable("SEPOLIA_RPC_URL"), + accounts: [configVariable("SEPOLIA_PRIVATE_KEY")], + }, + }, +}); diff --git a/assignments/Solidity/Timevaults/ignition/modules/Counter.ts b/assignments/Solidity/Timevaults/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/Solidity/Timevaults/ignition/modules/Counter.ts @@ -0,0 +1,9 @@ +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; + +export default buildModule("CounterModule", (m) => { + const counter = m.contract("Counter"); + + m.call(counter, "incBy", [5n]); + + return { counter }; +}); diff --git a/assignments/Solidity/Timevaults/package.json b/assignments/Solidity/Timevaults/package.json new file mode 100644 index 00000000..af71c13c --- /dev/null +++ b/assignments/Solidity/Timevaults/package.json @@ -0,0 +1,20 @@ +{ + "name": "Vault", + "version": "1.0.0", + "type": "module", + "devDependencies": { + "@nomicfoundation/hardhat-ethers": "^4.0.4", + "@nomicfoundation/hardhat-ignition": "^3.0.7", + "@nomicfoundation/hardhat-toolbox-mocha-ethers": "^3.0.2", + "@types/chai": "^4.3.20", + "@types/chai-as-promised": "^8.0.2", + "@types/mocha": "^10.0.10", + "@types/node": "^22.19.11", + "chai": "^5.3.3", + "ethers": "^6.16.0", + "forge-std": "github:foundry-rs/forge-std#v1.9.4", + "hardhat": "^3.1.7", + "mocha": "^11.7.5", + "typescript": "~5.8.0" + } +} diff --git a/assignments/Solidity/Timevaults/scripts/send-op-tx.ts b/assignments/Solidity/Timevaults/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/Solidity/Timevaults/scripts/send-op-tx.ts @@ -0,0 +1,22 @@ +import { network } from "hardhat"; + +const { ethers } = await network.connect({ + network: "hardhatOp", + chainType: "op", +}); + +console.log("Sending transaction using the OP chain type"); + +const [sender] = await ethers.getSigners(); + +console.log("Sending 1 wei from", sender.address, "to itself"); + +console.log("Sending L2 transaction"); +const tx = await sender.sendTransaction({ + to: sender.address, + value: 1n, +}); + +await tx.wait(); + +console.log("Transaction sent successfully"); diff --git a/assignments/Solidity/Timevaults/test/Counter.ts b/assignments/Solidity/Timevaults/test/Counter.ts new file mode 100644 index 00000000..f8c38986 --- /dev/null +++ b/assignments/Solidity/Timevaults/test/Counter.ts @@ -0,0 +1,36 @@ +import { expect } from "chai"; +import { network } from "hardhat"; + +const { ethers } = await network.connect(); + +describe("Counter", function () { + it("Should emit the Increment event when calling the inc() function", async function () { + const counter = await ethers.deployContract("Counter"); + + await expect(counter.inc()).to.emit(counter, "Increment").withArgs(1n); + }); + + it("The sum of the Increment events should match the current value", async function () { + const counter = await ethers.deployContract("Counter"); + const deploymentBlockNumber = await ethers.provider.getBlockNumber(); + + // run a series of increments + for (let i = 1; i <= 10; i++) { + await counter.incBy(i); + } + + const events = await counter.queryFilter( + counter.filters.Increment(), + deploymentBlockNumber, + "latest", + ); + + // check that the aggregated events match the current value + let total = 0n; + for (const event of events) { + total += event.args.by; + } + + expect(await counter.x()).to.equal(total); + }); +}); diff --git a/assignments/Solidity/Timevaults/test/TimeVault.ts b/assignments/Solidity/Timevaults/test/TimeVault.ts new file mode 100644 index 00000000..e69de29b diff --git a/assignments/Solidity/Timevaults/tsconfig.json b/assignments/Solidity/Timevaults/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/Solidity/Timevaults/tsconfig.json @@ -0,0 +1,13 @@ +/* Based on https://github.com/tsconfig/bases/blob/501da2bcd640cf95c95805783e1012b992338f28/bases/node22.json */ +{ + "compilerOptions": { + "lib": ["es2023"], + "module": "node16", + "target": "es2022", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "moduleResolution": "node16", + "outDir": "dist" + } +}