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" + } +}