Spark Spell 2026-11-05 Security Review

Development Stage Checklist

Preparation

Commits since the last externally reviewed spell

Spell Description & Comments

  • Spell PR has a clear description.
  • Spell PR has a correct spell target date: 2026-11-05
  • Spell contract has a clear description.
  • Spell contract has a correct spell target date.
  • Every significant action and parameter change are clearly commented in the code.
  • Every significant action has valid source url (forum post, poll, atlas).
  • Every parameter change is clearly commented with before/after values.
    • no such comments

Proposed changes

Forum post

[November 5, 2026] Proposed Changes to Spark for Upcoming Spell, https://forum.skyeco.com/t/november-5-2026-proposed-changes-to-spark-for-upcoming-spell/28351

  • Forum post follows the known template.

  • Verify spell content matches the combined scope of the forum posts listed above. From the forum post:

    1. [Base] Spark Liquidity Layer - Activate the Diamond PAU parallel controller on the existing Base ALM Proxy, open a CCTP V2 USDC route from Base to Ethereum, and update who can add and remove the controller’s operators.
    2. [Base] Spark Liquidity Layer - Grant the PAS Configurator admin over the Base PAU Access Controls and PAU Rate Limits.
    3. [Base] Spark Liquidity Layer - Transfer admin of the Base Diamond PAU Beacon from the Base Spark Executor to the Sky governance relay.
    4. [Ethereum] Spark Liquidity Layer - Onboard mint and redeem of OUSD through two Bridge deposit addresses held for the Spark Assets Foundation.
    5. [Ethereum] Spark Liquidity Layer - Onboard the Uniswap v4 OUSD/USDS pool for swaps and liquidity provision.
    6. [Ethereum] SparkLend - Claim accrued SparkLend reserves and route them to the Spark Liquidity Layer and the Spark Operations Multisig (recurring).

    From SparkBase_20261105.sol:

    @notice Spark Liquidity Layer:
      - Onboard Parallel Diamond PAU with CCTP V2.
      - Grant the PAS Configurator admin over the Base Diamond PAU access controls and rate limits.
      - Transfer admin of the Base Diamond PAU Beacon from the Spark Executor to the Sky governance relay.

    From SparkEthereum_20261105.sol:

    @notice Spark Liquidity Layer:
      - Onboard OUSD mint and redeem.
      - Onboard the Uniswap v4 OUSD/USDS pool.

    [Ethereum] SparkLend — Claim accrued SparkLend reserves (…) is a part of the execution process.

  • Verify forum posts contain all new addresses directly or indirectly used in the spell, their constructor arguments and rate limits.

  • IF the Prime Agent spell introduces a major change that can affect external parties, suggest the Core Facilitator to set Core Spell office hours to true.

Contract Structure & Code Quality

  • The only external non-view function in the spell contract is execute().
  • There are no methods that can modify contract state after deployment.
  • No unused imports, interfaces, methods, or variables.
  • All function visibility modifiers are explicitly declared.
  • No redundant code or commented-out functionality.
  • Addresses must be fetched from spark-address-registry IF they are present there, OTHERWISE defined as constant and have trusted source (e.g., when onboarding new contracts).

Every address used in the spell

  • [Ethereum] OUSD_USDS_POOL_ID 0x710254704dfff19aee863a784a36823eb9c926cae8d3d425080d7c88c144a9c7
    • Matches valid external source. (forum post)
  • [Ethereum] Ethereum.BRIDGE_USDC_DEPOSIT
    • Matches valid external source (spark-address-registry/Ethereum.sol)
  • [Ethereum] Ethereum.BRIDGE_OUSD_DEPOSIT
    • Matches valid external source (spark-address-registry/Ethereum.sol)
  • [Ethereum] Ethereum.ALM_CONTROLLER
    • Matches valid external source (spark-address-registry/Ethereum.sol)
  • [Ethereum] Ethereum.ALM_RATE_LIMITS
    • Matches valid external source (spark-address-registry/Ethereum.sol)
  • [Ethereum] Ethereum.USDC
    • Matches valid external source (spark-address-registry/Ethereum.sol)
  • [Ethereum] Ethereum.OUSD
    • Matches valid external source (spark-address-registry/Ethereum.sol)
  • [Base] PAS_CONFIGURATOR 0x458Acd38fD749F6325039e63e8Bc496dBc286D11
    • Matches valid external source. (forum post)
  • [Base] SOTER_FREEZER_MULTISIG 0x747BF29B189e2a070a921Af7Cf65681E3d5F5967
    • Matches valid external source. (forum post)
  • [Base] SOTER_GRANTOR_MULTISIG 0x97EC6398e5dD047BA3223cFC017bFC6436Ac3Fe7
    • Matches valid external source. (forum post)
  • [Base] Base.PLANNER_WALLET 0x062cE42caE04c51D04E77e3D64cc8953a2296FfE
    • Matches valid external source. (forum post)
  • [Base] Base.ALM_PROXY
    • Matches valid external source (spark-address-registry/Base.sol)
  • [Base] Base.PAU_CONTROLLER
    • Matches valid external source (spark-address-registry/Base.sol)
  • [Base] Base.PAU_ADMINISTERED_AGENT
    • Matches valid external source (spark-address-registry/Base.sol)
  • [Base] Base.PAU_GRANTOR_MULTISIG
    • Matches valid external source (spark-address-registry/Base.sol)
  • [Base] Base.PAU_RATELIMITS
    • Matches valid external source (spark-address-registry/Base.sol)
  • [Base] Base.PAU_ACCESS_CONTROLS
    • Matches valid external source (spark-address-registry/Base.sol)
  • [Base] Base.SPARK_BEACON
    • Matches valid external source (spark-address-registry/Base.sol)
  • [Base] Base.SKY_GOV_RELAY
    • Matches valid external source (spark-address-registry/Base.sol)
  • [Base] Base.SPARK_EXECUTOR
    • Matches valid external source (spark-address-registry/Base.sol)
  • [Base] Ethereum.ALM_PROXY (CCTP V2 mint recipient)
    • Matches valid external source (spark-address-registry/Ethereum.sol)

StarGuard execution

  • IF a StarGuard module is onboarded for this Prime Agent, the following additional checks are done:
    • The spell exposes view-only interface function isExecutable() external view returns (bool result).
    • isExecutable either simply returns true or implements additional logic communicated via the relevant forum post (e.g., by describing “earliest launch date” or “office hours” logic, etc).
    • The test ensures the spell is executable before expiration (i.e. isExecutable outputs true before StarGuard.maxDelay() is passed).
    • Third-party actors can not take advantage of the fact that Spell will be executed in a later block than the Core spell, otherwise suggest direct execution.
    • IF Prime Agent spell can not be executed in a later block OR have to be executed sequentially to another Prime Agent spell, direct execution is clearly mentioned in the forum post together with elaborated explanation why it is needed.
    • The direct execution explanation makes sense on the technical level and can not be circumvented by the use of isExecutable() interface.

On-boarding New Contracts

Deployment verification for Base’s PAU stack contracts (PAU_ACCESS_CONTROLS, PAU_RATELIMITS, SPARK_BEACON, PAU_CONTROLLER, and PAU_ADMINISTERED_AGENT) was completed as part of our report: https://portfolio.unvariant.io/reports/spark/spark-cctpv2-dual-controller/

PAS_CONFIGURATOR

Bytecode verification
BeamState state

PAS_BEAM_STATE, read on-chain 2026-10-28:

Wards

  • wards[Base.SKY_GOV_RELAY] = 1
  • wards[L2PASDeployer(0xD6A9F61AcC8241760DFbbbb84f81986835c84477)] = 0

userRoles

Global config

  • hop[address(0)] (general fallback) = 57600 (= 16 hours)
  • maxChange[address(0)] (general fallback) = 1.2e18 (×1.2 WAD growth cap)

Registries

  • rateLimits[Base.PAU_RATELIMITS] = 1
  • controllers[Base.PAU_CONTROLLER] = 1
  • cBeams[PAS_CBEAM(0x492aae70E59551768BAF3c7159d3f951B6ed76Fe)] = 1
  • rateLimitsCBeams[Base.PAU_RATELIMITS][PAS_CBEAM] = 1
  • controllersCBeams[Base.PAU_CONTROLLER][PAS_CBEAM] = 1

Init / pre-approved data

  • initControllerActions[keccak256(removeIntegrations([bytes32("CCTP_FACET")]))][Base.PAU_CONTROLLER] = 1
Timelock state

Roles

  • DEFAULT_ADMIN_ROLE: Base.SKY_GOV_RELAY (the deployer holds no admin)
  • PROPOSER_ROLE: PAS_CORE_COUNCIL
  • CANCELLER_ROLE : PAS_CORE_COUNCIL
  • PAUSER_ROLE: none
  • EXECUTOR_ROLE: address(0)

Global config

  • paused() = true
  • getMinDelay() = 1209600
  • getOperationsCount() = 0

Dependency checks

Interfaces

  • No unused static interfaces.
  • Declared static interface is not present in standard libraries, OTHERWISE should be imported from there.
  • Interface matches the deployed contract.
  • Each static interface declares only functions actually used in the spell code.

Variable Declarations

  • Every contract variable is declared as either constant or immutable.

  • Every precision variable (WAD, RAY, RAD, etc) match their expected value. - No precision variable declared. Variables using precision (e18, e6, e..., WAD, RAY, RAD, etc): SparkEthereum_20261105.sol:

    • setRateLimitData maxAmount: 5_000_000e6, slope: 50_000_000e6 / 1 days with precision 6
      • The precision matches provided source url.
    • setRateLimitData maxAmount: 5_000_000e6, slope: 200_000_000e6 / 1 days with precision 6
      • The precision matches provided source url.
    • configureUniswapV4Pool: depositMax 10_000_000e18, depositSlope 50_000_000e18 / 1 days, withdrawMax type(uint256).max (+ withdrawSlope 0), swapMax 5_000_000e18, swapSlope 100_000_000e18 / 1 days with precision 18
      • The precision matches provided source url.
    • maxSlippage: 0.999e18 (WAD)
      • The precision matches provided source url.

    SparkBase_20261105.sol:

    • setRateLimitData maxAmount: 5_000_000e6, slope: uint256(50_000_000e6) / 1 days with precision 6
      • The precision matches provided source url.
  • Rates are expressed correctly (e.g. per / 1 days).

  • Rates match their source (e.g., governance poll).

  • Timestamps are commented with the full UTC date and convert correctly. - No timestamps in the spell.

  • Timestamps match their source (e.g., governance poll). - No timestamps in the spell.

Deployment & Execution Security

  • No selfdestruct() operations in the spell.
  • No delegatecall() to untrusted contracts.
  • No use of tx.origin for authorization.
  • No external calls that could revert and fail the entire spell execution.
  • No loops with unbounded gas consumption. - the only loops are over SparkLend’s fixed reserve list (20) in the inherited reserve claim
  • No timestamp-dependent logic that could cause issues across the GSM delay.
  • All math operations use safe math libraries or are checked for overflow/underflow.
  • No unchecked return values from external calls.

Access Control

  • Spell execution cannot be front-run by malicious actors.
  • No privileged functions accessible by unauthorized users.

Parameter Changes & Protocol Integration

  • Prime Agent protocol invariants are maintained after spell execution.
  • All parameter changes use the appropriate helper functions IF available.
  • Parameter changes match the governance record applicable to this Prime Agent Spell exactly: the corresponding Atlas edit weekly proposal (Sky-governed), or the proposal forum post and Snapshot vote (self-governed, for changes to the Prime Agent’s own artifact).
  • Spell interacts correctly with existing protocol components.
  • Proper error handling for all external interactions.

Testing

All spell actions:

  • “Onboard Parallel Diamond PAU with CCTP V2.” tested via test_BASE_sll_parallelPAU_roles(), test_BASE_sll_parallelPAU_roles_events(), test_BASE_sll_pauCctpV2_onboarding(), test_BASE_sll_pauCctpV2_rateLimitEnforced(), test_BASE_sll_soterMultisigs_threshold(), test_E2E_sparkLiquidityLayerCCTPCrossChainSetup()
    • The unit test ensures the new value was changed in the spell.
    • The end-to-end test is sufficient to ensure correctness the high-level goal behind this spell action.
  • “Grant the PAS Configurator admin over the Base Diamond PAU access controls and rate limits.” tested via test_BASE_sll_parallelPAU_grantPASConfigurator(), test_BASE_pasConfigurator_setRateLimit(), test_BASE_sll_pasConfigurator_callControllerAction(), test_BASE_beamState_unsetCBeam_revokesConfiguratorAccess(), test_BASE_beamState_stop()
    • The unit test ensures the new value was changed in the spell.
    • The end-to-end test is sufficient to ensure correctness the high-level goal behind this spell action.
  • “Transfer admin of the Base Diamond PAU Beacon from the Spark Executor to the Sky governance relay.” tested via test_BASE_sll_beaconRoleTransfer()
    • The unit test ensures the new value was changed in the spell.
    • The end-to-end test is sufficient to ensure correctness the high-level goal behind this spell action.
  • “Onboard OUSD mint and redeem.” tested via test_ETHEREUM_sll_onboardBridgeUSDC_OUSD(), test_ETHEREUM_sll_onboardBridgeOUSD_USDC() (both are E2E transferAsset tests: over-limit revert, two-tranche transfer to the fixed destination, rate-limit decrement and recharge)
    • The unit test ensures the new value was changed in the spell.
    • The end-to-end test is sufficient to ensure correctness the high-level goal behind this spell action.
  • “Onboard the Uniswap v4 OUSD/USDS pool.” tested via test_ETHEREUM_sll_onboardUniswapV4OUSDUSDS(), test_ETHEREUM_sll_uniswapV4OUSDUSDS_swap(), test_ETHEREUM_sll_uniswapV4OUSDUSDS_poolKey()
    • The unit test ensures the new value was changed in the spell.
    • The end-to-end test is sufficient to ensure correctness the high-level goal behind this spell action.
  • “Claim SparkLend reserves (recurring, inherited base-payload logic)” tested via test_ETHEREUM_sparkLend_withdrawAllReserves()
    • The unit test ensures the new value was changed in the spell.
    • The end-to-end test is sufficient to ensure correctness the high-level goal behind this spell action.
  • All actions are covered by tests.
  • Integration tests verify the end-to-end execution flow.
  • Gas tests ensure execution is possible within the existing block gas limit.
    • no such test
  • All tests are passing in CI at c312d6d1837a4468acebcaf276a6fe673c0d8833.
    • currently CI failed at Spell Caster
  • All tests listed above are not skipped.
  • All tests are passing locally at c312d6d1837a4468acebcaf276a6fe673c0d8833.
❯ forge test
[⠊] Compiling...
No files changed, compilation skipped

Ran 14 tests for src/proposals/20261105/Spell_20261105.t.sol:SparkEthereum_20261105_SpellTests
[SKIP] test_ARBITRUM_ONE_PayloadBytecodeMatches() (gas: 0)
[SKIP] test_AVALANCHE_PayloadBytecodeMatches() (gas: 0)
[PASS] test_BASE_PayloadBytecodeMatches() (gas: 466811)
[PASS] test_BASE_sll_soterMultisigs_threshold() (gas: 86808)
[PASS] test_ETHEREUM_PayloadBytecodeMatches() (gas: 2582494)
[PASS] test_ETHEREUM_PayloadsConfigured() (gas: 74758)
[PASS] test_ETHEREUM_SparkProxyStorage() (gas: 70784678)
[PASS] test_ETHEREUM_sparkLend_withdrawAllReserves() (gas: 72625085)
[SKIP] test_GNOSIS_PayloadBytecodeMatches() (gas: 0)
[SKIP] test_OPTIMISM_PayloadBytecodeMatches() (gas: 0)
[SKIP] test_ROBINHOOD_PayloadBytecodeMatches() (gas: 0)
[SKIP] test_UNICHAIN_PayloadBytecodeMatches() (gas: 0)
[SKIP] test_XLAYER_PayloadBytecodeMatches() (gas: 0)
[PASS] test_officeHours() (gas: 55346)
Suite result: ok. 7 passed; 0 failed; 7 skipped; finished in 88.42s (347.65ms CPU time)

Warning: the following cheatcode(s) are deprecated and will be removed in future versions:
  revertTo(uint256): replaced by `revertToState`
  snapshot(): replaced by `snapshotState`
Ran 15 tests for src/proposals/20261105/Spell_20261105.t.sol:SparkEthereum_20261105_SparklendTests
[PASS] test_ETHEREUM_AllReservesSeeded() (gas: 70847832)
[PASS] test_ETHEREUM_CapAutomator() (gas: 101120960)
[PASS] test_ETHEREUM_E2E_sparkLend() (gas: 3877284777)
[PASS] test_ETHEREUM_EModePriceSources() (gas: 80232539)
[PASS] test_ETHEREUM_FreezerMom() (gas: 204514428)
[PASS] test_ETHEREUM_FreezerMom_Multisig() (gas: 164778024)
[PASS] test_ETHEREUM_Oracles() (gas: 72629192)
[PASS] test_ETHEREUM_RewardsConfiguration() (gas: 71629548)
[PASS] test_ETHEREUM_SpellExecutionDiff() (gas: 305943000)
[PASS] test_ETHEREUM_TokenImplementationsMatch() (gas: 71057094)
[SKIP] test_GNOSIS_AllReservesSeeded() (gas: 0)
[SKIP] test_GNOSIS_E2E_sparkLend() (gas: 0)
[SKIP] test_GNOSIS_Oracles() (gas: 0)
[SKIP] test_GNOSIS_SpellExecutionDiff() (gas: 0)
[SKIP] test_GNOSIS_TokenImplementationsMatch() (gas: 0)
Suite result: ok. 10 passed; 0 failed; 5 skipped; finished in 106.21s (15.56s CPU time)

Warning: the following cheatcode(s) are deprecated and will be removed in future versions:
  snapshot(): replaced by `snapshotState`
  revertTo(uint256): replaced by `revertToState`
Ran 40 tests for src/proposals/20261105/Spell_20261105.t.sol:SparkEthereum_20261105_SLLTests
[PASS] test_ARBITRUM_E2E_sparkLiquidityLayer() (gas: 801227676)
[PASS] test_AVALANCHE_E2E_sparkLiquidityLayer() (gas: 332379338)
[PASS] test_BASE_E2E_sparkLiquidityLayer() (gas: 644563590)
[PASS] test_BASE_beamState_actionRoles() (gas: 411098)
[PASS] test_BASE_beamState_cBeams() (gas: 72104)
[PASS] test_BASE_beamState_controllers() (gas: 72178)
[PASS] test_BASE_beamState_controllersCBeams() (gas: 78474)
[PASS] test_BASE_beamState_delCBeam_onlyCouncil() (gas: 45505)
[PASS] test_BASE_beamState_hop() (gas: 60819)
[PASS] test_BASE_beamState_initControllerActions() (gas: 81859)
[PASS] test_BASE_beamState_initRateLimits() (gas: 55536)
[PASS] test_BASE_beamState_maxChange() (gas: 65961)
[PASS] test_BASE_beamState_rateLimits() (gas: 77426)
[PASS] test_BASE_beamState_rateLimitsCBeams() (gas: 73207)
[PASS] test_BASE_beamState_setHop_onlyTimelock() (gas: 352004)
[PASS] test_BASE_beamState_stop() (gas: 70896526)
[PASS] test_BASE_beamState_stopped() (gas: 71841)
[PASS] test_BASE_beamState_unsetCBeam_revokesConfiguratorAccess() (gas: 70830343)
[PASS] test_BASE_beamState_userRoles() (gas: 80357)
[PASS] test_BASE_beamState_wards() (gas: 97318)
[PASS] test_BASE_pasConfigurator_setRateLimit() (gas: 71016238)
[PASS] test_BASE_pasConfigurator_state() (gas: 27621)
[PASS] test_BASE_sll_beaconRoleTransfer() (gas: 70715741)
[PASS] test_BASE_sll_parallelPAU_grantPASConfigurator() (gas: 70723067)
[PASS] test_BASE_sll_parallelPAU_roles() (gas: 70754851)
[PASS] test_BASE_sll_parallelPAU_roles_events() (gas: 69022984)
[PASS] test_BASE_sll_pasConfigurator_callControllerAction() (gas: 70993304)
[PASS] test_BASE_sll_pauCctpV2_onboarding() (gas: 70764475)
[PASS] test_BASE_sll_pauCctpV2_rateLimitEnforced() (gas: 70728075)
[PASS] test_BASE_timelock_events() (gas: 229971)
[PASS] test_BASE_timelock_state() (gas: 61016)
[PASS] test_E2E_sparkLiquidityLayerCCTPCrossChainSetup() (gas: 5572495896)
[PASS] test_ETHEREUM_E2E_sparkLiquidityLayer() (gas: 2197953573)
[PASS] test_ETHEREUM_sll_onboardBridgeOUSD_USDC() (gas: 70977186)
[PASS] test_ETHEREUM_sll_onboardBridgeUSDC_OUSD() (gas: 70845724)
[PASS] test_ETHEREUM_sll_onboardUniswapV4OUSDUSDS() (gas: 71714484)
[PASS] test_ETHEREUM_sll_uniswapV4OUSDUSDS_poolKey() (gas: 19799)
[PASS] test_ETHEREUM_sll_uniswapV4OUSDUSDS_swap() (gas: 77559111)
[PASS] test_OPTIMISM_E2E_sparkLiquidityLayer() (gas: 421955703)
[PASS] test_UNICHAIN_E2E_sparkLiquidityLayer() (gas: 386033543)
Suite result: ok. 40 passed; 0 failed; 0 skipped; finished in 112.11s (135.99s CPU time)

Ran 3 test suites in 112.15s (306.74s CPU time): 57 tests passed, 0 failed, 12 skipped (69 total tests)

Deployment Stage Checklist

Deployed Contract

  • Both reviewers gave explicit “Good to deploy”.
  • A new comment in the PR contains link to the deployed spell(s) and Tenderly vnet(s).
    • no sumulations
  • The comment also contains codehash of the deployed mainnet spell.
    • no codehash comment
  • The codehash matches one produced locally from the reviewed source code (checked 2026-10-29 at c312d6d):
❯ cast call --rpc-url $MAINNET_RPC_URL --create $(forge inspect SparkEthereum_20261105 bytecode) | cast keccak
0x084497cc546ef29a5597591d820a3342a0b7687bcbd6fed504830f915a4422d9
❯ cast code 0x9C3991A36aE899c7b267208bad2cC1237BE36F5b --rpc-url $MAINNET_RPC_URL | cast keccak
0x084497cc546ef29a5597591d820a3342a0b7687bcbd6fed504830f915a4422d9

❯ cast call --rpc-url $BASE_RPC_URL --create $(forge inspect SparkBase_20261105 bytecode) | cast keccak
0x43490d5e510e82379e9fd731f6d2e13f982fced55d44edbd617e52e6f2ede967
❯ cast code 0x9D65941254A167B844F48FA29fa987783886D82a --rpc-url $BASE_RPC_URL | cast keccak
0x43490d5e510e82379e9fd731f6d2e13f982fced55d44edbd617e52e6f2ede967
  • Every spell is verified on Etherscan or other primary block explorer for this chain.
  • Every spell code matches local source code at the “good to deploy” commit.
  • Etherscan settings (optimizer, EVM version, license) match local ones.
  • Every spell is deployed using standard CREATE (not CREATE2).
  • Tests are updated to execute against the deployed spell(s).
  • No test is skipped after deployment
  • All tests are passing in CI at c312d6d1837a4468acebcaf276a6fe673c0d8833.
    • currently CI failed at Spell Caster
  • All tests are passing locally at c312d6d1837a4468acebcaf276a6fe673c0d8833.