using System; using System.Collections.Generic; using System.Linq; namespace Decentralized.Domain { /// 一个钱包在革命军质押池里的仓位:计息的本金、排队等解锁的部分,以及存入时带来的污点与成本。 public sealed class StakeLot { public string Wallet; public double Amount; public List> Unlocking = new List>(); public double Taint, Basis; public double Total => Amount + Unlocking.Sum(u => u.Value); } /// 一次到期或利息的结果,供夜间日志使用。 public sealed class StakeEvent { public string Wallet; public double Interest, Released; } /// /// 革命军 CRA 质押池:质押的 CRA 每晚按日息计息(利息并入本金),取出要先申请,等 天,期间不计息、 /// 也不能提前取。到期自动回到质押时的钱包。质押的币不会被冻结,但仓位里仍是 CRA:螺旋来临时到期取回的币只值当时的价格。 /// 污点与成本随币存入、按比例随币取出。 /// public sealed class Staking { public List Lots = new List(); public StakeLot Of(string wallet) => Lots.Find(l => ChainAddress.Same(l.Wallet, wallet)); /// 全部计息中的质押量,补贴池按它算支出。 public double Earning => Lots.Sum(l => l.Amount); /// 质押:从钱包取出 CRA,污点与成本按价值比例随币留在仓位里。余额不足返回 false。 public bool Stake(Wallet w, double amount, Func price) { if (amount <= 0 || w.Avail(Assets.CRA) + 1e-9 < amount) return false; double total = w.Value(price); double share = total > 0 ? Math.Min(1, amount * price(Assets.CRA) / total) : 0; double taint = w.Taint * share, basis = w.Basis * share; w.Add(Assets.CRA, -amount); w.Taint -= taint; w.Basis -= basis; var lot = Of(w.Address) ?? AddLot(w.Address); lot.Amount += amount; lot.Taint += taint; lot.Basis += basis; return true; } private StakeLot AddLot(string wallet) { var l = new StakeLot { Wallet = wallet }; Lots.Add(l); return l; } /// 申请取出:这部分从计息本金里挪到排队区,day + 解锁天数 到期。额度不足返回 false。 public bool RequestUnstake(string wallet, double amount, int day) { var lot = Of(wallet); if (lot == null || amount <= 0 || lot.Amount + 1e-9 < amount) return false; lot.Amount -= amount; lot.Unlocking.Add(new KeyValuePair(day + Tuning.CraUnlockDays, amount)); return true; } /// 每晚:计息(日息 rate,利息并入本金),到期的部分回到钱包。返回有变化的仓位。 public List Night(int day, double rate, Func wallet) { var evs = new List(); foreach (var lot in Lots.ToList()) { var ev = new StakeEvent { Wallet = lot.Wallet }; ev.Interest = lot.Amount * rate; lot.Amount += ev.Interest; foreach (var u in lot.Unlocking.Where(x => day >= x.Key).ToList()) { double total = lot.Total; double share = total > 0 ? Math.Min(1, u.Value / total) : 0; double taint = lot.Taint * share, basis = lot.Basis * share; lot.Taint -= taint; lot.Basis -= basis; lot.Unlocking.Remove(u); var w = wallet(lot.Wallet); w.Add(Assets.CRA, u.Value); w.Taint += taint; w.Basis += basis; ev.Released += u.Value; } if (lot.Total < 1e-9) Lots.Remove(lot); if (ev.Interest > 0 || ev.Released > 0) evs.Add(ev); } return evs; } } }