Skip to content

Commit

Permalink
Set authority as an option (#56)
Browse files Browse the repository at this point in the history
* Set authority as an option

* Assert authority value

* Add update example
  • Loading branch information
febo authored Apr 10, 2024
1 parent c21dadc commit d108eff
Show file tree
Hide file tree
Showing 14 changed files with 326 additions and 55 deletions.
16 changes: 13 additions & 3 deletions clients/js/asset/src/generated/types/proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,33 @@ import {
struct,
u8,
} from '@metaplex-foundation/umi/serializers';
import {
NullablePublicKey,
NullablePublicKeyArgs,
getNullablePublicKeySerializer,
} from '../../hooked';

export type Proxy = {
program: PublicKey;
seeds: Array<number>;
bump: number;
authority: PublicKey;
authority: NullablePublicKey;
};

export type ProxyArgs = Proxy;
export type ProxyArgs = {
program: PublicKey;
seeds: Array<number>;
bump: number;
authority: NullablePublicKeyArgs;
};

export function getProxySerializer(): Serializer<ProxyArgs, Proxy> {
return struct<Proxy>(
[
['program', publicKeySerializer()],
['seeds', array(u8(), { size: 32 })],
['bump', u8()],
['authority', publicKeySerializer()],
['authority', getNullablePublicKeySerializer()],
],
{ description: 'Proxy' }
) as Serializer<ProxyArgs, Proxy>;
Expand Down
16 changes: 13 additions & 3 deletions clients/js/proxy/src/generated/instructions/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export type CreateInstructionAccounts = {
stub: Signer;
/** The owner of the asset */
owner: PublicKey | Pda;
/** The authority of the asset */
authority?: Signer;
/** The account paying for the storage fees */
payer?: Signer;
/** System program */
Expand Down Expand Up @@ -99,18 +101,23 @@ export function create(
isWritable: false as boolean,
value: input.owner ?? null,
},
payer: {
authority: {
index: 3,
isWritable: false as boolean,
value: input.authority ?? null,
},
payer: {
index: 4,
isWritable: true as boolean,
value: input.payer ?? null,
},
systemProgram: {
index: 4,
index: 5,
isWritable: false as boolean,
value: input.systemProgram ?? null,
},
niftyAssetProgram: {
index: 5,
index: 6,
isWritable: false as boolean,
value: input.niftyAssetProgram ?? null,
},
Expand All @@ -132,6 +139,9 @@ export function create(
),
};
}
if (!resolvedAccounts.authority.value) {
resolvedAccounts.authority.value = context.identity;
}
if (!resolvedAccounts.systemProgram.value) {
if (resolvedAccounts.payer.value) {
resolvedAccounts.systemProgram.value = context.programs.getPublicKey(
Expand Down
30 changes: 22 additions & 8 deletions clients/js/proxy/test/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import { generateSigner } from '@metaplex-foundation/umi';
import test from 'ava';
import { createUmi } from './_setup';
import { create } from '../src';
import { Discriminator, Standard, State, fetchAsset } from '@nifty-oss/asset';
import {
Discriminator,
ExtensionType,
Standard,
State,
fetchAsset,
getExtension,
} from '@nifty-oss/asset';
import { findProxiedAssetPda } from '../src/pda';

test('it can create a proxied asset', async (t) => {
Expand All @@ -22,12 +29,19 @@ test('it can create a proxied asset', async (t) => {
}).sendAndConfirm(umi);

// Then an asset was created with the correct data.
t.like(
await fetchAsset(umi, findProxiedAssetPda(umi, { stub: stub.publicKey })),
{
discriminator: Discriminator.Asset,
state: State.Unlocked,
standard: Standard.Proxied,
}
const asset = await fetchAsset(
umi,
findProxiedAssetPda(umi, { stub: stub.publicKey })
);
t.like(asset, {
discriminator: Discriminator.Asset,
state: State.Unlocked,
standard: Standard.Proxied,
});

// And the asset has a proxy extension.
const proxy = getExtension(asset, ExtensionType.Proxy);
t.like(proxy, {
authority: umi.identity.publicKey,
});
});
113 changes: 113 additions & 0 deletions clients/js/proxy/test/update.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { generateSigner } from '@metaplex-foundation/umi';
import test from 'ava';
import { createUmi } from './_setup';
import { create } from '../src';
import {
Discriminator,
ExtensionType,
Standard,
State,
fetchAsset,
getExtension,
update,
} from '@nifty-oss/asset';
import { findProxiedAssetPda } from '../src/pda';

test('it can update the name of a proxied asset', async (t) => {
// Given a Umi instance and a new signer.
const umi = await createUmi();
const owner = generateSigner(umi);
const authority = generateSigner(umi);

// And a stub signer.
const stub = generateSigner(umi);

// And we create a new proxied asset.
await create(umi, {
stub,
owner: owner.publicKey,
authority,
payer: umi.identity,
name: 'Proxied Asset',
}).sendAndConfirm(umi);

let asset = await fetchAsset(
umi,
findProxiedAssetPda(umi, { stub: stub.publicKey })
);
t.like(asset, {
discriminator: Discriminator.Asset,
state: State.Unlocked,
standard: Standard.Proxied,
name: 'Proxied Asset',
});

const proxy = getExtension(asset, ExtensionType.Proxy);
t.like(proxy, {
authority: authority.publicKey,
});

// When we update the name of the proxied asset.
await update(umi, {
asset: asset.publicKey,
authority,
proxy: proxy?.program,
name: 'Updated Proxied Asset',
}).sendAndConfirm(umi);

// Then the asset should have the updated name.
asset = await fetchAsset(umi, asset.publicKey);
t.like(asset, {
discriminator: Discriminator.Asset,
state: State.Unlocked,
standard: Standard.Proxied,
name: 'Updated Proxied Asset',
});
});

test('it cannot update a proxied asset without the proxy authority', async (t) => {
// Given a Umi instance and a new signer.
const umi = await createUmi();
const owner = generateSigner(umi);
const authority = generateSigner(umi);

// And a stub signer.
const stub = generateSigner(umi);

// And we create a new proxied asset.
await create(umi, {
stub,
owner: owner.publicKey,
authority,
payer: umi.identity,
name: 'Proxied Asset',
}).sendAndConfirm(umi);

let asset = await fetchAsset(
umi,
findProxiedAssetPda(umi, { stub: stub.publicKey })
);
t.like(asset, {
discriminator: Discriminator.Asset,
state: State.Unlocked,
standard: Standard.Proxied,
name: 'Proxied Asset',
});

const proxy = getExtension(asset, ExtensionType.Proxy);
t.like(proxy, {
authority: authority.publicKey,
});

// When we try to update the name with a 'fake' authority.
const fake = generateSigner(umi);
const promise = update(umi, {
asset: asset.publicKey,
authority: fake,
proxy: proxy?.program,
name: 'Updated Proxied Asset',
}).sendAndConfirm(umi);

// Then we expect an error.
await t.throwsAsync(promise, { message: /invalid program argument/ });
});
7 changes: 2 additions & 5 deletions clients/rust/asset/src/generated/types/proxy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
//! [https://github.com/metaplex-foundation/kinobi]
//!

use crate::hooked::NullablePublicKey;
use borsh::BorshDeserialize;
use borsh::BorshSerialize;
use solana_program::pubkey::Pubkey;
Expand All @@ -19,9 +20,5 @@ pub struct Proxy {
pub program: Pubkey,
pub seeds: [u8; 32],
pub bump: u8,
#[cfg_attr(
feature = "serde",
serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
)]
pub authority: Pubkey,
pub authority: NullablePublicKey,
}
2 changes: 1 addition & 1 deletion configs/kinobi-asset.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ kinobi.update(
}),
k.structFieldTypeNode({
name: "authority",
type: k.publicKeyTypeNode(),
type: k.definedTypeLinkNode("nullablePublicKey", "hooked"),
}),
]),
}),
Expand Down
8 changes: 8 additions & 0 deletions idls/proxy_program.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@
"The owner of the asset"
]
},
{
"name": "authority",
"isMut": false,
"isSigner": true,
"docs": [
"The authority of the asset"
]
},
{
"name": "payer",
"isMut": true,
Expand Down
27 changes: 12 additions & 15 deletions programs/asset/types/src/extensions/grouping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,20 +157,19 @@ impl GroupingBuilder {
}

/// Add a new attribute to the extension.
pub fn set(&mut self, max_size: Option<u64>, delegate: Option<Pubkey>) -> &mut Self {
pub fn set(&mut self, max_size: Option<u64>, delegate: Option<&Pubkey>) -> &mut Self {
// setting the data replaces any existing data
self.0.clear();

self.0.extend_from_slice(&u64::to_le_bytes(0));
self.0
.extend_from_slice(&u64::to_le_bytes(max_size.unwrap_or(0)));

let delegate = if let Some(delegate) = delegate {
delegate.to_bytes()
if let Some(delegate) = delegate {
self.0.extend_from_slice(delegate.as_ref());
} else {
Pubkey::default().to_bytes()
};
self.0.extend_from_slice(&delegate);
self.0.extend_from_slice(Pubkey::default().as_ref());
}

self
}
Expand All @@ -197,11 +196,9 @@ impl Deref for GroupingBuilder {
#[cfg(test)]
mod tests {
use solana_program::sysvar;
use std::ops::Deref;

use crate::{
extensions::{ExtensionBuilder, GroupingBuilder},
state::NullablePubkey,
};
use crate::extensions::{ExtensionBuilder, GroupingBuilder};

#[test]
fn test_set_max_size() {
Expand Down Expand Up @@ -230,14 +227,14 @@ mod tests {
fn test_set_delegate() {
// set delegate to a pubkey
let mut builder = GroupingBuilder::default();
builder.set(None, Some(sysvar::ID));
builder.set(None, Some(&sysvar::ID));
let grouping = builder.build();

assert!(grouping.delegate.value().is_some());
assert_eq!(
grouping.delegate.value().unwrap(),
&NullablePubkey::new(sysvar::ID)
);

if let Some(delegate) = grouping.delegate.value() {
assert_eq!(delegate.deref(), &sysvar::ID);
}

// set delegate to None
builder.set(None, None);
Expand Down
Loading

0 comments on commit d108eff

Please sign in to comment.