use df_hyperliquid_core::client::evm::abi::Token;
use df_hyperliquid_core::client::evm::rlp;
use crate::bridge::message::{Message, MessageKind};
use crate::client::read::one;
use crate::client::{Arg, Call, Method, Network, Read};
use crate::utils::{Address, keccak256};
use crate::{B256, U256};
pub const CREATE_RETRYABLE_TICKET: Method =
Method::new("createRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,bytes)", "uint256");
pub const UNSAFE_CREATE_RETRYABLE_TICKET: Method = Method::new(
"unsafeCreateRetryableTicket(address,uint256,uint256,address,address,uint256,uint256,bytes)",
"uint256",
);
pub const CALCULATE_RETRYABLE_SUBMISSION_FEE: Method =
Method::new("calculateRetryableSubmissionFee(uint256,uint256)", "uint256");
pub const SUBMIT_RETRYABLE_TX: u8 = 0x69;
const SUBMISSION_PER_BYTE: u64 = 6;
const SUBMISSION_BASE: u64 = 1_400;
#[must_use]
pub fn submission_fee(data_length: usize, base_fee: U256) -> U256 {
let units = (data_length as u64).saturating_mul(SUBMISSION_PER_BYTE).saturating_add(SUBMISSION_BASE);
base_fee.saturating_mul(U256::from_u64(units))
}
#[must_use]
pub fn submission_fee_read(network: Network, data_length: usize, base_fee: U256) -> Read<U256> {
let args = [Arg::u64(data_length as u64), Arg::Uint(base_fee)];
Read::new(network.contracts().inbox, &CALCULATE_RETRYABLE_SUBMISSION_FEE, &args, |answer| {
one(&CALCULATE_RETRYABLE_SUBMISSION_FEE, answer, Token::as_u256)
})
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Retryable {
pub to: Address,
pub l2_call_value: U256,
pub max_submission_cost: U256,
pub excess_fee_refund_address: Address,
pub call_value_refund_address: Address,
pub gas_limit: U256,
pub max_fee_per_gas: U256,
pub data: Vec<u8>,
}
impl Retryable {
#[must_use]
pub fn deposit(&self) -> Option<U256> {
self.gas_limit
.checked_mul(self.max_fee_per_gas)?
.checked_add(self.max_submission_cost)?
.checked_add(self.l2_call_value)
}
fn args(&self) -> [Arg<'_>; 8] {
[
Arg::Address(self.to),
Arg::Uint(self.l2_call_value),
Arg::Uint(self.max_submission_cost),
Arg::Address(self.excess_fee_refund_address),
Arg::Address(self.call_value_refund_address),
Arg::Uint(self.gas_limit),
Arg::Uint(self.max_fee_per_gas),
Arg::Bytes(&self.data),
]
}
#[must_use]
pub fn call(&self, network: Network) -> Option<Call> {
Some(Call {
to: Some(network.contracts().inbox),
value: self.deposit()?,
data: CREATE_RETRYABLE_TICKET.calldata(&self.args()),
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Ticket {
pub message_number: U256,
pub from: Address,
pub l1_base_fee: U256,
pub deposit: U256,
pub retryable: Retryable,
}
impl Ticket {
#[must_use]
pub fn from_message(message: &Message) -> Option<Ticket> {
if message.kind != MessageKind::SubmitRetryable {
return None;
}
let data = message.data.as_deref()?;
let word = |at: usize| data.get(at * 32..at * 32 + 32).and_then(U256::from_be_slice);
let address = |at: usize| word(at).map(B256::from).and_then(|word| word.to_address());
let length = usize::try_from(word(8)?.to_u64()?).ok()?;
let call = data.get(9 * 32..)?;
if call.len() != length {
return None;
}
Some(Ticket {
message_number: message.number,
from: message.sender,
l1_base_fee: message.base_fee_l1,
deposit: word(2)?,
retryable: Retryable {
to: address(0)?,
l2_call_value: word(1)?,
max_submission_cost: word(3)?,
excess_fee_refund_address: address(4)?,
call_value_refund_address: address(5)?,
gas_limit: word(6)?,
max_fee_per_gas: word(7)?,
data: call.to_vec(),
},
})
}
#[must_use]
pub fn id(&self, chain_id: u64) -> B256 {
let ticket = &self.retryable;
let to = match ticket.to.is_zero() {
true => rlp::bytes(&[]),
false => rlp::bytes(ticket.to.bytes()),
};
let fields = [
rlp::uint(u128::from(chain_id)),
rlp::bytes(&self.message_number.to_be_bytes()),
rlp::bytes(self.from.bytes()),
rlp::u256(self.l1_base_fee),
rlp::u256(self.deposit),
rlp::u256(ticket.max_fee_per_gas),
rlp::u256(ticket.gas_limit),
to,
rlp::u256(ticket.l2_call_value),
rlp::bytes(ticket.call_value_refund_address.bytes()),
rlp::u256(ticket.max_submission_cost),
rlp::bytes(ticket.excess_fee_refund_address.bytes()),
rlp::bytes(&ticket.data),
];
let mut typed = vec![SUBMIT_RETRYABLE_TX];
typed.extend(rlp::list(&fields));
B256(keccak256(&typed))
}
}
#[cfg(test)]
#[path = "tests/retryable.rs"]
mod tests;