Skip to content

Commit

Permalink
add confidential transfer InitializeMint
Browse files Browse the repository at this point in the history
  • Loading branch information
samkim-crypto committed Apr 16, 2024
1 parent e40125f commit 5900966
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 2 deletions.
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

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

3 changes: 2 additions & 1 deletion token/js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
"@solana/buffer-layout-utils": "^0.2.0",
"@solana/spl-token-group": "^0.0.2",
"@solana/spl-token-metadata": "^0.1.2",
"buffer": "^6.0.3"
"buffer": "^6.0.3",
"solana-zk-token-sdk-experimental": "^0.1.1"
},
"devDependencies": {
"@solana/codecs-strings": "2.0.0-preview.2",
Expand Down
22 changes: 22 additions & 0 deletions token/js/src/extensions/confidentialTransfer/elgamal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { blob, Layout } from '@solana/buffer-layout';
import { encodeDecode } from '@solana/buffer-layout-utils';
import { PodElGamalPubkey } from 'solana-zk-token-sdk-experimental';

export const elgamalPublicKey = (property?: string): Layout<PodElGamalPubkey> => {
const layout = blob(32, property);
const { encode, decode } = encodeDecode(layout);

const elgamalPublicKeyLayout = layout as Layout<unknown> as Layout<PodElGamalPubkey>;

elgamalPublicKeyLayout.decode = (buffer: Buffer, offset: number) => {
const src = decode(buffer, offset);
return new PodElGamalPubkey(src);
};

elgamalPublicKeyLayout.encode = (elgamalPublicKey: PodElGamalPubkey, buffer: Buffer, offset: number) => {
const src = elgamalPublicKey.toBytes();
return encode(src, buffer, offset);
};

return elgamalPublicKeyLayout;
};
2 changes: 2 additions & 0 deletions token/js/src/extensions/confidentialTransfer/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './state.js';
export * from './instructions.js';
56 changes: 56 additions & 0 deletions token/js/src/extensions/confidentialTransfer/instructions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { struct, u8 } from '@solana/buffer-layout';
import { bool, publicKey } from '@solana/buffer-layout-utils';
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
import { programSupportsExtensions, TOKEN_2022_PROGRAM_ID } from '../../constants.js';
import { TokenUnsupportedInstructionError } from '../../errors.js';
import { TokenInstruction } from '../../instructions/types.js';
import { PodElGamalPubkey } from 'solana-zk-token-sdk-experimental';
import { elgamalPublicKey } from './elgamal.js';

export enum ConfidentialTransferInstruction {
InitializeMint = 0,
}

export interface InitializeMintData {
instruction: TokenInstruction.ConfidentialTransferExtension;
confidentialTransferInstruction: ConfidentialTransferInstruction.InitializeMint;
confidentialTransferMintAuthority: PublicKey | null;
autoApproveNewAccounts: boolean;
auditorElGamalPubkey: PodElGamalPubkey | null;
}

export const initializeMintData = struct<InitializeMintData>([
u8('instruction'),
u8('confidentialTransferInstruction'),
publicKey('confidentialTransferMintAuthority'),
bool('autoApproveNewAccounts'),
elgamalPublicKey('auditorElGamalPubkey'),
]);

export function createConfidentialTransferInitializeMintInstruction(
mint: PublicKey,
confidentialTransferMintAuthority: PublicKey | null,
autoApproveNewAccounts: boolean,
auditorElGamalPubkey: PodElGamalPubkey | null,
programId = TOKEN_2022_PROGRAM_ID
): TransactionInstruction {
if (!programSupportsExtensions(programId)) {
throw new TokenUnsupportedInstructionError();
}
const keys = [{ pubkey: mint, isSigner: false, isWritable: true }];

const data = Buffer.alloc(initializeMintData.span);

initializeMintData.encode(
{
instruction: TokenInstruction.ConfidentialTransferExtension,
confidentialTransferInstruction: ConfidentialTransferInstruction.InitializeMint,
confidentialTransferMintAuthority: confidentialTransferMintAuthority ?? PublicKey.default,
autoApproveNewAccounts: autoApproveNewAccounts,
auditorElGamalPubkey: auditorElGamalPubkey ?? PodElGamalPubkey.default(),
},
data
);

return new TransactionInstruction({ keys, programId, data });
}
30 changes: 30 additions & 0 deletions token/js/src/extensions/confidentialTransfer/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { struct } from '@solana/buffer-layout';
import { publicKey, bool } from '@solana/buffer-layout-utils';
import type { PublicKey } from '@solana/web3.js';
import type { Mint } from '../../state/mint.js';
import { ExtensionType, getExtensionData } from '../extensionType.js';
import { PodElGamalPubkey } from 'solana-zk-token-sdk-experimental';
import { elgamalPublicKey } from './elgamal.js';

export interface ConfidentialTransferMint {
confidentialTransferMintAuthority: PublicKey;
autoApproveNewAccounts: boolean;
auditorElGamalPubkey: PodElGamalPubkey;
}

export const ConfidentialTransferMintLayout = struct<ConfidentialTransferMint>([
publicKey('confidentialTransferMintAuthority'),
bool('autoApproveNewAccounts'),
elgamalPublicKey('auditorElGamalPubkey'),
]);

export const CONFIDENTIAL_TRANSFER_MINT_SIZE = ConfidentialTransferMintLayout.span;

export function getConfidentialTransferMint(mint: Mint): ConfidentialTransferMint | null {
const extensionData = getExtensionData(ExtensionType.ConfidentialTransferMint, mint.tlvData);
if (extensionData !== null) {
return ConfidentialTransferMintLayout.decode(extensionData);
} else {
return null;
}
}
3 changes: 2 additions & 1 deletion token/js/src/extensions/extensionType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { PERMANENT_DELEGATE_SIZE } from './permanentDelegate.js';
import { TRANSFER_FEE_AMOUNT_SIZE, TRANSFER_FEE_CONFIG_SIZE } from './transferFee/index.js';
import { TRANSFER_HOOK_ACCOUNT_SIZE, TRANSFER_HOOK_SIZE } from './transferHook/index.js';
import { TOKEN_2022_PROGRAM_ID } from '../constants.js';
import { CONFIDENTIAL_TRANSFER_MINT_SIZE } from './confidentialTransfer/state.js';

// Sequence from https://github.com/solana-labs/solana-program-library/blob/master/token/program-2022/src/extension/mod.rs#L903
export enum ExtensionType {
Expand Down Expand Up @@ -78,7 +79,7 @@ export function getTypeLen(e: ExtensionType): number {
case ExtensionType.MintCloseAuthority:
return MINT_CLOSE_AUTHORITY_SIZE;
case ExtensionType.ConfidentialTransferMint:
return 65;
return CONFIDENTIAL_TRANSFER_MINT_SIZE;
case ExtensionType.ConfidentialTransferAccount:
return 295;
case ExtensionType.CpiGuard:
Expand Down
89 changes: 89 additions & 0 deletions token/js/test/e2e-2022/confidentialTransfer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { expect } from 'chai';
import type { Connection, Signer } from '@solana/web3.js';
import { PublicKey } from '@solana/web3.js';
import { Keypair, SystemProgram, Transaction, sendAndConfirmTransaction } from '@solana/web3.js';
import { ElGamalKeypair, PodElGamalPubkey } from 'solana-zk-token-sdk-experimental';
import { ExtensionType, createInitializeMintInstruction, getMint, getMintLen } from '../../src';
import { TEST_PROGRAM_ID, newAccountWithLamports, getConnection } from '../common';

import {
createConfidentialTransferInitializeMintInstruction,
getConfidentialTransferMint,
} from '../../src/extensions/confidentialTransfer/index';

const TEST_TOKEN_DECIMALS = 2;
const MINT_EXTENSIONS = [ExtensionType.ConfidentialTransferMint];

describe('confidentialTransfer', () => {
let connection: Connection;
let payer: Signer;
let mint: PublicKey;
let mintAuthority: Keypair;
before(async () => {
connection = await getConnection();
payer = await newAccountWithLamports(connection, 1000000000);
});

async function setupConfidentialTransferMint(
confidentialTransferMintAuthority: PublicKey | null,
autoApproveNewAccounts: boolean,
auditorPubkey: PodElGamalPubkey | null
) {
const mintKeypair = Keypair.generate();
mint = mintKeypair.publicKey;
mintAuthority = Keypair.generate();
const mintLen = getMintLen(MINT_EXTENSIONS);

const mintLamports = await connection.getMinimumBalanceForRentExemption(mintLen);
const mintTransaction = new Transaction().add(
SystemProgram.createAccount({
fromPubkey: payer.publicKey,
newAccountPubkey: mint,
space: mintLen,
lamports: mintLamports,
programId: TEST_PROGRAM_ID,
}),
createConfidentialTransferInitializeMintInstruction(
mint,
confidentialTransferMintAuthority,
autoApproveNewAccounts,
auditorPubkey
),
createInitializeMintInstruction(mint, TEST_TOKEN_DECIMALS, mintAuthority.publicKey, null, TEST_PROGRAM_ID)
);

await sendAndConfirmTransaction(connection, mintTransaction, [payer, mintKeypair], undefined);
}

describe('with authorities and auto approve', () => {
let confidentialTransferMintAuthority: Keypair;
let autoApproveNewAccounts: boolean;
let auditorKeypair: ElGamalKeypair;
let auditorPubkey: PodElGamalPubkey;
beforeEach(async () => {
confidentialTransferMintAuthority = Keypair.generate();
autoApproveNewAccounts = true;
auditorKeypair = ElGamalKeypair.new_rand();
auditorPubkey = PodElGamalPubkey.encoded(auditorKeypair.pubkey_owned());

await setupConfidentialTransferMint(
confidentialTransferMintAuthority.publicKey,
autoApproveNewAccounts,
auditorPubkey
);
});

it('initializes', async () => {
const mintInfo = await getMint(connection, mint, undefined, TEST_PROGRAM_ID);
const confidentialTransferMint = getConfidentialTransferMint(mintInfo);
expect(confidentialTransferMint).to.not.be.null;
if (confidentialTransferMint !== null) {
expect(confidentialTransferMint.confidentialTransferMintAuthority).to.eql(
confidentialTransferMintAuthority.publicKey
);
expect(confidentialTransferMint.autoApproveNewAccounts).to.eql(autoApproveNewAccounts);
expect(confidentialTransferMint.auditorElGamalPubkey.equals(auditorPubkey)); // TODO: equals?
}
});
});
});

0 comments on commit 5900966

Please sign in to comment.