From d0dfa932264523d953f0474a557ba30a0af7e13f Mon Sep 17 00:00:00 2001
From: ZHU MINCHAO <48154958+MinchaoZhu@users.noreply.github.com>
Date: Thu, 5 Nov 2026 07:14:38 +0800
Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E5=8D=81=E9=98=B6=E6=AE=B5=20stage=20?=
=?UTF-8?q?2=EF=BC=88=E4=BA=8C=EF=BC=89=EF=BC=9ACRA=20=E9=83=A8=E5=88=86?=
=?UTF-8?q?=E6=8A=B5=E6=8A=BC=EF=BC=88=E5=82=A8=E5=A4=87=E3=80=81=E6=8A=B5?=
=?UTF-8?q?=E6=8A=BC=E7=8E=87=E3=80=81=E6=8C=AA=E7=94=A8=E4=B8=8E=E5=B4=A9?=
=?UTF-8?q?=E7=9B=98=E7=BA=BF=E3=80=81=E5=8A=AB=E6=8E=A0=E8=A1=A5=E5=82=A8?=
=?UTF-8?q?=E5=A4=87=EF=BC=89=E3=80=81=E5=AE=98=E6=96=B9=E9=93=B8=E9=80=A0?=
=?UTF-8?q?=E4=B8=8E=E9=94=80=E6=AF=81=E6=8E=92=E9=98=9F=E3=80=81=E5=AE=98?=
=?UTF-8?q?=E6=96=B9=E8=B7=91=E8=B7=AF=E3=80=81=E8=B4=A8=E6=8A=BC=E5=A4=9A?=
=?UTF-8?q?=E5=B8=81=E7=A7=8D=E5=85=A5=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/Decentralized.Domain/Stablecoins.cs | 175 ++++++++++++++----
src/Decentralized.Domain/Tuning.cs | 11 +-
src/Decentralized.Domain/TuningTable.cs | 14 +-
src/Decentralized/DealerStock.cs | 8 +-
.../ModFiles/Localization/de.csv | 23 ++-
.../ModFiles/Localization/en.csv | 23 ++-
.../ModFiles/Localization/es.csv | 23 ++-
.../ModFiles/Localization/fr.csv | 23 ++-
.../ModFiles/Localization/ja.csv | 23 ++-
.../ModFiles/Localization/pl.csv | 23 ++-
.../ModFiles/Localization/ru.csv | 23 ++-
.../ModFiles/Localization/zh.csv | 23 ++-
src/Decentralized/Night.cs | 15 ++
src/Decentralized/PredictGame.cs | 12 +-
src/Decentralized/StoryLinks.cs | 2 +-
src/Decentralized/TerminalWindow.cs | 71 +++++--
tests/Decentralized.Domain.Tests/Program.cs | 118 ++++++++++--
17 files changed, 493 insertions(+), 117 deletions(-)
diff --git a/src/Decentralized.Domain/Stablecoins.cs b/src/Decentralized.Domain/Stablecoins.cs
index 734677b..133645a 100644
--- a/src/Decentralized.Domain/Stablecoins.cs
+++ b/src/Decentralized.Domain/Stablecoins.cs
@@ -1,19 +1,43 @@
using System;
using System.Collections.Generic;
+using System.Linq;
namespace Decentralized.Domain
{
///
/// 三种稳定币的价格与状态:CRC(上层,托管银行出事时脱锚、可能减记),CRT(黑市,镇压或火并时脱锚,
- /// 镇压期间按黑市声望出卖客户),CRA(革命军,质押付息,补贴池告急或外生触发时进入三天死亡螺旋)。
+ /// 镇压期间按黑市声望出卖客户),CRA(革命军,部分抵押:流通量 S、储备 R(CRC)、抵押率 c;质押利息以无抵押的新 CRA 付出,
+ /// 稀释储备覆盖率 R/S;革命军挪用储备时 R/S 低于崩盘线进入三天死亡螺旋,劫掠上层补回储备)。
///
+ public enum CraOrderKind { Minted, Burned, Lost }
+
+ /// 官方一级市场的排队单:铸造(存入 CRC 和 SPARK)或销毁(存入 CRA),到期结算,官方可能把它黑掉。
+ public sealed class CraOrder
+ {
+ public int Id, Due;
+ public string Wallet;
+ public bool Mint;
+ public double Cra, Crc, Spark, C;
+ }
+
+ public sealed class CraOrderEvent
+ {
+ public CraOrder Order;
+ public CraOrderKind Kind;
+ public double Cra, Crc, Spark;
+ }
+
public sealed class Stablecoins
{
public double Crc = 1, Crt = 1, Cra = 1;
public int CrcDepegEnd = -1, CrtDepegEnd = -1;
public bool CrcHaircutPending;
- public double CraSubsidy = Tuning.CraSubsidy0;
- public int CraWarnDay = -1, CraSpiralDay = -1;
+ /// CRA 流通量与储备(CRC);c 是现在铸造要求的 CRC 份额(0~1)。
+ public double Supply = Tuning.CraS0, Reserve = Tuning.CraS0 * Tuning.CraC0, C = Tuning.CraC0;
+ /// 预兆日(新闻发出的那天)、预定的挪用日、崩盘开始日(-1 = 没有)。
+ public int CraWarnDay = -1, CraEventDay = -1, CraCrashDay = -1;
+ public List Orders = new List();
+ public int OrderSeq;
public double RevPower = 50;
/// 平衡代币 SPARK 的价格(信用点);SparkPre 是螺旋开始前一刻的价格,螺旋三天按它的倍数走。
public double Spark = Tuning.SparkStart, SparkPre = -1;
@@ -45,7 +69,7 @@ namespace Decentralized.Domain
Crt = rng.Range(Tuning.CrtDepegMin, Tuning.CrtDepegMax);
CrtDepegEnd = day + rng.Range(Tuning.CrtDepegDaysMin, Tuning.CrtDepegDaysMax);
}
- if (ev == MarketEvent.RevRaidUpper) Spark *= Tuning.SparkRaidShock;
+ if (ev == MarketEvent.RevRaidUpper) { Spark *= Tuning.SparkRaidShock; if (CraCrashDay < 0) Reserve += Tuning.CraLoot * Supply; }
if (ev == MarketEvent.LowerUnrest) Spark *= Tuning.SparkUnrestShock;
return 1;
}
@@ -57,36 +81,68 @@ namespace Decentralized.Domain
/// 镇压开始时是否冻结玩家的 CRT:按黑市声望,声望越低越容易被黑市"出卖"。
public static bool CrtFrozenByCrackdown(double blackMarketRep, Rng rng) => rng.Chance(CrtFreezeChance(blackMarketRep));
- /// 铸造:用 CRA 按当前价换 SPARK,收手续费。余额不足返回 -1。
- public double Mint(Wallet w, double cra)
+ /// 储备覆盖率 R/S。
+ public double Ratio => Supply > 0 ? Reserve / Supply : 1;
+
+ /// 崩盘线之上的储备(CRC):吃空即必然崩盘。预测盘和线索用它估预兆。
+ public double Cushion => Reserve - Tuning.CraCrashRatio * Supply;
+ public static double Cushion0 => Tuning.CraS0 * (Tuning.CraC0 - Tuning.CraCrashRatio);
+
+ /// 每晚的垫子消耗:利息按无抵押的新 CRA 付出,每份把崩盘线抬高 CraCrashRatio。
+ public static double CushionDrain(double stakedTotal, double rate) => stakedTotal * rate * Tuning.CraCrashRatio;
+
+ /// 储备覆盖率对应的价格:高于安全线为 1,线性降到崩盘线的 1 − CraDiscountMax。
+ public static double PriceOfRatio(double r)
{
- if (cra <= 0 || Spark <= 0 || w.Avail(Assets.CRA) + 1e-9 < cra) return -1;
- double got = cra * Cra / Spark * (1 - Tuning.SparkFee);
- w.Add(Assets.CRA, -cra); w.Add(Assets.SPARK, got);
- w.RecordBuy(Assets.SPARK, got, cra * Cra);
- return got;
+ if (r >= Tuning.CraSafeRatio) return 1;
+ double t = Math.Max(0, (r - Tuning.CraCrashRatio) / (Tuning.CraSafeRatio - Tuning.CraCrashRatio));
+ return 1 - Tuning.CraDiscountMax * (1 - t);
}
- /// 销毁:用 SPARK 按当前价换 CRA,收手续费。余额不足返回 -1。
- public double Burn(Wallet w, double spark)
+ /// 铸造 cra 个 CRA 要的 CRC 和 SPARK 数量(SPARK 部分按现价折算)。
+ public void Collateral(double cra, out double crc, out double spark)
{
- if (spark <= 0 || Cra <= 0 || w.Avail(Assets.SPARK) + 1e-9 < spark) return -1;
- double got = spark * Spark / Cra * (1 - Tuning.SparkFee);
- w.Add(Assets.SPARK, -spark); w.Add(Assets.CRA, got);
- w.RecordBuy(Assets.CRA, got, spark * Spark);
- return got;
+ crc = cra * C;
+ spark = Spark > 0 ? cra * (1 - C) / Spark : 0;
}
- public bool CraSubsidyLow => CraSubsidy <= Tuning.CraSubsidy0 * Tuning.CraSubsidyWarn;
+ ///
+ /// 铸造申请:从钱包扣 CRC 和 SPARK,排队 CraMintDaysMin..Max 天,到期铸出 CRA。余额不足返回 null。
+ ///
+ public CraOrder RequestMint(Wallet w, double cra, int day, Rng rng)
+ {
+ if (cra <= 0) return null;
+ Collateral(cra, out double crc, out double spark);
+ if (w.Avail(Assets.CRC) + 1e-9 < crc || w.Avail(Assets.SPARK) + 1e-9 < spark) return null;
+ w.Add(Assets.CRC, -crc); w.Add(Assets.SPARK, -spark);
+ var o = new CraOrder { Id = ++OrderSeq, Wallet = w.Address, Mint = true, Cra = cra, Crc = crc, Spark = spark, C = C, Due = day + rng.Range(Tuning.CraMintDaysMin, Tuning.CraMintDaysMax) };
+ Orders.Add(o);
+ return o;
+ }
+
+ /// 销毁申请:从钱包扣 CRA,排队 CraUnlockDays 天,到期换回 CRC 和 SPARK。余额不足返回 null。
+ public CraOrder RequestBurn(Wallet w, double cra, int day)
+ {
+ if (cra <= 0 || w.Avail(Assets.CRA) + 1e-9 < cra) return null;
+ w.Add(Assets.CRA, -cra);
+ var o = new CraOrder { Id = ++OrderSeq, Wallet = w.Address, Mint = false, Cra = cra, C = C, Due = day + Tuning.CraUnlockDays };
+ Orders.Add(o);
+ return o;
+ }
+
+ /// 官方每晚黑掉一笔排队单的概率:系数 × (1 − CRA 价格),价格不低于 1 时为 0。
+ public double RugChance => Math.Max(0, 1 - Cra) * Tuning.CraRugPerDepeg;
+
+ public bool CraSubsidyLow => Cushion <= Cushion0 * Tuning.CraSubsidyWarn;
/// CRA 的预兆:外生螺旋的新闻已出,或补贴池告急(利率下调)。螺旋开始后不再算预兆。
- public bool CraWarning(int day) => (CraWarnDay >= 0 && CraWarnDay <= day || CraSubsidyLow) && Cra >= 0.97;
+ public bool CraWarning(int day) => (CraWarnDay >= 0 && CraWarnDay <= day || CraSubsidyLow) && CraCrashDay < 0 && Cra >= 0.97;
public double CraRate => CraSubsidyLow ? Tuning.CraRateLow : Tuning.CraRate;
///
/// 夜间结算。返回 CRC 持有量要乘的系数(减记兑现时为 0.95,否则为 1)。
- /// playerStaked:玩家的 CRA 质押额,用来算补贴支出(玩家的利息由调用方按 发放)。
+ /// playerStaked:玩家的 CRA 质押额,用来算利息带来的稀释(玩家的利息由调用方按 发放)。
///
public double Night(int day, Rng rng, double playerStaked)
{
@@ -107,32 +163,87 @@ namespace Decentralized.Domain
if (RevHistory.Count > 7) RevHistory.RemoveAt(0);
RevPower = Math.Max(0, Math.Min(100, RevPower + rng.Gauss() * 3));
- if (CraSpiralDay < 0)
+ if (CraCrashDay < 0)
{
- CraSubsidy -= (Tuning.CraNpcStake + playerStaked) * CraRate;
- bool revFalling = RevHistory.Count == 7 && RevPower < RevHistory[0] - 5;
- double weekly = Tuning.CraSpiralWeekly * (revFalling ? 3 : 1) * StoryMods.Current.CraSpiral(day);
- double daily = 1 - Math.Pow(1 - weekly, 1.0 / 7);
- if (CraWarnDay < 0 && (rng.Chance(daily) || CraSubsidy <= 0))
+ double interest = (Tuning.CraNpcStake + playerStaked) * CraRate;
+ Supply += interest;
+ // 抵押率调整:价格低于 1 上调,回到 1 下调;兑付的 CRC 份额不超过储备均值(见 )
+ if (Cra < 0.995) C = Math.Min(1, C + Tuning.CraCrStep);
+ else if (Cra >= 0.999) C = Math.Max(Tuning.CraCrMin, C - Tuning.CraCrStep);
+ if (CraEventDay < 0)
{
- CraWarnDay = day;
- CraSpiralDay = day + rng.Range(Tuning.CraWarnLeadMin, Tuning.CraWarnLeadMax);
+ bool revFalling = RevHistory.Count == 7 && RevPower < RevHistory[0] - 5;
+ double weekly = Tuning.CraSpiralWeekly * (revFalling ? 3 : 1) * StoryMods.Current.CraSpiral(day);
+ double daily = 1 - Math.Pow(1 - weekly, 1.0 / 7);
+ if (rng.Chance(daily) || Cushion <= 0)
+ {
+ CraWarnDay = day;
+ CraEventDay = day + rng.Range(Tuning.CraWarnLeadMin, Tuning.CraWarnLeadMax);
+ }
+ }
+ else if (day >= CraEventDay)
+ {
+ // 挪用:储备被革命军拿走一部分,R/S 掉到崩盘线以下就是挤兑
+ Reserve *= 1 - rng.Range(Tuning.CraEmbezzleMin, Tuning.CraEmbezzleMax);
+ CraEventDay = -1; CraWarnDay = -1;
+ if (Ratio < Tuning.CraCrashRatio) CraCrashDay = day;
}
}
- if (CraSpiralDay > 0 && day >= CraSpiralDay)
+ if (CraCrashDay >= 0)
{
- int k = Math.Min(day - CraSpiralDay, Tuning.CraSpiralPath.Length - 1);
+ int k = Math.Min(day - CraCrashDay, Tuning.CraSpiralPath.Length - 1);
Cra = Tuning.CraSpiralPath[k];
if (SparkPre < 0) SparkPre = Spark;
- Spark = SparkPre * Tuning.SparkSpiralPath[Math.Min(day - CraSpiralDay, Tuning.SparkSpiralPath.Length - 1)];
+ Spark = SparkPre * Tuning.SparkSpiralPath[Math.Min(day - CraCrashDay, Tuning.SparkSpiralPath.Length - 1)];
}
else
{
+ Cra = PriceOfRatio(Ratio);
Spark *= Math.Exp(rng.Gauss() * Tuning.SparkVol) * StoryMods.Current.TakeSparkPush();
Spark += (Tuning.SparkStart - Spark) * Tuning.SparkRevert;
Spark = Math.Max(Tuning.SparkStart * Tuning.SparkMinMult, Math.Min(Tuning.SparkStart * Tuning.SparkMaxMult, Spark));
}
return crcFactor;
}
+
+ ///
+ /// 销毁一份 CRA 的兑付:CRC 份额 = min(申请时的 c, 储备均值 R/S),其余按现价用新增发的 SPARK 补。
+ /// 返回 (CRC 数量, SPARK 数量),并扣减储备和流通量。
+ ///
+ public void Redeem(double cra, double c, out double crc, out double spark)
+ {
+ double share = Math.Min(c, Ratio);
+ crc = Math.Min(Reserve, cra * share);
+ spark = Spark > 0 ? cra * (1 - crc / Math.Max(1e-9, cra)) / Spark : 0;
+ Reserve -= crc; Supply = Math.Max(0, Supply - cra);
+ }
+
+ ///
+ /// 夜间处理排队单:先对每一笔掷官方跑路(整笔丢失),再结算到期的。
+ /// 铸造到期:CRC 进储备、SPARK 销毁、流通量增加;销毁到期:按 兑付。
+ ///
+ public List NightOrders(int day, Rng rng)
+ {
+ var evs = new List();
+ foreach (var o in Orders.ToList())
+ {
+ if (rng.Chance(RugChance)) { Orders.Remove(o); evs.Add(new CraOrderEvent { Order = o, Kind = CraOrderKind.Lost }); continue; }
+ if (day < o.Due) continue;
+ Orders.Remove(o);
+ var ev = new CraOrderEvent { Order = o };
+ if (o.Mint)
+ {
+ Supply += o.Cra; Reserve += o.Crc;
+ ev.Kind = CraOrderKind.Minted; ev.Cra = o.Cra;
+ }
+ else
+ {
+ Redeem(o.Cra, o.C, out ev.Crc, out ev.Spark);
+ ev.Kind = CraOrderKind.Burned;
+ }
+ evs.Add(ev);
+ }
+ return evs;
+ }
}
}
diff --git a/src/Decentralized.Domain/Tuning.cs b/src/Decentralized.Domain/Tuning.cs
index 57694aa..5124cd9 100644
--- a/src/Decentralized.Domain/Tuning.cs
+++ b/src/Decentralized.Domain/Tuning.cs
@@ -51,8 +51,15 @@ namespace Decentralized.Domain
public const double CrtSwapFee = 0.02;
public const double CraRate = 0.005, CraRateLow = 0.002;
public const int CraUnlockDays = 4;
- public const double CraSubsidy0 = 30000, CraSubsidyWarn = 0.2, CraNpcStake = 20000;
- public const double CraSpiralWeekly = 0.02;
+ public const double CraSubsidyWarn = 0.2, CraNpcStake = 20000;
+ /// CRA 部分抵押:初始流通量与抵押率、抵押率下限与每晚调整步长;R/S 安全线、崩盘线、安全线到崩盘线之间的最大折价;
+ /// 挪用储备的比例范围;劫掠上层补回的储备(占流通量);一级市场铸造等待天数;官方跑路系数(× (1 − CRA 价格))。数值由 research/crasim 定。
+ public const double CraS0 = 60000, CraC0 = 0.8, CraCrMin = 0.5, CraCrStep = 0.02;
+ public const double CraSafeRatio = 0.6, CraCrashRatio = 0.35, CraDiscountMax = 0.15;
+ public const double CraEmbezzleMin = 0.4, CraEmbezzleMax = 0.8, CraLoot = 0.09;
+ public const int CraMintDaysMin = 1, CraMintDaysMax = 2;
+ public const double CraRugPerDepeg = 1;
+ public const double CraSpiralWeekly = 0.03;
public const int CraWarnLeadMin = 4, CraWarnLeadMax = 7;
public static readonly double[] CraSpiralPath = { 0.85, 0.30, 0.02 };
/// 平衡代币 SPARK:起始价与长期锚(信用点)、日波动率、每晚向锚回归的比例、价格上下限(锚的倍数);铸销手续费;
diff --git a/src/Decentralized.Domain/TuningTable.cs b/src/Decentralized.Domain/TuningTable.cs
index d65c34f..18e40fd 100644
--- a/src/Decentralized.Domain/TuningTable.cs
+++ b/src/Decentralized.Domain/TuningTable.cs
@@ -172,11 +172,23 @@ namespace Decentralized.Domain
["CoinPayCrt"] = Tuning.CoinPayCrt,
["CoinPayPremium"] = Tuning.CoinPayPremium,
["CoinPayPsc"] = Tuning.CoinPayPsc,
+ ["CraC0"] = Tuning.CraC0,
+ ["CraCrMin"] = Tuning.CraCrMin,
+ ["CraCrStep"] = Tuning.CraCrStep,
+ ["CraCrashRatio"] = Tuning.CraCrashRatio,
+ ["CraDiscountMax"] = Tuning.CraDiscountMax,
+ ["CraEmbezzleMax"] = Tuning.CraEmbezzleMax,
+ ["CraEmbezzleMin"] = Tuning.CraEmbezzleMin,
+ ["CraLoot"] = Tuning.CraLoot,
+ ["CraMintDaysMax"] = Tuning.CraMintDaysMax,
+ ["CraMintDaysMin"] = Tuning.CraMintDaysMin,
["CraNpcStake"] = Tuning.CraNpcStake,
["CraRate"] = Tuning.CraRate,
["CraRateLow"] = Tuning.CraRateLow,
+ ["CraRugPerDepeg"] = Tuning.CraRugPerDepeg,
+ ["CraS0"] = Tuning.CraS0,
+ ["CraSafeRatio"] = Tuning.CraSafeRatio,
["CraSpiralWeekly"] = Tuning.CraSpiralWeekly,
- ["CraSubsidy0"] = Tuning.CraSubsidy0,
["CraSubsidyWarn"] = Tuning.CraSubsidyWarn,
["CraUnlockDays"] = Tuning.CraUnlockDays,
["CraWarnLeadMax"] = Tuning.CraWarnLeadMax,
diff --git a/src/Decentralized/DealerStock.cs b/src/Decentralized/DealerStock.cs
index 8468d8a..5158f70 100644
--- a/src/Decentralized/DealerStock.cs
+++ b/src/Decentralized/DealerStock.cs
@@ -89,8 +89,8 @@ namespace Decentralized
var d = Session.Data; int day = StoreStation.GetDayCounter(); var bk = d.Predict;
bk.Markets.Add(PredictMarket.Create(++bk.Seq, PredictKind.Era, "era", L.T("predict.era"), 0, day, day + 1, day + 3, PredictMath.EraProb(day, day + 1, day + 3, -1) + 0.05));
double h = 1 - System.Math.Pow(1 - Tuning.CraSpiralWeekly, 1.0 / 7);
- bk.Markets.Add(PredictMarket.Create(++bk.Seq, PredictKind.Cra, "cra", "CRA", 0, day, day + 1, day + 3, PredictMath.CraWarnProb(d.Stable.CraSubsidy, (Tuning.CraNpcStake + d.Staking.Earning) * d.Stable.CraRate, day, day + 1, day + 3, h)));
- Log.Info($"测试:矿机时代 {d.Network.EraDay},CRA 补贴池 {d.Stable.CraSubsidy:0}");
+ bk.Markets.Add(PredictMarket.Create(++bk.Seq, PredictKind.Cra, "cra", "CRA", 0, day, day + 1, day + 3, PredictMath.CraWarnProb(d.Stable.Cushion, Stablecoins.CushionDrain(Tuning.CraNpcStake + d.Staking.Earning, d.Stable.CraRate), day, day + 1, day + 3, h)));
+ Log.Info($"测试:矿机时代 {d.Network.EraDay},CRA 储备 {d.Stable.Reserve:0}(R/S {d.Stable.Ratio:0.00})");
});
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Informant: queue power shortage in 3 days (test)", () => Log.Guard("线人测试", () =>
{
@@ -186,8 +186,8 @@ namespace Decentralized
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "CRA spiral warning (test)", () =>
{
var st = Session.Data.Stable; int day = StoreStation.GetDayCounter();
- st.CraWarnDay = day; st.CraSpiralDay = day + 3;
- Log.Info($"测试:CRA 预兆今天,螺旋第 {st.CraSpiralDay} 天");
+ st.CraWarnDay = day; st.CraEventDay = day + 3;
+ Log.Info($"测试:CRA 预兆今天,挪用日第 {st.CraEventDay} 天");
});
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Give terminal (test)", () => { Items.Give(ModHelper.SpawnItem(Items.Terminal)); });
ModHelper.AddDebugFunction(ModInfo.Id, ModInfo.Name, "Hardware wallets (test)", () => { Items.Give(Items.NewHwWallet(false)); Items.Give(Items.NewHwWallet(true)); Log.Info("测试:给了一个正品、一个被投毒的硬件钱包"); });
diff --git a/src/Decentralized/ModFiles/Localization/de.csv b/src/Decentralized/ModFiles/Localization/de.csv
index 788cccd..979eee5 100644
--- a/src/Decentralized/ModFiles/Localization/de.csv
+++ b/src/Decentralized/ModFiles/Localization/de.csv
@@ -460,9 +460,6 @@ stake.ops,{0}
stake.avail,CRA {0} Gestakt {1}
stake.do,Staken
stake.undo,Auszahlung beantragen
-spark.h,Ausgleichstoken
-spark.mint,SPARK prägen
-spark.burn,SPARK verbrennen
stake.sum.in,{0} CRA staken
stake.sum.out,Auszahlung von {0} CRA beantragen (Gutschrift in {1} Tagen)
stake.done.in,{0} CRA gestakt.
@@ -470,9 +467,7 @@ stake.done.out,Auszahlung von {0} CRA beantragt; Rückkehr in die Wallet an Tag
nightlog.stakeint,Staking-Zinsen +{0} CRA.
nightlog.unstaked,{0} CRA aus dem Staking wurden an {1} zurückgebucht.
news.crawarn.title,Unruhe im Revolution-Staking-Pool
-news.crawarn.desc,"Großanleger ziehen Kapital ab und der Staking-Subventionspool leert sich. Die Entsperrung dauert vier Tage; manche meinen, es sei Zeit zu handeln."
news.cralow.title,Revolution senkt Staking-Zinssatz
-news.cralow.desc,Der Subventionspool ist fast leer und die Revolution hat den CRA-Staking-Zins auf einen Bruchteil gekürzt.
stake.avail2,SPARK {0}
poin.sub.spot,Spot
poin.sub.perp,Perps
@@ -906,3 +901,21 @@ swap.get,Erhalten
swap.preview,"Ca. {0} {1} {2} Pool(s), Verlust {3} %"
swap.go,Tauschen
swap.same,Zahlungs- und Empfangswährung müssen verschieden sein.
+cra.mint,Prägen
+cra.burn,Einlösen
+mint.h,Offizielle Prägung (CRC + SPARK)
+mint.need,Benötigt {0} CRC und {1} SPARK
+mint.reserve,Reservedeckung {0} % Besicherung {1} %
+mint.queue,Offene Aufträge
+mint.order,"{0} {1} CRA, Tag {2}"
+mint.sum.in,{0} CRA mit {1} CRC und {2} SPARK prägen ({3}–{4} Tage)
+mint.sum.out,{0} CRA gegen CRC und SPARK einlösen ({1} Tage)
+mint.done.in,Prägeauftrag erteilt; fällig an Tag {0}.
+mint.done.out,Einlöseauftrag erteilt; fällig an Tag {0}.
+stake.coin,Einzahlungswährung
+stake.sum.via,{0} {1} tauschen → ca. {2} CRA und staken
+nightlog.cra.minted,{0} CRA geprägt und gestakt ({1}).
+nightlog.cra.burned,"{0} CRA eingelöst: +{1} CRC, +{2} SPARK ({3})."
+nightlog.cra.lost,Der Herausgeber hat nicht reagiert: Der Auftrag ({1}) über {0} CRA ist verloren.
+news.crawarn.desc,"Jemand zählt die Reserve der Revolution nach; gestaktes CRA lässt sich womöglich nicht vollständig einlösen. Das Entsperren dauert vier Tage; manche sagen, es sei Zeit zu handeln."
+news.cralow.desc,Die Reservedeckung wird knapp; die Revolution hat den CRA-Staking-Zins auf einen Bruchteil gesenkt.
diff --git a/src/Decentralized/ModFiles/Localization/en.csv b/src/Decentralized/ModFiles/Localization/en.csv
index ed8a0bd..87cf2c1 100644
--- a/src/Decentralized/ModFiles/Localization/en.csv
+++ b/src/Decentralized/ModFiles/Localization/en.csv
@@ -460,9 +460,6 @@ stake.ops,{0}
stake.avail,CRA {0} Staked {1}
stake.do,Stake
stake.undo,Request withdrawal
-spark.h,Balance token
-spark.mint,Mint SPARK
-spark.burn,Burn SPARK
stake.sum.in,Stake {0} CRA
stake.sum.out,Request withdrawal of {0} CRA (arrives in {1} days)
stake.done.in,Staked {0} CRA.
@@ -470,9 +467,7 @@ stake.done.out,Requested withdrawal of {0} CRA; back in the wallet on day {1}.
nightlog.stakeint,Staking interest +{0} CRA.
nightlog.unstaked,{0} CRA withdrawn from staking returned to {1}.
news.crawarn.title,Unrest at the Revolution staking pool
-news.crawarn.desc,Big holders are pulling out and the staking subsidy pool is running dry. Unlocking takes four days; some say it is time to move.
news.cralow.title,Revolution cuts staking rate
-news.cralow.desc,"The subsidy pool is nearly empty, and the Revolution has cut the CRA staking rate to a fraction."
stake.avail2,SPARK {0}
poin.sub.spot,Spot
poin.sub.perp,Perps
@@ -906,3 +901,21 @@ swap.get,Receive
swap.preview,"Receive about {0} {1} {2} pool(s), loss {3}%"
swap.go,Swap
swap.same,Pay and receive must be different coins.
+cra.mint,Mint
+cra.burn,Redeem
+mint.h,Official mint (CRC + SPARK)
+mint.need,Needs {0} CRC and {1} SPARK
+mint.reserve,Reserve ratio {0}% Collateral ratio {1}%
+mint.queue,Pending orders
+mint.order,"{0} {1} CRA, day {2}"
+mint.sum.in,Mint {0} CRA with {1} CRC and {2} SPARK ({3}–{4} days)
+mint.sum.out,Redeem {0} CRA for CRC and SPARK ({1} days)
+mint.done.in,Mint order placed; due on day {0}.
+mint.done.out,Redeem order placed; due on day {0}.
+stake.coin,Deposit coin
+stake.sum.via,"Swap {0} {1} → about {2} CRA, then stake"
+nightlog.cra.minted,"Minted {0} CRA, now staked ({1})."
+nightlog.cra.burned,"Redeemed {0} CRA: +{1} CRC, +{2} SPARK ({3})."
+nightlog.cra.lost,The issuer did not respond: the {1} order of {0} CRA is lost.
+news.crawarn.desc,"Someone is counting the Revolution's reserve, and staked CRA may not be redeemable in full. Unlocking takes four days; some say it is time to move."
+news.cralow.desc,"The reserve ratio is running low, and the Revolution has cut the CRA staking rate to a fraction."
diff --git a/src/Decentralized/ModFiles/Localization/es.csv b/src/Decentralized/ModFiles/Localization/es.csv
index 3d948c4..734cf25 100644
--- a/src/Decentralized/ModFiles/Localization/es.csv
+++ b/src/Decentralized/ModFiles/Localization/es.csv
@@ -460,9 +460,6 @@ stake.ops,{0}
stake.avail,CRA {0} En staking {1}
stake.do,Hacer staking
stake.undo,Solicitar retiro
-spark.h,Token de balance
-spark.mint,Acuñar SPARK
-spark.burn,Quemar SPARK
stake.sum.in,Poner {0} CRA en staking
stake.sum.out,Solicitar retiro de {0} CRA (llega en {1} día(s))
stake.done.in,{0} CRA puestos en staking.
@@ -470,9 +467,7 @@ stake.done.out,Retiro de {0} CRA solicitado; vuelve a la billetera el día {1}.
nightlog.stakeint,Interés de staking +{0} CRA.
nightlog.unstaked,Los {0} CRA retirados de staking volvieron a {1}.
news.crawarn.title,Agitación en el fondo de staking de la Revolución
-news.crawarn.desc,Los grandes inversores están retirándose y el fondo de subsidio de staking se agota. El desbloqueo tarda cuatro días; algunos dicen que es hora de actuar.
news.cralow.title,La Revolución reduce la tasa de staking
-news.cralow.desc,"El fondo de subsidio está casi vacío, y la Revolución redujo la tasa de staking de CRA a una fracción."
stake.avail2,SPARK {0}
poin.sub.spot,Spot
poin.sub.perp,Perpetuos
@@ -906,3 +901,21 @@ swap.get,Recibir
swap.preview,"Aprox. {0} {1} {2} pool(s), pérdida {3} %"
swap.go,Canjear
swap.same,Las monedas a pagar y recibir deben ser distintas.
+cra.mint,Acuñar
+cra.burn,Redimir
+mint.h,Acuñación oficial (CRC + SPARK)
+mint.need,Requiere {0} CRC y {1} SPARK
+mint.reserve,Cobertura {0} % Colateral {1} %
+mint.queue,Órdenes pendientes
+mint.order,"{0} {1} CRA, día {2}"
+mint.sum.in,Acuñar {0} CRA con {1} CRC y {2} SPARK ({3}–{4} días)
+mint.sum.out,Redimir {0} CRA por CRC y SPARK ({1} días)
+mint.done.in,Orden de acuñación enviada; vence el día {0}.
+mint.done.out,Orden de redención enviada; vence el día {0}.
+stake.coin,Moneda a depositar
+stake.sum.via,Canjear {0} {1} → aprox. {2} CRA y apostar
+nightlog.cra.minted,{0} CRA acuñados y apostados ({1}).
+nightlog.cra.burned,"{0} CRA redimidos: +{1} CRC, +{2} SPARK ({3})."
+nightlog.cra.lost,El emisor no respondió: la orden ({1}) de {0} CRA se perdió.
+news.crawarn.desc,Alguien está contando la reserva de la Revolución y quizá los CRA apostados no se puedan redimir por completo. Desbloquear tarda cuatro días; algunos dicen que es hora de actuar.
+news.cralow.desc,La cobertura de la reserva se agota y la Revolución ha recortado el tipo de apuesta de CRA a una fracción.
diff --git a/src/Decentralized/ModFiles/Localization/fr.csv b/src/Decentralized/ModFiles/Localization/fr.csv
index 9428b72..e4f20bb 100644
--- a/src/Decentralized/ModFiles/Localization/fr.csv
+++ b/src/Decentralized/ModFiles/Localization/fr.csv
@@ -460,9 +460,6 @@ stake.ops,{0}
stake.avail,CRA {0} Staké {1}
stake.do,Staker
stake.undo,Demander un retrait
-spark.h,Jeton d'équilibre
-spark.mint,Frapper SPARK
-spark.burn,Brûler SPARK
stake.sum.in,Staker {0} CRA
stake.sum.out,Demander le retrait de {0} CRA (disponible dans {1} jours)
stake.done.in,{0} CRA stakés.
@@ -470,9 +467,7 @@ stake.done.out,Retrait de {0} CRA demandé ; retour au portefeuille le jour {1}.
nightlog.stakeint,Intérêts de staking +{0} CRA.
nightlog.unstaked,Les {0} CRA retirés du staking sont revenus sur {1}.
news.crawarn.title,Troubles au pool de staking de la Révolution
-news.crawarn.desc,Les gros détenteurs se retirent et le pool de subvention de staking s'épuise. Le déverrouillage prend quatre jours ; certains disent qu'il est temps d'agir.
news.cralow.title,La Révolution réduit le taux de staking
-news.cralow.desc,"Le pool de subvention est presque vide, et la Révolution a réduit le taux de staking du CRA à une fraction de sa valeur."
stake.avail2,SPARK {0}
poin.sub.spot,Spot
poin.sub.perp,Perps
@@ -906,3 +901,21 @@ swap.get,Recevoir
swap.preview,"Environ {0} {1} {2} pool(s), perte {3} %"
swap.go,Échanger
swap.same,Les monnaies payée et reçue doivent différer.
+cra.mint,Frapper
+cra.burn,Racheter
+mint.h,Frappe officielle (CRC + SPARK)
+mint.need,Nécessite {0} CRC et {1} SPARK
+mint.reserve,Couverture {0} % Collatéral {1} %
+mint.queue,Ordres en attente
+mint.order,"{0} {1} CRA, jour {2}"
+mint.sum.in,Frapper {0} CRA avec {1} CRC et {2} SPARK ({3}–{4} jours)
+mint.sum.out,Racheter {0} CRA contre CRC et SPARK ({1} jours)
+mint.done.in,Ordre de frappe passé ; échéance au jour {0}.
+mint.done.out,Ordre de rachat passé ; échéance au jour {0}.
+stake.coin,Monnaie déposée
+stake.sum.via,Échanger {0} {1} → environ {2} CRA puis staker
+nightlog.cra.minted,{0} CRA frappés et stakés ({1}).
+nightlog.cra.burned,"{0} CRA rachetés : +{1} CRC, +{2} SPARK ({3})."
+nightlog.cra.lost,L'émetteur n'a pas répondu : l'ordre ({1}) de {0} CRA est perdu.
+news.crawarn.desc,Quelqu'un fait l'inventaire de la réserve de la Révolution ; les CRA stakés pourraient ne pas être remboursés en totalité. Le déblocage prend quatre jours ; certains disent qu'il est temps d'agir.
+news.cralow.desc,La couverture de la réserve s'amenuise ; la Révolution a réduit le taux de staking du CRA à une fraction.
diff --git a/src/Decentralized/ModFiles/Localization/ja.csv b/src/Decentralized/ModFiles/Localization/ja.csv
index e5cf5bf..fc1e199 100644
--- a/src/Decentralized/ModFiles/Localization/ja.csv
+++ b/src/Decentralized/ModFiles/Localization/ja.csv
@@ -460,9 +460,6 @@ stake.ops,{0}
stake.avail,CRA {0} ステーキング中 {1}
stake.do,ステーキング
stake.undo,出金申請
-spark.h,バランストークン
-spark.mint,SPARKをミント
-spark.burn,SPARKをバーン
stake.sum.in,{0} CRA をステーキング
stake.sum.out,{0} CRA の出金申請({1}日後に着金)
stake.done.in,{0} CRA をステーキングしました。
@@ -470,9 +467,7 @@ stake.done.out,{0} CRA の出金を申請しました。{1}日目にウォレッ
nightlog.stakeint,ステーキング利息 +{0} CRA。
nightlog.unstaked,ステーキングから出金された {0} CRA が {1} に戻った。
news.crawarn.title,革命家のステーキングプールに不穏な動き
-news.crawarn.desc,大口保有者が相次いで資金を引き揚げており、補助金プールが枯渇寸前だ。ロック解除には4日かかるため、今すぐ動くべきだとの声も上がっている。
news.cralow.title,革命家がステーキングの日利を引き下げ
-news.cralow.desc,補助金プールがほぼ底をつき、革命家はCRAステーキングの日利を従来のごくわずかな水準まで引き下げた。
stake.avail2,SPARK {0}
poin.sub.spot,現物
poin.sub.perp,無期限先物
@@ -906,3 +901,21 @@ swap.get,受取
swap.preview,約 {0} {1} プール {2} 個、損失 {3}%
swap.go,スワップ
swap.same,支払いと受取は別の通貨にしてください。
+cra.mint,発行
+cra.burn,償還
+mint.h,公式発行(CRC+SPARK)
+mint.need,必要:{0} CRC と {1} SPARK
+mint.reserve,準備率 {0}% 担保率 {1}%
+mint.queue,処理待ち
+mint.order,{0} {1} CRA、{2} 日目
+mint.sum.in,{1} CRC と {2} SPARK で {0} CRA を発行({3}~{4} 日)
+mint.sum.out,{0} CRA を償還して CRC と SPARK に({1} 日)
+mint.done.in,発行を申請しました。{0} 日目に決済されます。
+mint.done.out,償還を申請しました。{0} 日目に決済されます。
+stake.coin,預入通貨
+stake.sum.via,{0} {1} をスワップ → 約 {2} CRA をステーク
+nightlog.cra.minted,発行した {0} CRA をステークしました({1})。
+nightlog.cra.burned,{0} CRA を償還:+{1} CRC、+{2} SPARK({3})。
+nightlog.cra.lost,発行元から応答がありません:{0} CRA の{1}注文は失われました。
+news.crawarn.desc,革命軍の準備金を数え直す者がいる。ステークした CRA は全額償還できないかもしれない。解除には四日かかる。動くなら今だという声もある。
+news.cralow.desc,準備率が危うくなり、革命軍は CRA のステーキング日利を元の数分の一に引き下げた。
diff --git a/src/Decentralized/ModFiles/Localization/pl.csv b/src/Decentralized/ModFiles/Localization/pl.csv
index aa1b017..e1eba28 100644
--- a/src/Decentralized/ModFiles/Localization/pl.csv
+++ b/src/Decentralized/ModFiles/Localization/pl.csv
@@ -460,9 +460,6 @@ stake.ops,{0}
stake.avail,CRA {0} W stakingu {1}
stake.do,Stakuj
stake.undo,Zgłoś wypłatę
-spark.h,Token równoważący
-spark.mint,Wybij SPARK
-spark.burn,Spal SPARK
stake.sum.in,Zastakuj {0} CRA
stake.sum.out,Zgłoś wypłatę {0} CRA (dostępne za: {1} d.)
stake.done.in,Zastakowano {0} CRA.
@@ -470,9 +467,7 @@ stake.done.out,Zgłoszono wypłatę {0} CRA; powrót do portfela w dniu {1}.
nightlog.stakeint,Odsetki ze stakingu +{0} CRA.
nightlog.unstaked,{0} CRA wycofane ze stakingu zwrócono do {1}.
news.crawarn.title,Niepokoje wokół puli stakingowej Rewolucji
-news.crawarn.desc,"Duzi posiadacze wycofują środki, a pula dotacji stakingowych wysycha. Odblokowanie trwa cztery dni; niektórzy mówią, że czas działać."
news.cralow.title,Rewolucja obniża stawkę stakingu
-news.cralow.desc,"Pula dotacji jest niemal pusta, a Rewolucja obniżyła stawkę stakingu CRA do ułamka pierwotnej wartości."
stake.avail2,SPARK {0}
poin.sub.spot,Spot
poin.sub.perp,Wieczyste
@@ -906,3 +901,21 @@ swap.get,Otrzymuję
swap.preview,"Około {0} {1} pule: {2}, strata {3}%"
swap.go,Wymień
swap.same,Waluty płatności i odbioru muszą się różnić.
+cra.mint,Wybij
+cra.burn,Umorz
+mint.h,Oficjalna emisja (CRC + SPARK)
+mint.need,Wymaga {0} CRC i {1} SPARK
+mint.reserve,Pokrycie rezerwy {0}% Zabezpieczenie {1}%
+mint.queue,Oczekujące zlecenia
+mint.order,"{0} {1} CRA, dzień {2}"
+mint.sum.in,Wybij {0} CRA za {1} CRC i {2} SPARK ({3}–{4} dni)
+mint.sum.out,Umorz {0} CRA na CRC i SPARK ({1} dni)
+mint.done.in,Zlecenie emisji złożone; termin: dzień {0}.
+mint.done.out,Zlecenie umorzenia złożone; termin: dzień {0}.
+stake.coin,Waluta wpłaty
+stake.sum.via,Wymień {0} {1} → około {2} CRA i stakuj
+nightlog.cra.minted,Wybito {0} CRA i zastakowano ({1}).
+nightlog.cra.burned,"Umorzono {0} CRA: +{1} CRC, +{2} SPARK ({3})."
+nightlog.cra.lost,Emitent nie odpowiedział: zlecenie ({1}) na {0} CRA przepadło.
+news.crawarn.desc,"Ktoś liczy rezerwę Rewolucji i zastakowane CRA mogą nie zostać w pełni wypłacone. Odblokowanie trwa cztery dni; niektórzy mówią, że czas działać."
+news.cralow.desc,"Pokrycie rezerwy maleje, a Rewolucja obniżyła stopę stakingu CRA do ułamka."
diff --git a/src/Decentralized/ModFiles/Localization/ru.csv b/src/Decentralized/ModFiles/Localization/ru.csv
index 8b3cce8..c8a339f 100644
--- a/src/Decentralized/ModFiles/Localization/ru.csv
+++ b/src/Decentralized/ModFiles/Localization/ru.csv
@@ -460,9 +460,6 @@ stake.ops,{0}
stake.avail,CRA {0} В стейкинге {1}
stake.do,В стейкинг
stake.undo,Запросить вывод
-spark.h,Балансовый токен
-spark.mint,Минт SPARK
-spark.burn,Сжигание SPARK
stake.sum.in,Внести {0} CRA в стейкинг
stake.sum.out,Запросить вывод {0} CRA (дней до зачисления: {1})
stake.done.in,{0} CRA внесено в стейкинг.
@@ -470,9 +467,7 @@ stake.done.out,Запрошен вывод {0} CRA; возврат в кошел
nightlog.stakeint,Доход от стейкинга: +{0} CRA.
nightlog.unstaked,Выведенные из стейкинга {0} CRA вернулись в {1}.
news.crawarn.title,Волнения вокруг пула стейкинга Революционеров
-news.crawarn.desc,"Крупные держатели выводят средства, и пул субсидий для стейкинга истощается. Разблокировка занимает четыре дня; ходят слухи, что пора действовать."
news.cralow.title,Революционеры снижают ставку стейкинга
-news.cralow.desc,"Пул субсидий почти опустел, и Революционеры снизили ставку стейкинга CRA до малой доли прежней."
stake.avail2,SPARK {0}
poin.sub.spot,Спот
poin.sub.perp,Бессрочные
@@ -906,3 +901,21 @@ swap.get,Получаю
swap.preview,"Около {0} {1} пулов: {2}, потери {3}%"
swap.go,Обменять
swap.same,Монеты оплаты и получения должны различаться.
+cra.mint,Выпустить
+cra.burn,Погасить
+mint.h,Официальный выпуск (CRC + SPARK)
+mint.need,Нужно {0} CRC и {1} SPARK
+mint.reserve,Покрытие резерва {0}% Залог {1}%
+mint.queue,Очередь
+mint.order,"{0} {1} CRA, день {2}"
+mint.sum.in,Выпустить {0} CRA за {1} CRC и {2} SPARK ({3}–{4} дн.)
+mint.sum.out,Погасить {0} CRA в CRC и SPARK ({1} дн.)
+mint.done.in,Заявка на выпуск подана; срок — день {0}.
+mint.done.out,Заявка на погашение подана; срок — день {0}.
+stake.coin,Монета для вклада
+stake.sum.via,Обменять {0} {1} → около {2} CRA и внести в стейкинг
+nightlog.cra.minted,"Выпущено {0} CRA, внесены в стейкинг ({1})."
+nightlog.cra.burned,"Погашено {0} CRA: +{1} CRC, +{2} SPARK ({3})."
+nightlog.cra.lost,Эмитент не ответил: заявка ({1}) на {0} CRA потеряна.
+news.crawarn.desc,"Кто-то пересчитывает резерв Революции, и CRA в стейкинге могут не выкупить полностью. Разблокировка занимает четыре дня; кое-кто говорит, что пора действовать."
+news.cralow.desc,"Покрытие резерва тает, и Революция снизила ставку по стейкингу CRA до малой доли."
diff --git a/src/Decentralized/ModFiles/Localization/zh.csv b/src/Decentralized/ModFiles/Localization/zh.csv
index 6965684..ac5fa6e 100644
--- a/src/Decentralized/ModFiles/Localization/zh.csv
+++ b/src/Decentralized/ModFiles/Localization/zh.csv
@@ -459,9 +459,6 @@ stake.ops,"{0}"
stake.avail,"CRA {0} 已质押 {1}"
stake.do,质押
stake.undo,申请取出
-spark.h,平衡代币
-spark.mint,铸造 SPARK
-spark.burn,销毁 SPARK
stake.sum.in,"质押 {0} CRA"
stake.sum.out,"申请取出 {0} CRA({1} 天后到账)"
stake.done.in,"已质押 {0} CRA。"
@@ -469,9 +466,7 @@ stake.done.out,"已申请取出 {0} CRA,第 {1} 天回到钱包。"
nightlog.stakeint,"质押利息 +{0} CRA。"
nightlog.unstaked,"质押取出的 {0} CRA 回到了{1}。"
news.crawarn.title,革命军质押池传出异动
-news.crawarn.desc,有大户在大量撤出,质押补贴池告急。解锁需要四天,有人说该动手了。
news.cralow.title,革命军下调质押日息
-news.cralow.desc,补贴池余额见底,革命军把 CRA 质押日息降到了原来的零头。
stake.avail2,"SPARK {0}"
poin.sub.spot,现货
poin.sub.perp,永续
@@ -905,3 +900,21 @@ swap.get,获得
swap.preview,约得 {0} {1} 经 {2} 个池,损耗 {3}%
swap.go,兑换
swap.same,支付和获得的币种不能相同。
+cra.mint,铸造
+cra.burn,销毁
+mint.h,官方铸造(CRC 加 SPARK)
+mint.need,需要 {0} CRC 和 {1} SPARK
+mint.reserve,储备覆盖率 {0}% 抵押率 {1}%
+mint.queue,待处理
+mint.order,{0} {1} CRA,第 {2} 天
+mint.sum.in,用 {1} CRC 和 {2} SPARK 铸造 {0} CRA({3}~{4} 天)
+mint.sum.out,销毁 {0} CRA 换回 CRC 和 SPARK({1} 天)
+mint.done.in,已提交铸造,第 {0} 天结算。
+mint.done.out,已提交销毁,第 {0} 天结算。
+stake.coin,存入币种
+stake.sum.via,兑换 {0} {1} → 约 {2} CRA 并质押
+nightlog.cra.minted,铸造的 {0} CRA 已进入质押({1})。
+nightlog.cra.burned,销毁 {0} CRA:收回 {1} CRC、{2} SPARK({3})。
+nightlog.cra.lost,官方没有回应:{0} CRA 的{1}单丢失。
+news.crawarn.desc,有人在清点革命军的储备,质押的 CRA 恐怕兑付不了。解锁需要四天,有人说该动手了。
+news.cralow.desc,储备覆盖率告急,革命军把 CRA 质押日息降到了原来的零头。
diff --git a/src/Decentralized/Night.cs b/src/Decentralized/Night.cs
index 5a1f86a..4590fba 100644
--- a/src/Decentralized/Night.cs
+++ b/src/Decentralized/Night.cs
@@ -228,6 +228,13 @@ namespace Decentralized
if (perpEvents.Any(e => e.Kind == "seize")) SeizeForDebt(d, day);
var stakeEvents = d.Staking.Night(day, d.Stable.CraRate, d.WalletAt);
double crcFactor = d.Stable.Night(day, d.Rng, d.Staking.Earning);
+ var orderEvents = d.Stable.NightOrders(day, d.Rng);
+ foreach (var oe in orderEvents)
+ {
+ var ow = d.WalletAt(oe.Order.Wallet);
+ if (oe.Kind == CraOrderKind.Minted) { ow.Add(Assets.CRA, oe.Cra); d.Staking.Stake(ow, oe.Cra, Session.Price); }
+ else if (oe.Kind == CraOrderKind.Burned) { ow.Add(Assets.CRC, oe.Crc); ow.Add(Assets.SPARK, oe.Spark); }
+ }
if (d.Stable.CraWarnDay == day) News.Show("pd_news_crawarn_" + day, L.T("news.crawarn.title"), L.T("news.crawarn.desc"), 4);
if (d.Stable.CraSubsidyLow && !d.CraLowNews) { d.CraLowNews = true; News.Show("pd_news_cralow_" + day, L.T("news.cralow.title"), L.T("news.cralow.desc"), 3); }
if (crcFactor < 1)
@@ -326,6 +333,14 @@ namespace Decentralized
var sw = d.Own(e.Wallet);
if (e.Released > 0) ps.AddNightLog(L.T("nightlog.unstaked", Fmt.Num(e.Released), sw != null ? WalletWindow.NameOf(sw) : e.Wallet));
}
+ foreach (var oe in orderEvents)
+ {
+ var ow = d.Own(oe.Order.Wallet);
+ string wn = ow != null ? WalletWindow.NameOf(ow) : oe.Order.Wallet;
+ if (oe.Kind == CraOrderKind.Minted) ps.AddNightLog(L.T("nightlog.cra.minted", Fmt.Num(oe.Cra), wn));
+ else if (oe.Kind == CraOrderKind.Burned) ps.AddNightLog(L.T("nightlog.cra.burned", Fmt.Num(oe.Order.Cra), Fmt.Num(oe.Crc), Fmt.Num(oe.Spark), wn));
+ else ps.AddNightLog(L.T("nightlog.cra.lost", Fmt.Num(oe.Order.Cra), oe.Order.Mint ? L.T("cra.mint") : L.T("cra.burn")), "cc7755");
+ }
double interest = stakeEvents.Sum(e => e.Interest);
if (interest > 0) ps.AddNightLog(L.T("nightlog.stakeint", Fmt.Num(interest)));
foreach (var name in report.Burnt) ps.AddNightLog(L.T("nightlog.burnt", name), "cc7755");
diff --git a/src/Decentralized/PredictGame.cs b/src/Decentralized/PredictGame.cs
index cb7ab53..d893a92 100644
--- a/src/Decentralized/PredictGame.cs
+++ b/src/Decentralized/PredictGame.cs
@@ -120,8 +120,8 @@ namespace Decentralized
double thr = t;
list.Add(new PredictCandidate { Kind = PredictKind.Gas, Key = "gas", Title = "Gas", Param = thr, Tomorrow = true, Prob = (a, b) => GasPublic(d, day + 1, thr) });
}
- if (d.Stable.CraWarnDay < 0 && d.Stable.CraSpiralDay < 0)
- list.Add(new PredictCandidate { Kind = PredictKind.Cra, Key = "cra", Title = "CRA", Prob = (from, to) => PredictMath.CraWarnProb(d.Stable.CraSubsidy, CraDrain(d), day, from, to, CraHazard) });
+ if (d.Stable.CraWarnDay < 0 && d.Stable.CraCrashDay < 0)
+ list.Add(new PredictCandidate { Kind = PredictKind.Cra, Key = "cra", Title = "CRA", Prob = (from, to) => PredictMath.CraWarnProb(d.Stable.Cushion, CraDrain(d), day, from, to, CraHazard) });
if (!d.Network.EraDone && d.Network.EraDay > 0 && day + 4 >= (d.Network.EraWarned ? d.Network.EraWarnDay : Tuning.EraMin))
list.Add(new PredictCandidate { Kind = PredictKind.Era, Key = "era", Title = L.T("predict.era"), Prob = (from, to) => PredictMath.EraProb(day, from, to, d.Network.EraWarned ? d.Network.EraWarnDay : -1) });
return list;
@@ -132,7 +132,7 @@ namespace Decentralized
d.GasPhase < 0 ? PredictMath.GasFlat(day, thr) : (1 - Tuning.PredictGasBlind) * PredictMath.GasAtLeast(day, d.GasPhase, thr) + Tuning.PredictGasBlind * PredictMath.GasFlat(day, thr);
private static double CraHazard => 1 - Math.Pow(1 - Tuning.CraSpiralWeekly, 1.0 / 7);
- private static double CraDrain(SaveData d) => (Tuning.CraNpcStake + d.Staking.Earning) * d.Stable.CraRate;
+ private static double CraDrain(SaveData d) => Stablecoins.CushionDrain(Tuning.CraNpcStake + d.Staking.Earning, d.Stable.CraRate);
/// 开门后补盘。价格盘一次只留一个(阈值不同的盘口会互相对冲)。
public static void Morning(SaveData d, int day)
@@ -199,8 +199,8 @@ namespace Decentralized
case PredictKind.Gas:
return d.GasPhase < 0 ? (double?)null : GasPublic(d, m.ToDay, m.Param);
case PredictKind.Cra:
- if (d.Stable.CraWarnDay >= 0 || d.Stable.CraSpiralDay >= 0) return 0.02;
- return PredictMath.CraWarnProb(d.Stable.CraSubsidy, CraDrain(d), day + 1, m.FromDay, m.ToDay, CraHazard);
+ if (d.Stable.CraWarnDay >= 0 || d.Stable.CraCrashDay >= 0) return 0.02;
+ return PredictMath.CraWarnProb(d.Stable.Cushion, CraDrain(d), day + 1, m.FromDay, m.ToDay, CraHazard);
case PredictKind.Era:
return PredictMath.EraProb(day, m.FromDay, m.ToDay, d.Network.EraWarned ? d.Network.EraWarnDay : -1);
}
@@ -309,7 +309,7 @@ namespace Decentralized
if (d.GasHist.Count > 0) list.Add(L.T("predict.clue.gas", string.Join(" ", d.GasHist.Skip(Math.Max(0, d.GasHist.Count - 7)).Select(x => x.ToString("0.0", System.Globalization.CultureInfo.InvariantCulture)))));
break;
case PredictKind.Cra:
- list.Add(L.T("predict.clue.subsidy", (d.Stable.CraSubsidy / Tuning.CraSubsidy0 * 100).ToString("0")));
+ list.Add(L.T("predict.clue.subsidy", (d.Stable.Cushion / Stablecoins.Cushion0 * 100).ToString("0")));
list.Add(L.T("predict.clue.drain", Fmt.Num(CraDrain(d))));
break;
case PredictKind.Era:
diff --git a/src/Decentralized/StoryLinks.cs b/src/Decentralized/StoryLinks.cs
index f815377..14775be 100644
--- a/src/Decentralized/StoryLinks.cs
+++ b/src/Decentralized/StoryLinks.cs
@@ -27,7 +27,7 @@ namespace Decentralized
private static void Apply(SaveData d, int day, string key)
{
if (!d.Story.Apply(key, day)) return;
- if (key == "serpico_rev") d.Stable.CraSubsidy += Tuning.StoryRevSubsidy;
+ if (key == "serpico_rev") d.Stable.Reserve += Tuning.StoryRevSubsidy;
News.Show("pd_news_story_" + key, L.T("news.story." + key + ".title"), L.T("news.story." + key + ".desc"), 4, 9);
Log.Info($"剧情联动生效:{key}(第 {day} 天)");
}
diff --git a/src/Decentralized/TerminalWindow.cs b/src/Decentralized/TerminalWindow.cs
index 29510da..fbf9d4f 100644
--- a/src/Decentralized/TerminalWindow.cs
+++ b/src/Decentralized/TerminalWindow.cs
@@ -2234,7 +2234,15 @@ namespace Decentralized
}
}
l.AddSeparator();
+ l.AddLabel(L.T("mint.reserve", (st.Ratio * 100).ToString("0", CultureInfo.InvariantCulture), (st.C * 100).ToString("0", CultureInfo.InvariantCulture)), "mint_reserve_dim");
l.AddLabel(L.T("spark.price", Fmt.Num(st.Spark)));
+ var mine = st.Orders.Where(o => d.Own(o.Wallet) != null).ToList();
+ if (mine.Count > 0)
+ {
+ l.AddSeparator();
+ l.AddLabel(L.T("mint.queue"));
+ foreach (var o in mine) l.AddLabel(L.T("mint.order", L.T(o.Mint ? "cra.mint" : "cra.burn"), Fmt.Num(o.Cra), o.Due), "mint_order_dim");
+ }
}, r =>
{
r.AddHeader(L.T("stake.ops", WalletWindow.NameOf(w)));
@@ -2242,19 +2250,35 @@ namespace Decentralized
r.AddLabel(L.T("stake.avail2", Fmt.Num(w.Avail(Assets.SPARK))), "stake_avail2_dim");
AmountRow(r);
r.AddSpace(4f);
+ r.AddLabel(L.T("stake.coin"));
+ r.BeginRow(4f);
+ foreach (var c in StakeCoins)
+ {
+ var coin = c;
+ PledgerSkin.Pick(r, coin, coin == _stakeCoin, () => { _stakeCoin = coin; Open(); }, _picked);
+ }
+ r.End();
r.BeginRow(6f);
r.AddButton(L.T("stake.do"), () => StakeCall(true));
r.AddButton(L.T("stake.undo"), () => StakeCall(false));
r.End();
r.AddSeparator();
- r.AddHeader(L.T("spark.h"));
+ r.AddHeader(L.T("mint.h"));
+ if (double.TryParse((_amount ?? "").Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out double mv) && mv > 0)
+ {
+ st.Collateral(mv, out double needCrc, out double needSpark);
+ r.AddLabel(L.T("mint.need", Fmt.Num(needCrc), Fmt.Num(needSpark)), "mint_need_dim");
+ }
r.BeginRow(6f);
- r.AddButton(L.T("spark.mint"), () => SparkCall(true));
- r.AddButton(L.T("spark.burn"), () => SparkCall(false));
+ r.AddButton(L.T("cra.mint"), () => MintCall(true));
+ r.AddButton(L.T("cra.burn"), () => MintCall(false));
r.End();
});
}
+ private static readonly string[] StakeCoins = { Assets.CRA, Assets.CRC, Assets.CRT, Assets.SPARK };
+ private static string _stakeCoin = Assets.CRA;
+
/// 手续费用钱包里价值最高的那种币付。
private static string BestFeeAsset(Wallet w) =>
w.Balances.Where(k => k.Value > 1e-9 && Session.Price(k.Key) > 0).OrderByDescending(k => k.Value * Session.Price(k.Key)).Select(k => k.Key).FirstOrDefault() ?? Assets.PSC;
@@ -2264,42 +2288,57 @@ namespace Decentralized
if (!ReadAmount(out double v)) return;
var d = Session.Data;
var w = d.Hot;
- if (into ? w.Avail(Assets.CRA) + 1e-9 < v : (d.Staking.Of(w.Address)?.Amount ?? 0) + 1e-9 < v) { _message = L.T("msg.balance"); Open(); return; }
+ string coin = into ? _stakeCoin : Assets.CRA;
+ if (into ? w.Avail(coin) + 1e-9 < v : (d.Staking.Of(w.Address)?.Amount ?? 0) + 1e-9 < v) { _message = L.T("msg.balance"); Open(); return; }
+ var q = into && coin != Assets.CRA ? d.Market.Quote(coin, Assets.CRA, v) : null;
_message = L.T("hw.wait");
Open();
WalletWindow.OpenForBill(new WalletWindow.Bill
{
- Contract = true, Asset = Assets.CRA, Amount = v, Spend = into ? v : 0, FeeAsset = into ? null : BestFeeAsset(w),
- Summary = L.T(into ? "stake.sum.in" : "stake.sum.out", Fmt.Num(v), Tuning.CraUnlockDays),
+ Contract = true, Asset = coin, Amount = v, Spend = into ? v : 0, FeeAsset = into ? null : BestFeeAsset(w),
+ Summary = into && q != null ? L.T("stake.sum.via", Fmt.Num(v), coin, Fmt.Num(q.Out)) : L.T(into ? "stake.sum.in" : "stake.sum.out", Fmt.Num(v), Tuning.CraUnlockDays),
Execute = pw =>
{
int day = StoreStation.GetDayCounter();
- bool ok = into ? d.Staking.Stake(pw, v, Session.Price) : d.Staking.RequestUnstake(pw.Address, v, day);
- _message = ok ? L.T(into ? "stake.done.in" : "stake.done.out", Fmt.Num(v), day + Tuning.CraUnlockDays) : L.T("msg.balance");
+ double cra = v;
+ if (into && coin != Assets.CRA)
+ {
+ if (pw.Avail(coin) + 1e-9 < v) { _message = L.T("msg.balance"); Open(); return; }
+ double spent = v * Session.Price(coin);
+ cra = d.Market.Swap(coin, Assets.CRA, v);
+ pw.Add(coin, -v); pw.Add(Assets.CRA, cra); pw.RecordBuy(Assets.CRA, cra, spent);
+ CandleChart.Trade(coin, spent); CandleChart.Trade(Assets.CRA, spent);
+ }
+ bool ok = into ? d.Staking.Stake(pw, cra, Session.Price) : d.Staking.RequestUnstake(pw.Address, v, day);
+ _message = ok ? L.T(into ? "stake.done.in" : "stake.done.out", Fmt.Num(cra), day + Tuning.CraUnlockDays) : L.T("msg.balance");
Session.Save();
Open();
},
});
}
- private static void SparkCall(bool mint)
+ /// 官方一级市场:存入 CRC 和 SPARK 铸造 CRA(铸出后直接进质押),或销毁 CRA 换回两者。排队等待,官方可能不兑现。
+ private static void MintCall(bool mint)
{
if (!ReadAmount(out double v)) return;
var d = Session.Data;
var w = d.Hot;
- string pay = mint ? Assets.CRA : Assets.SPARK, get = mint ? Assets.SPARK : Assets.CRA;
- if (w.Avail(pay) + 1e-9 < v) { _message = L.T("msg.balance"); Open(); return; }
- double quote = mint ? v * d.Stable.Cra / d.Stable.Spark * (1 - Tuning.SparkFee) : v * d.Stable.Spark / d.Stable.Cra * (1 - Tuning.SparkFee);
+ var st = d.Stable;
+ st.Collateral(v, out double needCrc, out double needSpark);
+ bool enough = mint ? w.Avail(Assets.CRC) + 1e-9 >= needCrc && w.Avail(Assets.SPARK) + 1e-9 >= needSpark : w.Avail(Assets.CRA) + 1e-9 >= v;
+ if (!enough) { _message = L.T("msg.balance"); Open(); return; }
+ int minD = Tuning.CraMintDaysMin, maxD = Tuning.CraMintDaysMax;
_message = L.T("hw.wait");
Open();
WalletWindow.OpenForBill(new WalletWindow.Bill
{
- Contract = true, Asset = pay, Amount = v, Summary = L.T("hw.swap", Fmt.Num(v), pay, Fmt.Num(quote), get),
+ Contract = true, Asset = mint ? Assets.CRC : Assets.CRA, Amount = mint ? needCrc : v, Spend = mint ? needCrc : v,
+ Summary = mint ? L.T("mint.sum.in", Fmt.Num(v), Fmt.Num(needCrc), Fmt.Num(needSpark), minD, maxD) : L.T("mint.sum.out", Fmt.Num(v), Tuning.CraUnlockDays),
Execute = pw =>
{
- double got = mint ? d.Stable.Mint(pw, v) : d.Stable.Burn(pw, v);
- if (got >= 0) { double cr = v * Session.Price(pay); CandleChart.Trade(Assets.SPARK, cr); CandleChart.Trade(Assets.CRA, cr); }
- _message = got < 0 ? L.T("msg.balance") : L.T("msg.trade", Fmt.Num(v), pay, Fmt.Num(got), get);
+ int day = StoreStation.GetDayCounter();
+ var o = mint ? st.RequestMint(pw, v, day, d.Rng) : st.RequestBurn(pw, v, day);
+ _message = o == null ? L.T("msg.balance") : L.T(mint ? "mint.done.in" : "mint.done.out", o.Due);
Session.Save();
Open();
},
diff --git a/tests/Decentralized.Domain.Tests/Program.cs b/tests/Decentralized.Domain.Tests/Program.cs
index f9371cf..68c3c61 100644
--- a/tests/Decentralized.Domain.Tests/Program.cs
+++ b/tests/Decentralized.Domain.Tests/Program.cs
@@ -376,18 +376,108 @@ internal static class Program
"CRT 冻结概率:声望 0 为 40%,25 为 20%,50 为 0");
});
- Test("稳定币:CRA 补贴池告急即为预兆;螺旋三天 0.85 / 0.30 / 0.02", () =>
+ Test("稳定币:CRA 垫子告急即为预兆;储备吃空必崩,螺旋三天 0.85 / 0.30 / 0.02", () =>
{
- var s = new Stablecoins { CraSubsidy = Tuning.CraSubsidy0 * 0.19 };
- Check(s.CraWarning(1) && s.CraRate == Tuning.CraRateLow, "补贴池低于 20%:预兆出现、利率降到 0.2%");
- var t = new Stablecoins { CraSubsidy = 1 };
+ var s = new Stablecoins { Reserve = Tuning.CraCrashRatio * Tuning.CraS0 + Stablecoins.Cushion0 * 0.19 };
+ Check(s.CraWarning(1) && s.CraRate == Tuning.CraRateLow, "垫子低于 20%:预兆出现、利率降到 0.2%");
+ var t = new Stablecoins { Reserve = Tuning.CraS0 * 0.2 };
var rng = new Rng(3);
t.Night(1, rng, 100000);
- Check(t.CraWarnDay == 1 && t.CraSpiralDay >= 5 && t.CraSpiralDay <= 8, $"补贴池吃空触发螺旋,提前 4–7 天:第 {t.CraSpiralDay} 天开始");
+ Check(t.CraWarnDay == 1 && t.CraEventDay >= 5 && t.CraEventDay <= 8, $"垫子吃空触发预兆,提前 4–7 天:第 {t.CraEventDay} 天挪用");
+ int ev = t.CraEventDay;
var path = new List();
- for (int d = 2; d <= t.CraSpiralDay + 3; d++) { t.Night(d, rng, 0); if (d >= t.CraSpiralDay) path.Add(t.Cra); }
- Check(path.Take(3).SequenceEqual(new[] { 0.85, 0.30, 0.02 }), $"螺旋路径:{string.Join(" / ", path)}");
- Check(!t.CraWarning(t.CraSpiralDay + 1), "螺旋开始后不再算预兆");
+ for (int d = 2; d <= ev + 3; d++) { t.Night(d, rng, 0); if (d >= ev) path.Add(t.Cra); }
+ Check(t.CraCrashDay == ev && path.Take(3).SequenceEqual(new[] { 0.85, 0.30, 0.02 }), $"崩盘路径:{string.Join(" / ", path)}");
+ Check(!t.CraWarning(ev + 1), "崩盘开始后不再算预兆");
+ });
+
+ Test("稳定币:挪用后 R/S 低于崩盘线才崩,否则只是折价;劫掠上层补回储备", () =>
+ {
+ int crashes = 0, discounts = 0;
+ for (ulong seed = 1; seed <= 60; seed++)
+ {
+ var s = new Stablecoins { Supply = 100000, Reserve = 100000, C = 0.8, CraWarnDay = 1, CraEventDay = 2 };
+ s.Night(2, new Rng(seed), 0);
+ double r = s.Ratio;
+ Check(r >= 1 - Tuning.CraEmbezzleMax - 0.01 && r <= 1 - Tuning.CraEmbezzleMin + 0.01 && s.CraEventDay < 0 && s.CraWarnDay < 0, $"储备被拿走 40%–80%:R/S {r:0.00}");
+ if (r < Tuning.CraCrashRatio) { crashes++; Check(s.CraCrashDay == 2 && s.Cra == Tuning.CraSpiralPath[0], "R/S 跌破崩盘线:当天进入螺旋"); }
+ else { discounts++; Check(s.CraCrashDay < 0 && Near(s.Cra, Stablecoins.PriceOfRatio(r), 1e-9), $"折价:{s.Cra:0.000}"); }
+ }
+ Check(crashes > 5 && discounts > 5, $"两种结果都会出现:崩盘 {crashes}、折价 {discounts}");
+ var t = new Stablecoins(); double before = t.Reserve;
+ t.OnEventStart(MarketEvent.RevRaidUpper, 3, new Rng(1));
+ Check(Near(t.Reserve, before + Tuning.CraLoot * t.Supply, 1e-6), "劫掠上层把流通量 × 9% 的 CRC 并入储备");
+ Check(Stablecoins.PriceOfRatio(Tuning.CraSafeRatio) == 1 && Near(Stablecoins.PriceOfRatio(Tuning.CraCrashRatio), 1 - Tuning.CraDiscountMax, 1e-9), "价格曲线:安全线 1,崩盘线折价 15%");
+ });
+
+ Test("CRA 长期行为:200 天里有的局崩盘有的不崩,质押越多崩得越多,崩盘前有预兆", () =>
+ {
+ Func run = staked =>
+ {
+ int crashes = 0, n = 300; double leadSum = 0; int leads = 0;
+ for (int i = 0; i < n; i++)
+ {
+ var s = new Stablecoins(); var rng = new Rng((ulong)(i + 1));
+ int warn = -1;
+ for (int d = 1; d <= 200 && s.CraCrashDay < 0; d++)
+ {
+ if (rng.Chance(0.03)) s.OnEventStart(MarketEvent.RevRaidUpper, d, rng);
+ s.Night(d, rng, staked);
+ if (warn < 0 && s.CraWarnDay >= 0) warn = s.CraWarnDay;
+ if (s.CraWarnDay < 0 && s.CraCrashDay < 0) warn = -1;
+ }
+ if (s.CraCrashDay >= 0) { crashes++; if (warn >= 0) { leadSum += s.CraCrashDay - warn; leads++; } }
+ }
+ return (crashes / (double)n, leads > 0 ? leadSum / leads : 0);
+ };
+ var lo = run(0); var hi = run(100000);
+ Check(lo.crash > 0.02 && lo.crash < 0.9, $"无质押 200 天崩盘比例 {lo.crash:P0}");
+ Check(hi.crash >= lo.crash, $"质押 10 万:{hi.crash:P0} ≥ 无质押 {lo.crash:P0}");
+ Check(lo.lead >= Tuning.CraWarnLeadMin - 0.01 && lo.lead <= Tuning.CraWarnLeadMax + 0.01, $"预兆到崩盘平均 {lo.lead:0.0} 天");
+ });
+
+ Test("CRA 一级市场:铸造按抵押率存 CRC 加 SPARK,排队后进流通量与储备;销毁按 min(c, R/S) 兑付", () =>
+ {
+ var s = new Stablecoins(); var rng = new Rng(2);
+ var w = new Wallet { Address = "w" };
+ s.Collateral(1000, out double crc, out double spark);
+ Check(Near(crc, 1000 * Tuning.CraC0, 1e-9) && Near(spark * s.Spark, 1000 * (1 - Tuning.CraC0), 1e-9), $"抵押:CRC {crc:0},SPARK {spark:0.##}");
+ Check(s.RequestMint(w, 1000, 1, rng) == null, "钱包里没有币:拒绝");
+ w.Add(Assets.CRC, crc); w.Add(Assets.SPARK, spark);
+ double S0 = s.Supply, R0 = s.Reserve;
+ var o = s.RequestMint(w, 1000, 1, rng);
+ Check(o != null && w.Get(Assets.CRC) == 0 && w.Get(Assets.SPARK) == 0 && o.Due >= 2 && o.Due <= 3, "存入后排队 1~2 天");
+ var evs = s.NightOrders(1, rng);
+ Check(evs.Count == 0 && s.Supply == S0, "没到期不结算");
+ evs = s.NightOrders(o.Due, rng);
+ Check(evs.Count == 1 && evs[0].Kind == CraOrderKind.Minted && Near(s.Supply, S0 + 1000, 1e-9) && Near(s.Reserve, R0 + crc, 1e-9), "到期:流通量加 1000,储备加 CRC 部分");
+ w.Add(Assets.CRA, 500);
+ double ratio = s.Ratio;
+ var b = s.RequestBurn(w, 500, 5);
+ Check(b != null && b.Due == 5 + Tuning.CraUnlockDays && w.Get(Assets.CRA) == 0, "销毁排队解锁期");
+ evs = s.NightOrders(b.Due, rng);
+ Check(evs.Count == 1 && evs[0].Kind == CraOrderKind.Burned && Near(evs[0].Crc, 500 * Math.Min(Tuning.CraC0, ratio), 1e-6) && evs[0].Spark > 0, $"兑付 CRC {evs[0].Crc:0}、SPARK {evs[0].Spark:0.##}");
+ var low = new Stablecoins { Supply = 1000, Reserve = 200 };
+ low.Redeem(100, 0.8, out double c2, out double sp2);
+ Check(Near(c2, 20, 1e-9) && Near(sp2 * low.Spark, 80, 1e-9), "储备只有 R/S = 0.2:CRC 份额降到 0.2,其余用 SPARK 补");
+ });
+
+ Test("CRA 一级市场:官方跑路概率 = 系数 × (1 − CRA 价格),命中整笔丢失", () =>
+ {
+ var s = new Stablecoins();
+ Check(s.RugChance == 0, "价格 1 时不会跑路");
+ s.Cra = 0.9;
+ Check(Near(s.RugChance, Tuning.CraRugPerDepeg * 0.1, 1e-9), "价格 0.9 时概率 10%");
+ int lost = 0, n = 4000;
+ for (int i = 0; i < n; i++)
+ {
+ var t = new Stablecoins { Cra = 0.9, Supply = 1000, Reserve = 800 };
+ var w = new Wallet { Address = "w" }; w.Add(Assets.CRA, 10);
+ t.RequestBurn(w, 10, 1);
+ var ev = t.NightOrders(1, new Rng((ulong)i));
+ if (ev.Count == 1 && ev[0].Kind == CraOrderKind.Lost) { lost++; Check(t.Orders.Count == 0 && w.Get(Assets.CRA) == 0, "丢失不退款"); }
+ }
+ Check(Math.Abs(lost / (double)n - 0.1) < 0.03, $"命中率 {lost / (double)n:P1}");
});
Test("合规出金:干净小额不查;污点高时重度命中记罪证;轻度命中只没收污点部分;只对收益计税", () =>
@@ -599,7 +689,7 @@ internal static class Program
Check(Near(back.Market.Price, m.Price, 1e-9) && back.Market.Pools.Count == m.Pools.Count && back.Market.Pools[1].Id == m.Pools[1].Id, "交易对与价格");
Check(back.Hot.Get(Assets.PSC) == 5 && back.Hot.Taint == 12 && back.Hot.Basis == 34, "钱包余额、污点、成本");
Check(back.Exchange.History.Count == 1 && back.Exchange.History[0].Value == 100, "出金记录");
- Check(Near(back.Network.NpcHash, net.NpcHash, 1e-9) && Near(back.Stable.CraSubsidy, st.CraSubsidy, 1e-9), "全网算力、CRA 补贴池");
+ Check(Near(back.Network.NpcHash, net.NpcHash, 1e-9) && Near(back.Stable.Reserve, st.Reserve, 1e-9), "全网算力、CRA 储备");
for (int d = 6; d <= 10; d++)
{
m.Night(new MarketEvent[0], rng, d, a => 1);
@@ -1042,12 +1132,12 @@ internal static class Program
Check(Math.Abs(w.Get(Assets.CRA) - 700) < 1e-6, $"到期自动回钱包 200:{w.Get(Assets.CRA):0.##}");
Check(w.Taint > 400 - lotTaint + 1, "污点按比例回来");
// 螺旋:质押的 CRA 取回后只值当时的价格
- var s = new Stablecoins(); s.CraSpiralDay = 5;
+ var s = new Stablecoins(); s.CraCrashDay = 5;
for (int d = 5; d <= 7; d++) s.Night(d, new Rng(1), 0);
Check(s.Cra < 0.05 && s.Spark < Tuning.SparkStart * 0.1, $"螺旋后 CRA {s.Cra},SPARK {s.Spark:0.00}");
});
- Test("SPARK:随机游走有界,铸销来回只损手续费,事件冲击", () =>
+ Test("SPARK:随机游走有界,铸销事件冲击", () =>
{
double lo = 99, hi = 0;
for (ulong seed = 1; seed <= 20; seed++)
@@ -1056,9 +1146,7 @@ internal static class Program
for (int d = 1; d <= 150 && s.CraWarnDay < 0; d++) { s.Night(d, rng, 0); lo = Math.Min(lo, s.Spark); hi = Math.Max(hi, s.Spark); } // 螺旋前
}
Check(lo >= Tuning.SparkStart * Tuning.SparkMinMult - 1e-9 && hi <= Tuning.SparkStart * Tuning.SparkMaxMult + 1e-9 && hi / lo > 1.5, $"螺旋前的范围 {lo:0.0}–{hi:0.0}");
- var s2 = new Stablecoins(); var w = new Wallet { Address = "w" }; w.Add(Assets.CRA, 100);
- double sp = s2.Mint(w, 100), back = s2.Burn(w, sp);
- Check(Math.Abs(back - 100 * Math.Pow(1 - Tuning.SparkFee, 2)) < 1e-6 && s2.Mint(w, 1e6) < 0, $"来回 100 → {back:0.00},余额不足返回 -1");
+ var s2 = new Stablecoins();
double before = s2.Spark; s2.OnEventStart(MarketEvent.RevRaidUpper, 1, new Rng(1));
Check(Math.Abs(s2.Spark - before * Tuning.SparkRaidShock) < 1e-9, "袭击上层 +15%");
});
@@ -1333,7 +1421,7 @@ internal static class Program
Check(Math.Abs(hit / (double)runs - PredictMath.GasAtLeast(10, 3, 1.3)) < 0.02, "gas 概率和真实抽样对得上");
double h = 1 - Math.Pow(1 - Tuning.CraSpiralWeekly, 1.0 / 7);
Check(Math.Abs(PredictMath.CraWarnProb(1e9, 100, 10, 12, 14, h) - (1 - Math.Pow(1 - h, 3)) * Math.Pow(1 - h, 2)) < 1e-9, "补贴池充足时按日概率,窗口前发出的不算");
- Check(PredictMath.CraWarnProb(150, 100, 10, 11, 12, h) > 0.99, "补贴池撑不过窗口前一晚就必发");
+ Check(Math.Abs(PredictMath.CraWarnProb(150, 100, 10, 11, 12, h) - (1 - h)) < 1e-9, "垫子撑不过窗口前一晚:窗口第一晚必发,只要之前没按日概率发出");
Check(PredictMath.CraWarnProb(50, 100, 10, 10, 10, h) == 1, "归零那晚必发");
Check(PredictMath.EraProb(60, 61, 63, -1) > 0 && PredictMath.EraProb(60, 61, 63, -1) < 0.2, "没预警时在 50–90 里均匀,3 天窗口很小");
Check(Math.Abs(PredictMath.EraProb(60, 62, 63, 58) - 0.5) < 1e-9 || PredictMath.EraProb(60, 62, 63, 58) > 0, "预警后落在 4–7 天内");