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

DRAFT: redesign - track cumulative borrows using a u64 counter instead of a bool flag #3

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ cluster = "localnet"
wallet = "~/.config/solana/id.json"

[scripts]
test = "mocha -t 1000000 tests/"
test = "yarn mocha -t 1000000 tests/"
3 changes: 1 addition & 2 deletions app/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,14 +153,13 @@ function borrow(user, mint, amount) {
// we could also have a client approval, and this would be my ideal design
// but requiring a third instruction drastically reduces avail bytes for transaction proper
// the new tx format might make this viable by cutting address repetition tho
let repayIxn = adobe.instruction.repay(new anchor.BN(amount), {
let repayIxn = adobe.instruction.repay({
accounts: {
user: user.publicKey,
state: stateKey,
pool: poolKey,
poolToken: poolTokenKey,
userToken: userTokenKey,
instructions: SYSVAR_INSTRUCTIONS_PUBKEY,
tokenProgram: TOKEN_PROGRAM_ID,
}});

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"type": "module",
"dependencies": {
"@project-serum/anchor": "^0.17.0",
"@project-serum/anchor": "0.18.0",
"@solana/spl-token": "^0.1.8"
},
"devDependencies": {
Expand Down
101 changes: 33 additions & 68 deletions programs/adobe/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use core::convert::TryInto;
use anchor_lang::prelude::*;
use anchor_lang::Discriminator;
use anchor_spl::token::{self, Mint, TokenAccount, MintTo, Burn, Transfer, Token};
use anchor_lang::solana_program as solana;
use anchor_lang::Discriminator;
use anchor_spl::token::{self, Burn, Mint, MintTo, Token, TokenAccount, Transfer};
use core::convert::TryInto;

declare_id!("VzRKfyFWHZtYWbQWfcnCGBrTg3tqqRV2weUqvrvVhuo");

const TOKEN_NAMESPACE: &[u8] = b"TOKEN";
const TOKEN_NAMESPACE: &[u8] = b"TOKEN";
const VOUCHER_NAMESPACE: &[u8] = b"VOUCHER";
const REPAY_OPCODE: u64 = 0xea674352d0eadba6;
const REPAY_OPCODE: u64 = 0xea674352d0eadba6;

#[program]
#[deny(unused_must_use)]
Expand All @@ -32,7 +32,7 @@ pub mod adobe {
msg!("adobe add_pool");

ctx.accounts.pool.bump = pool_bump;
ctx.accounts.pool.borrowing = false;
ctx.accounts.pool.borrowing = 0;
ctx.accounts.pool.token_mint = ctx.accounts.token_mint.key();
ctx.accounts.pool.pool_token = ctx.accounts.pool_token.key();
ctx.accounts.pool.voucher_mint = ctx.accounts.voucher_mint.key();
Expand All @@ -45,10 +45,7 @@ pub mod adobe {
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> ProgramResult {
msg!("adobe deposit");

let state_seed: &[&[&[u8]]] = &[&[
&State::discriminator()[..],
&[ctx.accounts.state.bump],
]];
let state_seed: &[&[&[u8]]] = &[&[&State::discriminator()[..], &[ctx.accounts.state.bump]]];

let transfer_ctx = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
Expand Down Expand Up @@ -82,10 +79,7 @@ pub mod adobe {
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> ProgramResult {
msg!("adobe withdraw");

let state_seed: &[&[&[u8]]] = &[&[
&State::discriminator()[..],
&[ctx.accounts.state.bump],
]];
let state_seed: &[&[&[u8]]] = &[&[&State::discriminator()[..], &[ctx.accounts.state.bump]]];

let burn_ctx = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
Expand Down Expand Up @@ -119,47 +113,39 @@ pub mod adobe {
pub fn borrow(ctx: Context<Borrow>, amount: u64) -> ProgramResult {
msg!("adobe borrow");

if ctx.accounts.pool.borrowing {
return Err(AdobeError::Borrowing.into());
}

let ixns = ctx.accounts.instructions.to_account_info();

// make sure this isnt a cpi call
let current_index = solana::sysvar::instructions::load_current_index_checked(&ixns)? as usize;
let current_ixn = solana::sysvar::instructions::load_instruction_at_checked(current_index, &ixns)?;
if current_ixn.program_id != *ctx.program_id {
return Err(AdobeError::CpiBorrow.into());
}
let current_index =
solana::sysvar::instructions::load_current_index_checked(&ixns)? as usize;

// loop through instructions, looking for an equivalent repay to this borrow
let mut i = current_index + 1;
loop {
// get the next instruction, die if theres no more
if let Ok(ixn) = solana::sysvar::instructions::load_instruction_at_checked(i, &ixns) {
// check if we have a toplevel repay toward the same pool
// if so, confirm the amount, otherwise next instruction
// check if we have a toplevel repay toward the same pool,
// otherwise next instruction
if ixn.program_id == *ctx.program_id
&& u64::from_be_bytes(ixn.data[..8].try_into().unwrap()) == REPAY_OPCODE
&& ixn.accounts[2].pubkey == ctx.accounts.pool.key() {
if u64::from_le_bytes(ixn.data[8..16].try_into().unwrap()) == amount {
break;
} else {
return Err(AdobeError::IncorrectRepay.into());
}
&& u64::from_be_bytes(ixn.data[..8].try_into().unwrap()) == REPAY_OPCODE
&& ixn.accounts[2].pubkey == ctx.accounts.pool.key()
{
break;
} else {
i += 1;
}
}
else {
} else {
return Err(AdobeError::NoRepay.into());
}
}

let state_seed: &[&[&[u8]]] = &[&[
&State::discriminator()[..],
&[ctx.accounts.state.bump],
]];
ctx.accounts.pool.borrowing = ctx
.accounts
.pool
.borrowing
.checked_add(amount)
.ok_or(AdobeError::ExcessiveBorrow)?;

let state_seed: &[&[&[u8]]] = &[&[&State::discriminator()[..], &[ctx.accounts.state.bump]]];

let transfer_ctx = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
Expand All @@ -172,29 +158,16 @@ pub mod adobe {
);

token::transfer(transfer_ctx, amount)?;
ctx.accounts.pool.borrowing = true;

Ok(())
}

// REPAY
// receives tokens
pub fn repay(ctx: Context<Repay>, amount: u64) -> ProgramResult {
pub fn repay(ctx: Context<Repay>) -> ProgramResult {
msg!("adobe repay");

let ixns = ctx.accounts.instructions.to_account_info();

// make sure this isnt a cpi call
let current_index = solana::sysvar::instructions::load_current_index_checked(&ixns)? as usize;
let current_ixn = solana::sysvar::instructions::load_instruction_at_checked(current_index, &ixns)?;
if current_ixn.program_id != *ctx.program_id {
return Err(AdobeError::CpiRepay.into());
}

let state_seed: &[&[&[u8]]] = &[&[
&State::discriminator()[..],
&[ctx.accounts.state.bump],
]];
let state_seed: &[&[&[u8]]] = &[&[&State::discriminator()[..], &[ctx.accounts.state.bump]]];

let transfer_ctx = CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
Expand All @@ -206,8 +179,8 @@ pub mod adobe {
state_seed,
);

token::transfer(transfer_ctx, amount)?;
ctx.accounts.pool.borrowing = false;
token::transfer(transfer_ctx, ctx.accounts.pool.borrowing)?;
ctx.accounts.pool.borrowing = 0;

Ok(())
}
Expand Down Expand Up @@ -331,8 +304,6 @@ pub struct Repay<'info> {
pub pool_token: Account<'info, TokenAccount>,
#[account(mut, constraint = user_token.mint == pool.token_mint)]
pub user_token: Account<'info, TokenAccount>,
#[account(address = solana::sysvar::instructions::ID)]
pub instructions: UncheckedAccount<'info>,
pub token_program: Program<'info, Token>,
}

Expand All @@ -347,7 +318,7 @@ pub struct State {
#[derive(Default)]
pub struct Pool {
bump: u8,
borrowing: bool,
borrowing: u64,
token_mint: Pubkey,
pool_token: Pubkey,
voucher_mint: Pubkey,
Expand All @@ -356,13 +327,7 @@ pub struct Pool {
#[error]
pub enum AdobeError {
#[msg("borrow requires an equivalent repay")]
NoRepay,
#[msg("repay exists but in the wrong amount")]
IncorrectRepay,
#[msg("cannot call borrow via cpi")]
CpiBorrow,
#[msg("cannot call repay via cpi")]
CpiRepay,
#[msg("a borrow is already in progress")]
Borrowing,
NoRepay, // 0x12c
#[msg("excessive borrow amount")]
ExcessiveBorrow, // 0x12d
}
7 changes: 3 additions & 4 deletions programs/evil/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use adobe::cpi::accounts::{Borrow, Repay};
use anchor_lang::prelude::*;
use adobe::cpi::accounts::{ Borrow, Repay};

declare_id!("5zAQ1XhjuHcQtUXJSTjbmyDagmKVHDMi5iADv5PfYEUK");

Expand All @@ -25,10 +25,10 @@ pub mod evil {
Ok(())
}

pub fn repay_proxy(ctx: Context<Adobe>, amount: u64) -> ProgramResult {
pub fn repay_proxy(ctx: Context<Adobe>) -> ProgramResult {
msg!("evil repay_proxy");

adobe::cpi::repay(ctx.accounts.into_repay_context(), amount)?;
adobe::cpi::repay(ctx.accounts.into_repay_context())?;

Ok(())
}
Expand Down Expand Up @@ -72,7 +72,6 @@ impl<'info> Adobe<'info> {
pool: self.pool.clone(),
pool_token: self.pool_token.clone(),
user_token: self.user_token.clone(),
instructions: self.instructions.clone(),
token_program: self.token_program.clone(),
},
)
Expand Down
34 changes: 11 additions & 23 deletions tests/adobe.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import assert from "assert";
import anchor from "@project-serum/anchor";
import spl from "@solana/spl-token";
import { findAddr, findAssocAddr, discriminator, airdrop } from "../app/util.js";
import { findAddr, discriminator, airdrop } from "../app/util.js";
import * as api from "../app/api.js";

const LAMPORTS_PER_SOL = anchor.web3.LAMPORTS_PER_SOL;
const SYSVAR_INSTRUCTIONS_PUBKEY = anchor.web3.SYSVAR_INSTRUCTIONS_PUBKEY;
const TOKEN_PROGRAM_ID = spl.TOKEN_PROGRAM_ID;
const ASSOCIATED_TOKEN_PROGRAM_ID = spl.ASSOCIATED_TOKEN_PROGRAM_ID;

const TXN_OPTS = {commitment: "processed", preflightCommitment: "processed", skipPreflight: true};
const TOKEN_DECIMALS = 6;
Expand Down Expand Up @@ -127,17 +126,6 @@ describe("adobe flash loan program", () => {
balAfter = await poolBalance(tokenMint);
assert.equal(balAfter, balBefore, "program token balance unchanged");

// dont fully repay
[borrowIxn] = api.borrow(wallet, tokenMint, amount + 1);
txn = new anchor.web3.Transaction;
txn.add(borrowIxn);
txn.add(repayIxn);

balBefore = await poolBalance(tokenMint);
await assert.rejects(provider.send(txn), "borrow more than repay fails");
balAfter = await poolBalance(tokenMint);
assert.equal(balAfter, balBefore, "program token balance unchanged");

// borrow too much
[borrowIxn, repayIxn] = api.borrow(wallet, tokenMint, amount * 10);
txn = new anchor.web3.Transaction;
Expand All @@ -150,18 +138,18 @@ describe("adobe flash loan program", () => {
assert.equal(balAfter, balBefore, "program token balance unchanged");

// double borrow (raw instruction)
[borrowIxn, repayIxn] = api.borrow(wallet, tokenMint, amount / 10);
txn = new anchor.web3.Transaction;
txn.add(borrowIxn);
txn.add(borrowIxn);
txn.add(repayIxn);

balBefore = await poolBalance(tokenMint);
await assert.rejects(provider.send(txn), "multiple borrow fails");
await provider.send(txn);
balAfter = await poolBalance(tokenMint);
assert.equal(balAfter, balBefore, "program token balance unchanged");

// dounle borrow (direct cpi)
[borrowIxn, repayIxn] = api.borrow(wallet, tokenMint, amount / 10);
// double borrow (direct cpi)
let evilIxn = evil.instruction.borrowProxy(new anchor.BN(amount / 10), {
accounts: {
user: wallet.publicKey,
Expand All @@ -181,7 +169,7 @@ describe("adobe flash loan program", () => {
txn.add(repayIxn);

balBefore = await poolBalance(tokenMint);
await assert.rejects(provider.send(txn), "borrow and cpi fails");
await provider.send(txn);
balAfter = await poolBalance(tokenMint);
assert.equal(balAfter, balBefore, "program token balance unchanged");

Expand All @@ -191,7 +179,7 @@ describe("adobe flash loan program", () => {
txn.add(repayIxn);

balBefore = await poolBalance(tokenMint);
await assert.rejects(provider.send(txn), "cpi and borrow fails");
await provider.send(txn);
balAfter = await poolBalance(tokenMint);
assert.equal(balAfter, balBefore, "program token balance unchanged");

Expand All @@ -201,11 +189,11 @@ describe("adobe flash loan program", () => {
txn.add(repayIxn);

balBefore = await poolBalance(tokenMint);
await assert.rejects(provider.send(txn), "cpi and cpi fails");
await provider.send(txn);
balAfter = await poolBalance(tokenMint);
assert.equal(balAfter, balBefore, "program token balance unchanged");

// dounle borrow (batched cpi)
// double borrow (batched cpi)
evilIxn = evil.instruction.borrowDouble(new anchor.BN(amount / 10), {
accounts: {
user: wallet.publicKey,
Expand All @@ -224,7 +212,7 @@ describe("adobe flash loan program", () => {
txn.add(repayIxn);

balBefore = await poolBalance(tokenMint);
await assert.rejects(provider.send(txn), "cpi double borrow fails");
await provider.send(txn);
balAfter = await poolBalance(tokenMint);
assert.equal(balAfter, balBefore, "program token balance unchanged");

Expand Down Expand Up @@ -261,7 +249,7 @@ describe("adobe flash loan program", () => {
adobeProgram: adobe.programId,
},
});
let evilRepay = evil.instruction.repayProxy(new anchor.BN(1), {
let evilRepay = evil.instruction.repayProxy({
accounts: {
user: wallet.publicKey,
state: stateKey,
Expand All @@ -281,7 +269,7 @@ describe("adobe flash loan program", () => {
txn.add(repayIxn);

balBefore = await poolBalance(tokenMint);
await assert.rejects(provider.send(txn), "cpi dummy repay fails");
await provider.send(txn);
balAfter = await poolBalance(tokenMint);
assert.equal(balAfter, balBefore, "program token balance unchanged");
});
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
dependencies:
regenerator-runtime "^0.13.4"

"@project-serum/anchor@^0.17.0":
version "0.17.0"
resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.17.0.tgz#99a2cc59cae2939c1db1197950b7b606319ea7a8"
integrity sha512-vctZHZD5rUP1WXUUaJFdKl+1u0dE+O2+jejmYcb0InAZiwXdGAW8mX47U902voT94PdCr0aNHuyOQ8I75nfinw==
"@project-serum/anchor@0.18.0":
version "0.18.0"
resolved "https://registry.yarnpkg.com/@project-serum/anchor/-/anchor-0.18.0.tgz#867144282e59482230f797f73ee9f5634f846061"
integrity sha512-WTm+UB93MoxyCbjnHIibv/uUEoO/5gL4GEtE/aMioLF8Z4i0vCMPnvAN0xpk9VBu3t7ld2DcCE/L+6Z7dwU++w==
dependencies:
"@project-serum/borsh" "^0.2.2"
"@solana/web3.js" "^1.17.0"
Expand Down