Skip to content

Commit 85e9283

Browse files
committed
realloc/steel - zero init, assertion
1 parent 55bf520 commit 85e9283

File tree

7 files changed

+221
-49
lines changed

7 files changed

+221
-49
lines changed

basics/realloc/steel/api/src/instruction/extend.rs

Lines changed: 9 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use sysvar::rent::Rent;
55
use super::SteelInstruction;
66

77
instruction!(SteelInstruction, ExtendAddressInfo);
8-
/// create address info
8+
/// extend address info
99
#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
1010
#[repr(C, packed)]
1111
pub struct ExtendAddressInfo {
@@ -25,50 +25,33 @@ impl ExtendAddressInfo {
2525
//
2626
let address_info_extended_data = Self::try_from_bytes(data)?.address_info;
2727

28-
let address_info = address_info_account.as_account::<AddressInfo>(&crate::ID)?;
29-
30-
// We collect our current address data and combime with the
31-
// data extension
32-
//
33-
let extended_address_info_data = ExtendedAddressInfo {
34-
name: address_info.name,
35-
house_number: address_info.house_number,
36-
street: address_info.street,
37-
city: address_info.city,
38-
zip: address_info_extended_data.zip,
39-
state: address_info_extended_data.state,
40-
};
41-
4228
// We get pay for the extra space we need before allocating the space
4329
//
44-
let account_span = 8 + (extended_address_info_data.to_bytes()).len();
30+
let account_span = 8 + std::mem::size_of::<ExtendedAddressInfo>(); // 8 byte Account Discriminator + sizeof ExtendedAddressInfo
4531
let lamports_required = (Rent::get()?).minimum_balance(account_span);
46-
let diff = lamports_required - address_info_account.lamports();
47-
48-
// transfer the difference
32+
let diff = lamports_required.saturating_sub(address_info_account.lamports());
4933
address_info_account.collect(diff, payer)?;
5034

5135
// we reallocate new space to accomodate the new data
5236
//
53-
address_info_account.realloc(account_span, false)?;
37+
address_info_account.realloc(account_span, false)?; // no zero init
5438

55-
// we set the discriminator to the `ExtendedAccountInfo`, so Steel can deserialize.
56-
// the account as such.
39+
// we set the discriminator to the `ExtendedAccountInfo`, so Steel can deserialize the account as such.
5740
//
5841
{
5942
let mut data = address_info_account.data.borrow_mut();
60-
// reset the account discriminator
61-
data[0] = ExtendAddressInfo::discriminator();
43+
data[0] = ExtendedAddressInfo::discriminator();
6244
}
6345

64-
// now we reset the account discriminator, we can deserialise as `ExtendedAddressInfo
46+
// now we reset the account discriminator, we can deserialise as `ExtendedAddressInfo`
6547
//
6648
let extended_address_info =
6749
address_info_account.as_account_mut::<ExtendedAddressInfo>(&crate::ID)?;
6850

6951
// set the extended address info
7052
//
71-
*extended_address_info = extended_address_info_data;
53+
extended_address_info.state = address_info_extended_data.state;
54+
extended_address_info.zip = address_info_extended_data.zip;
7255

7356
Ok(())
7457
}

basics/realloc/steel/api/src/instruction/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ pub use create::*;
44
pub mod extend;
55
pub use extend::*;
66

7+
pub mod zero_init;
8+
pub use zero_init::*;
9+
710
use steel::*;
811

912
#[repr(u8)]
@@ -12,4 +15,5 @@ use steel::*;
1215
pub enum SteelInstruction {
1316
CreateAddressInfo = 0,
1417
ExtendAddressInfo = 1,
18+
ZeroInit = 2,
1519
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use crate::state::*;
2+
use steel::*;
3+
use sysvar::rent::Rent;
4+
5+
use super::SteelInstruction;
6+
7+
instruction!(SteelInstruction, ZeroInit);
8+
/// work info
9+
#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
10+
#[repr(C)]
11+
pub struct ZeroInit {
12+
pub work_info: WorkInfo,
13+
}
14+
15+
impl ZeroInit {
16+
pub fn process(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
17+
// Steel uses zero_copy types, so the sizes are fixed, however we can move from one account
18+
// type to another, e.g ExtendedAddressInfo -> WorkInfo
19+
20+
let [payer, address_info_account, _system_program] = accounts else {
21+
return Err(ProgramError::NotEnoughAccountKeys);
22+
};
23+
24+
// we collect the extended data
25+
//
26+
let work_info_data = Self::try_from_bytes(data)?.work_info;
27+
28+
// We get pay for the extra space we need before allocating the space
29+
//
30+
let account_span = 8 + std::mem::size_of::<WorkInfo>(); // 8 byte Account Discriminator + sizeof WorkInfo
31+
let lamports_required = (Rent::get()?).minimum_balance(account_span);
32+
let diff = lamports_required.saturating_sub(address_info_account.lamports());
33+
address_info_account.collect(diff, payer)?;
34+
35+
// we reallocate new space to accomodate the new data
36+
//
37+
address_info_account.realloc(account_span, true)?; // zero init
38+
39+
// we set the discriminator to the `WorkInfo`, so Steel can deserialize the account as such.
40+
//
41+
{
42+
let mut data = address_info_account.data.borrow_mut();
43+
data[0] = WorkInfo::discriminator();
44+
}
45+
46+
// now we reset the account discriminator, we can deserialise as `WorkInfo``
47+
//
48+
let work_info = address_info_account.as_account_mut::<WorkInfo>(&crate::ID)?;
49+
50+
// set the extended address info
51+
//
52+
*work_info = work_info_data;
53+
54+
Ok(())
55+
}
56+
}

basics/realloc/steel/api/src/state.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ use steel::*;
55
pub enum SteelAccount {
66
AddressInfo,
77
ExtendedAddressInfo,
8+
WorkInfo,
89
}
910

1011
account!(SteelAccount, AddressInfo);
11-
#[repr(C, packed)]
12+
#[repr(C)]
1213
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
1314
pub struct AddressInfo {
1415
pub name: [u8; 48],
@@ -29,7 +30,17 @@ pub struct ExtendedAddressInfo {
2930
pub zip: u32,
3031
}
3132

32-
#[repr(C, packed)]
33+
account!(SteelAccount, WorkInfo);
34+
#[repr(C)]
35+
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
36+
pub struct WorkInfo {
37+
pub name: [u8; 48],
38+
pub position: [u8; 48],
39+
pub company: [u8; 48],
40+
pub years_employed: u8,
41+
}
42+
43+
#[repr(C)]
3344
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
3445
pub struct EnhancedAddressInfoExtender {
3546
pub state: [u8; 48],

basics/realloc/steel/program/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ pub fn process_instruction(
1313
match ix {
1414
SteelInstruction::CreateAddressInfo => CreateAddressInfo::process(accounts, data),
1515
SteelInstruction::ExtendAddressInfo => ExtendAddressInfo::process(accounts, data),
16+
SteelInstruction::ZeroInit => ZeroInit::process(accounts, data),
1617
}
1718
}

basics/realloc/steel/tests/schema.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as borsh from 'borsh';
44
export enum ReallocInstruction {
55
Create = 0,
66
Extend = 1,
7+
ZeroInit = 2,
78
}
89

910
export class AddressInfo {
@@ -165,3 +166,70 @@ const AddressInfoExtenderSchema = new Map([
165166
},
166167
],
167168
]);
169+
170+
export class WorkInfo {
171+
name: Uint8Array;
172+
position: Uint8Array;
173+
company: Uint8Array;
174+
years_employed: number;
175+
176+
constructor(info: {
177+
name: Uint8Array;
178+
position: Uint8Array;
179+
company: Uint8Array;
180+
years_employed: number;
181+
}) {
182+
this.name = info.name;
183+
this.position = info.position;
184+
this.company = info.company;
185+
this.years_employed = info.years_employed;
186+
}
187+
188+
toBuffer() {
189+
return Buffer.from(borsh.serialize(WorkInfoSchema, this));
190+
}
191+
192+
static fromAccountData(buffer: Buffer) {
193+
const _accountData = Uint8Array.from(buffer).slice(8); // remove 8 byte discriminator
194+
195+
return borsh.deserialize(WorkInfoSchema, WorkInfo, Buffer.from(_accountData));
196+
}
197+
198+
static fromData(info: {
199+
name: string;
200+
position: string;
201+
company: string;
202+
years_employed: number;
203+
}) {
204+
return new WorkInfo({
205+
name: Uint8Array.from(Buffer.from(info.name.padEnd(48, '\0'))),
206+
position: Uint8Array.from(Buffer.from(info.position.padEnd(48, '\0'))),
207+
company: Uint8Array.from(Buffer.from(info.company.padEnd(48, '\0'))),
208+
years_employed: info.years_employed,
209+
});
210+
}
211+
212+
toData() {
213+
return {
214+
name: Buffer.from(this.name).toString(),
215+
position: Buffer.from(this.position).toString(),
216+
company: Buffer.from(this.company).toString(),
217+
years_employed: this.years_employed,
218+
};
219+
}
220+
}
221+
222+
const WorkInfoSchema = new Map([
223+
[
224+
WorkInfo,
225+
{
226+
kind: 'struct',
227+
fields: [
228+
['name', [48]], // Fixed array of 48 bytes
229+
['position', [48]], // Fixed array of 48 bytes
230+
['company', [48]], // Fixed array of 48 bytes
231+
['years_employed', 'u8'],
232+
],
233+
},
234+
],
235+
]);

basics/realloc/steel/tests/test.ts

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { Buffer } from 'node:buffer';
22
import { describe, test } from 'node:test';
33
import { Keypair, PublicKey, SystemProgram, Transaction, TransactionInstruction } from '@solana/web3.js';
4+
import { assert } from 'chai';
45
import { start } from 'solana-bankrun';
5-
import { AddressInfo, AddressInfoExtender, ExtendedAddressInfo, ReallocInstruction } from './schema';
6+
import { AddressInfo, AddressInfoExtender, ExtendedAddressInfo, ReallocInstruction, WorkInfo } from './schema';
67

78
describe('Realloc!', async () => {
89
const PROGRAM_ID = new PublicKey('z7msBPQHDJjTvdQRoEcKyENgXDhSRYeHieN1ZMTqo35');
@@ -18,15 +19,14 @@ describe('Realloc!', async () => {
1819
console.log(`Payer Address : ${payer.publicKey}`);
1920
console.log(`Address Info Acct : ${addressInfoAccount.publicKey}`);
2021

21-
const ixData = Buffer.concat([
22-
Buffer.from([ReallocInstruction.Create]),
23-
AddressInfo.fromData({
24-
name: 'Joe C',
25-
house_number: 136,
26-
street: 'Mile High Dr.',
27-
city: 'Solana Beach',
28-
}).toBuffer(),
29-
]);
22+
const addressInfo = {
23+
name: 'Joe C',
24+
house_number: 136,
25+
street: 'Mile High Dr.',
26+
city: 'Solana Beach',
27+
};
28+
29+
const ixData = Buffer.concat([Buffer.from([ReallocInstruction.Create]), AddressInfo.fromData(addressInfo).toBuffer()]);
3030

3131
const ix = new TransactionInstruction({
3232
keys: [
@@ -48,9 +48,7 @@ describe('Realloc!', async () => {
4848
tx.recentBlockhash = blockhash;
4949
tx.add(ix).sign(payer, addressInfoAccount);
5050
await client.processTransaction(tx);
51-
});
5251

53-
test("Read the new account's data", async () => {
5452
const accountInfo = await client.getAccount(addressInfoAccount.publicKey);
5553

5654
const readAddressInfo = AddressInfo.fromAccountData(Buffer.from(accountInfo.data)).toData();
@@ -59,16 +57,20 @@ describe('Realloc!', async () => {
5957
console.log(`House Num: ${readAddressInfo.house_number}`);
6058
console.log(`Street : ${readAddressInfo.street}`);
6159
console.log(`City : ${readAddressInfo.city}`);
60+
61+
assert(readAddressInfo.name.slice(0, addressInfo.name.length) === addressInfo.name, 'name does not match');
62+
assert(readAddressInfo.house_number === addressInfo.house_number, 'house number does not match');
63+
assert(readAddressInfo.street.slice(0, addressInfo.street.length) === addressInfo.street, 'street does not match');
64+
assert(readAddressInfo.city.slice(0, addressInfo.city.length) === addressInfo.city, 'city does not match');
6265
});
6366

6467
test('Extend the address info account', async () => {
65-
const ixData = Buffer.concat([
66-
Buffer.from([ReallocInstruction.Extend]),
67-
AddressInfoExtender.fromData({
68-
state: 'Illinois',
69-
zip: 12345,
70-
}).toBuffer(),
71-
]);
68+
const addressInfoExtender = {
69+
state: 'Illinois',
70+
zip: 12345,
71+
};
72+
73+
const ixData = Buffer.concat([Buffer.from([ReallocInstruction.Extend]), AddressInfoExtender.fromData(addressInfoExtender).toBuffer()]);
7274

7375
const ix = new TransactionInstruction({
7476
keys: [
@@ -90,9 +92,7 @@ describe('Realloc!', async () => {
9092
tx.recentBlockhash = blockhash;
9193
tx.add(ix).sign(payer, addressInfoAccount);
9294
await client.processTransaction(tx);
93-
});
9495

95-
test("Read the new account's data", async () => {
9696
const accountInfo = await client.getAccount(addressInfoAccount.publicKey);
9797

9898
const readAddressInfo = ExtendedAddressInfo.fromAccountData(Buffer.from(accountInfo.data)).toData();
@@ -103,5 +103,54 @@ describe('Realloc!', async () => {
103103
console.log(`City : ${readAddressInfo.city}`);
104104
console.log(`State : ${readAddressInfo.state}`);
105105
console.log(`Zip : ${readAddressInfo.zip}`);
106+
107+
assert(readAddressInfo.state.slice(0, addressInfoExtender.state.length) === addressInfoExtender.state, 'state does not match');
108+
assert(readAddressInfo.zip === addressInfoExtender.zip, 'zip does not match');
109+
});
110+
111+
test('zero init work info account', async () => {
112+
const workInfo = {
113+
name: 'Pete',
114+
company: 'Solana Labs',
115+
position: 'Engineer',
116+
years_employed: 2,
117+
};
118+
119+
const ixData = Buffer.concat([Buffer.from([ReallocInstruction.ZeroInit]), WorkInfo.fromData(workInfo).toBuffer()]);
120+
121+
const ix = new TransactionInstruction({
122+
keys: [
123+
{ pubkey: payer.publicKey, isSigner: true, isWritable: true },
124+
{
125+
pubkey: addressInfoAccount.publicKey,
126+
isSigner: true,
127+
isWritable: true,
128+
},
129+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
130+
],
131+
programId: PROGRAM_ID,
132+
data: ixData,
133+
});
134+
135+
const blockhash = context.lastBlockhash;
136+
137+
const tx = new Transaction();
138+
tx.recentBlockhash = blockhash;
139+
tx.add(ix).sign(payer, addressInfoAccount);
140+
await client.processTransaction(tx);
141+
142+
const accountInfo = await client.getAccount(addressInfoAccount.publicKey);
143+
144+
const readWorkInfo = WorkInfo.fromAccountData(Buffer.from(accountInfo.data)).toData();
145+
146+
console.log(`Name : ${readWorkInfo.name}`);
147+
console.log(`Position : ${readWorkInfo.position}`);
148+
console.log(`Company : ${readWorkInfo.company}`);
149+
console.log(`Years Employed: ${readWorkInfo.years_employed}`);
150+
151+
assert(readWorkInfo.name.slice(0, workInfo.name.length) === workInfo.name, 'name does not match');
152+
assert(readWorkInfo.position.slice(0, workInfo.position.length) === workInfo.position, 'position does not match');
153+
assert(readWorkInfo.company.slice(0, workInfo.company.length) === workInfo.company, 'company does not match');
154+
assert(readWorkInfo.years_employed === workInfo.years_employed, 'years employed does not match');
106155
});
107156
});

0 commit comments

Comments
 (0)