Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions tests/src/__tests__/rest/assets/controllerTransfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { TestFactory } from '~/helpers';
import { RestClient } from '~/rest';
import { controllerTransferParams, createAssetParams } from '~/rest/assets/params';
import { ProcessMode } from '~/rest/common';
import { Identity } from '~/rest/identities/interfaces';
import { RestSuccessResult } from '~/rest/interfaces';
import { fungibleInstructionParams } from '~/rest/settlements/params';
import { expectBasicTxInfo } from '../utils';

const handles = ['issuer', 'holder'];
let factory: TestFactory;

describe('Fungible AssetController transfer', () => {
let restClient: RestClient;
let signer: string;
let issuer: Identity;
let holder: Identity;
let assetParams: ReturnType<typeof createAssetParams>;
let assetId: string;

beforeAll(async () => {
factory = await TestFactory.create({ handles });
({ restClient } = factory);
issuer = factory.getSignerIdentity(handles[0]);
holder = factory.getSignerIdentity(handles[1]);

signer = issuer.signer;

assetParams = createAssetParams({
options: { processMode: ProcessMode.Submit, signer },
});
});

afterAll(async () => {
await factory.close();
});

it('should create and fetch the Asset', async () => {
assetId = await restClient.assets.createAndGetAssetId(assetParams);

const asset = await restClient.assets.getAsset(assetId);

expect(asset).toMatchObject({
name: assetParams.name,
assetType: assetParams.assetType,
});
});

it('should transfer the asset to holder', async () => {
const transferToHolderTx = await restClient.settlements.createDirectInstruction(fungibleInstructionParams(assetId, issuer.did, holder.did, {
options: { processMode: ProcessMode.Submit, signer },
}));

// should have created an instruction
expect((transferToHolderTx as RestSuccessResult).instruction).toBeDefined();

const txData = await restClient.settlements.affirmInstruction(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(transferToHolderTx as any).instruction,
{
options: { processMode: ProcessMode.Submit, signer: holder.signer },
}
);

expect(txData).toMatchObject({
transactions: expect.arrayContaining([
{
transactionTag: 'settlement.affirmInstructionWithCount',
type: 'single',
...expectBasicTxInfo,
},
]),
});

const { results } = await restClient.assets.getAssetHolders(assetId);

expect(results.length).toEqual(2);
expect(results).toContainEqual(expect.objectContaining({
identity: issuer.did,
balance: '99990',
}));
expect(results).toContainEqual(expect.objectContaining({
identity: holder.did,
balance: '10',
}));
});

it('should run controller transfer and return the asset back to the issuer', async () => {
const controllerTransferTx = await restClient.assets.controllerTransfer(assetId, controllerTransferParams({ did: holder.did, id: '0' }, 10, {
options: { processMode: ProcessMode.Submit, signer },
})) as RestSuccessResult;

expect(controllerTransferTx).toMatchObject({
transactions: expect.arrayContaining([
{
transactionTag: 'asset.controllerTransfer',
type: 'single',
...expectBasicTxInfo,
},
]),
});


const { results } = await restClient.assets.getAssetHolders(assetId);

expect(results.length).toEqual(1);
expect(results).toContainEqual(expect.objectContaining({
identity: expect.objectContaining({
did: issuer.did,
}),
balance: expect.objectContaining({
amount: '100000',
}),
}));
});
});
38 changes: 38 additions & 0 deletions tests/src/__tests__/sdk/assets/controllerTransfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { LocalSigningManager } from '@polymeshassociation/local-signing-manager';
import { Polymesh } from '@polymeshassociation/polymesh-sdk';
import { FungibleAsset } from '@polymeshassociation/polymesh-sdk/types';

import { TestFactory } from '~/helpers';
import { fungibleAssetControllerTransfer } from '~/sdk/assets/controllerTransfer';

let factory: TestFactory;

describe('controllerTransfer', () => {
let asset: FungibleAsset;
let sdk: Polymesh;
let targetDid: string;


beforeAll(async () => {
factory = await TestFactory.create({});
sdk = factory.polymeshSdk;

const targetMnemonic = LocalSigningManager.generateAccount();
const targetAddress = factory.signingManager.addAccount({
mnemonic: targetMnemonic,
});

({
results: [{ did: targetDid }],
} = await factory.createIdentityForAddresses([targetAddress]));

});

afterAll(async () => {
await factory.close();
});

it('should execute controllerTransfer without errors', async () => {
await fungibleAssetControllerTransfer(sdk, targetDid);
});
});
11 changes: 10 additions & 1 deletion tests/src/rest/assets/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import {
setAssetDocumentParams,
setMetadataParams,
transferAssetOwnershipParams,
controllerTransferParams,
} from '~/rest/assets/params';
import { RestClient } from '~/rest/client';
import { TxBase } from '~/rest/common';
import { PostResult, RestSuccessResult } from '~/rest/interfaces';
import { PostResult, ResultSet, RestSuccessResult } from '~/rest/interfaces';

export class Assets {
constructor(private client: RestClient) {}
Expand Down Expand Up @@ -133,4 +134,12 @@ export class Assets {
public async unfreeze(asset: string, params: TxBase): Promise<PostResult> {
return this.client.post(`assets/${asset}/unfreeze`, { ...params });
}

public async controllerTransfer(asset: string, params: ReturnType<typeof controllerTransferParams>): Promise<PostResult> {
return this.client.post(`assets/${asset}/controller-transfer`, params);
}

public async getAssetHolders(asset: string): Promise<ResultSet<Record<string, unknown>>> {
return this.client.get(`assets/${asset}/holders`);
}
}
9 changes: 9 additions & 0 deletions tests/src/rest/assets/params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,12 @@ export const issueAssetParams = (amount: number, base: TxBase, extras: TxExtras
...extras,
...base,
} as const);


export const controllerTransferParams = (origin: { did: string, id: string }, amount: number, base: TxBase, extras: TxExtras = {}) =>
({
origin,
amount,
...extras,
...base,
} as const);
189 changes: 189 additions & 0 deletions tests/src/sdk/assets/controllerTransfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import assert from 'node:assert';

import { BigNumber, Polymesh } from '@polymeshassociation/polymesh-sdk';
import { createAsset } from '~/sdk/assets/createAsset';
import { createNftCollection } from '~/sdk/assets/createNftCollection';
import { KnownNftType, MetadataType } from '@polymeshassociation/polymesh-sdk/types';

/*
This script showcases VenueFiltering related functionality. It:
- Creates an asset with initial supply
- Transfers some of the asset to a new DID
- Force controller transfer for soma part back to default portfolio
- Creates new portfolio
- Force controller transfer to newly created portfolio
*/

export const fungibleAssetControllerTransfer = async (sdk: Polymesh, targetDid: string): Promise<void> => {
const asset = await createAsset(sdk, { initialSupply: new BigNumber(2000) });

assert(asset);

const [identity, counterParty] = await Promise.all([
sdk.getSigningIdentity(),
sdk.identities.getIdentity({ did: targetDid }),
]);
assert(identity);
const { account: counterPartyAccount } = await counterParty.getPrimaryAccount();

const transferTx = await sdk.settlements.addInstruction({
legs: [
{ asset, from: identity, to: targetDid, amount: new BigNumber(1000) },
],
});
const instruction = await transferTx.run();
assert(transferTx.isSuccess);

// affirm instruction
const { pending } = await counterParty.getInstructions();
const counterInstruction = pending.find(({ id }) => id.eq(instruction.id));
assert(counterInstruction, 'the counter party should have the instruction as pending');

const affirmTx = await counterInstruction.affirm({}, { signingAccount: counterPartyAccount });
await affirmTx.run();
assert(affirmTx.isSuccess);

const controllerTransferTx = await asset.controllerTransfer({
originPortfolio: targetDid,
amount: new BigNumber(100),
});
await controllerTransferTx.run();

assert(controllerTransferTx.isSuccess);

const assetHolders = await asset.assetHolders.get();

const heldByIssuer = assetHolders.data.find(({ identity }) => identity.isEqual(identity));
assert(heldByIssuer);
expect(heldByIssuer.balance.eq(new BigNumber(1100)));

const heldByCounterParty = assetHolders.data.find(({ identity }) => identity.did === targetDid);
assert(heldByCounterParty);
expect(heldByCounterParty.balance.eq(new BigNumber(900)));
};

/*
This script showcases VenueFiltering related functionality. It:
- Creates an asset with initial supply
- Transfers some of the asset to a new DID
- Force controller transfer for soma part back to default portfolio
- Creates new portfolio
- Force controller transfer to newly created portfolio
*/
export const nonFungibleAssetControllerTransfer = async (sdk: Polymesh, targetDid: string): Promise<void> => {
const collection = await createNftCollection(sdk, {
ticker: 'TEST',
nftType: KnownNftType.Derivative,
});

assert(collection);

const [identity, counterParty] = await Promise.all([
sdk.getSigningIdentity(),
sdk.identities.getIdentity({ did: targetDid }),
]);
assert(identity);
assert(counterParty);

const { account: counterPartyAccount } = await counterParty.getPrimaryAccount();

const issueTx = await collection.issue({
metadata: [
{
type: MetadataType.Local,
id: new BigNumber(1),
value: 'https://example.com/nft/1',
},
{
type: MetadataType.Local,
id: new BigNumber(2),
value: '0x35987a0f9ae77012a5146a982966661b75cdeaa4161d1d62b1e18d39438e7396',
},
],
});

const nft = await issueTx.run();

expect(nft.id).toEqual(new BigNumber(1));

const issueTx2 = await collection.issue({
metadata: [
{
type: MetadataType.Local,
id: new BigNumber(1),
value: 'https://example.com/nft/1',
},
{
type: MetadataType.Local,
id: new BigNumber(2),
value: '0x35987a0f9ae77012a5146a982966661b75cdeaa4161d1d62b1e18d39438e7396',
},
],
});

const nft2 = await issueTx2.run();

const transferTx = await sdk.settlements.addInstruction({
legs: [
{ asset: collection, nfts: [nft, nft2], from: identity, to: targetDid },
],
});
const instruction = await transferTx.run();
assert(transferTx.isSuccess);

// affirm instruction
const { pending } = await counterParty.getInstructions();
const counterInstruction = pending.find(({ id }) => id.eq(instruction.id));
assert(counterInstruction, 'the counter party should have the instruction as pending');

const affirmTx = await counterInstruction.affirm({}, { signingAccount: counterPartyAccount });
await affirmTx.run();
assert(affirmTx.isSuccess);

const controllerTransferTx = await collection.controllerTransfer({
originPortfolio: targetDid,
nfts: [nft],
});
await controllerTransferTx.run();

assert(controllerTransferTx.isSuccess);

const assetHolders = await collection.assetHolders.get({});

let heldByIssuer = assetHolders.data.find(({ identity }) => identity.isEqual(identity));
assert(heldByIssuer);
expect(heldByIssuer.nfts.length).toEqual(1);
expect(heldByIssuer.nfts[0].id.eq(nft.id));

let heldByCounterParty = assetHolders.data.find(({ identity }) => identity.did === targetDid);
assert(heldByCounterParty);
expect(heldByCounterParty.nfts.length).toEqual(1);
expect(heldByCounterParty.nfts[0].id.eq(nft2.id));

const createPortfolioTx = await sdk.identities.createPortfolio({
name: 'PORTFOLIO',
});
const portfolio = await createPortfolioTx.run();
assert(createPortfolioTx.isSuccess);

const controllerTransferTx2 = await collection.controllerTransfer({ originPortfolio: { identity, id: portfolio.id }, nfts: [nft2] });
await controllerTransferTx2.run();

assert(controllerTransferTx2.isSuccess);

const assetHolders2 = await collection.assetHolders.get({});

heldByIssuer = assetHolders2.data.find(({ identity }) => identity.isEqual(identity));
assert(heldByIssuer);
expect(heldByIssuer.nfts.length).toEqual(2);

heldByCounterParty = assetHolders2.data.find(({ identity }) => identity.did === targetDid);
expect(heldByCounterParty).toBeUndefined();

const portfolioCollections = await portfolio.getCollections({ collections: [collection] });

expect(portfolioCollections.length).toEqual(1);
expect(portfolioCollections[0].collection.id).toEqual(collection.id);
expect(portfolioCollections[0].free.length).toEqual(1);
expect(portfolioCollections[0].free[0].id).toEqual(nft2.id);
};