use std::collections::HashSet;
use alloy_primitives::{Address, Bytes, U256};
use alloy_sol_types::SolCall;
use wp_evm_algebra_interfaces::farming::{IFarmingCenter, IMulticall};
use wp_evm_base::types::{Call, PlanFragment};
use crate::data::{FarmingClaim, FarmingEarnedGrid};
pub fn build_farming_claims(grids: &[FarmingEarnedGrid]) -> Vec<FarmingClaim> {
let mut claims = Vec::new();
for g in grids {
let mut ids = Vec::new();
for i in 0..g.token_ids.len() {
if g.reward[i] > U256::ZERO || g.bonus_reward[i] > U256::ZERO {
ids.push(g.token_ids[i]);
}
}
if !ids.is_empty() {
claims.push(FarmingClaim { incentive_key: g.incentive_key.clone(), token_ids: ids });
}
}
claims
}
pub fn plan_collect_and_claim(
farming_center: Address,
claims: &[FarmingClaim],
recipient: Address,
) -> Option<PlanFragment> {
if claims.is_empty() {
return None;
}
let mut inner: Vec<Bytes> = Vec::new();
let mut tokens: HashSet<Address> = HashSet::new();
for c in claims {
for &tid in &c.token_ids {
inner.push(
IFarmingCenter::collectRewardsCall { key: c.incentive_key.clone(), tokenId: tid }
.abi_encode()
.into(),
);
tokens.insert(c.incentive_key.rewardToken);
tokens.insert(c.incentive_key.bonusRewardToken);
}
}
if inner.is_empty() {
return None;
}
for token in tokens {
inner.push(
IFarmingCenter::claimRewardCall {
rewardToken: token,
to: recipient,
amountRequested: U256::ZERO,
}
.abi_encode()
.into(),
);
}
let calldata: Bytes = if inner.len() == 1 {
inner.into_iter().next().unwrap()
} else {
IMulticall::multicallCall { data: inner }.abi_encode().into()
};
Some(PlanFragment {
calls: vec![Call { target: farming_center, calldata, value: U256::ZERO }],
approvals: vec![],
value: U256::ZERO,
})
}
pub fn claim_from_grids(
farming_center: Address,
grids: &[FarmingEarnedGrid],
recipient: Address,
) -> Option<PlanFragment> {
plan_collect_and_claim(farming_center, &build_farming_claims(grids), recipient)
}
#[cfg(test)]
mod tests {
use super::*;
use wp_evm_algebra_interfaces::farming::IncentiveKey;
fn key(r: u8, b: u8, p: u8) -> IncentiveKey {
IncentiveKey {
rewardToken: Address::repeat_byte(r),
bonusRewardToken: Address::repeat_byte(b),
pool: Address::repeat_byte(p),
nonce: U256::from(1),
}
}
#[test]
fn prune_drops_all_zero_positions_and_collapses_to_none() {
let g = FarmingEarnedGrid {
pool: Address::repeat_byte(9),
incentive_key: key(1, 2, 9),
token_ids: vec![U256::from(1), U256::from(2)],
reward: vec![U256::ZERO, U256::ZERO],
bonus_reward: vec![U256::ZERO, U256::ZERO],
};
assert!(build_farming_claims(std::slice::from_ref(&g)).is_empty());
assert!(claim_from_grids(Address::repeat_byte(7), &[g], Address::repeat_byte(8)).is_none());
}
#[test]
fn plan_collect_and_claim_returns_none_when_claims_have_no_token_ids() {
let claims = vec![FarmingClaim { incentive_key: key(1, 2, 9), token_ids: vec![] }];
assert!(plan_collect_and_claim(Address::repeat_byte(7), &claims, Address::repeat_byte(8))
.is_none());
}
#[test]
fn plan_decodes_to_expected_shape() {
let recipient = Address::repeat_byte(8);
let farming_center = Address::repeat_byte(7);
let incentive_key = key(1, 2, 9);
let claims = vec![FarmingClaim {
incentive_key: incentive_key.clone(),
token_ids: vec![U256::from(1)],
}];
let frag = plan_collect_and_claim(farming_center, &claims, recipient).unwrap();
assert_eq!(frag.calls.len(), 1);
assert_eq!(frag.calls[0].target, farming_center);
assert!(frag.approvals.is_empty());
assert_eq!(frag.value, U256::ZERO);
assert_eq!(frag.calls[0].value, U256::ZERO);
let outer = IMulticall::multicallCall::abi_decode(&frag.calls[0].calldata)
.expect("decode multicall");
assert_eq!(outer.data.len(), 3);
let mut collects = Vec::new();
let mut claim_tokens = Vec::new();
for call in outer.data {
if call.starts_with(IFarmingCenter::collectRewardsCall::SELECTOR.as_slice()) {
let decoded = IFarmingCenter::collectRewardsCall::abi_decode(&call)
.expect("decode collectRewards");
collects.push((decoded.key, decoded.tokenId));
} else if call.starts_with(IFarmingCenter::claimRewardCall::SELECTOR.as_slice()) {
let decoded =
IFarmingCenter::claimRewardCall::abi_decode(&call).expect("decode claimReward");
assert_eq!(decoded.to, recipient);
assert_eq!(decoded.amountRequested, U256::ZERO);
claim_tokens.push(decoded.rewardToken);
} else {
panic!(
"unexpected inner selector: 0x{}",
alloy_primitives::hex::encode(&call[..4])
);
}
}
assert_eq!(collects, vec![(incentive_key, U256::from(1))]);
claim_tokens.sort();
assert_eq!(claim_tokens, vec![Address::repeat_byte(1), Address::repeat_byte(2)]);
}
}