diff --git a/Cargo.lock b/Cargo.lock index 8b501f251..5809affdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1964,7 +1964,7 @@ dependencies = [ [[package]] name = "helium-sub-daos" -version = "0.1.10" +version = "0.1.11" dependencies = [ "anchor-lang", "anchor-spl", diff --git a/packages/helium-admin-cli/src/switch-mobile-ops-fund.ts b/packages/helium-admin-cli/src/switch-mobile-ops-fund.ts new file mode 100644 index 000000000..39e5f325c --- /dev/null +++ b/packages/helium-admin-cli/src/switch-mobile-ops-fund.ts @@ -0,0 +1,80 @@ +import * as anchor from "@coral-xyz/anchor"; +import { daoKey, init as initHsd } from "@helium/helium-sub-daos-sdk"; +import { HNT_MINT, MOBILE_MINT } from "@helium/spl-utils"; +import { getAssociatedTokenAddressSync } from "@solana/spl-token"; +import { PublicKey } from "@solana/web3.js"; +import Squads from "@sqds/sdk"; +import os from "os"; +import yargs from "yargs/yargs"; +import { loadKeypair, sendInstructionsOrSquads } from "./utils"; + +export async function run(args: any = process.argv) { + const yarg = yargs(args).options({ + wallet: { + alias: "k", + describe: "Anchor wallet keypair", + default: `${os.homedir()}/.config/solana/id.json`, + }, + url: { + alias: "u", + default: "http://127.0.0.1:8899", + describe: "The solana url", + }, + multisig: { + type: "string", + describe: + "Address of the squads multisig to be authority. If not provided, your wallet will be the authority", + }, + authorityIndex: { + type: "number", + describe: "Authority index for squads. Defaults to 1", + default: 1, + }, + }); + + const argv = await yarg.argv; + process.env.ANCHOR_WALLET = argv.wallet; + process.env.ANCHOR_PROVIDER_URL = argv.url; + anchor.setProvider(anchor.AnchorProvider.local(argv.url)); + const provider = anchor.getProvider() as anchor.AnchorProvider; + + const wallet = new anchor.Wallet(loadKeypair(argv.wallet)); + const squads = Squads.endpoint(process.env.ANCHOR_PROVIDER_URL, wallet, { + commitmentOrConfig: "finalized", + }); + let multisig = argv.multisig ? new PublicKey(argv.multisig) : undefined; + + const hsdProgram = await initHsd(provider); + + const dao = daoKey(HNT_MINT)[0] + const daoAuth = (await hsdProgram.account.subDaoV0.fetch(dao)) + .authority; + const instructions = [ + await hsdProgram.methods + .switchMobileOpsFund() + .accounts({ + authority: daoAuth, + payer: daoAuth, + opsFundHnt: getAssociatedTokenAddressSync( + daoAuth, + HNT_MINT + ), + opsFundMobile: getAssociatedTokenAddressSync( + daoAuth, + MOBILE_MINT + ), + dao, + }) + .instruction(), + ]; + + await sendInstructionsOrSquads({ + provider, + instructions, + signers: [], + executeTransaction: false, + squads, + multisig, + authorityIndex: argv.authorityIndex, + }); +} diff --git a/packages/migration-service/src/ledger.ts b/packages/migration-service/src/ledger.ts index 21661e2d5..89de426f3 100644 --- a/packages/migration-service/src/ledger.ts +++ b/packages/migration-service/src/ledger.ts @@ -44,7 +44,10 @@ export async function getMigrateTransactions( ): Promise { const assetApiUrl = process.env.ASSET_API_URL || provider.connection.rpcEndpoint; - const assets = await getAssetsByOwner(assetApiUrl, from.toBase58()); + // Filter out all subscriber NFTs + const assets = (await getAssetsByOwner(assetApiUrl, from.toBase58())).filter( + (asset) => asset?.content?.metadata?.symbol !== "SUBSCRIBER" + ); const uniqueAssets = new Set(assets.map((asset) => asset.id.toBase58())); const vsrProgram = await initVsr(provider); @@ -72,8 +75,10 @@ export async function getMigrateTransactions( proofPath.length - (canopyHeight || 0) ); if (anchorRemainingAccounts.length > 10) { - console.log(`Asset ${asset.id} skipped due to having insufficient canopy`) - continue + console.log( + `Asset ${asset.id} skipped due to having insufficient canopy` + ); + continue; } const ixn = createTransferInstruction( @@ -229,7 +234,7 @@ export async function getMigrateTransactions( return provider.wallet.signTransaction(tx); } - return tx + return tx; }) ); } diff --git a/programs/helium-sub-daos/Cargo.toml b/programs/helium-sub-daos/Cargo.toml index 7fec0cba5..e50692cae 100644 --- a/programs/helium-sub-daos/Cargo.toml +++ b/programs/helium-sub-daos/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "helium-sub-daos" -version = "0.1.10" +version = "0.1.11" description = "Created with Anchor" edition = "2021" diff --git a/programs/helium-sub-daos/src/instructions/issue_rewards_v0.rs b/programs/helium-sub-daos/src/instructions/issue_rewards_v0.rs index 93f9c054e..299d21c2f 100644 --- a/programs/helium-sub-daos/src/instructions/issue_rewards_v0.rs +++ b/programs/helium-sub-daos/src/instructions/issue_rewards_v0.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use anchor_lang::prelude::*; use anchor_spl::token::{Mint, Token, TokenAccount}; use circuit_breaker::{ @@ -6,7 +8,9 @@ use circuit_breaker::{ }; use shared_utils::precise_number::{InnerUint, PreciseNumber}; -use crate::{current_epoch, error::ErrorCode, state::*, OrArithError, EPOCH_LENGTH, TESTING}; +use crate::{ + current_epoch, dao_seeds, error::ErrorCode, state::*, OrArithError, EPOCH_LENGTH, TESTING, +}; #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] pub struct IssueRewardsArgsV0 { @@ -153,7 +157,7 @@ pub fn handler(ctx: Context, args: IssueRewardsArgsV0) -> Result .unwrap(); let total_rewards = PreciseNumber::new(emissions.into()).or_arith_error()?; let rewards_prec = percent_share.checked_mul(&total_rewards).or_arith_error()?; - let rewards_amount: u64 = rewards_prec + let mut rewards_amount: u64 = rewards_prec .floor() // Ensure we never overspend the defined rewards .or_arith_error()? .to_imprecise() @@ -220,13 +224,24 @@ pub fn handler(ctx: Context, args: IssueRewardsArgsV0) -> Result )?; } + // Until August 1st, 2025, emit the 2.9M HNT to the treasury. + // This contract will be deployed between December 6 and December 7 at UTC midnight. + // That means this will emit payment from December 7 to August 1st, 2025 (because epochs are paid in arrears). + // This is a total of 237 days. 2.9M HNT / 237 days = 12236.28691983 HNT per day. + #[allow(clippy::inconsistent_digit_grouping)] + if epoch_curr_ts < 1754006400 + && ctx.accounts.dnt_mint.key() + == Pubkey::from_str("mb1eu7TzEc71KxDpsmsKoucSSuuoGLv1drys1oP2jh6").unwrap() + { + rewards_amount += 12_236_28691983; + } + msg!("Minting {} to treasury", rewards_amount); mint_v0( - ctx.accounts.mint_treasury_emissions_ctx().with_signer(&[&[ - b"dao", - ctx.accounts.hnt_mint.key().as_ref(), - &[ctx.accounts.dao.bump_seed], - ]]), + ctx + .accounts + .mint_treasury_emissions_ctx() + .with_signer(&[dao_seeds!(ctx.accounts.dao)]), MintArgsV0 { amount: rewards_amount, }, diff --git a/programs/helium-sub-daos/src/instructions/mod.rs b/programs/helium-sub-daos/src/instructions/mod.rs index eef24b5a5..3d6155c85 100644 --- a/programs/helium-sub-daos/src/instructions/mod.rs +++ b/programs/helium-sub-daos/src/instructions/mod.rs @@ -7,6 +7,7 @@ pub mod initialize_sub_dao_v0; pub mod issue_hst_pool_v0; pub mod issue_rewards_v0; pub mod issue_voting_rewards_v0; +pub mod switch_mobile_ops_fund; pub mod temp_update_sub_dao_epoch_info; pub mod track_dc_burn_v0; pub mod track_dc_onboarding_fees_v0; @@ -23,6 +24,7 @@ pub use initialize_sub_dao_v0::*; pub use issue_hst_pool_v0::*; pub use issue_rewards_v0::*; pub use issue_voting_rewards_v0::*; +pub use switch_mobile_ops_fund::*; pub use temp_update_sub_dao_epoch_info::*; pub use track_dc_burn_v0::*; pub use track_dc_onboarding_fees_v0::*; diff --git a/programs/helium-sub-daos/src/instructions/switch_mobile_ops_fund.rs b/programs/helium-sub-daos/src/instructions/switch_mobile_ops_fund.rs new file mode 100644 index 000000000..76084e9f0 --- /dev/null +++ b/programs/helium-sub-daos/src/instructions/switch_mobile_ops_fund.rs @@ -0,0 +1,89 @@ +use std::str::FromStr; + +use anchor_lang::prelude::*; +use anchor_spl::{ + associated_token::AssociatedToken, + token::{burn, Burn, Mint, Token, TokenAccount}, +}; +use circuit_breaker::{ + cpi::{accounts::MintV0, mint_v0}, + CircuitBreaker, MintArgsV0, MintWindowedCircuitBreakerV0, +}; + +use crate::{dao_seeds, DaoV0}; + +#[derive(Accounts)] +pub struct SwitchMobileOpsFund<'info> { + #[account(mut)] + pub payer: Signer<'info>, + pub authority: Signer<'info>, + #[account( + init_if_needed, + payer = payer, + associated_token::mint = mobile_mint, + associated_token::authority = payer + )] + pub ops_fund_mobile: Account<'info, TokenAccount>, + #[account( + mut, + address = Pubkey::from_str("mb1eu7TzEc71KxDpsmsKoucSSuuoGLv1drys1oP2jh6").unwrap() + )] + pub mobile_mint: Account<'info, Mint>, + #[account( + init_if_needed, + payer = payer, + associated_token::mint = hnt_mint, + associated_token::authority = payer + )] + pub ops_fund_hnt: Account<'info, TokenAccount>, + #[account( + has_one = hnt_mint, + has_one = authority + )] + pub dao: Account<'info, DaoV0>, + #[account(mut)] + pub hnt_mint: Account<'info, Mint>, + #[account( + mut, + seeds = ["mint_windowed_breaker".as_bytes(), hnt_mint.key().as_ref()], + seeds::program = circuit_breaker_program.key(), + bump = hnt_circuit_breaker.bump_seed + )] + pub hnt_circuit_breaker: Box>, + pub circuit_breaker_program: Program<'info, CircuitBreaker>, + pub system_program: Program<'info, System>, + pub token_program: Program<'info, Token>, + pub associated_token_program: Program<'info, AssociatedToken>, +} + +#[allow(clippy::inconsistent_digit_grouping)] +pub fn handler(ctx: Context) -> Result<()> { + burn( + CpiContext::new( + ctx.accounts.token_program.to_account_info(), + Burn { + mint: ctx.accounts.mobile_mint.to_account_info(), + from: ctx.accounts.ops_fund_mobile.to_account_info(), + authority: ctx.accounts.authority.to_account_info(), + }, + ), + 18_197_425_725_000000, + )?; + mint_v0( + CpiContext::new_with_signer( + ctx.accounts.circuit_breaker_program.to_account_info(), + MintV0 { + mint: ctx.accounts.hnt_mint.to_account_info(), + to: ctx.accounts.ops_fund_hnt.to_account_info(), + circuit_breaker: ctx.accounts.hnt_circuit_breaker.to_account_info(), + token_program: ctx.accounts.token_program.to_account_info(), + mint_authority: ctx.accounts.dao.to_account_info(), + }, + &[dao_seeds!(ctx.accounts.dao)], + ), + MintArgsV0 { + amount: 1_300_000_00000000, + }, + )?; + Ok(()) +} diff --git a/programs/helium-sub-daos/src/lib.rs b/programs/helium-sub-daos/src/lib.rs index 010400c95..8595675be 100644 --- a/programs/helium-sub-daos/src/lib.rs +++ b/programs/helium-sub-daos/src/lib.rs @@ -134,4 +134,8 @@ pub mod helium_sub_daos { ) -> Result<()> { issue_voting_rewards_v0::handler(ctx, args) } + + pub fn switch_mobile_ops_fund(ctx: Context) -> Result<()> { + switch_mobile_ops_fund::handler(ctx) + } } diff --git a/programs/helium-sub-daos/src/state.rs b/programs/helium-sub-daos/src/state.rs index 633294701..c53f1a83e 100644 --- a/programs/helium-sub-daos/src/state.rs +++ b/programs/helium-sub-daos/src/state.rs @@ -102,6 +102,13 @@ pub struct DaoV0 { pub bump_seed: u8, } +#[macro_export] +macro_rules! dao_seeds { + ( $s:expr ) => { + &[b"dao".as_ref(), $s.hnt_mint.as_ref(), &[$s.bump_seed]] + }; +} + #[account] #[derive(Default)] pub struct DaoEpochInfoV0 { diff --git a/tests/helium-sub-daos.ts b/tests/helium-sub-daos.ts index c06a7e04d..d93e0f2cf 100644 --- a/tests/helium-sub-daos.ts +++ b/tests/helium-sub-daos.ts @@ -996,6 +996,7 @@ describe("helium-sub-daos", () => { expect((postMobileBalance - preMobileBalance).toString()).to.eq( ((SUB_DAO_EPOCH_REWARDS / 100) * 92).toString() ); + expect(vsrEpochInfoAcc.rewardsAmount.toString()).to.eq("0"); const acc = await program.account.subDaoEpochInfoV0.fetch( diff --git a/utils/shared-utils/src/precise_number.rs b/utils/shared-utils/src/precise_number.rs index 89d91fcdb..ec9c64445 100644 --- a/utils/shared-utils/src/precise_number.rs +++ b/utils/shared-utils/src/precise_number.rs @@ -330,7 +330,7 @@ impl PreciseNumber { Some((frac, -bits)) } } else { - let bits = 128_i64.checked_sub(i64::from(self.to_imprecise()?.leading_zeros()))?; + let bits = (128_i64).checked_sub(i64::from(self.to_imprecise()?.leading_zeros()))?; let frac = PreciseNumber { value: self.value >> bits, }; @@ -443,7 +443,7 @@ impl PreciseNumber { let f = f1.signed().checked_sub(&PreciseNumber::one().signed())?; - let s_divisor = PreciseNumber { value: two() }.signed().checked_add(&f)?; + let s_divisor = (PreciseNumber { value: two() }).signed().checked_add(&f)?; let s = &f.checked_div(&s_divisor)?; let s2 = s.checked_mul(s)?.value; let s4 = s2.checked_mul(&s2)?; @@ -459,7 +459,7 @@ impl PreciseNumber { let r = t1.checked_add(&t2)?; let hfsq = f .checked_mul(&f)? - .checked_div(&PreciseNumber { value: two() }.signed())?; + .checked_div(&(PreciseNumber { value: two() }).signed())?; let k = SignedPreciseNumber { value: PreciseNumber::new(u128::try_from(ki.abs()).ok()?)?, is_negative: ki < 0,