diff --git a/Cargo.lock b/Cargo.lock index c643f19..26ca50e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,14 @@ dependencies = [ "anchor-spl", ] +[[package]] +name = "adapter-genopets-staking" +version = "0.1.0" +dependencies = [ + "anchor-lang", + "anchor-spl", +] + [[package]] name = "adapter-katana" version = "0.1.0" diff --git a/package.json b/package.json index ce4c4d1..a441fba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dappio-wonderland/adapter-idls", - "version": "0.2.1", + "version": "0.2.1-test.2", "description": "Dappio Adapter IDLs: The IDL files of Dappio Adapters", "main": "dist/src/index.js", "types": "dist/src/index.d.ts", diff --git a/programs/adapter-genopets-staking/Cargo.toml b/programs/adapter-genopets-staking/Cargo.toml new file mode 100644 index 0000000..fa03d4f --- /dev/null +++ b/programs/adapter-genopets-staking/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "adapter-genopets-staking" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "adapter_genopets_staking" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] + +[profile.release] +overflow-checks = true + +[dependencies] +anchor-lang = {version = "0.24.2", features = [ "init-if-needed" ] } +anchor-spl = "0.24.2" diff --git a/programs/adapter-genopets-staking/Xargo.toml b/programs/adapter-genopets-staking/Xargo.toml new file mode 100644 index 0000000..475fb71 --- /dev/null +++ b/programs/adapter-genopets-staking/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/programs/adapter-genopets-staking/src/lib.rs b/programs/adapter-genopets-staking/src/lib.rs new file mode 100644 index 0000000..84b24fe --- /dev/null +++ b/programs/adapter-genopets-staking/src/lib.rs @@ -0,0 +1,311 @@ +use anchor_lang::prelude::*; +use anchor_lang::solana_program::{ + hash::hash, + instruction::{AccountMeta, Instruction}, + program::invoke, + pubkey::Pubkey, +}; +use anchor_spl::token::TokenAccount; +declare_id!("ADPTR3wPKDCZ8HNBBpY3GGXB8hu6DZDqyPJMimyHjKNk"); + +#[program] +pub mod adapter_genopets_staking { + use super::*; + + pub fn stake<'a, 'b, 'c, 'info>( + ctx: Context<'a, 'b, 'c, 'info, Action<'info>>, + input: Vec, + ) -> Result<()> { + // Get Input + let mut input_bytes = &input[..]; + let input_struct = StakeInputWrapper::deserialize(&mut input_bytes)?; + + let mut stake_data = sighash("global", "stake").to_vec(); + stake_data.append(&mut input_struct.amount.to_le_bytes().to_vec()); + stake_data.append(&mut input_struct.lock_for_months.to_le_bytes().to_vec()); + stake_data.push(0); // default False cuz it's deprecated + + let stake_accounts = load_remaining_accounts( + ctx.remaining_accounts, + vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], + ); + + let mut stake_token_account_and_balance = + load_token_account_and_balance(ctx.remaining_accounts, 5); + + let stake_ix = Instruction { + program_id: ctx.accounts.base_program_id.key(), + accounts: stake_accounts, + data: stake_data, + }; + invoke(&stake_ix, ctx.remaining_accounts)?; + + // Wrap Output + let output_struct = StakeOutputWrapper { + token_in_amount: stake_token_account_and_balance.get_balance_change(), + ..Default::default() + }; + let mut output: Vec = Vec::new(); + output_struct.serialize(&mut output).unwrap(); + + // Return Result + anchor_lang::solana_program::program::set_return_data(&output); + + msg!("Output: {:?}", output_struct); + Ok(()) + } + pub fn unstake<'a, 'b, 'c, 'info>( + ctx: Context<'a, 'b, 'c, 'info, Action<'info>>, + input: Vec, + ) -> Result<()> { + // Get Input + let mut input_bytes = &input[..]; + let input_struct = UnstakeInputWrapper::deserialize(&mut input_bytes)?; + + let mut unstake_data = sighash("global", "withdraw").to_vec(); // Instruction data + unstake_data.push(0); // default False cuz it's deprecated + let unstake_accout_index_array: Vec = + vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]; // Remaining accounts + + let unstake_ix_accounts = + load_remaining_accounts(ctx.remaining_accounts, unstake_accout_index_array); + + let mut unstake_token_account_and_balance = + load_token_account_and_balance(ctx.remaining_accounts, 5); + + let unstake_ix = Instruction { + program_id: ctx.accounts.base_program_id.key(), + accounts: unstake_ix_accounts, + data: unstake_data, + }; + invoke(&unstake_ix, ctx.remaining_accounts)?; + + // Wrap Output + let output_struct = UnstakeOutputWrapper { + token_out_amount: unstake_token_account_and_balance.get_balance_change(), + ..Default::default() + }; + let mut output: Vec = Vec::new(); + output_struct.serialize(&mut output).unwrap(); + + // Return Result + anchor_lang::solana_program::program::set_return_data(&output); + + msg!("Output: {:?}", output_struct); + + Ok(()) + } + pub fn harvest<'a, 'b, 'c, 'info>( + ctx: Context<'a, 'b, 'c, 'info, Action<'info>>, + input: Vec, + ) -> Result<()> { + // Get Input + let mut input_bytes = &input[..]; + let input_struct = HarvestInputWrapper::deserialize(&mut input_bytes)?; + + let mut harvest_data = vec![]; // Instruction data + let mut harvest_accout_index_array: Vec = vec![]; // Remaining accounts + let mut harvest_token_account_index: usize = 0; + match input_struct.harvest_type { + // the type index is to dispatch different type of harvest + // 0 is for initialize + 0 => { + harvest_data = sighash("global", "claim_rewards").to_vec(); + harvest_data.push(0); // default False cuz it's deprecated + harvest_accout_index_array = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + harvest_token_account_index = 5; + } + // 1 is for completeAsGene + 1 => { + harvest_data = sighash("global", "withdraw").to_vec(); + harvest_data.push(0); // default False cuz it's deprecated + harvest_accout_index_array = + vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]; + harvest_token_account_index = 5; + } + // 2 is for completeAsSGene + 2 => { + harvest_data = sighash("global", "withdraw_as_sgene").to_vec(); + harvest_accout_index_array = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + harvest_token_account_index = 4; + } + + _ => return Err(ErrorCode::UnsupportedAction.into()), + } + + let harvest_ix_accounts = + load_remaining_accounts(ctx.remaining_accounts, harvest_accout_index_array); + + let mut harvest_token_account_and_balance = + load_token_account_and_balance(ctx.remaining_accounts, harvest_token_account_index); + + let harvest_ix = Instruction { + program_id: ctx.accounts.base_program_id.key(), + accounts: harvest_ix_accounts, + data: harvest_data, + }; + invoke(&harvest_ix, ctx.remaining_accounts)?; + + // Wrap Output + let output_struct = HarvestOutputWrapper { + reward_amount: harvest_token_account_and_balance.get_balance_change(), + ..Default::default() + }; + let mut output: Vec = Vec::new(); + output_struct.serialize(&mut output).unwrap(); + + // Return Result + anchor_lang::solana_program::program::set_return_data(&output); + + msg!("Output: {:?}", output_struct); + Ok(()) + } +} +#[derive(Accounts)] +pub struct Action<'info> { + pub gateway_authority: Signer<'info>, + /// CHECK: Safe + pub base_program_id: AccountInfo<'info>, +} +#[error_code] +pub enum ErrorCode { + #[msg("Unsupported PoolDirection")] + UnsupportedPoolDirection, + #[msg("Unsupported Action")] + UnsupportedAction, +} +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Default)] +pub struct StakeInputWrapper { + pub amount: u64, + pub lock_for_months: u8, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Default)] +pub struct StakeOutputWrapper { + pub token_in_amount: u64, + pub dummy_2: u64, + pub dummy_3: u64, + pub dummy_4: u64, +} +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Default)] +pub struct UnstakeInputWrapper {} +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Default)] +pub struct UnstakeOutputWrapper { + pub token_out_amount: u64, + pub dummy_2: u64, + pub dummy_3: u64, + pub dummy_4: u64, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Default)] +pub struct HarvestInputWrapper { + pub harvest_type: u8, +} +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug, Default)] +pub struct HarvestOutputWrapper { + pub reward_amount: u64, + pub dummy_2: u64, + pub dummy_3: u64, + pub dummy_4: u64, +} + +pub type StakeOutputTuple = (u64, u64, u64, u64); +pub type UnstakeOutputTuple = (u64, u64, u64, u64); +pub type HarvestOutputTuple = (u64, u64, u64, u64); + +impl From for StakeOutputTuple { + fn from(result: StakeOutputWrapper) -> StakeOutputTuple { + let StakeOutputWrapper { + token_in_amount, + dummy_2, + dummy_3, + dummy_4, + } = result; + (token_in_amount, dummy_2, dummy_3, dummy_4) + } +} + +impl From for UnstakeOutputTuple { + fn from(result: UnstakeOutputWrapper) -> UnstakeOutputTuple { + let UnstakeOutputWrapper { + token_out_amount, + dummy_2, + dummy_3, + dummy_4, + } = result; + (token_out_amount, dummy_2, dummy_3, dummy_4) + } +} +impl From for HarvestOutputTuple { + fn from(result: HarvestOutputWrapper) -> HarvestOutputTuple { + let HarvestOutputWrapper { + reward_amount, + dummy_2, + dummy_3, + dummy_4, + } = result; + (reward_amount, dummy_2, dummy_3, dummy_4) + } +} + +pub fn sighash(namespace: &str, name: &str) -> [u8; 8] { + let preimage = format!("{}:{}", namespace, name); + let mut sighash = [0u8; 8]; + + sighash.copy_from_slice(&hash(preimage.as_bytes()).to_bytes()[..8]); + sighash +} + +pub fn load_token_account_and_balance<'info>( + remaining_accounts: &[AccountInfo<'info>], + account_index: usize, +) -> TokenAccountAndBalance<'info> { + let token_account_info = &remaining_accounts[account_index]; + let token_account = Account::::try_from(token_account_info).unwrap(); + let balance_before = token_account.amount.clone(); + return TokenAccountAndBalance { + token_accout: token_account, + balance_before: balance_before, + }; +} + +pub struct TokenAccountAndBalance<'info> { + token_accout: Account<'info, TokenAccount>, + balance_before: u64, +} + +impl<'info> TokenAccountAndBalance<'info> { + pub fn get_balance_change(&mut self) -> u64 { + self.token_accout.reload().unwrap(); + let balance_before = self.balance_before; + let balance_after = self.token_accout.amount; + if balance_after > balance_before { + balance_after.checked_sub(balance_before).unwrap() + } else if balance_after == balance_before { + 0_u64 + } else { + balance_before.checked_sub(balance_after).unwrap() + } + } +} + +pub fn load_remaining_accounts<'info>( + remaining_accounts: &[AccountInfo<'info>], + index_array: Vec, +) -> Vec { + let mut accounts: Vec = vec![]; + for index in index_array.iter() { + if remaining_accounts[*index].is_writable { + accounts.push(AccountMeta::new( + remaining_accounts[*index].key(), + remaining_accounts[*index].is_signer, + )) + } else { + accounts.push(AccountMeta::new_readonly( + remaining_accounts[*index].key(), + remaining_accounts[*index].is_signer, + )) + } + } + return accounts; +} diff --git a/target/idl/adapter_genopets_staking.json b/target/idl/adapter_genopets_staking.json new file mode 100644 index 0000000..95d0442 --- /dev/null +++ b/target/idl/adapter_genopets_staking.json @@ -0,0 +1,190 @@ +{ + "version": "0.1.0", + "name": "adapter_genopets_staking", + "instructions": [ + { + "name": "stake", + "accounts": [ + { + "name": "gatewayAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "baseProgramId", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "input", + "type": "bytes" + } + ] + }, + { + "name": "unstake", + "accounts": [ + { + "name": "gatewayAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "baseProgramId", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "input", + "type": "bytes" + } + ] + }, + { + "name": "harvest", + "accounts": [ + { + "name": "gatewayAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "baseProgramId", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "input", + "type": "bytes" + } + ] + } + ], + "types": [ + { + "name": "StakeInputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "amount", + "type": "u64" + }, + { + "name": "lockForMonths", + "type": "u8" + } + ] + } + }, + { + "name": "StakeOutputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "tokenInAmount", + "type": "u64" + }, + { + "name": "dummy2", + "type": "u64" + }, + { + "name": "dummy3", + "type": "u64" + }, + { + "name": "dummy4", + "type": "u64" + } + ] + } + }, + { + "name": "UnstakeInputWrapper", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "UnstakeOutputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "tokenOutAmount", + "type": "u64" + }, + { + "name": "dummy2", + "type": "u64" + }, + { + "name": "dummy3", + "type": "u64" + }, + { + "name": "dummy4", + "type": "u64" + } + ] + } + }, + { + "name": "HarvestInputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "harvestType", + "type": "u8" + } + ] + } + }, + { + "name": "HarvestOutputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "rewardAmount", + "type": "u64" + }, + { + "name": "dummy2", + "type": "u64" + }, + { + "name": "dummy3", + "type": "u64" + }, + { + "name": "dummy4", + "type": "u64" + } + ] + } + } + ], + "errors": [ + { + "code": 6000, + "name": "UnsupportedPoolDirection", + "msg": "Unsupported PoolDirection" + }, + { + "code": 6001, + "name": "UnsupportedAction", + "msg": "Unsupported Action" + } + ] +} \ No newline at end of file diff --git a/target/types/adapter_genopets_staking.ts b/target/types/adapter_genopets_staking.ts new file mode 100644 index 0000000..fcc1ace --- /dev/null +++ b/target/types/adapter_genopets_staking.ts @@ -0,0 +1,381 @@ +export type AdapterGenopetsStaking = { + "version": "0.1.0", + "name": "adapter_genopets_staking", + "instructions": [ + { + "name": "stake", + "accounts": [ + { + "name": "gatewayAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "baseProgramId", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "input", + "type": "bytes" + } + ] + }, + { + "name": "unstake", + "accounts": [ + { + "name": "gatewayAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "baseProgramId", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "input", + "type": "bytes" + } + ] + }, + { + "name": "harvest", + "accounts": [ + { + "name": "gatewayAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "baseProgramId", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "input", + "type": "bytes" + } + ] + } + ], + "types": [ + { + "name": "StakeInputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "amount", + "type": "u64" + }, + { + "name": "lockForMonths", + "type": "u8" + } + ] + } + }, + { + "name": "StakeOutputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "tokenInAmount", + "type": "u64" + }, + { + "name": "dummy2", + "type": "u64" + }, + { + "name": "dummy3", + "type": "u64" + }, + { + "name": "dummy4", + "type": "u64" + } + ] + } + }, + { + "name": "UnstakeInputWrapper", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "UnstakeOutputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "tokenOutAmount", + "type": "u64" + }, + { + "name": "dummy2", + "type": "u64" + }, + { + "name": "dummy3", + "type": "u64" + }, + { + "name": "dummy4", + "type": "u64" + } + ] + } + }, + { + "name": "HarvestInputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "harvestType", + "type": "u8" + } + ] + } + }, + { + "name": "HarvestOutputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "rewardAmount", + "type": "u64" + }, + { + "name": "dummy2", + "type": "u64" + }, + { + "name": "dummy3", + "type": "u64" + }, + { + "name": "dummy4", + "type": "u64" + } + ] + } + } + ], + "errors": [ + { + "code": 6000, + "name": "UnsupportedPoolDirection", + "msg": "Unsupported PoolDirection" + }, + { + "code": 6001, + "name": "UnsupportedAction", + "msg": "Unsupported Action" + } + ] +}; + +export const IDL: AdapterGenopetsStaking = { + "version": "0.1.0", + "name": "adapter_genopets_staking", + "instructions": [ + { + "name": "stake", + "accounts": [ + { + "name": "gatewayAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "baseProgramId", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "input", + "type": "bytes" + } + ] + }, + { + "name": "unstake", + "accounts": [ + { + "name": "gatewayAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "baseProgramId", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "input", + "type": "bytes" + } + ] + }, + { + "name": "harvest", + "accounts": [ + { + "name": "gatewayAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "baseProgramId", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "input", + "type": "bytes" + } + ] + } + ], + "types": [ + { + "name": "StakeInputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "amount", + "type": "u64" + }, + { + "name": "lockForMonths", + "type": "u8" + } + ] + } + }, + { + "name": "StakeOutputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "tokenInAmount", + "type": "u64" + }, + { + "name": "dummy2", + "type": "u64" + }, + { + "name": "dummy3", + "type": "u64" + }, + { + "name": "dummy4", + "type": "u64" + } + ] + } + }, + { + "name": "UnstakeInputWrapper", + "type": { + "kind": "struct", + "fields": [] + } + }, + { + "name": "UnstakeOutputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "tokenOutAmount", + "type": "u64" + }, + { + "name": "dummy2", + "type": "u64" + }, + { + "name": "dummy3", + "type": "u64" + }, + { + "name": "dummy4", + "type": "u64" + } + ] + } + }, + { + "name": "HarvestInputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "harvestType", + "type": "u8" + } + ] + } + }, + { + "name": "HarvestOutputWrapper", + "type": { + "kind": "struct", + "fields": [ + { + "name": "rewardAmount", + "type": "u64" + }, + { + "name": "dummy2", + "type": "u64" + }, + { + "name": "dummy3", + "type": "u64" + }, + { + "name": "dummy4", + "type": "u64" + } + ] + } + } + ], + "errors": [ + { + "code": 6000, + "name": "UnsupportedPoolDirection", + "msg": "Unsupported PoolDirection" + }, + { + "code": 6001, + "name": "UnsupportedAction", + "msg": "Unsupported Action" + } + ] +};