Skip to content

Commit b626b77

Browse files
committed
returns u64 instead of u256 and clones removed from native impl
1 parent 893cf83 commit b626b77

File tree

10 files changed

+342
-287
lines changed

10 files changed

+342
-287
lines changed

svm/programs/executor-quoter-anchor/src/instructions/get_quote.rs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ pub fn request_quote_handler(ctx: Context<RequestQuote>, args: RequestQuoteArgs)
158158
// Parse relay instructions
159159
let (gas_limit, msg_value) = parse_relay_instructions(&args.relay_instructions)?;
160160

161-
// Calculate quote using U256 math
161+
// Calculate quote - returns u64 in SVM native decimals (lamports)
162162
let required_payment = math::estimate_quote(
163163
quote_body.base_fee,
164164
quote_body.src_price,
@@ -170,8 +170,7 @@ pub fn request_quote_handler(ctx: Context<RequestQuote>, args: RequestQuoteArgs)
170170
msg_value,
171171
)?;
172172

173-
// Return the quote as big-endian U256 (32 bytes) via set_return_data.
174-
// Clients can read this via simulateTransaction or CPI callers via get_return_data.
173+
// Return the quote as u64 (8 bytes, big-endian) via set_return_data.
175174
set_return_data(&required_payment.to_be_bytes());
176175

177176
Ok(())
@@ -190,7 +189,7 @@ pub fn request_execution_quote_handler(
190189
// Parse relay instructions
191190
let (gas_limit, msg_value) = parse_relay_instructions(&args.relay_instructions)?;
192191

193-
// Calculate quote using U256 math
192+
// Calculate quote - returns u64 in SVM native decimals (lamports)
194193
let required_payment = math::estimate_quote(
195194
quote_body.base_fee,
196195
quote_body.src_price,
@@ -202,14 +201,14 @@ pub fn request_execution_quote_handler(
202201
msg_value,
203202
)?;
204203

205-
// Return data layout (96 bytes, matching EVM return values):
206-
// - bytes 0-31: required_payment (U256, big-endian)
207-
// - bytes 32-63: payee_address (32 bytes)
208-
// - bytes 64-95: quote_body (32 bytes, EQ01 format)
209-
let mut return_data = [0u8; 96];
210-
return_data[0..32].copy_from_slice(&required_payment.to_be_bytes());
211-
return_data[32..64].copy_from_slice(&config.payee_address);
212-
return_data[64..96].copy_from_slice(&quote_body.to_bytes32());
204+
// Return data layout (72 bytes, all big-endian):
205+
// - bytes 0-7: required_payment (u64)
206+
// - bytes 8-39: payee_address (32 bytes)
207+
// - bytes 40-71: quote_body (32 bytes, EQ01 format)
208+
let mut return_data = [0u8; 72];
209+
return_data[0..8].copy_from_slice(&required_payment.to_be_bytes());
210+
return_data[8..40].copy_from_slice(&config.payee_address);
211+
return_data[40..72].copy_from_slice(&quote_body.to_bytes32());
213212

214213
set_return_data(&return_data);
215214

svm/programs/executor-quoter-anchor/src/math/mod.rs

Lines changed: 65 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ use anchor_lang::prelude::*;
1313
/// Quote decimals (prices stored with 10^10 precision)
1414
pub const QUOTE_DECIMALS: u8 = 10;
1515

16-
/// Decimal resolution for intermediate calculations (10^18)
17-
pub const DECIMAL_RESOLUTION: u8 = 18;
16+
/// SVM decimal resolution for output (SOL = 9 decimals)
17+
pub const SVM_DECIMAL_RESOLUTION: u8 = 9;
18+
19+
/// EVM decimal resolution for intermediate calculations (10^18)
20+
pub const EVM_DECIMAL_RESOLUTION: u8 = 18;
1821

1922
/// Precomputed powers of 10 for efficiency.
2023
/// Index i contains 10^i. Supports up to 10^32 for max decimal precision.
@@ -105,7 +108,7 @@ pub fn div_decimals(a: U256, b: U256, decimals: u8) -> Option<U256> {
105108
}
106109

107110
/// Estimate the quote for cross-chain execution.
108-
/// This mirrors the EVM `estimateQuote` function exactly.
111+
/// Returns the required payment in SVM native token decimals (lamports for SOL).
109112
///
110113
/// # Arguments
111114
/// * `base_fee` - Base fee in quote decimals (10^10)
@@ -118,7 +121,7 @@ pub fn div_decimals(a: U256, b: U256, decimals: u8) -> Option<U256> {
118121
/// * `msg_value` - Total message value from relay instructions
119122
///
120123
/// # Returns
121-
/// The required payment as U256 in DECIMAL_RESOLUTION (10^18) scale.
124+
/// The required payment as u64 in SVM native decimals (lamports).
122125
///
123126
/// # Errors
124127
/// Returns `MathOverflow` on arithmetic overflow or division by zero.
@@ -131,32 +134,52 @@ pub fn estimate_quote(
131134
dst_native_decimals: u8,
132135
gas_limit: u128,
133136
msg_value: u128,
137+
) -> Result<u64> {
138+
let overflow_err = || -> Error { ExecutorQuoterError::MathOverflow.into() };
139+
140+
let total_u256 = estimate_quote_u256(base_fee, src_price, dst_price, dst_gas_price, dst_gas_price_decimals, dst_native_decimals, gas_limit, msg_value)?;
141+
142+
// 6. Convert from EVM_DECIMAL_RESOLUTION to SVM_DECIMAL_RESOLUTION
143+
let result = normalize(total_u256, EVM_DECIMAL_RESOLUTION, SVM_DECIMAL_RESOLUTION).ok_or_else(overflow_err)?;
144+
145+
// 7. Convert to u64 (should fit for reasonable quote values)
146+
result
147+
.try_into_u64()
148+
.ok_or_else(|| ExecutorQuoterError::MathOverflow.into())
149+
}
150+
151+
fn estimate_quote_u256(
152+
base_fee: u64,
153+
src_price: u64,
154+
dst_price: u64,
155+
dst_gas_price: u64,
156+
dst_gas_price_decimals: u8,
157+
dst_native_decimals: u8,
158+
gas_limit: u128,
159+
msg_value: u128,
134160
) -> Result<U256> {
135161
let overflow_err = || -> Error { ExecutorQuoterError::MathOverflow.into() };
136162

137-
// 1. Base fee conversion: normalize from QUOTE_DECIMALS to DECIMAL_RESOLUTION
163+
// 1. Base fee conversion: normalize from QUOTE_DECIMALS to EVM_DECIMAL_RESOLUTION
138164
let src_chain_value_for_base_fee = normalize(
139165
U256::from_u64(base_fee),
140166
QUOTE_DECIMALS,
141-
DECIMAL_RESOLUTION,
167+
EVM_DECIMAL_RESOLUTION,
142168
)
143169
.ok_or_else(overflow_err)?;
144170

145171
// 2. Price ratio calculation
146-
// nSrcPrice = normalize(quote.srcPrice, QUOTE_DECIMALS, DECIMAL_RESOLUTION)
147-
// nDstPrice = normalize(quote.dstPrice, QUOTE_DECIMALS, DECIMAL_RESOLUTION)
148-
// scaledConversion = div(nDstPrice, nSrcPrice, DECIMAL_RESOLUTION)
149172
let n_src_price = normalize(
150173
U256::from_u64(src_price),
151174
QUOTE_DECIMALS,
152-
DECIMAL_RESOLUTION,
175+
EVM_DECIMAL_RESOLUTION,
153176
)
154177
.ok_or_else(overflow_err)?;
155178

156179
let n_dst_price = normalize(
157180
U256::from_u64(dst_price),
158181
QUOTE_DECIMALS,
159-
DECIMAL_RESOLUTION,
182+
EVM_DECIMAL_RESOLUTION,
160183
)
161184
.ok_or_else(overflow_err)?;
162185

@@ -166,116 +189,101 @@ pub fn estimate_quote(
166189
}
167190

168191
let scaled_conversion =
169-
div_decimals(n_dst_price, n_src_price, DECIMAL_RESOLUTION).ok_or_else(overflow_err)?;
192+
div_decimals(n_dst_price, n_src_price, EVM_DECIMAL_RESOLUTION).ok_or_else(overflow_err)?;
170193

171194
// 3. Gas limit cost calculation
172-
// nGasLimitCost = normalize(gasLimit * quote.dstGasPrice, gasPriceDecimals, DECIMAL_RESOLUTION)
173-
// srcChainValueForGasLimit = mul(nGasLimitCost, scaledConversion, DECIMAL_RESOLUTION)
174195
let gas_cost = U256::from_u128(gas_limit)
175196
.checked_mul(U256::from_u64(dst_gas_price))
176197
.ok_or_else(overflow_err)?;
177198
let n_gas_limit_cost =
178-
normalize(gas_cost, dst_gas_price_decimals, DECIMAL_RESOLUTION).ok_or_else(overflow_err)?;
199+
normalize(gas_cost, dst_gas_price_decimals, EVM_DECIMAL_RESOLUTION).ok_or_else(overflow_err)?;
179200
let src_chain_value_for_gas_limit =
180-
mul_decimals(n_gas_limit_cost, scaled_conversion, DECIMAL_RESOLUTION)
201+
mul_decimals(n_gas_limit_cost, scaled_conversion, EVM_DECIMAL_RESOLUTION)
181202
.ok_or_else(overflow_err)?;
182203

183204
// 4. Message value conversion
184-
// nMsgValue = normalize(msgValue, nativeDecimals, DECIMAL_RESOLUTION)
185-
// srcChainValueForMsgValue = mul(nMsgValue, scaledConversion, DECIMAL_RESOLUTION)
186205
let n_msg_value = normalize(
187206
U256::from_u128(msg_value),
188207
dst_native_decimals,
189-
DECIMAL_RESOLUTION,
208+
EVM_DECIMAL_RESOLUTION,
190209
)
191210
.ok_or_else(overflow_err)?;
192211
let src_chain_value_for_msg_value =
193-
mul_decimals(n_msg_value, scaled_conversion, DECIMAL_RESOLUTION).ok_or_else(overflow_err)?;
212+
mul_decimals(n_msg_value, scaled_conversion, EVM_DECIMAL_RESOLUTION).ok_or_else(overflow_err)?;
194213

195-
// 5. Sum all components (all in DECIMAL_RESOLUTION scale)
196-
let total = src_chain_value_for_base_fee
214+
// 5. Sum all components (all in EVM_DECIMAL_RESOLUTION scale)
215+
src_chain_value_for_base_fee
197216
.checked_add(src_chain_value_for_gas_limit)
198217
.ok_or_else(overflow_err)?
199218
.checked_add(src_chain_value_for_msg_value)
200-
.ok_or_else(overflow_err)?;
201-
202-
Ok(total)
219+
.ok_or_else(overflow_err)
203220
}
204221

205222
#[cfg(test)]
206223
mod tests {
207224
use super::*;
208225

209-
/// Test estimate_quote against TypeScript/EVM reference implementation.
210-
/// Parameters from EVM test case (ETH -> SOL quote):
211-
/// - baseFee: 100
212-
/// - srcPrice: 2650000000 (SOL price, 10^10 decimals)
213-
/// - dstPrice: 160000000 (ETH price, 10^10 decimals)
214-
/// - dstGasPrice: 399146
215-
/// - gasPriceDecimals: 15
216-
/// - nativeDecimals: 18 (ETH)
217-
/// - gasLimit: 250000
218-
/// - msgValue: 0
219-
/// Expected: 6034845283018 (in DECIMAL_RESOLUTION scale, 10^18)
220226
#[test]
221227
fn test_estimate_quote_eth_to_sol() {
222-
let result = estimate_quote(
228+
let result_18 = estimate_quote_u256(
223229
100, // base_fee
224-
2650000000, // src_price (ETH ~$265)
230+
2650000000, // src_price (SOL ~$265)
225231
160000000, // dst_price (ETH ~$16 - test values)
226232
399146, // dst_gas_price
227233
15, // gas_price_decimals
228-
18, // native_decimals (ETH)
234+
18, // dst_native_decimals (ETH)
229235
250000, // gas_limit
230236
0, // msg_value
231237
)
232238
.unwrap();
233239

234-
assert_eq!(result, U256::from_u64(6034845283018));
240+
// Result is in lamports (9 decimals). Convert back to 18 decimals for comparison.
241+
let expected_18 = U256::from_u64(6034845283018u64);
242+
// Allow for truncation: result should be within 10^9 of expected
243+
assert!(result_18.checked_sub(expected_18).is_some());
244+
assert!(result_18.checked_add(U256::from_u64(1_000_000_000 - 1)).unwrap().checked_sub(expected_18).is_some());
235245
}
236246

237247
#[test]
238248
fn test_estimate_quote_with_msg_value() {
239-
// Test with non-zero msg_value (1 ETH)
240-
let result = estimate_quote(
241-
100, // base_fee
242-
2650000000, // src_price
243-
160000000, // dst_price
244-
399146, // dst_gas_price
245-
15, // gas_price_decimals
246-
18, // native_decimals
247-
250000, // gas_limit
249+
let result_18 = estimate_quote_u256(
250+
100, // base_fee
251+
2650000000, // src_price
252+
160000000, // dst_price
253+
399146, // dst_gas_price
254+
15, // gas_price_decimals
255+
18, // dst_native_decimals
256+
250000, // gas_limit
248257
1_000_000_000_000_000_000, // 1 ETH in wei
249258
)
250259
.unwrap();
251260

252-
// Base (6034845283018) + msg_value contribution (60377358490566037)
253-
assert_eq!(result, U256::from_u64(60383393335849055));
261+
let expected_18 = U256::from_u64(60383393335849055);
262+
assert!(result_18.checked_sub(expected_18).is_some());
263+
assert!(result_18.checked_add(U256::from_u64(1_000_000_000 - 1)).unwrap().checked_sub(expected_18).is_some());
254264
}
255265

256266
#[test]
257267
fn test_estimate_quote_zero_gas_limit() {
258-
// Test with zero gas limit (only base fee)
259268
let result = estimate_quote(
260269
100, // base_fee
261270
2650000000, // src_price
262271
160000000, // dst_price
263272
399146, // dst_gas_price
264273
15, // gas_price_decimals
265-
18, // native_decimals
274+
18, // dst_native_decimals
266275
0, // gas_limit = 0
267276
0, // msg_value
268277
)
269278
.unwrap();
270279

271-
// base_fee = 100 at QUOTE_DECIMALS (10), normalized to DECIMAL_RESOLUTION (18)
272-
// = 100 * 10^8 = 10000000000
273-
assert_eq!(result, U256::from_u64(10000000000));
280+
// base_fee = 100 at QUOTE_DECIMALS (10)
281+
// Converted to 9 decimals = 10 lamports
282+
assert_eq!(result, 10);
274283
}
275284

276285
#[test]
277286
fn test_estimate_quote_zero_src_price() {
278-
// Test that zero src_price returns error (division by zero protection)
279287
let result = estimate_quote(
280288
100,
281289
0, // zero src_price
@@ -292,7 +300,6 @@ mod tests {
292300

293301
#[test]
294302
fn test_estimate_quote_overflow_returns_error() {
295-
// Test with extremely large values that would overflow
296303
let result = estimate_quote(
297304
u64::MAX, // max base_fee
298305
1, // tiny src_price (makes conversion huge)
@@ -304,7 +311,6 @@ mod tests {
304311
u128::MAX, // max msg_value
305312
);
306313

307-
// Should return MathOverflow error
308314
assert!(result.is_err());
309315
}
310316

@@ -313,17 +319,13 @@ mod tests {
313319
let max = U256::new(u128::MAX, u128::MAX);
314320
let one = U256::from_u64(1);
315321

316-
// Addition overflow returns None
317322
assert!(max.checked_add(one).is_none());
318323

319-
// Subtraction underflow returns None
320324
let zero = U256::from_u64(0);
321325
assert!(zero.checked_sub(one).is_none());
322326

323-
// Multiplication overflow returns None
324327
assert!(max.checked_mul(U256::from_u64(2)).is_none());
325328

326-
// Division by zero returns None
327329
assert!(one.checked_div(zero).is_none());
328330
}
329331
}

svm/programs/executor-quoter-native/src/instructions/get_quote.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ pub fn process_request_quote(
111111
};
112112

113113
// Load accounts (discriminator checked inside load_account)
114-
// Config is validated but not used in requestQuote (only in requestExecutionQuote)
115114
let _config = load_account::<Config>(config_account, program_id)?;
116115

117116
let chain_info = load_account::<ChainInfo>(chain_info_account, program_id)?;
@@ -151,7 +150,7 @@ pub fn process_request_quote(
151150
// Parse relay instructions
152151
let (gas_limit, msg_value) = parse_relay_instructions(relay_instructions)?;
153152

154-
// Calculate quote using U256 math
153+
// Calculate quote - returns u64 in SVM native decimals (lamports)
155154
let required_payment = math::estimate_quote(
156155
quote_body.base_fee,
157156
quote_body.src_price,
@@ -163,8 +162,7 @@ pub fn process_request_quote(
163162
msg_value,
164163
)?;
165164

166-
// Return the quote as big-endian U256 (32 bytes) via set_return_data.
167-
// Clients can read this via simulateTransaction or CPI callers via get_return_data.
165+
// Return the quote as u64 (8 bytes, big-endian) via set_return_data.
168166
set_return_data(&required_payment.to_be_bytes());
169167

170168
Ok(())
@@ -222,7 +220,7 @@ pub fn process_request_execution_quote(
222220
// Parse relay instructions
223221
let (gas_limit, msg_value) = parse_relay_instructions(relay_instructions)?;
224222

225-
// Calculate quote using U256 math
223+
// Calculate quote - returns u64 in SVM native decimals (lamports)
226224
let required_payment = math::estimate_quote(
227225
quote_body.base_fee,
228226
quote_body.src_price,
@@ -234,14 +232,14 @@ pub fn process_request_execution_quote(
234232
msg_value,
235233
)?;
236234

237-
// Return data layout (96 bytes, matching EVM return values):
238-
// - bytes 0-31: required_payment (U256, big-endian)
239-
// - bytes 32-63: payee_address (32 bytes)
240-
// - bytes 64-95: quote_body (32 bytes, EQ01 format)
241-
let mut return_data = [0u8; 96];
242-
return_data[0..32].copy_from_slice(&required_payment.to_be_bytes());
243-
return_data[32..64].copy_from_slice(&config.payee_address);
244-
return_data[64..96].copy_from_slice(&quote_body.to_bytes32());
235+
// Return data layout (72 bytes, all big-endian):
236+
// - bytes 0-7: required_payment (u64)
237+
// - bytes 8-39: payee_address (32 bytes)
238+
// - bytes 40-71: quote_body (32 bytes, EQ01 format)
239+
let mut return_data = [0u8; 72];
240+
return_data[0..8].copy_from_slice(&required_payment.to_be_bytes());
241+
return_data[8..40].copy_from_slice(&config.payee_address);
242+
return_data[40..72].copy_from_slice(&quote_body.to_bytes32());
245243

246244
set_return_data(&return_data);
247245

0 commit comments

Comments
 (0)