diff --git a/README.md b/README.md index 14b8254..adda19d 100644 --- a/README.md +++ b/README.md @@ -215,39 +215,39 @@ program. | Language | CU Usage | | --- | --- | | Rust | 1115 | -| Zig | 166 | +| Zig | 158 | * Initialize Account | Language | CU Usage | | --- | --- | | Rust | 2071 | -| Zig | 189 | +| Zig | 176 | * Mint To | Language | CU Usage | | --- | --- | | Rust | 2189 | -| Zig | 195 | +| Zig | 179 | * Transfer | Language | CU Usage | | --- | --- | | Rust | 2208 | -| Zig | 160 | +| Zig | 148 | * Burn | Language | CU Usage | | --- | --- | | Rust | 2045 | -| Zig | 157 | +| Zig | 145 | * Close Account | Language | CU Usage | | --- | --- | | Rust | 1483 | -| Zig | 260 | +| Zig | 130 | diff --git a/token/zig/build.zig.zon b/token/zig/build.zig.zon index bec40ec..4f1fd17 100644 --- a/token/zig/build.zig.zon +++ b/token/zig/build.zig.zon @@ -16,8 +16,8 @@ // internet connectivity. .dependencies = .{ .@"solana-program-sdk" = .{ - .url = "https://github.com/joncinque/solana-program-sdk-zig/archive/refs/tags/v0.15.0.tar.gz", - .hash = "1220c255d7d80a59251d901da4d2982eb660d099680c1207b14f51078987c655c979", + .url = "https://github.com/joncinque/solana-program-sdk-zig/archive/refs/tags/v0.15.1.tar.gz", + .hash = "12203631b9eba91c479991ec8f0525f181addb5879bbb96e256427f802c2ca67e108", }, }, diff --git a/token/zig/src/main.zig b/token/zig/src/main.zig index 9c78f45..0816d7a 100644 --- a/token/zig/src/main.zig +++ b/token/zig/src/main.zig @@ -14,178 +14,342 @@ export fn entrypoint(input: [*]u8) u64 { return 0; } +fn initializeMint(accounts: []sol.Account, data: []const u8) TokenError!void { + if (accounts.len < 2) { + return TokenError.NotEnoughAccountKeys; + } + const ix_data: *align(1) const ix.InitializeMintData = @ptrCast(data[1..]); + const mint_account = accounts[0]; + const rent_sysvar = accounts[1]; + + var mint: *align(1) state.Mint = @ptrCast(mint_account.data()); + if (mint_account.dataLen() != state.Mint.len) { + return TokenError.InvalidAccountData; + } + if (mint.is_initialized == 1) { + return TokenError.AlreadyInUse; + } + + const rent: *align(1) Rent.Data = @ptrCast(rent_sysvar.data()); + if (!rent_sysvar.id().equals(Rent.id)) { + return TokenError.InvalidAccountData; + } + if (!rent.isExempt(mint_account.lamports().*, mint_account.dataLen())) { + return TokenError.NotRentExempt; + } + + mint.mint_authority = state.COption(PublicKey).fromValue(ix_data.mint_authority); + mint.decimals = ix_data.decimals; + mint.is_initialized = 1; + mint.freeze_authority = ix_data.freeze_authority.toCOption(); +} + +fn initializeAccount(program_id: *align(1) PublicKey, accounts: []sol.Account) TokenError!void { + if (accounts.len < 4) { + return TokenError.NotEnoughAccountKeys; + } + const token_account = accounts[0]; + const mint_account = accounts[1]; + const owner = accounts[2]; + const rent_sysvar = accounts[3]; + const rent: *align(1) Rent.Data = @ptrCast(rent_sysvar.data()); + + var account: *align(1) state.Account = @ptrCast(token_account.data()); + if (token_account.dataLen() != state.Account.len) { + return TokenError.InvalidAccountData; + } + if (account.state != state.Account.State.uninitialized) { + return TokenError.AlreadyInUse; + } + if (!rent.isExempt(token_account.lamports().*, token_account.dataLen())) { + return TokenError.NotRentExempt; + } + + account.mint = mint_account.id(); + account.owner = owner.id(); + //account.close_authority = state.COption(PublicKey).asNull(); + //account.delegate = state.COption(PublicKey).asNull(); + //account.delegated_amount = 0; + account.state = state.Account.State.initialized; + if (mint_account.id().equals(native_mint_id)) { + const rent_exempt_reserve = rent.getMinimumBalance(token_account.dataLen()); + account.is_native = state.COption(u64).fromValue(rent_exempt_reserve); + if (rent_exempt_reserve > token_account.lamports().*) { + return TokenError.Overflow; + } + account.amount = token_account.lamports().* - rent_exempt_reserve; + } else { + if (!mint_account.ownerId().equals(program_id.*)) { + return TokenError.IllegalOwner; + } + const mint: *align(1) state.Mint = @ptrCast(mint_account.data()); + if (mint_account.dataLen() != state.Mint.len) { + return TokenError.InvalidAccountData; + } + if (mint.is_initialized != 1) { + return TokenError.UninitializedState; + } + + //account.is_native = state.COption(u64).asNull(); + //account.amount = 0; + } +} + +fn transfer(program_id: *align(1) PublicKey, accounts: []sol.Account, data: []const u8) TokenError!void { + if (accounts.len < 3) { + return TokenError.NotEnoughAccountKeys; + } + const ix_data: *align(1) const ix.AmountData = @ptrCast(data[1..]); + const source_account = accounts[0]; + const destination_account = accounts[1]; + const authority_account = accounts[2]; + + var source = try state.Account.fromBytes(source_account.data()); + var destination = try state.Account.fromBytes(destination_account.data()); + + if (source.amount < ix_data.amount) { + return TokenError.InsufficientFunds; + } + if (!source.mint.equals(destination.mint)) { + return TokenError.MintMismatch; + } + + //match source_account.delegate { + // COption::Some(ref delegate) if Self::cmp_pubkeys(authority_info.key, delegate) => { + // Self::validate_owner( + // program_id, + // delegate, + // authority_info, + // account_info_iter.as_slice(), + // )?; + // if source_account.delegated_amount < amount { + // return Err(TokenError::InsufficientFunds.into()); + // } + // if !self_transfer { + // source_account.delegated_amount = source_account + // .delegated_amount + // .checked_sub(amount) + // .ok_or(TokenError::Overflow)?; + // if source_account.delegated_amount == 0 { + // source_account.delegate = COption::None; + // } + // } + // } + try validateOwner( + program_id, + &source.owner, + authority_account, + accounts[3..], + ); + + const pre_amount = source.amount; + source.amount -= ix_data.amount; + destination.amount += ix_data.amount; + + if (source.isNative()) { + source_account.lamports().* -= ix_data.amount; + destination_account.lamports().* += ix_data.amount; + } + + if (pre_amount == source.amount) { + // self transfer or 0 token amount, check owners for safety + if (!source_account.ownerId().equals(program_id.*)) { + return TokenError.IllegalOwner; + } + if (!destination_account.ownerId().equals(program_id.*)) { + return TokenError.IllegalOwner; + } + } +} + +fn mintTo(program_id: *align(1) PublicKey, accounts: []sol.Account, data: []const u8) TokenError!void { + if (accounts.len < 3) { + return TokenError.NotEnoughAccountKeys; + } + const ix_data: *align(1) const ix.AmountData = @ptrCast(data[1..]); + const mint_account = accounts[0]; + const destination_account = accounts[1]; + const authority_account = accounts[2]; + + var destination = try state.Account.fromBytes(destination_account.data()); + if (destination.isNative()) { + return TokenError.NativeNotSupported; + } + if (!mint_account.id().equals(destination.mint)) { + return TokenError.MintMismatch; + } + + var mint = try state.Mint.fromBytes(mint_account.data()); + //if let Some(expected_decimals) = expected_decimals { + // if expected_decimals != mint.decimals { + // return Err(TokenError::MintDecimalsMismatch.into()); + // } + //} + + if (mint.mint_authority.is_some == 0) { + return TokenError.FixedSupply; + } + + try validateOwner( + program_id, + &mint.mint_authority.value, + authority_account, + accounts[3..], + ); + + if (ix_data.amount == 0) { + if (!mint_account.ownerId().equals(program_id.*)) { + return TokenError.IllegalOwner; + } + if (!destination_account.ownerId().equals(program_id.*)) { + return TokenError.IllegalOwner; + } + } + + const supply = @addWithOverflow(mint.supply, ix_data.amount); + if (supply[1] != 0) { + return TokenError.Overflow; + } + mint.supply = supply[0]; + destination.amount += ix_data.amount; +} + +fn burn(program_id: *align(1) PublicKey, accounts: []sol.Account, data: []const u8) TokenError!void { + if (accounts.len < 3) { + return TokenError.NotEnoughAccountKeys; + } + const ix_data: *align(1) const ix.AmountData = @ptrCast(data[1..]); + const source_account = accounts[0]; + const mint_account = accounts[1]; + const authority_account = accounts[2]; + + var source = try state.Account.fromBytes(source_account.data()); + if (source.isNative()) { + return TokenError.NativeNotSupported; + } + + var mint = try state.Mint.fromBytes(mint_account.data()); + if (!mint_account.id().equals(source.mint)) { + return TokenError.MintMismatch; + } + if (source.amount < ix_data.amount) { + return TokenError.InsufficientFunds; + } + + //if let Some(expected_decimals) = expected_decimals { + // if expected_decimals != mint.decimals { + // return Err(TokenError::MintDecimalsMismatch.into()); + // } + //} + + //if !source_account.is_owned_by_system_program_or_incinerator() { + //match source_account.delegate { + //COption::Some(ref delegate) if Self::cmp_pubkeys(authority_info.key, delegate) => { + //Self::validate_owner( + //program_id, + //delegate, + //authority_info, + //account_info_iter.as_slice(), + //)?; + + //if source_account.delegated_amount < amount { + //return Err(TokenError::InsufficientFunds.into()); + //} + //source_account.delegated_amount = source_account + //.delegated_amount + //.checked_sub(amount) + //.ok_or(TokenError::Overflow)?; + //if source_account.delegated_amount == 0 { + //source_account.delegate = COption::None; + //} + //} + //} + //} + try validateOwner( + program_id, + &source.owner, + authority_account, + accounts[3..], + ); + + source.amount -= ix_data.amount; + mint.supply -= ix_data.amount; + + if (ix_data.amount == 0) { + if (!mint_account.ownerId().equals(program_id.*)) { + return TokenError.IllegalOwner; + } + if (!source_account.ownerId().equals(program_id.*)) { + return TokenError.IllegalOwner; + } + } +} + +fn closeAccount(program_id: *align(1) PublicKey, accounts: []sol.Account) TokenError!void { + if (accounts.len < 3) { + return TokenError.NotEnoughAccountKeys; + } + const source_account = accounts[0]; + const destination_account = accounts[1]; + const authority_account = accounts[2]; + + var source = try state.Account.fromBytes(source_account.data()); + if (!source.isNative() and source.amount != 0) { + return TokenError.NonNativeHasBalance; + } + + const authority = if (source.close_authority.is_some != 0) + &source.close_authority.value + else + &source.owner; + + //if !source_account.is_owned_by_system_program_or_incinerator() { + //Self::validate_owner( + //program_id, + //&authority, + //authority_info, + //account_info_iter.as_slice(), + //)?; + //} else if !solana_program::incinerator::check_id(destination_account_info.key) { + //return Err(ProgramError::InvalidAccountData); + //} + + try validateOwner( + program_id, + authority, + authority_account, + accounts[3..], + ); + + destination_account.lamports().* += source_account.lamports().*; + source_account.lamports().* = 0; + source_account.assign(system_program_id); + source_account.reallocUnchecked(0); + + // if the destination has no more lamports, then this was a self-close, + // which is not allowed + if (destination_account.lamports().* == 0) { + return TokenError.InvalidAccountData; + } +} + fn processInstruction(program_id: *align(1) PublicKey, accounts: []sol.Account, data: []const u8) TokenError!void { const instruction_type: *const ix.InstructionDiscriminant = @ptrCast(data); switch (instruction_type.*) { ix.InstructionDiscriminant.initialize_mint => { //sol.log("Instruction: InitializeMint"); - if (accounts.len < 2) { - return TokenError.NotEnoughAccountKeys; - } - const ix_data: *align(1) const ix.InitializeMintData = @ptrCast(data[1..]); - const mint_account = accounts[0]; - const rent_sysvar = accounts[1]; - - var mint: *align(1) state.Mint = @ptrCast(mint_account.data()); - if (mint_account.dataLen() != state.Mint.len) { - return TokenError.InvalidAccountData; - } - if (mint.is_initialized == 1) { - return TokenError.AlreadyInUse; - } - - const rent: *align(1) Rent.Data = @ptrCast(rent_sysvar.data()); - if (!rent_sysvar.id().equals(Rent.id)) { - return TokenError.InvalidAccountData; - } - if (!rent.isExempt(mint_account.lamports().*, mint_account.dataLen())) { - return TokenError.NotRentExempt; - } - - mint.mint_authority = state.COption(PublicKey).fromValue(ix_data.mint_authority); - mint.decimals = ix_data.decimals; - mint.is_initialized = 1; - mint.freeze_authority = ix_data.freeze_authority.toCOption(); + try initializeMint(accounts, data); }, ix.InstructionDiscriminant.initialize_account => { //sol.log("Instruction: InitializeAccount"); - if (accounts.len < 4) { - return TokenError.NotEnoughAccountKeys; - } - const token_account = accounts[0]; - const mint_account = accounts[1]; - const owner = accounts[2]; - const rent_sysvar = accounts[3]; - const rent: *align(1) Rent.Data = @ptrCast(rent_sysvar.data()); - - var account: *align(1) state.Account = @ptrCast(token_account.data()); - if (token_account.dataLen() != state.Account.len) { - return TokenError.InvalidAccountData; - } - if (account.state != state.Account.State.uninitialized) { - return TokenError.AlreadyInUse; - } - if (!rent.isExempt(token_account.lamports().*, token_account.dataLen())) { - return TokenError.NotRentExempt; - } - - account.mint = mint_account.id(); - account.owner = owner.id(); - //account.close_authority = state.COption(PublicKey).asNull(); - //account.delegate = state.COption(PublicKey).asNull(); - //account.delegated_amount = 0; - account.state = state.Account.State.initialized; - if (mint_account.id().equals(native_mint_id)) { - const rent_exempt_reserve = rent.getMinimumBalance(token_account.dataLen()); - account.is_native = state.COption(u64).fromValue(rent_exempt_reserve); - if (rent_exempt_reserve > token_account.lamports().*) { - return TokenError.Overflow; - } - account.amount = token_account.lamports().* - rent_exempt_reserve; - } else { - if (!mint_account.ownerId().equals(program_id.*)) { - return TokenError.IllegalOwner; - } - const mint: *align(1) state.Mint = @ptrCast(mint_account.data()); - if (mint_account.dataLen() != state.Mint.len) { - return TokenError.InvalidAccountData; - } - if (mint.is_initialized != 1) { - return TokenError.UninitializedState; - } - - //account.is_native = state.COption(u64).asNull(); - //account.amount = 0; - } + try initializeAccount(program_id, accounts); }, ix.InstructionDiscriminant.initialize_multisig => { return TokenError.InvalidState; }, ix.InstructionDiscriminant.transfer => { //sol.log("Instruction: Transfer"); - if (accounts.len < 3) { - return TokenError.NotEnoughAccountKeys; - } - const ix_data: *align(1) const ix.AmountData = @ptrCast(data[1..]); - const source_account = accounts[0]; - const destination_account = accounts[1]; - const authority_account = accounts[2]; - - var source: *align(1) state.Account = @ptrCast(source_account.data()); - if (source_account.dataLen() != state.Account.len) { - return TokenError.InvalidAccountData; - } - if (source.state == state.Account.State.uninitialized) { - return TokenError.UninitializedState; - } - if (source.state == state.Account.State.frozen) { - return TokenError.AccountFrozen; - } - - var destination: *align(1) state.Account = @ptrCast(destination_account.data()); - if (destination_account.dataLen() != state.Account.len) { - return TokenError.InvalidAccountData; - } - if (destination.state == state.Account.State.uninitialized) { - return TokenError.UninitializedState; - } - if (destination.state == state.Account.State.frozen) { - return TokenError.AccountFrozen; - } - - if (source.amount < ix_data.amount) { - return TokenError.InsufficientFunds; - } - if (!source.mint.equals(destination.mint)) { - return TokenError.MintMismatch; - } - - //match source_account.delegate { - // COption::Some(ref delegate) if Self::cmp_pubkeys(authority_info.key, delegate) => { - // Self::validate_owner( - // program_id, - // delegate, - // authority_info, - // account_info_iter.as_slice(), - // )?; - // if source_account.delegated_amount < amount { - // return Err(TokenError::InsufficientFunds.into()); - // } - // if !self_transfer { - // source_account.delegated_amount = source_account - // .delegated_amount - // .checked_sub(amount) - // .ok_or(TokenError::Overflow)?; - // if source_account.delegated_amount == 0 { - // source_account.delegate = COption::None; - // } - // } - // } - try validateOwner( - program_id, - &source.owner, - authority_account, - accounts[3..], - ); - - const pre_amount = source.amount; - source.amount -= ix_data.amount; - destination.amount += ix_data.amount; - - if (source.isNative()) { - source_account.lamports().* -= ix_data.amount; - destination_account.lamports().* += ix_data.amount; - } - - if (pre_amount == source.amount) { - // self transfer or 0 token amount, check owners for safety - if (!source_account.ownerId().equals(program_id.*)) { - return TokenError.IllegalOwner; - } - if (!destination_account.ownerId().equals(program_id.*)) { - return TokenError.IllegalOwner; - } - } + try transfer(program_id, accounts, data); }, ix.InstructionDiscriminant.approve => { return TokenError.InvalidState; @@ -198,217 +362,15 @@ fn processInstruction(program_id: *align(1) PublicKey, accounts: []sol.Account, }, ix.InstructionDiscriminant.mint_to => { //sol.log("Instruction: MintTo"); - if (accounts.len < 3) { - return TokenError.NotEnoughAccountKeys; - } - const ix_data: *align(1) const ix.AmountData = @ptrCast(data[1..]); - const mint_account = accounts[0]; - const destination_account = accounts[1]; - const authority_account = accounts[2]; - - var destination: *align(1) state.Account = @ptrCast(destination_account.data()); - if (destination_account.dataLen() != state.Account.len) { - return TokenError.InvalidAccountData; - } - if (destination.state == state.Account.State.uninitialized) { - return TokenError.UninitializedState; - } - if (destination.state == state.Account.State.frozen) { - return TokenError.AccountFrozen; - } - if (destination.isNative()) { - return TokenError.NativeNotSupported; - } - if (!mint_account.id().equals(destination.mint)) { - return TokenError.MintMismatch; - } - - var mint: *align(1) state.Mint = @ptrCast(mint_account.data()); - if (mint_account.dataLen() != state.Mint.len) { - return TokenError.InvalidAccountData; - } - if (mint.is_initialized != 1) { - return TokenError.UninitializedState; - } - //if let Some(expected_decimals) = expected_decimals { - // if expected_decimals != mint.decimals { - // return Err(TokenError::MintDecimalsMismatch.into()); - // } - //} - - if (mint.mint_authority.is_some == 0) { - return TokenError.FixedSupply; - } - - try validateOwner( - program_id, - &mint.mint_authority.value, - authority_account, - accounts[3..], - ); - - if (ix_data.amount == 0) { - if (!mint_account.ownerId().equals(program_id.*)) { - return TokenError.IllegalOwner; - } - if (!destination_account.ownerId().equals(program_id.*)) { - return TokenError.IllegalOwner; - } - } - - const destination_amount = @addWithOverflow(destination.amount, ix_data.amount); - if (destination_amount[1] != 0) { - return TokenError.Overflow; - } - destination.amount = destination_amount[0]; - const supply = @addWithOverflow(mint.supply, ix_data.amount); - if (supply[1] != 0) { - return TokenError.Overflow; - } - mint.supply = supply[0]; + try mintTo(program_id, accounts, data); }, ix.InstructionDiscriminant.burn => { - if (accounts.len < 3) { - return TokenError.NotEnoughAccountKeys; - } - const ix_data: *align(1) const ix.AmountData = @ptrCast(data[1..]); - const source_account = accounts[0]; - const mint_account = accounts[1]; - const authority_account = accounts[2]; - - var source: *align(1) state.Account = @ptrCast(source_account.data()); - if (source_account.dataLen() != state.Account.len) { - return TokenError.InvalidAccountData; - } - if (source.state == state.Account.State.uninitialized) { - return TokenError.UninitializedState; - } - if (source.state == state.Account.State.frozen) { - return TokenError.AccountFrozen; - } - - if (source.isNative()) { - return TokenError.NativeNotSupported; - } - if (!mint_account.id().equals(source.mint)) { - return TokenError.MintMismatch; - } - - var mint: *align(1) state.Mint = @ptrCast(mint_account.data()); - if (mint_account.dataLen() != state.Mint.len) { - return TokenError.InvalidAccountData; - } - if (mint.is_initialized != 1) { - return TokenError.UninitializedState; - } - - if (source.amount < ix_data.amount) { - return TokenError.InsufficientFunds; - } - - //if let Some(expected_decimals) = expected_decimals { - // if expected_decimals != mint.decimals { - // return Err(TokenError::MintDecimalsMismatch.into()); - // } - //} - - //if !source_account.is_owned_by_system_program_or_incinerator() { - //match source_account.delegate { - //COption::Some(ref delegate) if Self::cmp_pubkeys(authority_info.key, delegate) => { - //Self::validate_owner( - //program_id, - //delegate, - //authority_info, - //account_info_iter.as_slice(), - //)?; - - //if source_account.delegated_amount < amount { - //return Err(TokenError::InsufficientFunds.into()); - //} - //source_account.delegated_amount = source_account - //.delegated_amount - //.checked_sub(amount) - //.ok_or(TokenError::Overflow)?; - //if source_account.delegated_amount == 0 { - //source_account.delegate = COption::None; - //} - //} - //} - //} - try validateOwner( - program_id, - &source.owner, - authority_account, - accounts[3..], - ); - - if (ix_data.amount == 0) { - if (!mint_account.ownerId().equals(program_id.*)) { - return TokenError.IllegalOwner; - } - if (!source_account.ownerId().equals(program_id.*)) { - return TokenError.IllegalOwner; - } - } - - source.amount -= ix_data.amount; - mint.supply -= ix_data.amount; + //sol.log("Instruction: Burn"); + try burn(program_id, accounts, data); }, ix.InstructionDiscriminant.close_account => { - if (accounts.len < 3) { - return TokenError.NotEnoughAccountKeys; - } - const source_account = accounts[0]; - const destination_account = accounts[1]; - const authority_account = accounts[2]; - - var source: *align(1) state.Account = @ptrCast(source_account.data()); - if (source_account.dataLen() != state.Account.len) { - return TokenError.InvalidAccountData; - } - if (source.state == state.Account.State.uninitialized) { - return TokenError.UninitializedState; - } - if (source.state == state.Account.State.frozen) { - return TokenError.AccountFrozen; - } - if (!source.isNative() and source.amount != 0) { - return TokenError.NonNativeHasBalance; - } - - const authority = if (source.close_authority.is_some != 0) - source.close_authority.value - else - source.owner; - - //if !source_account.is_owned_by_system_program_or_incinerator() { - //Self::validate_owner( - //program_id, - //&authority, - //authority_info, - //account_info_iter.as_slice(), - //)?; - //} else if !solana_program::incinerator::check_id(destination_account_info.key) { - //return Err(ProgramError::InvalidAccountData); - //} - - try validateOwner( - program_id, - &authority, - authority_account, - accounts[3..], - ); - - destination_account.lamports().* += source_account.lamports().*; - source_account.lamports().* = 0; - source_account.assign(system_program_id); - source_account.reallocUnchecked(0); - - // if the destination has no more lamports, then this was a self-close, - // which is not allowed - if (destination_account.lamports().* == 0) { - return TokenError.InvalidAccountData; - } + //sol.log("Instruction: CloseAccount"); + try closeAccount(program_id, accounts); }, ix.InstructionDiscriminant.freeze_account => { return TokenError.InvalidState; @@ -494,29 +456,3 @@ test { const std = @import("std"); std.testing.refAllDecls(@This()); } - -// TODO make public key comparisons faster -comptime { - asm ( - \\.global my_func; - \\.type my_func, @function; - \\my_func: - \\ ldxdw r3, [r1 + 0] - \\ ldxdw r4, [r2 + 0] - \\ jne r3, r4, error - \\ ldxdw r3, [r1 + 8] - \\ ldxdw r4, [r2 + 8] - \\ jne r3, r4, error - \\ ldxdw r3, [r1 + 16] - \\ ldxdw r4, [r2 + 16] - \\ jne r3, r4, error - \\ ldxdw r3, [r1 + 24] - \\ ldxdw r4, [r2 + 24] - \\ jne r3, r4, error - \\ mov64 r0, 1 - \\ exit - \\error: - \\ exit - ); -} -extern fn my_func(a: *align(1) const PublicKey, b: *align(1) const PublicKey) bool; diff --git a/token/zig/src/state.zig b/token/zig/src/state.zig index 5ce26b7..179ca50 100644 --- a/token/zig/src/state.zig +++ b/token/zig/src/state.zig @@ -1,5 +1,6 @@ const std = @import("std"); const PublicKey = @import("solana-program-sdk").PublicKey; +const TokenError = @import("error.zig").TokenError; pub const Mint = packed struct { pub const len = 82; @@ -9,6 +10,17 @@ pub const Mint = packed struct { decimals: u8, is_initialized: u8, freeze_authority: COption(PublicKey), + + pub fn fromBytes(data: []u8) TokenError!*align(1) Mint { + const mint: *align(1) Mint = @ptrCast(data); + if (data.len != Mint.len) { + return TokenError.InvalidAccountData; + } + if (mint.is_initialized != 1) { + return TokenError.UninitializedState; + } + return mint; + } }; pub const Account = packed struct { @@ -32,6 +44,20 @@ pub const Account = packed struct { pub fn isNative(self: Account) bool { return self.is_native.is_some != 0; } + + pub fn fromBytes(data: []u8) TokenError!*align(1) Account { + const account: *align(1) Account = @ptrCast(data); + if (data.len != Account.len) { + return TokenError.InvalidAccountData; + } + if (account.state == Account.State.uninitialized) { + return TokenError.UninitializedState; + } + if (account.state == Account.State.frozen) { + return TokenError.AccountFrozen; + } + return account; + } }; pub fn COption(T: type) type {