Skip to content
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
4 changes: 2 additions & 2 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
name: Integration Tests
runs-on: ubuntu-latest
environment: TEST
timeout-minutes: 15
timeout-minutes: 20
env: # These will be available to all steps in this job
CELOSCAN_API_KEY: ${{ secrets.CELOSCAN_API_KEY }}
DEPLOYER_PRIVATE_KEY: ${{ secrets.DEPLOYER_PRIVATE_KEY }}
Expand Down Expand Up @@ -49,7 +49,7 @@ jobs:
name: Integration Tests (macOS)
runs-on: macos-latest
environment: TEST
timeout-minutes: 15
timeout-minutes: 20
env: # These will be available to all steps in this job
CELOSCAN_API_KEY: ${{ secrets.CELOSCAN_API_KEY }}
DEPLOYER_PRIVATE_KEY: ${{ secrets.DEPLOYER_PRIVATE_KEY }}
Expand Down
35 changes: 33 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,38 @@ The registry (`.treb/deployments.json`) is a comprehensive JSON structure tracki

## Configuration

### foundry.toml Sender Profiles
### treb.toml Sender Configuration (Recommended)
```toml
# treb.toml — Treb sender configuration
# Each [ns.<name>] section defines a namespace with sender configs.
# The optional 'profile' field maps to a foundry.toml profile (defaults to namespace name).

[ns.default]
profile = "default"

[ns.default.senders.deployer]
type = "private_key"
private_key = "${DEPLOYER_PRIVATE_KEY}"

[ns.production]
profile = "production"

# Production namespace with Safe multisig sender
[ns.production.senders.safe]
type = "safe"
safe = "0x32CB58b145d3f7e28c45cE4B2Cc31fa94248b23F"
signer = "proposer"

# Hardware wallet proposer
[ns.production.senders.proposer]
type = "ledger"
derivation_path = "${PROD_PROPOSER_DERIVATION_PATH}"
```

### foundry.toml Sender Profiles (Legacy)
```toml
# NOTE: This format is deprecated. Run `treb migrate-config` to migrate to treb.toml.

# Default profile with private key sender
[profile.default.treb.senders.deployer]
type = "private_key"
Expand Down Expand Up @@ -354,7 +384,8 @@ treb run DeployCounter --dry-run
- `src/internal/`: Internal utilities and registry contracts

### Configuration Files
- `foundry.toml`: Foundry configuration with sender profiles and RPC endpoints
- `treb.toml`: Treb sender configuration with namespace-based sender profiles (preferred)
- `foundry.toml`: Foundry configuration with RPC endpoints (legacy sender profiles deprecated)
- `.treb/deployments.json`: Deployment registry tracking all deployments
- `.treb/transactions.json`: Transaction registry tracking script executions
- `.treb/config.json`: Local project configuration (namespace, network)
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ setup-integration-test:
# Run integration tests
integration-test: setup-integration-test
@echo "🔗 Running integration tests..."
@gotestsum --format=testname --no-summary=output --rerun-fails --rerun-fails-max-failures=5 --packages=./test/integration -- -v -timeout=10m
@gotestsum --format=testname --no-summary=output --rerun-fails --rerun-fails-max-failures=5 --packages=./test/integration -- -v -timeout=15m

# Clean build artifacts
clean:
Expand Down
4 changes: 2 additions & 2 deletions internal/adapters/forge/forge.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (f *ForgeAdapter) Build() error {

output, err := cmd.CombinedOutput()
duration := time.Since(start)

if err != nil {
f.log.Error("forge build failed", "error", err, "output", string(output), "duration", duration)
// Only print error details if build actually failed
Expand Down Expand Up @@ -305,7 +305,7 @@ func (f *ForgeAdapter) buildEnv(config usecase.RunScriptConfig) []string {
maps.Copy(env, config.Parameters)

// Profile
env["FOUNDRY_PROFILE"] = config.Namespace
env["FOUNDRY_PROFILE"] = config.FoundryProfile
env["NAMESPACE"] = config.Namespace
env["NETWORK"] = config.Network.Name
env["DRYRUN"] = strconv.FormatBool(config.DryRun || config.Debug || config.DebugJSON)
Expand Down
7 changes: 4 additions & 3 deletions internal/adapters/forge/forge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ func baseRunScriptConfig() usecase.RunScriptConfig {
ChainID: 11155111,
RPCURL: "https://sepolia.example.com",
},
Namespace: "default",
Script: &models.Contract{Name: "DeployCounter", Path: "script/deploy/DeployCounter.s.sol"},
Parameters: map[string]string{"LABEL": "v1"},
Namespace: "default",
FoundryProfile: "default",
Script: &models.Contract{Name: "DeployCounter", Path: "script/deploy/DeployCounter.s.sol"},
Parameters: map[string]string{"LABEL": "v1"},
Comment on lines +25 to +28
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Differentiate Namespace and FoundryProfile in at least one test case

Initializing both fields to "default" weakens regression coverage: a bug that sources FOUNDRY_PROFILE from Namespace would still pass current assertions. Use distinct values in one test and assert FOUNDRY_PROFILE matches FoundryProfile.

Proposed test hardening
 func TestBuildEnv_WithoutForkOverride(t *testing.T) {
 	adapter := newTestForgeAdapter()
 	cfg := baseRunScriptConfig()
+	cfg.Namespace = "ns-default"
+	cfg.FoundryProfile = "ci-profile"

 	env := adapter.buildEnv(cfg)
 	envMap := envToMap(env)

 	_, hasForkVar := envMap["SEPOLIA_RPC_URL"]
 	assert.False(t, hasForkVar, "should not have fork RPC override when no fork is active")
-	assert.Equal(t, "default", envMap["FOUNDRY_PROFILE"])
+	assert.Equal(t, "ci-profile", envMap["FOUNDRY_PROFILE"])
 	assert.Equal(t, "sepolia", envMap["NETWORK"])
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Namespace: "default",
FoundryProfile: "default",
Script: &models.Contract{Name: "DeployCounter", Path: "script/deploy/DeployCounter.s.sol"},
Parameters: map[string]string{"LABEL": "v1"},
func TestBuildEnv_WithoutForkOverride(t *testing.T) {
adapter := newTestForgeAdapter()
cfg := baseRunScriptConfig()
cfg.Namespace = "ns-default"
cfg.FoundryProfile = "ci-profile"
env := adapter.buildEnv(cfg)
envMap := envToMap(env)
_, hasForkVar := envMap["SEPOLIA_RPC_URL"]
assert.False(t, hasForkVar, "should not have fork RPC override when no fork is active")
assert.Equal(t, "ci-profile", envMap["FOUNDRY_PROFILE"])
assert.Equal(t, "sepolia", envMap["NETWORK"])
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/adapters/forge/forge_test.go` around lines 25 - 28, One of the tests
initializes both Namespace and FoundryProfile to "default", which masks bugs
that copy Namespace into FOUNDRY_PROFILE; update at least one test case in
internal/adapters/forge/forge_test.go to use distinct values (e.g., Namespace =
"namespace-x", FoundryProfile = "foundry-profile-x") when constructing the
struct that contains Namespace and FoundryProfile, and add an assertion that the
produced environment/configuration sets FOUNDRY_PROFILE (or the value returned
by the code under test) equal to the FoundryProfile field to ensure the two are
not accidentally aliased.

SenderScriptConfig: config.SenderScriptConfig{
EncodedConfig: "encoded",
},
Expand Down
6 changes: 3 additions & 3 deletions internal/adapters/repository/contracts/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func NewRepository(projectRoot string, log *slog.Logger) *Repository {
func (i *Repository) Index() error {
start := time.Now()
i.log.Debug("starting contract indexing")

i.mu.Lock()
defer i.mu.Unlock()

Expand Down Expand Up @@ -106,7 +106,7 @@ func (i *Repository) Index() error {
func (i *Repository) runForgeBuild() error {
start := time.Now()
i.log.Debug("running forge build for contract indexing", "dir", i.projectRoot)

// Check if we need to rebuild by looking for a cache indicator
// For now, always build without --force to improve performance
// The --force flag was causing significant slowdowns
Expand All @@ -115,7 +115,7 @@ func (i *Repository) runForgeBuild() error {

output, err := cmd.CombinedOutput()
duration := time.Since(start)

if err != nil {
i.log.Error("forge build failed", "error", err, "duration", duration)
return fmt.Errorf("forge build failed: %w\nOutput: %s", err, string(output))
Expand Down
3 changes: 3 additions & 0 deletions internal/cli/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ func showConfig(cmd *cobra.Command) error {
return err
}

// Enrich result with config source from runtime config
result.ConfigSource = app.Config.ConfigSource

// Render result
renderer := render.NewConfigRenderer(cmd.OutOrStdout())
return renderer.RenderConfig(result)
Expand Down
Loading