Sickle Sol

Events and errors

This layer defines the 26 event structs the program emits and the 100 error variants the program returns.

Files

PathLinesPurpose
programs/sickle/src/events.rs696The #[event] structs, the allocation-reusing emitter, and 3 layout tests.
programs/sickle/src/errors.rs216The SickleError enum with 100 #[msg] variants.

Responsibilities

  • Define one #[event] struct per observable effect with #[derive(Clone)] at events.rs:108-481.
  • Provide ReusableEventEmitter, an emitter that reuses one buffer across a batch of self-CPIs at events.rs:64-106.
  • Check every event discriminator against sha256("event:<Name>")[..8] in a unit test at events.rs:492-540.
  • Define the SickleError enum. Anchor assigns code 6000 plus the variant index at errors.rs:3-4.
  • Keep the variant order append-only, as the comments at errors.rs:103, errors.rs:196, and errors.rs:213 require.

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.

EventDeclaredFieldsBuilt at
SickleCreated111sickle, owner, manager, referral_code: [u8; 32]instructions/sickle/initialize_sickle.rs:39
PositionOpened122sickle, protocol, pool, position, tick_lower_index, tick_upper_indexvenue/open_increase.rs:177 for the 2 CLMM open instructions. venue/owner_rebalance.rs:409 for the 4 CLMM rebalance instructions through map_events.
DlmmPositionOpened135sickle, protocol, pair, position, lower_bin_id, upper_bin_idinstructions/meteora/open_position.rs:136, instructions/meteora/intent_open_position.rs:224
DlmmPositionResized149sickle, protocol, pair, position, old_lower_bin_id, old_upper_bin_id, new_lower_bin_id, new_upper_bin_idinstructions/meteora/increase_position_length.rs:136, instructions/meteora/intent_increase_position_length.rs:183
DlmmLiquidityAdded165sickle, protocol, pair, position, min_bin_id, max_bin_id, strategy_type: u8, amount_x, amount_yinstructions/meteora/add_liquidity.rs:253, instructions/meteora/intent_add_liquidity.rs:211
DlmmLiquidityRemoved183sickle, protocol, pair, position, from_bin_id, to_bin_id, bps_to_remove: u16, amount_x, amount_yinstructions/meteora/remove_liquidity.rs:260, instructions/meteora/exit_for.rs:328, instructions/meteora/rebalance_for.rs:408
DlmmRebalanced201sickle, pair, old_position, new_position, old_min_bin_id, old_max_bin_id, new_min_bin_id, new_max_bin_id, drained_x, drained_yinstructions/meteora/rebalance_for.rs:477
PositionClosed220sickle, protocol, positionvenue/decrease_close.rs:362 for the 2 CLMM close instructions. instructions/meteora/close_position.rs:137.
LiquidityIncreased230sickle, protocol, pool, position, liquidity: u128, amount_a, amount_bvenue/open_increase.rs:361 for the 2 CLMM increase instructions.
LiquidityDecreased245sickle, protocol, pool, position, liquidity: u128, amount_a, amount_bvenue/decrease_close.rs:178 for the 2 CLMM decrease instructions.
FeesCollected260sickle, position, amount_a, amount_b, authority, action: FeeActioninstructions/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
RewardsCollected273sickle, position, mint, amount, authority, action: FeeActioninstructions/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
FeeCharged287sickle, action: FeeAction, mint, amount, collector32 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.
Rebalanced302sickle, pool, old_position, new_position, old_tick_lower_index, old_tick_upper_index, new_tick_lower_index, new_tick_upper_index, drained_a, drained_bvenue/owner_rebalance.rs:397 for the 4 CLMM rebalance instructions through map_events.
Exited320sickle, pool, position, amount_a, amount_bvenue/keeper_exit.rs:216 for exit_for and raydium_exit_for. instructions/meteora/exit_for.rs:397.
Swept332sickle, mint, amount, destinationinstructions/sickle/sweep_token.rs:76
Swapped345sickle, input_mint, output_mint, amount_in, amount_out, fee_action: Option<FeeAction>instructions/jupiter/swap.rs:514
SettingsUpdated360sickle, position, auto_rebalance, auto_exit, automate_rewards, removedinstructions/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
IntentAuthorized374sickle, nonce, executor, action: IntentAction, digest: [u8; 32], expiry_slot, signer_kind: u8instructions/intent/preauthorize_intent.rs:136
DlmmIntentAuthorized392the 7 IntentAuthorized fields plus min_bin_id, max_bin_id, strategy_type: u8instructions/intent/preauthorize_dlmm_intent.rs:119
IntentAuthorizationCancelled409sickle, nonceinstructions/intent/cancel_intent_authorization.rs:52
DlmmIntentAuthorizationCancelled417sickle, nonceinstructions/intent/cancel_dlmm_intent_authorization.rs:49
IntentAuthorizationExpired426sickle, nonce, expiry_slotinstructions/intent/expire_intent_authorization.rs:64
DlmmIntentAuthorizationExpired436sickle, nonce, expiry_slotinstructions/intent/expire_dlmm_intent_authorization.rs:61
IntentSessionUpdated447sickle, signer, active, permissions: u8, pool, expiry_slot, generationinstructions/intent/set_intent_session.rs:110
IntentFinalized463sickle, 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.

ItemKindMeaning
EVENT_BUFFER_CAPACITYconst8 bytes of tag plus 256 bytes. The initial capacity of the reusable buffer. events.rs:43.
serialize_event_cpi_datafnClears the buffer, then writes the event tag, the discriminator, and the borsh fields. events.rs:45-51.
ReusableEventEmitterstructpub(crate). Holds the event authority, one data buffer, and one account-meta vector. events.rs:64-68.
ReusableEventEmitter::newfnAllocates the buffer and the single-entry meta vector once. events.rs:71-78.
ReusableEventEmitter::emit_cpifnSerializes, 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_userfnCalls 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.

CodeVariantDeclaredMessageSitesRepresentative site
6000Unauthorizederrors.rs:6Operation not allowed for caller49venue/keeper_exit.rs:271
6001NotGuardianOrAdminerrors.rs:8Caller is neither guardian nor admin2instructions/admin/pause.rs:26
6002Pausederrors.rs:10Program is paused5state.rs:881
6003FeeTooHigherrors.rs:12Fee exceeds configured maximum2instructions/admin/set_fee.rs:21
6004SlippageExceedederrors.rs:14Slippage tolerance exceeded3venue/adapters/raydium_flows.rs:576
6005InvalidAccountOwnererrors.rs:16Unexpected account owner108intent.rs:1454
6006ProtocolNotAllowederrors.rs:18Protocol program is not allowlisted2state.rs:963
6007InvalidPositionSettingserrors.rs:20Invalid position settings24state.rs:388
6008InvalidTickRangeerrors.rs:22Tick lower index must be below tick upper index5state.rs:130
6009InvalidCutoffTickserrors.rs:24Cutoff ticks are inverted or outside the protocol tick bounds1state.rs:415
6010InvalidBufferTickserrors.rs:26Buffer ticks are outside the allowed band3state.rs:406
6011InvalidSlippageBpserrors.rs:28Slippage bps exceed the maximum2state.rs:425
6012InvalidPriceImpactBpserrors.rs:30Price impact bps are zero or exceed the maximum2state.rs:429
6013InvalidDustBpserrors.rs:32Dust bps are zero or exceed the maximum5intent.rs:393
6014InvalidHarvestTokenOuterrors.rs:34Harvest token out requires harvest reward behavior1state.rs:78
6015ExitTriggersNotSeterrors.rs:36Auto-exit requires at least one trigger tick2state.rs:478
6016InvalidExitTriggerserrors.rs:38Exit trigger ticks are inverted or outside the protocol tick bounds1state.rs:484
6017AutoRebalanceNotSeterrors.rs:40Rebalance cutoffs set while auto-rebalance is disabled5state.rs:464
6018AutoExitNotSeterrors.rs:42Exit config set while auto-exit is disabled3state.rs:497
6019InvalidPositionerrors.rs:44Position account does not match the expected protocol-derived address98meteora.rs:357
6020InvalidMetadataUpdateAutherrors.rs:46Metadata update authority does not match the protocol's expected key3venue/adapters/orca.rs:648
6021InvalidRentReceivererrors.rs:48Rent receiver must be the sickle owner28venue/decrease_close.rs:278
6022InvalidPermissionserrors.rs:50Unknown automation permission bits10instructions/admin/set_global_automator.rs:45
6023MathOverflowerrors.rs:52Arithmetic overflow60intent.rs:555
6024InvalidProtocolAccountDataerrors.rs:54Protocol account data is malformed106state.rs:127
6025InvalidTickWidtherrors.rs:56Position width does not match the configured tick spaces5state.rs:135
6026PositionSettingsMismatcherrors.rs:58Old and new position settings must be supplied together4venue/owner_rebalance.rs:492
6027AutoHarvestNotSeterrors.rs:60Auto-harvest is not enabled in the position settings2state.rs:531
6028AutoCompoundNotSeterrors.rs:62Auto-compound is not enabled in the position settings2state.rs:542
6029RebalanceConfigNotSeterrors.rs:64Auto-rebalance rebalance config is not set3state.rs:649
6030TickWithinRangeerrors.rs:66Pool tick is still within the position's rebalance range7state.rs:676
6031TickOutsideStopLossRangeerrors.rs:68Pool tick is outside the configured stop-loss range2state.rs:682
6032RebalanceTooSoonerrors.rs:70Rebalance is still within the configured debounce delay2state.rs:660
6033SwapAmountZeroerrors.rs:72Swap input amount must be nonzero1instructions/jupiter/swap.rs:102
6034SwapMinOutZeroerrors.rs:74Swap minimum output must be nonzero1instructions/jupiter/swap.rs:103
6035InvalidSwapMintserrors.rs:76Swap input and output mints must differ3instructions/jupiter/swap.rs:104-120
6036SwapInputExceedederrors.rs:78Swap spent more input than allowed1instructions/jupiter/swap.rs:285-288
6037SwapVaultNotAllowederrors.rs:80Sickle-owned token account not allowed in the swap route1instructions/jupiter/swap.rs:43
6038InvalidSwapFeeActionerrors.rs:82Only Deposit and Withdraw fees may be charged on a swap leg2instructions/jupiter/swap.rs:88,143
6039MissingFeeCollectorAccounterrors.rs:84Fee collector account required for the swap fee1instructions/jupiter/swap.rs:128-131
6040InvalidFeeCollectorMinterrors.rs:86Fee collector account mint does not match the charged side7fees.rs:86
6041InvalidProgramDataerrors.rs:88Program data account does not match the program2instructions/intent/initialize_intent_domain.rs:30
6042NotUpgradeAuthorityerrors.rs:90Initializer must be the program upgrade authority2instructions/intent/initialize_intent_domain.rs:38
6043SwapConsentRequirederrors.rs:92jupiter_swap outside an intent flow requires the sickle owner0none
6044IncreaseConsentRequirederrors.rs:94Automator increase requires position settings consenting to a matching automated flow2venue/open_increase.rs:268
6045MissingZapInLegerrors.rs:96Automated flow must re-add liquidity to the position later in the same transaction0none
6046InvalidRangePlacementerrors.rs:98Reopened range must match the configured tick spaces around the current pool tick3venue/keeper_rebalance.rs:309
6047SwapVaultTamperederrors.rs:100Swap route altered a vault's owner, mint, delegate, or close authority1instructions/jupiter/swap.rs:262-265
6048InvalidInstructionsSysvarerrors.rs:102Instructions sysvar account does not match the sysvar address11venue/keeper_exit.rs:260
6049InvalidIntentDomainVersionerrors.rs:105Intent domain is missing or has an unsupported version1intent.rs:709
6050IntentNonceMismatcherrors.rs:107Authorization nonce does not match the sickle's next intent nonce2intent.rs:745
6051MissingIntentSignatureerrors.rs:109Preauthorization requires an immediately preceding ed25519 verify instruction2intent.rs:1335
6052InvalidEd25519Instructionerrors.rs:111The ed25519 instruction is malformed or its offsets escape the instruction11intent.rs:1245
6053IntentDigestMismatcherrors.rs:113The signed message does not match the authorization digest1intent.rs:1320
6054InvalidIntentSignererrors.rs:115The digest signer is neither the sickle owner nor the named session signer1instructions/intent/preauthorize.rs:61
6055IntentSessionRequirederrors.rs:117Session-signed authorization requires the intent session account2intent.rs:1552
6056InvalidIntentSessionerrors.rs:119Intent session signer is invalid3intent.rs:791
6057SessionInactiveerrors.rs:121Intent session is not active1intent.rs:793
6058SessionExpirederrors.rs:123Intent session or authorization expiry exceeds the session expiry5intent.rs:794
6059SessionGenerationMismatcherrors.rs:125Authorization was signed against a stale session generation1intent.rs:801
6060SessionScopeExceedederrors.rs:127Authorization exceeds the session's action or pool scope2intent.rs:806
6061NoncanonicalIntenterrors.rs:131Authorization bounds are noncanonical (sorting, duplicates, padding, edges, or envelope)33intent.rs:278
6062IntentRequirederrors.rs:133Keeper flow requires a preauthorized action intent17venue/keeper_rebalance.rs:129
6063IntentExecutorMismatcherrors.rs:135Signer does not match the intent executor9intent.rs:1525
6064IntentActionMismatcherrors.rs:137Accounts or arguments do not match the preauthorized action44intent.rs:1504
6065IntentExpirederrors.rs:139Action intent has expired3intent.rs:1537
6066IntentNotYetValiderrors.rs:141Action intent is not yet valid2intent.rs:1533
6067IntentLedgerMismatcherrors.rs:143Vault set does not match the intent's canonical asset ledger35intent.rs:973
6068IntentEdgeNotAllowederrors.rs:145Swap leg does not follow a signed intent edge2intent.rs:1022
6069IntentBudgetExceedederrors.rs:147Leg exceeds the intent's measured credit or signed maximum input3intent.rs:1038
6070IntentMinOutNotMeterrors.rs:149Cumulative swap output is below the signed minimum1intent.rs:1119
6071VaultBelowBaselineerrors.rs:151A vault would end the flow below its pre-flow baseline1intent.rs:1110
6072IntentDustExceedederrors.rs:153Residual action credit exceeds the signed dust bound1intent.rs:1114
6073MissingIntentFinalizererrors.rs:155Intent flow requires a later finalize_intent instruction in the same transaction1intent.rs:1424
6074SwapOutputFloorRequirederrors.rs:157A swap output asset must carry a positive min_out floor2intent.rs:369
6075RewardGroupMissingerrors.rs:159Missing reward group accounts for an initialized pool reward slot3venue/adapters/raydium_flows.rs:880
6076RewardGroupMismatcherrors.rs:161Reward group account does not match the pool's reward slot14venue/adapters/orca.rs:1088
6077RewardRecipientMismatcherrors.rs:163Reward recipient is not the Sickle's associated token account11venue/adapters/orca.rs:288
6078SettingsCandidateRequirederrors.rs:165Only the Sickle owner may omit the position-settings candidate1venue/owner_rebalance.rs:485
6079RebalanceNoOperrors.rs:167Rebalance would reopen the range it is closing, or the one before it2state.rs:781
6080UnsupportedTransferHookMinterrors.rs:169Pool mint carries the Token-2022 transfer-hook extension, which this program cannot service2fees.rs:422
6081TransferHookAccountsMissingerrors.rs:173Transfer-hook accounts for a hooked pool mint are missing or do not match the mint's hook14venue/adapters/orca.rs:304
6082RewardCollectorMissingerrors.rs:175Missing reward fee-collector account for an initialized pool reward slot4venue/adapters/raydium_flows.rs:930
6083RewardCollectorMismatcherrors.rs:177Reward fee collector is not the fee collector's associated token account5instructions/meteora/claim_reward.rs:188
6084UnsupportedMintExtensionerrors.rs:179Pool mint carries a Token-2022 extension that is unsafe or unsupported for custody2fees.rs:390
6085WithdrawalFloorRequirederrors.rs:183Keeper rebalance and exit authorizations require a nonzero pool-token withdrawal floor2intent.rs:420
6086WithdrawalFloorMismatcherrors.rs:185Transaction withdrawal minima do not match the signed authorization1intent.rs:250
6087WithdrawalMinimumNotMeterrors.rs:187Measured post-drain pool-token credit is below the signed withdrawal floor1intent.rs:1095
6088IntentDustPolicyExceedederrors.rs:189Intent dust exceeds the action-specific or owner-configured ceiling4intent.rs:402
6089InvalidSettingsIdentityerrors.rs:193Position settings identity does not match the active sickle, protocol, pool, and position6state.rs:519
6090InvalidRebalanceDelayerrors.rs:195Auto-rebalance requires a nonzero delay4state.rs:410
6091InvalidDlmmPositionRangeerrors.rs:198Meteora position range is outside the pair or protocol bounds14state.rs:184
6092InvalidDlmmResizeerrors.rs:200Meteora position resize side or length is invalid9meteora.rs:553
6093UnsupportedDlmmPairerrors.rs:202Meteora pair is disabled or is not a liquidity-mining pair2meteora.rs:340
6094InvalidDlmmBinArrayserrors.rs:204Meteora claim requires the exact ordered bin-array account range5meteora.rs:742
6095InvalidDlmmRewarderrors.rs:206Meteora reward slot is out of range or is not fully initialized5meteora.rs:254
6096InvalidDlmmBitmapExtensionerrors.rs:208Meteora bitmap-extension account does not match the requested range and pair5meteora.rs:676
6097InvalidDlmmRemovalBpserrors.rs:210Meteora removal BPS are zero or exceed 100001meteora.rs:714
6098InvalidDlmmLiquidityParameterserrors.rs:212Meteora liquidity parameters are invalid3meteora.rs:622
6099IntentAuthorizationStillLiveerrors.rs:215Intent authorization has not expired4instructions/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.

  1. The handler builds Swept and calls emit_cpi!(event.clone()). instructions/sickle/sweep_token.rs:76-82.
  2. The macro reads ctx.accounts.event_authority, prepends the event tag to Event::data, and calls invoke_signed with seeds [b"__event_authority", bump]. Source: anchor-attribute-event-1.1.2/src/lib.rs:170-198.
  3. 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.
  4. The handler calls emit!(event). The macro logs the same bytes without the tag as a Program data: line. instructions/sickle/sweep_token.rs:83.

Emitter path.

  1. The handler creates one ReusableEventEmitter from ctx.accounts.event_authority. instructions/raydium/rebalance_for.rs:244-245.
  2. 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.
  3. Each FeeCharged goes through emit_cpi only. instructions/raydium/rebalance_for.rs:248, instructions/raydium/rebalance_for.rs:253.
  4. Rebalanced, PositionOpened, and the optional SettingsUpdated go through emit_user. instructions/raydium/rebalance_for.rs:256-259.

Error path.

  1. A handler or runner writes require!(cond, SickleError::FeeTooHigh). instructions/admin/set_fee.rs:21.
  2. Anchor builds an AnchorError with error_code_number 6003 and the message from errors.rs:11.
  3. The runtime returns the custom error to the caller. The TypeScript tests read err.error.errorCode.code and compare the variant name. tests/sickle/core.ts:1004.

Invariants the layer owns

  1. Every event discriminator equals sha256("event:<Name>")[..8]. The test at events.rs:492-540 recomputes it for all 26 events.
  2. The event data layout is the 8-byte discriminator followed by the borsh fields. The test at events.rs:547-586 checks a Protocol enum byte and an Option<FeeAction> tail.
  3. 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-695 compares the buffer with EVENT_IX_TAG_LE + Event::data and checks the pointer and capacity after each event.
  4. A user-facing event reaches both the inner-instruction record and the log. The handlers write emit_cpi! then emit!, or call emit_user. No check enforces the pair. See L06-N006.
  5. FeeCharged reaches only the inner-instruction record, and only for a nonzero amount. Every site sits under an amount > 0 or fee > 0 test. Examples: instructions/raydium/harvest_for.rs:140-148, venue/owner_rebalance.rs:360. For the six reward sites the test now lives once in reward_event_pair (venue/claim.rs:126), which returns None for a zero fee, and each caller emits only what it is handed.
  6. The error code of a variant never changes. The comments at errors.rs:103, errors.rs:196, and errors.rs:213 require appends only. The test at venue/adapters/raydium_flows.rs:1261 depends on it.

Tests

Test locationCovers
events.rs:492-540All 26 event discriminators.
events.rs:547-586The byte layout of PositionOpened and of Swapped with Some and None.
events.rs:589-695The reusable buffer matches Anchor bytes and keeps its pointer and capacity across 7 events.
venue/adapters/raydium_flows.rs:1261-1285Two error codes by number, RewardCollectorMismatch and RewardCollectorMissing.
tests/support/sickle-context.ts:117-156fetchEvents decodes both the inner-instruction copy and the log copy of every event in a transaction.
tests/sickle/*.ts, tests/surfpool-*.ts107 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

  1. L06-N001 — errors.rs:92 SwapConsentRequired and errors.rs:96 MissingZapInLeg have 0 sites in programs/sickle/src. The IDL copy at management-ui/src/lib/idl/sickle.json:8665 and :8675 still lists them. Their codes are 6043 and 6045. Removing them renumbers the 54 variants after MissingZapInLeg, so every stored code above 6045 changes.
  2. L06-N002 — errors.rs:91 gives SwapConsentRequired the message "jupiter_swap outside an intent flow requires the sickle owner". The live check for that condition at instructions/jupiter/swap.rs:72-76 returns Unauthorized. The message describes a check that returns a different variant.
  3. L06-N003 — errors.rs:103 opens a comment block named "Phase-2 ActionIntent". The block runs to errors.rs:195 and contains variants unrelated to authorizations: RewardGroupMissing at :159, UnsupportedTransferHookMint at :169, UnsupportedMintExtension at :179, and InvalidRebalanceDelay at :195. The section header no longer matches its contents.
  4. L06-N004 — Three variants account for 312 of the 905 sites: InvalidAccountOwner with 108, InvalidProtocolAccountData with 106, and InvalidPosition with 98. A failed transaction with one of these codes points to no specific check.
  5. L06-N005 — errors.rs:6 Unauthorized has 49 sites and errors.rs:8 NotGuardianOrAdmin has 2 sites, including instructions/admin/pause.rs:26. Two variants cover the same category of failure.
  6. L06-N006 — Every macro-path handler writes the pair emit_cpi!(event.clone()); emit!(event); by hand. The pair appears in 45 files. Every FeeCharged site skips emit! by hand at 32 sites. No helper or test enforces either convention on the macro path. The emitter has emit_user and emit_cpi for the same two cases.
  7. L06-N007 — events.rs:8-9 says the funded Raydium keeper handlers use the emitter. instructions/meteora/rebalance_for.rs:407 and instructions/meteora/exit_for.rs:327 also use it. The comment names 2 of the 4 users.
  8. L06-N008 — events.rs:325-326 says Exited comes from the keeper exit_for flow. venue/keeper_exit.rs:216 builds it for exit_for and raydium_exit_for, and instructions/meteora/exit_for.rs:397 builds it too. The comment names 1 of the 3 emitting instructions.
  9. L06-N009 — events.rs:26-30 says admin and config instructions emit nothing. create_sickle_ata and initialize_intent_domain also emit nothing and have no #[event_cpi]. The comment does not name them.
  10. L06-N010 — events.rs:89 reads crate::EVENT_AUTHORITY_AND_BUMP. declare_id! generates that constant only under Anchor's event-cpi feature. Source: anchor-attribute-account-1.1.2/src/id.rs:44-49. The emitter at events.rs:81-96 copies the body of Anchor's emit_cpi! expansion at anchor-attribute-event-1.1.2/src/lib.rs:170-198. An Anchor upgrade that changes the expansion does not change the emitter.
  11. L06-N011 — events.rs:99-105 emit_user logs self.instruction_data[8..] after emit_cpi returns. The bytes are valid only because emit_cpi moves the buffer back at events.rs:94. The two methods share state through the buffer instead of through a return value.
  12. L06-N012 — events.rs:43 sets the buffer capacity to 264 bytes. No compile-time or unit check proves every event kind fits. The reallocation test at events.rs:589-695 proves it for 7 kinds.
  13. L06-N013 — events.rs:80 and events.rs:98 mark both emitter methods #[inline(never)]. The attribute has no comment in events.rs. The reason is the 4 KiB SBF stack frame, and only the CI grep at .github/workflows/ci.yml:106-108 catches an overflow.
  14. L06-N014 — events.rs:50 calls .unwrap() on event.serialize(buffer). The comment on events.rs:49 says the writer is infallible. A panic here aborts the instruction without an Anchor error code.
  15. L06-N015 — instructions/intent/finalize_dlmm_intent.rs:100 emits IntentFinalized. The DLMM authorization side has its own DlmmIntentAuthorized, DlmmIntentAuthorizationCancelled, and DlmmIntentAuthorizationExpired at events.rs:402-450. The DLMM finalization side reuses the CLMM struct. An indexer cannot tell the family from the finalization event alone.
  16. L06-N016 — events.rs:493-532 lists the 26 event names as string literals beside the struct names. A new #[event] struct compiles and emits without joining the list.
  17. L06-N017 — venue/owner_rebalance.rs:378 map_events builds Rebalanced and PositionOpened for both the owner rebalance and the keeper rebalance. venue/keeper_rebalance.rs:366 calls it. The function lives in the owner runner but serves both runners.

Resolved

  1. L06-R001 — instructions/raydium/harvest_for.rs:271-280, instructions/raydium/compound_for.rs:310-318, and instructions/raydium/collect_fees.rs:190-198 emitted FeeCharged per reward but no RewardsCollected. instructions/orca/harvest_for.rs:274-284, instructions/orca/compound_for.rs:319, and instructions/orca/collect_rewards.rs:79 emit RewardsCollected per 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, because FeeCharged is emitted only when it is non-zero. All six sites now derive both events from reward_event_pair in programs/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.

Source: docs/dev/layers/06-events-and-errors.md in vfat-io/sickle-programs, vendored here by its sync workflow. Edit the markdown there; this page is rebuilt from it on deploy.