use k256::ecdsa::SigningKey;
use super::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Party {
pub creator: String,
pub expiry: u64,
pub status: u8,
pub escrow_wei: u128,
pub member_count: u64,
pub accepted_count: u64,
}
impl Party {
pub fn status_label(&self) -> &'static str {
match self.status {
0 => "forming",
1 => "active",
2 => "completed",
3 => "disbanded",
_ => "unknown",
}
}
}
pub(crate) fn encode_form_party(
member_token_ids: &[u64],
shares_bps: &[u16],
ttl_secs: u64,
) -> Vec<u8> {
let n = member_token_ids.len();
let m = shares_bps.len();
let mut out = Vec::with_capacity(4 + 3 * 32 + (1 + n) * 32 + (1 + m) * 32);
out.extend_from_slice(&selector("formParty(uint256[],uint16[],uint64)"));
out.extend_from_slice(&u256_be((3 * 32) as u128));
out.extend_from_slice(&u256_be((3 * 32 + 32 + 32 * n) as u128));
out.extend_from_slice(&u256_be(ttl_secs as u128));
out.extend_from_slice(&u256_be(n as u128));
for id in member_token_ids {
out.extend_from_slice(&u256_be(*id as u128));
}
out.extend_from_slice(&u256_be(m as u128));
for bps in shares_bps {
out.extend_from_slice(&u256_be(*bps as u128));
}
out
}
pub(crate) fn encode_fund_party(party_id: u64, amount_wei: u128) -> Vec<u8> {
let mut out = Vec::with_capacity(4 + 64);
out.extend_from_slice(&selector("fundParty(uint256,uint128)"));
out.extend_from_slice(&u256_be(party_id as u128));
out.extend_from_slice(&u256_be(amount_wei));
out
}
pub async fn form_party_sponsored(
sender: &SigningKey,
fee_payer: &SigningKey,
member_token_ids: &[u64],
shares_bps: &[u16],
ttl_secs: u64,
fee_token: &str,
) -> Result<String, String> {
let gas = 2_500_000 + (member_token_ids.len() as u128) * 400_000;
sponsored_diamond_call(
sender,
fee_payer,
encode_form_party(member_token_ids, shares_bps, ttl_secs),
fee_token,
gas,
)
.await
}
pub async fn join_party_sponsored(
sender: &SigningKey,
fee_payer: &SigningKey,
party_id: u64,
fee_token: &str,
) -> Result<String, String> {
sponsored_diamond_call(
sender,
fee_payer,
call_uint_bytes("joinParty(uint256)", party_id),
fee_token,
2_000_000,
)
.await
}
pub async fn fund_party_sponsored(
sender: &SigningKey,
fee_payer: &SigningKey,
party_id: u64,
amount_wei: u128,
fee_token: &str,
) -> Result<String, String> {
fund_party_sponsored_bridged(sender, fee_payer, party_id, amount_wei, fee_token, 0).await
}
pub async fn fund_party_sponsored_bridged(
sender: &SigningKey,
fee_payer: &SigningKey,
party_id: u64,
amount_wei: u128,
fee_token: &str,
bridge_wei: u128,
) -> Result<String, String> {
sponsored_escrow_diamond_call_bridged(
sender,
fee_payer,
amount_wei,
encode_fund_party(party_id, amount_wei),
fee_token,
2_000_000,
bridge_wei,
)
.await
}
pub async fn complete_party_sponsored(
sender: &SigningKey,
fee_payer: &SigningKey,
party_id: u64,
fee_token: &str,
) -> Result<String, String> {
sponsored_diamond_call(
sender,
fee_payer,
call_uint_bytes("completeParty(uint256)", party_id),
fee_token,
5_000_000,
)
.await
}
pub async fn disband_party_sponsored(
sender: &SigningKey,
fee_payer: &SigningKey,
party_id: u64,
fee_token: &str,
) -> Result<String, String> {
sponsored_diamond_call(
sender,
fee_payer,
call_uint_bytes("disbandParty(uint256)", party_id),
fee_token,
5_000_000,
)
.await
}
pub async fn get_party(party_id: u64) -> Result<Party, String> {
let result = read_view(selector("getParty(uint256)"), &[u256_be(party_id as u128)]).await?;
let bytes = hex_to_bytes(&result)?;
if bytes.len() < 6 * 32 {
return Err(format!("getParty: short response {} bytes", bytes.len()));
}
let word = |i: usize| &bytes[i * 32..(i + 1) * 32];
let creator = format!("0x{}", bytes_to_hex(&word(0)[12..32])); Ok(Party {
creator,
expiry: u64_low(word(1)),
status: bytes[2 * 32 + 31], escrow_wei: u128_low(word(3)), member_count: u64_low(word(4)),
accepted_count: u64_low(word(5)),
})
}
pub async fn party_members_of(party_id: u64) -> Result<Vec<u64>, String> {
let result =
read_view(selector("partyMembersOf(uint256)"), &[u256_be(party_id as u128)]).await?;
let bytes = hex_to_bytes(&result)?;
Ok(decode_u64_array(&bytes))
}
pub async fn party_shares_of(party_id: u64) -> Result<Vec<u16>, String> {
let result =
read_view(selector("partySharesOf(uint256)"), &[u256_be(party_id as u128)]).await?;
let bytes = hex_to_bytes(&result)?;
Ok(decode_u64_array(&bytes).into_iter().map(|v| v as u16).collect())
}
pub async fn party_consent_of(party_id: u64, token_id: u64) -> Result<bool, String> {
let result = read_view(
selector("partyConsentOf(uint256,uint256)"),
&[u256_be(party_id as u128), u256_be(token_id as u128)],
)
.await?;
decode_u256_as_u64(&result).map(|v| v != 0)
}
pub async fn party_funders_of(party_id: u64) -> Result<Vec<String>, String> {
let result =
read_view(selector("partyFundersOf(uint256)"), &[u256_be(party_id as u128)]).await?;
let bytes = hex_to_bytes(&result)?;
Ok(decode_address_array(&bytes))
}
pub async fn party_contribution_of(party_id: u64, funder_hex: &str) -> Result<u128, String> {
let funder = parse_eth_address(funder_hex)?;
let result = read_view(
selector("partyContributionOf(uint256,address)"),
&[u256_be(party_id as u128), addr_word(&funder)],
)
.await?;
decode_u256_as_u128(&result)
}
pub async fn parties_of(creator_hex: &str) -> Result<Vec<u64>, String> {
let creator = parse_eth_address(creator_hex)?;
let result = read_view(selector("partiesOf(address)"), &[addr_word(&creator)]).await?;
let bytes = hex_to_bytes(&result)?;
Ok(decode_u64_array(&bytes))
}
pub async fn party_count() -> Result<u64, String> {
let result = read_view(selector("partyCount()"), &[]).await?;
decode_u256_as_u64(&result)
}
pub async fn live_parties(start_after: u64, limit: u64) -> Result<Vec<u64>, String> {
let result = read_view(
selector("liveParties(uint256,uint256)"),
&[u256_be(start_after as u128), u256_be(limit as u128)],
)
.await?;
let bytes = hex_to_bytes(&result)?;
decode_uint_array_with_cursor(&bytes)
}
#[cfg(test)]
mod party_tests {
use super::*;
#[test]
fn form_party_calldata_layout() {
let members = [7u64, 8u64];
let shares = [6000u16, 4000u16];
let cd = encode_form_party(&members, &shares, 86_400);
assert_eq!(&cd[0..4], &selector("formParty(uint256[],uint16[],uint64)"));
assert_eq!(cd.len(), 4 + 3 * 32 + 3 * 32 + 3 * 32);
let word_u64 = |i: usize| {
u64::from_be_bytes(cd[4 + i * 32 + 24..4 + (i + 1) * 32].try_into().unwrap())
};
assert_eq!(word_u64(0), 96);
assert_eq!(word_u64(1), 192);
assert_eq!(word_u64(2), 86_400);
assert_eq!(word_u64(3), 2);
assert_eq!(word_u64(4), 7);
assert_eq!(word_u64(5), 8);
assert_eq!(word_u64(6), 2);
assert_eq!(word_u64(7), 6000);
assert_eq!(word_u64(8), 4000);
}
#[test]
fn form_party_single_member_offsets() {
let cd = encode_form_party(&[42], &[10_000], 3600);
assert_eq!(cd.len(), 4 + 3 * 32 + 2 * 32 + 2 * 32);
let word_u64 = |i: usize| {
u64::from_be_bytes(cd[4 + i * 32 + 24..4 + (i + 1) * 32].try_into().unwrap())
};
assert_eq!(word_u64(0), 96); assert_eq!(word_u64(1), 96 + 32 + 32); assert_eq!(word_u64(3), 1); assert_eq!(word_u64(4), 42);
assert_eq!(word_u64(5), 1); assert_eq!(word_u64(6), 10_000);
}
#[test]
fn fund_party_calldata_layout() {
let amount = 1_500_000_000_000_000_000u128; let cd = encode_fund_party(9, amount);
assert_eq!(&cd[0..4], &selector("fundParty(uint256,uint128)"));
assert_eq!(cd.len(), 4 + 64);
assert_eq!(u64::from_be_bytes(cd[4 + 24..4 + 32].try_into().unwrap()), 9);
assert_eq!(u128::from_be_bytes(cd[36 + 16..36 + 32].try_into().unwrap()), amount);
}
#[test]
fn single_arg_party_calldata_layouts() {
for sig in ["joinParty(uint256)", "completeParty(uint256)", "disbandParty(uint256)"] {
let cd = call_uint_bytes(sig, 11);
assert_eq!(&cd[0..4], &selector(sig));
assert_eq!(cd.len(), 36);
assert_eq!(u64::from_be_bytes(cd[28..36].try_into().unwrap()), 11);
}
}
#[test]
fn party_status_label_maps_enum() {
let mut p = Party {
creator: "0x00".into(),
expiry: 0,
status: 0,
escrow_wei: 0,
member_count: 0,
accepted_count: 0,
};
for (s, label) in [
(0u8, "forming"),
(1, "active"),
(2, "completed"),
(3, "disbanded"),
(9, "unknown"),
] {
p.status = s;
assert_eq!(p.status_label(), label);
}
}
}