diff --git a/NineChronicles.Headless.Tests/GraphTypes/DelegatorTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/DelegatorTypeTest.cs new file mode 100644 index 000000000..486303f27 --- /dev/null +++ b/NineChronicles.Headless.Tests/GraphTypes/DelegatorTypeTest.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Numerics; +using System.Threading.Tasks; +using GraphQL.Execution; +using Lib9c; +using Libplanet.Types.Tx; +using Nekoyume.ValidatorDelegation; +using NineChronicles.Headless.GraphTypes; +using Xunit; +using Xunit.Abstractions; + +namespace NineChronicles.Headless.Tests.GraphTypes +{ + public class DelegatorTypeTest + { + [Fact] + public async Task ExecuteQuery() + { + var lastDistributeHeight = 1L; + var share = new BigInteger(1000000000000000000) * 10; + var fav = Currencies.GuildGold * 10; + + var result = await GraphQLTestUtils.ExecuteQueryAsync( + "{ lastDistributeHeight share fav { currency quantity } }", + source: new DelegatorType + { + LastDistributeHeight = lastDistributeHeight, + Share = share, + Fav = fav, + } + ); + + // Then + var data = (Dictionary)((ExecutionNode)result.Data!).ToValue()!; + + Assert.Equal(lastDistributeHeight, data["lastDistributeHeight"]); + Assert.Equal(share.ToString("N0"), data["share"]); + + var favResult = (Dictionary)data["fav"]; + Assert.Equal(fav.Currency.Ticker, favResult["currency"]); + Assert.Equal(fav.GetQuantityString(minorUnit: true), favResult["quantity"]); + } + } +} diff --git a/NineChronicles.Headless/GraphTypes/DelegatorType.cs b/NineChronicles.Headless/GraphTypes/DelegatorType.cs new file mode 100644 index 000000000..e66514911 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/DelegatorType.cs @@ -0,0 +1,30 @@ +using System.Numerics; +using GraphQL.Types; +using Libplanet.Types.Assets; + +namespace NineChronicles.Headless.GraphTypes; + +public class DelegatorType : ObjectGraphType +{ + public long LastDistributeHeight { get; set; } + + public BigInteger Share { get; set; } + + public FungibleAssetValue Fav { get; set; } + + public DelegatorType() + { + Field>( + nameof(LastDistributeHeight), + description: "LastDistributeHeight of delegator", + resolve: context => context.Source.LastDistributeHeight); + Field>( + nameof(Share), + description: "Share of delegator", + resolve: context => context.Source.Share.ToString("N0")); + Field>( + nameof(Fav), + description: "Delegated FAV calculated based on Share value", + resolve: context => context.Source.Fav); + } +} diff --git a/NineChronicles.Headless/GraphTypes/GuildType.cs b/NineChronicles.Headless/GraphTypes/GuildType.cs new file mode 100644 index 000000000..fdfb46f8b --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/GuildType.cs @@ -0,0 +1,38 @@ +using GraphQL.Types; +using Libplanet.Crypto; +using Libplanet.Explorer.GraphTypes; +using Nekoyume.Model.Guild; + +namespace NineChronicles.Headless.GraphTypes; + +public class GuildType : ObjectGraphType +{ + public Address Address { get; set; } + + public Address ValidatorAddress { get; set; } + + public Address GuildMasterAddress { get; set; } + + public GuildType() + { + Field>( + nameof(Address), + description: "Address of the guild", + resolve: context => context.Source.Address); + Field>( + nameof(ValidatorAddress), + description: "Validator address of the guild", + resolve: context => context.Source.ValidatorAddress); + Field>( + nameof(GuildMasterAddress), + description: "Guild master address of the guild", + resolve: context => context.Source.GuildMasterAddress); + } + + public static GuildType FromDelegatee(Guild guild) => new GuildType + { + Address = guild.Address, + ValidatorAddress = guild.ValidatorAddress, + GuildMasterAddress = guild.GuildMasterAddress, + }; +} diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index a101aaeef..36e057e78 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -34,6 +34,7 @@ using Nekoyume.Module.Guild; using Nekoyume.TypedAddress; using Nekoyume.ValidatorDelegation; +using Nekoyume.Module.ValidatorDelegation; namespace NineChronicles.Headless.GraphTypes { @@ -721,7 +722,7 @@ public StateQuery() } ); - Field( + Field( name: "guild", description: "State for guild.", arguments: new QueryArguments( @@ -734,15 +735,14 @@ public StateQuery() resolve: context => { var agentAddress = new AgentAddress(context.GetArgument
("agentAddress")); - if (!(context.Source.WorldState.GetAgentState(agentAddress) is { } agentState)) + var repository = new GuildRepository(new World(context.Source.WorldState), new HallowActionContext { }); + if (repository.GetJoinedGuild(agentAddress) is { } guildAddress) { - return null; + var guild = repository.GetGuild(guildAddress); + return GuildType.FromDelegatee(guild); } - var repository = new GuildRepository(new World(context.Source.WorldState), new HallowActionContext { }); - var joinedGuild = (Address?)repository.GetJoinedGuild(agentAddress); - - return joinedGuild; + return null; } ); @@ -791,6 +791,63 @@ public StateQuery() return ValidatorType.FromDelegatee(delegatee); } ); + + Field( + name: "delegator", + description: "State for delegator.", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "address", + Description = "Agent or Validator address." + } + ), + resolve: context => + { + var address = context.GetArgument
("address"); + var agentAddress = new AgentAddress(address); + var guildRepository = new GuildRepository( + new World(context.Source.WorldState), new HallowActionContext { }); + if (guildRepository.TryGetGuildParticipant(agentAddress, out var guildParticipant)) + { + var guild = guildRepository.GetGuild(guildParticipant.GuildAddress); + var guildDelegatee = guildRepository.GetDelegatee(guild.ValidatorAddress); + var bond = guildRepository.GetBond(guildDelegatee, guildParticipant.Address); + var totalDelegated = guildDelegatee.Metadata.TotalDelegatedFAV; + var totalShare = guildDelegatee.Metadata.TotalShares; + var lastDistributeHeight = bond.LastDistributeHeight ?? -1; + var share = bond.Share; + var fav = (share * totalDelegated).DivRem(totalShare).Quotient; + return new DelegatorType + { + LastDistributeHeight = lastDistributeHeight, + Share = share, + Fav = fav, + }; + } + + var validatorAddress = address; + var validatorRepository = new ValidatorRepository( + new World(context.Source.WorldState), new HallowActionContext { }); + if (validatorRepository.TryGetDelegatee(validatorAddress, out var validatorDelegatee)) + { + var bond = validatorRepository.GetBond(validatorDelegatee, address); + var totalDelegated = validatorDelegatee.Metadata.TotalDelegatedFAV; + var totalShare = validatorDelegatee.Metadata.TotalShares; + var lastDistributeHeight = bond.LastDistributeHeight ?? -1; + var share = bond.Share; + var fav = (share * totalDelegated).DivRem(totalShare).Quotient; + return new DelegatorType + { + LastDistributeHeight = lastDistributeHeight, + Share = share, + Fav = fav, + }; + } + + return null; + } + ); } public static List GetRuneOptions(