Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(#629): Add configurable destination for lazy distributor #630

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/distributor-oracle/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ export class OracleServer {
(decoded.name !== "setCurrentRewardsV0" &&
decoded.name !== "distributeRewardsV0" &&
decoded.name !== "distributeCompressionRewardsV0" &&
decoded.name !== "distributeCustomDestinationV0" &&
decoded.name !== "initializeRecipientV0" &&
decoded.name !== "initializeCompressionRecipientV0" &&
decoded.name !== "setCurrentRewardsWrapperV0" &&
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IDL extends Idl>({
program,
assetId,
lazyDistributor,
rewardsMint,
payer,
destination,
...rest
}: {
program: Program<LazyDistributor>;
assetId: PublicKey;
rewardsMint?: PublicKey;
assetEndpoint?: string;
lazyDistributor: PublicKey;
owner?: PublicKey;
payer?: PublicKey;
destination: PublicKey | null;
getAssetFn?: (url: string, assetId: PublicKey) => Promise<Asset | undefined>;
getAssetProofFn?: (
url: string,
assetId: PublicKey
) => Promise<AssetProof | undefined>;
}) {
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);
}
1 change: 1 addition & 0 deletions packages/lazy-distributor-sdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
40 changes: 40 additions & 0 deletions packages/lazy-distributor-sdk/src/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down Expand Up @@ -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;
Expand Down
12 changes: 12 additions & 0 deletions packages/sus/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
3 changes: 3 additions & 0 deletions programs/lazy-distributor/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Account<'info, RecipientV0>>,
pub rewards_mint: Box<Account<'info, Mint>>,
Expand All @@ -37,6 +37,7 @@ pub struct DistributeRewardsCommonV0<'info> {
pub circuit_breaker: Box<Account<'info, AccountWindowedCircuitBreakerV0>>,
/// TODO: Should this be permissioned? Should the owner have to sign to receive rewards?
/// CHECK: Just required for ATA
#[account(mut)]
pub owner: AccountInfo<'info>,
#[account(
init_if_needed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand All @@ -27,6 +27,12 @@ pub fn handler<'info>(
ctx: Context<'_, '_, '_, 'info, DistributeCompressionRewardsV0<'info>>,
args: DistributeCompressionRewardsArgsV0,
) -> Result<()> {
require_eq!(
ctx.accounts.common.recipient.destination,
Pubkey::default(),
ErrorCode::CustomDestination
);

verify_compressed_nft(VerifyCompressedNftArgs {
data_hash: args.data_hash,
creator_hash: args.creator_hash,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<DistributeCustomDestinationV0>) -> Result<()> {
require_eq!(
ctx.accounts.common.owner.key(),
ctx.accounts.common.recipient.destination
);

distribute_impl(&mut ctx.accounts.common)
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::common::*;
use crate::error::ErrorCode;
use anchor_lang::prelude::*;
use anchor_spl::token::TokenAccount;

Expand All @@ -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<Account<'info, TokenAccount>>,
}

pub fn handler<'info>(ctx: Context<'_, '_, '_, 'info, DistributeRewardsV0<'info>>) -> Result<()> {
require_eq!(
ctx.accounts.common.recipient.destination,
Pubkey::default(),
ErrorCode::CustomDestination
);
distribute_impl(&mut ctx.accounts.common)
}
3 changes: 3 additions & 0 deletions programs/lazy-distributor/src/instructions/distribute/mod.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ pub fn handler(ctx: Context<InitializeRecipientV0>) -> 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(())
Expand Down
2 changes: 2 additions & 0 deletions programs/lazy-distributor/src/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ 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::*;
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::*;
Original file line number Diff line number Diff line change
@@ -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::*;
Original file line number Diff line number Diff line change
@@ -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<Account<'info, RecipientV0>>,
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(())
}
Original file line number Diff line number Diff line change
@@ -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<Account<'info, RecipientV0>>,
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<Account<'info, TokenAccount>>,
}

pub fn handler<'info>(ctx: Context<UpdateDestinationV0>) -> Result<()> {
ctx.accounts.recipient.destination = ctx.accounts.destination.key();

Ok(())
}
17 changes: 17 additions & 0 deletions programs/lazy-distributor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<UpdateDestinationV0>) -> 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)
}
}
2 changes: 2 additions & 0 deletions programs/lazy-distributor/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,6 @@ pub struct RecipientV0 {
pub current_config_version: u16,
pub current_rewards: Vec<Option<u64>>, // One for each oracle, matching indexes in` LazyDistrubutorV0`
pub bump_seed: u8,
/// Pubkey::Default if not being used.
pub destination: Pubkey,
}
Loading
Loading