Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: contracts package #5

Merged
merged 3 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/actions/setup/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,8 @@ runs:
with:
node-version: 20
cache: "pnpm"

- name: Use foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ node_modules
out/
build
dist
cache

# Debug
npm-debug.log*
Expand All @@ -49,4 +50,7 @@ contracts/cache
.idea

# vscode
.vscode
.vscode

# Coverage
lcov.info
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down
18 changes: 18 additions & 0 deletions packages/contracts/.solhint.json
Original file line number Diff line number Diff line change
@@ -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]
}
}
25 changes: 25 additions & 0 deletions packages/contracts/.solhint.tests.json
Original file line number Diff line number Diff line change
@@ -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]
}
}
38 changes: 38 additions & 0 deletions packages/contracts/README.md
Original file line number Diff line number Diff line change
@@ -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)
20 changes: 20 additions & 0 deletions packages/contracts/foundry.toml
Original file line number Diff line number Diff line change
@@ -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
28 changes: 28 additions & 0 deletions packages/contracts/package.json
Original file line number Diff line number Diff line change
@@ -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": "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'",
"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/*'"
},
"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"
}
}
4 changes: 4 additions & 0 deletions packages/contracts/remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
forge-std/=node_modules/forge-std/src

contracts/=src/contracts
interfaces/=src/interfaces
59 changes: 59 additions & 0 deletions packages/contracts/src/contracts/Greeter.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
74 changes: 74 additions & 0 deletions packages/contracts/src/interfaces/IGreeter.sol
Original file line number Diff line number Diff line change
@@ -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);
}
Loading