-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add withdraw excess with test cases (#35)
* Add withdraw excess with test cases * Fix idl * Arrange test case according to best practice * Apply best practices to test and order idl.json * Fix conflict in idl.json * Update code according to review * Add idl changes * Review update * Formatting * Fix conflict * Revert idl.json * Add withdraw excess into idl.json * Fix test case * Change the owner to payer
- Loading branch information
1 parent
8dbae66
commit 8947647
Showing
5 changed files
with
412 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
223 changes: 223 additions & 0 deletions
223
clients/js/src/generated/instructions/withdrawExcessLamports.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,223 @@ | ||
/** | ||
* This code was AUTOGENERATED using the codama library. | ||
* Please DO NOT EDIT THIS FILE, instead use visitors | ||
* to add features, then rerun codama to update it. | ||
* | ||
* @see https://github.com/codama-idl/codama | ||
*/ | ||
|
||
import { | ||
AccountRole, | ||
combineCodec, | ||
getStructDecoder, | ||
getStructEncoder, | ||
getU8Decoder, | ||
getU8Encoder, | ||
transformEncoder, | ||
type Address, | ||
type Codec, | ||
type Decoder, | ||
type Encoder, | ||
type IAccountMeta, | ||
type IAccountSignerMeta, | ||
type IInstruction, | ||
type IInstructionWithAccounts, | ||
type IInstructionWithData, | ||
type ReadonlyAccount, | ||
type ReadonlySignerAccount, | ||
type TransactionSigner, | ||
type WritableAccount, | ||
} from '@solana/web3.js'; | ||
import { TOKEN_2022_PROGRAM_ADDRESS } from '../programs'; | ||
import { getAccountMetaFactory, type ResolvedAccount } from '../shared'; | ||
|
||
export const WITHDRAW_EXCESS_LAMPORTS_DISCRIMINATOR = 38; | ||
|
||
export function getWithdrawExcessLamportsDiscriminatorBytes() { | ||
return getU8Encoder().encode(WITHDRAW_EXCESS_LAMPORTS_DISCRIMINATOR); | ||
} | ||
|
||
export type WithdrawExcessLamportsInstruction< | ||
TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS, | ||
TAccountSourceAccount extends string | IAccountMeta<string> = string, | ||
TAccountDestinationAccount extends string | IAccountMeta<string> = string, | ||
TAccountAuthority extends string | IAccountMeta<string> = string, | ||
TRemainingAccounts extends readonly IAccountMeta<string>[] = [], | ||
> = IInstruction<TProgram> & | ||
IInstructionWithData<Uint8Array> & | ||
IInstructionWithAccounts< | ||
[ | ||
TAccountSourceAccount extends string | ||
? WritableAccount<TAccountSourceAccount> | ||
: TAccountSourceAccount, | ||
TAccountDestinationAccount extends string | ||
? WritableAccount<TAccountDestinationAccount> | ||
: TAccountDestinationAccount, | ||
TAccountAuthority extends string | ||
? ReadonlyAccount<TAccountAuthority> | ||
: TAccountAuthority, | ||
...TRemainingAccounts, | ||
] | ||
>; | ||
|
||
export type WithdrawExcessLamportsInstructionData = { discriminator: number }; | ||
|
||
export type WithdrawExcessLamportsInstructionDataArgs = {}; | ||
|
||
export function getWithdrawExcessLamportsInstructionDataEncoder(): Encoder<WithdrawExcessLamportsInstructionDataArgs> { | ||
return transformEncoder( | ||
getStructEncoder([['discriminator', getU8Encoder()]]), | ||
(value) => ({ | ||
...value, | ||
discriminator: WITHDRAW_EXCESS_LAMPORTS_DISCRIMINATOR, | ||
}) | ||
); | ||
} | ||
|
||
export function getWithdrawExcessLamportsInstructionDataDecoder(): Decoder<WithdrawExcessLamportsInstructionData> { | ||
return getStructDecoder([['discriminator', getU8Decoder()]]); | ||
} | ||
|
||
export function getWithdrawExcessLamportsInstructionDataCodec(): Codec< | ||
WithdrawExcessLamportsInstructionDataArgs, | ||
WithdrawExcessLamportsInstructionData | ||
> { | ||
return combineCodec( | ||
getWithdrawExcessLamportsInstructionDataEncoder(), | ||
getWithdrawExcessLamportsInstructionDataDecoder() | ||
); | ||
} | ||
|
||
export type WithdrawExcessLamportsInput< | ||
TAccountSourceAccount extends string = string, | ||
TAccountDestinationAccount extends string = string, | ||
TAccountAuthority extends string = string, | ||
> = { | ||
/** Account holding excess lamports. */ | ||
sourceAccount: Address<TAccountSourceAccount>; | ||
/** Destination account for withdrawn lamports. */ | ||
destinationAccount: Address<TAccountDestinationAccount>; | ||
/** The source account's owner/delegate or its multisignature account. */ | ||
authority: Address<TAccountAuthority> | TransactionSigner<TAccountAuthority>; | ||
multiSigners?: Array<TransactionSigner>; | ||
}; | ||
|
||
export function getWithdrawExcessLamportsInstruction< | ||
TAccountSourceAccount extends string, | ||
TAccountDestinationAccount extends string, | ||
TAccountAuthority extends string, | ||
TProgramAddress extends Address = typeof TOKEN_2022_PROGRAM_ADDRESS, | ||
>( | ||
input: WithdrawExcessLamportsInput< | ||
TAccountSourceAccount, | ||
TAccountDestinationAccount, | ||
TAccountAuthority | ||
>, | ||
config?: { programAddress?: TProgramAddress } | ||
): WithdrawExcessLamportsInstruction< | ||
TProgramAddress, | ||
TAccountSourceAccount, | ||
TAccountDestinationAccount, | ||
(typeof input)['authority'] extends TransactionSigner<TAccountAuthority> | ||
? ReadonlySignerAccount<TAccountAuthority> & | ||
IAccountSignerMeta<TAccountAuthority> | ||
: TAccountAuthority | ||
> { | ||
// Program address. | ||
const programAddress = config?.programAddress ?? TOKEN_2022_PROGRAM_ADDRESS; | ||
|
||
// Original accounts. | ||
const originalAccounts = { | ||
sourceAccount: { value: input.sourceAccount ?? null, isWritable: true }, | ||
destinationAccount: { | ||
value: input.destinationAccount ?? null, | ||
isWritable: true, | ||
}, | ||
authority: { value: input.authority ?? null, isWritable: false }, | ||
}; | ||
const accounts = originalAccounts as Record< | ||
keyof typeof originalAccounts, | ||
ResolvedAccount | ||
>; | ||
|
||
// Original args. | ||
const args = { ...input }; | ||
|
||
// Remaining accounts. | ||
const remainingAccounts: IAccountMeta[] = (args.multiSigners ?? []).map( | ||
(signer) => ({ | ||
address: signer.address, | ||
role: AccountRole.READONLY_SIGNER, | ||
signer, | ||
}) | ||
); | ||
|
||
const getAccountMeta = getAccountMetaFactory(programAddress, 'programId'); | ||
const instruction = { | ||
accounts: [ | ||
getAccountMeta(accounts.sourceAccount), | ||
getAccountMeta(accounts.destinationAccount), | ||
getAccountMeta(accounts.authority), | ||
...remainingAccounts, | ||
], | ||
programAddress, | ||
data: getWithdrawExcessLamportsInstructionDataEncoder().encode({}), | ||
} as WithdrawExcessLamportsInstruction< | ||
TProgramAddress, | ||
TAccountSourceAccount, | ||
TAccountDestinationAccount, | ||
(typeof input)['authority'] extends TransactionSigner<TAccountAuthority> | ||
? ReadonlySignerAccount<TAccountAuthority> & | ||
IAccountSignerMeta<TAccountAuthority> | ||
: TAccountAuthority | ||
>; | ||
|
||
return instruction; | ||
} | ||
|
||
export type ParsedWithdrawExcessLamportsInstruction< | ||
TProgram extends string = typeof TOKEN_2022_PROGRAM_ADDRESS, | ||
TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[], | ||
> = { | ||
programAddress: Address<TProgram>; | ||
accounts: { | ||
/** Account holding excess lamports. */ | ||
sourceAccount: TAccountMetas[0]; | ||
/** Destination account for withdrawn lamports. */ | ||
destinationAccount: TAccountMetas[1]; | ||
/** The source account's owner/delegate or its multisignature account. */ | ||
authority: TAccountMetas[2]; | ||
}; | ||
data: WithdrawExcessLamportsInstructionData; | ||
}; | ||
|
||
export function parseWithdrawExcessLamportsInstruction< | ||
TProgram extends string, | ||
TAccountMetas extends readonly IAccountMeta[], | ||
>( | ||
instruction: IInstruction<TProgram> & | ||
IInstructionWithAccounts<TAccountMetas> & | ||
IInstructionWithData<Uint8Array> | ||
): ParsedWithdrawExcessLamportsInstruction<TProgram, TAccountMetas> { | ||
if (instruction.accounts.length < 3) { | ||
// TODO: Coded error. | ||
throw new Error('Not enough accounts'); | ||
} | ||
let accountIndex = 0; | ||
const getNextAccount = () => { | ||
const accountMeta = instruction.accounts![accountIndex]!; | ||
accountIndex += 1; | ||
return accountMeta; | ||
}; | ||
return { | ||
programAddress: instruction.programAddress, | ||
accounts: { | ||
sourceAccount: getNextAccount(), | ||
destinationAccount: getNextAccount(), | ||
authority: getNextAccount(), | ||
}, | ||
data: getWithdrawExcessLamportsInstructionDataDecoder().decode( | ||
instruction.data | ||
), | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import test from 'ava'; | ||
import { | ||
AccountState, | ||
TOKEN_2022_PROGRAM_ADDRESS, | ||
fetchToken, | ||
findAssociatedTokenPda, | ||
getCreateAssociatedTokenInstructionAsync, | ||
getMintToInstruction, | ||
getWithdrawExcessLamportsInstruction, | ||
} from '../src'; | ||
import { | ||
createDefaultSolanaClient, | ||
createMint, | ||
createToken, | ||
generateKeyPairSignerWithSol, | ||
sendAndConfirmInstructions, | ||
} from './_setup'; | ||
import { getTransferSolInstruction } from '@solana-program/system'; | ||
import { generateKeyPairSigner } from '@solana/web3.js'; | ||
|
||
test('it withdraws excess lamports from an associated token account', async (t) => { | ||
// Given: A client, a payer, mint authority, token owner, and destination account | ||
const client = createDefaultSolanaClient(); | ||
const [payer, mintAuthority, owner, destination] = await Promise.all([ | ||
generateKeyPairSignerWithSol(client, 200_000_000n), | ||
generateKeyPairSigner(), | ||
generateKeyPairSigner(), | ||
generateKeyPairSigner(), | ||
]); | ||
|
||
// And a mint and token are created | ||
const mint = await createMint({ | ||
client, | ||
payer, | ||
authority: mintAuthority, | ||
decimals: 9, | ||
}); | ||
const token = await createToken({ client, payer, mint, owner }); | ||
|
||
// And tokens are minted to the token account | ||
const mintToInstruction = getMintToInstruction({ | ||
mint, | ||
token, | ||
mintAuthority, | ||
amount: 100_000n, | ||
}); | ||
await sendAndConfirmInstructions(client, payer, [mintToInstruction]); | ||
|
||
// And an associated token account (ATA) is created for the owner | ||
const createAtaInstruction = await getCreateAssociatedTokenInstructionAsync({ | ||
payer, | ||
mint, | ||
owner: owner.address, | ||
}); | ||
await sendAndConfirmInstructions(client, payer, [createAtaInstruction]); | ||
|
||
const [ata] = await findAssociatedTokenPda({ | ||
mint, | ||
owner: owner.address, | ||
tokenProgram: TOKEN_2022_PROGRAM_ADDRESS, | ||
}); | ||
|
||
// Ensure the token account was initialized correctly | ||
const initialTokenAccount = await fetchToken(client.rpc, ata); | ||
t.is(initialTokenAccount.data.state, AccountState.Initialized); | ||
|
||
// When: SOL is mistakenly transferred to the ATA | ||
const transferSolInstruction = await getTransferSolInstruction({ | ||
source: payer, | ||
destination: ata, | ||
amount: 1_000_000n, | ||
}); | ||
await sendAndConfirmInstructions(client, payer, [transferSolInstruction]); | ||
|
||
// Capture initial balances for comparison after withdrawal | ||
const lamportsBefore = await client.rpc | ||
.getBalance(destination.address) | ||
.send(); | ||
const ataLamportsBefore = await client.rpc.getBalance(ata).send(); | ||
|
||
// And we initiate withdrawal of excess lamports from the ATA to the destination | ||
const withdrawInstruction = await getWithdrawExcessLamportsInstruction({ | ||
sourceAccount: ata, | ||
destinationAccount: destination.address, | ||
authority: owner, | ||
}); | ||
await sendAndConfirmInstructions(client, payer, [withdrawInstruction]); | ||
|
||
// Then: Verify that lamports were successfully withdrawn to the destination | ||
const lamportsAfter = await client.rpc.getBalance(destination.address).send(); | ||
const ataLamportsAfter = await client.rpc.getBalance(ata).send(); | ||
|
||
// Assertions to confirm successful transfer of lamports | ||
t.true( | ||
Number(lamportsAfter.value) > Number(lamportsBefore.value), | ||
'Lamports were successfully withdrawn to the destination account.' | ||
); | ||
t.true( | ||
Number(ataLamportsBefore.value) > Number(ataLamportsAfter.value), | ||
'Lamports were successfully withdrawn from the ATA.' | ||
); | ||
}); |
Oops, something went wrong.