Add payer accounts to pda-creation instructions (#119)
An extra account can be passed to - create_mango_account - create_spot_open_orders which will take the lamport cost for creating the new account. If you want to compose on mango and have a PDA account as an owner, it's impossible to also use it as a payer because accounts with data cannot pay for PDA creation.
This commit is contained in:
parent
b206e3d7b3
commit
750d80b29f
|
@ -836,12 +836,13 @@ pub enum MangoInstruction {
|
|||
|
||||
/// Create a PDA mango account for a user
|
||||
///
|
||||
/// Accounts expected by this instruction (4):
|
||||
/// Accounts expected by this instruction (5):
|
||||
///
|
||||
/// 0. `[writable]` mango_group_ai - MangoGroup that this mango account is for
|
||||
/// 1. `[writable]` mango_account_ai - the mango account data
|
||||
/// 2. `[signer]` owner_ai - Solana account of owner of the mango account
|
||||
/// 3. `[]` system_prog_ai - System program
|
||||
/// 4. `[signer, writable]` payer_ai - pays for the PDA creation
|
||||
CreateMangoAccount {
|
||||
account_num: u64,
|
||||
},
|
||||
|
@ -912,7 +913,7 @@ pub enum MangoInstruction {
|
|||
|
||||
/// Create an OpenOrders PDA and initialize it with InitOpenOrders call to serum dex
|
||||
///
|
||||
/// Accounts expected by this instruction (8):
|
||||
/// Accounts expected by this instruction (9):
|
||||
///
|
||||
/// 0. `[]` mango_group_ai - MangoGroup that this mango account is for
|
||||
/// 1. `[writable]` mango_account_ai - MangoAccount
|
||||
|
@ -922,6 +923,7 @@ pub enum MangoInstruction {
|
|||
/// 5. `[]` spot_market_ai - dex MarketState account
|
||||
/// 6. `[]` signer_ai - Group Signer Account
|
||||
/// 7. `[]` system_prog_ai - System program
|
||||
/// 8. `[signer, writable]` payer_ai - pays for the PDA creation
|
||||
CreateSpotOpenOrders, // instruction 60
|
||||
}
|
||||
|
||||
|
@ -1539,13 +1541,15 @@ pub fn create_mango_account(
|
|||
mango_account_pk: &Pubkey,
|
||||
owner_pk: &Pubkey,
|
||||
system_prog_pk: &Pubkey,
|
||||
payer_pk: &Pubkey,
|
||||
account_num: u64,
|
||||
) -> Result<Instruction, ProgramError> {
|
||||
let accounts = vec![
|
||||
AccountMeta::new(*mango_group_pk, false),
|
||||
AccountMeta::new(*mango_account_pk, false),
|
||||
AccountMeta::new(*owner_pk, true),
|
||||
AccountMeta::new_readonly(*owner_pk, true),
|
||||
AccountMeta::new_readonly(*system_prog_pk, false),
|
||||
AccountMeta::new(*payer_pk, true),
|
||||
];
|
||||
|
||||
let instr = MangoInstruction::CreateMangoAccount { account_num };
|
||||
|
@ -2221,16 +2225,18 @@ pub fn create_spot_open_orders(
|
|||
open_orders_pk: &Pubkey,
|
||||
spot_market_pk: &Pubkey,
|
||||
signer_pk: &Pubkey,
|
||||
payer_pk: &Pubkey,
|
||||
) -> Result<Instruction, ProgramError> {
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(*mango_group_pk, false),
|
||||
AccountMeta::new(*mango_account_pk, false),
|
||||
AccountMeta::new(*owner_pk, true),
|
||||
AccountMeta::new_readonly(*owner_pk, true),
|
||||
AccountMeta::new_readonly(*dex_prog_pk, false),
|
||||
AccountMeta::new(*open_orders_pk, false),
|
||||
AccountMeta::new_readonly(*spot_market_pk, false),
|
||||
AccountMeta::new_readonly(*signer_pk, false),
|
||||
AccountMeta::new_readonly(solana_program::system_program::ID, false),
|
||||
AccountMeta::new(*payer_pk, true),
|
||||
];
|
||||
|
||||
let instr = MangoInstruction::CreateSpotOpenOrders;
|
||||
|
|
|
@ -736,7 +736,7 @@ impl Processor {
|
|||
event_queue_ai, // write
|
||||
bids_ai, // write
|
||||
asks_ai, // write
|
||||
mngo_mint_ai, // read
|
||||
mngo_mint_ai, // read
|
||||
mngo_vault_ai, // write
|
||||
admin_ai, // signer (write if admin has SOL and no data)
|
||||
signer_ai, // write (if admin has data and is owned by governance)
|
||||
|
@ -1420,17 +1420,22 @@ impl Processor {
|
|||
/// Call the init_open_orders instruction in serum dex and add this OpenOrders account to margin account
|
||||
fn create_spot_open_orders(program_id: &Pubkey, accounts: &[AccountInfo]) -> MangoResult {
|
||||
const NUM_FIXED: usize = 8;
|
||||
let accounts = array_ref![accounts, 0, NUM_FIXED];
|
||||
let fixed_accounts = array_ref![accounts, 0, NUM_FIXED];
|
||||
let [
|
||||
mango_group_ai, // read
|
||||
mango_account_ai, // write
|
||||
owner_ai, // read
|
||||
owner_ai, // read (write if no payer passed) & signer
|
||||
dex_prog_ai, // read
|
||||
open_orders_ai, // write
|
||||
spot_market_ai, // read
|
||||
signer_ai, // read
|
||||
system_prog_ai, // read
|
||||
] = accounts;
|
||||
] = fixed_accounts;
|
||||
let payer_ai = if accounts.len() > NUM_FIXED {
|
||||
&accounts[NUM_FIXED] // write & signer
|
||||
} else {
|
||||
owner_ai
|
||||
};
|
||||
check!(
|
||||
system_prog_ai.key == &solana_program::system_program::id(),
|
||||
MangoErrorCode::InvalidProgramId
|
||||
|
@ -1451,13 +1456,14 @@ impl Processor {
|
|||
MangoErrorCode::InvalidOwner
|
||||
)?;
|
||||
check!(owner_ai.is_signer, MangoErrorCode::InvalidSignerKey)?;
|
||||
check!(payer_ai.is_signer, MangoErrorCode::InvalidSignerKey)?;
|
||||
check!(!mango_account.is_bankrupt, MangoErrorCode::Bankrupt)?;
|
||||
|
||||
let open_orders_seeds: &[&[u8]] =
|
||||
&[&mango_account_ai.key.as_ref(), &market_index.to_le_bytes(), b"OpenOrders"];
|
||||
seed_and_create_pda(
|
||||
program_id,
|
||||
owner_ai,
|
||||
payer_ai,
|
||||
&Rent::get()?,
|
||||
size_of::<serum_dex::state::OpenOrders>() + 12,
|
||||
dex_prog_ai.key,
|
||||
|
@ -5227,18 +5233,24 @@ impl Processor {
|
|||
account_num: u64,
|
||||
) -> MangoResult {
|
||||
const NUM_FIXED: usize = 4;
|
||||
let accounts = array_ref![accounts, 0, NUM_FIXED];
|
||||
let fixed_accounts = array_ref![accounts, 0, NUM_FIXED];
|
||||
let [
|
||||
mango_group_ai, // write
|
||||
mango_account_ai, // write
|
||||
owner_ai, // write & signer
|
||||
owner_ai, // read (write if no payer passed) & signer
|
||||
system_prog_ai, // read
|
||||
] = accounts;
|
||||
] = fixed_accounts;
|
||||
let payer_ai = if accounts.len() > NUM_FIXED {
|
||||
&accounts[NUM_FIXED] // write & signer
|
||||
} else {
|
||||
owner_ai
|
||||
};
|
||||
check!(
|
||||
system_prog_ai.key == &solana_program::system_program::id(),
|
||||
MangoErrorCode::InvalidProgramId
|
||||
)?;
|
||||
check!(owner_ai.is_signer, MangoErrorCode::SignerNecessary)?;
|
||||
check!(payer_ai.is_signer, MangoErrorCode::SignerNecessary)?;
|
||||
|
||||
let mut mango_group = MangoGroup::load_mut_checked(mango_group_ai, program_id)?;
|
||||
check!(
|
||||
|
@ -5249,10 +5261,9 @@ impl Processor {
|
|||
|
||||
let mango_account_seeds: &[&[u8]] =
|
||||
&[&mango_group_ai.key.as_ref(), &owner_ai.key.as_ref(), &account_num.to_le_bytes()];
|
||||
// TODO - test passing in MangoAccount that already has some data in it
|
||||
seed_and_create_pda(
|
||||
program_id,
|
||||
owner_ai,
|
||||
payer_ai,
|
||||
&rent,
|
||||
size_of::<MangoAccount>(),
|
||||
program_id,
|
||||
|
|
|
@ -16,6 +16,7 @@ use solana_program::{
|
|||
};
|
||||
use solana_program_test::*;
|
||||
use solana_sdk::{
|
||||
account::ReadableAccount,
|
||||
instruction::Instruction,
|
||||
signature::{Keypair, Signer},
|
||||
transaction::Transaction,
|
||||
|
@ -404,6 +405,11 @@ impl MangoProgramTest {
|
|||
return self.context.payer.pubkey();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_lamport_balance(&mut self, address: Pubkey) -> u64 {
|
||||
self.context.banks_client.get_account(address).await.unwrap().unwrap().lamports()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_token_balance(&mut self, address: Pubkey) -> u64 {
|
||||
let token = self.context.banks_client.get_account(address).await.unwrap().unwrap();
|
||||
|
@ -1007,36 +1013,89 @@ impl MangoProgramTest {
|
|||
open_orders_pk
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn create_mango_account(
|
||||
&mut self,
|
||||
mango_group_pk: &Pubkey,
|
||||
user_index: usize,
|
||||
account_num: u64,
|
||||
payer: Option<&Keypair>,
|
||||
) -> Pubkey {
|
||||
let owner_key = &self.users[user_index];
|
||||
let owner_pk = owner_key.pubkey();
|
||||
let seeds: &[&[u8]] =
|
||||
&[&mango_group_pk.as_ref(), &owner_pk.as_ref(), &account_num.to_le_bytes()];
|
||||
let (mango_account_pk, _) = Pubkey::find_program_address(seeds, &self.mango_program_id);
|
||||
|
||||
let mut instruction = create_mango_account(
|
||||
&self.mango_program_id,
|
||||
mango_group_pk,
|
||||
&mango_account_pk,
|
||||
&owner_pk,
|
||||
&solana_sdk::system_program::id(),
|
||||
&payer.map(|k| k.pubkey()).unwrap_or(owner_pk),
|
||||
account_num,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Allow testing the compatibility case with no payer
|
||||
if payer.is_none() {
|
||||
instruction.accounts.pop();
|
||||
instruction.accounts[2].is_writable = true; // owner pays lamports
|
||||
}
|
||||
|
||||
let instructions = vec![instruction];
|
||||
let owner_key_c = Keypair::from_base58_string(&owner_key.to_base58_string());
|
||||
let mut signers = vec![&owner_key_c];
|
||||
if let Some(payer_key) = payer {
|
||||
signers.push(payer_key);
|
||||
}
|
||||
self.process_transaction(&instructions, Some(&signers)).await.unwrap();
|
||||
mango_account_pk
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn create_spot_open_orders(
|
||||
&mut self,
|
||||
mango_group_pk: &Pubkey,
|
||||
mango_group: &MangoGroup,
|
||||
mango_account_pk: &Pubkey,
|
||||
mango_account: &MangoAccount,
|
||||
user_index: usize,
|
||||
market_index: usize,
|
||||
payer: Option<&Keypair>,
|
||||
) -> Pubkey {
|
||||
let open_orders_seeds: &[&[u8]] =
|
||||
&[&mango_account_pk.as_ref(), &market_index.to_le_bytes(), b"OpenOrders"];
|
||||
let (open_orders_pk, _) =
|
||||
Pubkey::find_program_address(open_orders_seeds, &self.mango_program_id);
|
||||
|
||||
let create_spot_open_orders_instruction = create_spot_open_orders(
|
||||
let owner_key = &self.users[user_index];
|
||||
let owner_pk = owner_key.pubkey();
|
||||
let mut instruction = create_spot_open_orders(
|
||||
&self.mango_program_id,
|
||||
mango_group_pk,
|
||||
mango_account_pk,
|
||||
&mango_account.owner,
|
||||
&owner_pk,
|
||||
&self.serum_program_id,
|
||||
&open_orders_pk,
|
||||
&mango_group.spot_markets[market_index].spot_market,
|
||||
&mango_group.signer_key,
|
||||
&payer.map(|k| k.pubkey()).unwrap_or(owner_pk),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let instructions = vec![create_spot_open_orders_instruction];
|
||||
let user = Keypair::from_bytes(&self.users[user_index].to_bytes()).unwrap();
|
||||
let signers = vec![&user];
|
||||
// Allow testing the compatibility case with no payer
|
||||
if payer.is_none() {
|
||||
instruction.accounts.pop();
|
||||
instruction.accounts[2].is_writable = true; // owner pays lamports
|
||||
}
|
||||
|
||||
let instructions = vec![instruction];
|
||||
let owner_key_c = Keypair::from_bytes(&owner_key.to_bytes()).unwrap();
|
||||
let mut signers = vec![&owner_key_c];
|
||||
if let Some(payer_key) = payer {
|
||||
signers.push(payer_key);
|
||||
}
|
||||
self.process_transaction(&instructions, Some(&signers)).await.unwrap();
|
||||
open_orders_pk
|
||||
}
|
||||
|
@ -1217,9 +1276,9 @@ impl MangoProgramTest {
|
|||
&mango_group_pk,
|
||||
&mango_group,
|
||||
&mango_account_pk,
|
||||
&mango_account,
|
||||
user_index,
|
||||
x,
|
||||
None,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
@ -1305,9 +1364,9 @@ impl MangoProgramTest {
|
|||
&mango_group_pk,
|
||||
&mango_group,
|
||||
&mango_account_pk,
|
||||
&mango_account,
|
||||
user_index,
|
||||
x,
|
||||
None,
|
||||
)
|
||||
.await,
|
||||
);
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
use solana_program_test::*;
|
||||
use solana_sdk::signature::Signer;
|
||||
use solana_sdk::signer::keypair::Keypair;
|
||||
|
||||
use mango::state::{MangoAccount, ZERO_I80F48};
|
||||
use program_test::cookies::*;
|
||||
use program_test::*;
|
||||
|
||||
mod program_test;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_account() {
|
||||
// === Arrange ===
|
||||
let config = MangoProgramTestConfig { compute_limit: 200_000, num_users: 2, num_mints: 2 };
|
||||
let mut test = MangoProgramTest::start_new(&config).await;
|
||||
|
||||
let mut mango_group_cookie = MangoGroupCookie::default(&mut test).await;
|
||||
let num_precreated_mango_users = 0; // create manually
|
||||
mango_group_cookie
|
||||
.full_setup(&mut test, num_precreated_mango_users, config.num_mints - 1)
|
||||
.await;
|
||||
|
||||
let mango_group_pk = &mango_group_cookie.address;
|
||||
|
||||
//
|
||||
// paid for by owner (test.users[0])
|
||||
//
|
||||
let account0_pk = test.create_mango_account(mango_group_pk, 0, 0, None).await;
|
||||
test.create_spot_open_orders(
|
||||
mango_group_pk,
|
||||
&mango_group_cookie.mango_group,
|
||||
&account0_pk,
|
||||
0,
|
||||
0,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
//
|
||||
// paid for by separate payer (test.users[1]) still owned by test.users[0]
|
||||
//
|
||||
let payer = Keypair::from_base58_string(&test.users[1].to_base58_string());
|
||||
let payer_lamports = test.get_lamport_balance(payer.pubkey()).await;
|
||||
let owner_lamports = test.get_lamport_balance(test.users[0].pubkey()).await;
|
||||
let account1_pk = test.create_mango_account(mango_group_pk, 0, 1, Some(&payer)).await;
|
||||
let account1 = test.load_account::<MangoAccount>(account1_pk).await;
|
||||
assert_eq!(account1.owner, test.users[0].pubkey());
|
||||
assert_eq!(test.get_lamport_balance(test.users[0].pubkey()).await, owner_lamports);
|
||||
assert!(test.get_lamport_balance(payer.pubkey()).await < payer_lamports);
|
||||
assert!(account0_pk != account1_pk);
|
||||
|
||||
test.create_spot_open_orders(
|
||||
mango_group_pk,
|
||||
&mango_group_cookie.mango_group,
|
||||
&account1_pk,
|
||||
0,
|
||||
0,
|
||||
Some(&payer),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(test.get_lamport_balance(test.users[0].pubkey()).await, owner_lamports);
|
||||
}
|
Loading…
Reference in New Issue