Skip to content
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/[email protected]
- uses: pre-commit/[email protected]
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/[email protected] --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