From 10bbfcdcfc5ecc84173c1de0357629d6c08ee335 Mon Sep 17 00:00:00 2001 From: Noah Prince <83885631+ChewingGlass@users.noreply.github.com> Date: Mon, 3 Jun 2024 11:13:53 -0700 Subject: [PATCH] Feat/632 hip 109 (#634) HIP 109 implementation --- Cargo.lock | 2 +- .../src/create-boost-config.ts | 2 +- .../src/update-boost-config.ts | 4 +-- packages/hexboosting-sdk/src/pdas.ts | 17 ++++++++- packages/hexboosting-sdk/src/resolvers.ts | 10 +++++- programs/hexboosting/Cargo.toml | 2 +- .../hexboosting/src/instructions/boost_v0.rs | 17 +++++---- .../src/instructions/close_boost_v0.rs | 4 +-- .../src/instructions/start_boost_v0.rs | 4 +-- programs/hexboosting/src/state.rs | 26 +++++++++++++- tests/hexboosting.ts | 36 ++++++++++--------- tests/utils/compression.ts | 6 ++-- utils/bulk-claim-rewards/Cargo.lock | 2 +- utils/bulk-claim-rewards/src/claim_rewards.rs | 1 - 14 files changed, 92 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c2c986333..3d3fe7b01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1999,7 +1999,7 @@ dependencies = [ [[package]] name = "hexboosting" -version = "0.0.5" +version = "0.1.0" dependencies = [ "anchor-lang", "anchor-spl", diff --git a/packages/helium-admin-cli/src/create-boost-config.ts b/packages/helium-admin-cli/src/create-boost-config.ts index ce4a1cbba..65d2335ec 100644 --- a/packages/helium-admin-cli/src/create-boost-config.ts +++ b/packages/helium-admin-cli/src/create-boost-config.ts @@ -104,7 +104,7 @@ export async function run(args: any = process.argv) { rentReclaimAuthority: new PublicKey(argv.rentReclaimAuthority), authority: subDaoAuth, subDao, - startAuthority: new PublicKey(argv.startAuthority) + startAuthority: new PublicKey(argv.startAuthority), }) .instruction(), ]; diff --git a/packages/helium-admin-cli/src/update-boost-config.ts b/packages/helium-admin-cli/src/update-boost-config.ts index 3009efe74..3ce2b623a 100644 --- a/packages/helium-admin-cli/src/update-boost-config.ts +++ b/packages/helium-admin-cli/src/update-boost-config.ts @@ -50,11 +50,11 @@ export async function run(args: any = process.argv) { }, minimumPeriods: { type: "number", - describe: "The new minimum number of periods" + describe: "The new minimum number of periods", }, boostPrice: { type: "string", - describe: "The boost price in bones" + describe: "The boost price in bones", }, dntMint: { type: "string", diff --git a/packages/hexboosting-sdk/src/pdas.ts b/packages/hexboosting-sdk/src/pdas.ts index 639157a38..19b336b81 100644 --- a/packages/hexboosting-sdk/src/pdas.ts +++ b/packages/hexboosting-sdk/src/pdas.ts @@ -2,15 +2,30 @@ import { PublicKey } from "@solana/web3.js"; import { PROGRAM_ID } from "./constants"; import BN from "bn.js"; +enum DeviceType { + cbrsIndoor, + cbrsOutdoor, + wifiIndoor, + wifiOutdoor, +} + export function boostedHexKey( boostConfig: PublicKey, + deviceType: any, location: BN, programId: PublicKey = PROGRAM_ID ) { const locBuffer = Buffer.alloc(8); locBuffer.writeBigUint64LE(BigInt(location.toString())); + const deviceTypeName = Object.keys(deviceType)[0]; + let deviceTypeValue = DeviceType[deviceTypeName]; return PublicKey.findProgramAddressSync( - [Buffer.from("boosted_hex", "utf-8"), boostConfig.toBuffer(), locBuffer], + [ + Buffer.from("boosted_hex", "utf-8"), + boostConfig.toBuffer(), + Buffer.from([deviceTypeValue]), + locBuffer, + ], programId ); } diff --git a/packages/hexboosting-sdk/src/resolvers.ts b/packages/hexboosting-sdk/src/resolvers.ts index d0eb7e978..67bd4cc61 100644 --- a/packages/hexboosting-sdk/src/resolvers.ts +++ b/packages/hexboosting-sdk/src/resolvers.ts @@ -6,13 +6,21 @@ import { } from "@helium/anchor-resolvers"; import { subDaoKey } from "@helium/helium-sub-daos-sdk"; import { PublicKey } from "@solana/web3.js"; +import { boostedHexKey } from "./pdas"; export const hexboostingResolvers = combineResolvers( heliumCommonResolver, - resolveIndividual(async ({ path, accounts }) => { + resolveIndividual(async ({ path, accounts, args }) => { if (path[path.length - 1] === "subDao" && accounts.dntMint) { return subDaoKey(accounts.dntMint as PublicKey)[0]; } + if (path[path.length - 1] === "boostedHex" && accounts.boostConfig && args[0].deviceType && args[0].location) { + return boostedHexKey( + accounts.boostConfig as PublicKey, + args[0].deviceType, + args[0].location + )[0] + } }), ataResolver({ instruction: "boostV0", diff --git a/programs/hexboosting/Cargo.toml b/programs/hexboosting/Cargo.toml index 4ba727415..c230d4123 100644 --- a/programs/hexboosting/Cargo.toml +++ b/programs/hexboosting/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hexboosting" -version = "0.0.5" +version = "0.1.0" description = "Created with Anchor" edition = "2021" diff --git a/programs/hexboosting/src/instructions/boost_v0.rs b/programs/hexboosting/src/instructions/boost_v0.rs index 4fea62691..2ca72e9d8 100644 --- a/programs/hexboosting/src/instructions/boost_v0.rs +++ b/programs/hexboosting/src/instructions/boost_v0.rs @@ -1,4 +1,4 @@ -use crate::error::ErrorCode; +use crate::{error::ErrorCode, DeviceTypeV0}; use anchor_lang::prelude::*; use anchor_spl::{ associated_token::AssociatedToken, @@ -8,7 +8,7 @@ use mobile_entity_manager::CarrierV0; use pyth_sdk_solana::load_price_feed_from_account_info; use shared_utils::resize_to_fit; -use crate::{BoostConfigV0, BoostedHexV0}; +use crate::{BoostConfigV0, BoostedHexV1}; pub const TESTING: bool = std::option_env!("TESTING").is_some(); @@ -20,6 +20,7 @@ pub struct BoostArgsV0 { // invalid pub version: u32, pub amounts: Vec, + pub device_type: DeviceTypeV0, } #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] @@ -30,7 +31,7 @@ pub struct BoostAmountV0 { fn get_space(boosted_hex: &AccountInfo) -> usize { if boosted_hex.data_len() == 0 { - 8 + 60 + std::mem::size_of::() + 8 + 60 + std::mem::size_of::() } else { boosted_hex.data_len() } @@ -69,11 +70,11 @@ pub struct BoostV0<'info> { init_if_needed, payer = payer, space = get_space(boosted_hex), - seeds = [b"boosted_hex", boost_config.key().as_ref(), &args.location.to_le_bytes()], + seeds = [b"boosted_hex", boost_config.key().as_ref(), &[(args.device_type as u8)], &args.location.to_le_bytes()], bump, constraint = boosted_hex.version == args.version @ ErrorCode::InvalidVersion, )] - pub boosted_hex: Box>, + pub boosted_hex: Box>, pub system_program: Program<'info, System>, pub token_program: Program<'info, Token>, pub associated_token_program: Program<'info, AssociatedToken>, @@ -87,6 +88,7 @@ pub fn handler(ctx: Context, args: BoostArgsV0) -> Result<()> { ctx.accounts.boosted_hex.location = args.location; ctx.accounts.boosted_hex.bump_seed = ctx.bumps["boosted_hex"]; ctx.accounts.boosted_hex.version += 1; + ctx.accounts.boosted_hex.device_type = args.device_type; // Insert the new periods let max_period = args @@ -102,6 +104,7 @@ pub fn handler(ctx: Context, args: BoostArgsV0) -> Result<()> { .boosts_by_period .resize(max_period + 1, 0); } + let now = Clock::get()?.unix_timestamp; for amount in args.amounts.clone() { @@ -163,8 +166,8 @@ pub fn handler(ctx: Context, args: BoostArgsV0) -> Result<()> { let total_fee: u64 = args .amounts .iter() - .map(|amount| amount.amount as u64 * ctx.accounts.boost_config.boost_price) - .sum(); + .map(|amount| (amount.amount as u64 * ctx.accounts.boost_config.boost_price)) + .sum::(); let mobile_price_oracle = load_price_feed_from_account_info(&ctx.accounts.price_oracle).map_err(|e| { msg!("Pyth error {}", e); diff --git a/programs/hexboosting/src/instructions/close_boost_v0.rs b/programs/hexboosting/src/instructions/close_boost_v0.rs index 09998e26d..26da47cff 100644 --- a/programs/hexboosting/src/instructions/close_boost_v0.rs +++ b/programs/hexboosting/src/instructions/close_boost_v0.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -use crate::{BoostConfigV0, BoostedHexV0}; +use crate::{BoostConfigV0, BoostedHexV1}; #[derive(Accounts)] pub struct CloseBoostV0<'info> { @@ -15,7 +15,7 @@ pub struct CloseBoostV0<'info> { constraint = boosted_hex.is_expired(&boost_config), has_one = boost_config )] - pub boosted_hex: Box>, + pub boosted_hex: Box>, } pub fn handler(_ctx: Context) -> Result<()> { diff --git a/programs/hexboosting/src/instructions/start_boost_v0.rs b/programs/hexboosting/src/instructions/start_boost_v0.rs index 81e6e6689..98288359a 100644 --- a/programs/hexboosting/src/instructions/start_boost_v0.rs +++ b/programs/hexboosting/src/instructions/start_boost_v0.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -use crate::{BoostConfigV0, BoostedHexV0}; +use crate::{BoostConfigV0, BoostedHexV1}; #[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] pub struct StartBoostArgsV0 { @@ -18,7 +18,7 @@ pub struct StartBoostV0<'info> { mut, has_one = boost_config, )] - pub boosted_hex: Box>, + pub boosted_hex: Box>, } pub fn handler(ctx: Context, args: StartBoostArgsV0) -> Result<()> { diff --git a/programs/hexboosting/src/state.rs b/programs/hexboosting/src/state.rs index f34425e65..27d03629c 100644 --- a/programs/hexboosting/src/state.rs +++ b/programs/hexboosting/src/state.rs @@ -20,6 +20,15 @@ pub struct BoostConfigV0 { pub start_authority: Pubkey, } +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Default, PartialEq)] +pub enum DeviceTypeV0 { + #[default] + CbrsIndoor = 0, + CbrsOutdoor = 1, + WifiIndoor = 2, + WifiOutdoor = 3, +} + #[account] pub struct BoostedHexV0 { pub boost_config: Pubkey, @@ -36,7 +45,22 @@ pub struct BoostedHexV0 { pub version: u32, } -impl BoostedHexV0 { +#[account] +pub struct BoostedHexV1 { + pub device_type: DeviceTypeV0, + pub boost_config: Pubkey, + // Track changes to the boosted hex so client can pass what version it made a change to + pub version: u32, + pub location: u64, + // 0 if the boosting has not yet started. Avoding using an option here to keep serialization length + // consistent + pub start_ts: i64, + pub bump_seed: u8, + /// Each entry represents the boost multiplier for a given period + pub boosts_by_period: Vec, +} + +impl BoostedHexV1 { pub fn is_expired(&self, boost_config: &BoostConfigV0) -> bool { if self.start_ts == 0 { false diff --git a/tests/hexboosting.ts b/tests/hexboosting.ts index 596cc5cc5..ffe20a0d7 100644 --- a/tests/hexboosting.ts +++ b/tests/hexboosting.ts @@ -21,13 +21,11 @@ import { } from "@solana/web3.js"; import { BN } from "bn.js"; import { expect } from "chai"; -import { - init as initHeliumEntityManager -} from "../packages/helium-entity-manager-sdk/src"; +import { init as initHeliumEntityManager } from "../packages/helium-entity-manager-sdk/src"; import { boostConfigKey, boostedHexKey, - init + init, } from "../packages/hexboosting-sdk"; import { init as initMobileEntityManager } from "../packages/mobile-entity-manager-sdk/src"; import { DataCredits } from "../target/types/data_credits"; @@ -39,10 +37,11 @@ import { ensureHEMIdl, ensureHSDIdl, ensureMemIdl, - initTestDataCredits + initTestDataCredits, } from "./utils/fixtures"; import { random } from "./utils/string"; + describe("hexboosting", () => { anchor.setProvider(anchor.AnchorProvider.local("http://127.0.0.1:8899")); @@ -253,6 +252,7 @@ describe("hexboosting", () => { .boostV0({ location: new BN(1), version: 0, + deviceType: { wifiIndoor: {} }, amounts: [ { period: 0, @@ -293,7 +293,6 @@ describe("hexboosting", () => { ) ).amount; - console.log(pythPrice); const expected = Number( BigInt(toBN((6 * 0.005) / pythPrice, 6).toNumber()) ); @@ -302,8 +301,9 @@ describe("hexboosting", () => { expected ); - const hex = await program.account.boostedHexV0.fetch(boostedHex!); + const hex = await program.account.boostedHexV1.fetch(boostedHex!); + expect(Object.keys(hex.deviceType)[0]).to.eq("wifiIndoor"); expect(hex.location.toNumber()).to.eq(1); expect(hex.startTs.toNumber()).to.eq(0); expect(hex.boostsByPeriod.toJSON().data).to.deep.eq([1, 1, 1, 1, 1, 1]); @@ -315,6 +315,7 @@ describe("hexboosting", () => { .boostV0({ location: new BN(1), version: 0, + deviceType: { wifiIndoor: {} }, amounts: [ { period: 0, @@ -362,6 +363,7 @@ describe("hexboosting", () => { .boostV0({ location: new BN(1), version: 1, + deviceType: { wifiIndoor: {} }, amounts: [ { period: 2, @@ -393,21 +395,19 @@ describe("hexboosting", () => { Number(expected) ); - const hex = await program.account.boostedHexV0.fetch(boostedHex!); + const hex = await program.account.boostedHexV1.fetch(boostedHex!); expect(hex.boostsByPeriod.toJSON().data).to.deep.eq([ - 1, - 1, - 2, - 1, - 1, - 1, - 2, + 1, 1, 2, 1, 1, 1, 2, ]); }); it("allows starting a boost", async () => { - const boostedHex = boostedHexKey(boostConfigKey(mint)[0], new BN(1))[0]; + const boostedHex = boostedHexKey( + boostConfigKey(mint)[0], + { wifiIndoor: {} }, + new BN(1) + )[0]; await program.methods .startBoostV0({ startTs: new BN(1), @@ -416,7 +416,7 @@ describe("hexboosting", () => { boostedHex, }) .rpc({ skipPreflight: true }); - const acc = await program.account.boostedHexV0.fetch(boostedHex!); + const acc = await program.account.boostedHexV1.fetch(boostedHex!); expect(acc.startTs.toNumber()).to.not.eq(0); }); @@ -429,6 +429,7 @@ describe("hexboosting", () => { beforeEach(async () => { const boostedHex = boostedHexKey( boostConfigKey(mint)[0], + { wifiIndoor: {} }, new BN(1) )[0]; await program.methods @@ -444,6 +445,7 @@ describe("hexboosting", () => { it("allows closing the boost when it's done", async () => { const boostedHex = boostedHexKey( boostConfigKey(mint)[0], + { wifiIndoor: {} }, new BN(1) )[0]; // Wait 7 seconds so it is fully expired diff --git a/tests/utils/compression.ts b/tests/utils/compression.ts index b5b010700..3b1b5743b 100644 --- a/tests/utils/compression.ts +++ b/tests/utils/compression.ts @@ -94,7 +94,7 @@ export async function createCompressionNft({ creators: [], editionNonce: 0, tokenProgramVersion: TokenProgramVersion.Original, - tokenStandard: TokenStandard.Fungible, + tokenStandard: TokenStandard.NonFungible, uses: null, collection: null, primarySaleHappened: false, @@ -126,7 +126,7 @@ export async function createCompressionNft({ await bubblegum.methods .mintToCollectionV1({ ...metadata, - tokenStandard: { fungible: {} }, + tokenStandard: { nonFungible: {} }, tokenProgramVersion: { original: {} }, }) .accounts({ @@ -144,7 +144,7 @@ export async function createCompressionNft({ await bubblegum.methods .mintV1({ ...metadata, - tokenStandard: { fungible: {} }, + tokenStandard: { nonFungible: {} }, tokenProgramVersion: { original: {} }, }) .accounts({ diff --git a/utils/bulk-claim-rewards/Cargo.lock b/utils/bulk-claim-rewards/Cargo.lock index cd6505543..e244a0d08 100644 --- a/utils/bulk-claim-rewards/Cargo.lock +++ b/utils/bulk-claim-rewards/Cargo.lock @@ -2285,7 +2285,7 @@ dependencies = [ [[package]] name = "helium-sub-daos" -version = "0.1.5" +version = "0.1.6" dependencies = [ "anchor-lang", "anchor-spl", diff --git a/utils/bulk-claim-rewards/src/claim_rewards.rs b/utils/bulk-claim-rewards/src/claim_rewards.rs index 6f35e3de7..00cee4517 100644 --- a/utils/bulk-claim-rewards/src/claim_rewards.rs +++ b/utils/bulk-claim-rewards/src/claim_rewards.rs @@ -958,7 +958,6 @@ fn construct_distribute_rewards_accounts + Clone> }, merkle_tree, compression_program, - token_program: anchor_spl::token::ID, }) }