diff --git a/flake.nix b/flake.nix index 16bd8911..210e081e 100644 --- a/flake.nix +++ b/flake.nix @@ -42,6 +42,7 @@ nilexplorer = (pkgs.callPackage ./nix/nilexplorer.nix { }); uniswap = (pkgs.callPackage ./nix/uniswap.nix { }); docsaibackend = (pkgs.callPackage ./nix/docsaibackend.nix { }); + l1-contracts = (pkgs.callPackage ./nix/l1-contracts.nix { }); }; checks = rec { nil = (pkgs.callPackage ./nix/nil.nix { @@ -92,12 +93,14 @@ mkdir -p ./usr/share/${packages.nildocs.pname} mkdir -p ./usr/share/${packages.nilexplorer.name} mkdir -p ./usr/share/${packages.docsaibackend.name} + mkdir -p ./usr/share/${packages.l1-contracts.name} cp -r ${pkg}/bin ./usr/ cp -r ${pkg}/share ./usr/ cp -r ${packages.nildocs.outPath}/* ./usr/share/${packages.nildocs.pname} cp -r ${packages.nilexplorer.outPath}/* ./usr/share/${packages.nilexplorer.name} cp -r ${packages.docsaibackend.outPath}/* ./usr/share/${packages.nilexplorer.name} + cp -r ${packages.l1-contracts.outPath}/* ./usr/share/${packages.l1-contracts.name} chmod -R u+rw,g+r,o+r ./usr chmod -R u+rwx,g+rx,o+rx ./usr/bin diff --git a/l1-contracts/NilChain.abi b/l1-contracts/NilChain.abi new file mode 100644 index 00000000..745ea01a --- /dev/null +++ b/l1-contracts/NilChain.abi @@ -0,0 +1,434 @@ +[ + { + "inputs": [ + { + "internalType": "uint64", + "name": "_chainId", + "type": "uint64" + }, + { + "internalType": "uint256", + "name": "_version", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "EnforcedPause", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorCallEvaluationPrecompileFailed", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorCallerIsNotMemberOfSC", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorEvaluationPrecompileOutputWrong", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorMustBeEOA", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorNewStateRootAlreadyFinalized", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorNewStateRootIsZero", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorOldStateRootIsZero", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorOldStateRootNotMatch", + "type": "error" + }, + { + "inputs": [], + "name": "ExpectedPause", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "OwnableUnauthorizedAccount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "batchIndex", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "oldStateRoot", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newStateRoot", + "type": "bytes32" + } + ], + "name": "BatchIsFinalized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "member", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "oldStatus", + "type": "bool" + }, + { + "indexed": false, + "internalType": "bool", + "name": "newStatus", + "type": "bool" + } + ], + "name": "CommitteeMemberUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "chainID", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "finalizedBatchIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_batchIndex", + "type": "uint256" + } + ], + "name": "isBatchFinalized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isCommitteeMember", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_stateRoot", + "type": "bytes32" + } + ], + "name": "isRootFinalized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_prevStateRoot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "_newStateRoot", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_blobProof", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "_batchIndexInBlobStorage", + "type": "uint256" + } + ], + "name": "proofBatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_member", + "type": "address" + }, + { + "internalType": "bool", + "name": "_status", + "type": "bool" + } + ], + "name": "setSyncCommMemberStatus", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "stateRootIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "stateRoots", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/l1-contracts/NilChain.bin b/l1-contracts/NilChain.bin new file mode 100644 index 00000000..325b33f7 --- /dev/null +++ b/l1-contracts/NilChain.bin @@ -0,0 +1 @@ +0x60a03461025757601f61092538819003918201601f19168301916001600160401b0383118484101761025c578084926040948552833981010312610257578051906001600160401b0382168203610257576020015160008051602061090583398151915254604081901c60ff161592906001600160401b0381168015908161024f575b6001149081610245575b15908161023c575b5061022b576001600160401b031981166001176000805160206109058339815191525583610200575b5060805260016000818155805260208190527fa6eef7e35abe7026729641147f7915573c7e97b47efa546f5f6e3230263bcb49556004556100fc610272565b610104610272565b33156101ea577f9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c1993008054336001600160a01b0319821681179092556040519291906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a361018f575b60405161066490816102a182396080518161022d0152f35b60207fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29168ff00000000000000001960008051602061090583398151915254166000805160206109058339815191525560018152a138610177565b631e4fbdf760e01b600052600060045260246000fd5b6001600160481b031916680100000000000000011760008051602061090583398151915255386100bd565b63f92ee8a960e01b60005260046000fd5b90501538610094565b303b15915061008c565b859150610082565b600080fd5b634e487b7160e01b600052604160045260246000fd5b60ff6000805160206109058339815191525460401c161561028f57565b631afcd79f60e31b60005260046000fdfe608080604052600436101561001357600080fd5b60003560e01c908163116a1f42146105a3575080634d8f59fd1461057757806354fd4d50146105595780635c975abb146105175780636af78c5c146103a1578063715018a6146103375780638da5cb5b1461030157806393d948fe14610251578063adc879e91461020c578063df9f4d4e146101ee578063e636d84b146101af578063e91d8acc14610181578063f2fde38b146100ea5763f4cac30d146100b957600080fd5b346100e55760203660031901126100e55760043560005260016020526020604060002054604051908152f35b600080fd5b346100e55760203660031901126100e5576101036105c2565b61010b6105d8565b6001600160a01b0316801561016b5760008051602061060f83398151915280546001600160a01b0319811683179091556001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0600080a3005b631e4fbdf760e01b600052600060045260246000fd5b346100e55760203660031901126100e557600435600052600260205260206040600020541515604051908152f35b346100e55760203660031901126100e5576001600160a01b036101d06105c2565b166000526003602052602060ff604060002054166040519015158152f35b346100e55760003660031901126100e5576020600054604051908152f35b346100e55760003660031901126100e557602060405167ffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000168152f35b346100e55760403660031901126100e55761026a6105c2565b602435908115158092036100e5576102806105d8565b60018060a01b03169081600052600360205260ff60406000205416903332036102f1577f3f2b95367921b70f2b132c9841e48adedb8c79b6be1dd9796b5e166a9a97d133916040918460005260036020528260002060ff1981541660ff8316179055825191151582526020820152a2005b6216d91360e11b60005260046000fd5b346100e55760003660031901126100e55760008051602061060f833981519152546040516001600160a01b039091168152602090f35b346100e55760003660031901126100e5576103506105d8565b60008051602061060f83398151915280546001600160a01b031981169091556000906001600160a01b03167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e08280a3005b346100e55760803660031901126100e55760043560243560443567ffffffffffffffff81116100e557366023820112156100e557806004013567ffffffffffffffff81116100e557369101602401116100e55733600052600360205260ff60406000205416156105065781156104f55780156104e4576000549160001983018381116104ac57600052600160205280604060002054036104d3578260005260016020526040600020546104c25760209260005260018352816040600020557fba4c374eb4ac59189e2e6d57f937bb584ff295ac9d38ccae3eee65036e24f8bf600054938492846000526002825283604060002055604051908152a3600181018091116104ac57600055005b634e487b7160e01b600052601160045260246000fd5b6349655db960e01b60005260046000fd5b63fa51c6f760e01b60005260046000fd5b63e7cc0c2f60e01b60005260046000fd5b630720873f60e31b60005260046000fd5b637fc8cd7160e01b60005260046000fd5b346100e55760003660031901126100e557602060ff7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f0330054166040519015158152f35b346100e55760003660031901126100e5576020600454604051908152f35b346100e55760203660031901126100e55760043560005260026020526020604060002054604051908152f35b346100e55760203660031901126100e557602090600435600054118152f35b600435906001600160a01b03821682036100e557565b60008051602061060f833981519152546001600160a01b031633036105f957565b63118cdaa760e01b6000523360045260246000fdfe9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300a264697066735822122071d5616a126af81d62bf51e87b9986d20f6def97068258265671e922c5a43ee264736f6c634300081b0033f0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00 \ No newline at end of file diff --git a/l1-contracts/README.md b/l1-contracts/README.md new file mode 100644 index 00000000..af746a5f --- /dev/null +++ b/l1-contracts/README.md @@ -0,0 +1,43 @@ +# NilChain Verifier contract + +- Contract `NilChain` is to be deployed on L1 Chain +- SyncCommittee refers to this L1 contract when submitting proof to get verified +- StateRoot updates are also done on this contract + +## Build + +1. install node dependencies + +```sh +npm i +``` + + +2. copy `.env.example` to `.env` + +3. set all pre-requisite variables in .env + - WALLET_ADDRESS + - PRIVATE_KEY + - This address is same as the address used for deployment and acts as the owner of the NilChain contract + - The address is to be used when running SyncCommitee node + + +## Local Run + +- For build pipeline or local testing, the contract is to be deployed on local Nil Node + +### Please follow the steps mentioned below: + +1. copy `.env.example` to `.env` +2. set all pre-requisite variables in .env + - WALLET_ADDRESS + - PRIVATE_KEY +3. This address is same as the address used for deployment and acts as the owner of the NilChain contract +4. The address is to be used when running SyncCommitee node + +Try running some of the following tasks: + +```shell +npx hardhat compile +npx hardhat deploy --network local +``` diff --git a/l1-contracts/contracts/NilChain.sol b/l1-contracts/contracts/NilChain.sol new file mode 100644 index 00000000..957b48be --- /dev/null +++ b/l1-contracts/contracts/NilChain.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.2 <0.9.0; + +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; +import {INilChain} from "./interfaces/INilChain.sol"; + +contract NilChain is OwnableUpgradeable, PausableUpgradeable, INilChain { + + // ================== @ERRORS ================== + + /// @dev Error if not part of Synchronization Committee. + error ErrorCallerIsNotMemberOfSC(); + + /// @dev Error if not part of Synchronization Committee. + error ErrorMustBeEOA(); + + /// @dev Wrong attempt to set new state root to 0. + error ErrorNewStateRootIsZero(); + + /// @dev Wrong attempt to set old state root to 0. + error ErrorOldStateRootIsZero(); + + /// @dev Call of kzg evaluation precompile failed for unknown reason. + error ErrorCallEvaluationPrecompileFailed(); + + /// @dev Output from evaluation precompile doesn't match expected result. + error ErrorEvaluationPrecompileOutputWrong(); + + /// @dev The current state root doesn't match the submitted old root. + error ErrorOldStateRootNotMatch(); + + /// @dev New state root was already finalized. + error ErrorNewStateRootAlreadyFinalized(); + + // ================== @CONSTANTS ================== + + /// @dev BLS Modulus defined in EIP-4844. + uint256 private constant BLS_MODULUS = + 52435875175126190479447740508185965837690552500527637822603658699938581184513; + + /// @dev Address of the kzg precompiled contract. + address private constant KZG_EVALUATION_PRECOMPILE = address(0x0A); + + /// @dev L2 chain ID. Set in the constructor. + uint64 public immutable chainID; + + // ================== @VARIABLES ================== + + /// @dev last finalized batch index + uint256 public finalizedBatchIndex; + + /// @dev Indexed finalized stateroots. + mapping(uint256 => bytes32) public stateRoots; + + /// @dev Finalized state id. + mapping(bytes32 => uint256) public stateRootIndex; + + /// @dev List of active Synchronization Committee members. + mapping(address => bool) public isCommitteeMember; + + uint256 public version; + + // ================== @CODE ================== + + + modifier OnlySyncCommittee() { + // @note In the decentralized mode, it should be only called by a list of validator. + if (!isCommitteeMember[msg.sender]) revert ErrorCallerIsNotMemberOfSC(); + _; + } + + + constructor ( + uint64 _chainId, + uint256 _version + ) initializer { + chainID = _chainId; + finalizedBatchIndex = 1; + /// @dev this is "genesis" root + stateRoots[0] = 0x0000000000000000000000000000000000000000000000000000000000000001; + version = _version; + OwnableUpgradeable.__Ownable_init(msg.sender); + } + + function isBatchFinalized(uint256 _batchIndex) external override view returns (bool) { + return finalizedBatchIndex > _batchIndex; + } + + function isRootFinalized(bytes32 _stateRoot) external override view returns (bool) { + return stateRootIndex[_stateRoot] != 0; + } + + function setSyncCommMemberStatus(address _member, bool _status) external override onlyOwner { + bool oldStatus = isCommitteeMember[_member]; + + if (tx.origin != msg.sender) { + revert ErrorMustBeEOA(); + } + + isCommitteeMember[_member] = _status; + + emit CommitteeMemberUpdated(_member, oldStatus, _status); + } + + + /// @dev Memory layout of _blobProof: + /// | z | y | kzg_commitment | kzg_proof | + /// |---------|---------|----------------|-----------| + /// | bytes32 | bytes32 | bytes48 | bytes48 | + /// if _batchIndexInBlobStorage is 0 -- we skip blob proof verification. + /// verification tested and works fine + function proofBatch( + bytes32 _prevStateRoot, + bytes32 _newStateRoot, + bytes calldata _blobProof, + uint256 _batchIndexInBlobStorage + ) external override OnlySyncCommittee { + + if (_prevStateRoot == bytes32(0)) { + revert ErrorOldStateRootIsZero(); + } + if (_newStateRoot == bytes32(0)) { + revert ErrorNewStateRootIsZero(); + } + + // verify blob proof if needed + // if (_batchIndexInBlobStorage != 0) { + // bytes32 blobVersionedHash = blobhash(_batchIndexInBlobStorage); + // (bool success, bytes memory data) = KZG_EVALUATION_PRECOMPILE.staticcall( + // abi.encodePacked(blobVersionedHash, _blobProof) + // ); + + // if (!success) { + // revert ErrorCallEvaluationPrecompileFailed(); + // } + + // (, uint256 result) = abi.decode(data, (uint256, uint256)); + + // if (result != BLS_MODULUS) { + // revert ErrorEvaluationPrecompileOutputWrong(); + // } + // } + + // verify previous state root. + if (stateRoots[finalizedBatchIndex - 1] != _prevStateRoot) { + revert ErrorOldStateRootNotMatch(); + } + + // avoid duplicated verification + if (stateRoots[finalizedBatchIndex] != bytes32(0)) { + revert ErrorNewStateRootAlreadyFinalized(); + } + + stateRoots[finalizedBatchIndex] = _newStateRoot; + stateRootIndex[_newStateRoot] = finalizedBatchIndex; + emit BatchIsFinalized(finalizedBatchIndex, _prevStateRoot, _newStateRoot); + + finalizedBatchIndex += 1; + } +} \ No newline at end of file diff --git a/l1-contracts/contracts/interfaces/INilChain.sol b/l1-contracts/contracts/interfaces/INilChain.sol new file mode 100644 index 00000000..aae370a5 --- /dev/null +++ b/l1-contracts/contracts/interfaces/INilChain.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT + +pragma solidity >=0.8.2 <0.9.0; + +interface INilChain { + + // ================== @EVENTS ================== + + /// @dev Notify about changes in the status of Sync Committee member. + event CommitteeMemberUpdated(address indexed member, bool oldStatus, bool newStatus); + + /// @dev Emitted when old state route replaced with new state root with corresponding batch. + event BatchIsFinalized(uint256 indexed batchIndex, bytes32 oldStateRoot, bytes32 indexed newStateRoot); + + function isBatchFinalized(uint256 _batchIndex) external view returns (bool); + + function isRootFinalized(bytes32 _stateRoot) external view returns (bool); + + function setSyncCommMemberStatus(address _member, bool _status) external; + + function proofBatch( + bytes32 _prevStateRoot, + bytes32 _newStateRoot, + bytes calldata _blobProof, + uint256 _batchIndexInBlobStorage + ) external; +} \ No newline at end of file diff --git a/l1-contracts/create-wallet-with-funding.ts b/l1-contracts/create-wallet-with-funding.ts new file mode 100644 index 00000000..ccc1bd11 --- /dev/null +++ b/l1-contracts/create-wallet-with-funding.ts @@ -0,0 +1,61 @@ +import { Wallet, ethers } from "ethers"; +import * as dotenv from "dotenv"; +dotenv.config(); + +// npx ts-node scripts/create-wallet-with-funding.ts +async function createAndUseWallet() { + + const provider = new ethers.JsonRpcProvider("http://localhost:8545"); // Change URL as needed + + const accounts = await provider.send("eth_accounts", []); + const defaultAccount = accounts[0]; + console.log("Default Account Address:", defaultAccount); + + console.log("Wallet Connected to Provider."); + + const wallet = Wallet.createRandom(); + console.log("New Wallet Created:"); + console.log("Address:", wallet.address); + console.log("Private Key:", wallet.privateKey); + const connectedWallet = wallet.connect(provider); + + const value = ethers.parseEther("1"); + console.log("Value:", value.toString()); + const valueInHex = ethers.toQuantity(ethers.parseEther("1")); + console.log("Value in Hex:", valueInHex); + + const fundingTx = await provider.send("eth_sendTransaction", [ + { + from: defaultAccount, + to: wallet.address, + value: valueInHex, + }, + ]); + + console.log("Funding Transaction Sent:", fundingTx); + + // Step 1: Test Create a new random wallet + const receivingWallet = Wallet.createRandom(); + + // Step 2: Display wallet details + console.log("New Wallet Created:"); + console.log("Address:", receivingWallet.address); + console.log("Private Key:", receivingWallet.privateKey); + + // Step 3: Use the wallet to send a transaction + const tx = await connectedWallet.sendTransaction({ + to: receivingWallet.address, + value: ethers.parseEther("0.1"), + gasLimit: 21000, + }); + + console.log("Transaction Sent:", tx.hash); + + // Step 4: Wait for the transaction to be mined + const receipt = await tx.wait(); + console.log("Transaction Mined:", receipt?.hash); +} + +createAndUseWallet().catch((error) => { + console.error("Error:", error.message); +}); diff --git a/l1-contracts/deploy-nilchain.ts b/l1-contracts/deploy-nilchain.ts new file mode 100644 index 00000000..90cb712b --- /dev/null +++ b/l1-contracts/deploy-nilchain.ts @@ -0,0 +1,92 @@ +import { ethers } from "ethers"; +import fs from "fs"; +import path from "path"; +import dotenv from "dotenv"; +dotenv.config(); + +async function main() { + // Connect to the local Geth node + const L1_RPC_ENDPOINT = process.env.L1_RPC_ENDPOINT as string; + console.log("L1 RPC Endpoint:", L1_RPC_ENDPOINT); + const provider = new ethers.JsonRpcProvider(L1_RPC_ENDPOINT); + + // Predefined private key from environment variable + const privateKey = process.env.PRIVATE_KEY; + if (!privateKey) { + throw new Error("PRIVATE_KEY environment variable is not set"); + } + + const accounts = await provider.send("eth_accounts", []); + const defaultAccount = accounts[0]; + console.log("Default Account Address:", defaultAccount); + + console.log("Wallet Connected to Provider."); + + const valueInHex = ethers.toQuantity(ethers.parseEther("100")); + const walletAddress = process.env.WALLET_ADDRESS as string; + console.log("Wallet Address:", walletAddress); + + const fundingTx = await provider.send("eth_sendTransaction", [ + { + from: defaultAccount, + to: walletAddress, + value: valueInHex, + }, + ]); + + console.log("Funding Transaction Sent:", fundingTx); + + const fundTransactionHash = fundingTx; + console.log("Funding Transaction Hash:", fundTransactionHash); + + // Wait for the transaction to be mined + const fundReceipt = await provider.waitForTransaction(fundTransactionHash); + console.log("Funding Transaction Mined:", fundReceipt); + + // query balance + const balance = await provider.getBalance(walletAddress); + console.log(`Balance of Wallet: ${walletAddress} After funding:" ${balance}`); + + // Read the compiled contract ABI and bytecode + const abi = JSON.parse(fs.readFileSync(path.join(__dirname, "NilChain.abi"), "utf8")); + const bytecode = fs.readFileSync(path.join(__dirname, "NilChain.bin"), "utf8"); + + const wallet = new ethers.Wallet(privateKey, provider); + + // Create a ContractFactory and deploy the contract + const factory = new ethers.ContractFactory(abi, bytecode, wallet); + + const chainId = parseInt(process.env.CHAIN_ID as string, 10); + const version = parseInt(process.env.VERSION as string, 10); + + const contract = await factory.deploy(chainId, version); + const deployReceipt = await contract.waitForDeployment(); + + if (!deployReceipt || deployReceipt.getAddress() === null) { + throw new Error("NilChain Contract deployment failed"); + } + + const contractAddress = await contract.getAddress(); + console.log("NilChain contract deployed at:", contractAddress); + + // Wait for a few seconds + await new Promise((resolve) => setTimeout(resolve, 5000)); + + // Create a contract instance with the correct ABI and address + const nilChainContract = new ethers.Contract(contractAddress, abi, wallet); + + // Call setSyncCommMemberStatus with the deployer address and _status set to true + const setSyncCommMemberStatusTxn = await nilChainContract.setSyncCommMemberStatus(walletAddress, true); + await setSyncCommMemberStatusTxn.wait(); + + console.log(`setSyncCommMemberStatus called with member: ${walletAddress}, status: true`); + + // Query if the member is really added to the sync committee + const isMember = await nilChainContract.isCommitteeMember(walletAddress); + console.log(`Is ${walletAddress} a committee member?`, isMember); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/l1-contracts/deploy/nilchain.ts b/l1-contracts/deploy/nilchain.ts new file mode 100644 index 00000000..798108f6 --- /dev/null +++ b/l1-contracts/deploy/nilchain.ts @@ -0,0 +1,23 @@ +import { DeployFunction } from "hardhat-deploy/types"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; + +const deployNilChain: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const { deployments, getNamedAccounts, ethers } = hre; + const { deploy } = deployments; + + const { deployer } = await getNamedAccounts(); + + const chainId = 1337; + const version = 1; + + const nilChain = await deploy("NilChain", { + from: deployer, + args: [chainId, version], + log: true, + }); + + console.log("NilChain deployed to:", nilChain.address); +}; + +export default deployNilChain; +deployNilChain.tags = ["NilChain"]; \ No newline at end of file diff --git a/l1-contracts/deploy/query-nilchain.ts b/l1-contracts/deploy/query-nilchain.ts new file mode 100644 index 00000000..83418c7e --- /dev/null +++ b/l1-contracts/deploy/query-nilchain.ts @@ -0,0 +1,25 @@ +import { task } from "hardhat/config"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; + +task("query-owner", "Queries the owner of the NilChain contract") + .addParam("contract", "The address of the NilChain contract") + .setAction(async (taskArgs, hre: HardhatRuntimeEnvironment) => { + const { ethers } = hre; + + const contractAddress = taskArgs.contract; + + // ABI of the NilChain contract (assuming it has an owner() function) + const abi = [ + "function owner() view returns (address)" + ]; + + // Create a contract instance + const nilChain = new ethers.Contract(contractAddress, abi, ethers.provider); + + // Query the owner + const owner = await nilChain.owner(); + + console.log("Owner of NilChain contract:", owner); + }); + +export default {}; \ No newline at end of file diff --git a/l1-contracts/extract-artifacts.ts b/l1-contracts/extract-artifacts.ts new file mode 100644 index 00000000..d4dbe497 --- /dev/null +++ b/l1-contracts/extract-artifacts.ts @@ -0,0 +1,20 @@ +import fs from 'fs'; +import path from 'path'; + +async function main() { + const artifactsPath = path.join(__dirname, '../artifacts/contracts/NilChain.sol/NilChain.json'); + const artifacts = JSON.parse(fs.readFileSync(artifactsPath, 'utf8')); + + const abi = JSON.stringify(artifacts.abi, null, 2); + const bytecode = artifacts.bytecode; + + fs.writeFileSync(path.join(__dirname, '../NilChain.abi'), abi); + fs.writeFileSync(path.join(__dirname, '../NilChain.bin'), bytecode); + + console.log('ABI and bytecode files have been generated.'); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); \ No newline at end of file diff --git a/l1-contracts/fund-wallet.ts b/l1-contracts/fund-wallet.ts new file mode 100644 index 00000000..14f4bdf0 --- /dev/null +++ b/l1-contracts/fund-wallet.ts @@ -0,0 +1,48 @@ +import { Wallet, ethers } from "ethers"; +import * as dotenv from "dotenv"; +dotenv.config(); + +// npx ts-node scripts/fund-wallet.ts +async function createAndUseWallet() { + + const L1_RPC_ENDPOINT = process.env.L1_RPC_ENDPOINT as string; + console.log("L1 RPC Endpoint:", L1_RPC_ENDPOINT); + const provider = new ethers.JsonRpcProvider(L1_RPC_ENDPOINT); + + const accounts = await provider.send("eth_accounts", []); + const defaultAccount = accounts[0]; + console.log("Default Account Address:", defaultAccount); + + console.log("Wallet Connected to Provider."); + + const valueInHex = ethers.toQuantity(ethers.parseEther("100")); + console.log("Value in Hex:", valueInHex); + + const walletAddress = process.env.WALLET_ADDRESS as string; + console.log("Wallet Address:", walletAddress); + + const fundingTx = await provider.send("eth_sendTransaction", [ + { + from: defaultAccount, + to: walletAddress, + value: valueInHex, + }, + ]); + + console.log("Funding Transaction Sent:", fundingTx); + + const transactionHash = fundingTx; + console.log("Transaction Hash:", transactionHash); + + // Wait for the transaction to be mined + const receipt = await provider.waitForTransaction(transactionHash); + console.log("Transaction Mined:", receipt); + + // query balance + const balance = await provider.getBalance(walletAddress); + console.log("Balance:", balance); +} + +createAndUseWallet().catch((error) => { + console.error("Error:", error.message); +}); diff --git a/l1-contracts/get-function-selector.ts b/l1-contracts/get-function-selector.ts new file mode 100644 index 00000000..61636da7 --- /dev/null +++ b/l1-contracts/get-function-selector.ts @@ -0,0 +1,14 @@ +import { ethers } from 'ethers'; + +function getFunctionSelector(signature: string): string { + // Hash the function signature using ethers + const hash = ethers.keccak256(ethers.toUtf8Bytes(signature)); + // Take the first 4 bytes of the hash + const functionSelector = hash.slice(2, 10); // Remove '0x' prefix and take the first 8 characters + return functionSelector; +} + +// npx ts-node scripts/get-function-selector.ts +const signature = "proofBatch(bytes32,bytes32,bytes,uint256)"; +const selector = getFunctionSelector(signature); +console.log(selector); \ No newline at end of file diff --git a/l1-contracts/ignition/modules/Lock.ts b/l1-contracts/ignition/modules/Lock.ts new file mode 100644 index 00000000..9ee4a2f1 --- /dev/null +++ b/l1-contracts/ignition/modules/Lock.ts @@ -0,0 +1,20 @@ +// This setup uses Hardhat Ignition to manage smart contract deployments. +// Learn more about it at https://hardhat.org/ignition + +import { buildModule } from "@nomicfoundation/hardhat-ignition/modules"; + +const JAN_1ST_2030 = 1893456000; +const ONE_GWEI: bigint = 1_000_000_000n; + +const LockModule = buildModule("LockModule", (m) => { + const unlockTime = m.getParameter("unlockTime", JAN_1ST_2030); + const lockedAmount = m.getParameter("lockedAmount", ONE_GWEI); + + const lock = m.contract("Lock", [unlockTime], { + value: lockedAmount, + }); + + return { lock }; +}); + +export default LockModule; diff --git a/l1-contracts/package.json b/l1-contracts/package.json new file mode 100644 index 00000000..92d79b17 --- /dev/null +++ b/l1-contracts/package.json @@ -0,0 +1,15 @@ +{ + "name": "hardhat-project", + "devDependencies": { + "@nomicfoundation/hardhat-toolbox": "^5.0.0", + "hardhat": "^2.22.15" + }, + "dependencies": { + "@openzeppelin/contracts": "^5.1.0", + "@openzeppelin/contracts-upgradeable": "^5.1.0", + "dotenv": "^16.4.5", + "ethers": "^6.13.4", + "hardhat-deploy": "^0.14.0" + } +} + diff --git a/l1-contracts/tsconfig.json b/l1-contracts/tsconfig.json new file mode 100644 index 00000000..44eae946 --- /dev/null +++ b/l1-contracts/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "types": ["node", "hardhat-deploy"] + }, + "include": ["./scripts", "./test", "./deploy", "./typechain"] +} diff --git a/nix/l1-contracts.nix b/nix/l1-contracts.nix new file mode 100644 index 00000000..6ed3b7ac --- /dev/null +++ b/nix/l1-contracts.nix @@ -0,0 +1,20 @@ +{ lib +, stdenv +}: +let + inherit (lib) optional; +in +stdenv.mkDerivation { + + name = "l1-contracts"; + pname = "l1-contracts"; + + src = lib.sourceByRegex ./.. [ + "^l1-contracts(/.*)?$" + ]; + + buildPhase = '' + cp -r ./l1-contracts/ $out/ + ''; + +}