-
Notifications
You must be signed in to change notification settings - Fork 52
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3088 from planetarium/feature/issue-3006
�Introduce ClaimPatrolReward
- Loading branch information
Showing
14 changed files
with
652 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
namespace Lib9c.Tests.Action | ||
{ | ||
using System.Linq; | ||
using Lib9c.Tests.Util; | ||
using Libplanet.Action.State; | ||
using Libplanet.Crypto; | ||
using Libplanet.Mocks; | ||
using Nekoyume; | ||
using Nekoyume.Action; | ||
using Nekoyume.Model.Mail; | ||
using Nekoyume.Model.State; | ||
using Nekoyume.Module; | ||
using Serilog; | ||
using Xunit; | ||
using Xunit.Abstractions; | ||
|
||
public class ClaimPatrolRewardTest | ||
{ | ||
private readonly IWorld _initialState; | ||
private readonly TableSheets _tableSheets; | ||
|
||
public ClaimPatrolRewardTest(ITestOutputHelper outputHelper) | ||
{ | ||
Log.Logger = new LoggerConfiguration() | ||
.MinimumLevel.Verbose() | ||
.WriteTo.TestOutput(outputHelper) | ||
.CreateLogger(); | ||
|
||
_initialState = new World(MockUtil.MockModernWorldState); | ||
|
||
var sheets = TableSheetsImporter.ImportSheets(); | ||
foreach (var (key, value) in sheets) | ||
{ | ||
_initialState = _initialState | ||
.SetLegacyState(Addresses.TableSheet.Derive(key), value.Serialize()); | ||
} | ||
|
||
_tableSheets = new TableSheets(sheets); | ||
} | ||
|
||
[Fact] | ||
public void Execute() | ||
{ | ||
var privateKey = new PrivateKey(); | ||
var agentAddress = privateKey.Address; | ||
var row = _tableSheets.PatrolRewardSheet.Values.First(); | ||
var (state, avatar, _) = InitializeUtil.AddAvatar(_initialState, _tableSheets.GetAvatarSheets(), agentAddress); | ||
var avatarAddress = avatar.address; | ||
var action = new ClaimPatrolReward(avatar.address); | ||
|
||
var nextState = action.Execute(new ActionContext | ||
{ | ||
Signer = agentAddress, | ||
BlockIndex = 1L, | ||
PreviousState = state, | ||
RandomSeed = 0, | ||
}); | ||
|
||
var avatarState = nextState.GetAvatarState(avatarAddress); | ||
var itemSheet = _tableSheets.ItemSheet; | ||
var mail = Assert.IsType<PatrolRewardMail>(avatarState.mailBox.Single()); | ||
|
||
foreach (var reward in row.Rewards) | ||
{ | ||
var ticker = reward.Ticker; | ||
if (string.IsNullOrEmpty(ticker)) | ||
{ | ||
var itemId = reward.ItemId; | ||
var rowId = itemSheet[itemId].Id; | ||
Assert.True(avatarState.inventory.HasItem(rowId, reward.Count)); | ||
var item = mail.Items.First(i => i.id == itemId); | ||
Assert.Equal(item.count, reward.Count); | ||
} | ||
else | ||
{ | ||
var currency = Currencies.GetMinterlessCurrency(ticker); | ||
var recipient = Currencies.PickAddress(currency, agentAddress, avatarAddress); | ||
var fav = nextState.GetBalance(recipient, currency); | ||
Assert.Equal(currency * reward.Count, fav); | ||
Assert.Contains(fav, mail.FungibleAssetValues); | ||
} | ||
} | ||
|
||
Assert.Equal(1L, nextState.GetPatrolRewardClaimedBlockIndex(avatarAddress)); | ||
Assert.True(row.Interval > 1L); | ||
|
||
// Throw RequiredBlockIndex by reward interval | ||
Assert.Throws<RequiredBlockIndexException>(() => action.Execute(new ActionContext | ||
{ | ||
Signer = agentAddress, | ||
BlockIndex = 2L, | ||
PreviousState = nextState, | ||
RandomSeed = 0, | ||
})); | ||
} | ||
|
||
[Fact] | ||
public void Execute_Throw_InvalidAddressException() | ||
{ | ||
var signer = new PrivateKey().Address; | ||
var action = new ClaimPatrolReward(signer); | ||
|
||
Assert.Throws<InvalidAddressException>(() => action.Execute(new ActionContext | ||
{ | ||
Signer = signer, | ||
BlockIndex = 0, | ||
PreviousState = _initialState, | ||
})); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
namespace Lib9c.Tests.TableData.Event | ||
{ | ||
using System.Linq; | ||
using Nekoyume.TableData.Event; | ||
using Xunit; | ||
|
||
public class PatrolRewardSheetTest | ||
{ | ||
private readonly PatrolRewardSheet _sheet = new (); | ||
|
||
public PatrolRewardSheetTest() | ||
{ | ||
const string csv = | ||
"id,start,end,interval,min_level,max_level,reward_count,reward_item_id,reward_ticker,reward_count,reward_item_id,reward_ticker,reward_count,reward_item_id,reward_ticker,reward_count,reward_item_id,reward_ticker\n1,0,100,8400,1,200,1,500000,,1,600201,\n2,100,200,8400,201,350,1,500000,,2,600201,,100000,,CRYSTAL\n3,200,300,8400,351,,2,500000,,3,600201,,200000,,CRYSTAL,1,600202,\n"; | ||
_sheet.Set(csv); | ||
} | ||
|
||
[Fact] | ||
public void Set() | ||
{ | ||
var row = _sheet[1]; | ||
Assert.Equal(0L, row.StartedBlockIndex); | ||
Assert.Equal(100L, row.EndedBlockIndex); | ||
Assert.Equal(8400L, row.Interval); | ||
Assert.Equal(1, row.MinimumLevel); | ||
Assert.Equal(200, row.MaxLevel); | ||
var apReward = row.Rewards.First(); | ||
Assert.Equal(1, apReward.Count); | ||
Assert.Equal(500000, apReward.ItemId); | ||
Assert.True(string.IsNullOrEmpty(apReward.Ticker)); | ||
var gdReward = row.Rewards.Last(); | ||
Assert.Equal(1, gdReward.Count); | ||
Assert.Equal(600201, gdReward.ItemId); | ||
Assert.True(string.IsNullOrEmpty(gdReward.Ticker)); | ||
|
||
row = _sheet[2]; | ||
Assert.Equal(100L, row.StartedBlockIndex); | ||
Assert.Equal(200L, row.EndedBlockIndex); | ||
Assert.Equal(8400L, row.Interval); | ||
Assert.Equal(201, row.MinimumLevel); | ||
Assert.Equal(350, row.MaxLevel); | ||
apReward = row.Rewards.First(); | ||
Assert.Equal(1, apReward.Count); | ||
Assert.Equal(500000, apReward.ItemId); | ||
Assert.True(string.IsNullOrEmpty(apReward.Ticker)); | ||
gdReward = row.Rewards[1]; | ||
Assert.Equal(2, gdReward.Count); | ||
Assert.Equal(600201, gdReward.ItemId); | ||
Assert.True(string.IsNullOrEmpty(gdReward.Ticker)); | ||
var crystalReward = row.Rewards.Last(); | ||
Assert.Equal(100000, crystalReward.Count); | ||
Assert.Equal(0, crystalReward.ItemId); | ||
Assert.Equal("CRYSTAL", crystalReward.Ticker); | ||
|
||
row = _sheet[3]; | ||
Assert.Equal(200L, row.StartedBlockIndex); | ||
Assert.Equal(300L, row.EndedBlockIndex); | ||
Assert.Equal(8400L, row.Interval); | ||
Assert.Equal(351, row.MinimumLevel); | ||
Assert.Null(row.MaxLevel); | ||
apReward = row.Rewards.First(); | ||
Assert.Equal(2, apReward.Count); | ||
Assert.Equal(500000, apReward.ItemId); | ||
Assert.True(string.IsNullOrEmpty(apReward.Ticker)); | ||
gdReward = row.Rewards[1]; | ||
Assert.Equal(3, gdReward.Count); | ||
Assert.Equal(600201, gdReward.ItemId); | ||
Assert.True(string.IsNullOrEmpty(gdReward.Ticker)); | ||
crystalReward = row.Rewards[2]; | ||
Assert.Equal(200000, crystalReward.Count); | ||
Assert.Equal(0, crystalReward.ItemId); | ||
Assert.Equal("CRYSTAL", crystalReward.Ticker); | ||
var rdReward = row.Rewards.Last(); | ||
Assert.Equal(1, rdReward.Count); | ||
Assert.Equal(600202, rdReward.ItemId); | ||
Assert.True(string.IsNullOrEmpty(rdReward.Ticker)); | ||
} | ||
|
||
[Theory] | ||
[InlineData(2, 1, 0)] | ||
[InlineData(350, 2, 150)] | ||
[InlineData(500, 3, 300)] | ||
public void FindByLevel(int level, int id, long blockIndex) | ||
{ | ||
Assert.Equal(id, _sheet.FindByLevel(level, blockIndex).Id); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using Bencodex.Types; | ||
using Lib9c; | ||
using Libplanet.Action; | ||
using Libplanet.Action.State; | ||
using Libplanet.Crypto; | ||
using Libplanet.Types.Assets; | ||
using Nekoyume.Extensions; | ||
using Nekoyume.Helper; | ||
using Nekoyume.Model.Mail; | ||
using Nekoyume.Model.State; | ||
using Nekoyume.Module; | ||
using Nekoyume.TableData.Event; | ||
|
||
namespace Nekoyume.Action | ||
{ | ||
/// <summary> | ||
/// Claim patrol reward | ||
/// </summary> | ||
[Serializable] | ||
[ActionType(TypeIdentifier)] | ||
public class ClaimPatrolReward : ActionBase | ||
{ | ||
public const string TypeIdentifier = "claim_patrol_reward"; | ||
|
||
/// <summary> | ||
/// The address of the avatar to receive the patrol reward. | ||
/// </summary> | ||
public Address AvatarAddress; | ||
|
||
public ClaimPatrolReward() | ||
{ | ||
} | ||
|
||
public ClaimPatrolReward(Address avatarAddress) | ||
{ | ||
AvatarAddress = avatarAddress; | ||
} | ||
public override IWorld Execute(IActionContext context) | ||
{ | ||
GasTracer.UseGas(1); | ||
var signer = context.Signer; | ||
var states = context.PreviousState; | ||
|
||
// Validate that the avatar address belongs to the signer. | ||
// This ensures that only the owner of the avatar can claim the patrol reward for it. | ||
if (!Addresses.CheckAvatarAddrIsContainedInAgent(signer, AvatarAddress)) | ||
{ | ||
throw new InvalidAddressException(); | ||
} | ||
|
||
// avatar | ||
var avatarState = states.GetAvatarState(AvatarAddress, true, false, false); | ||
var avatarLevel = avatarState.level; | ||
var inventory = avatarState.inventory; | ||
|
||
// sheets | ||
var sheets = states.GetSheets(containItemSheet: true, sheetTypes: new[] | ||
{ | ||
typeof(PatrolRewardSheet) | ||
}); | ||
var patrolRewardSheet = sheets.GetSheet<PatrolRewardSheet>(); | ||
var itemSheet = sheets.GetItemSheet(); | ||
|
||
// validate | ||
states.TryGetPatrolRewardClaimedBlockIndex(AvatarAddress, out var claimedBlockIndex); | ||
var row = patrolRewardSheet.FindByLevel(avatarLevel, context.BlockIndex); | ||
|
||
// Ensure rewards cannot be claimed too frequently. | ||
// If the last claimed block index is set and the current block index is less than the allowed interval, throw an exception. | ||
if (claimedBlockIndex > 0L && claimedBlockIndex + row.Interval > context.BlockIndex) | ||
{ | ||
throw new RequiredBlockIndexException(); | ||
} | ||
|
||
// mint rewards | ||
var random = context.GetRandom(); | ||
var fav = new List<FungibleAssetValue>(); | ||
var items = new List<(int id, int count)>(); | ||
foreach (var reward in row.Rewards) | ||
{ | ||
var ticker = reward.Ticker; | ||
if (string.IsNullOrEmpty(ticker)) | ||
{ | ||
var itemRow = itemSheet[reward.ItemId]; | ||
inventory.MintItem(itemRow, reward.Count, false, random); | ||
items.Add(new (reward.ItemId, reward.Count)); | ||
} | ||
else | ||
{ | ||
var currency = Currencies.GetMinterlessCurrency(ticker); | ||
var recipient = Currencies.PickAddress(currency, signer, AvatarAddress); | ||
var asset = currency * reward.Count; | ||
states = states.MintAsset(context, recipient, asset); | ||
fav.Add(asset); | ||
} | ||
} | ||
|
||
var mailBox = avatarState.mailBox; | ||
var mail = new PatrolRewardMail(context.BlockIndex, random.GenerateRandomGuid(), context.BlockIndex, fav, items); | ||
mailBox.Add(mail); | ||
mailBox.CleanUp(); | ||
avatarState.mailBox = mailBox; | ||
|
||
// set states | ||
return states | ||
.SetAvatarState(AvatarAddress, avatarState, setAvatar: true, setInventory: true, setWorldInformation: false, setQuestList: false) | ||
.SetPatrolRewardClaimedBlockIndex(AvatarAddress, context.BlockIndex); | ||
} | ||
|
||
public override IValue PlainValue => Dictionary.Empty | ||
.Add("type_id", TypeIdentifier) | ||
.Add("values", AvatarAddress.Serialize()); | ||
public override void LoadPlainValue(IValue plainValue) | ||
{ | ||
AvatarAddress = ((Dictionary)plainValue)["values"].ToAddress(); | ||
} | ||
} | ||
} |
Oops, something went wrong.