Skip to content

Commit 2ced816

Browse files
committed
token-zig: Avoid so many pubkey comparisons
#### Problem Pubkey comparisons are expensive operations on-chain, but they aren't always required. #### Summary of changes Update the Zig Token program to avoid pubkey comparisons, which gives much better performance. Also, update the performance numbers.
1 parent 2451bde commit 2ced816

File tree

2 files changed

+49
-49
lines changed

2 files changed

+49
-49
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ Token program.
197197
| Language | CU Usage |
198198
| --- | --- |
199199
| Rust | 1115 |
200-
| Zig | 165 |
200+
| Zig | 166 |
201201

202202
* Initialize Account
203203

@@ -211,25 +211,25 @@ Token program.
211211
| Language | CU Usage |
212212
| --- | --- |
213213
| Rust | 2189 |
214-
| Zig | 215 |
214+
| Zig | 195 |
215215

216216
* Transfer
217217

218218
| Language | CU Usage |
219219
| --- | --- |
220220
| Rust | 2208 |
221-
| Zig | 205 |
221+
| Zig | 160 |
222222

223223
* Burn
224224

225225
| Language | CU Usage |
226226
| --- | --- |
227227
| Rust | 2045 |
228-
| Zig | 175 |
228+
| Zig | 157 |
229229

230230
* Close Account
231231

232232
| Language | CU Usage |
233233
| --- | --- |
234234
| Rust | 1483 |
235-
| Zig | 291 |
235+
| Zig | 260 |

token/zig/src/main.zig

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -22,31 +22,30 @@ fn processInstruction(program_id: *align(1) PublicKey, accounts: []sol.Account,
2222
if (accounts.len < 2) {
2323
return TokenError.NotEnoughAccountKeys;
2424
}
25-
const init: *align(1) const ix.InitializeMintData = @ptrCast(data[1..]);
25+
const ix_data: *align(1) const ix.InitializeMintData = @ptrCast(data[1..]);
2626
const mint_account = accounts[0];
2727
const rent_sysvar = accounts[1];
28-
if (!rent_sysvar.id().equals(Rent.id)) {
29-
return TokenError.InvalidAccountData;
30-
}
3128

29+
var mint: *align(1) state.Mint = @ptrCast(mint_account.data());
3230
if (mint_account.dataLen() != state.Mint.len) {
3331
return TokenError.InvalidAccountData;
3432
}
35-
36-
var mint: *align(1) state.Mint = @ptrCast(mint_account.data());
3733
if (mint.is_initialized == 1) {
3834
return TokenError.AlreadyInUse;
3935
}
4036

4137
const rent: *align(1) Rent.Data = @ptrCast(rent_sysvar.data());
38+
if (!rent_sysvar.id().equals(Rent.id)) {
39+
return TokenError.InvalidAccountData;
40+
}
4241
if (!rent.isExempt(mint_account.lamports().*, mint_account.dataLen())) {
4342
return TokenError.NotRentExempt;
4443
}
4544

46-
mint.mint_authority = state.COption(PublicKey).fromValue(init.mint_authority);
47-
mint.decimals = init.decimals;
45+
mint.mint_authority = state.COption(PublicKey).fromValue(ix_data.mint_authority);
46+
mint.decimals = ix_data.decimals;
4847
mint.is_initialized = 1;
49-
mint.freeze_authority = init.freeze_authority.toCOption();
48+
mint.freeze_authority = ix_data.freeze_authority.toCOption();
5049
},
5150
ix.InstructionDiscriminant.initialize_account => {
5251
//sol.log("Instruction: InitializeAccount");
@@ -59,14 +58,13 @@ fn processInstruction(program_id: *align(1) PublicKey, accounts: []sol.Account,
5958
const rent_sysvar = accounts[3];
6059
const rent: *align(1) Rent.Data = @ptrCast(rent_sysvar.data());
6160

61+
var account: *align(1) state.Account = @ptrCast(token_account.data());
6262
if (token_account.dataLen() != state.Account.len) {
6363
return TokenError.InvalidAccountData;
6464
}
65-
var account: *align(1) state.Account = @ptrCast(token_account.data());
6665
if (account.state != state.Account.State.uninitialized) {
6766
return TokenError.AlreadyInUse;
6867
}
69-
7068
if (!rent.isExempt(token_account.lamports().*, token_account.dataLen())) {
7169
return TokenError.NotRentExempt;
7270
}
@@ -80,11 +78,10 @@ fn processInstruction(program_id: *align(1) PublicKey, accounts: []sol.Account,
8078
if (mint_account.id().equals(native_mint_id)) {
8179
const rent_exempt_reserve = rent.getMinimumBalance(token_account.dataLen());
8280
account.is_native = state.COption(u64).fromValue(rent_exempt_reserve);
83-
const amount = @subWithOverflow(token_account.lamports().*, rent_exempt_reserve);
84-
if (amount[1] != 0) {
81+
if (rent_exempt_reserve > token_account.lamports().*) {
8582
return TokenError.Overflow;
8683
}
87-
account.amount = amount[0];
84+
account.amount = token_account.lamports().* - rent_exempt_reserve;
8885
} else {
8986
if (!mint_account.ownerId().equals(program_id.*)) {
9087
return TokenError.IllegalOwner;
@@ -109,41 +106,40 @@ fn processInstruction(program_id: *align(1) PublicKey, accounts: []sol.Account,
109106
if (accounts.len < 3) {
110107
return TokenError.NotEnoughAccountKeys;
111108
}
112-
const transfer_data: *align(1) const ix.AmountData = @ptrCast(data[1..]);
109+
const ix_data: *align(1) const ix.AmountData = @ptrCast(data[1..]);
113110
const source_account = accounts[0];
114111
const destination_account = accounts[1];
115112
const authority_account = accounts[2];
116113

114+
var source: *align(1) state.Account = @ptrCast(source_account.data());
117115
if (source_account.dataLen() != state.Account.len) {
118116
return TokenError.InvalidAccountData;
119117
}
120-
var source: *align(1) state.Account = @ptrCast(source_account.data());
121118
if (source.state == state.Account.State.uninitialized) {
122119
return TokenError.UninitializedState;
123120
}
124121
if (source.state == state.Account.State.frozen) {
125122
return TokenError.AccountFrozen;
126123
}
127124

125+
var destination: *align(1) state.Account = @ptrCast(destination_account.data());
128126
if (destination_account.dataLen() != state.Account.len) {
129127
return TokenError.InvalidAccountData;
130128
}
131-
var destination: *align(1) state.Account = @ptrCast(destination_account.data());
132129
if (destination.state == state.Account.State.uninitialized) {
133130
return TokenError.UninitializedState;
134131
}
135132
if (destination.state == state.Account.State.frozen) {
136133
return TokenError.AccountFrozen;
137134
}
138135

139-
if (source.amount < transfer_data.amount) {
136+
if (source.amount < ix_data.amount) {
140137
return TokenError.InsufficientFunds;
141138
}
142139
if (!source.mint.equals(destination.mint)) {
143140
return TokenError.MintMismatch;
144141
}
145142

146-
const self_transfer = source_account.id().equals(destination_account.id());
147143
//match source_account.delegate {
148144
// COption::Some(ref delegate) if Self::cmp_pubkeys(authority_info.key, delegate) => {
149145
// Self::validate_owner(
@@ -172,22 +168,23 @@ fn processInstruction(program_id: *align(1) PublicKey, accounts: []sol.Account,
172168
accounts[3..],
173169
);
174170

175-
if (self_transfer or transfer_data.amount == 0) {
171+
const pre_amount = source.amount;
172+
source.amount -= ix_data.amount;
173+
destination.amount += ix_data.amount;
174+
175+
if (source.isNative()) {
176+
source_account.lamports().* -= ix_data.amount;
177+
destination_account.lamports().* += ix_data.amount;
178+
}
179+
180+
if (pre_amount == source.amount) {
181+
// self transfer or 0 token amount, check owners for safety
176182
if (!source_account.ownerId().equals(program_id.*)) {
177183
return TokenError.IllegalOwner;
178184
}
179185
if (!destination_account.ownerId().equals(program_id.*)) {
180186
return TokenError.IllegalOwner;
181187
}
182-
return;
183-
}
184-
185-
source.amount -= transfer_data.amount;
186-
destination.amount += transfer_data.amount;
187-
188-
if (source.isNative()) {
189-
source_account.lamports().* -= transfer_data.amount;
190-
destination_account.lamports().* += transfer_data.amount;
191188
}
192189
},
193190
ix.InstructionDiscriminant.approve => {
@@ -208,28 +205,28 @@ fn processInstruction(program_id: *align(1) PublicKey, accounts: []sol.Account,
208205
const mint_account = accounts[0];
209206
const destination_account = accounts[1];
210207
const authority_account = accounts[2];
208+
209+
var destination: *align(1) state.Account = @ptrCast(destination_account.data());
211210
if (destination_account.dataLen() != state.Account.len) {
212211
return TokenError.InvalidAccountData;
213212
}
214-
var destination: *align(1) state.Account = @ptrCast(destination_account.data());
215213
if (destination.state == state.Account.State.uninitialized) {
216214
return TokenError.UninitializedState;
217215
}
218216
if (destination.state == state.Account.State.frozen) {
219217
return TokenError.AccountFrozen;
220218
}
221-
222219
if (destination.isNative()) {
223220
return TokenError.NativeNotSupported;
224221
}
225222
if (!mint_account.id().equals(destination.mint)) {
226223
return TokenError.MintMismatch;
227224
}
228225

226+
var mint: *align(1) state.Mint = @ptrCast(mint_account.data());
229227
if (mint_account.dataLen() != state.Mint.len) {
230228
return TokenError.InvalidAccountData;
231229
}
232-
var mint: *align(1) state.Mint = @ptrCast(mint_account.data());
233230
if (mint.is_initialized != 1) {
234231
return TokenError.UninitializedState;
235232
}
@@ -274,15 +271,15 @@ fn processInstruction(program_id: *align(1) PublicKey, accounts: []sol.Account,
274271
if (accounts.len < 3) {
275272
return TokenError.NotEnoughAccountKeys;
276273
}
277-
const burn_data: *align(1) const ix.AmountData = @ptrCast(data[1..]);
274+
const ix_data: *align(1) const ix.AmountData = @ptrCast(data[1..]);
278275
const source_account = accounts[0];
279276
const mint_account = accounts[1];
280277
const authority_account = accounts[2];
281278

279+
var source: *align(1) state.Account = @ptrCast(source_account.data());
282280
if (source_account.dataLen() != state.Account.len) {
283281
return TokenError.InvalidAccountData;
284282
}
285-
var source: *align(1) state.Account = @ptrCast(source_account.data());
286283
if (source.state == state.Account.State.uninitialized) {
287284
return TokenError.UninitializedState;
288285
}
@@ -297,15 +294,15 @@ fn processInstruction(program_id: *align(1) PublicKey, accounts: []sol.Account,
297294
return TokenError.MintMismatch;
298295
}
299296

297+
var mint: *align(1) state.Mint = @ptrCast(mint_account.data());
300298
if (mint_account.dataLen() != state.Mint.len) {
301299
return TokenError.InvalidAccountData;
302300
}
303-
var mint: *align(1) state.Mint = @ptrCast(mint_account.data());
304301
if (mint.is_initialized != 1) {
305302
return TokenError.UninitializedState;
306303
}
307304

308-
if (source.amount < burn_data.amount) {
305+
if (source.amount < ix_data.amount) {
309306
return TokenError.InsufficientFunds;
310307
}
311308

@@ -345,7 +342,7 @@ fn processInstruction(program_id: *align(1) PublicKey, accounts: []sol.Account,
345342
accounts[3..],
346343
);
347344

348-
if (burn_data.amount == 0) {
345+
if (ix_data.amount == 0) {
349346
if (!mint_account.ownerId().equals(program_id.*)) {
350347
return TokenError.IllegalOwner;
351348
}
@@ -354,8 +351,8 @@ fn processInstruction(program_id: *align(1) PublicKey, accounts: []sol.Account,
354351
}
355352
}
356353

357-
source.amount -= burn_data.amount;
358-
mint.supply -= burn_data.amount;
354+
source.amount -= ix_data.amount;
355+
mint.supply -= ix_data.amount;
359356
},
360357
ix.InstructionDiscriminant.close_account => {
361358
if (accounts.len < 3) {
@@ -365,19 +362,16 @@ fn processInstruction(program_id: *align(1) PublicKey, accounts: []sol.Account,
365362
const destination_account = accounts[1];
366363
const authority_account = accounts[2];
367364

365+
var source: *align(1) state.Account = @ptrCast(source_account.data());
368366
if (source_account.dataLen() != state.Account.len) {
369367
return TokenError.InvalidAccountData;
370368
}
371-
var source: *align(1) state.Account = @ptrCast(source_account.data());
372369
if (source.state == state.Account.State.uninitialized) {
373370
return TokenError.UninitializedState;
374371
}
375372
if (source.state == state.Account.State.frozen) {
376373
return TokenError.AccountFrozen;
377374
}
378-
if (source_account.id().equals(destination_account.id())) {
379-
return TokenError.InvalidAccountData;
380-
}
381375
if (!source.isNative() and source.amount != 0) {
382376
return TokenError.NonNativeHasBalance;
383377
}
@@ -409,6 +403,12 @@ fn processInstruction(program_id: *align(1) PublicKey, accounts: []sol.Account,
409403
source_account.lamports().* = 0;
410404
source_account.assign(system_program_id);
411405
source_account.reallocUnchecked(0);
406+
407+
// if the destination has no more lamports, then this was a self-close,
408+
// which is not allowed
409+
if (destination_account.lamports().* == 0) {
410+
return TokenError.InvalidAccountData;
411+
}
412412
},
413413
ix.InstructionDiscriminant.freeze_account => {
414414
return TokenError.InvalidState;
@@ -467,7 +467,7 @@ fn validateOwner(
467467
if (!expected_owner.equals(owner_account.id())) {
468468
return TokenError.OwnerMismatch;
469469
}
470-
if (program_id.equals(owner_account.ownerId()) and owner_account.dataLen() == state.Multisig.len) {
470+
if (owner_account.dataLen() == state.Multisig.len and program_id.equals(owner_account.ownerId())) {
471471
//let multisig = Multisig::unpack(&owner_account_info.data.borrow())?;
472472
//let mut num_signers = 0;
473473
//let mut matched = [false; MAX_SIGNERS];

0 commit comments

Comments
 (0)