Skip to content

Commit f365420

Browse files
committed
test: add recover_m unit tests
1 parent c4f922e commit f365420

File tree

1 file changed

+319
-2
lines changed

1 file changed

+319
-2
lines changed

tests/unit/earn.test.ts

Lines changed: 319 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ import {
3030
ExtensionType,
3131
getExtensionData,
3232
createApproveCheckedInstruction,
33+
createThawAccountInstruction,
34+
createFreezeAccountInstruction,
3335
AccountState,
3436
} from '@solana/spl-token';
3537
import { randomInt } from 'crypto';
@@ -596,19 +598,32 @@ class EarnTest<V extends Variant = Variant.New> {
596598
public async mintM(to: PublicKey, amount: BN) {
597599
const toATA: PublicKey = await this.getATA(this.mMint.publicKey, to);
598600

601+
// Check if the account is frozen, and if so, thaw it temporarily for minting
602+
const accountInfo = await getAccount(this.provider.connection, toATA, undefined, TOKEN_2022_PROGRAM_ID);
603+
const wasFrozen = accountInfo.isFrozen;
604+
605+
if (wasFrozen) {
606+
await this.thawTokenAccount(toATA);
607+
}
608+
599609
const mintToInstruction = createMintToCheckedInstruction(
600610
this.mMint.publicKey,
601611
toATA,
602612
this.mMintAuthority.publicKey,
603613
BigInt(amount.toString()),
604614
6,
605-
[this.admin],
615+
[],
606616
TOKEN_2022_PROGRAM_ID,
607617
);
608618

609619
let tx = new Transaction();
610620
tx.add(mintToInstruction);
611-
await this.provider.sendAndConfirm!(tx, [this.admin]);
621+
await this.provider.sendAndConfirm!(tx, [this.mMintAuthority]);
622+
623+
// Re-freeze the account if it was originally frozen
624+
if (wasFrozen) {
625+
await this.freezeTokenAccount(toATA);
626+
}
612627
}
613628

614629
public async getTokenBalance(tokenAccount: PublicKey) {
@@ -666,6 +681,32 @@ class EarnTest<V extends Variant = Variant.New> {
666681
return { sourceATA };
667682
}
668683

684+
public async thawTokenAccount(tokenAccount: PublicKey) {
685+
// For testing purposes, we'll directly manipulate the account state using the SVM
686+
// In a real scenario, only the freeze authority (global account) can thaw the account
687+
const accountInfo = this.svm.getAccount(tokenAccount)!;
688+
689+
// Token account state is at offset 108 for Token2022 accounts
690+
// AccountState: Uninitialized = 0, Initialized = 1, Frozen = 2
691+
// We set it to Initialized (1) to thaw it
692+
accountInfo.data[108] = 1;
693+
694+
this.svm.setAccount(tokenAccount, accountInfo);
695+
}
696+
697+
public async freezeTokenAccount(tokenAccount: PublicKey) {
698+
// For testing purposes, we'll directly manipulate the account state using the SVM
699+
// In a real scenario, only the freeze authority (global account) can freeze the account
700+
const accountInfo = this.svm.getAccount(tokenAccount)!;
701+
702+
// Token account state is at offset 108 for Token2022 accounts
703+
// AccountState: Uninitialized = 0, Initialized = 1, Frozen = 2
704+
// We set it to Frozen (2) to freeze it
705+
accountInfo.data[108] = 2;
706+
707+
this.svm.setAccount(tokenAccount, accountInfo);
708+
}
709+
669710
// general SVM cheat functions
670711
public warp(seconds: BN, increment: boolean) {
671712
const clock = this.svm.getClock();
@@ -1902,5 +1943,281 @@ for (const variant of VARIANTS) {
19021943
await $.expectTokenAccountState(earnerTwoATA, AccountState.Frozen);
19031944
});
19041945
});
1946+
1947+
describe('recover_m unit tests', () => {
1948+
beforeEach(async () => {
1949+
// Initialize the program
1950+
await $.initializeEarn(initialIndex);
1951+
});
1952+
1953+
test('source_token_account not frozen - reverts', async () => {
1954+
// Create source and destination token accounts
1955+
const sourceAccount = await $.getATA($.mMint.publicKey, $.earnerOne.publicKey);
1956+
const destinationAccount = await $.getATA($.mMint.publicKey, $.admin.publicKey);
1957+
1958+
// Mint some tokens to the source account
1959+
await $.mintM($.earnerOne.publicKey, new BN(1000));
1960+
1961+
// Source account should be frozen by default due to M mint configuration
1962+
// Thaw the source account to make it invalid for recover_m
1963+
await $.thawTokenAccount(sourceAccount);
1964+
1965+
// Attempt to recover from unfrozen source account should fail
1966+
await $.expectAnchorError(
1967+
$.earn.methods
1968+
.recoverM(null)
1969+
.accountsPartial({
1970+
admin: $.admin.publicKey,
1971+
sourceTokenAccount: sourceAccount,
1972+
destinationTokenAccount: destinationAccount,
1973+
})
1974+
.signers([$.admin])
1975+
.rpc(),
1976+
'InvalidAccount',
1977+
);
1978+
});
1979+
1980+
test('m_mint doesnt match stored value in global account - reverts', async () => {
1981+
// Create a different mint
1982+
const wrongMint = new Keypair();
1983+
await $.createMint(wrongMint, $.mMintAuthority.publicKey);
1984+
1985+
// Create source and destination token accounts for the wrong mint
1986+
const sourceAccount = await $.getATA(wrongMint.publicKey, $.earnerOne.publicKey);
1987+
const destinationAccount = await $.getATA(wrongMint.publicKey, $.admin.publicKey);
1988+
1989+
// Attempt to recover with wrong mint should fail
1990+
await $.expectSystemError(
1991+
$.earn.methods
1992+
.recoverM(null)
1993+
.accountsPartial({
1994+
admin: $.admin.publicKey,
1995+
mMint: wrongMint.publicKey,
1996+
sourceTokenAccount: sourceAccount,
1997+
destinationTokenAccount: destinationAccount,
1998+
})
1999+
.signers([$.admin])
2000+
.rpc(),
2001+
);
2002+
});
2003+
2004+
test('token accounts are for the wrong mint - reverts', async () => {
2005+
// Create a different mint
2006+
const wrongMint = new Keypair();
2007+
await $.createMint(wrongMint, $.admin.publicKey);
2008+
2009+
// Create source and destination token accounts for the wrong mint
2010+
const sourceAccount = await $.getATA(wrongMint.publicKey, $.earnerOne.publicKey);
2011+
const destinationAccount = await $.getATA(wrongMint.publicKey, $.admin.publicKey);
2012+
2013+
// Attempt to recover with token accounts for wrong mint should fail
2014+
await $.expectAnchorError(
2015+
$.earn.methods
2016+
.recoverM(null)
2017+
.accountsPartial({
2018+
admin: $.admin.publicKey,
2019+
sourceTokenAccount: sourceAccount,
2020+
destinationTokenAccount: destinationAccount,
2021+
})
2022+
.signers([$.admin])
2023+
.rpc(),
2024+
'InvalidAccount',
2025+
);
2026+
});
2027+
2028+
test('amount is more than source token balance - reverts', async () => {
2029+
// Create source and destination token accounts
2030+
const sourceAccount = await $.getATA($.mMint.publicKey, $.earnerOne.publicKey);
2031+
const destinationAccount = await $.getATA($.mMint.publicKey, $.admin.publicKey);
2032+
2033+
// Mint some tokens to the source account
2034+
const sourceBalance = new BN(1000);
2035+
await $.mintM($.earnerOne.publicKey, sourceBalance);
2036+
2037+
// Attempt to recover more than available balance
2038+
const excessiveAmount = sourceBalance.add(new BN(500));
2039+
await $.expectSystemError(
2040+
$.earn.methods
2041+
.recoverM(excessiveAmount)
2042+
.accountsPartial({
2043+
admin: $.admin.publicKey,
2044+
sourceTokenAccount: sourceAccount,
2045+
destinationTokenAccount: destinationAccount,
2046+
})
2047+
.signers([$.admin])
2048+
.rpc(),
2049+
);
2050+
});
2051+
2052+
test('amount is None, transfers full balance - success', async () => {
2053+
// Create source and destination token accounts
2054+
const sourceAccount = await $.getATA($.mMint.publicKey, $.earnerOne.publicKey);
2055+
const destinationAccount = await $.getATA($.mMint.publicKey, $.admin.publicKey);
2056+
2057+
// Mint some tokens to the source account
2058+
const sourceBalance = new BN(1000);
2059+
await $.mintM($.earnerOne.publicKey, sourceBalance);
2060+
2061+
// Get initial balances
2062+
const initialSourceBalance = await $.getTokenBalance(sourceAccount);
2063+
const initialDestBalance = await $.getTokenBalance(destinationAccount);
2064+
2065+
// Execute recover_m with no amount (should transfer full balance)
2066+
await $.earn.methods
2067+
.recoverM(null)
2068+
.accountsPartial({
2069+
admin: $.admin.publicKey,
2070+
sourceTokenAccount: sourceAccount,
2071+
destinationTokenAccount: destinationAccount,
2072+
})
2073+
.signers([$.admin])
2074+
.rpc();
2075+
2076+
// Verify balances after recovery
2077+
await $.expectTokenBalance(sourceAccount, new BN(0));
2078+
await $.expectTokenBalance(destinationAccount, initialDestBalance.add(initialSourceBalance));
2079+
2080+
// Verify account states
2081+
await $.expectTokenAccountState(sourceAccount, AccountState.Frozen);
2082+
await $.expectTokenAccountState(destinationAccount, AccountState.Initialized);
2083+
});
2084+
2085+
test('amount is less than or equal to source_token_balance - success', async () => {
2086+
// Create source and destination token accounts
2087+
const sourceAccount = await $.getATA($.mMint.publicKey, $.earnerOne.publicKey);
2088+
const destinationAccount = await $.getATA($.mMint.publicKey, $.admin.publicKey);
2089+
2090+
// Mint some tokens to the source account
2091+
const sourceBalance = new BN(1000);
2092+
await $.mintM($.earnerOne.publicKey, sourceBalance);
2093+
2094+
// Get initial balances
2095+
const initialSourceBalance = await $.getTokenBalance(sourceAccount);
2096+
const initialDestBalance = await $.getTokenBalance(destinationAccount);
2097+
2098+
// Execute recover_m with partial amount
2099+
const transferAmount = new BN(600);
2100+
await $.earn.methods
2101+
.recoverM(transferAmount)
2102+
.accountsPartial({
2103+
admin: $.admin.publicKey,
2104+
sourceTokenAccount: sourceAccount,
2105+
destinationTokenAccount: destinationAccount,
2106+
})
2107+
.signers([$.admin])
2108+
.rpc();
2109+
2110+
// Verify balances after recovery
2111+
const expectedSourceBalance = initialSourceBalance.sub(transferAmount);
2112+
const expectedDestBalance = initialDestBalance.add(transferAmount);
2113+
await $.expectTokenBalance(sourceAccount, expectedSourceBalance);
2114+
await $.expectTokenBalance(destinationAccount, expectedDestBalance);
2115+
2116+
// Verify account states
2117+
await $.expectTokenAccountState(sourceAccount, AccountState.Frozen);
2118+
await $.expectTokenAccountState(destinationAccount, AccountState.Initialized);
2119+
});
2120+
2121+
test('destination token account already thawed - success', async () => {
2122+
// Create source and destination token accounts
2123+
const sourceAccount = await $.getATA($.mMint.publicKey, $.earnerOne.publicKey);
2124+
const destinationAccount = await $.getATA($.mMint.publicKey, $.admin.publicKey);
2125+
2126+
// Mint some tokens to the source account
2127+
const sourceBalance = new BN(1000);
2128+
await $.mintM($.earnerOne.publicKey, sourceBalance);
2129+
2130+
// Thaw destination account
2131+
await $.thawTokenAccount(destinationAccount);
2132+
2133+
// Get initial balances
2134+
const initialSourceBalance = await $.getTokenBalance(sourceAccount);
2135+
const initialDestBalance = await $.getTokenBalance(destinationAccount);
2136+
2137+
// Execute recover_m
2138+
const transferAmount = new BN(600);
2139+
await $.earn.methods
2140+
.recoverM(transferAmount)
2141+
.accountsPartial({
2142+
admin: $.admin.publicKey,
2143+
sourceTokenAccount: sourceAccount,
2144+
destinationTokenAccount: destinationAccount,
2145+
})
2146+
.signers([$.admin])
2147+
.rpc();
2148+
2149+
// Verify balances after recovery
2150+
const expectedSourceBalance = initialSourceBalance.sub(transferAmount);
2151+
const expectedDestBalance = initialDestBalance.add(transferAmount);
2152+
await $.expectTokenBalance(sourceAccount, expectedSourceBalance);
2153+
await $.expectTokenBalance(destinationAccount, expectedDestBalance);
2154+
2155+
// Verify account states
2156+
await $.expectTokenAccountState(sourceAccount, AccountState.Frozen);
2157+
await $.expectTokenAccountState(destinationAccount, AccountState.Initialized);
2158+
});
2159+
2160+
test('destination token account frozen - success', async () => {
2161+
// Create source and destination token accounts
2162+
const sourceAccount = await $.getATA($.mMint.publicKey, $.earnerOne.publicKey);
2163+
const destinationAccount = await $.getATA($.mMint.publicKey, $.admin.publicKey);
2164+
2165+
// Mint some tokens to the source account
2166+
const sourceBalance = new BN(1000);
2167+
await $.mintM($.earnerOne.publicKey, sourceBalance);
2168+
2169+
// Destination account should be frozen by default due to M mint configuration
2170+
await $.expectTokenAccountState(destinationAccount, AccountState.Frozen);
2171+
2172+
// Get initial balances
2173+
const initialSourceBalance = await $.getTokenBalance(sourceAccount);
2174+
const initialDestBalance = await $.getTokenBalance(destinationAccount);
2175+
2176+
// Execute recover_m
2177+
const transferAmount = new BN(600);
2178+
await $.earn.methods
2179+
.recoverM(transferAmount)
2180+
.accountsPartial({
2181+
admin: $.admin.publicKey,
2182+
sourceTokenAccount: sourceAccount,
2183+
destinationTokenAccount: destinationAccount,
2184+
})
2185+
.signers([$.admin])
2186+
.rpc();
2187+
2188+
// Verify balances after recovery
2189+
const expectedSourceBalance = initialSourceBalance.sub(transferAmount);
2190+
const expectedDestBalance = initialDestBalance.add(transferAmount);
2191+
await $.expectTokenBalance(sourceAccount, expectedSourceBalance);
2192+
await $.expectTokenBalance(destinationAccount, expectedDestBalance);
2193+
2194+
// Verify account states
2195+
await $.expectTokenAccountState(sourceAccount, AccountState.Frozen);
2196+
await $.expectTokenAccountState(destinationAccount, AccountState.Initialized);
2197+
});
2198+
2199+
test('non-admin cannot recover - reverts', async () => {
2200+
// Create source and destination token accounts
2201+
const sourceAccount = await $.getATA($.mMint.publicKey, $.earnerOne.publicKey);
2202+
const destinationAccount = await $.getATA($.mMint.publicKey, $.nonAdmin.publicKey);
2203+
2204+
// Mint some tokens to the source account
2205+
await $.mintM($.earnerOne.publicKey, new BN(1000));
2206+
2207+
// Attempt to recover as non-admin should fail
2208+
await $.expectAnchorError(
2209+
$.earn.methods
2210+
.recoverM(null)
2211+
.accountsPartial({
2212+
admin: $.nonAdmin.publicKey,
2213+
sourceTokenAccount: sourceAccount,
2214+
destinationTokenAccount: destinationAccount,
2215+
})
2216+
.signers([$.nonAdmin])
2217+
.rpc(),
2218+
'NotAuthorized',
2219+
);
2220+
});
2221+
});
19052222
});
19062223
}

0 commit comments

Comments
 (0)