Skip to content
Open
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: 2 additions & 0 deletions crates/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,7 @@ sbpf-debugger = ["litesvm/sbpf-debugger"]
sqlite = ["surfpool-db/sqlite"]
postgres = ["surfpool-db/postgres"]
ignore_tests_ci = []
# Tests that fetch live mainnet accounts; off by default because they need a network.
integration-tests = []
register-tracing = ["litesvm/register-tracing"]
prometheus = ["dep:opentelemetry", "dep:opentelemetry_sdk", "dep:opentelemetry-prometheus", "dep:prometheus", "dep:axum"]
30 changes: 29 additions & 1 deletion crates/core/src/scenarios/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Protocols that are natively supported by Surfpool will have their IDLs included
- **Pyth v2** - Price oracle with 4 price feed templates (SOL/USD, BTC/USD, ETH/BTC, ETH/USD)
- **Jupiter v6** - DEX aggregator with TokenLedger manipulation template
- **Switchboard On-Demand** - On-demand oracle with QuoteAccount override template
- **Kamino v1.x** – Lending protocol with Reserve liquidity, risk config, and Obligation health override templates
- **Kamino** – Lending (v1.23.0), Scope oracle, Farms, Swap/LIMO, Earn vaults and Liquidity, across six programs. See [protocols/kamino/README.md](./protocols/kamino/README.md)
- **Drift v2** - Perp and spot markets, user state, and global state

For custom protocols, an IDL can be registered at runtime using the [`surfnet_registerIdl`](https://docs.surfpool.run/rpc/cheatcodes#surfnet-registeridl) RPC cheatcode.
Expand All @@ -26,6 +26,34 @@ Scenarios can be registered at runtime using the [`surfnet_registerScenario`](ht
This cheatcode takes in a scenario definition in JSON format, which includes the scenario name, description, and a list of overrides to apply to accounts.
Each override contains a map of the field in the account to override (as indexed in the IDL), and the value to apply for that key.

Field keys use dot notation. Segments address struct fields by name and array elements by
zero-based index, so `liquidity.total_available_amount`, `deposits.0.deposited_amount` and
`config.borrow_rate_curve.points.3.borrow_rate_bps` are all valid. Supplying a composite value (a
whole struct or array) also works, but it must be **complete** - every field of every element,
padding included - because the account is re-encoded with Borsh. An out-of-range index or a
non-numeric segment on an array is a hard error, never a silent write elsewhere.

By default an override applies to exactly one slot. Set `"persist": true` and it is re-applied on
every following slot, which is needed when something else writes the account in between - a
transaction, or another override fetching it fresh. Persist inputs nothing in the scenario writes
(an oracle price, a disabled switch, a risk parameter), never state the transactions under test
mutate: re-applying reverts their writes at the start of the next slot, so a pool would refill
itself after every swap. Only one entry is queued per override, so it is never applied twice to
one slot, and `fetchBeforeUse` applies to the first slot only - once the account is forked, later
slots re-pin the fields without re-fetching it.

### Kamino integration tests

Byte-level Kamino coverage lives in `crates/core/src/tests/kamino/`. Those tests fetch the real
accounts from mainnet, so they need a network connection and are compiled only behind a feature:

```
cargo test -p surfpool-core --features integration-tests kamino
```

Set `SURFPOOL_TEST_RPC_URL` to use a private endpoint instead of the public one. The default test
run needs no network.

### Override Templates
Directly using the `surfnet_registerScenario` endpoint requires building out a map of account keys that are specific to the schema of the account that is being written to.
This is a cumbersome process in most cases.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,58 +1,66 @@
{
"id": "kamino-liquidation-arb-example",
"name": "Kamino Liquidation Arbitrage - POPCAT/SOL",
"description": "A scenario replicating the liquidation arbitrage from tx 5xDtqZcY4CzDHjdT61VsGuF1YL7fADUhPz6hCdA2RVMFMhUjuSh5rqkrLKFXfh4gXevMN1L2NjnCaRCAZYxVmqpz. This scenario sets up a Kamino obligation to be liquidatable, and manipulates Whirlpool and Raydium AMM pool states to create a profitable arbitrage opportunity.",
"tags": ["liquidation", "arbitrage", "kamino", "whirlpool", "raydium"],
"name": "Kamino Liquidation Arbitrage - POPCAT/USDC",
"description": "Puts a live Kamino obligation on the Altcoins Market underwater and leaves a profitable exit. Halving POPCAT in Scope makes Kamino value the collateral below its USDC debt while the Whirlpool pools keep their real price, so a liquidator seizes POPCAT cheaply and sells it POPCAT -> SOL -> USDC. Obligation: 3iprSGrEQdBxhmqV399tYQQPG8Z1Hh2aYFrBwgqFXjGS",
"tags": ["liquidation", "arbitrage", "kamino", "scope", "whirlpool"],
"overrides": [
{
"id": "obligation-unhealthy",
"templateId": "kamino-obligation-health",
"label": "Make Obligation Unhealthy",
"id": "scope-crash-popcat",
"templateId": "kamino-scope-price",
"label": "POPCAT crashes 50% in Scope (index 492)",
"scenarioRelativeSlot": 0,
"enabled": true,
"fetchBeforeUse": true,
"persist": true,
"account": {
"pubkey": "3iprSGrEQdBxhmqV399tYQQPG8Z1Hh2aYFrBwgqFXjGS"
"pubkey": "3NJYftD5sjVfxSnUdZ1wVML8f3aC6mp1CXCL6L7TnU8C"
},
"values": {
"borrowed_value_sf": 1000000000000000000,
"unhealthy_borrow_value_sf": 500000000000000000,
"deposited_value_sf": 800000000000000000,
"allowed_borrow_value_sf": 600000000000000000
"prices.492.price.value": 2124828,
"prices.492.price.exp": 8
}
},
{
"id": "whirlpool-popcat-sol-price",
"id": "popcat-reserve-tighten-threshold",
"templateId": "kamino-reserve-config",
"label": "POPCAT reserve liquidates above 29% LTV (was 40%)",
"scenarioRelativeSlot": 0,
"enabled": true,
"fetchBeforeUse": true,
"account": {
"pubkey": "3xSpNvuHAfyzpWxUg2kJkVHWhrBcGLL7RxkZyzecQZkw"
},
"values": {
"config.liquidation_threshold_pct": 29,
"config.max_liquidation_bonus_bps": 1000
}
},
{
"id": "whirlpool-popcat-sol-depth",
"templateId": "whirlpool-popcat-sol",
"label": "Set Whirlpool POPCAT/SOL Price",
"label": "Deepen POPCAT/SOL so the exit does not slip",
"scenarioRelativeSlot": 0,
"enabled": true,
"fetchBeforeUse": true,
"account": {
"pubkey": "Czfq3xZZDmsdGdUyrNLtRhGc47cXcZtLG4crryfu44zE"
},
"values": {
"liquidity": "5000000000000000",
"sqrt_price": "1844674407370955161",
"tick_current_index": 0
"liquidity": 5000000000000000
}
},
{
"id": "raydium-amm-popcat-sol-state",
"templateId": "raydium-amm-popcat-sol",
"label": "Set Raydium AMM POPCAT/SOL State",
"id": "whirlpool-sol-usdc-depth",
"templateId": "whirlpool-sol-usdc",
"label": "Deepen SOL/USDC to close the route back to USDC",
"scenarioRelativeSlot": 0,
"enabled": true,
"fetchBeforeUse": true,
"account": {
"pubkey": "FRhB8L7Y9Qq41qZXYLtC2nw8An1RJfLLxRF2x9RwLLMo"
"pubkey": "HJPjoWUrhoZzkNfRpHuieeFk9WcZWjwy6PBjZ81ngndJ"
},
"values": {
"status": 1,
"state": 1,
"lp_amount": 10000000000000,
"fees.swap_fee_numerator": 25,
"fees.swap_fee_denominator": 10000
"liquidity": 50000000000000000
}
}
]
Expand Down
Loading