// Solmate v1. This program manages holder-access records and their rent deposits. #include #include enum { GRANT = 0, REVOKE = 1, REVOKE_ALL = 2 }; enum { VAULT_TAG = 1, GRANT_TAG = 2, STATE_SIZE = 41, GRANT_SIZE = 146, MAX_ACCOUNTS = 4 }; enum { OK = 0, BAD_EXPIRY = 101, BAD_SCOPE = 102, BAD_PDA = 103, INVALID_STATE = 104, ARITHMETIC_ERROR = 105, UNSUPPORTED_RENT = 106 }; static const SolPubkey SYSTEM_PROGRAM = {{0}}; static const uint64_t MAX_LIFETIME = 366UL * 24 * 60 * 60; // Runtime syscall ABI. Both structs match the aligned repr(C) sysvars. typedef struct { uint64_t slot; int64_t start; uint64_t epoch, leader_epoch; int64_t now; } Clock; typedef struct { uint64_t price, threshold_bits; uint8_t burn, padding[7]; } Rent; extern uint64_t sol_get_clock_sysvar(Clock *); extern uint64_t sol_get_rent_sysvar(Rent *); extern void sol_memset_(void *, uint8_t, uint64_t); // LLVM may lower a constant-size zeroing operation to the C ABI. __attribute__((visibility("hidden"))) void *memset(void *dst, int value, size_t size) { sol_memset_(dst, (uint8_t)value, size); return dst; } static uint64_t read_u64(const uint8_t *p) { uint64_t n; __builtin_memcpy(&n, p, sizeof(n)); return n; } static void write_u64(uint8_t *p, uint64_t n) { __builtin_memcpy(p, &n, sizeof(n)); } static bool same(const uint8_t *a, const uint8_t *b) { for (int i = 0; i < 32; i++) if (a[i] != b[i]) return false; return true; } static uint64_t check_pda(const SolParameters *p, const SolAccountInfo *account, SolSignerSeed *seeds, int count, uint8_t *bump) { SolPubkey derived; uint64_t err = sol_try_find_program_address(seeds, count, p->program_id, &derived, bump); if (err) return err; if (!SolPubkey_same(&derived, account->key)) return BAD_PDA; seeds[count] = (SolSignerSeed){bump, 1}; return OK; } // Each grant belongs to precisely one (vault, delegate, audience, scope, asset). static void grant_seeds(SolSignerSeed seeds[7], const SolPubkey *vault, const uint8_t *delegate, const uint8_t *audience, const uint8_t *scope, const uint8_t *asset) { seeds[0] = (SolSignerSeed){(const uint8_t *)"grant", 5}; seeds[1] = (SolSignerSeed){vault->x, 32}; seeds[2] = (SolSignerSeed){delegate, 32}; seeds[3] = (SolSignerSeed){audience, 32}; seeds[4] = (SolSignerSeed){scope, 1}; seeds[5] = (SolSignerSeed){asset, 32}; } static uint64_t system_call(const SolParameters *p, SolAccountMeta *metas, uint64_t count, uint8_t *data, uint64_t length, const SolSignerSeeds *signer) { SolInstruction ix = {p->ka[3].key, metas, count, data, length}; return sol_invoke_signed(&ix, p->ka, p->ka_num, signer, signer ? 1 : 0); } // Allocate/assign works even if a third party previously sent lamports to the PDA. // Only the vault can pay, and the only beneficiaries are the canonical registry PDAs. static uint64_t ensure_record(const SolParameters *p, SolAccountInfo *record, uint64_t size, SolSignerSeed *seeds, int count, bool *created) { *created = false; if (!record->is_writable || record->executable) return ERROR_INVALID_ACCOUNT_DATA; if (SolPubkey_same(record->owner, p->program_id)) return record->data_len == size ? OK : INVALID_STATE; if (!SolPubkey_same(record->owner, &SYSTEM_PROGRAM) || record->data_len != 0) return ERROR_INCORRECT_PROGRAM_ID; Rent rent = {0}; uint64_t err = sol_get_rent_sysvar(&rent); if (err) return err; // The current and legacy rent sysvars use exact 1.0 and 2.0 thresholds. uint64_t years; if (rent.threshold_bits == 0x3ff0000000000000UL) years = 1; else if (rent.threshold_bits == 0x4000000000000000UL) years = 2; else return UNSUPPORTED_RENT; uint64_t units = (128 + size) * years; // Maximum allocation in this program is GRANT_SIZE bytes, at most two years. if (rent.price > UINT64_MAX / ((128 + GRANT_SIZE) * 2)) return ARITHMETIC_ERROR; uint64_t minimum = rent.price * units; SolAccountMeta payer = {p->ka[0].key, true, true}; SolAccountMeta target = {record->key, true, false}; if (*record->lamports < minimum) { uint8_t transfer[12] = {2}; write_u64(transfer + 4, minimum - *record->lamports); SolAccountMeta metas[] = {payer, target}; err = system_call(p, metas, 2, transfer, sizeof(transfer), NULL); if (err) return err; } SolSignerSeeds signer = {seeds, count}; target.is_signer = true; uint8_t allocate[12] = {8}; write_u64(allocate + 4, size); err = system_call(p, &target, 1, allocate, sizeof(allocate), &signer); if (err) return err; uint8_t assign[36] = {1}; __builtin_memcpy(assign + 4, p->program_id->x, 32); err = system_call(p, &target, 1, assign, sizeof(assign), &signer); if (err) return err; *created = true; return OK; } static bool valid_state(const SolParameters *p, const SolAccountInfo *state) { return SolPubkey_same(state->owner, p->program_id) && state->data_len == STATE_SIZE && state->data[0] == VAULT_TAG && same(p->ka[0].key->x, state->data + 1); } static uint64_t grant(const SolParameters *p) { if (p->ka_num != 4 || p->data_len != 106) return ERROR_INVALID_INSTRUCTION_DATA; SolAccountInfo *vault = &p->ka[0], *state = &p->ka[1], *grant = &p->ka[2]; if (!vault->is_writable || !SolPubkey_same(p->ka[3].key, &SYSTEM_PROGRAM)) return ERROR_INVALID_ARGUMENT; const uint8_t *scope = p->data + 1, *delegate = p->data + 2, *audience = p->data + 34, *asset = p->data + 66; if (same(delegate, SYSTEM_PROGRAM.x) || same(delegate, vault->key->x) || same(audience, SYSTEM_PROGRAM.x)) return ERROR_INVALID_ARGUMENT; if (*scope > 1 || ((*scope == 0) != same(asset, SYSTEM_PROGRAM.x))) return BAD_SCOPE; Clock clock; uint64_t err = sol_get_clock_sysvar(&clock); if (err) return err; int64_t expiry = (int64_t)read_u64(p->data + 98); if (clock.now < 0 || expiry <= clock.now || (uint64_t)(expiry - clock.now) > MAX_LIFETIME) return BAD_EXPIRY; SolSignerSeed state_seeds[3] = {{(const uint8_t *)"vault", 5}, {vault->key->x, 32}}; SolSignerSeed seeds[7]; grant_seeds(seeds, vault->key, delegate, audience, scope, asset); uint8_t state_bump, grant_bump; err = check_pda(p, state, state_seeds, 2, &state_bump); if (err) return err; err = check_pda(p, grant, seeds, 6, &grant_bump); if (err) return err; bool created; err = ensure_record(p, state, STATE_SIZE, state_seeds, 3, &created); if (err) return err; if (created) { state->data[0] = VAULT_TAG; __builtin_memcpy(state->data + 1, vault->key->x, 32); write_u64(state->data + 33, 0); } else if (!valid_state(p, state)) return INVALID_STATE; err = ensure_record(p, grant, GRANT_SIZE, seeds, 7, &created); if (err) return err; if (!created && (grant->data[0] != GRANT_TAG || !same(grant->data + 1, vault->key->x) || !same(grant->data + 33, delegate) || !same(grant->data + 65, audience) || grant->data[97] != *scope || !same(grant->data + 98, asset))) return INVALID_STATE; grant->data[0] = GRANT_TAG; __builtin_memcpy(grant->data + 1, vault->key->x, 32); __builtin_memcpy(grant->data + 33, delegate, 32); __builtin_memcpy(grant->data + 65, audience, 32); grant->data[97] = *scope; __builtin_memcpy(grant->data + 98, asset, 32); write_u64(grant->data + 130, read_u64(state->data + 33)); write_u64(grant->data + 138, expiry); return OK; } static uint64_t revoke_one(const SolParameters *p) { if (p->ka_num != 2 || p->data_len != 1) return ERROR_INVALID_INSTRUCTION_DATA; SolAccountInfo *vault = &p->ka[0], *grant = &p->ka[1]; if (!vault->is_writable || !grant->is_writable || !SolPubkey_same(grant->owner, p->program_id) || grant->data_len != GRANT_SIZE || grant->data[0] != GRANT_TAG || !same(grant->data + 1, vault->key->x)) return INVALID_STATE; SolSignerSeed seeds[7]; uint8_t bump; grant_seeds(seeds, vault->key, grant->data + 33, grant->data + 65, grant->data + 97, grant->data + 98); uint64_t err = check_pda(p, grant, seeds, 6, &bump); if (err) return err; if (*vault->lamports > UINT64_MAX - *grant->lamports) return ARITHMETIC_ERROR; *vault->lamports += *grant->lamports; *grant->lamports = 0; // Clear the serialized length and contents before returning ownership to System. // A same-transaction deposit can no longer revive a closed grant. __builtin_memset(grant->data, 0, GRANT_SIZE); write_u64(grant->data - sizeof(uint64_t), 0); grant->data_len = 0; __builtin_memset(grant->owner->x, 0, 32); return OK; } static uint64_t revoke_all(const SolParameters *p) { if (p->ka_num != 2 || p->data_len != 1) return ERROR_INVALID_INSTRUCTION_DATA; SolAccountInfo *state = &p->ka[1]; if (!state->is_writable || !valid_state(p, state)) return INVALID_STATE; SolSignerSeed seeds[3] = {{(const uint8_t *)"vault", 5}, {p->ka[0].key->x, 32}}; uint8_t bump; uint64_t err = check_pda(p, state, seeds, 2, &bump); if (err) return err; uint64_t epoch = read_u64(state->data + 33); if (epoch == UINT64_MAX) return ARITHMETIC_ERROR; write_u64(state->data + 33, epoch + 1); return OK; } uint64_t entrypoint(const uint8_t *input) { SolAccountInfo accounts[MAX_ACCOUNTS]; SolParameters p = {.ka = accounts}; if (!sol_deserialize(input, &p, MAX_ACCOUNTS) || !p.ka_num || p.ka_num > MAX_ACCOUNTS || !p.data_len) return ERROR_INVALID_INSTRUCTION_DATA; if (!p.ka[0].is_signer) return ERROR_MISSING_REQUIRED_SIGNATURES; if (!SolPubkey_same(p.ka[0].owner, &SYSTEM_PROGRAM) || p.ka[0].data_len || p.ka[0].executable) return ERROR_INVALID_ARGUMENT; switch (p.data[0]) { case GRANT: return grant(&p); case REVOKE: return revoke_one(&p); case REVOKE_ALL: return revoke_all(&p); default: return ERROR_INVALID_INSTRUCTION_DATA; } }