@xgamesfun/solana 0.26.0 → 0.26.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/js/client/idl/loot.js +1 -1
- package/js/client/idl/loot.json +4 -3
- package/js/client/sizes.js +180 -66
- package/js/economy/check.mjs +36 -19
- package/js/economy/guide.mjs +1 -1
- package/js/economy/plan.mjs +32 -8
- package/js/play/index.js +15 -7
- package/package.json +1 -1
- package/release.json +5 -5
- package/skills/xsolana-economy/SKILL.md +3 -3
- package/skills/xsolana-economy/references/telling-the-person.md +2 -1
- package/types/client/sizes.d.ts +42 -8
- package/types/economy/check.d.mts +6 -2
- package/types/economy/cost.d.mts +1 -0
- package/types/economy/live.d.mts +2 -0
- package/types/economy/plan.d.mts +9 -1
package/js/client/idl/loot.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"address": "9cwqM1TXytPnFZsLfmUyv2dg4uW6Ch9C6UJVPns7NFQ",
|
|
3
3
|
"metadata": {
|
|
4
4
|
"name": "loot",
|
|
5
|
-
"version": "0.26.
|
|
5
|
+
"version": "0.26.1",
|
|
6
6
|
"spec": "0.1.0",
|
|
7
7
|
"description": "Economy for Anything: one program every coin of a project passes through",
|
|
8
8
|
"repository": "https://github.com/gamesfun/loot"
|
|
@@ -9551,7 +9551,7 @@
|
|
|
9551
9551
|
{
|
|
9552
9552
|
"code": 6141,
|
|
9553
9553
|
"name": "PackTooLarge",
|
|
9554
|
-
"msg": "A box's draws (one, or each open's times the opens bought together) must all fit one answer of the oracle's: fewer entries, prizes an entry or
|
|
9554
|
+
"msg": "A box's draws (one, or each open's times the opens bought together) must all fit one answer of the oracle's, and the sales one buy adds up one transaction: fewer entries, prizes an entry, draws or opens"
|
|
9555
9555
|
},
|
|
9556
9556
|
{
|
|
9557
9557
|
"code": 6142,
|
|
@@ -11575,7 +11575,8 @@
|
|
|
11575
11575
|
"docs": [
|
|
11576
11576
|
"The carry: rounding of a share over the total weight, times `PRECISION`. The next move before",
|
|
11577
11577
|
"the farm's end credits it to the seats there once it adds up to a share; a move that leaves no",
|
|
11578
|
-
"weight, or any move once the farm is over, frees its whole tokens (`FarmSide::settle`)
|
|
11578
|
+
"weight, or any move once the farm is over, frees its whole tokens (`FarmSide::settle`), and so",
|
|
11579
|
+
"does a walk that begins with no weight or where the weight reaches 0 (`Farm::walk`)."
|
|
11579
11580
|
],
|
|
11580
11581
|
"type": "u128"
|
|
11581
11582
|
},
|
package/js/client/sizes.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
// Rent sizes from the current IDL. Fixed allocations reserve the largest enum and option forms,
|
|
2
2
|
// matching Anchor InitSpace. Dynamic records use their actual encoded fields.
|
|
3
|
+
import { addressBytes } from '../chain/base58.js';
|
|
3
4
|
import { encodeAccount, encodeBorsh } from '../chain/borsh.js';
|
|
5
|
+
import { uintLE, utf8 } from '../chain/bytes.js';
|
|
4
6
|
import IDL from './idl/loot.js';
|
|
7
|
+
import { lootAddress } from './pda.js';
|
|
5
8
|
|
|
6
9
|
const scalar = { bool: 1, u8: 1, u16: 2, u32: 4, i32: 4, u64: 8, i64: 8, u128: 16, pubkey: 32 };
|
|
7
10
|
// What a fixed-size account reserves for its lists and strings: a roll's fate (`Fate.shifts`, each key at most 32 bytes).
|
|
@@ -170,7 +173,7 @@ export function orderBytes(lootBox, shares = 0, opens = 1, items) {
|
|
|
170
173
|
|
|
171
174
|
// What one oracle answer's roll spends, counted at no less than each part costs (`order.rs`'s `RollCost`, measured by
|
|
172
175
|
// the program's `tests/roll_costs.rs` and `tests/roll_heap.rs`), against the compute and the heap the oracle gives its
|
|
173
|
-
// answer, less a margin.
|
|
176
|
+
// answer, less a margin. Every number of the program's count is here, and only here: a change of one is one edit.
|
|
174
177
|
const ROLL_BUDGET = 300_000n;
|
|
175
178
|
const ROLL_HEAP = 32n * 1024n - 512n;
|
|
176
179
|
const COST = {
|
|
@@ -184,7 +187,14 @@ const COST = {
|
|
|
184
187
|
tokens: 6_000n,
|
|
185
188
|
draw: 9_000n,
|
|
186
189
|
group: 250n,
|
|
190
|
+
row: 10n,
|
|
191
|
+
// One try of an address search, what the runtime charges (`COST_TRY`).
|
|
192
|
+
try: 1_500n,
|
|
193
|
+
// Reading a prize of an entry after its first (`COST_PRIZE`).
|
|
194
|
+
prize: 300n,
|
|
187
195
|
reread: 1_000n,
|
|
196
|
+
// A draw of an entry that gives nothing, beyond its weighing (`COST_NOTHING`).
|
|
197
|
+
nothing: 1_100n,
|
|
188
198
|
finish: 15_000n,
|
|
189
199
|
won: 150n,
|
|
190
200
|
drawn: 50n,
|
|
@@ -195,6 +205,22 @@ const COST = {
|
|
|
195
205
|
slotEntry: 350n,
|
|
196
206
|
openSlot: 150n,
|
|
197
207
|
};
|
|
208
|
+
/** The most draws one `open_box` takes in all, its count times the box's draws: what one answer could weigh, each draw
|
|
209
|
+
* at its weighing at least (`ROLL_BUDGET / COST_WEIGH`, 300). More is refused before any price is added up
|
|
210
|
+
* (`PackTooLarge`). */
|
|
211
|
+
export const MOST_DRAWS_A_PURCHASE = Number(ROLL_BUDGET / COST.weigh);
|
|
212
|
+
/** The compute one transaction has, and what adding up one sale's price costs at most (`catalog.rs` `COST_SALE`). */
|
|
213
|
+
const MAX_TX_CU = 1_400_000;
|
|
214
|
+
const COST_SALE = 55;
|
|
215
|
+
/** The most sales one `buy` takes (`count`): what one transaction can price (`MAX_TX_CU / COST_SALE`, rounded down:
|
|
216
|
+
* 25,454). More is refused before any price is added up (`PackTooLarge`). */
|
|
217
|
+
export const MOST_SALES_A_BUY = Math.floor(MAX_TX_CU / COST_SALE);
|
|
218
|
+
/**
|
|
219
|
+
* The tries counted for finding a pool's or a stash's address when the project is not known yet (a spec checked before
|
|
220
|
+
* any wallet is named): 2, what an address takes on average (half are found at the first try, a quarter at the second,
|
|
221
|
+
* and so on). With the project known, the count is exact: `256 - bump` tries an address.
|
|
222
|
+
*/
|
|
223
|
+
export const ASSUMED_TRIES = 2;
|
|
198
224
|
const BYTES = {
|
|
199
225
|
start: 6_000n,
|
|
200
226
|
prizeRead: 600n,
|
|
@@ -310,88 +336,152 @@ const accountOf = p => {
|
|
|
310
336
|
};
|
|
311
337
|
|
|
312
338
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
* `shares` and `fates` counted, and `bytes` (`boxBytes`) of heap reading the box takes beyond its entries. `slots` may
|
|
316
|
-
* be a number: that many draws among every entry. The answer's heap counts for a box that rolls, or one sold through
|
|
317
|
-
* the oracle (`oracle`: a box that doesn't roll but one `buy` can't carry, `buyFits`). The program refuses a box whose
|
|
318
|
-
* one open doesn't fit at its upsert, and a purchase that doesn't at `open_box`, where it also reads what the accounts
|
|
319
|
-
* hold (`PackTooLarge`).
|
|
339
|
+
* The tries finding a pool's (`pool` true) or a stash's address takes (`order::searched`: `256 - bump`), for `project`'s
|
|
340
|
+
* pool or stash `id`. With no project, `ASSUMED_TRIES`.
|
|
320
341
|
*/
|
|
321
|
-
export function
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
342
|
+
export function searchTries(project, pool, id) {
|
|
343
|
+
if (!project) return ASSUMED_TRIES;
|
|
344
|
+
const [, bump] = pool ? lootAddress(pdaSeeds.pool(project, id)) : lootAddress(pdaSeeds.stash(project, id));
|
|
345
|
+
return 256 - bump;
|
|
346
|
+
}
|
|
347
|
+
const pdaSeeds = {
|
|
348
|
+
pool: (project, index) => [utf8('pool'), addressBytes(project), uintLE(index, 2)],
|
|
349
|
+
stash: (project, id) => [utf8('stash'), addressBytes(project), uintLE(id, 4)],
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* What a roll of `entries` spends, as the program's `RollCost::new` counts it from the box alone (no table rows, a
|
|
354
|
+
* stash of one group, a named NFT's stash of one row): `fixed` (reading the box, every entry's accounts, finding each
|
|
355
|
+
* pool's and stash's address), each entry's draw (`prizes`), its stock read again (`reread`), the prizes it gives, its
|
|
356
|
+
* heap, and the heap reading the box leaves used (`start`, the box's own `bytes` included). `project`: whose pools and
|
|
357
|
+
* stashes these are, to count each search exactly; without it, at `ASSUMED_TRIES` tries an address.
|
|
358
|
+
*/
|
|
359
|
+
function rollCost(entries, { odds = 0, shares = 0, fates = 0, bytes = 0n, project = null }) {
|
|
332
360
|
const n = BigInt(entries.length);
|
|
361
|
+
// Each address is found by what finding it takes, once a prize names it (`draws_fit`'s `reads`): what a search cost is
|
|
362
|
+
// kept by the address's kind and id; once past the answer's budget nothing more is searched for (the box is refused).
|
|
363
|
+
const found = new Map();
|
|
364
|
+
let searched = 0n;
|
|
365
|
+
const reading = p => {
|
|
366
|
+
const pool = p.Tokens?.from?.Pool;
|
|
367
|
+
const stash = p.Stash ?? p.Nft?.stash;
|
|
368
|
+
if (pool == null && stash == null) return 0n;
|
|
369
|
+
const key = pool != null ? `pool ${pool[0] ?? pool}` : `stash ${stash[0] ?? stash}`;
|
|
370
|
+
let cost = found.get(key);
|
|
371
|
+
if (cost === undefined) {
|
|
372
|
+
if (searched > ROLL_BUDGET) cost = 0n;
|
|
373
|
+
else {
|
|
374
|
+
const id = Number(pool != null ? (pool[0] ?? pool) : (stash[0] ?? stash));
|
|
375
|
+
cost = COST.try * BigInt(searchTries(project, pool != null, id));
|
|
376
|
+
found.set(key, cost);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
searched += cost;
|
|
380
|
+
return cost;
|
|
381
|
+
};
|
|
382
|
+
let fixed = COST.fixed + COST.odds * BigInt(odds) + COST.share * BigInt(shares) + COST.fate * BigInt(fates);
|
|
383
|
+
let start = BYTES.start + BigInt(outgrown(entries.length, ENTRY_BYTES)) + BigInt(bytes);
|
|
384
|
+
const prizes = [];
|
|
385
|
+
const reread = [];
|
|
386
|
+
const heap = [];
|
|
387
|
+
for (const e of entries) {
|
|
388
|
+
let [set, again, used] = [0n, 0n, BYTES.draw];
|
|
389
|
+
fixed += e.prizes.length ? COST.entry : COST.blank;
|
|
390
|
+
start += BYTES.entryRead;
|
|
391
|
+
e.prizes.forEach((p, k) => {
|
|
392
|
+
fixed += reading(p) + (k > 0 ? COST.prize : 0n);
|
|
393
|
+
start += BYTES.prizeRead;
|
|
394
|
+
if (p.Item != null || p.Compressed) [set, again] = [set + COST.item, again + COST.reread];
|
|
395
|
+
else if (p.Tokens) [set, again] = [set + COST.tokens, again + (p.Tokens.from.Pool ? COST.reread : 0n)];
|
|
396
|
+
else if (p.Stash != null) [set, again] = [set + COST.draw + COST.group, again + COST.reread];
|
|
397
|
+
else if (p.Nft) {
|
|
398
|
+
// A named NFT is searched for row by row: when its stock is read, again, and when it is taken (one row here).
|
|
399
|
+
fixed += COST.row;
|
|
400
|
+
[set, again] = [set + COST.item + COST.row, again + COST.reread + COST.row];
|
|
401
|
+
} else set += COST.item;
|
|
402
|
+
used += p.Tokens ? (p.Tokens.from.Pool ? BYTES.pooled : BYTES.minted) : BYTES.prize;
|
|
403
|
+
});
|
|
404
|
+
// A draw that gives nothing still draws, and keeps its draw's lists.
|
|
405
|
+
prizes.push(e.prizes.length ? set : COST.nothing);
|
|
406
|
+
reread.push(again);
|
|
407
|
+
heap.push(used);
|
|
408
|
+
}
|
|
333
409
|
const weigh = COST.weigh + COST.weighEntry * n;
|
|
334
|
-
const reads = entries.map(e => e.prizes.map(accountOf));
|
|
335
|
-
const cost = entries.map(e =>
|
|
336
|
-
e.prizes.reduce(
|
|
337
|
-
(c, p) => {
|
|
338
|
-
if (p.Item != null || p.Compressed || p.Nft)
|
|
339
|
-
return { set: c.set + COST.item, again: c.again + COST.reread, bytes: c.bytes + BYTES.prize };
|
|
340
|
-
if (p.Tokens)
|
|
341
|
-
return {
|
|
342
|
-
set: c.set + COST.tokens,
|
|
343
|
-
again: c.again + (p.Tokens.from.Pool ? COST.reread : 0n),
|
|
344
|
-
bytes: c.bytes + (p.Tokens.from.Pool ? BYTES.pooled : BYTES.minted),
|
|
345
|
-
};
|
|
346
|
-
if (p.Stash != null)
|
|
347
|
-
return { set: c.set + COST.draw + COST.group, again: c.again + COST.reread, bytes: c.bytes + BYTES.prize };
|
|
348
|
-
return { set: c.set + COST.item, again: c.again, bytes: c.bytes + BYTES.prize };
|
|
349
|
-
},
|
|
350
|
-
{ set: 0n, again: 0n, bytes: BYTES.draw },
|
|
351
|
-
),
|
|
352
|
-
);
|
|
353
|
-
const next = entries.map(
|
|
354
|
-
(_, i) =>
|
|
355
|
-
weigh +
|
|
356
|
-
cost[i].set +
|
|
357
|
-
cost.reduce((sum, c, j) => (j === i || reads[i].some(a => a && reads[j].includes(a)) ? sum + c.again : sum), 0n),
|
|
358
|
-
);
|
|
359
410
|
const most = BigInt(Math.max(1, ...entries.map(e => e.prizes.length)));
|
|
411
|
+
return { fixed, start, weigh, prizes, reread, heap, knowing: COST.pair * n * n * most * most };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/** The entries whose stock a draw of entry `i` reads again: itself, and each naming an account it names. */
|
|
415
|
+
const sharing = (entries, i) => {
|
|
416
|
+
const reads = entries.map(e => e.prizes.map(accountOf));
|
|
417
|
+
return entries.map((_, j) => j).filter(j => j === i || reads[i].some(a => a && reads[j].includes(a)));
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Whether `opens` opens by `list` (slots, never empty) fit one answer (`RollCost::fits`): each draw at the dearest its
|
|
422
|
+
* slot could make, which entries share accounts found first when there are several draws. `next`: each entry's draw.
|
|
423
|
+
* `heap`: the answer's heap counts too.
|
|
424
|
+
*/
|
|
425
|
+
function fitsOne(cost, entries, list, opens, next, heap) {
|
|
426
|
+
const n = BigInt(entries.length);
|
|
427
|
+
const draws = list.reduce((d, s) => d + BigInt(s.count), 0n) * BigInt(opens);
|
|
360
428
|
let spent =
|
|
361
|
-
|
|
362
|
-
COST.
|
|
363
|
-
entries.reduce((c, e) => c + (e.prizes.length ? COST.entry : COST.blank), 0n);
|
|
364
|
-
let used =
|
|
365
|
-
BYTES.start +
|
|
366
|
-
BigInt(outgrown(entries.length, ENTRY_BYTES)) +
|
|
367
|
-
BigInt(bytes) +
|
|
368
|
-
BYTES.entryRead * n +
|
|
369
|
-
BYTES.prizeRead * BigInt(entries.reduce((c, e) => c + e.prizes.length, 0));
|
|
370
|
-
let won = 0n;
|
|
371
|
-
spent +=
|
|
372
|
-
(COST.slot + COST.slotEntry * n) * BigInt(Math.max(1, list.length)) +
|
|
429
|
+
cost.fixed +
|
|
430
|
+
(COST.slot + COST.slotEntry * n) * BigInt(list.length) +
|
|
373
431
|
COST.openSlot * BigInt(list.length) * BigInt(opens) +
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
432
|
+
(draws > 1n ? cost.knowing : 0n);
|
|
433
|
+
let used = cost.start;
|
|
434
|
+
let won = 0n;
|
|
377
435
|
for (const s of list) {
|
|
378
436
|
const count = BigInt(s.count) * BigInt(opens);
|
|
379
437
|
const among = entries.map((_, i) => i).filter(i => !(s.only ?? []).length || (s.only ?? []).includes(i));
|
|
380
438
|
const dearest = f => among.reduce((m, i) => (f(i) > m ? f(i) : m), 0n);
|
|
381
439
|
spent += count * dearest(i => next[i]);
|
|
382
|
-
used += count * dearest(i => cost[i]
|
|
440
|
+
used += count * dearest(i => cost.heap[i]);
|
|
383
441
|
won += count * dearest(i => BigInt(entries[i].prizes.length));
|
|
384
442
|
}
|
|
385
|
-
const rolls = oracle || entries.length > 1 || entries.some(e => e.prizes.some(p => p.Stash != null));
|
|
386
443
|
return (
|
|
387
444
|
spent + COST.finish + COST.won * won + COST.drawn * draws <= ROLL_BUDGET &&
|
|
388
|
-
(!
|
|
445
|
+
(!heap || used + BYTES.finish + BYTES.won * won + draws <= ROLL_HEAP)
|
|
389
446
|
);
|
|
390
447
|
}
|
|
391
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Whether all of `opens` opens of `entries` by `slots` fit one oracle answer (`order::draws_fit`), each draw at the
|
|
451
|
+
* dearest its slot could make, with what is known from the box alone (no table rows, a stash of one group, a named
|
|
452
|
+
* NFT's stash of one row), its `odds`, `shares` and `fates` counted, and `bytes` (`boxBytes`) of heap reading the box
|
|
453
|
+
* takes beyond its entries. `slots` may be a number: that many draws among every entry. The answer's heap counts for a
|
|
454
|
+
* box that rolls, or one sold through the oracle (`oracle`: a box that doesn't roll but one `buy` can't carry,
|
|
455
|
+
* `buyFits`). `project`: the project the box is in, whose pools' and stashes' addresses the write searches for (each
|
|
456
|
+
* search counted at `256 - bump` tries of 1,500 units); without it, at `ASSUMED_TRIES` tries an address. The program
|
|
457
|
+
* refuses a box whose one open doesn't fit at its upsert (`PackTooLarge`). `purchase`: counted as `open_box` counts a
|
|
458
|
+
* purchase of `opens` instead (the answer's heap always; a draw's own stock read again only when there are several; at
|
|
459
|
+
* most `MOST_DRAWS_A_PURCHASE` draws in all), from what the box alone tells: the program reads the accounts too.
|
|
460
|
+
*/
|
|
461
|
+
export function drawsFit(
|
|
462
|
+
entries,
|
|
463
|
+
slots,
|
|
464
|
+
opens = 1,
|
|
465
|
+
{ odds = 0, shares = 0, fates = 0, bytes = 0n, oracle = false, project = null, purchase = false } = {},
|
|
466
|
+
) {
|
|
467
|
+
const given = typeof slots === 'number' || typeof slots === 'bigint' ? [{ count: slots, only: [] }] : (slots ?? []);
|
|
468
|
+
// A box with no slots draws once among every entry.
|
|
469
|
+
const list = given.length ? given : [{ count: 1, only: [] }];
|
|
470
|
+
const draws = list.reduce((d, s) => d + BigInt(s.count), 0n) * BigInt(opens);
|
|
471
|
+
if (purchase && draws > BigInt(MOST_DRAWS_A_PURCHASE)) return false;
|
|
472
|
+
const cost = rollCost(entries, { odds, shares, fates, bytes, project });
|
|
473
|
+
const heap = purchase || oracle || entries.length > 1 || entries.some(e => e.prizes.some(p => p.Stash != null));
|
|
474
|
+
// Reading the box alone already past the answer: refused before its entries are compared.
|
|
475
|
+
if (cost.fixed > ROLL_BUDGET || (heap && cost.start > ROLL_HEAP)) return false;
|
|
476
|
+
// Each entry's draw reads again the stock of the entries sharing its accounts; a purchase of one draw, its own alone.
|
|
477
|
+
const again = i => (purchase && draws <= 1n ? [] : sharing(entries, i)).reduce((s, j) => s + cost.reread[j], 0n);
|
|
478
|
+
const next = entries.map((_, i) => cost.weigh + cost.prizes[i] + again(i));
|
|
479
|
+
return fitsOne(cost, entries, list, opens, next, heap);
|
|
480
|
+
}
|
|
481
|
+
|
|
392
482
|
// What one `buy` takes, as the program counts it at the box's write (`catalog::buy_fits`): a transaction holds 64
|
|
393
483
|
// accounts, 64 instructions and 1,400,000 compute units.
|
|
394
|
-
const BUY = { accounts: 15, calls: 7, cu: 30_000, most: { accounts: 64, calls: 64, cu:
|
|
484
|
+
const BUY = { accounts: 15, calls: 7, cu: 30_000, most: { accounts: 64, calls: 64, cu: MAX_TX_CU } };
|
|
395
485
|
const sameJson = (a, b) =>
|
|
396
486
|
JSON.stringify(a, (_, v) => (typeof v === 'bigint' ? String(v) : v)) ===
|
|
397
487
|
JSON.stringify(b, (_, v) => (typeof v === 'bigint' ? String(v) : v));
|
|
@@ -444,6 +534,28 @@ export function buyFits(box, draws = box.slots?.length ? box.slots.reduce((n, s)
|
|
|
444
534
|
return accounts <= BUY.most.accounts && calls <= BUY.most.calls && cu <= BUY.most.cu;
|
|
445
535
|
}
|
|
446
536
|
|
|
537
|
+
/**
|
|
538
|
+
* Whether the program writes `box` (its args, as `upsertBoxIx` takes them, or a box read from the chain) as far as what
|
|
539
|
+
* it costs goes (`catalog::loot_box`, `PackTooLarge`): a box that doesn't roll sells by `buy` when one transaction carries
|
|
540
|
+
* the whole purchase (`buyFits`), else through the oracle, whose heap then counts and which can't give a roll; and every
|
|
541
|
+
* draw of one open fits one answer (`drawsFit`). `project`: whose pools and stashes the box names (a box read from the
|
|
542
|
+
* chain says); without it, each search at `ASSUMED_TRIES` tries.
|
|
543
|
+
*/
|
|
544
|
+
export function boxFits(box, { project = box.project ?? null } = {}) {
|
|
545
|
+
const entries = box.entries ?? [];
|
|
546
|
+
const rolls = entries.length > 1 || entries.some(e => e.prizes.some(p => p.Stash != null));
|
|
547
|
+
const byBuy = rolls || box.subject != null || buyFits(box);
|
|
548
|
+
if (!byBuy && entries.some(e => e.prizes.some(p => p.Roll != null))) return false;
|
|
549
|
+
return drawsFit(entries, box.slots?.length ? box.slots : 1, 1, {
|
|
550
|
+
odds: (box.odds ?? []).length,
|
|
551
|
+
shares: (box.shares ?? []).length,
|
|
552
|
+
fates: (box.fates ?? []).length,
|
|
553
|
+
bytes: boxBytes(box),
|
|
554
|
+
oracle: !byBuy,
|
|
555
|
+
project,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
447
559
|
/** The most program rent and fee one order takes from its player, in lamports (0.01 SOL): a client cap (`opensAnOrder`). */
|
|
448
560
|
export const ORDER_COST_CAP = 10_000_000n;
|
|
449
561
|
/** Rent a byte, as devnet and mainnet-beta charge it ((bytes + 128) × 5,080 lamports). */
|
|
@@ -452,7 +564,8 @@ const RENT_PER_BYTE = 5_080n;
|
|
|
452
564
|
const PLAYER_FEE = 2_000_000n;
|
|
453
565
|
|
|
454
566
|
/**
|
|
455
|
-
* The most opens of `lootBox` a client puts in one order: what one oracle answer draws (`drawsFit`
|
|
567
|
+
* The most opens of `lootBox` a client puts in one order: what one oracle answer draws (`drawsFit` as a purchase counts
|
|
568
|
+
* it, the box's project's searches counted when the box names it: a box read from the chain does), and what keeps the
|
|
456
569
|
* order's rent and the player's fee within `ORDER_COST_CAP` (`orderBytes` with `items`, so with the box's shares). At
|
|
457
570
|
* least one: a box whose one open is over the cap is still bought one open an order. A purchase of more is several
|
|
458
571
|
* orders, each its own answer.
|
|
@@ -467,10 +580,11 @@ export function opensAnOrder(
|
|
|
467
580
|
shares,
|
|
468
581
|
fates: (lootBox.fates ?? []).length,
|
|
469
582
|
bytes: boxBytes(lootBox),
|
|
470
|
-
|
|
583
|
+
project: lootBox.project ?? null,
|
|
584
|
+
purchase: true,
|
|
471
585
|
}) && (BigInt(orderBytes(lootBox, shares, n, items)) + 128n) * rentPerByte + PLAYER_FEE <= ORDER_COST_CAP;
|
|
472
586
|
let n = 1;
|
|
473
|
-
while (n <
|
|
587
|
+
while (n < MOST_DRAWS_A_PURCHASE && fits(n + 1)) n += 1;
|
|
474
588
|
return n;
|
|
475
589
|
}
|
|
476
590
|
|
package/js/economy/check.mjs
CHANGED
|
@@ -9,7 +9,7 @@ import { gateList } from '../client/grant.js';
|
|
|
9
9
|
import { MAX_BID_NFTS, MAX_SHARES } from '../client/ix.js';
|
|
10
10
|
import { MAX_POTS } from '../client/accounts.js';
|
|
11
11
|
import { vault } from '../client/pda.js';
|
|
12
|
-
import { boxBytes, buyFits, drawsFit, MAX_ORDER_BYTES, tableBudget } from '../client/sizes.js';
|
|
12
|
+
import { ASSUMED_TRIES, boxBytes, buyFits, drawsFit, MAX_ORDER_BYTES, tableBudget } from '../client/sizes.js';
|
|
13
13
|
import { TOKEN_2022_PROGRAM, TOKEN_PROGRAM } from '../chain/pda.js';
|
|
14
14
|
import { addressBytes, encodeBase58 } from '../chain/base58.js';
|
|
15
15
|
import { isOnCurve } from '../chain/curve.js';
|
|
@@ -59,7 +59,7 @@ import {
|
|
|
59
59
|
weights,
|
|
60
60
|
} from './model.mjs';
|
|
61
61
|
// plan.mjs imports check() in turn: each module only calls the other's functions, never at load time.
|
|
62
|
-
import { boxArgs, boxOrderBytes, placeholderContext, recipeArgs, setupSteps,
|
|
62
|
+
import { boxArgs, boxOrderBytes, placeholderContext, recipeArgs, setupSteps, stepAloneBytes } from './plan.mjs';
|
|
63
63
|
import { findSecrets } from './secrets.mjs';
|
|
64
64
|
import { simulate } from './simulate.mjs';
|
|
65
65
|
import { BUDGET, MAX_TRANSACTION_BYTES, transactionLocks, transactionSize, WALLET_ROOM } from './txsize.mjs';
|
|
@@ -685,10 +685,16 @@ export function dateWords(seconds) {
|
|
|
685
685
|
}
|
|
686
686
|
|
|
687
687
|
/**
|
|
688
|
-
* Checks an economy spec. `now` (Unix seconds) dates "publish".
|
|
688
|
+
* Checks an economy spec. `now` (Unix seconds) dates "publish". `project`: the project's address, once a wallet is named
|
|
689
|
+
* (the plan for a real setup passes it; a live economy's file names it): a box's write searches for each of its pools'
|
|
690
|
+
* and stashes' addresses, and the checker counts each search exactly with it, else at an assumed 2 tries an address
|
|
691
|
+
* (`ASSUMED_TRIES`).
|
|
689
692
|
* Returns { ok, refusals: [{ code, where, message, fix }], warnings: [{ code, where, message, fix? }] }.
|
|
690
693
|
*/
|
|
691
|
-
export function check(
|
|
694
|
+
export function check(
|
|
695
|
+
economy,
|
|
696
|
+
{ network = 'devnet', now = Math.floor(Date.now() / 1000), project = economy?.live?.project ?? null } = {},
|
|
697
|
+
) {
|
|
692
698
|
const refusals = [];
|
|
693
699
|
const warnings = [];
|
|
694
700
|
const refuse = (code, where, message, fix) => refusals.push({ code, where, message, fix });
|
|
@@ -2398,7 +2404,9 @@ export function check(economy, { network = 'devnet', now = Math.floor(Date.now()
|
|
|
2398
2404
|
// rolls, or one outcome of items, with no outcome that burns the payment and no NFT to roll on (BadEntries).
|
|
2399
2405
|
const slots = b.slots ?? [];
|
|
2400
2406
|
const outs = b.outcomes ?? [];
|
|
2407
|
+
const heldBefore = refusals.length;
|
|
2401
2408
|
answerHolds(b, where);
|
|
2409
|
+
const answerRefused = refusals.length > heldBefore;
|
|
2402
2410
|
for (const [i, o] of outs.entries()) {
|
|
2403
2411
|
const given = outcomeParts(o);
|
|
2404
2412
|
tooMany(
|
|
@@ -2555,8 +2563,10 @@ export function check(economy, { network = 'devnet', now = Math.floor(Date.now()
|
|
|
2555
2563
|
// Every draw of one open fits one answer of the oracle's (PackTooLarge): a pack past it could only draw its first
|
|
2556
2564
|
// cards, so the program refuses the box. A box that doesn't roll sells by `buy`, the whole purchase in one
|
|
2557
2565
|
// transaction, or through the oracle when one `buy` can't carry it (`buyFits`): then the answer's heap counts, and it
|
|
2558
|
-
// can't give a roll, which only `buy` gives (catalog.rs). The heap counts the box's own bytes too (`boxBytes`)
|
|
2559
|
-
|
|
2566
|
+
// can't give a roll, which only `buy` gives (catalog.rs). The heap counts the box's own bytes too (`boxBytes`), and
|
|
2567
|
+
// the compute finding each pool's and stash's address its prizes name (exact with the project, else assumed).
|
|
2568
|
+
// A box of one draw is held to the same count: past it the program refuses the box when it is written.
|
|
2569
|
+
if (outs.length && (cards > 1 || !answerRefused)) {
|
|
2560
2570
|
const args = (() => {
|
|
2561
2571
|
try {
|
|
2562
2572
|
return boxArgs(b, m);
|
|
@@ -2566,38 +2576,45 @@ export function check(economy, { network = 'devnet', now = Math.floor(Date.now()
|
|
|
2566
2576
|
})();
|
|
2567
2577
|
const entries = args?.entries;
|
|
2568
2578
|
if (entries) {
|
|
2569
|
-
const byBuy = boxRolls(b, m) || Boolean(b.rollsOn) || buyFits(args, cards);
|
|
2579
|
+
const byBuy = boxRolls(b, m) || Boolean(b.rollsOn) || buyFits(args, Math.max(1, cards));
|
|
2570
2580
|
const extras = {
|
|
2571
2581
|
odds: args.odds.length,
|
|
2572
2582
|
shares: args.shares.length,
|
|
2573
2583
|
fates: args.fates.length,
|
|
2574
2584
|
bytes: boxBytes(args),
|
|
2575
2585
|
oracle: !byBuy,
|
|
2586
|
+
project,
|
|
2576
2587
|
};
|
|
2588
|
+
const searches = entries.some(e => e.prizes.some(p => p.Tokens?.from?.Pool || p.Stash != null || p.Nft));
|
|
2589
|
+
const searched = searches
|
|
2590
|
+
? project
|
|
2591
|
+
? ' (finding the address of each pool and stash it names counted for this project)'
|
|
2592
|
+
: ` (finding the address of each pool and stash it names counted at ${ASSUMED_TRIES} tries each, as no wallet is named yet: the check with your wallet counts it exactly)`
|
|
2593
|
+
: '';
|
|
2594
|
+
const drawn = slots.length ? slots.map(s => ({ count: s.count, only: s.from ?? [] })) : 1;
|
|
2577
2595
|
if (!byBuy && entries.some(e => e.prizes.some(p => p.Roll != null)))
|
|
2578
2596
|
refuse(
|
|
2579
2597
|
'E-ODDS',
|
|
2580
2598
|
`${where}.slots`,
|
|
2581
|
-
`${label(b)} gives a roll, which only a sale in one step gives, but ${cards} of what it gives don’t fit one transaction (its accounts, its instructions or its compute), so the program refuses the box (PackTooLarge).`,
|
|
2599
|
+
`${label(b)} gives a roll, which only a sale in one step gives, but ${Math.max(1, cards)} of what it gives don’t fit one transaction (its accounts, its instructions or its compute), so the program refuses the box (PackTooLarge).`,
|
|
2582
2600
|
'Fewer cards an open, fewer prizes in its outcome, or fewer shares in its split.',
|
|
2583
2601
|
);
|
|
2584
|
-
else if (
|
|
2585
|
-
!drawsFit(
|
|
2586
|
-
entries,
|
|
2587
|
-
slots.map(s => ({ count: s.count, only: s.from ?? [] })),
|
|
2588
|
-
1,
|
|
2589
|
-
extras,
|
|
2590
|
-
)
|
|
2591
|
-
) {
|
|
2602
|
+
else if (cards > 1 && !drawsFit(entries, drawn, 1, extras)) {
|
|
2592
2603
|
let most = cards - 1;
|
|
2593
2604
|
while (most > 1 && !drawsFit(entries, most, 1, extras)) most -= 1;
|
|
2594
2605
|
refuse(
|
|
2595
2606
|
'E-ODDS',
|
|
2596
2607
|
`${where}.slots`,
|
|
2597
|
-
`${label(b)} draws ${cards} cards an open among ${outs.length} outcome${outs.length === 1 ? '' : 's'}: more than one answer of the oracle’s can draw (about ${most} here), so the program refuses the box (PackTooLarge).`,
|
|
2608
|
+
`${label(b)} draws ${cards} cards an open among ${outs.length} outcome${outs.length === 1 ? '' : 's'}: more than one answer of the oracle’s can draw (about ${most} here${searched}), so the program refuses the box (PackTooLarge).`,
|
|
2598
2609
|
'Fewer cards an open (sell two packs instead of one), fewer outcomes, fewer prizes in an outcome, or a smaller box (fewer odds, shares, slots or outcomes that act on the NFT).',
|
|
2599
2610
|
);
|
|
2600
|
-
}
|
|
2611
|
+
} else if (cards <= 1 && !drawsFit(entries, drawn, 1, extras))
|
|
2612
|
+
refuse(
|
|
2613
|
+
'E-ODDS',
|
|
2614
|
+
`${where}.outcomes`,
|
|
2615
|
+
`${label(b)} is too long for one answer of the oracle’s to draw it once${searched}, so the program refuses the box when it is written (PackTooLarge).`,
|
|
2616
|
+
'Fewer outcomes, fewer prizes in an outcome, fewer pools or stashes named, or a smaller box (fewer odds, shares or slots). Put a long weighted list in a table or a stash, drawn by one outcome.',
|
|
2617
|
+
);
|
|
2601
2618
|
}
|
|
2602
2619
|
}
|
|
2603
2620
|
if (cards > 1 && b.rollsOn)
|
|
@@ -5700,7 +5717,7 @@ export function check(economy, { network = 'devnet', now = Math.floor(Date.now()
|
|
|
5700
5717
|
for (const step of setup?.steps ?? []) {
|
|
5701
5718
|
let bytes;
|
|
5702
5719
|
try {
|
|
5703
|
-
bytes =
|
|
5720
|
+
bytes = stepAloneBytes(step, ctx);
|
|
5704
5721
|
} catch {
|
|
5705
5722
|
continue;
|
|
5706
5723
|
}
|
package/js/economy/guide.mjs
CHANGED
|
@@ -39,7 +39,7 @@ export const PARTS = {
|
|
|
39
39
|
boxes: {
|
|
40
40
|
def: 'box',
|
|
41
41
|
nested: ['outcome', 'odds', 'gate', 'rule', 'effect', 'rise', 'fall', 'split', 'perWalletEach'],
|
|
42
|
-
says: 'A paid (or free) roll on chain with MagicBlock’s verifiable randomness, optionally on an NFT (`rollsOn`: an enchant, a mission’s result, a risky forge), whose outcomes then move its numbers (`shifts`) and send it back, burn it or give it away (`then`; a box that can burn or give it away holds it until the roll settles): each outcome gives an item, a fixed prize from a pool, tokens minted (`tokens`), an NFT of a stash (picked at random, or a named one: `nft`), a roll of another box (`roll`), several of these together (`bundle`: { "gives": "bundle", "prizes": [...] }, a pack; it gives all it names or isn’t given, so it names a pool once, a stash’s random NFT beside nothing else of that stash, and two of an item need two left), or nothing. A box of one outcome with nothing random and no NFT it rolls on is bought in one step (`buy`): the price split and what it gives handed over at once, with no order and no oracle. A box takes an NFT as its price through `rollsOn` (which NFTs) and `then` (where the one paid goes: burned, given, or into a stash, a trade-in that becomes another player’s prize). A box is also the prize any route gives: a grant (`gives: { "roll": box }`), a lottery’s place (`{ "percent", "roll" }`), a contest’s winners (`roll`), an auction’s lot (`{ "roll": box }`), a mission’s tier and a recipe roll it for a winner, who gets whatever it gives; such a box is left `active: false`. How long a box can be is what one answer of the oracle’s holds (measured: 128 outcomes that give nothing, 29 of token amounts, 18 each of its own item; 41 slots; 72 odds rules; 16 token prizes or 18 collectibles in one outcome; 340 texts in a gate): a longer weighted list goes in a table or a stash, drawn by one outcome. A roll is on its written odds whatever is in stock, and `whenOut` says what a draw on an outcome that is out does: "stopSales" (the default) stops selling while one couldn’t be covered, a paid miss gives that draw’s worth back at the settle (with no draw made a paid open fails and is refunded), and a free roll goes back to waiting and is asked again until it hits; "drawLeft" draws among what is left; "giveNothing" keeps selling and a miss gives nothing, paid or free. Odds rules (`odds`) add any percent (99,900 makes an outcome a thousand times likelier; a weight stops at the largest, 4,294,967,295) and count on every route, read when a roll is first asked. A sale in one step sells only while its pools hold every fixed prize. A prize won is set aside for its winner at once. An edit of a box’s roll fails only a purchase still waiting (for its refund); any other roll waiting rolls on the box as edited. A gate (a collectible shown, a few wallets, or a list of any size: `{ "list": [address, …] }`, an allowlist whose proof each player passes) and odds can read what a player shows; `opens`/`closes` time a drop; `perWallet`/`perWalletEach` limit each wallet (one ticket per wallet). A split share "chosen" lets the buyer name a referrer; "chosen:<item>" the holder of a collectible they name (plot rent).',
|
|
42
|
+
says: 'A paid (or free) roll on chain with MagicBlock’s verifiable randomness, optionally on an NFT (`rollsOn`: an enchant, a mission’s result, a risky forge), whose outcomes then move its numbers (`shifts`) and send it back, burn it or give it away (`then`; a box that can burn or give it away holds it until the roll settles): each outcome gives an item, a fixed prize from a pool, tokens minted (`tokens`), an NFT of a stash (picked at random, or a named one: `nft`), a roll of another box (`roll`), several of these together (`bundle`: { "gives": "bundle", "prizes": [...] }, a pack; it gives all it names or isn’t given, so it names a pool once, a stash’s random NFT beside nothing else of that stash, and two of an item need two left), or nothing. A box of one outcome with nothing random and no NFT it rolls on is bought in one step (`buy`): the price split and what it gives handed over at once, with no order and no oracle. A box takes an NFT as its price through `rollsOn` (which NFTs) and `then` (where the one paid goes: burned, given, or into a stash, a trade-in that becomes another player’s prize). A box is also the prize any route gives: a grant (`gives: { "roll": box }`), a lottery’s place (`{ "percent", "roll" }`), a contest’s winners (`roll`), an auction’s lot (`{ "roll": box }`), a mission’s tier and a recipe roll it for a winner, who gets whatever it gives; such a box is left `active: false`. How long a box can be is what one answer of the oracle’s holds (measured: 128 outcomes that give nothing, 29 of token amounts, 18 each of its own item; 41 slots; 72 odds rules; 16 token prizes or 18 collectibles in one outcome; 340 texts in a gate; fewer where its outcomes name pools or stashes, each search for one’s address counted: sixteen stashes fit 9 draws an open where finding them takes 36 tries): a longer weighted list goes in a table or a stash, drawn by one outcome. A roll is on its written odds whatever is in stock, and `whenOut` says what a draw on an outcome that is out does: "stopSales" (the default) stops selling while one couldn’t be covered, a paid miss gives that draw’s worth back at the settle (with no draw made a paid open fails and is refunded), and a free roll goes back to waiting and is asked again until it hits; "drawLeft" draws among what is left; "giveNothing" keeps selling and a miss gives nothing, paid or free. Odds rules (`odds`) add any percent (99,900 makes an outcome a thousand times likelier; a weight stops at the largest, 4,294,967,295) and count on every route, read when a roll is first asked. A sale in one step sells only while its pools hold every fixed prize. A prize won is set aside for its winner at once. An edit of a box’s roll fails only a purchase still waiting (for its refund); any other roll waiting rolls on the box as edited. A gate (a collectible shown, a few wallets, or a list of any size: `{ "list": [address, …] }`, an allowlist whose proof each player passes) and odds can read what a player shows; `opens`/`closes` time a drop; `perWallet`/`perWalletEach` limit each wallet (one ticket per wallet). A split share "chosen" lets the buyer name a referrer; "chosen:<item>" the holder of a collectible they name (plot rent).',
|
|
43
43
|
acts: `the player opens (${sol(FEES.player)} SOL + ${sol(ORACLE_FEE)} SOL to the oracle); its player, a key they named or the project’s keys settle. Chance that pays money is flagged`,
|
|
44
44
|
},
|
|
45
45
|
payments: {
|
package/js/economy/plan.mjs
CHANGED
|
@@ -21,7 +21,12 @@ import {
|
|
|
21
21
|
} from '../chain/pda.js';
|
|
22
22
|
import { createTableIx, extendTableIx } from '../chain/lookup.js';
|
|
23
23
|
import { MEMO_PROGRAM } from '../chain/token2022.js';
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
computeUnitLimit,
|
|
26
|
+
createAssociatedTokenAccountIdempotent,
|
|
27
|
+
syncNative,
|
|
28
|
+
transferLamports,
|
|
29
|
+
} from '../chain/programs.js';
|
|
25
30
|
import {
|
|
26
31
|
createAccountIx,
|
|
27
32
|
initializeMint2Ix,
|
|
@@ -34,7 +39,7 @@ import { compileMessage, transactionSize } from '../chain/tx.js';
|
|
|
34
39
|
import { sha256 } from '../chain/sha256.js';
|
|
35
40
|
import { utf8 } from '../chain/bytes.js';
|
|
36
41
|
import * as loot from '../client/index.js';
|
|
37
|
-
import { check } from './check.mjs';
|
|
42
|
+
import { check, MAX_COMPUTE_UNITS } from './check.mjs';
|
|
38
43
|
import {
|
|
39
44
|
ACCOUNT_OVERHEAD,
|
|
40
45
|
boxBuys,
|
|
@@ -944,6 +949,10 @@ export function setupSteps(economy, { now = 0, only = null, live: given = null }
|
|
|
944
949
|
id: `${part}:${kind}`,
|
|
945
950
|
part,
|
|
946
951
|
says,
|
|
952
|
+
// A box's write finds each of its pools' and stashes' addresses (1,500 units a try) and compares its entries
|
|
953
|
+
// pair by pair: a box of 16 stashes takes some 170,000 units, more where its addresses take more tries, past
|
|
954
|
+
// the 200,000 an instruction has by default. Its transaction asks for all a transaction can have.
|
|
955
|
+
compute: Boolean(def.Box),
|
|
947
956
|
creates: [{ what, bytes }, ...pot],
|
|
948
957
|
build: ctx => [
|
|
949
958
|
loot.upsertIx({ owner: ctx.owner, project: project(ctx), def: resolve(def, ctx), ...options(ctx) }),
|
|
@@ -1785,7 +1794,18 @@ export function setupContext(m, { owner: ownerKey, keys, live: given = {}, rent
|
|
|
1785
1794
|
export const stepBytes = (instructions, ctx) =>
|
|
1786
1795
|
transactionSize(compileMessage({ payer: ctx.owner, blockhash: placeholderKey('blockhash'), instructions }));
|
|
1787
1796
|
|
|
1788
|
-
/**
|
|
1797
|
+
/** `instructions`, led by a compute limit of all a transaction can have when `compute` (a box's write is in them). */
|
|
1798
|
+
const budgeted = (instructions, compute) =>
|
|
1799
|
+
compute ? [computeUnitLimit(MAX_COMPUTE_UNITS), ...instructions] : instructions;
|
|
1800
|
+
|
|
1801
|
+
/** The bytes of a transaction of `step` alone, as `pack` sends it (its compute limit included). */
|
|
1802
|
+
export const stepAloneBytes = (step, ctx) => stepBytes(budgeted(step.build(ctx), Boolean(step.compute)), ctx);
|
|
1803
|
+
|
|
1804
|
+
/**
|
|
1805
|
+
* Packs steps into transactions under the size budget, one signer set at a time; each step whole. A transaction that
|
|
1806
|
+
* carries a step needing more compute than an instruction has by default (`compute`: a box's write) asks for all a
|
|
1807
|
+
* transaction can have, first.
|
|
1808
|
+
*/
|
|
1789
1809
|
export function pack(steps, ctx) {
|
|
1790
1810
|
const transactions = [];
|
|
1791
1811
|
let current = null;
|
|
@@ -1795,25 +1815,28 @@ export function pack(steps, ctx) {
|
|
|
1795
1815
|
// A step with nothing to send (the lookup table of a plan built with no recent slot) is left out.
|
|
1796
1816
|
if (!instructions.length) continue;
|
|
1797
1817
|
const signerKind = step.signers.includes('authority') ? 'authority' : 'owner';
|
|
1798
|
-
// A write that names boxes to roll takes all the compute a transaction has: alone.
|
|
1799
1818
|
if (current && current.signer === signerKind && !step.alone && !current.alone) {
|
|
1800
|
-
const
|
|
1819
|
+
const compute = current.compute || Boolean(step.compute);
|
|
1820
|
+
const next = budgeted([...current.built.flat(), ...instructions], compute);
|
|
1801
1821
|
if (sizeOf(next) <= BUDGET) {
|
|
1802
1822
|
current.instructions = next;
|
|
1823
|
+
current.compute = compute;
|
|
1803
1824
|
current.steps.push(step);
|
|
1804
1825
|
current.built.push(instructions);
|
|
1805
1826
|
current.signers = [...new Set([...current.signers, ...step.signers])];
|
|
1806
1827
|
continue;
|
|
1807
1828
|
}
|
|
1808
1829
|
}
|
|
1809
|
-
const
|
|
1830
|
+
const alone = budgeted(instructions, Boolean(step.compute));
|
|
1831
|
+
const size = sizeOf(alone);
|
|
1810
1832
|
if (size > BUDGET) throw new Error(`The step “${step.says}” is ${size} bytes: too big for one transaction.`);
|
|
1811
1833
|
// `built`: each step's own instructions, in the order of `steps`.
|
|
1812
1834
|
current = {
|
|
1813
1835
|
signer: signerKind,
|
|
1814
1836
|
steps: [step],
|
|
1815
1837
|
built: [instructions],
|
|
1816
|
-
instructions,
|
|
1838
|
+
instructions: alone,
|
|
1839
|
+
compute: Boolean(step.compute),
|
|
1817
1840
|
signers: [...step.signers],
|
|
1818
1841
|
alone: Boolean(step.alone),
|
|
1819
1842
|
};
|
|
@@ -1828,7 +1851,8 @@ export function pack(steps, ctx) {
|
|
|
1828
1851
|
* real instructions; without it they are built on stand-in addresses (for sizing, cost and showing).
|
|
1829
1852
|
*/
|
|
1830
1853
|
export function plan(economy, { now = Math.floor(Date.now() / 1000), ctx = null, only = null, live = null } = {}) {
|
|
1831
|
-
|
|
1854
|
+
// With a real context the project is known: a box's write is checked with its own address searches counted.
|
|
1855
|
+
const checked = check(economy, { now, ...(ctx?.project ? { project: ctx.project } : {}) });
|
|
1832
1856
|
if (!checked.ok) return { ok: false, refusals: checked.refusals, warnings: checked.warnings };
|
|
1833
1857
|
const { steps, model: m } = setupSteps(economy, { now, only, live });
|
|
1834
1858
|
if (ctx)
|