Skip to content

Commit 06ffd41

Browse files
authored
Merge pull request #164 from m0-foundation/proto-364-hal-02
PROTO-364: HAL-02: Add'l mint checks on migration
2 parents 5339724 + 1907d26 commit 06ffd41

File tree

4 files changed

+96
-9
lines changed

4 files changed

+96
-9
lines changed

programs/earn/src/instructions/admin/initialize.rs

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use spl_token_2022::extension::{
1717

1818
// local dependencies
1919
use crate::{
20-
constants::{ANCHOR_DISCRIMINATOR_SIZE, PORTAL_PROGRAM},
20+
constants::{ANCHOR_DISCRIMINATOR_SIZE, PORTAL_PROGRAM, INDEX_SCALE_F64},
2121
errors::EarnError,
2222
state::{EarnGlobal, GLOBAL_SEED, TOKEN_AUTHORITY_SEED},
2323
utils::{conversion::{update_multiplier, index_to_multiplier}, token::thaw_token_account},
@@ -27,6 +27,7 @@ cfg_if::cfg_if!(
2727
if #[cfg(feature = "migrate")] {
2828
declare_program!(old_earn);
2929
use old_earn::{accounts::Global as OldGlobal, ID as OLD_EARN_PROGRAM_ID};
30+
use crate::utils::conversion::{get_scaled_ui_config, principal_to_amount_up};
3031
}
3132
);
3233

@@ -57,10 +58,18 @@ pub struct Initialize<'info> {
5758

5859
#[account(
5960
mut,
60-
mint::token_program = token_program
61+
mint::token_program = token_program,
62+
mint::decimals = 6, // Must be 6 decimals
6163
)]
6264
pub m_mint: InterfaceAccount<'info, Mint>,
6365

66+
#[cfg(feature = "migrate")]
67+
#[account(
68+
address = old_global_account.mint @ EarnError::InvalidMint,
69+
mint::decimals = m_mint.decimals
70+
)]
71+
pub old_m_mint: InterfaceAccount<'info, Mint>,
72+
6473
/// CHECK: This account is validated by its seeds
6574
#[account(
6675
seeds = [TOKEN_AUTHORITY_SEED],
@@ -103,7 +112,7 @@ pub struct Initialize<'info> {
103112
}
104113

105114
impl Initialize<'_> {
106-
fn validate(&self) -> Result<()> {
115+
fn validate(&self, _current_index: u64) -> Result<()> {
107116
// Get the mint account data once and reuse it
108117
let account_info = self.m_mint.to_account_info();
109118
let mint_data = account_info.try_borrow_data()?;
@@ -123,6 +132,23 @@ impl Initialize<'_> {
123132
return err!(EarnError::InvalidMint);
124133
}
125134

135+
// Verify that the new multiplier is less than or equal to the current index (if migrating) or provided index (if not migrating)
136+
// This is required because the call to our update_multiplier fn will fail silently if the multiplier on the mint is greater.
137+
// That behavior is desired except when initializing the program. Therefore, we catch the error here.
138+
let current_multiplier: f64;
139+
let mint_multiplier: f64 = scaled_ui_config.new_multiplier.into();
140+
cfg_if! {
141+
if #[cfg(feature = "migrate")] {
142+
current_multiplier = self.old_global_account.index as f64 / INDEX_SCALE_F64;
143+
144+
} else {
145+
current_multiplier = _current_index as f64 / INDEX_SCALE_F64;
146+
}
147+
}
148+
if mint_multiplier > current_multiplier {
149+
return err!(EarnError::InvalidMint);
150+
}
151+
126152
// 2. Must have the Default Account State extension
127153
// and the global account as the freeze authority
128154
if !extensions.contains(&ExtensionType::DefaultAccountState) {
@@ -158,7 +184,7 @@ impl Initialize<'_> {
158184
Ok(())
159185
}
160186

161-
#[access_control(ctx.accounts.validate())]
187+
#[access_control(ctx.accounts.validate(_current_index))]
162188
pub fn handler(ctx: Context<Initialize>, _current_index: u64) -> Result<()> {
163189
// Set global state
164190
ctx.accounts.global_account.set_inner(EarnGlobal {
@@ -184,6 +210,14 @@ impl Initialize<'_> {
184210
index_to_multiplier(ctx.accounts.old_global_account.index)?, // index
185211
ctx.accounts.old_global_account.timestamp as i64, // timestamp
186212
)?;
213+
214+
// Check that the supply of the new mint (adjusted for the multiplier) is not greater than the supply of the old m mint
215+
let scaled_ui_config = get_scaled_ui_config(&ctx.accounts.m_mint)?;
216+
let new_supply_amount = principal_to_amount_up(ctx.accounts.m_mint.supply, scaled_ui_config.new_multiplier.into())?;
217+
218+
if new_supply_amount > ctx.accounts.old_m_mint.supply {
219+
return err!(EarnError::InvalidMint);
220+
}
187221
} else {
188222
update_multiplier(
189223
&mut ctx.accounts.m_mint, // mint

tests/test-utils.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ export async function createMintInstruction(
143143
defaultAccountState = AccountState.Initialized,
144144
vault?: PublicKey,
145145
mintTokens = false,
146+
decimals = 6,
146147
) {
147148
// mint size with extensions
148149
const mintLen = getMintLen([
@@ -164,7 +165,7 @@ export async function createMintInstruction(
164165
createInitializeScaledUiAmountConfigInstruction(mint, extensionAuth, 1.0, TOKEN_2022_PROGRAM_ID),
165166
createInitializeDefaultAccountStateInstruction(mint, AccountState.Initialized, TOKEN_2022_PROGRAM_ID),
166167
createInitializePermanentDelegateInstruction(mint, extensionAuth, TOKEN_2022_PROGRAM_ID),
167-
createInitializeMintInstruction(mint, 6, payer.publicKey, payer.publicKey, TOKEN_2022_PROGRAM_ID),
168+
createInitializeMintInstruction(mint, decimals, payer.publicKey, payer.publicKey, TOKEN_2022_PROGRAM_ID),
168169
];
169170

170171
const tokenAccount = getAssociatedTokenAddressSync(mint, payer.publicKey, false, TOKEN_2022_PROGRAM_ID);

tests/unit/earn.test.ts

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,9 @@ class EarnTest<V extends Variant = Variant.New> {
463463
this.getEarnGlobalAccount(),
464464
mint.publicKey,
465465
AccountState.Frozen,
466+
undefined,
467+
false,
468+
decimals,
466469
);
467470

468471
let tx = new Transaction();
@@ -849,7 +852,7 @@ class EarnTest<V extends Variant = Variant.New> {
849852
case Variant.New:
850853
await this.earn.methods
851854
.initialize(initialIndex!)
852-
.accounts({
855+
.accountsPartial({
853856
admin: this.admin.publicKey,
854857
mMint: this.mMint.publicKey,
855858
})
@@ -862,6 +865,7 @@ class EarnTest<V extends Variant = Variant.New> {
862865
.accounts({
863866
admin: this.admin.publicKey,
864867
mMint: this.mMint.publicKey,
868+
oldMMint: this.oldMMint!.publicKey,
865869
})
866870
.signers([this.admin])
867871
.rpc();
@@ -979,6 +983,7 @@ for (const variant of VARIANTS) {
979983
.accounts({
980984
admin: $.admin.publicKey,
981985
mMint: $.mMint.publicKey,
986+
oldMMint: $.oldMMint!.publicKey,
982987
})
983988
.signers([$.admin])
984989
.rpc(),
@@ -987,7 +992,7 @@ for (const variant of VARIANTS) {
987992
await $.expectSystemError(
988993
$.earn.methods
989994
.initialize(initialIndex)
990-
.accounts({
995+
.accountsPartial({
991996
admin: $.admin.publicKey,
992997
mMint: $.mMint.publicKey,
993998
})
@@ -1013,6 +1018,7 @@ for (const variant of VARIANTS) {
10131018
admin: $.admin.publicKey,
10141019
mMint: $.mMint.publicKey,
10151020
globalAccount: wrongGlobalAccount,
1021+
oldMMint: $.oldMMint!.publicKey,
10161022
})
10171023
.signers([$.admin])
10181024
.rpc(),
@@ -1046,6 +1052,7 @@ for (const variant of VARIANTS) {
10461052
.accountsPartial({
10471053
admin: $.admin.publicKey,
10481054
mMint: wrongMint.publicKey,
1055+
oldMMint: $.oldMMint!.publicKey,
10491056
tokenProgram: TOKEN_PROGRAM_ID,
10501057
})
10511058
.signers([$.admin])
@@ -1083,6 +1090,7 @@ for (const variant of VARIANTS) {
10831090
.accountsPartial({
10841091
admin: $.admin.publicKey,
10851092
mMint: $.mMint.publicKey,
1093+
oldMMint: $.oldMMint!.publicKey,
10861094
portalTokenAuthority: wrongPortalAuthority,
10871095
})
10881096
.signers([$.admin])
@@ -1119,6 +1127,7 @@ for (const variant of VARIANTS) {
11191127
.accountsPartial({
11201128
admin: $.admin.publicKey,
11211129
mMint: $.mMint.publicKey,
1130+
oldMMint: $.oldMMint!.publicKey,
11221131
extSwapGlobal: wrongExtSwapGlobal,
11231132
})
11241133
.signers([$.admin])
@@ -1153,6 +1162,7 @@ for (const variant of VARIANTS) {
11531162
.accountsPartial({
11541163
admin: $.admin.publicKey,
11551164
mMint: wrongMint.publicKey,
1165+
oldMMint: $.oldMMint!.publicKey,
11561166
})
11571167
.signers([$.admin])
11581168
.rpc(),
@@ -1187,6 +1197,7 @@ for (const variant of VARIANTS) {
11871197
.accountsPartial({
11881198
admin: $.admin.publicKey,
11891199
mMint: wrongMint.publicKey,
1200+
oldMMint: $.oldMMint!.publicKey,
11901201
})
11911202
.signers([$.admin])
11921203
.rpc(),
@@ -1220,6 +1231,7 @@ for (const variant of VARIANTS) {
12201231
.accountsPartial({
12211232
admin: $.admin.publicKey,
12221233
mMint: mint.publicKey,
1234+
oldMMint: $.oldMMint!.publicKey,
12231235
})
12241236
.signers([$.admin])
12251237
.rpc(),
@@ -1258,6 +1270,10 @@ for (const variant of VARIANTS) {
12581270
// migrate test cases
12591271
// [X] given the old global account does not match the seed + program ID
12601272
// [X] it reverts with a constraint seed error
1273+
// [ ] given the new m mint doesn't have the same decimals as the old one
1274+
// [ ] it reverts with a mint decimals error
1275+
// [ ] given the effective supply of the new mint is greater than than the supply of the old mint
1276+
// [ ] it reverts with an invalid mint error
12611277
// [X] given all the accounts are correct
12621278
// [X] the global account is created
12631279
// [X] the admin is set to the signer
@@ -1289,12 +1305,47 @@ for (const variant of VARIANTS) {
12891305
.accountsPartial({
12901306
admin: $.admin.publicKey,
12911307
mMint: $.mMint.publicKey,
1308+
oldMMint: $.oldMMint!.publicKey,
12921309
oldGlobalAccount: wrongGlobalAccount,
12931310
})
12941311
.signers([$.admin])
12951312
.rpc(),
12961313
);
12971314
});
1315+
1316+
test('New m mint has different decimals - reverts', async () => {
1317+
const wrongMint = new Keypair();
1318+
await $.createMMint(wrongMint, $.nonAdmin, 9);
1319+
1320+
await $.expectAnchorError(
1321+
$.earn.methods
1322+
.initialize()
1323+
.accountsPartial({
1324+
admin: $.admin.publicKey,
1325+
mMint: wrongMint.publicKey,
1326+
oldMMint: $.oldMMint!.publicKey,
1327+
})
1328+
.signers([$.admin])
1329+
.rpc(),
1330+
'ConstraintMintDecimals',
1331+
);
1332+
});
1333+
1334+
test('New m mint supply is too large - reverts', async () => {
1335+
await $.mintM($.admin.publicKey, new BN(100_000_000)); // mint 100 m tokens to admin
1336+
1337+
await $.expectSystemError(
1338+
$.earn.methods
1339+
.initialize()
1340+
.accountsPartial({
1341+
admin: $.admin.publicKey,
1342+
mMint: $.mMint.publicKey,
1343+
oldMMint: $.oldMMint!.publicKey,
1344+
})
1345+
.signers([$.admin])
1346+
.rpc(),
1347+
);
1348+
});
12981349
}
12991350

13001351
// given the admin signs the transaction
@@ -1310,14 +1361,15 @@ for (const variant of VARIANTS) {
13101361
.accounts({
13111362
admin: $.admin.publicKey,
13121363
mMint: $.mMint.publicKey,
1364+
oldMMint: $.oldMMint!.publicKey,
13131365
})
13141366
.signers([$.admin])
13151367
.rpc();
13161368
} else {
13171369
// Create and send the transaction
13181370
await $.earn.methods
13191371
.initialize(initialIndex)
1320-
.accounts({
1372+
.accountsPartial({
13211373
admin: $.admin.publicKey,
13221374
mMint: $.mMint.publicKey,
13231375
})

tests/unit/portal.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ describe('Portal unit tests', () => {
279279
});
280280
test('initialize earn', async () => {
281281
await earn.methods
282-
.initialize(new BN(100_000_000))
282+
.initialize(new BN(1e12))
283283
.accounts({
284284
admin: admin.publicKey,
285285
mMint: mint.publicKey,

0 commit comments

Comments
 (0)