From 5ef6259dcbe8814769b31ef5a9ba21d49f81d9da Mon Sep 17 00:00:00 2001 From: s2quake Date: Mon, 20 Jan 2025 16:44:44 +0900 Subject: [PATCH 1/4] feat: Add action queries for guild system --- .../GraphTypes/ActionQuery.cs | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index c6b43ef95..a8c494eab 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -13,6 +13,8 @@ using Nekoyume.Action.ValidatorDelegation; using Nekoyume.Action.Guild.Migration; using Lib9c; +using Nekoyume.Action.Guild; +using Nekoyume.TypedAddress; namespace NineChronicles.Headless.GraphTypes { @@ -574,6 +576,100 @@ public ActionQuery(StandaloneContext standaloneContext) context, new MigrateDelegationHeight(context.GetArgument("amount")))); + Field( + name: "makeGuild", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "validatorAddress", + Description = "The validator address to create a guild." + }), + resolve: context => + { + var validatorAddress = context.GetArgument
("validatorAddress"); + return Encode(context, new MakeGuild(validatorAddress)); + }); + + Field( + name: "removeGuild", + resolve: context => Encode(context, new RemoveGuild())); + + Field( + name: "joinGuild", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "guildAddress", + Description = "The guild address to join." + }), + resolve: context => + { + var address = context.GetArgument
("guildAddress"); + var guildAddress = new GuildAddress(address); + return Encode(context, new JoinGuild(guildAddress)); + }); + + Field( + name: "quitGuild", + resolve: context => Encode(context, new QuitGuild())); + + Field( + name: "moveGuild", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "guildAddress", + Description = "The guild address to move." + }), + resolve: context => + { + var address = context.GetArgument
("guildAddress"); + var guildAddress = new GuildAddress(address); + return Encode(context, new MoveGuild(guildAddress)); + }); + + Field( + name: "banGuildMember", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "agentAddress", + Description = "The agent address to ban." + }), + resolve: context => + { + var address = context.GetArgument
("agentAddress"); + var agentAddress = new AgentAddress(address); + return Encode(context, new BanGuildMember(agentAddress)); + }); + + Field( + name: "unbanGuildMember", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "agentAddress", + Description = "The agent address to unban." + }), + resolve: context => + { + var address = context.GetArgument
("agentAddress"); + var agentAddress = new AgentAddress(address); + return Encode(context, new UnbanGuildMember(agentAddress)); + }); + + Field( + name: "claimReward", + resolve: context => Encode(context, new ClaimReward())); + + Field( + name: "claimGuildReward", + resolve: context => Encode(context, new ClaimGuildReward())); + + Field( + name: "claimUnbonded", + resolve: context => Encode(context, new ClaimUnbonded())); + RegisterHackAndSlash(); RegisterHackAndSlashSweep(); RegisterDailyReward(); From 157aec1e90efb95cc419dd9c04d1af68c5b89756 Mon Sep 17 00:00:00 2001 From: s2quake Date: Wed, 22 Jan 2025 09:49:28 +0900 Subject: [PATCH 2/4] feat: State query for delegatee and delegator --- .../GraphTypes/DelegatorTypeTest.cs | 4 - .../GraphTypes/DelegateeRepositoryType.cs | 22 ++++++ .../GraphTypes/DelegateeType.cs | 75 ++++++++++++++++++ .../GraphTypes/DelegatorRepositoryType.cs | 50 ++++++++++++ .../GraphTypes/DelegatorType.cs | 76 +++++++++++++++++++ .../GraphTypes/StateQuery.cs | 66 +++++++++------- 6 files changed, 260 insertions(+), 33 deletions(-) create mode 100644 NineChronicles.Headless/GraphTypes/DelegateeRepositoryType.cs create mode 100644 NineChronicles.Headless/GraphTypes/DelegateeType.cs create mode 100644 NineChronicles.Headless/GraphTypes/DelegatorRepositoryType.cs diff --git a/NineChronicles.Headless.Tests/GraphTypes/DelegatorTypeTest.cs b/NineChronicles.Headless.Tests/GraphTypes/DelegatorTypeTest.cs index 486303f27..3cac79616 100644 --- a/NineChronicles.Headless.Tests/GraphTypes/DelegatorTypeTest.cs +++ b/NineChronicles.Headless.Tests/GraphTypes/DelegatorTypeTest.cs @@ -1,14 +1,10 @@ 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 { diff --git a/NineChronicles.Headless/GraphTypes/DelegateeRepositoryType.cs b/NineChronicles.Headless/GraphTypes/DelegateeRepositoryType.cs new file mode 100644 index 000000000..5bf70ad9a --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/DelegateeRepositoryType.cs @@ -0,0 +1,22 @@ +using GraphQL.Types; + +namespace NineChronicles.Headless.GraphTypes; + +public class DelegateeRepositoryType : ObjectGraphType +{ + public DelegateeType? GuildDelegatee { get; set; } + + public DelegateeType? ValidatorDelegatee { get; set; } + + public DelegateeRepositoryType() + { + Field>( + nameof(GuildDelegatee), + description: "Delegatee of the guild repository", + resolve: context => context.Source.GuildDelegatee); + Field>( + nameof(ValidatorDelegatee), + description: "Delegatee of the validator repository", + resolve: context => context.Source.ValidatorDelegatee); + } +} diff --git a/NineChronicles.Headless/GraphTypes/DelegateeType.cs b/NineChronicles.Headless/GraphTypes/DelegateeType.cs new file mode 100644 index 000000000..e2a2080c7 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/DelegateeType.cs @@ -0,0 +1,75 @@ +using System.Numerics; +using GraphQL.Types; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Model.Guild; +using Nekoyume.ValidatorDelegation; + +namespace NineChronicles.Headless.GraphTypes; + +public class DelegateeType : ObjectGraphType +{ + public BigInteger TotalShares { get; set; } + + public bool Jailed { get; set; } + + public long JailedUntil { get; set; } + + public bool Tombstoned { get; set; } + + public FungibleAssetValue TotalDelegated { get; set; } + + public BigInteger CommissionPercentage { get; set; } + + public DelegateeType() + { + Field>( + nameof(TotalShares), + description: "Total shares of delegatee", + resolve: context => context.Source.TotalShares.ToString("N0")); + Field>( + nameof(Jailed), + description: "Specifies whether the delegatee is jailed.", + resolve: context => context.Source.Jailed); + Field>( + nameof(JailedUntil), + description: "Block height until which the delegatee is jailed.", + resolve: context => context.Source.JailedUntil); + Field>( + nameof(Tombstoned), + description: "Specifies whether the delegatee is tombstoned.", + resolve: context => context.Source.Tombstoned); + Field>( + nameof(TotalDelegated), + description: "Total delegated amount of the delegatee.", + resolve: context => context.Source.TotalDelegated); + } + + public static DelegateeType From(GuildRepository guildRepository, Address validatorAddress) + { + var delegatee = guildRepository.GetDelegatee(validatorAddress); + + return new DelegateeType + { + TotalShares = delegatee.TotalShares, + Jailed = delegatee.Jailed, + JailedUntil = delegatee.JailedUntil, + Tombstoned = delegatee.Tombstoned, + TotalDelegated = delegatee.TotalDelegated, + }; + } + + public static DelegateeType From(ValidatorRepository validatorRepository, Address validatorAddress) + { + var delegatee = validatorRepository.GetDelegatee(validatorAddress); + + return new DelegateeType + { + TotalShares = delegatee.TotalShares, + Jailed = delegatee.Jailed, + JailedUntil = delegatee.JailedUntil, + Tombstoned = delegatee.Tombstoned, + TotalDelegated = delegatee.TotalDelegated, + }; + } +} diff --git a/NineChronicles.Headless/GraphTypes/DelegatorRepositoryType.cs b/NineChronicles.Headless/GraphTypes/DelegatorRepositoryType.cs new file mode 100644 index 000000000..9c0601120 --- /dev/null +++ b/NineChronicles.Headless/GraphTypes/DelegatorRepositoryType.cs @@ -0,0 +1,50 @@ +using GraphQL.Types; +using Libplanet.Crypto; +using Nekoyume.Model.Guild; +using Nekoyume.ValidatorDelegation; + +namespace NineChronicles.Headless.GraphTypes; + +public class DelegatorRepositoryType : ObjectGraphType +{ + public DelegatorType? GuildDelegator { get; set; } + + public DelegatorType? ValidatorDelegator { get; set; } + + public DelegatorRepositoryType() + { + Field( + nameof(GuildDelegator), + description: "Delegator of the guild repository", + resolve: context => context.Source.GuildDelegator); + Field( + nameof(ValidatorDelegator), + description: "Delegator of the validator repository", + resolve: context => context.Source.ValidatorDelegator); + } + + public static DelegatorRepositoryType From(GuildRepository guildRepository, GuildParticipant guildParticipant) + { + var validatorRepository = new ValidatorRepository( + guildRepository.World, guildRepository.ActionContext); + var guild = guildRepository.GetGuild(guildParticipant.GuildAddress); + + return new DelegatorRepositoryType + { + GuildDelegator = DelegatorType.From(guildRepository, guildParticipant), + ValidatorDelegator = DelegatorType.From(validatorRepository, guild), + }; + } + + public static DelegatorRepositoryType From(ValidatorRepository validatorRepository, Address validatorAddress) + { + var guildRepository = new GuildRepository( + validatorRepository.World, validatorRepository.ActionContext); + + return new DelegatorRepositoryType + { + GuildDelegator = DelegatorType.From(guildRepository, validatorAddress), + ValidatorDelegator = DelegatorType.From(validatorRepository, validatorAddress), + }; + } +} diff --git a/NineChronicles.Headless/GraphTypes/DelegatorType.cs b/NineChronicles.Headless/GraphTypes/DelegatorType.cs index e66514911..4471a88ab 100644 --- a/NineChronicles.Headless/GraphTypes/DelegatorType.cs +++ b/NineChronicles.Headless/GraphTypes/DelegatorType.cs @@ -1,6 +1,9 @@ using System.Numerics; using GraphQL.Types; +using Libplanet.Crypto; using Libplanet.Types.Assets; +using Nekoyume.Model.Guild; +using Nekoyume.ValidatorDelegation; namespace NineChronicles.Headless.GraphTypes; @@ -27,4 +30,77 @@ public DelegatorType() description: "Delegated FAV calculated based on Share value", resolve: context => context.Source.Fav); } + + public static DelegatorType From(GuildRepository guildRepository, GuildParticipant guildParticipant) + { + var guild = guildRepository.GetGuild(guildParticipant.GuildAddress); + var delegatee = guildRepository.GetDelegatee(guild.ValidatorAddress); + var bond = guildRepository.GetBond(delegatee, guildParticipant.Address); + var totalFAV = delegatee.Metadata.TotalDelegatedFAV; + var totalShare = delegatee.Metadata.TotalShares; + var lastDistributeHeight = bond.LastDistributeHeight ?? -1; + var share = bond.Share; + var fav = (share * totalFAV).DivRem(totalShare).Quotient; + + return new DelegatorType + { + LastDistributeHeight = lastDistributeHeight, + Share = share, + Fav = fav, + }; + } + + public static DelegatorType From(ValidatorRepository validatorRepository, Guild guild) + { + var delegatee = validatorRepository.GetDelegatee(guild.ValidatorAddress); + var bond = validatorRepository.GetBond(delegatee, guild.Address); + var totalFAV = delegatee.Metadata.TotalDelegatedFAV; + var totalShare = delegatee.Metadata.TotalShares; + var lastDistributeHeight = bond.LastDistributeHeight ?? -1; + var share = bond.Share; + var fav = (share * totalFAV).DivRem(totalShare).Quotient; + + return new DelegatorType + { + LastDistributeHeight = lastDistributeHeight, + Share = share, + Fav = fav, + }; + } + + public static DelegatorType From(GuildRepository guildRepository, Address validatorAddress) + { + var delegatee = guildRepository.GetDelegatee(validatorAddress); + var bond = guildRepository.GetBond(delegatee, validatorAddress); + var totalFAV = delegatee.Metadata.TotalDelegatedFAV; + var totalShare = delegatee.Metadata.TotalShares; + var lastDistributeHeight = bond.LastDistributeHeight ?? -1; + var share = bond.Share; + var fav = (share * totalFAV).DivRem(totalShare).Quotient; + + return new DelegatorType + { + LastDistributeHeight = lastDistributeHeight, + Share = share, + Fav = fav, + }; + } + + public static DelegatorType From(ValidatorRepository validatorRepository, Address validatorAddress) + { + var delegatee = validatorRepository.GetDelegatee(validatorAddress); + var bond = validatorRepository.GetBond(delegatee, validatorAddress); + var totalFAV = delegatee.Metadata.TotalDelegatedFAV; + var totalShare = delegatee.Metadata.TotalShares; + var lastDistributeHeight = bond.LastDistributeHeight ?? -1; + var share = bond.Share; + var fav = (share * totalFAV).DivRem(totalShare).Quotient; + + return new DelegatorType + { + LastDistributeHeight = lastDistributeHeight, + Share = share, + Fav = fav, + }; + } } diff --git a/NineChronicles.Headless/GraphTypes/StateQuery.cs b/NineChronicles.Headless/GraphTypes/StateQuery.cs index f7faff214..cd2b291df 100644 --- a/NineChronicles.Headless/GraphTypes/StateQuery.cs +++ b/NineChronicles.Headless/GraphTypes/StateQuery.cs @@ -793,7 +793,38 @@ public StateQuery() } ); - Field( + Field( + name: "delegatee", + description: "State for delegatee.", + arguments: new QueryArguments( + new QueryArgument> + { + Name = "address", + Description = "Address of the validator." + } + ), + resolve: context => + { + var address = context.GetArgument
("address"); + var guildRepository = new GuildRepository( + new World(context.Source.WorldState), new HallowActionContext { }); + var validatorRepository = new ValidatorRepository( + new World(context.Source.WorldState), new HallowActionContext { }); + + if (validatorRepository.TryGetDelegatee(address, out var validatorDelegatee)) + { + return new DelegateeRepositoryType + { + GuildDelegatee = DelegateeType.From(guildRepository, address), + ValidatorDelegatee = DelegateeType.From(validatorRepository, address), + }; + } + + return null; + } + ); + + Field( name: "delegator", description: "State for delegator.", arguments: new QueryArguments( @@ -809,41 +840,18 @@ public StateQuery() var agentAddress = new AgentAddress(address); var guildRepository = new GuildRepository( new World(context.Source.WorldState), new HallowActionContext { }); + var validatorRepository = new ValidatorRepository( + 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, - }; + return DelegatorRepositoryType.From(guildRepository, guildParticipant); } 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 DelegatorRepositoryType.From(validatorRepository, validatorAddress); } return null; From db116dfaa585e7ad615d8bd10756770ef3749c6c Mon Sep 17 00:00:00 2001 From: s2quake Date: Wed, 22 Jan 2025 10:56:54 +0900 Subject: [PATCH 3/4] fix: Fix an issue where validator could not stake becuase validator did not have an avatar --- NineChronicles.Headless/GraphTypes/ActionQuery.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index a8c494eab..06cfc58db 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -35,14 +35,20 @@ public ActionQuery(StandaloneContext standaloneContext) Name = "amount", Description = "An amount to stake.", }, - new QueryArgument> + new QueryArgument { Name = "avatarAddress", Description = "Address of avatar.", }), - resolve: context => Encode( - context, - new Stake(context.GetArgument("amount"), context.GetArgument
("avatarAddress")))); + resolve: context => + { + var amount = context.GetArgument("amount"); + var avatarAddress = context.GetArgument("avatarAddress"); + var stake = avatarAddress is not null + ? new Stake(amount, avatarAddress.Value) + : new Stake(amount); + return Encode(context, stake); + }); Field( name: "claimStakeReward", From ebb135d90b024f9216e8a2ee5916c6457a43428d Mon Sep 17 00:00:00 2001 From: s2quake Date: Wed, 22 Jan 2025 11:17:55 +0900 Subject: [PATCH 4/4] feat: Add action queries for validator --- .../GraphTypes/ActionQuery.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/NineChronicles.Headless/GraphTypes/ActionQuery.cs b/NineChronicles.Headless/GraphTypes/ActionQuery.cs index 06cfc58db..1f85784bf 100644 --- a/NineChronicles.Headless/GraphTypes/ActionQuery.cs +++ b/NineChronicles.Headless/GraphTypes/ActionQuery.cs @@ -15,6 +15,7 @@ using Lib9c; using Nekoyume.Action.Guild; using Nekoyume.TypedAddress; +using System.Globalization; namespace NineChronicles.Headless.GraphTypes { @@ -571,6 +572,42 @@ public ActionQuery(StandaloneContext standaloneContext) currency)); }); + Field( + name: "unjailValidator", + resolve: context => Encode(context, new UnjailValidator())); + + Field( + name: "delegateValidator", + arguments: new QueryArguments( + new QueryArgument> + { + Description = "A string value of guild gold to delegate.", + Name = "amount", + }), + resolve: context => + { + var fav = FungibleAssetValue.Parse( + currency: Currencies.GuildGold, + value: context.GetArgument("amount")); + return Encode(context, new DelegateValidator(fav)); + }); + + Field( + name: "undelegateValidator", + arguments: new QueryArguments( + new QueryArgument> + { + Description = "A string value of share to undelegate.", + Name = "share", + }), + resolve: context => + { + var share = BigInteger.Parse( + value: context.GetArgument("share"), + style: NumberStyles.Number); + return Encode(context, new UndelegateValidator(share)); + }); + Field( name: "migrateDelegationHeight", arguments: new QueryArguments(new QueryArgument