Events and errors
This layer defines the 26 event structs the program emits and the 100 error variants the program returns.
Files
| Path | Lines | Purpose |
|---|---|---|
programs/sickle/src/events.rs | 696 | The #[event] structs, the allocation-reusing emitter, and 3 layout tests. |
programs/sickle/src/errors.rs | 216 | The SickleError enum with 100 #[msg] variants. |
Responsibilities
- Define one
#[event]struct per observable effect with#[derive(Clone)]atevents.rs:108-481. - Provide
ReusableEventEmitter, an emitter that reuses one buffer across a batch of self-CPIs atevents.rs:64-106. - Check every event discriminator against
sha256("event:<Name>")[..8]in a unit test atevents.rs:492-540. - Define the
SickleErrorenum. Anchor assigns code 6000 plus the variant index aterrors.rs:3-4. - Keep the variant order append-only, as the comments at
errors.rs:103,errors.rs:196, anderrors.rs:213require.
The layer does not emit events and does not return errors itself. The handlers and runners do both. This page lists where.
Public items
events.rs
Every event has #[event] and #[derive(Clone)]. The Clone derive exists so a handler can pass the same value to emit_cpi! and then to emit!. The module comment at events.rs:14-20 shows the pattern.
| Event | Declared | Fields | Built at |
|---|---|---|---|
SickleCreated | 111 | sickle, owner, manager, referral_code: [u8; 32] | instructions/sickle/initialize_sickle.rs:39 |
PositionOpened | 122 | sickle, protocol, pool, position, tick_lower_index, tick_upper_index | venue/open_increase.rs:177 for the 2 CLMM open instructions. venue/owner_rebalance.rs:409 for the 4 CLMM rebalance instructions through map_events. |
DlmmPositionOpened | 135 | sickle, protocol, pair, position, lower_bin_id, upper_bin_id | instructions/meteora/open_position.rs:136, instructions/meteora/intent_open_position.rs:224 |
DlmmPositionResized | 149 | sickle, protocol, pair, position, old_lower_bin_id, old_upper_bin_id, new_lower_bin_id, new_upper_bin_id | instructions/meteora/increase_position_length.rs:136, instructions/meteora/intent_increase_position_length.rs:183 |
DlmmLiquidityAdded | 165 | sickle, protocol, pair, position, min_bin_id, max_bin_id, strategy_type: u8, amount_x, amount_y | instructions/meteora/add_liquidity.rs:253, instructions/meteora/intent_add_liquidity.rs:211 |
DlmmLiquidityRemoved | 183 | sickle, protocol, pair, position, from_bin_id, to_bin_id, bps_to_remove: u16, amount_x, amount_y | instructions/meteora/remove_liquidity.rs:260, instructions/meteora/exit_for.rs:328, instructions/meteora/rebalance_for.rs:408 |
DlmmRebalanced | 201 | sickle, pair, old_position, new_position, old_min_bin_id, old_max_bin_id, new_min_bin_id, new_max_bin_id, drained_x, drained_y | instructions/meteora/rebalance_for.rs:477 |
PositionClosed | 220 | sickle, protocol, position | venue/decrease_close.rs:362 for the 2 CLMM close instructions. instructions/meteora/close_position.rs:137. |
LiquidityIncreased | 230 | sickle, protocol, pool, position, liquidity: u128, amount_a, amount_b | venue/open_increase.rs:361 for the 2 CLMM increase instructions. |
LiquidityDecreased | 245 | sickle, protocol, pool, position, liquidity: u128, amount_a, amount_b | venue/decrease_close.rs:178 for the 2 CLMM decrease instructions. |
FeesCollected | 260 | sickle, position, amount_a, amount_b, authority, action: FeeAction | instructions/orca/collect_fees.rs:131, instructions/raydium/collect_fees.rs:109, instructions/orca/harvest_for.rs:154, instructions/orca/compound_for.rs:198, instructions/raydium/harvest_for.rs:126, instructions/raydium/compound_for.rs:165, venue/owner_rebalance.rs:315, venue/owner_rebalance.rs:315, instructions/meteora/claim_fees.rs:296, instructions/meteora/harvest_for.rs:148, instructions/meteora/compound_for.rs:148, instructions/meteora/exit_for.rs:339, instructions/meteora/rebalance_for.rs:419 |
RewardsCollected | 273 | sickle, position, mint, amount, authority, action: FeeAction | instructions/orca/collect_rewards.rs:78, instructions/orca/harvest_for.rs:193, instructions/orca/compound_for.rs:237, instructions/raydium/collect_fees.rs:148, instructions/raydium/harvest_for.rs:165, instructions/raydium/compound_for.rs:204, venue/decrease_close.rs:370, venue/owner_rebalance.rs:352, venue/owner_rebalance.rs:352, instructions/meteora/claim_reward.rs:269, instructions/meteora/harvest_for.rs:173, instructions/meteora/compound_for.rs:173, instructions/meteora/exit_for.rs:379, instructions/meteora/rebalance_for.rs:459 |
FeeCharged | 287 | sickle, action: FeeAction, mint, amount, collector | 32 sites. Examples: instructions/jupiter/swap.rs:529, instructions/orca/collect_fees.rs:143, venue/owner_rebalance.rs:335, venue/owner_rebalance.rs:335, venue/decrease_close.rs:379, instructions/meteora/exit_for.rs:360. |
Rebalanced | 302 | sickle, pool, old_position, new_position, old_tick_lower_index, old_tick_upper_index, new_tick_lower_index, new_tick_upper_index, drained_a, drained_b | venue/owner_rebalance.rs:397 for the 4 CLMM rebalance instructions through map_events. |
Exited | 320 | sickle, pool, position, amount_a, amount_b | venue/keeper_exit.rs:216 for exit_for and raydium_exit_for. instructions/meteora/exit_for.rs:397. |
Swept | 332 | sickle, mint, amount, destination | instructions/sickle/sweep_token.rs:76 |
Swapped | 345 | sickle, input_mint, output_mint, amount_in, amount_out, fee_action: Option<FeeAction> | instructions/jupiter/swap.rs:514 |
SettingsUpdated | 360 | sickle, position, auto_rebalance, auto_exit, automate_rewards, removed | instructions/position/set_position_settings.rs:97, instructions/position/unset_position_settings.rs:59, settings.rs:55, venue/owner_rebalance.rs:530, venue/keeper_rebalance.rs:213, instructions/intent/finalize_intent.rs:307, instructions/meteora/rebalance_for.rs:489 |
IntentAuthorized | 374 | sickle, nonce, executor, action: IntentAction, digest: [u8; 32], expiry_slot, signer_kind: u8 | instructions/intent/preauthorize_intent.rs:136 |
DlmmIntentAuthorized | 392 | the 7 IntentAuthorized fields plus min_bin_id, max_bin_id, strategy_type: u8 | instructions/intent/preauthorize_dlmm_intent.rs:119 |
IntentAuthorizationCancelled | 409 | sickle, nonce | instructions/intent/cancel_intent_authorization.rs:52 |
DlmmIntentAuthorizationCancelled | 417 | sickle, nonce | instructions/intent/cancel_dlmm_intent_authorization.rs:49 |
IntentAuthorizationExpired | 426 | sickle, nonce, expiry_slot | instructions/intent/expire_intent_authorization.rs:64 |
DlmmIntentAuthorizationExpired | 436 | sickle, nonce, expiry_slot | instructions/intent/expire_dlmm_intent_authorization.rs:61 |
IntentSessionUpdated | 447 | sickle, signer, active, permissions: u8, pool, expiry_slot, generation | instructions/intent/set_intent_session.rs:110 |
IntentFinalized | 463 | sickle, nonce, action: IntentAction, position, new_position, credits: [u64; 3], residuals: [u64; 3] | instructions/intent/finalize_intent.rs:326. instructions/intent/finalize_dlmm_intent.rs:100 emits the same struct through the shared receipt. |
Untyped fields are Pubkey, u64, i32, or bool as their names suggest. Read the declared line for the exact type.
A runner builds an event and returns it inside a receipt struct. The handler emits it. Example: venue/open_increase.rs:87 stores a PositionOpened in the run receipt, and instructions/orca/open_position.rs:96-97 emits it.
Two Anchor accounts make emission possible. #[event_cpi] on an accounts struct appends event_authority and program to the account list. Source: anchor-attribute-event-1.1.2/src/lib.rs:201-214. 49 handler files carry the attribute. 16 do not: the 12 admin handlers, set_manager, set_custom_automator, create_sickle_ata, and initialize_intent_domain.
| Item | Kind | Meaning |
|---|---|---|
EVENT_BUFFER_CAPACITY | const | 8 bytes of tag plus 256 bytes. The initial capacity of the reusable buffer. events.rs:43. |
serialize_event_cpi_data | fn | Clears the buffer, then writes the event tag, the discriminator, and the borsh fields. events.rs:45-51. |
ReusableEventEmitter | struct | pub(crate). Holds the event authority, one data buffer, and one account-meta vector. events.rs:64-68. |
ReusableEventEmitter::new | fn | Allocates the buffer and the single-entry meta vector once. events.rs:71-78. |
ReusableEventEmitter::emit_cpi | fn | Serializes, moves both vectors into an Instruction, calls invoke_signed with the event authority seeds, and moves the vectors back. events.rs:81-96. |
ReusableEventEmitter::emit_user | fn | Calls emit_cpi, then logs the buffer after the tag with sol_log_data. events.rs:99-105. |
Four handlers use the emitter: instructions/raydium/rebalance_for.rs:244, instructions/raydium/exit_for.rs:254, instructions/meteora/rebalance_for.rs:407, and instructions/meteora/exit_for.rs:327. The other 45 emitting files use the Anchor macros.
The emitter exists because the SBF bump allocator does not free a dropped Vec inside one instruction. The comment at events.rs:55-63 states that Anchor's emit_cpi! allocates 3 vectors per event and that the late Raydium event batch ran out of the 32 KiB heap. The emitter allocates once and recovers the capacity after each self-CPI.
errors.rs
SickleError is one #[error_code] enum at errors.rs:3-216. Anchor assigns each variant the code 6000 plus its index. The test at venue/adapters/raydium_flows.rs:1261 computes 6000 + SickleError::RewardCollectorMismatch as u32 and compares it with error_code_number. The comment at errors.rs:103 states the order rule.
The table lists every variant, its code, its message, the number of SickleError::<Variant> sites outside errors.rs, and one representative site. Sites include unit tests. There are 905 sites in total.
| Code | Variant | Declared | Message | Sites | Representative site |
|---|---|---|---|---|---|
| 6000 | Unauthorized | errors.rs:6 | Operation not allowed for caller | 49 | venue/keeper_exit.rs:271 |
| 6001 | NotGuardianOrAdmin | errors.rs:8 | Caller is neither guardian nor admin | 2 | instructions/admin/pause.rs:26 |
| 6002 | Paused | errors.rs:10 | Program is paused | 5 | state.rs:881 |
| 6003 | FeeTooHigh | errors.rs:12 | Fee exceeds configured maximum | 2 | instructions/admin/set_fee.rs:21 |
| 6004 | SlippageExceeded | errors.rs:14 | Slippage tolerance exceeded | 3 | venue/adapters/raydium_flows.rs:576 |
| 6005 | InvalidAccountOwner | errors.rs:16 | Unexpected account owner | 108 | intent.rs:1454 |
| 6006 | ProtocolNotAllowed | errors.rs:18 | Protocol program is not allowlisted | 2 | state.rs:963 |
| 6007 | InvalidPositionSettings | errors.rs:20 | Invalid position settings | 24 | state.rs:388 |
| 6008 | InvalidTickRange | errors.rs:22 | Tick lower index must be below tick upper index | 5 | state.rs:130 |
| 6009 | InvalidCutoffTicks | errors.rs:24 | Cutoff ticks are inverted or outside the protocol tick bounds | 1 | state.rs:415 |
| 6010 | InvalidBufferTicks | errors.rs:26 | Buffer ticks are outside the allowed band | 3 | state.rs:406 |
| 6011 | InvalidSlippageBps | errors.rs:28 | Slippage bps exceed the maximum | 2 | state.rs:425 |
| 6012 | InvalidPriceImpactBps | errors.rs:30 | Price impact bps are zero or exceed the maximum | 2 | state.rs:429 |
| 6013 | InvalidDustBps | errors.rs:32 | Dust bps are zero or exceed the maximum | 5 | intent.rs:393 |
| 6014 | InvalidHarvestTokenOut | errors.rs:34 | Harvest token out requires harvest reward behavior | 1 | state.rs:78 |
| 6015 | ExitTriggersNotSet | errors.rs:36 | Auto-exit requires at least one trigger tick | 2 | state.rs:478 |
| 6016 | InvalidExitTriggers | errors.rs:38 | Exit trigger ticks are inverted or outside the protocol tick bounds | 1 | state.rs:484 |
| 6017 | AutoRebalanceNotSet | errors.rs:40 | Rebalance cutoffs set while auto-rebalance is disabled | 5 | state.rs:464 |
| 6018 | AutoExitNotSet | errors.rs:42 | Exit config set while auto-exit is disabled | 3 | state.rs:497 |
| 6019 | InvalidPosition | errors.rs:44 | Position account does not match the expected protocol-derived address | 98 | meteora.rs:357 |
| 6020 | InvalidMetadataUpdateAuth | errors.rs:46 | Metadata update authority does not match the protocol's expected key | 3 | venue/adapters/orca.rs:648 |
| 6021 | InvalidRentReceiver | errors.rs:48 | Rent receiver must be the sickle owner | 28 | venue/decrease_close.rs:278 |
| 6022 | InvalidPermissions | errors.rs:50 | Unknown automation permission bits | 10 | instructions/admin/set_global_automator.rs:45 |
| 6023 | MathOverflow | errors.rs:52 | Arithmetic overflow | 60 | intent.rs:555 |
| 6024 | InvalidProtocolAccountData | errors.rs:54 | Protocol account data is malformed | 106 | state.rs:127 |
| 6025 | InvalidTickWidth | errors.rs:56 | Position width does not match the configured tick spaces | 5 | state.rs:135 |
| 6026 | PositionSettingsMismatch | errors.rs:58 | Old and new position settings must be supplied together | 4 | venue/owner_rebalance.rs:492 |
| 6027 | AutoHarvestNotSet | errors.rs:60 | Auto-harvest is not enabled in the position settings | 2 | state.rs:531 |
| 6028 | AutoCompoundNotSet | errors.rs:62 | Auto-compound is not enabled in the position settings | 2 | state.rs:542 |
| 6029 | RebalanceConfigNotSet | errors.rs:64 | Auto-rebalance rebalance config is not set | 3 | state.rs:649 |
| 6030 | TickWithinRange | errors.rs:66 | Pool tick is still within the position's rebalance range | 7 | state.rs:676 |
| 6031 | TickOutsideStopLossRange | errors.rs:68 | Pool tick is outside the configured stop-loss range | 2 | state.rs:682 |
| 6032 | RebalanceTooSoon | errors.rs:70 | Rebalance is still within the configured debounce delay | 2 | state.rs:660 |
| 6033 | SwapAmountZero | errors.rs:72 | Swap input amount must be nonzero | 1 | instructions/jupiter/swap.rs:102 |
| 6034 | SwapMinOutZero | errors.rs:74 | Swap minimum output must be nonzero | 1 | instructions/jupiter/swap.rs:103 |
| 6035 | InvalidSwapMints | errors.rs:76 | Swap input and output mints must differ | 3 | instructions/jupiter/swap.rs:104-120 |
| 6036 | SwapInputExceeded | errors.rs:78 | Swap spent more input than allowed | 1 | instructions/jupiter/swap.rs:285-288 |
| 6037 | SwapVaultNotAllowed | errors.rs:80 | Sickle-owned token account not allowed in the swap route | 1 | instructions/jupiter/swap.rs:43 |
| 6038 | InvalidSwapFeeAction | errors.rs:82 | Only Deposit and Withdraw fees may be charged on a swap leg | 2 | instructions/jupiter/swap.rs:88,143 |
| 6039 | MissingFeeCollectorAccount | errors.rs:84 | Fee collector account required for the swap fee | 1 | instructions/jupiter/swap.rs:128-131 |
| 6040 | InvalidFeeCollectorMint | errors.rs:86 | Fee collector account mint does not match the charged side | 7 | fees.rs:86 |
| 6041 | InvalidProgramData | errors.rs:88 | Program data account does not match the program | 2 | instructions/intent/initialize_intent_domain.rs:30 |
| 6042 | NotUpgradeAuthority | errors.rs:90 | Initializer must be the program upgrade authority | 2 | instructions/intent/initialize_intent_domain.rs:38 |
| 6043 | SwapConsentRequired | errors.rs:92 | jupiter_swap outside an intent flow requires the sickle owner | 0 | none |
| 6044 | IncreaseConsentRequired | errors.rs:94 | Automator increase requires position settings consenting to a matching automated flow | 2 | venue/open_increase.rs:268 |
| 6045 | MissingZapInLeg | errors.rs:96 | Automated flow must re-add liquidity to the position later in the same transaction | 0 | none |
| 6046 | InvalidRangePlacement | errors.rs:98 | Reopened range must match the configured tick spaces around the current pool tick | 3 | venue/keeper_rebalance.rs:309 |
| 6047 | SwapVaultTampered | errors.rs:100 | Swap route altered a vault's owner, mint, delegate, or close authority | 1 | instructions/jupiter/swap.rs:262-265 |
| 6048 | InvalidInstructionsSysvar | errors.rs:102 | Instructions sysvar account does not match the sysvar address | 11 | venue/keeper_exit.rs:260 |
| 6049 | InvalidIntentDomainVersion | errors.rs:105 | Intent domain is missing or has an unsupported version | 1 | intent.rs:709 |
| 6050 | IntentNonceMismatch | errors.rs:107 | Authorization nonce does not match the sickle's next intent nonce | 2 | intent.rs:745 |
| 6051 | MissingIntentSignature | errors.rs:109 | Preauthorization requires an immediately preceding ed25519 verify instruction | 2 | intent.rs:1335 |
| 6052 | InvalidEd25519Instruction | errors.rs:111 | The ed25519 instruction is malformed or its offsets escape the instruction | 11 | intent.rs:1245 |
| 6053 | IntentDigestMismatch | errors.rs:113 | The signed message does not match the authorization digest | 1 | intent.rs:1320 |
| 6054 | InvalidIntentSigner | errors.rs:115 | The digest signer is neither the sickle owner nor the named session signer | 1 | instructions/intent/preauthorize.rs:61 |
| 6055 | IntentSessionRequired | errors.rs:117 | Session-signed authorization requires the intent session account | 2 | intent.rs:1552 |
| 6056 | InvalidIntentSession | errors.rs:119 | Intent session signer is invalid | 3 | intent.rs:791 |
| 6057 | SessionInactive | errors.rs:121 | Intent session is not active | 1 | intent.rs:793 |
| 6058 | SessionExpired | errors.rs:123 | Intent session or authorization expiry exceeds the session expiry | 5 | intent.rs:794 |
| 6059 | SessionGenerationMismatch | errors.rs:125 | Authorization was signed against a stale session generation | 1 | intent.rs:801 |
| 6060 | SessionScopeExceeded | errors.rs:127 | Authorization exceeds the session's action or pool scope | 2 | intent.rs:806 |
| 6061 | NoncanonicalIntent | errors.rs:131 | Authorization bounds are noncanonical (sorting, duplicates, padding, edges, or envelope) | 33 | intent.rs:278 |
| 6062 | IntentRequired | errors.rs:133 | Keeper flow requires a preauthorized action intent | 17 | venue/keeper_rebalance.rs:129 |
| 6063 | IntentExecutorMismatch | errors.rs:135 | Signer does not match the intent executor | 9 | intent.rs:1525 |
| 6064 | IntentActionMismatch | errors.rs:137 | Accounts or arguments do not match the preauthorized action | 44 | intent.rs:1504 |
| 6065 | IntentExpired | errors.rs:139 | Action intent has expired | 3 | intent.rs:1537 |
| 6066 | IntentNotYetValid | errors.rs:141 | Action intent is not yet valid | 2 | intent.rs:1533 |
| 6067 | IntentLedgerMismatch | errors.rs:143 | Vault set does not match the intent's canonical asset ledger | 35 | intent.rs:973 |
| 6068 | IntentEdgeNotAllowed | errors.rs:145 | Swap leg does not follow a signed intent edge | 2 | intent.rs:1022 |
| 6069 | IntentBudgetExceeded | errors.rs:147 | Leg exceeds the intent's measured credit or signed maximum input | 3 | intent.rs:1038 |
| 6070 | IntentMinOutNotMet | errors.rs:149 | Cumulative swap output is below the signed minimum | 1 | intent.rs:1119 |
| 6071 | VaultBelowBaseline | errors.rs:151 | A vault would end the flow below its pre-flow baseline | 1 | intent.rs:1110 |
| 6072 | IntentDustExceeded | errors.rs:153 | Residual action credit exceeds the signed dust bound | 1 | intent.rs:1114 |
| 6073 | MissingIntentFinalizer | errors.rs:155 | Intent flow requires a later finalize_intent instruction in the same transaction | 1 | intent.rs:1424 |
| 6074 | SwapOutputFloorRequired | errors.rs:157 | A swap output asset must carry a positive min_out floor | 2 | intent.rs:369 |
| 6075 | RewardGroupMissing | errors.rs:159 | Missing reward group accounts for an initialized pool reward slot | 3 | venue/adapters/raydium_flows.rs:880 |
| 6076 | RewardGroupMismatch | errors.rs:161 | Reward group account does not match the pool's reward slot | 14 | venue/adapters/orca.rs:1088 |
| 6077 | RewardRecipientMismatch | errors.rs:163 | Reward recipient is not the Sickle's associated token account | 11 | venue/adapters/orca.rs:288 |
| 6078 | SettingsCandidateRequired | errors.rs:165 | Only the Sickle owner may omit the position-settings candidate | 1 | venue/owner_rebalance.rs:485 |
| 6079 | RebalanceNoOp | errors.rs:167 | Rebalance would reopen the range it is closing, or the one before it | 2 | state.rs:781 |
| 6080 | UnsupportedTransferHookMint | errors.rs:169 | Pool mint carries the Token-2022 transfer-hook extension, which this program cannot service | 2 | fees.rs:422 |
| 6081 | TransferHookAccountsMissing | errors.rs:173 | Transfer-hook accounts for a hooked pool mint are missing or do not match the mint's hook | 14 | venue/adapters/orca.rs:304 |
| 6082 | RewardCollectorMissing | errors.rs:175 | Missing reward fee-collector account for an initialized pool reward slot | 4 | venue/adapters/raydium_flows.rs:930 |
| 6083 | RewardCollectorMismatch | errors.rs:177 | Reward fee collector is not the fee collector's associated token account | 5 | instructions/meteora/claim_reward.rs:188 |
| 6084 | UnsupportedMintExtension | errors.rs:179 | Pool mint carries a Token-2022 extension that is unsafe or unsupported for custody | 2 | fees.rs:390 |
| 6085 | WithdrawalFloorRequired | errors.rs:183 | Keeper rebalance and exit authorizations require a nonzero pool-token withdrawal floor | 2 | intent.rs:420 |
| 6086 | WithdrawalFloorMismatch | errors.rs:185 | Transaction withdrawal minima do not match the signed authorization | 1 | intent.rs:250 |
| 6087 | WithdrawalMinimumNotMet | errors.rs:187 | Measured post-drain pool-token credit is below the signed withdrawal floor | 1 | intent.rs:1095 |
| 6088 | IntentDustPolicyExceeded | errors.rs:189 | Intent dust exceeds the action-specific or owner-configured ceiling | 4 | intent.rs:402 |
| 6089 | InvalidSettingsIdentity | errors.rs:193 | Position settings identity does not match the active sickle, protocol, pool, and position | 6 | state.rs:519 |
| 6090 | InvalidRebalanceDelay | errors.rs:195 | Auto-rebalance requires a nonzero delay | 4 | state.rs:410 |
| 6091 | InvalidDlmmPositionRange | errors.rs:198 | Meteora position range is outside the pair or protocol bounds | 14 | state.rs:184 |
| 6092 | InvalidDlmmResize | errors.rs:200 | Meteora position resize side or length is invalid | 9 | meteora.rs:553 |
| 6093 | UnsupportedDlmmPair | errors.rs:202 | Meteora pair is disabled or is not a liquidity-mining pair | 2 | meteora.rs:340 |
| 6094 | InvalidDlmmBinArrays | errors.rs:204 | Meteora claim requires the exact ordered bin-array account range | 5 | meteora.rs:742 |
| 6095 | InvalidDlmmReward | errors.rs:206 | Meteora reward slot is out of range or is not fully initialized | 5 | meteora.rs:254 |
| 6096 | InvalidDlmmBitmapExtension | errors.rs:208 | Meteora bitmap-extension account does not match the requested range and pair | 5 | meteora.rs:676 |
| 6097 | InvalidDlmmRemovalBps | errors.rs:210 | Meteora removal BPS are zero or exceed 10000 | 1 | meteora.rs:714 |
| 6098 | InvalidDlmmLiquidityParameters | errors.rs:212 | Meteora liquidity parameters are invalid | 3 | meteora.rs:622 |
| 6099 | IntentAuthorizationStillLive | errors.rs:215 | Intent authorization has not expired | 4 | instructions/intent/expire_dlmm_intent_authorization.rs:59 |
How a call flows through it
This follows the Swept event of sweep_token, then the emitter path of raydium_rebalance_for, then one error.
Macro path.
- The handler builds
Sweptand callsemit_cpi!(event.clone()).instructions/sickle/sweep_token.rs:76-82. - The macro reads
ctx.accounts.event_authority, prepends the event tag toEvent::data, and callsinvoke_signedwith seeds[b"__event_authority", bump]. Source:anchor-attribute-event-1.1.2/src/lib.rs:170-198. - The self-CPI re-enters the program. The dispatcher sees the event tag and returns. Source:
anchor-syn-1.1.2/src/codegen/program/dispatch.rs:90-93. - The handler calls
emit!(event). The macro logs the same bytes without the tag as aProgram data:line.instructions/sickle/sweep_token.rs:83.
Emitter path.
- The handler creates one
ReusableEventEmitterfromctx.accounts.event_authority.instructions/raydium/rebalance_for.rs:244-245. emit_user(&run.fees_collected)serializes into the reused buffer, runs the self-CPI, restores the buffer, and logs the bytes after the tag.instructions/raydium/rebalance_for.rs:246,events.rs:99-105.- Each
FeeChargedgoes throughemit_cpionly.instructions/raydium/rebalance_for.rs:248,instructions/raydium/rebalance_for.rs:253. Rebalanced,PositionOpened, and the optionalSettingsUpdatedgo throughemit_user.instructions/raydium/rebalance_for.rs:256-259.
Error path.
- A handler or runner writes
require!(cond, SickleError::FeeTooHigh).instructions/admin/set_fee.rs:21. - Anchor builds an
AnchorErrorwitherror_code_number6003 and the message fromerrors.rs:11. - The runtime returns the custom error to the caller. The TypeScript tests read
err.error.errorCode.codeand compare the variant name.tests/sickle/core.ts:1004.
Invariants the layer owns
- Every event discriminator equals
sha256("event:<Name>")[..8]. The test atevents.rs:492-540recomputes it for all 26 events. - The event data layout is the 8-byte discriminator followed by the borsh fields. The test at
events.rs:547-586checks aProtocolenum byte and anOption<FeeAction>tail. - The emitter produces the same bytes as Anchor's macros and never reallocates its buffer across 7 event kinds. The test at
events.rs:589-695compares the buffer withEVENT_IX_TAG_LE + Event::dataand checks the pointer and capacity after each event. - A user-facing event reaches both the inner-instruction record and the log. The handlers write
emit_cpi!thenemit!, or callemit_user. No check enforces the pair. See L06-N006. FeeChargedreaches only the inner-instruction record, and only for a nonzero amount. Every site sits under anamount > 0orfee > 0test. Examples:instructions/raydium/harvest_for.rs:140-148,venue/owner_rebalance.rs:360. For the six reward sites the test now lives once inreward_event_pair(venue/claim.rs:126), which returnsNonefor a zero fee, and each caller emits only what it is handed.- The error code of a variant never changes. The comments at
errors.rs:103,errors.rs:196, anderrors.rs:213require appends only. The test atvenue/adapters/raydium_flows.rs:1261depends on it.
Tests
| Test location | Covers |
|---|---|
events.rs:492-540 | All 26 event discriminators. |
events.rs:547-586 | The byte layout of PositionOpened and of Swapped with Some and None. |
events.rs:589-695 | The reusable buffer matches Anchor bytes and keeps its pointer and capacity across 7 events. |
venue/adapters/raydium_flows.rs:1261-1285 | Two error codes by number, RewardCollectorMismatch and RewardCollectorMissing. |
tests/support/sickle-context.ts:117-156 | fetchEvents decodes both the inner-instruction copy and the log copy of every event in a transaction. |
tests/sickle/*.ts, tests/surfpool-*.ts | 107 assertions on errorCode?.code over 14 distinct variant names. 8 test files call fetchEvents. |
Gaps. No test asserts that each user-facing event appears once in the inner instructions and once in the logs for every emitting instruction. No test asserts that FeeCharged never appears in the logs. The reallocation test covers 7 event kinds, not the DlmmLiquidityRemoved and DlmmRebalanced kinds that meteora_exit_for and meteora_rebalance_for send through the emitter. The list at events.rs:493-532 is hand-written. No test checks that every #[event] struct is in it.
The variant-name grep over tests/**/*.ts and over the Rust #[cfg(test)] modules finds no test for 29 variants. They are: InvalidBufferTicks, InvalidSlippageBps, InvalidPriceImpactBps, InvalidHarvestTokenOut, InvalidExitTriggers, InvalidMetadataUpdateAuth, SwapConsentRequired, InvalidInstructionsSysvar, InvalidIntentDomainVersion, InvalidEd25519Instruction, IntentEdgeNotAllowed, IntentBudgetExceeded, IntentMinOutNotMet, VaultBelowBaseline, RewardGroupMissing, RewardGroupMismatch, SettingsCandidateRequired, RebalanceNoOp, WithdrawalFloorMismatch, WithdrawalMinimumNotMet, IntentDustPolicyExceeded, InvalidDlmmPositionRange, InvalidDlmmResize, UnsupportedDlmmPair, InvalidDlmmBinArrays, InvalidDlmmReward, InvalidDlmmBitmapExtension, InvalidDlmmRemovalBps, InvalidDlmmLiquidityParameters. Some Rust tests may reach these variants through is_err() without naming them.
Simplification and audit notes
- L06-N001 —
errors.rs:92SwapConsentRequiredanderrors.rs:96MissingZapInLeghave 0 sites inprograms/sickle/src. The IDL copy atmanagement-ui/src/lib/idl/sickle.json:8665and:8675still lists them. Their codes are 6043 and 6045. Removing them renumbers the 54 variants afterMissingZapInLeg, so every stored code above 6045 changes. - L06-N002 —
errors.rs:91givesSwapConsentRequiredthe message "jupiter_swap outside an intent flow requires the sickle owner". The live check for that condition atinstructions/jupiter/swap.rs:72-76returnsUnauthorized. The message describes a check that returns a different variant. - L06-N003 —
errors.rs:103opens a comment block named "Phase-2 ActionIntent". The block runs toerrors.rs:195and contains variants unrelated to authorizations:RewardGroupMissingat:159,UnsupportedTransferHookMintat:169,UnsupportedMintExtensionat:179, andInvalidRebalanceDelayat:195. The section header no longer matches its contents. - L06-N004 — Three variants account for 312 of the 905 sites:
InvalidAccountOwnerwith 108,InvalidProtocolAccountDatawith 106, andInvalidPositionwith 98. A failed transaction with one of these codes points to no specific check. - L06-N005 —
errors.rs:6Unauthorizedhas 49 sites anderrors.rs:8NotGuardianOrAdminhas 2 sites, includinginstructions/admin/pause.rs:26. Two variants cover the same category of failure. - L06-N006 — Every macro-path handler writes the pair
emit_cpi!(event.clone()); emit!(event);by hand. The pair appears in 45 files. EveryFeeChargedsite skipsemit!by hand at 32 sites. No helper or test enforces either convention on the macro path. The emitter hasemit_userandemit_cpifor the same two cases. - L06-N007 —
events.rs:8-9says the funded Raydium keeper handlers use the emitter.instructions/meteora/rebalance_for.rs:407andinstructions/meteora/exit_for.rs:327also use it. The comment names 2 of the 4 users. - L06-N008 —
events.rs:325-326saysExitedcomes from the keeperexit_forflow.venue/keeper_exit.rs:216builds it forexit_forandraydium_exit_for, andinstructions/meteora/exit_for.rs:397builds it too. The comment names 1 of the 3 emitting instructions. - L06-N009 —
events.rs:26-30says admin and config instructions emit nothing.create_sickle_ataandinitialize_intent_domainalso emit nothing and have no#[event_cpi]. The comment does not name them. - L06-N010 —
events.rs:89readscrate::EVENT_AUTHORITY_AND_BUMP.declare_id!generates that constant only under Anchor'sevent-cpifeature. Source:anchor-attribute-account-1.1.2/src/id.rs:44-49. The emitter atevents.rs:81-96copies the body of Anchor'semit_cpi!expansion atanchor-attribute-event-1.1.2/src/lib.rs:170-198. An Anchor upgrade that changes the expansion does not change the emitter. - L06-N011 —
events.rs:99-105emit_userlogsself.instruction_data[8..]afteremit_cpireturns. The bytes are valid only becauseemit_cpimoves the buffer back atevents.rs:94. The two methods share state through the buffer instead of through a return value. - L06-N012 —
events.rs:43sets the buffer capacity to 264 bytes. No compile-time or unit check proves every event kind fits. The reallocation test atevents.rs:589-695proves it for 7 kinds. - L06-N013 —
events.rs:80andevents.rs:98mark both emitter methods#[inline(never)]. The attribute has no comment inevents.rs. The reason is the 4 KiB SBF stack frame, and only the CI grep at.github/workflows/ci.yml:106-108catches an overflow. - L06-N014 —
events.rs:50calls.unwrap()onevent.serialize(buffer). The comment onevents.rs:49says the writer is infallible. A panic here aborts the instruction without an Anchor error code. - L06-N015 —
instructions/intent/finalize_dlmm_intent.rs:100emitsIntentFinalized. The DLMM authorization side has its ownDlmmIntentAuthorized,DlmmIntentAuthorizationCancelled, andDlmmIntentAuthorizationExpiredatevents.rs:402-450. The DLMM finalization side reuses the CLMM struct. An indexer cannot tell the family from the finalization event alone. - L06-N016 —
events.rs:493-532lists the 26 event names as string literals beside the struct names. A new#[event]struct compiles and emits without joining the list. - L06-N017 —
venue/owner_rebalance.rs:378map_eventsbuildsRebalancedandPositionOpenedfor both the owner rebalance and the keeper rebalance.venue/keeper_rebalance.rs:366calls it. The function lives in the owner runner but serves both runners.
Resolved
- L06-R001 —
instructions/raydium/harvest_for.rs:271-280,instructions/raydium/compound_for.rs:310-318, andinstructions/raydium/collect_fees.rs:190-198emittedFeeChargedper reward but noRewardsCollected.instructions/orca/harvest_for.rs:274-284,instructions/orca/compound_for.rs:319, andinstructions/orca/collect_rewards.rs:79emitRewardsCollectedper reward. An indexer saw the Raydium reward fee but not the Raydium reward amount for these 3 instructions, and saw nothing at all when the fee floored to zero, becauseFeeChargedis emitted only when it is non-zero. All six sites now derive both events fromreward_event_pairinprograms/sickle/src/venue/claim.rs, which withholds the pair for a Raydium reward whose payout is folded into a pool-token delta and therefore has no exact amount of its own. Named by symbol rather than by line: the checker skips resolved notes, so a line here would rot unchecked. Resolved in #184.