agent-bober 0.1.0
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/.claude-plugin/plugin.json +9 -0
- package/LICENSE +21 -0
- package/README.md +495 -0
- package/agents/bober-evaluator.md +323 -0
- package/agents/bober-generator.md +245 -0
- package/agents/bober-planner.md +248 -0
- package/dist/cli/commands/eval.d.ts +6 -0
- package/dist/cli/commands/eval.d.ts.map +1 -0
- package/dist/cli/commands/eval.js +129 -0
- package/dist/cli/commands/eval.js.map +1 -0
- package/dist/cli/commands/init.d.ts +5 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +547 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/plan.d.ts +5 -0
- package/dist/cli/commands/plan.d.ts.map +1 -0
- package/dist/cli/commands/plan.js +87 -0
- package/dist/cli/commands/plan.js.map +1 -0
- package/dist/cli/commands/run.d.ts +5 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +120 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/sprint.d.ts +6 -0
- package/dist/cli/commands/sprint.d.ts.map +1 -0
- package/dist/cli/commands/sprint.js +206 -0
- package/dist/cli/commands/sprint.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +124 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config/defaults.d.ts +15 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +226 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/loader.d.ts +18 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +189 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +904 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +181 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/contracts/eval-result.d.ts +205 -0
- package/dist/contracts/eval-result.d.ts.map +1 -0
- package/dist/contracts/eval-result.js +87 -0
- package/dist/contracts/eval-result.js.map +1 -0
- package/dist/contracts/index.d.ts +4 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +16 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/spec.d.ts +101 -0
- package/dist/contracts/spec.d.ts.map +1 -0
- package/dist/contracts/spec.js +51 -0
- package/dist/contracts/spec.js.map +1 -0
- package/dist/contracts/sprint-contract.d.ts +141 -0
- package/dist/contracts/sprint-contract.d.ts.map +1 -0
- package/dist/contracts/sprint-contract.js +80 -0
- package/dist/contracts/sprint-contract.js.map +1 -0
- package/dist/evaluators/builtin/api-check.d.ts +13 -0
- package/dist/evaluators/builtin/api-check.d.ts.map +1 -0
- package/dist/evaluators/builtin/api-check.js +152 -0
- package/dist/evaluators/builtin/api-check.js.map +1 -0
- package/dist/evaluators/builtin/build-check.d.ts +17 -0
- package/dist/evaluators/builtin/build-check.d.ts.map +1 -0
- package/dist/evaluators/builtin/build-check.js +155 -0
- package/dist/evaluators/builtin/build-check.js.map +1 -0
- package/dist/evaluators/builtin/command-runner.d.ts +26 -0
- package/dist/evaluators/builtin/command-runner.d.ts.map +1 -0
- package/dist/evaluators/builtin/command-runner.js +114 -0
- package/dist/evaluators/builtin/command-runner.js.map +1 -0
- package/dist/evaluators/builtin/lint.d.ts +17 -0
- package/dist/evaluators/builtin/lint.d.ts.map +1 -0
- package/dist/evaluators/builtin/lint.js +264 -0
- package/dist/evaluators/builtin/lint.js.map +1 -0
- package/dist/evaluators/builtin/playwright.d.ts +16 -0
- package/dist/evaluators/builtin/playwright.d.ts.map +1 -0
- package/dist/evaluators/builtin/playwright.js +238 -0
- package/dist/evaluators/builtin/playwright.js.map +1 -0
- package/dist/evaluators/builtin/typescript-check.d.ts +12 -0
- package/dist/evaluators/builtin/typescript-check.d.ts.map +1 -0
- package/dist/evaluators/builtin/typescript-check.js +155 -0
- package/dist/evaluators/builtin/typescript-check.js.map +1 -0
- package/dist/evaluators/builtin/unit-test.d.ts +18 -0
- package/dist/evaluators/builtin/unit-test.d.ts.map +1 -0
- package/dist/evaluators/builtin/unit-test.js +279 -0
- package/dist/evaluators/builtin/unit-test.js.map +1 -0
- package/dist/evaluators/index.d.ts +11 -0
- package/dist/evaluators/index.d.ts.map +1 -0
- package/dist/evaluators/index.js +13 -0
- package/dist/evaluators/index.js.map +1 -0
- package/dist/evaluators/plugin-interface.d.ts +50 -0
- package/dist/evaluators/plugin-interface.d.ts.map +1 -0
- package/dist/evaluators/plugin-interface.js +2 -0
- package/dist/evaluators/plugin-interface.js.map +1 -0
- package/dist/evaluators/plugin-loader.d.ts +18 -0
- package/dist/evaluators/plugin-loader.d.ts.map +1 -0
- package/dist/evaluators/plugin-loader.js +107 -0
- package/dist/evaluators/plugin-loader.js.map +1 -0
- package/dist/evaluators/registry.d.ts +78 -0
- package/dist/evaluators/registry.d.ts.map +1 -0
- package/dist/evaluators/registry.js +238 -0
- package/dist/evaluators/registry.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator/context-handoff.d.ts +543 -0
- package/dist/orchestrator/context-handoff.d.ts.map +1 -0
- package/dist/orchestrator/context-handoff.js +133 -0
- package/dist/orchestrator/context-handoff.js.map +1 -0
- package/dist/orchestrator/evaluator-agent.d.ts +15 -0
- package/dist/orchestrator/evaluator-agent.d.ts.map +1 -0
- package/dist/orchestrator/evaluator-agent.js +233 -0
- package/dist/orchestrator/evaluator-agent.js.map +1 -0
- package/dist/orchestrator/generator-agent.d.ts +16 -0
- package/dist/orchestrator/generator-agent.d.ts.map +1 -0
- package/dist/orchestrator/generator-agent.js +147 -0
- package/dist/orchestrator/generator-agent.js.map +1 -0
- package/dist/orchestrator/pipeline.d.ts +24 -0
- package/dist/orchestrator/pipeline.d.ts.map +1 -0
- package/dist/orchestrator/pipeline.js +290 -0
- package/dist/orchestrator/pipeline.js.map +1 -0
- package/dist/orchestrator/planner-agent.d.ts +10 -0
- package/dist/orchestrator/planner-agent.d.ts.map +1 -0
- package/dist/orchestrator/planner-agent.js +187 -0
- package/dist/orchestrator/planner-agent.js.map +1 -0
- package/dist/state/helpers.d.ts +5 -0
- package/dist/state/helpers.d.ts.map +1 -0
- package/dist/state/helpers.js +8 -0
- package/dist/state/helpers.js.map +1 -0
- package/dist/state/history.d.ts +39 -0
- package/dist/state/history.d.ts.map +1 -0
- package/dist/state/history.js +162 -0
- package/dist/state/history.js.map +1 -0
- package/dist/state/index.d.ts +8 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +22 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/plan-state.d.ts +21 -0
- package/dist/state/plan-state.d.ts.map +1 -0
- package/dist/state/plan-state.js +108 -0
- package/dist/state/plan-state.js.map +1 -0
- package/dist/state/sprint-state.d.ts +20 -0
- package/dist/state/sprint-state.d.ts.map +1 -0
- package/dist/state/sprint-state.js +98 -0
- package/dist/state/sprint-state.js.map +1 -0
- package/dist/utils/fs.d.ts +31 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +67 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/git.d.ts +35 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +84 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/logger.d.ts +45 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +73 -0
- package/dist/utils/logger.js.map +1 -0
- package/hooks/hooks.json +10 -0
- package/package.json +67 -0
- package/scripts/detect-stack.sh +287 -0
- package/scripts/init-project.sh +206 -0
- package/scripts/run-eval.sh +175 -0
- package/skills/bober.anchor/SKILL.md +365 -0
- package/skills/bober.anchor/references/anchor-guide.md +567 -0
- package/skills/bober.brownfield/SKILL.md +422 -0
- package/skills/bober.brownfield/references/codebase-analysis.md +304 -0
- package/skills/bober.eval/SKILL.md +235 -0
- package/skills/bober.eval/references/eval-strategies.md +407 -0
- package/skills/bober.eval/references/feedback-format.md +182 -0
- package/skills/bober.plan/SKILL.md +244 -0
- package/skills/bober.plan/references/clarification-guide.md +124 -0
- package/skills/bober.plan/references/spec-schema.md +253 -0
- package/skills/bober.react/SKILL.md +330 -0
- package/skills/bober.react/references/react-scaffold.md +344 -0
- package/skills/bober.run/SKILL.md +303 -0
- package/skills/bober.solidity/SKILL.md +416 -0
- package/skills/bober.solidity/references/solidity-guide.md +487 -0
- package/skills/bober.sprint/SKILL.md +280 -0
- package/skills/bober.sprint/references/contract-schema.md +251 -0
- package/templates/base/CLAUDE.md +20 -0
- package/templates/base/bober.config.json +35 -0
- package/templates/brownfield/CLAUDE.md +34 -0
- package/templates/brownfield/bober.config.json +37 -0
- package/templates/presets/anchor/CLAUDE.md +163 -0
- package/templates/presets/anchor/bober.config.json +9 -0
- package/templates/presets/api-node/CLAUDE.md +153 -0
- package/templates/presets/api-node/bober.config.json +10 -0
- package/templates/presets/nextjs/CLAUDE.md +82 -0
- package/templates/presets/nextjs/bober.config.json +14 -0
- package/templates/presets/python-api/CLAUDE.md +202 -0
- package/templates/presets/python-api/bober.config.json +9 -0
- package/templates/presets/react-vite/CLAUDE.md +71 -0
- package/templates/presets/react-vite/bober.config.json +53 -0
- package/templates/presets/react-vite/scaffold/package.json +45 -0
- package/templates/presets/react-vite/scaffold/server/index.ts +38 -0
- package/templates/presets/react-vite/scaffold/server/tsconfig.json +24 -0
- package/templates/presets/react-vite/scaffold/src/App.tsx +37 -0
- package/templates/presets/react-vite/scaffold/src/index.html +12 -0
- package/templates/presets/react-vite/scaffold/src/main.tsx +12 -0
- package/templates/presets/react-vite/scaffold/tsconfig.json +27 -0
- package/templates/presets/react-vite/scaffold/vite.config.ts +34 -0
- package/templates/presets/solidity/CLAUDE.md +106 -0
- package/templates/presets/solidity/bober.config.json +9 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
# Solidity Development Reference Guide
|
|
2
|
+
|
|
3
|
+
## Hardhat vs Foundry Project Structure
|
|
4
|
+
|
|
5
|
+
### Hardhat Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
project-root/
|
|
9
|
+
contracts/ # Solidity source files
|
|
10
|
+
interfaces/ # Contract interfaces
|
|
11
|
+
libraries/ # Shared libraries
|
|
12
|
+
mocks/ # Mock contracts for testing
|
|
13
|
+
MyContract.sol
|
|
14
|
+
test/ # Test files (TypeScript/JavaScript)
|
|
15
|
+
unit/ # Unit tests
|
|
16
|
+
integration/ # Integration tests
|
|
17
|
+
helpers/ # Test utilities and fixtures
|
|
18
|
+
scripts/ # Deployment and utility scripts
|
|
19
|
+
deploy/
|
|
20
|
+
artifacts/ # Compiled contract artifacts (generated)
|
|
21
|
+
cache/ # Hardhat cache (generated)
|
|
22
|
+
typechain-types/ # TypeScript bindings (generated)
|
|
23
|
+
hardhat.config.ts # Hardhat configuration
|
|
24
|
+
.solhint.json # Solhint linter configuration
|
|
25
|
+
package.json
|
|
26
|
+
tsconfig.json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Key config (`hardhat.config.ts`):**
|
|
30
|
+
```typescript
|
|
31
|
+
import { HardhatUserConfig } from "hardhat/config";
|
|
32
|
+
import "@nomicfoundation/hardhat-toolbox";
|
|
33
|
+
|
|
34
|
+
const config: HardhatUserConfig = {
|
|
35
|
+
solidity: {
|
|
36
|
+
version: "0.8.24",
|
|
37
|
+
settings: {
|
|
38
|
+
optimizer: { enabled: true, runs: 200 },
|
|
39
|
+
viaIR: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
networks: {
|
|
43
|
+
sepolia: {
|
|
44
|
+
url: process.env.SEPOLIA_RPC_URL || "",
|
|
45
|
+
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
etherscan: {
|
|
49
|
+
apiKey: process.env.ETHERSCAN_API_KEY || "",
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export default config;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Foundry Project Structure
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
project-root/
|
|
60
|
+
src/ # Solidity source files
|
|
61
|
+
interfaces/
|
|
62
|
+
libraries/
|
|
63
|
+
MyContract.sol
|
|
64
|
+
test/ # Solidity test files
|
|
65
|
+
unit/
|
|
66
|
+
integration/
|
|
67
|
+
mocks/
|
|
68
|
+
script/ # Deployment scripts (Solidity)
|
|
69
|
+
lib/ # Installed dependencies (git submodules)
|
|
70
|
+
forge-std/
|
|
71
|
+
openzeppelin-contracts/
|
|
72
|
+
out/ # Compiled artifacts (generated)
|
|
73
|
+
cache/ # Foundry cache (generated)
|
|
74
|
+
foundry.toml # Foundry configuration
|
|
75
|
+
remappings.txt # Import remappings
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Key config (`foundry.toml`):**
|
|
79
|
+
```toml
|
|
80
|
+
[profile.default]
|
|
81
|
+
src = "src"
|
|
82
|
+
out = "out"
|
|
83
|
+
libs = ["lib"]
|
|
84
|
+
solc_version = "0.8.24"
|
|
85
|
+
optimizer = true
|
|
86
|
+
optimizer_runs = 200
|
|
87
|
+
via_ir = false
|
|
88
|
+
|
|
89
|
+
[profile.default.fuzz]
|
|
90
|
+
runs = 256
|
|
91
|
+
max_test_rejects = 65536
|
|
92
|
+
|
|
93
|
+
[rpc_endpoints]
|
|
94
|
+
sepolia = "${SEPOLIA_RPC_URL}"
|
|
95
|
+
mainnet = "${MAINNET_RPC_URL}"
|
|
96
|
+
|
|
97
|
+
[etherscan]
|
|
98
|
+
sepolia = { key = "${ETHERSCAN_API_KEY}" }
|
|
99
|
+
mainnet = { key = "${ETHERSCAN_API_KEY}" }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Common Contract Patterns
|
|
103
|
+
|
|
104
|
+
### ERC-20 Token
|
|
105
|
+
|
|
106
|
+
```solidity
|
|
107
|
+
// SPDX-License-Identifier: MIT
|
|
108
|
+
pragma solidity ^0.8.20;
|
|
109
|
+
|
|
110
|
+
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
|
111
|
+
import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
|
|
112
|
+
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
|
|
113
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
114
|
+
|
|
115
|
+
contract MyToken is ERC20, ERC20Burnable, ERC20Permit, Ownable {
|
|
116
|
+
constructor(address initialOwner)
|
|
117
|
+
ERC20("MyToken", "MTK")
|
|
118
|
+
ERC20Permit("MyToken")
|
|
119
|
+
Ownable(initialOwner)
|
|
120
|
+
{
|
|
121
|
+
_mint(initialOwner, 1_000_000 * 10 ** decimals());
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function mint(address to, uint256 amount) public onlyOwner {
|
|
125
|
+
_mint(to, amount);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### ERC-721 NFT
|
|
131
|
+
|
|
132
|
+
```solidity
|
|
133
|
+
// SPDX-License-Identifier: MIT
|
|
134
|
+
pragma solidity ^0.8.20;
|
|
135
|
+
|
|
136
|
+
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
|
137
|
+
import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
|
|
138
|
+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
|
|
139
|
+
|
|
140
|
+
contract MyNFT is ERC721, ERC721URIStorage, Ownable {
|
|
141
|
+
uint256 private _nextTokenId;
|
|
142
|
+
|
|
143
|
+
constructor(address initialOwner)
|
|
144
|
+
ERC721("MyNFT", "MNFT")
|
|
145
|
+
Ownable(initialOwner)
|
|
146
|
+
{}
|
|
147
|
+
|
|
148
|
+
function safeMint(address to, string memory uri) public onlyOwner {
|
|
149
|
+
uint256 tokenId = _nextTokenId++;
|
|
150
|
+
_safeMint(to, tokenId);
|
|
151
|
+
_setTokenURI(tokenId, uri);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Required overrides
|
|
155
|
+
function tokenURI(uint256 tokenId)
|
|
156
|
+
public view override(ERC721, ERC721URIStorage) returns (string memory)
|
|
157
|
+
{
|
|
158
|
+
return super.tokenURI(tokenId);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function supportsInterface(bytes4 interfaceId)
|
|
162
|
+
public view override(ERC721, ERC721URIStorage) returns (bool)
|
|
163
|
+
{
|
|
164
|
+
return super.supportsInterface(interfaceId);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### ERC-1155 Multi-Token
|
|
170
|
+
|
|
171
|
+
```solidity
|
|
172
|
+
// SPDX-License-Identifier: MIT
|
|
173
|
+
pragma solidity ^0.8.20;
|
|
174
|
+
|
|
175
|
+
import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
|
|
176
|
+
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
|
|
177
|
+
|
|
178
|
+
contract MyMultiToken is ERC1155, AccessControl {
|
|
179
|
+
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
|
|
180
|
+
|
|
181
|
+
constructor(address admin) ERC1155("https://api.example.com/metadata/{id}.json") {
|
|
182
|
+
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
|
183
|
+
_grantRole(MINTER_ROLE, admin);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function mint(address to, uint256 id, uint256 amount, bytes memory data)
|
|
187
|
+
public onlyRole(MINTER_ROLE)
|
|
188
|
+
{
|
|
189
|
+
_mint(to, id, amount, data);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function supportsInterface(bytes4 interfaceId)
|
|
193
|
+
public view override(ERC1155, AccessControl) returns (bool)
|
|
194
|
+
{
|
|
195
|
+
return super.supportsInterface(interfaceId);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### Governor (DAO Governance)
|
|
201
|
+
|
|
202
|
+
```solidity
|
|
203
|
+
// SPDX-License-Identifier: MIT
|
|
204
|
+
pragma solidity ^0.8.20;
|
|
205
|
+
|
|
206
|
+
import {Governor} from "@openzeppelin/contracts/governance/Governor.sol";
|
|
207
|
+
import {GovernorSettings} from "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
|
|
208
|
+
import {GovernorCountingSimple} from "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
|
|
209
|
+
import {GovernorVotes} from "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
|
|
210
|
+
import {GovernorVotesQuorumFraction} from "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
|
|
211
|
+
import {GovernorTimelockControl} from "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
|
|
212
|
+
import {IVotes} from "@openzeppelin/contracts/governance/utils/IVotes.sol";
|
|
213
|
+
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
|
|
214
|
+
|
|
215
|
+
contract MyGovernor is
|
|
216
|
+
Governor,
|
|
217
|
+
GovernorSettings,
|
|
218
|
+
GovernorCountingSimple,
|
|
219
|
+
GovernorVotes,
|
|
220
|
+
GovernorVotesQuorumFraction,
|
|
221
|
+
GovernorTimelockControl
|
|
222
|
+
{
|
|
223
|
+
constructor(IVotes _token, TimelockController _timelock)
|
|
224
|
+
Governor("MyGovernor")
|
|
225
|
+
GovernorSettings(7200 /* 1 day */, 50400 /* 1 week */, 0)
|
|
226
|
+
GovernorVotes(_token)
|
|
227
|
+
GovernorVotesQuorumFraction(4)
|
|
228
|
+
GovernorTimelockControl(_timelock)
|
|
229
|
+
{}
|
|
230
|
+
|
|
231
|
+
// Required overrides omitted for brevity
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Timelock Controller
|
|
236
|
+
|
|
237
|
+
```solidity
|
|
238
|
+
// SPDX-License-Identifier: MIT
|
|
239
|
+
pragma solidity ^0.8.20;
|
|
240
|
+
|
|
241
|
+
import {TimelockController} from "@openzeppelin/contracts/governance/TimelockController.sol";
|
|
242
|
+
|
|
243
|
+
contract MyTimelock is TimelockController {
|
|
244
|
+
constructor(
|
|
245
|
+
uint256 minDelay,
|
|
246
|
+
address[] memory proposers,
|
|
247
|
+
address[] memory executors,
|
|
248
|
+
address admin
|
|
249
|
+
) TimelockController(minDelay, proposers, executors, admin) {}
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Testing Patterns
|
|
254
|
+
|
|
255
|
+
### Hardhat Testing (TypeScript)
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import { expect } from "chai";
|
|
259
|
+
import { ethers } from "hardhat";
|
|
260
|
+
import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
|
|
261
|
+
|
|
262
|
+
describe("MyToken", function () {
|
|
263
|
+
async function deployFixture() {
|
|
264
|
+
const [owner, addr1, addr2] = await ethers.getSigners();
|
|
265
|
+
const MyToken = await ethers.getContractFactory("MyToken");
|
|
266
|
+
const token = await MyToken.deploy(owner.address);
|
|
267
|
+
return { token, owner, addr1, addr2 };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
describe("Deployment", function () {
|
|
271
|
+
it("should set the right owner", async function () {
|
|
272
|
+
const { token, owner } = await loadFixture(deployFixture);
|
|
273
|
+
expect(await token.owner()).to.equal(owner.address);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("should assign the total supply to the owner", async function () {
|
|
277
|
+
const { token, owner } = await loadFixture(deployFixture);
|
|
278
|
+
const ownerBalance = await token.balanceOf(owner.address);
|
|
279
|
+
expect(await token.totalSupply()).to.equal(ownerBalance);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe("Transfers", function () {
|
|
284
|
+
it("should transfer tokens between accounts", async function () {
|
|
285
|
+
const { token, owner, addr1, addr2 } = await loadFixture(deployFixture);
|
|
286
|
+
await expect(token.transfer(addr1.address, 50))
|
|
287
|
+
.to.changeTokenBalances(token, [owner, addr1], [-50, 50]);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("should emit Transfer event", async function () {
|
|
291
|
+
const { token, owner, addr1 } = await loadFixture(deployFixture);
|
|
292
|
+
await expect(token.transfer(addr1.address, 50))
|
|
293
|
+
.to.emit(token, "Transfer")
|
|
294
|
+
.withArgs(owner.address, addr1.address, 50);
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it("should revert when sender has insufficient balance", async function () {
|
|
298
|
+
const { token, addr1, addr2 } = await loadFixture(deployFixture);
|
|
299
|
+
await expect(token.connect(addr1).transfer(addr2.address, 1))
|
|
300
|
+
.to.be.revertedWithCustomError(token, "ERC20InsufficientBalance");
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe("Access Control", function () {
|
|
305
|
+
it("should only allow owner to mint", async function () {
|
|
306
|
+
const { token, addr1 } = await loadFixture(deployFixture);
|
|
307
|
+
await expect(token.connect(addr1).mint(addr1.address, 100))
|
|
308
|
+
.to.be.revertedWithCustomError(token, "OwnableUnauthorizedAccount");
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Foundry Testing (Solidity)
|
|
315
|
+
|
|
316
|
+
```solidity
|
|
317
|
+
// SPDX-License-Identifier: MIT
|
|
318
|
+
pragma solidity ^0.8.20;
|
|
319
|
+
|
|
320
|
+
import {Test, console2} from "forge-std/Test.sol";
|
|
321
|
+
import {MyToken} from "../src/MyToken.sol";
|
|
322
|
+
|
|
323
|
+
contract MyTokenTest is Test {
|
|
324
|
+
MyToken public token;
|
|
325
|
+
address public owner;
|
|
326
|
+
address public addr1;
|
|
327
|
+
address public addr2;
|
|
328
|
+
|
|
329
|
+
function setUp() public {
|
|
330
|
+
owner = makeAddr("owner");
|
|
331
|
+
addr1 = makeAddr("addr1");
|
|
332
|
+
addr2 = makeAddr("addr2");
|
|
333
|
+
|
|
334
|
+
vm.prank(owner);
|
|
335
|
+
token = new MyToken(owner);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function test_OwnerIsSetCorrectly() public view {
|
|
339
|
+
assertEq(token.owner(), owner);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function test_TotalSupplyAssignedToOwner() public view {
|
|
343
|
+
assertEq(token.balanceOf(owner), token.totalSupply());
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function test_Transfer() public {
|
|
347
|
+
vm.prank(owner);
|
|
348
|
+
token.transfer(addr1, 50);
|
|
349
|
+
assertEq(token.balanceOf(addr1), 50);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function test_RevertWhen_InsufficientBalance() public {
|
|
353
|
+
vm.prank(addr1);
|
|
354
|
+
vm.expectRevert();
|
|
355
|
+
token.transfer(addr2, 1);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function test_RevertWhen_NonOwnerMints() public {
|
|
359
|
+
vm.prank(addr1);
|
|
360
|
+
vm.expectRevert();
|
|
361
|
+
token.mint(addr1, 100);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Fuzz test example
|
|
365
|
+
function testFuzz_Transfer(uint256 amount) public {
|
|
366
|
+
amount = bound(amount, 0, token.balanceOf(owner));
|
|
367
|
+
vm.prank(owner);
|
|
368
|
+
token.transfer(addr1, amount);
|
|
369
|
+
assertEq(token.balanceOf(addr1), amount);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
## Deployment and Verification Workflow
|
|
375
|
+
|
|
376
|
+
### Hardhat Deployment
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
// scripts/deploy.ts
|
|
380
|
+
import { ethers } from "hardhat";
|
|
381
|
+
|
|
382
|
+
async function main() {
|
|
383
|
+
const [deployer] = await ethers.getSigners();
|
|
384
|
+
console.log("Deploying with:", deployer.address);
|
|
385
|
+
|
|
386
|
+
const MyToken = await ethers.getContractFactory("MyToken");
|
|
387
|
+
const token = await MyToken.deploy(deployer.address);
|
|
388
|
+
await token.waitForDeployment();
|
|
389
|
+
|
|
390
|
+
const address = await token.getAddress();
|
|
391
|
+
console.log("MyToken deployed to:", address);
|
|
392
|
+
|
|
393
|
+
// Wait for block confirmations before verifying
|
|
394
|
+
console.log("Waiting for confirmations...");
|
|
395
|
+
await token.deploymentTransaction()?.wait(5);
|
|
396
|
+
|
|
397
|
+
// Verify on Etherscan
|
|
398
|
+
console.log("Verifying on Etherscan...");
|
|
399
|
+
await hre.run("verify:verify", {
|
|
400
|
+
address: address,
|
|
401
|
+
constructorArguments: [deployer.address],
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
main().catch((error) => {
|
|
406
|
+
console.error(error);
|
|
407
|
+
process.exitCode = 1;
|
|
408
|
+
});
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Foundry Deployment
|
|
412
|
+
|
|
413
|
+
```solidity
|
|
414
|
+
// script/Deploy.s.sol
|
|
415
|
+
// SPDX-License-Identifier: MIT
|
|
416
|
+
pragma solidity ^0.8.20;
|
|
417
|
+
|
|
418
|
+
import {Script, console2} from "forge-std/Script.sol";
|
|
419
|
+
import {MyToken} from "../src/MyToken.sol";
|
|
420
|
+
|
|
421
|
+
contract DeployScript is Script {
|
|
422
|
+
function run() public {
|
|
423
|
+
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
|
|
424
|
+
address deployer = vm.addr(deployerPrivateKey);
|
|
425
|
+
|
|
426
|
+
vm.startBroadcast(deployerPrivateKey);
|
|
427
|
+
MyToken token = new MyToken(deployer);
|
|
428
|
+
vm.stopBroadcast();
|
|
429
|
+
|
|
430
|
+
console2.log("MyToken deployed to:", address(token));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
# Deploy with Foundry
|
|
437
|
+
forge script script/Deploy.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast --verify
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### Verification
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
# Hardhat
|
|
444
|
+
npx hardhat verify --network sepolia <CONTRACT_ADDRESS> <CONSTRUCTOR_ARGS>
|
|
445
|
+
|
|
446
|
+
# Foundry (automatic with --verify flag during deployment)
|
|
447
|
+
forge verify-contract <CONTRACT_ADDRESS> MyToken --chain sepolia
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## Security Checklist
|
|
451
|
+
|
|
452
|
+
Before considering a contract ready for deployment:
|
|
453
|
+
|
|
454
|
+
### Critical
|
|
455
|
+
|
|
456
|
+
- [ ] No reentrancy vulnerabilities (checks-effects-interactions or ReentrancyGuard)
|
|
457
|
+
- [ ] All external/public functions have appropriate access control
|
|
458
|
+
- [ ] No unchecked external call return values (use SafeERC20 for token transfers)
|
|
459
|
+
- [ ] Integer arithmetic is safe (Solidity 0.8+ default, check unchecked blocks)
|
|
460
|
+
- [ ] No delegatecall to untrusted contracts
|
|
461
|
+
- [ ] Upgradeable contracts have proper storage gaps and initializers
|
|
462
|
+
|
|
463
|
+
### High Priority
|
|
464
|
+
|
|
465
|
+
- [ ] Front-running protection where needed (commit-reveal, deadlines)
|
|
466
|
+
- [ ] Oracle data has staleness checks and fallback mechanisms
|
|
467
|
+
- [ ] No unbounded loops that could hit block gas limit
|
|
468
|
+
- [ ] Signature replay protection (nonces, deadlines, chain ID via EIP-712)
|
|
469
|
+
- [ ] Proper event emissions for all state changes
|
|
470
|
+
- [ ] Constructor/initializer sets all critical state variables
|
|
471
|
+
|
|
472
|
+
### Medium Priority
|
|
473
|
+
|
|
474
|
+
- [ ] Gas optimization: storage packing, calldata usage, view/pure modifiers
|
|
475
|
+
- [ ] NatSpec documentation on all public interfaces
|
|
476
|
+
- [ ] Consistent error handling (custom errors preferred over require strings)
|
|
477
|
+
- [ ] Immutable and constant keywords used where applicable
|
|
478
|
+
- [ ] No floating pragma (use exact version: `pragma solidity 0.8.24;`)
|
|
479
|
+
|
|
480
|
+
### Pre-Mainnet
|
|
481
|
+
|
|
482
|
+
- [ ] Professional audit completed
|
|
483
|
+
- [ ] Testnet deployment tested end-to-end
|
|
484
|
+
- [ ] Deployment scripts tested on forked mainnet
|
|
485
|
+
- [ ] Emergency pause mechanism if appropriate
|
|
486
|
+
- [ ] Admin key management plan (multisig, timelock)
|
|
487
|
+
- [ ] Monitoring and alerting set up for critical events
|