Prune instruction (#1)

This commit is contained in:
Armani Ferrante 2021-07-21 22:44:35 -07:00 committed by GitHub
parent 94908126b9
commit 0a0eb2c0ae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 259 additions and 30 deletions

25
Cargo.lock generated
View File

@ -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",

2
deps/serum-dex vendored

@ -1 +1 @@
Subproject commit 86a413a0f99eb7b2195f797e60183878d82e7fed
Subproject commit 814c1fd05b00ae99d68d8f9617cc3868b7aceae1

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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
),
})
);

View File

@ -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,
};