Skip to content

Commit 261a6ff

Browse files
starknetdevclaude
andcommitted
refactor(metagame): drop redundant extension config storage
The extension contract is the sole source of truth for its own config — duplicating it on the host-side component was convenience state that also cost variable-length Vec<felt252> storage proportional to config size (substantial for prizes with many positions). The component only needs the extension address to dispatch claim/pay calls, which doubles as the "is this an extension?" flag. Removed (now Span<felt252> config is forward-only, no on-chain copy): - EntryFee_extension_config / Prize_extension_config storage maps - get/set/push extension_config Store trait methods - read_extension_config / write_extension_config helpers in both *_store.cairo bridge layers and the component public surface - _set_extension's write_extension_config call What stays: - *_extension_address storage (required for dispatch + "is registered?") - get_extension_address public accessor - IEntryFeeExtension.set_entry_fee_config / IPrizeExtension.add_prize dispatch — config is forwarded to the extension, which stores what it cares about Indexer impact: hosts emitting their original create/add events with the full ExtensionConfig payload (e.g. budokan's TournamentCreated carries Option<EntryFeeKind> which includes the config) are unaffected — indexers should source config from the host's event stream rather than from on-chain component state. For prize extensions with no analogous host event, indexers should call the extension contract directly. Test impact: removed config-storage-specific tests; address-storage and dispatch tests stay. 430 metagame tests pass (was 440, -10 dropped storage tests). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 51e555e commit 261a6ff

10 files changed

Lines changed: 36 additions & 351 deletions

File tree

packages/metagame/src/entry_fee/entry_fee_component.cairo

Lines changed: 13 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ pub mod EntryFeeComponent {
2323
use openzeppelin_introspection::src5::SRC5Component;
2424
use openzeppelin_introspection::src5::SRC5Component::InternalTrait as SRC5InternalTrait;
2525
use starknet::storage::{
26-
Map, MutableVecTrait, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess,
27-
Vec, VecTrait,
26+
Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess,
2827
};
2928
use starknet::{ContractAddress, get_caller_address, get_contract_address};
3029
use crate::entry_fee::entry_fee_store::{EntryFeeStoreImpl, EntryFeeStoreTrait};
@@ -62,10 +61,12 @@ pub mod EntryFeeComponent {
6261
EntryFee_distribution: Map<u64, PackedDistribution>,
6362
/// Refund claimed: (context_id, token_id) -> claimed
6463
EntryFee_refund_claimed: Map<(u64, felt252), bool>,
65-
/// Extension address for extension-enhanced entry fees
64+
/// Extension address for extension-enhanced entry fees. Doubles as
65+
/// the "is this context's fee an extension?" flag: zero means no
66+
/// extension is configured. The extension contract owns the
67+
/// authoritative config — this component only stores the address
68+
/// it needs for dispatch.
6669
EntryFee_extension_address: Map<u64, ContractAddress>,
67-
/// Extension config data (stored as Vec)
68-
EntryFee_extension_config: Map<u64, Vec<felt252>>,
6970
}
7071

7172
#[event]
@@ -151,22 +152,6 @@ pub mod EntryFeeComponent {
151152
self.EntryFee_extension_address.entry(context_id).write(address);
152153
}
153154

154-
fn get_extension_config_len(self: @ComponentState<TContractState>, context_id: u64) -> u64 {
155-
self.EntryFee_extension_config.entry(context_id).len()
156-
}
157-
158-
fn get_extension_config_at(
159-
self: @ComponentState<TContractState>, context_id: u64, index: u64,
160-
) -> felt252 {
161-
self.EntryFee_extension_config.entry(context_id).at(index).read()
162-
}
163-
164-
fn push_extension_config(
165-
ref self: ComponentState<TContractState>, context_id: u64, value: felt252,
166-
) {
167-
self.EntryFee_extension_config.entry(context_id).push(value);
168-
}
169-
170155
fn get_distribution_shares_packed(
171156
self: @ComponentState<TContractState>, context_id: u64, slot: u8,
172157
) -> CustomShares {
@@ -298,12 +283,16 @@ pub mod EntryFeeComponent {
298283
EntryFeeStoreTrait::set_entry_fee_config(ref self, context_id, config);
299284
}
300285

301-
/// Internal: store extension config and notify extension contract
286+
/// Internal: persist the extension address and forward the config
287+
/// to the extension contract. The config blob is NOT persisted here
288+
/// — the extension is the sole source of truth for its own config,
289+
/// and indexers wanting to recover it should read the original
290+
/// extension call (e.g. via the host's `TournamentCreated`-style
291+
/// event) or query the extension's own view methods.
302292
fn _set_extension(
303293
ref self: ComponentState<TContractState>, context_id: u64, ext: ExtensionConfig,
304294
) {
305295
EntryFeeStoreTrait::store_extension_address(ref self, context_id, ext.address);
306-
EntryFeeStoreTrait::write_extension_config(ref self, context_id, ext.config);
307296

308297
let dispatcher = IEntryFeeExtensionDispatcher { contract_address: ext.address };
309298
dispatcher.set_entry_fee_config(context_id, ext.config);
@@ -392,21 +381,7 @@ pub mod EntryFeeComponent {
392381

393382
// --- Extension helpers ---
394383

395-
/// Read extension config for a context
396-
fn read_extension_config(
397-
self: @ComponentState<TContractState>, context_id: u64,
398-
) -> Span<felt252> {
399-
EntryFeeStoreTrait::read_extension_config(self, context_id)
400-
}
401-
402-
/// Write extension config for a context
403-
fn write_extension_config(
404-
ref self: ComponentState<TContractState>, context_id: u64, config: Span<felt252>,
405-
) {
406-
EntryFeeStoreTrait::write_extension_config(ref self, context_id, config);
407-
}
408-
409-
/// Get extension address for a context
384+
/// Get extension address for a context (zero = no extension configured).
410385
fn get_extension_address(
411386
self: @ComponentState<TContractState>, context_id: u64,
412387
) -> ContractAddress {

packages/metagame/src/entry_fee/entry_fee_store.cairo

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,6 @@ pub trait EntryFeeStoreTrait<T> {
3535
fn is_claimed(self: @T, context_id: u64, claim_type: EntryFeeClaimType) -> bool;
3636
/// Mark a claim as completed.
3737
fn set_claimed(ref self: T, context_id: u64, claim_type: EntryFeeClaimType);
38-
/// Read extension config for a context.
39-
fn read_extension_config(self: @T, context_id: u64) -> Span<felt252>;
40-
/// Write extension config for a context.
41-
fn write_extension_config(ref self: T, context_id: u64, config: Span<felt252>);
4238
/// Store extension address.
4339
fn store_extension_address(ref self: T, context_id: u64, address: ContractAddress);
4440
/// Get extension address.
@@ -298,31 +294,6 @@ pub impl EntryFeeStoreImpl<T, +Store<T>, +Drop<T>> of EntryFeeStoreTrait<T> {
298294
}
299295
}
300296

301-
fn read_extension_config(self: @T, context_id: u64) -> Span<felt252> {
302-
let len = self.get_extension_config_len(context_id);
303-
let mut arr = ArrayTrait::new();
304-
let mut i: u64 = 0;
305-
loop {
306-
if i >= len {
307-
break;
308-
}
309-
arr.append(self.get_extension_config_at(context_id, i));
310-
i += 1;
311-
}
312-
arr.span()
313-
}
314-
315-
fn write_extension_config(ref self: T, context_id: u64, config: Span<felt252>) {
316-
let mut i: u32 = 0;
317-
loop {
318-
if i >= config.len() {
319-
break;
320-
}
321-
self.push_extension_config(context_id, *config.at(i));
322-
i += 1;
323-
};
324-
}
325-
326297
fn store_extension_address(ref self: T, context_id: u64, address: ContractAddress) {
327298
self.set_extension_address(context_id, address);
328299
}

packages/metagame/src/entry_fee/store.cairo

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ pub trait Store<T> {
2222
fn set_refund_claimed(ref self: T, context_id: u64, token_id: felt252, claimed: bool);
2323
fn get_extension_address(self: @T, context_id: u64) -> ContractAddress;
2424
fn set_extension_address(ref self: T, context_id: u64, address: ContractAddress);
25-
fn get_extension_config_len(self: @T, context_id: u64) -> u64;
26-
fn get_extension_config_at(self: @T, context_id: u64, index: u64) -> felt252;
27-
fn push_extension_config(ref self: T, context_id: u64, value: felt252);
2825
/// Packed custom distribution shares for slot `slot` (15 `u16` per slot).
2926
/// The number of shares is sourced from `get_distribution().positions` —
3027
/// no separate count is stored here.

packages/metagame/src/entry_fee/tests/mocks/entry_fee_mock.cairo

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,6 @@ pub mod EntryFeeMock {
5656
self.entry_fee.set_claimed(context_id, claim_type);
5757
}
5858

59-
#[external(v0)]
60-
fn read_extension_config(self: @ContractState, context_id: u64) -> Span<felt252> {
61-
self.entry_fee.read_extension_config(context_id)
62-
}
63-
64-
#[external(v0)]
65-
fn write_extension_config(ref self: ContractState, context_id: u64, config: Span<felt252>) {
66-
self.entry_fee.write_extension_config(context_id, config);
67-
}
68-
6959
#[external(v0)]
7060
fn get_extension_address(self: @ContractState, context_id: u64) -> ContractAddress {
7161
self.entry_fee.get_extension_address(context_id)

packages/metagame/src/entry_fee/tests/test_entry_fee_store.cairo

Lines changed: 10 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ trait IEntryFeeMockFull<TContractState> {
2727
fn is_claimed(self: @TContractState, context_id: u64, claim_type: EntryFeeClaimType) -> bool;
2828
fn set_claimed(ref self: TContractState, context_id: u64, claim_type: EntryFeeClaimType);
2929
// Extension functions
30-
fn read_extension_config(self: @TContractState, context_id: u64) -> Span<felt252>;
31-
fn write_extension_config(ref self: TContractState, context_id: u64, config: Span<felt252>);
3230
fn get_extension_address(self: @TContractState, context_id: u64) -> ContractAddress;
3331
fn claim_entry_fee_extension(
3432
ref self: TContractState, context_id: u64, claim_params: Span<felt252>,
@@ -540,13 +538,15 @@ fn test_additional_share_claim_across_slots() {
540538

541539
// ============================================================================
542540
// 7. Extension storage via set_entry_fee(Extension) with mock_call
543-
// This exercises: _set_extension -> store_extension_address,
544-
// write_extension_config, and the IEntryFeeExtension dispatch.
541+
// Exercises _set_extension -> store_extension_address and the
542+
// IEntryFeeExtension.set_entry_fee_config dispatch. The extension
543+
// config blob is forwarded to the extension and not persisted on the
544+
// component — the extension is the sole source of truth for it.
545545
// Also exercises is_entry_fee_set returning true for extension.
546546
// ============================================================================
547547

548548
#[test]
549-
fn test_set_entry_fee_extension_stores_address_and_config() {
549+
fn test_set_entry_fee_extension_stores_address() {
550550
let mock = deploy_mock();
551551
let ext_addr = make_address(0xDEAD);
552552

@@ -561,16 +561,9 @@ fn test_set_entry_fee_extension_stores_address_and_config() {
561561
);
562562
mock.set_entry_fee(1, entry_fee);
563563

564-
// Verify extension address was stored via store_extension_address bridge
565-
let stored_addr = mock.get_extension_address(1);
566-
assert!(stored_addr == ext_addr, "extension address mismatch");
567-
568-
// Verify extension config was written via write_extension_config bridge
569-
let stored_config = mock.read_extension_config(1);
570-
assert!(stored_config.len() == 3, "extension config should have 3 elements");
571-
assert!(*stored_config.at(0) == 0x111, "config element 0 mismatch");
572-
assert!(*stored_config.at(1) == 0x222, "config element 1 mismatch");
573-
assert!(*stored_config.at(2) == 0x333, "config element 2 mismatch");
564+
// Address is persisted on the component; config is forwarded to the
565+
// extension (mocked here) and not stored.
566+
assert!(mock.get_extension_address(1) == ext_addr, "extension address mismatch");
574567
}
575568

576569
#[test]
@@ -614,11 +607,7 @@ fn test_set_entry_fee_extension_with_empty_config() {
614607
);
615608
mock.set_entry_fee(1, entry_fee);
616609

617-
let stored_addr = mock.get_extension_address(1);
618-
assert!(stored_addr == ext_addr, "extension address mismatch");
619-
620-
let stored_config = mock.read_extension_config(1);
621-
assert!(stored_config.len() == 0, "extension config should be empty");
610+
assert!(mock.get_extension_address(1) == ext_addr, "extension address mismatch");
622611
}
623612

624613
#[test]
@@ -640,77 +629,6 @@ fn test_get_entry_fee_returns_none_for_extension_only() {
640629
assert!(result.is_none(), "should be None for extension-only entry fee (no token)");
641630
}
642631

643-
// ============================================================================
644-
// 8. Extension read/write directly via mock helpers
645-
// Exercises read_extension_config and write_extension_config bridge paths.
646-
// ============================================================================
647-
648-
#[test]
649-
fn test_extension_config_empty_by_default() {
650-
let mock = deploy_mock();
651-
let config = mock.read_extension_config(1);
652-
assert!(config.len() == 0, "extension config should be empty by default");
653-
}
654-
655-
#[test]
656-
fn test_write_and_read_extension_config_single_element() {
657-
let mock = deploy_mock();
658-
659-
let config = array![0x12345].span();
660-
mock.write_extension_config(1, config);
661-
662-
let result = mock.read_extension_config(1);
663-
assert!(result.len() == 1, "should have 1 element");
664-
assert!(*result.at(0) == 0x12345, "element 0 mismatch");
665-
}
666-
667-
#[test]
668-
fn test_write_and_read_extension_config_multiple_elements() {
669-
let mock = deploy_mock();
670-
671-
let config = array![0x111, 0x222, 0x333, 0x444, 0x555].span();
672-
mock.write_extension_config(1, config);
673-
674-
let result = mock.read_extension_config(1);
675-
assert!(result.len() == 5, "should have 5 elements");
676-
assert!(*result.at(0) == 0x111, "element 0 mismatch");
677-
assert!(*result.at(1) == 0x222, "element 1 mismatch");
678-
assert!(*result.at(2) == 0x333, "element 2 mismatch");
679-
assert!(*result.at(3) == 0x444, "element 3 mismatch");
680-
assert!(*result.at(4) == 0x555, "element 4 mismatch");
681-
}
682-
683-
#[test]
684-
fn test_extension_config_isolation_by_context() {
685-
let mock = deploy_mock();
686-
687-
mock.write_extension_config(1, array![0xAAA, 0xBBB].span());
688-
mock.write_extension_config(2, array![0xCCC].span());
689-
690-
let config1 = mock.read_extension_config(1);
691-
let config2 = mock.read_extension_config(2);
692-
let config3 = mock.read_extension_config(3);
693-
694-
assert!(config1.len() == 2, "context 1 should have 2 elements");
695-
assert!(*config1.at(0) == 0xAAA, "context 1 element 0 mismatch");
696-
assert!(*config1.at(1) == 0xBBB, "context 1 element 1 mismatch");
697-
698-
assert!(config2.len() == 1, "context 2 should have 1 element");
699-
assert!(*config2.at(0) == 0xCCC, "context 2 element 0 mismatch");
700-
701-
assert!(config3.len() == 0, "context 3 should be empty");
702-
}
703-
704-
#[test]
705-
fn test_write_extension_config_empty_span() {
706-
let mock = deploy_mock();
707-
708-
mock.write_extension_config(1, array![].span());
709-
710-
let result = mock.read_extension_config(1);
711-
assert!(result.len() == 0, "writing empty span should result in empty config");
712-
}
713-
714632
// ============================================================================
715633
// 9. Extension address via get_extension_address
716634
// ============================================================================
@@ -862,37 +780,7 @@ fn test_exactly_16_shares_full_slot() {
862780
}
863781

864782
// ============================================================================
865-
// 13. Extension address and config together via mock helpers
866-
// ============================================================================
867-
868-
#[test]
869-
fn test_extension_address_and_config_together() {
870-
let mock = deploy_mock();
871-
let ext_addr = make_address(0xE0E0E0);
872-
873-
mock_extension_calls(ext_addr);
874-
875-
let config_data = array![0x42, 0x84, 0xFF];
876-
let entry_fee = EntryFee::Extension(
877-
metagame_extensions_interfaces::extension::ExtensionConfig {
878-
address: ext_addr, config: config_data.span(),
879-
},
880-
);
881-
mock.set_entry_fee(1, entry_fee);
882-
883-
// Verify address via get_extension bridge
884-
assert!(mock.get_extension_address(1) == ext_addr, "extension address mismatch");
885-
886-
// Verify config via read_extension_config bridge
887-
let config = mock.read_extension_config(1);
888-
assert!(config.len() == 3, "config should have 3 elements");
889-
assert!(*config.at(0) == 0x42, "config element 0 mismatch");
890-
assert!(*config.at(1) == 0x84, "config element 1 mismatch");
891-
assert!(*config.at(2) == 0xFF, "config element 2 mismatch");
892-
}
893-
894-
// ============================================================================
895-
// 14. claim_entry_fee_extension dispatch
783+
// 13. claim_entry_fee_extension dispatch
896784
// ============================================================================
897785

898786
#[test]

0 commit comments

Comments
 (0)