Skip to content
Merged
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
58 changes: 54 additions & 4 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,39 @@ src/
```
Deployer → REVDeployer.deployFor()
→ Create JB project via JBController
→ Convert REV stages → JBRulesetConfigs
→ Convert REV stages → JBRulesetConfigs (see Stage-to-Ruleset Mapping below)
→ Each stage: duration, weight, cashOutTaxRate, splits
→ Auto-issuance: pre-mint tokens to specified beneficiaries per chain
→ Auto-issuance: record per-beneficiary token counts for later claiming
→ Set REVDeployer as data hook (controls pay + cashout behavior)
→ Initialize buyback pools at 1:1 price, configure buyback hook
→ Deploy suckers for cross-chain operation
→ Deploy tiered ERC-721 hook (always — empty by default, pre-configured if specified)
→ Compute matching hash for cross-chain deployment verification
→ Compute matching hash and store it for cross-chain sucker deployment
```

#### Matching Hash

The matching hash ensures that revnet deployments on different chains share identical economic parameters. It is computed inside `_makeRulesetConfigurations` by incrementally ABI-encoding the configuration fields, then taking the `keccak256` of the result.

Fields included in the hash (in encoding order):
1. **Base fields:** `baseCurrency`, `description.name`, `description.ticker`, `description.salt`
2. **Per-stage fields** (appended for each stage): `startsAtOrAfter` (defaults to `block.timestamp` for the first stage if zero), `splitPercent`, `initialIssuance`, `issuanceCutFrequency`, `issuanceCutPercent`, `cashOutTaxRate`
3. **Per-auto-issuance fields** (appended for each auto-issuance within a stage): `chainId`, `beneficiary`, `count`

The hash is stored in `hashedEncodedConfigurationOf[revnetId]` and used as part of the CREATE2 salt when deploying suckers via `SUCKER_REGISTRY.deploySuckersFor`. This guarantees that cross-chain sucker peers can only be deployed for revnets whose economic configuration matches exactly — a deployment on Chain B with different stage parameters would produce a different hash, a different salt, and therefore a different sucker address, preventing it from peering with Chain A's suckers.

Note that `splits` (the specific split recipient addresses) are **not** included in the hash. Splits may contain chain-specific addresses, so they are excluded to allow legitimate cross-chain deployments where the only difference is the split recipient addresses.

#### Auto-Issuance

Auto-issuance pre-allocates tokens to specified beneficiaries when a stage begins. Each `REVAutoIssuance` entry specifies a `chainId`, a `beneficiary` address, and a token `count`.

During deployment, the deployer records auto-issuance amounts in `amountToAutoIssue[revnetId][stageId][beneficiary]` — but only for entries whose `chainId` matches `block.chainid`. Entries for other chains are still included in the matching hash (ensuring cross-chain consistency) but are skipped for on-chain storage.

Claiming is a separate step: anyone can call `autoIssueFor(revnetId, stageId, beneficiary)` after the stage has started. This function verifies the stage's ruleset has begun (`ruleset.start <= block.timestamp`), zeroes the stored amount, and calls `CONTROLLER.mintTokensOf` to mint tokens directly to the beneficiary — bypassing the reserved percent so the full count goes to the beneficiary.

Stage IDs are assigned as `block.timestamp + i` (where `i` is the stage index), matching the JBRulesets ID assignment scheme when all stages are queued in a single transaction.

### Data Hook Behavior
```
Payment → REVDeployer.beforePayRecordedWith()
Expand Down Expand Up @@ -61,6 +84,31 @@ Liquidate → REVLoans.liquidateExpiredLoansFrom()
→ Collateral permanently destroyed (was burned at borrow time)
```

## Stage-to-Ruleset Mapping

Each `REVStageConfig` is converted to a `JBRulesetConfig` by `_makeRulesetConfiguration`. The mapping is direct — revnet stages are a constrained interface over Juicebox rulesets:

| REVStageConfig field | JBRulesetConfig field | Notes |
|---|---|---|
| `startsAtOrAfter` | `mustStartAtOrAfter` | Passed through directly |
| `issuanceCutFrequency` | `duration` | How often the issuance rate decays |
| `initialIssuance` | `weight` | Tokens per unit of base currency |
| `issuanceCutPercent` | `weightCutPercent` | Percent decrease each cycle (out of 1,000,000,000) |
| `splitPercent` | `metadata.reservedPercent` | Percent of new tokens split to recipients (out of 10,000) |
| `cashOutTaxRate` | `metadata.cashOutTaxRate` | Bonding curve tax on cash outs (out of 10,000) |
| `splits` | `splitGroups[0].splits` | Reserved token split recipients (group ID: RESERVED_TOKENS) |
| `extraMetadata` | `metadata.metadata` | 14-bit field for hook-specific flags |

Fields set automatically by the deployer (not configurable per stage):
- `metadata.baseCurrency` — from `REVConfig.baseCurrency`
- `metadata.useTotalSurplusForCashOuts` — always `true`
- `metadata.allowOwnerMinting` — always `true` (required for auto-issuance)
- `metadata.useDataHookForPay` — always `true`
- `metadata.useDataHookForCashOut` — always `true`
- `metadata.dataHook` — always `address(REVDeployer)`
- `approvalHook` — always `address(0)` (no approval hook; stages are immutable)
- `fundAccessLimitGroups` — set to `uint224.max` surplus allowance per terminal token for loan withdrawals

## Extension Points

| Point | Interface | Purpose |
Expand All @@ -82,6 +130,8 @@ Liquidate → REVLoans.liquidateExpiredLoansFrom()

## Key Design Decisions
- Stages are immutable after deployment — no owner can change ruleset parameters
- Matching hash ensures cross-chain deployments have identical economic parameters
- Matching hash ensures cross-chain deployments have identical economic parameters. It covers all economic fields (issuance, decay, tax rates, auto-issuances) but intentionally excludes split recipient addresses, which may differ by chain. The hash is used as a CREATE2 salt component for sucker deployment, so mismatched configs produce different sucker addresses that cannot peer with each other.
- REVDeployer is the data hook for all revnets it deploys — centralizes behavioral control
- Loans use bonding curve value, not market price — independent of external DEX pricing
- Auto-issuance is deferred, not instant — token amounts are recorded at deploy time but minted via a separate `autoIssueFor` call after the stage starts. This separates deployment from issuance, allows anyone to trigger the mint permissionlessly, and ensures tokens are not minted before their stage is active.
- No approval hook — revnet rulesets set `approvalHook` to `address(0)` because stages are configured immutably at deployment. There is no governance or owner who could queue a change that would need approval.
Loading