diff --git a/.Lib9c.Tests/Action/ActionEvaluationTest.cs b/.Lib9c.Tests/Action/ActionEvaluationTest.cs index 3b04609245..540a719a96 100644 --- a/.Lib9c.Tests/Action/ActionEvaluationTest.cs +++ b/.Lib9c.Tests/Action/ActionEvaluationTest.cs @@ -2,6 +2,7 @@ namespace Lib9c.Tests.Action { using System; using System.Collections.Generic; + using System.Numerics; using Bencodex.Types; using Lib9c.Formatters; using Libplanet.Action.State; @@ -93,6 +94,7 @@ public ActionEvaluationTest() [InlineData(typeof(RuneSummon))] [InlineData(typeof(ActivateCollection))] [InlineData(typeof(RetrieveAvatarAssets))] + [InlineData(typeof(MigrateFee))] public void Serialize_With_MessagePack(Type actionType) { var action = GetAction(actionType); @@ -479,6 +481,14 @@ private ActionBase GetAction(Type type) }, }, RetrieveAvatarAssets _ => new RetrieveAvatarAssets(avatarAddress: new PrivateKey().Address), + MigrateFee _ => new MigrateFee + { + TransferData = new List<(Address sender, Address recipient, BigInteger amount)> + { + (new PrivateKey().Address, new PrivateKey().Address, 1), + (new PrivateKey().Address, new PrivateKey().Address, 2), + }, + }, _ => throw new InvalidCastException(), }; } diff --git a/.Lib9c.Tests/Action/MigrateFeeTest.cs b/.Lib9c.Tests/Action/MigrateFeeTest.cs new file mode 100644 index 0000000000..c73bb9dbbe --- /dev/null +++ b/.Lib9c.Tests/Action/MigrateFeeTest.cs @@ -0,0 +1,105 @@ +namespace Lib9c.Tests.Action; + +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Mocks; +using Libplanet.Types.Assets; +using Nekoyume.Action; +using Nekoyume.Model.State; +using Nekoyume.Module; +using Xunit; + +public class MigrateFeeTest +{ + private readonly Currency _ncgCurrency; + + public MigrateFeeTest() + { +#pragma warning disable CS0618 + _ncgCurrency = Currency.Legacy("NCG", 2, null); +#pragma warning restore CS0618 + } + + [Fact] + public void Execute() + { + var admin = new Address("8d9f76aF8Dc5A812aCeA15d8bf56E2F790F47fd7"); + var context = new ActionContext(); + var state = new World(MockUtil.MockModernWorldState) + .SetLegacyState(AdminState.Address, new AdminState(admin, 100).Serialize()) + .SetLegacyState(GoldCurrencyState.Address, new GoldCurrencyState(_ncgCurrency).Serialize()); + var recipient = new PrivateKey().Address; + var transferData = new List<(Address sender, Address recipient, BigInteger amount)>(); + var amount = FungibleAssetValue.Parse(_ncgCurrency, 0.1m.ToString(CultureInfo.InvariantCulture)); + for (int i = 1; i < 10; i++) + { + var address = new PrivateKey().Address; + var balance = 0.1m * i; + var fav = FungibleAssetValue.Parse(_ncgCurrency, balance.ToString(CultureInfo.InvariantCulture)); + state = state.MintAsset(context, address, fav); + transferData.Add((address, recipient, amount.RawValue)); + } + + var action = new MigrateFee + { + TransferData = transferData, + Memo = "memo", + }; + + var nextState = action.Execute(new ActionContext + { + BlockIndex = 1L, + PreviousState = state, + RandomSeed = 0, + Signer = admin, + }); + + foreach (var (sender, _, _) in transferData) + { + var prevBalance = state.GetBalance(sender, _ncgCurrency); + Assert.Equal(prevBalance - amount, nextState.GetBalance(sender, _ncgCurrency)); + } + + Assert.Equal(FungibleAssetValue.Parse(_ncgCurrency, "0.9"), nextState.GetBalance(recipient, _ncgCurrency)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void PlainValue(bool memo) + { + var transferData = new List<(Address sender, Address recipient, BigInteger amount)>(); + // 0.9 + // 1.0 + // 1.1 + for (int i = 9; i < 12; i++) + { + var sender = new PrivateKey().Address; + var recipient = new PrivateKey().Address; + var amount = FungibleAssetValue.Parse(_ncgCurrency, (0.1m * i).ToString(CultureInfo.InvariantCulture)); + transferData.Add((sender, recipient, amount.RawValue)); + } + + var action = new MigrateFee + { + TransferData = transferData, + Memo = memo ? "memo" : null, + }; + + var des = new MigrateFee(); + des.LoadPlainValue(action.PlainValue); + + for (int i = 0; i < action.TransferData.Count; i++) + { + var data = action.TransferData[i]; + Assert.Equal(des.TransferData[i].sender, data.sender); + Assert.Equal(des.TransferData[i].recipient, data.recipient); + Assert.Equal(des.TransferData[i].amount, data.amount); + } + + Assert.Equal(memo, !string.IsNullOrEmpty(des.Memo)); + } +} diff --git a/Lib9c/Action/MigrateFee.cs b/Lib9c/Action/MigrateFee.cs new file mode 100644 index 0000000000..aaa2b621c2 --- /dev/null +++ b/Lib9c/Action/MigrateFee.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Bencodex.Types; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Model.State; +using Nekoyume.Module; + +namespace Nekoyume.Action +{ + [ActionType(TypeIdentifier)] + public class MigrateFee : ActionBase + { + public const string TypeIdentifier = "migrate_fee"; + public List<(Address sender, Address recipient, BigInteger amount)> TransferData; + public string Memo; + + public MigrateFee() + { + } + + public override IValue PlainValue + { + get + { + var values = Dictionary.Empty + .Add("td", + new List(TransferData.Select(a => + List.Empty + .Add(a.sender.Serialize()) + .Add(a.recipient.Serialize()) + .Add(a.amount.Serialize())) + ) + ); + if (!string.IsNullOrEmpty(Memo)) + { + values = values.Add("m", Memo); + } + return Dictionary.Empty + .Add("type_id", TypeIdentifier) + .Add("values", values); + } + } + + public override void LoadPlainValue(IValue plainValue) + { + var dict = (Dictionary)((Dictionary)plainValue)["values"]; + var asList = (List) dict["td"]; + TransferData = new List<(Address sender, Address recipient, BigInteger amount)>(); + foreach (var v in asList) + { + var innerList = (List) v; + var sender = innerList[0].ToAddress(); + var recipient = innerList[1].ToAddress(); + var amount = innerList[2].ToBigInteger(); + TransferData.Add((sender, recipient, amount)); + } + + if (dict.TryGetValue((Text)"m", out var m)) + { + Memo = (Text) m; + } + } + + public override IWorld Execute(IActionContext context) + { + context.UseGas(1); + + CheckPermission(context); + var states = context.PreviousState; + var goldCurrency = states.GetGoldCurrency(); + foreach (var (sender, recipient, raw) in TransferData) + { + var balance = states.GetBalance(sender, goldCurrency); + var amount = FungibleAssetValue.FromRawValue(goldCurrency, raw); + if (balance >= amount) + { + states = states.TransferAsset(context, sender, recipient, amount); + } + } + + return states; + } + } +}