diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cbd55a65..0e55c302 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ test: add coverage for revert conditions 4. Pull Request Checklist - [x] Code compiles - [x] Tests pass -- [x] README included +- [x] README - [x] No changes outside /submissions 5. PR Review Process diff --git a/assignments/6-wallet-address/README.md b/assignments/6-wallet-address/README.md index 0d3a2654..f2181247 100644 --- a/assignments/6-wallet-address/README.md +++ b/assignments/6-wallet-address/README.md @@ -1,219 +1,190 @@ -# Custom Mnemonic to Ethereum Address Generator +# Quantum-computer Wallet Alogrithm and Improvement Analysis +Blockchain wallets are fundamental tools for managing digital assets. Their security relies heavily on cryptographic primitives such as hash functions and public-key cryptography. Current wallet systems, including those used by major blockchains like Bitcoin and Ethereum, are designed under the assumption that adversaries use classical computers. -This project demonstrates how a cryptographic wallet can be built from scratch using basic primitives. -It starts from secure randomness, turns that randomness into human readable words, hashes those words into a seed, derives a private key, generates a public key using secp256k1, and finally produces an Ethereum address. +However, the emergence of quantum computing introduces new attack models that significantly weaken or completely break many classical cryptographic systems. Algorithms such as Shor’s algorithm and Grover’s algorithm fundamentally change the security assumptions behind elliptic-curve cryptography and hash functions. +## Wallet Generation Algorithm +### Mnemonic Code Word Generation -This is an educational implementation meant to explain the full flow in simple human language. +The random entropy generated by the CSPRNG is converted into a set of mnemonic code words. These words serve two purposes: +They allow human-readable backup and recovery -## What This Project Does +They preserve the underlying entropy in a structured form -This script generates an Ethereum address by following a clear logical chain. +### Concatenation of Mnemonic Words -1. Random numbers -2. Mnemonic words -3. Mnemonic phrase -4. Seed -5. Private key -6. Public key -7. Ethereum address +All mnemonic words are concatenated into a single string. This creates a deterministic input for hashing while ensuring that the entire mnemonic contributes to the final seed. +HA Hashing of the Mnemonic -Every step is visible, logged, and easy to follow. +The concatenated mnemonic string is hashed using the Secure Hash Algorithm (SHA). This step compresses the input into a fixed-length output and ensures diffusion, meaning small changes in the input produce large, unpredictable changes in the output. +### Seed Consolidation -## Dictionary and Words +The output of the SHA hash is treated as the cryptographic seed. At this stage, all previous steps are combined into a single value that represents the wallet’s core secret material. -A local dictionary.json file is used as the word source. +### Keccak-256 Hashing -The dictionary values are converted into an array of words. Each random number is mapped to one word using modulo arithmetic so it always fits inside the dictionary length. +The seed is hashed again using Keccak-256. Keccak is the hash function used by Ethereum and is designed to be resistant to known cryptanalytic attacks. This second hashing step further randomizes the seed and reduces structural patterns. -This is similar in spirit to BIP-39 but simplified for learning purposes. +### Private Key Generation -## Step by Step Process +The output of the Keccak-256 hash is interpreted as a 256-bit private key. This private key is the most sensitive component of the wallet, as possession of it grants full control over the associated funds. -### 1. Secure Random Number Generation +### Public Key Derivation Using secp256k1 -The process starts with a cryptographically secure random number generator. +Using elliptic-curve cryptography (ECC) on the secp256k1 curve, the private key is transformed into a public key. This operation is computationally easy in one direction but extremely difficult to reverse using classical computers. +### Public Key Hashing -Node.js crypto.randomBytes is used to generate random bytes. -Each byte is a number between 0 and 255. +The public key is hashed using Keccak-256. This step hides the raw public key and adds an additional layer of protection against certain attacks. -These numbers are the root of all security in this system. +### Address Generation -If randomness is weak, everything after it is weak. +The final wallet address is produced by taking the last 20 bytes of the Keccak-256 hash of the public key and prefixing it with 0x. This address is what users share publicly to receive funds. ---- +# Role of BIP-32 Hierarchical Deterministic Wallets -### 2. Converting Random Numbers to Words +BIP-32 allows multiple private keys to be deterministically derived from a single master seed. This design enables: -Each random number is converted into a word. +Easy backup and recovery using one seed -The number is reduced using modulo dictionary length so it always maps to a valid word index. +Generation of many addresses without storing each private key -This creates a list of human readable words. +Structured wallet organization using derivation paths ---- +Despite these benefits, BIP-32 relies on elliptic-curve cryptography and therefore inherits its quantum vulnerabilities. -### 3. Creating the Mnemonic Phrase -All generated words are concatenated into a single phrase separated by spaces. +# Quantum Threat Model and Vulnerability Analysis -This phrase is the mnemonic phrase. +### Grover’s Algorithm and Hash Functions -Example +Grover’s algorithm provides a quadratic speedup for brute-force search. This effectively halves the security of hash functions. For example: -word1 word2 word3 word4 ... +SHA-256 provides ~128-bit security against a quantum attacker -At this stage it is just text and has no cryptographic power yet. +Keccak-256 provides ~128-bit security against a quantum attacker ---- +While these levels are still secure today, they are insufficient for long-term, future-proof systems. -### 4. Creating the Seed Using SHA-256 +### Shor’s Algorithm and Elliptic-Curve Cryptography -The mnemonic phrase is hashed using SHA-256. +Shor’s algorithm can efficiently solve the elliptic-curve discrete logarithm problem. This means that any system relying on secp256k1 becomes completely insecure once a sufficiently powerful quantum computer exists. An attacker could derive the private key directly from the public key. -The output is a 256-bit value represented as 64 hex characters. +### Public Key Exposure Risk -This hash is treated as the seed. +In many blockchain systems, public keys are revealed during transaction signing. This creates a direct attack surface for quantum adversaries. -The seed represents all previous steps combined. +# proposed Improvements +1. #### Stronger Hash Functions (SHA-512 and Keccak-512) ---- +###### What is changed: -### 5. Creating the Private Key Using Keccak-256 +* SHA-256 is replaced with SHA-512 -The seed is hashed again using Keccak-256. +* Keccak-256 is replaced with Keccak-512 -This produces another 256-bit value. +##### Why it is chosen: -This value is treated as the Ethereum private key. +* Minimal performance overhead -At this point you must treat it as extremely sensitive data. +* Well-studied and widely trusted -Anyone with this key owns the funds. +* Easy to integrate into existing systems + + 2. #### Increased Entropy Size ---- +###### What is changed: -### 6. Generating the Public Key Using secp256k1 +* Entropy size is increased from 128 bits to 256 or 512 bits -The private key is passed into the secp256k1 elliptic curve. +* Why it is important: Higher entropy significantly increases the difficulty of brute-force attacks, even with quantum acceleration. -This produces a public key. +##### Why it is chosen: -The public key is uncompressed and starts with 0x04. +* Directly strengthens the foundation of the wallet -It contains both X and Y coordinates of the elliptic curve point. +* Simple change with large security benefits. ---- +3. #### Memory-Hard Key Derivation Functions (Argon2id) -### 7. Hashing the Public Key +##### What is changed: -The 0x04 prefix is removed from the public key. +* Simple hashing is replaced with Argon2id -The remaining bytes are hashed using Keccak-256. +##### Why it is chosen: -This produces a 32 byte hash. +* Winner of the Password Hashing Competition ---- +* Strong resistance to GPU, ASIC, and quantum attacks -### 8. Creating the Ethereum Address +4. #### Replacement of secp256k1 with Post-Quantum Signatures -The last 20 bytes of the public key hash are taken. +##### What is changed: -That is 40 hexadecimal characters. +* secp256k1 is replaced with post-quantum digital signature schemes such as Dilithium or SPHINCS+ -0x is prepended to the result. +* Why it is important: Elliptic-curve cryptography is completely broken by Shor’s algorithm, making this the most critical improvement. -This final value is the Ethereum address. +##### Why it is chosen: ---- +* Dilithium is a NIST-standardized algorithm -## Final Output Summary +* SPHINCS+ relies only on hash functions and offers strong long-term security. -Mnemonic phrase -Seed generated using SHA-256 -Private key generated using Keccak-256 -Public key generated using secp256k1 -Ethereum address derived from public key hash +5. #### Post-Quantum Hierarchical Deterministic Wallets -Each step depends fully on the previous one. +##### What is changed: ---- +* BIP-32 is replaced with a Merkle-tree–based HD wallet -## Important Notes +* Why it is important: This removes reliance on elliptic curves while preserving deterministic key derivation and wallet scalability. -This implementation is for learning and experimentation. +###### Why it is chosen: -It does not fully follow official wallet standards. +* Hash-based structures are well understood -Do not use this code to store real funds. +* Naturally compatible with post-quantum signatures ---- +6. #### Hash-Based Address Design -## How Real Wallets Improve This Process +##### What is changed: -Real wallets such as MetaMask or hardware wallets add extra security layers. +* Addresses are derived solely from hashed public keys -Below are the key improvements. +* Why it is important: This minimizes public key exposure, reducing the attack surface for quantum adversaries. ---- +##### Why it is chosen: -## BIP-39 Mnemonic Standard Improvement +* Simple -Instead of directly hashing the mnemonic phrase, real wallets do the following: +* Backward-compatible in concept. -The mnemonic words come from a fixed 2048 word list. +* Enhances privacy and security. -A checksum is embedded inside the mnemonic. +7. #### Hybrid Cryptography (Transition Strategy) -This ensures typing errors can be detected. +##### What is changed: ---- +* Classical and post-quantum signatures are used together -## PBKDF2 with 2048 Rounds +* Why it is important: This allows systems to remain secure during the transition period before quantum computers become widespread. -After generating the mnemonic phrase, real wallets do not hash it only once. +##### Why it is chosen: -They use PBKDF2 with HMAC-SHA512. +* Practical -The mnemonic phrase is used as the password. +* Already under research by blockchain communities -An optional passphrase is added as salt. +# Proposed Post-Quantum Wallet Architecture -The hashing process is repeated 2048 times. +1. High-entropy or quantum-based random number generation -This makes brute force attacks much slower. +2. 512-bit entropy input -This step dramatically improves security. +3. SHA-512 combined with Argon2id ---- +4. Master seed generation -## Hierarchical Deterministic Wallets +5. Merkle-tree–based HD derivation -Instead of generating one key, real wallets generate a master seed. - -From that seed, unlimited private keys can be derived. - -This allows one mnemonic to control many addresses. - -This is defined in BIP-32 and BIP-44. - ---- - -## Why This Project Still Matters - -This code shows what is really happening under the hood. It shows that wallets are built from simple cryptographic steps. - -Understanding this flow makes you a better blockchain developer. - ---- - -## Final Warning - -Never expose private keys or seeds. - -Never log them in production. - -Never store them in plain text. - -This project is for educational purposes only. +6. Post-quantum digital signature scheme \ No newline at end of file diff --git a/assignments/6-wallet-address/dictionary.json b/assignments/6-wallet-address/dictionary.json deleted file mode 100644 index a9bd31d7..00000000 --- a/assignments/6-wallet-address/dictionary.json +++ /dev/null @@ -1,258 +0,0 @@ -{ - "0": "apple", - "1": "bravo", - "2": "cactus", - "3": "delta", - "4": "ember", - "5": "falcon", - "6": "galaxy", - "7": "harbor", - "8": "island", - "9": "jungle", - "10": "kernel", - "11": "lunar", - "12": "meteor", - "13": "nebula", - "14": "oasis", - "15": "planet", - "16": "quantum", - "17": "rocket", - "18": "saturn", - "19": "tiger", - "20": "umbrella", - "21": "vector", - "22": "wander", - "23": "xenon", - "24": "yonder", - "25": "zephyr", - "26": "anchor", - "27": "beacon", - "28": "comet", - "29": "dragon", - "30": "echo", - "31": "forest", - "32": "glacier", - "33": "horizon", - "34": "ignite", - "35": "javelin", - "36": "keystone", - "37": "labyrinth", - "38": "magnet", - "39": "navigator", - "40": "oracle", - "41": "phoenix", - "42": "quartz", - "43": "ripple", - "44": "summit", - "45": "thunder", - "46": "utopia", - "47": "vortex", - "48": "willow", - "49": "xplorer", - "50": "yield", - "51": "zenith", - "52": "atlas", - "53": "binary", - "54": "cipher", - "55": "drift", - "56": "emberly", - "57": "fracture", - "58": "gravity", - "59": "helix", - "60": "ion", - "61": "jigsaw", - "62": "krypton", - "63": "lighthouse", - "64": "monsoon", - "65": "neutron", - "66": "orbit", - "67": "pulse", - "68": "quiver", - "69": "resonance", - "70": "signal", - "71": "terminal", - "72": "uplink", - "73": "velocity", - "74": "waveform", - "75": "xylophone", - "76": "yearling", - "77": "zodiac", - "78": "apex", - "79": "buffer", - "80": "cluster", - "81": "daemon", - "82": "entropy", - "83": "firefly", - "84": "grid", - "85": "hash", - "86": "inertia", - "87": "junction", - "88": "kilobyte", - "89": "latency", - "90": "matrix", - "91": "node", - "92": "opcode", - "93": "payload", - "94": "queue", - "95": "runtime", - "96": "sandbox", - "97": "thread", - "98": "uplift", - "99": "variable", - "100": "webhook", - "101": "xencode", - "102": "yielding", - "103": "zbuffer", - "104": "alpha", - "105": "beta", - "106": "gamma", - "107": "epsilon", - "108": "lambda", - "109": "sigma", - "110": "omega", - "111": "theta", - "112": "deltaflow", - "113": "bitrate", - "114": "checksum", - "115": "datagram", - "116": "endpoint", - "117": "firewall", - "118": "gateway", - "119": "handshake", - "120": "index", - "121": "json", - "122": "keypair", - "123": "ledger", - "124": "middleware", - "125": "namespace", - "126": "overflow", - "127": "protocol", - "128": "query", - "129": "response", - "130": "state", - "131": "token", - "132": "uptime", - "133": "validator", - "134": "worker", - "135": "xml", - "136": "yaml", - "137": "zero", - "138": "aurora", - "139": "blizzard", - "140": "cyclone", - "141": "drought", - "142": "earthquake", - "143": "flood", - "144": "gust", - "145": "hail", - "146": "iceberg", - "147": "jetstream", - "148": "kilowatt", - "149": "lightning", - "150": "monolith", - "151": "nightfall", - "152": "outbreak", - "153": "pressure", - "154": "quarantine", - "155": "rainfall", - "156": "seismic", - "157": "tempest", - "158": "updraft", - "159": "volcano", - "160": "whirlwind", - "161": "xray", - "162": "yearstorm", - "163": "zenwind", - "164": "acorn", - "165": "boulder", - "166": "canopy", - "167": "dandelion", - "168": "evergreen", - "169": "fern", - "170": "grove", - "171": "hollow", - "172": "ivy", - "173": "juniper", - "174": "kelp", - "175": "lichen", - "176": "meadow", - "177": "nutmeg", - "178": "oak", - "179": "pine", - "180": "quince", - "181": "root", - "182": "sprout", - "183": "thicket", - "184": "underbrush", - "185": "vine", - "186": "willowtree", - "187": "xerophyte", - "188": "yucca", - "189": "zinnia", - "190": "arch", - "191": "bridge", - "192": "column", - "193": "dome", - "194": "embankment", - "195": "foundation", - "196": "girder", - "197": "hinge", - "198": "insulation", - "199": "joist", - "200": "keel", - "201": "lintel", - "202": "mortar", - "203": "nail", - "204": "overlay", - "205": "pillar", - "206": "quarry", - "207": "rebar", - "208": "scaffold", - "209": "truss", - "210": "undercut", - "211": "vault", - "212": "weld", - "213": "xbrace", - "214": "yardarm", - "215": "zigzag", - "216": "amber", - "217": "bronze", - "218": "copper", - "219": "diamond", - "220": "emerald", - "221": "feldspar", - "222": "gold", - "223": "hematite", - "224": "iron", - "225": "jade", - "226": "kaolin", - "227": "limestone", - "228": "marble", - "229": "nickel", - "230": "obsidian", - "231": "platinum", - "232": "quartzite", - "233": "ruby", - "234": "silver", - "235": "topaz", - "236": "uranium", - "237": "vanadium", - "238": "wolfram", - "239": "xenolith", - "240": "yellowstone", - "241": "zircon", - "242": "alphaone", - "243": "betatwo", - "244": "gammathree", - "245": "deltafour", - "246": "epsilonfive", - "247": "zetasix", - "248": "etaseven", - "249": "thetaeight", - "250": "iotanine", - "251": "kappaten", - "252": "lambdatwelve", - "253": "mutathirteen", - "254": "nusixteen", - "255": "omegafinal" -} diff --git a/assignments/6-wallet-address/package-lock.json b/assignments/6-wallet-address/package-lock.json deleted file mode 100644 index 9cbca588..00000000 --- a/assignments/6-wallet-address/package-lock.json +++ /dev/null @@ -1,328 +0,0 @@ -{ - "name": "6-wallet-address", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "6-wallet-address", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "ethers": "^6.16.0" - }, - "devDependencies": { - "ts-node": "^10.9.2" - } - }, - "node_modules/@adraffy/ens-normalize": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", - "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", - "license": "MIT" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@noble/curves": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", - "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.3.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", - "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", - "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.7.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", - "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~6.19.2" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aes-js": { - "version": "4.0.0-beta.5", - "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", - "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", - "license": "MIT" - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/diff": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", - "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/ethers": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.16.0.tgz", - "integrity": "sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/ethers-io/" - }, - { - "type": "individual", - "url": "https://www.buymeacoffee.com/ricmoo" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "1.10.1", - "@noble/curves": "1.2.0", - "@noble/hashes": "1.3.2", - "@types/node": "22.7.5", - "aes-js": "4.0.0-beta.5", - "tslib": "2.7.0", - "ws": "8.17.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "license": "0BSD" - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "license": "MIT" - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - } - } -} diff --git a/assignments/6-wallet-address/package.json b/assignments/6-wallet-address/package.json index 8570b2d5..996156f5 100644 --- a/assignments/6-wallet-address/package.json +++ b/assignments/6-wallet-address/package.json @@ -1,19 +1,28 @@ { - "name": "6-wallet-address", + "name": "deborahcohort-8", "version": "1.0.0", - "description": "", + "description": "Blockchain Development Bootcamp — Cohort 8", "main": "index.js", "scripts": { - "wallet-address": "ts-node wallet-address.ts" + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/deborahamoni0-prog/deborahcohort-8.git" }, "keywords": [], "author": "", "license": "ISC", - "type": "module", - "dependencies": { - "ethers": "^6.16.0" + "bugs": { + "url": "https://github.com/deborahamoni0-prog/deborahcohort-8/issues" }, - "devDependencies": { - "ts-node": "^10.9.2" + "homepage": "https://github.com/deborahamoni0-prog/deborahcohort-8#readme", + "dependencies": { + "bip32": "^5.0.0", + "bip39": "^3.1.0", + "ethereumjs-util": "^7.1.5", + "hdkey": "^2.1.0", + "keccak": "^3.0.4", + "tiny-secp256k1": "^2.2.4" } } diff --git a/assignments/6-wallet-address/script.js b/assignments/6-wallet-address/script.js new file mode 100644 index 00000000..43d41c74 --- /dev/null +++ b/assignments/6-wallet-address/script.js @@ -0,0 +1,41 @@ +const bip39 = require("bip39"); +const { BIP32Factory } = require("bip32"); +const tinysecp = require("tiny-secp256k1"); +const bip32 = BIP32Factory(tinysecp); +const secp256k1 = require("secp256k1"); +const { keccak256 } = require("ethereumjs-util"); +const crypto = require("crypto"); + +const entropy = crypto.randomBytes(32); + + +const mnemonic = bip39.entropyToMnemonic(entropy); +console.log("Mnemonic Phrase:\n", mnemonic); + +const seed = bip39.mnemonicToSeedSync(mnemonic); + +const root = bip32.fromSeed(seed); + +const path = "m/44'/60'/0'/0/0"; +const child = root.derivePath(path);const ADDRESS_COUNT = 4; + +for (let i = 0; i < ADDRESS_COUNT; i++) { + const path = `m/44'/60'/0'/0/${i}`; + const child = root.derivePath(path); + + const privateKey = child.privateKey; + // console.log("\nPrivate Key:\n", Buffer.from(privateKey).toString("hex")); + + const publicKeyArray = secp256k1.publicKeyCreate(privateKey, false).slice(1); + const publicKey = Buffer.from(publicKeyArray); + // console.log("\nPublic Key (64 bytes):\n", publicKey.toString("hex")); + + const hash = keccak256(publicKey); + + const address = "0x" + hash.slice(-20).toString("hex"); + // console.log("\nEthereum Address:\n", address); + console.log(`\nAddress #${i}`); + console.log("\nPrivate Key:\n", Buffer.from(privateKey).toString("hex")); + console.log("\nPublic Key (64 bytes):\n", publicKey.toString("hex")); + console.log("\nEthereum Address:\n", address); +} \ No newline at end of file diff --git a/assignments/6-wallet-address/tsconfig.json b/assignments/6-wallet-address/tsconfig.json deleted file mode 100644 index 6d203e3b..00000000 --- a/assignments/6-wallet-address/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "esnext", - "moduleResolution": "node", - "types": ["node"], - "strict": true, - "esModuleInterop": true - } -} diff --git a/assignments/6-wallet-address/wallet-address.ts b/assignments/6-wallet-address/wallet-address.ts deleted file mode 100644 index 08f09656..00000000 --- a/assignments/6-wallet-address/wallet-address.ts +++ /dev/null @@ -1,126 +0,0 @@ - -import fs from 'node:fs'; -import { randomBytes, createHash } from 'node:crypto'; -import { keccak256, SigningKey } from 'ethers'; - -// Read dictionary -const rawDictionaryData = fs.readFileSync('./dictionary.json', 'utf-8'); -const wordDictionary: Record = JSON.parse(rawDictionaryData); - -// Convert dictionary values to array -const wordList: string[] = Object.values(wordDictionary); - -// wordCount -const dictionaryLength: number = wordList.length; - -const mnemonicCount: number = 12; - -function generateRandomNumbers(count: number): number[] { - console.log('Random bytes are: ', count); - return Array.from(randomBytes(count)); -} - -function numberToWord(n: number): string { - const index = n % dictionaryLength; - return wordList[index]; -} - -// 1. Generate secure numbers -const numberArray: number[] = generateRandomNumbers(mnemonicCount); - -// 2. Convert numbers to words -const wordArray: string[] = numberArray.map(numberToWord); - -// 3. Concatenate into final mnemonic phrase -function generateCodeWords(): string { - return wordArray.join(' '); -} - -console.log(' '); -const mnemonicPhrase = generateCodeWords(); -console.log(`My mnemonic phrase is: ${mnemonicPhrase}`); - -console.log(' '); - -// SHA256 and Keccak256 hashing functions -function sha256Hash(input: string): string { - const hash = createHash('sha256').update(input, 'utf8').digest('hex'); - - console.log('SHA-256 seed:', hash); - console.log('Seed length (hex):', hash.length); // 64 - return hash; -} - -// function keccak256Hash(hexInput: string): string { -// const bytes = arrayify('0x' + hexInput.replace(/^0x/, '')); -// const hash = keccak256(bytes); - -// console.log('Keccak-256 hash:', hash); -// console.log('Hash length:', hash.length); // 66 (0x + 64) -// return hash; -// } - -function keccak256Hash(hexInput: string): string { - // Ensure 0x-prefixed hex so ethers treats it as BYTES - const normalized = hexInput.startsWith('0x') ? hexInput : '0x' + hexInput; - - const hash = keccak256(normalized); - - console.log('Keccak-256 hash:', hash); - console.log('Hash length:', hash.length); // 66 - return hash; -} - -// 4. Seed: Hash the concatenated words (mnemonic phrase) -const seed = sha256Hash(mnemonicPhrase); -console.log(' '); - -// 5. Private Key: Hashing the seed using Keccak-256 algorithm -const privateKey = keccak256Hash(seed); -console.log(' '); - -// 6. Generate public key from private key (secp256k1) -// in `ethers`, `SigningKey` is a thin wrapper around *secp256k1* elliptic curve -const signingKey = new SigningKey(privateKey); -const publicKey = signingKey.publicKey; - -console.log('Public Key:', publicKey); -console.log('Public Key length:', publicKey.length); // 132 -console.log(' '); - -// 7. Remove uncompressed prefix (0x04) and hash as BYTES -// const publicKeyWithoutPrefix = '0x' + publicKey.slice(4); -// const publicKeyHash = keccak256(publicKeyWithoutPrefix); -const publicKeyHash = keccak256(publicKey); -console.log('Keccak-256(Public Key) hash:', publicKeyHash); -console.log('The length of the Public Key hash:', publicKeyHash.length); -console.log(' '); - -// Step 10: Take last 20 bytes (40 hex chars) -const slicedPublicKeyHash = publicKeyHash.slice(-40); -console.log("Sliced public key hash is: ", slicedPublicKeyHash); -console.log(' '); - -// const address = '0x' + publicKeyHash.slice(-40); -const address = "0x" + slicedPublicKeyHash; -console.log('Ethereum Address:', address); -console.log('Address length is: ', address.length); -console.log(' '); - - - -// 1. Start with a cryptographically secure pseudo-random number generator -// 2. Generate a set of code words from the random output -// 3. Concatenate all the code words into a single word or phrase -// 4. Hash the concatenated words using SHA to form the mnemonic seed -// 5. Treat the seed as the hash result of all previous steps combined -// 6. Hash the seed again using Keccak-256 -// 7. The Keccak-256 output is the final 256-bit private key - -// 8. Use *secp256k1* to hash your private key to get a public key -// 9. Use Keccak-256 to hash the public key -// 10. Slice the first 20 index of the output of the result of the keccak-256 and pre-pend 0x to its result, you get your *address* - - - - diff --git a/assignments/7-Todo/Todos/.gitignore b/assignments/7-Todo/Todos/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/7-Todo/Todos/.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/7-Todo/Todos/README.md b/assignments/7-Todo/Todos/README.md new file mode 100644 index 00000000..968246e9 --- /dev/null +++ b/assignments/7-Todo/Todos/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/7-Todo/Todos/contracts/Counter.sol b/assignments/7-Todo/Todos/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/7-Todo/Todos/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/7-Todo/Todos/contracts/Counter.t.sol b/assignments/7-Todo/Todos/contracts/Counter.t.sol new file mode 100644 index 00000000..ac71d5b8 --- /dev/null +++ b/assignments/7-Todo/Todos/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/7-Todo/Todos/contracts/bank.sol b/assignments/7-Todo/Todos/contracts/bank.sol new file mode 100644 index 00000000..a81ad62c --- /dev/null +++ b/assignments/7-Todo/Todos/contracts/bank.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.31; + +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; + + 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 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; + } + } +} diff --git a/assignments/7-Todo/Todos/hardhat.config.ts b/assignments/7-Todo/Todos/hardhat.config.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/7-Todo/Todos/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/7-Todo/Todos/ignition/modules/Counter.ts b/assignments/7-Todo/Todos/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/7-Todo/Todos/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/7-Todo/Todos/package.json b/assignments/7-Todo/Todos/package.json new file mode 100644 index 00000000..331be59b --- /dev/null +++ b/assignments/7-Todo/Todos/package.json @@ -0,0 +1,20 @@ +{ + "name": "Todos", + "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/7-Todo/Todos/scripts/send-op-tx.ts b/assignments/7-Todo/Todos/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/7-Todo/Todos/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/7-Todo/Todos/test/Counter.ts b/assignments/7-Todo/Todos/test/Counter.ts new file mode 100644 index 00000000..f8c38986 --- /dev/null +++ b/assignments/7-Todo/Todos/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/7-Todo/Todos/tsconfig.json b/assignments/7-Todo/Todos/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/7-Todo/Todos/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/8-escrowv1/escrow/.gitignore b/assignments/8-escrowv1/escrow/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/8-escrowv1/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/8-escrowv1/escrow/README.md b/assignments/8-escrowv1/escrow/README.md new file mode 100644 index 00000000..968246e9 --- /dev/null +++ b/assignments/8-escrowv1/escrow/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/8-escrowv1/escrow/contracts/Counter.sol b/assignments/8-escrowv1/escrow/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/8-escrowv1/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/8-escrowv1/escrow/contracts/Counter.t.sol b/assignments/8-escrowv1/escrow/contracts/Counter.t.sol new file mode 100644 index 00000000..ac71d5b8 --- /dev/null +++ b/assignments/8-escrowv1/escrow/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/8-escrowv1/escrow/contracts/assignment.sol b/assignments/8-escrowv1/escrow/contracts/assignment.sol new file mode 100644 index 00000000..9429ea92 --- /dev/null +++ b/assignments/8-escrowv1/escrow/contracts/assignment.sol @@ -0,0 +1,45 @@ +// SPDX License-Identifier: MIT +pragma solidity 0.8.28; + +contract payment { + enum Status { + Roles, + client, + freelancer, + + } + + +contract paymentMilestone{ + uint256 paymentMilestone; + + struct payment { + uint id; + address payable creator; + string text; + Status status; + uint deadline; + uint freelancer; + } + + + + + mapping(uint => payment) payments; + + event paymentCreated(string text, uint deadline); + + +function creatpayment(string memory _text, uint _freelancer) external returns(uint){ + require(bytes(_text).length > 0, "Empty text"); + require(_freelancer > 0, "Invalid freelancer"); + require(msg.sender != address(0), "Zero address"); + + + emit paymentCreated(_text, _freelancer); + return payment; + + + +} + diff --git a/assignments/8-escrowv1/escrow/contracts/escrow.sol b/assignments/8-escrowv1/escrow/contracts/escrow.sol new file mode 100644 index 00000000..85c49d40 --- /dev/null +++ b/assignments/8-escrowv1/escrow/contracts/escrow.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +contract Escrow { + enum State { + AWAITING_PAYMENT, + AWAITING_DELIVERY, + COMPLETE + } + + State public currentState; + + address public buyer; + address payable public seller; + address public escrowAgent; + + modifier onlyBuyer() { + require(msg.sender == buyer, "Only buyer can call this method"); + _; + } + + modifier onlySeller() { + require(msg.sender == seller, "Only seller can call this method"); + _; + } + + modifier onlyEscrowAgent() { + require( + msg.sender == escrowAgent, + "Only escrow agent can call this method" + ); + _; + } + + modifier inState(State _state) { + require(currentState == _state, "Invalid state"); + _; + } + + event Deposit(address indexed buyer, uint256 amount); + event DeliveryConfirmed(address indexed seller); + event FundsReleased(address indexed seller, uint256 amount); + event FundsRefunded(address indexed buyer, uint256 amount); + + constructor(address _buyer, address payable _seller, address _escrowAgent) { + buyer = _buyer; + seller = _seller; + escrowAgent = _escrowAgent; + currentState = State.AWAITING_PAYMENT; + } + + function deposit() + external + payable + onlyBuyer + inState(State.AWAITING_PAYMENT) + { + require(msg.value > 0, "Deposit amount must be greater than 0"); + currentState = State.AWAITING_DELIVERY; + emit Deposit(msg.sender, msg.value); + } + + function confirmDelivery() + external + onlySeller + inState(State.AWAITING_DELIVERY) + { + emit DeliveryConfirmed(msg.sender); + } + + function releaseFunds() + external + onlyEscrowAgent + inState(State.AWAITING_DELIVERY) + { + currentState = State.COMPLETE; + uint256 amount = address(this).balance; + (bool success, ) = seller.call{value: amount}(""); + require(success, "Transfer to seller failed"); + emit FundsReleased(seller, amount); + } + + function refundBuyer() + external + onlyEscrowAgent + inState(State.AWAITING_DELIVERY) + { + currentState = State.COMPLETE; + uint256 amount = address(this).balance; + (bool success, ) = buyer.call{value: amount}(""); + require(success, "Transfer to buyer failed"); + emit FundsRefunded(buyer, amount); + } +} diff --git a/assignments/8-escrowv1/escrow/contracts/escrowFactory.sol b/assignments/8-escrowv1/escrow/contracts/escrowFactory.sol new file mode 100644 index 00000000..0c0db349 --- /dev/null +++ b/assignments/8-escrowv1/escrow/contracts/escrowFactory.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +import "./escrow.sol"; + +contract EscrowFactory { + event EscrowCreated( + address indexed escrowAddress, + address indexed buyer, + address indexed seller, + address escrowAgent + ); + + function createEscrow( + address payable _seller, + address _escrowAgent + ) external returns (address) { + Escrow newEscrow = new Escrow(msg.sender, _seller, _escrowAgent); + emit EscrowCreated( + address(newEscrow), + msg.sender, + _seller, + _escrowAgent + ); + return address(newEscrow); + } +} diff --git a/assignments/8-escrowv1/escrow/hardhat.config.ts b/assignments/8-escrowv1/escrow/hardhat.config.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/8-escrowv1/escrow/hardhat.config.ts @@ -0,0 +1,38 @@ +import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers"; +import { configVariable, defineConfig } from "hardhat/config"; + +export default defineConfig({ + plugins: [hardhatToolboxMochaEthersPlugin], + solidity: { + profiles: { + default: { + version: "0.8.28", + }, + production: { + version: "0.8.28", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + }, + }, + networks: { + hardhatMainnet: { + type: "edr-simulated", + chainType: "l1", + }, + hardhatOp: { + type: "edr-simulated", + chainType: "op", + }, + sepolia: { + type: "http", + chainType: "l1", + url: configVariable("SEPOLIA_RPC_URL"), + accounts: [configVariable("SEPOLIA_PRIVATE_KEY")], + }, + }, +}); diff --git a/assignments/8-escrowv1/escrow/ignition/modules/Counter.ts b/assignments/8-escrowv1/escrow/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/8-escrowv1/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/8-escrowv1/escrow/package.json b/assignments/8-escrowv1/escrow/package.json new file mode 100644 index 00000000..535b8cd1 --- /dev/null +++ b/assignments/8-escrowv1/escrow/package.json @@ -0,0 +1,20 @@ +{ + "name": "escrow", + "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/8-escrowv1/escrow/scripts/send-op-tx.ts b/assignments/8-escrowv1/escrow/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/8-escrowv1/escrow/scripts/send-op-tx.ts @@ -0,0 +1,22 @@ +import { network } from "hardhat"; + +const { ethers } = await network.connect({ + network: "hardhatOp", + chainType: "op", +}); + +console.log("Sending transaction using the OP chain type"); + +const [sender] = await ethers.getSigners(); + +console.log("Sending 1 wei from", sender.address, "to itself"); + +console.log("Sending L2 transaction"); +const tx = await sender.sendTransaction({ + to: sender.address, + value: 1n, +}); + +await tx.wait(); + +console.log("Transaction sent successfully"); diff --git a/assignments/8-escrowv1/escrow/test/Counter.ts b/assignments/8-escrowv1/escrow/test/Counter.ts new file mode 100644 index 00000000..f8c38986 --- /dev/null +++ b/assignments/8-escrowv1/escrow/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/8-escrowv1/escrow/tsconfig.json b/assignments/8-escrowv1/escrow/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/8-escrowv1/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/9-milestone-escrowv2/milestone/.gitignore b/assignments/9-milestone-escrowv2/milestone/.gitignore new file mode 100644 index 00000000..991a319e --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/.gitignore @@ -0,0 +1,20 @@ +# Node modules +/node_modules + +# Compilation output +/dist + +# pnpm deploy output +/bundle + +# Hardhat Build Artifacts +/artifacts + +# Hardhat compilation (v2) support directory +/cache + +# Typechain output +/types + +# Hardhat coverage reports +/coverage diff --git a/assignments/9-milestone-escrowv2/milestone/README.md b/assignments/9-milestone-escrowv2/milestone/README.md new file mode 100644 index 00000000..968246e9 --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/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/9-milestone-escrowv2/milestone/contracts/Counter.sol b/assignments/9-milestone-escrowv2/milestone/contracts/Counter.sol new file mode 100644 index 00000000..8d00cb7c --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/contracts/Counter.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +contract Counter { + uint public x; + + event Increment(uint by); + + function inc() public { + x++; + emit Increment(1); + } + + function incBy(uint by) public { + require(by > 0, "incBy: increment should be positive"); + x += by; + emit Increment(by); + } +} diff --git a/assignments/9-milestone-escrowv2/milestone/contracts/Counter.t.sol b/assignments/9-milestone-escrowv2/milestone/contracts/Counter.t.sol new file mode 100644 index 00000000..ac71d5b8 --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/contracts/Counter.t.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import {Counter} from "./Counter.sol"; +import {Test} from "forge-std/Test.sol"; + +// Solidity tests are compatible with foundry, so they +// use the same syntax and offer the same functionality. + +contract CounterTest is Test { + Counter counter; + + function setUp() public { + counter = new Counter(); + } + + function test_InitialValue() public view { + require(counter.x() == 0, "Initial value should be 0"); + } + + function testFuzz_Inc(uint8 x) public { + for (uint8 i = 0; i < x; i++) { + counter.inc(); + } + require(counter.x() == x, "Value after calling inc x times should be x"); + } + + function test_IncByZero() public { + vm.expectRevert(); + counter.incBy(0); + } +} diff --git a/assignments/9-milestone-escrowv2/milestone/contracts/payment.sol b/assignments/9-milestone-escrowv2/milestone/contracts/payment.sol new file mode 100644 index 00000000..021ef1f6 --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/contracts/payment.sol @@ -0,0 +1,99 @@ +// SPDX License-Identifier: unlicensed +pragma solidity 0.8.28; + +contract milestone { + address public Client; + address public Freelancer; + bool public validMilestone; + uint256 public totalMilestones; + uint256 public AmountPerMilestone; + + uint256 public currentMilestoneId; + + enum MilestoneStatus { + PENDING, + COMPLETED, + PAID + } + + struct Milestone { + uint256 id; + bool isCompleted; // Marked by freelancer + bool isPaid; + MilestoneStatus status; + // Marked by client/contract + } + mapping(address => uint256) public Milestones; + + modifier onlyClient() { + require(msg.sender == Client, "Only client can call this function"); + _; + } + + modifier onlyFreelancer() { + require( + msg.sender == Freelancer, + "Only freelancer can call this function" + ); + _; + } + + constructor( + address _client, + address payable _freelancer, + uint256 _ethPerMilestone, + uint256 _totalMilestones, + uint256 _currentMilestoneId + ) payable { + Client = _client; + Freelancer = _freelancer; + AmountPerMilestone = _ethPerMilestone; + totalMilestones = _totalMilestones; + } + + event MilestoneCreated(uint256 milestoneId); + event MilestoneCompleted(uint256 milestoneId); + event MilestonePaid(uint256 milestoneId, uint256 amount); + + function creatJob( + address payable _freelancer, + uint256 _totalMilestones, + uint256 _ethPerMilestone + ) public onlyClient { + require(msg.sender == Client, "Only client can create a milestone"); + Freelancer = _freelancer; + AmountPerMilestone = _ethPerMilestone; + } + + function completeMilestone( + uint256 _id + ) public onlyFreelancer returns (bool isCompleted) { + require( + msg.sender == Freelancer, + "Only freelancer can complete a milestone" + ); + require(Milestones[msg.sender] == _id, "Milestone ID does not exist"); + Milestones[msg.sender] = _id; + return true; + } + + function payMilestone( + uint256 _ethPerMilestone, + uint256 _currentMilestonesId + ) public onlyClient returns (bool isPaid) { + require(msg.sender == Client, "Only client can pay for a milestone"); + require(Milestones[Freelancer] > 0, "Milestone does not exist"); + require(_ethPerMilestone > 0, "Payment amount must be greater than 0"); + payable(Freelancer).transfer(_ethPerMilestone); + Milestones[Freelancer] = 0; + return true; + } + + function calculateMilestone() + public + pure + returns (uint256 _totalMilestones, uint256 _ethPerMilestone) + { + return (10, 1000); + } +} diff --git a/assignments/9-milestone-escrowv2/milestone/hardhat.config.ts b/assignments/9-milestone-escrowv2/milestone/hardhat.config.ts new file mode 100644 index 00000000..7092b852 --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/hardhat.config.ts @@ -0,0 +1,38 @@ +import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers"; +import { configVariable, defineConfig } from "hardhat/config"; + +export default defineConfig({ + plugins: [hardhatToolboxMochaEthersPlugin], + solidity: { + profiles: { + default: { + version: "0.8.28", + }, + production: { + version: "0.8.28", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + }, + }, + }, + }, + networks: { + hardhatMainnet: { + type: "edr-simulated", + chainType: "l1", + }, + hardhatOp: { + type: "edr-simulated", + chainType: "op", + }, + sepolia: { + type: "http", + chainType: "l1", + url: configVariable("SEPOLIA_RPC_URL"), + accounts: [configVariable("SEPOLIA_PRIVATE_KEY")], + }, + }, +}); diff --git a/assignments/9-milestone-escrowv2/milestone/ignition/modules/Counter.ts b/assignments/9-milestone-escrowv2/milestone/ignition/modules/Counter.ts new file mode 100644 index 00000000..042e61c8 --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/ignition/modules/Counter.ts @@ -0,0 +1,9 @@ +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; + +export default buildModule("CounterModule", (m) => { + const counter = m.contract("Counter"); + + m.call(counter, "incBy", [5n]); + + return { counter }; +}); diff --git a/assignments/9-milestone-escrowv2/milestone/package.json b/assignments/9-milestone-escrowv2/milestone/package.json new file mode 100644 index 00000000..906e6eb2 --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/package.json @@ -0,0 +1,20 @@ +{ + "name": "payment", + "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/9-milestone-escrowv2/milestone/scripts/send-op-tx.ts b/assignments/9-milestone-escrowv2/milestone/scripts/send-op-tx.ts new file mode 100644 index 00000000..c10a2360 --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/scripts/send-op-tx.ts @@ -0,0 +1,22 @@ +import { network } from "hardhat"; + +const { ethers } = await network.connect({ + network: "hardhatOp", + chainType: "op", +}); + +console.log("Sending transaction using the OP chain type"); + +const [sender] = await ethers.getSigners(); + +console.log("Sending 1 wei from", sender.address, "to itself"); + +console.log("Sending L2 transaction"); +const tx = await sender.sendTransaction({ + to: sender.address, + value: 1n, +}); + +await tx.wait(); + +console.log("Transaction sent successfully"); diff --git a/assignments/9-milestone-escrowv2/milestone/test/Counter.ts b/assignments/9-milestone-escrowv2/milestone/test/Counter.ts new file mode 100644 index 00000000..7fa5473a --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/test/Counter.ts @@ -0,0 +1,96 @@ + +import { expect } from "chai"; +import { network } from "hardhat"; +const { networkHelpers } = await network.connect(); + +const { ethers } = await network.connect(); + + +describe("milestone", function () { +let milestone : any; +const ZeroAddress = "0x0000000000000000000000000000000000000000"; + + +beforeEach(async () => { + milestone = await ethers.deployContract("Milestone"); + + + }); + + + describe("Create Auction", function (){ + it("should create an auction successfully", async () => { + let [owner] = await ethers.getSigners() + await expect(milestone.createAuction(1000, 1200)).to.emit(milestone, "AuctionInitialaized").withArgs(1n); + const a = await milestone.auctionCounter(); + expect(a).to.equal(1); + + const createdAuction = await milestone.auctions(a); + expect(createdAuction[0]).to.equal(1); + expect(createdAuction[1]).to.equal(1000); + expect(createdAuction[2]).to.equal(0); + expect(createdAuction[3]).to.equal(owner); + expect(createdAuction[4]).to.equal(ZeroAddress); + expect(createdAuction[5]).to.equal(0); + expect(createdAuction[6]).to.equal(1200); + + + }) + + it("should create more than an auction successfully", async () => { + let [owner] = await ethers.getSigners() + // await expect(milestone.createAuction(1000, 1200)).to.emit(milestone, "AuctionInitialaized").withArgs(1n); + await milestone.createAuction(1000, 1200); + const a = await milestone.auctionCounter(); + expect(a).to.equal(1); + + // await expect(milestone.createAuction(1500, 2000)).to.emit(milestone, "AuctionInitialaized").withArgs(2n); + await milestone.createAuction(1500, 2000) + + const b = await milestone.auctionCounter(); + + + const createdAuction = await milestone.auctions(b); + expect(createdAuction[0]).to.equal(2); + expect(createdAuction[1]).to.equal(1500); + expect(createdAuction[2]).to.equal(0); + expect(createdAuction[3]).to.equal(owner); + expect(createdAuction[4]).to.equal(ZeroAddress); + expect(createdAuction[5]).to.equal(0); + expect(createdAuction[6]).to.equal(2000); + + + }) + }) + + describe.only("Start Auction", () => { + it("Should Start Auction Successfully", async () => { + let [owner] = await ethers.getSigners() + await expect(milestone.createAuction(1000, 1200)).to.emit(milestone, "AuctionInitialaized").withArgs(1n); + const a = await milestone.auctionCounter(); + + await milestone.startAuction(a); + const currentTime = await networkHelpers.time.latest() + + const startedAuction = await milestone.auctions(a); + expect(startedAuction[2]).to.equal(1); + expect(startedAuction[5]).to.be.closeTo(currentTime, 5); + + }) + + + + it("Should Fail When a Wrong address tries to start the Auction", async () => { + let [owner, addr1] = await ethers.getSigners() + await expect(milestone.createAuction(1000, 1200)).to.emit(milestone, "AuctionInitialaized").withArgs(1n); + const a = await milestone.auctionCounter(); + + await expect( milestone.connect(addr1).startAuction(a)).to.be.revertedWith("Not your Auction"); + + }) + }) + + + + +}); \ No newline at end of file diff --git a/assignments/9-milestone-escrowv2/milestone/tsconfig.json b/assignments/9-milestone-escrowv2/milestone/tsconfig.json new file mode 100644 index 00000000..9b1380cc --- /dev/null +++ b/assignments/9-milestone-escrowv2/milestone/tsconfig.json @@ -0,0 +1,13 @@ +/* Based on https://github.com/tsconfig/bases/blob/501da2bcd640cf95c95805783e1012b992338f28/bases/node22.json */ +{ + "compilerOptions": { + "lib": ["es2023"], + "module": "node16", + "target": "es2022", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "moduleResolution": "node16", + "outDir": "dist" + } +}