1use anchor_lang::prelude::*;
2use anchor_lang::solana_program::{
3 entrypoint::ProgramResult,
4 program::{invoke, invoke_signed},
5 system_instruction,
6};
7use bs58::encode;
8
9declare_id!("PoWSNH2hEZogtCg1Zgm51FnkmJperzYDgPK4fvs8taL");
10
11pub fn create_account<'a, 'info>(
12 payer: &'a AccountInfo<'info>,
13 new_account: &'a AccountInfo<'info>,
14 system_program: &'a AccountInfo<'info>,
15 program_owner: &Pubkey,
16 rent: &Rent,
17 space: u64,
18 seeds: Vec<Vec<u8>>,
19) -> ProgramResult {
20 let current_lamports = **new_account.try_borrow_lamports()?;
21 if current_lamports == 0 {
22 invoke_signed(
24 &system_instruction::create_account(
25 payer.key,
26 new_account.key,
27 rent.minimum_balance(space as usize),
28 space,
29 program_owner,
30 ),
31 &[payer.clone(), new_account.clone(), system_program.clone()],
32 &[seeds
33 .iter()
34 .map(|seed| seed.as_slice())
35 .collect::<Vec<&[u8]>>()
36 .as_slice()],
37 )
38 } else {
39 let required_lamports = rent
41 .minimum_balance(space as usize)
42 .max(1)
43 .saturating_sub(current_lamports);
44 if required_lamports > 0 {
45 invoke(
46 &system_instruction::transfer(payer.key, new_account.key, required_lamports),
47 &[payer.clone(), new_account.clone(), system_program.clone()],
48 )?;
49 }
50 invoke_signed(
52 &system_instruction::allocate(new_account.key, space),
53 &[new_account.clone(), system_program.clone()],
54 &[seeds
55 .iter()
56 .map(|seed| seed.as_slice())
57 .collect::<Vec<&[u8]>>()
58 .as_slice()],
59 )?;
60 invoke_signed(
62 &system_instruction::assign(new_account.key, program_owner),
63 &[new_account.clone(), system_program.clone()],
64 &[seeds
65 .iter()
66 .map(|seed| seed.as_slice())
67 .collect::<Vec<&[u8]>>()
68 .as_slice()],
69 )
70 }
71}
72
73#[program]
74pub mod proof_of_work_faucet {
75 use super::*;
76
77 pub fn create(ctx: Context<Create>, difficulty: u8, amount: u64) -> Result<()> {
78 ctx.accounts.spec.difficulty = difficulty;
79 ctx.accounts.spec.amount = amount;
80 Ok(())
81 }
82
83 pub fn airdrop(ctx: Context<Airdrop>) -> Result<()> {
84 let Airdrop {
85 payer,
86 signer,
87 receipt,
88 spec,
89 source,
90 system_program,
91 } = ctx.accounts;
92
93 let prefix_len = encode(signer.key().as_ref())
95 .into_string()
96 .chars()
97 .take_while(|ch| ch == &'A')
98 .count();
99
100 if prefix_len < spec.difficulty as usize {
101 msg!(
102 "Public key does not meet difficulty requirement of {}: {}",
103 spec.difficulty,
104 signer.key()
105 );
106 return Err(ProgramError::MissingRequiredSignature.into());
107 }
108
109 msg!("Source wallet balance: {}", source.lamports());
110 msg!(
111 "Airdropping {} lamports to {}",
112 spec.amount.min(source.lamports()),
113 payer.key()
114 );
115
116 invoke_signed(
117 &system_instruction::transfer(
118 &source.key(),
119 &payer.key(),
120 spec.amount.min(source.lamports()),
121 ),
122 &[
123 system_program.to_account_info(),
124 payer.to_account_info(),
125 source.to_account_info(),
126 ],
127 &[&[b"source", spec.key().as_ref(), &[ctx.bumps["source"]]]],
128 )?;
129
130 create_account(
132 &payer,
133 &receipt,
134 system_program,
135 ctx.program_id,
136 &Rent::get()?,
137 0,
138 vec![
139 b"receipt".to_vec(),
140 signer.key().to_bytes().to_vec(),
141 spec.difficulty.to_le_bytes().to_vec(),
142 vec![ctx.bumps["receipt"]],
143 ],
144 )?;
145 Ok(())
146 }
147}
148
149#[account]
150pub struct Difficulty {
151 pub difficulty: u8,
152 pub amount: u64,
153}
154
155#[derive(Accounts)]
156#[instruction(difficulty: u8, amount: u64)]
157pub struct Create<'info> {
158 #[account(mut)]
159 pub payer: Signer<'info>,
160 #[account(
161 init,
162 seeds=[b"spec", difficulty.to_le_bytes().as_ref(), amount.to_le_bytes().as_ref()],
163 bump,
164 space=8 + 1 + 8,
165 payer=payer,
166 )]
167 pub spec: Account<'info, Difficulty>,
168 pub system_program: Program<'info, System>,
169}
170
171#[derive(Accounts)]
172pub struct Airdrop<'info> {
173 #[account(mut)]
174 pub payer: Signer<'info>,
175 pub signer: Signer<'info>,
176 #[account(
178 mut,
179 seeds=[b"receipt", signer.key().as_ref(), spec.difficulty.to_le_bytes().as_ref()],
180 bump,
181 )]
182 pub receipt: UncheckedAccount<'info>,
183 #[account(
184 seeds=[b"spec", spec.difficulty.to_le_bytes().as_ref(), spec.amount.to_le_bytes().as_ref()],
185 bump,
186 )]
187 pub spec: Account<'info, Difficulty>,
188 #[account(mut, seeds=[b"source", spec.key().as_ref()], bump)]
190 pub source: UncheckedAccount<'info>,
191 pub system_program: Program<'info, System>,
192}