Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
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
100 changes: 96 additions & 4 deletions auction-server/build.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
use std::process::Command;
use {
anchor_lang_idl::{
convert::convert_idl,
types::{
Idl,
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 +35,85 @@ fn main() {
);
}
}


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

fn extract_account_position(idl: Idl, instruction_name: &str, account_name: &str) -> usize {
let instruction = idl
.instructions
.iter()
.find(|i| i.name == instruction_name)
.unwrap_or_else(|| panic!("Instruction {} not found in IDL", instruction_name));
instruction
.accounts
.iter()
.position(|a| match a {
IdlInstructionAccountItem::Single(a) => a.name == account_name,
IdlInstructionAccountItem::Composite(a) => a.name == account_name,
})
.unwrap_or_else(|| {
panic!(
"Account {} not found in instruction {}",
account_name, instruction_name
)
})
}

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");
println!(
"cargo:rustc-env=SUBMIT_BID_PERMISSION_ACCOUNT_POSITION={}",
extract_account_position(
express_relay_idl.clone(),
SUBMIT_BID_INSTRUCTION_SVM,
PERMISSION_ACCOUNT_SVM,
)
);
println!(
"cargo:rustc-env=SUBMIT_BID_ROUTER_ACCOUNT_POSITION={}",
extract_account_position(
express_relay_idl.clone(),
SUBMIT_BID_INSTRUCTION_SVM,
ROUTER_ACCOUNT_SVM,
)
);
}

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();
}
97 changes: 86 additions & 11 deletions auction-server/src/auction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,20 @@ use {
BidStatus,
ChainStoreEvm,
ChainStoreSvm,
ExpressRelaySvm,
PermissionKey,
SimulatedBid,
Store,
},
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 +89,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 +891,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 +906,73 @@ 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_account_svm(
accounts: &[Pubkey],
instruction: CompiledInstruction,
position: usize,
) -> Result<Pubkey, RestError> {
if instruction.accounts.len() <= position {
tracing::error!(
"Account position not found in instruction: {:?} - {}",
instruction,
position,
);
return Err(RestError::BadParameters(
"Permission account not found in submit_bid instruction".to_string(),
));
}
Ok(())

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

Ok(accounts[account_position])
}

fn extract_bid_data_svm(
express_relay_svm: ExpressRelaySvm,
accounts: &[Pubkey],
instruction: CompiledInstruction,
) -> Result<(u64, PermissionKey), 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)))?;

let permission_account = extract_account_svm(
accounts,
instruction.clone(),
express_relay_svm.permission_account_position,
)?;
let router_account = extract_account_svm(
accounts,
instruction.clone(),
express_relay_svm.router_account_position,
)?;

let concat = [permission_account.to_bytes(), router_account.to_bytes()].concat();
Ok((submit_bid_data.bid_amount, concat.into()))
}

#[tracing::instrument(skip_all)]
Expand All @@ -918,7 +987,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_key) = extract_bid_data_svm(
store.express_relay_svm.clone(),
bid.transaction.message.static_account_keys(),
submit_bid_instruction,
)?;

// TODO implement this
Err(RestError::NotImplemented)
Expand Down
12 changes: 11 additions & 1 deletion auction-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use {
state::{
ChainStoreEvm,
ChainStoreSvm,
ExpressRelaySvm,
OpportunityStore,
Store,
},
Expand Down Expand Up @@ -267,6 +268,15 @@ pub async fn start_server(run_options: RunOptions) -> anyhow::Result<()> {
};

let chains_svm = setup_chain_store_svm(config_map);
let express_relay_svm = ExpressRelaySvm {
idl: load_express_relay_idl()?,
permission_account_position: env!("SUBMIT_BID_PERMISSION_ACCOUNT_POSITION")
.parse::<usize>()
.expect("Failed to parse permission account position"),
router_account_position: env!("SUBMIT_BID_ROUTER_ACCOUNT_POSITION")
.parse::<usize>()
.expect("Failed to parse router account position"),
};

let (broadcast_sender, broadcast_receiver) =
tokio::sync::broadcast::channel(NOTIFICATIONS_CHAN_LEN);
Expand Down Expand Up @@ -312,7 +322,7 @@ pub async fn start_server(run_options: RunOptions) -> anyhow::Result<()> {
secret_key: run_options.secret_key.clone(),
access_tokens: RwLock::new(access_tokens),
metrics_recorder: setup_metrics_recorder()?,
express_relay_idl: load_express_relay_idl()?,
express_relay_svm,
});

tokio::join!(
Expand Down
9 changes: 8 additions & 1 deletion auction-server/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,13 @@ pub struct BidStatusWithId {
pub bid_status: BidStatus,
}

#[derive(Clone)]
pub struct ExpressRelaySvm {
pub idl: Idl,
pub permission_account_position: usize,
pub router_account_position: usize,
}

pub struct Store {
pub chains: HashMap<ChainId, ChainStoreEvm>,
pub chains_svm: HashMap<ChainId, ChainStoreSvm>,
Expand All @@ -311,7 +318,7 @@ pub struct Store {
pub secret_key: String,
pub access_tokens: RwLock<HashMap<models::AccessTokenToken, models::Profile>>,
pub metrics_recorder: PrometheusHandle,
pub express_relay_idl: Idl,
pub express_relay_svm: ExpressRelaySvm,
}

impl SimulatedBid {
Expand Down
Loading