use crate::{Address, Market, Offer, Sig, Word, U256};
pub const TAKE_SELECTOR: [u8; 4] = [0x6a, 0x14, 0xc9, 0xef];
pub const CANCEL_ROOT_SELECTOR: [u8; 4] = [0xbb, 0x1f, 0x12, 0xaa];
pub const SET_IS_ROOT_RATIFIED_SELECTOR: [u8; 4] = [0x2f, 0xd0, 0xe4, 0x5d];
pub const SUPPLY_COLLATERAL_SELECTOR: [u8; 4] = [0x32, 0x29, 0x2d, 0x96];
pub const WITHDRAW_SELECTOR: [u8; 4] = [0xa6, 0xdb, 0x17, 0x50];
pub const SET_IS_AUTHORIZED_SELECTOR: [u8; 4] = [0xb7, 0x82, 0x12, 0xab];
pub const ERC20_APPROVE_SELECTOR: [u8; 4] = [0x09, 0x5e, 0xa7, 0xb3];
pub const BUNDLE_REPAY_WITHDRAW_SELECTOR: [u8; 4] = [0x7f, 0x19, 0xab, 0xac];
pub const BUNDLE_BUY_UNITS_SELECTOR: [u8; 4] = [0xff, 0xb4, 0x58, 0x4e];
pub const BUNDLE_SELL_UNITS_SELECTOR: [u8; 4] = [0xb7, 0x07, 0xc6, 0x08];
pub const BUNDLE_BUY_ASSETS_SELECTOR: [u8; 4] = [0xa8, 0x5d, 0x52, 0xe5];
pub const BUNDLE_SELL_ASSETS_SELECTOR: [u8; 4] = [0x2f, 0xbc, 0x23, 0xf8];
#[inline]
fn addr_word(a: &Address) -> Word {
let mut w = [0u8; 32];
w[12..].copy_from_slice(a);
w
}
#[inline]
fn bool_word(b: bool) -> Word {
let mut w = [0u8; 32];
w[31] = b as u8;
w
}
#[inline]
fn u128_word(x: u128) -> Word {
let mut w = [0u8; 32];
w[16..].copy_from_slice(&x.to_be_bytes());
w
}
#[inline]
fn usize_word(x: usize) -> Word {
let mut w = [0u8; 32];
w[24..].copy_from_slice(&(x as u64).to_be_bytes());
w
}
enum Value {
Word(Word),
Bytes(Vec<u8>),
Array(Vec<Value>),
Tuple(Vec<Value>),
}
impl Value {
fn is_dynamic(&self) -> bool {
match self {
Value::Word(_) => false,
Value::Bytes(_) | Value::Array(_) => true,
Value::Tuple(items) => items.iter().any(Value::is_dynamic),
}
}
fn static_size(&self) -> usize {
match self {
Value::Word(_) => 32,
Value::Tuple(items) => items.iter().map(Value::static_size).sum(),
Value::Bytes(_) | Value::Array(_) => {
unreachable!("static_size on a dynamic value")
}
}
}
fn encode(&self) -> Vec<u8> {
match self {
Value::Word(w) => w.to_vec(),
Value::Bytes(b) => {
let mut out = usize_word(b.len()).to_vec();
out.extend_from_slice(b);
let rem = b.len() % 32;
if rem != 0 {
out.extend(std::iter::repeat(0u8).take(32 - rem));
}
out
}
Value::Array(items) => {
let mut out = usize_word(items.len()).to_vec();
out.extend(encode_sequence(items));
out
}
Value::Tuple(items) => encode_sequence(items),
}
}
}
fn encode_sequence(values: &[Value]) -> Vec<u8> {
let head_size: usize = values
.iter()
.map(|v| if v.is_dynamic() { 32 } else { v.static_size() })
.sum();
let mut head = Vec::with_capacity(head_size);
let mut tail = Vec::new();
for v in values {
if v.is_dynamic() {
let offset = head_size + tail.len();
head.extend_from_slice(&usize_word(offset));
tail.extend(v.encode());
} else {
head.extend(v.encode());
}
}
head.extend(tail);
head
}
fn collateral_params_value(cp: &crate::CollateralParams) -> Value {
Value::Tuple(vec![
Value::Word(addr_word(&cp.token)),
Value::Word(cp.lltv),
Value::Word(cp.liquidation_cursor),
Value::Word(addr_word(&cp.oracle)),
])
}
fn market_value(m: &crate::Market) -> Value {
Value::Tuple(vec![
Value::Word(m.chain_id),
Value::Word(addr_word(&m.midnight)),
Value::Word(addr_word(&m.loan_token)),
Value::Array(
m.collateral_params
.iter()
.map(collateral_params_value)
.collect(),
),
Value::Word(m.maturity),
Value::Word(m.rcf_threshold),
Value::Word(addr_word(&m.enter_gate)),
Value::Word(addr_word(&m.liquidator_gate)),
])
}
fn offer_value(o: &Offer) -> Value {
Value::Tuple(vec![
market_value(&o.market),
Value::Word(bool_word(o.buy)),
Value::Word(addr_word(&o.maker)),
Value::Word(o.start),
Value::Word(o.expiry),
Value::Word(o.tick),
Value::Word(o.group),
Value::Word(addr_word(&o.callback)),
Value::Bytes(o.callback_data.clone()),
Value::Word(addr_word(&o.receiver_if_maker_is_seller)),
Value::Word(addr_word(&o.ratifier)),
Value::Word(bool_word(o.reduce_only)),
Value::Word(u128_word(o.max_units)),
Value::Word(u128_word(o.max_assets)),
Value::Word(o.continuous_fee_cap),
])
}
pub fn encode_market_params(market: &crate::Market) -> Vec<u8> {
encode_sequence(&[market_value(market)])
}
fn selector_call(selector: [u8; 4], args: &[Value]) -> Vec<u8> {
let encoded = encode_sequence(args);
let mut out = Vec::with_capacity(4 + encoded.len());
out.extend_from_slice(&selector);
out.extend(encoded);
out
}
pub fn encode_ratifier_data(sig: &Sig, root: &Word, leaf_index: usize, proof: &[Word]) -> Vec<u8> {
let signature = Value::Tuple(vec![
Value::Word(usize_word(sig.v as usize)),
Value::Word(sig.r),
Value::Word(sig.s),
]);
let proof_arr = Value::Array(proof.iter().map(|w| Value::Word(*w)).collect());
encode_sequence(&[
signature,
Value::Word(*root),
Value::Word(usize_word(leaf_index)),
proof_arr,
])
}
pub fn encode_setter_ratifier_data(root: &Word, leaf_index: usize, proof: &[Word]) -> Vec<u8> {
encode_sequence(&[
Value::Word(*root),
Value::Word(usize_word(leaf_index)),
Value::Array(proof.iter().map(|w| Value::Word(*w)).collect()),
])
}
#[allow(clippy::too_many_arguments)]
pub fn encode_take_calldata(
offer: &Offer,
ratifier_data: &[u8],
units: U256,
taker: &Address,
receiver_if_taker_is_seller: &Address,
taker_callback: &Address,
taker_callback_data: &[u8],
) -> Vec<u8> {
let args = encode_sequence(&[
offer_value(offer),
Value::Bytes(ratifier_data.to_vec()),
Value::Word(units.to_be_bytes::<32>()),
Value::Word(addr_word(taker)),
Value::Word(addr_word(receiver_if_taker_is_seller)),
Value::Word(addr_word(taker_callback)),
Value::Bytes(taker_callback_data.to_vec()),
]);
let mut out = Vec::with_capacity(4 + args.len());
out.extend_from_slice(&TAKE_SELECTOR);
out.extend(args);
out
}
pub fn encode_bundle_calldata(b: &crate::BundleCall) -> Vec<u8> {
let fills = Value::Array(
b.fills
.iter()
.map(|f| {
Value::Tuple(vec![
offer_value(&f.offer),
Value::Bytes(f.ratifier_data_raw.clone()),
Value::Word(f.units.to_be_bytes::<32>()),
])
})
.collect(),
);
let tail = [
Value::Word(b.referral_fee_pct.to_be_bytes::<32>()),
Value::Word(addr_word(&b.referral_fee_recipient)),
Value::Word(b.max_continuous_fee.to_be_bytes::<32>()),
Value::Word(b.deadline.to_be_bytes::<32>()),
];
let common = [
Value::Word(b.target.to_be_bytes::<32>()),
Value::Word(b.limit.to_be_bytes::<32>()),
Value::Word(addr_word(&b.taker)),
Value::Word(bool_word(b.reduce_only)),
];
let permit_value = |p: &crate::TokenPermit| {
Value::Tuple(vec![
Value::Word(usize_word(p.kind as usize)),
Value::Bytes(p.data.clone()),
])
};
let mut args: Vec<Value> = common.into();
match &b.side {
crate::BundleSide::Buy {
loan_token_permit,
collateral_withdrawals,
collateral_receiver,
} => {
args.push(permit_value(loan_token_permit));
args.push(fills);
args.push(Value::Array(
collateral_withdrawals
.iter()
.map(|w| {
Value::Tuple(vec![
Value::Word(w.collateral_index.to_be_bytes::<32>()),
Value::Word(w.assets.to_be_bytes::<32>()),
])
})
.collect(),
));
args.push(Value::Word(addr_word(collateral_receiver)));
}
crate::BundleSide::Sell {
receiver,
collateral_supplies,
} => {
args.push(Value::Word(addr_word(receiver)));
args.push(Value::Array(
collateral_supplies
.iter()
.map(|s| {
Value::Tuple(vec![
Value::Word(s.collateral_index.to_be_bytes::<32>()),
Value::Word(s.assets.to_be_bytes::<32>()),
permit_value(&s.permit),
])
})
.collect(),
));
args.push(fills);
}
}
args.extend(tail);
let encoded = encode_sequence(&args);
let mut out = Vec::with_capacity(4 + encoded.len());
out.extend_from_slice(&b.kind.selector());
out.extend(encoded);
out
}
pub fn encode_supply_collateral_calldata(
market: &Market,
collateral_index: U256,
assets: U256,
on_behalf: &Address,
) -> Vec<u8> {
selector_call(
SUPPLY_COLLATERAL_SELECTOR,
&[
market_value(market),
Value::Word(collateral_index.to_be_bytes::<32>()),
Value::Word(assets.to_be_bytes::<32>()),
Value::Word(addr_word(on_behalf)),
],
)
}
pub fn encode_withdraw_calldata(
market: &Market,
units: U256,
on_behalf: &Address,
receiver: &Address,
) -> Vec<u8> {
selector_call(
WITHDRAW_SELECTOR,
&[
market_value(market),
Value::Word(units.to_be_bytes::<32>()),
Value::Word(addr_word(on_behalf)),
Value::Word(addr_word(receiver)),
],
)
}
pub fn encode_set_is_authorized_calldata(
authorized: &Address,
is_authorized: bool,
on_behalf: &Address,
) -> Vec<u8> {
selector_call(
SET_IS_AUTHORIZED_SELECTOR,
&[
Value::Word(addr_word(authorized)),
Value::Word(bool_word(is_authorized)),
Value::Word(addr_word(on_behalf)),
],
)
}
pub fn encode_erc20_approve_calldata(spender: &Address, amount: U256) -> Vec<u8> {
selector_call(
ERC20_APPROVE_SELECTOR,
&[
Value::Word(addr_word(spender)),
Value::Word(amount.to_be_bytes::<32>()),
],
)
}
#[allow(clippy::too_many_arguments)]
pub fn encode_repay_withdraw_collateral_calldata(
market: &Market,
repay_assets: U256,
on_behalf: &Address,
loan_token_permit: &crate::TokenPermit,
collateral_withdrawals: &[crate::CollateralWithdrawal],
collateral_receiver: &Address,
referral_fee_pct: U256,
referral_fee_recipient: &Address,
deadline: U256,
) -> Vec<u8> {
let permit = Value::Tuple(vec![
Value::Word(usize_word(loan_token_permit.kind as usize)),
Value::Bytes(loan_token_permit.data.clone()),
]);
let withdrawals = Value::Array(
collateral_withdrawals
.iter()
.map(|withdrawal| {
Value::Tuple(vec![
Value::Word(withdrawal.collateral_index.to_be_bytes::<32>()),
Value::Word(withdrawal.assets.to_be_bytes::<32>()),
])
})
.collect(),
);
selector_call(
BUNDLE_REPAY_WITHDRAW_SELECTOR,
&[
market_value(market),
Value::Word(repay_assets.to_be_bytes::<32>()),
Value::Word(addr_word(on_behalf)),
permit,
withdrawals,
Value::Word(addr_word(collateral_receiver)),
Value::Word(referral_fee_pct.to_be_bytes::<32>()),
Value::Word(addr_word(referral_fee_recipient)),
Value::Word(deadline.to_be_bytes::<32>()),
],
)
}
pub fn encode_cancel_root_calldata(maker: &Address, root: &Word) -> Vec<u8> {
let args = encode_sequence(&[Value::Word(addr_word(maker)), Value::Word(*root)]);
let mut out = Vec::with_capacity(4 + args.len());
out.extend_from_slice(&CANCEL_ROOT_SELECTOR);
out.extend(args);
out
}
pub fn encode_set_is_root_ratified_calldata(
maker: &Address,
root: &Word,
ratified: bool,
) -> Vec<u8> {
let args = encode_sequence(&[
Value::Word(addr_word(maker)),
Value::Word(*root),
Value::Word(bool_word(ratified)),
]);
let mut out = Vec::with_capacity(4 + args.len());
out.extend_from_slice(&SET_IS_ROOT_RATIFIED_SELECTOR);
out.extend(args);
out
}