@typesol/cli 0.16.0 → 0.18.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typesol/cli",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Compile a TypeScript-syntax language directly to Solana SBF programs.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,10 +30,12 @@
30
30
  #define TS_ERR_TOKEN_OWNER 2015 /* ConstraintTokenOwner */
31
31
  #define TS_ERR_MINT_AUTHORITY 2016 /* ConstraintMintMintAuthority */
32
32
  #define TS_ERR_FREEZE_AUTHORITY 2017 /* ConstraintMintFreezeAuthority */
33
+ #define TS_ERR_MINT_DECIMALS 2018 /* ConstraintMintDecimals */
33
34
  #define TS_ERR_DUPLICATE_MUT 2040 /* ConstraintDuplicateMutableAccount */
34
35
  #define TS_ERR_ALREADY_INITIALIZED 3000 /* AccountDiscriminatorAlreadySet */
35
36
  #define TS_ERR_DISCRIMINATOR 3002 /* AccountDiscriminatorMismatch */
36
37
  #define TS_ERR_ACCOUNT_TOO_SMALL 3003 /* AccountDidNotDeserialize */
38
+ #define TS_ERR_DID_NOT_SERIALIZE 3004 /* AccountDidNotSerialize */
37
39
  #define TS_ERR_TOKEN_ACCOUNT 3003 /* AccountDidNotDeserialize (not an initialized token account) */
38
40
  #define TS_ERR_MINT 3003 /* AccountDidNotDeserialize (not an initialized mint) */
39
41
  #define TS_ERR_NOT_ENOUGH_ACCOUNTS 3005 /* AccountNotEnoughKeys */
@@ -41,6 +43,7 @@
41
43
  #define TS_ERR_WRONG_PROGRAM 3008 /* InvalidProgramId */
42
44
  #define TS_ERR_NOT_EXECUTABLE 3009 /* InvalidProgramExecutable */
43
45
  #define TS_ERR_NOT_SIGNER 3010 /* AccountNotSigner */
46
+ #define TS_ERR_NOT_SYSTEM_OWNED 3011 /* AccountNotSystemOwned */
44
47
  #define TS_ERR_NOT_INITIALIZED 3012 /* AccountNotInitialized */
45
48
  #define TS_ERR_REALLOC 3016 /* AccountReallocExceedsLimit */
46
49
  #define TS_ERR_CAST 4102 /* InvalidNumericConversion */
@@ -162,8 +165,11 @@ static inline TS_Str ts_str(const uint8_t *ptr, uint64_t len) {
162
165
  static inline bool ts_utf8_valid(const uint8_t *p, uint64_t n) {
163
166
  uint64_t i = 0;
164
167
  while (i < n) {
168
+ /* ASCII eight bytes at a time (no high bit set), then byte by byte up to the next multi-byte sequence. */
169
+ while (i + 8 <= n && (ts_ld64(p + i) & 0x8080808080808080ULL) == 0) i += 8;
170
+ while (i < n && p[i] < 0x80) i++;
171
+ if (i == n) break;
165
172
  uint8_t c = p[i];
166
- if (c < 0x80) { i++; continue; }
167
173
  uint64_t len;
168
174
  uint32_t cp;
169
175
  if (c >= 0xc2 && c <= 0xdf) { len = 2; cp = c & 0x1f; }
@@ -630,6 +636,129 @@ static inline uint64_t ts_create_pda(TsParams *params, TsAccount *payer, TsAccou
630
636
  return SUCCESS;
631
637
  }
632
638
 
639
+ /*
640
+ * Create `acct` with `space` bytes owned by `owner` (a token program), funded by `payer`: a PDA of this program when
641
+ * `n` > 0 (seeds without the bump; a pre-funded address is topped up, allocated and assigned), otherwise a new keypair
642
+ * account that signed the transaction. Writes the bump (0 for a keypair).
643
+ */
644
+ static inline uint64_t ts_create_owned(TsParams *params, TsAccount *payer, TsAccount *acct, SolSignerSeed *seeds, int n,
645
+ const SolPubkey *owner, uint64_t space, uint8_t *bump_out) {
646
+ uint8_t bump = 0;
647
+ if (n > 0) {
648
+ SolPubkey derived;
649
+ if (ts_find_pda(seeds, n, params->program_id, &derived, &bump) != SUCCESS || !ts_key_eq(&derived, &acct->key))
650
+ TS_FAIL(TS_ERR_SEEDS, "TypeSOL: PDA seeds do not match account");
651
+ seeds[n].addr = &bump;
652
+ seeds[n].len = 1;
653
+ } else if (!acct->is_signer) {
654
+ TS_FAIL(TS_ERR_NOT_SIGNER, "TypeSOL: a new keypair account must sign");
655
+ }
656
+ if (acct->data_len != 0 || !ts_key_eq(&acct->owner, &TS_SYSTEM_PROGRAM_ID))
657
+ TS_FAIL(TS_ERR_ALREADY_INITIALIZED, "TypeSOL: account already exists");
658
+ uint64_t lamports;
659
+ TS_TRY(ts_minimum_balance(space, &lamports));
660
+ SolSignerSeeds signer = {seeds, (uint64_t)(n + 1)};
661
+ SolSignerSeeds *signers = n > 0 ? &signer : NULL;
662
+ int nsigners = n > 0 ? 1 : 0;
663
+ if (acct->lamports == 0) {
664
+ uint8_t data[52];
665
+ uint32_t tag = 0; /* CreateAccount */
666
+ ts_memcpy(data, &tag, 4);
667
+ ts_memcpy(data + 4, &lamports, 8);
668
+ ts_memcpy(data + 12, &space, 8);
669
+ ts_memcpy(data + 20, owner, 32);
670
+ SolAccountMeta metas[2] = {{&payer->key, true, true}, {&acct->key, true, true}};
671
+ SolInstruction ix = {(SolPubkey *)&TS_SYSTEM_PROGRAM_ID, metas, 2, data, sizeof(data)};
672
+ TS_TRY(ts_invoke(params, &ix, signers, nsigners));
673
+ } else {
674
+ if (acct->lamports < lamports) TS_TRY(ts_system_transfer(params, payer, acct, lamports - acct->lamports));
675
+ uint8_t alloc[12];
676
+ uint32_t alloc_tag = 8; /* Allocate */
677
+ ts_memcpy(alloc, &alloc_tag, 4);
678
+ ts_memcpy(alloc + 4, &space, 8);
679
+ SolAccountMeta one[1] = {{&acct->key, true, true}};
680
+ SolInstruction alloc_ix = {(SolPubkey *)&TS_SYSTEM_PROGRAM_ID, one, 1, alloc, sizeof(alloc)};
681
+ TS_TRY(ts_invoke(params, &alloc_ix, signers, nsigners));
682
+ uint8_t assign[36];
683
+ uint32_t assign_tag = 1; /* Assign */
684
+ ts_memcpy(assign, &assign_tag, 4);
685
+ ts_memcpy(assign + 4, owner, 32);
686
+ SolInstruction assign_ix = {(SolPubkey *)&TS_SYSTEM_PROGRAM_ID, one, 1, assign, sizeof(assign)};
687
+ TS_TRY(ts_invoke(params, &assign_ix, signers, nsigners));
688
+ }
689
+ *bump_out = bump;
690
+ return SUCCESS;
691
+ }
692
+
693
+ /* InitializeMint2: decimals, mint authority, optional freeze authority. */
694
+ static inline uint64_t ts_token_init_mint(TsParams *params, TsAccount *tp, TsAccount *mint, uint8_t decimals,
695
+ const SolPubkey *authority, const SolPubkey *freeze) {
696
+ uint8_t data[67];
697
+ data[0] = 20;
698
+ data[1] = decimals;
699
+ ts_memcpy(data + 2, authority, 32);
700
+ data[34] = freeze ? 1 : 0;
701
+ if (freeze) ts_memcpy(data + 35, freeze, 32);
702
+ SolAccountMeta metas[1] = {{&mint->key, true, false}};
703
+ SolInstruction ix = {(SolPubkey *)&tp->key, metas, 1, data, freeze ? 67 : 35};
704
+ return ts_invoke(params, &ix, NULL, 0);
705
+ }
706
+
707
+ /* Bytes a token account of `mint` needs: 165 under SPL Token, Token-2022's GetAccountDataSize answer otherwise
708
+ (a mint's extensions can add account extensions). */
709
+ static inline uint64_t ts_token_account_space(TsParams *params, TsAccount *tp, TsAccount *mint, uint64_t *space) {
710
+ if (ts_key_eq(&tp->key, &TS_TOKEN_PROGRAM_ID)) {
711
+ *space = TS_TOKEN_ACCOUNT_LEN;
712
+ return SUCCESS;
713
+ }
714
+ uint8_t data[1] = {21}; /* GetAccountDataSize */
715
+ SolAccountMeta metas[1] = {{&mint->key, false, false}};
716
+ SolInstruction ix = {(SolPubkey *)&tp->key, metas, 1, data, 1};
717
+ TS_TRY(ts_invoke(params, &ix, NULL, 0));
718
+ SolPubkey from;
719
+ uint64_t size = 0;
720
+ if (sol_get_return_data((uint8_t *)&size, 8, &from) != 8 || !ts_key_eq(&from, &tp->key))
721
+ TS_FAIL(TS_ERR_TOKEN_ACCOUNT, "TypeSOL: the token program did not report an account size");
722
+ *space = size;
723
+ return SUCCESS;
724
+ }
725
+
726
+ /* InitializeAccount3: `acct` holds `mint` tokens for `owner`. */
727
+ static inline uint64_t ts_token_init_account(TsParams *params, TsAccount *tp, TsAccount *acct, TsAccount *mint, const SolPubkey *owner) {
728
+ uint8_t data[33];
729
+ data[0] = 18;
730
+ ts_memcpy(data + 1, owner, 32);
731
+ SolAccountMeta metas[2] = {{&acct->key, true, false}, {&mint->key, false, false}};
732
+ SolInstruction ix = {(SolPubkey *)&tp->key, metas, 2, data, sizeof(data)};
733
+ return ts_invoke(params, &ix, NULL, 0);
734
+ }
735
+
736
+ /* An existing account `init_if_needed` found: the PDA it must be (when seeds are given) and owned by token program `tp`. */
737
+ static inline uint64_t ts_token_existing(TsParams *params, TsAccount *acct, TsAccount *tp, SolSignerSeed *seeds, int n,
738
+ uint8_t *bump_out) {
739
+ *bump_out = 0;
740
+ if (n > 0) {
741
+ SolPubkey derived;
742
+ if (ts_find_pda(seeds, n, params->program_id, &derived, bump_out) != SUCCESS || !ts_key_eq(&derived, &acct->key))
743
+ TS_FAIL(TS_ERR_SEEDS, "TypeSOL: PDA seeds do not match account");
744
+ }
745
+ if (!ts_key_eq(&acct->owner, &tp->key)) TS_FAIL(TS_ERR_WRONG_OWNER, "TypeSOL: account not owned by the token program");
746
+ return SUCCESS;
747
+ }
748
+
749
+ /* An existing mint must have these decimals and authorities (Anchor's mint:: constraints on init_if_needed). */
750
+ static inline uint64_t ts_check_mint_is(const TsAccount *mint, uint8_t decimals, const SolPubkey *authority, const SolPubkey *freeze) {
751
+ const uint8_t *d = mint->data;
752
+ uint32_t tag;
753
+ ts_memcpy(&tag, d, 4);
754
+ if (tag != 1 || !ts_key_eq((const SolPubkey *)(d + 4), authority)) TS_FAIL(TS_ERR_MINT_AUTHORITY, "TypeSOL: mint authority does not match");
755
+ ts_memcpy(&tag, d + 46, 4);
756
+ if (freeze ? (tag != 1 || !ts_key_eq((const SolPubkey *)(d + 50), freeze)) : tag != 0)
757
+ TS_FAIL(TS_ERR_FREEZE_AUTHORITY, "TypeSOL: freeze authority does not match");
758
+ if (d[44] != decimals) TS_FAIL(TS_ERR_MINT_DECIMALS, "TypeSOL: mint decimals do not match");
759
+ return SUCCESS;
760
+ }
761
+
633
762
  /*
634
763
  * Resize a program-owned account to `new_len` bytes (realloc). Growth is limited to 10 KiB past the
635
764
  * size the account had when the instruction started; new bytes are zeroed. When growing, `payer`
@@ -670,16 +799,17 @@ static inline uint64_t ts_realloc(TsParams *params, TsAccount *payer, TsAccount
670
799
  return ts_resize(params, payer, acct, new_len, entry_len);
671
800
  }
672
801
 
802
+ /* `space` for a new account; an existing one needs `min_space` (less for a @layout borsh account). */
673
803
  static inline uint64_t ts_create_pda_if_needed(TsParams *params, TsAccount *payer, TsAccount *acct,
674
804
  SolSignerSeed *seeds, int n, const uint8_t disc[8], uint64_t space,
675
- uint8_t *bump_out, bool *created) {
805
+ uint64_t min_space, uint8_t *bump_out, bool *created) {
676
806
  if (ts_key_eq(&acct->owner, params->program_id)) {
677
807
  SolPubkey derived;
678
808
  uint8_t bump;
679
809
  if (ts_find_pda(seeds, n, params->program_id, &derived, &bump) != SUCCESS ||
680
810
  !ts_key_eq(&derived, &acct->key))
681
811
  TS_FAIL(TS_ERR_SEEDS, "TypeSOL: PDA seeds do not match account");
682
- if (acct->data_len < space) TS_FAIL(TS_ERR_ACCOUNT_TOO_SMALL, "TypeSOL: account data too small");
812
+ if (acct->data_len < min_space) TS_FAIL(TS_ERR_ACCOUNT_TOO_SMALL, "TypeSOL: account data too small");
683
813
  if (!ts_mem_eq(acct->data, disc, 8)) TS_FAIL(TS_ERR_DISCRIMINATOR, "TypeSOL: account discriminator mismatch");
684
814
  *bump_out = bump;
685
815
  *created = false;
@@ -970,6 +1100,44 @@ static inline uint64_t ts_alloc(uint64_t n, uint8_t **out) {
970
1100
  return SUCCESS;
971
1101
  }
972
1102
 
1103
+ /* Base58 text of a 32-byte value (a Pubkey or Hash in a template), on the heap. The value is eight big-endian u32
1104
+ limbs divided by 58^5 at a time, so each pass gives five digits with one 64-bit division per limb. */
1105
+ static uint64_t ts_base58_32(const uint8_t *in, TS_Str *out) {
1106
+ static const char digits[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
1107
+ const uint64_t D = 656356768ULL; /* 58^5 */
1108
+ uint32_t limb[8];
1109
+ for (int i = 0; i < 8; i++)
1110
+ limb[i] = (uint32_t)in[4 * i] << 24 | (uint32_t)in[4 * i + 1] << 16 | (uint32_t)in[4 * i + 2] << 8 | (uint32_t)in[4 * i + 3];
1111
+ uint8_t rev[50]; /* base-58 digits, least significant first; 32 bytes need at most 44 */
1112
+ uint64_t len = 0;
1113
+ int start = 0;
1114
+ while (start < 8 && limb[start] == 0) start++;
1115
+ while (start < 8) {
1116
+ uint64_t rem = 0;
1117
+ for (int i = start; i < 8; i++) {
1118
+ uint64_t cur = (rem << 32) | limb[i];
1119
+ uint64_t q = cur / D;
1120
+ rem = cur - q * D;
1121
+ limb[i] = (uint32_t)q;
1122
+ }
1123
+ while (start < 8 && limb[start] == 0) start++;
1124
+ for (int k = 0; k < 5; k++) {
1125
+ uint64_t q = rem / 58;
1126
+ rev[len++] = (uint8_t)(rem - q * 58);
1127
+ rem = q;
1128
+ }
1129
+ }
1130
+ while (len > 0 && rev[len - 1] == 0) len--;
1131
+ uint64_t zeros = 0;
1132
+ while (zeros < 32 && in[zeros] == 0) zeros++;
1133
+ uint8_t *p;
1134
+ TS_TRY(ts_alloc(zeros + len, &p));
1135
+ for (uint64_t i = 0; i < zeros; i++) p[i] = '1';
1136
+ for (uint64_t j = 0; j < len; j++) p[zeros + j] = (uint8_t)digits[rev[len - 1 - j]];
1137
+ *out = ts_str(p, zeros + len);
1138
+ return SUCCESS;
1139
+ }
1140
+
973
1141
  static inline uint64_t ts_str_concat(TS_Str a, TS_Str b, TS_Str *out) {
974
1142
  uint8_t *p;
975
1143
  TS_TRY(ts_alloc(a.len + b.len, &p));
@@ -1000,6 +1168,41 @@ static inline uint64_t ts_mul_div(uint64_t a, uint64_t b, uint64_t c, bool ceil,
1000
1168
  return SUCCESS;
1001
1169
  }
1002
1170
 
1171
+ /* floor or ceil of a * b / c for u128 through a 256-bit product; fails on c == 0 or a result above u128. */
1172
+ static uint64_t ts_mul_div_u128(unsigned __int128 a, unsigned __int128 b, unsigned __int128 c, bool ceil, unsigned __int128 *out) {
1173
+ if (c == 0) TS_FAIL(TS_ERR_DIV_ZERO, "TypeSOL: division by zero");
1174
+ const unsigned __int128 M = (unsigned __int128)UINT64_MAX;
1175
+ unsigned __int128 a0 = a & M, a1 = a >> 64, b0 = b & M, b1 = b >> 64;
1176
+ unsigned __int128 p00 = a0 * b0, p01 = a0 * b1, p10 = a1 * b0, p11 = a1 * b1;
1177
+ unsigned __int128 mid = (p00 >> 64) + (p01 & M) + (p10 & M);
1178
+ unsigned __int128 lo = (p00 & M) | (mid << 64);
1179
+ unsigned __int128 hi = p11 + (p01 >> 64) + (p10 >> 64) + (mid >> 64);
1180
+ unsigned __int128 q, r;
1181
+ if (hi == 0) {
1182
+ q = lo / c;
1183
+ r = lo % c;
1184
+ } else {
1185
+ /* The quotient fits in 128 bits only when hi < c; then long division of hi:lo by c, one bit at a time. */
1186
+ if (hi >= c) TS_FAIL(TS_ERR_OVERFLOW, "TypeSOL: arithmetic overflow");
1187
+ q = 0;
1188
+ r = hi;
1189
+ for (int i = 127; i >= 0; i--) {
1190
+ bool carry = (r >> 127) != 0;
1191
+ r = (r << 1) | ((lo >> i) & 1);
1192
+ if (carry || r >= c) {
1193
+ r -= c;
1194
+ q |= (unsigned __int128)1 << i;
1195
+ }
1196
+ }
1197
+ }
1198
+ if (ceil && r != 0) {
1199
+ if (q == ~(unsigned __int128)0) TS_FAIL(TS_ERR_OVERFLOW, "TypeSOL: arithmetic overflow");
1200
+ q += 1;
1201
+ }
1202
+ *out = q;
1203
+ return SUCCESS;
1204
+ }
1205
+
1003
1206
  /* Integer square roots (floor), by Newton's method from an over-estimate. */
1004
1207
  static inline uint64_t ts_isqrt_u64(uint64_t x) {
1005
1208
  if (x < 2) return x;
@@ -1209,9 +1412,21 @@ static inline uint64_t ts_metadata_address(const SolPubkey *mint, SolPubkey *out
1209
1412
  return SUCCESS;
1210
1413
  }
1211
1414
 
1415
+ static inline uint64_t ts_master_edition_address(const SolPubkey *mint, SolPubkey *out) {
1416
+ SolSignerSeed seeds[4] = {{(const uint8_t *)"metadata", 8},
1417
+ {(const uint8_t *)&TS_MPL_METADATA_ID, 32},
1418
+ {(const uint8_t *)mint, 32},
1419
+ {(const uint8_t *)"edition", 7}};
1420
+ uint8_t bump;
1421
+ if (ts_find_pda(seeds, 4, &TS_MPL_METADATA_ID, out, &bump) != SUCCESS)
1422
+ TS_FAIL(TS_ERR_SEEDS, "TypeSOL: cannot derive the master edition address");
1423
+ return SUCCESS;
1424
+ }
1425
+
1212
1426
  /*
1213
1427
  * Metaplex CreateMetadataAccountV3 (instruction 33): DataV2 { name, symbol, uri, seller_fee_basis_points u16,
1214
- * creators Option<Vec<Creator>>, collection Option<Collection>, uses None }, is_mutable bool, collection_details None.
1428
+ * creators Option<Vec<Creator>>, collection Option<Collection>, uses None }, is_mutable bool, collection_details
1429
+ * None, or Some(V1 { size: 0 }) when `sized_collection`.
1215
1430
  * `creators` holds `ncreators` packed Creator records (address, verified, share: 34 bytes); ncreators < 0 is None.
1216
1431
  * `collection` (NULL for None) is written unverified, as Metaplex requires at creation.
1217
1432
  * Accounts: metadata (w), mint, mint authority (s), payer (ws), update authority, system program.
@@ -1220,10 +1435,10 @@ static inline uint64_t ts_mpl_create_v3(TsParams *params, const TsAccount *progr
1220
1435
  TsAccount *mint, TsAccount *mint_authority, TsAccount *payer,
1221
1436
  TsAccount *update_authority, TsAccount *system, TS_Str name, TS_Str symbol,
1222
1437
  TS_Str uri, uint16_t fee_bps, bool is_mutable, const uint8_t *creators, int ncreators,
1223
- const SolPubkey *collection, const SolSignerSeed *seeds, int n) {
1438
+ const SolPubkey *collection, bool sized_collection, const SolSignerSeed *seeds, int n) {
1224
1439
  uint8_t *data;
1225
1440
  uint64_t ncr = ncreators > 0 ? (uint64_t)ncreators : 0;
1226
- TS_TRY(ts_alloc(1 + 12 + name.len + symbol.len + uri.len + 2 + (1 + 4 + 34 * ncr) + (1 + 33) + 1 + 1 + 1, &data));
1441
+ TS_TRY(ts_alloc(1 + 12 + name.len + symbol.len + uri.len + 2 + (1 + 4 + 34 * ncr) + (1 + 33) + 1 + 1 + (1 + 9), &data));
1227
1442
  uint64_t at = 0;
1228
1443
  data[at++] = 33;
1229
1444
  ts_put_str(data, &at, name);
@@ -1251,7 +1466,15 @@ static inline uint64_t ts_mpl_create_v3(TsParams *params, const TsAccount *progr
1251
1466
  }
1252
1467
  data[at++] = 0; /* uses: None */
1253
1468
  data[at++] = is_mutable ? 1 : 0;
1254
- data[at++] = 0; /* collection_details: None */
1469
+ if (sized_collection) {
1470
+ /* collection_details: Some(V1 { size: 0 }): this NFT is a collection items can be verified into */
1471
+ data[at++] = 1;
1472
+ data[at++] = 0;
1473
+ ts_memzero(data + at, 8);
1474
+ at += 8;
1475
+ } else {
1476
+ data[at++] = 0; /* collection_details: None */
1477
+ }
1255
1478
  bool ua_signs = update_authority->is_signer || ts_key_eq(&update_authority->key, &mint_authority->key);
1256
1479
  SolAccountMeta metas[6] = {{&metadata->key, true, false}, {&mint->key, false, false},
1257
1480
  {&mint_authority->key, false, true}, {&payer->key, true, true},