├── .env.example ├── .gitignore ├── LICENSE ├── README.md ├── contracts └── Lock.sol ├── foundry.toml ├── hardhat.config.ts ├── package.json ├── scripts └── deploy.ts ├── test └── Lock.ts ├── tsconfig.json └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1 2 | # SCROLL_TESTNET_URL=https://prealpha-rpc.scroll.io/l2 - Uncomment this line to use the pre-alpha instead of alpha 3 | SCROLL_TESTNET_URL=https://alpha-rpc.scroll.io/l2 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | coverage 4 | coverage.json 5 | typechain 6 | typechain-types 7 | 8 | #Hardhat files 9 | cache 10 | artifacts 11 | 12 | # IDE files 13 | .idea 14 | 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Scroll 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Project moved to the [scroll-guides](https://github.com/scroll-tech/scroll-guides/tree/main/gas-estimation-demo) repo. 2 | 3 | --- 4 | 5 | # Scroll Contract Deployment Demo 6 | 7 | This project demonstrates how to use hardhat or foundry to deploy a contract in Scroll's rollup network. This project contains a simple contract that will lock a certain amount of Ether in the deployed contract for a specified amount of time. 8 | 9 | 10 | ## Prerequisites 11 | 12 | - Network setup: https://guide.scroll.io/user-guide/setup 13 | 14 | 15 | ## Deploy with Hardhat 16 | 17 | 1. If you haven't already, install [nodejs](https://nodejs.org/en/download/) and [yarn](https://classic.yarnpkg.com/lang/en/docs/install). 18 | 2. Run `yarn install` to install dependencies. 19 | 3. Create a `.env` file following the example `.env.example` in the root directory. Change `PRIVATE_KEY` to your own account private key in the `.env`. 20 | 4. Run `yarn compile` to compile the contract. 21 | 5. Run `yarn deploy:scrollTestnet` to deploy the contract on the Scroll Alpha Testnet. 22 | 6. Run `yarn test` for hardhat tests. 23 | 24 | 25 | ## Deploy with Foundry 26 | 27 | 1. Install Foundry. 28 | ```shell 29 | curl -L https://foundry.paradigm.xyz | bash 30 | foundryup 31 | ``` 32 | 2. Build the project. 33 | ``` 34 | forge build 35 | ``` 36 | 3. Deploy the contract. 37 | ``` 38 | forge create --rpc-url https://alpha-rpc.scroll.io/l2 \ 39 | --value \ 40 | --constructor-args \ 41 | --private-key \ 42 | --legacy \ 43 | contracts/Lock.sol:Lock 44 | ``` 45 | - `` is the amount of `ETH` to be locked in the contract. Try setting this to some small amount, like `0.0000001ether`. 46 | - `` is the Unix timestamp after which the funds locked in the contract will become available for withdrawal. Try setting this to some Unix timestamp in the future, like `1696118400` (this Unix timestamp corresponds to October 1, 2023). 47 | 48 | For example: 49 | ``` 50 | forge create --rpc-url https://alpha-rpc.scroll.io/l2 \ 51 | --value 0.00000000002ether \ 52 | --constructor-args 1696118400 \ 53 | --private-key 0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1 \ 54 | --legacy \ 55 | contracts/Lock.sol:Lock 56 | ``` 57 | 58 | ## Support 59 | 60 | Join our Discord: https://scroll.io/ 61 | -------------------------------------------------------------------------------- /contracts/Lock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.9; 3 | 4 | contract Lock { 5 | uint public unlockTime; 6 | address payable public owner; 7 | 8 | event Withdrawal(uint amount, uint when); 9 | 10 | constructor(uint _unlockTime) payable { 11 | require( 12 | block.timestamp < _unlockTime, 13 | "Unlock time should be in the future" 14 | ); 15 | 16 | unlockTime = _unlockTime; 17 | owner = payable(msg.sender); 18 | } 19 | 20 | function withdraw() public { 21 | require(block.timestamp >= unlockTime, "You can't withdraw yet"); 22 | require(msg.sender == owner, "You aren't the owner"); 23 | 24 | emit Withdrawal(address(this).balance, block.timestamp); 25 | 26 | owner.transfer(address(this).balance); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | ffi = false 3 | fuzz_runs = 256 4 | optimizer = true 5 | optimizer_runs = 1000000 6 | remappings = [ 7 | "solmate/=lib/solmate/src/", 8 | "forge-std/=lib/forge-std/src/" 9 | ] 10 | verbosity = 1 11 | evm_version = "london" 12 | 13 | # Extreme Fuzzing CI Profile :P 14 | [profile.ci] 15 | fuzz_runs = 100_000 16 | verbosity = 4 17 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | 3 | import { HardhatUserConfig } from "hardhat/config"; 4 | import "@nomicfoundation/hardhat-toolbox"; 5 | 6 | dotenv.config(); 7 | 8 | const config: HardhatUserConfig = { 9 | solidity: { 10 | version: "0.8.9", 11 | settings: { 12 | optimizer: { 13 | enabled: true, 14 | runs: 200, 15 | }, 16 | evmVersion: "london" 17 | } 18 | }, 19 | networks: { 20 | scrollTestnet: { 21 | url: process.env.SCROLL_TESTNET_URL || "", 22 | accounts: 23 | process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], 24 | }, 25 | }, 26 | }; 27 | 28 | export default config; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scroll-deploy-contract-demo", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "compile": "npx hardhat compile", 8 | "test": "npx hardhat test", 9 | "clean": "npx hardhat clean", 10 | "deploy:hh": "npx hardhat run --network hardhat scripts/deploy.ts", 11 | "deploy:scrollTestnet": "npx hardhat run --network scrollTestnet scripts/deploy.ts" 12 | }, 13 | "devDependencies": { 14 | "@ethersproject/abi": "^5.4.7", 15 | "@ethersproject/providers": "^5.4.7", 16 | "@nomicfoundation/hardhat-chai-matchers": "^1.0.0", 17 | "@nomicfoundation/hardhat-network-helpers": "^1.0.0", 18 | "@nomicfoundation/hardhat-toolbox": "^1.0.1", 19 | "@nomiclabs/hardhat-ethers": "^2.0.0", 20 | "@nomiclabs/hardhat-etherscan": "^3.0.0", 21 | "@typechain/ethers-v5": "^10.1.0", 22 | "@typechain/hardhat": "^6.1.2", 23 | "@types/chai": "^4.2.0", 24 | "@types/mocha": "^9.1.0", 25 | "@types/node": ">=12.0.0", 26 | "chai": "^4.2.0", 27 | "dotenv": "^16.0.1", 28 | "ethers": "^5.4.7", 29 | "hardhat": "^2.10.2", 30 | "hardhat-gas-reporter": "^1.0.8", 31 | "solidity-coverage": "^0.7.21", 32 | "ts-node": ">=8.0.0", 33 | "typechain": "^8.1.0", 34 | "typescript": ">=4.5.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /scripts/deploy.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat"; 2 | 3 | async function main() { 4 | const currentTimestampInSeconds = Math.round(Date.now() / 1000); 5 | const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; 6 | const unlockTime = currentTimestampInSeconds + ONE_YEAR_IN_SECS; 7 | 8 | const lockedAmount = ethers.utils.parseEther("0.00000001"); 9 | 10 | const Lock = await ethers.getContractFactory("Lock"); 11 | const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); 12 | 13 | await lock.deployed(); 14 | 15 | console.log(`Lock with 0.00000001 ETH and unlock timestamp ${unlockTime} deployed to ${lock.address}`); 16 | // console.log(`Block explorer URL: https://l2scan.scroll.io/address/${lock.address}`); Uncomment here to use the pre-alpha 17 | console.log(`Block explorer URL: https://blockscout.scroll.io/address/${lock.address}`); 18 | } 19 | 20 | // We recommend this pattern to be able to use async/await everywhere 21 | // and properly handle errors. 22 | main().catch((error) => { 23 | console.error(error); 24 | process.exitCode = 1; 25 | }); 26 | -------------------------------------------------------------------------------- /test/Lock.ts: -------------------------------------------------------------------------------- 1 | import { time, loadFixture } from "@nomicfoundation/hardhat-network-helpers"; 2 | import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs"; 3 | import { expect } from "chai"; 4 | import { ethers } from "hardhat"; 5 | 6 | describe("Lock", function () { 7 | // We define a fixture to reuse the same setup in every test. 8 | // We use loadFixture to run this setup once, snapshot that state, 9 | // and reset Hardhat Network to that snapshot in every test. 10 | async function deployOneYearLockFixture() { 11 | const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60; 12 | const ONE_GWEI = 1_000_000_000; 13 | 14 | const lockedAmount = ONE_GWEI; 15 | const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS; 16 | 17 | // Contracts are deployed using the first signer/account by default 18 | const [owner, otherAccount] = await ethers.getSigners(); 19 | 20 | const Lock = await ethers.getContractFactory("Lock"); 21 | const lock = await Lock.deploy(unlockTime, { value: lockedAmount }); 22 | 23 | return { lock, unlockTime, lockedAmount, owner, otherAccount }; 24 | } 25 | 26 | describe("Deployment", function () { 27 | it("Should set the right unlockTime", async function () { 28 | const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture); 29 | 30 | expect(await lock.unlockTime()).to.equal(unlockTime); 31 | }); 32 | 33 | it("Should set the right owner", async function () { 34 | const { lock, owner } = await loadFixture(deployOneYearLockFixture); 35 | 36 | expect(await lock.owner()).to.equal(owner.address); 37 | }); 38 | 39 | it("Should receive and store the funds to lock", async function () { 40 | const { lock, lockedAmount } = await loadFixture( 41 | deployOneYearLockFixture 42 | ); 43 | 44 | expect(await ethers.provider.getBalance(lock.address)).to.equal( 45 | lockedAmount 46 | ); 47 | }); 48 | 49 | it("Should fail if the unlockTime is not in the future", async function () { 50 | // We don't use the fixture here because we want a different deployment 51 | const latestTime = await time.latest(); 52 | const Lock = await ethers.getContractFactory("Lock"); 53 | await expect(Lock.deploy(latestTime, { value: 1 })).to.be.revertedWith( 54 | "Unlock time should be in the future" 55 | ); 56 | }); 57 | }); 58 | 59 | describe("Withdrawals", function () { 60 | describe("Validations", function () { 61 | it("Should revert with the right error if called too soon", async function () { 62 | const { lock } = await loadFixture(deployOneYearLockFixture); 63 | 64 | await expect(lock.withdraw()).to.be.revertedWith( 65 | "You can't withdraw yet" 66 | ); 67 | }); 68 | 69 | it("Should revert with the right error if called from another account", async function () { 70 | const { lock, unlockTime, otherAccount } = await loadFixture( 71 | deployOneYearLockFixture 72 | ); 73 | 74 | // We can increase the time in Hardhat Network 75 | await time.increaseTo(unlockTime); 76 | 77 | // We use lock.connect() to send a transaction from another account 78 | await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith( 79 | "You aren't the owner" 80 | ); 81 | }); 82 | 83 | it("Shouldn't fail if the unlockTime has arrived and the owner calls it", async function () { 84 | const { lock, unlockTime } = await loadFixture( 85 | deployOneYearLockFixture 86 | ); 87 | 88 | // Transactions are sent using the first signer by default 89 | await time.increaseTo(unlockTime); 90 | 91 | await expect(lock.withdraw()).not.to.be.reverted; 92 | }); 93 | }); 94 | 95 | describe("Events", function () { 96 | it("Should emit an event on withdrawals", async function () { 97 | const { lock, unlockTime, lockedAmount } = await loadFixture( 98 | deployOneYearLockFixture 99 | ); 100 | 101 | await time.increaseTo(unlockTime); 102 | 103 | await expect(lock.withdraw()) 104 | .to.emit(lock, "Withdrawal") 105 | .withArgs(lockedAmount, anyValue); // We accept any value as `when` arg 106 | }); 107 | }); 108 | 109 | describe("Transfers", function () { 110 | it("Should transfer the funds to the owner", async function () { 111 | const { lock, unlockTime, lockedAmount, owner } = await loadFixture( 112 | deployOneYearLockFixture 113 | ); 114 | 115 | await time.increaseTo(unlockTime); 116 | 117 | await expect(lock.withdraw()).to.changeEtherBalances( 118 | [owner, lock], 119 | [lockedAmount, -lockedAmount] 120 | ); 121 | }); 122 | }); 123 | }); 124 | }); 125 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "commonjs", 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "skipLibCheck": true 9 | } 10 | } 11 | --------------------------------------------------------------------------------