Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit c6c8398

Browse files
stegaBOBjoncinque
andauthored
Feat: SNS Realloc Name Account (#3955)
* add new realloc ix * add tests * update param order to be consistent with other builders * add realloc ix to instructions.ts * ts bindings for realloc * ts linting * fix logs and clippy arithmetic * satisfy clippy * Update name-service/program/src/instruction.rs Co-authored-by: Jon Cinque <[email protected]> * Update name-service/program/src/instruction.rs Co-authored-by: Jon Cinque <[email protected]> * docs nits * fix import ordering * Update name-service/program/tests/functional.rs Co-authored-by: Jon Cinque <[email protected]> * blockhash nits * Update name-service/js/src/bindings.ts Co-authored-by: Jon Cinque <[email protected]> * fix hashed_name naming Co-authored-by: Jon Cinque <[email protected]>
1 parent c4269cb commit c6c8398

File tree

5 files changed

+243
-2
lines changed

5 files changed

+243
-2
lines changed

name-service/js/src/bindings.ts

+47
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
import {
99
createInstruction,
1010
deleteInstruction,
11+
reallocInstruction,
1112
transferInstruction,
1213
updateInstruction,
1314
} from './instructions';
@@ -217,3 +218,49 @@ export async function deleteNameRegistry(
217218

218219
return changeAuthoritiesInstr;
219220
}
221+
222+
/**
223+
* Realloc the name account space.
224+
*
225+
* @param connection The solana connection object to the RPC node
226+
* @param name The name of the name account
227+
* @param space The new space to be allocated
228+
* @param payerKey The allocation cost payer if new space is larger than current or the refund destination if smaller
229+
* @param nameClass The class of this name, if it exsists
230+
* @param nameParent The parent name of this name, if it exists
231+
* @returns
232+
*/
233+
export async function reallocNameAccount(
234+
connection: Connection,
235+
name: string,
236+
space: number,
237+
payerKey: PublicKey,
238+
nameClass?: PublicKey,
239+
nameParent?: PublicKey
240+
): Promise<TransactionInstruction> {
241+
const hashedName = await getHashedName(name);
242+
const nameAccountKey = await getNameAccountKey(
243+
hashedName,
244+
nameClass,
245+
nameParent
246+
);
247+
248+
let nameOwner: PublicKey;
249+
if (nameClass) {
250+
nameOwner = nameClass;
251+
} else {
252+
nameOwner = (await NameRegistryState.retrieve(connection, nameAccountKey))
253+
.owner;
254+
}
255+
256+
const reallocInstr = reallocInstruction(
257+
NAME_PROGRAM_ID,
258+
SystemProgram.programId,
259+
payerKey,
260+
nameAccountKey,
261+
nameOwner,
262+
new Numberu32(space)
263+
);
264+
265+
return reallocInstr;
266+
}

name-service/js/src/instructions.ts

+41
Original file line numberDiff line numberDiff line change
@@ -214,3 +214,44 @@ export function deleteInstruction(
214214
data,
215215
});
216216
}
217+
218+
export function reallocInstruction(
219+
nameProgramId: PublicKey,
220+
systemProgramId: PublicKey,
221+
payerKey: PublicKey,
222+
nameAccountKey: PublicKey,
223+
nameOwnerKey: PublicKey,
224+
space: Numberu32
225+
): TransactionInstruction {
226+
const buffers = [Buffer.from(Int8Array.from([4])), space.toBuffer()];
227+
228+
const data = Buffer.concat(buffers);
229+
const keys = [
230+
{
231+
pubkey: systemProgramId,
232+
isSigner: false,
233+
isWritable: false,
234+
},
235+
{
236+
pubkey: payerKey,
237+
isSigner: true,
238+
isWritable: true,
239+
},
240+
{
241+
pubkey: nameAccountKey,
242+
isSigner: false,
243+
isWritable: true,
244+
},
245+
{
246+
pubkey: nameOwnerKey,
247+
isSigner: true,
248+
isWritable: false,
249+
},
250+
];
251+
252+
return new TransactionInstruction({
253+
keys,
254+
programId: nameProgramId,
255+
data,
256+
});
257+
}

name-service/program/src/instruction.rs

+40
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,23 @@ pub enum NameRegistryInstruction {
8989
/// 2. `[writeable]` Refund account
9090
///
9191
Delete,
92+
93+
/// Realloc the data of a name record.
94+
///
95+
/// The space change cannot be more than `MAX_PERMITTED_DATA_LENGTH` greater than
96+
/// current `space`.
97+
///
98+
/// Accounts expected by this instruction:
99+
/// 0. `[]` System program
100+
/// 1. `[writeable, signer]` Payer account (will be refunded if new `space` is less than current `space`)
101+
/// 2. `[writeable]` Name record to be reallocated
102+
/// 3. `[signer]` Account owner
103+
///
104+
Realloc {
105+
/// New total number of bytes in addition to the `NameRecordHeader`.
106+
/// There are no checks on the existing data; it will be truncated if the new space is less than the current space.
107+
space: u32,
108+
},
92109
}
93110

94111
#[allow(clippy::too_many_arguments)]
@@ -201,3 +218,26 @@ pub fn delete(
201218
data,
202219
})
203220
}
221+
222+
pub fn realloc(
223+
name_service_program_id: Pubkey,
224+
payer_key: Pubkey,
225+
name_account_key: Pubkey,
226+
name_owner_key: Pubkey,
227+
space: u32,
228+
) -> Result<Instruction, ProgramError> {
229+
let instruction_data = NameRegistryInstruction::Realloc { space };
230+
let data = instruction_data.try_to_vec().unwrap();
231+
let accounts = vec![
232+
AccountMeta::new_readonly(system_program::id(), false),
233+
AccountMeta::new(payer_key, true),
234+
AccountMeta::new(name_account_key, false),
235+
AccountMeta::new_readonly(name_owner_key, true),
236+
];
237+
238+
Ok(Instruction {
239+
program_id: name_service_program_id,
240+
accounts,
241+
data,
242+
})
243+
}

name-service/program/src/processor.rs

+58
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ use {
1313
program_error::ProgramError,
1414
program_pack::Pack,
1515
pubkey::Pubkey,
16+
rent::Rent,
1617
system_instruction,
18+
sysvar::Sysvar,
1719
},
20+
std::cmp::Ordering,
1821
};
1922

2023
pub struct Processor {}
@@ -239,6 +242,57 @@ impl Processor {
239242
Ok(())
240243
}
241244

245+
fn process_realloc(accounts: &[AccountInfo], space: u32) -> ProgramResult {
246+
let accounts_iter = &mut accounts.iter();
247+
let system_program = next_account_info(accounts_iter)?;
248+
let payer_account = next_account_info(accounts_iter)?;
249+
let name_account = next_account_info(accounts_iter)?;
250+
let name_owner = next_account_info(accounts_iter)?;
251+
252+
let name_record_header = NameRecordHeader::unpack_from_slice(&name_account.data.borrow())?;
253+
254+
// Verifications
255+
if !name_owner.is_signer || name_record_header.owner != *name_owner.key {
256+
msg!("The given name owner is incorrect or not a signer.");
257+
return Err(ProgramError::InvalidArgument);
258+
}
259+
260+
let new_space = NameRecordHeader::LEN.saturating_add(space as usize);
261+
let required_lamports = Rent::get()?.minimum_balance(new_space);
262+
match name_account.lamports().cmp(&required_lamports) {
263+
Ordering::Less => {
264+
// Overflow cannot happen here because we already checked the sizes.
265+
#[allow(clippy::integer_arithmetic)]
266+
let lamports_to_add = required_lamports - name_account.lamports();
267+
invoke(
268+
&system_instruction::transfer(
269+
payer_account.key,
270+
name_account.key,
271+
lamports_to_add,
272+
),
273+
&[
274+
payer_account.clone(),
275+
name_account.clone(),
276+
system_program.clone(),
277+
],
278+
)?;
279+
}
280+
Ordering::Greater => {
281+
// Overflow cannot happen here because we already checked the sizes.
282+
#[allow(clippy::integer_arithmetic)]
283+
let lamports_to_remove = name_account.lamports() - required_lamports;
284+
let source_amount: &mut u64 = &mut name_account.lamports.borrow_mut();
285+
let dest_amount: &mut u64 = &mut payer_account.lamports.borrow_mut();
286+
*source_amount = source_amount.saturating_sub(lamports_to_remove);
287+
*dest_amount = dest_amount.saturating_add(lamports_to_remove);
288+
}
289+
Ordering::Equal => {}
290+
}
291+
// Max data increase is checked in realloc. No need to check here.
292+
name_account.realloc(new_space, false)?;
293+
Ok(())
294+
}
295+
242296
pub fn process_instruction(
243297
program_id: &Pubkey,
244298
accounts: &[AccountInfo],
@@ -270,6 +324,10 @@ impl Processor {
270324
msg!("Instruction: Delete Name");
271325
Processor::process_delete(accounts)?;
272326
}
327+
NameRegistryInstruction::Realloc { space } => {
328+
msg!("Instruction: Realloc Name Record");
329+
Processor::process_realloc(accounts, space)?;
330+
}
273331
}
274332
Ok(())
275333
}

name-service/program/tests/functional.rs

+57-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
use std::str::FromStr;
33

44
use solana_program::{instruction::Instruction, program_pack::Pack, pubkey::Pubkey};
5-
use solana_program_test::{processor, tokio, ProgramTest, ProgramTestContext};
5+
use solana_program_test::{
6+
processor, tokio, ProgramTest, ProgramTestBanksClientExt, ProgramTestContext,
7+
};
68

79
use solana_program::hash::hashv;
810
use solana_sdk::{
@@ -11,7 +13,7 @@ use solana_sdk::{
1113
transport::TransportError,
1214
};
1315
use spl_name_service::{
14-
instruction::{create, delete, transfer, update, NameRegistryInstruction},
16+
instruction::{create, delete, realloc, transfer, update, NameRegistryInstruction},
1517
processor::Processor,
1618
state::{get_seeds_and_key, NameRecordHeader, HASH_PREFIX},
1719
};
@@ -175,6 +177,59 @@ async fn test_name_service() {
175177
.unwrap();
176178
println!("Name Record Header: {:?}", name_record_header);
177179

180+
let data = "hello".as_bytes().to_vec();
181+
let update_instruction = update(
182+
program_id,
183+
space as u32,
184+
data,
185+
name_account_key,
186+
sol_subdomains_class.pubkey(),
187+
Some(name_record_header.parent_name),
188+
)
189+
.unwrap();
190+
191+
sign_send_instruction(
192+
&mut ctx,
193+
update_instruction.clone(),
194+
vec![&sol_subdomains_class],
195+
)
196+
.await
197+
.unwrap_err();
198+
199+
let new_space = space.checked_mul(2).unwrap();
200+
let payer_key = ctx.payer.pubkey();
201+
let realloc_instruction = |space| {
202+
realloc(
203+
program_id,
204+
payer_key,
205+
name_account_key,
206+
payer_key,
207+
space as u32,
208+
)
209+
.unwrap()
210+
};
211+
212+
sign_send_instruction(&mut ctx, realloc_instruction(new_space), vec![])
213+
.await
214+
.unwrap();
215+
216+
// update blockhash to prevent losing txn to dedup
217+
ctx.last_blockhash = ctx
218+
.banks_client
219+
.get_new_latest_blockhash(&ctx.last_blockhash)
220+
.await
221+
.unwrap();
222+
223+
// resend update ix. Should succeed this time.
224+
sign_send_instruction(&mut ctx, update_instruction, vec![&sol_subdomains_class])
225+
.await
226+
.unwrap();
227+
228+
// realloc to smaller this time
229+
sign_send_instruction(&mut ctx, realloc_instruction(space / 2), vec![])
230+
.await
231+
.unwrap();
232+
178233
let delete_instruction = delete(
179234
program_id,
180235
name_account_key,

0 commit comments

Comments
 (0)