Skip to content

Commit

Permalink
Merge pull request #258 from cardinal-labs/remove-and-delete
Browse files Browse the repository at this point in the history
Retransfer tokens on release to reset token account before deleting
  • Loading branch information
jpbogle authored Jul 12, 2022
2 parents ad89020 + 9ca2729 commit 23d6eee
Show file tree
Hide file tree
Showing 3 changed files with 227 additions and 6 deletions.
30 changes: 30 additions & 0 deletions programs/cardinal-token-manager/src/instructions/invalidate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,36 @@ pub fn handler<'key, 'accounts, 'remaining, 'info>(ctx: Context<'key, 'accounts,
};
}
t if t == InvalidationType::Release as u8 => {
// https://github.com/solana-labs/solana-program-library/pull/2872
// remove delegate
// let cpi_accounts = Revoke {
// source: ctx.accounts.recipient_token_account.to_account_info(),
// authority: token_manager.to_account_info(),
// };
// let cpi_program = ctx.accounts.token_program.to_account_info();
// let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(token_manager_signer);
// token::revoke(cpi_context)?;

// transfer to token_manager
let cpi_accounts = Transfer {
from: ctx.accounts.recipient_token_account.to_account_info(),
to: ctx.accounts.token_manager_token_account.to_account_info(),
authority: token_manager.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(token_manager_signer);
token::transfer(cpi_context, token_manager.amount)?;

// transfer back to receipient unlocked
let cpi_accounts = Transfer {
from: ctx.accounts.token_manager_token_account.to_account_info(),
to: ctx.accounts.recipient_token_account.to_account_info(),
authority: token_manager.to_account_info(),
};
let cpi_program = ctx.accounts.token_program.to_account_info();
let cpi_context = CpiContext::new(cpi_program, cpi_accounts).with_signer(token_manager_signer);
token::transfer(cpi_context, token_manager.amount)?;

// close token_manager_token_account
let cpi_accounts = CloseAccount {
account: ctx.accounts.token_manager_token_account.to_account_info(),
Expand Down
191 changes: 191 additions & 0 deletions tests/timeInvalidationRelease.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import { BN } from "@project-serum/anchor";
import { expectTXTable } from "@saberhq/chai-solana";
import {
SignerWallet,
SolanaProvider,
TransactionEnvelope,
} from "@saberhq/solana-contrib";
import type { Token } from "@solana/spl-token";
import type { PublicKey } from "@solana/web3.js";
import { Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js";
import { expect } from "chai";

import { findAta, invalidate, rentals, tryGetAccount } from "../src";
import { tokenManager } from "../src/programs";
import {
InvalidationType,
TokenManagerState,
} from "../src/programs/tokenManager";
import { createMint } from "./utils";
import { getProvider } from "./workspace";

describe("Time invalidation release", () => {
const recipient = Keypair.generate();
const tokenCreator = Keypair.generate();
let issuerTokenAccountId: PublicKey;
let rentalMint: Token;

before(async () => {
const provider = getProvider();
const airdropCreator = await provider.connection.requestAirdrop(
tokenCreator.publicKey,
LAMPORTS_PER_SOL
);
await provider.connection.confirmTransaction(airdropCreator);

const airdropRecipient = await provider.connection.requestAirdrop(
recipient.publicKey,
LAMPORTS_PER_SOL
);
await provider.connection.confirmTransaction(airdropRecipient);

// create rental mint
[issuerTokenAccountId, rentalMint] = await createMint(
provider.connection,
tokenCreator,
provider.wallet.publicKey,
1,
provider.wallet.publicKey
);
});

it("Create rental", async () => {
const provider = getProvider();
const [transaction, tokenManagerId] = await rentals.createRental(
provider.connection,
provider.wallet,
{
timeInvalidation: { maxExpiration: Date.now() / 1000 + 1 },
mint: rentalMint.publicKey,
issuerTokenAccountId: issuerTokenAccountId,
amount: new BN(1),
invalidationType: InvalidationType.Release,
}
);
const txEnvelope = new TransactionEnvelope(
SolanaProvider.init({
connection: provider.connection,
wallet: provider.wallet,
opts: provider.opts,
}),
[...transaction.instructions]
);
await expectTXTable(txEnvelope, "create", {
verbosity: "error",
formatLogs: true,
}).to.be.fulfilled;

const tokenManagerData = await tokenManager.accounts.getTokenManager(
provider.connection,
tokenManagerId
);
expect(tokenManagerData.parsed.state).to.eq(TokenManagerState.Issued);
expect(tokenManagerData.parsed.amount.toNumber()).to.eq(1);
expect(tokenManagerData.parsed.mint).to.eqAddress(rentalMint.publicKey);
expect(tokenManagerData.parsed.invalidators).length.greaterThanOrEqual(1);
expect(tokenManagerData.parsed.issuer).to.eqAddress(
provider.wallet.publicKey
);

const checkIssuerTokenAccount = await rentalMint.getAccountInfo(
issuerTokenAccountId
);
expect(checkIssuerTokenAccount.amount.toNumber()).to.eq(0);
});

it("Claim rental", async () => {
const provider = getProvider();

const tokenManagerId = await tokenManager.pda.tokenManagerAddressFromMint(
provider.connection,
rentalMint.publicKey
);

const transaction = await rentals.claimRental(
provider.connection,
new SignerWallet(recipient),
tokenManagerId
);

const txEnvelope = new TransactionEnvelope(
SolanaProvider.init({
connection: provider.connection,
wallet: new SignerWallet(recipient),
opts: provider.opts,
}),
[...transaction.instructions]
);

await expectTXTable(txEnvelope, "claim", {
verbosity: "error",
formatLogs: true,
}).to.be.fulfilled;

const tokenManagerData = await tokenManager.accounts.getTokenManager(
provider.connection,
tokenManagerId
);
expect(tokenManagerData.parsed.state).to.eq(TokenManagerState.Claimed);
expect(tokenManagerData.parsed.amount.toNumber()).to.eq(1);

const checkIssuerTokenAccount = await rentalMint.getAccountInfo(
issuerTokenAccountId
);
expect(checkIssuerTokenAccount.amount.toNumber()).to.eq(0);

const checkRecipientTokenAccount = await rentalMint.getAccountInfo(
await findAta(rentalMint.publicKey, recipient.publicKey)
);
expect(checkRecipientTokenAccount.amount.toNumber()).to.eq(1);
});

it("Invalidate", async () => {
await new Promise((r) => setTimeout(r, 2000));

const provider = getProvider();
const transaction = await invalidate(
provider.connection,
new SignerWallet(recipient),
rentalMint.publicKey
);

const txEnvelope = new TransactionEnvelope(
SolanaProvider.init({
connection: provider.connection,
wallet: new SignerWallet(recipient),
opts: provider.opts,
}),
[...transaction.instructions]
);

await expectTXTable(txEnvelope, "use", {
verbosity: "error",
formatLogs: true,
}).to.be.fulfilled;

const tokenManagerId = await tokenManager.pda.tokenManagerAddressFromMint(
provider.connection,
rentalMint.publicKey
);

const tokenManagerData = await tryGetAccount(() =>
tokenManager.accounts.getTokenManager(provider.connection, tokenManagerId)
);
expect(tokenManagerData).to.eq(null);

const checkIssuerTokenAccount = await rentalMint.getAccountInfo(
issuerTokenAccountId
);
expect(checkIssuerTokenAccount.amount.toNumber()).to.eq(0);
console.log(checkIssuerTokenAccount);

const checkRecipientTokenAccount = await rentalMint.getAccountInfo(
await findAta(rentalMint.publicKey, recipient.publicKey)
);
console.log(checkRecipientTokenAccount);
expect(checkRecipientTokenAccount.amount.toNumber()).to.eq(1);
expect(checkRecipientTokenAccount.isFrozen).to.eq(false);
expect(checkRecipientTokenAccount.delegatedAmount.toNumber()).to.eq(0);
expect(checkRecipientTokenAccount.delegate).to.eq(null);
});
});
12 changes: 6 additions & 6 deletions tools/getPaymentManager.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import { connectionFor } from "./utils";
import { getPaymentManager } from "../src/programs/paymentManager/accounts";
import { findPaymentManagerAddress } from "../src/programs/paymentManager/pda";
import { tryGetAccount } from "../src";
import { connectionFor } from "./connection";

const main = async (
paymentManagerName: string,
cluster = "mainnet"
) => {
const main = async (paymentManagerName: string, cluster = "mainnet") => {
const connection = connectionFor(cluster);
const [paymentManagerId] = await findPaymentManagerAddress(
paymentManagerName
Expand All @@ -17,7 +14,10 @@ const main = async (
if (!paymentManagerData) {
console.log("Error: Failed to create payment manager");
} else {
console.log(`Created payment manager ${paymentManagerName} (${paymentManagerId.toString()})`, paymentManagerData.parsed.feeCollector.toString());
console.log(
`Created payment manager ${paymentManagerName} (${paymentManagerId.toString()})`,
paymentManagerData.parsed.feeCollector.toString()
);
}
};

Expand Down

0 comments on commit 23d6eee

Please sign in to comment.