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

Add tokens/escrow/steel #305

Closed
wants to merge 7 commits into from
Closed
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
11 changes: 11 additions & 0 deletions tokens/escrow/steel/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[workspace]
members = ["api", "program"]
resolver = "2"

[workspace.dependencies]
solana-program = "1.18.17"
steel = {version ="2.1", features=["spl"]}
bytemuck = "1.14"
num_enum = "0.7"
fixed = "=1.27.0"
spl-token = { version = "4.0.0", features = [ "no-entrypoint" ] }
15 changes: 15 additions & 0 deletions tokens/escrow/steel/api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "escrow-steel-api"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "lib"]

[dependencies]
solana-program.workspace = true
steel.workspace = true
bytemuck.workspace = true
num_enum.workspace = true
fixed.workspace = true
spl-token.workspace = true
100 changes: 100 additions & 0 deletions tokens/escrow/steel/api/src/instruction/make_offer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use {
crate::{state::*, EscrowInstruction},
steel::*,
};

instruction!(EscrowInstruction, MakeOffer);
// MakeOffer Instruction
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct MakeOffer {
pub id: u64,
pub token_a_offered_amount: u64,
pub token_b_wanted_amount: u64,
}

impl MakeOffer {
pub fn process(accounts: &[AccountInfo<'_>], data: &[u8]) -> ProgramResult {
let args = MakeOffer::try_from_bytes(data)?;
let [
// accounts order
offer_info,
token_mint_a,
token_mint_b,
maker_token_account_a,
vault,
maker,
payer,
token_program,
associated_token_program,
system_program
] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};

// make sure the maker is a signer
//
maker.is_signer()?;

// make sure the maker is a writable signer
//
maker_token_account_a.as_associated_token_account(maker.key, token_mint_a.key)?;

let offer_seeds = &[Offer::SEEDS, maker.key.as_ref(), &args.id.to_le_bytes()];
let (offer_address, offer_bump) = Pubkey::find_program_address(offer_seeds, &crate::ID);

// check we have the right address, derived from the provided seeds
//
offer_info.has_address(&offer_address)?;

// create the offer account
//
create_account::<Offer>(offer_info, system_program, payer, &crate::ID, offer_seeds)?;

// create the vault token account, where the maker will send funds to
//
create_associated_token_account(
payer,
offer_info,
vault,
token_mint_a,
system_program,
token_program,
associated_token_program,
)?;

// validate the vault the maker token a will be sent to
//
vault.as_associated_token_account(offer_info.key, token_mint_a.key)?;

// maker transfer token a to the vault
//
transfer(
maker,
maker_token_account_a,
vault,
token_program,
args.token_a_offered_amount,
)?;

let offer = offer_info.as_account_mut::<Offer>(&crate::ID)?;

// we record our offer data
//
*offer = Offer {
id: args.id,
bump: offer_bump,
maker: *maker.key,
token_b_wanted_amount: args.token_b_wanted_amount,
token_mint_a: *token_mint_a.key,
token_mint_b: *token_mint_b.key,
};

solana_program::msg!(
"Token A balance in vault: {}",
vault.as_token_account()?.amount
);

Ok(())
}
}
14 changes: 14 additions & 0 deletions tokens/escrow/steel/api/src/instruction/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pub mod make_offer;
pub mod take_offer;

pub use make_offer::*;
pub use take_offer::*;

use steel::*;

#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, TryFromPrimitive)]
pub enum EscrowInstruction {
MakeOffer = 0,
TakeOffer = 1,
}
174 changes: 174 additions & 0 deletions tokens/escrow/steel/api/src/instruction/take_offer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use {
crate::{state::*, EscrowInstruction},
steel::*,
};

instruction!(EscrowInstruction, TakeOffer);
// TakeOffer Instruction
#[repr(C)]
#[derive(Clone, Copy, Debug, Pod, Zeroable)]
pub struct TakeOffer {}

impl TakeOffer {
pub fn process(accounts: &[AccountInfo<'_>]) -> ProgramResult {
let [
// accounts order
offer_info,
token_mint_a,
token_mint_b,
maker_token_account_b,
taker_token_account_a,
taker_token_account_b,
vault,
maker,
taker,
payer,
token_program,
associated_token_program,
system_program
] = accounts else {
return Err(ProgramError::NotEnoughAccountKeys);
};

// Ensure the taker is a signer
//
taker.is_signer()?;

// Validate the offer
//
let offer = offer_info
.as_account::<Offer>(&crate::ID)?
.assert(|offer| {
offer.maker == *maker.key
&& offer.token_mint_a == *token_mint_a.key
&& offer.token_mint_b == *token_mint_b.key
})?;

let offer_seeds = &[Offer::SEEDS, maker.key.as_ref(), &offer.id.to_le_bytes()];

// validate offer account
//
offer_info.has_seeds(offer_seeds, &crate::ID)?;

// Create taker token a account, if needed
//
if taker_token_account_a.lamports() == 0 {
create_associated_token_account(
payer,
taker,
taker_token_account_a,
token_mint_a,
system_program,
token_program,
associated_token_program,
)?;
}

// Create maker token b account, if needed
//
if maker_token_account_b.lamports() == 0 {
create_associated_token_account(
payer,
maker,
maker_token_account_b,
token_mint_b,
system_program,
token_program,
associated_token_program,
)?;
}

// Validate the token accounts, then get the current amounts
//
let vault_amount = vault
.as_associated_token_account(offer_info.key, token_mint_a.key)?
.amount;
let taker_a_amount_before_transfer = taker_token_account_a
.as_associated_token_account(taker.key, token_mint_a.key)?
.amount;
let maker_b_amount_before_transfer = maker_token_account_b
.as_associated_token_account(maker.key, token_mint_b.key)?
.amount;
let taker_b_amount_before_transfer = taker_token_account_b
.as_associated_token_account(taker.key, token_mint_b.key)?
.amount;

solana_program::msg!("Vault A Balance Before Transfer: {}", vault_amount);
solana_program::msg!(
"Taker A Balance Before Transfer: {}",
taker_a_amount_before_transfer
);
solana_program::msg!(
"Maker B Balance Before Transfer: {}",
maker_b_amount_before_transfer
);
solana_program::msg!(
"Taker B Balance Before Transfer: {}",
taker_b_amount_before_transfer
);

// taker makes a transfer of the wanted amount to maker
//
transfer(
taker,
taker_token_account_b,
maker_token_account_b,
token_program,
offer.token_b_wanted_amount, // offer amount
)?;

// tokens in the vault is transfered to the taker
//
transfer_signed(
offer_info,
vault,
taker_token_account_a,
token_program,
vault_amount, // all tokens in the vault
offer_seeds,
)?;

let taker_a_amount = taker_token_account_a.as_token_account()?.amount;
let maker_b_amount = maker_token_account_b.as_token_account()?.amount;

// assert the token balances after transfers
//
assert_eq!(
taker_a_amount,
vault_amount + taker_a_amount_before_transfer
);
assert_eq!(
maker_b_amount,
maker_b_amount_before_transfer + offer.token_b_wanted_amount
);

let vault_amount = vault.as_token_account()?.amount;
let taker_b_amount = taker_token_account_b.as_token_account()?.amount;

solana_program::msg!("Vault A Balance After Transfer: {}", vault_amount);
solana_program::msg!("Taker A Balance After Transfer: {}", taker_a_amount);
solana_program::msg!("Maker B Balance After Transfer: {}", maker_b_amount);
solana_program::msg!("Taker B Balance After Transfer: {}", taker_b_amount);

// close the vault because it is no longer needed
//
invoke_signed_with_bump(
&spl_token::instruction::close_account(
token_program.key, // token program
vault.key, // token account to close
taker.key, // account to transfer lamports
offer_info.key, // token account ownder
&[offer_info.key], // signer pubkeys
)?,
&[vault.clone(), taker.clone(), offer_info.clone()],
offer_seeds,
offer.bump,
)?;

// close the offer account
//
offer_info.close(taker)?;

Ok(())
}
}
12 changes: 12 additions & 0 deletions tokens/escrow/steel/api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pub mod instruction;
pub mod state;

pub use instruction::*;
use steel::*;

declare_id!("z7msBPQHDJjTvdQRoEcKyENgXDhSRYeHieN1ZMTqo35");

pub mod prelude {
pub use crate::instruction::*;
pub use crate::state::*;
}
11 changes: 11 additions & 0 deletions tokens/escrow/steel/api/src/state/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pub mod offer;

pub use offer::*;

use steel::*;

#[repr(u8)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, IntoPrimitive, TryFromPrimitive)]
pub enum EscrowAccount {
Offer = 0,
}
20 changes: 20 additions & 0 deletions tokens/escrow/steel/api/src/state/offer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
use steel::*;

use super::EscrowAccount;

account!(EscrowAccount, Offer);
// Offer Account
#[repr(C, packed)]
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
pub struct Offer {
pub id: u64,
pub maker: Pubkey,
pub token_mint_a: Pubkey,
pub token_mint_b: Pubkey,
pub token_b_wanted_amount: u64,
pub bump: u8,
}

impl Offer {
pub const SEEDS: &'static [u8] = b"offer";
}
8 changes: 8 additions & 0 deletions tokens/escrow/steel/cicd.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

# This script is for quick building & deploying of the program.
# It also serves as a reference for the commands used for building & deploying Solana programs.
# Run this bad boy with "bash cicd.sh" or "./cicd.sh"

cargo build-sbf --manifest-path=./program/Cargo.toml --bpf-out-dir=./program/target/so
solana program deploy ./program/target/so/program.so
Loading