From 728004a2678672696c10972591707367bb5aa49b Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 5 Dec 2024 01:26:12 +0900 Subject: [PATCH 01/20] feat: Introduce RewardBase --- Lib9c/Delegation/RewardBase.cs | 188 +++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 Lib9c/Delegation/RewardBase.cs diff --git a/Lib9c/Delegation/RewardBase.cs b/Lib9c/Delegation/RewardBase.cs new file mode 100644 index 0000000000..85b3acd14d --- /dev/null +++ b/Lib9c/Delegation/RewardBase.cs @@ -0,0 +1,188 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Numerics; +using Bencodex; +using Bencodex.Types; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Action; + +namespace Nekoyume.Delegation +{ + /// + /// RewardBase is a class that represents the base of the reward. + /// If it's multiplied by the number of shares, it will be the reward for the period. + /// Also, it holds the significant figure to calculate the reward. + /// + public class RewardBase : IBencodable, IEquatable + { + private const string StateTypeName = "reward_base"; + private const long StateVersion = 1; + private readonly IComparer _currencyComparer = new CurrencyComparer(); + + public RewardBase( + Address address, + BigInteger totalShares, + IEnumerable currencies) + : this( + address, + totalShares, + currencies.Select(c => c * 0), + RecommendedSigFig(totalShares)) + { + } + + public RewardBase(Address address, IValue bencoded) + : this(address, (List)bencoded) + { + } + + public RewardBase( + Address address, + BigInteger totalShares, + IEnumerable rewardPortion, + int sigfig) + { + Address = address; + + if (totalShares.Sign <= 0) + { + throw new ArgumentOutOfRangeException(nameof(totalShares)); + } + + TotalShares = totalShares; + + if (!rewardPortion.Select(f => f.Currency).All(new HashSet().Add)) + { + throw new ArgumentException("Duplicated currency in reward base."); + } + + RewardPortion = rewardPortion.ToImmutableDictionary(f => f.Currency, f => f); + SigFig = sigfig; + } + + + public RewardBase(Address address, List bencoded) + { + if (bencoded[0] is not Text text || text != StateTypeName || bencoded[1] is not Integer integer) + { + throw new InvalidCastException(); + } + + if (integer > StateVersion) + { + throw new FailedLoadStateException("Un-deserializable state."); + } + + Address = address; + TotalShares = (Integer)bencoded[2]; + var rewardPortion = ((List)bencoded[3]).Select(v => new FungibleAssetValue(v)); + + if (!rewardPortion.Select(f => f.Currency).All(new HashSet().Add)) + { + throw new ArgumentException("Duplicated currency in reward base."); + } + + RewardPortion = rewardPortion.ToImmutableDictionary(f => f.Currency, f => f); + SigFig = (Integer)bencoded[4]; + } + + private RewardBase( + Address address, + BigInteger totalShares, + ImmutableDictionary rewardPortion, + int sigfig) + { + Address = address; + TotalShares = totalShares; + RewardPortion = rewardPortion; + SigFig = sigfig; + } + + public Address Address { get; } + + public BigInteger TotalShares { get; } + + public int SigFig { get; private set; } + + public static int Margin => 2; + + public ImmutableDictionary RewardPortion { get; } + + public List Bencoded + => List.Empty + .Add(StateTypeName) + .Add(StateVersion) + .Add(TotalShares) + .Add(new List(RewardPortion + .OrderBy(r => r.Key, _currencyComparer) + .Select(r => r.Value.Serialize()))) + .Add(SigFig); + + IValue IBencodable.Bencoded => Bencoded; + + public RewardBase AddRewards(IEnumerable rewards) + => rewards.Aggregate(this, (accum, next) => AddReward(accum, next)); + + public RewardBase AddReward(FungibleAssetValue reward) + => AddReward(this, reward); + + public RewardBase UpdateTotalShares(BigInteger totalShares) + => UpdateTotalShares(this, totalShares); + + public static RewardBase AddReward(RewardBase rewardBase, FungibleAssetValue reward) + => new RewardBase( + rewardBase.Address, + rewardBase.TotalShares, + rewardBase.RewardPortion.TryGetValue(reward.Currency, out var portion) + ? rewardBase.RewardPortion.SetItem( + reward.Currency, + portion + (reward * rewardBase.SigFig).DivRem(rewardBase.TotalShares).Quotient) + : throw new ArgumentException($"Invalid reward currency: {reward.Currency}"), + rewardBase.SigFig); + + public static RewardBase UpdateTotalShares(RewardBase rewardBase, BigInteger totalShares) + { + var newSigFig = Math.Max(rewardBase.SigFig, RecommendedSigFig(totalShares)); + var multiplier = BigInteger.Pow(10, newSigFig - rewardBase.SigFig); + var newPortion = rewardBase.RewardPortion.ToImmutableDictionary( + kvp => kvp.Key, + kvp => (kvp.Value * multiplier).DivRem(rewardBase.TotalShares).Quotient); + + return new RewardBase( + rewardBase.Address, + totalShares, + newPortion, + newSigFig); + } + + public static int RecommendedSigFig(BigInteger totalShares) + => (int)Math.Floor(BigInteger.Log10(totalShares)) + Margin; + + public ImmutableSortedDictionary RewardsDuringPeriod(BigInteger share) + => RewardPortion.Keys.Select(k => RewardsDuringPeriod(share, k)) + .ToImmutableSortedDictionary(f => f.Currency, f => f, _currencyComparer); + + public FungibleAssetValue RewardsDuringPeriod(BigInteger share, Currency currency) + => RewardPortion.TryGetValue(currency, out var portion) + ? (portion * share).DivRem(SigFig).Quotient + : throw new ArgumentException($"Invalid reward currency: {currency}"); + + public override bool Equals(object? obj) + => obj is RewardBase other && Equals(other); + + public bool Equals(RewardBase? other) + => ReferenceEquals(this, other) + || (other is RewardBase rewardBase + && Address == rewardBase.Address + && TotalShares == rewardBase.TotalShares + && RewardPortion.Equals(rewardBase.RewardPortion) + && SigFig == rewardBase.SigFig); + + public override int GetHashCode() + => Address.GetHashCode(); + } +} From 5ae345ef2544d17af7c24dfab62ef1009142fb4f Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 5 Dec 2024 14:09:32 +0900 Subject: [PATCH 02/20] feat: Implement Getter and Setter for RewardBase --- Lib9c/Delegation/Delegatee.cs | 6 +++++ Lib9c/Delegation/DelegateeMetadata.cs | 10 +++++-- Lib9c/Delegation/DelegationAddress.cs | 18 ++++++------- Lib9c/Delegation/DelegationRepository.cs | 32 +++++++++++++++++++++++ Lib9c/Delegation/IDelegatee.cs | 4 +++ Lib9c/Delegation/IDelegationRepository.cs | 4 +++ 6 files changed, 63 insertions(+), 11 deletions(-) diff --git a/Lib9c/Delegation/Delegatee.cs b/Lib9c/Delegation/Delegatee.cs index fbf1f40991..a09ab958ad 100644 --- a/Lib9c/Delegation/Delegatee.cs +++ b/Lib9c/Delegation/Delegatee.cs @@ -165,6 +165,12 @@ public Address UnbondLockInAddress(Address delegatorAddress) public Address RebondGraceAddress(Address delegatorAddress) => Metadata.RebondGraceAddress(delegatorAddress); + public Address CurrentRewardBaseAddress() + => Metadata.CurrentRewardBaseAddress(); + + public Address RewardBaseAddress(long height) + => Metadata.RewardBaseAddress(height); + public Address CurrentLumpSumRewardsRecordAddress() => Metadata.CurrentLumpSumRewardsRecordAddress(); diff --git a/Lib9c/Delegation/DelegateeMetadata.cs b/Lib9c/Delegation/DelegateeMetadata.cs index 3f9121a9cd..48cda93baf 100644 --- a/Lib9c/Delegation/DelegateeMetadata.cs +++ b/Lib9c/Delegation/DelegateeMetadata.cs @@ -340,11 +340,17 @@ public Address UnbondLockInAddress(Address delegatorAddress) public virtual Address RebondGraceAddress(Address delegatorAddress) => DelegationAddress.RebondGraceAddress(Address, delegatorAddress); + public virtual Address CurrentRewardBaseAddress() + => DelegationAddress.CurrentRewardBaseAddress(Address); + + public virtual Address RewardBaseAddress(long height) + => DelegationAddress.RewardBaseAddress(Address, height); + public virtual Address CurrentLumpSumRewardsRecordAddress() - => DelegationAddress.CurrentLumpSumRewardsRecordAddress(Address); + => DelegationAddress.CurrentRewardBaseAddress(Address); public virtual Address LumpSumRewardsRecordAddress(long height) - => DelegationAddress.LumpSumRewardsRecordAddress(Address, height); + => DelegationAddress.RewardBaseAddress(Address, height); public override bool Equals(object? obj) => obj is IDelegateeMetadata other && Equals(other); diff --git a/Lib9c/Delegation/DelegationAddress.cs b/Lib9c/Delegation/DelegationAddress.cs index 243b21e382..288c125927 100644 --- a/Lib9c/Delegation/DelegationAddress.cs +++ b/Lib9c/Delegation/DelegationAddress.cs @@ -65,29 +65,29 @@ public static Address RebondGraceAddress( delegateeMetadataAddress, delegatorAddress.ByteArray); - public static Address CurrentLumpSumRewardsRecordAddress( + public static Address CurrentRewardBaseAddress( Address delegateeAddress, Address delegateeAccountAddress) => DeriveAddress( - DelegationElementType.LumpSumRewardsRecord, + DelegationElementType.RewardBase, DelegateeMetadataAddress(delegateeAddress, delegateeAccountAddress)); - public static Address CurrentLumpSumRewardsRecordAddress( + public static Address CurrentRewardBaseAddress( Address delegateeMetadataAddress) => DeriveAddress( - DelegationElementType.LumpSumRewardsRecord, + DelegationElementType.RewardBase, delegateeMetadataAddress); - public static Address LumpSumRewardsRecordAddress( + public static Address RewardBaseAddress( Address delegateeAddress, Address delegateeAccountAddress, long height) => DeriveAddress( - DelegationElementType.LumpSumRewardsRecord, + DelegationElementType.RewardBase, DelegateeMetadataAddress(delegateeAddress, delegateeAccountAddress), BitConverter.GetBytes(height)); - public static Address LumpSumRewardsRecordAddress( + public static Address RewardBaseAddress( Address delegateeMetadataAddress, long height) => DeriveAddress( - DelegationElementType.LumpSumRewardsRecord, + DelegationElementType.RewardBase, delegateeMetadataAddress, BitConverter.GetBytes(height)); @@ -138,7 +138,7 @@ private enum DelegationElementType Bond, UnbondLockIn, RebondGrace, - LumpSumRewardsRecord, + RewardBase, RewardPool, DelegationPool, } diff --git a/Lib9c/Delegation/DelegationRepository.cs b/Lib9c/Delegation/DelegationRepository.cs index c4002c2372..f0b17a9055 100644 --- a/Lib9c/Delegation/DelegationRepository.cs +++ b/Lib9c/Delegation/DelegationRepository.cs @@ -19,6 +19,8 @@ public abstract class DelegationRepository : IDelegationRepository protected IAccount unbondLockInAccount; protected IAccount rebondGraceAccount; protected IAccount unbondingSetAccount; + protected IAccount rewardBaseAccount; + // TODO: [Migration] Remove this field after migration. protected IAccount lumpSumRewardsRecordAccount; public DelegationRepository( @@ -32,6 +34,7 @@ public DelegationRepository( Address unbondLockInAccountAddress, Address rebondGraceAccountAddress, Address unbondingSetAccountAddress, + Address rewardBaseAccountAddress, Address lumpSumRewardRecordAccountAddress) { previousWorld = world; @@ -44,6 +47,7 @@ public DelegationRepository( UnbondLockInAccountAddress = unbondLockInAccountAddress; RebondGraceAccountAddress = rebondGraceAccountAddress; UnbondingSetAccountAddress = unbondingSetAccountAddress; + RewardBaseAccountAddress = rewardBaseAccountAddress; LumpSumRewardsRecordAccountAddress = lumpSumRewardRecordAccountAddress; delegateeAccount = world.GetAccount(DelegateeAccountAddress); @@ -54,6 +58,7 @@ public DelegationRepository( unbondLockInAccount = world.GetAccount(UnbondLockInAccountAddress); rebondGraceAccount = world.GetAccount(RebondGraceAccountAddress); unbondingSetAccount = world.GetAccount(UnbondingSetAccountAddress); + rewardBaseAccount = world.GetAccount(RewardBaseAccountAddress); lumpSumRewardsRecordAccount = world.GetAccount(LumpSumRewardsRecordAccountAddress); } @@ -66,6 +71,7 @@ public DelegationRepository( .SetAccount(UnbondLockInAccountAddress, unbondLockInAccount) .SetAccount(RebondGraceAccountAddress, rebondGraceAccount) .SetAccount(UnbondingSetAccountAddress, unbondingSetAccount) + .SetAccount(RewardBaseAccountAddress, rewardBaseAccount) .SetAccount(LumpSumRewardsRecordAccountAddress, lumpSumRewardsRecordAccount); public IActionContext ActionContext { get; } @@ -86,6 +92,8 @@ public DelegationRepository( private Address UnbondingSetAccountAddress { get; } + private Address RewardBaseAccountAddress { get; } + private Address LumpSumRewardsRecordAccountAddress { get; } public abstract IDelegatee GetDelegatee(Address address); @@ -162,6 +170,24 @@ public UnbondingSet GetUnbondingSet() ? new UnbondingSet(bencoded, this) : new UnbondingSet(this); + public RewardBase GetCurrentRewardBase(IDelegatee delegatee) + { + Address address = delegatee.CurrentRewardBaseAddress(); + IValue? value = rewardBaseAccount.GetState(address); + return value is IValue bencoded + ? new RewardBase(address, bencoded) + : throw new FailedLoadStateException("RewardBase not found."); + } + + public RewardBase GetRewardBase(IDelegatee delegatee, long height) + { + Address address = delegatee.RewardBaseAddress(height); + IValue? value = rewardBaseAccount.GetState(address); + return value is IValue bencoded + ? new RewardBase(address, bencoded) + : throw new FailedLoadStateException("RewardBase not found."); + } + public LumpSumRewardsRecord? GetLumpSumRewardsRecord(IDelegatee delegatee, long height) { Address address = delegatee.LumpSumRewardsRecordAddress(height); @@ -225,6 +251,11 @@ public void SetUnbondingSet(UnbondingSet unbondingSet) : unbondingSetAccount.SetState(UnbondingSet.Address, unbondingSet.Bencoded); } + public void SetRewardBase(RewardBase rewardBase) + { + rewardBaseAccount = rewardBaseAccount.SetState(rewardBase.Address, rewardBase.Bencoded); + } + public void SetLumpSumRewardsRecord(LumpSumRewardsRecord lumpSumRewardsRecord) { lumpSumRewardsRecordAccount = lumpSumRewardsRecordAccount.SetState( @@ -245,6 +276,7 @@ public virtual void UpdateWorld(IWorld world) unbondLockInAccount = world.GetAccount(UnbondLockInAccountAddress); rebondGraceAccount = world.GetAccount(RebondGraceAccountAddress); unbondingSetAccount = world.GetAccount(UnbondingSetAccountAddress); + rewardBaseAccount = world.GetAccount(RewardBaseAccountAddress); lumpSumRewardsRecordAccount = world.GetAccount(LumpSumRewardsRecordAccountAddress); } } diff --git a/Lib9c/Delegation/IDelegatee.cs b/Lib9c/Delegation/IDelegatee.cs index 9cb3503613..54144830b1 100644 --- a/Lib9c/Delegation/IDelegatee.cs +++ b/Lib9c/Delegation/IDelegatee.cs @@ -65,6 +65,10 @@ public interface IDelegatee Address RebondGraceAddress(Address delegatorAddress); + Address CurrentRewardBaseAddress(); + + Address RewardBaseAddress(long height); + Address CurrentLumpSumRewardsRecordAddress(); Address LumpSumRewardsRecordAddress(long height); diff --git a/Lib9c/Delegation/IDelegationRepository.cs b/Lib9c/Delegation/IDelegationRepository.cs index 47c988a0b5..a1c2ba3d9b 100644 --- a/Lib9c/Delegation/IDelegationRepository.cs +++ b/Lib9c/Delegation/IDelegationRepository.cs @@ -36,6 +36,8 @@ public interface IDelegationRepository UnbondingSet GetUnbondingSet(); + RewardBase GetRewardBase(IDelegatee delegatee, long height); + LumpSumRewardsRecord? GetLumpSumRewardsRecord(IDelegatee delegatee, long height); LumpSumRewardsRecord? GetCurrentLumpSumRewardsRecord(IDelegatee delegatee); @@ -58,6 +60,8 @@ public interface IDelegationRepository void SetUnbondingSet(UnbondingSet unbondingSet); + void SetRewardBase(RewardBase rewardBase); + void SetLumpSumRewardsRecord(LumpSumRewardsRecord lumpSumRewardsRecord); void TransferAsset(Address sender, Address recipient, FungibleAssetValue value); From 00dca35477d5d0f8d5807f8180fd7fda6d3d1e7b Mon Sep 17 00:00:00 2001 From: ilgyu Date: Mon, 9 Dec 2024 13:57:36 +0900 Subject: [PATCH 03/20] feat: Make use of RewardBase --- .Lib9c.Tests/Delegation/DummyRepository.cs | 3 +- .Lib9c.Tests/Delegation/TestRepository.cs | 3 +- Lib9c/Addresses.cs | 12 ++ Lib9c/Delegation/Delegatee.cs | 174 ++++++++++++------ Lib9c/Delegation/DelegationRepository.cs | 5 + Lib9c/Delegation/IDelegationRepository.cs | 8 +- Lib9c/Delegation/RewardBase.cs | 47 ++++- Lib9c/Model/Guild/GuildRepository.cs | 1 + .../ValidatorRepository.cs | 1 + 9 files changed, 190 insertions(+), 64 deletions(-) diff --git a/.Lib9c.Tests/Delegation/DummyRepository.cs b/.Lib9c.Tests/Delegation/DummyRepository.cs index 85332a4785..2293822e19 100644 --- a/.Lib9c.Tests/Delegation/DummyRepository.cs +++ b/.Lib9c.Tests/Delegation/DummyRepository.cs @@ -21,7 +21,8 @@ public DummyRepository(IWorld world, IActionContext context) unbondLockInAccountAddress: new Address("0000000000000000000000000000000000000005"), rebondGraceAccountAddress: new Address("0000000000000000000000000000000000000006"), unbondingSetAccountAddress: new Address("0000000000000000000000000000000000000007"), - lumpSumRewardRecordAccountAddress: new Address("0000000000000000000000000000000000000008")) + rewardBaseAccountAddress: new Address("0000000000000000000000000000000000000008"), + lumpSumRewardRecordAccountAddress: new Address("0000000000000000000000000000000000000009")) { } diff --git a/.Lib9c.Tests/Delegation/TestRepository.cs b/.Lib9c.Tests/Delegation/TestRepository.cs index b072d9ce86..ccd60898c6 100644 --- a/.Lib9c.Tests/Delegation/TestRepository.cs +++ b/.Lib9c.Tests/Delegation/TestRepository.cs @@ -24,7 +24,8 @@ public TestRepository(IWorld world, IActionContext context) unbondLockInAccountAddress: new Address("0000000000000000000000000000000000000005"), rebondGraceAccountAddress: new Address("0000000000000000000000000000000000000006"), unbondingSetAccountAddress: new Address("0000000000000000000000000000000000000007"), - lumpSumRewardRecordAccountAddress: new Address("0000000000000000000000000000000000000008")) + rewardBaseAccountAddress: new Address("0000000000000000000000000000000000000008"), + lumpSumRewardRecordAccountAddress: new Address("0000000000000000000000000000000000000009")) { _context = context; } diff --git a/Lib9c/Addresses.cs b/Lib9c/Addresses.cs index 18097cdec2..5a0ac42632 100644 --- a/Lib9c/Addresses.cs +++ b/Lib9c/Addresses.cs @@ -146,6 +146,12 @@ public static readonly Address GuildLumpSumRewardsRecord public static readonly Address GuildUnbondingSet = new Address("0000000000000000000000000000000000000216"); + /// + /// An address of an account having . + /// + public static readonly Address GuildRewardBase + = new Address("0000000000000000000000000000000000000217"); + #endregion #region Validator @@ -233,6 +239,12 @@ public static readonly Address CommunityPool public static readonly Address NonValidatorDelegatee = new Address("0000000000000000000000000000000000000313"); + /// + /// An address of an account having . + /// + public static readonly Address ValidatorRewardBase + = new Address("0000000000000000000000000000000000000314"); + #endregion #region Migration diff --git a/Lib9c/Delegation/Delegatee.cs b/Lib9c/Delegation/Delegatee.cs index a09ab958ad..c7ffc0cfd5 100644 --- a/Lib9c/Delegation/Delegatee.cs +++ b/Lib9c/Delegation/Delegatee.cs @@ -246,14 +246,23 @@ public void DistributeReward(T delegator, long height) if (!share.IsZero && bond.LastDistributeHeight.HasValue) { - IEnumerable lumpSumRewardsRecords - = GetLumpSumRewardsRecords(bond.LastDistributeHeight); - - foreach (LumpSumRewardsRecord record in lumpSumRewardsRecords) - { - TransferReward(delegator, share, record); + if (Repository.GetCurrentRewardBase(this) is RewardBase rewardBase + && Repository.GetRewardBase(this, bond.LastDistributeHeight.Value) is RewardBase lastRewardBase) + { + TransferReward(delegator, share, rewardBase, lastRewardBase); // TransferRemainders(newRecord); - Repository.SetLumpSumRewardsRecord(record); + } + else + { + IEnumerable lumpSumRewardsRecords + = GetLumpSumRewardsRecords(bond.LastDistributeHeight); + + foreach (LumpSumRewardsRecord record in lumpSumRewardsRecords) + { + TransferReward(delegator, share, record); + // TransferRemainders(newRecord); + Repository.SetLumpSumRewardsRecord(record); + } } } @@ -271,23 +280,31 @@ void IDelegatee.DistributeReward(IDelegator delegator, long height) public void CollectRewards(long height) { var rewards = RewardCurrencies.Select(c => Repository.GetBalance(RewardPoolAddress, c)); - LumpSumRewardsRecord record = Repository.GetCurrentLumpSumRewardsRecord(this) - ?? new LumpSumRewardsRecord( - CurrentLumpSumRewardsRecordAddress(), - height, - TotalShares, - RewardCurrencies); - record = record.AddLumpSumRewards(rewards); - - foreach (var rewardsEach in rewards) + if (Repository.GetCurrentRewardBase(this) is RewardBase rewardBase) + { + rewardBase = rewards.Aggregate(rewardBase, (accum, next) => accum.AddReward(next)); + Repository.SetRewardBase(rewardBase); + } + else { - if (rewardsEach.Sign > 0) + LumpSumRewardsRecord record = Repository.GetCurrentLumpSumRewardsRecord(this) + ?? new LumpSumRewardsRecord( + CurrentLumpSumRewardsRecordAddress(), + height, + TotalShares, + RewardCurrencies); + record = record.AddLumpSumRewards(rewards); + + foreach (var rewardsEach in rewards) { - Repository.TransferAsset(RewardPoolAddress, record.Address, rewardsEach); + if (rewardsEach.Sign > 0) + { + Repository.TransferAsset(RewardPoolAddress, record.Address, rewardsEach); + } } - } - Repository.SetLumpSumRewardsRecord(record); + Repository.SetLumpSumRewardsRecord(record); + } } public virtual void Slash(BigInteger slashFactor, long infractionHeight, long height) @@ -377,47 +394,31 @@ ImmutableDictionary reward private void StartNewRewardPeriod(long height) { - LumpSumRewardsRecord? currentRecord = Repository.GetCurrentLumpSumRewardsRecord(this); - long? lastStartHeight = null; - if (currentRecord is LumpSumRewardsRecord lastRecord) + MigrateLumpSumRewardsRecords(); + + if (Repository.GetCurrentRewardBase(this) is RewardBase rewardBase) { - lastStartHeight = lastRecord.StartHeight; - if (lastStartHeight == height) + rewardBase = rewardBase.UpdateTotalShares(TotalShares); + if (rewardBase.StartHeight == height) { - currentRecord = new( - currentRecord.Address, - currentRecord.StartHeight, - TotalShares, - RewardCurrencies, - currentRecord.LastStartHeight); - - Repository.SetLumpSumRewardsRecord(currentRecord); + Repository.SetRewardBase(rewardBase); return; } - Address archiveAddress = LumpSumRewardsRecordAddress(lastRecord.StartHeight); - - foreach (var rewardCurrency in RewardCurrencies) - { - FungibleAssetValue reward = Repository.GetBalance(lastRecord.Address, rewardCurrency); - if (reward.Sign > 0) - { - Repository.TransferAsset(lastRecord.Address, archiveAddress, reward); - } - } - - lastRecord = lastRecord.MoveAddress(archiveAddress); - Repository.SetLumpSumRewardsRecord(lastRecord); + Address archiveAddress = RewardBaseAddress(rewardBase.StartHeight); + var archivedRewardBase = rewardBase.MoveAddress(archiveAddress); + Repository.SetRewardBase(archivedRewardBase); + } + else + { + rewardBase = new( + CurrentRewardBaseAddress(), + height, + TotalShares, + RewardCurrencies); } - LumpSumRewardsRecord newRecord = new( - CurrentLumpSumRewardsRecordAddress(), - height, - TotalShares, - RewardCurrencies, - lastStartHeight); - - Repository.SetLumpSumRewardsRecord(newRecord); + Repository.SetRewardBase(rewardBase); } private List GetLumpSumRewardsRecords(long? lastRewardHeight) @@ -458,6 +459,31 @@ private void TransferReward(T delegator, BigInteger share, LumpSumRewardsRecord } } + private void TransferReward( + T delegator, + BigInteger share, + RewardBase currentRewardBase, + RewardBase lastRewardBase) + { + var currentCumulative = currentRewardBase.CumulativeRewardDuringPeriod(share); + var lastCumulative = lastRewardBase.CumulativeRewardDuringPeriod(share); + + foreach (var c in currentCumulative) + { + if (c.Value < lastCumulative[c.Key]) + { + throw new InvalidOperationException("Invalid reward base."); + } + + var reward = c.Value - lastCumulative[c.Key]; + + if (reward.Sign > 0) + { + Repository.TransferAsset(RewardPoolAddress, delegator.RewardAddress, reward); + } + } + } + private void TransferRemainders(LumpSumRewardsRecord record) { foreach (var rewardCurrency in RewardCurrencies) @@ -470,5 +496,45 @@ private void TransferRemainders(LumpSumRewardsRecord record) } } } + + private void MigrateLumpSumRewardsRecords() + { + List records = new(); + if (!(Repository.GetCurrentLumpSumRewardsRecord(this) is LumpSumRewardsRecord record)) + { + return; + } + + while (record.LastStartHeight is long lastStartHeight) + { + records.Add(record); + record = Repository.GetLumpSumRewardsRecord(this, lastStartHeight) + ?? throw new InvalidOperationException( + $"Lump sum rewards record for #{lastStartHeight} is missing"); + } + + records.Reverse(); + + RewardBase? rewardBase = null; + foreach (var recordEach in records) + { + if (rewardBase is null) + { + rewardBase = new RewardBase( + RewardBaseAddress(recordEach.StartHeight), + recordEach.StartHeight, + recordEach.TotalShares, + recordEach.LumpSumRewards.Keys); + } + + foreach (var r in recordEach.LumpSumRewards) + { + rewardBase = rewardBase.AddReward(r.Value); + Repository.TransferAsset(recordEach.Address, RewardPoolAddress, Repository.GetBalance(recordEach.Address, r.Key)); + } + + Repository.RemoveLumpSumRewardsRecord(recordEach); + } + } } } diff --git a/Lib9c/Delegation/DelegationRepository.cs b/Lib9c/Delegation/DelegationRepository.cs index f0b17a9055..573c65a795 100644 --- a/Lib9c/Delegation/DelegationRepository.cs +++ b/Lib9c/Delegation/DelegationRepository.cs @@ -262,6 +262,11 @@ public void SetLumpSumRewardsRecord(LumpSumRewardsRecord lumpSumRewardsRecord) lumpSumRewardsRecord.Address, lumpSumRewardsRecord.Bencoded); } + public void RemoveLumpSumRewardsRecord(LumpSumRewardsRecord lumpSumRewardsRecord) + { + lumpSumRewardsRecordAccount = lumpSumRewardsRecordAccount.RemoveState(lumpSumRewardsRecord.Address); + } + public void TransferAsset(Address sender, Address recipient, FungibleAssetValue value) => previousWorld = previousWorld.TransferAsset(ActionContext, sender, recipient, value); diff --git a/Lib9c/Delegation/IDelegationRepository.cs b/Lib9c/Delegation/IDelegationRepository.cs index a1c2ba3d9b..c3520d4c41 100644 --- a/Lib9c/Delegation/IDelegationRepository.cs +++ b/Lib9c/Delegation/IDelegationRepository.cs @@ -36,12 +36,14 @@ public interface IDelegationRepository UnbondingSet GetUnbondingSet(); - RewardBase GetRewardBase(IDelegatee delegatee, long height); + RewardBase? GetCurrentRewardBase(IDelegatee delegatee); - LumpSumRewardsRecord? GetLumpSumRewardsRecord(IDelegatee delegatee, long height); + RewardBase? GetRewardBase(IDelegatee delegatee, long height); LumpSumRewardsRecord? GetCurrentLumpSumRewardsRecord(IDelegatee delegatee); + LumpSumRewardsRecord? GetLumpSumRewardsRecord(IDelegatee delegatee, long height); + FungibleAssetValue GetBalance(Address address, Currency currency); void SetDelegatee(IDelegatee delegatee); @@ -64,6 +66,8 @@ public interface IDelegationRepository void SetLumpSumRewardsRecord(LumpSumRewardsRecord lumpSumRewardsRecord); + void RemoveLumpSumRewardsRecord(LumpSumRewardsRecord lumpSumRewardsRecord); + void TransferAsset(Address sender, Address recipient, FungibleAssetValue value); void UpdateWorld(IWorld world); diff --git a/Lib9c/Delegation/RewardBase.cs b/Lib9c/Delegation/RewardBase.cs index 85b3acd14d..eb3c6f85c2 100644 --- a/Lib9c/Delegation/RewardBase.cs +++ b/Lib9c/Delegation/RewardBase.cs @@ -25,16 +25,33 @@ public class RewardBase : IBencodable, IEquatable public RewardBase( Address address, + long startHeight, BigInteger totalShares, IEnumerable currencies) : this( address, + startHeight, totalShares, currencies.Select(c => c * 0), RecommendedSigFig(totalShares)) { } + public RewardBase( + Address address, + long startHeight, + BigInteger totalShares, + IEnumerable currencies, + int sigFig) + : this( + address, + startHeight, + totalShares, + currencies.Select(c => c * 0), + sigFig) + { + } + public RewardBase(Address address, IValue bencoded) : this(address, (List)bencoded) { @@ -42,11 +59,13 @@ public RewardBase(Address address, IValue bencoded) public RewardBase( Address address, + long startHeight, BigInteger totalShares, IEnumerable rewardPortion, int sigfig) { Address = address; + StartHeight = startHeight; if (totalShares.Sign <= 0) { @@ -78,8 +97,9 @@ public RewardBase(Address address, List bencoded) } Address = address; - TotalShares = (Integer)bencoded[2]; - var rewardPortion = ((List)bencoded[3]).Select(v => new FungibleAssetValue(v)); + StartHeight = (Integer)bencoded[2]; + TotalShares = (Integer)bencoded[3]; + var rewardPortion = ((List)bencoded[4]).Select(v => new FungibleAssetValue(v)); if (!rewardPortion.Select(f => f.Currency).All(new HashSet().Add)) { @@ -87,16 +107,18 @@ public RewardBase(Address address, List bencoded) } RewardPortion = rewardPortion.ToImmutableDictionary(f => f.Currency, f => f); - SigFig = (Integer)bencoded[4]; + SigFig = (Integer)bencoded[5]; } private RewardBase( Address address, + long startHeight, BigInteger totalShares, ImmutableDictionary rewardPortion, int sigfig) { Address = address; + StartHeight = startHeight; TotalShares = totalShares; RewardPortion = rewardPortion; SigFig = sigfig; @@ -104,6 +126,8 @@ private RewardBase( public Address Address { get; } + public long StartHeight { get; } + public BigInteger TotalShares { get; } public int SigFig { get; private set; } @@ -116,6 +140,7 @@ public List Bencoded => List.Empty .Add(StateTypeName) .Add(StateVersion) + .Add(StartHeight) .Add(TotalShares) .Add(new List(RewardPortion .OrderBy(r => r.Key, _currencyComparer) @@ -133,9 +158,18 @@ public RewardBase AddReward(FungibleAssetValue reward) public RewardBase UpdateTotalShares(BigInteger totalShares) => UpdateTotalShares(this, totalShares); + public RewardBase MoveAddress(Address address) + => new RewardBase( + address, + StartHeight, + TotalShares, + RewardPortion, + SigFig); + public static RewardBase AddReward(RewardBase rewardBase, FungibleAssetValue reward) => new RewardBase( rewardBase.Address, + rewardBase.StartHeight, rewardBase.TotalShares, rewardBase.RewardPortion.TryGetValue(reward.Currency, out var portion) ? rewardBase.RewardPortion.SetItem( @@ -154,6 +188,7 @@ public static RewardBase UpdateTotalShares(RewardBase rewardBase, BigInteger tot return new RewardBase( rewardBase.Address, + rewardBase.StartHeight, totalShares, newPortion, newSigFig); @@ -162,11 +197,11 @@ public static RewardBase UpdateTotalShares(RewardBase rewardBase, BigInteger tot public static int RecommendedSigFig(BigInteger totalShares) => (int)Math.Floor(BigInteger.Log10(totalShares)) + Margin; - public ImmutableSortedDictionary RewardsDuringPeriod(BigInteger share) - => RewardPortion.Keys.Select(k => RewardsDuringPeriod(share, k)) + public ImmutableSortedDictionary CumulativeRewardDuringPeriod(BigInteger share) + => RewardPortion.Keys.Select(k => CumulativeRewardDuringPeriod(share, k)) .ToImmutableSortedDictionary(f => f.Currency, f => f, _currencyComparer); - public FungibleAssetValue RewardsDuringPeriod(BigInteger share, Currency currency) + public FungibleAssetValue CumulativeRewardDuringPeriod(BigInteger share, Currency currency) => RewardPortion.TryGetValue(currency, out var portion) ? (portion * share).DivRem(SigFig).Quotient : throw new ArgumentException($"Invalid reward currency: {currency}"); diff --git a/Lib9c/Model/Guild/GuildRepository.cs b/Lib9c/Model/Guild/GuildRepository.cs index f1394e99e5..54bd7d223b 100644 --- a/Lib9c/Model/Guild/GuildRepository.cs +++ b/Lib9c/Model/Guild/GuildRepository.cs @@ -34,6 +34,7 @@ public GuildRepository(IWorld world, IActionContext actionContext) unbondLockInAccountAddress: Addresses.GuildUnbondLockIn, rebondGraceAccountAddress: Addresses.GuildRebondGrace, unbondingSetAccountAddress: Addresses.GuildUnbondingSet, + rewardBaseAccountAddress: Addresses.GuildRewardBase, lumpSumRewardRecordAccountAddress: Addresses.GuildLumpSumRewardsRecord) { _guildAccount = world.GetAccount(guildAddress); diff --git a/Lib9c/ValidatorDelegation/ValidatorRepository.cs b/Lib9c/ValidatorDelegation/ValidatorRepository.cs index 69e7563c38..7e4a6d5760 100644 --- a/Lib9c/ValidatorDelegation/ValidatorRepository.cs +++ b/Lib9c/ValidatorDelegation/ValidatorRepository.cs @@ -32,6 +32,7 @@ public ValidatorRepository(IWorld world, IActionContext actionContext) Addresses.ValidatorUnbondLockIn, Addresses.ValidatorRebondGrace, Addresses.ValidatorUnbondingSet, + Addresses.ValidatorRewardBase, Addresses.ValidatorLumpSumRewardsRecord) { _validatorListAccount = world.GetAccount(validatorListAddress); From dee20cbb1756988bf73f17f1534292daf0a3d8f9 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Mon, 9 Dec 2024 14:48:00 +0900 Subject: [PATCH 04/20] fix: Fix to return null when RewardBase not exists --- Lib9c/Delegation/DelegationRepository.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib9c/Delegation/DelegationRepository.cs b/Lib9c/Delegation/DelegationRepository.cs index 573c65a795..8275326cf9 100644 --- a/Lib9c/Delegation/DelegationRepository.cs +++ b/Lib9c/Delegation/DelegationRepository.cs @@ -170,22 +170,22 @@ public UnbondingSet GetUnbondingSet() ? new UnbondingSet(bencoded, this) : new UnbondingSet(this); - public RewardBase GetCurrentRewardBase(IDelegatee delegatee) + public RewardBase? GetCurrentRewardBase(IDelegatee delegatee) { Address address = delegatee.CurrentRewardBaseAddress(); IValue? value = rewardBaseAccount.GetState(address); return value is IValue bencoded ? new RewardBase(address, bencoded) - : throw new FailedLoadStateException("RewardBase not found."); + : null; } - public RewardBase GetRewardBase(IDelegatee delegatee, long height) + public RewardBase? GetRewardBase(IDelegatee delegatee, long height) { Address address = delegatee.RewardBaseAddress(height); IValue? value = rewardBaseAccount.GetState(address); return value is IValue bencoded ? new RewardBase(address, bencoded) - : throw new FailedLoadStateException("RewardBase not found."); + : null; } public LumpSumRewardsRecord? GetLumpSumRewardsRecord(IDelegatee delegatee, long height) From da8e214b5e6f174cc15c8356538bca5d17337482 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Tue, 10 Dec 2024 14:56:49 +0900 Subject: [PATCH 05/20] fix: Fix malfunctions for RewardBase --- Lib9c/Delegation/Delegatee.cs | 34 +++++++++++++++++++++------------- Lib9c/Delegation/Delegator.cs | 1 + Lib9c/Delegation/RewardBase.cs | 21 ++++++++++++--------- 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/Lib9c/Delegation/Delegatee.cs b/Lib9c/Delegation/Delegatee.cs index c7ffc0cfd5..80e4be54db 100644 --- a/Lib9c/Delegation/Delegatee.cs +++ b/Lib9c/Delegation/Delegatee.cs @@ -246,9 +246,9 @@ public void DistributeReward(T delegator, long height) if (!share.IsZero && bond.LastDistributeHeight.HasValue) { - if (Repository.GetCurrentRewardBase(this) is RewardBase rewardBase - && Repository.GetRewardBase(this, bond.LastDistributeHeight.Value) is RewardBase lastRewardBase) - { + if (Repository.GetCurrentRewardBase(this) is RewardBase rewardBase) + { + var lastRewardBase = Repository.GetRewardBase(this, bond.LastDistributeHeight.Value); TransferReward(delegator, share, rewardBase, lastRewardBase); // TransferRemainders(newRecord); } @@ -392,16 +392,17 @@ ImmutableDictionary reward return reward; } - private void StartNewRewardPeriod(long height) + public void StartNewRewardPeriod(long height) { MigrateLumpSumRewardsRecords(); + RewardBase newRewardBase; if (Repository.GetCurrentRewardBase(this) is RewardBase rewardBase) { - rewardBase = rewardBase.UpdateTotalShares(TotalShares); + newRewardBase = rewardBase.UpdateTotalShares(TotalShares, height); if (rewardBase.StartHeight == height) { - Repository.SetRewardBase(rewardBase); + Repository.SetRewardBase(newRewardBase); return; } @@ -410,15 +411,20 @@ private void StartNewRewardPeriod(long height) Repository.SetRewardBase(archivedRewardBase); } else - { - rewardBase = new( + { + if (TotalShares.IsZero) + { + return; + } + + newRewardBase = new( CurrentRewardBaseAddress(), height, TotalShares, RewardCurrencies); } - Repository.SetRewardBase(rewardBase); + Repository.SetRewardBase(newRewardBase); } private List GetLumpSumRewardsRecords(long? lastRewardHeight) @@ -463,19 +469,21 @@ private void TransferReward( T delegator, BigInteger share, RewardBase currentRewardBase, - RewardBase lastRewardBase) + RewardBase? lastRewardBase) { var currentCumulative = currentRewardBase.CumulativeRewardDuringPeriod(share); - var lastCumulative = lastRewardBase.CumulativeRewardDuringPeriod(share); + var lastCumulative = lastRewardBase?.CumulativeRewardDuringPeriod(share); foreach (var c in currentCumulative) { - if (c.Value < lastCumulative[c.Key]) + var lastCumulativeEach = lastCumulative?[c.Key] ?? c.Key * 0; + + if (c.Value < lastCumulativeEach) { throw new InvalidOperationException("Invalid reward base."); } - var reward = c.Value - lastCumulative[c.Key]; + var reward = c.Value - lastCumulativeEach; if (reward.Sign > 0) { diff --git a/Lib9c/Delegation/Delegator.cs b/Lib9c/Delegation/Delegator.cs index 044d6a7f91..43159d17ea 100644 --- a/Lib9c/Delegation/Delegator.cs +++ b/Lib9c/Delegation/Delegator.cs @@ -221,6 +221,7 @@ public void ClaimReward( T delegatee, long height) { delegatee.DistributeReward(this, height); + delegatee.StartNewRewardPeriod(height); Repository.SetDelegator(this); } diff --git a/Lib9c/Delegation/RewardBase.cs b/Lib9c/Delegation/RewardBase.cs index eb3c6f85c2..15d7fc5311 100644 --- a/Lib9c/Delegation/RewardBase.cs +++ b/Lib9c/Delegation/RewardBase.cs @@ -155,8 +155,8 @@ public RewardBase AddRewards(IEnumerable rewards) public RewardBase AddReward(FungibleAssetValue reward) => AddReward(this, reward); - public RewardBase UpdateTotalShares(BigInteger totalShares) - => UpdateTotalShares(this, totalShares); + public RewardBase UpdateTotalShares(BigInteger totalShares, long startHeight) + => UpdateTotalShares(this, totalShares, startHeight); public RewardBase MoveAddress(Address address) => new RewardBase( @@ -166,7 +166,7 @@ public RewardBase MoveAddress(Address address) RewardPortion, SigFig); - public static RewardBase AddReward(RewardBase rewardBase, FungibleAssetValue reward) + private static RewardBase AddReward(RewardBase rewardBase, FungibleAssetValue reward) => new RewardBase( rewardBase.Address, rewardBase.StartHeight, @@ -174,21 +174,21 @@ public static RewardBase AddReward(RewardBase rewardBase, FungibleAssetValue rew rewardBase.RewardPortion.TryGetValue(reward.Currency, out var portion) ? rewardBase.RewardPortion.SetItem( reward.Currency, - portion + (reward * rewardBase.SigFig).DivRem(rewardBase.TotalShares).Quotient) + portion + (reward * Multiplier(rewardBase.SigFig)).DivRem(rewardBase.TotalShares).Quotient) : throw new ArgumentException($"Invalid reward currency: {reward.Currency}"), rewardBase.SigFig); - public static RewardBase UpdateTotalShares(RewardBase rewardBase, BigInteger totalShares) + private static RewardBase UpdateTotalShares(RewardBase rewardBase, BigInteger totalShares, long startHeight) { var newSigFig = Math.Max(rewardBase.SigFig, RecommendedSigFig(totalShares)); - var multiplier = BigInteger.Pow(10, newSigFig - rewardBase.SigFig); + var multiplier = Multiplier(newSigFig - rewardBase.SigFig); var newPortion = rewardBase.RewardPortion.ToImmutableDictionary( kvp => kvp.Key, - kvp => (kvp.Value * multiplier).DivRem(rewardBase.TotalShares).Quotient); + kvp => kvp.Value * multiplier); return new RewardBase( rewardBase.Address, - rewardBase.StartHeight, + startHeight, totalShares, newPortion, newSigFig); @@ -197,13 +197,16 @@ public static RewardBase UpdateTotalShares(RewardBase rewardBase, BigInteger tot public static int RecommendedSigFig(BigInteger totalShares) => (int)Math.Floor(BigInteger.Log10(totalShares)) + Margin; + public static BigInteger Multiplier(int sigFig) + => BigInteger.Pow(10, sigFig); + public ImmutableSortedDictionary CumulativeRewardDuringPeriod(BigInteger share) => RewardPortion.Keys.Select(k => CumulativeRewardDuringPeriod(share, k)) .ToImmutableSortedDictionary(f => f.Currency, f => f, _currencyComparer); public FungibleAssetValue CumulativeRewardDuringPeriod(BigInteger share, Currency currency) => RewardPortion.TryGetValue(currency, out var portion) - ? (portion * share).DivRem(SigFig).Quotient + ? (portion * share).DivRem(Multiplier(SigFig)).Quotient : throw new ArgumentException($"Invalid reward currency: {currency}"); public override bool Equals(object? obj) From 77f9444d7377eb7aa1e5b475820163351f37a2a4 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Tue, 10 Dec 2024 18:20:26 +0900 Subject: [PATCH 06/20] fix: Malfunctions for reward --- Lib9c/Delegation/Delegatee.cs | 27 +++++--- Lib9c/Delegation/DelegateeMetadata.cs | 11 +-- Lib9c/Delegation/DelegationAddress.cs | 13 ++++ Lib9c/Delegation/RewardBase.cs | 99 ++++++++++++++++----------- 4 files changed, 97 insertions(+), 53 deletions(-) diff --git a/Lib9c/Delegation/Delegatee.cs b/Lib9c/Delegation/Delegatee.cs index 80e4be54db..419ecaa972 100644 --- a/Lib9c/Delegation/Delegatee.cs +++ b/Lib9c/Delegation/Delegatee.cs @@ -165,6 +165,9 @@ public Address UnbondLockInAddress(Address delegatorAddress) public Address RebondGraceAddress(Address delegatorAddress) => Metadata.RebondGraceAddress(delegatorAddress); + public Address DistributionPoolAddress() + => Metadata.DistributionPoolAddress(); + public Address CurrentRewardBaseAddress() => Metadata.CurrentRewardBaseAddress(); @@ -283,6 +286,15 @@ public void CollectRewards(long height) if (Repository.GetCurrentRewardBase(this) is RewardBase rewardBase) { rewardBase = rewards.Aggregate(rewardBase, (accum, next) => accum.AddReward(next)); + + foreach (var rewardsEach in rewards) + { + if (rewardsEach.Sign > 0) + { + Repository.TransferAsset(RewardPoolAddress, DistributionPoolAddress(), rewardsEach); + } + } + Repository.SetRewardBase(rewardBase); } else @@ -399,15 +411,15 @@ public void StartNewRewardPeriod(long height) RewardBase newRewardBase; if (Repository.GetCurrentRewardBase(this) is RewardBase rewardBase) { - newRewardBase = rewardBase.UpdateTotalShares(TotalShares, height); - if (rewardBase.StartHeight == height) + newRewardBase = rewardBase.UpdateTotalShares(TotalShares); + if (Repository.GetRewardBase(this, height) is not null) { Repository.SetRewardBase(newRewardBase); return; } - Address archiveAddress = RewardBaseAddress(rewardBase.StartHeight); - var archivedRewardBase = rewardBase.MoveAddress(archiveAddress); + Address archiveAddress = RewardBaseAddress(height); + var archivedRewardBase = rewardBase.AttachHeight(archiveAddress, height); Repository.SetRewardBase(archivedRewardBase); } else @@ -419,7 +431,6 @@ public void StartNewRewardPeriod(long height) newRewardBase = new( CurrentRewardBaseAddress(), - height, TotalShares, RewardCurrencies); } @@ -487,7 +498,7 @@ private void TransferReward( if (reward.Sign > 0) { - Repository.TransferAsset(RewardPoolAddress, delegator.RewardAddress, reward); + Repository.TransferAsset(DistributionPoolAddress(), delegator.RewardAddress, reward); } } } @@ -530,9 +541,9 @@ record = Repository.GetLumpSumRewardsRecord(this, lastStartHeight) { rewardBase = new RewardBase( RewardBaseAddress(recordEach.StartHeight), - recordEach.StartHeight, recordEach.TotalShares, - recordEach.LumpSumRewards.Keys); + recordEach.LumpSumRewards.Keys, + recordEach.StartHeight); } foreach (var r in recordEach.LumpSumRewards) diff --git a/Lib9c/Delegation/DelegateeMetadata.cs b/Lib9c/Delegation/DelegateeMetadata.cs index 48cda93baf..a322ea03d8 100644 --- a/Lib9c/Delegation/DelegateeMetadata.cs +++ b/Lib9c/Delegation/DelegateeMetadata.cs @@ -62,8 +62,8 @@ public DelegateeMetadata( } public DelegateeMetadata( - Address address, - Address accountAddress, + Address delegateeAddress, + Address delegateeAccountAddress, List bencoded) { Currency delegationCurrency; @@ -152,8 +152,8 @@ public DelegateeMetadata( "Total shares must be non-negative."); } - DelegateeAddress = address; - DelegateeAccountAddress = accountAddress; + DelegateeAddress = delegateeAddress; + DelegateeAccountAddress = delegateeAccountAddress; DelegationCurrency = delegationCurrency; RewardCurrencies = rewardCurrencies.ToImmutableSortedSet(_currencyComparer); DelegationPoolAddress = delegationPoolAddress; @@ -340,6 +340,9 @@ public Address UnbondLockInAddress(Address delegatorAddress) public virtual Address RebondGraceAddress(Address delegatorAddress) => DelegationAddress.RebondGraceAddress(Address, delegatorAddress); + public virtual Address DistributionPoolAddress() + => DelegationAddress.DistributionPoolAddress(Address); + public virtual Address CurrentRewardBaseAddress() => DelegationAddress.CurrentRewardBaseAddress(Address); diff --git a/Lib9c/Delegation/DelegationAddress.cs b/Lib9c/Delegation/DelegationAddress.cs index 288c125927..1e7a340b92 100644 --- a/Lib9c/Delegation/DelegationAddress.cs +++ b/Lib9c/Delegation/DelegationAddress.cs @@ -103,6 +103,18 @@ public static Address RewardPoolAddress( DelegationElementType.RewardPool, delegateeMetadataAddress); + public static Address DistributionPoolAddress( + Address delegateeAddress, Address delegateeAccountAddress) + => DeriveAddress( + DelegationElementType.DistributionPool, + DelegateeMetadataAddress(delegateeAddress, delegateeAccountAddress)); + + public static Address DistributionPoolAddress( + Address delegateeMetadataAddress) + => DeriveAddress( + DelegationElementType.DistributionPool, + delegateeMetadataAddress); + public static Address DelegationPoolAddress( Address delegateeAddress, Address delegateeAccountAddress) => DeriveAddress( @@ -141,6 +153,7 @@ private enum DelegationElementType RewardBase, RewardPool, DelegationPool, + DistributionPool, } } } diff --git a/Lib9c/Delegation/RewardBase.cs b/Lib9c/Delegation/RewardBase.cs index 15d7fc5311..0eb7bf54c4 100644 --- a/Lib9c/Delegation/RewardBase.cs +++ b/Lib9c/Delegation/RewardBase.cs @@ -25,30 +25,30 @@ public class RewardBase : IBencodable, IEquatable public RewardBase( Address address, - long startHeight, BigInteger totalShares, - IEnumerable currencies) + IEnumerable currencies, + long? startHeight = null) : this( address, - startHeight, totalShares, currencies.Select(c => c * 0), - RecommendedSigFig(totalShares)) + RecommendedSigFig(totalShares), + startHeight) { } public RewardBase( Address address, - long startHeight, BigInteger totalShares, IEnumerable currencies, - int sigFig) + int sigFig, + long? startHeight = null) : this( address, - startHeight, totalShares, currencies.Select(c => c * 0), - sigFig) + sigFig, + startHeight) { } @@ -59,13 +59,12 @@ public RewardBase(Address address, IValue bencoded) public RewardBase( Address address, - long startHeight, BigInteger totalShares, IEnumerable rewardPortion, - int sigfig) + int sigfig, + long? startHeight = null) { Address = address; - StartHeight = startHeight; if (totalShares.Sign <= 0) { @@ -81,6 +80,7 @@ public RewardBase( RewardPortion = rewardPortion.ToImmutableDictionary(f => f.Currency, f => f); SigFig = sigfig; + StartHeight = startHeight; } @@ -97,9 +97,8 @@ public RewardBase(Address address, List bencoded) } Address = address; - StartHeight = (Integer)bencoded[2]; - TotalShares = (Integer)bencoded[3]; - var rewardPortion = ((List)bencoded[4]).Select(v => new FungibleAssetValue(v)); + TotalShares = (Integer)bencoded[2]; + var rewardPortion = ((List)bencoded[3]).Select(v => new FungibleAssetValue(v)); if (!rewardPortion.Select(f => f.Currency).All(new HashSet().Add)) { @@ -107,26 +106,35 @@ public RewardBase(Address address, List bencoded) } RewardPortion = rewardPortion.ToImmutableDictionary(f => f.Currency, f => f); - SigFig = (Integer)bencoded[5]; + SigFig = (Integer)bencoded[4]; + + try + { + StartHeight = (Integer)bencoded[5]; + } + catch (IndexOutOfRangeException) + { + StartHeight = null; + } } private RewardBase( Address address, - long startHeight, BigInteger totalShares, ImmutableDictionary rewardPortion, - int sigfig) + int sigfig, + long? startHeight = null) { Address = address; - StartHeight = startHeight; TotalShares = totalShares; RewardPortion = rewardPortion; SigFig = sigfig; + StartHeight = startHeight; } public Address Address { get; } - public long StartHeight { get; } + public long? StartHeight { get; } public BigInteger TotalShares { get; } @@ -137,15 +145,23 @@ private RewardBase( public ImmutableDictionary RewardPortion { get; } public List Bencoded - => List.Empty - .Add(StateTypeName) - .Add(StateVersion) - .Add(StartHeight) - .Add(TotalShares) - .Add(new List(RewardPortion - .OrderBy(r => r.Key, _currencyComparer) - .Select(r => r.Value.Serialize()))) - .Add(SigFig); + { + get + { + var bencoded = List.Empty + .Add(StateTypeName) + .Add(StateVersion) + .Add(TotalShares) + .Add(new List(RewardPortion + .OrderBy(r => r.Key, _currencyComparer) + .Select(r => r.Value.Serialize()))) + .Add(SigFig); + + return StartHeight is long height + ? bencoded.Add(height) + : bencoded; + } + } IValue IBencodable.Bencoded => Bencoded; @@ -155,30 +171,32 @@ public RewardBase AddRewards(IEnumerable rewards) public RewardBase AddReward(FungibleAssetValue reward) => AddReward(this, reward); - public RewardBase UpdateTotalShares(BigInteger totalShares, long startHeight) - => UpdateTotalShares(this, totalShares, startHeight); + public RewardBase UpdateTotalShares(BigInteger totalShares) + => UpdateTotalShares(this, totalShares); - public RewardBase MoveAddress(Address address) - => new RewardBase( - address, - StartHeight, - TotalShares, - RewardPortion, - SigFig); + public RewardBase AttachHeight(Address address, long startHeight) + => StartHeight is null + ? new RewardBase( + address, + TotalShares, + RewardPortion, + SigFig, + startHeight) + : throw new InvalidOperationException("StartHeight is already attached."); private static RewardBase AddReward(RewardBase rewardBase, FungibleAssetValue reward) => new RewardBase( rewardBase.Address, - rewardBase.StartHeight, rewardBase.TotalShares, rewardBase.RewardPortion.TryGetValue(reward.Currency, out var portion) ? rewardBase.RewardPortion.SetItem( reward.Currency, portion + (reward * Multiplier(rewardBase.SigFig)).DivRem(rewardBase.TotalShares).Quotient) : throw new ArgumentException($"Invalid reward currency: {reward.Currency}"), - rewardBase.SigFig); + rewardBase.SigFig, + rewardBase.StartHeight); - private static RewardBase UpdateTotalShares(RewardBase rewardBase, BigInteger totalShares, long startHeight) + private static RewardBase UpdateTotalShares(RewardBase rewardBase, BigInteger totalShares) { var newSigFig = Math.Max(rewardBase.SigFig, RecommendedSigFig(totalShares)); var multiplier = Multiplier(newSigFig - rewardBase.SigFig); @@ -188,7 +206,6 @@ private static RewardBase UpdateTotalShares(RewardBase rewardBase, BigInteger to return new RewardBase( rewardBase.Address, - startHeight, totalShares, newPortion, newSigFig); From b64c6768fc3a219d9793939924f2b308e06a15b6 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Tue, 10 Dec 2024 18:42:12 +0900 Subject: [PATCH 07/20] test: Fix tests for rewards --- .../AllocateGuildRewardTest.cs | 2 +- .Lib9c.Tests/Delegation/DelegatorTest.cs | 65 +++++++++++++------ 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/.Lib9c.Tests/Action/ValidatorDelegation/AllocateGuildRewardTest.cs b/.Lib9c.Tests/Action/ValidatorDelegation/AllocateGuildRewardTest.cs index 789736d50f..9451670e5b 100644 --- a/.Lib9c.Tests/Action/ValidatorDelegation/AllocateGuildRewardTest.cs +++ b/.Lib9c.Tests/Action/ValidatorDelegation/AllocateGuildRewardTest.cs @@ -231,7 +231,7 @@ var expectedProposerReward var validatorAddress = vote.ValidatorPublicKey.Address; var actualDelegatee = actualRepository.GetValidatorDelegatee(validatorAddress); - var validatorRewardAddress = actualDelegatee.CurrentLumpSumRewardsRecordAddress(); + var validatorRewardAddress = actualDelegatee.DistributionPoolAddress(); var actualDelegationBalance = world.GetBalance(validatorAddress, DelegationCurrency); var actualCommission = world.GetBalance(validatorAddress, GuildAllocateRewardCurrency); var actualUnclaimedReward = world.GetBalance(validatorRewardAddress, GuildAllocateRewardCurrency); diff --git a/.Lib9c.Tests/Delegation/DelegatorTest.cs b/.Lib9c.Tests/Delegation/DelegatorTest.cs index 8533601a90..65bc9df9df 100644 --- a/.Lib9c.Tests/Delegation/DelegatorTest.cs +++ b/.Lib9c.Tests/Delegation/DelegatorTest.cs @@ -328,13 +328,16 @@ public void RewardOnDelegate() delegator2Balance = repo.World.GetBalance(delegator2.Address, delegatee.DelegationCurrency); var delegator1RewardBalances = delegatee.RewardCurrencies.Select( c => repo.World.GetBalance(delegator1.Address, c)); - var collectedRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); + var collectedRewards = delegatee.RewardCurrencies.Select( + c => repo.World.GetBalance(delegatee.DistributionPoolAddress(), c)); + var legacyRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); var rewards1 = rewards.Select(r => (r * share1).DivRem(totalShares, out _)); Assert.Equal(delegatorInitialBalance - delegatingFAV1 * 2, delegator1Balance); Assert.Equal(delegatorInitialBalance - delegatingFAV2, delegator2Balance); Assert.Equal(rewards1, delegator1RewardBalances); - Assert.Equal(rewards.Zip(rewards1, (f, s) => f * 2 - s), collectedRewards); + Assert.Equal(rewards.Zip(rewards1, (f, s) => f - s), collectedRewards); + Assert.Equal(rewards, legacyRewards); delegator2.Delegate(delegatee, delegatingFAV2, 11L); delegator2Balance = repo.World.GetBalance(delegator2.Address, delegatee.DelegationCurrency); @@ -342,7 +345,9 @@ public void RewardOnDelegate() c => repo.World.GetBalance(delegator1.Address, c)); var delegator2RewardBalances = delegatee.RewardCurrencies.Select( c => repo.World.GetBalance(delegator2.Address, c)); - collectedRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); + collectedRewards = delegatee.RewardCurrencies.Select( + c => repo.World.GetBalance(delegatee.DistributionPoolAddress(), c)); + legacyRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); var rewards2 = rewards.Select(r => (r * share2).DivRem(totalShares, out _)); Assert.Equal(delegatorInitialBalance - delegatingFAV1 * 2, delegator1Balance); @@ -353,8 +358,9 @@ public void RewardOnDelegate() // Flushing to remainder pool is now inactive. // Assert.Equal(delegatee.RewardCurrencies.Select(c => c * 0), collectedRewards); Assert.Equal( - rewards.Select(r => r * 2).Zip(rewards1.Zip(rewards2, (f, s) => f + s), (f, s) => f - s).ToArray(), + rewards.Zip(rewards1.Zip(rewards2, (f, s) => f + s), (f, s) => f - s).ToArray(), collectedRewards); + Assert.Equal(rewards, legacyRewards); } [Fact] @@ -374,7 +380,7 @@ public void RewardOnUndelegate() repo.MintAsset(delegatee.RewardPoolAddress, reward); } - // EndBlock after delegatee's reward + // BeginBlock after delegatee's reward delegatee.CollectRewards(10L); var delegatingFAV1 = delegatee.DelegationCurrency * 10; @@ -398,21 +404,24 @@ public void RewardOnUndelegate() repo.MintAsset(delegatee.RewardPoolAddress, reward); } - // EndBlock after delegatee's reward - delegatee.CollectRewards(10L); + // BeginBlock after delegatee's reward + delegatee.CollectRewards(11L); var shareToUndelegate = repo.GetBond(delegatee, delegator1.Address).Share / 3; delegator1.Undelegate(delegatee, shareToUndelegate, 11L); delegator1Balance = repo.World.GetBalance(delegator1.Address, delegatee.DelegationCurrency); var delegator1RewardBalances = delegatee.RewardCurrencies.Select( c => repo.World.GetBalance(delegator1.Address, c)); - var collectedRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); + var collectedRewards = delegatee.RewardCurrencies.Select( + c => repo.World.GetBalance(delegatee.DistributionPoolAddress(), c)); + var legacyRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); var rewards1 = rewards.Select(r => (r * share1).DivRem(totalShares, out _)); Assert.Equal(delegatorInitialBalance - delegatingFAV1, delegator1Balance); Assert.Equal(delegatorInitialBalance - delegatingFAV2, delegator2Balance); Assert.Equal(rewards1, delegator1RewardBalances); - Assert.Equal(rewards.Zip(rewards1, (f, s) => f * 2 - s), collectedRewards); + Assert.Equal(rewards.Zip(rewards1, (f, s) => f - s), collectedRewards); + Assert.Equal(rewards, legacyRewards); shareToUndelegate = repo.GetBond(delegatee, delegator2.Address).Share / 2; delegator2.Undelegate(delegatee, shareToUndelegate, 11L); @@ -421,7 +430,9 @@ public void RewardOnUndelegate() c => repo.World.GetBalance(delegator1.Address, c)); var delegator2RewardBalances = delegatee.RewardCurrencies.Select( c => repo.World.GetBalance(delegator2.Address, c)); - collectedRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); + collectedRewards = delegatee.RewardCurrencies.Select( + c => repo.World.GetBalance(delegatee.DistributionPoolAddress(), c)); + legacyRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); var rewards2 = rewards.Select(r => (r * share2).DivRem(totalShares, out _)); Assert.Equal(delegatorInitialBalance - delegatingFAV1, delegator1Balance); @@ -430,10 +441,12 @@ public void RewardOnUndelegate() Assert.Equal(rewards2, delegator2RewardBalances); // Flushing to remainder pool is now inactive. - // Assert.Equal(delegatee.RewardCurrencies.Select(c => c * 0), collectedRewards); Assert.Equal( - rewards.Select(r => r * 2).Zip(rewards1.Zip(rewards2, (f, s) => f + s), (f, s) => f - s).ToArray(), + rewards.Zip(rewards1.Zip(rewards2, (f, s) => f + s), (f, s) => f - s).ToArray(), collectedRewards); + Assert.Equal( + rewards, + legacyRewards); } [Fact] @@ -486,13 +499,16 @@ public void RewardOnRedelegate() delegator1Balance = repo.World.GetBalance(delegator1.Address, delegatee.DelegationCurrency); var delegator1RewardBalances = delegatee.RewardCurrencies.Select( c => repo.World.GetBalance(delegator1.Address, c)); - var collectedRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); + var collectedRewards = delegatee.RewardCurrencies.Select( + c => repo.World.GetBalance(delegatee.DistributionPoolAddress(), c)); + var legacyRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); var rewards1 = rewards.Select(r => (r * share1).DivRem(totalShares, out _)); Assert.Equal(delegatorInitialBalance - delegatingFAV1, delegator1Balance); Assert.Equal(delegatorInitialBalance - delegatingFAV2, delegator2Balance); Assert.Equal(rewards1, delegator1RewardBalances); - Assert.Equal(rewards.Zip(rewards1, (f, s) => f * 2 - s), collectedRewards); + Assert.Equal(rewards.Zip(rewards1, (f, s) => f - s), collectedRewards); + Assert.Equal(rewards, legacyRewards); shareToRedelegate = repo.GetBond(delegatee, delegator2.Address).Share / 2; delegator2.Redelegate(delegatee, dstDelegatee, shareToRedelegate, 11L); @@ -501,7 +517,9 @@ public void RewardOnRedelegate() c => repo.World.GetBalance(delegator1.Address, c)); var delegator2RewardBalances = delegatee.RewardCurrencies.Select( c => repo.World.GetBalance(delegator2.Address, c)); - collectedRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); + collectedRewards = delegatee.RewardCurrencies.Select( + c => repo.World.GetBalance(delegatee.DistributionPoolAddress(), c)); + legacyRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); var rewards2 = rewards.Select(r => (r * share2).DivRem(totalShares, out _)); Assert.Equal(delegatorInitialBalance - delegatingFAV1, delegator1Balance); @@ -512,8 +530,9 @@ public void RewardOnRedelegate() // Flushing to remainder pool is now inactive. // Assert.Equal(delegatee.RewardCurrencies.Select(c => c * 0), collectedRewards); Assert.Equal( - rewards.Select(r => r * 2).Zip(rewards1.Zip(rewards2, (f, s) => f + s), (f, s) => f - s).ToArray(), + rewards.Zip(rewards1.Zip(rewards2, (f, s) => f + s), (f, s) => f - s).ToArray(), collectedRewards); + Assert.Equal(rewards, legacyRewards); } [Fact] @@ -566,13 +585,16 @@ public void RewardOnClaim() delegator1Balance = repo.World.GetBalance(delegator1.Address, delegatee.DelegationCurrency); var delegator1RewardBalances = delegatee.RewardCurrencies.Select( c => repo.World.GetBalance(delegator1.Address, c)); - var collectedRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); + var collectedRewards = delegatee.RewardCurrencies.Select( + c => repo.World.GetBalance(delegatee.DistributionPoolAddress(), c)); + var legacyRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); var rewards1 = rewards.Select(r => (r * share1).DivRem(totalShares, out _)); Assert.Equal(delegatorInitialBalance - delegatingFAV1, delegator1Balance); Assert.Equal(delegatorInitialBalance - delegatingFAV2, delegator2Balance); Assert.Equal(rewards1, delegator1RewardBalances); - Assert.Equal(rewards.Zip(rewards1, (f, s) => f * 2 - s), collectedRewards); + Assert.Equal(rewards.Zip(rewards1, (f, s) => f - s), collectedRewards); + Assert.Equal(rewards, legacyRewards); shareToRedelegate = repo.GetBond(delegatee, delegator2.Address).Share / 2; delegator2.ClaimReward(delegatee, 11L); @@ -581,7 +603,9 @@ public void RewardOnClaim() c => repo.World.GetBalance(delegator1.Address, c)); var delegator2RewardBalances = delegatee.RewardCurrencies.Select( c => repo.World.GetBalance(delegator2.Address, c)); - collectedRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); + collectedRewards = delegatee.RewardCurrencies.Select( + c => repo.World.GetBalance(delegatee.DistributionPoolAddress(), c)); + legacyRewards = DelegationFixture.TotalRewardsOfRecords(delegatee, repo); var rewards2 = rewards.Select(r => (r * share2).DivRem(totalShares, out _)); Assert.Equal(delegatorInitialBalance - delegatingFAV1, delegator1Balance); @@ -592,8 +616,9 @@ public void RewardOnClaim() // Flushing to remainder pool is now inactive. // Assert.Equal(delegatee.RewardCurrencies.Select(c => c * 0), collectedRewards); Assert.Equal( - rewards.Select(r => r * 2).Zip(rewards1.Zip(rewards2, (f, s) => f + s), (f, s) => f - s).ToArray(), + rewards.Zip(rewards1.Zip(rewards2, (f, s) => f + s), (f, s) => f - s).ToArray(), collectedRewards); + Assert.Equal(rewards, legacyRewards); } } } From 8680558de6147e7c8ff277f30974e75e8a24b9c5 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Wed, 11 Dec 2024 02:54:51 +0900 Subject: [PATCH 08/20] fix: Reward migration --- Lib9c/Delegation/Delegatee.cs | 26 ++++++++++++++++++++---- Lib9c/Delegation/DelegationRepository.cs | 16 +++++++-------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/Lib9c/Delegation/Delegatee.cs b/Lib9c/Delegation/Delegatee.cs index 419ecaa972..c5d194e178 100644 --- a/Lib9c/Delegation/Delegatee.cs +++ b/Lib9c/Delegation/Delegatee.cs @@ -535,25 +535,43 @@ record = Repository.GetLumpSumRewardsRecord(this, lastStartHeight) records.Reverse(); RewardBase? rewardBase = null; + RewardBase? newRewardBase = null; foreach (var recordEach in records) { if (rewardBase is null) { rewardBase = new RewardBase( - RewardBaseAddress(recordEach.StartHeight), + CurrentRewardBaseAddress(), recordEach.TotalShares, - recordEach.LumpSumRewards.Keys, - recordEach.StartHeight); + recordEach.LumpSumRewards.Keys); + } + else + { + newRewardBase = rewardBase.UpdateTotalShares(recordEach.TotalShares); + if (Repository.GetRewardBase(this, recordEach.StartHeight) is not null) + { + Repository.SetRewardBase(newRewardBase); + } + else + { + Address archiveAddress = RewardBaseAddress(recordEach.StartHeight); + var archivedRewardBase = rewardBase.AttachHeight(archiveAddress, recordEach.StartHeight); + Repository.SetRewardBase(archivedRewardBase); + } + + rewardBase = newRewardBase; } foreach (var r in recordEach.LumpSumRewards) { rewardBase = rewardBase.AddReward(r.Value); - Repository.TransferAsset(recordEach.Address, RewardPoolAddress, Repository.GetBalance(recordEach.Address, r.Key)); + Repository.TransferAsset(recordEach.Address, DistributionPoolAddress(), Repository.GetBalance(recordEach.Address, r.Key)); } Repository.RemoveLumpSumRewardsRecord(recordEach); } + + Repository.SetRewardBase(rewardBase!); } } } diff --git a/Lib9c/Delegation/DelegationRepository.cs b/Lib9c/Delegation/DelegationRepository.cs index 8275326cf9..d67bc2f614 100644 --- a/Lib9c/Delegation/DelegationRepository.cs +++ b/Lib9c/Delegation/DelegationRepository.cs @@ -80,21 +80,21 @@ public DelegationRepository( public Address DelegatorAccountAddress { get; } - private Address DelegateeMetadataAccountAddress { get; } + public Address DelegateeMetadataAccountAddress { get; } - private Address DelegatorMetadataAccountAddress { get; } + public Address DelegatorMetadataAccountAddress { get; } - private Address BondAccountAddress { get; } + public Address BondAccountAddress { get; } - private Address UnbondLockInAccountAddress { get; } + public Address UnbondLockInAccountAddress { get; } - private Address RebondGraceAccountAddress { get; } + public Address RebondGraceAccountAddress { get; } - private Address UnbondingSetAccountAddress { get; } + public Address UnbondingSetAccountAddress { get; } - private Address RewardBaseAccountAddress { get; } + public Address RewardBaseAccountAddress { get; } - private Address LumpSumRewardsRecordAccountAddress { get; } + public Address LumpSumRewardsRecordAccountAddress { get; } public abstract IDelegatee GetDelegatee(Address address); From 75ee32b5e699c5a9b519971b09ec9a5f91b21fbe Mon Sep 17 00:00:00 2001 From: ilgyu Date: Wed, 11 Dec 2024 02:55:11 +0900 Subject: [PATCH 09/20] test: Add reward migration test --- .Lib9c.Tests/Delegation/DelegationFixture.cs | 6 + .../Migration/LegacyDelegateeMetadata.cs | 221 -------- .../Migration/LegacyLumpSumRewardsRecord.cs | 138 ----- .../Migration/LegacyTestDelegatee.cs | 471 ++++++++++++++++++ .../Migration/LegacyTestDelegator.cs | 228 +++++++++ .../Migration/MigrateLegacyStateTest.cs | 81 --- .../Migration/RewardBaseMigrationTest.cs | 138 +++++ 7 files changed, 843 insertions(+), 440 deletions(-) delete mode 100644 .Lib9c.Tests/Delegation/Migration/LegacyDelegateeMetadata.cs delete mode 100644 .Lib9c.Tests/Delegation/Migration/LegacyLumpSumRewardsRecord.cs create mode 100644 .Lib9c.Tests/Delegation/Migration/LegacyTestDelegatee.cs create mode 100644 .Lib9c.Tests/Delegation/Migration/LegacyTestDelegator.cs delete mode 100644 .Lib9c.Tests/Delegation/Migration/MigrateLegacyStateTest.cs create mode 100644 .Lib9c.Tests/Delegation/Migration/RewardBaseMigrationTest.cs diff --git a/.Lib9c.Tests/Delegation/DelegationFixture.cs b/.Lib9c.Tests/Delegation/DelegationFixture.cs index 961775d217..ceab140717 100644 --- a/.Lib9c.Tests/Delegation/DelegationFixture.cs +++ b/.Lib9c.Tests/Delegation/DelegationFixture.cs @@ -40,12 +40,18 @@ public DelegationFixture() new Address("0x67A44E11506b8f0Bb625fEECccb205b33265Bb48"), TestRepository.DelegateeAccountAddress, TestRepository); TestDelegatee2 = new TestDelegatee( new Address("0xea1C4eedEfC99691DEfc6eF2753FAfa8C17F4584"), TestRepository.DelegateeAccountAddress, TestRepository); + TestRepository.SetDelegator(TestDelegator1); + TestRepository.SetDelegator(TestDelegator2); + TestRepository.SetDelegatee(TestDelegatee1); + TestRepository.SetDelegatee(TestDelegatee2); DummyRepository = new DummyRepository(world, context); DummyDelegatee1 = new DummyDelegatee( new Address("0x67A44E11506b8f0Bb625fEECccb205b33265Bb48"), DummyRepository.DelegateeAccountAddress, DummyRepository); DummyDelegator1 = new DummyDelegator( new Address("0x0054E98312C47E7Fa0ABed45C23Fa187e31C373a"), DummyRepository.DelegateeAccountAddress, DummyRepository); + DummyRepository.SetDelegator(DummyDelegator1); + DummyRepository.SetDelegatee(DummyDelegatee1); } public TestRepository TestRepository { get; } diff --git a/.Lib9c.Tests/Delegation/Migration/LegacyDelegateeMetadata.cs b/.Lib9c.Tests/Delegation/Migration/LegacyDelegateeMetadata.cs deleted file mode 100644 index 0cf0b90457..0000000000 --- a/.Lib9c.Tests/Delegation/Migration/LegacyDelegateeMetadata.cs +++ /dev/null @@ -1,221 +0,0 @@ -#nullable enable -namespace Lib9c.Tests.Delegation.Migration -{ - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Linq; - using System.Numerics; - using Bencodex; - using Bencodex.Types; - using Libplanet.Crypto; - using Libplanet.Types.Assets; - using Nekoyume.Delegation; - - public class LegacyDelegateeMetadata : IDelegateeMetadata - { - private readonly IComparer _currencyComparer = new CurrencyComparer(); - private Address? _address; - - public LegacyDelegateeMetadata( - Address delegateeAddress, - Address delegateeAccountAddress, - Currency delegationCurrency, - IEnumerable rewardCurrencies, - Address delegationPoolAddress, - Address rewardPoolAddress, - Address rewardRemainderPoolAddress, - Address slashedPoolAddress, - long unbondingPeriod, - int maxUnbondLockInEntries, - int maxRebondGraceEntries) - : this( - delegateeAddress, - delegateeAccountAddress, - delegationCurrency, - rewardCurrencies, - delegationPoolAddress, - rewardPoolAddress, - rewardRemainderPoolAddress, - slashedPoolAddress, - unbondingPeriod, - maxUnbondLockInEntries, - maxRebondGraceEntries, - ImmutableSortedSet
.Empty, - delegationCurrency * 0, - BigInteger.Zero, - false, - -1L, - false, - ImmutableSortedSet.Empty) - { - } - - public LegacyDelegateeMetadata( - Address delegateeAddress, - Address delegateeAccountAddress, - IValue bencoded) - : this(delegateeAddress, delegateeAccountAddress, (List)bencoded) - { - } - - public LegacyDelegateeMetadata( - Address address, - Address accountAddress, - List bencoded) - : this( - address, - accountAddress, - new Currency(bencoded[0]), - ((List)bencoded[1]).Select(v => new Currency(v)), - new Address(bencoded[2]), - new Address(bencoded[3]), - new Address(bencoded[4]), - new Address(bencoded[5]), - (Integer)bencoded[6], - (Integer)bencoded[7], - (Integer)bencoded[8], - ((List)bencoded[9]).Select(item => new Address(item)), - new FungibleAssetValue(bencoded[10]), - (Integer)bencoded[11], - (Bencodex.Types.Boolean)bencoded[12], - (Integer)bencoded[13], - (Bencodex.Types.Boolean)bencoded[14], - ((List)bencoded[15]).Select(item => new UnbondingRef(item))) - { - } - - private LegacyDelegateeMetadata( - Address delegateeAddress, - Address delegateeAccountAddress, - Currency delegationCurrency, - IEnumerable rewardCurrencies, - Address delegationPoolAddress, - Address rewardPoolAddress, - Address rewardRemainderPoolAddress, - Address slashedPoolAddress, - long unbondingPeriod, - int maxUnbondLockInEntries, - int maxRebondGraceEntries, - IEnumerable
delegators, - FungibleAssetValue totalDelegated, - BigInteger totalShares, - bool jailed, - long jailedUntil, - bool tombstoned, - IEnumerable unbondingRefs) - { - if (!totalDelegated.Currency.Equals(delegationCurrency)) - { - throw new InvalidOperationException("Invalid currency."); - } - - if (totalDelegated.Sign < 0) - { - throw new ArgumentOutOfRangeException( - nameof(totalDelegated), - totalDelegated, - "Total delegated must be non-negative."); - } - - if (totalShares.Sign < 0) - { - throw new ArgumentOutOfRangeException( - nameof(totalShares), - totalShares, - "Total shares must be non-negative."); - } - - DelegateeAddress = delegateeAddress; - DelegateeAccountAddress = delegateeAccountAddress; - DelegationCurrency = delegationCurrency; - RewardCurrencies = rewardCurrencies.ToImmutableSortedSet(_currencyComparer); - DelegationPoolAddress = delegationPoolAddress; - RewardPoolAddress = rewardPoolAddress; - RewardRemainderPoolAddress = rewardRemainderPoolAddress; - SlashedPoolAddress = slashedPoolAddress; - UnbondingPeriod = unbondingPeriod; - MaxUnbondLockInEntries = maxUnbondLockInEntries; - MaxRebondGraceEntries = maxRebondGraceEntries; - Delegators = delegators.ToImmutableSortedSet(); - TotalDelegatedFAV = totalDelegated; - TotalShares = totalShares; - Jailed = jailed; - JailedUntil = jailedUntil; - Tombstoned = tombstoned; - UnbondingRefs = unbondingRefs.ToImmutableSortedSet(); - } - - public Address DelegateeAddress { get; } - - public Address DelegateeAccountAddress { get; } - - public Address Address - => _address ??= DelegationAddress.DelegateeMetadataAddress( - DelegateeAddress, - DelegateeAccountAddress); - - public Currency DelegationCurrency { get; } - - public ImmutableSortedSet RewardCurrencies { get; } - - public Address DelegationPoolAddress { get; internal set; } - - public Address RewardPoolAddress { get; } - - public Address RewardRemainderPoolAddress { get; } - - public Address SlashedPoolAddress { get; } - - public long UnbondingPeriod { get; private set; } - - public int MaxUnbondLockInEntries { get; } - - public int MaxRebondGraceEntries { get; } - - public ImmutableSortedSet
Delegators { get; private set; } - - public FungibleAssetValue TotalDelegatedFAV { get; private set; } - - public BigInteger TotalShares { get; private set; } - - public bool Jailed { get; internal set; } - - public long JailedUntil { get; internal set; } - - public bool Tombstoned { get; internal set; } - - public ImmutableSortedSet UnbondingRefs { get; private set; } - - // TODO : Better serialization - public List Bencoded => List.Empty - .Add(DelegationCurrency.Serialize()) - .Add(new List(RewardCurrencies.Select(c => c.Serialize()))) - .Add(DelegationPoolAddress.Bencoded) - .Add(RewardPoolAddress.Bencoded) - .Add(RewardRemainderPoolAddress.Bencoded) - .Add(SlashedPoolAddress.Bencoded) - .Add(UnbondingPeriod) - .Add(MaxUnbondLockInEntries) - .Add(MaxRebondGraceEntries) - .Add(new List(Delegators.Select(delegator => delegator.Bencoded))) - .Add(TotalDelegatedFAV.Serialize()) - .Add(TotalShares) - .Add(Jailed) - .Add(JailedUntil) - .Add(Tombstoned) - .Add(new List(UnbondingRefs.Select(unbondingRef => unbondingRef.Bencoded))); - - IValue IBencodable.Bencoded => Bencoded; - - public BigInteger ShareFromFAV(FungibleAssetValue fav) - => TotalShares.IsZero - ? fav.RawValue - : TotalShares * fav.RawValue / TotalDelegatedFAV.RawValue; - - public FungibleAssetValue FAVFromShare(BigInteger share) - => TotalShares == share - ? TotalDelegatedFAV - : (TotalDelegatedFAV * share).DivRem(TotalShares).Quotient; - } -} diff --git a/.Lib9c.Tests/Delegation/Migration/LegacyLumpSumRewardsRecord.cs b/.Lib9c.Tests/Delegation/Migration/LegacyLumpSumRewardsRecord.cs deleted file mode 100644 index abb6be9f37..0000000000 --- a/.Lib9c.Tests/Delegation/Migration/LegacyLumpSumRewardsRecord.cs +++ /dev/null @@ -1,138 +0,0 @@ -#nullable enable -namespace Lib9c.Tests.Delegation.Migration -{ - using System; - using System.Collections.Generic; - using System.Collections.Immutable; - using System.Linq; - using System.Numerics; - using Bencodex; - using Bencodex.Types; - using Libplanet.Crypto; - using Libplanet.Types.Assets; - using Nekoyume.Delegation; - - public class LegacyLumpSumRewardsRecord : IBencodable - { - private readonly IComparer _currencyComparer = new CurrencyComparer(); - - public LegacyLumpSumRewardsRecord( - Address address, - long startHeight, - BigInteger totalShares, - ImmutableSortedSet
delegators, - IEnumerable currencies) - : this( - address, - startHeight, - totalShares, - delegators, - currencies, - null) - { - } - - public LegacyLumpSumRewardsRecord( - Address address, - long startHeight, - BigInteger totalShares, - ImmutableSortedSet
delegators, - IEnumerable currencies, - long? lastStartHeight) - : this( - address, - startHeight, - totalShares, - delegators, - currencies.Select(c => c * 0), - lastStartHeight) - { - } - - public LegacyLumpSumRewardsRecord( - Address address, - long startHeight, - BigInteger totalShares, - ImmutableSortedSet
delegators, - IEnumerable lumpSumRewards, - long? lastStartHeight) - { - Address = address; - StartHeight = startHeight; - TotalShares = totalShares; - Delegators = delegators; - - if (!lumpSumRewards.Select(f => f.Currency).All(new HashSet().Add)) - { - throw new ArgumentException("Duplicated currency in lump sum rewards."); - } - - LumpSumRewards = lumpSumRewards.ToImmutableDictionary(f => f.Currency, f => f); - LastStartHeight = lastStartHeight; - } - - public LegacyLumpSumRewardsRecord(Address address, IValue bencoded) - : this(address, (List)bencoded) - { - } - - public LegacyLumpSumRewardsRecord(Address address, List bencoded) - : this( - address, - (Integer)bencoded[0], - (Integer)bencoded[1], - ((List)bencoded[2]).Select(a => new Address(a)).ToImmutableSortedSet(), - ((List)bencoded[3]).Select(v => new FungibleAssetValue(v)), - (Integer?)bencoded.ElementAtOrDefault(4)) - { - } - - private LegacyLumpSumRewardsRecord( - Address address, - long startHeight, - BigInteger totalShares, - ImmutableSortedSet
delegators, - ImmutableDictionary lumpSumRewards, - long? lastStartHeight) - { - Address = address; - StartHeight = startHeight; - TotalShares = totalShares; - Delegators = delegators; - LumpSumRewards = lumpSumRewards; - LastStartHeight = lastStartHeight; - } - - public Address Address { get; } - - public long StartHeight { get; } - - public BigInteger TotalShares { get; } - - public ImmutableDictionary LumpSumRewards { get; } - - public ImmutableSortedSet
Delegators { get; } - - public long? LastStartHeight { get; } - - public List Bencoded - { - get - { - var bencoded = List.Empty - .Add(StartHeight) - .Add(TotalShares) - .Add(new List(Delegators.Select(a => a.Bencoded))) - .Add(new List(LumpSumRewards - .OrderBy(r => r.Key, _currencyComparer) - .Select(r => r.Value.Serialize()))); - - return LastStartHeight is long lastStartHeight - ? bencoded.Add(lastStartHeight) - : bencoded; - } - } - - IValue IBencodable.Bencoded => Bencoded; - } -} diff --git a/.Lib9c.Tests/Delegation/Migration/LegacyTestDelegatee.cs b/.Lib9c.Tests/Delegation/Migration/LegacyTestDelegatee.cs new file mode 100644 index 0000000000..ef76cd00bc --- /dev/null +++ b/.Lib9c.Tests/Delegation/Migration/LegacyTestDelegatee.cs @@ -0,0 +1,471 @@ +#nullable enable +namespace Lib9c.Tests.Delegation.Migration +{ + using System; + using System.Collections.Generic; + using System.Collections.Immutable; + using System.Linq; + using System.Numerics; + using Bencodex.Types; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume.Delegation; + + public class LegacyTestDelegatee : IDelegatee + { + public LegacyTestDelegatee( + Address address, + Address accountAddress, + Currency delegationCurrency, + IEnumerable rewardCurrencies, + Address delegationPoolAddress, + Address rewardPoolAddress, + Address rewardRemainderPoolAddress, + Address slashedPoolAddress, + long unbondingPeriod, + int maxUnbondLockInEntries, + int maxRebondGraceEntries, + IDelegationRepository repository) + : this( + new DelegateeMetadata( + address, + accountAddress, + delegationCurrency, + rewardCurrencies, + delegationPoolAddress, + rewardPoolAddress, + rewardRemainderPoolAddress, + slashedPoolAddress, + unbondingPeriod, + maxUnbondLockInEntries, + maxRebondGraceEntries), + repository) + { + } + + public LegacyTestDelegatee( + Address address, + IDelegationRepository repository) + : this(repository.GetDelegateeMetadata(address), repository) + { + } + + private LegacyTestDelegatee(DelegateeMetadata metadata, IDelegationRepository repository) + { + Metadata = metadata; + Repository = repository; + } + + public event EventHandler? DelegationChanged; + + public event EventHandler? Enjailed; + + public event EventHandler? Unjailed; + + public DelegateeMetadata Metadata { get; } + + public IDelegationRepository Repository { get; } + + public Address Address => Metadata.DelegateeAddress; + + public Address AccountAddress => Metadata.DelegateeAccountAddress; + + public Address MetadataAddress => Metadata.Address; + + public Currency DelegationCurrency => Metadata.DelegationCurrency; + + public ImmutableSortedSet RewardCurrencies => Metadata.RewardCurrencies; + + public Address DelegationPoolAddress => Metadata.DelegationPoolAddress; + + public Address RewardPoolAddress => Metadata.RewardPoolAddress; + + public Address RewardRemainderPoolAddress => Metadata.RewardRemainderPoolAddress; + + public Address SlashedPoolAddress => Metadata.SlashedPoolAddress; + + public long UnbondingPeriod => Metadata.UnbondingPeriod; + + public int MaxUnbondLockInEntries => Metadata.MaxUnbondLockInEntries; + + public int MaxRebondGraceEntries => Metadata.MaxRebondGraceEntries; + + public FungibleAssetValue TotalDelegated => Metadata.TotalDelegatedFAV; + + public BigInteger TotalShares => Metadata.TotalShares; + + public bool Jailed => Metadata.Jailed; + + public long JailedUntil => Metadata.JailedUntil; + + public bool Tombstoned => Metadata.Tombstoned; + + public List MetadataBencoded => Metadata.Bencoded; + + public BigInteger ShareFromFAV(FungibleAssetValue fav) + => Metadata.ShareFromFAV(fav); + + public FungibleAssetValue FAVFromShare(BigInteger share) + => Metadata.FAVFromShare(share); + + public BigInteger Bond(IDelegator delegator, FungibleAssetValue fav, long height) + => Bond((LegacyTestDelegator)delegator, fav, height); + + public FungibleAssetValue Unbond(IDelegator delegator, BigInteger share, long height) + => Unbond((LegacyTestDelegator)delegator, share, height); + + public void DistributeReward(IDelegator delegator, long height) + => DistributeReward((LegacyTestDelegator)delegator, height); + + public void Jail(long releaseHeight) + { + Metadata.JailedUntil = releaseHeight; + Metadata.Jailed = true; + Repository.SetDelegateeMetadata(Metadata); + Enjailed?.Invoke(this, EventArgs.Empty); + } + + public void Unjail(long height) + { + if (!Jailed) + { + throw new InvalidOperationException("Cannot unjail non-jailed delegatee."); + } + + if (Tombstoned) + { + throw new InvalidOperationException("Cannot unjail tombstoned delegatee."); + } + + if (JailedUntil >= height) + { + throw new InvalidOperationException("Cannot unjail before jailed until."); + } + + Metadata.JailedUntil = -1L; + Metadata.Jailed = false; + Repository.SetDelegateeMetadata(Metadata); + Unjailed?.Invoke(this, EventArgs.Empty); + } + + public void Tombstone() + { + Jail(long.MaxValue); + Metadata.Tombstoned = true; + Repository.SetDelegateeMetadata(Metadata); + } + + public Address BondAddress(Address delegatorAddress) + => Metadata.BondAddress(delegatorAddress); + + public Address UnbondLockInAddress(Address delegatorAddress) + => Metadata.UnbondLockInAddress(delegatorAddress); + + public Address RebondGraceAddress(Address delegatorAddress) + => Metadata.RebondGraceAddress(delegatorAddress); + + public Address CurrentLumpSumRewardsRecordAddress() + => Metadata.CurrentLumpSumRewardsRecordAddress(); + + public Address LumpSumRewardsRecordAddress(long height) + => Metadata.LumpSumRewardsRecordAddress(height); + + public Address CurrentRewardBaseAddress() => throw new NotImplementedException(); + + public Address RewardBaseAddress(long height) => throw new NotImplementedException(); + + public BigInteger Bond(LegacyTestDelegator delegator, FungibleAssetValue fav, long height) + { + DistributeReward(delegator, height); + + if (!fav.Currency.Equals(DelegationCurrency)) + { + throw new InvalidOperationException( + "Cannot bond with invalid currency."); + } + + if (Tombstoned) + { + throw new InvalidOperationException( + "Cannot bond to tombstoned delegatee."); + } + + Bond bond = Repository.GetBond(this, delegator.Address); + BigInteger share = ShareFromFAV(fav); + bond = bond.AddShare(share); + Metadata.AddShare(share); + Metadata.AddDelegatedFAV(fav); + Repository.SetBond(bond); + StartNewRewardPeriod(height); + Repository.SetDelegateeMetadata(Metadata); + DelegationChanged?.Invoke(this, height); + + return share; + } + + BigInteger IDelegatee.Bond(IDelegator delegator, FungibleAssetValue fav, long height) + => Bond((LegacyTestDelegator)delegator, fav, height); + + public FungibleAssetValue Unbond(LegacyTestDelegator delegator, BigInteger share, long height) + { + DistributeReward(delegator, height); + if (TotalShares.IsZero || TotalDelegated.RawValue.IsZero) + { + throw new InvalidOperationException( + "Cannot unbond without bonding."); + } + + Bond bond = Repository!.GetBond(this, delegator.Address); + FungibleAssetValue fav = FAVFromShare(share); + bond = bond.SubtractShare(share); + if (bond.Share.IsZero) + { + bond = bond.ClearLastDistributeHeight(); + } + + Metadata.RemoveShare(share); + Metadata.RemoveDelegatedFAV(fav); + Repository.SetBond(bond); + StartNewRewardPeriod(height); + Repository.SetDelegateeMetadata(Metadata); + DelegationChanged?.Invoke(this, height); + + return fav; + } + + FungibleAssetValue IDelegatee.Unbond(IDelegator delegator, BigInteger share, long height) + => Unbond((LegacyTestDelegator)delegator, share, height); + + public void DistributeReward(LegacyTestDelegator delegator, long height) + { + Bond bond = Repository.GetBond(this, delegator.Address); + BigInteger share = bond.Share; + + if (!share.IsZero && bond.LastDistributeHeight.HasValue) + { + IEnumerable lumpSumRewardsRecords + = GetLumpSumRewardsRecords(bond.LastDistributeHeight); + + foreach (LumpSumRewardsRecord record in lumpSumRewardsRecords) + { + TransferReward(delegator, share, record); + // TransferRemainders(newRecord); + Repository.SetLumpSumRewardsRecord(record); + } + } + + if (bond.LastDistributeHeight != height) + { + bond = bond.UpdateLastDistributeHeight(height); + } + + Repository.SetBond(bond); + } + + void IDelegatee.DistributeReward(IDelegator delegator, long height) + => DistributeReward((LegacyTestDelegator)delegator, height); + + public void CollectRewards(long height) + { + var rewards = RewardCurrencies.Select(c => Repository.GetBalance(RewardPoolAddress, c)); + LumpSumRewardsRecord record = Repository.GetCurrentLumpSumRewardsRecord(this) + ?? new LumpSumRewardsRecord( + CurrentLumpSumRewardsRecordAddress(), + height, + TotalShares, + RewardCurrencies); + record = record.AddLumpSumRewards(rewards); + + foreach (var rewardsEach in rewards) + { + if (rewardsEach.Sign > 0) + { + Repository.TransferAsset(RewardPoolAddress, record.Address, rewardsEach); + } + } + + Repository.SetLumpSumRewardsRecord(record); + } + + public virtual void Slash(BigInteger slashFactor, long infractionHeight, long height) + { + FungibleAssetValue slashed = TotalDelegated.DivRem(slashFactor, out var rem); + if (rem.Sign > 0) + { + slashed += FungibleAssetValue.FromRawValue(rem.Currency, 1); + } + + if (slashed > Metadata.TotalDelegatedFAV) + { + slashed = Metadata.TotalDelegatedFAV; + } + + Metadata.RemoveDelegatedFAV(slashed); + + foreach (var item in Metadata.UnbondingRefs) + { + var unbonding = UnbondingFactory.GetUnbondingFromRef(item, Repository); + + unbonding = unbonding.Slash(slashFactor, infractionHeight, height, out var slashedFAV); + + if (slashedFAV.HasValue) + { + slashed += slashedFAV.Value; + } + + if (unbonding.IsEmpty) + { + Metadata.RemoveUnbondingRef(item); + } + + switch (unbonding) + { + case UnbondLockIn unbondLockIn: + Repository.SetUnbondLockIn(unbondLockIn); + break; + case RebondGrace rebondGrace: + Repository.SetRebondGrace(rebondGrace); + break; + default: + throw new InvalidOperationException("Invalid unbonding type."); + } + } + + var delegationBalance = Repository.GetBalance(DelegationPoolAddress, DelegationCurrency); + if (delegationBalance < slashed) + { + slashed = delegationBalance; + } + + if (slashed > DelegationCurrency * 0) + { + Repository.TransferAsset(DelegationPoolAddress, SlashedPoolAddress, slashed); + } + + Repository.SetDelegateeMetadata(Metadata); + DelegationChanged?.Invoke(this, height); + } + + void IDelegatee.Slash(BigInteger slashFactor, long infractionHeight, long height) + => Slash(slashFactor, infractionHeight, height); + + public void AddUnbondingRef(UnbondingRef reference) + => Metadata.AddUnbondingRef(reference); + + public void RemoveUnbondingRef(UnbondingRef reference) + => Metadata.RemoveUnbondingRef(reference); + + public ImmutableDictionary CalculateReward( + BigInteger share, + IEnumerable lumpSumRewardsRecords) + { + ImmutableDictionary reward + = RewardCurrencies.ToImmutableDictionary(c => c, c => c * 0); + + foreach (LumpSumRewardsRecord record in lumpSumRewardsRecords) + { + var rewardDuringPeriod = record.RewardsDuringPeriod(share); + reward = rewardDuringPeriod.Aggregate(reward, (acc, pair) + => acc.SetItem(pair.Key, acc[pair.Key] + pair.Value)); + } + + return reward; + } + + private void StartNewRewardPeriod(long height) + { + LumpSumRewardsRecord? currentRecord = Repository.GetCurrentLumpSumRewardsRecord(this); + long? lastStartHeight = null; + if (currentRecord is LumpSumRewardsRecord lastRecord) + { + lastStartHeight = lastRecord.StartHeight; + if (lastStartHeight == height) + { + currentRecord = new ( + currentRecord.Address, + currentRecord.StartHeight, + TotalShares, + RewardCurrencies, + currentRecord.LastStartHeight); + + Repository.SetLumpSumRewardsRecord(currentRecord); + return; + } + + Address archiveAddress = LumpSumRewardsRecordAddress(lastRecord.StartHeight); + + foreach (var rewardCurrency in RewardCurrencies) + { + FungibleAssetValue reward = Repository.GetBalance(lastRecord.Address, rewardCurrency); + if (reward.Sign > 0) + { + Repository.TransferAsset(lastRecord.Address, archiveAddress, reward); + } + } + + lastRecord = lastRecord.MoveAddress(archiveAddress); + Repository.SetLumpSumRewardsRecord(lastRecord); + } + + LumpSumRewardsRecord newRecord = new ( + CurrentLumpSumRewardsRecordAddress(), + height, + TotalShares, + RewardCurrencies, + lastStartHeight); + + Repository.SetLumpSumRewardsRecord(newRecord); + } + + private List GetLumpSumRewardsRecords(long? lastRewardHeight) + { + List records = new (); + if (lastRewardHeight is null + || !(Repository.GetCurrentLumpSumRewardsRecord(this) is LumpSumRewardsRecord record)) + { + return records; + } + + while (record.StartHeight >= lastRewardHeight) + { + records.Add(record); + + if (!(record.LastStartHeight is long lastStartHeight)) + { + break; + } + + record = Repository.GetLumpSumRewardsRecord(this, lastStartHeight) + ?? throw new InvalidOperationException( + $"Lump sum rewards record for #{lastStartHeight} is missing"); + } + + return records; + } + + private void TransferReward(LegacyTestDelegator delegator, BigInteger share, LumpSumRewardsRecord record) + { + ImmutableSortedDictionary reward = record.RewardsDuringPeriod(share); + foreach (var rewardEach in reward) + { + if (rewardEach.Value.Sign > 0) + { + Repository.TransferAsset(record.Address, delegator.RewardAddress, rewardEach.Value); + } + } + } + + private void TransferRemainders(LumpSumRewardsRecord record) + { + foreach (var rewardCurrency in RewardCurrencies) + { + FungibleAssetValue remainder = Repository.GetBalance(record.Address, rewardCurrency); + + if (remainder.Sign > 0) + { + Repository.TransferAsset(record.Address, RewardRemainderPoolAddress, remainder); + } + } + } + } +} diff --git a/.Lib9c.Tests/Delegation/Migration/LegacyTestDelegator.cs b/.Lib9c.Tests/Delegation/Migration/LegacyTestDelegator.cs new file mode 100644 index 0000000000..a2ab3ade94 --- /dev/null +++ b/.Lib9c.Tests/Delegation/Migration/LegacyTestDelegator.cs @@ -0,0 +1,228 @@ +#nullable enable +namespace Lib9c.Tests.Delegation.Migration +{ + using System; + using System.Collections.Immutable; + using System.Numerics; + using Bencodex.Types; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume.Delegation; + + public class LegacyTestDelegator : IDelegator + { + public LegacyTestDelegator( + Address address, + Address accountAddress, + Address delegationPoolAddress, + Address rewardAddress, + IDelegationRepository repository) + : this( + new DelegatorMetadata( + address, + accountAddress, + delegationPoolAddress, + rewardAddress), + repository) + { + } + + public LegacyTestDelegator( + Address address, + IDelegationRepository repository) + : this(repository.GetDelegatorMetadata(address), repository) + { + } + + private LegacyTestDelegator(DelegatorMetadata metadata, IDelegationRepository repository) + { + Metadata = metadata; + Repository = repository; + } + + public DelegatorMetadata Metadata { get; } + + public IDelegationRepository Repository { get; } + + public Address Address => Metadata.DelegatorAddress; + + public Address AccountAddress => Metadata.DelegatorAccountAddress; + + public Address MetadataAddress => Metadata.Address; + + public Address DelegationPoolAddress => Metadata.DelegationPoolAddress; + + public Address RewardAddress => Metadata.RewardAddress; + + public ImmutableSortedSet
Delegatees => Metadata.Delegatees; + + public List MetadataBencoded => Metadata.Bencoded; + + public void Delegate( + LegacyTestDelegatee delegatee, FungibleAssetValue fav, long height) + { + if (fav.Sign <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(fav), fav, "Fungible asset value must be positive."); + } + + if (delegatee.Tombstoned) + { + throw new InvalidOperationException("Delegatee is tombstoned."); + } + + delegatee.Bond(this, fav, height); + Metadata.AddDelegatee(delegatee.Address); + Repository.TransferAsset(DelegationPoolAddress, delegatee.DelegationPoolAddress, fav); + Repository.SetDelegatorMetadata(Metadata); + } + + void IDelegator.Delegate( + IDelegatee delegatee, FungibleAssetValue fav, long height) + => Delegate((LegacyTestDelegatee)delegatee, fav, height); + + public void Undelegate( + LegacyTestDelegatee delegatee, BigInteger share, long height) + { + if (share.Sign <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(share), share, "Share must be positive."); + } + + if (height <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(height), height, "Height must be positive."); + } + + UnbondLockIn unbondLockIn = Repository.GetUnbondLockIn(delegatee, Address); + + if (unbondLockIn.IsFull) + { + throw new InvalidOperationException("Undelegation is full."); + } + + FungibleAssetValue fav = delegatee.Unbond(this, share, height); + unbondLockIn = unbondLockIn.LockIn( + fav, height, height + delegatee.UnbondingPeriod); + + if (Repository.GetBond(delegatee, Address).Share.IsZero) + { + Metadata.RemoveDelegatee(delegatee.Address); + } + + delegatee.AddUnbondingRef(UnbondingFactory.ToReference(unbondLockIn)); + + Repository.SetUnbondLockIn(unbondLockIn); + Repository.SetUnbondingSet( + Repository.GetUnbondingSet().SetUnbonding(unbondLockIn)); + Repository.SetDelegatorMetadata(Metadata); + } + + void IDelegator.Undelegate( + IDelegatee delegatee, BigInteger share, long height) + => Undelegate((LegacyTestDelegatee)delegatee, share, height); + + public void Redelegate( + LegacyTestDelegatee srcDelegatee, LegacyTestDelegatee dstDelegatee, BigInteger share, long height) + { + if (share.Sign <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(share), share, "Share must be positive."); + } + + if (height <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(height), height, "Height must be positive."); + } + + if (dstDelegatee.Tombstoned) + { + throw new InvalidOperationException("Destination delegatee is tombstoned."); + } + + FungibleAssetValue fav = srcDelegatee.Unbond( + this, share, height); + dstDelegatee.Bond( + this, fav, height); + RebondGrace srcRebondGrace = Repository.GetRebondGrace(srcDelegatee, Address).Grace( + dstDelegatee.Address, + fav, + height, + height + srcDelegatee.UnbondingPeriod); + + if (Repository.GetBond(srcDelegatee, Address).Share.IsZero) + { + Metadata.RemoveDelegatee(srcDelegatee.Address); + } + + Metadata.AddDelegatee(dstDelegatee.Address); + + srcDelegatee.AddUnbondingRef(UnbondingFactory.ToReference(srcRebondGrace)); + + Repository.SetRebondGrace(srcRebondGrace); + Repository.SetUnbondingSet( + Repository.GetUnbondingSet().SetUnbonding(srcRebondGrace)); + Repository.SetDelegatorMetadata(Metadata); + } + + void IDelegator.Redelegate( + IDelegatee srcDelegatee, IDelegatee dstDelegatee, BigInteger share, long height) + => Redelegate((LegacyTestDelegatee)srcDelegatee, (LegacyTestDelegatee)dstDelegatee, share, height); + + public void CancelUndelegate( + LegacyTestDelegatee delegatee, FungibleAssetValue fav, long height) + { + if (fav.Sign <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(fav), fav, "Fungible asset value must be positive."); + } + + if (height <= 0) + { + throw new ArgumentOutOfRangeException( + nameof(height), height, "Height must be positive."); + } + + UnbondLockIn unbondLockIn = Repository.GetUnbondLockIn(delegatee, Address); + + if (unbondLockIn.IsFull) + { + throw new InvalidOperationException("Undelegation is full."); + } + + delegatee.Bond(this, fav, height); + unbondLockIn = unbondLockIn.Cancel(fav, height); + Metadata.AddDelegatee(delegatee.Address); + + if (unbondLockIn.IsEmpty) + { + delegatee.RemoveUnbondingRef(UnbondingFactory.ToReference(unbondLockIn)); + } + + Repository.SetUnbondLockIn(unbondLockIn); + Repository.SetUnbondingSet( + Repository.GetUnbondingSet().SetUnbonding(unbondLockIn)); + Repository.SetDelegatorMetadata(Metadata); + } + + void IDelegator.CancelUndelegate( + IDelegatee delegatee, FungibleAssetValue fav, long height) + => CancelUndelegate((LegacyTestDelegatee)delegatee, fav, height); + + public void ClaimReward( + LegacyTestDelegatee delegatee, long height) + { + delegatee.DistributeReward(this, height); + Repository.SetDelegatorMetadata(Metadata); + } + + void IDelegator.ClaimReward(IDelegatee delegatee, long height) + => ClaimReward((LegacyTestDelegatee)delegatee, height); + } +} diff --git a/.Lib9c.Tests/Delegation/Migration/MigrateLegacyStateTest.cs b/.Lib9c.Tests/Delegation/Migration/MigrateLegacyStateTest.cs deleted file mode 100644 index 4ed115b985..0000000000 --- a/.Lib9c.Tests/Delegation/Migration/MigrateLegacyStateTest.cs +++ /dev/null @@ -1,81 +0,0 @@ -namespace Lib9c.Tests.Delegation.Migration -{ - using System.Collections.Immutable; - using System.Linq; - using Libplanet.Crypto; - using Libplanet.Types.Assets; - using Nekoyume.Delegation; - using Xunit; - - public class MigrateLegacyStateTest - { - [Fact] - public void ParseLegacyDelegateeMetadata() - { - var address = new PrivateKey().Address; - var accountAddress = new PrivateKey().Address; - var delegationCurrency = Currency.Uncapped("del", 5, null); - var rewardCurrencies = new Currency[] { Currency.Uncapped("rew", 5, null), }; - var delegationPoolAddress = new PrivateKey().Address; - var rewardPoolAddress = new PrivateKey().Address; - var rewardRemainderPoolAddress = new PrivateKey().Address; - var slashedPoolAddress = new PrivateKey().Address; - var unbondingPeriod = 1L; - var maxUnbondLockInEntries = 2; - var maxRebondGraceEntries = 3; - - var legacyDelegateeMetadataBencoded = new LegacyDelegateeMetadata( - address, - accountAddress, - delegationCurrency, - rewardCurrencies, - delegationPoolAddress, - rewardPoolAddress, - rewardRemainderPoolAddress, - slashedPoolAddress, - unbondingPeriod, - maxUnbondLockInEntries, - maxRebondGraceEntries).Bencoded; - - var delegateeMetadata = new DelegateeMetadata(address, accountAddress, legacyDelegateeMetadataBencoded); - - Assert.Equal(address, delegateeMetadata.DelegateeAddress); - Assert.Equal(accountAddress, delegateeMetadata.DelegateeAccountAddress); - Assert.Equal(delegationCurrency, delegateeMetadata.DelegationCurrency); - Assert.Equal(rewardCurrencies, delegateeMetadata.RewardCurrencies); - Assert.Equal(delegationPoolAddress, delegateeMetadata.DelegationPoolAddress); - Assert.Equal(rewardPoolAddress, delegateeMetadata.RewardPoolAddress); - Assert.Equal(rewardRemainderPoolAddress, delegateeMetadata.RewardRemainderPoolAddress); - Assert.Equal(slashedPoolAddress, delegateeMetadata.SlashedPoolAddress); - Assert.Equal(unbondingPeriod, delegateeMetadata.UnbondingPeriod); - Assert.Equal(maxUnbondLockInEntries, delegateeMetadata.MaxUnbondLockInEntries); - Assert.Equal(maxRebondGraceEntries, delegateeMetadata.MaxRebondGraceEntries); - } - - [Fact] - public void ParseLegacyLumpSumRewardsRecord() - { - var address = new PrivateKey().Address; - var startHeight = 1L; - var totalShares = 2; - var delegators = ImmutableSortedSet.Create
(new PrivateKey().Address); - var currencies = new Currency[] { Currency.Uncapped("cur", 5, null), }; - var lastStartHeight = 3L; - - var legacyLumpSumRewardsRecordBencoded = new LegacyLumpSumRewardsRecord( - address, - startHeight, - totalShares, - delegators, - currencies, - lastStartHeight).Bencoded; - - var lumpSumRewardsRecord = new LumpSumRewardsRecord(address, legacyLumpSumRewardsRecordBencoded); - - Assert.Equal(address, lumpSumRewardsRecord.Address); - Assert.Equal(startHeight, lumpSumRewardsRecord.StartHeight); - Assert.Equal(totalShares, lumpSumRewardsRecord.TotalShares); - Assert.Equal(currencies, lumpSumRewardsRecord.LumpSumRewards.Select(c => c.Key)); - } - } -} diff --git a/.Lib9c.Tests/Delegation/Migration/RewardBaseMigrationTest.cs b/.Lib9c.Tests/Delegation/Migration/RewardBaseMigrationTest.cs new file mode 100644 index 0000000000..3dcca62e6c --- /dev/null +++ b/.Lib9c.Tests/Delegation/Migration/RewardBaseMigrationTest.cs @@ -0,0 +1,138 @@ +#nullable enable +namespace Lib9c.Tests.Delegation.Migration +{ + using System.Linq; + using Nekoyume.Delegation; + using Nekoyume.Extensions; + using Xunit; + + public class RewardBaseMigrationTest + { + private readonly DelegationFixture _fixture; + + public RewardBaseMigrationTest() + { + _fixture = new DelegationFixture(); + } + + public LegacyTestDelegatee LegacyDelegatee + => new LegacyTestDelegatee( + _fixture.TestDelegatee1.Address, + _fixture.TestRepository); + + public LegacyTestDelegator LegacyDelegator1 + => new LegacyTestDelegator( + _fixture.TestDelegator1.Address, + _fixture.TestRepository); + + public LegacyTestDelegator LegacyDelegator2 + => new LegacyTestDelegator( + _fixture.TestDelegator2.Address, + _fixture.TestRepository); + + [Fact] + public void Migrate() + { + var repo = _fixture.TestRepository; + + var delegatorInitialBalance = LegacyDelegatee.DelegationCurrency * 2000; + repo.MintAsset(LegacyDelegator1.Address, delegatorInitialBalance); + repo.MintAsset(LegacyDelegator2.Address, delegatorInitialBalance); + + var rewards = LegacyDelegatee.RewardCurrencies.Select(r => r * 100); + foreach (var reward in rewards) + { + repo.MintAsset(LegacyDelegatee.RewardPoolAddress, reward); + } + + LegacyDelegatee.CollectRewards(7L); + + var delegatingFAV = LegacyDelegatee.DelegationCurrency * 100; + LegacyDelegator1.Delegate(LegacyDelegatee, delegatingFAV, 10L); + + foreach (var reward in rewards) + { + repo.MintAsset(LegacyDelegatee.RewardPoolAddress, reward); + } + + LegacyDelegatee.CollectRewards(13L); + + var delegatingFAV1 = LegacyDelegatee.DelegationCurrency * 100; + LegacyDelegator2.Delegate(LegacyDelegatee, delegatingFAV1, 15L); + + foreach (var reward in rewards) + { + repo.MintAsset(LegacyDelegatee.RewardPoolAddress, reward); + } + + LegacyDelegatee.CollectRewards(17L); + var delegatingFAV2 = LegacyDelegatee.DelegationCurrency * 200; + LegacyDelegator2.Delegate(LegacyDelegatee, delegatingFAV2, 20L); + + foreach (var reward in rewards) + { + repo.MintAsset(LegacyDelegatee.RewardPoolAddress, reward); + } + + LegacyDelegatee.CollectRewards(23L); + var delegatingFAV3 = LegacyDelegatee.DelegationCurrency * 300; + LegacyDelegator2.Delegate(LegacyDelegatee, delegatingFAV3, 25L); + + foreach (var reward in rewards) + { + repo.MintAsset(LegacyDelegatee.RewardPoolAddress, reward); + } + + LegacyDelegatee.CollectRewards(27L); + var delegatingFAV4 = LegacyDelegatee.DelegationCurrency * 400; + LegacyDelegator2.Delegate(LegacyDelegatee, delegatingFAV4, 30L); + + foreach (var reward in rewards) + { + repo.MintAsset(LegacyDelegatee.RewardPoolAddress, reward); + } + + LegacyDelegatee.CollectRewards(23L); + + _fixture.TestRepository.UpdateWorld(_fixture.TestRepository.World.MutateAccount( + _fixture.TestRepository.DelegateeMetadataAccountAddress, + a => a.SetState(LegacyDelegatee.MetadataAddress, LegacyDelegatee.MetadataBencoded))); + _fixture.TestRepository.UpdateWorld(_fixture.TestRepository.World.MutateAccount( + _fixture.TestRepository.DelegatorMetadataAccountAddress, + a => a.SetState(LegacyDelegatee.Metadata.Address, LegacyDelegatee.Metadata.Bencoded))); + _fixture.TestRepository.UpdateWorld(_fixture.TestRepository.World.MutateAccount( + _fixture.TestRepository.DelegatorMetadataAccountAddress, + a => a.SetState(LegacyDelegatee.Metadata.Address, LegacyDelegatee.Metadata.Bencoded))); + + var delegator1 = _fixture.TestRepository.GetDelegator(_fixture.TestDelegator1.Address); + var delegator2 = _fixture.TestRepository.GetDelegator(_fixture.TestDelegator2.Address); + var delegatee = _fixture.TestRepository.GetDelegatee(_fixture.TestDelegatee1.Address); + + var delegatingFAV5 = delegatee.DelegationCurrency * 500; + delegator2.Delegate(delegatee, delegatingFAV5, 35L); + + foreach (var reward in rewards) + { + repo.MintAsset(delegatee.RewardPoolAddress, reward); + } + + delegatee.CollectRewards(37); + + var delegator1RewardBeforeDelegate = repo.GetBalance(delegator1.RewardAddress, DelegationFixture.TestRewardCurrency); + Assert.Equal(DelegationFixture.TestRewardCurrency * 0, delegator1RewardBeforeDelegate); + + delegator1.Delegate(delegatee, delegatingFAV5, 40L); + + var delegator1Reward = repo.GetBalance(delegator1.RewardAddress, DelegationFixture.TestRewardCurrency); + + var expectedReward = DelegationFixture.TestRewardCurrency * 100 + + (DelegationFixture.TestRewardCurrency * 100 * 100).DivRem(200).Quotient + + (DelegationFixture.TestRewardCurrency * 100 * 100).DivRem(400).Quotient + + (DelegationFixture.TestRewardCurrency * 100 * 100).DivRem(700).Quotient + + (DelegationFixture.TestRewardCurrency * 100 * 100).DivRem(1100).Quotient + + (DelegationFixture.TestRewardCurrency * 100 * 100).DivRem(1600).Quotient; + + Assert.Equal(expectedReward.MajorUnit, delegator1Reward.MajorUnit); + } + } +} From 211421ce27223a4adfe3b8d894c177baae0c6d3a Mon Sep 17 00:00:00 2001 From: ilgyu Date: Wed, 11 Dec 2024 02:59:00 +0900 Subject: [PATCH 10/20] chore: Redefine RewardBase.Margin as a constant --- Lib9c/Delegation/RewardBase.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib9c/Delegation/RewardBase.cs b/Lib9c/Delegation/RewardBase.cs index 0eb7bf54c4..515524f31b 100644 --- a/Lib9c/Delegation/RewardBase.cs +++ b/Lib9c/Delegation/RewardBase.cs @@ -21,6 +21,7 @@ public class RewardBase : IBencodable, IEquatable { private const string StateTypeName = "reward_base"; private const long StateVersion = 1; + public const int Margin = 2; private readonly IComparer _currencyComparer = new CurrencyComparer(); public RewardBase( @@ -140,8 +141,6 @@ private RewardBase( public int SigFig { get; private set; } - public static int Margin => 2; - public ImmutableDictionary RewardPortion { get; } public List Bencoded From b40b9a0af1984796cf89ccdf52aa178e54656a3d Mon Sep 17 00:00:00 2001 From: ilgyu Date: Wed, 11 Dec 2024 03:00:29 +0900 Subject: [PATCH 11/20] chore: Fix Equals for RewardBase --- Lib9c/Delegation/RewardBase.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Lib9c/Delegation/RewardBase.cs b/Lib9c/Delegation/RewardBase.cs index 515524f31b..3577ce79a3 100644 --- a/Lib9c/Delegation/RewardBase.cs +++ b/Lib9c/Delegation/RewardBase.cs @@ -22,7 +22,7 @@ public class RewardBase : IBencodable, IEquatable private const string StateTypeName = "reward_base"; private const long StateVersion = 1; public const int Margin = 2; - private readonly IComparer _currencyComparer = new CurrencyComparer(); + private static readonly IComparer _currencyComparer = new CurrencyComparer(); public RewardBase( Address address, @@ -79,7 +79,7 @@ public RewardBase( throw new ArgumentException("Duplicated currency in reward base."); } - RewardPortion = rewardPortion.ToImmutableDictionary(f => f.Currency, f => f); + RewardPortion = rewardPortion.ToImmutableSortedDictionary(f => f.Currency, f => f, _currencyComparer); SigFig = sigfig; StartHeight = startHeight; } @@ -106,7 +106,7 @@ public RewardBase(Address address, List bencoded) throw new ArgumentException("Duplicated currency in reward base."); } - RewardPortion = rewardPortion.ToImmutableDictionary(f => f.Currency, f => f); + RewardPortion = rewardPortion.ToImmutableSortedDictionary(f => f.Currency, f => f, _currencyComparer); SigFig = (Integer)bencoded[4]; try @@ -122,7 +122,7 @@ public RewardBase(Address address, List bencoded) private RewardBase( Address address, BigInteger totalShares, - ImmutableDictionary rewardPortion, + ImmutableSortedDictionary rewardPortion, int sigfig, long? startHeight = null) { @@ -141,7 +141,7 @@ private RewardBase( public int SigFig { get; private set; } - public ImmutableDictionary RewardPortion { get; } + public ImmutableSortedDictionary RewardPortion { get; } public List Bencoded { @@ -199,9 +199,10 @@ private static RewardBase UpdateTotalShares(RewardBase rewardBase, BigInteger to { var newSigFig = Math.Max(rewardBase.SigFig, RecommendedSigFig(totalShares)); var multiplier = Multiplier(newSigFig - rewardBase.SigFig); - var newPortion = rewardBase.RewardPortion.ToImmutableDictionary( + var newPortion = rewardBase.RewardPortion.ToImmutableSortedDictionary( kvp => kvp.Key, - kvp => kvp.Value * multiplier); + kvp => kvp.Value * multiplier, + _currencyComparer); return new RewardBase( rewardBase.Address, @@ -233,7 +234,7 @@ public bool Equals(RewardBase? other) || (other is RewardBase rewardBase && Address == rewardBase.Address && TotalShares == rewardBase.TotalShares - && RewardPortion.Equals(rewardBase.RewardPortion) + && RewardPortion.SequenceEqual(rewardBase.RewardPortion) && SigFig == rewardBase.SigFig); public override int GetHashCode() From 22e55f69410ba3ca29c64e7e1b806c1cb954aabb Mon Sep 17 00:00:00 2001 From: ilgyu Date: Wed, 11 Dec 2024 14:01:19 +0900 Subject: [PATCH 12/20] fix: Fix Migration to skip set RewardBase when not needed --- Lib9c/Delegation/Delegatee.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib9c/Delegation/Delegatee.cs b/Lib9c/Delegation/Delegatee.cs index c5d194e178..1105887610 100644 --- a/Lib9c/Delegation/Delegatee.cs +++ b/Lib9c/Delegation/Delegatee.cs @@ -571,7 +571,10 @@ record = Repository.GetLumpSumRewardsRecord(this, lastStartHeight) Repository.RemoveLumpSumRewardsRecord(recordEach); } - Repository.SetRewardBase(rewardBase!); + if (rewardBase is RewardBase rewardBaseToSet) + { + Repository.SetRewardBase(rewardBaseToSet); + } } } } From 25b17cb1657e316c8e7c8592f868bc00117b1f4b Mon Sep 17 00:00:00 2001 From: ilgyu Date: Wed, 11 Dec 2024 23:32:12 +0900 Subject: [PATCH 13/20] doc: Add docstrings for RewardBase --- Lib9c/Delegation/Delegatee.cs | 4 +- Lib9c/Delegation/RewardBase.cs | 262 ++++++++++++++++++++++++++------- 2 files changed, 209 insertions(+), 57 deletions(-) diff --git a/Lib9c/Delegation/Delegatee.cs b/Lib9c/Delegation/Delegatee.cs index 1105887610..29c589c722 100644 --- a/Lib9c/Delegation/Delegatee.cs +++ b/Lib9c/Delegation/Delegatee.cs @@ -285,7 +285,7 @@ public void CollectRewards(long height) var rewards = RewardCurrencies.Select(c => Repository.GetBalance(RewardPoolAddress, c)); if (Repository.GetCurrentRewardBase(this) is RewardBase rewardBase) { - rewardBase = rewards.Aggregate(rewardBase, (accum, next) => accum.AddReward(next)); + rewardBase = rewardBase.AddRewards(rewards); foreach (var rewardsEach in rewards) { @@ -562,9 +562,9 @@ record = Repository.GetLumpSumRewardsRecord(this, lastStartHeight) rewardBase = newRewardBase; } + rewardBase = rewardBase.AddRewards(recordEach.LumpSumRewards.Values); foreach (var r in recordEach.LumpSumRewards) { - rewardBase = rewardBase.AddReward(r.Value); Repository.TransferAsset(recordEach.Address, DistributionPoolAddress(), Repository.GetBalance(recordEach.Address, r.Key)); } diff --git a/Lib9c/Delegation/RewardBase.cs b/Lib9c/Delegation/RewardBase.cs index 3577ce79a3..f6b19d08a2 100644 --- a/Lib9c/Delegation/RewardBase.cs +++ b/Lib9c/Delegation/RewardBase.cs @@ -21,70 +21,71 @@ public class RewardBase : IBencodable, IEquatable { private const string StateTypeName = "reward_base"; private const long StateVersion = 1; + + /// + /// Margin for significant figure. It's used to calculate the significant figure of the reward base. + /// public const int Margin = 2; private static readonly IComparer _currencyComparer = new CurrencyComparer(); + /// + /// Constructor for new . + /// This constructor is used only for the initial reward base creation. + /// + /// + /// of . + /// + /// + /// of 's creation height. + /// + /// + /// of 's creation height. + /// It initializes the reward portion with 0. + /// public RewardBase( Address address, BigInteger totalShares, - IEnumerable currencies, - long? startHeight = null) + IEnumerable currencies) : this( address, totalShares, currencies.Select(c => c * 0), RecommendedSigFig(totalShares), - startHeight) - { - } - - public RewardBase( - Address address, - BigInteger totalShares, - IEnumerable currencies, - int sigFig, - long? startHeight = null) - : this( - address, - totalShares, - currencies.Select(c => c * 0), - sigFig, - startHeight) + null) { } + /// + /// Constructor for new from bencoded data. + /// + /// + /// of . + /// + /// + /// Bencoded data of . + /// public RewardBase(Address address, IValue bencoded) : this(address, (List)bencoded) { } - public RewardBase( - Address address, - BigInteger totalShares, - IEnumerable rewardPortion, - int sigfig, - long? startHeight = null) - { - Address = address; - - if (totalShares.Sign <= 0) - { - throw new ArgumentOutOfRangeException(nameof(totalShares)); - } - - TotalShares = totalShares; - - if (!rewardPortion.Select(f => f.Currency).All(new HashSet().Add)) - { - throw new ArgumentException("Duplicated currency in reward base."); - } - - RewardPortion = rewardPortion.ToImmutableSortedDictionary(f => f.Currency, f => f, _currencyComparer); - SigFig = sigfig; - StartHeight = startHeight; - } - - + /// + /// Constructor for new from bencoded data. + /// + /// + /// of . + /// + /// + /// Bencoded data of . + /// + /// Thrown when the bencoded data is not valid format for . + /// + /// + /// Thrown when the of the bencoded data is higher than the current version. + /// + /// + /// Thrown when the bencoded data has duplicated currency. + /// public RewardBase(Address address, List bencoded) { if (bencoded[0] is not Text text || text != StateTypeName || bencoded[1] is not Integer integer) @@ -119,6 +120,74 @@ public RewardBase(Address address, List bencoded) } } + /// + /// Constructor for new . + /// + /// + /// of . + /// + /// + /// of 's creation height. + /// + /// + /// Cumulative reward portion of 's creation height. + /// + /// + /// Significant figure of . + /// + /// + /// Start height of that attached when archived. + /// + /// + /// Thrown when the is less than or equal to 0. + /// + /// + /// Thrown when the has duplicated currency. + /// + private RewardBase( + Address address, + BigInteger totalShares, + IEnumerable rewardPortion, + int sigfig, + long? startHeight = null) + { + Address = address; + + if (totalShares.Sign <= 0) + { + throw new ArgumentOutOfRangeException(nameof(totalShares)); + } + + TotalShares = totalShares; + + if (!rewardPortion.Select(f => f.Currency).All(new HashSet().Add)) + { + throw new ArgumentException("Duplicated currency in reward base."); + } + + RewardPortion = rewardPortion.ToImmutableSortedDictionary(f => f.Currency, f => f, _currencyComparer); + SigFig = sigfig; + StartHeight = startHeight; + } + + /// + /// Constructor for new . + /// + /// + /// of . + /// + /// + /// of 's creation height. + /// + /// + /// Cumulative reward portion of 's creation height. + /// + /// + /// Significant figure of . + /// + /// + /// Start height of that attached when archived. + /// private RewardBase( Address address, BigInteger totalShares, @@ -133,14 +202,30 @@ private RewardBase( StartHeight = startHeight; } + /// + /// of . + /// public Address Address { get; } + /// + /// Start height of that attached when archived. + /// public long? StartHeight { get; } + /// + /// of 's creation height. + /// public BigInteger TotalShares { get; } + /// + /// Significant figure of . + /// public int SigFig { get; private set; } + /// + /// Cumulative reward portion of . + /// When it's multiplied by the number of shares, it will be the reward for the period. + /// public ImmutableSortedDictionary RewardPortion { get; } public List Bencoded @@ -164,15 +249,57 @@ public List Bencoded IValue IBencodable.Bencoded => Bencoded; + /// + /// Add rewards to the . + /// + /// + /// Rewards to add. + /// + /// + /// New with added rewards. + /// public RewardBase AddRewards(IEnumerable rewards) => rewards.Aggregate(this, (accum, next) => AddReward(accum, next)); + /// + /// Add reward to the . + /// + /// + /// Reward to add. + /// + /// + /// New with added reward. + /// public RewardBase AddReward(FungibleAssetValue reward) => AddReward(this, reward); + /// + /// Update the total shares of the . + /// + /// + /// New of the height that created. + /// + /// + /// New with updated total shares. + /// public RewardBase UpdateTotalShares(BigInteger totalShares) => UpdateTotalShares(this, totalShares); + /// + /// Attach the start height to the to be archived. + /// + /// + /// of . + /// + /// + /// Start height of that attached when archived. + /// + /// + /// New with attached start height. + /// + /// + /// Thrown when the start height is already attached. + /// public RewardBase AttachHeight(Address address, long startHeight) => StartHeight is null ? new RewardBase( @@ -183,6 +310,39 @@ public RewardBase AttachHeight(Address address, long startHeight) startHeight) : throw new InvalidOperationException("StartHeight is already attached."); + /// + /// Calculate the cumulative reward during the period. + /// + /// + /// The number of shares to calculate the reward. + /// + /// + /// Cumulative reward during the period. + /// + public ImmutableSortedDictionary CumulativeRewardDuringPeriod(BigInteger share) + => RewardPortion.Keys.Select(k => CumulativeRewardDuringPeriod(share, k)) + .ToImmutableSortedDictionary(f => f.Currency, f => f, _currencyComparer); + + /// + /// Calculate the cumulative reward during the period, for the specific currency. + /// + /// + /// The number of shares to calculate the reward. + /// + /// + /// The currency to calculate the reward. + /// + /// + /// Cumulative reward during the period, for the specific currency. + /// + /// + /// Thrown when the is not in the . + /// + public FungibleAssetValue CumulativeRewardDuringPeriod(BigInteger share, Currency currency) + => RewardPortion.TryGetValue(currency, out var portion) + ? (portion * share).DivRem(Multiplier(SigFig)).Quotient + : throw new ArgumentException($"Invalid reward currency: {currency}"); + private static RewardBase AddReward(RewardBase rewardBase, FungibleAssetValue reward) => new RewardBase( rewardBase.Address, @@ -211,20 +371,12 @@ private static RewardBase UpdateTotalShares(RewardBase rewardBase, BigInteger to newSigFig); } - public static int RecommendedSigFig(BigInteger totalShares) + private static int RecommendedSigFig(BigInteger totalShares) => (int)Math.Floor(BigInteger.Log10(totalShares)) + Margin; - public static BigInteger Multiplier(int sigFig) + private static BigInteger Multiplier(int sigFig) => BigInteger.Pow(10, sigFig); - public ImmutableSortedDictionary CumulativeRewardDuringPeriod(BigInteger share) - => RewardPortion.Keys.Select(k => CumulativeRewardDuringPeriod(share, k)) - .ToImmutableSortedDictionary(f => f.Currency, f => f, _currencyComparer); - - public FungibleAssetValue CumulativeRewardDuringPeriod(BigInteger share, Currency currency) - => RewardPortion.TryGetValue(currency, out var portion) - ? (portion * share).DivRem(Multiplier(SigFig)).Quotient - : throw new ArgumentException($"Invalid reward currency: {currency}"); public override bool Equals(object? obj) => obj is RewardBase other && Equals(other); From 4cdcc5b71e05e950228598c82b3e993920555ed2 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 12 Dec 2024 15:56:31 +0900 Subject: [PATCH 14/20] fix: Fix bug for zero-transfer --- Lib9c/Delegation/Delegatee.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib9c/Delegation/Delegatee.cs b/Lib9c/Delegation/Delegatee.cs index 29c589c722..124f951268 100644 --- a/Lib9c/Delegation/Delegatee.cs +++ b/Lib9c/Delegation/Delegatee.cs @@ -565,7 +565,11 @@ record = Repository.GetLumpSumRewardsRecord(this, lastStartHeight) rewardBase = rewardBase.AddRewards(recordEach.LumpSumRewards.Values); foreach (var r in recordEach.LumpSumRewards) { - Repository.TransferAsset(recordEach.Address, DistributionPoolAddress(), Repository.GetBalance(recordEach.Address, r.Key)); + var toTransfer = Repository.GetBalance(recordEach.Address, r.Key); + if (toTransfer.Sign > 0) + { + Repository.TransferAsset(recordEach.Address, DistributionPoolAddress(), toTransfer); + } } Repository.RemoveLumpSumRewardsRecord(recordEach); From 519eb30afdfcb3c7d6f0c36aa3827b966fa22383 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 12 Dec 2024 18:01:22 +0900 Subject: [PATCH 15/20] chore: Apply reviews for better structure. Co-authored-by: Jeesu Choi --- Lib9c/Delegation/Delegatee.cs | 21 +++++++++++++------ Lib9c/Delegation/RewardBase.cs | 37 ++++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/Lib9c/Delegation/Delegatee.cs b/Lib9c/Delegation/Delegatee.cs index 124f951268..1aa75e5211 100644 --- a/Lib9c/Delegation/Delegatee.cs +++ b/Lib9c/Delegation/Delegatee.cs @@ -483,11 +483,12 @@ private void TransferReward( RewardBase? lastRewardBase) { var currentCumulative = currentRewardBase.CumulativeRewardDuringPeriod(share); - var lastCumulative = lastRewardBase?.CumulativeRewardDuringPeriod(share); + var lastCumulative = lastRewardBase?.CumulativeRewardDuringPeriod(share) + ?? ImmutableSortedDictionary.Empty; foreach (var c in currentCumulative) { - var lastCumulativeEach = lastCumulative?[c.Key] ?? c.Key * 0; + var lastCumulativeEach = lastCumulative.GetValueOrDefault(c.Key, defaultValue: c.Key * 0); if (c.Value < lastCumulativeEach) { @@ -518,7 +519,9 @@ private void TransferRemainders(LumpSumRewardsRecord record) private void MigrateLumpSumRewardsRecords() { - List records = new(); + var growSize = 100; + var capacity = 5000; + List records = new(capacity); if (!(Repository.GetCurrentLumpSumRewardsRecord(this) is LumpSumRewardsRecord record)) { return; @@ -526,18 +529,24 @@ private void MigrateLumpSumRewardsRecords() while (record.LastStartHeight is long lastStartHeight) { + if (records.Count == capacity) + { + capacity += growSize; + records.Capacity = capacity; + } + records.Add(record); record = Repository.GetLumpSumRewardsRecord(this, lastStartHeight) ?? throw new InvalidOperationException( $"Lump sum rewards record for #{lastStartHeight} is missing"); } - records.Reverse(); - RewardBase? rewardBase = null; RewardBase? newRewardBase = null; - foreach (var recordEach in records) + for (var i = records.Count - 1; i >= 0; i--) { + var recordEach = records[i]; + if (rewardBase is null) { rewardBase = new RewardBase( diff --git a/Lib9c/Delegation/RewardBase.cs b/Lib9c/Delegation/RewardBase.cs index f6b19d08a2..36bad23bd2 100644 --- a/Lib9c/Delegation/RewardBase.cs +++ b/Lib9c/Delegation/RewardBase.cs @@ -208,14 +208,15 @@ private RewardBase( public Address Address { get; } /// - /// Start height of that attached when archived. + /// of 's creation height. /// - public long? StartHeight { get; } + public BigInteger TotalShares { get; } /// - /// of 's creation height. + /// Cumulative reward portion of . + /// When it's multiplied by the number of shares, it will be the reward for the period. /// - public BigInteger TotalShares { get; } + public ImmutableSortedDictionary RewardPortion { get; } /// /// Significant figure of . @@ -223,10 +224,9 @@ private RewardBase( public int SigFig { get; private set; } /// - /// Cumulative reward portion of . - /// When it's multiplied by the number of shares, it will be the reward for the period. + /// Start height of that attached when archived. /// - public ImmutableSortedDictionary RewardPortion { get; } + public long? StartHeight { get; } public List Bencoded { @@ -344,16 +344,23 @@ public FungibleAssetValue CumulativeRewardDuringPeriod(BigInteger share, Currenc : throw new ArgumentException($"Invalid reward currency: {currency}"); private static RewardBase AddReward(RewardBase rewardBase, FungibleAssetValue reward) - => new RewardBase( + { + if (!rewardBase.RewardPortion.TryGetValue(reward.Currency, out var portion)) + { + throw new ArgumentException( + $"Invalid reward currency: {reward.Currency}", nameof(reward)); + } + + var portionNumerator = reward * Multiplier(rewardBase.SigFig); + var updatedPortion = portion + portionNumerator.DivRem(rewardBase.TotalShares).Quotient; + + return new RewardBase( rewardBase.Address, rewardBase.TotalShares, - rewardBase.RewardPortion.TryGetValue(reward.Currency, out var portion) - ? rewardBase.RewardPortion.SetItem( - reward.Currency, - portion + (reward * Multiplier(rewardBase.SigFig)).DivRem(rewardBase.TotalShares).Quotient) - : throw new ArgumentException($"Invalid reward currency: {reward.Currency}"), + rewardBase.RewardPortion.SetItem(reward.Currency, updatedPortion), rewardBase.SigFig, rewardBase.StartHeight); + } private static RewardBase UpdateTotalShares(RewardBase rewardBase, BigInteger totalShares) { @@ -377,7 +384,6 @@ private static int RecommendedSigFig(BigInteger totalShares) private static BigInteger Multiplier(int sigFig) => BigInteger.Pow(10, sigFig); - public override bool Equals(object? obj) => obj is RewardBase other && Equals(other); @@ -387,7 +393,8 @@ public bool Equals(RewardBase? other) && Address == rewardBase.Address && TotalShares == rewardBase.TotalShares && RewardPortion.SequenceEqual(rewardBase.RewardPortion) - && SigFig == rewardBase.SigFig); + && SigFig == rewardBase.SigFig + && StartHeight == rewardBase.StartHeight); public override int GetHashCode() => Address.GetHashCode(); From 01791cda55a16cc79004598b0662683843807be0 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 12 Dec 2024 18:26:57 +0900 Subject: [PATCH 16/20] chore: Fix RewardBase to use BigInteger instead of FAV --- Lib9c/Delegation/RewardBase.cs | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Lib9c/Delegation/RewardBase.cs b/Lib9c/Delegation/RewardBase.cs index 36bad23bd2..f219168f38 100644 --- a/Lib9c/Delegation/RewardBase.cs +++ b/Lib9c/Delegation/RewardBase.cs @@ -49,7 +49,7 @@ public RewardBase( : this( address, totalShares, - currencies.Select(c => c * 0), + currencies.Select(c => (c, BigInteger.Zero)), RecommendedSigFig(totalShares), null) { @@ -100,14 +100,16 @@ public RewardBase(Address address, List bencoded) Address = address; TotalShares = (Integer)bencoded[2]; - var rewardPortion = ((List)bencoded[3]).Select(v => new FungibleAssetValue(v)); + var bencodedRewardPortion = ((List)bencoded[3]).Select(v => (List)v); + var rewardPortion = bencodedRewardPortion.Select( + p => (Currency: new Currency(p[0]), Portion: (BigInteger)(Integer)p[1])); if (!rewardPortion.Select(f => f.Currency).All(new HashSet().Add)) { throw new ArgumentException("Duplicated currency in reward base."); } - RewardPortion = rewardPortion.ToImmutableSortedDictionary(f => f.Currency, f => f, _currencyComparer); + RewardPortion = rewardPortion.ToImmutableSortedDictionary(f => f.Currency, f => f.Portion, _currencyComparer); SigFig = (Integer)bencoded[4]; try @@ -147,7 +149,7 @@ public RewardBase(Address address, List bencoded) private RewardBase( Address address, BigInteger totalShares, - IEnumerable rewardPortion, + IEnumerable<(Currency, BigInteger)> rewardPortion, int sigfig, long? startHeight = null) { @@ -160,12 +162,12 @@ private RewardBase( TotalShares = totalShares; - if (!rewardPortion.Select(f => f.Currency).All(new HashSet().Add)) + if (!rewardPortion.Select(f => f.Item1).All(new HashSet().Add)) { throw new ArgumentException("Duplicated currency in reward base."); } - RewardPortion = rewardPortion.ToImmutableSortedDictionary(f => f.Currency, f => f, _currencyComparer); + RewardPortion = rewardPortion.ToImmutableSortedDictionary(f => f.Item1, f => f.Item2, _currencyComparer); SigFig = sigfig; StartHeight = startHeight; } @@ -191,7 +193,7 @@ private RewardBase( private RewardBase( Address address, BigInteger totalShares, - ImmutableSortedDictionary rewardPortion, + ImmutableSortedDictionary rewardPortion, int sigfig, long? startHeight = null) { @@ -216,7 +218,7 @@ private RewardBase( /// Cumulative reward portion of . /// When it's multiplied by the number of shares, it will be the reward for the period. /// - public ImmutableSortedDictionary RewardPortion { get; } + public ImmutableSortedDictionary RewardPortion { get; } /// /// Significant figure of . @@ -238,7 +240,7 @@ public List Bencoded .Add(TotalShares) .Add(new List(RewardPortion .OrderBy(r => r.Key, _currencyComparer) - .Select(r => r.Value.Serialize()))) + .Select(r => new List(r.Key.Serialize(), new Integer(r.Value))))) .Add(SigFig); return StartHeight is long height @@ -340,7 +342,7 @@ public ImmutableSortedDictionary CumulativeRewardD /// public FungibleAssetValue CumulativeRewardDuringPeriod(BigInteger share, Currency currency) => RewardPortion.TryGetValue(currency, out var portion) - ? (portion * share).DivRem(Multiplier(SigFig)).Quotient + ? FungibleAssetValue.FromRawValue(currency, (portion * share) / (Multiplier(SigFig))) : throw new ArgumentException($"Invalid reward currency: {currency}"); private static RewardBase AddReward(RewardBase rewardBase, FungibleAssetValue reward) @@ -351,8 +353,8 @@ private static RewardBase AddReward(RewardBase rewardBase, FungibleAssetValue re $"Invalid reward currency: {reward.Currency}", nameof(reward)); } - var portionNumerator = reward * Multiplier(rewardBase.SigFig); - var updatedPortion = portion + portionNumerator.DivRem(rewardBase.TotalShares).Quotient; + var portionNumerator = reward.RawValue * Multiplier(rewardBase.SigFig); + var updatedPortion = portion + (portionNumerator / rewardBase.TotalShares); return new RewardBase( rewardBase.Address, From 023c2f53e60dea13cab043a2fc114b298dbe8b1d Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 12 Dec 2024 18:46:48 +0900 Subject: [PATCH 17/20] chore: Change MaxAbstainAllowance to 9 --- Lib9c/ValidatorDelegation/AbstainHistory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib9c/ValidatorDelegation/AbstainHistory.cs b/Lib9c/ValidatorDelegation/AbstainHistory.cs index 430d2c78cd..deaedb1f12 100644 --- a/Lib9c/ValidatorDelegation/AbstainHistory.cs +++ b/Lib9c/ValidatorDelegation/AbstainHistory.cs @@ -42,7 +42,7 @@ public AbstainHistory(List bencoded) public static int WindowSize => 10; - public static int MaxAbstainAllowance => 3; + public static int MaxAbstainAllowance => 9; public static Address Address => new Address( ImmutableArray.Create( From e6e35c5c5e6123773c7e168da3846ae17a239b11 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 12 Dec 2024 19:14:55 +0900 Subject: [PATCH 18/20] fix: ReleaseUnbondings to set unbondings properly --- .../Action/ValidatorDelegation/ReleaseValidatorUnbondings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib9c/Action/ValidatorDelegation/ReleaseValidatorUnbondings.cs b/Lib9c/Action/ValidatorDelegation/ReleaseValidatorUnbondings.cs index c68def6be6..b8863034bd 100644 --- a/Lib9c/Action/ValidatorDelegation/ReleaseValidatorUnbondings.cs +++ b/Lib9c/Action/ValidatorDelegation/ReleaseValidatorUnbondings.cs @@ -51,13 +51,13 @@ public override IWorld Execute(IActionContext context) switch (unbonding) { case UnbondLockIn unbondLockIn: - unbondLockIn.Release(context.BlockIndex, out var releasedFAV); + unbondLockIn = unbondLockIn.Release(context.BlockIndex, out var releasedFAV); repository.SetUnbondLockIn(unbondLockIn); repository.UpdateWorld( Unstake(repository.World, context, unbondLockIn, releasedFAV)); break; case RebondGrace rebondGrace: - rebondGrace.Release(context.BlockIndex, out _); + rebondGrace = rebondGrace.Release(context.BlockIndex, out _); repository.SetRebondGrace(rebondGrace); break; default: From a683969be793f7ae3c846b88405839b1d5733321 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 12 Dec 2024 21:39:33 +0900 Subject: [PATCH 19/20] chore: Rename parameter name as camelCase --- Lib9c/Delegation/RewardBase.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib9c/Delegation/RewardBase.cs b/Lib9c/Delegation/RewardBase.cs index f219168f38..18102d0c26 100644 --- a/Lib9c/Delegation/RewardBase.cs +++ b/Lib9c/Delegation/RewardBase.cs @@ -134,7 +134,7 @@ public RewardBase(Address address, List bencoded) /// /// Cumulative reward portion of 's creation height. /// - /// + /// /// Significant figure of . /// /// @@ -150,7 +150,7 @@ private RewardBase( Address address, BigInteger totalShares, IEnumerable<(Currency, BigInteger)> rewardPortion, - int sigfig, + int sigFig, long? startHeight = null) { Address = address; @@ -168,7 +168,7 @@ private RewardBase( } RewardPortion = rewardPortion.ToImmutableSortedDictionary(f => f.Item1, f => f.Item2, _currencyComparer); - SigFig = sigfig; + SigFig = sigFig; StartHeight = startHeight; } @@ -184,7 +184,7 @@ private RewardBase( /// /// Cumulative reward portion of 's creation height. /// - /// + /// /// Significant figure of . /// /// @@ -194,13 +194,13 @@ private RewardBase( Address address, BigInteger totalShares, ImmutableSortedDictionary rewardPortion, - int sigfig, + int sigFig, long? startHeight = null) { Address = address; TotalShares = totalShares; RewardPortion = rewardPortion; - SigFig = sigfig; + SigFig = sigFig; StartHeight = startHeight; } From 9e8d87c0c8aa5f7175a2fb782c1318c19aba8342 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Thu, 12 Dec 2024 21:40:14 +0900 Subject: [PATCH 20/20] test: Fix reward calculation test method --- .../ValidatorDelegation/ValidatorDelegationTestBase.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.Lib9c.Tests/Action/ValidatorDelegation/ValidatorDelegationTestBase.cs b/.Lib9c.Tests/Action/ValidatorDelegation/ValidatorDelegationTestBase.cs index 8105c0839f..e1b05ca90c 100644 --- a/.Lib9c.Tests/Action/ValidatorDelegation/ValidatorDelegationTestBase.cs +++ b/.Lib9c.Tests/Action/ValidatorDelegation/ValidatorDelegationTestBase.cs @@ -25,6 +25,7 @@ namespace Lib9c.Tests.Action.ValidatorDelegation; using Nekoyume.ValidatorDelegation; using Xunit; using Nekoyume.Action.Guild.Migration.LegacyModels; +using Nekoyume.Delegation; public class ValidatorDelegationTestBase { @@ -592,7 +593,10 @@ protected static FungibleAssetValue CalculateBonusPropserReward( } protected static FungibleAssetValue CalculateClaim(BigInteger share, BigInteger totalShare, FungibleAssetValue totalClaim) - => (totalClaim * share).DivRem(totalShare).Quotient; + { + var multiplier = BigInteger.Pow(10, (int)Math.Floor(BigInteger.Log10(totalShare)) + RewardBase.Margin); + return ((totalClaim * multiplier).DivRem(totalShare).Quotient * share).DivRem(multiplier).Quotient; + } protected static FungibleAssetValue CalculateCommunityFund(ImmutableArray votes, FungibleAssetValue reward) {