Skip to content

Commit

Permalink
feat: more updates and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
hiddentao committed Aug 21, 2023
1 parent 3c2ae58 commit ba79347
Show file tree
Hide file tree
Showing 17 changed files with 364 additions and 43 deletions.
24 changes: 24 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Contribute to Gemforge

This guide guidelines for those wishing to contribute to Gemforge.

## Contributor license agreement

By submitting code as an individual or as an entity you agree that your code is [licensed the same as Gemforge](LICENSE.md).

## Issues and pull requests

Issues and merge requests should be in English and contain appropriate language for audiences of all ages.

We will only accept a merge requests which meets the following criteria:

* Code compiles properly (`npm run build`).
* Includes proper tests and all tests pass (unless it contains a test exposing a bug in existing code).
* Can be merged without problems (if not please use: `git rebase master`).
* Does not break any existing functionality.
* Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed).
* Keeps the Gemforge code base clean and well structured.
* Contains functionality we think other users will benefit from too.
* Doesn't add unnessecary configuration options since they complicate future changes.
* Update the docs in the `README.md` if necessary.

154 changes: 146 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
# gemforge
# Gemforge

> Command-line tool for building, deploying and upgrading Diamond Standard contracts on EVM chains.
## Why
## Why

The Diamond Standard (EIP-2535) is one of _the_ best ways to build and deploy [infinite sized, upgradeable contracts](https://twitter.com/hiddentao/status/1692567215059407048).

But utilizing the standard involves having to write a lot of boilerplate code, including but not limited to the core diamond proxy contract, interface code to enable easy access for dapps, deployment code which calculates what facets to add and remove in each upgrade, etc.

**gemforge** makes it easy to

**Gemforge** to the rescue!

By automating almost all aspects of this boilerplate code whilst still remaining highly configurable, Gemforge lessens the workload and saves time when developing with Diamond Standard.

## Features

* Auto-generates Diamond proxy code.
* Auto-calculates facet deployment and upgrades.
* Auto-generates deployment code for Foundry tests.
* Auto-calculates facet deployment and upgrades accurately and efficiently.
* Records diamond addresses to JSON file for history tracking.
* Supports [Foundry](https://github.com/foundry-rs/foundry) and [Hardhat](https://hardhat.org/) environments.
* Fully configurable for each project.
* Highly configurable per project.
* _Coming soon: Extensive documentation_

## Installation

_[Node.js](https://nodejs.org/) 16+ is required to run `gemforge`. We recommend using [nvm](https://github.com/nvm-sh/nvm) to handle different Node versions._
_[Node.js](https://nodejs.org/) 16+ is required to run Gemforge. We recommend using [nvm](https://github.com/nvm-sh/nvm) to handle different Node versions._

We recommend installing `gemforge` globally:

Expand All @@ -30,9 +34,143 @@ We recommend installing `gemforge` globally:

## Usage

## Contributing
_NOTE: Full documentation is coming soon_.

### The basics

You can use `gemforge --help` to see what commands are available:

```
Usage: gemforge [options] [command]
Options:
-V, --version output the version number
-h, --help display help for command
Commands:
init [options] Initialize a new project, generating necessary config files.
scaffold [options] Generate diamond smart contract project scaffolding.
build [options] Build a project.
deploy [options] [network] Deploy the diamond to a network.
help [command] display help for command
```

And use`gemforge <command> --help` to help for a specific command. E.g, for `gemforge init --help`:

```
Usage: gemforge deploy [options] [network]
Deploy the diamond to a network.
Arguments:
network network to deploy to (default: "local")
Options:
-v, --verbose verbose logging output
-q, --quiet disable logging output
-f, --folder <folder> folder to run gemforge in (default: ".")
-c, --config <config> gemforge config file to use (default: "gemforge.config.cjs")
-n, --new do a fresh deployment, ignoring any existing one
-h, --help display help for command
```

### init

This command will initialize a new Gemforge project by creating a Gemforge config file.

If using Gemforge with an existing smart contract project then this command is the first one to use.

```
> gemforge init
GEMFORGE: Working folder: /Users/ram/dev/gemstation/contracts
GEMFORGE: Initializing for foundry ...
GEMFORGE: Writing config file...
GEMFORGE: Wrote config file: /Users/ram/dev/gemstation/contracts/gemforge.config.cjs
GEMFORGE: Please edit the config file to your liking!
GEMFORGE: All done.
```

### scaffold

This command will clone the [Gemforge Foundry example repository](https://github.com/gemstation/contracts-foundry/) locally and set up all of its dependencies locally.

If you're not sure about how to configure Gemforge and use it with your own contracts then use this command to see a working example. This example repository contains the bare minimum code and is a great starting point for your own smart contract projects.

```
> gemforge scaffold --folder ./tmp
GEMFORGE: Working folder: /Users/ram/dev/gemstation/contracts/tmp1
GEMFORGE: Checking Node.js version...
GEMFORGE: Ensuring folder is empty...
GEMFORGE: Checking that foundry is installed...
GEMFORGE: Generating Foundry scaffolding...
GEMFORGE: Clone git@github.com:gemstation/contracts-foundry.git...
...
GEMFORGE: All done.
```

### build

This command builds the given smart contract project using Gemforge.

Gemforge will first load the Gemforge config file for the given project and use it to work out all the other information it needs. Gemforge will auto-generate all necessary files to enable you to either test/deploy your code as the next step.

```
> gemforge build
GEMFORGE: Working folder: /Users/ram/dev/gemstation/contracts
GEMFORGE: Checking diamond folder lib path...
GEMFORGE: Creating folder for solidity output...
GEMFORGE: Generating DiamondProxy.sol...
GEMFORGE: Generating IDiamondProxy.sol...
GEMFORGE: Generating LibDiamondHelper.sol ...
GEMFORGE: Creating folder for support output...
GEMFORGE: Generating facets.json...
GEMFORGE: Running build...
...
GEMFORGE: All done.
```

### deploy

This command deploys the built code to either a local test network or the named network.

All network and deployment wallet information is loaded from the Gemforge config file. All created contracts will have their creation information placed into a generated JSON file, allowing you to save this information in youre repo and/or use it for other purposes.

```
> gemforge deploy
GEMFORGE: Working folder: /Users/ram/dev/gemstation/contracts
GEMFORGE: Selected network: local
GEMFORGE: Setting up network connection...
GEMFORGE: Network chainId: 31337
GEMFORGE: Setting up wallet "wallet1" ...
GEMFORGE: Wallet deployer address: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
GEMFORGE: Load existing deployment ...
GEMFORGE: No existing deployment found.
GEMFORGE: Deploying diamond...
GEMFORGE: DiamondProxy deployed at: 0xCD8a1C3ba11CF5ECfa6267617243239504a98d90
GEMFORGE: Loading facet artifacts...
GEMFORGE: 1 facets found.
GEMFORGE: Resolving what changes need to be applied ...
GEMFORGE: 1 facets need to be deployed.
GEMFORGE: 1 facet cuts need to be applied.
GEMFORGE: Deploying facets...
GEMFORGE: Deploying ERC20Facet ...
GEMFORGE: Deployed ERC20Facet at: 0x82e01223d51Eb87e16A03E24687EDF0F294da6f1
GEMFORGE: Deploying initialization contract: InitDiamond ...
GEMFORGE: Initialization contract deployed at: 0x2bdCC0de6bE1f7D2ee689a0342D76F52E8EFABa3
GEMFORGE: Calling diamondCut() on the proxy...
GEMFORGE: Deployments took place, saving info...
GEMFORGE: Running post-deploy hook...
GEMFORGE: All done.
```


## Contributing

Issues and PRs are welcome. Please read the [contributing guidelines](CONTRIBUTING.md).

## License

Expand Down
2 changes: 1 addition & 1 deletion bin/gemforge.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const __dirname = dirname(__filename)
const index = process.argv.findIndex((arg) => arg.endsWith('bin/gemforge.js'))
const args = [
'--no-warnings',
resolve(__dirname, "../build/cli.js"),
resolve(__dirname, "../build/gemforge.js"),
].concat(process.argv.slice(index + 1))

// Say our original entrance script is `app.js`
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@commitlint/config-conventional": "^17.2.0",
"@types/lodash": "^4.14.197",
"@types/node": "^18.16.16",
"@types/semver": "^7.5.0",
"@types/tmp": "^0.2.3",
"eslint": "8.27.0",
"husky": "^8.0.2",
Expand All @@ -57,6 +58,7 @@
"execa": "^7.2.0",
"glob": "^10.3.3",
"lodash.get": "^4.4.2",
"semver": "^7.5.4",
"spdx-license-ids": "^3.0.13",
"tmp": "^0.2.1"
}
Expand Down
13 changes: 10 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 40 additions & 1 deletion src/cli/build.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { getContext } from '../shared/context.js'
import { $$, FacetDefinition, ensureGeneratedFolderExists, fileExists, getFacetsAndFunctions, saveJson, writeFile, writeTemplate } from '../shared/fs.js'
import { $, FacetDefinition, ensureGeneratedFolderExists, fileExists, getFacetsAndFunctions, saveJson, writeFile, writeTemplate } from '../shared/fs.js'
import path from 'node:path'
import { createCommand, logSuccess } from './common.js'
import { error, info, trace } from '../shared/log.js'
import { getFinalizedFacetCuts } from '../shared/diamond.js'

export const command = () =>
createCommand('build', 'Build a project.')
.action(async args => {
const ctx = await getContext(args)

const $$ = $({ cwd: ctx.folder, quiet: args.quiet })

// run prebuild hook
if (ctx.config.hooks.preBuild) {
info('Running pre-build hook...')
Expand Down Expand Up @@ -50,6 +53,42 @@ export const command = () =>
.join('\n')
})

info('Generating LibDiamondHelper.sol ...')

let numMethods = 0
const importPaths: Record<string, string> = {}
let cutStr = ''

facets.forEach((f, facetNum) => {
numMethods += f.functions.length

if (!importPaths[f.contractName]) {
const relativeImportPath = path.relative(generatedSolidityPath, f.file)
importPaths[f.contractName] = relativeImportPath.startsWith('.') ? relativeImportPath : `./${relativeImportPath}`
}

const varName = `f${facetNum}`

cutStr += `
bytes4[] memory ${varName} = new bytes4[](${f.functions.length});
${f.functions.map((f, i) => `${varName}[${i}] = IDiamondProxy.${f.name}.selector;`).join('\n')}
cut[${facetNum}] = IDiamondCut.FacetCut({
facetAddress: address(new ${f.contractName}()),
action: IDiamondCut.FacetCutAction.Add,
functionSelectors: ${varName}
});
`
})

writeTemplate('LibDiamondHelper.sol', `${generatedSolidityPath}/LibDiamondHelper.sol`, {
__SOLC_SPDX__: ctx.config.solc.license,
__SOLC_VERSION__: ctx.config.solc.version,
__LIB_DIAMOND_PATH__: ctx.config.paths.lib.diamond,
__FACET_IMPORTS__: Object.keys(importPaths).map(name => `import { ${name} } from "${importPaths[name]}";`).join('\n'),
__NUM_FACETS__: `${facets.length}`,
__CUTS__: cutStr,
})

info('Creating folder for support output...')
await ensureGeneratedFolderExists(generatedSupportPath)

Expand Down
4 changes: 2 additions & 2 deletions src/cli/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ export const createCommand = (name: string, desc: string, opts?: CreateCommandOp
.description(desc)
.option('-v, --verbose', 'verbose logging output')
.option('-q, --quiet', 'disable logging output')
.option('-f, --folder <folder>', 'folder to run the build in', '.')
.option('-f, --folder <folder>', 'folder to run gemforge in', '.')

if (!opts?.skipConfigOption) {
c = c.option('-c, --config <config>', 'gemforge config file', 'gemforge.config.cjs')
c = c.option('-c, --config <config>', 'gemforge config file to use', 'gemforge.config.cjs')
}

return c
Expand Down
6 changes: 4 additions & 2 deletions src/cli/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ethers } from 'ethers'
import { error, info } from '../shared/log.js'
import { Context, getContext } from '../shared/context.js'
import { $$, FacetDefinition, loadJson } from '../shared/fs.js'
import { $, FacetDefinition, loadJson } from '../shared/fs.js'
import path from 'node:path'
import { createCommand, logSuccess } from './common.js'
import { ContractArtifact, OnChainContract, saveDeploymentInfo, deployContract, execContractMethod, getContractAt, getContractValue, loadContractArtifact, setupNetwork, setupWallet, clearDeploymentRecords, getDeploymentRecords, readDeploymentInfo } from '../shared/chain.js'
Expand All @@ -11,10 +11,12 @@ import { Signer } from 'ethers'
export const command = () =>
createCommand('deploy', 'Deploy the diamond to a network.')
.argument('[network]', 'network to deploy to', 'local')
.option('-n, --new', 'do a fresh deployment, ignore any existing one')
.option('-n, --new', 'do a fresh deployment, ignoring any existing one')
.action(async (networkArg, args) => {
const ctx = await getContext(args)

const $$ = $({ cwd: ctx.folder, quiet: args.quiet })

// run pre-deploy hook
if (ctx.config.hooks.preDeploy) {
info('Running pre-deploy hook...')
Expand Down
2 changes: 1 addition & 1 deletion src/cli/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export const command = () =>
createCommand('init', 'Initialize a new project, generating necessary config files.', { skipConfigOption: true })
.option('-n, --name <name>', 'name to use for the config file', 'gemforge.config.cjs')
.option('-o, --overwrite', 'overwrite config file if it already exists')
.option('--hardhat', 'generate config file for a hardhat project')
.option('--hardhat', 'generate config for a Hardhat project')
.action(async (args) => {
const ctx = await getContext(args)

Expand Down
Loading

0 comments on commit ba79347

Please sign in to comment.