Prune instruction (#1)
This commit is contained in:
parent
94908126b9
commit
0a0eb2c0ae
|
@ -24,7 +24,7 @@ checksum = "6b2d54853319fd101b8dd81de382bcbf3e03410a64d8928bbee85a3e7dcde483"
|
|||
[[package]]
|
||||
name = "anchor-attribute-access-control"
|
||||
version = "0.11.1"
|
||||
source = "git+https://github.com/project-serum/anchor#615764b9c85fe1507b946ee1f5c3714f0ff1ad64"
|
||||
source = "git+https://github.com/project-serum/anchor#0998422348fa5f8ebdf907006f5334ad14c4e452"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"anyhow",
|
||||
|
@ -37,7 +37,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "anchor-attribute-account"
|
||||
version = "0.11.1"
|
||||
source = "git+https://github.com/project-serum/anchor#615764b9c85fe1507b946ee1f5c3714f0ff1ad64"
|
||||
source = "git+https://github.com/project-serum/anchor#0998422348fa5f8ebdf907006f5334ad14c4e452"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"anyhow",
|
||||
|
@ -49,7 +49,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "anchor-attribute-error"
|
||||
version = "0.11.1"
|
||||
source = "git+https://github.com/project-serum/anchor#615764b9c85fe1507b946ee1f5c3714f0ff1ad64"
|
||||
source = "git+https://github.com/project-serum/anchor#0998422348fa5f8ebdf907006f5334ad14c4e452"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"proc-macro2",
|
||||
|
@ -60,7 +60,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "anchor-attribute-event"
|
||||
version = "0.11.1"
|
||||
source = "git+https://github.com/project-serum/anchor#615764b9c85fe1507b946ee1f5c3714f0ff1ad64"
|
||||
source = "git+https://github.com/project-serum/anchor#0998422348fa5f8ebdf907006f5334ad14c4e452"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"anyhow",
|
||||
|
@ -72,7 +72,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "anchor-attribute-interface"
|
||||
version = "0.11.1"
|
||||
source = "git+https://github.com/project-serum/anchor#615764b9c85fe1507b946ee1f5c3714f0ff1ad64"
|
||||
source = "git+https://github.com/project-serum/anchor#0998422348fa5f8ebdf907006f5334ad14c4e452"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"anyhow",
|
||||
|
@ -85,7 +85,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "anchor-attribute-program"
|
||||
version = "0.11.1"
|
||||
source = "git+https://github.com/project-serum/anchor#615764b9c85fe1507b946ee1f5c3714f0ff1ad64"
|
||||
source = "git+https://github.com/project-serum/anchor#0998422348fa5f8ebdf907006f5334ad14c4e452"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"anyhow",
|
||||
|
@ -97,7 +97,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "anchor-attribute-state"
|
||||
version = "0.11.1"
|
||||
source = "git+https://github.com/project-serum/anchor#615764b9c85fe1507b946ee1f5c3714f0ff1ad64"
|
||||
source = "git+https://github.com/project-serum/anchor#0998422348fa5f8ebdf907006f5334ad14c4e452"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"anyhow",
|
||||
|
@ -109,7 +109,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "anchor-derive-accounts"
|
||||
version = "0.11.1"
|
||||
source = "git+https://github.com/project-serum/anchor#615764b9c85fe1507b946ee1f5c3714f0ff1ad64"
|
||||
source = "git+https://github.com/project-serum/anchor#0998422348fa5f8ebdf907006f5334ad14c4e452"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"anyhow",
|
||||
|
@ -121,7 +121,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "anchor-lang"
|
||||
version = "0.11.1"
|
||||
source = "git+https://github.com/project-serum/anchor#615764b9c85fe1507b946ee1f5c3714f0ff1ad64"
|
||||
source = "git+https://github.com/project-serum/anchor#0998422348fa5f8ebdf907006f5334ad14c4e452"
|
||||
dependencies = [
|
||||
"anchor-attribute-access-control",
|
||||
"anchor-attribute-account",
|
||||
|
@ -136,12 +136,13 @@ dependencies = [
|
|||
"bytemuck",
|
||||
"solana-program",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-spl"
|
||||
version = "0.11.1"
|
||||
source = "git+https://github.com/project-serum/anchor#615764b9c85fe1507b946ee1f5c3714f0ff1ad64"
|
||||
source = "git+https://github.com/project-serum/anchor#0998422348fa5f8ebdf907006f5334ad14c4e452"
|
||||
dependencies = [
|
||||
"anchor-lang",
|
||||
"lazy_static",
|
||||
|
@ -153,7 +154,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "anchor-syn"
|
||||
version = "0.11.1"
|
||||
source = "git+https://github.com/project-serum/anchor#615764b9c85fe1507b946ee1f5c3714f0ff1ad64"
|
||||
source = "git+https://github.com/project-serum/anchor#0998422348fa5f8ebdf907006f5334ad14c4e452"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
|
@ -880,7 +881,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "serum_dex"
|
||||
version = "0.3.1"
|
||||
source = "git+https://github.com/project-serum/serum-dex?branch=armani/auth#2037a646f82e689f8e7a00c8a34b30e20253ba11"
|
||||
source = "git+https://github.com/project-serum/serum-dex?branch=armani/auth#814c1fd05b00ae99d68d8f9617cc3868b7aceae1"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"bincode",
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 86a413a0f99eb7b2195f797e60183878d82e7fed
|
||||
Subproject commit 814c1fd05b00ae99d68d8f9617cc3868b7aceae1
|
|
@ -68,6 +68,14 @@ pub mod permissioned_markets {
|
|||
/// The identity token must be given as the fist account.
|
||||
struct Identity;
|
||||
|
||||
impl Identity {
|
||||
fn prepare_pda<'info>(acc_info: &AccountInfo<'info>) -> AccountInfo<'info> {
|
||||
let mut acc_info = acc_info.clone();
|
||||
acc_info.is_signer = true;
|
||||
acc_info
|
||||
}
|
||||
}
|
||||
|
||||
impl MarketMiddleware for Identity {
|
||||
/// Accounts:
|
||||
///
|
||||
|
@ -117,6 +125,25 @@ impl MarketMiddleware for Identity {
|
|||
verify_and_strip_auth(ctx)
|
||||
}
|
||||
|
||||
/// Accounts:
|
||||
///
|
||||
/// 0. Authorization token (revoked).
|
||||
/// ..
|
||||
fn prune(&self, ctx: &mut Context) -> ProgramResult {
|
||||
verify_revoked_and_strip_auth(ctx)?;
|
||||
|
||||
// Sign with the prune authority.
|
||||
let market = &ctx.accounts[0];
|
||||
ctx.seeds.push(prune_authority! {
|
||||
program = ctx.program_id,
|
||||
dex_program = ctx.dex_program_id,
|
||||
market = market.key
|
||||
});
|
||||
|
||||
ctx.accounts[3] = Self::prepare_pda(&ctx.accounts[3]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Accounts:
|
||||
///
|
||||
/// 0. Authorization token.
|
||||
|
@ -139,12 +166,63 @@ fn verify_and_strip_auth(ctx: &mut Context) -> ProgramResult {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_revoked_and_strip_auth(ctx: &mut Context) -> ProgramResult {
|
||||
// The rent sysvar is used as a dummy example of an identity token.
|
||||
let auth = &ctx.accounts[0];
|
||||
require!(auth.key != &rent::ID, TokenNotRevoked);
|
||||
|
||||
// Strip off the account before possing on the message.
|
||||
ctx.accounts = (&ctx.accounts[1..]).to_vec();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Macros.
|
||||
|
||||
/// Returns the seeds used for the prune authority.
|
||||
#[macro_export]
|
||||
macro_rules! prune_authority {
|
||||
(
|
||||
program = $program:expr,
|
||||
dex_program = $dex_program:expr,
|
||||
market = $market:expr,
|
||||
bump = $bump:expr
|
||||
) => {
|
||||
vec![
|
||||
b"prune".to_vec(),
|
||||
$dex_program.as_ref().to_vec(),
|
||||
$market.as_ref().to_vec(),
|
||||
vec![$bump],
|
||||
]
|
||||
};
|
||||
(
|
||||
program = $program:expr,
|
||||
dex_program = $dex_program:expr,
|
||||
market = $market:expr
|
||||
) => {
|
||||
vec![
|
||||
b"prune".to_vec(),
|
||||
$dex_program.as_ref().to_vec(),
|
||||
$market.as_ref().to_vec(),
|
||||
vec![
|
||||
Pubkey::find_program_address(
|
||||
&[b"prune".as_ref(), $dex_program.as_ref(), $market.as_ref()],
|
||||
$program,
|
||||
)
|
||||
.1,
|
||||
],
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
// Error.
|
||||
|
||||
#[error]
|
||||
pub enum ErrorCode {
|
||||
#[msg("Invalid auth token provided")]
|
||||
InvalidAuth,
|
||||
#[msg("Auth token not revoked")]
|
||||
TokenNotRevoked,
|
||||
}
|
||||
|
||||
// Constants.
|
||||
|
|
|
@ -164,21 +164,6 @@ describe("permissioned-markets", () => {
|
|||
assert.ok(afterOoAccount.quoteTokenTotal.eq(usdcPosted));
|
||||
});
|
||||
|
||||
// Need to crank the cancel so that we can close later.
|
||||
it("Cranks the cancel transaction", async () => {
|
||||
// TODO: can do this in a single transaction if we covert the pubkey bytes
|
||||
// into a [u64; 4] array and sort. I'm lazy though.
|
||||
let eq = await marketProxy.market.loadEventQueue(provider.connection);
|
||||
while (eq.length > 0) {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
marketProxy.market.makeConsumeEventsInstruction([eq[0].openOrders], 1)
|
||||
);
|
||||
await provider.send(tx);
|
||||
eq = await marketProxy.market.loadEventQueue(provider.connection);
|
||||
}
|
||||
});
|
||||
|
||||
it("Settles funds on the orderbook", async () => {
|
||||
// Given.
|
||||
const beforeTokenAccount = await usdcClient.getAccountInfo(usdcAccount);
|
||||
|
@ -204,6 +189,124 @@ describe("permissioned-markets", () => {
|
|||
);
|
||||
});
|
||||
|
||||
// Need to crank the cancel so that we can close later.
|
||||
it("Cranks the cancel transaction", async () => {
|
||||
await crankEventQueue(provider, marketProxy);
|
||||
});
|
||||
|
||||
it("Closes an open orders account", async () => {
|
||||
// Given.
|
||||
const beforeAccount = await program.provider.connection.getAccountInfo(
|
||||
program.provider.wallet.publicKey
|
||||
);
|
||||
|
||||
// When.
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
marketProxy.instruction.closeOpenOrders(
|
||||
openOrders,
|
||||
provider.wallet.publicKey,
|
||||
provider.wallet.publicKey
|
||||
)
|
||||
);
|
||||
await provider.send(tx);
|
||||
|
||||
// Then.
|
||||
const afterAccount = await program.provider.connection.getAccountInfo(
|
||||
program.provider.wallet.publicKey
|
||||
);
|
||||
const closedAccount = await program.provider.connection.getAccountInfo(
|
||||
openOrders
|
||||
);
|
||||
assert.ok(23352768 === afterAccount.lamports - beforeAccount.lamports);
|
||||
assert.ok(closedAccount === null);
|
||||
});
|
||||
|
||||
it("Re-opens an open orders account", async () => {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
await marketProxy.instruction.initOpenOrders(
|
||||
program.provider.wallet.publicKey,
|
||||
marketProxy.market.address,
|
||||
marketProxy.market.address, // Dummy. Replaced by middleware.
|
||||
marketProxy.market.address // Dummy. Replaced by middleware.
|
||||
)
|
||||
);
|
||||
await provider.send(tx);
|
||||
|
||||
const account = await provider.connection.getAccountInfo(openOrders);
|
||||
assert.ok(account.owner.toString() === DEX_PID.toString());
|
||||
});
|
||||
|
||||
it("Posts several bids and asks on the orderbook", async () => {
|
||||
const size = 10;
|
||||
const price = 2;
|
||||
for (let k = 0; k < 10; k += 1) {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
marketProxy.instruction.newOrderV3({
|
||||
owner: program.provider.wallet.publicKey,
|
||||
payer: usdcAccount,
|
||||
side: "buy",
|
||||
price,
|
||||
size,
|
||||
orderType: "postOnly",
|
||||
clientId: new BN(999),
|
||||
openOrdersAddressKey: openOrders,
|
||||
selfTradeBehavior: "abortTransaction",
|
||||
})
|
||||
);
|
||||
await provider.send(tx);
|
||||
}
|
||||
|
||||
const sizeAsk = 10;
|
||||
const priceAsk = 10;
|
||||
|
||||
for (let k = 0; k < 10; k += 1) {
|
||||
const txAsk = new Transaction();
|
||||
txAsk.add(
|
||||
marketProxy.instruction.newOrderV3({
|
||||
owner: program.provider.wallet.publicKey,
|
||||
payer: tokenAccount,
|
||||
side: "sell",
|
||||
price: priceAsk,
|
||||
size: sizeAsk,
|
||||
orderType: "postOnly",
|
||||
clientId: new BN(1000),
|
||||
openOrdersAddressKey: openOrders,
|
||||
selfTradeBehavior: "abortTransaction",
|
||||
})
|
||||
);
|
||||
await provider.send(txAsk);
|
||||
}
|
||||
});
|
||||
|
||||
it("Prunes the orderbook", async () => {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
marketProxy.instruction.prune(openOrders, provider.wallet.publicKey)
|
||||
);
|
||||
await provider.send(tx);
|
||||
});
|
||||
|
||||
it("Settles the account", async () => {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
await marketProxy.instruction.settleFunds(
|
||||
openOrders,
|
||||
provider.wallet.publicKey,
|
||||
tokenAccount,
|
||||
usdcAccount,
|
||||
referral
|
||||
)
|
||||
);
|
||||
await provider.send(tx);
|
||||
});
|
||||
|
||||
it("Cranks the prune transaction", async () => {
|
||||
await crankEventQueue(provider, marketProxy);
|
||||
});
|
||||
|
||||
it("Closes an open orders account", async () => {
|
||||
// Given.
|
||||
const beforeAccount = await program.provider.connection.getAccountInfo(
|
||||
|
@ -232,3 +335,17 @@ describe("permissioned-markets", () => {
|
|||
assert.ok(closedAccount === null);
|
||||
});
|
||||
});
|
||||
|
||||
async function crankEventQueue(provider, marketProxy) {
|
||||
// TODO: can do this in a single transaction if we covert the pubkey bytes
|
||||
// into a [u64; 4] array and sort. I'm lazy though.
|
||||
let eq = await marketProxy.market.loadEventQueue(provider.connection);
|
||||
while (eq.length > 0) {
|
||||
const tx = new Transaction();
|
||||
tx.add(
|
||||
marketProxy.market.makeConsumeEventsInstruction([eq[0].openOrders], 1)
|
||||
);
|
||||
await provider.send(tx);
|
||||
eq = await marketProxy.market.loadEventQueue(provider.connection);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,12 +7,14 @@ const {
|
|||
SystemProgram,
|
||||
} = require("@project-serum/anchor").web3;
|
||||
const { TOKEN_PROGRAM_ID } = require("@solana/spl-token");
|
||||
const serum = require("@project-serum/serum");
|
||||
const {
|
||||
DexInstructions,
|
||||
TokenInstructions,
|
||||
OpenOrdersPda,
|
||||
MARKET_STATE_LAYOUT_V3,
|
||||
} = require("@project-serum/serum");
|
||||
} = serum;
|
||||
const { Identity } = require("./market-proxy");
|
||||
const { DEX_PID } = require("./common");
|
||||
|
||||
// Creates a market on the dex.
|
||||
|
@ -129,6 +131,11 @@ async function list({
|
|||
DEX_PID,
|
||||
proxyProgramId
|
||||
),
|
||||
pruneAuthority: await Identity.pruneAuthority(
|
||||
market.publicKey,
|
||||
DEX_PID,
|
||||
proxyProgramId
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
const { SYSVAR_RENT_PUBKEY } = require("@solana/web3.js");
|
||||
const anchor = require("@project-serum/anchor");
|
||||
const {
|
||||
PublicKey,
|
||||
SYSVAR_RENT_PUBKEY,
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
} = require("@solana/web3.js");
|
||||
const {
|
||||
OpenOrders,
|
||||
OpenOrdersPda,
|
||||
|
@ -51,14 +56,35 @@ class Identity {
|
|||
closeOpenOrders(ix) {
|
||||
this.proxy(ix);
|
||||
}
|
||||
prune(ix) {
|
||||
this.proxyRevoked(ix);
|
||||
}
|
||||
proxy(ix) {
|
||||
ix.keys = [
|
||||
{ pubkey: SYSVAR_RENT_PUBKEY, isWritable: false, isSigner: false },
|
||||
...ix.keys,
|
||||
];
|
||||
}
|
||||
proxyRevoked(ix) {
|
||||
ix.keys = [
|
||||
{ pubkey: SYSVAR_CLOCK_PUBKEY, isWritable: false, isSigner: false },
|
||||
...ix.keys,
|
||||
];
|
||||
}
|
||||
static async pruneAuthority(market, dexProgramId, proxyProgramId) {
|
||||
const [addr] = await PublicKey.findProgramAddress(
|
||||
[
|
||||
anchor.utils.bytes.utf8.encode("prune"),
|
||||
dexProgramId.toBuffer(),
|
||||
market.toBuffer(),
|
||||
],
|
||||
proxyProgramId
|
||||
);
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
load,
|
||||
Identity,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue