diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..acd01fd --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "packages/contracts/lib/forge-std"] + path = packages/contracts/lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/packages/contracts/.eslintrc.js b/packages/contracts/.eslintrc.js deleted file mode 100644 index ac96533..0000000 --- a/packages/contracts/.eslintrc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - extends: '../../.eslintrc.json', -} diff --git a/packages/contracts/.eslintrc.json b/packages/contracts/.eslintrc.json deleted file mode 100644 index 167952a..0000000 --- a/packages/contracts/.eslintrc.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "env": { - "es6": true, - "mocha": true, - "node": true - }, - "extends": [ - "eslint:recommended", - "plugin:import/recommended", - "plugin:import/typescript", - "plugin:@typescript-eslint/recommended", - "plugin:@typescript-eslint/recommended-requiring-type-checking", - "plugin:@typescript-eslint/strict" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { - "project": "./tsconfig.json", - "sourceType": "module" - }, - "plugins": ["simple-import-sort", "no-only-tests", "unused-imports", "react"], - "rules": { - "@typescript-eslint/prefer-readonly": ["error"], - "@typescript-eslint/ban-tslint-comment": "off", - "@typescript-eslint/explicit-function-return-type": "error", - "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-extra-semi": "off", - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-throw-literal": "off", - "@typescript-eslint/no-unused-vars": [ - "warn", - { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" } - ], - "import/no-cycle": "error", - "import/no-default-export": "error", - "import/no-extraneous-dependencies": [ - "error", - { "devDependencies": false } - ], - "import/no-unresolved": ["error", { "ignore": ["^knex/types/tables$"] }], - "import/no-internal-modules": [ - "error", - { - "forbid": ["@l2beat/*/**"] - } - ], - "no-constant-condition": "off", - "no-only-tests/no-only-tests": "error", - "no-restricted-imports": [ - "error", - { - "paths": [".", "..", "../..", "../../.."], - "patterns": [ - { - "group": ["ethers/*"], - "message": "Do not import from ethers submodules. For example instead of importing getAddress from ethers/lib/utils, import utils from ethers and use utils.getAddress" - } - ] - } - ], - "simple-import-sort/imports": "error", - "simple-import-sort/exports": "error", - "unused-imports/no-unused-imports": "error", - "eqeqeq": "error", - "react/self-closing-comp": "error" - }, - "overrides": [ - { - "files": ["**/test/**/*.ts", "*.test.ts"], - "rules": { - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-unsafe-assignment": "off", - "@typescript-eslint/no-unsafe-call": "off", - "@typescript-eslint/no-unsafe-member-access": "off", - "@typescript-eslint/no-unsafe-return": "off", - "@typescript-eslint/require-await": "off", - "@typescript-eslint/unbound-method": "off", - "import/no-extraneous-dependencies": "off", - "no-unused-expressions": "off" - } - }, - { - "files": ["src/**/*.stories.{ts,tsx}"], - "rules": { - "import/no-extraneous-dependencies": "off", - "import/no-default-export": "off" - } - } - ] -} diff --git a/packages/contracts/.gitignore b/packages/contracts/.gitignore index 2a4cd8c..daca723 100644 --- a/packages/contracts/.gitignore +++ b/packages/contracts/.gitignore @@ -1,2 +1,11 @@ -# Hardhat -build/ +# Foundry +cache/ +out/ +.env +broadcast/ + +# JS +node_modules/ + +#yarn +*.log \ No newline at end of file diff --git a/packages/contracts/.solhint.json b/packages/contracts/.solhint.json new file mode 100644 index 0000000..d0a0acd --- /dev/null +++ b/packages/contracts/.solhint.json @@ -0,0 +1,14 @@ +{ + "extends": "solhint:recommended", + "rules": { + "code-complexity": ["error", 8], + "compiler-version": ["error", ">=0.8.0 <0.8.20"], + "func-name-mixedcase": "off", + "func-visibility": ["error", { "ignoreConstructors": true }], + "max-line-length": ["error", 120], + "named-parameters-mapping": "warn", + "no-console": "off", + "not-rely-on-time": "off", + "one-contract-per-file": "off" + } +} diff --git a/packages/contracts/README.md b/packages/contracts/README.md new file mode 100644 index 0000000..0ca18dd --- /dev/null +++ b/packages/contracts/README.md @@ -0,0 +1,20 @@ +# Byor Contracts + +## Usage +```bash +# Install +$ cd packages/contracts && forge install + +# Build +$ yarn build + +# Test +$ yarn test + +# Deploy to anvil +# open new terminal window +$ anvil +# update your .env file with a private key given to you by Anvil +$ source .env +$ yarn deploy:localhost +``` diff --git a/packages/contracts/foundry.toml b/packages/contracts/foundry.toml new file mode 100644 index 0000000..54e024e --- /dev/null +++ b/packages/contracts/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["node_modules", "lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/packages/contracts/hardhat.config.ts b/packages/contracts/hardhat.config.ts deleted file mode 100644 index ccf9745..0000000 --- a/packages/contracts/hardhat.config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { HardhatUserConfig } from 'hardhat/config' -import '@nomicfoundation/hardhat-toolbox' - -const config: HardhatUserConfig = { - solidity: '0.8.18', - paths: { - sources: './src', - tests: './test', - cache: './build/cache', - artifacts: './build/artifacts', - }, - typechain: { - outDir: 'build/typechain', - }, -} - -export default config diff --git a/packages/contracts/lib/forge-std b/packages/contracts/lib/forge-std new file mode 160000 index 0000000..f73c73d --- /dev/null +++ b/packages/contracts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit f73c73d2018eb6a111f35e4dae7b4f27401e9421 diff --git a/packages/contracts/package.json b/packages/contracts/package.json index a68c35c..5f66540 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -3,37 +3,18 @@ "version": "0.1.0", "private": true, "scripts": { - "deploy": "hardhat run scripts/deploy.ts", - "test": "hardhat test", - "compile": "hardhat compile", - "clean": "rm -rf build", + "deploy": "forge script script/deploy.s.sol:DeployScript --broadcast", + "deploy:localhost": "yarn deploy --fork-url http://localhost:8545 --private-keys $PRIVATE_KEY", + "test": "forge test", + "compile": "forge build", + "clean": "rm -rf cache out", "format:fix": "prettier --write .", "format": "prettier --check .", - "lint:fix": "yarn lint --fix", - "lint": "eslint --ext .ts,.tsx --max-warnings 0 test scripts", - "typecheck": "tsc --noEmit" + "lint": "forge fmt --check && solhint {script,src,test}/**/*.sol", + "typecheck": "forge build" }, "devDependencies": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/providers": "^5.7.2", - "@nomicfoundation/hardhat-chai-matchers": "^1.0.6", - "@nomicfoundation/hardhat-network-helpers": "^1.0.8", - "@nomicfoundation/hardhat-toolbox": "^2.0.2", - "@nomiclabs/hardhat-ethers": "^2.2.3", - "@nomiclabs/hardhat-etherscan": "^3.1.7", - "@typechain/ethers-v5": "^10.2.1", - "@typechain/hardhat": "^6.1.6", - "@types/chai": "^4.3.5", - "@types/mocha": "^10.0.1", - "@types/node": "^20.1.2", - "chai": "^4.3.7", - "ethers": "^5.0.0", - "hardhat-gas-reporter": "^1.0.9", - "solidity-coverage": "^0.8.2", - "ts-node": "^10.9.1", - "typechain": "^8.1.1" - }, - "dependencies": { - "hardhat": "^2.14.0" + "prettier": "^3.1.0", + "solhint": "^4.0.0" } } diff --git a/packages/contracts/remappings.txt b/packages/contracts/remappings.txt new file mode 100644 index 0000000..845bd0a --- /dev/null +++ b/packages/contracts/remappings.txt @@ -0,0 +1,2 @@ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ diff --git a/packages/contracts/script/deploy.s.sol b/packages/contracts/script/deploy.s.sol new file mode 100644 index 0000000..68a2b41 --- /dev/null +++ b/packages/contracts/script/deploy.s.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; +import {Inputs} from "../src/Inputs.sol"; + +contract DeployScript is Script { + function run() public returns (Inputs inputs) { + vm.broadcast(); + inputs = new Inputs(); + } +} diff --git a/packages/contracts/scripts/deploy.ts b/packages/contracts/scripts/deploy.ts deleted file mode 100644 index 29ec81c..0000000 --- a/packages/contracts/scripts/deploy.ts +++ /dev/null @@ -1,68 +0,0 @@ -import net from 'node:net' - -import hre, { ethers } from 'hardhat' - -const DEFAULT_HARDHAT_NODE_PORT = hre.tasks.node?.paramDefinitions.port - ?.defaultValue as number - -async function isPortReachable( - port: number, - host: string, - timeout = 100, -): Promise { - const promise = new Promise((resolve, reject) => { - const socket = new net.Socket() - - const onError = (): void => { - socket.destroy() - reject() - } - - socket.setTimeout(timeout) - socket.once('error', onError) - socket.once('timeout', onError) - - socket.connect(port, host, () => { - socket.end() - resolve(0) - }) - }) - - try { - await promise - return true - } catch { - return false - } -} - -async function startPerpetualHardhatNode(): Promise { - hre.run('node').finally(() => { - console.log('Stopping node') - }) - - return isPortReachable(DEFAULT_HARDHAT_NODE_PORT, '127.0.0.1', 5000) -} - -async function main(): Promise { - if (hre.network.name === 'hardhat') { - // If we don't provide a network, hardhat will create a new one, we will - // deploy our contract to that node but once this function finishes - // the node is stopped. We want to have a node that keeps on - // running, so if we say the node is on the hardhat network, manually - // start a new one that won't shutdown after the contract is - // deployed. - await startPerpetualHardhatNode() - } - - const inputsFactory = await ethers.getContractFactory('Inputs') - const inputs = await inputsFactory.deploy() - await inputs.deployed() - - console.log(`Inputs deployed to ${inputs.address}`) -} - -main().catch((error) => { - console.error(error) - process.exit(1) -}) diff --git a/packages/contracts/src/Caller.sol b/packages/contracts/src/Caller.sol index 7be30a3..d2e2293 100644 --- a/packages/contracts/src/Caller.sol +++ b/packages/contracts/src/Caller.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import './Inputs.sol'; +import {Inputs} from "./Inputs.sol"; // Only used for testing contract Caller { - Inputs inputs; + Inputs internal inputs; constructor(address inputsContract) { inputs = Inputs(inputsContract); diff --git a/packages/contracts/test/Inputs.t.sol b/packages/contracts/test/Inputs.t.sol new file mode 100644 index 0000000..5aadcbd --- /dev/null +++ b/packages/contracts/test/Inputs.t.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {Inputs} from "../src/Inputs.sol"; +import {Caller} from "../src/Caller.sol"; + +contract InputsTest is Test { + bytes private constant RANDOM_BYTES = "0x12345678907654321234567890987654321234567890987654"; + address private constant BOB = address(0xb0b); + Inputs private inputs; + Caller private caller; + + event BatchAppended(address sender); + + function setUp() public { + inputs = new Inputs(); + caller = new Caller(address(inputs)); + } + + function testEventBatchAppended() public { + // should emit BatchAppended event. + // expect address `bob` to be emitted. + vm.prank(BOB, BOB); + vm.expectEmit(); + emit BatchAppended(BOB); + inputs.appendBatch(RANDOM_BYTES); + } + + function testRevertCaller() public { + // should revert if called from a contract + vm.expectRevert(); + caller.appendBatch(RANDOM_BYTES); + } +} diff --git a/packages/contracts/test/Inputs.test.ts b/packages/contracts/test/Inputs.test.ts deleted file mode 100644 index 9f9cbde..0000000 --- a/packages/contracts/test/Inputs.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers' -import { assert, expect } from 'chai' -import hre from 'hardhat' - -describe('Inputs', () => { - const randomBytes = '0x12345678907654321234567890987654321234567890987654' - - async function getDeployer(): Promise { - const accounts = await hre.ethers.getSigners() - assert(accounts[0] !== undefined, 'First signer account is undefined') - return accounts[0] - } - - it('Should emit BatchAppended event', async () => { - const deployer = await getDeployer() - - const inputsFactory = await hre.ethers.getContractFactory('Inputs') - const inputs = await inputsFactory.deploy() - - await expect(inputs.appendBatch(randomBytes)) - .to.emit(inputs, 'BatchAppended') - .withArgs(await deployer.getAddress()) - }) - - it('Should revert if called from another contract', async () => { - const inputsFactory = await hre.ethers.getContractFactory('Inputs') - const inputs = await inputsFactory.deploy() - const callerFactory = await hre.ethers.getContractFactory('Caller') - const caller = await callerFactory.deploy(inputs.address) - - await expect(caller.appendBatch(randomBytes)).to.be.reverted - }) -}) diff --git a/packages/contracts/tsconfig.json b/packages/contracts/tsconfig.json deleted file mode 100644 index 4082f16..0000000 --- a/packages/contracts/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../tsconfig.json" -}