Skip to content

Add svm bid data extraction from transaction #136

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

Merged
merged 7 commits into from
Sep 9, 2024
Merged
Show file tree
Hide file tree
Changes from 4 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: 2 additions & 0 deletions .github/workflows/ci-pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ jobs:
- name: Install forge dependencies 5
working-directory: contracts/evm
run: forge install nomad-xyz/ExcessivelySafeCall@be417ab0c26233578b8d8f3a37b87bd1fcb4e286 --no-git --no-commit
- name: Install Anchor CLI
run: npm install -g @coral-xyz/anchor-cli@v0.30.1
- uses: pre-commit/action@v3.0.0
if: ${{ github.event_name == 'pull_request' }}
with:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/push-rust-services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
- v*
branches:
- main
- feat/svm-extract-bid-data
workflow_dispatch:
inputs:
dispatch_description:
Expand Down
26 changes: 3 additions & 23 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,11 @@ COPY contracts/evm contracts/evm
WORKDIR /src/contracts/evm
RUN npm install

# Build solana anchor
FROM solanalabs/solana:v1.18.18 AS solana_build
RUN apt-get update \
&& apt-get install -y \
apt-utils \
curl \
gcc \
&& rm -rf /var/lib/apt/lists/*
RUN curl https://sh.rustup.rs -sSf > /tmp/rustup-init.sh \
&& chmod +x /tmp/rustup-init.sh \
&& sh /tmp/rustup-init.sh -y \
&& rm -rf /tmp/rustup-init.sh
ENV PATH="/root/.cargo/bin:${PATH}"
RUN ["/bin/bash", "-c", "source $HOME/.cargo/env"]
FROM rust:${RUST_VERSION} AS build

# Latest version supporting anchor
RUN rustup default nightly-2024-02-04
RUN cargo install --git https://github.com/coral-xyz/anchor --tag v0.30.1 anchor-cli --locked
WORKDIR /src
COPY contracts/svm contracts/svm
WORKDIR /src/contracts/svm
RUN anchor build

FROM rust:${RUST_VERSION} AS build

# Set default toolchain
RUN rustup default nightly-2024-04-10
Expand All @@ -51,9 +34,6 @@ RUN forge install OpenZeppelin/openzeppelin-contracts-upgradeable@v4.9.6 --no-gi
RUN forge install Uniswap/permit2@0x000000000022D473030F116dDEE9F6B43aC78BA3 --no-git --no-commit
RUN forge install nomad-xyz/ExcessivelySafeCall@be417ab0c26233578b8d8f3a37b87bd1fcb4e286 --no-git --no-commit

# Add solana dependencies
COPY --from=solana_build /src/contracts/svm/target/ /src/contracts/svm/target/

# Build auction-server
WORKDIR /src
COPY auction-server auction-server
Expand Down
3 changes: 3 additions & 0 deletions auction-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ version = "0.10.0"
edition = "2021"
license-file = "license.txt"

[build-dependencies]
anchor-lang-idl = { version = "0.1.1", features = ["convert"] }

[dependencies]
tokio = { version = "1.28", features = ["macros", "sync", "rt-multi-thread", "signal"] }
tokio-stream = "0.1.14"
Expand Down
86 changes: 82 additions & 4 deletions auction-server/build.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
use std::process::Command;
use {
anchor_lang_idl::{
convert::convert_idl,
types::IdlInstructionAccountItem,
},
std::{
fs,
process::Command,
},
};

fn main() {
fn build_evm_contracts() {
let contract_setup = r#"
cd ../contracts/evm
forge build --via-ir
"#;
println!("cargo:rerun-if-changed=../contracts/evm");
println!("cargo:rerun-if-changed=migrations");

// Build the contracts and generate the ABIs. This is required for abigen! macro expansions to work.
let output = Command::new("sh")
.args(["-c", contract_setup])
Expand All @@ -25,3 +32,74 @@ fn main() {
);
}
}


const SUBMIT_BID_INSTRUCTION_SVM: &str = "submit_bid";
const PERMISSION_ACCOUNT_SVM: &str = "permission";
const IDL_LOCATION: &str = "../contracts/svm/target/idl/express_relay.json";

fn verify_and_extract_idl_data() {
let idl_json = fs::read(IDL_LOCATION).expect("Failed to read IDL JSON");
let express_relay_idl =
convert_idl(idl_json.as_slice()).expect("Failed to convert IDL to Rust");

let position = match express_relay_idl
.instructions
.iter()
.find(|i| i.name == SUBMIT_BID_INSTRUCTION_SVM)
{
Some(instruction) => {
match instruction.accounts.iter().position(|a| match a {
IdlInstructionAccountItem::Single(a) => a.name == PERMISSION_ACCOUNT_SVM,
IdlInstructionAccountItem::Composite(a) => a.name == PERMISSION_ACCOUNT_SVM,
}) {
Some(position) => position,
None => panic!(
"{} account not found in {} instruction",
PERMISSION_ACCOUNT_SVM, SUBMIT_BID_INSTRUCTION_SVM
),
}
}
None => panic!(
"{} instruction not found in IDL",
SUBMIT_BID_INSTRUCTION_SVM,
),
};
println!(
"cargo:rustc-env=SUBMIT_BID_PERMISSION_ACCOUNT_POSITION={}",
position
);
}

fn build_svm_contracts() {
let contract_setup_svm = r#"
cd ../contracts/svm/programs/express_relay
mkdir -p ../../target/idl
anchor idl build > ../../target/idl/express_relay.json
"#;
println!("cargo:rerun-if-changed=../contracts/svm");
// Build the svm contract and generate the IDLs.
let output = Command::new("sh")
.args(["-c", contract_setup_svm])
.output()
.expect("Failed to run build svm contracts command");
if !output.status.success() {
panic!(
"Failed to build svm contracts: {}",
String::from_utf8_lossy(&output.stderr)
);
} else {
println!(
"Built all svm contracts {}",
String::from_utf8_lossy(&output.stdout)
);
}
}

fn main() {
println!("cargo:rerun-if-changed=migrations");

build_evm_contracts();
build_svm_contracts();
verify_and_extract_idl_data();
}
75 changes: 64 additions & 11 deletions auction-server/src/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@ use {
},
traced_client::TracedClient,
},
::express_relay as express_relay_svm,
anchor_lang::Discriminator,
::express_relay::{
self as express_relay_svm,
},
anchor_lang::{
AnchorDeserialize,
Discriminator,
},
anyhow::{
anyhow,
Result,
Expand Down Expand Up @@ -82,7 +87,11 @@ use {
Deserializer,
Serialize,
},
solana_sdk::transaction::VersionedTransaction,
solana_sdk::{
instruction::CompiledInstruction,
pubkey::Pubkey,
transaction::VersionedTransaction,
},
sqlx::types::time::OffsetDateTime,
std::{
result,
Expand Down Expand Up @@ -880,8 +889,8 @@ pub async fn run_tracker_loop(store: Arc<Store>, chain_id: String) -> Result<()>
pub fn verify_submit_bid_instruction_svm(
chain_store: &ChainStoreSvm,
transaction: VersionedTransaction,
) -> Result<(), RestError> {
if transaction
) -> Result<CompiledInstruction, RestError> {
let submit_bid_instructions: Vec<CompiledInstruction> = transaction
.message
.instructions()
.iter()
Expand All @@ -895,15 +904,53 @@ pub fn verify_submit_bid_instruction_svm(
.data
.starts_with(&express_relay_svm::instruction::SubmitBid::discriminator())
})
.count()
!= 1
{
return Err(RestError::BadParameters(
.cloned()
.collect();

match submit_bid_instructions.len() {
1 => Ok(submit_bid_instructions[0].clone()),
_ => Err(RestError::BadParameters(
"Bid has to include exactly one submit_bid instruction to Express Relay program"
.to_string(),
)),
}
}

fn extract_bid_data_svm(
permission_account_position: usize,
accounts: &[Pubkey],
instruction: CompiledInstruction,
) -> Result<(u64, Pubkey), RestError> {
let discriminator = express_relay_svm::instruction::SubmitBid::discriminator();
let submit_bid_data = express_relay_svm::SubmitBidArgs::try_from_slice(
&instruction.data.as_slice()[discriminator.len()..],
)
.map_err(|e| RestError::BadParameters(format!("Invalid submit_bid instruction data: {}", e)))?;

if instruction.accounts.len() <= permission_account_position {
tracing::error!(
"Permission account not found in submit_bid instruction: {:?} - {}",
instruction.accounts,
permission_account_position,
);
return Err(RestError::BadParameters(
"Permission account not found in submit_bid instruction".to_string(),
));
}
Ok(())

let account_position = instruction.accounts[permission_account_position] as usize;
if account_position >= accounts.len() {
tracing::error!(
"Permission account not found in transaction accounts: {:?} - {}",
accounts,
account_position,
);
return Err(RestError::BadParameters(
"Permission account not found in transaction accounts".to_string(),
));
}

Ok((submit_bid_data.bid_amount, accounts[account_position]))
}

#[tracing::instrument(skip_all)]
Expand All @@ -918,7 +965,13 @@ pub async fn handle_bid_svm(
.get(&bid.chain_id)
.ok_or(RestError::InvalidChainId)?;

verify_submit_bid_instruction_svm(chain_store, bid.transaction.clone())?;
let submit_bid_instruction =
verify_submit_bid_instruction_svm(chain_store, bid.transaction.clone())?;
let (_bid_amount, _permission) = extract_bid_data_svm(
store.permission_account_position,
bid.transaction.message.static_account_keys(),
submit_bid_instruction,
)?;

// TODO implement this
Err(RestError::NotImplemented)
Expand Down
3 changes: 3 additions & 0 deletions auction-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,9 @@ pub async fn start_server(run_options: RunOptions) -> anyhow::Result<()> {
access_tokens: RwLock::new(access_tokens),
metrics_recorder: setup_metrics_recorder()?,
express_relay_idl: load_express_relay_idl()?,
permission_account_position: env!("SUBMIT_BID_PERMISSION_ACCOUNT_POSITION")
.parse::<usize>()
.expect("Failed to parse permission account position"),
});

tokio::join!(
Expand Down
31 changes: 16 additions & 15 deletions auction-server/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,21 +297,22 @@ pub struct BidStatusWithId {
}

pub struct Store {
pub chains: HashMap<ChainId, ChainStoreEvm>,
pub chains_svm: HashMap<ChainId, ChainStoreSvm>,
pub bids: RwLock<HashMap<AuctionKey, Vec<SimulatedBid>>>,
pub event_sender: broadcast::Sender<UpdateEvent>,
pub opportunity_store: OpportunityStore,
pub relayer: LocalWallet,
pub ws: WsState,
pub db: sqlx::PgPool,
pub task_tracker: TaskTracker,
pub auction_lock: Mutex<HashMap<AuctionKey, AuctionLock>>,
pub submitted_auctions: RwLock<HashMap<ChainId, Vec<models::Auction>>>,
pub secret_key: String,
pub access_tokens: RwLock<HashMap<models::AccessTokenToken, models::Profile>>,
pub metrics_recorder: PrometheusHandle,
pub express_relay_idl: Idl,
pub chains: HashMap<ChainId, ChainStoreEvm>,
pub chains_svm: HashMap<ChainId, ChainStoreSvm>,
pub bids: RwLock<HashMap<AuctionKey, Vec<SimulatedBid>>>,
pub event_sender: broadcast::Sender<UpdateEvent>,
pub opportunity_store: OpportunityStore,
pub relayer: LocalWallet,
pub ws: WsState,
pub db: sqlx::PgPool,
pub task_tracker: TaskTracker,
pub auction_lock: Mutex<HashMap<AuctionKey, AuctionLock>>,
pub submitted_auctions: RwLock<HashMap<ChainId, Vec<models::Auction>>>,
pub secret_key: String,
pub access_tokens: RwLock<HashMap<models::AccessTokenToken, models::Profile>>,
pub metrics_recorder: PrometheusHandle,
pub express_relay_idl: Idl,
pub permission_account_position: usize,
}

impl SimulatedBid {
Expand Down
Loading