use k256::ecdsa::SigningKey;
use super::*;
pub fn fiat_mint_domain_separator() -> Result<[u8; 32], String> {
let diamond = parse_eth_address(REGISTRY_ADDRESS())?;
let mut dom = Vec::with_capacity(160);
dom.extend_from_slice(&keccak32(
b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)",
));
dom.extend_from_slice(&keccak32(b"localharness-mintgate"));
dom.extend_from_slice(&keccak32(b"1"));
dom.extend_from_slice(&u256_be(CHAIN_ID() as u128));
dom.extend_from_slice(&addr_word(&diamond));
Ok(keccak32(&dom))
}
pub fn fiat_mint_digest(
to: &[u8; 20],
amount_wei: u128,
receipt_id: &[u8; 32],
valid_before: u64,
) -> Result<[u8; 32], String> {
let mut st = Vec::with_capacity(160);
st.extend_from_slice(&keccak32(
b"FiatMint(address to,uint256 amount,bytes32 receiptId,uint256 validBefore)",
));
st.extend_from_slice(&addr_word(to));
st.extend_from_slice(&u256_be(amount_wei));
st.extend_from_slice(receipt_id);
st.extend_from_slice(&u256_be(valid_before as u128));
let struct_hash = keccak32(&st);
let mut pre = Vec::with_capacity(66);
pre.extend_from_slice(&[0x19, 0x01]);
pre.extend_from_slice(&fiat_mint_domain_separator()?);
pre.extend_from_slice(&struct_hash);
Ok(keccak32(&pre))
}
pub fn sign_fiat_mint(
signer: &SigningKey,
to: &[u8; 20],
amount_wei: u128,
receipt_id: &[u8; 32],
valid_before: u64,
) -> Result<[u8; 65], String> {
let digest = fiat_mint_digest(to, amount_wei, receipt_id, valid_before)?;
Ok(crate::wallet::sign_hash(signer, &digest))
}
pub(crate) fn encode_mint_from_fiat(
to: &[u8; 20],
amount_wei: u128,
receipt_id: &[u8; 32],
valid_before: u64,
signature: &[u8; 65],
) -> Vec<u8> {
let mut out = Vec::with_capacity(4 + 32 * 6 + 96);
out.extend_from_slice(&selector(
"mintFromFiat(address,uint256,bytes32,uint256,bytes)",
));
out.extend_from_slice(&addr_word(to));
out.extend_from_slice(&u256_be(amount_wei));
out.extend_from_slice(receipt_id);
out.extend_from_slice(&u256_be(valid_before as u128));
out.extend_from_slice(&u256_be(5 * 32)); out.extend_from_slice(&u256_be(signature.len() as u128)); out.extend_from_slice(signature);
out.resize(out.len() + 31, 0); out
}
#[allow(clippy::too_many_arguments)]
pub async fn mint_from_fiat_sponsored(
submitter: &SigningKey,
fee_payer: &SigningKey,
to: &[u8; 20],
amount_wei: u128,
receipt_id: &[u8; 32],
valid_before: u64,
signature: &[u8; 65],
fee_token: &str,
) -> Result<String, String> {
sponsored_diamond_call(
submitter,
fee_payer,
encode_mint_from_fiat(to, amount_wei, receipt_id, valid_before, signature),
fee_token,
2_000_000,
)
.await
}
pub async fn circulating_supply() -> Result<u128, String> {
let result = read_view(selector("circulatingSupply()"), &[]).await?;
decode_u256_as_u128(&result)
}
pub async fn fiat_locked_of(account_hex: &str) -> Result<(u128, u64), String> {
let account = parse_eth_address(account_hex)?;
let result = read_view(selector("fiatLockedOf(address)"), &[addr_word(&account)]).await?;
let (w0, w1) = two_words(&result);
Ok((decode_u256_as_u128(&w0)?, decode_u256_as_u64(&w1)?))
}
pub async fn receipt_used(receipt_id: &[u8; 32]) -> Result<bool, String> {
let result = read_view(selector("receiptUsed(bytes32)"), &[*receipt_id]).await?;
Ok(decode_u256_as_u64(&result).map(|v| v != 0).unwrap_or(false))
}
pub async fn fiat_mint_window() -> Result<(u128, u64, u64, u128), String> {
let result = read_view(selector("fiatMintWindow()"), &[]).await?;
let s = result.trim().trim_start_matches("0x");
let word = |i: usize| format!("0x{}", s.get(i * 64..i * 64 + 64).unwrap_or(""));
Ok((
decode_u256_as_u128(&word(0))?,
decode_u256_as_u64(&word(1))?,
decode_u256_as_u64(&word(2))?,
decode_u256_as_u128(&word(3))?,
))
}
pub async fn token_mint_window() -> Result<(u128, u64, u64, u128), String> {
let cap = eth_call(LOCALHARNESS_TOKEN_ADDRESS(), &encode_call_hex(selector("mintWindowCapWei()"), &[])).await?;
let secs = eth_call(LOCALHARNESS_TOKEN_ADDRESS(), &encode_call_hex(selector("mintWindowSecs()"), &[])).await?;
let start = eth_call(LOCALHARNESS_TOKEN_ADDRESS(), &encode_call_hex(selector("mintWindowStart()"), &[])).await?;
let minted = eth_call(LOCALHARNESS_TOKEN_ADDRESS(), &encode_call_hex(selector("mintedInWindow()"), &[])).await?;
Ok((
decode_u256_as_u128(&cap)?,
decode_u256_as_u64(&secs)?,
decode_u256_as_u64(&start)?,
decode_u256_as_u128(&minted)?,
))
}
fn two_words(hex: &str) -> (String, String) {
let s = hex.trim().trim_start_matches("0x");
(
format!("0x{}", s.get(0..64).unwrap_or("")),
format!("0x{}", s.get(64..128).unwrap_or("")),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mint_from_fiat_calldata_layout() {
let to = [0x11u8; 20];
let receipt = [0x33u8; 32];
let sig = [0x44u8; 65];
let amount = 7_000u128;
let cd = encode_mint_from_fiat(&to, amount, &receipt, 1_999_999_999, &sig);
assert_eq!(&cd[0..4], &selector("mintFromFiat(address,uint256,bytes32,uint256,bytes)"));
assert_eq!(cd.len(), 4 + 4 * 32 + 32 + 32 + 96);
assert_eq!(&cd[4 + 12..4 + 32], &to); assert_eq!(u128::from_be_bytes(cd[4 + 48..4 + 64].try_into().unwrap()), amount); assert_eq!(&cd[4 + 2 * 32..4 + 3 * 32], &receipt); assert_eq!(u64::from_be_bytes(cd[4 + 4 * 32 + 24..4 + 5 * 32].try_into().unwrap()), 5 * 32);
assert_eq!(u64::from_be_bytes(cd[4 + 5 * 32 + 24..4 + 6 * 32].try_into().unwrap()), 65);
assert_eq!(&cd[4 + 6 * 32..4 + 6 * 32 + 65], &sig);
assert_eq!(&cd[4 + 6 * 32 + 65..], &[0u8; 31]);
}
#[test]
fn fiat_mint_sign_recovers_issuer() {
let w = crate::wallet::generate();
let to = [0x22u8; 20];
let receipt = [0xABu8; 32];
let sig = sign_fiat_mint(&w.signer, &to, 1_000_000, &receipt, 9_999_999_999).unwrap();
let digest = fiat_mint_digest(&to, 1_000_000, &receipt, 9_999_999_999).unwrap();
let recovered = crate::wallet::recover_address(&sig, &digest).unwrap();
assert_eq!(recovered, w.address);
}
#[test]
fn two_words_splits_and_pads() {
let (a, b) = two_words(&format!("0x{}{}", "0".repeat(63) + "a", "0".repeat(63) + "5"));
assert_eq!(decode_u256_as_u128(&a).unwrap(), 10);
assert_eq!(decode_u256_as_u64(&b).unwrap(), 5);
let (_, z) = two_words("0x00");
assert_eq!(decode_u256_as_u64(&z).unwrap(), 0);
}
}