From 93e195db85d5242f92d7da5546764410be7a3efc Mon Sep 17 00:00:00 2001 From: ilgyu Date: Mon, 18 Nov 2024 15:07:02 +0900 Subject: [PATCH 1/3] feat: Add admin fix action --- .../FixToRefundFromNonValidatorTest.cs | 135 ++++++++++++++++++ .../Migration/FixToRefundFromNonValidator.cs | 101 +++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 .Lib9c.Tests/Action/Guild/Migration/FixToRefundFromNonValidatorTest.cs create mode 100644 Lib9c/Action/Guild/Migration/FixToRefundFromNonValidator.cs diff --git a/.Lib9c.Tests/Action/Guild/Migration/FixToRefundFromNonValidatorTest.cs b/.Lib9c.Tests/Action/Guild/Migration/FixToRefundFromNonValidatorTest.cs new file mode 100644 index 0000000000..66491c9c30 --- /dev/null +++ b/.Lib9c.Tests/Action/Guild/Migration/FixToRefundFromNonValidatorTest.cs @@ -0,0 +1,135 @@ +namespace Lib9c.Tests.Action.Guild.Migration +{ + using System; + using System.Collections.Generic; + using Lib9c.Tests.Fixtures.TableCSV.Stake; + using Lib9c.Tests.Util; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume; + using Nekoyume.Action; + using Nekoyume.Action.Guild.Migration; + using Nekoyume.Extensions; + using Nekoyume.Model.Stake; + using Nekoyume.Model.State; + using Nekoyume.Module; + using Nekoyume.TableData.Stake; + using Xunit; + + // TODO: Remove this test class after the migration is completed. + public class FixToRefundFromNonValidatorTest : GuildTestBase + { + private IWorld _world; + private Currency _ncg; + private Currency _gg; + private Address _agentAddress; + private Address _stakeAddress; + private Address _adminAddress; + + public FixToRefundFromNonValidatorTest() + { + _agentAddress = new PrivateKey().Address; + _adminAddress = new PrivateKey().Address; + _stakeAddress = StakeState.DeriveAddress(_agentAddress); + _ncg = World.GetGoldCurrency(); + _gg = Currencies.GuildGold; + var sheetsOverride = new Dictionary + { + { + "StakeRegularFixedRewardSheet_V1", + StakeRegularFixedRewardSheetFixtures.V1 + }, + { + "StakeRegularFixedRewardSheet_V2", + StakeRegularFixedRewardSheetFixtures.V2 + }, + { + "StakeRegularRewardSheet_V1", + StakeRegularRewardSheetFixtures.V1 + }, + { + "StakeRegularRewardSheet_V2", + StakeRegularRewardSheetFixtures.V2 + }, + { + nameof(StakePolicySheet), + StakePolicySheetFixtures.V2 + }, + }; + (_, _, _, _world) = InitializeUtil.InitializeStates( + agentAddr: _agentAddress, + sheetsOverride: sheetsOverride); + var stakePolicySheet = _world.GetSheet(); + var contract = new Contract(stakePolicySheet); + var adminState = new AdminState(_adminAddress, 100L); + var stakeState = new StakeState(new Contract(stakePolicySheet), 1L); + + _world = World + .SetLegacyState(Addresses.Admin, adminState.Serialize()) + .SetLegacyState(_stakeAddress, stakeState.Serialize()) + .MintAsset(new ActionContext { }, Addresses.NonValidatorDelegatee, _gg * 10000) + .MintAsset(new ActionContext { }, _stakeAddress, _ncg * 10) + .MintAsset(new ActionContext { }, _stakeAddress, _gg * 5); + } + + [Fact] + public void Execute() + { + var world = new FixToRefundFromNonValidator(_agentAddress).Execute(new ActionContext + { + PreviousState = _world, + Signer = _adminAddress, + }); + + Assert.Equal(_gg * 10, world.GetBalance(_stakeAddress, _gg)); + Assert.Equal(_gg * (10000 - 5), world.GetBalance(Addresses.NonValidatorDelegatee, _gg)); + } + + [Fact] + public void AssertWhenExecutedByNonAdmin() + { + Assert.Throws(() => + { + new FixToRefundFromNonValidator(_agentAddress).Execute(new ActionContext + { + PreviousState = _world, + Signer = new PrivateKey().Address, + BlockIndex = 2L, + }); + }); + } + + [Fact] + public void AssertWhenHasSufficientGG() + { + var world = _world + .MintAsset(new ActionContext { }, _stakeAddress, _gg * 5); + Assert.Throws(() => + { + new FixToRefundFromNonValidator(_agentAddress).Execute(new ActionContext + { + PreviousState = world, + Signer = _adminAddress, + BlockIndex = 2L, + }); + }); + } + + [Fact] + public void AssertWhenLegacyStakeState() + { + var stakeState = new LegacyStakeState(_stakeAddress, 0L); + var world = _world.SetLegacyState(_stakeAddress, stakeState.Serialize()); + Assert.Throws(() => + { + new FixToRefundFromNonValidator(_agentAddress).Execute(new ActionContext + { + PreviousState = World, + Signer = _adminAddress, + BlockIndex = 2L, + }); + }); + } + } +} diff --git a/Lib9c/Action/Guild/Migration/FixToRefundFromNonValidator.cs b/Lib9c/Action/Guild/Migration/FixToRefundFromNonValidator.cs new file mode 100644 index 0000000000..a27a63d7de --- /dev/null +++ b/Lib9c/Action/Guild/Migration/FixToRefundFromNonValidator.cs @@ -0,0 +1,101 @@ +using System; +using Bencodex.Types; +using Lib9c; +using Libplanet.Action.State; +using Libplanet.Action; +using Libplanet.Types.Assets; +using Nekoyume.Model.Stake; +using Nekoyume.Model.State; +using Nekoyume.Module; +using Libplanet.Crypto; + +namespace Nekoyume.Action.Guild.Migration +{ + // TODO: [GuildMigration] Remove this class when the migration is done. + /// + /// An action to fix refund from non-validator. + /// + [ActionType(TypeIdentifier)] + public class FixToRefundFromNonValidator : ActionBase + { + public const string TypeIdentifier = "fix_to_refund_from_non_validator"; + + private const string TargetKey = "t"; + + public Address Target { get; private set; } + + [Obsolete("Don't call in code.", error: false)] + public FixToRefundFromNonValidator() + { + } + + public FixToRefundFromNonValidator(Address target) + { + Target = target; + } + + public override IValue PlainValue => Dictionary.Empty + .Add("type_id", TypeIdentifier) + .Add("values", Dictionary.Empty + .Add(TargetKey, Target.Bencoded)); + + public override void LoadPlainValue(IValue plainValue) + { + if (plainValue is not Dictionary root || + !root.TryGetValue((Text)"values", out var rawValues) || + rawValues is not Dictionary values || + !values.TryGetValue((Text)TargetKey, out var rawTarget) || + rawTarget is not Binary target) + { + throw new InvalidCastException(); + } + + Target = new Address(target); + } + + public override IWorld Execute(IActionContext context) + { + GasTracer.UseGas(1); + + var world = context.PreviousState; + + if (!TryGetAdminState(context, out AdminState adminState)) + { + throw new InvalidOperationException("Couldn't find admin state"); + } + + if (context.Signer != adminState.AdminAddress) + { + throw new PermissionDeniedException(adminState, context.Signer); + } + + var stakeStateAddress = StakeState.DeriveAddress(Target); + + if (!world.TryGetStakeState(Target, out var stakeState) + || stakeState.StateVersion != 3) + { + throw new InvalidOperationException( + "Target is not valid for refunding from non-validator."); + } + + var ncgStaked = world.GetBalance(stakeStateAddress, world.GetGoldCurrency()); + var ggStaked = world.GetBalance(stakeStateAddress, Currencies.GuildGold); + + var requiredGG = GetGuildCoinFromNCG(ncgStaked) - ggStaked; + + if (requiredGG.Sign != 1) + { + throw new InvalidOperationException( + "Target has sufficient amount of guild gold."); + } + + return world.TransferAsset(context, Addresses.NonValidatorDelegatee, stakeStateAddress, requiredGG); + } + + private static FungibleAssetValue GetGuildCoinFromNCG(FungibleAssetValue balance) + { + return FungibleAssetValue.Parse(Currencies.GuildGold, + balance.GetQuantityString(true)); + } + } +} From dcfa7a47083768d995f01e7e74efb46744de0587 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Mon, 18 Nov 2024 15:57:30 +0900 Subject: [PATCH 2/3] fix: Fix refund admin action to handle multiple addresses --- .../FixToRefundFromNonValidatorTest.cs | 8 ++--- .../Migration/FixToRefundFromNonValidator.cs | 32 +++++++++++++------ 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/.Lib9c.Tests/Action/Guild/Migration/FixToRefundFromNonValidatorTest.cs b/.Lib9c.Tests/Action/Guild/Migration/FixToRefundFromNonValidatorTest.cs index 66491c9c30..3f2a646d74 100644 --- a/.Lib9c.Tests/Action/Guild/Migration/FixToRefundFromNonValidatorTest.cs +++ b/.Lib9c.Tests/Action/Guild/Migration/FixToRefundFromNonValidatorTest.cs @@ -76,7 +76,7 @@ public FixToRefundFromNonValidatorTest() [Fact] public void Execute() { - var world = new FixToRefundFromNonValidator(_agentAddress).Execute(new ActionContext + var world = new FixToRefundFromNonValidator(new Address[] { _agentAddress }).Execute(new ActionContext { PreviousState = _world, Signer = _adminAddress, @@ -91,7 +91,7 @@ public void AssertWhenExecutedByNonAdmin() { Assert.Throws(() => { - new FixToRefundFromNonValidator(_agentAddress).Execute(new ActionContext + new FixToRefundFromNonValidator(new Address[] { _agentAddress }).Execute(new ActionContext { PreviousState = _world, Signer = new PrivateKey().Address, @@ -107,7 +107,7 @@ public void AssertWhenHasSufficientGG() .MintAsset(new ActionContext { }, _stakeAddress, _gg * 5); Assert.Throws(() => { - new FixToRefundFromNonValidator(_agentAddress).Execute(new ActionContext + new FixToRefundFromNonValidator(new Address[] { _agentAddress }).Execute(new ActionContext { PreviousState = world, Signer = _adminAddress, @@ -123,7 +123,7 @@ public void AssertWhenLegacyStakeState() var world = _world.SetLegacyState(_stakeAddress, stakeState.Serialize()); Assert.Throws(() => { - new FixToRefundFromNonValidator(_agentAddress).Execute(new ActionContext + new FixToRefundFromNonValidator(new Address[] { _agentAddress }).Execute(new ActionContext { PreviousState = World, Signer = _adminAddress, diff --git a/Lib9c/Action/Guild/Migration/FixToRefundFromNonValidator.cs b/Lib9c/Action/Guild/Migration/FixToRefundFromNonValidator.cs index a27a63d7de..1fee77142f 100644 --- a/Lib9c/Action/Guild/Migration/FixToRefundFromNonValidator.cs +++ b/Lib9c/Action/Guild/Migration/FixToRefundFromNonValidator.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Bencodex.Types; using Lib9c; using Libplanet.Action.State; @@ -8,6 +9,7 @@ using Nekoyume.Model.State; using Nekoyume.Module; using Libplanet.Crypto; +using System.Linq; namespace Nekoyume.Action.Guild.Migration { @@ -20,37 +22,37 @@ public class FixToRefundFromNonValidator : ActionBase { public const string TypeIdentifier = "fix_to_refund_from_non_validator"; - private const string TargetKey = "t"; + private const string TargetsKey = "t"; - public Address Target { get; private set; } + public List
Targets { get; private set; } [Obsolete("Don't call in code.", error: false)] public FixToRefundFromNonValidator() { } - public FixToRefundFromNonValidator(Address target) + public FixToRefundFromNonValidator(IEnumerable
targets) { - Target = target; + Targets = targets.ToList(); } public override IValue PlainValue => Dictionary.Empty .Add("type_id", TypeIdentifier) .Add("values", Dictionary.Empty - .Add(TargetKey, Target.Bencoded)); + .Add(TargetsKey, new List(Targets.Select(t => t.Bencoded)))); public override void LoadPlainValue(IValue plainValue) { if (plainValue is not Dictionary root || !root.TryGetValue((Text)"values", out var rawValues) || rawValues is not Dictionary values || - !values.TryGetValue((Text)TargetKey, out var rawTarget) || - rawTarget is not Binary target) + !values.TryGetValue((Text)TargetsKey, out var rawTarget) || + rawTarget is not List targets) { throw new InvalidCastException(); } - Target = new Address(target); + Targets = targets.Select(t => new Address(t)).ToList(); } public override IWorld Execute(IActionContext context) @@ -69,9 +71,19 @@ public override IWorld Execute(IActionContext context) throw new PermissionDeniedException(adminState, context.Signer); } - var stakeStateAddress = StakeState.DeriveAddress(Target); + foreach (var target in Targets) + { + world = RefundFromNonValidator(context, world, target); + } + + return world; + } + + private IWorld RefundFromNonValidator(IActionContext context, IWorld world, Address target) + { + var stakeStateAddress = StakeState.DeriveAddress(target); - if (!world.TryGetStakeState(Target, out var stakeState) + if (!world.TryGetStakeState(target, out var stakeState) || stakeState.StateVersion != 3) { throw new InvalidOperationException( From 61cf9f9700f101c4b7d66207d5691a66619cce15 Mon Sep 17 00:00:00 2001 From: ilgyu Date: Mon, 18 Nov 2024 16:16:53 +0900 Subject: [PATCH 3/3] fix: Remove obsolete from admin action --- Lib9c/Action/Guild/Migration/FixToRefundFromNonValidator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib9c/Action/Guild/Migration/FixToRefundFromNonValidator.cs b/Lib9c/Action/Guild/Migration/FixToRefundFromNonValidator.cs index 1fee77142f..1f807cb93e 100644 --- a/Lib9c/Action/Guild/Migration/FixToRefundFromNonValidator.cs +++ b/Lib9c/Action/Guild/Migration/FixToRefundFromNonValidator.cs @@ -26,7 +26,6 @@ public class FixToRefundFromNonValidator : ActionBase public List
Targets { get; private set; } - [Obsolete("Don't call in code.", error: false)] public FixToRefundFromNonValidator() { }