Skip to content

Commit 8cc59eb

Browse files
starknetdevclaude
andcommitted
refactor(prize)!: hoist common fields into PrizeRecord wrapper
Factors common host-assigned metadata (id, context_id, sponsor_address) out of the Prize sum-type variants into a wrapping PrizeRecord struct. The Prize enum now carries only variant-specific payload data, mirroring its role as add_prize INPUT (where host fields are irrelevant) and add_prize EVENT payload (where host fields are top-level event keys/fields anyway). Shapes: pub struct PrizeRecord { id: u64, context_id: u64, sponsor_address: ContractAddress, prize: Prize, } pub enum Prize { Token: TokenPrizePayload, // { token_address, token_type } Extension: ExtensionPrizePayload, // { address, config } } IPrize.get_prize -> PrizeRecord (was Prize). For extension prizes the host now also captures sponsor_address (the caller of add_prize at registration), stored alongside the existing prize_id -> context_id reverse index in a new Prize_extension_prize_sponsor map. Symmetric with built-in prizes, which keep sponsor in StoredPrize. Renames: - TokenPrize -> TokenPrizePayload (no host fields) - ExtensionPrize -> ExtensionPrizePayload (no host fields) - PrizeStoreTrait::get_token_prize/set_token_prize -> get_token_record/set_token_record (set takes context_id + sponsor + payload separately) - StoredPrizeTrait::from_token_prize/to_token_prize -> from_token_record/to_token_record - PrizeInternalImpl::set_token_prize -> set_token_record - _add_token_prize now takes TokenPrizePayload only Tests updated: helpers return (id, context_id, sponsor, payload) tuples for compatibility with the new set_token_record arity; get_prize results pattern-match on record.prize. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 74afe22 commit 8cc59eb

10 files changed

Lines changed: 381 additions & 246 deletions

File tree

packages/interfaces/src/lib.cairo

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ pub use minigame::{
8282

8383
// Prize
8484
pub use prize::{
85-
ERC20Data, ERC721Data, ExtensionPrize, IPrize, IPrizeDispatcher, IPrizeDispatcherTrait, Prize,
86-
PrizeType, TokenPrize, TokenTypeData,
85+
ERC20Data, ERC721Data, ExtensionPrizePayload, IPrize, IPrizeDispatcher, IPrizeDispatcherTrait,
86+
Prize, PrizeRecord, PrizeType, TokenPrizePayload, TokenTypeData,
8787
};
8888

8989
// Registration

packages/interfaces/src/prize.cairo

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -30,50 +30,52 @@ pub enum TokenTypeData {
3030
erc721: ERC721Data,
3131
}
3232

33-
/// Tagged union for the two prize lifecycles. Used both as input to
34-
/// `add_prize` (sponsor passes host-assigned fields as zero — the host
35-
/// overwrites them with the real id / sponsor) AND as output from
36-
/// `get_prize` (fully populated).
33+
/// Tagged union for the two prize lifecycles. Carries only the
34+
/// variant-specific payload — common host-assigned metadata
35+
/// (`id`, `context_id`, `sponsor_address`) lives on `PrizeRecord`.
36+
/// Used as:
37+
/// - input to `add_prize` (sponsor submits a Prize value; host
38+
/// wraps with the assigned id / context_id / sponsor address)
39+
/// - payload inside `PrizeRecord` returned by `get_prize`
40+
/// - payload field of host events like `PrizeAdded`.
3741
///
3842
/// - `Token` — built-in ERC20/ERC721 flow. The host stores the
39-
/// `token_address` + `token_type` and tracks `id`/`context_id`/
40-
/// `sponsor_address`.
43+
/// `token_address` + `token_type`.
4144
/// - `Extension` — external `IPrizeExtension`. The host stores only
42-
/// the `address` and the `id`/`context_id` mapping; the `config`
43-
/// blob is fetched dynamically from the extension via
44-
/// `IPrizeExtension.get_config` on each `get_prize` read.
45+
/// the `address`; the `config` blob is fetched dynamically from
46+
/// the extension via `IPrizeExtension.get_config` on each
47+
/// `get_prize` read.
4548
#[derive(Drop, Serde)]
4649
pub enum Prize {
47-
Token: TokenPrize,
48-
Extension: ExtensionPrize,
50+
Token: TokenPrizePayload,
51+
Extension: ExtensionPrizePayload,
4952
}
5053

51-
/// Built-in token-prize variant payload. `id`, `context_id` and
52-
/// `sponsor_address` are set by the host at `add_prize` time (input
53-
/// values are ignored). Sponsors building calldata should pass these
54-
/// as zero.
5554
#[derive(Drop, Serde)]
56-
pub struct TokenPrize {
57-
pub id: u64,
58-
pub context_id: u64,
59-
pub sponsor_address: ContractAddress,
55+
pub struct TokenPrizePayload {
6056
pub token_address: ContractAddress,
6157
pub token_type: TokenTypeData,
6258
}
6359

64-
/// Extension-prize variant payload. `id` and `context_id` are set by
65-
/// the host at `add_prize` time (input values are ignored). Sponsors
66-
/// building calldata should pass these as zero. Note there is no
67-
/// `sponsor_address` — extension prizes don't track a host-side
68-
/// sponsor (the extension contract is the authoritative owner).
6960
#[derive(Drop, Serde)]
70-
pub struct ExtensionPrize {
71-
pub id: u64,
72-
pub context_id: u64,
61+
pub struct ExtensionPrizePayload {
7362
pub address: ContractAddress,
7463
pub config: Span<felt252>,
7564
}
7665

66+
/// Full prize view returned by `IPrize.get_prize`. Combines the
67+
/// host-assigned identity / context / sponsor metadata with the
68+
/// variant-specific `Prize` payload. Same shape for built-in and
69+
/// extension prizes — consumers branch on `record.prize` only when
70+
/// they need the variant-specific data.
71+
#[derive(Drop, Serde)]
72+
pub struct PrizeRecord {
73+
pub id: u64,
74+
pub context_id: u64,
75+
pub sponsor_address: ContractAddress,
76+
pub prize: Prize,
77+
}
78+
7779
#[allow(starknet::store_no_default_variant)]
7880
#[derive(Copy, Drop, Serde, PartialEq)]
7981
pub enum PrizeType {
@@ -83,12 +85,12 @@ pub enum PrizeType {
8385

8486
#[starknet::interface]
8587
pub trait IPrize<TState> {
86-
/// Get a prize by its ID. Returns the full `Prize` sum type with
87-
/// all host-assigned and payload fields populated. For `Extension`
88-
/// prizes the `config` blob is fetched live from the extension
89-
/// contract via `IPrizeExtension.get_config` (one cross-contract
90-
/// call per read).
91-
fn get_prize(self: @TState, prize_id: u64) -> Prize;
88+
/// Get a prize by its ID. Returns the full `PrizeRecord`
89+
/// (id + context_id + sponsor_address + the variant-specific
90+
/// `Prize` payload). For `Extension` prizes the payload's
91+
/// `config` blob is fetched live from the extension contract via
92+
/// `IPrizeExtension.get_config` (one cross-contract call per read).
93+
fn get_prize(self: @TState, prize_id: u64) -> PrizeRecord;
9294

9395
/// Get total prizes count
9496
fn get_total_prizes(self: @TState) -> u64;

packages/metagame/src/prize/prize_component.cairo

Lines changed: 78 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ pub mod PrizeComponent {
3030
use crate::prize::prize_store::{PrizeStoreImpl, PrizeStoreTrait};
3131
use crate::prize::store::Store;
3232
use crate::prize::structs::{
33-
CustomShares, ERC20Data, ExtensionPrize, Prize, PrizeType, StoredPrize, TokenPrize,
34-
TokenTypeData,
33+
CustomShares, ERC20Data, ExtensionPrizePayload, Prize, PrizeRecord, PrizeType, StoredPrize,
34+
TokenPrizePayload, TokenTypeData,
3535
};
3636

3737
#[storage]
@@ -62,6 +62,10 @@ pub mod PrizeComponent {
6262
/// prizes have their context_id stored in `StoredPrize.context_id`
6363
/// and are absent from this map.
6464
Prize_extension_prize_context: Map<u64, u64>,
65+
/// `prize_id -> sponsor_address` for extension prizes (the
66+
/// caller of `add_prize` at registration). Built-in prizes
67+
/// keep sponsor on `StoredPrize.sponsor_address`.
68+
Prize_extension_prize_sponsor: Map<u64, ContractAddress>,
6569
}
6670

6771
#[event]
@@ -148,6 +152,18 @@ pub mod PrizeComponent {
148152
) {
149153
self.Prize_extension_prize_context.entry(prize_id).write(context_id);
150154
}
155+
156+
fn get_extension_prize_sponsor(
157+
self: @ComponentState<TContractState>, prize_id: u64,
158+
) -> ContractAddress {
159+
self.Prize_extension_prize_sponsor.entry(prize_id).read()
160+
}
161+
162+
fn set_extension_prize_sponsor(
163+
ref self: ComponentState<TContractState>, prize_id: u64, sponsor: ContractAddress,
164+
) {
165+
self.Prize_extension_prize_sponsor.entry(prize_id).write(sponsor);
166+
}
151167
}
152168

153169
/// Resolve a `prize_id` to its full `Prize` sum-type view.
@@ -161,27 +177,31 @@ pub mod PrizeComponent {
161177
/// `PrizeStoreTrait::get_token_prize` store-bridge path.
162178
fn resolve_prize<TContractState, +HasComponent<TContractState>>(
163179
self: @ComponentState<TContractState>, prize_id: u64,
164-
) -> Prize {
180+
) -> PrizeRecord {
165181
let context_id = Store::get_extension_prize_context(self, prize_id);
166182
if context_id == 0 {
167-
return Prize::Token(PrizeStoreTrait::get_token_prize(self, prize_id));
183+
return PrizeStoreTrait::get_token_record(self, prize_id);
168184
}
169185
let extension_address = Store::get_extension_address(self, context_id, prize_id);
186+
let sponsor_address = Store::get_extension_prize_sponsor(self, prize_id);
170187
let context_owner = get_contract_address();
171188
let dispatcher = IPrizeExtensionDispatcher { contract_address: extension_address };
172189
let extension_config = dispatcher.get_config(context_owner, context_id, prize_id);
173-
Prize::Extension(
174-
ExtensionPrize {
175-
id: prize_id, context_id, address: extension_address, config: extension_config,
176-
},
177-
)
190+
PrizeRecord {
191+
id: prize_id,
192+
context_id,
193+
sponsor_address,
194+
prize: Prize::Extension(
195+
ExtensionPrizePayload { address: extension_address, config: extension_config },
196+
),
197+
}
178198
}
179199

180200
#[embeddable_as(PrizeImpl)]
181201
impl PrizeComponentImpl<
182202
TContractState, +HasComponent<TContractState>,
183203
> of IPrize<ComponentState<TContractState>> {
184-
fn get_prize(self: @ComponentState<TContractState>, prize_id: u64) -> Prize {
204+
fn get_prize(self: @ComponentState<TContractState>, prize_id: u64) -> PrizeRecord {
185205
resolve_prize(self, prize_id)
186206
}
187207

@@ -218,7 +238,7 @@ pub mod PrizeComponent {
218238
/// Note: extension prizes incur a cross-contract call on every
219239
/// read. Callers wanting bulk reads should batch / cache
220240
/// accordingly.
221-
fn _get_prize(self: @ComponentState<TContractState>, prize_id: u64) -> Prize {
241+
fn _get_prize(self: @ComponentState<TContractState>, prize_id: u64) -> PrizeRecord {
222242
resolve_prize(self, prize_id)
223243
}
224244

@@ -228,12 +248,18 @@ pub mod PrizeComponent {
228248
PrizeStoreTrait::get_custom_shares(self, prize_id)
229249
}
230250

231-
/// Store a token prize (converts to StoredPrize for storage).
251+
/// Store a token-prize record (converts to StoredPrize for storage).
232252
/// Extension prizes are not persisted via this path.
233-
fn set_token_prize(
234-
ref self: ComponentState<TContractState>, prize_id: u64, prize: TokenPrize,
253+
fn set_token_record(
254+
ref self: ComponentState<TContractState>,
255+
prize_id: u64,
256+
context_id: u64,
257+
sponsor_address: ContractAddress,
258+
payload: TokenPrizePayload,
235259
) {
236-
PrizeStoreTrait::set_token_prize(ref self, prize_id, prize);
260+
PrizeStoreTrait::set_token_record(
261+
ref self, prize_id, context_id, sponsor_address, payload,
262+
);
237263
}
238264

239265
/// Get total prizes count (internal)
@@ -302,20 +328,19 @@ pub mod PrizeComponent {
302328

303329
/// Add a prize or set extension for a context.
304330
/// Returns the prize_id in both cases.
305-
/// - `Prize::Token` : deposits tokens, stores prize data. Host-
306-
/// assigned fields on the input variant (`id`, `context_id`,
307-
/// `sponsor_address`) are ignored — the host overwrites them.
308-
/// - `Prize::Extension` : increments prize count, registers the
309-
/// extension address keyed by `(context_id, prize_id)`, and
310-
/// dispatches `IPrizeExtension.add_prize(...)`. Input host
311-
/// fields (`id`, `context_id`) are likewise ignored.
331+
/// - `Prize::Token(payload)`: deposits tokens, stores prize
332+
/// data. Host assigns id/context_id/sponsor_address.
333+
/// - `Prize::Extension(payload)`: increments prize count,
334+
/// registers the extension address keyed by
335+
/// `(context_id, prize_id)`, captures sponsor, and
336+
/// dispatches `IPrizeExtension.add_prize(...)`.
312337
fn add_prize(
313338
ref self: ComponentState<TContractState>, context_id: u64, prize: Prize,
314339
) -> u64 {
315340
match prize {
316-
Prize::Token(input) => self._add_token_prize(context_id, input),
317-
Prize::Extension(input) => {
318-
let ext = ExtensionConfig { address: input.address, config: input.config };
341+
Prize::Token(payload) => self._add_token_prize(context_id, payload),
342+
Prize::Extension(payload) => {
343+
let ext = ExtensionConfig { address: payload.address, config: payload.config };
319344
assert!(!ext.address.is_zero(), "Prize: Extension address cannot be zero");
320345
let src5 = ISRC5Dispatcher { contract_address: ext.address };
321346
let display_address: felt252 = ext.address.into();
@@ -332,15 +357,14 @@ pub mod PrizeComponent {
332357
}
333358

334359
/// Internal: deposit tokens, store prize data, return prize_id.
335-
/// `input` is a `TokenPrize` whose host-assigned fields (id,
336-
/// context_id, sponsor_address) are ignored — the host
337-
/// overwrites them with the assigned id, the caller-supplied
338-
/// `context_id` parameter, and `get_caller_address()`.
360+
/// Host fills in the id (assigned), context_id (caller-supplied),
361+
/// and sponsor_address (`get_caller_address()`) around the
362+
/// supplied `payload`.
339363
fn _add_token_prize(
340-
ref self: ComponentState<TContractState>, context_id: u64, input: TokenPrize,
364+
ref self: ComponentState<TContractState>, context_id: u64, payload: TokenPrizePayload,
341365
) -> u64 {
342-
let token_address = input.token_address;
343-
let token_type = input.token_type;
366+
let token_address = payload.token_address;
367+
let token_type = payload.token_type;
344368

345369
// Deposit the prize tokens
346370
match @token_type {
@@ -379,14 +403,11 @@ pub mod PrizeComponent {
379403
}
380404
}
381405

382-
// Persist the built-in token prize. Host-assigned fields
383-
// (id, context_id, sponsor_address) overwrite whatever the
384-
// caller passed in `input`.
406+
// Persist the built-in token prize. Host fills id,
407+
// context_id, sponsor_address around the supplied payload.
385408
let sponsor = get_caller_address();
386-
let prize = TokenPrize {
387-
id, context_id, sponsor_address: sponsor, token_address, token_type,
388-
};
389-
PrizeStoreTrait::set_token_prize(ref self, id, prize);
409+
let payload = TokenPrizePayload { token_address, token_type };
410+
PrizeStoreTrait::set_token_record(ref self, id, context_id, sponsor, payload);
390411

391412
id
392413
}
@@ -405,6 +426,8 @@ pub mod PrizeComponent {
405426
Store::set_extension_address(ref self, context_id, prize_id, ext.address);
406427
// Reverse index for the prize_id-only `get_prize` lookup.
407428
Store::set_extension_prize_context(ref self, prize_id, context_id);
429+
// Capture sponsor (the caller of the host's `add_prize`).
430+
Store::set_extension_prize_sponsor(ref self, prize_id, get_caller_address());
408431

409432
let dispatcher = IPrizeExtensionDispatcher { contract_address: ext.address };
410433
dispatcher.add_prize(context_id, prize_id, ext.config);
@@ -457,10 +480,14 @@ pub mod PrizeComponent {
457480
fn refund_prize_erc20(
458481
ref self: ComponentState<TContractState>, prize_id: u64, amount: u128,
459482
) {
460-
let prize = PrizeStoreTrait::get_token_prize(@self, prize_id);
461-
let erc20 = IERC20Dispatcher { contract_address: prize.token_address };
483+
let record = PrizeStoreTrait::get_token_record(@self, prize_id);
484+
let token_address = match record.prize {
485+
Prize::Token(payload) => payload.token_address,
486+
Prize::Extension(_) => panic!("Prize: extension prize cannot be refunded as ERC20"),
487+
};
488+
let erc20 = IERC20Dispatcher { contract_address: token_address };
462489
assert!(
463-
erc20.transfer(prize.sponsor_address, amount.into()),
490+
erc20.transfer(record.sponsor_address, amount.into()),
464491
"Prize: ERC20 refund transfer failed",
465492
);
466493
}
@@ -469,9 +496,15 @@ pub mod PrizeComponent {
469496
fn refund_prize_erc721(
470497
ref self: ComponentState<TContractState>, prize_id: u64, token_id: u128,
471498
) {
472-
let prize = PrizeStoreTrait::get_token_prize(@self, prize_id);
473-
let erc721 = IERC721Dispatcher { contract_address: prize.token_address };
474-
erc721.transfer_from(get_contract_address(), prize.sponsor_address, token_id.into());
499+
let record = PrizeStoreTrait::get_token_record(@self, prize_id);
500+
let token_address = match record.prize {
501+
Prize::Token(payload) => payload.token_address,
502+
Prize::Extension(_) => panic!(
503+
"Prize: extension prize cannot be refunded as ERC721",
504+
),
505+
};
506+
let erc721 = IERC721Dispatcher { contract_address: token_address };
507+
erc721.transfer_from(get_contract_address(), record.sponsor_address, token_id.into());
475508
}
476509

477510
// --- Extension helpers ---

0 commit comments

Comments
 (0)