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

Call SendRewards from maintainer #481

Merged
merged 16 commits into from
Dec 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
31 changes: 19 additions & 12 deletions anker/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,43 +4,50 @@ use num_derive::FromPrimitive;
use solana_program::{decode_error::DecodeError, program_error::ProgramError};

/// Errors that may be returned by the Anker program.
///
/// Note: the integer representations of these errors start counting at 4000,
/// to avoid them overlapping with the Solido errors. When a Solana program fails,
/// all we get is the error code, and if we use the same "namespace" of small
/// integers, we can't tell from the error alone which program it was that failed.
/// This matters in the CLI client where we print all possible interpretations of
/// the error code.
#[derive(Clone, Debug, Eq, FromPrimitive, PartialEq)]
pub enum AnkerError {
/// We failed to deserialize an SPL token account.
InvalidTokenAccount = 0,
InvalidTokenAccount = 4000,

/// We expected the SPL token account to be owned by the SPL token program.
InvalidTokenAccountOwner = 1,
InvalidTokenAccountOwner = 4001,

/// The mint of a provided SPL token account does not match the expected mint.
InvalidTokenMint = 2,
InvalidTokenMint = 4002,

/// The provided reserve is invalid.
InvalidReserveAccount = 3,
InvalidReserveAccount = 4003,

/// The provided Solido state is different from the stored one.
InvalidSolidoInstance = 4,
InvalidSolidoInstance = 4004,

/// The one of the provided accounts does not match the expected derived address.
InvalidDerivedAccount = 5,
InvalidDerivedAccount = 4005,

/// An account is not owned by the expected owner.
InvalidOwner = 6,
InvalidOwner = 4006,

/// Wrong SPL Token Swap instance.
WrongSplTokenSwap = 7,
WrongSplTokenSwap = 4007,

/// Wrong parameters for the SPL Token Swap instruction.
WrongSplTokenSwapParameters = 8,
WrongSplTokenSwapParameters = 4008,

/// The provided rewards destination is different from what is stored in the instance.
InvalidRewardsDestination = 9,
InvalidRewardsDestination = 4009,

/// The amount of rewards to be claimed are zero.
ZeroRewardsToClaim = 10,
ZeroRewardsToClaim = 4010,

/// Arguments/Accounts for SendRewards are wrong.
InvalidSendRewardsParameters = 11,
InvalidSendRewardsParameters = 4011,
}

// Just reuse the generated Debug impl for Display. It shows the variant names.
Expand Down
29 changes: 21 additions & 8 deletions anker/src/instruction.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// TODO(#449): Remove this once Anker functions are all complete.
#![allow(dead_code)]
// SPDX-FileCopyrightText: 2021 Chorus One AG
// SPDX-License-Identifier: GPL-3.0

use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use lido::{accounts_struct, accounts_struct_meta, error::LidoError, token::StLamports};
Expand All @@ -17,6 +17,7 @@ use crate::{token::BLamports, wormhole::TerraAddress};
#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)]
pub enum AnkerInstruction {
Initialize {
#[allow(dead_code)] // It is not dead code when compiled for BPF.
terra_rewards_destination: TerraAddress,
},

Expand All @@ -39,13 +40,17 @@ pub enum AnkerInstruction {

/// Sell rewards to the UST reserve.
SellRewards,

/// Transfer from the UST reserve to terra through Wormhole.
SendRewards {
wormhole_nonce: u32, // Random number used to differentiate similar transaction.
/// Random number used to differentiate similar transactions.
#[allow(dead_code)] // It is not dead code when compiled for BPF.
wormhole_nonce: u32,
},
/// Change the Anker's rewards destination address on Terra:
/// `terra_rewards_destination`.
ChangeTerraRewardsDestination {
#[allow(dead_code)] // It is not dead code when compiled for BPF.
terra_rewards_destination: TerraAddress,
},
/// Change the token pool instance.
Expand Down Expand Up @@ -404,6 +409,8 @@ pub fn change_token_swap_pool(
}

accounts_struct! {
// For the Wormhole accounts, see also
// https://github.com/certusone/wormhole/blob/537d56b37aa041a585f2c90515fa3a7ffa5898b5/solana/modules/token_bridge/program/src/instructions.rs#L328-L390.
SendRewardsAccountsMeta, SendRewardsAccountsInfo {
pub anker {
is_signer: false,
Expand Down Expand Up @@ -442,22 +449,25 @@ accounts_struct! {
is_signer: false,
is_writable: true,
},
pub custody_key {
// Program-derived address derived from the mint address.
pub wrapped_meta_key {
is_signer: false,
is_writable: true,
},
// Wormhole program-derived account that will sign the SPL
// token transfer out of the source account. This means we will need
// to call spl_token::approve before we can send.
pub authority_signer_key {
is_signer: false,
is_writable: false,
},
pub custody_signer_key {
is_signer: false,
is_writable: false,
},
pub bridge_config {
is_signer: false,
is_writable: true,
},
// The message account needs to be a new, uninitialized account, and then
// calling Wormhole will initialize it. (This is why it needs to be a
// signer.)
pub message {
is_signer: true,
is_writable: true,
Expand All @@ -470,6 +480,9 @@ accounts_struct! {
is_signer: false,
is_writable: true,
},
// To make a Wormhole transfer, we need to pay a transaction fee (on top
// of the Solana transaction fee). The Wormhole program enforces this by
// transferring some SOL from the payer account to the fee collector.
pub fee_collector_key {
is_signer: false,
is_writable: true,
Expand Down
5 changes: 5 additions & 0 deletions anker/src/logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,11 @@ pub fn initialize_spl_account<'a, 'b>(
mint: &'a AccountInfo<'b>,
) -> ProgramResult {
// Initialize the reserve account.
msg!(
"Initializing SPL token account for mint {} at {} ...",
mint.key,
account.key
);
invoke_signed(
&spl_token::instruction::initialize_account(
&spl_token::id(),
Expand Down
121 changes: 85 additions & 36 deletions anker/src/processor.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2021 Chorus One AG
// SPDX-License-Identifier: GPL-3.0

use borsh::BorshDeserialize;
use lido::token::Lamports;
use solana_program::{
Expand Down Expand Up @@ -86,6 +89,7 @@ fn process_initialize(
ANKER_STSOL_RESERVE_ACCOUNT,
&[st_sol_reserve_account_bump_seed],
];
msg!("Allocating account for stSOL reserve ...");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to put some debug msgs! 👍

create_account(
&spl_token::ID,
&accounts,
Expand All @@ -94,6 +98,7 @@ fn process_initialize(
spl_token::state::Account::LEN,
&st_sol_reserve_account_seeds,
)?;
msg!("Initializing SPL token account for stSOL reserve ...");
initialize_spl_account(
&accounts,
&st_sol_reserve_account_seeds,
Expand All @@ -107,6 +112,7 @@ fn process_initialize(
ANKER_UST_RESERVE_ACCOUNT,
&[ust_reserve_account_bump_seed],
];
msg!("Allocating account for UST reserve ...");
create_account(
&spl_token::ID,
&accounts,
Expand All @@ -115,6 +121,7 @@ fn process_initialize(
spl_token::state::Account::LEN,
&ust_reserve_account_seeds,
)?;
msg!("Initializing SPL token account for UST reserve ...");
initialize_spl_account(
&accounts,
&ust_reserve_account_seeds,
Expand Down Expand Up @@ -223,6 +230,7 @@ fn process_deposit(
}

/// Sell Anker rewards.
#[inline(never)]
fn process_sell_rewards(program_id: &Pubkey, accounts_raw: &[AccountInfo]) -> ProgramResult {
let accounts = SellRewardsAccountsInfo::try_from_slice(accounts_raw)?;
let (solido, mut anker) = deserialize_anker(program_id, accounts.anker, accounts.solido)?;
Expand Down Expand Up @@ -392,63 +400,104 @@ fn process_change_token_swap_pool(
}

/// Send rewards via Wormhole from the UST reserve address to Terra.
#[inline(never)]
fn process_send_rewards(
program_id: &Pubkey,
accounts_raw: &[AccountInfo],
wormhole_nonce: u32,
) -> ProgramResult {
let accounts = SendRewardsAccountsInfo::try_from_slice(accounts_raw)?;
let (_solido, anker) = deserialize_anker(program_id, accounts.anker, accounts.solido)?;
let anker = deserialize_anker(program_id, accounts.anker, accounts.solido)?.1;
anker.check_ust_reserve_address(
program_id,
accounts.anker.key,
accounts.ust_reserve_account,
)?;
let wormhole_transfer_args = anker.check_send_rewards(&accounts)?;
let ust_reserve_state =
spl_token::state::Account::unpack_from_slice(&accounts.ust_reserve_account.data.borrow())?;
// Check UST mint.
if &ust_reserve_state.mint != accounts.ust_mint.key {
return Err(AnkerError::InvalidTokenMint.into());

// We put the temporaries in a scope here to make sure they are popped from
// the stack before we continue the function, because this function is scarce
// on stack space.
let reserve_ust_amount = {
let ust_reserve_state = spl_token::state::Account::unpack_from_slice(
&accounts.ust_reserve_account.data.borrow(),
)?;

// Check UST mint.
if &ust_reserve_state.mint != accounts.ust_mint.key {
return Err(AnkerError::InvalidTokenMint.into());
}
MicroUst(ust_reserve_state.amount)
};

let reserve_seeds = [
accounts.anker.key.as_ref(),
ANKER_RESERVE_AUTHORITY,
&[anker.reserve_authority_bump_seed],
];

// Stack space is scarce in this function, so we put as many things as we can
// in a scope to make sure the stack space of the temporaries is reclaimed.
{
// Wormhole signs the SPL token transfer with its "authority signer key",
// which means we need to authorize that key to modify our UST reserve.
let instr = Box::new(spl_token::instruction::approve(
accounts.spl_token.key,
accounts.ust_reserve_account.key,
accounts.authority_signer_key.key,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: do we check this authority_signer?

Copy link
Contributor Author

@ruuda ruuda Dec 24, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not you, but we do check it now since c5f3d30 (rebased as 5ff4988) :-)

accounts.reserve_authority.key,
// The next argument is "signers", which is only relevant for this SPL
// token multisig feature, which we do not use.
&[],
reserve_ust_amount.0,
)?);

invoke_signed(
&instr,
// This vec is not useless, we want the data to go on the heap, not on the stack!
#[allow(clippy::useless_vec)]
&vec![
accounts.ust_reserve_account.clone(),
accounts.authority_signer_key.clone(),
accounts.reserve_authority.clone(),
],
&[&reserve_seeds[..]],
)?;
}

let reserve_ust_amount = MicroUst(ust_reserve_state.amount);
let payload = crate::wormhole::Payload::new(
wormhole_nonce,
reserve_ust_amount,
anker.terra_rewards_destination.to_foreign(),
);

// Send UST tokens via Wormhole 🤞.
let reserve_seeds = [
accounts.anker.key.as_ref(),
ANKER_RESERVE_AUTHORITY,
&[anker.reserve_authority_bump_seed],
// For the order and meaning of the accounts, see also
// https://github.com/certusone/wormhole/blob/537d56b37aa041a585f2c90515fa3a7ffa5898b5/solana/modules/token_bridge/program/src/instructions.rs#L328-L390.
let instr = Box::new(get_wormhole_transfer_instruction(
&payload,
&wormhole_transfer_args,
));
let accounts = vec![
accounts.payer.clone(),
accounts.config_key.clone(),
accounts.ust_reserve_account.clone(),
accounts.reserve_authority.clone(),
accounts.ust_mint.clone(),
accounts.wrapped_meta_key.clone(),
accounts.authority_signer_key.clone(),
accounts.bridge_config.clone(),
accounts.message.clone(),
accounts.emitter_key.clone(),
accounts.sequence_key.clone(),
accounts.fee_collector_key.clone(),
accounts.sysvar_clock.clone(),
accounts.sysvar_rent.clone(),
accounts.system_program.clone(),
accounts.wormhole_core_bridge_program_id.clone(),
accounts.spl_token.clone(),
];

invoke_signed(
&get_wormhole_transfer_instruction(&payload, &wormhole_transfer_args),
&vec![
accounts.payer.clone(),
accounts.config_key.clone(),
accounts.ust_reserve_account.clone(),
accounts.ust_mint.clone(),
accounts.custody_key.clone(),
accounts.authority_signer_key.clone(),
accounts.custody_signer_key.clone(),
accounts.bridge_config.clone(),
accounts.message.clone(),
accounts.emitter_key.clone(),
accounts.sequence_key.clone(),
accounts.fee_collector_key.clone(),
accounts.sysvar_clock.clone(),
accounts.sysvar_rent.clone(),
accounts.system_program.clone(),
accounts.wormhole_core_bridge_program_id.clone(),
accounts.spl_token.clone(),
],
&[&reserve_seeds[..]],
)
// Send UST tokens via Wormhole 🤞.
invoke_signed(&instr, &accounts[..], &[&reserve_seeds[..]])
}

/// Processes [Instruction](enum.Instruction.html).
Expand Down
15 changes: 7 additions & 8 deletions anker/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: 2021 Chorus One AG
// SPDX-License-Identifier: GPL-3.0

use crate::instruction::{SellRewardsAccountsInfo, SendRewardsAccountsInfo};
use crate::metrics::Metrics;
use crate::wormhole::{check_wormhole_account, TerraAddress, WormholeTransferArgs};
Expand Down Expand Up @@ -523,6 +526,7 @@ impl Anker {
*accounts.ust_mint.key,
*accounts.payer.key,
*accounts.ust_reserve_account.key,
*accounts.reserve_authority.key,
*accounts.message.key,
);

Expand All @@ -532,20 +536,15 @@ impl Anker {
accounts.config_key.key,
)?;
check_wormhole_account(
"custody key",
&wormhole_transfer_args.custody_key,
accounts.custody_key.key,
"wrapped meta key",
&wormhole_transfer_args.wrapped_meta_key,
accounts.wrapped_meta_key.key,
)?;
check_wormhole_account(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we check this?!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good catch! Fixed. I think what happened here is that I removed the custody key and custody signer key, because they are only used for native tokens, not wrapped tokens like UST, but I was a bit too eager and also removed this check in between that I shouldn’t have removed.

"authority signer key",
&wormhole_transfer_args.authority_signer_key,
accounts.authority_signer_key.key,
)?;
check_wormhole_account(
"custody signer key",
&wormhole_transfer_args.custody_signer_key,
accounts.custody_signer_key.key,
)?;
check_wormhole_account(
"bridge config",
&wormhole_transfer_args.bridge_config,
Expand Down
Loading