Skip to content
This repository has been archived by the owner on Sep 7, 2023. It is now read-only.

Commit

Permalink
feature: Abstract SNS, ANS and GPL Nameservice
Browse files Browse the repository at this point in the history
  • Loading branch information
abishekk92 committed Mar 15, 2023
1 parent 16006e6 commit cfba4f0
Show file tree
Hide file tree
Showing 7 changed files with 383 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions programs/gpl_nameservice/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
use anchor_lang::prelude::*;
use anchor_lang::solana_program::keccak::hashv;

mod nameservice;

pub use nameservice::*;

declare_id!("7LEuQxAEegasvBSq7dDrMregc3mrDtTyHiytNK9pU68u");

#[program]
Expand Down Expand Up @@ -135,6 +139,7 @@ pub fn transfer_name_record_handler(ctx: Context<TransferNameRecord>) -> Result<

// NameRecord Account
#[account]
#[derive(Debug)]
pub struct NameRecord {
pub name: String,
pub authority: Pubkey,
Expand Down Expand Up @@ -171,4 +176,10 @@ pub enum NameServiceError {
TldTooLong,
#[msg("Name is already taken.")]
NameTaken,
#[msg("The PDA is not issued by a supported name service program")]
InvalidNameService,
InvalidOwner,
InvalidDataLength,
InvalidAuthority,
InvalidNameRecord,
}
73 changes: 73 additions & 0 deletions programs/gpl_nameservice/src/nameservice/ans.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use crate::nameservice::NameServiceParser;
use crate::NameServiceError;
use anchor_lang::prelude::*;
use borsh::{BorshDeserialize, BorshSerialize};

#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, PartialEq)]
pub struct ANSNameRecord {
pub parent_name: Pubkey,

// The owner of this name
pub owner: Pubkey,

// If `Pubkey::default()` the data is unspecified.
pub class: Pubkey,

//TODO: Check with ANS team for the right type here.
pub expires_at: u64,
}

impl ANSNameRecord {
pub const LEN: usize = 104;
}

pub struct ANSNameService;

impl NameServiceParser for ANSNameService {
type ServiceName = ANSNameRecord;

fn id_str() -> &'static str {
"ALTNSZ46uaAUU7XUV6awvdorLGqAsPwa9shm7h4uP2FK"
}

fn unpack(data: &[u8]) -> Result<ANSNameRecord> {
let record = ANSNameRecord::try_from_slice(&data)?;
Ok(record)
}

fn from_program_id(program_id: &Pubkey) -> Option<Self>
where
Self: Sized,
{
if program_id == &Self::id() {
Some(Self)
} else {
None
}
}

fn validate(accounts: &[AccountInfo]) -> Result<bool> {
let accounts_iter = &mut accounts.iter();

let record = next_account_info(accounts_iter)?;

let authority = next_account_info(accounts_iter)?;

// Validate the owner
Self::validate_owner(record)?;

if record.data_len() != ANSNameRecord::LEN {
return Err(NameServiceError::InvalidDataLength.into());
}

let record_data = &record.try_borrow_data()?;
let name_record = Self::unpack(&record_data)?;

// The authority should be the same as the owner in the record
if authority.key != &name_record.owner {
return Err(ProgramError::InvalidAccountData.into());
}

Ok(true)
}
}
73 changes: 73 additions & 0 deletions programs/gpl_nameservice/src/nameservice/gpl_nameservice.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use anchor_lang::Discriminator;

use crate::nameservice::NameServiceParser;
use crate::NameRecord;
use anchor_lang::prelude::*;

pub struct GplNameService;
impl NameServiceParser for GplNameService {
type ServiceName = NameRecord;

fn id_str() -> &'static str {
"7LEuQxAEegasvBSq7dDrMregc3mrDtTyHiytNK9pU68u"
}

fn unpack(data: &[u8]) -> Result<NameRecord> {
let record = NameRecord::try_from_slice(&data[8..])?;
Ok(record)
}

fn from_program_id(program_id: &Pubkey) -> Option<Self>
where
Self: Sized,
{
if program_id == &Self::id() {
Some(Self)
} else {
None
}
}

fn validate(accounts: &[AccountInfo]) -> Result<bool> {
let accounts_iter = &mut accounts.iter();

let record = next_account_info(accounts_iter)?;

let authority = next_account_info(accounts_iter)?;

// Validate the owner
Self::validate_owner(record)?;

let record_data = &record.try_borrow_data()?;

// The first 8 bytes should be same as the discriminator
if record_data[0..8] != NameRecord::DISCRIMINATOR {
return Err(ProgramError::InvalidAccountData.into());
}

let name_record = Self::unpack(&record_data)?;

// record.key should be the same as the PDA generated from NameRecord::SEED_PREFIX, hash of
// the name, domain key and the bump seed

let (expected_pda, _) = Pubkey::find_program_address(
&[
NameRecord::SEED_PREFIX.as_bytes(),
&NameRecord::hash(&name_record.name),
&name_record.domain.to_bytes(),
],
&Self::id(),
);

if record.key != &expected_pda {
return Err(ProgramError::InvalidSeeds.into());
}

// The authority should be the same as the one in the record
if authority.key != &name_record.authority {
return Err(ProgramError::InvalidAccountData.into());
}

Ok(true)
}
}
120 changes: 120 additions & 0 deletions programs/gpl_nameservice/src/nameservice/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
use crate::nameservice::{
ans::ANSNameService, gpl_nameservice::GplNameService, sns::SNSNameService,
};

use crate::NameServiceError;

use anchor_lang::prelude::*;

mod ans;
mod gpl_nameservice;
mod nameservice_parser;
mod sns;

pub use nameservice_parser::*;

enum NameService {
GplNameService,
SNSNameService,
ANSNameService,
}

impl NameService {
pub fn from_program_id(program_id: &Pubkey) -> Option<Self> {
if program_id == &GplNameService::id() {
Some(Self::GplNameService)
} else if program_id == &SNSNameService::id() {
Some(Self::SNSNameService)
} else if program_id == &ANSNameService::id() {
Some(Self::ANSNameService)
} else {
None
}
}

pub fn validate(&self, accounts: &[AccountInfo]) -> Result<bool> {
match self {
Self::GplNameService => GplNameService::validate(accounts),
Self::SNSNameService => SNSNameService::validate(accounts),
Self::ANSNameService => ANSNameService::validate(accounts),
}
}
}

pub fn validate(accounts: &[AccountInfo]) -> Result<bool> {
let record = &accounts[0];
let name_service =
NameService::from_program_id(&record.owner).ok_or(NameServiceError::InvalidNameService)?;
name_service.validate(accounts)
}

#[cfg(test)]
mod tests {
use super::*;
use crate::NameRecord;
use anchor_lang::system_program::System;
use anchor_lang::Discriminator;

// test validate_record
#[test]
fn test_validate_gpl_namerecord() {
let program_id = crate::id();
let authority = Pubkey::new_unique();

let gum_tld = NameRecord {
name: "gum".to_string(),
authority,
domain: Pubkey::default(),
};

// Create the NameRecord PDA
let (gum_tld_key, _bump_seed) = Pubkey::find_program_address(
&[
NameRecord::SEED_PREFIX.as_bytes(),
&NameRecord::hash(&gum_tld.name),
&gum_tld.domain.to_bytes(),
],
&crate::id(),
);

let mut lamports = 10u64;

let mut account_data: Vec<u8> = vec![];
account_data.append(&mut NameRecord::DISCRIMINATOR.to_vec());
account_data.append(&mut gum_tld.try_to_vec().unwrap());

// Create the NameRecord account
let gum_tld_account = AccountInfo::new(
&gum_tld_key,
false,
false,
&mut lamports,
&mut account_data,
&program_id,
false,
0,
);

let mut authority_lamports = 10u64;

let mut authority_account_data: Vec<u8> = vec![];

let system_program_id = System::id();

let record_authority_account = AccountInfo::new(
&gum_tld.authority,
true,
false,
&mut authority_lamports,
&mut authority_account_data,
&system_program_id,
false,
0,
);

// Validate the NameRecord
let result = validate(&[gum_tld_account, record_authority_account]);
assert!(result.is_ok());
assert_eq!(result.unwrap(), true);
}
}
27 changes: 27 additions & 0 deletions programs/gpl_nameservice/src/nameservice/nameservice_parser.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use anchor_lang::prelude::*;
use std::str::FromStr;

pub trait NameServiceParser {
type ServiceName;

fn id_str() -> &'static str;

fn id() -> Pubkey {
// Unwrap because this is infallible
Pubkey::from_str(Self::id_str()).unwrap()
}
fn unpack(data: &[u8]) -> Result<Self::ServiceName>;

fn from_program_id(program_id: &Pubkey) -> Option<Self>
where
Self: Sized;

fn validate(accounts: &[AccountInfo]) -> Result<bool>;

fn validate_owner(record: &AccountInfo) -> Result<bool> {
if record.owner != &Self::id() {
return Err(ProgramError::IncorrectProgramId.into());
}
Ok(true)
}
}
Loading

0 comments on commit cfba4f0

Please sign in to comment.