Files
probably-decentralized/src/Decentralized.Domain/Mixer.cs
T

105 lines
5.0 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Decentralized.Domain
{
/// <summary>混币器里一笔在途的存入。</summary>
public sealed class MixDeposit
{
public string Asset;
public double Amount;
/// <summary>存入时一并带走的污点与成本(信用点)。</summary>
public double Taint, Basis;
public int Rounds;
public int ReadyDay;
/// <summary>存入的钱包地址:到期提回这里。</summary>
public string Wallet;
}
/// <summary>一笔混币结束时的结果,供夜间日志使用。</summary>
public sealed class MixResult
{
public bool Rugged;
public string Asset;
public double Amount, TaintBefore, TaintAfter;
public bool Flagged;
}
/// <summary>
/// 混币器:把币存进去,每轮扣手续费、降低污点,轮数 = 等待天数,到期提回同一个地址。
/// 匿名集 = NPC 背景量 + 玩家存入量,玩家占比越大清洗效果越差。镇压事件开始时可能被制裁:
/// 制裁期间提出的币视为被标记(出金审查按"来自混币器或被标记地址"加重,且地址被标记)。每晚有极小概率跑路。
/// </summary>
public sealed class Mixer
{
public List<MixDeposit> Pending = new List<MixDeposit>();
public int SanctionedUntil = -1;
/// <summary>提回的币最近一次从混币器出来的天数,用来给出金审查加重。</summary>
public int LastReturnDay = -999;
public bool Sanctioned(int day) => day <= SanctionedUntil;
/// <summary>资金来自混币器:最近 AML 窗口内提回过币,或者被标记。</summary>
public bool RecentlyMixed(int day) => day - LastReturnDay <= Tuning.AmlWindowDays;
/// <summary>一轮的清洗率:基准 × 匿名集系数。</summary>
public static double RoundRate(double depositValue) => Tuning.MixerCleanRate * Tuning.MixerNpcVolume / (Tuning.MixerNpcVolume + Math.Max(0, depositValue));
/// <summary>存入:从钱包按价值比例带走污点与成本。轮数超出范围或余额不足返回 null。</summary>
public MixDeposit Deposit(Wallet w, string asset, double amount, int rounds, int day, Func<string, double> price)
{
if (rounds < 1 || rounds > Tuning.MixerMaxRounds || amount <= 0 || w.Avail(asset) + 1e-9 < amount) return null;
double value = amount * price(asset);
if (value < Tuning.MixerMin) return null;
double total = w.Value(price);
double share = total > 0 ? Math.Min(1, value / total) : 0;
double taint = w.Taint * share, basis = w.Basis * share;
w.Add(asset, -amount);
w.Taint -= taint;
w.Basis -= basis;
var d = new MixDeposit { Asset = asset, Amount = amount, Taint = taint, Basis = basis, Rounds = rounds, ReadyDay = day + rounds, Wallet = w.Address };
Pending.Add(d);
return d;
}
/// <summary>镇压事件开始:有一定概率被制裁,持续若干天。</summary>
public void OnEvent(MarketEvent ev, int day, Rng rng)
{
if (ev == MarketEvent.Crackdown && rng.Chance(Math.Min(1, Tuning.MixerSanctionChance * StoryMods.Current.Sanction(day)))) SanctionedUntil = day + Tuning.MixerSanctionDays;
}
/// <summary>每晚:到期的存入提回钱包(污点按轮数降低),在途的各有小概率跑路。</summary>
public List<MixResult> Night(Wallet w, int day, Func<string, double> price, Rng rng) => Night(_ => w, day, price, rng);
/// <summary>每晚(多钱包):到期的存入按存入时的地址提回原钱包。</summary>
public List<MixResult> Night(Func<string, Wallet> wallet, int day, Func<string, double> price, Rng rng)
{
var results = new List<MixResult>();
foreach (var d in Pending.ToList())
{
if (rng.Chance(Tuning.MixerRugChance))
{
Pending.Remove(d);
results.Add(new MixResult { Rugged = true, Asset = d.Asset, Amount = d.Amount });
continue;
}
if (day < d.ReadyDay) continue;
Pending.Remove(d);
double rate = RoundRate(d.Amount * price(d.Asset));
double taint = d.Taint * Math.Pow(1 - rate, d.Rounds);
double amount = d.Amount * Math.Pow(1 - Tuning.MixerFee, d.Rounds);
bool flagged = Sanctioned(day);
var w = wallet(d.Wallet);
w.Add(d.Asset, amount);
w.Taint += taint;
w.Basis += d.Basis;
if (flagged) w.Flagged = true;
LastReturnDay = day;
results.Add(new MixResult { Asset = d.Asset, Amount = amount, TaintBefore = d.Taint, TaintAfter = taint, Flagged = flagged });
}
return results;
}
}
}