diff --git a/.gitmodules b/.gitmodules index 322d68162..66888488c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ url = https://github.com/OpenZeppelin/openzeppelin-contracts [submodule "dao/dao/Openzeppelin-DAO"] path = dao/dao/Openzeppelin-DAO url = https://github.com/tamphill/openzeppelin_dao.git +[submodule "basic/79-hardhat-foundry/lib/openzeppelin-contracts"] + path = basic/79-hardhat-foundry/lib/openzeppelin-contracts + url = https://github.com/Openzeppelin/openzeppelin-contracts diff --git a/basic/07-hardhat/contracts/Greeter.sol b/basic/07-hardhat/contracts/Greeter.sol index 60502c5ad..e70e5bbf4 100644 --- a/basic/07-hardhat/contracts/Greeter.sol +++ b/basic/07-hardhat/contracts/Greeter.sol @@ -1,9 +1,6 @@ //SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; - - - contract Greeter { string greeting; diff --git a/basic/07-hardhat/hardhat.config.js b/basic/07-hardhat/hardhat.config.js index e1e514c7b..750b7a978 100644 --- a/basic/07-hardhat/hardhat.config.js +++ b/basic/07-hardhat/hardhat.config.js @@ -21,6 +21,11 @@ function mnemonic() { module.exports = { solidity: '0.8.0', networks: { + hardhat: { + forking: { + url: 'https://mainnet.infura.io/v3/'+ process.env.INFURA_ID, + } + }, localhost: { url: 'http://localhost:8545', //gasPrice: 125000000000, // you can adjust gasPrice locally to see how much it will cost on production diff --git a/basic/07-hardhat/package.json b/basic/07-hardhat/package.json index 813f723bc..291e9c9c2 100644 --- a/basic/07-hardhat/package.json +++ b/basic/07-hardhat/package.json @@ -23,5 +23,8 @@ "hardhat-deploy-ethers": "^0.3.0-beta.13", "mocha": "^10.0.0", "sol-merger": "^3.1.0" + }, + "dependencies": { + "@openzeppelin/contracts": "^5.0.0" } } diff --git a/basic/07-hardhat/scripts/deploy.js b/basic/07-hardhat/scripts/deploy.js index 701bc9c80..61c32f4dd 100644 --- a/basic/07-hardhat/scripts/deploy.js +++ b/basic/07-hardhat/scripts/deploy.js @@ -15,10 +15,9 @@ async function main() { const Token = await ethers.getContractFactory('SimpleToken'); const token = await Token.deploy('SimpleToken', 'SimpleToken', 18, 10000000000); - console.log('Token address:', token.address); + console.log('deploy address:', token.address); - let balance = await token.balanceOf(deployer.address); - console.log(balance.toString()); + } // We recommend this pattern to be able to use async/await everywhere diff --git a/basic/22-zk-snarkjs/.gitignore b/basic/22-zk-snarkjs/.gitignore index 9a80525be..bf1344229 100644 --- a/basic/22-zk-snarkjs/.gitignore +++ b/basic/22-zk-snarkjs/.gitignore @@ -14,3 +14,4 @@ witness.wtns witness.json proof.json proving_key.json +*//multiplier2_cpp/ \ No newline at end of file diff --git a/basic/22-zk-snarkjs/circom2/Multiplier2.circom b/basic/22-zk-snarkjs/circom2/Multiplier2.circom new file mode 100644 index 000000000..de5387f20 --- /dev/null +++ b/basic/22-zk-snarkjs/circom2/Multiplier2.circom @@ -0,0 +1,16 @@ +pragma circom 2.0.0; + +/*This circuit template checks that c is the multiplication of a and b.*/ + +template Multiplier2 () { + + // Declaration of signals. + signal input a; + signal input b; + signal output c; + + // Constraints. + c <== a * b; +} + +component main = Multiplier2(); \ No newline at end of file diff --git a/basic/22-zk-snarkjs/circom2/circuit_final.zkey b/basic/22-zk-snarkjs/circom2/circuit_final.zkey new file mode 100644 index 000000000..89e471828 Binary files /dev/null and b/basic/22-zk-snarkjs/circom2/circuit_final.zkey differ diff --git a/basic/22-zk-snarkjs/circom2/multiplier2.r1cs b/basic/22-zk-snarkjs/circom2/multiplier2.r1cs new file mode 100644 index 000000000..e61b9b091 Binary files /dev/null and b/basic/22-zk-snarkjs/circom2/multiplier2.r1cs differ diff --git a/basic/22-zk-snarkjs/circom2/multiplier2.r1cs.json b/basic/22-zk-snarkjs/circom2/multiplier2.r1cs.json new file mode 100644 index 000000000..959be3988 --- /dev/null +++ b/basic/22-zk-snarkjs/circom2/multiplier2.r1cs.json @@ -0,0 +1,34 @@ +{ + "n8": 32, + "prime": "21888242871839275222246405745257275088548364400416034343698204186575808495617", + "nVars": 4, + "nOutputs": 1, + "nPubInputs": 0, + "nPrvInputs": 2, + "nLabels": 4, + "nConstraints": 1, + "useCustomGates": false, + "constraints": [ + [ + { + "2": "21888242871839275222246405745257275088548364400416034343698204186575808495616" + }, + { + "3": "1" + }, + { + "1": "21888242871839275222246405745257275088548364400416034343698204186575808495616" + } + ] + ], + "map": [ + 0, + 1, + 2, + 3 + ], + "customGates": [ + ], + "customGatesUses": [ + ] +} \ No newline at end of file diff --git a/basic/22-zk-snarkjs/circom2/multiplier2.sym b/basic/22-zk-snarkjs/circom2/multiplier2.sym new file mode 100644 index 000000000..9432aa87a --- /dev/null +++ b/basic/22-zk-snarkjs/circom2/multiplier2.sym @@ -0,0 +1,3 @@ +1,1,0,main.c +2,2,0,main.a +3,3,0,main.b diff --git a/basic/22-zk-snarkjs/circom2/multiplier2_0000.zkey b/basic/22-zk-snarkjs/circom2/multiplier2_0000.zkey new file mode 100644 index 000000000..6bf9abad0 Binary files /dev/null and b/basic/22-zk-snarkjs/circom2/multiplier2_0000.zkey differ diff --git a/basic/22-zk-snarkjs/circom2/multiplier2_0001.zkey b/basic/22-zk-snarkjs/circom2/multiplier2_0001.zkey new file mode 100644 index 000000000..b8800ab1b Binary files /dev/null and b/basic/22-zk-snarkjs/circom2/multiplier2_0001.zkey differ diff --git a/basic/22-zk-snarkjs/circom2/multiplier2_js/generate_witness.js b/basic/22-zk-snarkjs/circom2/multiplier2_js/generate_witness.js new file mode 100644 index 000000000..eabb86e58 --- /dev/null +++ b/basic/22-zk-snarkjs/circom2/multiplier2_js/generate_witness.js @@ -0,0 +1,20 @@ +const wc = require("./witness_calculator.js"); +const { readFileSync, writeFile } = require("fs"); + +if (process.argv.length != 5) { + console.log("Usage: node generate_witness.js "); +} else { + const input = JSON.parse(readFileSync(process.argv[3], "utf8")); + + const buffer = readFileSync(process.argv[2]); + wc(buffer).then(async witnessCalculator => { + // const w= await witnessCalculator.calculateWitness(input,0); + // for (let i=0; i< w.length; i++){ + // console.log(w[i]); + // } + const buff= await witnessCalculator.calculateWTNSBin(input,0); + writeFile(process.argv[4], buff, function(err) { + if (err) throw err; + }); + }); +} diff --git a/basic/22-zk-snarkjs/circom2/multiplier2_js/multiplier2.wasm b/basic/22-zk-snarkjs/circom2/multiplier2_js/multiplier2.wasm new file mode 100644 index 000000000..c8cecba52 Binary files /dev/null and b/basic/22-zk-snarkjs/circom2/multiplier2_js/multiplier2.wasm differ diff --git a/basic/22-zk-snarkjs/circom2/multiplier2_js/witness_calculator.js b/basic/22-zk-snarkjs/circom2/multiplier2_js/witness_calculator.js new file mode 100644 index 000000000..20e6e20ad --- /dev/null +++ b/basic/22-zk-snarkjs/circom2/multiplier2_js/witness_calculator.js @@ -0,0 +1,337 @@ +module.exports = async function builder(code, options) { + + options = options || {}; + + let wasmModule; + try { + wasmModule = await WebAssembly.compile(code); + } catch (err) { + console.log(err); + console.log("\nTry to run circom --c in order to generate c++ code instead\n"); + throw new Error(err); + } + + let wc; + + let errStr = ""; + let msgStr = ""; + + const instance = await WebAssembly.instantiate(wasmModule, { + runtime: { + exceptionHandler : function(code) { + let err; + if (code == 1) { + err = "Signal not found.\n"; + } else if (code == 2) { + err = "Too many signals set.\n"; + } else if (code == 3) { + err = "Signal already set.\n"; + } else if (code == 4) { + err = "Assert Failed.\n"; + } else if (code == 5) { + err = "Not enough memory.\n"; + } else if (code == 6) { + err = "Input signal array access exceeds the size.\n"; + } else { + err = "Unknown error.\n"; + } + throw new Error(err + errStr); + }, + printErrorMessage : function() { + errStr += getMessage() + "\n"; + // console.error(getMessage()); + }, + writeBufferMessage : function() { + const msg = getMessage(); + // Any calls to `log()` will always end with a `\n`, so that's when we print and reset + if (msg === "\n") { + console.log(msgStr); + msgStr = ""; + } else { + // If we've buffered other content, put a space in between the items + if (msgStr !== "") { + msgStr += " " + } + // Then append the message to the message we are creating + msgStr += msg; + } + }, + showSharedRWMemory : function() { + printSharedRWMemory (); + } + + } + }); + + const sanityCheck = + options +// options && +// ( +// options.sanityCheck || +// options.logGetSignal || +// options.logSetSignal || +// options.logStartComponent || +// options.logFinishComponent +// ); + + + wc = new WitnessCalculator(instance, sanityCheck); + return wc; + + function getMessage() { + var message = ""; + var c = instance.exports.getMessageChar(); + while ( c != 0 ) { + message += String.fromCharCode(c); + c = instance.exports.getMessageChar(); + } + return message; + } + + function printSharedRWMemory () { + const shared_rw_memory_size = instance.exports.getFieldNumLen32(); + const arr = new Uint32Array(shared_rw_memory_size); + for (let j=0; j { + const h = fnvHash(k); + const hMSB = parseInt(h.slice(0,8), 16); + const hLSB = parseInt(h.slice(8,16), 16); + const fArr = flatArray(input[k]); + let signalSize = this.instance.exports.getInputSignalSize(hMSB, hLSB); + if (signalSize < 0){ + throw new Error(`Signal ${k} not found\n`); + } + if (fArr.length < signalSize) { + throw new Error(`Not enough values for input signal ${k}\n`); + } + if (fArr.length > signalSize) { + throw new Error(`Too many values for input signal ${k}\n`); + } + for (let i=0; i0) { + res.unshift(0); + i--; + } + } + return res; +} + +function fromArray32(arr) { //returns a BigInt + var res = BigInt(0); + const radix = BigInt(0x100000000); + for (let i = 0; i. +*/ + +pragma solidity >=0.7.0 <0.9.0; + +contract Groth16Verifier { + // Scalar field size + uint256 constant r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // Verification Key data + uint256 constant alphax = 5253868672545201124648698677088289555142021129357716645696773054382726861037; + uint256 constant alphay = 16767295459177503158737273790991340982009586210896243194337350327655975298969; + uint256 constant betax1 = 1517177208055623231036383643157791737914004038543285091176976763810633516024; + uint256 constant betax2 = 13020819549771277211610313769058699131150885822436659242252693902346979949689; + uint256 constant betay1 = 7307514811841853286384695995993737967931697761470244287285024542422346434107; + uint256 constant betay2 = 14093737879491093671247320146695948607359517051041418927268729764932906538891; + uint256 constant gammax1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant gammax2 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant gammay1 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + uint256 constant gammay2 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + uint256 constant deltax1 = 3068521436480821370597636270305818368787362657628357104531391290259323356873; + uint256 constant deltax2 = 4388367894020072747245833611639303477786255804865803676095639566942415152421; + uint256 constant deltay1 = 149614604007101980574802460526087370915479140660998551971860879828324825493; + uint256 constant deltay2 = 20488555401460498307150365361877239532200986507036709879609922659129974700879; + + + uint256 constant IC0x = 20051042181581545479939447722357565201638036042374374239908364633108925846952; + uint256 constant IC0y = 19036623794848954414824512606821531123311848022955437018241695756043257614093; + + uint256 constant IC1x = 16305314406124537814345102825906381211799744194477046235563220479013565680400; + uint256 constant IC1y = 8339568598412892344378130691157116347853089604043378402456574632391806900354; + + + // Memory data + uint16 constant pVk = 0; + uint16 constant pPairing = 128; + + uint16 constant pLastMem = 896; + + function verifyProof(uint[2] calldata _pA, uint[2][2] calldata _pB, uint[2] calldata _pC, uint[1] calldata _pubSignals) public view returns (bool) { + assembly { + function checkField(v) { + if iszero(lt(v, q)) { + mstore(0, 0) + return(0, 0x20) + } + } + + // G1 function to multiply a G1 value(x,y) to value in an address + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn, 32), y) + mstore(add(mIn, 64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + + mstore(add(mIn, 64), mload(pR)) + mstore(add(mIn, 96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0, 0x20) + } + } + + function checkPairing(pA, pB, pC, pubSignals, pMem) -> isOk { + let _pPairing := add(pMem, pPairing) + let _pVk := add(pMem, pVk) + + mstore(_pVk, IC0x) + mstore(add(_pVk, 32), IC0y) + + // Compute the linear combination vk_x + + g1_mulAccC(_pVk, IC1x, IC1y, calldataload(add(pubSignals, 0))) + + + // -A + mstore(_pPairing, calldataload(pA)) + mstore(add(_pPairing, 32), mod(sub(q, calldataload(add(pA, 32))), q)) + + // B + mstore(add(_pPairing, 64), calldataload(pB)) + mstore(add(_pPairing, 96), calldataload(add(pB, 32))) + mstore(add(_pPairing, 128), calldataload(add(pB, 64))) + mstore(add(_pPairing, 160), calldataload(add(pB, 96))) + + // alpha1 + mstore(add(_pPairing, 192), alphax) + mstore(add(_pPairing, 224), alphay) + + // beta2 + mstore(add(_pPairing, 256), betax1) + mstore(add(_pPairing, 288), betax2) + mstore(add(_pPairing, 320), betay1) + mstore(add(_pPairing, 352), betay2) + + // vk_x + mstore(add(_pPairing, 384), mload(add(pMem, pVk))) + mstore(add(_pPairing, 416), mload(add(pMem, add(pVk, 32)))) + + + // gamma2 + mstore(add(_pPairing, 448), gammax1) + mstore(add(_pPairing, 480), gammax2) + mstore(add(_pPairing, 512), gammay1) + mstore(add(_pPairing, 544), gammay2) + + // C + mstore(add(_pPairing, 576), calldataload(pC)) + mstore(add(_pPairing, 608), calldataload(add(pC, 32))) + + // delta2 + mstore(add(_pPairing, 640), deltax1) + mstore(add(_pPairing, 672), deltax2) + mstore(add(_pPairing, 704), deltay1) + mstore(add(_pPairing, 736), deltay2) + + + let success := staticcall(sub(gas(), 2000), 8, _pPairing, 768, _pPairing, 0x20) + + isOk := and(success, mload(_pPairing)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, pLastMem)) + + // Validate that all evaluations ∈ F + + checkField(calldataload(add(_pubSignals, 0))) + + checkField(calldataload(add(_pubSignals, 32))) + + + // Validate all evaluations + let isValid := checkPairing(_pA, _pB, _pC, _pubSignals, pMem) + + mstore(0, isValid) + return(0, 0x20) + } + } + } diff --git a/basic/22-zk-snarkjs/circom2/pot12_0000.ptau b/basic/22-zk-snarkjs/circom2/pot12_0000.ptau new file mode 100644 index 000000000..d1fc4fa87 Binary files /dev/null and b/basic/22-zk-snarkjs/circom2/pot12_0000.ptau differ diff --git a/basic/22-zk-snarkjs/circom2/pot12_0001.ptau b/basic/22-zk-snarkjs/circom2/pot12_0001.ptau new file mode 100644 index 000000000..647a59ddc Binary files /dev/null and b/basic/22-zk-snarkjs/circom2/pot12_0001.ptau differ diff --git a/basic/22-zk-snarkjs/circom2/pot12_final.ptau b/basic/22-zk-snarkjs/circom2/pot12_final.ptau new file mode 100644 index 000000000..ce86049f8 Binary files /dev/null and b/basic/22-zk-snarkjs/circom2/pot12_final.ptau differ diff --git a/basic/22-zk-snarkjs/circom2/public.json b/basic/22-zk-snarkjs/circom2/public.json new file mode 100644 index 000000000..bf2030970 --- /dev/null +++ b/basic/22-zk-snarkjs/circom2/public.json @@ -0,0 +1,3 @@ +[ + "33" +] \ No newline at end of file diff --git a/basic/22-zk-snarkjs/circom2/readme.md b/basic/22-zk-snarkjs/circom2/readme.md new file mode 100644 index 000000000..8793fbaf1 --- /dev/null +++ b/basic/22-zk-snarkjs/circom2/readme.md @@ -0,0 +1,23 @@ +# circom2 + + +## circom 语法 + + 1. <-- assigns a value to a signal without adding a constraint. + 2. Whereas === adds a constraint without assigning a value. + 3. <== both assigns a value to a signal and adds a contraint。Which means it’s just the combination of === and <--. + +## 参考资料 + +- 创建第一个零知识 snark 电路: +- 参考文档: +- circom2 doc: +- snarkjs: +- 深入浅出零知识证明之 zk-SNARKs: +- ZK Jargon Decoder: +- CTN rollup 分享: +- zkRollup tutorial: +- 零知识证明的可信设置是否值得担心?: +- Diving into the zk-SNARKs Setup Phase: +- scaffold-eth-zk: +- online circom editor: https://zkrepl.dev/ \ No newline at end of file diff --git a/basic/22-zk-snarkjs/circom2/verification_key.json b/basic/22-zk-snarkjs/circom2/verification_key.json new file mode 100644 index 000000000..e6bbc2ca9 --- /dev/null +++ b/basic/22-zk-snarkjs/circom2/verification_key.json @@ -0,0 +1,63 @@ +{ + "protocol": "plonk", + "curve": "bn128", + "nPublic": 1, + "power": 3, + "k1": "2", + "k2": "3", + "Qm": [ + "6699088001123109952183962588223396229634400680774617861482376109866173590415", + "10472862863404696730237000817687961982481823937409036496392612294007500191870", + "1" + ], + "Ql": [ + "7653042754748282460925155674115802066186740095112438098089677730825851742561", + "3881489376554609598818401640548973087844286524086744140689566043079220234653", + "1" + ], + "Qr": [ + "0", + "1", + "0" + ], + "Qo": [ + "6699088001123109952183962588223396229634400680774617861482376109866173590415", + "11415380008434578492009404927569313106214487219888787166296425600637726016713", + "1" + ], + "Qc": [ + "0", + "1", + "0" + ], + "S1": [ + "12595984855861242061649666268418958443285151063605611873146794297408558416836", + "13705084462179478240736413689935073047312759999024126566597654725029421808009", + "1" + ], + "S2": [ + "16285909367754550288358159035997699094368387144009879119019270648519999440116", + "1295729502968671379118165227997341875919978495457150719822893621307947271783", + "1" + ], + "S3": [ + "14606951403545053284258360588318930654368768246829249183502572774985607977923", + "9505097472223332796829678678148885853274639253871716812065493604196418738384", + "1" + ], + "X_2": [ + [ + "18935944361004143412805704977309399715739170487629982915447996963383767772739", + "6811002817459883196920556250750369153193187180198975763660544368610033000117" + ], + [ + "21721768279008477101272404552028427623562871621192112392566662061814769383663", + "20846124843981009602488447623630715579517110693802445484941301159200541614978" + ], + [ + "1", + "0" + ] + ], + "w": "19540430494807482326159819597004422086093766032135589407132600596362845576832" +} \ No newline at end of file diff --git a/basic/22-zk-snarkjs/circom2/verifier.sol b/basic/22-zk-snarkjs/circom2/verifier.sol new file mode 100644 index 000000000..524199137 --- /dev/null +++ b/basic/22-zk-snarkjs/circom2/verifier.sol @@ -0,0 +1,697 @@ +// SPDX-License-Identifier: GPL-3.0 +/* + Copyright 2021 0KIMS association. + + This file is generated with [snarkJS](https://github.com/iden3/snarkjs). + + snarkJS is a free software: you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + snarkJS is distributed in the hope that it will be useful, but WITHOUT + ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public + License for more details. + + You should have received a copy of the GNU General Public License + along with snarkJS. If not, see . +*/ + + +pragma solidity >=0.7.0 <0.9.0; + +import "hardhat/console.sol"; + +contract PlonkVerifier { + // Omega + uint256 constant w1 = 19540430494807482326159819597004422086093766032135589407132600596362845576832; + // Scalar field size + uint256 constant q = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // Base field size + uint256 constant qf = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // [1]_1 + uint256 constant G1x = 1; + uint256 constant G1y = 2; + // [1]_2 + uint256 constant G2x1 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant G2x2 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant G2y1 = 8495653923123431417604973247489272438418190587263600148770280649306958101930; + uint256 constant G2y2 = 4082367875863433681332203403145435568316851327593401208105741076214120093531; + + // Verification Key data + uint32 constant n = 8; + uint16 constant nPublic = 1; + uint16 constant nLagrange = 1; + + uint256 constant Qmx = 6699088001123109952183962588223396229634400680774617861482376109866173590415; + uint256 constant Qmy = 10472862863404696730237000817687961982481823937409036496392612294007500191870; + uint256 constant Qlx = 7653042754748282460925155674115802066186740095112438098089677730825851742561; + uint256 constant Qly = 3881489376554609598818401640548973087844286524086744140689566043079220234653; + uint256 constant Qrx = 0; + uint256 constant Qry = 0; + uint256 constant Qox = 6699088001123109952183962588223396229634400680774617861482376109866173590415; + uint256 constant Qoy = 11415380008434578492009404927569313106214487219888787166296425600637726016713; + uint256 constant Qcx = 0; + uint256 constant Qcy = 0; + uint256 constant S1x = 12595984855861242061649666268418958443285151063605611873146794297408558416836; + uint256 constant S1y = 13705084462179478240736413689935073047312759999024126566597654725029421808009; + uint256 constant S2x = 16285909367754550288358159035997699094368387144009879119019270648519999440116; + uint256 constant S2y = 1295729502968671379118165227997341875919978495457150719822893621307947271783; + uint256 constant S3x = 14606951403545053284258360588318930654368768246829249183502572774985607977923; + uint256 constant S3y = 9505097472223332796829678678148885853274639253871716812065493604196418738384; + uint256 constant k1 = 2; + uint256 constant k2 = 3; + uint256 constant X2x1 = 18935944361004143412805704977309399715739170487629982915447996963383767772739; + uint256 constant X2x2 = 6811002817459883196920556250750369153193187180198975763660544368610033000117; + uint256 constant X2y1 = 21721768279008477101272404552028427623562871621192112392566662061814769383663; + uint256 constant X2y2 = 20846124843981009602488447623630715579517110693802445484941301159200541614978; + + // Proof calldata + // Byte offset of every parameter of the calldata + // Polynomial commitments + uint16 constant pA = 4 + 0; + uint16 constant pB = 4 + 64; + uint16 constant pC = 4 + 128; + uint16 constant pZ = 4 + 192; + uint16 constant pT1 = 4 + 256; + uint16 constant pT2 = 4 + 320; + uint16 constant pT3 = 4 + 384; + uint16 constant pWxi = 4 + 448; + uint16 constant pWxiw = 4 + 512; + // Opening evaluations + uint16 constant pEval_a = 4 + 576; + uint16 constant pEval_b = 4 + 608; + uint16 constant pEval_c = 4 + 640; + uint16 constant pEval_s1 = 4 + 672; + uint16 constant pEval_s2 = 4 + 704; + uint16 constant pEval_zw = 4 + 736; + + // Memory data + // Challenges + uint16 constant pAlpha = 0; + uint16 constant pBeta = 32; + uint16 constant pGamma = 64; + uint16 constant pXi = 96; + uint16 constant pXin = 128; + uint16 constant pBetaXi = 160; + uint16 constant pV1 = 192; + uint16 constant pV2 = 224; + uint16 constant pV3 = 256; + uint16 constant pV4 = 288; + uint16 constant pV5 = 320; + uint16 constant pU = 352; + + uint16 constant pPI = 384; + uint16 constant pEval_r0 = 416; + uint16 constant pD = 448; + uint16 constant pF = 512; + uint16 constant pE = 576; + uint16 constant pTmp = 640; + uint16 constant pAlpha2 = 704; + uint16 constant pZh = 736; + uint16 constant pZhInv = 768; + + + uint16 constant pEval_l1 = 800; + + + + uint16 constant lastMem = 832; + + function verifyProof(uint256[24] calldata _proof, uint256[1] calldata _pubSignals) public view returns (bool) { + assembly { + ///////// + // Computes the inverse using the extended euclidean algorithm + ///////// + function inverse(a, q) -> inv { + let t := 0 + let newt := 1 + let r := q + let newr := a + let quotient + let aux + + for { } newr { } { + quotient := sdiv(r, newr) + aux := sub(t, mul(quotient, newt)) + t:= newt + newt:= aux + + aux := sub(r,mul(quotient, newr)) + r := newr + newr := aux + } + + if gt(r, 1) { revert(0,0) } + if slt(t, 0) { t:= add(t, q) } + + inv := t + } + + /////// + // Computes the inverse of an array of values + // See https://vitalik.ca/general/2018/07/21/starks_part_3.html in section where explain fields operations + ////// + function inverseArray(pVals, n) { + + let pAux := mload(0x40) // Point to the next free position + let pIn := pVals + let lastPIn := add(pVals, mul(n, 32)) // Read n elemnts + let acc := mload(pIn) // Read the first element + pIn := add(pIn, 32) // Point to the second element + let inv + + + for { } lt(pIn, lastPIn) { + pAux := add(pAux, 32) + pIn := add(pIn, 32) + } + { + mstore(pAux, acc) + acc := mulmod(acc, mload(pIn), q) + } + acc := inverse(acc, q) + + // At this point pAux pint to the next free position we substract 1 to point to the last used + pAux := sub(pAux, 32) + // pIn points to the n+1 element, we substract to point to n + pIn := sub(pIn, 32) + lastPIn := pVals // We don't process the first element + for { } gt(pIn, lastPIn) { + pAux := sub(pAux, 32) + pIn := sub(pIn, 32) + } + { + inv := mulmod(acc, mload(pAux), q) + acc := mulmod(acc, mload(pIn), q) + mstore(pIn, inv) + } + // pIn points to first element, we just set it. + mstore(pIn, acc) + } + + function checkField(v) { + if iszero(lt(v, q)) { + mstore(0, 0) + return(0,0x20) + } + } + + function checkInput() { + checkField(calldataload(pEval_a)) + checkField(calldataload(pEval_b)) + checkField(calldataload(pEval_c)) + checkField(calldataload(pEval_s1)) + checkField(calldataload(pEval_s2)) + checkField(calldataload(pEval_zw)) + } + + function calculateChallenges(pMem, pPublic) { + let beta + let aux + + let mIn := mload(0x40) // Pointer to the next free memory position + + // Compute challenge.beta & challenge.gamma + mstore(mIn, Qmx) + mstore(add(mIn, 32), Qmy) + mstore(add(mIn, 64), Qlx) + mstore(add(mIn, 96), Qly) + mstore(add(mIn, 128), Qrx) + mstore(add(mIn, 160), Qry) + mstore(add(mIn, 192), Qox) + mstore(add(mIn, 224), Qoy) + mstore(add(mIn, 256), Qcx) + mstore(add(mIn, 288), Qcy) + mstore(add(mIn, 320), S1x) + mstore(add(mIn, 352), S1y) + mstore(add(mIn, 384), S2x) + mstore(add(mIn, 416), S2y) + mstore(add(mIn, 448), S3x) + mstore(add(mIn, 480), S3y) + + + mstore(add(mIn, 512), calldataload(add(pPublic, 0))) + + mstore(add(mIn, 544 ), calldataload(pA)) + mstore(add(mIn, 576 ), calldataload(add(pA, 32))) + mstore(add(mIn, 608 ), calldataload(pB)) + mstore(add(mIn, 640 ), calldataload(add(pB, 32))) + mstore(add(mIn, 672 ), calldataload(pC)) + mstore(add(mIn, 704 ), calldataload(add(pC, 32))) + + beta := mod(keccak256(mIn, 736), q) + mstore(add(pMem, pBeta), beta) + + // challenges.gamma + mstore(add(pMem, pGamma), mod(keccak256(add(pMem, pBeta), 32), q)) + + // challenges.alpha + mstore(mIn, mload(add(pMem, pBeta))) + mstore(add(mIn, 32), mload(add(pMem, pGamma))) + mstore(add(mIn, 64), calldataload(pZ)) + mstore(add(mIn, 96), calldataload(add(pZ, 32))) + + aux := mod(keccak256(mIn, 128), q) + mstore(add(pMem, pAlpha), aux) + mstore(add(pMem, pAlpha2), mulmod(aux, aux, q)) + + // challenges.xi + mstore(mIn, aux) + mstore(add(mIn, 32), calldataload(pT1)) + mstore(add(mIn, 64), calldataload(add(pT1, 32))) + mstore(add(mIn, 96), calldataload(pT2)) + mstore(add(mIn, 128), calldataload(add(pT2, 32))) + mstore(add(mIn, 160), calldataload(pT3)) + mstore(add(mIn, 192), calldataload(add(pT3, 32))) + + aux := mod(keccak256(mIn, 224), q) + mstore( add(pMem, pXi), aux) + + // challenges.v + mstore(mIn, aux) + mstore(add(mIn, 32), calldataload(pEval_a)) + mstore(add(mIn, 64), calldataload(pEval_b)) + mstore(add(mIn, 96), calldataload(pEval_c)) + mstore(add(mIn, 128), calldataload(pEval_s1)) + mstore(add(mIn, 160), calldataload(pEval_s2)) + mstore(add(mIn, 192), calldataload(pEval_zw)) + + let v1 := mod(keccak256(mIn, 224), q) + mstore(add(pMem, pV1), v1) + + // challenges.beta * challenges.xi + mstore(add(pMem, pBetaXi), mulmod(beta, aux, q)) + + // challenges.xi^n + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + aux:= mulmod(aux, aux, q) + + mstore(add(pMem, pXin), aux) + + // Zh + aux:= mod(add(sub(aux, 1), q), q) + mstore(add(pMem, pZh), aux) + mstore(add(pMem, pZhInv), aux) // We will invert later together with lagrange pols + + // challenges.v^2, challenges.v^3, challenges.v^4, challenges.v^5 + aux := mulmod(v1, v1, q) + mstore(add(pMem, pV2), aux) + aux := mulmod(aux, v1, q) + mstore(add(pMem, pV3), aux) + aux := mulmod(aux, v1, q) + mstore(add(pMem, pV4), aux) + aux := mulmod(aux, v1, q) + mstore(add(pMem, pV5), aux) + + // challenges.u + mstore(mIn, calldataload(pWxi)) + mstore(add(mIn, 32), calldataload(add(pWxi, 32))) + mstore(add(mIn, 64), calldataload(pWxiw)) + mstore(add(mIn, 96), calldataload(add(pWxiw, 32))) + + mstore(add(pMem, pU), mod(keccak256(mIn, 128), q)) + } + + function calculateLagrange(pMem) { + let w := 1 + + mstore( + add(pMem, pEval_l1), + mulmod( + n, + mod( + add( + sub( + mload(add(pMem, pXi)), + w + ), + q + ), + q + ), + q + ) + ) + + + + inverseArray(add(pMem, pZhInv), 2 ) + + let zh := mload(add(pMem, pZh)) + w := 1 + + + mstore( + add(pMem, pEval_l1 ), + mulmod( + mload(add(pMem, pEval_l1 )), + zh, + q + ) + ) + + + + + + } + + function calculatePI(pMem, pPub) { + let pl := 0 + + + pl := mod( + add( + sub( + pl, + mulmod( + mload(add(pMem, pEval_l1)), + calldataload(add(pPub, 0)), + q + ) + ), + q + ), + q + ) + + + mstore(add(pMem, pPI), pl) + } + + function calculateR0(pMem) { + let e1 := mload(add(pMem, pPI)) + + let e2 := mulmod(mload(add(pMem, pEval_l1)), mload(add(pMem, pAlpha2)), q) + + let e3a := addmod( + calldataload(pEval_a), + mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s1), q), + q) + e3a := addmod(e3a, mload(add(pMem, pGamma)), q) + + let e3b := addmod( + calldataload(pEval_b), + mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s2), q), + q) + e3b := addmod(e3b, mload(add(pMem, pGamma)), q) + + let e3c := addmod( + calldataload(pEval_c), + mload(add(pMem, pGamma)), + q) + + let e3 := mulmod(mulmod(e3a, e3b, q), e3c, q) + e3 := mulmod(e3, calldataload(pEval_zw), q) + e3 := mulmod(e3, mload(add(pMem, pAlpha)), q) + + let r0 := addmod(e1, mod(sub(q, e2), q), q) + r0 := addmod(r0, mod(sub(q, e3), q), q) + + mstore(add(pMem, pEval_r0) , r0) + } + + function g1_set(pR, pP) { + mstore(pR, mload(pP)) + mstore(add(pR, 32), mload(add(pP,32))) + } + + function g1_setC(pR, x, y) { + mstore(pR, x) + mstore(add(pR, 32), y) + } + + function g1_calldataSet(pR, pP) { + mstore(pR, calldataload(pP)) + mstore(add(pR, 32), calldataload(add(pP, 32))) + } + + function g1_acc(pR, pP) { + let mIn := mload(0x40) + mstore(mIn, mload(pR)) + mstore(add(mIn,32), mload(add(pR, 32))) + mstore(add(mIn,64), mload(pP)) + mstore(add(mIn,96), mload(add(pP, 32))) + + let success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + } + + function g1_mulAcc(pR, pP, s) { + let success + let mIn := mload(0x40) + mstore(mIn, mload(pP)) + mstore(add(mIn,32), mload(add(pP, 32))) + mstore(add(mIn,64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + + mstore(add(mIn,64), mload(pR)) + mstore(add(mIn,96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + + } + + function g1_mulAccC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn,32), y) + mstore(add(mIn,64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, mIn, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + + mstore(add(mIn,64), mload(pR)) + mstore(add(mIn,96), mload(add(pR, 32))) + + success := staticcall(sub(gas(), 2000), 6, mIn, 128, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + } + + function g1_mulSetC(pR, x, y, s) { + let success + let mIn := mload(0x40) + mstore(mIn, x) + mstore(add(mIn,32), y) + mstore(add(mIn,64), s) + + success := staticcall(sub(gas(), 2000), 7, mIn, 96, pR, 64) + + if iszero(success) { + mstore(0, 0) + return(0,0x20) + } + } + + function g1_mulSet(pR, pP, s) { + g1_mulSetC(pR, mload(pP), mload(add(pP, 32)), s) + } + + function calculateD(pMem) { + let _pD:= add(pMem, pD) + let gamma := mload(add(pMem, pGamma)) + let mIn := mload(0x40) + mstore(0x40, add(mIn, 256)) // d1, d2, d3 & d4 (4*64 bytes) + + g1_setC(_pD, Qcx, Qcy) + g1_mulAccC(_pD, Qmx, Qmy, mulmod(calldataload(pEval_a), calldataload(pEval_b), q)) + g1_mulAccC(_pD, Qlx, Qly, calldataload(pEval_a)) + g1_mulAccC(_pD, Qrx, Qry, calldataload(pEval_b)) + g1_mulAccC(_pD, Qox, Qoy, calldataload(pEval_c)) + + let betaxi := mload(add(pMem, pBetaXi)) + let val1 := addmod( + addmod(calldataload(pEval_a), betaxi, q), + gamma, q) + + let val2 := addmod( + addmod( + calldataload(pEval_b), + mulmod(betaxi, k1, q), + q), gamma, q) + + let val3 := addmod( + addmod( + calldataload(pEval_c), + mulmod(betaxi, k2, q), + q), gamma, q) + + let d2a := mulmod( + mulmod(mulmod(val1, val2, q), val3, q), + mload(add(pMem, pAlpha)), + q + ) + + let d2b := mulmod( + mload(add(pMem, pEval_l1)), + mload(add(pMem, pAlpha2)), + q + ) + + // We'll use mIn to save d2 + g1_calldataSet(add(mIn, 192), pZ) + g1_mulSet( + mIn, + add(mIn, 192), + addmod(addmod(d2a, d2b, q), mload(add(pMem, pU)), q)) + + + val1 := addmod( + addmod( + calldataload(pEval_a), + mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s1), q), + q), gamma, q) + + val2 := addmod( + addmod( + calldataload(pEval_b), + mulmod(mload(add(pMem, pBeta)), calldataload(pEval_s2), q), + q), gamma, q) + + val3 := mulmod( + mulmod(mload(add(pMem, pAlpha)), mload(add(pMem, pBeta)), q), + calldataload(pEval_zw), q) + + + // We'll use mIn + 64 to save d3 + g1_mulSetC( + add(mIn, 64), + S3x, + S3y, + mulmod(mulmod(val1, val2, q), val3, q)) + + // We'll use mIn + 128 to save d4 + g1_calldataSet(add(mIn, 128), pT1) + + g1_mulAccC(add(mIn, 128), calldataload(pT2), calldataload(add(pT2, 32)), mload(add(pMem, pXin))) + let xin2 := mulmod(mload(add(pMem, pXin)), mload(add(pMem, pXin)), q) + g1_mulAccC(add(mIn, 128), calldataload(pT3), calldataload(add(pT3, 32)) , xin2) + + g1_mulSetC(add(mIn, 128), mload(add(mIn, 128)), mload(add(mIn, 160)), mload(add(pMem, pZh))) + + mstore(add(add(mIn, 64), 32), mod(sub(qf, mload(add(add(mIn, 64), 32))), qf)) + mstore(add(mIn, 160), mod(sub(qf, mload(add(mIn, 160))), qf)) + g1_acc(_pD, mIn) + g1_acc(_pD, add(mIn, 64)) + g1_acc(_pD, add(mIn, 128)) + } + + function calculateF(pMem) { + let p := add(pMem, pF) + + g1_set(p, add(pMem, pD)) + g1_mulAccC(p, calldataload(pA), calldataload(add(pA, 32)), mload(add(pMem, pV1))) + g1_mulAccC(p, calldataload(pB), calldataload(add(pB, 32)), mload(add(pMem, pV2))) + g1_mulAccC(p, calldataload(pC), calldataload(add(pC, 32)), mload(add(pMem, pV3))) + g1_mulAccC(p, S1x, S1y, mload(add(pMem, pV4))) + g1_mulAccC(p, S2x, S2y, mload(add(pMem, pV5))) + } + + function calculateE(pMem) { + let s := mod(sub(q, mload(add(pMem, pEval_r0))), q) + + s := addmod(s, mulmod(calldataload(pEval_a), mload(add(pMem, pV1)), q), q) + s := addmod(s, mulmod(calldataload(pEval_b), mload(add(pMem, pV2)), q), q) + s := addmod(s, mulmod(calldataload(pEval_c), mload(add(pMem, pV3)), q), q) + s := addmod(s, mulmod(calldataload(pEval_s1), mload(add(pMem, pV4)), q), q) + s := addmod(s, mulmod(calldataload(pEval_s2), mload(add(pMem, pV5)), q), q) + s := addmod(s, mulmod(calldataload(pEval_zw), mload(add(pMem, pU)), q), q) + + g1_mulSetC(add(pMem, pE), G1x, G1y, s) + } + + function checkPairing(pMem) -> isOk { + let mIn := mload(0x40) + mstore(0x40, add(mIn, 576)) // [0..383] = pairing data, [384..447] = pWxi, [448..512] = pWxiw + + let _pWxi := add(mIn, 384) + let _pWxiw := add(mIn, 448) + let _aux := add(mIn, 512) + + g1_calldataSet(_pWxi, pWxi) + g1_calldataSet(_pWxiw, pWxiw) + + // A1 + g1_mulSet(mIn, _pWxiw, mload(add(pMem, pU))) + g1_acc(mIn, _pWxi) + mstore(add(mIn, 32), mod(sub(qf, mload(add(mIn, 32))), qf)) + + // [X]_2 + mstore(add(mIn,64), X2x2) + mstore(add(mIn,96), X2x1) + mstore(add(mIn,128), X2y2) + mstore(add(mIn,160), X2y1) + + // B1 + g1_mulSet(add(mIn, 192), _pWxi, mload(add(pMem, pXi))) + + let s := mulmod(mload(add(pMem, pU)), mload(add(pMem, pXi)), q) + s := mulmod(s, w1, q) + g1_mulSet(_aux, _pWxiw, s) + g1_acc(add(mIn, 192), _aux) + g1_acc(add(mIn, 192), add(pMem, pF)) + mstore(add(pMem, add(pE, 32)), mod(sub(qf, mload(add(pMem, add(pE, 32)))), qf)) + g1_acc(add(mIn, 192), add(pMem, pE)) + + // [1]_2 + mstore(add(mIn,256), G2x2) + mstore(add(mIn,288), G2x1) + mstore(add(mIn,320), G2y2) + mstore(add(mIn,352), G2y1) + + let success := staticcall(sub(gas(), 2000), 8, mIn, 384, mIn, 0x20) + + isOk := and(success, mload(mIn)) + } + + let pMem := mload(0x40) + mstore(0x40, add(pMem, lastMem)) + + checkInput() + calculateChallenges(pMem, _pubSignals) + calculateLagrange(pMem) + calculatePI(pMem, _pubSignals) + calculateR0(pMem) + calculateD(pMem) + calculateF(pMem) + calculateE(pMem) + let isValid := checkPairing(pMem) + + mstore(0x40, sub(pMem, lastMem)) + mstore(0, isValid) + return(0,0x20) + } + + } +} \ No newline at end of file diff --git a/basic/30-zksync-layer2/zkSync2.0-examples/package.json b/basic/30-zksync-layer2/zkSync2.0-examples/package.json index 604ff828b..bad56b621 100644 --- a/basic/30-zksync-layer2/zkSync2.0-examples/package.json +++ b/basic/30-zksync-layer2/zkSync2.0-examples/package.json @@ -20,7 +20,7 @@ "babel-eslint": "^10.1.0", "eslint": "^6.7.2", "eslint-plugin-vue": "^6.2.2", - "ethers": "^5.6.2", + "ethers": "^5.5.4", "hardhat": "^2.9.3", "ts-node": "^10.7.0", "typescript": "^4.6.3", diff --git a/basic/34-scroll-layer2/contracts/MultipleSend.sol b/basic/34-scroll-layer2/contracts/MultipleSend.sol new file mode 100644 index 000000000..31a33a012 --- /dev/null +++ b/basic/34-scroll-layer2/contracts/MultipleSend.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +interface IERC20 { + function transferFrom(address from, address to, uint256 amount) external returns (bool); + function balanceOf(address account) external view returns (uint256); + function allowance(address owner, address spender) external view returns (uint256); +} + +contract MultipleEtherAndTokenTransfer { + address public owner; + mapping(address => bool) public admins; + + constructor() { + owner = msg.sender; + admins[owner] = true; // The owner is automatically an admin + + } + + modifier onlyOwner() { + require(msg.sender == owner, "Not the owner"); + _; + } + + modifier onlyAdmin() { + require(msg.sender == owner || admins[msg.sender], "Not an admin"); + _; + } + + // Function to transfer ownership of the contract + function transferOwnership(address newOwner) public onlyOwner { + require(newOwner != address(0), "New owner cannot be the zero address"); + owner = newOwner; + admins[newOwner] = true; // Make the new owner an admin + } + + // Function to add an admin + function addAdmin(address _admin) public onlyOwner { + admins[_admin] = true; + } + + // Function to remove an admin + function removeAdmin(address _admin) public onlyOwner { + require(_admin != owner, "Cannot remove owner"); + admins[_admin] = false; + } + + + function transferMultipleEther(address[] memory _to, uint[] memory _amount) public payable onlyAdmin { + require(_to.length == _amount.length, "Arrays must be of the same length"); + + for (uint i = 0; i < _to.length; i++) { + payable(_to[i]).transfer(_amount[i]); + } + } + + function transferMultipleERC20(IERC20 token, address[] memory _to, uint[] memory _amount) public onlyAdmin { + require(_to.length == _amount.length, "Arrays must be of the same length"); + + uint totalAmount = 0; + for (uint i = 0; i < _amount.length; i++) { + totalAmount += _amount[i]; + } + + require(token.balanceOf(msg.sender) >= totalAmount, "Insufficient balance"); + require(token.allowance(msg.sender, address(this)) >= totalAmount, "Insufficient allowance"); + + for (uint i = 0; i < _to.length; i++) { + require(token.transferFrom(msg.sender, _to[i], _amount[i]), "Transfer failed"); + } + } + + function withdrawEther() public onlyAdmin { + uint balance = address(this).balance; + require(balance > 0, "No funds to withdraw"); + payable(owner).transfer(balance); + } + + function withdrawERC20(IERC20 token) public onlyAdmin { + uint balance = token.balanceOf(address(this)); + require(balance > 0, "No tokens to withdraw"); + require(token.transfer(owner, balance), "Transfer failed"); + } + + + receive() external payable {} +} \ No newline at end of file diff --git a/basic/34-scroll-layer2/hardhat.config.js b/basic/34-scroll-layer2/hardhat.config.js index 1a380512c..3c9521130 100644 --- a/basic/34-scroll-layer2/hardhat.config.js +++ b/basic/34-scroll-layer2/hardhat.config.js @@ -77,6 +77,12 @@ module.exports = { accounts: [ mnemonic() ] + }, + scroll: { + url: "https://rpc.scroll.io", + accounts: [ + mnemonic() + ] }, polygonZkEVM: { url: `https://rpc.public.zkevm-test.net`, diff --git a/basic/34-scroll-layer2/scripts/deploy.js b/basic/34-scroll-layer2/scripts/deploy.js index 206edcf69..eeb37328b 100644 --- a/basic/34-scroll-layer2/scripts/deploy.js +++ b/basic/34-scroll-layer2/scripts/deploy.js @@ -4,6 +4,7 @@ // When running the script with `hardhat run