Skip to content

Commit ee530d6

Browse files
authored
Add svm bid data extraction from transaction (#136)
1 parent ffbdff2 commit ee530d6

File tree

7 files changed

+204
-54
lines changed

7 files changed

+204
-54
lines changed

.github/workflows/ci-pre-commit.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ jobs:
4646
- name: Install forge dependencies 5
4747
working-directory: contracts/evm
4848
run: forge install nomad-xyz/ExcessivelySafeCall@be417ab0c26233578b8d8f3a37b87bd1fcb4e286 --no-git --no-commit
49+
- name: Install Anchor CLI
50+
run: npm install -g @coral-xyz/anchor-cli@v0.30.1
4951
- uses: pre-commit/action@v3.0.0
5052
if: ${{ github.event_name == 'pull_request' }}
5153
with:

Dockerfile

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,11 @@ COPY contracts/evm contracts/evm
77
WORKDIR /src/contracts/evm
88
RUN npm install
99

10-
# Build solana anchor
11-
FROM solanalabs/solana:v1.18.18 AS solana_build
12-
RUN apt-get update \
13-
&& apt-get install -y \
14-
apt-utils \
15-
curl \
16-
gcc \
17-
&& rm -rf /var/lib/apt/lists/*
18-
RUN curl https://sh.rustup.rs -sSf > /tmp/rustup-init.sh \
19-
&& chmod +x /tmp/rustup-init.sh \
20-
&& sh /tmp/rustup-init.sh -y \
21-
&& rm -rf /tmp/rustup-init.sh
22-
ENV PATH="/root/.cargo/bin:${PATH}"
23-
RUN ["/bin/bash", "-c", "source $HOME/.cargo/env"]
10+
FROM rust:${RUST_VERSION} AS build
11+
12+
# Latest version supporting anchor
2413
RUN rustup default nightly-2024-02-04
2514
RUN cargo install --git https://github.com/coral-xyz/anchor --tag v0.30.1 anchor-cli --locked
26-
WORKDIR /src
27-
COPY contracts/svm contracts/svm
28-
WORKDIR /src/contracts/svm
29-
RUN anchor build
30-
31-
FROM rust:${RUST_VERSION} AS build
3215

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

54-
# Add solana dependencies
55-
COPY --from=solana_build /src/contracts/svm/target/ /src/contracts/svm/target/
56-
5737
# Build auction-server
5838
WORKDIR /src
5939
COPY auction-server auction-server

auction-server/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ version = "0.10.0"
44
edition = "2021"
55
license-file = "license.txt"
66

7+
[build-dependencies]
8+
anchor-lang-idl = { version = "0.1.1", features = ["convert"] }
9+
710
[dependencies]
811
tokio = { version = "1.28", features = ["macros", "sync", "rt-multi-thread", "signal"] }
912
tokio-stream = "0.1.14"
@@ -43,7 +46,6 @@ serde_path_to_error = "0.1.16"
4346
solana-sdk = "2.0.7"
4447
bincode = "1.3.3"
4548
serde_with = "3.9.0"
46-
anchor-lang-idl = { version = "0.1.1", features = ["convert"] }
4749
anchor-lang = "0.30.1"
4850
express-relay = { path = "../contracts/svm/programs/express_relay" }
4951

auction-server/build.rs

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
1-
use std::process::Command;
1+
use {
2+
anchor_lang_idl::{
3+
convert::convert_idl,
4+
types::{
5+
Idl,
6+
IdlInstructionAccountItem,
7+
},
8+
},
9+
std::{
10+
fs,
11+
process::Command,
12+
},
13+
};
214

3-
fn main() {
15+
fn build_evm_contracts() {
416
let contract_setup = r#"
517
cd ../contracts/evm
618
forge build --via-ir
719
"#;
820
println!("cargo:rerun-if-changed=../contracts/evm");
9-
println!("cargo:rerun-if-changed=migrations");
10-
1121
// Build the contracts and generate the ABIs. This is required for abigen! macro expansions to work.
1222
let output = Command::new("sh")
1323
.args(["-c", contract_setup])
@@ -25,3 +35,85 @@ fn main() {
2535
);
2636
}
2737
}
38+
39+
40+
const SUBMIT_BID_INSTRUCTION_SVM: &str = "submit_bid";
41+
const PERMISSION_ACCOUNT_SVM: &str = "permission";
42+
const ROUTER_ACCOUNT_SVM: &str = "router";
43+
const IDL_LOCATION: &str = "../contracts/svm/target/idl/express_relay.json";
44+
45+
fn extract_account_position(idl: Idl, instruction_name: &str, account_name: &str) -> usize {
46+
let instruction = idl
47+
.instructions
48+
.iter()
49+
.find(|i| i.name == instruction_name)
50+
.unwrap_or_else(|| panic!("Instruction {} not found in IDL", instruction_name));
51+
instruction
52+
.accounts
53+
.iter()
54+
.position(|a| match a {
55+
IdlInstructionAccountItem::Single(a) => a.name == account_name,
56+
IdlInstructionAccountItem::Composite(a) => a.name == account_name,
57+
})
58+
.unwrap_or_else(|| {
59+
panic!(
60+
"Account {} not found in instruction {}",
61+
account_name, instruction_name
62+
)
63+
})
64+
}
65+
66+
fn verify_and_extract_idl_data() {
67+
let idl_json = fs::read(IDL_LOCATION).expect("Failed to read IDL JSON");
68+
let express_relay_idl =
69+
convert_idl(idl_json.as_slice()).expect("Failed to convert IDL to Rust");
70+
println!(
71+
"cargo:rustc-env=SUBMIT_BID_PERMISSION_ACCOUNT_POSITION={}",
72+
extract_account_position(
73+
express_relay_idl.clone(),
74+
SUBMIT_BID_INSTRUCTION_SVM,
75+
PERMISSION_ACCOUNT_SVM,
76+
)
77+
);
78+
println!(
79+
"cargo:rustc-env=SUBMIT_BID_ROUTER_ACCOUNT_POSITION={}",
80+
extract_account_position(
81+
express_relay_idl.clone(),
82+
SUBMIT_BID_INSTRUCTION_SVM,
83+
ROUTER_ACCOUNT_SVM,
84+
)
85+
);
86+
}
87+
88+
fn build_svm_contracts() {
89+
let contract_setup_svm = r#"
90+
cd ../contracts/svm/programs/express_relay
91+
mkdir -p ../../target/idl
92+
anchor idl build > ../../target/idl/express_relay.json
93+
"#;
94+
println!("cargo:rerun-if-changed=../contracts/svm");
95+
// Build the svm contract and generate the IDLs.
96+
let output = Command::new("sh")
97+
.args(["-c", contract_setup_svm])
98+
.output()
99+
.expect("Failed to run build svm contracts command");
100+
if !output.status.success() {
101+
panic!(
102+
"Failed to build svm contracts: {}",
103+
String::from_utf8_lossy(&output.stderr)
104+
);
105+
} else {
106+
println!(
107+
"Built all svm contracts {}",
108+
String::from_utf8_lossy(&output.stdout)
109+
);
110+
}
111+
}
112+
113+
fn main() {
114+
println!("cargo:rerun-if-changed=migrations");
115+
116+
build_evm_contracts();
117+
build_svm_contracts();
118+
verify_and_extract_idl_data();
119+
}

auction-server/src/auction.rs

Lines changed: 83 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,20 @@ use {
1919
BidStatus,
2020
ChainStoreEvm,
2121
ChainStoreSvm,
22+
ExpressRelaySvm,
23+
PermissionKey,
2224
SimulatedBid,
2325
Store,
2426
},
2527
traced_client::TracedClient,
2628
},
27-
::express_relay as express_relay_svm,
28-
anchor_lang::Discriminator,
29+
::express_relay::{
30+
self as express_relay_svm,
31+
},
32+
anchor_lang::{
33+
AnchorDeserialize,
34+
Discriminator,
35+
},
2936
anyhow::{
3037
anyhow,
3138
Result,
@@ -82,7 +89,11 @@ use {
8289
Deserializer,
8390
Serialize,
8491
},
85-
solana_sdk::transaction::VersionedTransaction,
92+
solana_sdk::{
93+
instruction::CompiledInstruction,
94+
pubkey::Pubkey,
95+
transaction::VersionedTransaction,
96+
},
8697
sqlx::types::time::OffsetDateTime,
8798
std::{
8899
result,
@@ -880,8 +891,8 @@ pub async fn run_tracker_loop(store: Arc<Store>, chain_id: String) -> Result<()>
880891
pub fn verify_submit_bid_instruction_svm(
881892
chain_store: &ChainStoreSvm,
882893
transaction: VersionedTransaction,
883-
) -> Result<(), RestError> {
884-
if transaction
894+
) -> Result<CompiledInstruction, RestError> {
895+
let submit_bid_instructions: Vec<CompiledInstruction> = transaction
885896
.message
886897
.instructions()
887898
.iter()
@@ -895,15 +906,69 @@ pub fn verify_submit_bid_instruction_svm(
895906
.data
896907
.starts_with(&express_relay_svm::instruction::SubmitBid::discriminator())
897908
})
898-
.count()
899-
!= 1
900-
{
901-
return Err(RestError::BadParameters(
909+
.cloned()
910+
.collect();
911+
912+
match submit_bid_instructions.len() {
913+
1 => Ok(submit_bid_instructions[0].clone()),
914+
_ => Err(RestError::BadParameters(
902915
"Bid has to include exactly one submit_bid instruction to Express Relay program"
903916
.to_string(),
904-
));
917+
)),
905918
}
906-
Ok(())
919+
}
920+
921+
fn extract_account_svm(
922+
accounts: &[Pubkey],
923+
instruction: CompiledInstruction,
924+
position: usize,
925+
) -> Result<Pubkey, RestError> {
926+
let account_position = instruction.accounts.get(position).ok_or_else(|| {
927+
tracing::error!(
928+
"Account position not found in instruction: {:?} - {}",
929+
instruction,
930+
position,
931+
);
932+
RestError::BadParameters("Account not found in submit_bid instruction".to_string())
933+
})?;
934+
935+
let account_position: usize = (*account_position).into();
936+
let account = accounts.get(account_position).ok_or_else(|| {
937+
tracing::error!(
938+
"Account not found in transaction accounts: {:?} - {}",
939+
accounts,
940+
account_position,
941+
);
942+
RestError::BadParameters("Account not found in transaction accounts".to_string())
943+
})?;
944+
945+
Ok(*account)
946+
}
947+
948+
fn extract_bid_data_svm(
949+
express_relay_svm: ExpressRelaySvm,
950+
accounts: &[Pubkey],
951+
instruction: CompiledInstruction,
952+
) -> Result<(u64, PermissionKey), RestError> {
953+
let discriminator = express_relay_svm::instruction::SubmitBid::discriminator();
954+
let submit_bid_data = express_relay_svm::SubmitBidArgs::try_from_slice(
955+
&instruction.data.as_slice()[discriminator.len()..],
956+
)
957+
.map_err(|e| RestError::BadParameters(format!("Invalid submit_bid instruction data: {}", e)))?;
958+
959+
let permission_account = extract_account_svm(
960+
accounts,
961+
instruction.clone(),
962+
express_relay_svm.permission_account_position,
963+
)?;
964+
let router_account = extract_account_svm(
965+
accounts,
966+
instruction.clone(),
967+
express_relay_svm.router_account_position,
968+
)?;
969+
970+
let concat = [permission_account.to_bytes(), router_account.to_bytes()].concat();
971+
Ok((submit_bid_data.bid_amount, concat.into()))
907972
}
908973

909974
#[tracing::instrument(skip_all)]
@@ -918,7 +983,13 @@ pub async fn handle_bid_svm(
918983
.get(&bid.chain_id)
919984
.ok_or(RestError::InvalidChainId)?;
920985

921-
verify_submit_bid_instruction_svm(chain_store, bid.transaction.clone())?;
986+
let submit_bid_instruction =
987+
verify_submit_bid_instruction_svm(chain_store, bid.transaction.clone())?;
988+
let (_bid_amount, _permission_key) = extract_bid_data_svm(
989+
store.express_relay_svm.clone(),
990+
bid.transaction.message.static_account_keys(),
991+
submit_bid_instruction,
992+
)?;
922993

923994
// TODO implement this
924995
Err(RestError::NotImplemented)

auction-server/src/server.rs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,12 @@ use {
2727
state::{
2828
ChainStoreEvm,
2929
ChainStoreSvm,
30+
ExpressRelaySvm,
3031
OpportunityStore,
3132
Store,
3233
},
3334
traced_client::TracedClient,
3435
},
35-
anchor_lang_idl::{
36-
convert::convert_idl,
37-
types::Idl,
38-
},
3936
anyhow::anyhow,
4037
axum_prometheus::{
4138
metrics_exporter_prometheus::{
@@ -68,7 +65,6 @@ use {
6865
},
6966
std::{
7067
collections::HashMap,
71-
fs,
7268
sync::{
7369
atomic::{
7470
AtomicBool,
@@ -232,12 +228,6 @@ async fn setup_chain_store(
232228
.collect()
233229
}
234230

235-
pub fn load_express_relay_idl() -> anyhow::Result<Idl> {
236-
let idl = fs::read("../contracts/svm/target/idl/express_relay.json")?;
237-
convert_idl(idl.as_slice())
238-
.map_err(|err| anyhow!("Failed to convert express relay idl: {:?}", err))
239-
}
240-
241231
const NOTIFICATIONS_CHAN_LEN: usize = 1000;
242232
pub async fn start_server(run_options: RunOptions) -> anyhow::Result<()> {
243233
tokio::spawn(async move {
@@ -267,6 +257,14 @@ pub async fn start_server(run_options: RunOptions) -> anyhow::Result<()> {
267257
};
268258

269259
let chains_svm = setup_chain_store_svm(config_map);
260+
let express_relay_svm = ExpressRelaySvm {
261+
permission_account_position: env!("SUBMIT_BID_PERMISSION_ACCOUNT_POSITION")
262+
.parse::<usize>()
263+
.expect("Failed to parse permission account position"),
264+
router_account_position: env!("SUBMIT_BID_ROUTER_ACCOUNT_POSITION")
265+
.parse::<usize>()
266+
.expect("Failed to parse router account position"),
267+
};
270268

271269
let (broadcast_sender, broadcast_receiver) =
272270
tokio::sync::broadcast::channel(NOTIFICATIONS_CHAN_LEN);
@@ -312,7 +310,7 @@ pub async fn start_server(run_options: RunOptions) -> anyhow::Result<()> {
312310
secret_key: run_options.secret_key.clone(),
313311
access_tokens: RwLock::new(access_tokens),
314312
metrics_recorder: setup_metrics_recorder()?,
315-
express_relay_idl: load_express_relay_idl()?,
313+
express_relay_svm,
316314
});
317315

318316
tokio::join!(

0 commit comments

Comments
 (0)