diff --git a/assignments/1/saving-vault/.gitignore b/assignments/1/saving-vault/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/1/saving-vault/.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/1/saving-vault/README.md b/assignments/1/saving-vault/README.md new file mode 100644 index 00000000..4743e82a --- /dev/null +++ b/assignments/1/saving-vault/README.md @@ -0,0 +1,57 @@ +# Sample Hardhat 3 Beta Project (`node:test` and `viem`) + +This project showcases a Hardhat 3 Beta project using the native Node.js test runner (`node:test`) and the `viem` library for Ethereum interactions. + +To learn more about the Hardhat 3 Beta, please visit the [Getting Started guide](https://hardhat.org/docs/getting-started#getting-started-with-hardhat-3). To share your feedback, join our [Hardhat 3 Beta](https://hardhat.org/hardhat3-beta-telegram-group) Telegram group or [open an issue](https://github.com/NomicFoundation/hardhat/issues/new) in our GitHub issue tracker. + +## Project Overview + +This example project includes: + +- A simple Hardhat configuration file. +- Foundry-compatible Solidity unit tests. +- TypeScript integration tests using [`node:test`](nodejs.org/api/test.html), the new Node.js native test runner, and [`viem`](https://viem.sh/). +- Examples demonstrating how to connect to different types of networks, including locally simulating OP mainnet. + +## Usage + +### Running Tests + +To run all the tests in the project, execute the following command: + +```shell +npx hardhat test +``` + +You can also selectively run the Solidity or `node:test` tests: + +```shell +npx hardhat test solidity +npx hardhat test nodejs +``` + +### Make a deployment to Sepolia + +This project includes an example Ignition module to deploy the contract. You can deploy this module to a locally simulated chain or to Sepolia. + +To run the deployment to a local chain: + +```shell +npx hardhat ignition deploy ignition/modules/Counter.ts +``` + +To run the deployment to Sepolia, you need an account with funds to send the transaction. The provided Hardhat configuration includes a Configuration Variable called `SEPOLIA_PRIVATE_KEY`, which you can use to set the private key of the account you want to use. + +You can set the `SEPOLIA_PRIVATE_KEY` variable using the `hardhat-keystore` plugin or by setting it as an environment variable. + +To set the `SEPOLIA_PRIVATE_KEY` config variable using `hardhat-keystore`: + +```shell +npx hardhat keystore set SEPOLIA_PRIVATE_KEY +``` + +After setting the variable, you can run the deployment with the Sepolia network: + +```shell +npx hardhat ignition deploy --network sepolia ignition/modules/Counter.ts +``` diff --git a/assignments/1/saving-vault/contracts/Counter.sol b/assignments/1/saving-vault/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/1/saving-vault/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/1/saving-vault/contracts/Counter.t.sol b/assignments/1/saving-vault/contracts/Counter.t.sol new file mode 100644 index 00000000..582fc074 --- /dev/null +++ b/assignments/1/saving-vault/contracts/Counter.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import {Counter} from "./Counter.sol"; +import {Test} from "forge-std/Test.sol"; + +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/1/saving-vault/contracts/vault.sol b/assignments/1/saving-vault/contracts/vault.sol new file mode 100644 index 00000000..dd6e6fc6 --- /dev/null +++ b/assignments/1/saving-vault/contracts/vault.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +contract TimelockedVault { + + enum VaultStatus { + Empty, + Locked + } + + struct Vault { + uint amount; + uint unlockTime; + VaultStatus status; + } + + mapping(address => Vault) public userVaults; + + event Deposited(address indexed user, uint amount, uint unlockTime); + event Withdrawn(address indexed user, uint amount); + + function deposit(uint _lockDurationSeconds) external payable { + + require(msg.value > 0, "Must deposit some ETH"); + require(userVaults[msg.sender].status == VaultStatus.Empty, "Vault already active"); + + + uint unlockTimestamp = block.timestamp + _lockDurationSeconds; + + userVaults[msg.sender] = Vault({ + amount: msg.value, + unlockTime: unlockTimestamp, + status: VaultStatus.Locked + }); + + emit Deposited(msg.sender, msg.value, unlockTimestamp); + } + + function withdraw() external { + Vault storage myVault = userVaults[msg.sender]; + + require(myVault.status == VaultStatus.Locked, "No active vault found"); + require(block.timestamp >= myVault.unlockTime, "Funds are still locked"); + + uint amountToTransfer = myVault.amount; + + + delete userVaults[msg.sender]; + + // The Transfer + (bool success, ) = payable(msg.sender).call{value: amountToTransfer}(""); + require(success, "Withdrawal failed"); + + emit Withdrawn(msg.sender, amountToTransfer); + } +} \ No newline at end of file diff --git a/assignments/1/saving-vault/hardhat.config.ts b/assignments/1/saving-vault/hardhat.config.ts new file mode 100644 index 00000000..1acc094d --- /dev/null +++ b/assignments/1/saving-vault/hardhat.config.ts @@ -0,0 +1,38 @@ +import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem"; +import { configVariable, defineConfig } from "hardhat/config"; + +export default defineConfig({ + plugins: [hardhatToolboxViemPlugin], + 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/1/saving-vault/ignition/modules/Counter.ts b/assignments/1/saving-vault/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/1/saving-vault/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/1/saving-vault/package.json b/assignments/1/saving-vault/package.json new file mode 100644 index 00000000..797d8ffe --- /dev/null +++ b/assignments/1/saving-vault/package.json @@ -0,0 +1,14 @@ +{ + "name": "saving-vault", + "version": "1.0.0", + "type": "module", + "devDependencies": { + "@nomicfoundation/hardhat-ignition": "^3.0.7", + "@nomicfoundation/hardhat-toolbox-viem": "^5.0.2", + "@types/node": "^22.19.8", + "forge-std": "github:foundry-rs/forge-std#v1.9.4", + "hardhat": "^3.1.6", + "typescript": "~5.8.0", + "viem": "^2.45.1" + } +} diff --git a/assignments/1/saving-vault/scripts/send-op-tx.ts b/assignments/1/saving-vault/scripts/send-op-tx.ts new file mode 100644 index 00000000..bb738c5c --- /dev/null +++ b/assignments/1/saving-vault/scripts/send-op-tx.ts @@ -0,0 +1,31 @@ +import { network } from "hardhat"; + +const { viem } = await network.connect({ + network: "hardhatOp", + chainType: "op", +}); + +console.log("Sending transaction using the OP chain type"); + +const publicClient = await viem.getPublicClient(); +const [senderClient] = await viem.getWalletClients(); + +console.log("Sending 1 wei from", senderClient.account.address, "to itself"); + +const l1Gas = await publicClient.estimateL1Gas({ + account: senderClient.account.address, + to: senderClient.account.address, + value: 1n, +}); + +console.log("Estimated L1 gas:", l1Gas); + +console.log("Sending L2 transaction"); +const tx = await senderClient.sendTransaction({ + to: senderClient.account.address, + value: 1n, +}); + +await publicClient.waitForTransactionReceipt({ hash: tx }); + +console.log("Transaction sent successfully"); diff --git a/assignments/1/saving-vault/test/Counter.ts b/assignments/1/saving-vault/test/Counter.ts new file mode 100644 index 00000000..50c2713d --- /dev/null +++ b/assignments/1/saving-vault/test/Counter.ts @@ -0,0 +1,46 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import { network } from "hardhat"; + +describe("Counter", async function () { + const { viem } = await network.connect(); + const publicClient = await viem.getPublicClient(); + + it("Should emit the Increment event when calling the inc() function", async function () { + const counter = await viem.deployContract("Counter"); + + await viem.assertions.emitWithArgs( + counter.write.inc(), + counter, + "Increment", + [1n], + ); + }); + + it("The sum of the Increment events should match the current value", async function () { + const counter = await viem.deployContract("Counter"); + const deploymentBlockNumber = await publicClient.getBlockNumber(); + + // run a series of increments + for (let i = 1n; i <= 10n; i++) { + await counter.write.incBy([i]); + } + + const events = await publicClient.getContractEvents({ + address: counter.address, + abi: counter.abi, + eventName: "Increment", + fromBlock: deploymentBlockNumber, + strict: true, + }); + + // check that the aggregated events match the current value + let total = 0n; + for (const event of events) { + total += event.args.by; + } + + assert.equal(total, await counter.read.x()); + }); +}); diff --git a/assignments/1/saving-vault/tsconfig.json b/assignments/1/saving-vault/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/1/saving-vault/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" + } +} diff --git a/assignments/crowd-funding/.gitignore b/assignments/crowd-funding/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/crowd-funding/.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/crowd-funding/README.md b/assignments/crowd-funding/README.md new file mode 100644 index 00000000..4743e82a --- /dev/null +++ b/assignments/crowd-funding/README.md @@ -0,0 +1,57 @@ +# Sample Hardhat 3 Beta Project (`node:test` and `viem`) + +This project showcases a Hardhat 3 Beta project using the native Node.js test runner (`node:test`) and the `viem` library for Ethereum interactions. + +To learn more about the Hardhat 3 Beta, please visit the [Getting Started guide](https://hardhat.org/docs/getting-started#getting-started-with-hardhat-3). To share your feedback, join our [Hardhat 3 Beta](https://hardhat.org/hardhat3-beta-telegram-group) Telegram group or [open an issue](https://github.com/NomicFoundation/hardhat/issues/new) in our GitHub issue tracker. + +## Project Overview + +This example project includes: + +- A simple Hardhat configuration file. +- Foundry-compatible Solidity unit tests. +- TypeScript integration tests using [`node:test`](nodejs.org/api/test.html), the new Node.js native test runner, and [`viem`](https://viem.sh/). +- Examples demonstrating how to connect to different types of networks, including locally simulating OP mainnet. + +## Usage + +### Running Tests + +To run all the tests in the project, execute the following command: + +```shell +npx hardhat test +``` + +You can also selectively run the Solidity or `node:test` tests: + +```shell +npx hardhat test solidity +npx hardhat test nodejs +``` + +### Make a deployment to Sepolia + +This project includes an example Ignition module to deploy the contract. You can deploy this module to a locally simulated chain or to Sepolia. + +To run the deployment to a local chain: + +```shell +npx hardhat ignition deploy ignition/modules/Counter.ts +``` + +To run the deployment to Sepolia, you need an account with funds to send the transaction. The provided Hardhat configuration includes a Configuration Variable called `SEPOLIA_PRIVATE_KEY`, which you can use to set the private key of the account you want to use. + +You can set the `SEPOLIA_PRIVATE_KEY` variable using the `hardhat-keystore` plugin or by setting it as an environment variable. + +To set the `SEPOLIA_PRIVATE_KEY` config variable using `hardhat-keystore`: + +```shell +npx hardhat keystore set SEPOLIA_PRIVATE_KEY +``` + +After setting the variable, you can run the deployment with the Sepolia network: + +```shell +npx hardhat ignition deploy --network sepolia ignition/modules/Counter.ts +``` diff --git a/assignments/crowd-funding/contracts/Counter.sol b/assignments/crowd-funding/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/crowd-funding/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/crowd-funding/contracts/Counter.t.sol b/assignments/crowd-funding/contracts/Counter.t.sol new file mode 100644 index 00000000..582fc074 --- /dev/null +++ b/assignments/crowd-funding/contracts/Counter.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import {Counter} from "./Counter.sol"; +import {Test} from "forge-std/Test.sol"; + +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/crowd-funding/contracts/crowd-funding.sol b/assignments/crowd-funding/contracts/crowd-funding.sol new file mode 100644 index 00000000..e28f729b --- /dev/null +++ b/assignments/crowd-funding/contracts/crowd-funding.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +contract Crowdfunding { + + enum ProjectStatus { + Ongoing, + Successful, + Failed + } + ProjectStatus public status; + + struct Project { + address owner; + uint goal; + uint deadline; + uint totalRaised; + } + + Project public project; + + mapping(address => uint) public contributions; + + event Contributed(address indexed user, uint amount); + event GoalReached(uint totalAmount); + event RefundClaimed(address indexed user, uint amount); + + modifier onlyOwner() { + require(msg.sender == project.owner, "Only owner can call this"); + _; + } + + constructor(uint _goal, uint _durationInSeconds) { + + project = Project({ + owner: msg.sender, + goal: _goal, + deadline: block.timestamp + _durationInSeconds, + totalRaised: 0 + }); + status = ProjectStatus.Ongoing; + } + + function contribute() external payable { + require(block.timestamp < project.deadline, "Deadline has passed"); + require(msg.value > 0, "Contribution must be > 0"); + require(status == ProjectStatus.Ongoing, "Project no longer accepting funds"); + + contributions[msg.sender] += msg.value; + + // Update the total in STRUCT + project.totalRaised += msg.value; + + emit Contributed(msg.sender, msg.value); + } + + // Owner withdraws if goal is met + function withdrawFunds() external onlyOwner { + require(project.totalRaised >= project.goal, "Goal not reached"); + require(block.timestamp >= project.deadline, "Deadline not reached yet"); + + status = ProjectStatus.Successful; + uint amount = address(this).balance; + + (bool success, ) = payable(project.owner).call{value: amount}(""); + require(success, "Transfer failed"); + } + + // Contributors get money back i f goal is NOT met + function claimRefund() external { + + require(block.timestamp >= project.deadline, "Deadline not reached yet"); + require(project.totalRaised < project.goal, "Goal was met, no refunds"); + + uint amountToRefund = contributions[msg.sender]; + require(amountToRefund > 0, "No funds to claim"); + + contributions[msg.sender] = 0; + + status = ProjectStatus.Failed; + + (bool success, ) = payable(msg.sender).call{value: amountToRefund}(""); + require(success, "Refund failed"); + + emit RefundClaimed(msg.sender, amountToRefund); + } +} \ No newline at end of file diff --git a/assignments/crowd-funding/hardhat.config.ts b/assignments/crowd-funding/hardhat.config.ts new file mode 100644 index 00000000..1acc094d --- /dev/null +++ b/assignments/crowd-funding/hardhat.config.ts @@ -0,0 +1,38 @@ +import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem"; +import { configVariable, defineConfig } from "hardhat/config"; + +export default defineConfig({ + plugins: [hardhatToolboxViemPlugin], + 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/crowd-funding/ignition/modules/Counter.ts b/assignments/crowd-funding/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/crowd-funding/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/crowd-funding/package.json b/assignments/crowd-funding/package.json new file mode 100644 index 00000000..d40004a7 --- /dev/null +++ b/assignments/crowd-funding/package.json @@ -0,0 +1,14 @@ +{ + "name": "crowd-funding", + "version": "1.0.0", + "type": "module", + "devDependencies": { + "@nomicfoundation/hardhat-ignition": "^3.0.7", + "@nomicfoundation/hardhat-toolbox-viem": "^5.0.2", + "@types/node": "^22.19.8", + "forge-std": "github:foundry-rs/forge-std#v1.9.4", + "hardhat": "^3.1.6", + "typescript": "~5.8.0", + "viem": "^2.45.1" + } +} diff --git a/assignments/crowd-funding/scripts/send-op-tx.ts b/assignments/crowd-funding/scripts/send-op-tx.ts new file mode 100644 index 00000000..bb738c5c --- /dev/null +++ b/assignments/crowd-funding/scripts/send-op-tx.ts @@ -0,0 +1,31 @@ +import { network } from "hardhat"; + +const { viem } = await network.connect({ + network: "hardhatOp", + chainType: "op", +}); + +console.log("Sending transaction using the OP chain type"); + +const publicClient = await viem.getPublicClient(); +const [senderClient] = await viem.getWalletClients(); + +console.log("Sending 1 wei from", senderClient.account.address, "to itself"); + +const l1Gas = await publicClient.estimateL1Gas({ + account: senderClient.account.address, + to: senderClient.account.address, + value: 1n, +}); + +console.log("Estimated L1 gas:", l1Gas); + +console.log("Sending L2 transaction"); +const tx = await senderClient.sendTransaction({ + to: senderClient.account.address, + value: 1n, +}); + +await publicClient.waitForTransactionReceipt({ hash: tx }); + +console.log("Transaction sent successfully"); diff --git a/assignments/crowd-funding/test/Counter.ts b/assignments/crowd-funding/test/Counter.ts new file mode 100644 index 00000000..50c2713d --- /dev/null +++ b/assignments/crowd-funding/test/Counter.ts @@ -0,0 +1,46 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import { network } from "hardhat"; + +describe("Counter", async function () { + const { viem } = await network.connect(); + const publicClient = await viem.getPublicClient(); + + it("Should emit the Increment event when calling the inc() function", async function () { + const counter = await viem.deployContract("Counter"); + + await viem.assertions.emitWithArgs( + counter.write.inc(), + counter, + "Increment", + [1n], + ); + }); + + it("The sum of the Increment events should match the current value", async function () { + const counter = await viem.deployContract("Counter"); + const deploymentBlockNumber = await publicClient.getBlockNumber(); + + // run a series of increments + for (let i = 1n; i <= 10n; i++) { + await counter.write.incBy([i]); + } + + const events = await publicClient.getContractEvents({ + address: counter.address, + abi: counter.abi, + eventName: "Increment", + fromBlock: deploymentBlockNumber, + strict: true, + }); + + // check that the aggregated events match the current value + let total = 0n; + for (const event of events) { + total += event.args.by; + } + + assert.equal(total, await counter.read.x()); + }); +}); diff --git a/assignments/crowd-funding/tsconfig.json b/assignments/crowd-funding/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/crowd-funding/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" + } +} diff --git a/assignments/escrow-agent/.gitignore b/assignments/escrow-agent/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/escrow-agent/.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/escrow-agent/README.md b/assignments/escrow-agent/README.md new file mode 100644 index 00000000..968246e9 --- /dev/null +++ b/assignments/escrow-agent/README.md @@ -0,0 +1,57 @@ +# Sample Hardhat 3 Beta Project (`mocha` and `ethers`) + +This project showcases a Hardhat 3 Beta project using `mocha` for tests and the `ethers` library for Ethereum interactions. + +To learn more about the Hardhat 3 Beta, please visit the [Getting Started guide](https://hardhat.org/docs/getting-started#getting-started-with-hardhat-3). To share your feedback, join our [Hardhat 3 Beta](https://hardhat.org/hardhat3-beta-telegram-group) Telegram group or [open an issue](https://github.com/NomicFoundation/hardhat/issues/new) in our GitHub issue tracker. + +## Project Overview + +This example project includes: + +- A simple Hardhat configuration file. +- Foundry-compatible Solidity unit tests. +- TypeScript integration tests using `mocha` and ethers.js +- Examples demonstrating how to connect to different types of networks, including locally simulating OP mainnet. + +## Usage + +### Running Tests + +To run all the tests in the project, execute the following command: + +```shell +npx hardhat test +``` + +You can also selectively run the Solidity or `mocha` tests: + +```shell +npx hardhat test solidity +npx hardhat test mocha +``` + +### Make a deployment to Sepolia + +This project includes an example Ignition module to deploy the contract. You can deploy this module to a locally simulated chain or to Sepolia. + +To run the deployment to a local chain: + +```shell +npx hardhat ignition deploy ignition/modules/Counter.ts +``` + +To run the deployment to Sepolia, you need an account with funds to send the transaction. The provided Hardhat configuration includes a Configuration Variable called `SEPOLIA_PRIVATE_KEY`, which you can use to set the private key of the account you want to use. + +You can set the `SEPOLIA_PRIVATE_KEY` variable using the `hardhat-keystore` plugin or by setting it as an environment variable. + +To set the `SEPOLIA_PRIVATE_KEY` config variable using `hardhat-keystore`: + +```shell +npx hardhat keystore set SEPOLIA_PRIVATE_KEY +``` + +After setting the variable, you can run the deployment with the Sepolia network: + +```shell +npx hardhat ignition deploy --network sepolia ignition/modules/Counter.ts +``` diff --git a/assignments/escrow-agent/contracts/Counter.sol b/assignments/escrow-agent/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/escrow-agent/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/escrow-agent/contracts/Counter.t.sol b/assignments/escrow-agent/contracts/Counter.t.sol new file mode 100644 index 00000000..ac71d5b8 --- /dev/null +++ b/assignments/escrow-agent/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/escrow-agent/contracts/Escrow.sol b/assignments/escrow-agent/contracts/Escrow.sol new file mode 100644 index 00000000..c8dd44ed --- /dev/null +++ b/assignments/escrow-agent/contracts/Escrow.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + + +contract Escrow { + address public buyer; + address public seller; + uint public amount; + + enum Status { NotPaid, Paid, Done } + Status public status; + + event FundsReceived(address indexed buyer, uint amount); + event FundsRedeemed(address indexed seller, uint amount); + + constructor(address _buyer, address _seller) { + buyer = _buyer; + seller = _seller; + status = Status.NotPaid; + } + + // Party B (Buyer) pays the money into this specific contract + function pay() external payable { + require(msg.sender == buyer, "Only buyer can pay"); + require(status == Status.NotPaid, "Already paid"); + require(msg.value > 0, "Must send ETH"); + + amount = msg.value; + status = Status.Paid; + emit FundsReceived(msg.sender, msg.value); + } + + // Buyer confirms receipt and releases money to Seller + function redeem() external { + require(msg.sender == buyer, "Only buyer can redeem"); + require(status == Status.Paid, "Funds not deposited or already released"); + + status = Status.Done; + uint payout = amount; + amount = 0; // Reset amount for security + + (bool success, ) = payable(seller).call{value: payout}(""); + require(success, "Transfer to seller failed"); + + emit FundsRedeemed(seller, payout); + } +} + + // EscrowFactory + +contract EscrowFactory { + address[] public allEscrows; + + event EscrowCreated(address indexed escrowAddress, address indexed buyer, address indexed seller); + + // Creates a new Escrow contract + function createEscrow(address _seller) external returns (address) { + + Escrow newEscrow = new Escrow(msg.sender, _seller); + + address escrowAddress = address(newEscrow); + allEscrows.push(escrowAddress); + + emit EscrowCreated(escrowAddress, msg.sender, _seller); + return escrowAddress; + } + + // Helper function to get all escrows for the frontend + function getAllEscrows() external view returns (address[] memory) { + return allEscrows; + } +} \ No newline at end of file diff --git a/assignments/escrow-agent/hardhat.config.ts b/assignments/escrow-agent/hardhat.config.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/escrow-agent/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/escrow-agent/ignition/modules/Counter.ts b/assignments/escrow-agent/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/escrow-agent/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/escrow-agent/package.json b/assignments/escrow-agent/package.json new file mode 100644 index 00000000..528d273a --- /dev/null +++ b/assignments/escrow-agent/package.json @@ -0,0 +1,20 @@ +{ + "name": "escrow-agent", + "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" + } +} diff --git a/assignments/escrow-agent/scripts/send-op-tx.ts b/assignments/escrow-agent/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/escrow-agent/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/escrow-agent/test/Counter.ts b/assignments/escrow-agent/test/Counter.ts new file mode 100644 index 00000000..f8c38986 --- /dev/null +++ b/assignments/escrow-agent/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/escrow-agent/tsconfig.json b/assignments/escrow-agent/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/escrow-agent/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" + } +} diff --git a/assignments/freelance-escrow/.gitignore b/assignments/freelance-escrow/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/freelance-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/freelance-escrow/README.md b/assignments/freelance-escrow/README.md new file mode 100644 index 00000000..4743e82a --- /dev/null +++ b/assignments/freelance-escrow/README.md @@ -0,0 +1,57 @@ +# Sample Hardhat 3 Beta Project (`node:test` and `viem`) + +This project showcases a Hardhat 3 Beta project using the native Node.js test runner (`node:test`) and the `viem` library for Ethereum interactions. + +To learn more about the Hardhat 3 Beta, please visit the [Getting Started guide](https://hardhat.org/docs/getting-started#getting-started-with-hardhat-3). To share your feedback, join our [Hardhat 3 Beta](https://hardhat.org/hardhat3-beta-telegram-group) Telegram group or [open an issue](https://github.com/NomicFoundation/hardhat/issues/new) in our GitHub issue tracker. + +## Project Overview + +This example project includes: + +- A simple Hardhat configuration file. +- Foundry-compatible Solidity unit tests. +- TypeScript integration tests using [`node:test`](nodejs.org/api/test.html), the new Node.js native test runner, and [`viem`](https://viem.sh/). +- Examples demonstrating how to connect to different types of networks, including locally simulating OP mainnet. + +## Usage + +### Running Tests + +To run all the tests in the project, execute the following command: + +```shell +npx hardhat test +``` + +You can also selectively run the Solidity or `node:test` tests: + +```shell +npx hardhat test solidity +npx hardhat test nodejs +``` + +### Make a deployment to Sepolia + +This project includes an example Ignition module to deploy the contract. You can deploy this module to a locally simulated chain or to Sepolia. + +To run the deployment to a local chain: + +```shell +npx hardhat ignition deploy ignition/modules/Counter.ts +``` + +To run the deployment to Sepolia, you need an account with funds to send the transaction. The provided Hardhat configuration includes a Configuration Variable called `SEPOLIA_PRIVATE_KEY`, which you can use to set the private key of the account you want to use. + +You can set the `SEPOLIA_PRIVATE_KEY` variable using the `hardhat-keystore` plugin or by setting it as an environment variable. + +To set the `SEPOLIA_PRIVATE_KEY` config variable using `hardhat-keystore`: + +```shell +npx hardhat keystore set SEPOLIA_PRIVATE_KEY +``` + +After setting the variable, you can run the deployment with the Sepolia network: + +```shell +npx hardhat ignition deploy --network sepolia ignition/modules/Counter.ts +``` diff --git a/assignments/freelance-escrow/contracts/Counter.sol b/assignments/freelance-escrow/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/freelance-escrow/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/freelance-escrow/contracts/Counter.t.sol b/assignments/freelance-escrow/contracts/Counter.t.sol new file mode 100644 index 00000000..582fc074 --- /dev/null +++ b/assignments/freelance-escrow/contracts/Counter.t.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import {Counter} from "./Counter.sol"; +import {Test} from "forge-std/Test.sol"; + +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/freelance-escrow/contracts/escrow.sol b/assignments/freelance-escrow/contracts/escrow.sol new file mode 100644 index 00000000..39b1f328 --- /dev/null +++ b/assignments/freelance-escrow/contracts/escrow.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.26; + +contract MilestoneEscrow { + + enum JobStatus { + AwaitingPayment, + InProgress, + Completed + } + + JobStatus public status; + + struct Milestone { + string taskName; + uint payout; + bool isFinished; + } + + mapping(uint => Milestone) public milestones; + + mapping(address => uint) public totalEarnings; + + address public client; + address public freelancer; + uint public totalMilestones; + uint public currentMilestone; + + modifier onlyClient() { + require(msg.sender == client, "Not the client"); + _; + } + + constructor(address _freelancer, uint _numMilestones, uint _payPerMilestone) { + client = msg.sender; + freelancer = _freelancer; + totalMilestones = _numMilestones; + status = JobStatus.AwaitingPayment; + + } + + // Client funds the contract + function depositFunds() external payable onlyClient { + require(msg.value > 0, "Must fund the contract"); + status = JobStatus.InProgress; + } + + function releaseNextMilestone(string memory _taskName, uint _payout) external onlyClient { + require(status == JobStatus.InProgress, "Job not active"); + require(currentMilestone < totalMilestones, "All milestones finished"); + + + Milestone storage m = milestones[currentMilestone]; + m.taskName = _taskName; + m.payout = _payout; + m.isFinished = true; + + currentMilestone++; + + if (currentMilestone == totalMilestones) { + status = JobStatus.Completed; + } + + totalEarnings[freelancer] += _payout; + + // Send money + (bool success, ) = payable(freelancer).call{value: _payout}(""); + require(success, "Transfer failed"); + } +} \ No newline at end of file diff --git a/assignments/freelance-escrow/hardhat.config.ts b/assignments/freelance-escrow/hardhat.config.ts new file mode 100644 index 00000000..1acc094d --- /dev/null +++ b/assignments/freelance-escrow/hardhat.config.ts @@ -0,0 +1,38 @@ +import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem"; +import { configVariable, defineConfig } from "hardhat/config"; + +export default defineConfig({ + plugins: [hardhatToolboxViemPlugin], + 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/freelance-escrow/ignition/modules/Counter.ts b/assignments/freelance-escrow/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/freelance-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/freelance-escrow/package.json b/assignments/freelance-escrow/package.json new file mode 100644 index 00000000..dcc8b55a --- /dev/null +++ b/assignments/freelance-escrow/package.json @@ -0,0 +1,14 @@ +{ + "name": "freelance-escrow", + "version": "1.0.0", + "type": "module", + "devDependencies": { + "@nomicfoundation/hardhat-ignition": "^3.0.7", + "@nomicfoundation/hardhat-toolbox-viem": "^5.0.2", + "@types/node": "^22.19.8", + "forge-std": "github:foundry-rs/forge-std#v1.9.4", + "hardhat": "^3.1.6", + "typescript": "~5.8.0", + "viem": "^2.45.1" + } +} diff --git a/assignments/freelance-escrow/scripts/send-op-tx.ts b/assignments/freelance-escrow/scripts/send-op-tx.ts new file mode 100644 index 00000000..bb738c5c --- /dev/null +++ b/assignments/freelance-escrow/scripts/send-op-tx.ts @@ -0,0 +1,31 @@ +import { network } from "hardhat"; + +const { viem } = await network.connect({ + network: "hardhatOp", + chainType: "op", +}); + +console.log("Sending transaction using the OP chain type"); + +const publicClient = await viem.getPublicClient(); +const [senderClient] = await viem.getWalletClients(); + +console.log("Sending 1 wei from", senderClient.account.address, "to itself"); + +const l1Gas = await publicClient.estimateL1Gas({ + account: senderClient.account.address, + to: senderClient.account.address, + value: 1n, +}); + +console.log("Estimated L1 gas:", l1Gas); + +console.log("Sending L2 transaction"); +const tx = await senderClient.sendTransaction({ + to: senderClient.account.address, + value: 1n, +}); + +await publicClient.waitForTransactionReceipt({ hash: tx }); + +console.log("Transaction sent successfully"); diff --git a/assignments/freelance-escrow/test/Counter.ts b/assignments/freelance-escrow/test/Counter.ts new file mode 100644 index 00000000..50c2713d --- /dev/null +++ b/assignments/freelance-escrow/test/Counter.ts @@ -0,0 +1,46 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import { network } from "hardhat"; + +describe("Counter", async function () { + const { viem } = await network.connect(); + const publicClient = await viem.getPublicClient(); + + it("Should emit the Increment event when calling the inc() function", async function () { + const counter = await viem.deployContract("Counter"); + + await viem.assertions.emitWithArgs( + counter.write.inc(), + counter, + "Increment", + [1n], + ); + }); + + it("The sum of the Increment events should match the current value", async function () { + const counter = await viem.deployContract("Counter"); + const deploymentBlockNumber = await publicClient.getBlockNumber(); + + // run a series of increments + for (let i = 1n; i <= 10n; i++) { + await counter.write.incBy([i]); + } + + const events = await publicClient.getContractEvents({ + address: counter.address, + abi: counter.abi, + eventName: "Increment", + fromBlock: deploymentBlockNumber, + strict: true, + }); + + // check that the aggregated events match the current value + let total = 0n; + for (const event of events) { + total += event.args.by; + } + + assert.equal(total, await counter.read.x()); + }); +}); diff --git a/assignments/freelance-escrow/tsconfig.json b/assignments/freelance-escrow/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/freelance-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" + } +} diff --git a/assignments/todo/.gitignore b/assignments/todo/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/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/todo/README.md b/assignments/todo/README.md new file mode 100644 index 00000000..968246e9 --- /dev/null +++ b/assignments/todo/README.md @@ -0,0 +1,57 @@ +# Sample Hardhat 3 Beta Project (`mocha` and `ethers`) + +This project showcases a Hardhat 3 Beta project using `mocha` for tests and the `ethers` library for Ethereum interactions. + +To learn more about the Hardhat 3 Beta, please visit the [Getting Started guide](https://hardhat.org/docs/getting-started#getting-started-with-hardhat-3). To share your feedback, join our [Hardhat 3 Beta](https://hardhat.org/hardhat3-beta-telegram-group) Telegram group or [open an issue](https://github.com/NomicFoundation/hardhat/issues/new) in our GitHub issue tracker. + +## Project Overview + +This example project includes: + +- A simple Hardhat configuration file. +- Foundry-compatible Solidity unit tests. +- TypeScript integration tests using `mocha` and ethers.js +- Examples demonstrating how to connect to different types of networks, including locally simulating OP mainnet. + +## Usage + +### Running Tests + +To run all the tests in the project, execute the following command: + +```shell +npx hardhat test +``` + +You can also selectively run the Solidity or `mocha` tests: + +```shell +npx hardhat test solidity +npx hardhat test mocha +``` + +### Make a deployment to Sepolia + +This project includes an example Ignition module to deploy the contract. You can deploy this module to a locally simulated chain or to Sepolia. + +To run the deployment to a local chain: + +```shell +npx hardhat ignition deploy ignition/modules/Counter.ts +``` + +To run the deployment to Sepolia, you need an account with funds to send the transaction. The provided Hardhat configuration includes a Configuration Variable called `SEPOLIA_PRIVATE_KEY`, which you can use to set the private key of the account you want to use. + +You can set the `SEPOLIA_PRIVATE_KEY` variable using the `hardhat-keystore` plugin or by setting it as an environment variable. + +To set the `SEPOLIA_PRIVATE_KEY` config variable using `hardhat-keystore`: + +```shell +npx hardhat keystore set SEPOLIA_PRIVATE_KEY +``` + +After setting the variable, you can run the deployment with the Sepolia network: + +```shell +npx hardhat ignition deploy --network sepolia ignition/modules/Counter.ts +``` diff --git a/assignments/todo/contracts/Todo.sol b/assignments/todo/contracts/Todo.sol new file mode 100644 index 00000000..351e3a86 --- /dev/null +++ b/assignments/todo/contracts/Todo.sol @@ -0,0 +1,121 @@ +// 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) todos; + mapping(address => uint[]) userTodoIds; + + event TodoCreated(string text, uint deadline); + event TodoMarkedDone(uint id); + event TodoCancelled(uint id); + event TodoUpdated(uint id, string newText, uint newDeadline); + +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); + userTodoIds[msg.sender].push(todoCounter); + + emit TodoCreated(_text, _deadline); + return todoCounter; +} + +function markAsDone(uint _id) external { + 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; + } + + emit TodoMarkedDone(_id); +} + +function cancelTodo(uint _id) external { + 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"); + + todo.status = Status.Cancelled; + + emit TodoCancelled(_id); +} + +function updateTodo(uint _id, string memory _newText, uint _newDeadline) external { + 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"); + require(bytes(_newText).length > 0, "Empty text"); + require(_newDeadline > block.timestamp, "Invalid deadline"); + + todo.text = _newText; + todo.deadline = _newDeadline; + + emit TodoUpdated(_id, _newText, _newDeadline); +} + +function getTodo(uint _id) external view returns(TodoList memory) { + require((_id > 0) && (_id <= todoCounter), "Invalid id"); + return todos[_id]; +} + +function getMyTodos() external view returns(TodoList[] memory) { + uint[] storage ids = userTodoIds[msg.sender]; + TodoList[] memory result = new TodoList[](ids.length); + + for (uint i = 0; i < ids.length; i++) { + result[i] = todos[ids[i]]; + } + + return result; +} + +function getTodoCount() external view returns(uint) { + return todoCounter; +} + +function getUserTodoCount(address _user) external view returns(uint) { + return userTodoIds[_user].length; +} + +function checkDefaultedTodos(uint[] calldata _ids) external { + for (uint i = 0; i < _ids.length; i++) { + uint id = _ids[i]; + require((id > 0) && (id <= todoCounter), "Invalid id"); + TodoList storage todo = todos[id]; + + if (todo.status == Status.Pending && block.timestamp > todo.deadline) { + todo.status = Status.Defaulted; + } + } +} + +} \ No newline at end of file diff --git a/assignments/todo/hardhat.config.ts b/assignments/todo/hardhat.config.ts new file mode 100644 index 00000000..1b645c9b --- /dev/null +++ b/assignments/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/todo/ignition/deployments/chain-11155111/artifacts/Todo#Todo.json b/assignments/todo/ignition/deployments/chain-11155111/artifacts/Todo#Todo.json new file mode 100644 index 00000000..104857e8 --- /dev/null +++ b/assignments/todo/ignition/deployments/chain-11155111/artifacts/Todo#Todo.json @@ -0,0 +1,70 @@ +{ + "_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": "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": "_id", + "type": "uint256" + } + ], + "name": "markAsDone", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "bytecode": "0x6080604052348015600e575f5ffd5b506106568061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610034575f3560e01c8063d74de14d14610038578063ece28c6c1461004d575b5f5ffd5b61004b610046366004610372565b610072565b005b61006061005b36600461039d565b6101a4565b60405190815260200160405180910390f35b5f8111801561008257505f548111155b6100c05760405162461bcd60e51b815260206004820152600a6024820152691a5b9d985b1a59081a5960b21b60448201526064015b60405180910390fd5b5f8181526001602052604081209060038083015460ff16908111156100e7576100e7610452565b146101225760405162461bcd60e51b815260206004820152600b60248201526a4e6f742070656e64696e6760a81b60448201526064016100b7565b60018101546001600160a01b031633146101745760405162461bcd60e51b81526020600482015260136024820152723ab730baba3437b934bd32b21021b0b63632b960691b60448201526064016100b7565b8060040154421115610193576003908101805460ff1916909117905550565b600301805460ff1916600117905550565b5f5f8351116101e25760405162461bcd60e51b815260206004820152600a602482015269115b5c1d1e481d195e1d60b21b60448201526064016100b7565b6101ee4261025861047a565b821161022f5760405162461bcd60e51b815260206004820152601060248201526f496e76616c696420646561646c696e6560801b60448201526064016100b7565b3361026b5760405162461bcd60e51b815260206004820152600c60248201526b5a65726f206164647265737360a01b60448201526064016100b7565b5f805490806102798361048d565b90915550506040805160a0810182525f80548252336020830152918101859052906060820190815260209081018490525f805481526001808352604091829020845181559284015190830180546001600160a01b0319166001600160a01b0390921691909117905582015160028201906102f39082610529565b506060820151816003015f6101000a81548160ff0219169083600381111561031d5761031d610452565b0217905550608082015181600401559050507f022c3fffe570decf2328dbf4f2af598df5112da7e31568af8b927c48276ccae383836040516103609291906105e4565b60405180910390a1505f545b92915050565b5f60208284031215610382575f5ffd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156103ae575f5ffd5b823567ffffffffffffffff8111156103c4575f5ffd5b8301601f810185136103d4575f5ffd5b803567ffffffffffffffff8111156103ee576103ee610389565b604051601f8201601f19908116603f0116810167ffffffffffffffff8111828210171561041d5761041d610389565b604052818152828201602001871015610434575f5ffd5b816020840160208301375f6020928201830152969401359450505050565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b8082018082111561036c5761036c610466565b5f6001820161049e5761049e610466565b5060010190565b600181811c908216806104b957607f821691505b6020821081036104d757634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561052457805f5260205f20601f840160051c810160208510156105025750805b601f840160051c820191505b81811015610521575f815560010161050e565b50505b505050565b815167ffffffffffffffff81111561054357610543610389565b6105578161055184546104a5565b846104dd565b6020601f821160018114610589575f83156105725750848201515b5f19600385901b1c1916600184901b178455610521565b5f84815260208120601f198516915b828110156105b85787850151825560209485019460019092019101610598565b50848210156105d557868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b604081525f83518060408401528060208601606085015e5f606082850101526060601f19601f830116840101915050826020830152939250505056fea264697066735822122058a230f22781a7908f9b744117232535d23a25fa1216e8526855770e6f935e6364736f6c634300081c0033", + "deployedBytecode": "0x608060405234801561000f575f5ffd5b5060043610610034575f3560e01c8063d74de14d14610038578063ece28c6c1461004d575b5f5ffd5b61004b610046366004610372565b610072565b005b61006061005b36600461039d565b6101a4565b60405190815260200160405180910390f35b5f8111801561008257505f548111155b6100c05760405162461bcd60e51b815260206004820152600a6024820152691a5b9d985b1a59081a5960b21b60448201526064015b60405180910390fd5b5f8181526001602052604081209060038083015460ff16908111156100e7576100e7610452565b146101225760405162461bcd60e51b815260206004820152600b60248201526a4e6f742070656e64696e6760a81b60448201526064016100b7565b60018101546001600160a01b031633146101745760405162461bcd60e51b81526020600482015260136024820152723ab730baba3437b934bd32b21021b0b63632b960691b60448201526064016100b7565b8060040154421115610193576003908101805460ff1916909117905550565b600301805460ff1916600117905550565b5f5f8351116101e25760405162461bcd60e51b815260206004820152600a602482015269115b5c1d1e481d195e1d60b21b60448201526064016100b7565b6101ee4261025861047a565b821161022f5760405162461bcd60e51b815260206004820152601060248201526f496e76616c696420646561646c696e6560801b60448201526064016100b7565b3361026b5760405162461bcd60e51b815260206004820152600c60248201526b5a65726f206164647265737360a01b60448201526064016100b7565b5f805490806102798361048d565b90915550506040805160a0810182525f80548252336020830152918101859052906060820190815260209081018490525f805481526001808352604091829020845181559284015190830180546001600160a01b0319166001600160a01b0390921691909117905582015160028201906102f39082610529565b506060820151816003015f6101000a81548160ff0219169083600381111561031d5761031d610452565b0217905550608082015181600401559050507f022c3fffe570decf2328dbf4f2af598df5112da7e31568af8b927c48276ccae383836040516103609291906105e4565b60405180910390a1505f545b92915050565b5f60208284031215610382575f5ffd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156103ae575f5ffd5b823567ffffffffffffffff8111156103c4575f5ffd5b8301601f810185136103d4575f5ffd5b803567ffffffffffffffff8111156103ee576103ee610389565b604051601f8201601f19908116603f0116810167ffffffffffffffff8111828210171561041d5761041d610389565b604052818152828201602001871015610434575f5ffd5b816020840160208301375f6020928201830152969401359450505050565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b8082018082111561036c5761036c610466565b5f6001820161049e5761049e610466565b5060010190565b600181811c908216806104b957607f821691505b6020821081036104d757634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561052457805f5260205f20601f840160051c810160208510156105025750805b601f840160051c820191505b81811015610521575f815560010161050e565b50505b505050565b815167ffffffffffffffff81111561054357610543610389565b6105578161055184546104a5565b846104dd565b6020601f821160018114610589575f83156105725750848201515b5f19600385901b1c1916600184901b178455610521565b5f84815260208120601f198516915b828110156105b85787850151825560209485019460019092019101610598565b50848210156105d557868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b604081525f83518060408401528060208601606085015e5f606082850101526060601f19601f830116840101915050826020830152939250505056fea264697066735822122058a230f22781a7908f9b744117232535d23a25fa1216e8526855770e6f935e6364736f6c634300081c0033", + "linkReferences": {}, + "deployedLinkReferences": {}, + "immutableReferences": {}, + "inputSourceName": "project/contracts/Todo.sol", + "buildInfoId": "solc-0_8_28-f65f8ff8903a412c1854c9b0f7553b2537651194" +} \ No newline at end of file diff --git a/assignments/todo/ignition/deployments/chain-11155111/build-info/solc-0_8_28-f65f8ff8903a412c1854c9b0f7553b2537651194.json b/assignments/todo/ignition/deployments/chain-11155111/build-info/solc-0_8_28-f65f8ff8903a412c1854c9b0f7553b2537651194.json new file mode 100644 index 00000000..15d25ff5 --- /dev/null +++ b/assignments/todo/ignition/deployments/chain-11155111/build-info/solc-0_8_28-f65f8ff8903a412c1854c9b0f7553b2537651194.json @@ -0,0 +1,39 @@ +{ + "_format": "hh3-sol-build-info-1", + "id": "solc-0_8_28-f65f8ff8903a412c1854c9b0f7553b2537651194", + "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\n\n\ncontract Bank {\n\nuint public totalBalance;\nuint interest;\naddress owner;\n\nmapping(address => uint) public balances;\n\nmodifier onlyOwner{\n require(msg.sender == owner);\n _;\n}\n\n event depositSuccessful(string sent, uint amount);\n\nconstructor(uint _interest){\n interest = _interest; \n owner = msg.sender;\n}\n\nfunction re() public view returns(address){\n return address(this);\n}\n\nfunction deposit() external payable returns(bool){\n require(msg.value > 0, \"Cannot deposit Zero\");\n balances[msg.sender] += msg.value;\n\n emit depositSuccessful(\"Sent\", msg.value);\n return true;\n} \n\nfunction withdraw(uint _amount) external {\n require(balances[msg.sender] >= _amount, \"Insufficient funds\");\n require(address(this).balance >= _amount, \"Contract cannot pay\");\n\n balances[msg.sender]-= _amount;\n (bool success,) = msg.sender.call{value: _amount}(\" \");\n require(success, \"transcation failed\");\n\n \n}\n\nfunction drain(address recepient) public onlyOwner{\n uint amount = address(this).balance;\n\n (bool success,) = recepient.call{value: amount}(\" \");\n require(success, \"transcation failed\");\n}\n\n// receive() external payable {}\n\n\n}\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\n mapping(uint => TodoList) todos;\n\n event TodoCreated(string text, uint deadline);\n\n\n\nfunction createTodo(string memory _text, uint _deadline) 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(todoCounter, msg.sender, _text, Status.Pending, _deadline);\n\n emit TodoCreated(_text, _deadline);\n return todoCounter; \n}\n\nfunction markAsDone(uint _id) external {\n require((_id > 0) && (_id <= todoCounter) , 'invalid id');\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 }\n else{\n todo.status = Status.Done;\n }\n\n}\n\n}" + } + } + } +} \ No newline at end of file diff --git a/assignments/todo/ignition/deployments/chain-11155111/deployed_addresses.json b/assignments/todo/ignition/deployments/chain-11155111/deployed_addresses.json new file mode 100644 index 00000000..8edb6916 --- /dev/null +++ b/assignments/todo/ignition/deployments/chain-11155111/deployed_addresses.json @@ -0,0 +1,3 @@ +{ + "Todo#Todo": "0x50b86D16380e8046b127e2dDED3D31aeF51f82fE" +} \ No newline at end of file diff --git a/assignments/todo/ignition/deployments/chain-11155111/journal.jsonl b/assignments/todo/ignition/deployments/chain-11155111/journal.jsonl new file mode 100644 index 00000000..3d5666bd --- /dev/null +++ b/assignments/todo/ignition/deployments/chain-11155111/journal.jsonl @@ -0,0 +1,8 @@ + +{"chainId":11155111,"type":"DEPLOYMENT_INITIALIZE"} +{"artifactId":"Todo#Todo","constructorArgs":[],"contractName":"Todo","dependencies":[],"from":"0xa2f4cf70b1952b4a8bfd24cb90d3633b6cf57c36","futureId":"Todo#Todo","futureType":"NAMED_ARTIFACT_CONTRACT_DEPLOYMENT","libraries":{},"strategy":"basic","strategyConfig":{},"type":"DEPLOYMENT_EXECUTION_STATE_INITIALIZE","value":{"_kind":"bigint","value":"0"}} +{"futureId":"Todo#Todo","networkInteraction":{"data":"0x6080604052348015600e575f5ffd5b506106568061001c5f395ff3fe608060405234801561000f575f5ffd5b5060043610610034575f3560e01c8063d74de14d14610038578063ece28c6c1461004d575b5f5ffd5b61004b610046366004610372565b610072565b005b61006061005b36600461039d565b6101a4565b60405190815260200160405180910390f35b5f8111801561008257505f548111155b6100c05760405162461bcd60e51b815260206004820152600a6024820152691a5b9d985b1a59081a5960b21b60448201526064015b60405180910390fd5b5f8181526001602052604081209060038083015460ff16908111156100e7576100e7610452565b146101225760405162461bcd60e51b815260206004820152600b60248201526a4e6f742070656e64696e6760a81b60448201526064016100b7565b60018101546001600160a01b031633146101745760405162461bcd60e51b81526020600482015260136024820152723ab730baba3437b934bd32b21021b0b63632b960691b60448201526064016100b7565b8060040154421115610193576003908101805460ff1916909117905550565b600301805460ff1916600117905550565b5f5f8351116101e25760405162461bcd60e51b815260206004820152600a602482015269115b5c1d1e481d195e1d60b21b60448201526064016100b7565b6101ee4261025861047a565b821161022f5760405162461bcd60e51b815260206004820152601060248201526f496e76616c696420646561646c696e6560801b60448201526064016100b7565b3361026b5760405162461bcd60e51b815260206004820152600c60248201526b5a65726f206164647265737360a01b60448201526064016100b7565b5f805490806102798361048d565b90915550506040805160a0810182525f80548252336020830152918101859052906060820190815260209081018490525f805481526001808352604091829020845181559284015190830180546001600160a01b0319166001600160a01b0390921691909117905582015160028201906102f39082610529565b506060820151816003015f6101000a81548160ff0219169083600381111561031d5761031d610452565b0217905550608082015181600401559050507f022c3fffe570decf2328dbf4f2af598df5112da7e31568af8b927c48276ccae383836040516103609291906105e4565b60405180910390a1505f545b92915050565b5f60208284031215610382575f5ffd5b5035919050565b634e487b7160e01b5f52604160045260245ffd5b5f5f604083850312156103ae575f5ffd5b823567ffffffffffffffff8111156103c4575f5ffd5b8301601f810185136103d4575f5ffd5b803567ffffffffffffffff8111156103ee576103ee610389565b604051601f8201601f19908116603f0116810167ffffffffffffffff8111828210171561041d5761041d610389565b604052818152828201602001871015610434575f5ffd5b816020840160208301375f6020928201830152969401359450505050565b634e487b7160e01b5f52602160045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b8082018082111561036c5761036c610466565b5f6001820161049e5761049e610466565b5060010190565b600181811c908216806104b957607f821691505b6020821081036104d757634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561052457805f5260205f20601f840160051c810160208510156105025750805b601f840160051c820191505b81811015610521575f815560010161050e565b50505b505050565b815167ffffffffffffffff81111561054357610543610389565b6105578161055184546104a5565b846104dd565b6020601f821160018114610589575f83156105725750848201515b5f19600385901b1c1916600184901b178455610521565b5f84815260208120601f198516915b828110156105b85787850151825560209485019460019092019101610598565b50848210156105d557868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b604081525f83518060408401528060208601606085015e5f606082850101526060601f19601f830116840101915050826020830152939250505056fea264697066735822122058a230f22781a7908f9b744117232535d23a25fa1216e8526855770e6f935e6364736f6c634300081c0033","id":1,"type":"ONCHAIN_INTERACTION","value":{"_kind":"bigint","value":"0"}},"type":"NETWORK_INTERACTION_REQUEST"} +{"futureId":"Todo#Todo","networkInteractionId":1,"nonce":1,"type":"TRANSACTION_PREPARE_SEND"} +{"futureId":"Todo#Todo","networkInteractionId":1,"nonce":1,"transaction":{"fees":{"maxFeePerGas":{"_kind":"bigint","value":"2168509975"},"maxPriorityFeePerGas":{"_kind":"bigint","value":"1439189"}},"hash":"0x5add6193450acf5861e9b001123d090e15b3492cbaefead884cef5bfa481fcc0"},"type":"TRANSACTION_SEND"} +{"futureId":"Todo#Todo","hash":"0x5add6193450acf5861e9b001123d090e15b3492cbaefead884cef5bfa481fcc0","networkInteractionId":1,"receipt":{"blockHash":"0x1c8fd973dae86a801afe12f5781d3a2741debac3f811f246bf553b6e3f8d0c55","blockNumber":10237828,"contractAddress":"0x50b86D16380e8046b127e2dDED3D31aeF51f82fE","logs":[],"status":"SUCCESS"},"type":"TRANSACTION_CONFIRM"} +{"futureId":"Todo#Todo","result":{"address":"0x50b86D16380e8046b127e2dDED3D31aeF51f82fE","type":"SUCCESS"},"type":"DEPLOYMENT_EXECUTION_STATE_COMPLETE"} \ No newline at end of file diff --git a/assignments/todo/ignition/modules/Todo.ts b/assignments/todo/ignition/modules/Todo.ts new file mode 100644 index 00000000..29efcd8c --- /dev/null +++ b/assignments/todo/ignition/modules/Todo.ts @@ -0,0 +1,9 @@ +import {buildModule} from "@nomicfoundation/hardhat-ignition/modules"; + +export default buildModule("Todo", (m) => { + const todo = m.contract("Todo"); + + return { + todo, + }; +}) \ No newline at end of file diff --git a/assignments/todo/package.json b/assignments/todo/package.json new file mode 100644 index 00000000..508a8d12 --- /dev/null +++ b/assignments/todo/package.json @@ -0,0 +1,23 @@ +{ + "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": { + "dotenv": "^17.2.4" + } +} diff --git a/assignments/todo/scripts/send-op-tx.ts b/assignments/todo/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/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/todo/test/Counter.ts b/assignments/todo/test/Counter.ts new file mode 100644 index 00000000..f8c38986 --- /dev/null +++ b/assignments/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/todo/tsconfig.json b/assignments/todo/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/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" + } +}