@towns-protocol/contracts 0.0.430 → 0.0.432
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/docs/permitAndStake_integration_guide.md +1 -1
- package/package.json +7 -10
- package/scripts/bytecode-diff/README.md +182 -0
- package/scripts/readme.md +289 -0
- package/src/diamond/readme.md +50 -0
- package/src/spaces/facets/membership/MembershipFacet.sol +10 -5
- package/src/spaces/facets/membership/join/MembershipJoin.sol +14 -0
package/README.md
CHANGED
|
@@ -58,11 +58,10 @@ The system also supports cross-chain delegation between Ethereum L1 and Base L2,
|
|
|
58
58
|
|
|
59
59
|
## Requirements
|
|
60
60
|
|
|
61
|
-
Install [
|
|
61
|
+
Install [Bun](https://bun.sh/):
|
|
62
62
|
|
|
63
63
|
```shell
|
|
64
|
-
|
|
65
|
-
corepack enable
|
|
64
|
+
curl -fsSL https://bun.sh/install | bash
|
|
66
65
|
```
|
|
67
66
|
|
|
68
67
|
Install [Foundry](https://github.com/foundry-rs/foundry):
|
|
@@ -77,7 +76,7 @@ foundryup
|
|
|
77
76
|
Clone the repo, then:
|
|
78
77
|
|
|
79
78
|
```shell
|
|
80
|
-
|
|
79
|
+
bun install
|
|
81
80
|
```
|
|
82
81
|
|
|
83
82
|
**To compile the smart contracts:**
|
package/package.json
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@towns-protocol/contracts",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.432",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"clean": "forge clean",
|
|
6
6
|
"compile": "forge build",
|
|
7
|
-
"format": "
|
|
8
|
-
"lint": "
|
|
7
|
+
"format": "bun run prettier:write",
|
|
8
|
+
"lint": "bun run solhint \"{script,src,tests}/**/*.sol\"",
|
|
9
9
|
"prettier:check": "prettier --check \"**/*.{sol,json,js,md,yml,ts}\" --ignore-path \".prettierignore\"",
|
|
10
10
|
"prettier:write": "prettier --write \"**/*.{sol,json,js,md,yml,ts}\" --ignore-path \".prettierignore\"",
|
|
11
11
|
"snapshot": "forge snapshot --isolate",
|
|
12
12
|
"test": "forge test --ffi --nmc Fork --fuzz-runs 4096",
|
|
13
|
-
"test:unit": "
|
|
13
|
+
"test:unit": "bun run test",
|
|
14
14
|
"typings": "wagmi generate"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@erc6900/reference-implementation": "
|
|
17
|
+
"@erc6900/reference-implementation": "github:erc6900/reference-implementation#v0.8.0",
|
|
18
18
|
"@ethereum-attestation-service/eas-contracts": "^1.8.0",
|
|
19
19
|
"@layerzerolabs/oft-evm": "^3.1.4",
|
|
20
20
|
"@openzeppelin/contracts": "^5.4.0",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@layerzerolabs/oapp-evm": "^0.3.2",
|
|
34
34
|
"@openzeppelin/merkle-tree": "^1.0.8",
|
|
35
35
|
"@prb/test": "^0.6.4",
|
|
36
|
-
"@towns-protocol/prettier-config": "^0.0.
|
|
36
|
+
"@towns-protocol/prettier-config": "^0.0.432",
|
|
37
37
|
"@wagmi/cli": "^2.2.0",
|
|
38
38
|
"forge-std": "github:foundry-rs/forge-std#v1.10.0",
|
|
39
39
|
"prettier": "^3.5.3",
|
|
@@ -47,11 +47,8 @@
|
|
|
47
47
|
"scripts/**/*.sol",
|
|
48
48
|
"README.md"
|
|
49
49
|
],
|
|
50
|
-
"installConfig": {
|
|
51
|
-
"hoistingLimits": "dependencies"
|
|
52
|
-
},
|
|
53
50
|
"publishConfig": {
|
|
54
51
|
"access": "public"
|
|
55
52
|
},
|
|
56
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "aed1393cc61f669c852b2d244630e27ae27a5922"
|
|
57
54
|
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Bytecode-Diff Tool
|
|
2
|
+
|
|
3
|
+
Bytecode-Diff is a tool to retrieve and display contract bytecode diff for Base deployed contracts and Solidity source compiled bytecode compiled with forge. It provides functionality to run source code diffs or remote bytecode diffs and create reports that detail changes between two compiled bytecode versions of contracts.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Go 1.22 or later
|
|
8
|
+
- Base RPC Provider URL
|
|
9
|
+
- River Chain RPC Provider URL
|
|
10
|
+
- Basescan API Key
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
### Local Source Code Diff
|
|
15
|
+
|
|
16
|
+
Compile contracts with forge and run source code diff comparing nearest commit report with checked out commit.
|
|
17
|
+
|
|
18
|
+
The basic command structure is:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# ensure contracts are compiled with forge
|
|
22
|
+
cd ../../packages/contracts
|
|
23
|
+
make build
|
|
24
|
+
# disable go.work file since bytecode-diff is not a module in parent go workspace
|
|
25
|
+
go mod download
|
|
26
|
+
# run source diff from checked out commitSha compared nearest commit with a source diff report in SOURCE_DIFF_DIR
|
|
27
|
+
GOWORK=off go run main.go -v -s
|
|
28
|
+
|
|
29
|
+
# write report with contract addresses, their keccak256 compilaed bytecode hash under two keys, existing and updated.
|
|
30
|
+
➜ bytecode-diff ✗ yq eval '.existing' source-diffs/00adc44f_08292024_5.yaml
|
|
31
|
+
Architect: 0xd291e489716f2c9cfc2e2c6047ce777159969943c85d09c51aaf7bbad10f7c13
|
|
32
|
+
ArchitectBase: 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
|
|
33
|
+
ArchitectStorage: 0x86159d997458669c4df8af2da4b5ce9ca742099a3f854c5eb3e718e16a74e4da
|
|
34
|
+
Banning: 0xde1354882fd30088cce4b00ff720a6dbc8c9f25653477c6ee99e20e17edb6068
|
|
35
|
+
BanningBase: 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
|
|
36
|
+
BanningStorage: 0x86159d997458669c4df8af2da4b5ce9ca742099a3f854c5eb3e718e16a74e4da
|
|
37
|
+
ChannelBase: 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470
|
|
38
|
+
...
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Run pairwise remote bytecode diff on facets deployed to two networks
|
|
42
|
+
|
|
43
|
+
Runs bytecode diff from deployed facets for diamonds in alpha, beta, and omega environments as per source coordinates of diamonds for each environment.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# compare omega against beta facets and facet selectors
|
|
47
|
+
GOWORK=off go run ./main.go beta omega -v
|
|
48
|
+
|
|
49
|
+
# output facet implementation changes by facet or selectors that are missing from omega
|
|
50
|
+
➜ bytecode-diff git:(jt/net-62-contract-differ) ✗ yq eval deployed-diffs/facet_diff_090324_18.yaml
|
|
51
|
+
diamonds:
|
|
52
|
+
- name: spaceOwner
|
|
53
|
+
source: beta
|
|
54
|
+
target: omega
|
|
55
|
+
facets:
|
|
56
|
+
- sourceContractName: ""
|
|
57
|
+
sourceFacetAddress: 0xfa98a1648761e494fc7d6efe5a06e357a76bd6fb
|
|
58
|
+
selectorsDiff:
|
|
59
|
+
- "0x3953801b"
|
|
60
|
+
- "0x91de4a83"
|
|
61
|
+
sourceBytecodeHash: 0xf86d9dbe53c89e14fa69cde925cca02b6efad519fe172f7b04d9515d7700a59b
|
|
62
|
+
sourceVerified: false
|
|
63
|
+
targetVerified: false
|
|
64
|
+
- sourceContractName: SpaceOwner
|
|
65
|
+
sourceFacetAddress: 0x30c912d8ceb9793e4cd240862acfd0e6c4436c52
|
|
66
|
+
targetContractAddresses:
|
|
67
|
+
- 0x63bC35259Ac32DF43Fba3b890F0F74951451976A
|
|
68
|
+
- 0xe7EB1313f0E7076616534225e16E971B72b50C42
|
|
69
|
+
selectorsDiff: []
|
|
70
|
+
sourceBytecodeHash: 0x461b53ab37fd24283ecd63eb0d4e71bd554a266036c73caf6d2ac39c435e7732
|
|
71
|
+
targetBytecodeHashes:
|
|
72
|
+
- 0x86d20161a13671a6138b80551e94dd8c1638bc5151807ff2194aa1e50cdb3cac
|
|
73
|
+
- 0xff0a94e93a4f4f6ee0ecd0d0e469e55ca40f1ab6c10e6af9da5b2b597f32b178
|
|
74
|
+
sourceVerified: true
|
|
75
|
+
targetVerified: true
|
|
76
|
+
- sourceContractName: ""
|
|
77
|
+
sourceFacetAddress: 0xdba2ce6125cc6b7f93c63d181a0780d5b421940b
|
|
78
|
+
selectorsDiff:
|
|
79
|
+
- "0x0d653654"
|
|
80
|
+
- "0x466a18de"
|
|
81
|
+
sourceBytecodeHash: 0x583c2852056f90c96ed1cab935489f644b8ef564e0a7f11564925d07cf3bc593
|
|
82
|
+
sourceVerified: false
|
|
83
|
+
targetVerified: false
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Run keccak256 hash generation on deployed contracts
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
GOWORK=off go run main.go add-hashes beta deployed-diffs/facet_diff_090624_1.yaml
|
|
91
|
+
|
|
92
|
+
# output to new yaml file suffixed with _hashed.yaml including bytecodeHash for each contract in deployments section
|
|
93
|
+
➜ bytecode-diff git:(jt/net-62-upgrade-script-2) ✗ yq e '.deployments' deployed-diffs/facet_diff_090624_1_hashed.yaml
|
|
94
|
+
Architect:
|
|
95
|
+
address: 0xa18a3df4f63cdcae943d9c76730adf2812388de4
|
|
96
|
+
baseScanLink: https://sepolia.basescan.org/tx/0x4280ef1300fe001e7d85e7495eba13fc99be53ee7a7060e753d466f8bebf1622
|
|
97
|
+
bytecodeHash: 0x20d0a86e9ea31a39663285aacfe88705983520a4482a7bac5ada891c9adfe090
|
|
98
|
+
deploymentDate: 2024-09-06 19:04
|
|
99
|
+
transactionHash: 0x4280ef1300fe001e7d85e7495eba13fc99be53ee7a7060e753d466f8bebf1622
|
|
100
|
+
Banning:
|
|
101
|
+
address: 0x4d88d1fbba6ce6bcdb4381549ee0b7c0d2b56919
|
|
102
|
+
baseScanLink: https://sepolia.basescan.org/tx/0x4ccbaf9750bcd0971975e73a24b05f1c51d4703cf72a406356c79eb54de9c33c
|
|
103
|
+
bytecodeHash: 0xa2ce3e77ba060ff1d59ed384e1c6c5788f308ad8bbbef612eb3e5de4e1d79de8
|
|
104
|
+
deploymentDate: 2024-09-06 19:05
|
|
105
|
+
transactionHash: 0x4ccbaf9750bcd0971975e73a24b05f1c51d4703cf72a406356c79eb54de9c33c
|
|
106
|
+
...
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Flags
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
➜ bytecode-diff ✗ GOWORK=off go run ./main.go --help
|
|
113
|
+
A tool to retrieve and display contract bytecode diff for Base
|
|
114
|
+
|
|
115
|
+
Usage:
|
|
116
|
+
bytecode-diff [source_environment] [target_environment] [flags]
|
|
117
|
+
|
|
118
|
+
Flags:
|
|
119
|
+
-b, --base-rpc string Base RPC provider URL
|
|
120
|
+
--base-sepolia-rpc string Base Sepolia RPC provider URL
|
|
121
|
+
--compiled-facets string Path to compiled facets
|
|
122
|
+
--deployments string Path to deployments directory (default "../../packages/contracts/deployments")
|
|
123
|
+
--facets string Path to facet source files
|
|
124
|
+
-h, --help help for bytecode-diff
|
|
125
|
+
--log-level string Set the logging level (debug, info, warn, error) (default "info")
|
|
126
|
+
--report-out-dir string Path to report output directory (default "deployed-diffs")
|
|
127
|
+
--source-diff-log string Path to diff log file (default "source-diffs")
|
|
128
|
+
-s, --source-diff-only Run source code diff
|
|
129
|
+
-v, --verbose Enable verbose output
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Environment Variables
|
|
133
|
+
|
|
134
|
+
You can also set the following environment variables instead of using flags:
|
|
135
|
+
|
|
136
|
+
- `BASE_RPC_URL`: Base RPC provider URL
|
|
137
|
+
- `BASE_SEPOLIA_RPC_URL`: Base Sepolia RPC provider URL
|
|
138
|
+
- `FACET_SOURCE_PATH`: Path to facet source files
|
|
139
|
+
- `ETHERSCAN_API_KEY`: Your API key for Etherscan.
|
|
140
|
+
- `COMPILED_FACETS_PATH`: (Optional) Path to compiled facets
|
|
141
|
+
- `DEPLOYMENTS_PATH`: (Optional) Path to deployed contracts
|
|
142
|
+
- `REPORT_OUT_DIR`: (Optional) Path to report output directory
|
|
143
|
+
- `SOURCE_DIFF_DIR`: (Optional) Path to source diff reports
|
|
144
|
+
|
|
145
|
+
## Examples
|
|
146
|
+
|
|
147
|
+
1. Run source code diff with all parameters specified via flags:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
./bytecode-diff --source-diff-only \
|
|
151
|
+
--source-diff-dir /path/to/source-diff-reports \
|
|
152
|
+
--facets /path/to/facet/sources \
|
|
153
|
+
--compiled-facets /path/to/compiled/facets \
|
|
154
|
+
--report-out-dir /path/to/report/output \
|
|
155
|
+
--verbose
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
2. Run source code diff using environment variables:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
export SOURCE_DIFF_DIR=/path/to/source_diff.log
|
|
162
|
+
export FACET_SOURCE_PATH=/path/to/facet/sources
|
|
163
|
+
export COMPILED_FACETS_PATH=/path/to/compiled/facets
|
|
164
|
+
export REPORT_OUT_DIR=/path/to/report/output
|
|
165
|
+
|
|
166
|
+
./bytecode-diff -s --verbose
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
3. Run source code diff with r/w to remote s3 bucket:
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
export AWS_ACCESS_KEY_ID=<your-access-key-id>
|
|
173
|
+
export AWS_SECRET_ACCESS_KEY=<your-secret-access-key>
|
|
174
|
+
export SOURCE_DIFF_DIR=s3://bucket/path
|
|
175
|
+
./bytecode-diff -s --verbose
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Notes
|
|
179
|
+
|
|
180
|
+
- If a `.env` file is present in the same directory as the script, it will be loaded automatically.
|
|
181
|
+
- When running source code diff, all required paths must be provided either via flags or environment variables.
|
|
182
|
+
- Use the `--verbose` flag for more detailed output during execution.
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
## Opinionated deployment scripting 🚀
|
|
2
|
+
|
|
3
|
+
Inspired by [hardhat-deploy](https://github.com/wighawag/hardhat-deploy)
|
|
4
|
+
|
|
5
|
+
This project supports two methods for deploying contracts:
|
|
6
|
+
|
|
7
|
+
### Method 1: Custom Deployment Scripts
|
|
8
|
+
|
|
9
|
+
For each contract being deployed, we create a script that will:
|
|
10
|
+
|
|
11
|
+
1. inherit from `Deployer`
|
|
12
|
+
2. implement a `versionName()` and `__deploy()` function
|
|
13
|
+
|
|
14
|
+
Example contract:
|
|
15
|
+
|
|
16
|
+
<details>
|
|
17
|
+
<summary>Click to expand example code</summary>
|
|
18
|
+
|
|
19
|
+
```solidity
|
|
20
|
+
// SPDX-License-Identifier: MIT
|
|
21
|
+
pragma solidity ^0.8.24;
|
|
22
|
+
|
|
23
|
+
import { Deployer } from "scripts/common/Deployer.s.sol";
|
|
24
|
+
import { MockERC721A } from "test/mocks/MockERC721A.sol";
|
|
25
|
+
|
|
26
|
+
contract DeployMockERC721A is Deployer {
|
|
27
|
+
function versionName() public pure override returns (string memory) {
|
|
28
|
+
return "mockERC721A";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function __deploy(address deployer) public override returns (address) {
|
|
32
|
+
vm.broadcast(deployer);
|
|
33
|
+
return address(new MockERC721A());
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
</details>
|
|
39
|
+
|
|
40
|
+
The framework will:
|
|
41
|
+
|
|
42
|
+
1. Load an existing deployment from
|
|
43
|
+
`contracts/deployments/<deploymentContext>/<chainIdAlias>/<contracts>.json`
|
|
44
|
+
|
|
45
|
+
2. if `OVERRIDE_DEPLOYMENTS=1` is set or if no deployments are found, it will:
|
|
46
|
+
|
|
47
|
+
- read `PRIVATE_KEY` from env (LOCAL_PRIVATE_KEY for anvil) or wait for ledger
|
|
48
|
+
- invoke `__deploy()` function
|
|
49
|
+
- if `SAVE_DEPLOYMENTS` is set; it will save the deployment to
|
|
50
|
+
`contracts/deployments/deploymentContext/<network>/<contract>.json`
|
|
51
|
+
|
|
52
|
+
### Method 2: DeployFacet Script (For Facet Contracts)
|
|
53
|
+
|
|
54
|
+
For facet contract deployments, we also support a standardized deployment process using the `DeployFacet.s.sol` script which:
|
|
55
|
+
|
|
56
|
+
1. Uses deterministic CREATE2 addresses for predictable deployment outcomes
|
|
57
|
+
2. Supports batch deployments for gas efficiency
|
|
58
|
+
3. Provides advanced gas estimation and deployment verification
|
|
59
|
+
|
|
60
|
+
- `OVERRIDE_DEPLOYMENTS=1`: It will redeploy a version of the contracts even if there's a cache in
|
|
61
|
+
deployments assigned, be very careful when using this
|
|
62
|
+
- `SAVE_DEPLOYMENTS=1`: It will save a cached address of deployments to
|
|
63
|
+
`contracts/deployments/<network>/<contract>.json`
|
|
64
|
+
- `DEPLOYMENT_CONTEXT=string`: It will save the addresses on a subdirectory with the given name,
|
|
65
|
+
useful for deployment contract to same network
|
|
66
|
+
|
|
67
|
+
## How to write new deployment libraries and diamonds
|
|
68
|
+
|
|
69
|
+
This project uses two distinct but complementary components for diamond pattern deployments:
|
|
70
|
+
|
|
71
|
+
### 1. Facet Deployment Libraries
|
|
72
|
+
|
|
73
|
+
Facet deployment libraries (like `DeployMembershipMetadata.s.sol`) define helper functions for diamond cuts but don't handle the actual facet contract deployment. These libraries typically provide:
|
|
74
|
+
|
|
75
|
+
1. A `selectors()` function that returns an array of function selectors the facet provides
|
|
76
|
+
2. A `makeCut()` function that creates a `FacetCut` struct for diamond upgrades
|
|
77
|
+
3. Optional: A `deploy()` function for manual deployment (though we prefer using `DeployFacet` for this)
|
|
78
|
+
|
|
79
|
+
To create a new facet deployment library:
|
|
80
|
+
|
|
81
|
+
1. Create a new file `Deploy[YourFacet].s.sol` in `scripts/deployments/facets/`
|
|
82
|
+
2. Implement the required functions as shown in the example below
|
|
83
|
+
|
|
84
|
+
Example of a facet deployment library:
|
|
85
|
+
|
|
86
|
+
<details>
|
|
87
|
+
<summary>Click to expand example code</summary>
|
|
88
|
+
|
|
89
|
+
```solidity
|
|
90
|
+
import { IDiamond } from "@towns-protocol/diamond/src/Diamond.sol";
|
|
91
|
+
import { IMembershipMetadata } from "src/spaces/facets/membership/metadata/IMembershipMetadata.sol";
|
|
92
|
+
import { LibDeploy } from "@towns-protocol/diamond/src/utils/LibDeploy.sol";
|
|
93
|
+
|
|
94
|
+
library DeployMembershipMetadata {
|
|
95
|
+
// Return all function selectors this facet provides
|
|
96
|
+
function selectors() internal pure returns (bytes4[] memory res) {
|
|
97
|
+
res = new bytes4[](2);
|
|
98
|
+
res[0] = IMembershipMetadata.refreshMetadata.selector;
|
|
99
|
+
res[1] = IMembershipMetadata.tokenURI.selector;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Create a FacetCut struct for diamond upgrades
|
|
103
|
+
function makeCut(
|
|
104
|
+
address facetAddress,
|
|
105
|
+
IDiamond.FacetCutAction action
|
|
106
|
+
) internal pure returns (IDiamond.FacetCut memory) {
|
|
107
|
+
return IDiamond.FacetCut(facetAddress, action, selectors());
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Optional direct deployment method (prefer using DeployFacet instead)
|
|
111
|
+
function deploy() internal returns (address) {
|
|
112
|
+
return LibDeploy.deployCode("MembershipMetadata.sol", "");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
</details>
|
|
118
|
+
|
|
119
|
+
### 2. Diamond Deployment Scripts
|
|
120
|
+
|
|
121
|
+
Diamond deployment scripts (like `DeploySpace.s.sol`) coordinate the deployment of multiple facets and their integration into a diamond. These scripts:
|
|
122
|
+
|
|
123
|
+
1. Use `DeployFacet` to efficiently deploy multiple facets using CREATE2 and Multicall3
|
|
124
|
+
2. Use the facet deployment libraries to prepare diamond cuts
|
|
125
|
+
3. Handle the initialization and configuration of the diamond
|
|
126
|
+
|
|
127
|
+
To create a new diamond deployment script:
|
|
128
|
+
|
|
129
|
+
1. Create a new file `Deploy[YourDiamond].s.sol` in `scripts/deployments/diamonds/`
|
|
130
|
+
2. Inherit from `DiamondHelper`, `Deployer` and implement required functions
|
|
131
|
+
3. Use `DeployFacet` for batch deployments of facets
|
|
132
|
+
4. Use facet deployment libraries to create diamond cuts
|
|
133
|
+
|
|
134
|
+
Example pattern from `DeploySpace.s.sol`:
|
|
135
|
+
|
|
136
|
+
<details>
|
|
137
|
+
<summary>Click to expand example code</summary>
|
|
138
|
+
|
|
139
|
+
```solidity
|
|
140
|
+
contract DeploySpace is DiamondHelper, Deployer {
|
|
141
|
+
// Create a DeployFacet helper for batch deployments
|
|
142
|
+
DeployFacet private facetHelper = new DeployFacet();
|
|
143
|
+
|
|
144
|
+
function versionName() public pure override returns (string memory) {
|
|
145
|
+
return "space";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function __deploy(address deployer) internal override returns (address) {
|
|
149
|
+
// Add core facets (like DiamondCut, DiamondLoupe, etc.)
|
|
150
|
+
addImmutableCuts(deployer);
|
|
151
|
+
|
|
152
|
+
// Set up diamond initialization parameters with additional facets
|
|
153
|
+
Diamond.InitParams memory initDiamondCut = diamondInitParams(deployer);
|
|
154
|
+
|
|
155
|
+
// Deploy the diamond with all facets
|
|
156
|
+
vm.broadcast(deployer);
|
|
157
|
+
Diamond diamond = new Diamond(initDiamondCut);
|
|
158
|
+
|
|
159
|
+
return address(diamond);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function diamondInitParams(
|
|
163
|
+
address deployer
|
|
164
|
+
) public returns (Diamond.InitParams memory) {
|
|
165
|
+
// Queue facets for batch deployment
|
|
166
|
+
facetHelper.add("MembershipToken");
|
|
167
|
+
facetHelper.add("MembershipMetadata");
|
|
168
|
+
// ... add other facets ...
|
|
169
|
+
|
|
170
|
+
// Deploy all queued facets in a single transaction
|
|
171
|
+
facetHelper.deployBatch(deployer);
|
|
172
|
+
|
|
173
|
+
// Add each facet to the diamond cut using the corresponding deployment library
|
|
174
|
+
address facet = facetHelper.getDeployedAddress("MembershipMetadata");
|
|
175
|
+
addCut(
|
|
176
|
+
DeployMembershipMetadata.makeCut(facet, IDiamond.FacetCutAction.Add)
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// ... add other facets ...
|
|
180
|
+
|
|
181
|
+
// Return the diamond initialization parameters
|
|
182
|
+
return
|
|
183
|
+
Diamond.InitParams({
|
|
184
|
+
baseFacets: baseFacets(),
|
|
185
|
+
init: multiInit,
|
|
186
|
+
initData: abi.encodeCall(
|
|
187
|
+
MultiInit.multiInit,
|
|
188
|
+
(_initAddresses, _initDatas)
|
|
189
|
+
)
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
</details>
|
|
196
|
+
|
|
197
|
+
### Integration with makefile
|
|
198
|
+
|
|
199
|
+
Once you've created your deployment components, you can use them with the makefile:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
# To deploy a specific facet:
|
|
203
|
+
make deploy-facet rpc=base_sepolia contract=YourFacet
|
|
204
|
+
|
|
205
|
+
# To deploy a diamond:
|
|
206
|
+
make deploy-base-sepolia contract=DeployYourDiamond type=diamonds
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
This approach separates facet contract deployment (using `DeployFacet` with CREATE2) from diamond integration (using facet deployment libraries), providing both efficiency and flexibility.
|
|
210
|
+
|
|
211
|
+
### How to deploy locally (step-by-step)
|
|
212
|
+
|
|
213
|
+
<details>
|
|
214
|
+
<summary>Click to expand deployment steps</summary>
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# say you want to deploy a new MockERC721A
|
|
218
|
+
|
|
219
|
+
# Provision a new deployer
|
|
220
|
+
-> cast wallet new
|
|
221
|
+
|
|
222
|
+
# save the key in .env (LOCAL_PRIVATE_KEY=...)
|
|
223
|
+
|
|
224
|
+
# Fund the deployer address (this is the first address shown when runing `anvil`)
|
|
225
|
+
-> cast send ${NEW_WALLET_ADDRESS} --value 1ether -f 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --unlocked
|
|
226
|
+
|
|
227
|
+
# perform a local simulation
|
|
228
|
+
-> forge script script/${CONTRACT}.s.sol
|
|
229
|
+
|
|
230
|
+
# perform a simulation against a network
|
|
231
|
+
-> forge script script/${CONTRACT}.s.sol --rpc-url <network>
|
|
232
|
+
|
|
233
|
+
# run anvil in separate terminal
|
|
234
|
+
-> anvil
|
|
235
|
+
|
|
236
|
+
# perform the deployment to a local network
|
|
237
|
+
# Option 1: Custom deployment script
|
|
238
|
+
-> make deploy-any-local contract=DeployMockERC721A type=facets
|
|
239
|
+
|
|
240
|
+
# Option 2: Using the DeployFacet script (for facet contracts)
|
|
241
|
+
-> make deploy-facet-local rpc=base_anvil contract=MockERC721A
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
</details>
|
|
245
|
+
|
|
246
|
+
### How to deploy to a testnet or mainnet
|
|
247
|
+
|
|
248
|
+
<details>
|
|
249
|
+
<summary>Click to expand deployment steps</summary>
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
# Method 1: Using custom deployment scripts
|
|
253
|
+
# To deploy a contract to Base Sepolia in the "beta" deployment context:
|
|
254
|
+
-> make deploy-base-sepolia contract=DeployWalletLink type=facets context=beta
|
|
255
|
+
|
|
256
|
+
# Method 2: Using the DeployFacet script
|
|
257
|
+
# To deploy a facet to Base Sepolia:
|
|
258
|
+
-> make deploy-facet rpc=base_sepolia contract=WalletLink context=beta
|
|
259
|
+
|
|
260
|
+
# To deploy with a ledger hardware wallet to Base mainnet:
|
|
261
|
+
# Method 1: Using custom deployment scripts
|
|
262
|
+
-> make deploy-base contract=DeploySpaceFactory type=diamonds context=omega
|
|
263
|
+
|
|
264
|
+
# Method 2: Using the DeployFacet script
|
|
265
|
+
-> make deploy-facet-ledger rpc=base contract=WalletLink context=omega
|
|
266
|
+
|
|
267
|
+
# To redeploy a contract to Base Sepolia in the "beta" deployment context:
|
|
268
|
+
-> OVERRIDE_DEPLOYMENTS=1 make deploy-base-sepolia contract=DeployWalletLink type=facets context=beta
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
</details>
|
|
272
|
+
|
|
273
|
+
### How to script (interact with deployed contracts through foundry)
|
|
274
|
+
|
|
275
|
+
<details>
|
|
276
|
+
<summary>Click to expand example steps</summary>
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# say you want to mint from MockERC721A
|
|
280
|
+
|
|
281
|
+
# deploy a local implementation of MockERC721A by calling DeployFacet
|
|
282
|
+
-> make deploy-facet-local rpc=base_anvil contract=MockERC721A
|
|
283
|
+
|
|
284
|
+
# next we'll call the script InteractMockERC721A
|
|
285
|
+
# This will grab new and existing deployment addresses from our deployments cache and use those to interact with each other
|
|
286
|
+
-> make interact-any-local rpc=base_anvil contract=InteractMockERC721A
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
</details>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
## Guidelines for creating a facet
|
|
2
|
+
|
|
3
|
+
- Use diamond storage `Layout` for storing variables `<FacetName>Storage`
|
|
4
|
+
- Specify your internal business logic, and interaction with storage in `<FacetName>Base` abstract
|
|
5
|
+
contract
|
|
6
|
+
- Create you initializers and your protected external calls in the `<FacetName>Facet` contract
|
|
7
|
+
- Define your external and internal interface in `I<FacetName>` interface file
|
|
8
|
+
- `I<FacetName>Base` internal interface gets inherited by your `<FacetName>Base` abstract contract
|
|
9
|
+
and it usually holds structs, enums, errors and events
|
|
10
|
+
- `I<FacetName>` external interface gets inherited by your `<FacetName>Facet` contract and it
|
|
11
|
+
usually holds external functions, this interface can inherit your internal `I<FacetName>Base` to
|
|
12
|
+
have access to its data types
|
|
13
|
+
|
|
14
|
+
> Minimal example
|
|
15
|
+
|
|
16
|
+
```solidity
|
|
17
|
+
library SampleStorage {
|
|
18
|
+
struct Layout {
|
|
19
|
+
uint256 value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function layout() internal pure returns (Layout storage ds) {
|
|
23
|
+
bytes32 slot = keccak256("sample.storage");
|
|
24
|
+
assembly {
|
|
25
|
+
ds.slot := slot
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ISampleBase {
|
|
31
|
+
event ValueSet(uint256 value);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
inteface ISample is ISampleBase {
|
|
35
|
+
function setValue(uint256) external;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
abstract contract SampleBase is ISampleBase {
|
|
39
|
+
function _setValue(uint256 val) internal {
|
|
40
|
+
SampleStorage.layout().value = x;
|
|
41
|
+
emit ValueSet(val);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
contract SampleFacet is ISample, SampleBase {
|
|
46
|
+
function setValue(uint256 val) onlyOwner external {
|
|
47
|
+
_setValue(val);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
```
|
|
@@ -103,21 +103,26 @@ contract MembershipFacet is IMembership, MembershipJoin, ReentrancyGuard, Facet
|
|
|
103
103
|
|
|
104
104
|
/// @inheritdoc IMembership
|
|
105
105
|
function getMembershipPrice() external view returns (uint256 totalRequired) {
|
|
106
|
-
|
|
106
|
+
uint256 membershipPrice = _getMembershipPrice(_totalSupply());
|
|
107
|
+
if (membershipPrice == 0) return 0;
|
|
108
|
+
|
|
109
|
+
(totalRequired, ) = _getTotalMembershipPayment(membershipPrice);
|
|
107
110
|
}
|
|
108
111
|
|
|
109
112
|
/// @inheritdoc IMembership
|
|
110
113
|
function getMembershipRenewalPrice(
|
|
111
114
|
uint256 tokenId
|
|
112
115
|
) external view returns (uint256 totalRequired) {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
);
|
|
116
|
+
uint256 renewalPrice = _getMembershipRenewalPrice(tokenId, _totalSupply());
|
|
117
|
+
if (renewalPrice == 0) return 0;
|
|
118
|
+
(totalRequired, ) = _getTotalMembershipPayment(renewalPrice);
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
/// @inheritdoc IMembership
|
|
119
122
|
function getProtocolFee() external view returns (uint256 protocolFee) {
|
|
120
|
-
|
|
123
|
+
uint256 membershipPrice = _getMembershipPrice(_totalSupply());
|
|
124
|
+
if (membershipPrice == 0) return 0;
|
|
125
|
+
(, protocolFee) = _getTotalMembershipPayment(membershipPrice);
|
|
121
126
|
}
|
|
122
127
|
|
|
123
128
|
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
|
|
@@ -499,6 +499,20 @@ abstract contract MembershipJoin is
|
|
|
499
499
|
|
|
500
500
|
uint256 duration = _getMembershipDuration();
|
|
501
501
|
uint256 basePrice = _getMembershipRenewalPrice(tokenId, _totalSupply());
|
|
502
|
+
|
|
503
|
+
// Handle free renewal
|
|
504
|
+
if (basePrice == 0) {
|
|
505
|
+
// Refund any ETH sent
|
|
506
|
+
CurrencyTransfer.transferCurrency(
|
|
507
|
+
_getMembershipCurrency(),
|
|
508
|
+
address(this),
|
|
509
|
+
payer,
|
|
510
|
+
msg.value
|
|
511
|
+
);
|
|
512
|
+
_renewSubscription(tokenId, uint64(duration));
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
502
516
|
(uint256 totalRequired, ) = _getTotalMembershipPayment(basePrice);
|
|
503
517
|
|
|
504
518
|
if (totalRequired > msg.value) Membership__InvalidPayment.selector.revertWith();
|