Skip to content

Commit

Permalink
Build and link new SPL packages before tests and decode token account…
Browse files Browse the repository at this point in the history
… for AccountStore
  • Loading branch information
acheroncrypto committed Aug 31, 2022
1 parent 83fb65a commit 57f0f7e
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 9 deletions.
9 changes: 6 additions & 3 deletions .github/actions/setup-ts/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ runs:
path: |
./ts/dist/
key: solana-${{ runner.os }}-v0000-${{ env.NODE_VERSION }}-${{ hashFiles('./ts/**/*.ts') }}
- run: cd ts/packages/anchor && yarn --frozen-lockfile && yarn build:node && yarn link && cd ../../../
- run: cd ts/packages/anchor && yarn --frozen-lockfile && yarn build:node && yarn link && cd ../
shell: bash
- run: cd spl-associated-token-account && yarn --frozen-lockfile && yarn build:node && yarn link && cd ../
shell: bash
- run: cd spl-token && yarn --frozen-lockfile && yarn build:node && yarn link && cd ../../../
shell: bash
- run: cd examples/tutorial && yarn link @project-serum/anchor && yarn --frozen-lockfile && cd ../../
shell: bash
- run: cd tests && yarn link @project-serum/anchor && yarn --frozen-lockfile && cd ..
- run: cd tests && yarn link @project-serum/anchor && yarn link @project-serum/spl-associated-token-account && yarn link @project-serum/spl-token && yarn --frozen-lockfile && cd ..
shell: bash

1 change: 0 additions & 1 deletion ts/build-packages.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ for D in */;
cd $D && yarn init:yarn; cd ..;
fi
done

24 changes: 19 additions & 5 deletions ts/packages/anchor/src/program/accounts-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { TOKEN_PROGRAM_ID, ASSOCIATED_PROGRAM_ID } from "../utils/token.js";
import { AllInstructions } from "./namespace/types.js";
import Provider from "../provider.js";
import { AccountNamespace } from "./namespace/account.js";
import { decodeTokenAccount } from "./token-account-layout.js";

// Populates a given accounts context with PDAs and common missing accounts.
export class AccountsResolver<IDL extends Idl> {
Expand All @@ -26,7 +27,7 @@ export class AccountsResolver<IDL extends Idl> {
private _idlIx: AllInstructions<IDL>,
_accountNamespace: AccountNamespace<IDL>
) {
this._accountStore = new AccountStore(_accountNamespace);
this._accountStore = new AccountStore(_provider, _accountNamespace);
}

// Note: We serially resolve PDAs one by one rather than doing them
Expand Down Expand Up @@ -230,17 +231,30 @@ export class AccountsResolver<IDL extends Idl> {
export class AccountStore<IDL extends Idl> {
private _cache = new Map<string, any>();

constructor(private _accounts: AccountNamespace<IDL>) {}
constructor(
private _provider: Provider,
private _accounts: AccountNamespace<IDL>
) {}

public async fetchAccount<T = any>(
name: string,
publicKey: PublicKey
): Promise<T> {
const address = publicKey.toString();
if (!this._cache.has(address)) {
if (name === "TokenAccount") name = "Account";
const account = this._accounts[camelCase(name)].fetch(publicKey);
this._cache.set(address, account);
if (name === "TokenAccount") {
const accountInfo = await this._provider.connection.getAccountInfo(
publicKey
);
if (accountInfo === null) {
throw new Error(`invalid account info for ${address}`);
}
const decodedAccount = decodeTokenAccount(accountInfo.data);
this._cache.set(address, decodedAccount);
} else {
const account = this._accounts[camelCase(name)].fetch(publicKey);
this._cache.set(address, account);
}
}
return this._cache.get(address);
}
Expand Down
146 changes: 146 additions & 0 deletions ts/packages/anchor/src/program/token-account-layout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import BN from "bn.js";
import * as BufferLayout from "buffer-layout";
import { Layout } from "buffer-layout";
import { PublicKey } from "@solana/web3.js";

function uint64(property?: string): Layout<u64 | null> {
return new WrappedLayout(
BufferLayout.blob(8),
(b: Buffer) => u64.fromBuffer(b),
(n: u64) => n.toBuffer(),
property
);
}

function publicKey(property?: string): Layout<PublicKey> {
return new WrappedLayout(
BufferLayout.blob(32),
(b: Buffer) => new PublicKey(b),
(key: PublicKey) => key.toBuffer(),
property
);
}

function coption<T>(layout: Layout<T>, property?: string): Layout<T | null> {
return new COptionLayout<T>(layout, property);
}

class WrappedLayout<T, U> extends Layout<U> {
layout: Layout<T>;
decoder: (data: T) => U;
encoder: (src: U) => T;

constructor(
layout: Layout<T>,
decoder: (data: T) => U,
encoder: (src: U) => T,
property?: string
) {
super(layout.span, property);
this.layout = layout;
this.decoder = decoder;
this.encoder = encoder;
}

decode(b: Buffer, offset?: number): U {
return this.decoder(this.layout.decode(b, offset));
}

encode(src: U, b: Buffer, offset?: number): number {
return this.layout.encode(this.encoder(src), b, offset);
}

getSpan(b: Buffer, offset?: number): number {
return this.layout.getSpan(b, offset);
}
}

class COptionLayout<T> extends Layout<T | null> {
layout: Layout<T>;
discriminator: Layout<number>;

constructor(layout: Layout<T>, property?: string) {
super(-1, property);
this.layout = layout;
this.discriminator = BufferLayout.u32();
}

encode(src: T | null, b: Buffer, offset = 0): number {
if (src === null || src === undefined) {
return this.layout.span + this.discriminator.encode(0, b, offset);
}
this.discriminator.encode(1, b, offset);
return this.layout.encode(src, b, offset + 4) + 4;
}

decode(b: Buffer, offset = 0): T | null {
const discriminator = this.discriminator.decode(b, offset);
if (discriminator === 0) {
return null;
} else if (discriminator === 1) {
return this.layout.decode(b, offset + 4);
}
throw new Error("Invalid coption " + this.layout.property);
}

getSpan(b: Buffer, offset = 0): number {
return this.layout.getSpan(b, offset + 4) + 4;
}
}

class u64 extends BN {
/**
* Convert to Buffer representation
*/
toBuffer(): Buffer {
const a = super.toArray().reverse();
const b = Buffer.from(a);
if (length === 8) {
return b;
}
if (length >= 8) {
throw new Error("u64 too large");
}

const zeroPad = Buffer.alloc(8);
b.copy(zeroPad);
return zeroPad;
}

/**
* Construct a u64 from Buffer representation
*/
static fromBuffer(buffer: Buffer): u64 {
if (buffer.length !== 8) {
throw new Error(`Invalid buffer length: ${buffer.length}`);
}
return new u64(
[...buffer]
.reverse()
.map((i) => `00${i.toString(16)}`.slice(-2))
.join(""),
16
);
}
}

const TOKEN_ACCOUNT_LAYOUT = BufferLayout.struct([
publicKey("mint"),
publicKey("owner"),
uint64("amount"),
coption(publicKey(), "delegate"),
((p: string) => {
const U = BufferLayout.union(BufferLayout.u8("discriminator"), null, p);
U.addVariant(0, BufferLayout.struct([]), "uninitialized");
U.addVariant(1, BufferLayout.struct([]), "initialized");
U.addVariant(2, BufferLayout.struct([]), "frozen");
return U;
})("state"),
coption(uint64(), "isNative"),
uint64("delegatedAmount"),
coption(publicKey(), "closeAuthority"),
]);

export function decodeTokenAccount(b: Buffer) {
return TOKEN_ACCOUNT_LAYOUT.decode(b);
}

0 comments on commit 57f0f7e

Please sign in to comment.