@solana/spl-token#u64 TypeScript Examples

The following examples show how to use @solana/spl-token#u64. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.ts    From protocol-v1 with Apache License 2.0 6 votes vote down vote up
export function parseTokenAccount(data: Buffer): AccountInfo {
	const accountInfo = AccountLayout.decode(data);
	accountInfo.mint = new PublicKey(accountInfo.mint);
	accountInfo.owner = new PublicKey(accountInfo.owner);
	accountInfo.amount = u64.fromBuffer(accountInfo.amount);

	if (accountInfo.delegateOption === 0) {
		accountInfo.delegate = null;
		// eslint-disable-next-line new-cap
		accountInfo.delegatedAmount = new u64(0);
	} else {
		accountInfo.delegate = new PublicKey(accountInfo.delegate);
		accountInfo.delegatedAmount = u64.fromBuffer(accountInfo.delegatedAmount);
	}

	accountInfo.isInitialized = accountInfo.state !== 0;
	accountInfo.isFrozen = accountInfo.state === 2;

	if (accountInfo.isNativeOption === 1) {
		accountInfo.rentExemptReserve = u64.fromBuffer(accountInfo.isNative);
		accountInfo.isNative = true;
	} else {
		accountInfo.rentExemptReserve = null;
		accountInfo.isNative = false;
	}

	if (accountInfo.closeAuthorityOption === 0) {
		accountInfo.closeAuthority = null;
	} else {
		accountInfo.closeAuthority = new PublicKey(accountInfo.closeAuthority);
	}

	return accountInfo;
}
Example #2
Source File: pools.ts    From serum-ts with Apache License 2.0 6 votes vote down vote up
async getHoldings(
    connection: Connection,
  ): Promise<{ account: PublicKey; mint: PublicKey; holding: u64 }[]> {
    const accounts = await Promise.all([
      this.getCachedTokenAccount(connection, this._holdingAccounts[0]),
      this.getCachedTokenAccount(connection, this._holdingAccounts[1]),
    ]);
    return accounts.map(account => {
      return {
        account: account.pubkey,
        mint: account.info.mint,
        holding: account.info.amount,
      };
    });
  }
Example #3
Source File: instructions.ts    From serum-ts with Apache License 2.0 6 votes vote down vote up
deserializeMint = (data: Buffer): MintInfo => {
  if (data.length !== MintLayout.span) {
    throw new Error('Not a valid Mint');
  }

  const mintInfo = MintLayout.decode(data);

  if (mintInfo.mintAuthorityOption === 0) {
    mintInfo.mintAuthority = null;
  } else {
    mintInfo.mintAuthority = new PublicKey(mintInfo.mintAuthority);
  }

  mintInfo.supply = u64.fromBuffer(mintInfo.supply);
  mintInfo.isInitialized = mintInfo.isInitialized !== 0;

  if (mintInfo.freezeAuthorityOption === 0) {
    mintInfo.freezeAuthority = null;
  } else {
    mintInfo.freezeAuthority = new PublicKey(mintInfo.freezeAuthority);
  }

  return mintInfo as MintInfo;
}
Example #4
Source File: instructions.ts    From serum-ts with Apache License 2.0 6 votes vote down vote up
export function parseTokenAccount(data: Buffer): AccountInfo {
  const accountInfo = AccountLayout.decode(data);
  accountInfo.mint = new PublicKey(accountInfo.mint);
  accountInfo.owner = new PublicKey(accountInfo.owner);
  accountInfo.amount = u64.fromBuffer(accountInfo.amount);

  if (accountInfo.delegateOption === 0) {
    accountInfo.delegate = null;
    // eslint-disable-next-line new-cap
    accountInfo.delegatedAmount = new u64(0);
  } else {
    accountInfo.delegate = new PublicKey(accountInfo.delegate);
    accountInfo.delegatedAmount = u64.fromBuffer(accountInfo.delegatedAmount);
  }

  accountInfo.isInitialized = accountInfo.state !== 0;
  accountInfo.isFrozen = accountInfo.state === 2;

  if (accountInfo.isNativeOption === 1) {
    accountInfo.rentExemptReserve = u64.fromBuffer(accountInfo.isNative);
    accountInfo.isNative = true;
  } else {
    accountInfo.rentExemptReserve = null;
    accountInfo.isNative = false;
  }

  if (accountInfo.closeAuthorityOption === 0) {
    accountInfo.closeAuthority = null;
  } else {
    accountInfo.closeAuthority = new PublicKey(accountInfo.closeAuthority);
  }

  return accountInfo;
}
Example #5
Source File: index.ts    From serum-ts with Apache License 2.0 6 votes vote down vote up
export function parseTokenAccount(data: Buffer): AccountInfo {
  const accountInfo = AccountLayout.decode(data);
  accountInfo.mint = new PublicKey(accountInfo.mint);
  accountInfo.owner = new PublicKey(accountInfo.owner);
  accountInfo.amount = u64.fromBuffer(accountInfo.amount);

  if (accountInfo.delegateOption === 0) {
    accountInfo.delegate = null;
    // eslint-disable-next-line new-cap
    accountInfo.delegatedAmount = new u64(0);
  } else {
    accountInfo.delegate = new PublicKey(accountInfo.delegate);
    accountInfo.delegatedAmount = u64.fromBuffer(accountInfo.delegatedAmount);
  }

  accountInfo.isInitialized = accountInfo.state !== 0;
  accountInfo.isFrozen = accountInfo.state === 2;

  if (accountInfo.isNativeOption === 1) {
    accountInfo.rentExemptReserve = u64.fromBuffer(accountInfo.isNative);
    accountInfo.isNative = true;
  } else {
    accountInfo.rentExemptReserve = null;
    accountInfo.isNative = false;
  }

  if (accountInfo.closeAuthorityOption === 0) {
    accountInfo.closeAuthority = null;
  } else {
    accountInfo.closeAuthority = new PublicKey(accountInfo.closeAuthority);
  }

  return accountInfo;
}
Example #6
Source File: TokenAccountParser.ts    From port-sdk with MIT License 6 votes vote down vote up
function deserialize(data: Buffer): AccountInfo {
  const accountInfo = AccountLayout.decode(data);
  accountInfo.mint = new PublicKey(accountInfo.mint);
  accountInfo.owner = new PublicKey(accountInfo.owner);
  accountInfo.amount = u64.fromBuffer(accountInfo.amount);

  if (accountInfo.delegateOption === 0) {
    accountInfo.delegate = null;
    // eslint-disable-next-line new-cap
    accountInfo.delegatedAmount = new u64(0);
  } else {
    accountInfo.delegate = new PublicKey(accountInfo.delegate);
    accountInfo.delegatedAmount = u64.fromBuffer(accountInfo.delegatedAmount);
  }

  accountInfo.isInitialized = accountInfo.state !== 0;
  accountInfo.isFrozen = accountInfo.state === 2;

  if (accountInfo.isNativeOption === 1) {
    accountInfo.rentExemptReserve = u64.fromBuffer(accountInfo.isNative);
    accountInfo.isNative = true;
  } else {
    accountInfo.rentExemptReserve = null;
    accountInfo.isNative = false;
  }

  if (accountInfo.closeAuthorityOption === 0) {
    accountInfo.closeAuthority = null;
  } else {
    accountInfo.closeAuthority = new PublicKey(accountInfo.closeAuthority);
  }

  return accountInfo;
}
Example #7
Source File: TokenAccount.ts    From port-sdk with MIT License 6 votes vote down vote up
public static fromRaw(raw: RawData): TokenAccount {
    const buffer = Buffer.from(raw.account.data);
    const accountInfo = AccountLayout.decode(buffer);

    accountInfo.mint = new PublicKey(accountInfo.mint);
    accountInfo.owner = new PublicKey(accountInfo.owner);
    accountInfo.amount = u64.fromBuffer(accountInfo.amount);

    return new TokenAccount(
      TokenAccountId.of(raw.pubkey),
      WalletId.of(accountInfo.owner),
      MintId.of(accountInfo.mint),
      Lamport.of(accountInfo.amount)
    );
  }
Example #8
Source File: index.ts    From zo-client with Apache License 2.0 6 votes vote down vote up
export async function getMintInfo(
  provider: Provider,
  pubkey: PublicKey,
): Promise<MintInfo> {
  const data = (await provider.connection.getAccountInfo(pubkey))?.data;
  if (!data) throw Error(`Couldn't load mint data for ${pubkey.toBase58()}`);
  const m = MintLayout.decode(data);
  return {
    mintAuthority: new PublicKey(m.mintAuthority),
    supply: u64.fromBuffer(m.supply),
    decimals: m.decimals,
    isInitialized: !!m.isInitialized,
    freezeAuthority: new PublicKey(m.freezeAuthority),
  };
}
Example #9
Source File: accounts.ts    From metaplex with Apache License 2.0 6 votes vote down vote up
deserializeAccount = (data: Buffer) => {
  const accountInfo = AccountLayout.decode(data);
  accountInfo.mint = new PublicKey(accountInfo.mint);
  accountInfo.owner = new PublicKey(accountInfo.owner);
  accountInfo.amount = u64.fromBuffer(accountInfo.amount);

  if (accountInfo.delegateOption === 0) {
    accountInfo.delegate = null;
    accountInfo.delegatedAmount = new u64(0);
  } else {
    accountInfo.delegate = new PublicKey(accountInfo.delegate);
    accountInfo.delegatedAmount = u64.fromBuffer(accountInfo.delegatedAmount);
  }

  accountInfo.isInitialized = accountInfo.state !== 0;
  accountInfo.isFrozen = accountInfo.state === 2;

  if (accountInfo.isNativeOption === 1) {
    accountInfo.rentExemptReserve = u64.fromBuffer(accountInfo.isNative);
    accountInfo.isNative = true;
  } else {
    accountInfo.rentExemptReserve = null;
    accountInfo.isNative = false;
  }

  if (accountInfo.closeAuthorityOption === 0) {
    accountInfo.closeAuthority = null;
  } else {
    accountInfo.closeAuthority = new PublicKey(accountInfo.closeAuthority);
  }

  return accountInfo;
}
Example #10
Source File: accounts.tsx    From metaplex with Apache License 2.0 6 votes vote down vote up
function wrapNativeAccount(
  pubkey: StringPublicKey,
  account?: AccountInfo<Buffer>,
): TokenAccount | undefined {
  if (!account) {
    return undefined;
  }

  const key = new PublicKey(pubkey);

  return {
    pubkey: pubkey,
    account,
    info: {
      address: key,
      mint: WRAPPED_SOL_MINT,
      owner: key,
      amount: new u64(account.lamports),
      delegate: null,
      delegatedAmount: new u64(0),
      isInitialized: true,
      isFrozen: false,
      isNative: true,
      rentExemptReserve: null,
      closeAuthority: null,
    },
  };
}
Example #11
Source File: deserialize.ts    From metaplex with Apache License 2.0 6 votes vote down vote up
deserializeAccount = (data: Buffer) => {
  const accountInfo = AccountLayout.decode(data);
  accountInfo.mint = new PublicKey(accountInfo.mint);
  accountInfo.owner = new PublicKey(accountInfo.owner);
  accountInfo.amount = u64.fromBuffer(accountInfo.amount);

  if (accountInfo.delegateOption === 0) {
    accountInfo.delegate = null;
    accountInfo.delegatedAmount = new u64(0);
  } else {
    accountInfo.delegate = new PublicKey(accountInfo.delegate);
    accountInfo.delegatedAmount = u64.fromBuffer(accountInfo.delegatedAmount);
  }

  accountInfo.isInitialized = accountInfo.state !== 0;
  accountInfo.isFrozen = accountInfo.state === 2;

  if (accountInfo.isNativeOption === 1) {
    accountInfo.rentExemptReserve = u64.fromBuffer(accountInfo.isNative);
    accountInfo.isNative = true;
  } else {
    accountInfo.rentExemptReserve = null;
    accountInfo.isNative = false;
  }

  if (accountInfo.closeAuthorityOption === 0) {
    accountInfo.closeAuthority = null;
  } else {
    accountInfo.closeAuthority = new PublicKey(accountInfo.closeAuthority);
  }

  return accountInfo;
}
Example #12
Source File: deserialize.ts    From metaplex with Apache License 2.0 6 votes vote down vote up
deserializeMint = (data: Buffer) => {
  if (data.length !== MintLayout.span) {
    throw new Error('Not a valid Mint');
  }

  const mintInfo = MintLayout.decode(data);

  if (mintInfo.mintAuthorityOption === 0) {
    mintInfo.mintAuthority = null;
  } else {
    mintInfo.mintAuthority = new PublicKey(mintInfo.mintAuthority);
  }

  mintInfo.supply = u64.fromBuffer(mintInfo.supply);
  mintInfo.isInitialized = mintInfo.isInitialized !== 0;

  if (mintInfo.freezeAuthorityOption === 0) {
    mintInfo.freezeAuthority = null;
  } else {
    mintInfo.freezeAuthority = new PublicKey(mintInfo.freezeAuthority);
  }

  return mintInfo as MintInfo;
}
Example #13
Source File: index.ts    From kin-node with MIT License 5 votes vote down vote up
export function bigNumberToU64(bn: BigNumber): u64 {
    const b = Buffer.alloc(8);
    b.writeBigUInt64LE(BigInt(bn), 0);
    return u64.fromBuffer(b);
}
Example #14
Source File: index.ts    From serum-ts with Apache License 2.0 5 votes vote down vote up
export function parseMintAccount(data: Buffer): MintInfo {
  const m = MintLayout.decode(data);
  m.mintAuthority = new PublicKey(m.mintAuthority);
  m.supply = u64.fromBuffer(m.supply);
  m.isInitialized = m.state !== 0;
  return m;
}
Example #15
Source File: WrappedLamport.ts    From port-sdk with MIT License 5 votes vote down vote up
public toU64(): u64 {
    return this.getAmount().toU64();
  }
Example #16
Source File: Lamport.ts    From port-sdk with MIT License 5 votes vote down vote up
public toU64(): u64 {
    // eslint-disable-next-line new-cap
    return new u64(this.raw.toFixed(0, 0)); // RoundDown
  }
Example #17
Source File: mint_v2.ts    From psyoptions with Apache License 2.0 4 votes vote down vote up
describe("mintOption", () => {
  const payer = anchor.web3.Keypair.generate();
  const mintAuthority = anchor.web3.Keypair.generate();
  const program = anchor.workspace.PsyAmerican as Program<PsyAmerican>;
  const provider = program.provider;

  const minter = anchor.web3.Keypair.generate();

  let quoteToken: Token;
  let underlyingToken: Token;
  let optionToken: Token;
  let optionMarket: OptionMarketV2;
  let underlyingAmountPerContract: anchor.BN;
  let quoteAmountPerContract: anchor.BN;
  let expiration: anchor.BN;
  let optionMarketKey: PublicKey;
  let bumpSeed: number;
  let mintFeeKey: PublicKey | null;
  let exerciseFeeKey: PublicKey;
  let optionMintAccount: Keypair;
  let writerTokenMintAccount: Keypair;
  let underlyingAssetPoolAccount: Keypair;
  let quoteAssetPoolAccount: Keypair;
  let remainingAccounts: AccountMeta[] = [];
  let instructions: TransactionInstruction[] = [];

  let optionAccount: Keypair;
  let underlyingAccount: Keypair;
  let writerTokenAccount: Keypair;
  let size = new u64(2);

  beforeEach(async () => {
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(payer.publicKey, 10_000_000_000),
      "confirmed"
    );
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        minter.publicKey,
        10_000_000_000
      ),
      "confirmed"
    );
    size = new u64(2);
  });

  const mintOptionsTx = async (
    opts: {
      underlyingAssetPoolKey?: PublicKey;
      remainingAccounts?: AccountMeta[];
    } = {}
  ) => {
    await program.rpc.mintOptionV2(size, {
      accounts: {
        userAuthority: minter.publicKey,
        underlyingAssetMint: optionMarket?.underlyingAssetMint,
        underlyingAssetPool:
          opts.underlyingAssetPoolKey || optionMarket?.underlyingAssetPool,
        underlyingAssetSrc: underlyingAccount.publicKey,
        optionMint: optionMarket?.optionMint,
        mintedOptionDest: optionAccount.publicKey,
        writerTokenMint: optionMarket?.writerTokenMint,
        mintedWriterTokenDest: writerTokenAccount.publicKey,
        optionMarket: optionMarket?.key,
        tokenProgram: TOKEN_PROGRAM_ID,
      },
      remainingAccounts: opts.remainingAccounts
        ? opts.remainingAccounts
        : remainingAccounts,
      signers: [minter],
    });
  };

  describe("proper mint", () => {
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        optionToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          size.mul(optionMarket.underlyingAmountPerContract).muln(2).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));
    });
    it("should mint size OptionTokens", async () => {
      try {
        await mintOptionsTx();
      } catch (err) {
        console.error((err as AnchorError).error.errorMessage);
        throw err;
      }
      const mintInfo = await optionToken.getMintInfo();
      assert.equal(mintInfo.supply.toString(), size.toString());
    });

    it("should mint size WriterTokens", async () => {
      try {
        await mintOptionsTx();
      } catch (err) {
        console.error((err as AnchorError).error.errorMessage);
        throw err;
      }
      const writerToken = new Token(
        provider.connection,
        optionMarket.writerTokenMint,
        TOKEN_PROGRAM_ID,
        payer
      );
      const mintInfo = await writerToken.getMintInfo();
      assert.equal(mintInfo.supply.toString(), size.toString());
    });

    it("should transfer the underlying from the minter to the pool", async () => {
      if (!mintFeeKey) {
        throw new Error("mintFeeKey wasn't set when it should be");
      }
      const underlyingPoolBefore = await underlyingToken.getAccountInfo(
        optionMarket.underlyingAssetPool
      );
      const minterUnderlyingBefore = await underlyingToken.getAccountInfo(
        underlyingAccount.publicKey
      );
      try {
        await mintOptionsTx();
      } catch (err) {
        console.error((err as AnchorError).error.errorMessage);
        throw err;
      }
      const expectedUnderlyingTransfered = size.mul(
        underlyingAmountPerContract
      );

      const underlyingPoolAfter = await underlyingToken.getAccountInfo(
        optionMarket.underlyingAssetPool
      );
      const poolDiff = underlyingPoolAfter.amount.sub(
        underlyingPoolBefore.amount
      );
      assert.equal(
        poolDiff.toString(),
        expectedUnderlyingTransfered.toString()
      );

      const minterUnderlyingAfter = await underlyingToken.getAccountInfo(
        underlyingAccount.publicKey
      );
      const minterUnderlyingDiff = minterUnderlyingAfter.amount.sub(
        minterUnderlyingBefore.amount
      );
      assert.equal(
        expectedUnderlyingTransfered.neg().toString(),
        minterUnderlyingDiff.toString()
      );
    });
  });

  describe("OptionMarket expired", () => {
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program, {
        // set expiration to 2 seconds from now
        expiration: new anchor.BN(new Date().getTime() / 1000 + 2),
      }));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          optionMarket.underlyingAmountPerContract.muln(2).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));
    });
    it("should error", async () => {
      try {
        await wait(2000);
        await mintOptionsTx();
        assert.ok(false);
      } catch (err) {
        const errMsg = "OptionMarket is expired, can't mint";
        assert.equal((err as AnchorError).error.errorMessage, errMsg);
      }
    });
  });

  describe("Underlying pool key differs from option market", () => {
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          size.mul(optionMarket.underlyingAmountPerContract.muln(2)).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));
      // Create a new token account and set it as the underlyingAssetPoolAccount
      const { tokenAccount } = await initNewTokenAccount(
        provider.connection,
        payer.publicKey,
        underlyingToken.publicKey,
        payer
      );
      optionMarket.underlyingAssetPool = tokenAccount.publicKey;
    });
    it("should error", async () => {
      try {
        await mintOptionsTx();
        assert.ok(false);
      } catch (err) {
        const errMsg =
          "Underlying pool account does not match the value on the OptionMarket";
        assert.equal((err as AnchorError).error.errorMessage, errMsg);
      }
    });
  });

  describe("OptionToken Mint key differs from option market", () => {
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new token mint and set it as the optionMintAccount
      const { mintAccount } = await initNewTokenMint(
        provider.connection,
        payer.publicKey,
        payer
      );
      optionMarket.optionMint = mintAccount.publicKey;
      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          size.mul(optionMarket.underlyingAmountPerContract.muln(2)).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));
    });
    it("should error", async () => {
      try {
        await mintOptionsTx();
        assert.ok(false);
      } catch (err) {
        const errMsg =
          "OptionToken mint does not match the value on the OptionMarket";
        assert.equal((err as AnchorError).error.errorMessage, errMsg);
      }
    });
  });

  describe("WriterToken Mint key differs from option market", () => {
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new token mint and set it as the optionMintAccount
      const { mintAccount } = await initNewTokenMint(
        provider.connection,
        payer.publicKey,
        payer
      );
      optionMarket.writerTokenMint = mintAccount.publicKey;
      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          size.mul(optionMarket.underlyingAmountPerContract.muln(2)).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));
    });
    it("should error", async () => {
      try {
        await mintOptionsTx();
        assert.ok(false);
      } catch (err) {
        const errMsg =
          "WriterToken mint does not match the value on the OptionMarket";
        assert.equal((err as AnchorError).error.errorMessage, errMsg);
      }
    });
  });

  describe("Size <= 0", () => {
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          optionMarket.underlyingAmountPerContract.muln(2).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));

      // Set the size to 0 to trigger an error
      size = new anchor.BN(0);
    });
    it("should error", async () => {
      try {
        await mintOptionsTx();
        assert.ok(false);
      } catch (err) {
        const errMsg = "The size argument must be > 0";
        assert.equal((err as AnchorError).error.errorMessage, errMsg);
      }
    });
  });
});
Example #18
Source File: burnWriterForQuote.ts    From psyoptions with Apache License 2.0 4 votes vote down vote up
// TODO: exercise a token so there are quote assets in the pool

describe("burnWriterForQuote", () => {
  const payer = anchor.web3.Keypair.generate();
  const mintAuthority = anchor.web3.Keypair.generate();
  const program = anchor.workspace.PsyAmerican as Program<PsyAmerican>;
  const provider = program.provider;

  const minter = anchor.web3.Keypair.generate();
  const exerciser = anchor.web3.Keypair.generate();

  let quoteToken: Token;
  let underlyingToken: Token;
  let writerToken: Token;
  let underlyingAmountPerContract: anchor.BN;
  let quoteAmountPerContract: anchor.BN;
  let optionMarketKey: PublicKey;
  let optionMarket: OptionMarketV2;
  let exerciseFeeKey: PublicKey;
  let optionMintAccount: Keypair;
  let writerTokenMintAccount: Keypair;
  let underlyingAssetPoolAccount: Keypair;
  let quoteAssetPoolAccount: Keypair;
  let remainingAccounts: AccountMeta[] = [];
  let instructions: TransactionInstruction[] = [];

  let minterWriterAcct: Keypair;
  let minterOptionAcct: Keypair;
  let minterUnderlyingAccount: Keypair;
  let minterQuoteAccount: Keypair;
  let size = new u64(1);

  before(async () => {
    // airdrop SOL to the payer and minter
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        payer.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        minter.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        exerciser.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
  });

  describe("Unexpired OptionMarket", () => {
    before(async () => {
      // Initialize a new OptionMarket
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        optionMarketKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program, {
        // set expiration to 2 seconds from now
        expiration: new anchor.BN(new Date().getTime() / 1000 + 600),
      }));
      writerToken = new Token(
        provider.connection,
        optionMarket.writerTokenMint,
        TOKEN_PROGRAM_ID,
        payer
      );
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new minter
      ({
        optionAccount: minterOptionAcct,
        underlyingAccount: minterUnderlyingAccount,
        writerTokenAccount: minterWriterAcct,
        quoteAccount: minterQuoteAccount,
      } = await createMinter(
        provider.connection,
        minter,
        mintAuthority,
        underlyingToken,
        new anchor.BN(100)
          .mul(optionMarket.underlyingAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.writerTokenMint,
        quoteToken
      ));
      // Mint a bunch of contracts to the minter
      await mintOptionsTx(
        program,
        minter,
        minterOptionAcct,
        minterWriterAcct,
        minterUnderlyingAccount,
        new anchor.BN(100),
        optionMarket
      );
    });
    beforeEach(async () => {
      size = new u64(1);
    });

    describe("No quotes in pool", () => {
      it("should error", async () => {
        try {
          await burnWriterForQuote(
            program,
            minter,
            size,
            optionMarket.key,
            optionMarket.writerTokenMint,
            minterWriterAcct.publicKey,
            optionMarket.quoteAssetPool,
            minterQuoteAccount.publicKey
          );
          assert.ok(false);
        } catch (err) {
          const errMsg = "Not enough assets in the quote asset pool";
          assert.equal((err as AnchorError).error.errorMessage, errMsg);
        }
      });
    });
    describe("someone has exercised", () => {
      before(async () => {
        const optionToken = new Token(
          provider.connection,
          optionMarket.optionMint,
          TOKEN_PROGRAM_ID,
          payer
        );
        // Create an exerciser
        const {
          optionAccount: exerciserOptionAcct,
          quoteAccount: exerciserQuoteAcct,
          underlyingAccount: exerciserUnderlyingAcct,
        } = await createExerciser(
          provider.connection,
          exerciser,
          mintAuthority,
          quoteToken,
          new anchor.BN(100)
            .mul(optionMarket.quoteAmountPerContract)
            .muln(2)
            .toNumber(),
          optionMarket.optionMint,
          optionMarket.underlyingAssetMint
        );
        // Transfer options to the exerciser
        await optionToken.transfer(
          minterOptionAcct.publicKey,
          exerciserOptionAcct.publicKey,
          minter,
          [],
          new u64(100)
        );
        // exercise 100 options so there's plenty of quote assets in the pool
        await exerciseOptionTx(
          program,
          new anchor.BN(100),
          optionMarket.key,
          optionMarket.optionMint,
          exerciser,
          exerciser,
          exerciserOptionAcct.publicKey,
          optionMarket.underlyingAssetPool,
          exerciserUnderlyingAcct.publicKey,
          optionMarket.quoteAssetPool,
          exerciserQuoteAcct.publicKey,
          [
            {
              pubkey: exerciseFeeKey,
              isWritable: true,
              isSigner: false,
            },
          ]
        );
      });
      describe("proper burn writer for quote", () => {
        it("should burn the WriteToken and transfer the quote assets", async () => {
          const writerMintBefore = await writerToken.getMintInfo();
          const writerQuoteBefore = await quoteToken.getAccountInfo(
            minterQuoteAccount.publicKey
          );
          try {
            await burnWriterForQuote(
              program,
              minter,
              size,
              optionMarket.key,
              optionMarket.writerTokenMint,
              minterWriterAcct.publicKey,
              optionMarket.quoteAssetPool,
              minterQuoteAccount.publicKey
            );
          } catch (err) {
            console.error((err as AnchorError).error.errorMessage);
            throw err;
          }
          const writerMintAfter = await writerToken.getMintInfo();
          const writerMintDiff = writerMintAfter.supply.sub(
            writerMintBefore.supply
          );
          assert.equal(writerMintDiff.toString(), size.neg().toString());

          const writerQuoteAfter = await quoteToken.getAccountInfo(
            minterQuoteAccount.publicKey
          );
          const writerQuoteDiff = writerQuoteAfter.amount.sub(
            writerQuoteBefore.amount
          );
          assert.equal(
            writerQuoteDiff.toString(),
            size.mul(quoteAmountPerContract).toString()
          );
        });
      });
      describe("Quote pool does not match OptionMarket", () => {
        let badQuotePool: Keypair;
        before(async () => {
          const { tokenAccount } = await initNewTokenAccount(
            provider.connection,
            payer.publicKey,
            underlyingToken.publicKey,
            payer
          );
          badQuotePool = tokenAccount;
        });
        it("should error", async () => {
          try {
            await burnWriterForQuote(
              program,
              minter,
              size,
              optionMarket.key,
              optionMarket.writerTokenMint,
              minterWriterAcct.publicKey,
              badQuotePool.publicKey,
              minterQuoteAccount.publicKey
            );
            assert.ok(false);
          } catch (err) {
            const errMsg =
              "Quote pool account does not match the value on the OptionMarket";
            assert.equal((err as AnchorError).error.errorMessage, errMsg);
          }
        });
      });
      describe("WriterToken mint does not match OptionMarket", () => {
        let badWriterMint: Keypair;
        before(async () => {
          const { mintAccount } = await initNewTokenMint(
            provider.connection,
            payer.publicKey,
            payer
          );
          badWriterMint = mintAccount;
        });
        it("should error", async () => {
          try {
            await burnWriterForQuote(
              program,
              minter,
              size,
              optionMarket.key,
              badWriterMint.publicKey,
              minterWriterAcct.publicKey,
              optionMarket.quoteAssetPool,
              minterQuoteAccount.publicKey
            );
            assert.ok(false);
          } catch (err) {
            const errMsg =
              "WriterToken mint does not match the value on the OptionMarket";
            assert.equal((err as AnchorError).error.errorMessage, errMsg);
          }
        });
      });
    });
  });
});
Example #19
Source File: mint_tests.ts    From psyoptions with Apache License 2.0 4 votes vote down vote up
describe("mintOption", () => {
  const payer = anchor.web3.Keypair.generate();
  const mintAuthority = anchor.web3.Keypair.generate();
  const program = anchor.workspace.PsyAmerican as Program<PsyAmerican>;
  const provider = program.provider;

  const minter = anchor.web3.Keypair.generate();

  let quoteToken: Token;
  let underlyingToken: Token;
  let optionToken: Token;
  let optionMarket: OptionMarketV2;
  let underlyingAmountPerContract: anchor.BN;
  let quoteAmountPerContract: anchor.BN;
  let expiration: anchor.BN;
  let optionMarketKey: PublicKey;
  let bumpSeed: number;
  let mintFeeKey: PublicKey | null;
  let exerciseFeeKey: PublicKey;
  let optionMintAccount: Keypair;
  let writerTokenMintAccount: Keypair;
  let underlyingAssetPoolAccount: Keypair;
  let quoteAssetPoolAccount: Keypair;
  let remainingAccounts: AccountMeta[] = [];
  let instructions: TransactionInstruction[] = [];

  let optionAccount: Keypair;
  let underlyingAccount: Keypair;
  let writerTokenAccount: Keypair;
  let size = new u64(2);

  beforeEach(async () => {
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(payer.publicKey, 10_000_000_000),
      "confirmed"
    );
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        minter.publicKey,
        10_000_000_000
      ),
      "confirmed"
    );
    size = new u64(2);
  });

  const mintOptionsTx = async (
    opts: {
      underlyingAssetPoolKey?: PublicKey;
      remainingAccounts?: AccountMeta[];
      feeOwner?: PublicKey;
    } = {}
  ) => {
    await program.rpc.mintOption(size, {
      accounts: {
        userAuthority: minter.publicKey,
        underlyingAssetMint: optionMarket?.underlyingAssetMint,
        underlyingAssetPool:
          opts.underlyingAssetPoolKey || optionMarket?.underlyingAssetPool,
        underlyingAssetSrc: underlyingAccount.publicKey,
        optionMint: optionMarket?.optionMint,
        mintedOptionDest: optionAccount.publicKey,
        writerTokenMint: optionMarket?.writerTokenMint,
        mintedWriterTokenDest: writerTokenAccount.publicKey,
        optionMarket: optionMarket?.key,
        feeOwner: opts.feeOwner || FEE_OWNER_KEY,
        tokenProgram: TOKEN_PROGRAM_ID,
        associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
        clock: SYSVAR_CLOCK_PUBKEY,
        rent: SYSVAR_RENT_PUBKEY,
        systemProgram: SystemProgram.programId,
      },
      remainingAccounts: opts.remainingAccounts
        ? opts.remainingAccounts
        : remainingAccounts,
      signers: [minter],
    });
  };

  describe("proper mint", () => {
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        optionToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          size.mul(optionMarket.underlyingAmountPerContract).muln(2).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));
    });
    it("should mint size OptionTokens", async () => {
      try {
        await mintOptionsTx();
      } catch (err) {
        console.error((err as AnchorError).error.errorMessage);
        throw err;
      }
      const mintInfo = await optionToken.getMintInfo();
      assert.equal(mintInfo.supply.toString(), size.toString());
    });

    it("should mint size WriterTokens", async () => {
      try {
        await mintOptionsTx();
      } catch (err) {
        console.error((err as AnchorError).error.errorMessage);
        throw err;
      }
      const writerToken = new Token(
        provider.connection,
        optionMarket.writerTokenMint,
        TOKEN_PROGRAM_ID,
        payer
      );
      const mintInfo = await writerToken.getMintInfo();
      assert.equal(mintInfo.supply.toString(), size.toString());
    });

    it("should transfer the underlying from the minter to the pool and take a fee", async () => {
      if (!mintFeeKey) {
        throw new Error("mintFeeKey wasn't set when it should be");
      }
      const mintFeeAcctBefore = await underlyingToken.getAccountInfo(
        mintFeeKey
      );
      const underlyingPoolBefore = await underlyingToken.getAccountInfo(
        optionMarket.underlyingAssetPool
      );
      const minterUnderlyingBefore = await underlyingToken.getAccountInfo(
        underlyingAccount.publicKey
      );
      try {
        await mintOptionsTx();
      } catch (err) {
        console.error((err as AnchorError).error.errorMessage);
        throw err;
      }
      const expectedUnderlyingTransfered = size.mul(
        underlyingAmountPerContract
      );
      const mintFeeAmountPerContract = feeAmountPerContract(
        underlyingAmountPerContract
      );
      const mintFeeAmount = mintFeeAmountPerContract.mul(size);

      const underlyingPoolAfter = await underlyingToken.getAccountInfo(
        optionMarket.underlyingAssetPool
      );
      const poolDiff = underlyingPoolAfter.amount.sub(
        underlyingPoolBefore.amount
      );
      const mintFeeAcctAfter = await underlyingToken.getAccountInfo(mintFeeKey);
      assert.equal(
        poolDiff.toString(),
        expectedUnderlyingTransfered.toString()
      );

      const minterUnderlyingAfter = await underlyingToken.getAccountInfo(
        underlyingAccount.publicKey
      );
      const minterUnderlyingDiff = minterUnderlyingAfter.amount.sub(
        minterUnderlyingBefore.amount
      );
      assert.equal(
        expectedUnderlyingTransfered.add(mintFeeAmount).neg().toString(),
        minterUnderlyingDiff.toString()
      );
      const mintFeeAcctDiff = mintFeeAcctAfter.amount.sub(
        mintFeeAcctBefore.amount
      );
      assert.equal(mintFeeAcctDiff.toString(), mintFeeAmount.toString());
    });
  });

  describe("OptionMarket expired", () => {
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program, {
        // set expiration to 2 seconds from now
        expiration: new anchor.BN(new Date().getTime() / 1000 + 2),
      }));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          optionMarket.underlyingAmountPerContract.muln(2).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));
    });
    it("should error", async () => {
      try {
        await wait(2000);
        await mintOptionsTx();
        assert.ok(false);
      } catch (err) {
        const errMsg = "OptionMarket is expired, can't mint";
        assert.equal((err as AnchorError).error.errorMessage, errMsg);
      }
    });
  });

  describe("Underlying pool key differs from option market", () => {
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          size.mul(optionMarket.underlyingAmountPerContract.muln(2)).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));
      // Create a new token account and set it as the underlyingAssetPoolAccount
      const { tokenAccount } = await initNewTokenAccount(
        provider.connection,
        payer.publicKey,
        underlyingToken.publicKey,
        payer
      );
      optionMarket.underlyingAssetPool = tokenAccount.publicKey;
    });
    it("should error", async () => {
      try {
        await mintOptionsTx();
        assert.ok(false);
      } catch (err) {
        const errMsg =
          "Underlying pool account does not match the value on the OptionMarket";
        assert.equal((err as AnchorError).error.errorMessage, errMsg);
      }
    });
  });

  describe("OptionToken Mint key differs from option market", () => {
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new token mint and set it as the optionMintAccount
      const { mintAccount } = await initNewTokenMint(
        provider.connection,
        payer.publicKey,
        payer
      );
      optionMarket.optionMint = mintAccount.publicKey;
      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          size.mul(optionMarket.underlyingAmountPerContract.muln(2)).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));
    });
    it("should error", async () => {
      try {
        await mintOptionsTx();
        assert.ok(false);
      } catch (err) {
        const errMsg =
          "OptionToken mint does not match the value on the OptionMarket";
        assert.equal((err as AnchorError).error.errorMessage, errMsg);
      }
    });
  });

  describe("WriterToken Mint key differs from option market", () => {
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new token mint and set it as the optionMintAccount
      const { mintAccount } = await initNewTokenMint(
        provider.connection,
        payer.publicKey,
        payer
      );
      optionMarket.writerTokenMint = mintAccount.publicKey;
      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          size.mul(optionMarket.underlyingAmountPerContract.muln(2)).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));
    });
    it("should error", async () => {
      try {
        await mintOptionsTx();
        assert.ok(false);
      } catch (err) {
        const errMsg =
          "WriterToken mint does not match the value on the OptionMarket";
        assert.equal((err as AnchorError).error.errorMessage, errMsg);
      }
    });
  });

  describe("MintFee account differs from option market", () => {
    let badMintFeeKey: PublicKey;
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new token account and set it as the mintFeeKey
      const { tokenAccount } = await initNewTokenAccount(
        provider.connection,
        FEE_OWNER_KEY,
        underlyingToken.publicKey,
        payer
      );
      badMintFeeKey = tokenAccount.publicKey;

      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          optionMarket.underlyingAmountPerContract.muln(2).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));
    });
    it("should error", async () => {
      try {
        await mintOptionsTx({
          remainingAccounts: [
            {
              pubkey: badMintFeeKey,
              isWritable: true,
              isSigner: false,
            },
          ],
        });
        assert.ok(false);
      } catch (err) {
        const errMsg =
          "MintFee key does not match the value on the OptionMarket";
        assert.equal((err as AnchorError).error.errorMessage, errMsg);
      }
    });
  });

  describe("Size <= 0", () => {
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          optionMarket.underlyingAmountPerContract.muln(2).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));

      // Set the size to 0 to trigger an error
      size = new anchor.BN(0);
    });
    it("should error", async () => {
      try {
        await mintOptionsTx();
        assert.ok(false);
      } catch (err) {
        const errMsg = "The size argument must be > 0";
        assert.equal((err as AnchorError).error.errorMessage, errMsg);
      }
    });
  });
  describe("Fee owner is incorrect", () => {
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          optionMarket.underlyingAmountPerContract.muln(2).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));
    });
    it("should error", async () => {
      try {
        await mintOptionsTx({
          feeOwner: new Keypair().publicKey,
        });
        assert.ok(false);
      } catch (err) {
        const errMsg = "Fee owner does not match the program's fee owner";
        assert.equal((err as AnchorError).error.errorMessage, errMsg);
      }
    });
  });
  describe("OptionMarket is for NFT", () => {
    beforeEach(async () => {
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        expiration,
        optionMarketKey,
        bumpSeed,
        mintFeeKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program, {
        underlyingAmountPerContract: new anchor.BN("1"),
      }));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      ({ optionAccount, underlyingAccount, writerTokenAccount } =
        await createMinter(
          provider.connection,
          minter,
          mintAuthority,
          underlyingToken,
          optionMarket.underlyingAmountPerContract.muln(2).toNumber(),
          optionMarket.optionMint,
          optionMarket.writerTokenMint,
          quoteToken
        ));
    });
    it("should transfer enough lamports as required by the fee", async () => {
      const minterBefore = await provider.connection.getAccountInfo(
        minter.publicKey
      );
      const feeOwnerBefore =
        (await provider.connection.getAccountInfo(FEE_OWNER_KEY))?.lamports ||
        0;
      try {
        await mintOptionsTx();
      } catch (err) {
        console.error((err as AnchorError).error.errorMessage);
        throw err;
      }
      const minterAfter = await provider.connection.getAccountInfo(
        minter.publicKey
      );
      const feeOwnerAfter =
        (await provider.connection.getAccountInfo(FEE_OWNER_KEY))?.lamports ||
        0;
      if (!minterAfter?.lamports || !minterBefore?.lamports) {
        throw new Error("minter has no lamports");
      }
      const minterDiff = minterAfter?.lamports - minterBefore?.lamports;
      const feeOwnerDiff = feeOwnerAfter - feeOwnerBefore;
      assert.equal(-minterDiff, size.mul(new BN(NFT_MINT_LAMPORTS)));
      assert.equal(feeOwnerDiff, size.mul(new BN(NFT_MINT_LAMPORTS)));
    });
  });
});
Example #20
Source File: exercise_v2.ts    From psyoptions with Apache License 2.0 4 votes vote down vote up
describe("exerciseOption", () => {
  const payer = anchor.web3.Keypair.generate();
  const mintAuthority = anchor.web3.Keypair.generate();
  const program = anchor.workspace.PsyAmerican as anchor.Program<PsyAmerican>;
  const provider = program.provider;
  // @ts-ignore
  const wallet = provider.wallet as unknown as anchor.Wallet;

  const minter = anchor.web3.Keypair.generate();
  const minterProvider = new AnchorProvider(
    provider.connection,
    new Wallet(minter),
    {}
  );
  const minterProgram = new Program(
    program.idl,
    program.programId,
    minterProvider
  );
  const exerciser = anchor.web3.Keypair.generate();
  const exerciserProvider = new AnchorProvider(
    provider.connection,
    new Wallet(exerciser),
    {}
  );
  const exerciserProgram = new Program(
    program.idl,
    program.programId,
    exerciserProvider
  );

  let quoteToken: Token;
  let underlyingToken: Token;
  let optionToken: Token;
  let underlyingAmountPerContract: anchor.BN;
  let quoteAmountPerContract: anchor.BN;
  let optionMarketKey: PublicKey;
  let optionMarket: OptionMarketWithKey;
  let exerciseFeeKey: PublicKey;
  let exerciserOptionAcct: Keypair;
  let exerciserQuoteAcct: Keypair;
  let exerciserUnderlyingAcct: Keypair;
  let remainingAccounts: AccountMeta[] = [];
  let instructions: TransactionInstruction[] = [];

  let size = new u64(2);

  before(async () => {
    // airdrop SOL to the payer and minter
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        payer.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        minter.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        exerciser.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
  });

  describe("Non-nft OptionMarket", () => {
    before(async () => {
      // Initialize a new OptionMarket
      ({
        quoteToken,
        underlyingToken,
        optionToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        optionMarketKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new minter
      const {
        optionAccount: minterOptionAcct,
        underlyingAccount: minterUnderlyingAccount,
        writerTokenAccount: minterWriterAcct,
      } = await createMinter(
        provider.connection,
        minter,
        mintAuthority,
        underlyingToken,
        new anchor.BN(100)
          .mul(optionMarket.underlyingAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.writerTokenMint,
        quoteToken
      );
      // Mint a bunch of contracts to the minter
      const { ix: mintOptionsIx } =
        await psyAmericanInstructions.mintOptionV2Instruction(
          minterProgram,
          minterOptionAcct.publicKey,
          minterWriterAcct.publicKey,
          minterUnderlyingAccount.publicKey,
          new anchor.BN(100),
          optionMarket
        );
      await program.provider.sendAndConfirm!(
        new Transaction().add(mintOptionsIx),
        [minter]
      );
      // Create an exerciser
      ({
        optionAccount: exerciserOptionAcct,
        quoteAccount: exerciserQuoteAcct,
        underlyingAccount: exerciserUnderlyingAcct,
      } = await createExerciser(
        provider.connection,
        exerciser,
        mintAuthority,
        quoteToken,
        new anchor.BN(100)
          .mul(optionMarket.quoteAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        underlyingToken.publicKey
      ));

      // Transfer a options to the exerciser
      await optionToken.transfer(
        minterOptionAcct.publicKey,
        exerciserOptionAcct.publicKey,
        minter,
        [],
        new u64(100)
      );
    });
    beforeEach(async () => {
      size = new u64(2);
    });

    it("should be properly setup", async () => {
      const exerciserOption = await optionToken.getAccountInfo(
        exerciserOptionAcct.publicKey
      );
      assert.equal(exerciserOption.amount.toString(), new u64(100).toString());
    });

    describe("proper exercise", () => {
      it("should burn the option token, swap the quote and underlying assets", async () => {
        const optionTokenBefore = await optionToken.getMintInfo();
        const underlyingPoolBefore = await underlyingToken.getAccountInfo(
          optionMarket.underlyingAssetPool
        );
        const quotePoolBefore = await quoteToken.getAccountInfo(
          optionMarket.quoteAssetPool
        );
        const exerciserQuoteBefore = await quoteToken.getAccountInfo(
          exerciserQuoteAcct.publicKey
        );
        try {
          const instruction =
            psyAmericanInstructions.exerciseOptionsV2Instruction(
              exerciserProgram,
              size,
              optionMarket,
              exerciserOptionAcct.publicKey,
              exerciserUnderlyingAcct.publicKey,
              exerciserQuoteAcct.publicKey
            );
          await exerciserProgram.provider.sendAndConfirm!(
            new Transaction().add(instruction)
          );
        } catch (err) {
          console.error((err as AnchorError).error.errorMessage);
          throw err;
        }
        const optionTokenAfter = await optionToken.getMintInfo();
        const optionTokenDiff = optionTokenAfter.supply.sub(
          optionTokenBefore.supply
        );
        assert.equal(optionTokenDiff.toString(), size.neg().toString());

        const underlyingPoolAfter = await underlyingToken.getAccountInfo(
          optionMarket.underlyingAssetPool
        );
        const underlyingPoolDiff = underlyingPoolAfter.amount.sub(
          underlyingPoolBefore.amount
        );
        assert.equal(
          underlyingPoolDiff.toString(),
          size.mul(underlyingAmountPerContract).neg().toString()
        );

        const quotePoolAfter = await quoteToken.getAccountInfo(
          optionMarket.quoteAssetPool
        );
        const quotePoolDiff = quotePoolAfter.amount.sub(quotePoolBefore.amount);
        assert.equal(
          quotePoolDiff.toString(),
          size.mul(quoteAmountPerContract).toString()
        );

        const exerciserQuoteAfter = await quoteToken.getAccountInfo(
          exerciserQuoteAcct.publicKey
        );
        const exerciserQuoteDiff = exerciserQuoteAfter.amount.sub(
          exerciserQuoteBefore.amount
        );
        const exerciseFee = new BN(0);
        assert.equal(
          exerciserQuoteDiff.neg().toString(),
          exerciseFee.add(size.mul(quoteAmountPerContract)).toString()
        );
      });
    });
    describe("quote asset pool is not the same as the OptionMarket", () => {
      let badQuoteAssetPoolAcct: Keypair;
      beforeEach(async () => {
        // Create a new token account and set it as the mintFeeKey
        const { tokenAccount } = await initNewTokenAccount(
          provider.connection,
          FEE_OWNER_KEY,
          quoteToken.publicKey,
          payer
        );
        badQuoteAssetPoolAcct = tokenAccount;
      });
      it("should error", async () => {
        try {
          const instruction =
            psyAmericanInstructions.exerciseOptionsV2Instruction(
              exerciserProgram,
              size,
              {
                ...optionMarket,
                quoteAssetPool: badQuoteAssetPoolAcct.publicKey,
              },
              exerciserOptionAcct.publicKey,
              exerciserUnderlyingAcct.publicKey,
              exerciserQuoteAcct.publicKey
            );
          await exerciserProgram.provider.sendAndConfirm!(
            new Transaction().add(instruction)
          );
          assert.ok(false);
        } catch (err) {
          const programError = parseTransactionError(err);
          console.log("*** programError", programError);
          const errMsg =
            "Quote pool account does not match the value on the OptionMarket";
          assert.equal(programError.msg, errMsg);
        }
      });
    });
    describe("Underlying asset pool is not the same as the OptionMarket", () => {
      let badUnderlyingAssetPoolAcct: Keypair;
      beforeEach(async () => {
        // Create a new token account and set it as the mintFeeKey
        const { tokenAccount } = await initNewTokenAccount(
          provider.connection,
          FEE_OWNER_KEY,
          underlyingToken.publicKey,
          payer
        );
        badUnderlyingAssetPoolAcct = tokenAccount;
      });
      it("should error", async () => {
        try {
          const instruction =
            psyAmericanInstructions.exerciseOptionsV2Instruction(
              exerciserProgram,
              size,
              {
                ...optionMarket,
                underlyingAssetPool: badUnderlyingAssetPoolAcct.publicKey,
              },
              exerciserOptionAcct.publicKey,
              exerciserUnderlyingAcct.publicKey,
              exerciserQuoteAcct.publicKey
            );
          await exerciserProgram.provider.sendAndConfirm!(
            new Transaction().add(instruction)
          );
          assert.ok(false);
        } catch (err) {
          const programError = parseTransactionError(err);
          const errMsg =
            "Underlying pool account does not match the value on the OptionMarket";
          assert.equal(programError.msg, errMsg);
        }
      });
    });
    describe("Underlying destination mint is not the same as the underlying asset", () => {
      let badUnderlyingDest: Keypair;
      beforeEach(async () => {
        // Create a new token account and set it as the mintFeeKey
        const { tokenAccount } = await initNewTokenAccount(
          provider.connection,
          FEE_OWNER_KEY,
          quoteToken.publicKey,
          payer
        );
        badUnderlyingDest = tokenAccount;
      });
      it("should error", async () => {
        try {
          const instruction =
            psyAmericanInstructions.exerciseOptionsV2Instruction(
              exerciserProgram,
              size,
              optionMarket,
              exerciserOptionAcct.publicKey,
              badUnderlyingDest.publicKey,
              exerciserQuoteAcct.publicKey
            );
          await exerciserProgram.provider.sendAndConfirm!(
            new Transaction().add(instruction)
          );
          assert.ok(false);
        } catch (err) {
          const programError = parseTransactionError(err);
          const errMsg =
            "Underlying destination mint must match underlying asset mint address";
          assert.equal(programError.msg, errMsg);
        }
      });
    });
    describe("OptionToken Mint is not the same as the OptionMarket", () => {
      let badOptionToken: Token;
      beforeEach(async () => {
        // Create a new token account and set it as the mintFeeKey
        const { mintAccount } = await initNewTokenMint(
          provider.connection,
          FEE_OWNER_KEY,
          payer
        );
        badOptionToken = new Token(
          provider.connection,
          mintAccount.publicKey,
          TOKEN_PROGRAM_ID,
          payer
        );
      });
      it("should error", async () => {
        try {
          const instruction =
            psyAmericanInstructions.exerciseOptionsV2Instruction(
              exerciserProgram,
              size,
              { ...optionMarket, optionMint: badOptionToken.publicKey },
              exerciserOptionAcct.publicKey,
              exerciserUnderlyingAcct.publicKey,
              exerciserQuoteAcct.publicKey
            );
          await exerciserProgram.provider.sendAndConfirm!(
            new Transaction().add(instruction)
          );
          assert.ok(false);
        } catch (err) {
          const programError = parseTransactionError(err);
          const errMsg =
            "OptionToken mint does not match the value on the OptionMarket";
          assert.equal(programError.msg, errMsg);
        }
      });
    });
  });

  describe("Expired option market", () => {
    before(async () => {
      // Initialize a new OptionMarket
      ({
        quoteToken,
        underlyingToken,
        optionToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        optionMarketKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program, {
        // set expiration to 2 seconds from now
        expiration: new anchor.BN(new Date().getTime() / 1000 + 4),
      }));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new minter
      const {
        optionAccount: minterOptionAcct,
        underlyingAccount: minterUnderlyingAccount,
        writerTokenAccount: minterWriterAcct,
      } = await createMinter(
        provider.connection,
        minter,
        mintAuthority,
        underlyingToken,
        new anchor.BN(100)
          .mul(optionMarket.underlyingAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.writerTokenMint,
        quoteToken
      );
      // Mint a bunch of contracts to the minter
      const { ix: mintOptionsIx } =
        await psyAmericanInstructions.mintOptionV2Instruction(
          minterProgram,
          minterOptionAcct.publicKey,
          minterWriterAcct.publicKey,
          minterUnderlyingAccount.publicKey,
          new anchor.BN(100),
          optionMarket
        );
      await program.provider.sendAndConfirm!(
        new Transaction().add(mintOptionsIx),
        [minter]
      );
      // Create an exerciser
      ({
        optionAccount: exerciserOptionAcct,
        quoteAccount: exerciserQuoteAcct,
        underlyingAccount: exerciserUnderlyingAcct,
      } = await createExerciser(
        provider.connection,
        exerciser,
        mintAuthority,
        quoteToken,
        new anchor.BN(100)
          .mul(optionMarket.quoteAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.underlyingAssetMint
      ));

      // Transfer a options to the exerciser
      await optionToken.transfer(
        minterOptionAcct.publicKey,
        exerciserOptionAcct.publicKey,
        minter,
        [],
        new u64(100)
      );
    });
    beforeEach(async () => {
      size = new u64(2);
    });
    it("should error", async () => {
      try {
        await wait(3000);
        const instruction =
          psyAmericanInstructions.exerciseOptionsV2Instruction(
            exerciserProgram,
            size,
            optionMarket,
            exerciserOptionAcct.publicKey,
            exerciserUnderlyingAcct.publicKey,
            exerciserQuoteAcct.publicKey
          );
        await exerciserProgram.provider.sendAndConfirm!(
          new Transaction().add(instruction)
        );
        assert.ok(false);
      } catch (err) {
        const programError = parseTransactionError(err);
        const errMsg = "OptionMarket is expired, can't exercise";
        assert.equal(programError.msg, errMsg);
      }
    });
  });
});
Example #21
Source File: exercise_tests.ts    From psyoptions with Apache License 2.0 4 votes vote down vote up
describe("exerciseOption", () => {
  const payer = anchor.web3.Keypair.generate();
  const mintAuthority = anchor.web3.Keypair.generate();
  const program = anchor.workspace.PsyAmerican as Program<PsyAmerican>;
  const provider = program.provider;
  // @ts-ignore
  const wallet = provider.wallet as unknown as anchor.Wallet;

  const minter = anchor.web3.Keypair.generate();
  const minterProvider = new AnchorProvider(
    provider.connection,
    new anchor.Wallet(minter),
    {}
  );
  const minterProgram = new Program(
    program.idl,
    program.programId,
    minterProvider
  );
  const exerciser = anchor.web3.Keypair.generate();

  let quoteToken: Token;
  let underlyingToken: Token;
  let optionToken: Token;
  let underlyingAmountPerContract: anchor.BN;
  let quoteAmountPerContract: anchor.BN;
  let optionMarketKey: PublicKey;
  let optionMarket: OptionMarketWithKey;
  let exerciseFeeKey: PublicKey;
  let exerciserOptionAcct: Keypair;
  let exerciserQuoteAcct: Keypair;
  let exerciserUnderlyingAcct: Keypair;
  let remainingAccounts: AccountMeta[] = [];
  let instructions: TransactionInstruction[] = [];

  let size = new u64(2);

  before(async () => {
    // airdrop SOL to the payer and minter
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        payer.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        minter.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        exerciser.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
  });

  describe("Non-nft OptionMarket", () => {
    before(async () => {
      // Initialize a new OptionMarket
      ({
        quoteToken,
        underlyingToken,
        optionToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        optionMarketKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new minter
      const {
        optionAccount: minterOptionAcct,
        underlyingAccount: minterUnderlyingAccount,
        writerTokenAccount: minterWriterAcct,
      } = await createMinter(
        provider.connection,
        minter,
        mintAuthority,
        underlyingToken,
        new anchor.BN(100)
          .mul(optionMarket.underlyingAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.writerTokenMint,
        quoteToken
      );
      // Mint a bunch of contracts to the minter
      const { ix: mintOptionsIx } =
        await psyAmericanInstructions.mintOptionV2Instruction(
          minterProgram,
          minterOptionAcct.publicKey,
          minterWriterAcct.publicKey,
          minterUnderlyingAccount.publicKey,
          new anchor.BN(100),
          optionMarket
        );
      await provider.sendAndConfirm!(new Transaction().add(mintOptionsIx), [
        minter,
      ]);
      // Create an exerciser
      ({
        optionAccount: exerciserOptionAcct,
        quoteAccount: exerciserQuoteAcct,
        underlyingAccount: exerciserUnderlyingAcct,
      } = await createExerciser(
        provider.connection,
        exerciser,
        mintAuthority,
        quoteToken,
        new anchor.BN(100)
          .mul(optionMarket.quoteAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        underlyingToken.publicKey
      ));

      // Transfer a options to the exerciser
      await optionToken.transfer(
        minterOptionAcct.publicKey,
        exerciserOptionAcct.publicKey,
        minter,
        [],
        new u64(100)
      );
    });
    beforeEach(async () => {
      size = new u64(2);
    });

    it("should be properly setup", async () => {
      const exerciserOption = await optionToken.getAccountInfo(
        exerciserOptionAcct.publicKey
      );
      assert.equal(exerciserOption.amount.toString(), new u64(100).toString());
    });

    describe("proper exercise", () => {
      it("should burn the option token, swap the quote and underlying assets", async () => {
        const optionTokenBefore = await optionToken.getMintInfo();
        const underlyingPoolBefore = await underlyingToken.getAccountInfo(
          optionMarket.underlyingAssetPool
        );
        const quotePoolBefore = await quoteToken.getAccountInfo(
          optionMarket.quoteAssetPool
        );
        const exerciserQuoteBefore = await quoteToken.getAccountInfo(
          exerciserQuoteAcct.publicKey
        );
        try {
          await exerciseOptionTx(
            program,
            size,
            optionMarket.key,
            optionMarket.optionMint,
            exerciser,
            exerciser,
            exerciserOptionAcct.publicKey,
            optionMarket.underlyingAssetPool,
            exerciserUnderlyingAcct.publicKey,
            optionMarket.quoteAssetPool,
            exerciserQuoteAcct.publicKey,
            [
              {
                pubkey: exerciseFeeKey,
                isWritable: true,
                isSigner: false,
              },
            ]
          );
        } catch (err) {
          console.error((err as AnchorError).error.errorMessage);
          throw err;
        }
        const optionTokenAfter = await optionToken.getMintInfo();
        const optionTokenDiff = optionTokenAfter.supply.sub(
          optionTokenBefore.supply
        );
        assert.equal(optionTokenDiff.toString(), size.neg().toString());

        const underlyingPoolAfter = await underlyingToken.getAccountInfo(
          optionMarket.underlyingAssetPool
        );
        const underlyingPoolDiff = underlyingPoolAfter.amount.sub(
          underlyingPoolBefore.amount
        );
        assert.equal(
          underlyingPoolDiff.toString(),
          size.mul(underlyingAmountPerContract).neg().toString()
        );

        const quotePoolAfter = await quoteToken.getAccountInfo(
          optionMarket.quoteAssetPool
        );
        const quotePoolDiff = quotePoolAfter.amount.sub(quotePoolBefore.amount);
        assert.equal(
          quotePoolDiff.toString(),
          size.mul(quoteAmountPerContract).toString()
        );

        const exerciserQuoteAfter = await quoteToken.getAccountInfo(
          exerciserQuoteAcct.publicKey
        );
        const exerciserQuoteDiff = exerciserQuoteAfter.amount.sub(
          exerciserQuoteBefore.amount
        );
        const exerciseFeePerContract = feeAmountPerContract(
          quoteAmountPerContract
        );
        const exerciseFee = exerciseFeePerContract.mul(size);
        console.log("*** exerciseFee", exerciseFee.toString());
        assert.equal(
          exerciserQuoteDiff.neg().toString(),
          exerciseFee.add(size.mul(quoteAmountPerContract)).toString()
        );
      });
    });
    describe("exercise fee key does not match OptionMarket", () => {
      let badExerciseFeeKey: PublicKey;
      beforeEach(async () => {
        // Create a new token account and set it as the mintFeeKey
        const { tokenAccount } = await initNewTokenAccount(
          provider.connection,
          FEE_OWNER_KEY,
          quoteToken.publicKey,
          payer
        );
        badExerciseFeeKey = tokenAccount.publicKey;
      });
      it("should error", async () => {
        try {
          await exerciseOptionTx(
            program,
            size,
            optionMarketKey,
            optionToken.publicKey,
            exerciser,
            exerciser,
            exerciserOptionAcct.publicKey,
            optionMarket.underlyingAssetPool,
            exerciserUnderlyingAcct.publicKey,
            optionMarket.quoteAssetPool,
            exerciserQuoteAcct.publicKey,
            [
              {
                pubkey: badExerciseFeeKey,
                isWritable: true,
                isSigner: false,
              },
            ]
          );
          assert.ok(false);
        } catch (err) {
          const errMsg =
            "exerciseFee key does not match the value on the OptionMarket";
          assert.equal((err as AnchorError).error.errorMessage, errMsg);
        }
      });
    });
    describe("quote asset pool is not the same as the OptionMarket", () => {
      let badQuoteAssetPoolAcct: Keypair;
      beforeEach(async () => {
        // Create a new token account and set it as the mintFeeKey
        const { tokenAccount } = await initNewTokenAccount(
          provider.connection,
          FEE_OWNER_KEY,
          quoteToken.publicKey,
          payer
        );
        badQuoteAssetPoolAcct = tokenAccount;
      });
      it("should error", async () => {
        try {
          await exerciseOptionTx(
            program,
            size,
            optionMarket.key,
            optionMarket.optionMint,
            exerciser,
            exerciser,
            exerciserOptionAcct.publicKey,
            optionMarket.underlyingAssetPool,
            exerciserUnderlyingAcct.publicKey,
            badQuoteAssetPoolAcct.publicKey,
            exerciserQuoteAcct.publicKey,
            [
              {
                pubkey: exerciseFeeKey,
                isWritable: true,
                isSigner: false,
              },
            ]
          );
          assert.ok(false);
        } catch (err) {
          const errMsg =
            "Quote pool account does not match the value on the OptionMarket";
          assert.equal((err as AnchorError).error.errorMessage, errMsg);
        }
      });
    });
    describe("Underlying asset pool is not the same as the OptionMarket", () => {
      let badUnderlyingAssetPoolAcct: Keypair;
      beforeEach(async () => {
        // Create a new token account and set it as the mintFeeKey
        const { tokenAccount } = await initNewTokenAccount(
          provider.connection,
          FEE_OWNER_KEY,
          underlyingToken.publicKey,
          payer
        );
        badUnderlyingAssetPoolAcct = tokenAccount;
      });
      it("should error", async () => {
        try {
          await exerciseOptionTx(
            program,
            size,
            optionMarket.key,
            optionMarket.optionMint,
            exerciser,
            exerciser,
            exerciserOptionAcct.publicKey,
            badUnderlyingAssetPoolAcct.publicKey,
            exerciserUnderlyingAcct.publicKey,
            optionMarket.quoteAssetPool,
            exerciserQuoteAcct.publicKey,
            [
              {
                pubkey: exerciseFeeKey,
                isWritable: true,
                isSigner: false,
              },
            ]
          );
          assert.ok(false);
        } catch (err) {
          const errMsg =
            "Underlying pool account does not match the value on the OptionMarket";
          assert.equal((err as AnchorError).error.errorMessage, errMsg);
        }
      });
    });
    describe("Underlying destination mint is not the same as the underlying asset", () => {
      let badUnderlyingDest: Keypair;
      beforeEach(async () => {
        // Create a new token account and set it as the mintFeeKey
        const { tokenAccount } = await initNewTokenAccount(
          provider.connection,
          FEE_OWNER_KEY,
          quoteToken.publicKey,
          payer
        );
        badUnderlyingDest = tokenAccount;
      });
      it("should error", async () => {
        try {
          await exerciseOptionTx(
            program,
            size,
            optionMarket.key,
            optionMarket.optionMint,
            exerciser,
            exerciser,
            exerciserOptionAcct.publicKey,
            optionMarket.underlyingAssetPool,
            badUnderlyingDest.publicKey,
            optionMarket.quoteAssetPool,
            exerciserQuoteAcct.publicKey,
            [
              {
                pubkey: exerciseFeeKey,
                isWritable: true,
                isSigner: false,
              },
            ]
          );
          assert.ok(false);
        } catch (err) {
          const errMsg =
            "Underlying destination mint must match underlying asset mint address";
          assert.equal((err as AnchorError).error.errorMessage, errMsg);
        }
      });
    });
    describe("OptionToken Mint is not the same as the OptionMarket", () => {
      let badOptionToken: Token;
      beforeEach(async () => {
        // Create a new token account and set it as the mintFeeKey
        const { mintAccount } = await initNewTokenMint(
          provider.connection,
          FEE_OWNER_KEY,
          payer
        );
        badOptionToken = new Token(
          provider.connection,
          mintAccount.publicKey,
          TOKEN_PROGRAM_ID,
          payer
        );
      });
      it("should error", async () => {
        try {
          await exerciseOptionTx(
            program,
            size,
            optionMarket.key,
            badOptionToken.publicKey,
            exerciser,
            exerciser,
            exerciserOptionAcct.publicKey,
            optionMarket.underlyingAssetPool,
            exerciserUnderlyingAcct.publicKey,
            optionMarket.quoteAssetPool,
            exerciserQuoteAcct.publicKey,
            [
              {
                pubkey: exerciseFeeKey,
                isWritable: true,
                isSigner: false,
              },
            ]
          );
          assert.ok(false);
        } catch (err) {
          const errMsg =
            "OptionToken mint does not match the value on the OptionMarket";
          assert.equal((err as AnchorError).error.errorMessage, errMsg);
        }
      });
    });
    describe("Fee Owner does not match the program's fee owner", () => {
      let badFeeOwner: Keypair;
      beforeEach(async () => {
        badFeeOwner = new Keypair();
      });
      it("should error", async () => {
        try {
          await exerciseOptionTx(
            program,
            size,
            optionMarket.key,
            optionMarket.optionMint,
            exerciser,
            exerciser,
            exerciserOptionAcct.publicKey,
            optionMarket.underlyingAssetPool,
            exerciserUnderlyingAcct.publicKey,
            optionMarket.quoteAssetPool,
            exerciserQuoteAcct.publicKey,
            [
              {
                pubkey: exerciseFeeKey,
                isWritable: true,
                isSigner: false,
              },
            ],
            { feeOwner: badFeeOwner.publicKey }
          );
          assert.ok(false);
        } catch (err) {
          const errMsg = "Fee owner does not match the program's fee owner";
          assert.equal((err as AnchorError).error.errorMessage, errMsg);
        }
      });
    });
  });

  describe("Expired option market", () => {
    before(async () => {
      // Initialize a new OptionMarket
      ({
        quoteToken,
        underlyingToken,
        optionToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        optionMarketKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program, {
        // set expiration to 2 seconds from now
        expiration: new anchor.BN(new Date().getTime() / 1000 + 4),
      }));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new minter
      const {
        optionAccount: minterOptionAcct,
        underlyingAccount: minterUnderlyingAccount,
        writerTokenAccount: minterWriterAcct,
      } = await createMinter(
        provider.connection,
        minter,
        mintAuthority,
        underlyingToken,
        new anchor.BN(100)
          .mul(optionMarket.underlyingAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.writerTokenMint,
        quoteToken
      );
      // Mint a bunch of contracts to the minter
      const { ix: mintOptionsIx } =
        await psyAmericanInstructions.mintOptionV2Instruction(
          minterProgram,
          minterOptionAcct.publicKey,
          minterWriterAcct.publicKey,
          minterUnderlyingAccount.publicKey,
          new anchor.BN(100),
          optionMarket
        );
      await program.provider.sendAndConfirm!(
        new Transaction().add(mintOptionsIx),
        [minter]
      );
      // Create an exerciser
      ({
        optionAccount: exerciserOptionAcct,
        quoteAccount: exerciserQuoteAcct,
        underlyingAccount: exerciserUnderlyingAcct,
      } = await createExerciser(
        provider.connection,
        exerciser,
        mintAuthority,
        quoteToken,
        new anchor.BN(100)
          .mul(optionMarket.quoteAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.underlyingAssetMint
      ));

      // Transfer a options to the exerciser
      await optionToken.transfer(
        minterOptionAcct.publicKey,
        exerciserOptionAcct.publicKey,
        minter,
        [],
        new u64(100)
      );
    });
    beforeEach(async () => {
      size = new u64(2);
    });
    it("should error", async () => {
      try {
        await wait(3000);
        await exerciseOptionTx(
          program,
          size,
          optionMarket.key,
          optionMarket.optionMint,
          exerciser,
          exerciser,
          exerciserOptionAcct.publicKey,
          optionMarket.underlyingAssetPool,
          exerciserUnderlyingAcct.publicKey,
          optionMarket.quoteAssetPool,
          exerciserQuoteAcct.publicKey,
          [
            {
              pubkey: exerciseFeeKey,
              isWritable: true,
              isSigner: false,
            },
          ]
        );
        assert.ok(false);
      } catch (err) {
        const errMsg = "OptionMarket is expired, can't exercise";
        assert.equal((err as AnchorError).error.errorMessage, errMsg);
      }
    });
  });
  describe("OptionMarket is for NFT", () => {
    before(async () => {
      // Initialize a new OptionMarket
      ({
        quoteToken,
        underlyingToken,
        optionToken,
        underlyingAmountPerContract,
        quoteAmountPerContract,
        optionMarketKey,
        exerciseFeeKey,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program, {
        quoteAmountPerContract: new anchor.BN(1),
      }));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new minter
      const {
        optionAccount: minterOptionAcct,
        underlyingAccount: minterUnderlyingAccount,
        writerTokenAccount: minterWriterAcct,
      } = await createMinter(
        provider.connection,
        minter,
        mintAuthority,
        underlyingToken,
        new anchor.BN(100)
          .mul(optionMarket.underlyingAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.writerTokenMint,
        quoteToken
      );
      // Mint a bunch of contracts to the minter
      const { ix: mintOptionsIx } =
        await psyAmericanInstructions.mintOptionV2Instruction(
          minterProgram,
          minterOptionAcct.publicKey,
          minterWriterAcct.publicKey,
          minterUnderlyingAccount.publicKey,
          new anchor.BN(100),
          optionMarket
        );
      await program.provider.sendAndConfirm!(
        new Transaction().add(mintOptionsIx),
        [minter]
      );
      // Create an exerciser
      ({
        optionAccount: exerciserOptionAcct,
        quoteAccount: exerciserQuoteAcct,
        underlyingAccount: exerciserUnderlyingAcct,
      } = await createExerciser(
        provider.connection,
        exerciser,
        mintAuthority,
        quoteToken,
        new anchor.BN(100)
          .mul(optionMarket.quoteAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.underlyingAssetMint
      ));

      size = new u64(2);
      // Transfer a options to the exerciser
      await optionToken.transfer(
        minterOptionAcct.publicKey,
        exerciserOptionAcct.publicKey,
        minter,
        [],
        size
      );
    });
    it("should transfer enough lamports as required by the fee", async () => {
      const exerciserBefore = await provider.connection.getAccountInfo(
        exerciser.publicKey
      );
      const feeOwnerBefore =
        (await provider.connection.getAccountInfo(FEE_OWNER_KEY))?.lamports ||
        0;
      try {
        await exerciseOptionTx(
          program,
          size,
          optionMarket.key,
          optionMarket.optionMint,
          exerciser,
          exerciser,
          exerciserOptionAcct.publicKey,
          optionMarket.underlyingAssetPool,
          exerciserUnderlyingAcct.publicKey,
          optionMarket.quoteAssetPool,
          exerciserQuoteAcct.publicKey,
          [
            {
              pubkey: exerciseFeeKey,
              isWritable: true,
              isSigner: false,
            },
          ]
        );
      } catch (err) {
        console.error((err as AnchorError).error.errorMessage);
        throw err;
      }
      const exerciserAfter = await provider.connection.getAccountInfo(
        exerciser.publicKey
      );
      const feeOwnerAfter =
        (await provider.connection.getAccountInfo(FEE_OWNER_KEY))?.lamports ||
        0;
      if (!exerciserAfter?.lamports || !exerciserBefore?.lamports) {
        throw new Error("minter has no lamports");
      }
      const exerciserDiff =
        exerciserAfter?.lamports - exerciserBefore?.lamports;
      const feeOwnerDiff = feeOwnerAfter - feeOwnerBefore;
      assert.equal(
        -exerciserDiff,
        size.mul(new BN(NFT_MINT_LAMPORTS)).toNumber()
      );
      assert.equal(
        feeOwnerDiff,
        size.mul(new BN(NFT_MINT_LAMPORTS)).toNumber()
      );
    });
  });
});
Example #22
Source File: newOrder.ts    From psyoptions with Apache License 2.0 4 votes vote down vote up
describe("cpi_examples newOrder", () => {
  const program = anchor.workspace.CpiExamples as Program<CpiExamples>;
  const provider = program.provider;
  const americanOptionsProgram = anchor.workspace
    .PsyAmerican as Program<PsyAmerican>;

  const payer = web3.Keypair.fromSecretKey(
    Buffer.from(
      JSON.parse(
        require("fs").readFileSync(process.env.ANCHOR_WALLET, {
          encoding: "utf-8",
        })
      )
    )
  );
  const wallet = payer;
  const mintAuthority = anchor.web3.Keypair.generate();
  let underlyingToken: Token, usdcToken: Token, optionToken: Token;
  // Global PsyOptions variables
  let optionMarket: OptionMarketV2;
  // Global DEX variables
  let marketProxy: MarketProxy,
    marketAuthority: anchor.web3.PublicKey,
    marketAuthorityBump: number,
    usdcMint: anchor.web3.PublicKey,
    usdcMintInfo: MintInfo,
    referral: anchor.web3.PublicKey,
    openOrders: PublicKey,
    openOrdersBump: number,
    vault: anchor.web3.PublicKey,
    vaultBumpSeed: number,
    vaultAuthority: anchor.web3.PublicKey,
    vaultAuthBump: number;
  before(async () => {
    // Setup - Create an OptionMarket
    const {
      optionMarket: newOptionMarket,
      remainingAccounts,
      instructions,
    } = await initSetup(
      provider,
      wallet,
      mintAuthority,
      americanOptionsProgram
    );
    optionMarket = newOptionMarket;
    await initOptionMarket(
      americanOptionsProgram,
      wallet,
      optionMarket,
      remainingAccounts,
      instructions
    );
    [usdcMint] = await createMintAndVault(
      provider,
      new anchor.BN("1000000000000000000"),
      undefined,
      6
    );
    // Initialize a permissioned Serum Market
    ({ marketAuthority, marketAuthorityBump } = await getMarketAndAuthorityInfo(
      americanOptionsProgram,
      optionMarket,
      DEX_PID,
      usdcMint
    ));
    // Setup - Create a Serum market for the OptionMarket's option tokens
    ({ marketA: marketProxy } = await initMarket(
      provider,
      americanOptionsProgram,
      marketLoader(provider, program, optionMarket.key, marketAuthorityBump),
      optionMarket,
      usdcMint
    ));
    // Set the token variables for use in later tests
    underlyingToken = new Token(
      provider.connection,
      optionMarket.underlyingAssetMint,
      TOKEN_PROGRAM_ID,
      wallet
    );
    optionToken = new Token(
      provider.connection,
      optionMarket.optionMint,
      TOKEN_PROGRAM_ID,
      wallet
    );
    usdcToken = new Token(
      provider.connection,
      usdcMint,
      TOKEN_PROGRAM_ID,
      wallet
    );
    referral = await usdcToken.createAssociatedTokenAccount(FEE_OWNER_KEY);
  });

  describe("cpi_examples initNewOrderVault", () => {
    it("should create a USDC vault owned by the program", async () => {
      // Generate a PDA for the USDC vault
      [vault, vaultBumpSeed] = await anchor.web3.PublicKey.findProgramAddress(
        [usdcToken.publicKey.toBuffer(), textEncoder.encode("vault")],
        program.programId
      );
      [vaultAuthority, vaultAuthBump] =
        await anchor.web3.PublicKey.findProgramAddress(
          [vault.toBuffer(), textEncoder.encode("vaultAuthority")],
          program.programId
        );
      try {
        await program.rpc.initNewOrderVault({
          accounts: {
            authority: wallet.publicKey,
            usdcMint: usdcMint,
            vault,
            vaultAuthority,
            tokenProgram: TOKEN_PROGRAM_ID,
            rent: SYSVAR_RENT_PUBKEY,
            systemProgram: SystemProgram.programId,
          },
        });
      } catch (err) {
        console.log((err as Error).toString());
        throw err;
      }

      // validate that the vault was initialized and owned by the program
      const vaultAcct = await usdcToken.getAccountInfo(vault);
      assert.ok(vaultAcct.owner.equals(vaultAuthority));
    });
  });

  describe("place newOrder", () => {
    before(async () => {
      // Vault is already initialized because these tests run sequentially
      // transfer USDC to that vault so it can place an order
      usdcMintInfo = await usdcToken.getMintInfo();
      await usdcToken.mintTo(
        vault,
        wallet.publicKey,
        [],
        new u64(10_000_000 * usdcMintInfo.decimals)
      );
      // Get the open orders account that needs to be optionally created
      [openOrders, openOrdersBump] = await PublicKey.findProgramAddress(
        [
          openOrdersSeed,
          marketProxy.dexProgramId.toBuffer(),
          marketProxy.market.address.toBuffer(),
          // NOTE: For other developers, this should be changed to be the User or Vault that has the authority over the account.
          vaultAuthority.toBuffer(),
        ],
        americanOptionsProgram.programId
      );
    });

    it("should create an open orders account and place an order on the Serum market", async () => {
      // test the vault contains USDC
      const vaultAcct = await usdcToken.getAccountInfo(vault);
      assert.equal(
        vaultAcct.amount.toString(),
        new u64(10_000_000 * usdcMintInfo.decimals).toString()
      );
      // test the order book is blank
      let bids = await marketProxy.market.loadBids(provider.connection);
      let l2 = await bids.getL2(3);
      assert.equal(l2.length, 0);

      const price = 1;
      const size = 22;

      // Run placeOrder instruction for vault
      try {
        await program.rpc.placeOrder(
          vaultAuthBump,
          openOrdersBump,
          marketAuthorityBump,
          Side.Bid, // Side
          marketProxy.market.priceNumberToLots(price), // liimit_price
          marketProxy.market.baseSizeNumberToLots(size), // max_coin_qty
          OrderType.PostOnly, // order_type
          new anchor.BN(999), // client_order_id
          SelfTradeBehavior.AbortTransaction, // self_trade_behavior
          new anchor.BN(65535), // limit - no idea what this is
          new anchor.BN(
            // @ts-ignore: serum
            marketProxy.market._decoded.quoteLotSize.toNumber()
          ).mul(
            marketProxy.market
              .baseSizeNumberToLots(size)
              .mul(marketProxy.market.priceNumberToLots(price))
          ), // max_native_pc_qty_including_fees - no idea what exactly this is
          {
            accounts: {
              userAuthority: wallet.publicKey,
              psyAmericanProgram: americanOptionsProgram.programId,
              dexProgram: DEX_PID,
              openOrders,
              market: marketProxy.market.address,
              psyMarketAuthority: marketAuthority,
              vault,
              vaultAuthority,
              // @ts-ignore: Dumb serum stuff
              requestQueue: marketProxy.market._decoded.requestQueue,
              // @ts-ignore: Dumb serum stuff
              eventQueue: marketProxy.market._decoded.eventQueue,
              marketBids: marketProxy.market.bidsAddress,
              marketAsks: marketProxy.market.asksAddress,
              // @ts-ignore: Dumb serum stuff
              coinVault: marketProxy.market._decoded.baseVault,
              // @ts-ignore: Dumb serum stuff
              pcVault: marketProxy.market._decoded.quoteVault,

              systemProgram: SystemProgram.programId,
              tokenProgram: TOKEN_PROGRAM_ID,
              rent: SYSVAR_RENT_PUBKEY,
            },
          }
        );
      } catch (err) {
        console.log("*** error", (err as Error).toString());
        throw err;
      }
      // Test that a new open orders account was created
      const openOrdersAcct = await OpenOrders.load(
        provider.connection,
        openOrders,
        DEX_PID
      );
      assert.ok(openOrdersAcct.owner.equals(openOrders));

      // test that the order book contains the new order.
      bids = await marketProxy.market.loadBids(provider.connection);
      l2 = await bids.getL2(3);
      assert.equal(l2.length, 1);
      assert.equal(l2[0][0], price);
      assert.equal(l2[0][1], size);
    });

    describe("Open orders account exists", () => {
      it("should place the order without fail", async () => {
        // Test that the open orders account already exists
        const openOrdersAcct = await OpenOrders.load(
          provider.connection,
          openOrders,
          DEX_PID
        );
        assert.ok(openOrdersAcct.owner.equals(openOrders));
        // test that the order book contains the new order.
        let bids = await marketProxy.market.loadBids(provider.connection);
        let l2 = await bids.getL2(3);
        assert.equal(l2.length, 1);

        const price = 2;
        const size = 1;

        // Run placeOrder instruction for vault
        try {
          await program.rpc.placeOrder(
            vaultAuthBump,
            openOrdersBump,
            marketAuthorityBump,
            Side.Bid, // Side
            marketProxy.market.priceNumberToLots(price), // liimit_price
            marketProxy.market.baseSizeNumberToLots(size), // max_coin_qty
            OrderType.PostOnly, // order_type
            new anchor.BN(998), // client_order_id
            SelfTradeBehavior.AbortTransaction, // self_trade_behavior
            new anchor.BN(65535), // limit
            new anchor.BN(
              // @ts-ignore: serum
              marketProxy.market._decoded.quoteLotSize.toNumber()
            ).mul(
              marketProxy.market
                .baseSizeNumberToLots(size)
                .mul(marketProxy.market.priceNumberToLots(price))
            ), // max_native_pc_qty_including_fees
            {
              accounts: {
                userAuthority: wallet.publicKey,
                psyAmericanProgram: americanOptionsProgram.programId,
                dexProgram: DEX_PID,
                openOrders,
                market: marketProxy.market.address,
                psyMarketAuthority: marketAuthority,
                vault,
                vaultAuthority,
                // @ts-ignore: Dumb serum stuff
                requestQueue: marketProxy.market._decoded.requestQueue,
                // @ts-ignore: Dumb serum stuff
                eventQueue: marketProxy.market._decoded.eventQueue,
                marketBids: marketProxy.market.bidsAddress,
                marketAsks: marketProxy.market.asksAddress,
                // @ts-ignore: Dumb serum stuff
                coinVault: marketProxy.market._decoded.baseVault,
                // @ts-ignore: Dumb serum stuff
                pcVault: marketProxy.market._decoded.quoteVault,

                systemProgram: SystemProgram.programId,
                tokenProgram: TOKEN_PROGRAM_ID,
                rent: SYSVAR_RENT_PUBKEY,
              },
            }
          );
        } catch (err) {
          console.log("*** error", (err as Error).toString());
          throw err;
        }
        bids = await marketProxy.market.loadBids(provider.connection);
        l2 = await bids.getL2(3);
        assert.equal(l2.length, 2);
      });
    });
  });
});
Example #23
Source File: exercise.ts    From psyoptions with Apache License 2.0 4 votes vote down vote up
describe("cpi_examples exercise", () => {
  const payer = anchor.web3.Keypair.generate();
  const program = anchor.workspace.CpiExamples as Program<CpiExamples>;
  const provider = program.provider;
  const americanOptionsProgram = anchor.workspace
    .PsyAmerican as Program<PsyAmerican>;
  let optionMarket: OptionMarketV2;
  let vaultAccount: AccountInfo;
  const mintAuthority = anchor.web3.Keypair.generate();

  const user = anchor.web3.Keypair.generate();

  let underlyingToken: Token;
  let optionToken: Token;
  let optionMarketKey: PublicKey;
  let remainingAccounts: AccountMeta[] = [];

  let userWriterAcct: Keypair;
  let userOptionAcct: Keypair;
  let userUnderlyingAccount: Keypair;
  let userQuoteAccount: Keypair;
  let size = new u64(1);

  let vaultAuthority: PublicKey;
  let vaultAuthorityBump: number;
  let exerciseFeeKey: PublicKey;

  describe("OptionMarket is not expired", () => {
    before(async () => {
      await provider.connection.confirmTransaction(
        await provider.connection.requestAirdrop(
          payer.publicKey,
          10_000_000_000
        ),
        "confirmed"
      );
      await provider.connection.confirmTransaction(
        await provider.connection.requestAirdrop(
          user.publicKey,
          10_000_000_000
        ),
        "confirmed"
      );
      const {
        instructions,
        optionMarket: newOptionMarket,
        optionMarketKey: _optionMarketKey,
        quoteToken,
        remainingAccounts: _remainingAccounts,
        underlyingToken: _underlyingToken,
      } = await initSetup(
        provider,
        (provider.wallet as anchor.Wallet).payer,
        mintAuthority,
        americanOptionsProgram,
        {
          // set expiration to 4 seconds from now
          expiration: new anchor.BN(new Date().getTime() / 1000 + 4),
        }
      );
      optionMarketKey = _optionMarketKey;
      optionMarket = newOptionMarket;
      remainingAccounts = _remainingAccounts;
      underlyingToken = _underlyingToken;
      await initOptionMarket(
        americanOptionsProgram,
        (provider.wallet as anchor.Wallet).payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      optionToken = new Token(
        provider.connection,
        optionMarket.optionMint,
        TOKEN_PROGRAM_ID,
        payer
      );
      ({
        optionAccount: userOptionAcct,
        underlyingAccount: userUnderlyingAccount,
        writerTokenAccount: userWriterAcct,
        quoteAccount: userQuoteAccount,
      } = await createMinter(
        provider.connection,
        user,
        mintAuthority,
        underlyingToken,
        new anchor.BN(100)
          .mul(optionMarket.underlyingAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.writerTokenMint,
        quoteToken,
        // Make sure the minter has access to enough quote assets to exercise
        new anchor.BN(100)
          .mul(newOptionMarket.quoteAmountPerContract)
          .muln(2)
          .toNumber()
      ));
      await mintOptionsTx(
        americanOptionsProgram,
        user,
        userOptionAcct,
        userWriterAcct,
        userUnderlyingAccount,
        new anchor.BN(25),
        optionMarket
      );

      // Initialize and deposit options into a vault
      const size = new anchor.BN(1);
      const textEncoder = new TextEncoder();
      const [vault, _vaultBump] = await PublicKey.findProgramAddress(
        [optionMarket.optionMint.toBuffer(), textEncoder.encode("vault")],
        program.programId
      );
      [vaultAuthority, vaultAuthorityBump] = await PublicKey.findProgramAddress(
        [optionMarket.key.toBuffer(), textEncoder.encode("vaultAuthority")],
        program.programId
      );

      await program.rpc.initialize(size, {
        accounts: {
          authority: user.publicKey,
          optionSource: userOptionAcct.publicKey,
          optionMint: optionMarket.optionMint,
          vault,
          vaultAuthority,
          tokenProgram: TOKEN_PROGRAM_ID,
          rent: SYSVAR_RENT_PUBKEY,
          systemProgram: SystemProgram.programId,
        },
        signers: [user],
      });

      vaultAccount = await optionToken.getAccountInfo(vault);
    });

    it("should exercise the options in the vault", async () => {
      // Validate the vault has an option in it
      assert.equal(vaultAccount.amount.toString(), size.toString());

      const userUnderlyingBefore = await underlyingToken.getAccountInfo(
        userUnderlyingAccount.publicKey
      );

      const exerciseFeePerContract = feeAmountPerContract(
        optionMarket.quoteAmountPerContract
      );
      if (exerciseFeePerContract.gtn(0)) {
        exerciseFeeKey = await Token.getAssociatedTokenAddress(
          ASSOCIATED_TOKEN_PROGRAM_ID,
          TOKEN_PROGRAM_ID,
          optionMarket.quoteAssetMint,
          FEE_OWNER_KEY
        );
        remainingAccounts = [
          {
            pubkey: exerciseFeeKey,
            isWritable: true,
            isSigner: false,
          },
        ];
      }

      try {
        await program.rpc.exercise(vaultAuthorityBump, {
          accounts: {
            authority: user.publicKey,
            psyAmericanProgram: americanOptionsProgram.programId,
            vaultAuthority: vaultAuthority,
            optionMarket: optionMarket.key,
            optionMint: optionMarket.optionMint,
            exerciserOptionTokenSrc: vaultAccount.address,
            underlyingAssetPool: optionMarket.underlyingAssetPool,
            underlyingAssetDest: userUnderlyingAccount.publicKey,
            quoteAssetPool: optionMarket.quoteAssetPool,
            quoteAssetSrc: userQuoteAccount.publicKey,
            feeOwner: FEE_OWNER_KEY,
            tokenProgram: TOKEN_PROGRAM_ID,
            systemProgram: SystemProgram.programId,
            clock: SYSVAR_CLOCK_PUBKEY,
          },
          remainingAccounts,
          signers: [user],
        });
      } catch (err) {
        console.log((err as Error).toString());
        throw err;
      }

      // TODO: Validate the minter received the underlying assets
      const userUnderlyingAfter = await underlyingToken.getAccountInfo(
        userUnderlyingAccount.publicKey
      );
      const userUnderlyingDiff = userUnderlyingAfter.amount.sub(
        userUnderlyingBefore.amount
      );

      assert.equal(
        userUnderlyingDiff.toString(),
        size.mul(optionMarket.underlyingAmountPerContract).toString()
      );
    });
  });
});
Example #24
Source File: deposit.ts    From psyoptions with Apache License 2.0 4 votes vote down vote up
describe("cpi_examples deposit", () => {
  const payer = anchor.web3.Keypair.generate();
  const program = anchor.workspace.CpiExamples as Program<CpiExamples>;
  const provider = program.provider;
  const americanOptionsProgram = anchor.workspace
    .PsyAmerican as Program<PsyAmerican>;
  let optionMarket: OptionMarketV2;
  const mintAuthority = anchor.web3.Keypair.generate();

  const user = anchor.web3.Keypair.generate();

  let underlyingToken: Token;
  let optionToken: Token;
  let optionMarketKey: PublicKey;
  let remainingAccounts: AccountMeta[] = [];

  let userWriterAcct: Keypair;
  let userOptionAcct: Keypair;
  let userUnderlyingAccount: Keypair;
  let size = new u64(1);

  before(async () => {
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(payer.publicKey, 10_000_000_000),
      "confirmed"
    );
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(user.publicKey, 10_000_000_000),
      "confirmed"
    );
    const {
      instructions,
      optionMarket: newOptionMarket,
      optionMarketKey: _optionMarketKey,
      quoteToken,
      remainingAccounts: _remainingAccounts,
      underlyingAmountPerContract,
      underlyingToken: _underlyingToken,
    } = await initSetup(
      provider,
      provider.wallet.payer,
      mintAuthority,
      americanOptionsProgram
    );
    optionMarketKey = _optionMarketKey;
    optionMarket = newOptionMarket;
    remainingAccounts = _remainingAccounts;
    underlyingToken = _underlyingToken;
    await initOptionMarket(
      americanOptionsProgram,
      (provider.wallet as anchor.Wallet).payer,
      optionMarket,
      remainingAccounts,
      instructions
    );
    optionToken = new Token(
      provider.connection,
      optionMarket.optionMint,
      TOKEN_PROGRAM_ID,
      payer
    );
    ({
      optionAccount: userOptionAcct,
      underlyingAccount: userUnderlyingAccount,
      writerTokenAccount: userWriterAcct,
    } = await createMinter(
      provider.connection,
      user,
      mintAuthority,
      underlyingToken,
      new anchor.BN(100)
        .mul(optionMarket.underlyingAmountPerContract)
        .muln(2)
        .toNumber(),
      optionMarket.optionMint,
      optionMarket.writerTokenMint,
      quoteToken
    ));
    await mintOptionsTx(
      americanOptionsProgram,
      user,
      userOptionAcct,
      userWriterAcct,
      userUnderlyingAccount,
      size,
      optionMarket
    );
  });

  it("should allow an option to be deposited", async () => {
    const size = new anchor.BN(1);
    const textEncoder = new TextEncoder();
    const [vault, _vaultBump] = await PublicKey.findProgramAddress(
      [optionMarket.optionMint.toBuffer(), textEncoder.encode("vault")],
      program.programId
    );
    const [vaultAuthority, _vaultAuthorityBump] =
      await PublicKey.findProgramAddress(
        [optionMarket.key.toBuffer(), textEncoder.encode("vaultAuthority")],
        program.programId
      );

    await program.rpc.initialize(size, {
      accounts: {
        authority: user.publicKey,
        optionSource: userOptionAcct.publicKey,
        optionMint: optionMarket.optionMint,
        vault,
        vaultAuthority,
        tokenProgram: TOKEN_PROGRAM_ID,
        rent: SYSVAR_RENT_PUBKEY,
        systemProgram: SystemProgram.programId,
      },
      signers: [user],
    });

    const vaultAccount = await optionToken.getAccountInfo(vault);
    assert.ok(vaultAccount.amount.eq(size as u64));
  });
});
Example #25
Source File: closePostExpiration.ts    From psyoptions with Apache License 2.0 4 votes vote down vote up
describe("closePostExpiration", () => {
  const payer = anchor.web3.Keypair.generate();
  const mintAuthority = anchor.web3.Keypair.generate();
  const program = anchor.workspace.PsyAmerican as Program<PsyAmerican>;
  const provider = program.provider;

  const minter = anchor.web3.Keypair.generate();
  const exerciser = anchor.web3.Keypair.generate();

  let quoteToken: Token;
  let underlyingToken: Token;
  let optionToken: Token;
  let underlyingAmountPerContract: anchor.BN;
  let optionMarket: OptionMarketV2;
  let exerciserOptionAcct: Keypair;
  let remainingAccounts: AccountMeta[] = [];
  let instructions: TransactionInstruction[] = [];

  let minterWriterAcct: Keypair;
  let minterOptionAcct: Keypair;
  let minterUnderlyingAccount: Keypair;
  let size = new u64(1);

  before(async () => {
    // airdrop SOL to the payer and minter
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        payer.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        minter.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        exerciser.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
  });

  describe("Expired OptionMarket", () => {
    before(async () => {
      // Initialize a new OptionMarket
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        optionMarket,
        optionToken,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program, {
        // set expiration to 2 seconds from now
        expiration: new anchor.BN(new Date().getTime() / 1000 + 3),
      }));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new minter
      ({
        optionAccount: minterOptionAcct,
        underlyingAccount: minterUnderlyingAccount,
        writerTokenAccount: minterWriterAcct,
      } = await createMinter(
        provider.connection,
        minter,
        mintAuthority,
        underlyingToken,
        new anchor.BN(100)
          .mul(optionMarket.underlyingAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.writerTokenMint,
        quoteToken
      ));
      // Mint a bunch of contracts to the minter
      await mintOptionsTx(
        program,
        minter,
        minterOptionAcct,
        minterWriterAcct,
        minterUnderlyingAccount,
        new anchor.BN(100),
        optionMarket
      );
      // Create an exerciser
      ({ optionAccount: exerciserOptionAcct } = await createExerciser(
        provider.connection,
        exerciser,
        mintAuthority,
        quoteToken,
        new anchor.BN(100)
          .mul(optionMarket.quoteAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.underlyingAssetMint
      ));

      // Transfer a options to the exerciser
      await optionToken.transfer(
        minterOptionAcct.publicKey,
        exerciserOptionAcct.publicKey,
        minter,
        [],
        new u64(100)
      );
      // Wait so the market is expired
      await wait(3000);
    });
    beforeEach(async () => {
      size = new u64(1);
    });

    describe("proper close post expiration", () => {
      it("should burn the WriteToken and transfer the underlying", async () => {
        const writerToken = new Token(
          provider.connection,
          optionMarket.writerTokenMint,
          TOKEN_PROGRAM_ID,
          payer
        );
        const writerMintBefore = await writerToken.getMintInfo();
        const minterUnderlyingBefore = await underlyingToken.getAccountInfo(
          minterUnderlyingAccount.publicKey
        );
        try {
          await closePostExpiration(
            program,
            minter,
            size,
            optionMarket.key,
            optionMarket.writerTokenMint,
            minterWriterAcct.publicKey,
            optionMarket.underlyingAssetPool,
            minterUnderlyingAccount.publicKey
          );
        } catch (err) {
          console.error((err as AnchorError).error.errorMessage);
          throw err;
        }
        const writerMintAfter = await writerToken.getMintInfo();
        const writerMintDiff = writerMintAfter.supply.sub(
          writerMintBefore.supply
        );
        assert.equal(writerMintDiff.toString(), size.neg().toString());

        const minterUnderlyingAfter = await underlyingToken.getAccountInfo(
          minterUnderlyingAccount.publicKey
        );
        const minterUnderlyingDiff = minterUnderlyingAfter.amount.sub(
          minterUnderlyingBefore.amount
        );
        assert.equal(
          minterUnderlyingDiff.toString(),
          size.mul(underlyingAmountPerContract).toString()
        );
      });
    });
    describe("underlying asset pool does not match the OptionMarket", () => {
      let badUnderlyingPool: Keypair;
      before(async () => {
        const { tokenAccount } = await initNewTokenAccount(
          provider.connection,
          payer.publicKey,
          underlyingToken.publicKey,
          payer
        );
        badUnderlyingPool = tokenAccount;
      });
      it("should error", async () => {
        try {
          await closePostExpiration(
            program,
            minter,
            size,
            optionMarket.key,
            optionMarket.writerTokenMint,
            minterWriterAcct.publicKey,
            badUnderlyingPool.publicKey,
            minterUnderlyingAccount.publicKey
          );
          assert.ok(false);
        } catch (err) {
          const errorMsg =
            "Underlying pool account does not match the value on the OptionMarket";
          assert.equal((err as AnchorError).error.errorMessage, errorMsg);
        }
      });
    });
    describe("Validate the writer mint is the same as the OptionMarket", () => {
      let badWriterMint: Keypair;
      before(async () => {
        const { mintAccount } = await initNewTokenMint(
          provider.connection,
          payer.publicKey,
          payer
        );
        badWriterMint = mintAccount;
      });
      it("should error", async () => {
        try {
          await closePostExpiration(
            program,
            minter,
            size,
            optionMarket.key,
            badWriterMint.publicKey,
            minterWriterAcct.publicKey,
            optionMarket.underlyingAssetPool,
            minterUnderlyingAccount.publicKey
          );
          assert.ok(false);
        } catch (err) {
          const errorMsg =
            "WriterToken mint does not match the value on the OptionMarket";
          assert.equal((err as AnchorError).error.errorMessage, errorMsg);
        }
      });
    });
    describe("Validate the underlying destination is the same as the OptionMarket", () => {
      let badUnderlyingDest: Keypair;
      before(async () => {
        const { tokenAccount } = await initNewTokenAccount(
          provider.connection,
          payer.publicKey,
          quoteToken.publicKey,
          payer
        );
        badUnderlyingDest = tokenAccount;
      });
      it("should error", async () => {
        try {
          await closePostExpiration(
            program,
            minter,
            size,
            optionMarket.key,
            optionMarket.writerTokenMint,
            minterWriterAcct.publicKey,
            optionMarket.underlyingAssetPool,
            badUnderlyingDest.publicKey
          );
          assert.ok(false);
        } catch (err) {
          const errorMsg =
            "Underlying destination mint must match underlying asset mint address";
          assert.equal((err as AnchorError).error.errorMessage, errorMsg);
        }
      });
    });
  });
  describe("Unexpired OptionMarket", () => {
    before(async () => {
      // Initialize a new OptionMarket
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        optionMarket,
        optionToken,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program, {
        // set expiration to 2 seconds from now
        expiration: new anchor.BN(new Date().getTime() / 1000 + 600),
      }));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new minter
      ({
        optionAccount: minterOptionAcct,
        underlyingAccount: minterUnderlyingAccount,
        writerTokenAccount: minterWriterAcct,
      } = await createMinter(
        provider.connection,
        minter,
        mintAuthority,
        underlyingToken,
        new anchor.BN(100)
          .mul(optionMarket.underlyingAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.writerTokenMint,
        quoteToken
      ));
      // Mint a bunch of contracts to the minter
      await mintOptionsTx(
        program,
        minter,
        minterOptionAcct,
        minterWriterAcct,
        minterUnderlyingAccount,
        new anchor.BN(100),
        optionMarket
      );
      // Create an exerciser
      ({ optionAccount: exerciserOptionAcct } = await createExerciser(
        provider.connection,
        exerciser,
        mintAuthority,
        quoteToken,
        new anchor.BN(100)
          .mul(optionMarket.quoteAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.underlyingAssetMint
      ));

      // Transfer a options to the exerciser
      await optionToken.transfer(
        minterOptionAcct.publicKey,
        exerciserOptionAcct.publicKey,
        minter,
        [],
        new u64(100)
      );
    });
    beforeEach(async () => {
      size = new u64(1);
    });

    it("should error", async () => {
      try {
        await closePostExpiration(
          program,
          minter,
          size,
          optionMarket.key,
          optionMarket.writerTokenMint,
          minterWriterAcct.publicKey,
          optionMarket.underlyingAssetPool,
          minterUnderlyingAccount.publicKey
        );
        assert.ok(false);
      } catch (err) {
        const errorMsg = "OptionMarket has not expired, can't close";
        assert.equal((err as AnchorError).error.errorMessage, errorMsg);
      }
    });
  });
});
Example #26
Source File: closePosition.ts    From psyoptions with Apache License 2.0 4 votes vote down vote up
describe("closeOptionPosition", () => {
  const payer = anchor.web3.Keypair.generate();
  const mintAuthority = anchor.web3.Keypair.generate();
  const program = anchor.workspace.PsyAmerican as Program<PsyAmerican>;
  const provider = program.provider;

  const minter = anchor.web3.Keypair.generate();
  const exerciser = anchor.web3.Keypair.generate();

  let quoteToken: Token;
  let underlyingToken: Token;
  let underlyingAmountPerContract: anchor.BN;
  let optionMarket: OptionMarketV2;
  let remainingAccounts: AccountMeta[] = [];
  let instructions: TransactionInstruction[] = [];

  let minterWriterAcct: Keypair;
  let minterOptionAcct: Keypair;
  let minterUnderlyingAccount: Keypair;
  let size = new u64(1);

  before(async () => {
    // airdrop SOL to the payer and minter
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        payer.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        minter.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
    await provider.connection.confirmTransaction(
      await provider.connection.requestAirdrop(
        exerciser.publicKey,
        100 * LAMPORTS_PER_SOL
      ),
      "confirmed"
    );
  });

  describe("Unexpired OptionMarket", () => {
    before(async () => {
      // Initialize a new OptionMarket
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program, {
        // set expiration to 2 seconds from now
        expiration: new anchor.BN(new Date().getTime() / 1000 + 600),
      }));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new minter
      ({
        optionAccount: minterOptionAcct,
        underlyingAccount: minterUnderlyingAccount,
        writerTokenAccount: minterWriterAcct,
      } = await createMinter(
        provider.connection,
        minter,
        mintAuthority,
        underlyingToken,
        new anchor.BN(100)
          .mul(optionMarket.underlyingAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.writerTokenMint,
        quoteToken
      ));
      // Mint a bunch of contracts to the minter
      await mintOptionsTx(
        program,
        minter,
        minterOptionAcct,
        minterWriterAcct,
        minterUnderlyingAccount,
        new anchor.BN(100),
        optionMarket
      );
    });
    beforeEach(async () => {
      size = new u64(1);
    });

    describe("proper close position", () => {
      it("should burn the WriteToken + OptionToken and transfer the underlying", async () => {
        const writerToken = new Token(
          provider.connection,
          optionMarket.writerTokenMint,
          TOKEN_PROGRAM_ID,
          payer
        );
        const optionToken = new Token(
          provider.connection,
          optionMarket.optionMint,
          TOKEN_PROGRAM_ID,
          payer
        );
        const writerMintBefore = await writerToken.getMintInfo();
        const optionMintBefore = await optionToken.getMintInfo();
        const minterUnderlyingBefore = await underlyingToken.getAccountInfo(
          minterUnderlyingAccount.publicKey
        );
        try {
          await closeOptionPosition(
            program,
            minter,
            size,
            optionMarket.key,
            optionMarket.writerTokenMint,
            minterWriterAcct.publicKey,
            optionMarket.optionMint,
            minterOptionAcct.publicKey,
            optionMarket.underlyingAssetPool,
            minterUnderlyingAccount.publicKey
          );
        } catch (err) {
          console.error((err as AnchorError).error.errorMessage);
          throw err;
        }
        const writerMintAfter = await writerToken.getMintInfo();
        const writerMintDiff = writerMintAfter.supply.sub(
          writerMintBefore.supply
        );
        assert.equal(writerMintDiff.toString(), size.neg().toString());

        const optionMintAfter = await optionToken.getMintInfo();
        const optionMintDiff = optionMintAfter.supply.sub(
          optionMintBefore.supply
        );
        assert.equal(optionMintDiff.neg().toString(), size.toString());

        const minterUnderlyingAfter = await underlyingToken.getAccountInfo(
          minterUnderlyingAccount.publicKey
        );
        const minterUnderlyingDiff = minterUnderlyingAfter.amount.sub(
          minterUnderlyingBefore.amount
        );
        assert.equal(
          minterUnderlyingDiff.toString(),
          size.mul(underlyingAmountPerContract).toString()
        );
      });
    });

    describe("WriterToken mint does not match OptionMarket", () => {
      let badWriterMint: Keypair;
      before(async () => {
        const { mintAccount } = await initNewTokenMint(
          provider.connection,
          payer.publicKey,
          payer
        );
        badWriterMint = mintAccount;
      });
      it("should error", async () => {
        try {
          await closeOptionPosition(
            program,
            minter,
            size,
            optionMarket.key,
            badWriterMint.publicKey,
            minterWriterAcct.publicKey,
            optionMarket.optionMint,
            minterOptionAcct.publicKey,
            optionMarket.underlyingAssetPool,
            minterUnderlyingAccount.publicKey
          );
          assert.ok(false);
        } catch (err) {
          const errorMsg =
            "WriterToken mint does not match the value on the OptionMarket";
          assert.equal((err as AnchorError).error.errorMessage, errorMsg);
        }
      });
    });

    describe("OptionToken mint does not match OptionMarket", () => {
      let badOptionMint: Keypair;
      before(async () => {
        const { mintAccount } = await initNewTokenMint(
          provider.connection,
          payer.publicKey,
          payer
        );
        badOptionMint = mintAccount;
      });
      it("should error", async () => {
        try {
          await closeOptionPosition(
            program,
            minter,
            size,
            optionMarket.key,
            optionMarket.writerTokenMint,
            minterWriterAcct.publicKey,
            badOptionMint.publicKey,
            minterOptionAcct.publicKey,
            optionMarket.underlyingAssetPool,
            minterUnderlyingAccount.publicKey
          );
          assert.ok(false);
        } catch (err) {
          const errorMsg =
            "OptionToken mint does not match the value on the OptionMarket";
          assert.equal((err as AnchorError).error.errorMessage, errorMsg);
        }
      });
    });

    describe("Underlying asset pool does not match OptionMarket", () => {
      let badUnderlyingPool: Keypair;
      before(async () => {
        const { tokenAccount } = await initNewTokenAccount(
          provider.connection,
          payer.publicKey,
          underlyingToken.publicKey,
          payer
        );
        badUnderlyingPool = tokenAccount;
      });
      it("should error", async () => {
        try {
          await closeOptionPosition(
            program,
            minter,
            size,
            optionMarket.key,
            optionMarket.writerTokenMint,
            minterWriterAcct.publicKey,
            optionMarket.optionMint,
            minterOptionAcct.publicKey,
            badUnderlyingPool.publicKey,
            minterUnderlyingAccount.publicKey
          );
          assert.ok(false);
        } catch (err) {
          const errorMsg =
            "Underlying pool account does not match the value on the OptionMarket";
          assert.equal((err as AnchorError).error.errorMessage, errorMsg);
        }
      });
    });
  });
  describe("Expired OptionMarket", () => {
    before(async () => {
      // Initialize a new OptionMarket
      ({
        quoteToken,
        underlyingToken,
        underlyingAmountPerContract,
        optionMarket,
        remainingAccounts,
        instructions,
      } = await initSetup(provider, payer, mintAuthority, program, {
        // set expiration to 2 seconds from now
        expiration: new anchor.BN(new Date().getTime() / 1000 + 3),
      }));
      await initOptionMarket(
        program,
        payer,
        optionMarket,
        remainingAccounts,
        instructions
      );
      // Create a new minter
      ({
        optionAccount: minterOptionAcct,
        underlyingAccount: minterUnderlyingAccount,
        writerTokenAccount: minterWriterAcct,
      } = await createMinter(
        provider.connection,
        minter,
        mintAuthority,
        underlyingToken,
        new anchor.BN(100)
          .mul(optionMarket.underlyingAmountPerContract)
          .muln(2)
          .toNumber(),
        optionMarket.optionMint,
        optionMarket.writerTokenMint,
        quoteToken
      ));
      // Mint a bunch of contracts to the minter
      await mintOptionsTx(
        program,
        minter,
        minterOptionAcct,
        minterWriterAcct,
        minterUnderlyingAccount,
        new anchor.BN(100),
        optionMarket
      );
      // Wait till the market is expired
      await wait(3000);
    });
    beforeEach(async () => {
      size = new u64(1);
    });

    describe("proper close position", () => {
      it("should burn the WriteToken + OptionToken and transfer the underlying", async () => {
        const writerToken = new Token(
          provider.connection,
          optionMarket.writerTokenMint,
          TOKEN_PROGRAM_ID,
          payer
        );
        const optionToken = new Token(
          provider.connection,
          optionMarket.optionMint,
          TOKEN_PROGRAM_ID,
          payer
        );
        const writerMintBefore = await writerToken.getMintInfo();
        const optionMintBefore = await optionToken.getMintInfo();
        const minterUnderlyingBefore = await underlyingToken.getAccountInfo(
          minterUnderlyingAccount.publicKey
        );
        try {
          await closeOptionPosition(
            program,
            minter,
            size,
            optionMarket.key,
            optionMarket.writerTokenMint,
            minterWriterAcct.publicKey,
            optionMarket.optionMint,
            minterOptionAcct.publicKey,
            optionMarket.underlyingAssetPool,
            minterUnderlyingAccount.publicKey
          );
        } catch (err) {
          console.error((err as AnchorError).error.errorMessage);
          throw err;
        }
        const writerMintAfter = await writerToken.getMintInfo();
        const writerMintDiff = writerMintAfter.supply.sub(
          writerMintBefore.supply
        );
        assert.equal(writerMintDiff.toString(), size.neg().toString());

        const optionMintAfter = await optionToken.getMintInfo();
        const optionMintDiff = optionMintAfter.supply.sub(
          optionMintBefore.supply
        );
        assert.equal(optionMintDiff.neg().toString(), size.toString());

        const minterUnderlyingAfter = await underlyingToken.getAccountInfo(
          minterUnderlyingAccount.publicKey
        );
        const minterUnderlyingDiff = minterUnderlyingAfter.amount.sub(
          minterUnderlyingBefore.amount
        );
        assert.equal(
          minterUnderlyingDiff.toString(),
          size.mul(underlyingAmountPerContract).toString()
        );
      });
    });
  });
});