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
2 changes: 1 addition & 1 deletion ADMINISTRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
## Operational notes

- Validate game timings, tier setup, fee routing, and attestation settings before launch.
- If `JBProjects` has a creation fee, include the exact native-token fee when calling `launchGameWith()`.
- If `JBProjects` has a creation fee, include the exact native-token fee when calling `launchGameWith()` directly or through the configured trusted forwarder.
- Treat `launchGameWith()` as the real admin commitment.
- During scoring, follow the submission, attestation, and ratification flow rather than looking for discretionary overrides.
- Use `triggerNoContestFor()` only when the game has actually entered the documented no-contest condition.
Expand Down
5 changes: 3 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

| Module | Responsibility | Notes |
| --- | --- | --- |
| `DefifaDeployer` | Launches games, sets phased rulesets, clones hooks, initializes governance, and fulfills commitments | Launch-time and completion-time runtime surface |
| `DefifaDeployer` | Launches games, sets phased rulesets, clones hooks, resolves ERC-2771 launch callers, initializes governance, and fulfills commitments | Launch-time and completion-time runtime surface |
| `DefifaHook` | NFT minting, delegation, game-phase-aware cash-out behavior, and completion claims | Main game-facing runtime hook |
| `DefifaGovernor` | Scorecard submission, attestation weighting, quorum, grace periods, and ratification | Governance surface |
| `DefifaHookLib` | Shared validation and weight math extracted from the hook | Bytecode-management helper |
Expand All @@ -41,9 +41,10 @@
```text
creator
-> deployer validates mint/refund/start timings
-> deployer resolves the ERC-2771 caller and advertises that account as the creation-fee payer
-> deployer predicts the game project ID and clones a game hook deterministically
-> deployer builds phased rulesets and optional fee splits
-> deployer advertises the resolved fee payer, then reserves the game project via createFor
-> deployer reserves the game project via createFor
-> controller launches the project
-> governor is initialized for the game
-> hook ownership and project ownership are transferred into the intended long-term shape
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,5 @@ Shared ABI artifacts checked with no ABI item changes:
- Replace every `DefifaDelegate` reference with `DefifaHook` and regenerate ABI types.
- Re-check any scorecard, attestation, or cash-out indexing code against the V6 events. V5 scorecard assumptions are not selector- or payload-stable.
- Do not depend on `DefifaProjectOwner` in V6 deployments.
- `DefifaDeployer` now implements `IJBPayerTracker`. While forwarding a project-creation fee to `JBProjects.createFor`, it advertises the resolved fee payer (the `launchGameWith` caller) through the transient `originalPayer` getter, so a `pay`-routing fee receiver credits the player who paid rather than the deployer. Regenerate ABI types to pick up the added `originalPayer()` getter.
- `DefifaDeployer` now implements `IJBPayerTracker` and `ERC2771Context`. Its constructor accepts a `trustedForwarder`, and while forwarding a project-creation fee to `JBProjects.createFor`, it advertises the resolved fee payer (the `launchGameWith` ERC-2771 caller) through the transient `originalPayer` getter, so a `pay`-routing fee receiver credits the player who paid rather than the deployer or forwarder. Regenerate ABI types to pick up the constructor and ABI changes.
- `DefifaDeployer` caches its controller's `PROJECTS`, `RULESETS`, and `DIRECTORY` as constructor immutables (resolved once from `CONTROLLER`) instead of reading `CONTROLLER.PROJECTS()` / `CONTROLLER.RULESETS()` / `CONTROLLER.DIRECTORY()` at each use. They are exposed as `IDefifaDeployer` getters alongside `CONTROLLER`; regenerate ABI types to pick them up.
12 changes: 6 additions & 6 deletions INVARIANTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ NO_CONTEST is reported by the view as soon as the condition is met; the on-chain
- **Reserve mints blocked in NO_CONTEST.** Prevents a malicious reserve mint from inflating `totalMintCost` past `minParticipation` after the game has already failed the participation check, which would otherwise revive the game from NO_CONTEST → SCORING (`DefifaHook.sol:577-579`).
- **Delegate changes locked after MINT.** `setTierDelegateTo` / `setTierDelegatesTo` revert outside the MINT phase. This freezes voting power before scoring begins, so attestation can't be hot-swapped to a colluding delegate after scorecards drop (`DefifaHook.sol:817-819, 830-832`).
- **`addToBalanceOf` cannot inflate participation.** `minParticipation` is checked against `totalMintCost` (incremented only by paid mints and reserve mints), not terminal balance — donations to the terminal cannot artificially satisfy the participation threshold (`DefifaDeployer.sol:256-260`).
- **Front-run-resistant hook clone.** `cloneDeterministic` salts on `keccak256(msg.sender, nonce)` so a different caller produces a different address; a watcher cannot front-run `launchGameWith` to deploy a hook at the predicted address and DoS initialization (`DefifaDeployer.sol:582-591`).
- **Front-run-resistant hook clone.** `cloneDeterministic` salts on `keccak256(_msgSender(), nonce)` so a different resolved caller produces a different address; a watcher cannot front-run `launchGameWith` to deploy a hook at the predicted address and DoS initialization. For trusted-forwarder calls, `_msgSender()` is the ERC-2771 signer, not the forwarder (`DefifaDeployer.sol:582-591`).
- **Commitment fulfillment is single-shot.** `commitmentsFulfilledFor[gameId]` set BEFORE external calls; a re-entrant call returns early (`DefifaDeployer.sol:319-321`).
- **NO_CONTEST trigger is single-shot.** `noContestTriggeredFor[gameId]` set BEFORE queuing the refund ruleset; a re-entrant call reverts `NoContestAlreadyTriggered` (`DefifaDeployer.sol:662-670`).

Expand All @@ -67,7 +67,7 @@ NO_CONTEST is reported by the view as soon as the condition is met; the on-chain

### B.1 DefifaDeployer bindings

- **Dependency bindings are constructor `immutable`s, not a runtime setter.** `HOOK_CODE_ORIGIN`, `TOKEN_URI_RESOLVER`, `GOVERNOR`, `CONTROLLER`, `REGISTRY`, `DEFIFA_PROJECT_ID`, `BASE_PROTOCOL_PROJECT_ID`, and `HOOK_STORE` are all fixed at construction (`DefifaDeployer.sol:288-308`). These dependencies share unified CREATE2 addresses / canonical project IDs across chains, so nothing chain-specific remains to wire post-deploy — no address can mutate them after deployment.
- **Dependency bindings are constructor `immutable`s, not a runtime setter.** `HOOK_CODE_ORIGIN`, `TOKEN_URI_RESOLVER`, `GOVERNOR`, `CONTROLLER`, `REGISTRY`, `DEFIFA_PROJECT_ID`, `BASE_PROTOCOL_PROJECT_ID`, `HOOK_STORE`, and the ERC-2771 `trustedForwarder` are all fixed at construction (`DefifaDeployer.sol:288-308`). These dependencies share unified CREATE2 addresses / canonical project IDs across chains, so nothing chain-specific remains to wire post-deploy — no address can mutate them after deployment.

No caller can retro-edit any existing game's rulesets, splits, fee divisors, or tier configuration. The protocol-fee divisor (`BASE_PROTOCOL_FEE_DIVISOR = 40` ⇒ 2.5%) and Defifa-fee divisor (`DEFIFA_FEE_DIVISOR = 20` ⇒ 5%) are `constant` (`DefifaDeployer.sol:72, 76`).

Expand All @@ -93,7 +93,7 @@ Owns every game's project NFT (`PROJECTS.createFor(this)` in `launchGameWith`).

**Permissionless game launch:**

- **`launchGameWith(DefifaLaunchProjectData)` payable → gameId** — anyone. Forwards `msg.value` to `JBProjects.createFor` for the creation fee. Validates timing/tier/currency/timeout consistency; clones the Defifa hook via `cloneDeterministic` salted with `msg.sender || nonce`; queues MINT (optional REFUND) and SCORING rulesets via `controller.launchRulesetsFor`; calls `governor.initializeGame`; transfers hook ownership to the governor; registers the clone in the address registry. (`DefifaDeployer.sol:381-642`)
- **`launchGameWith(DefifaLaunchProjectData)` payable → gameId** — anyone, including through the configured ERC-2771 trusted forwarder. Forwards `msg.value` to `JBProjects.createFor` for the creation fee and advertises the resolved `_msgSender()` as `originalPayer` while `createFor` runs. Validates timing/tier/currency/timeout consistency; clones the Defifa hook via `cloneDeterministic` salted with `_msgSender() || nonce`; queues MINT (optional REFUND) and SCORING rulesets via `controller.launchRulesetsFor`; calls `governor.initializeGame`; transfers hook ownership to the governor; registers the clone in the address registry. (`DefifaDeployer.sol:381-642`)
- **Invariant:** game ID reserved before hook deployment so an interleaving `createFor` cannot invalidate the salt. Project NFT permanently held by this contract.

**Permissionless lifecycle triggers:**
Expand All @@ -105,7 +105,7 @@ Owns every game's project NFT (`PROJECTS.createFor(this)` in `launchGameWith`).

**Construction-time bindings:**

- All Defifa dependencies (hook origin, URI resolver, governor, controller, registry, fee project IDs, hook store) are constructor `immutable`s — there is no post-deploy setter to bind or rebind them. (`DefifaDeployer.sol:288-308`)
- All Defifa dependencies (hook origin, URI resolver, governor, controller, registry, fee project IDs, hook store, and trusted forwarder) are constructor `immutable`s — there is no post-deploy setter to bind or rebind them. (`DefifaDeployer.sol:288-308`)

**ERC-721 receipt:**

Expand Down Expand Up @@ -185,7 +185,7 @@ Pure rendering surface — no privileged surface that affects game outcome or fu
11. **State-before-external-call ordering.** `commitmentsFulfilledFor`, `noContestTriggeredFor`, `ratifiedScorecardIdOf`, and `cashOutWeightIsSet` are all written BEFORE the external call that consumes them — re-entrancy cannot replay the action.
12. **Permissionless settlement triggers extract no value beyond canonical allocation.** `fulfillCommitmentsOf`, `triggerNoContestFor`, `mintReservesFor`, `submitScorecardFor`, `attestToScorecardFrom`, `ratifyScorecardFrom` — caller's reward is exactly the gas-funded service to the game, never a redirected payout.
13. **One-shot bindings.** `DefifaHook.initialize`, `DefifaHook.setTierCashOutWeightsTo`, `DefifaGovernor.initializeGame` — all irreversible. `DefifaDeployer`'s own dependencies are constructor `immutable`s (no setter at all).
14. **Front-run-resistant clone deployment.** `cloneDeterministic` salt includes `msg.sender`; a different caller produces a different address (`DefifaDeployer.sol:589-592`).
14. **Front-run-resistant clone deployment.** `cloneDeterministic` salt includes `_msgSender()`; a different resolved caller produces a different address (`DefifaDeployer.sol:589-592`).
15. **Participation immune to balance inflation.** `minParticipation` checks `hook.totalMintCost`, not terminal balance — `addToBalanceOf` donations cannot satisfy the threshold (`DefifaDeployer.sol:256-260`).
16. **NFT-only cash-out path.** `beforeCashOutRecordedWith` reverts if fungible project tokens are cashed out (`DefifaHook.sol:289`). The hook is the sole cash-out surface for Defifa games.

Expand All @@ -197,7 +197,7 @@ For the underlying parimutuel game mechanics, pot-formation math, fee pipeline,

These are NOT third-party attack vectors but are powers held by privileged addresses:

- **`DefifaDeployer` dependency wiring** (governor, controller, registry, fee project IDs, hook origin, URI resolver, hook store) is fixed at construction as `immutable`s — there is no privileged post-deploy setter. Misconfiguration would require deploying with wrong constructor args (wrong governor, wrong fee project IDs, etc.) — operationally caught by deploy script validation.
- **`DefifaDeployer` dependency wiring** (governor, controller, registry, fee project IDs, hook origin, URI resolver, hook store, trusted forwarder) is fixed at construction as `immutable`s — there is no privileged post-deploy setter. Misconfiguration would require deploying with wrong constructor args (wrong governor, wrong fee project IDs, wrong forwarder, etc.) — operationally caught by deploy script validation.
- **`DefifaGovernor` Ownable owner** can call `initializeGame`. In production deployment this owner is the `DefifaDeployer` (called during `launchGameWith`). If the governor's owner were ever rotated to a non-deployer address, that address could bootstrap rogue scorecards for games it didn't deploy — but only games whose hook ownership it also controls, which would require breaking `DefifaDeployer.launchGameWith`'s `hook.transferOwnership(governor)` flow.
- **`DefifaGovernor` as `DefifaHook` owner** is the **single ratifier** of every game's scorecard. The governor itself doesn't decide outcomes — it only enforces the BWA quorum + grace + timelock state machine. But the governor's *bytecode* is the source of truth for ratification rules; replacing the governor (via a controller-level migration or hook-ownership transfer) would change the rules. The deploy script intentionally leaves the governor in place and the hooks owned by it — there is no path in this codebase to rotate hook ownership away from the original governor.
- **`DefifaDeployer` as `JBProjects` NFT holder** is the sole `ownerMustSendPayouts` invoker during SCORING (the SCORING ruleset sets `ownerMustSendPayouts=true`). `fulfillCommitmentsOf` is the deployer's `sendPayoutsOf` invocation — and it's permissionless. No human address has owner power over a Defifa game's payouts post-launch.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Then read the upstream repos this package depends on:

| Contract | Role |
| --- | --- |
| `DefifaDeployer` | Launches games, clones hooks, initializes governance, and fulfills post-game fee commitments. |
| `DefifaDeployer` | Launches games, clones hooks, resolves ERC-2771 callers for launch attribution, initializes governance, and fulfills post-game fee commitments. |
| `DefifaHook` | ERC-721 game-piece hook that tracks tiers, delegation, pending reserves, and cash-out weights for settlement. |
| `DefifaGovernor` | Scorecard governance surface that accepts submissions, attestations, quorum checks, grace periods, and ratification. |
| `DefifaHookLib` | Shared validation and weight logic extracted from the hook. |
Expand Down Expand Up @@ -155,7 +155,7 @@ references/

## Deployment notes

Deployments are handled through Sphinx. The deployer composes Juicebox core, the 721 hook stack, Defifa-specific governance, and metadata rendering into one game-launch surface.
Deployments are handled through Sphinx. The deployer composes Juicebox core, the 721 hook stack, Defifa-specific governance, metadata rendering, and the core trusted forwarder into one ERC-2771-aware game-launch surface.

## Where state lives

Expand Down
4 changes: 2 additions & 2 deletions USER_JOURNEYS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ This repo turns a Juicebox project into a prediction-game lifecycle with fixed p
- the creator knows the game start time, mint duration, optional refund duration, and scoring-timeout assumptions
- tier count, tier names, tier price, and split commitments are finalized
- the chosen terminal and payment token are correct because the launch path is intentionally one-way
- if `JBProjects` has a creation fee, the launch caller sends that exact native-token amount
- if `JBProjects` has a creation fee, the direct caller or trusted forwarder sends that exact native-token amount

**Main Flow**

1. Prepare launch data with timing, tiers, splits, fee-project settings, terminal, and governance params.
2. Call `DefifaDeployer.launchGameWith(...)`.
3. The deployer launches the JB project, clones and initializes `DefifaHook`, initializes the governor state, and stores the game's immutable ops data.
3. The deployer resolves the ERC-2771 caller, advertises that account as the creation-fee payer while reserving the JB project, clones and initializes `DefifaHook`, initializes the governor state, and stores the game's immutable ops data.
4. The game advances through its documented phase sequence.

**Failure Modes**
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ballkidz/defifa",
"version": "1.0.1",
"version": "1.0.2",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
Expand Down
3 changes: 2 additions & 1 deletion script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ contract DeployMainnet is Script, Sphinx {
registry: registry.registry,
defifaProjectId: _defifaProjectId,
baseProtocolProjectId: _baseProtocolProjectId,
hookStore: hookStore
hookStore: hookStore,
trustedForwarder: core.trustedForwarder
});

governor.transferOwnership(address(deployer));
Expand Down
Loading
Loading