From 962c2a985d205ea729f0ba70601d9ddcd475584c Mon Sep 17 00:00:00 2001 From: nigiri <168690269+0xnigir1@users.noreply.github.com> Date: Mon, 9 Sep 2024 18:36:24 -0300 Subject: [PATCH 1/3] feat: contracts package w/ foundry --- .gitignore | 6 +- packages/contracts/.solhint.json | 18 ++ packages/contracts/.solhint.tests.json | 25 ++ packages/contracts/foundry.toml | 20 ++ packages/contracts/package.json | 28 +++ packages/contracts/remappings.txt | 4 + packages/contracts/src/contracts/Greeter.sol | 59 +++++ .../contracts/src/interfaces/IGreeter.sol | 74 ++++++ packages/contracts/test/unit/Greeter.t.sol | 99 ++++++++ pnpm-lock.yaml | 215 ++++++++++++++++++ 10 files changed, 547 insertions(+), 1 deletion(-) create mode 100644 packages/contracts/.solhint.json create mode 100644 packages/contracts/.solhint.tests.json create mode 100644 packages/contracts/foundry.toml create mode 100644 packages/contracts/package.json create mode 100644 packages/contracts/remappings.txt create mode 100644 packages/contracts/src/contracts/Greeter.sol create mode 100644 packages/contracts/src/interfaces/IGreeter.sol create mode 100644 packages/contracts/test/unit/Greeter.t.sol diff --git a/.gitignore b/.gitignore index 7e04ec7..c009e19 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ node_modules out/ build dist +cache # Debug npm-debug.log* @@ -49,4 +50,7 @@ contracts/cache .idea # vscode -.vscode \ No newline at end of file +.vscode + +# Coverage +lcov.info \ No newline at end of file diff --git a/packages/contracts/.solhint.json b/packages/contracts/.solhint.json new file mode 100644 index 0000000..4be8b14 --- /dev/null +++ b/packages/contracts/.solhint.json @@ -0,0 +1,18 @@ +{ + "extends": "solhint:recommended", + "rules": { + "compiler-version": ["off"], + "constructor-syntax": "warn", + "quotes": ["error", "single"], + "func-visibility": ["warn", { "ignoreConstructors": true }], + "not-rely-on-time": "off", + "no-inline-assembly": "off", + "no-empty-blocks": "off", + "private-vars-leading-underscore": ["warn", { "strict": false }], + "ordering": "warn", + "immutable-name-snakecase": "warn", + "avoid-low-level-calls": "off", + "no-console": "off", + "max-line-length": ["warn", 120] + } +} diff --git a/packages/contracts/.solhint.tests.json b/packages/contracts/.solhint.tests.json new file mode 100644 index 0000000..5014137 --- /dev/null +++ b/packages/contracts/.solhint.tests.json @@ -0,0 +1,25 @@ +{ + "extends": "solhint:recommended", + "rules": { + "compiler-version": ["off"], + "constructor-syntax": "warn", + "quotes": ["error", "single"], + "func-visibility": ["warn", { "ignoreConstructors": true }], + "not-rely-on-time": "off", + "style-guide-casing": "off", + "var-name-mixedcase": "off", + "const-name-snakecase": "off", + "no-inline-assembly": "off", + "no-empty-blocks": "error", + "definition-name-capwords": "off", + "named-parameters-function": "off", + "no-global-import": "off", + "max-states-count": "off", + "private-vars-leading-underscore": ["warn", { "strict": false }], + "ordering": "off", + "immutable-name-snakecase": "warn", + "avoid-low-level-calls": "off", + "one-contract-per-file": "off", + "max-line-length": ["warn", 120] + } +} diff --git a/packages/contracts/foundry.toml b/packages/contracts/foundry.toml new file mode 100644 index 0000000..d076d55 --- /dev/null +++ b/packages/contracts/foundry.toml @@ -0,0 +1,20 @@ +[fmt] +line_length = 120 +tab_width = 2 +bracket_spacing = false +int_types = 'long' +quote_style = 'single' +number_underscore = 'thousands' +multiline_func_header = 'params_first' +sort_imports = true + +[profile.default] +solc_version = '0.8.23' +libs = ['node_modules', 'lib'] +optimizer=false +src = "src" +out = "out" +# This translates to `solc --allow-paths node_modules` +allow_paths = ["node_modules"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/packages/contracts/package.json b/packages/contracts/package.json new file mode 100644 index 0000000..7bc44cf --- /dev/null +++ b/packages/contracts/package.json @@ -0,0 +1,28 @@ +{ + "name": "@ts-turborepo-boilerplate/contracts", + "version": "0.0.1", + "private": true, + "description": "", + "license": "MIT", + "author": "Wonderland", + "type": "module", + "scripts": { + "build": "forge build", + "lint": "pnpm lint:sol-tests && pnpm lint:sol-logic && forge fmt --check", + "lint:fix": "forge fmt && pnpm lint:sol-tests --fix && pnpm lint:sol-logic --fix", + "lint:sol-logic": "solhint -c .solhint.json 'src/**/*.sol' 'script/**/*.sol'", + "lint:sol-tests": "solhint -c .solhint.tests.json 'test/**/*.sol'", + "test": "forge test -vvv", + "test:coverage": "forge coverage --report summary --report lcov --match-path 'test/unit/*'" + }, + "lint-staged": { + "(src|test)*.{js,css,md,ts,sol}": "forge fmt", + "src/**/*.sol": "pnpm lint:sol-logic", + "test/**/*.sol": "pnpm lint:sol-tests", + "package.json": "sort-package-json" + }, + "devDependencies": { + "forge-std": "github:foundry-rs/forge-std#semver:1.9.2", + "solhint-community": "4.0.0" + } +} diff --git a/packages/contracts/remappings.txt b/packages/contracts/remappings.txt new file mode 100644 index 0000000..140bbc1 --- /dev/null +++ b/packages/contracts/remappings.txt @@ -0,0 +1,4 @@ +forge-std/=node_modules/forge-std/src + +contracts/=src/contracts +interfaces/=src/interfaces \ No newline at end of file diff --git a/packages/contracts/src/contracts/Greeter.sol b/packages/contracts/src/contracts/Greeter.sol new file mode 100644 index 0000000..7167b5c --- /dev/null +++ b/packages/contracts/src/contracts/Greeter.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; +import {IGreeter} from 'interfaces/IGreeter.sol'; + +contract Greeter is IGreeter { + /** + * @notice Empty string for revert checks + * @dev result of doing keccak256(bytes('')) + */ + bytes32 internal constant _EMPTY_STRING = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; + + /// @inheritdoc IGreeter + address public immutable OWNER; + + /// @inheritdoc IGreeter + string public greeting; + + /// @inheritdoc IGreeter + IERC20 public token; + + /** + * @notice Reverts in case the function was not called by the owner of the contract + */ + modifier onlyOwner() { + if (msg.sender != OWNER) { + revert Greeter_OnlyOwner(); + } + _; + } + + /** + * @notice Defines the owner to the msg.sender and sets the initial greeting + * @param _greeting Initial greeting + * @param _token Initial token + */ + constructor(string memory _greeting, IERC20 _token) { + OWNER = msg.sender; + token = _token; + setGreeting(_greeting); + } + + /// @inheritdoc IGreeter + function greet() external view returns (string memory _greeting, uint256 _balance) { + _greeting = greeting; + _balance = token.balanceOf(msg.sender); + } + + /// @inheritdoc IGreeter + function setGreeting(string memory _greeting) public onlyOwner { + if (keccak256(bytes(_greeting)) == _EMPTY_STRING) { + revert Greeter_InvalidGreeting(); + } + + greeting = _greeting; + emit GreetingSet(_greeting); + } +} diff --git a/packages/contracts/src/interfaces/IGreeter.sol b/packages/contracts/src/interfaces/IGreeter.sol new file mode 100644 index 0000000..53b4a64 --- /dev/null +++ b/packages/contracts/src/interfaces/IGreeter.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; + +/** + * @title Greeter Contract + * @author Wonderland + * @notice This is a basic contract created in order to portray some + * best practices and foundry functionality. + */ +interface IGreeter { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + /** + * @notice Greeting has changed + * @param _greeting The new greeting + */ + event GreetingSet(string _greeting); + + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + /** + * @notice Throws if the function was called by someone else than the owner + */ + error Greeter_OnlyOwner(); + + /** + * @notice Throws if the greeting set is invalid + * @dev Empty string is an invalid greeting + */ + error Greeter_InvalidGreeting(); + + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + /** + * @notice Returns the owner of the contract + * @dev The owner will always be the deployer of the contract + * @return _owner The owner of the contract + */ + function OWNER() external view returns (address _owner); + + /** + * @notice Returns the previously set greeting + * @return _greet The greeting + */ + function greeting() external view returns (string memory _greet); + + /** + * @notice Returns the token used to greet callers + * @return _token The address of the token + */ + function token() external view returns (IERC20 _token); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + /** + * @notice Sets a new greeting + * @dev Only callable by the owner + * @param _newGreeting The new greeting to be set + */ + function setGreeting(string memory _newGreeting) external; + + /** + * @notice Greets the caller + * @return _greeting The greeting + * @return _balance Current token balance of the caller + */ + function greet() external view returns (string memory _greeting, uint256 _balance); +} diff --git a/packages/contracts/test/unit/Greeter.t.sol b/packages/contracts/test/unit/Greeter.t.sol new file mode 100644 index 0000000..82f59c9 --- /dev/null +++ b/packages/contracts/test/unit/Greeter.t.sol @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.23; + +import {Greeter, IGreeter} from 'contracts/Greeter.sol'; +import {Test} from 'forge-std/Test.sol'; +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; + +contract UnitGreeter is Test { + address internal _owner = makeAddr('owner'); + IERC20 internal _token = IERC20(makeAddr('token')); + uint256 internal _initialBalance = 100; + string internal _initialGreeting = 'hola'; + + Greeter internal _greeter; + + event GreetingSet(string _greeting); + + function setUp() external { + vm.prank(_owner); + _greeter = new Greeter(_initialGreeting, _token); + + vm.etch(address(_token), new bytes(0x1)); + } + + function test_EmptyTestExample() external { + // it does nothing + vm.skip(true); + } + + function test_ConstructorWhenPassingValidGreetingString() external { + vm.prank(_owner); + + // it deploys + _greeter = new Greeter(_initialGreeting, _token); + + // it sets the greeting string + assertEq(_greeter.greeting(), _initialGreeting); + + // it sets the owner as sender + assertEq(_greeter.OWNER(), _owner); + + // it sets the token used + assertEq(address(_greeter.token()), address(_token)); + } + + function test_ConstructorWhenPassingAnEmptyGreetingString() external { + vm.prank(_owner); + + // it reverts + vm.expectRevert(IGreeter.Greeter_InvalidGreeting.selector); + _greeter = new Greeter('', _token); + } + + function test_GreetWhenCalled() external { + vm.mockCall(address(_token), abi.encodeWithSelector(IERC20.balanceOf.selector), abi.encode(_initialBalance)); + vm.expectCall(address(_token), abi.encodeWithSelector(IERC20.balanceOf.selector)); + (string memory _greet, uint256 _balance) = _greeter.greet(); + + // it returns the greeting string + assertEq(_greet, _initialGreeting); + + // it returns the token balance of the contract + assertEq(_balance, _initialBalance); + } + + modifier whenCalledByTheOwner() { + vm.startPrank(_owner); + _; + vm.stopPrank(); + } + + function test_SetGreetingWhenPassingAValidGreetingString() external whenCalledByTheOwner { + string memory _newGreeting = 'hello'; + + // it emit GreetingSet + vm.expectEmit(true, true, true, true, address(_greeter)); + emit GreetingSet(_newGreeting); + + _greeter.setGreeting(_newGreeting); + + // it sets the greeting string + assertEq(_greeter.greeting(), _newGreeting); + } + + function test_SetGreetingWhenPassingAnEmptyGreetingString() external whenCalledByTheOwner { + // it reverts + vm.expectRevert(IGreeter.Greeter_InvalidGreeting.selector); + _greeter.setGreeting(''); + } + + function test_SetGreetingWhenCalledByANon_owner(address _caller) external { + vm.assume(_caller != _owner); + vm.prank(_caller); + + // it reverts + vm.expectRevert(IGreeter.Greeter_OnlyOwner.selector); + _greeter.setGreeting('new greeting'); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f027216..f9f18e3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,6 +53,15 @@ importers: specifier: 5.5.4 version: 5.5.4 + packages/contracts: + devDependencies: + forge-std: + specifier: github:foundry-rs/forge-std#semver:1.9.2 + version: https://codeload.github.com/foundry-rs/forge-std/tar.gz/1714bee72e286e73f76e320d110e0eaf5c4e649d + solhint-community: + specifier: 4.0.0 + version: 4.0.0(typescript@5.5.4) + packages: "@ampproject/remapping@2.3.0": resolution: @@ -428,6 +437,12 @@ packages: } engines: { node: ^12.20.0 || ^14.18.0 || >=16.0.0 } + "@solidity-parser/parser@0.16.2": + resolution: + { + integrity: sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg==, + } + "@types/conventional-commits-parser@5.0.0": resolution: { @@ -605,6 +620,19 @@ packages: } engines: { node: ">=12" } + antlr4@4.13.2: + resolution: + { + integrity: sha512-QiVbZhyy4xAZ17UPEuG3YTOt8ZaoeOR1CvEAqrEsDBsOqINslaB147i9xqljZqoyf5S+EUlGStaj+t22LT9MOg==, + } + engines: { node: ">=16" } + + antlr4ts@0.5.0-alpha.4: + resolution: + { + integrity: sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==, + } + argparse@2.0.1: resolution: { @@ -624,6 +652,19 @@ packages: } engines: { node: ">=8" } + ast-parents@0.0.1: + resolution: + { + integrity: sha512-XHusKxKz3zoYk1ic8Un640joHbFMhbqneyoZfoKnEGtf2ey9Uh/IdpcQplODdO/kENaMIWsD0nJm4+wX3UNLHA==, + } + + astral-regex@2.0.0: + resolution: + { + integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==, + } + engines: { node: ">=8" } + balanced-match@1.0.2: resolution: { @@ -743,6 +784,13 @@ packages: integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==, } + commander@11.1.0: + resolution: + { + integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==, + } + engines: { node: ">=16" } + commander@12.1.0: resolution: { @@ -809,6 +857,18 @@ packages: cosmiconfig: ">=8.2" typescript: ">=4" + cosmiconfig@8.3.6: + resolution: + { + integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==, + } + engines: { node: ">=14" } + peerDependencies: + typescript: ">=4.9.5" + peerDependenciesMeta: + typescript: + optional: true + cosmiconfig@9.0.0: resolution: { @@ -1127,6 +1187,13 @@ packages: integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==, } + forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/1714bee72e286e73f76e320d110e0eaf5c4e649d: + resolution: + { + tarball: https://codeload.github.com/foundry-rs/forge-std/tar.gz/1714bee72e286e73f76e320d110e0eaf5c4e649d, + } + version: 1.9.2 + fs.realpath@1.0.0: resolution: { @@ -1203,6 +1270,14 @@ packages: } deprecated: Glob versions prior to v9 are no longer supported + glob@8.1.0: + resolution: + { + integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==, + } + engines: { node: ">=12" } + deprecated: Glob versions prior to v9 are no longer supported + global-directory@4.0.1: resolution: { @@ -1579,6 +1654,12 @@ packages: integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==, } + lodash.truncate@4.4.2: + resolution: + { + integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==, + } + lodash.uniq@4.5.0: resolution: { @@ -1591,6 +1672,12 @@ packages: integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==, } + lodash@4.17.21: + resolution: + { + integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==, + } + log-update@6.1.0: resolution: { @@ -1651,6 +1738,13 @@ packages: integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==, } + minimatch@5.1.6: + resolution: + { + integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==, + } + engines: { node: ">=10" } + minimatch@9.0.5: resolution: { @@ -1821,6 +1915,13 @@ packages: engines: { node: ">=0.10" } hasBin: true + pluralize@8.0.0: + resolution: + { + integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==, + } + engines: { node: ">=4" } + prelude-ls@1.2.1: resolution: { @@ -1835,6 +1936,14 @@ packages: } engines: { node: ">=6.0.0" } + prettier@2.8.8: + resolution: + { + integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==, + } + engines: { node: ">=10.13.0" } + hasBin: true + prettier@3.3.3: resolution: { @@ -1968,6 +2077,13 @@ packages: } engines: { node: ">=12" } + slice-ansi@4.0.0: + resolution: + { + integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==, + } + engines: { node: ">=10" } + slice-ansi@5.0.0: resolution: { @@ -1982,6 +2098,13 @@ packages: } engines: { node: ">=18" } + solhint-community@4.0.0: + resolution: + { + integrity: sha512-BERw3qYzkJE64EwvYrp2+iiTN8yAZOJ74FCiL4bTBp7v0JFUvRYCEGZKAqfHcfi/koKkzM6qThsJUceKm9vvfg==, + } + hasBin: true + sort-object-keys@1.1.3: resolution: { @@ -2072,6 +2195,13 @@ packages: } engines: { node: ^14.18.0 || >=16.0.0 } + table@6.8.2: + resolution: + { + integrity: sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==, + } + engines: { node: ">=10.0.0" } + text-extensions@2.4.0: resolution: { @@ -2616,6 +2746,10 @@ snapshots: "@pkgr/core@0.1.1": {} + "@solidity-parser/parser@0.16.2": + dependencies: + antlr4ts: 0.5.0-alpha.4 + "@types/conventional-commits-parser@5.0.0": dependencies: "@types/node": 22.5.4 @@ -2750,12 +2884,20 @@ snapshots: ansi-styles@6.2.1: {} + antlr4@4.13.2: {} + + antlr4ts@0.5.0-alpha.4: {} + argparse@2.0.1: {} array-ify@1.0.0: {} array-union@2.1.0: {} + ast-parents@0.0.1: {} + + astral-regex@2.0.0: {} + balanced-match@1.0.2: {} brace-expansion@1.1.11: @@ -2824,6 +2966,8 @@ snapshots: colorette@2.0.20: {} + commander@11.1.0: {} + commander@12.1.0: {} commitlint@19.4.1(@types/node@22.5.4)(typescript@5.5.4): @@ -2865,6 +3009,15 @@ snapshots: jiti: 1.21.6 typescript: 5.5.4 + cosmiconfig@8.3.6(typescript@5.5.4): + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.5.4 + cosmiconfig@9.0.0(typescript@5.5.4): dependencies: env-paths: 2.2.1 @@ -3068,6 +3221,9 @@ snapshots: flatted@3.3.1: {} + forge-std@https://codeload.github.com/foundry-rs/forge-std/tar.gz/1714bee72e286e73f76e320d110e0eaf5c4e649d: + {} + fs.realpath@1.0.0: {} gensync@1.0.0-beta.2: {} @@ -3105,6 +3261,14 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + global-directory@4.0.1: dependencies: ini: 4.1.1 @@ -3277,10 +3441,14 @@ snapshots: lodash.startcase@4.4.0: {} + lodash.truncate@4.4.2: {} + lodash.uniq@4.5.0: {} lodash.upperfirst@4.3.1: {} + lodash@4.17.21: {} + log-update@6.1.0: dependencies: ansi-escapes: 7.0.0 @@ -3312,6 +3480,10 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@5.1.6: + dependencies: + brace-expansion: 2.0.1 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 @@ -3394,12 +3566,17 @@ snapshots: pidtree@0.6.0: {} + pluralize@8.0.0: {} + prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.0: dependencies: fast-diff: 1.3.0 + prettier@2.8.8: + optional: true + prettier@3.3.3: {} punycode@2.3.1: {} @@ -3447,6 +3624,12 @@ snapshots: slash@4.0.0: {} + slice-ansi@4.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + slice-ansi@5.0.0: dependencies: ansi-styles: 6.2.1 @@ -3457,6 +3640,30 @@ snapshots: ansi-styles: 6.2.1 is-fullwidth-code-point: 5.0.0 + solhint-community@4.0.0(typescript@5.5.4): + dependencies: + "@solidity-parser/parser": 0.16.2 + ajv: 6.12.6 + antlr4: 4.13.2 + ast-parents: 0.0.1 + chalk: 4.1.2 + commander: 11.1.0 + cosmiconfig: 8.3.6(typescript@5.5.4) + fast-diff: 1.3.0 + glob: 8.1.0 + ignore: 5.3.2 + js-yaml: 4.1.0 + lodash: 4.17.21 + pluralize: 8.0.0 + semver: 6.3.1 + strip-ansi: 6.0.1 + table: 6.8.2 + text-table: 0.2.0 + optionalDependencies: + prettier: 2.8.8 + transitivePeerDependencies: + - typescript + sort-object-keys@1.1.3: {} sort-package-json@2.10.1: @@ -3511,6 +3718,14 @@ snapshots: "@pkgr/core": 0.1.1 tslib: 2.7.0 + table@6.8.2: + dependencies: + ajv: 8.17.1 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + text-extensions@2.4.0: {} text-table@0.2.0: {} From 53e12f9477024b9c8135db95b642d71b820a00c2 Mon Sep 17 00:00:00 2001 From: nigiri <168690269+0xnigir1@users.noreply.github.com> Date: Mon, 9 Sep 2024 19:03:54 -0300 Subject: [PATCH 2/3] docs: update readme --- .github/actions/setup/action.yml | 3 +++ README.md | 7 ++++++ packages/contracts/README.md | 38 ++++++++++++++++++++++++++++++++ packages/contracts/package.json | 2 +- 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 packages/contracts/README.md diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index e333e27..c372d2b 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -14,3 +14,6 @@ runs: with: node-version: 20 cache: "pnpm" + + - name: Use foundry + uses: foundry-rs/foundry-toolchain@v1 diff --git a/README.md b/README.md index a14d8b7..716dc40 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ ## Features +### Sample contracts with Foundry + +Basic Greeter contract with an external interface + +Foundry configuration out-of-the-box + ### Lint and format Use ESLint and Prettier to easily find issues as you code @@ -16,6 +22,7 @@ Run all tests and see the coverage before merging changes. - [pnpm](https://pnpm.io/): package and workspace manager - [turborepo](https://turbo.build/repo/docs): for managing the monorepo and the build system +- [foundry](https://book.getfoundry.sh/forge/): for writing Solidity smart contracts ### Configuring Prettier sort import plugin diff --git a/packages/contracts/README.md b/packages/contracts/README.md new file mode 100644 index 0000000..1bb6dfa --- /dev/null +++ b/packages/contracts/README.md @@ -0,0 +1,38 @@ +# ts-turborepo-boilerplate: Contracts package + +> Note: in case you don't require writing contracts, it's recommended +to delete this package. Also delete Foundry install from your GH workflow setup + +Starter package for starting to write you Solidity smart contracts +using Foundry in seconds. It contains a basic Greeter contract with +an external interface + +## Setup +1. Change package name to follow yours project one in [`package.json`](./package.json) +2. Install dependencies running `pnpm install` + +## Available Scripts + +Available scripts that can be run using `pnpm`: + +| Script | Description | +| -------------- | ------------------------------------------------------------ | +| `build` | Build contracts using Foundry's forge | +| `lint` | Run forge fmt and solhint on all contracts | +| `lint:fix` | Run linter and automatically fix code formatting issues. | +| `lint:sol-logic`| Run forge fmt and solhint on contracts | +| `lint:sol-tests`| Run forge fmt and solhint on test contracts | +| `format:check` | Check code formatting and linting without making changes. | +| `test` | Run tests using forge | +| `test:cov` | Run tests with coverage report | + + +### Finding compiled contracts output +By default, forge writes output to [out](./out) folder which is not git-tracked. +There you can find all contracts output including their respective ABIs, +deployment bytecode and more stuff + + +## References +- [Foundry docs](https://book.getfoundry.sh/forge/) +- [Foundry repo](https://github.com/foundry-rs) \ No newline at end of file diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 7bc44cf..5af7296 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -13,7 +13,7 @@ "lint:sol-logic": "solhint -c .solhint.json 'src/**/*.sol' 'script/**/*.sol'", "lint:sol-tests": "solhint -c .solhint.tests.json 'test/**/*.sol'", "test": "forge test -vvv", - "test:coverage": "forge coverage --report summary --report lcov --match-path 'test/unit/*'" + "test:cov": "forge coverage --report summary --report lcov --match-path 'test/unit/*'" }, "lint-staged": { "(src|test)*.{js,css,md,ts,sol}": "forge fmt", From c3276ae778c18a87bc49b577f1c1ac0027a293c6 Mon Sep 17 00:00:00 2001 From: nigiri <168690269+0xnigir1@users.noreply.github.com> Date: Mon, 9 Sep 2024 19:55:52 -0300 Subject: [PATCH 3/3] ci: fix gh workflow --- .github/actions/setup/action.yml | 2 ++ packages/contracts/package.json | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index c372d2b..9a35e4c 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -17,3 +17,5 @@ runs: - name: Use foundry uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 5af7296..89b23f0 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -8,9 +8,9 @@ "type": "module", "scripts": { "build": "forge build", - "lint": "pnpm lint:sol-tests && pnpm lint:sol-logic && forge fmt --check", + "lint": "forge fmt", "lint:fix": "forge fmt && pnpm lint:sol-tests --fix && pnpm lint:sol-logic --fix", - "lint:sol-logic": "solhint -c .solhint.json 'src/**/*.sol' 'script/**/*.sol'", + "lint:sol-logic": "solhint -c .solhint.json 'src/**/*.sol'", "lint:sol-tests": "solhint -c .solhint.tests.json 'test/**/*.sol'", "test": "forge test -vvv", "test:cov": "forge coverage --report summary --report lcov --match-path 'test/unit/*'"