use crate::client::evm::abi::{self, AbiError, Kind, Token};
use crate::client::evm::u256::{B256, U256};
use crate::utils::sign::PublicKey;
use crate::utils::{AccountIndex, Address, ApiKeyIndex, AssetId, MarketId, OrderKind, Route};
pub const DEPOSIT: [u8; 4] = [0x8a, 0x85, 0x70, 0x83];
pub const DEPOSIT_BATCH: [u8; 4] = [0xea, 0x21, 0x02, 0xe4];
pub const WITHDRAW: [u8; 4] = [0xd2, 0x01, 0x91, 0xbd];
pub const CHANGE_PUB_KEY: [u8; 4] = [0x17, 0x01, 0x0c, 0x68];
pub const CANCEL_ALL_ORDERS: [u8; 4] = [0xa4, 0xb6, 0xf7, 0x56];
pub const CREATE_ORDER: [u8; 4] = [0x3c, 0x40, 0xc6, 0x76];
pub const BURN_SHARES: [u8; 4] = [0xae, 0x15, 0x0b, 0x39];
pub const WITHDRAW_PENDING_BALANCE: [u8; 4] = [0x2f, 0x25, 0x80, 0x7e];
pub const ADDRESS_TO_ACCOUNT_INDEX: [u8; 4] = [0xab, 0xf6, 0xa0, 0x38];
pub const GET_PENDING_BALANCE: [u8; 4] = [0xd1, 0xcb, 0xc6, 0x4f];
pub const TOKEN_TO_ASSET_INDEX: [u8; 4] = [0x89, 0x9c, 0xfa, 0x29];
pub const DESERT_MODE: [u8; 4] = [0x02, 0xcf, 0xb5, 0x63];
pub const LAST_ACCOUNT_INDEX: [u8; 4] = [0xfa, 0xe2, 0xe0, 0x46];
pub const SIGNATURES: [(&str, [u8; 4]); 13] = [
("deposit(address,uint16,uint8,uint256)", DEPOSIT),
("depositBatch(uint64[],address[],uint48[])", DEPOSIT_BATCH),
("withdraw(uint48,uint16,uint8,uint64)", WITHDRAW),
("changePubKey(uint48,uint8,bytes)", CHANGE_PUB_KEY),
("cancelAllOrders(uint48)", CANCEL_ALL_ORDERS),
("createOrder(uint48,uint16,uint48,uint32,uint8,uint8)", CREATE_ORDER),
("burnShares(uint48,uint48,uint64)", BURN_SHARES),
("withdrawPendingBalance(address,uint16,uint128)", WITHDRAW_PENDING_BALANCE),
("addressToAccountIndex(address)", ADDRESS_TO_ACCOUNT_INDEX),
("getPendingBalance(address,uint16)", GET_PENDING_BALANCE),
("tokenToAssetIndex(address)", TOKEN_TO_ASSET_INDEX),
("desertMode()", DESERT_MODE),
("lastAccountIndex()", LAST_ACCOUNT_INDEX),
];
fn call(selector: [u8; 4], words: &[[u8; 32]]) -> Vec<u8> {
let mut out = selector.to_vec();
for word in words {
out.extend_from_slice(word);
}
out
}
fn word(value: impl Into<U256>) -> [u8; 32] {
value.into().to_be_bytes()
}
fn word_of(address: Address) -> [u8; 32] {
B256::from_address(address).0
}
#[must_use]
pub fn deposit(to: Address, asset: AssetId, route: Route, amount: U256) -> Vec<u8> {
call(DEPOSIT, &[word_of(to), word(asset.get()), word(route.code()), amount.to_be_bytes()])
}
pub fn deposit_batch(amounts: &[u64], to: &[Address], accounts: &[AccountIndex]) -> Result<Vec<u8>, AbiError> {
if to.len() != amounts.len() || accounts.len() != amounts.len() {
let got = if to.len() != amounts.len() { to.len() } else { accounts.len() };
return Err(AbiError::Count { wants: amounts.len(), got });
}
let kinds = [
Kind::Array(Box::new(Kind::Uint(64))),
Kind::Array(Box::new(Kind::Address)),
Kind::Array(Box::new(Kind::Uint(48))),
];
let tokens = [
Token::Array(amounts.iter().map(|amount| Token::uint(*amount)).collect()),
Token::Array(to.iter().map(|address| Token::address(*address)).collect()),
Token::Array(accounts.iter().map(|account| Token::uint(account.get())).collect()),
];
let mut out = DEPOSIT_BATCH.to_vec();
out.extend(abi::encode(&kinds, &tokens)?);
Ok(out)
}
#[must_use]
pub fn withdraw(account: AccountIndex, asset: AssetId, route: Route, amount: u64) -> Vec<u8> {
call(WITHDRAW, &[word(account.get()), word(asset.get()), word(route.code()), word(amount)])
}
#[must_use]
pub fn change_pub_key(account: AccountIndex, slot: ApiKeyIndex, key: &PublicKey) -> Vec<u8> {
let bytes = key.bytes();
let mut tail = [0u8; 64];
tail[..bytes.len()].copy_from_slice(bytes);
let mut first = [0u8; 32];
let mut second = [0u8; 32];
first.copy_from_slice(&tail[..32]);
second.copy_from_slice(&tail[32..]);
call(CHANGE_PUB_KEY, &[word(account.get()), word(slot.get()), word(96u8), word(bytes.len() as u64), first, second])
}
#[must_use]
pub fn cancel_all_orders(account: AccountIndex) -> Vec<u8> {
call(CANCEL_ALL_ORDERS, &[word(account.get())])
}
pub fn create_order(
account: AccountIndex,
market: MarketId,
base_amount: u64,
price: u32,
is_ask: bool,
kind: OrderKind,
) -> Result<Vec<u8>, AbiError> {
let market_word = u16::try_from(market.get())
.map_err(|_| AbiError::Mismatch { kind: "uint16".to_owned(), token: format!("market {market}") })?;
if base_amount >= 1 << 48 {
return Err(AbiError::Mismatch { kind: "uint48".to_owned(), token: format!("the integer {base_amount}") });
}
Ok(call(
CREATE_ORDER,
&[
word(account.get()),
word(market_word),
word(base_amount),
word(price),
word(u8::from(is_ask)),
word(kind.code()),
],
))
}
#[must_use]
pub fn burn_shares(account: AccountIndex, pool: AccountIndex, shares: u64) -> Vec<u8> {
call(BURN_SHARES, &[word(account.get()), word(pool.get()), word(shares)])
}
#[must_use]
pub fn withdraw_pending_balance(owner: Address, asset: AssetId, amount: u128) -> Vec<u8> {
call(WITHDRAW_PENDING_BALANCE, &[word_of(owner), word(asset.get()), word(amount)])
}
#[must_use]
pub fn address_to_account_index(owner: Address) -> Vec<u8> {
call(ADDRESS_TO_ACCOUNT_INDEX, &[word_of(owner)])
}
#[must_use]
pub fn get_pending_balance(owner: Address, asset: AssetId) -> Vec<u8> {
call(GET_PENDING_BALANCE, &[word_of(owner), word(asset.get())])
}
#[must_use]
pub fn token_to_asset_index(token: Address) -> Vec<u8> {
call(TOKEN_TO_ASSET_INDEX, &[word_of(token)])
}
#[must_use]
pub fn desert_mode() -> Vec<u8> {
DESERT_MODE.to_vec()
}
#[must_use]
pub fn last_account_index() -> Vec<u8> {
LAST_ACCOUNT_INDEX.to_vec()
}
fn one(kind: Kind, answer: &[u8]) -> Result<Token, AbiError> {
abi::decode(&[kind], answer)?.into_iter().next().ok_or(AbiError::Short(answer.len()))
}
fn whole(kind: Kind, answer: &[u8]) -> Result<U256, AbiError> {
one(kind, answer)?.as_u256().ok_or(AbiError::Short(answer.len()))
}
fn account_of(value: U256) -> Result<AccountIndex, AbiError> {
value
.to_u64()
.and_then(|index| AccountIndex::new(index).ok())
.ok_or_else(|| AbiError::Misfit { at: 0, kind: "an account index".to_owned() })
}
pub fn read_account_index(answer: &[u8]) -> Result<Option<AccountIndex>, AbiError> {
let value = whole(Kind::Uint(48), answer)?;
match value.is_zero() {
true => Ok(None),
false => account_of(value).map(Some),
}
}
pub fn read_pending_balance(answer: &[u8]) -> Result<u128, AbiError> {
whole(Kind::Uint(128), answer)?.to_u128().ok_or(AbiError::Short(answer.len()))
}
pub fn read_asset(answer: &[u8]) -> Result<Option<AssetId>, AbiError> {
let index = whole(Kind::Uint(16), answer)?.to_u64().and_then(|index| u16::try_from(index).ok());
Ok(index.and_then(|index| AssetId::new(index).ok()))
}
pub fn read_desert_mode(answer: &[u8]) -> Result<bool, AbiError> {
one(Kind::Bool, answer)?.as_bool().ok_or(AbiError::Short(answer.len()))
}
pub fn read_last_account_index(answer: &[u8]) -> Result<AccountIndex, AbiError> {
account_of(whole(Kind::Uint(48), answer)?)
}
#[cfg(test)]
#[path = "tests/zklighter.rs"]
mod tests;