use tiny_keccak::{Hasher, Keccak};
use crate::wallet::key::Address;
use crate::wallet::sign::Eip712;
#[must_use]
pub fn metaflux_chain_tag(chain_id: u64) -> &'static str {
match chain_id {
8964 => "Mainnet",
114514 => "Testnet",
31337 => "Devnet",
_ => "Devnet",
}
}
fn keccak(input: &[u8]) -> [u8; 32] {
let mut h = Keccak::v256();
h.update(input);
let mut out = [0u8; 32];
h.finalize(&mut out);
out
}
fn enc_addr(a: &Address) -> [u8; 32] {
let mut out = [0u8; 32];
out[12..].copy_from_slice(a.as_bytes());
out
}
fn enc_u64(v: u64) -> [u8; 32] {
let mut out = [0u8; 32];
out[24..].copy_from_slice(&v.to_be_bytes());
out
}
fn enc_u32(v: u32) -> [u8; 32] {
let mut out = [0u8; 32];
out[28..].copy_from_slice(&v.to_be_bytes());
out
}
fn enc_u16(v: u16) -> [u8; 32] {
let mut out = [0u8; 32];
out[30..].copy_from_slice(&v.to_be_bytes());
out
}
fn enc_u8(v: u8) -> [u8; 32] {
let mut out = [0u8; 32];
out[31] = v;
out
}
fn enc_bool(v: bool) -> [u8; 32] {
enc_u8(u8::from(v))
}
fn enc_string(s: &str) -> [u8; 32] {
keccak(s.as_bytes())
}
fn enc_addr_array(addrs: &[Address]) -> [u8; 32] {
let mut k = Keccak::v256();
for a in addrs {
k.update(&enc_addr(a));
}
let mut out = [0u8; 32];
k.finalize(&mut out);
out
}
const SEND_ASSET_TYPE: &[u8] =
b"MetaFluxTransaction:SendAsset(string metafluxChain,uint32 sourceDex,uint32 destinationDex,uint32 asset,address destination,string amount,bool toPerp,uint64 nonce)";
const USD_CLASS_TRANSFER_TYPE: &[u8] =
b"MetaFluxTransaction:UsdClassTransfer(string metafluxChain,string ntl,bool toPerp,uint64 nonce)";
const WITHDRAW_TYPE: &[u8] =
b"MetaFluxTransaction:Withdraw(string metafluxChain,uint32 asset,string amount,uint32 destinationChainId,bool useCctp,uint64 nonce)";
const APPROVE_AGENT_TYPE: &[u8] =
b"MetaFluxTransaction:ApproveAgent(string metafluxChain,address agentAddress,string agentName,uint64 nonce)";
const SET_REFERRER_TYPE: &[u8] =
b"MetaFluxTransaction:SetReferrer(string metafluxChain,address referrer,uint64 nonce)";
const APPROVE_BUILDER_FEE_TYPE: &[u8] =
b"MetaFluxTransaction:ApproveBuilderFee(string metafluxChain,address builder,uint16 maxFeeBps,uint64 nonce)";
const SET_DISPLAY_NAME_TYPE: &[u8] =
b"MetaFluxTransaction:SetDisplayName(string metafluxChain,string displayName,uint64 nonce)";
const SET_POSITION_MODE_TYPE: &[u8] =
b"MetaFluxTransaction:SetPositionMode(string metafluxChain,bool hedge,uint64 nonce)";
const USER_PORTFOLIO_MARGIN_TYPE: &[u8] =
b"MetaFluxTransaction:UserPortfolioMargin(string metafluxChain,bool enroll,uint64 nonce)";
const CONVERT_TO_MULTI_SIG_USER_TYPE: &[u8] =
b"MetaFluxTransaction:ConvertToMultiSigUser(string metafluxChain,address[] signers,uint32 threshold,uint64 nonce)";
const UPDATE_LEVERAGE_TYPE: &[u8] =
b"MetaFluxTransaction:UpdateLeverage(string metafluxChain,uint32 asset,uint32 leverage,bool isIsolated,uint64 nonce)";
const CLAIM_REWARDS_TYPE: &[u8] =
b"MetaFluxTransaction:ClaimRewards(string metafluxChain,address validator,uint64 nonce)";
const LINK_STAKING_USER_TYPE: &[u8] =
b"MetaFluxTransaction:LinkStakingUser(string metafluxChain,address target,uint64 nonce)";
const CREATE_VAULT_TYPE: &[u8] =
b"MetaFluxTransaction:CreateVault(string metafluxChain,string name,uint64 lockPeriodSecs,uint8 kind,uint64 nonce)";
const VAULT_MODIFY_TYPE: &[u8] =
b"MetaFluxTransaction:VaultModify(string metafluxChain,uint64 vaultId,string newName,uint64 nonce)";
const SPOT_MARGIN_CLOSE_TYPE: &[u8] =
b"MetaFluxTransaction:SpotMarginClose(string metafluxChain,uint32 pair,uint64 limitPx,uint64 nonce)";
const SET_METALIQUIDITY_SET_TYPE: &[u8] =
b"MetaFluxTransaction:SetMetaliquiditySet(string metafluxChain,address account,bool allowed,uint64 nonce)";
const REGISTER_METALIQUIDITY_OPERATOR_TYPE: &[u8] =
b"MetaFluxTransaction:RegisterMetaliquidityOperator(string metafluxChain,uint64 vaultId,address operator,bool allowed,uint64 expiresAtMs,uint64 nonce)";
const UPDATE_ISOLATED_MARGIN_TYPE: &[u8] =
b"MetaFluxTransaction:UpdateIsolatedMargin(string metafluxChain,uint32 asset,string delta,uint64 nonce)";
const TOP_UP_ISOLATED_ONLY_MARGIN_TYPE: &[u8] =
b"MetaFluxTransaction:TopUpIsolatedOnlyMargin(string metafluxChain,uint32 asset,string amount,uint64 nonce)";
const TOKEN_DELEGATE_TYPE: &[u8] =
b"MetaFluxTransaction:TokenDelegate(string metafluxChain,address validator,string amount,bool isUndelegate,uint64 nonce)";
const VAULT_TRANSFER_TYPE: &[u8] =
b"MetaFluxTransaction:VaultTransfer(string metafluxChain,uint64 vaultId,bool deposit,string amount,uint64 nonce)";
const VAULT_WITHDRAW_TYPE: &[u8] =
b"MetaFluxTransaction:VaultWithdraw(string metafluxChain,uint64 vaultId,string shares,uint64 nonce)";
const SPOT_MARGIN_DEPOSIT_TYPE: &[u8] =
b"MetaFluxTransaction:SpotMarginDeposit(string metafluxChain,uint32 pair,string amount,uint64 nonce)";
const SPOT_MARGIN_WITHDRAW_TYPE: &[u8] =
b"MetaFluxTransaction:SpotMarginWithdraw(string metafluxChain,uint32 pair,string amount,uint64 nonce)";
const SPOT_MARGIN_OPEN_TYPE: &[u8] =
b"MetaFluxTransaction:SpotMarginOpen(string metafluxChain,uint32 pair,uint64 size,uint64 limitPx,string borrow,uint64 nonce)";
const EARN_DEPOSIT_TYPE: &[u8] =
b"MetaFluxTransaction:EarnDeposit(string metafluxChain,uint32 asset,string amount,uint64 nonce)";
const EARN_WITHDRAW_TYPE: &[u8] =
b"MetaFluxTransaction:EarnWithdraw(string metafluxChain,uint32 asset,string shares,uint64 nonce)";
const AGENT_SET_ABSTRACTION_TYPE: &[u8] =
b"MetaFluxTransaction:AgentSetAbstraction(string metafluxChain,address user,uint8 kind,string value,uint64 nonce)";
const MB_WITHDRAW_TYPE: &[u8] =
b"MetaFluxTransaction:MbWithdraw(string metafluxChain,uint8 chain,uint32 asset,uint64 amount,string dstAddr,uint64 nonce)";
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum TypedAction {
SendAsset {
metaflux_chain: String,
source_dex: u32,
destination_dex: u32,
asset: u32,
destination: Address,
amount: String,
to_perp: bool,
nonce: u64,
},
UsdClassTransfer {
metaflux_chain: String,
ntl: String,
to_perp: bool,
nonce: u64,
},
Withdraw {
metaflux_chain: String,
asset: u32,
amount: String,
destination_chain_id: u32,
use_cctp: bool,
nonce: u64,
},
ApproveAgent {
metaflux_chain: String,
agent_address: Address,
agent_name: String,
nonce: u64,
},
SetReferrer {
metaflux_chain: String,
referrer: Address,
nonce: u64,
},
ApproveBuilderFee {
metaflux_chain: String,
builder: Address,
max_fee_bps: u16,
nonce: u64,
},
SetDisplayName {
metaflux_chain: String,
display_name: String,
nonce: u64,
},
SetPositionMode {
metaflux_chain: String,
hedge: bool,
nonce: u64,
},
UserPortfolioMargin {
metaflux_chain: String,
enroll: bool,
nonce: u64,
},
ConvertToMultiSigUser {
metaflux_chain: String,
signers: Vec<Address>,
threshold: u32,
nonce: u64,
},
UpdateLeverage {
metaflux_chain: String,
asset: u32,
leverage: u32,
is_isolated: bool,
nonce: u64,
},
ClaimRewards {
metaflux_chain: String,
validator: Address,
nonce: u64,
},
LinkStakingUser {
metaflux_chain: String,
target: Address,
nonce: u64,
},
CreateVault {
metaflux_chain: String,
name: String,
lock_period_secs: u64,
kind: u8,
nonce: u64,
},
VaultModify {
metaflux_chain: String,
vault_id: u64,
new_name: String,
nonce: u64,
},
SpotMarginClose {
metaflux_chain: String,
pair: u32,
limit_px: u64,
nonce: u64,
},
SetMetaliquiditySet {
metaflux_chain: String,
account: Address,
allowed: bool,
nonce: u64,
},
RegisterMetaliquidityOperator {
metaflux_chain: String,
vault_id: u64,
operator: Address,
allowed: bool,
expires_at_ms: u64,
nonce: u64,
},
UpdateIsolatedMargin {
metaflux_chain: String,
asset: u32,
delta: String,
nonce: u64,
},
TopUpIsolatedOnlyMargin {
metaflux_chain: String,
asset: u32,
amount: String,
nonce: u64,
},
TokenDelegate {
metaflux_chain: String,
validator: Address,
amount: String,
is_undelegate: bool,
nonce: u64,
},
VaultTransfer {
metaflux_chain: String,
vault_id: u64,
deposit: bool,
amount: String,
nonce: u64,
},
VaultWithdraw {
metaflux_chain: String,
vault_id: u64,
shares: String,
nonce: u64,
},
SpotMarginDeposit {
metaflux_chain: String,
pair: u32,
amount: String,
nonce: u64,
},
SpotMarginWithdraw {
metaflux_chain: String,
pair: u32,
amount: String,
nonce: u64,
},
SpotMarginOpen {
metaflux_chain: String,
pair: u32,
size: u64,
limit_px: u64,
borrow: String,
nonce: u64,
},
EarnDeposit {
metaflux_chain: String,
asset: u32,
amount: String,
nonce: u64,
},
EarnWithdraw {
metaflux_chain: String,
asset: u32,
shares: String,
nonce: u64,
},
AgentSetAbstraction {
metaflux_chain: String,
user: Address,
kind: u8,
value: String,
nonce: u64,
},
MbWithdraw {
metaflux_chain: String,
chain: u8,
asset: u32,
amount: u64,
dst_addr: String,
nonce: u64,
},
}
impl TypedAction {
fn type_string(&self) -> &'static [u8] {
match self {
TypedAction::SendAsset { .. } => SEND_ASSET_TYPE,
TypedAction::UsdClassTransfer { .. } => USD_CLASS_TRANSFER_TYPE,
TypedAction::Withdraw { .. } => WITHDRAW_TYPE,
TypedAction::ApproveAgent { .. } => APPROVE_AGENT_TYPE,
TypedAction::SetReferrer { .. } => SET_REFERRER_TYPE,
TypedAction::ApproveBuilderFee { .. } => APPROVE_BUILDER_FEE_TYPE,
TypedAction::SetDisplayName { .. } => SET_DISPLAY_NAME_TYPE,
TypedAction::SetPositionMode { .. } => SET_POSITION_MODE_TYPE,
TypedAction::UserPortfolioMargin { .. } => USER_PORTFOLIO_MARGIN_TYPE,
TypedAction::ConvertToMultiSigUser { .. } => CONVERT_TO_MULTI_SIG_USER_TYPE,
TypedAction::UpdateLeverage { .. } => UPDATE_LEVERAGE_TYPE,
TypedAction::ClaimRewards { .. } => CLAIM_REWARDS_TYPE,
TypedAction::LinkStakingUser { .. } => LINK_STAKING_USER_TYPE,
TypedAction::CreateVault { .. } => CREATE_VAULT_TYPE,
TypedAction::VaultModify { .. } => VAULT_MODIFY_TYPE,
TypedAction::SpotMarginClose { .. } => SPOT_MARGIN_CLOSE_TYPE,
TypedAction::SetMetaliquiditySet { .. } => SET_METALIQUIDITY_SET_TYPE,
TypedAction::RegisterMetaliquidityOperator { .. } => {
REGISTER_METALIQUIDITY_OPERATOR_TYPE
}
TypedAction::UpdateIsolatedMargin { .. } => UPDATE_ISOLATED_MARGIN_TYPE,
TypedAction::TopUpIsolatedOnlyMargin { .. } => TOP_UP_ISOLATED_ONLY_MARGIN_TYPE,
TypedAction::TokenDelegate { .. } => TOKEN_DELEGATE_TYPE,
TypedAction::VaultTransfer { .. } => VAULT_TRANSFER_TYPE,
TypedAction::VaultWithdraw { .. } => VAULT_WITHDRAW_TYPE,
TypedAction::SpotMarginDeposit { .. } => SPOT_MARGIN_DEPOSIT_TYPE,
TypedAction::SpotMarginWithdraw { .. } => SPOT_MARGIN_WITHDRAW_TYPE,
TypedAction::SpotMarginOpen { .. } => SPOT_MARGIN_OPEN_TYPE,
TypedAction::EarnDeposit { .. } => EARN_DEPOSIT_TYPE,
TypedAction::EarnWithdraw { .. } => EARN_WITHDRAW_TYPE,
TypedAction::AgentSetAbstraction { .. } => AGENT_SET_ABSTRACTION_TYPE,
TypedAction::MbWithdraw { .. } => MB_WITHDRAW_TYPE,
}
}
#[must_use]
pub fn type_hash(&self) -> [u8; 32] {
keccak(self.type_string())
}
fn encode_data(&self) -> Vec<[u8; 32]> {
match self {
TypedAction::SendAsset {
metaflux_chain,
source_dex,
destination_dex,
asset,
destination,
amount,
to_perp,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u32(*source_dex),
enc_u32(*destination_dex),
enc_u32(*asset),
enc_addr(destination),
enc_string(amount),
enc_bool(*to_perp),
enc_u64(*nonce),
],
TypedAction::UsdClassTransfer {
metaflux_chain,
ntl,
to_perp,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_string(ntl),
enc_bool(*to_perp),
enc_u64(*nonce),
],
TypedAction::Withdraw {
metaflux_chain,
asset,
amount,
destination_chain_id,
use_cctp,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u32(*asset),
enc_string(amount),
enc_u32(*destination_chain_id),
enc_bool(*use_cctp),
enc_u64(*nonce),
],
TypedAction::ApproveAgent {
metaflux_chain,
agent_address,
agent_name,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_addr(agent_address),
enc_string(agent_name),
enc_u64(*nonce),
],
TypedAction::SetReferrer {
metaflux_chain,
referrer,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_addr(referrer),
enc_u64(*nonce),
],
TypedAction::ApproveBuilderFee {
metaflux_chain,
builder,
max_fee_bps,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_addr(builder),
enc_u16(*max_fee_bps),
enc_u64(*nonce),
],
TypedAction::SetDisplayName {
metaflux_chain,
display_name,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_string(display_name),
enc_u64(*nonce),
],
TypedAction::SetPositionMode {
metaflux_chain,
hedge,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_bool(*hedge),
enc_u64(*nonce),
],
TypedAction::UserPortfolioMargin {
metaflux_chain,
enroll,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_bool(*enroll),
enc_u64(*nonce),
],
TypedAction::ConvertToMultiSigUser {
metaflux_chain,
signers,
threshold,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_addr_array(signers),
enc_u32(*threshold),
enc_u64(*nonce),
],
TypedAction::UpdateLeverage {
metaflux_chain,
asset,
leverage,
is_isolated,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u32(*asset),
enc_u32(*leverage),
enc_bool(*is_isolated),
enc_u64(*nonce),
],
TypedAction::ClaimRewards {
metaflux_chain,
validator,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_addr(validator),
enc_u64(*nonce),
],
TypedAction::LinkStakingUser {
metaflux_chain,
target,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_addr(target),
enc_u64(*nonce),
],
TypedAction::CreateVault {
metaflux_chain,
name,
lock_period_secs,
kind,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_string(name),
enc_u64(*lock_period_secs),
enc_u8(*kind),
enc_u64(*nonce),
],
TypedAction::VaultModify {
metaflux_chain,
vault_id,
new_name,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u64(*vault_id),
enc_string(new_name),
enc_u64(*nonce),
],
TypedAction::SpotMarginClose {
metaflux_chain,
pair,
limit_px,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u32(*pair),
enc_u64(*limit_px),
enc_u64(*nonce),
],
TypedAction::SetMetaliquiditySet {
metaflux_chain,
account,
allowed,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_addr(account),
enc_bool(*allowed),
enc_u64(*nonce),
],
TypedAction::RegisterMetaliquidityOperator {
metaflux_chain,
vault_id,
operator,
allowed,
expires_at_ms,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u64(*vault_id),
enc_addr(operator),
enc_bool(*allowed),
enc_u64(*expires_at_ms),
enc_u64(*nonce),
],
TypedAction::UpdateIsolatedMargin {
metaflux_chain,
asset,
delta,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u32(*asset),
enc_string(delta),
enc_u64(*nonce),
],
TypedAction::TopUpIsolatedOnlyMargin {
metaflux_chain,
asset,
amount,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u32(*asset),
enc_string(amount),
enc_u64(*nonce),
],
TypedAction::TokenDelegate {
metaflux_chain,
validator,
amount,
is_undelegate,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_addr(validator),
enc_string(amount),
enc_bool(*is_undelegate),
enc_u64(*nonce),
],
TypedAction::VaultTransfer {
metaflux_chain,
vault_id,
deposit,
amount,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u64(*vault_id),
enc_bool(*deposit),
enc_string(amount),
enc_u64(*nonce),
],
TypedAction::VaultWithdraw {
metaflux_chain,
vault_id,
shares,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u64(*vault_id),
enc_string(shares),
enc_u64(*nonce),
],
TypedAction::SpotMarginDeposit {
metaflux_chain,
pair,
amount,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u32(*pair),
enc_string(amount),
enc_u64(*nonce),
],
TypedAction::SpotMarginWithdraw {
metaflux_chain,
pair,
amount,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u32(*pair),
enc_string(amount),
enc_u64(*nonce),
],
TypedAction::SpotMarginOpen {
metaflux_chain,
pair,
size,
limit_px,
borrow,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u32(*pair),
enc_u64(*size),
enc_u64(*limit_px),
enc_string(borrow),
enc_u64(*nonce),
],
TypedAction::EarnDeposit {
metaflux_chain,
asset,
amount,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u32(*asset),
enc_string(amount),
enc_u64(*nonce),
],
TypedAction::EarnWithdraw {
metaflux_chain,
asset,
shares,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u32(*asset),
enc_string(shares),
enc_u64(*nonce),
],
TypedAction::AgentSetAbstraction {
metaflux_chain,
user,
kind,
value,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_addr(user),
enc_u8(*kind),
enc_string(value),
enc_u64(*nonce),
],
TypedAction::MbWithdraw {
metaflux_chain,
chain,
asset,
amount,
dst_addr,
nonce,
} => vec![
enc_string(metaflux_chain),
enc_u8(*chain),
enc_u32(*asset),
enc_u64(*amount),
enc_string(dst_addr),
enc_u64(*nonce),
],
}
}
#[must_use]
pub fn hash_struct(&self) -> [u8; 32] {
let mut k = Keccak::v256();
k.update(&self.type_hash());
for word in self.encode_data() {
k.update(&word);
}
let mut out = [0u8; 32];
k.finalize(&mut out);
out
}
}
#[derive(Clone, Debug)]
pub struct TypedActionDigest<'a> {
action: &'a TypedAction,
chain_id: u64,
}
impl<'a> TypedActionDigest<'a> {
#[must_use]
pub fn new(action: &'a TypedAction, chain_id: u64) -> Self {
Self { action, chain_id }
}
}
impl Eip712 for TypedActionDigest<'_> {
fn domain_separator(&self) -> [u8; 32] {
metaflux_domain_separator(self.chain_id)
}
fn struct_hash(&self) -> [u8; 32] {
self.action.hash_struct()
}
}
#[must_use]
pub fn metaflux_domain_separator(chain_id: u64) -> [u8; 32] {
let type_hash = keccak(
b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)",
);
let name_hash = keccak(b"MetaFlux");
let version_hash = keccak(b"1");
let mut chain_be = [0u8; 32];
chain_be[24..].copy_from_slice(&chain_id.to_be_bytes());
let verifying_padded = [0u8; 32];
let mut k = Keccak::v256();
k.update(&type_hash);
k.update(&name_hash);
k.update(&version_hash);
k.update(&chain_be);
k.update(&verifying_padded);
let mut out = [0u8; 32];
k.finalize(&mut out);
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::wallet::sign::Eip712;
fn addr(byte: u8) -> Address {
Address::from_bytes([byte; 20])
}
#[test]
fn kat_vectors_chain_114514() {
let approve_agent = TypedAction::ApproveAgent {
metaflux_chain: "Testnet".into(),
agent_address: addr(0xA1),
agent_name: "trading-bot".into(),
nonce: 1,
};
assert_eq!(
hex::encode(TypedActionDigest::new(&approve_agent, 114514).to_digest()),
"b5a1178200a97f6ea644abdf4eb21525ad8e13c8ff07b5c4a6809815e6c91820"
);
let send_asset = TypedAction::SendAsset {
metaflux_chain: "Testnet".into(),
source_dex: 0,
destination_dex: 1,
asset: 2,
destination: addr(0x3C),
amount: "750.25".into(),
to_perp: true,
nonce: 28,
};
assert_eq!(
hex::encode(TypedActionDigest::new(&send_asset, 114514).to_digest()),
"88aa17af1dc0d6d35934ada321549a4b8b6a4d964f9c5263e1200b4f696cac4d"
);
let multi_sig = TypedAction::ConvertToMultiSigUser {
metaflux_chain: "Testnet".into(),
signers: vec![addr(0x11), addr(0x22), addr(0x33)],
threshold: 2,
nonce: 7,
};
assert_eq!(
hex::encode(TypedActionDigest::new(&multi_sig, 114514).to_digest()),
"981a2b3adb1d0c03a7af30076f3c6497ffeabe79e380b01be4f1f14eb1252e84"
);
}
#[test]
fn kat_vectors_extended_chain_114514() {
let cases: Vec<(TypedAction, &str)> = vec![
(
TypedAction::UpdateIsolatedMargin {
metaflux_chain: "Testnet".into(),
asset: 1,
delta: "-100.5".into(),
nonce: 9,
},
"f3ca20d10ce710d31de3d321d61d60b53550adbb4dfd09fca9b7a8c8dbc08162",
),
(
TypedAction::TopUpIsolatedOnlyMargin {
metaflux_chain: "Testnet".into(),
asset: 1,
amount: "50".into(),
nonce: 10,
},
"47647d208358a681eb657867da2ce00dfeb010a7f2023ecb69e195642da24c8a",
),
(
TypedAction::TokenDelegate {
metaflux_chain: "Testnet".into(),
validator: addr(0xD4),
amount: "1000".into(),
is_undelegate: false,
nonce: 11,
},
"5327737fefc7ee3b38b59743fb4ba311d9142a7c672ec59ca92c5a871173008b",
),
(
TypedAction::VaultTransfer {
metaflux_chain: "Testnet".into(),
vault_id: 42,
deposit: true,
amount: "250.75".into(),
nonce: 16,
},
"d5da325a4e1331ebd6a158d7192795a3eeaf2a39c86b90d44cd5506c98ececc9",
),
(
TypedAction::VaultWithdraw {
metaflux_chain: "Testnet".into(),
vault_id: 42,
shares: "10.5".into(),
nonce: 18,
},
"ca6c76e49c7cedd99df8d27ee85d14175b954d25bdac53f9525e6b8c71f6b5a7",
),
(
TypedAction::SpotMarginDeposit {
metaflux_chain: "Testnet".into(),
pair: 5,
amount: "100".into(),
nonce: 20,
},
"3d2f440131e3059d8ac4329864f258ae8c799f82323785a36420182ed3e304fd",
),
(
TypedAction::SpotMarginWithdraw {
metaflux_chain: "Testnet".into(),
pair: 5,
amount: "50".into(),
nonce: 21,
},
"44540925574b90c68c0cb4c5773d2d51e14d3c3ddd6c9fe5b97e81aba67e768c",
),
(
TypedAction::SpotMarginOpen {
metaflux_chain: "Testnet".into(),
pair: 5,
size: 1_000,
limit_px: 5_000_000_000,
borrow: "200".into(),
nonce: 22,
},
"d56110f1e4adb4fbd07a72b870678425bd5440d2119e3d9d9f205469c6dbd4c1",
),
(
TypedAction::EarnDeposit {
metaflux_chain: "Testnet".into(),
asset: 0,
amount: "500".into(),
nonce: 24,
},
"947530d85221850f892412799ef45baef7f5a75663272bc565e81c519879664e",
),
(
TypedAction::EarnWithdraw {
metaflux_chain: "Testnet".into(),
asset: 0,
shares: "25.5".into(),
nonce: 25,
},
"5244365c226ab1b7ec786129f134d104a2923a57b9cc2588d6b215aef5b55018",
),
(
TypedAction::AgentSetAbstraction {
metaflux_chain: "Testnet".into(),
user: addr(0xF6),
kind: 3,
value: "abstraction-value".into(),
nonce: 14,
},
"0dd8a92857e2f4aafd97dd0131704bab22969345844389d2b214d55f2a7de71e",
),
(
TypedAction::MbWithdraw {
metaflux_chain: "Testnet".into(),
chain: 2,
asset: 1,
amount: 1_000_000,
dst_addr: "0xdeadbeef".into(),
nonce: 19,
},
"423f327abdec7b3469b6dc5d4993ac4a11f0a09487cec564b85d8162abdee2e8",
),
];
for (action, want) in cases {
assert_eq!(
hex::encode(TypedActionDigest::new(&action, 114514).to_digest()),
want,
"digest drift for {action:?}"
);
}
}
#[test]
fn chain_tag_mapping() {
assert_eq!(metaflux_chain_tag(8964), "Mainnet");
assert_eq!(metaflux_chain_tag(114514), "Testnet");
assert_eq!(metaflux_chain_tag(31337), "Devnet");
assert_eq!(metaflux_chain_tag(7), "Devnet");
}
}