diff --git a/packages/distributor-oracle/src/server.ts b/packages/distributor-oracle/src/server.ts index 337f97319..ff19b36f0 100644 --- a/packages/distributor-oracle/src/server.ts +++ b/packages/distributor-oracle/src/server.ts @@ -403,6 +403,7 @@ export class OracleServer { (decoded.name !== "setCurrentRewardsV0" && decoded.name !== "distributeRewardsV0" && decoded.name !== "distributeCompressionRewardsV0" && + decoded.name !== "distributeCustom" && decoded.name !== "initializeRecipientV0" && decoded.name !== "initializeCompressionRecipientV0" && decoded.name !== "setCurrentRewardsWrapperV0" && diff --git a/packages/lazy-distributor-sdk/src/functions/updateCompressionDestination.ts b/packages/lazy-distributor-sdk/src/functions/updateCompressionDestination.ts new file mode 100644 index 000000000..2bfbb642b --- /dev/null +++ b/packages/lazy-distributor-sdk/src/functions/updateCompressionDestination.ts @@ -0,0 +1,54 @@ +import { Idl, Program } from "@coral-xyz/anchor"; +import { LazyDistributor } from "@helium/idls/lib/types/lazy_distributor"; +import { Asset, AssetProof, proofArgsAndAccounts } from "@helium/spl-utils"; +import { PublicKey } from "@solana/web3.js"; +import { recipientKey } from "../pdas"; + +export async function updateCompressionDestination({ + program, + assetId, + lazyDistributor, + rewardsMint, + payer, + destination, + ...rest +}: { + program: Program; + assetId: PublicKey; + rewardsMint?: PublicKey; + assetEndpoint?: string; + lazyDistributor: PublicKey; + owner?: PublicKey; + payer?: PublicKey; + destination: PublicKey | null; + getAssetFn?: (url: string, assetId: PublicKey) => Promise; + getAssetProofFn?: ( + url: string, + assetId: PublicKey + ) => Promise; +}) { + const { + asset: { + ownership: { owner }, + }, + args, + accounts, + remainingAccounts, + } = await proofArgsAndAccounts({ + connection: program.provider.connection, + assetId, + ...rest, + }); + + return program.methods + .updateCompressionDestinationV0({ + ...args, + }) + .accounts({ + ...accounts, + owner, + recipient: recipientKey(lazyDistributor, assetId)[0], + destination: destination == null ? PublicKey.default : destination, + }) + .remainingAccounts(remainingAccounts); +} diff --git a/packages/lazy-distributor-sdk/src/index.ts b/packages/lazy-distributor-sdk/src/index.ts index 5be6c3cb5..7a00b0bf8 100644 --- a/packages/lazy-distributor-sdk/src/index.ts +++ b/packages/lazy-distributor-sdk/src/index.ts @@ -4,6 +4,7 @@ import { PublicKey } from "@solana/web3.js"; import { PROGRAM_ID } from "./constants"; import { lazyDistributorResolvers } from "./resolvers"; +export { updateCompressionDestination } from "./functions/updateCompressionDestination"; export { distributeCompressionRewards } from "./functions/distributeCompressionRewards"; export { initializeCompressionRecipient } from "./functions/initializeCompressionRecipient"; diff --git a/packages/lazy-distributor-sdk/src/resolvers.ts b/packages/lazy-distributor-sdk/src/resolvers.ts index 8b81e263f..b02ed6b9b 100644 --- a/packages/lazy-distributor-sdk/src/resolvers.ts +++ b/packages/lazy-distributor-sdk/src/resolvers.ts @@ -48,6 +48,12 @@ export const lazyDistributorResolvers = combineResolvers( mint: 'common.rewardsMint', owner: 'common.owner', }), + ataResolver({ + instruction: 'distributeCustomDestinationV0', + account: 'common.destinationAccount', + mint: 'common.rewardsMint', + owner: 'common.owner', + }), circuitBreakerResolvers, resolveIndividual(async ({ path, accounts, idlIx }) => { if (path[path.length - 1] === 'targetMetadata') { @@ -101,6 +107,40 @@ export const lazyDistributorResolvers = combineResolvers( resolved, accounts, }; + }, + async ({ accounts, provider, idlIx }) => { + let resolved = 0; + if ( + idlIx.name === 'updateDestinationV0' && + // @ts-ignore + (!accounts.recipientMintAccount || + // @ts-ignore + !accounts.owner) + ) { + // @ts-ignore + const recipient = accounts.recipient as PublicKey; + const recipientAcc = await provider.connection.getAccountInfo(recipient); + const recipientMint = new PublicKey( + recipientAcc!.data.subarray(8 + 32, 8 + 32 + 32) + ); + const recipientMintAccount = ( + await provider.connection.getTokenLargestAccounts(recipientMint) + ).value[0].address; + const recipientMintTokenAccount = await getAccount( + provider.connection, + recipientMintAccount + ); + // @ts-ignore + accounts.owner = recipientMintTokenAccount.owner; + // @ts-ignore + accounts.recipientMintAccount = recipientMintAccount; + resolved += 1; + } + + return { + accounts, + resolved, + }; }, async ({ accounts, provider, idlIx }) => { let resolved = 0; diff --git a/packages/sus/src/index.ts b/packages/sus/src/index.ts index 490fdea49..31d675fd3 100644 --- a/packages/sus/src/index.ts +++ b/packages/sus/src/index.ts @@ -464,6 +464,18 @@ export async function sus({ "This transaction is attempting to steal your locked HNT positions", }); } + if ( + instructions.some( + (ix) => ix.parsed?.name === "updateDestinationV0" + ) + ) { + warningsByTx[index].push({ + severity: "warning", + shortMessage: "Rewards Destination Changed", + message: + "This transaction will change the destination wallet of your mining rewards", + }); + } if ( ( await Promise.all( diff --git a/programs/lazy-distributor/src/error.rs b/programs/lazy-distributor/src/error.rs index 140c32bc2..b07911247 100644 --- a/programs/lazy-distributor/src/error.rs +++ b/programs/lazy-distributor/src/error.rs @@ -16,4 +16,7 @@ pub enum ErrorCode { #[msg("Approver signature required")] InvalidApproverSignature, + + #[msg("This recipient uses a custom destination. Use distribute_custom_destination_v0")] + CustomDestination, } diff --git a/programs/lazy-distributor/src/instructions/distribute/common.rs b/programs/lazy-distributor/src/instructions/distribute/common.rs index 10aa4d5d2..7508d50b8 100644 --- a/programs/lazy-distributor/src/instructions/distribute/common.rs +++ b/programs/lazy-distributor/src/instructions/distribute/common.rs @@ -22,7 +22,7 @@ pub struct DistributeRewardsCommonV0<'info> { #[account( mut, has_one = lazy_distributor, - constraint = recipient.current_rewards.iter().flatten().count() >= ((lazy_distributor.oracles.len() + 1) / 2) + constraint = recipient.current_rewards.iter().flatten().count() >= ((lazy_distributor.oracles.len() + 1) / 2), )] pub recipient: Box>, pub rewards_mint: Box>, @@ -35,8 +35,8 @@ pub struct DistributeRewardsCommonV0<'info> { bump = circuit_breaker.bump_seed )] pub circuit_breaker: Box>, - /// TODO: Should this be permissioned? Should the owner have to sign to receive rewards? - /// CHECK: Just required for ATA + /// CHECK: Checked by either verifying the owner via nft, or via destination on recipient + #[account(mut)] pub owner: AccountInfo<'info>, #[account( init_if_needed, diff --git a/programs/lazy-distributor/src/instructions/distribute/distribute_compression_rewards_v0.rs b/programs/lazy-distributor/src/instructions/distribute/distribute_compression_rewards_v0.rs index 1927869e1..5b35624c4 100644 --- a/programs/lazy-distributor/src/instructions/distribute/distribute_compression_rewards_v0.rs +++ b/programs/lazy-distributor/src/instructions/distribute/distribute_compression_rewards_v0.rs @@ -17,7 +17,7 @@ pub struct DistributeCompressionRewardsArgsV0 { #[derive(Accounts)] pub struct DistributeCompressionRewardsV0<'info> { pub common: DistributeRewardsCommonV0<'info>, - /// CHECK: THe merkle tree + /// CHECK: The merkle tree pub merkle_tree: UncheckedAccount<'info>, pub compression_program: Program<'info, SplAccountCompression>, pub token_program: Program<'info, Token>, @@ -27,6 +27,12 @@ pub fn handler<'info>( ctx: Context<'_, '_, '_, 'info, DistributeCompressionRewardsV0<'info>>, args: DistributeCompressionRewardsArgsV0, ) -> Result<()> { + require_neq!( + ctx.accounts.common.recipient.destination, + Pubkey::default(), + ErrorCode::CustomDestination + ); + verify_compressed_nft(VerifyCompressedNftArgs { data_hash: args.data_hash, creator_hash: args.creator_hash, diff --git a/programs/lazy-distributor/src/instructions/distribute/distribute_custom_destination_v0.rs b/programs/lazy-distributor/src/instructions/distribute/distribute_custom_destination_v0.rs new file mode 100644 index 000000000..beb3730bc --- /dev/null +++ b/programs/lazy-distributor/src/instructions/distribute/distribute_custom_destination_v0.rs @@ -0,0 +1,16 @@ +use super::*; +use anchor_lang::prelude::*; + +#[derive(Accounts)] +pub struct DistributeCustomDestinationV0<'info> { + pub common: DistributeRewardsCommonV0<'info>, +} + +pub fn handler(ctx: Context) -> Result<()> { + require_eq!( + ctx.accounts.common.owner.key(), + ctx.accounts.common.recipient.destination + ); + + distribute_impl(&mut ctx.accounts.common) +} diff --git a/programs/lazy-distributor/src/instructions/distribute/distribute_rewards_v0.rs b/programs/lazy-distributor/src/instructions/distribute/distribute_rewards_v0.rs index 5d079ce94..90e8ce147 100644 --- a/programs/lazy-distributor/src/instructions/distribute/distribute_rewards_v0.rs +++ b/programs/lazy-distributor/src/instructions/distribute/distribute_rewards_v0.rs @@ -1,4 +1,5 @@ use super::common::*; +use crate::error::ErrorCode; use anchor_lang::prelude::*; use anchor_spl::token::TokenAccount; @@ -8,11 +9,16 @@ pub struct DistributeRewardsV0<'info> { #[account( token::mint = common.recipient.asset, constraint = recipient_mint_account.amount > 0, - constraint = recipient_mint_account.owner == common.owner.key() + constraint = recipient_mint_account.owner == common.owner.key(), )] pub recipient_mint_account: Box>, } pub fn handler<'info>(ctx: Context<'_, '_, '_, 'info, DistributeRewardsV0<'info>>) -> Result<()> { + require_neq!( + ctx.accounts.common.recipient.destination, + Pubkey::default(), + ErrorCode::CustomDestination + ); distribute_impl(&mut ctx.accounts.common) } diff --git a/programs/lazy-distributor/src/instructions/distribute/mod.rs b/programs/lazy-distributor/src/instructions/distribute/mod.rs index 2a24ef8c1..f43344ab2 100644 --- a/programs/lazy-distributor/src/instructions/distribute/mod.rs +++ b/programs/lazy-distributor/src/instructions/distribute/mod.rs @@ -1,6 +1,9 @@ pub mod common; pub mod distribute_compression_rewards_v0; +pub mod distribute_custom_destination_v0; pub mod distribute_rewards_v0; + pub use common::*; pub use distribute_compression_rewards_v0::*; +pub use distribute_custom_destination_v0::*; pub use distribute_rewards_v0::*; diff --git a/programs/lazy-distributor/src/instructions/initialize_compression_recipient_v0.rs b/programs/lazy-distributor/src/instructions/initialize_compression_recipient_v0.rs index 69f8ab377..dfd98dca9 100644 --- a/programs/lazy-distributor/src/instructions/initialize_compression_recipient_v0.rs +++ b/programs/lazy-distributor/src/instructions/initialize_compression_recipient_v0.rs @@ -70,6 +70,7 @@ pub fn handler<'info>( current_rewards: vec![None; ctx.accounts.lazy_distributor.oracles.len()], lazy_distributor: ctx.accounts.lazy_distributor.key(), bump_seed: ctx.bumps["recipient"], + destination: Pubkey::default(), }); Ok(()) diff --git a/programs/lazy-distributor/src/instructions/initialize_recipient_v0.rs b/programs/lazy-distributor/src/instructions/initialize_recipient_v0.rs index bdbef6e80..875c67e64 100644 --- a/programs/lazy-distributor/src/instructions/initialize_recipient_v0.rs +++ b/programs/lazy-distributor/src/instructions/initialize_recipient_v0.rs @@ -39,6 +39,7 @@ pub fn handler(ctx: Context) -> Result<()> { current_rewards: vec![None; ctx.accounts.lazy_distributor.oracles.len()], lazy_distributor: ctx.accounts.lazy_distributor.key(), bump_seed: ctx.bumps["recipient"], + destination: Pubkey::default(), }); Ok(()) diff --git a/programs/lazy-distributor/src/instructions/mod.rs b/programs/lazy-distributor/src/instructions/mod.rs index 8521adf9a..29cc2295e 100644 --- a/programs/lazy-distributor/src/instructions/mod.rs +++ b/programs/lazy-distributor/src/instructions/mod.rs @@ -3,6 +3,7 @@ pub mod initialize_compression_recipient_v0; pub mod initialize_lazy_distributor_v0; pub mod initialize_recipient_v0; pub mod set_current_rewards_v0; +pub mod update_destination; pub mod update_lazy_distributor_v0; pub use distribute::*; @@ -10,4 +11,5 @@ pub use initialize_compression_recipient_v0::*; pub use initialize_lazy_distributor_v0::*; pub use initialize_recipient_v0::*; pub use set_current_rewards_v0::*; +pub use update_destination::*; pub use update_lazy_distributor_v0::*; diff --git a/programs/lazy-distributor/src/instructions/update_destination/mod.rs b/programs/lazy-distributor/src/instructions/update_destination/mod.rs new file mode 100644 index 000000000..1a0fb72ba --- /dev/null +++ b/programs/lazy-distributor/src/instructions/update_destination/mod.rs @@ -0,0 +1,5 @@ +pub mod update_compression_destination_v0; +pub mod update_destination_v0; + +pub use update_compression_destination_v0::*; +pub use update_destination_v0::*; diff --git a/programs/lazy-distributor/src/instructions/update_destination/update_compression_destination_v0.rs b/programs/lazy-distributor/src/instructions/update_destination/update_compression_destination_v0.rs new file mode 100644 index 000000000..6527f4af1 --- /dev/null +++ b/programs/lazy-distributor/src/instructions/update_destination/update_compression_destination_v0.rs @@ -0,0 +1,46 @@ +use crate::state::*; +use account_compression_cpi::program::SplAccountCompression; +use anchor_lang::prelude::*; +use shared_utils::{verify_compressed_nft, VerifyCompressedNftArgs}; + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Default)] +pub struct UpdateCompressionDestinationArgsV0 { + pub data_hash: [u8; 32], + pub creator_hash: [u8; 32], + pub root: [u8; 32], + pub index: u32, +} + +#[derive(Accounts)] +#[instruction(args: UpdateCompressionDestinationArgsV0)] +pub struct UpdateCompressionDestinationV0<'info> { + #[account(mut)] + pub recipient: Box>, + pub owner: Signer<'info>, + /// CHECK: User provided destination + pub destination: UncheckedAccount<'info>, + /// CHECK: Checked via verify_compressed_nft + pub merkle_tree: UncheckedAccount<'info>, + pub compression_program: Program<'info, SplAccountCompression>, +} + +pub fn handler<'info>( + ctx: Context<'_, '_, '_, 'info, UpdateCompressionDestinationV0<'info>>, + args: UpdateCompressionDestinationArgsV0, +) -> Result<()> { + verify_compressed_nft(VerifyCompressedNftArgs { + data_hash: args.data_hash, + creator_hash: args.creator_hash, + root: args.root, + index: args.index, + compression_program: ctx.accounts.compression_program.to_account_info(), + merkle_tree: ctx.accounts.merkle_tree.to_account_info(), + owner: ctx.accounts.owner.key(), + delegate: ctx.accounts.owner.key(), + proof_accounts: ctx.remaining_accounts.to_vec(), + })?; + + ctx.accounts.recipient.destination = ctx.accounts.destination.key(); + + Ok(()) +} diff --git a/programs/lazy-distributor/src/instructions/update_destination/update_destination_v0.rs b/programs/lazy-distributor/src/instructions/update_destination/update_destination_v0.rs new file mode 100644 index 000000000..668ba86ea --- /dev/null +++ b/programs/lazy-distributor/src/instructions/update_destination/update_destination_v0.rs @@ -0,0 +1,24 @@ +use crate::state::*; +use anchor_lang::prelude::*; +use anchor_spl::token::TokenAccount; + +#[derive(Accounts)] +pub struct UpdateDestinationV0<'info> { + #[account(mut)] + pub recipient: Box>, + pub owner: Signer<'info>, + /// CHECK: User provided destination + pub destination: UncheckedAccount<'info>, + #[account( + token::mint = recipient.asset, + constraint = recipient_mint_account.amount > 0, + constraint = recipient_mint_account.owner == owner.key() + )] + pub recipient_mint_account: Box>, +} + +pub fn handler<'info>(ctx: Context) -> Result<()> { + ctx.accounts.recipient.destination = ctx.accounts.destination.key(); + + Ok(()) +} diff --git a/programs/lazy-distributor/src/lib.rs b/programs/lazy-distributor/src/lib.rs index 360aa6661..b60ece956 100644 --- a/programs/lazy-distributor/src/lib.rs +++ b/programs/lazy-distributor/src/lib.rs @@ -77,4 +77,21 @@ pub mod lazy_distributor { ) -> Result<()> { update_lazy_distributor_v0::handler(ctx, args) } + + pub fn update_compression_destination_v0<'info>( + ctx: Context<'_, '_, '_, 'info, UpdateCompressionDestinationV0<'info>>, + args: UpdateCompressionDestinationArgsV0, + ) -> Result<()> { + update_compression_destination_v0::handler(ctx, args) + } + + pub fn update_destination_v0(ctx: Context) -> Result<()> { + update_destination_v0::handler(ctx) + } + + pub fn distribute_custom_destination_v0<'info>( + ctx: Context<'_, '_, '_, 'info, DistributeCustomDestinationV0<'info>>, + ) -> Result<()> { + distribute_custom_destination_v0::handler(ctx) + } } diff --git a/programs/lazy-distributor/src/state.rs b/programs/lazy-distributor/src/state.rs index 69cfdaf68..0553362d9 100644 --- a/programs/lazy-distributor/src/state.rs +++ b/programs/lazy-distributor/src/state.rs @@ -29,4 +29,6 @@ pub struct RecipientV0 { pub current_config_version: u16, pub current_rewards: Vec>, // One for each oracle, matching indexes in` LazyDistrubutorV0` pub bump_seed: u8, + /// Pubkey::Default if not being used. + pub destination: Pubkey, } diff --git a/tests/lazy-distributor.ts b/tests/lazy-distributor.ts index 0dc5cd61f..b00b2c091 100644 --- a/tests/lazy-distributor.ts +++ b/tests/lazy-distributor.ts @@ -2,20 +2,26 @@ import * as anchor from "@coral-xyz/anchor"; import { Program } from "@coral-xyz/anchor"; import { ThresholdType } from "@helium/circuit-breaker-sdk"; import { - Asset, createAtaAndMint, createMint, createNft, sendInstructions + Asset, + createAtaAndMint, + createMint, + createNft, + sendInstructions, } from "@helium/spl-utils"; +import { getAssociatedTokenAddressSync } from "@solana/spl-token"; import { Keypair, PublicKey } from "@solana/web3.js"; import { assert, expect } from "chai"; import { distributeCompressionRewards, init, - initializeCompressionRecipient + initializeCompressionRecipient, + updateCompressionDestination, } from "../packages/lazy-distributor-sdk/src"; import { PROGRAM_ID } from "../packages/lazy-distributor-sdk/src/constants"; import { LazyDistributor } from "../target/types/lazy_distributor"; import { createCompressionNft } from "./utils/compression"; import { ensureLDIdl } from "./utils/fixtures"; -import { MerkleTree } from "@solana/spl-account-compression"; +import { MerkleTree, MerkleTreeProof } from "@solana/spl-account-compression"; describe("lazy-distributor", () => { // Configure the client to use the local cluster. @@ -53,7 +59,7 @@ describe("lazy-distributor", () => { thresholdType: ThresholdType.Absolute as never, threshold: new anchor.BN(1000000000), }, - approver: null + approver: null, }) .accounts({ rewardsMint, @@ -86,11 +92,12 @@ describe("lazy-distributor", () => { const { mintKey } = await createNft(provider, me); mint = mintKey; - ({ asset, merkleTree, creatorHash, dataHash } = await createCompressionNft({ - provider, - recipient: me, - merkle, - })); + ({ asset, merkleTree, creatorHash, dataHash } = + await createCompressionNft({ + provider, + recipient: me, + merkle, + })); const method = await program.methods .initializeLazyDistributorV0({ @@ -154,7 +161,7 @@ describe("lazy-distributor", () => { compression: { leafId: 0, dataHash, - creatorHash + creatorHash, }, } as Asset; }, @@ -248,7 +255,11 @@ describe("lazy-distributor", () => { .rpc({ skipPreflight: true }); const proof = merkleTree.getProof(0); - const getAssetFn = async () => ({ ownership: { owner: me }, compression: { leafId: 0, creatorHash, dataHash } } as Asset); + const getAssetFn = async () => + ({ + ownership: { owner: me }, + compression: { leafId: 0, creatorHash, dataHash }, + } as Asset); const getAssetProofFn = async () => { return { root: new PublicKey(proof.root), @@ -300,6 +311,100 @@ describe("lazy-distributor", () => { ); expect(balance2.value.uiAmount).to.eq(5); }); + + describe("with custom destination", () => { + const destinationWallet = Keypair.generate(); + let proof: MerkleTreeProof; + let getAssetFn: any; + let getAssetProofFn: any; + beforeEach(async () => { + proof = merkleTree.getProof(0); + getAssetFn = async () => + ({ + ownership: { owner: me }, + compression: { leafId: 0, creatorHash, dataHash }, + } as Asset); + getAssetProofFn = async () => { + return { + root: new PublicKey(proof.root), + proof: proof.proof.map((p) => new PublicKey(p)), + nodeIndex: 0, + leaf: new PublicKey(proof.leaf), + treeId: merkle.publicKey, + }; + }; + ( + await updateCompressionDestination({ + program, + assetId: asset, + lazyDistributor, + destination: destinationWallet.publicKey, + getAssetFn, + getAssetProofFn, + }) + ).rpc({ skipPreflight: true }); + }); + + it("allows distributing current rewards", async () => { + await program.methods + .setCurrentRewardsV0({ + currentRewards: new anchor.BN("5000000"), + oracleIndex: 0, + }) + .accounts({ + lazyDistributor, + recipient, + }) + .rpc({ skipPreflight: true }); + + const method = await program.methods + .distributeCustomDestinationV0() + .accounts({ + common: { + recipient, + lazyDistributor, + rewardsMint, + owner: destinationWallet.publicKey, + } + }); + + await method.rpc({ skipPreflight: true }); + const destination = getAssociatedTokenAddressSync( + rewardsMint, + destinationWallet.publicKey + ); + + const balance = await provider.connection.getTokenAccountBalance( + destination + ); + expect(balance.value.uiAmount).to.eq(5); + + // ensure dist same amount does nothing + await program.methods + .setCurrentRewardsV0({ + currentRewards: new anchor.BN("5000000"), + oracleIndex: 0, + }) + .accounts({ + lazyDistributor, + recipient, + }) + .rpc({ skipPreflight: true }); + await ( + await distributeCompressionRewards({ + program, + assetId: asset, + lazyDistributor, + getAssetFn, + getAssetProofFn, + }) + ).rpc({ skipPreflight: true }); + const balance2 = await provider.connection.getTokenAccountBalance( + destination + ); + expect(balance2.value.uiAmount).to.eq(5); + }); + }); }); describe("with recipient", () => { @@ -378,28 +483,98 @@ describe("lazy-distributor", () => { ); expect(balance2.value.uiAmount).to.eq(5); }); + + describe("with custom destination", () => { + const destinationWallet = Keypair.generate(); + beforeEach(async () => { + await program.methods + .updateDestinationV0() + .accounts({ + recipient, + destination: destinationWallet.publicKey, + }) + .rpc({ skipPreflight: true }); + }); + + it("allows distributing current rewards", async () => { + await program.methods + .setCurrentRewardsV0({ + currentRewards: new anchor.BN("5000000"), + oracleIndex: 0, + }) + .accounts({ + lazyDistributor, + recipient, + }) + .rpc({ skipPreflight: true }); + const method = await program.methods.distributeCustomDestinationV0().accounts({ + common: { + recipient, + lazyDistributor, + rewardsMint, + owner: destinationWallet.publicKey, + }, + }); + await method.rpc({ skipPreflight: true }); + // @ts-ignore + const destination = getAssociatedTokenAddressSync( + rewardsMint, + destinationWallet.publicKey + ); + + const balance = await provider.connection.getTokenAccountBalance( + destination + ); + expect(balance.value.uiAmount).to.eq(5); + + // ensure dist same amount does nothing + await program.methods + .setCurrentRewardsV0({ + currentRewards: new anchor.BN("5000000"), + oracleIndex: 0, + }) + .accounts({ + lazyDistributor, + recipient, + }) + .rpc({ skipPreflight: true }); + await program.methods + .distributeRewardsV0() + .accounts({ + common: { recipient, lazyDistributor, rewardsMint }, + }) + .rpc({ skipPreflight: true }); + const balance2 = await provider.connection.getTokenAccountBalance( + destination + ); + expect(balance2.value.uiAmount).to.eq(5); + }); + }); }); - it("updates lazy distributor", async() => { - await program.methods.updateLazyDistributorV0({ - authority: PublicKey.default, - oracles: [ - { - oracle: PublicKey.default, - url: "https://some-other-url", - } - ], - approver: null, - }).accounts({ - rewardsMint - }).rpc() + it("updates lazy distributor", async () => { + await program.methods + .updateLazyDistributorV0({ + authority: PublicKey.default, + oracles: [ + { + oracle: PublicKey.default, + url: "https://some-other-url", + }, + ], + approver: null, + }) + .accounts({ + rewardsMint, + }) + .rpc(); const ld = await program.account.lazyDistributorV0.fetch(lazyDistributor); assert.isTrue(PublicKey.default.equals(ld.authority)); assert.isTrue(ld.oracles.length == 1); assert.equal(ld.oracles[0].url, "https://some-other-url"); assert.isTrue(PublicKey.default.equals(ld.oracles[0].oracle)); - }) + }); }); describe("multiple oracles", () => {