From 54588c28f3bfb609e4104d4ba5d382890e349e79 Mon Sep 17 00:00:00 2001 From: s2quake Date: Mon, 8 Apr 2024 10:57:36 +0900 Subject: [PATCH] test: Add test code for slashing. --- .../Action/DPoS/Control/EvidenceCtrlTest.cs | 155 +++ .../Action/DPoS/Control/SlashCtrlTest.cs | 916 ++++++++++++++++++ .../Action/DPoS/Control/ValidatorCtrlTest.cs | 152 +++ .../Control/ValidatorSigningInfoCtrlTest.cs | 218 +++++ .Lib9c.Tests/Action/DPoS/PoSTest.cs | 145 +++ 5 files changed, 1586 insertions(+) create mode 100644 .Lib9c.Tests/Action/DPoS/Control/EvidenceCtrlTest.cs create mode 100644 .Lib9c.Tests/Action/DPoS/Control/SlashCtrlTest.cs create mode 100644 .Lib9c.Tests/Action/DPoS/Control/ValidatorSigningInfoCtrlTest.cs diff --git a/.Lib9c.Tests/Action/DPoS/Control/EvidenceCtrlTest.cs b/.Lib9c.Tests/Action/DPoS/Control/EvidenceCtrlTest.cs new file mode 100644 index 0000000000..2521d5bebe --- /dev/null +++ b/.Lib9c.Tests/Action/DPoS/Control/EvidenceCtrlTest.cs @@ -0,0 +1,155 @@ +namespace Lib9c.Tests.Action.DPoS.Control +{ + using System; + using System.Linq; + using System.Numerics; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume.Action.DPoS.Control; + using Nekoyume.Action.DPoS.Exception; + using Nekoyume.Action.DPoS.Misc; + using Nekoyume.Action.DPoS.Model; + using Nekoyume.Module; + using Xunit; + using Environment = Nekoyume.Action.DPoS.Control.Environment; + + public class EvidenceCtrlTest : PoSTest + { + private readonly PublicKey _operatorPublicKey; + private readonly Address _operatorAddress; + private readonly Address _delegatorAddress; + private readonly Address _validatorAddress; + private readonly FungibleAssetValue _governanceToken + = new FungibleAssetValue(Asset.GovernanceToken, 100, 0); + + private IWorld _states; + + public EvidenceCtrlTest() + { + _operatorPublicKey = new PrivateKey().PublicKey; + _operatorAddress = _operatorPublicKey.Address; + _delegatorAddress = CreateAddress(); + _validatorAddress = Validator.DeriveAddress(_operatorAddress); + _states = InitializeStates(); + } + + [Fact] + public void Execute_Test() + { + var governanceToken = _governanceToken; + var states = _states; + var operatorPublicKey = _operatorPublicKey; + var validatorAddress = _validatorAddress; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: governanceToken); + states = Update( + states: states, + blockIndex: 1); + + var power = GetPower(states, validatorAddress); + var evidence = new Evidence() + { + Address = validatorAddress, + Height = 1, + Power = power.RawValue, + }; + + states = EvidenceCtrl.Execute( + world: states, + actionContext: new ActionContext() { PreviousState = states, BlockIndex = 2 }, + validatorAddress: validatorAddress, + evidence: evidence, + nativeTokens: NativeTokens); + + var validator = ValidatorCtrl.GetValidator(states, validatorAddress); + var signingInfo = ValidatorSigningInfoCtrl.GetSigningInfo(states, validatorAddress); + var actualPower = GetPower(states, validatorAddress); + + Assert.NotEqual(power, actualPower); + Assert.True(validator.Jailed); + //Assert.Equal(BondingStatus.Unbonded, validator.Status); + Assert.Equal(long.MaxValue, signingInfo.JailedUntil); + Assert.True(signingInfo.Tombstoned); + } + + [Fact] + public void Execute_MaxAge_Test() + { + var governanceToken = _governanceToken; + var states = _states; + var operatorPublicKey = _operatorPublicKey; + var validatorAddress = _validatorAddress; + var blockIndex = 1; + + states = Promote( + states: states, + blockIndex: blockIndex, + operatorPublicKey: operatorPublicKey, + governanceToken: governanceToken); + states = Update( + states: states, + blockIndex: blockIndex); + + var farFutureHeight = blockIndex + Environment.MaxAgeNumBlocks + 1; + var expectedPower = GetPower(states, validatorAddress); + var evidence = new Evidence() + { + Address = validatorAddress, + Height = blockIndex, + Power = expectedPower.RawValue, + }; + + states = EvidenceCtrl.Execute( + world: states, + actionContext: new ActionContext() { PreviousState = states, BlockIndex = farFutureHeight }, + validatorAddress: validatorAddress, + evidence: evidence, + nativeTokens: NativeTokens); + + var validator = ValidatorCtrl.GetValidator(states, validatorAddress); + var signingInfo = ValidatorSigningInfoCtrl.GetSigningInfo(states, validatorAddress); + var actualPower = GetPower(states, validatorAddress); + + Assert.Equal(expectedPower, actualPower); + Assert.False(validator.Jailed); + Assert.NotEqual(long.MaxValue, signingInfo.JailedUntil); + Assert.False(signingInfo.Tombstoned); + } + + [Fact] + public void Execute_NotPromotedValidator_FailTest() + { + var states = _states; + var validatorAddress = _validatorAddress; + var power = GetPower(states, validatorAddress); + var evidence = new Evidence() + { + Address = validatorAddress, + Height = 1, + Power = power.RawValue, + }; + + Assert.Throws(() => + { + states = EvidenceCtrl.Execute( + world: states, + actionContext: new ActionContext() { PreviousState = states, BlockIndex = 2 }, + validatorAddress: validatorAddress, + evidence: evidence, + nativeTokens: NativeTokens); + }); + } + + private static FungibleAssetValue GetPower(IWorldState worldState, Address validatorAddress) + { + return worldState.GetBalance( + address: validatorAddress, + currency: Asset.ConsensusToken); + } + } +} diff --git a/.Lib9c.Tests/Action/DPoS/Control/SlashCtrlTest.cs b/.Lib9c.Tests/Action/DPoS/Control/SlashCtrlTest.cs new file mode 100644 index 0000000000..2d086e8ba5 --- /dev/null +++ b/.Lib9c.Tests/Action/DPoS/Control/SlashCtrlTest.cs @@ -0,0 +1,916 @@ +namespace Lib9c.Tests.Action.DPoS.Control +{ + using System; + using System.Linq; + using System.Numerics; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume.Action.DPoS.Control; + using Nekoyume.Action.DPoS.Exception; + using Nekoyume.Action.DPoS.Misc; + using Nekoyume.Action.DPoS.Model; + using Nekoyume.Module; + using Xunit; + + public class SlashCtrlTest : PoSTest + { + public static readonly object[][] TestData = new object[][] + { + new object[] { false, new BigInteger(20) }, + // new object[] { false, new BigInteger(1) }, + new object[] { true, new BigInteger(20) }, + // new object[] { true, new BigInteger(1) }, + }; + + private const int ValidatorCount = 2; + private const int DelegatorCount = 2; + + private readonly PublicKey[] _operatorPublicKeys; + private readonly Address[] _operatorAddresses; + private readonly Address[] _delegatorAddresses; + private readonly Address[] _validatorAddresses; + private readonly FungibleAssetValue _defaultNCG + = new FungibleAssetValue(Asset.GovernanceToken, 100, 0); + + private readonly BigInteger _slashFactor = new BigInteger(20); + private IWorld _states; + + public SlashCtrlTest() + { + _operatorPublicKeys = Enumerable.Range(0, ValidatorCount) + .Select(_ => new PrivateKey().PublicKey) + .ToArray(); + _operatorAddresses = _operatorPublicKeys.Select(item => item.Address).ToArray(); + _delegatorAddresses = Enumerable.Range(0, DelegatorCount) + .Select(_ => CreateAddress()) + .ToArray(); + _validatorAddresses = _operatorAddresses + .Select(item => Validator.DeriveAddress(item)) + .ToArray(); + _states = InitializeStates(); + } + + [Theory] + [MemberData(nameof(TestData))] + public void Slash_WithoutDelegation_Test(bool jailed, BigInteger slashFactor) + { + var validatorNCG = _defaultNCG; + var consensusToken = Asset.ConsensusFromGovernance(validatorNCG); + + var operatorPublicKey = _operatorPublicKeys[0]; + var validatorAddress = _validatorAddresses[0]; + var states = _states; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: validatorNCG); + states = Update( + states: states, + blockIndex: 1); + + var bondedPoolNCG = states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken); + if (jailed) + { + states = Jail( + states: states, + validatorAddress: validatorAddress); + } + + states = SlashCtrl.Slash( + world: states, + actionContext: new ActionContext { PreviousState = states, BlockIndex = 3, }, + validatorAddress: validatorAddress, + infractionHeight: 1, + power: consensusToken.RawValue, + slashFactor: slashFactor, + nativeTokens: NativeTokens); + + // Expect 95 ConsensusToken in validator + var expectedConsensusToken = SlashAsset(consensusToken, slashFactor); + // Expect 100 Share in validator + var expectedShare = GetShare(states, validatorAddress, expectedConsensusToken); + // Expect 95 NCG in bonded pool + var expectedBondedPoolNCG = bondedPoolNCG - (validatorNCG - SlashAsset(validatorNCG, slashFactor)); + // Expect 5 NCG in community pool + var expectedCommunityPoolNCG = validatorNCG - expectedBondedPoolNCG; + + var actualShare = GetShare(states, validatorAddress); + var actualConsensusToken = GetPower(states, validatorAddress); + var actualBondedPoolNCG = states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken); + var actualCommunityPoolNCG = states.GetBalance(ReservedAddress.CommunityPool, Asset.GovernanceToken); + + Assert.Equal(expectedShare, actualShare); + Assert.Equal(expectedConsensusToken, actualConsensusToken); + Assert.Equal(expectedBondedPoolNCG, actualBondedPoolNCG); + Assert.Equal(expectedCommunityPoolNCG, actualCommunityPoolNCG); + + _states = states; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Slash_WithDelegation_Test(bool jailed) + { + var validatorNCG = _defaultNCG; + var delegatorNCG = _defaultNCG; + var consensusToken = Asset.ConsensusFromGovernance(validatorNCG + delegatorNCG); + var slashFactor = _slashFactor; + + var operatorPublicKey = _operatorPublicKeys[0]; + var validatorAddress = _validatorAddresses[0]; + var delegatorAddress = _delegatorAddresses[0]; + var states = _states; + + // Delegate 100 NCG by validator + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: validatorNCG); + // Delegate 100 NCG by delegator + states = Delegate( + states: states, + blockIndex: 1, + delegatorAddress: delegatorAddress, + validatorAddress: validatorAddress, + governanceToken: validatorNCG); + states = Update( + states: states, + blockIndex: 1); + + var bondedPoolNCG = states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken); + if (jailed) + { + states = Jail( + states: states, + validatorAddress: validatorAddress); + } + + states = SlashCtrl.Slash( + world: states, + actionContext: new ActionContext { PreviousState = states, BlockIndex = 2, }, + validatorAddress: validatorAddress, + infractionHeight: 1, + power: consensusToken.RawValue, + slashFactor: slashFactor, + nativeTokens: NativeTokens); + + // Expect 100 Share in validator + var expectedShare = FungibleAssetValue.FromRawValue(Asset.Share, consensusToken.RawValue); + // Expect 95 ConsensusToken in validator + var expectedConsensusToken = SlashAsset(consensusToken, slashFactor); + // Expect 95 NCG in bonded pool + var expectedBondedPoolNCG = SlashAsset(validatorNCG + delegatorNCG, slashFactor); + // Expect 5 NCG in community pool + var expectedCommunityPoolNCG = bondedPoolNCG - expectedBondedPoolNCG; + + var actualShare = GetShare(states, validatorAddress); + var actualConsensusToken = GetPower(states, validatorAddress); + var actualBondedPoolNCG = states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken); + var actualCommunityPoolNCG = states.GetBalance(ReservedAddress.CommunityPool, Asset.GovernanceToken); + + Assert.Equal(expectedShare, actualShare); + Assert.Equal(expectedConsensusToken, actualConsensusToken); + Assert.Equal(expectedBondedPoolNCG, actualBondedPoolNCG); + Assert.Equal(expectedCommunityPoolNCG, actualCommunityPoolNCG); + + _states = states; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Slash_WithUndelegation_Test(bool jailed) + { + var validatorNCG = _defaultNCG; + var delegatorNCG = _defaultNCG; + var slashFactor = _slashFactor; + var undelegationShare = new FungibleAssetValue( + Asset.Share, + delegatorNCG.MajorUnit, + delegatorNCG.MinorUnit); + + var operatorPublicKey = _operatorPublicKeys[0]; + var validatorAddress = _validatorAddresses[0]; + var delegatorAddress = _delegatorAddresses[0]; + var states = _states; + + // Delegate 100 NCG by validator + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: validatorNCG); + states = Update(states, blockIndex: 1); + + // Delegate 100 NCG by delegator + states = Delegate( + states: states, + blockIndex: 2, + delegatorAddress: delegatorAddress, + validatorAddress: validatorAddress, + governanceToken: delegatorNCG); + states = Update(states, blockIndex: 2); + + var consensusTokenBeforeInfraction = GetPower(states, validatorAddress); + + // Undelegate 100 Share by delegator + states = Undelegate( + states: states, + blockIndex: 3, + delegatorAddress: delegatorAddress, + validatorAddress: validatorAddress, + share: undelegationShare + ); + states = Update(states, blockIndex: 3); + + var bondedPoolNCG = states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken); + var unbondedPoolNCG = states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken); + var consensusToken = GetPower(states, validatorAddress); + var share = GetShare(states, validatorAddress); + + if (jailed) + { + states = Jail( + states: states, + validatorAddress: validatorAddress); + } + + // Slash src validator at height 2 + states = SlashCtrl.Slash( + world: states, + actionContext: new ActionContext { PreviousState = states, BlockIndex = 4, }, + validatorAddress: validatorAddress, + infractionHeight: 2, + power: consensusTokenBeforeInfraction.RawValue, + slashFactor: slashFactor, + nativeTokens: NativeTokens); + + // Expect 100 Share in validator + var expectedShare = share; + // Expect 95 ConsensusToken in validator + var expectedConsensusToken = SlashAsset(consensusToken, slashFactor); + // Expect 95 NCG in bonded pool + var expectedBondedPoolNCG = SlashAsset(validatorNCG, slashFactor); + // Expect 100 NCG in unbonded pool + var expectedUnbondedPoolNCG = SlashAsset(validatorNCG, slashFactor); + // Expect 5 NCG in community pool + var expectedCommunityPoolNCG = bondedPoolNCG + unbondedPoolNCG - expectedBondedPoolNCG - expectedUnbondedPoolNCG; + + var undelegation = UndelegateCtrl.GetUndelegation(states, delegatorAddress, validatorAddress); + var undelegationEntry = UndelegateCtrl.GetUndelegationEntry(states, undelegation.UndelegationEntryAddresses[0]); + + Assert.NotNull(undelegation); + Assert.NotNull(undelegationEntry); + Assert.Equal(expectedConsensusToken, undelegationEntry.UnbondingConsensusToken); + + var actualShare = GetShare(states, validatorAddress); + var actualConsensusToken = GetPower(states, validatorAddress); + var actualBondedPoolNCG = states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken); + var actualUnbondedPoolNCG = states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken); + var actualCommunityPoolNCG = states.GetBalance(ReservedAddress.CommunityPool, Asset.GovernanceToken); + + Assert.Equal(expectedShare, actualShare); + Assert.Equal(expectedConsensusToken, actualConsensusToken); + Assert.Equal(expectedBondedPoolNCG, actualBondedPoolNCG); + Assert.Equal(expectedUnbondedPoolNCG, actualUnbondedPoolNCG); + Assert.Equal(expectedCommunityPoolNCG, actualCommunityPoolNCG); + + _states = states; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Slash_AfterUndelegating_Test(bool jailed) + { + var validatorNCG = _defaultNCG; + var delegatorNCG = _defaultNCG; + var slashFactor = _slashFactor; + var undelegationShare = new FungibleAssetValue( + Asset.Share, + delegatorNCG.MajorUnit, + delegatorNCG.MinorUnit); + + var operatorPublicKey = _operatorPublicKeys[0]; + var validatorAddress = _validatorAddresses[0]; + var delegatorAddress = _delegatorAddresses[0]; + var states = _states; + + // Delegate 100 NCG by validator + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: validatorNCG); + states = Update(states, blockIndex: 1); + + // Delegate 100 NCG by delegator + states = Delegate( + states: states, + blockIndex: 2, + delegatorAddress: delegatorAddress, + validatorAddress: validatorAddress, + governanceToken: delegatorNCG); + states = Update(states, blockIndex: 2); + + // Undelegate 100 Share by delegator + states = Undelegate( + states: states, + blockIndex: 3, + delegatorAddress: delegatorAddress, + validatorAddress: validatorAddress, + share: undelegationShare); + states = Update(states, blockIndex: 3); + + var consensusTokenBeforeInfraction = GetPower(states, validatorAddress); + var bondedPoolNCG = states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken); + var unbondedPoolNCG = states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken); + var consensusToken = GetPower(states, validatorAddress); + var share = GetShare(states, validatorAddress); + + if (jailed) + { + states = Jail( + states: states, + validatorAddress: validatorAddress); + } + + // Slash src validator at height 4 + states = SlashCtrl.Slash( + world: states, + actionContext: new ActionContext { PreviousState = states, BlockIndex = 4, }, + validatorAddress: validatorAddress, + infractionHeight: 4, + power: consensusTokenBeforeInfraction.RawValue, + slashFactor: slashFactor, + nativeTokens: NativeTokens); + + // Expect 100 Share in validator + var expectedShare = share; + // Expect 95 ConsensusToken in validator + var expectedConsensusToken = SlashAsset(consensusToken, slashFactor); + // Expect 95 NCG in bonded pool + var expectedBondedPoolNCG = SlashAsset(validatorNCG, slashFactor); + // Expect 100 NCG in unbonded pool + var expectedUnbondedPoolNCG = delegatorNCG; + // Expect 5 NCG in community pool + var expectedCommunityPoolNCG = bondedPoolNCG + unbondedPoolNCG - expectedBondedPoolNCG - expectedUnbondedPoolNCG; + + var undelegation = UndelegateCtrl.GetUndelegation(states, delegatorAddress, validatorAddress); + var undelegationEntry = UndelegateCtrl.GetUndelegationEntry(states, undelegation.UndelegationEntryAddresses[0]); + + Assert.NotNull(undelegation); + Assert.NotNull(undelegationEntry); + Assert.Equal( + expected: Asset.ConsensusFromGovernance(delegatorNCG), + actual: undelegationEntry.UnbondingConsensusToken); + + var actualShare = GetShare(states, validatorAddress); + var actualConsensusToken = GetPower(states, validatorAddress); + var actualBondedPoolNCG = states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken); + var actualUnbondedPoolNCG = states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken); + var actualCommunityPoolNCG = states.GetBalance(ReservedAddress.CommunityPool, Asset.GovernanceToken); + + Assert.Equal(expectedShare, actualShare); + Assert.Equal(expectedConsensusToken, actualConsensusToken); + Assert.Equal(expectedBondedPoolNCG, actualBondedPoolNCG); + Assert.Equal(expectedUnbondedPoolNCG, actualUnbondedPoolNCG); + Assert.Equal(expectedCommunityPoolNCG, actualCommunityPoolNCG); + + _states = states; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Slash_WithRedelegation_Test(bool jailed) + { + var slashFactor = _slashFactor; + + var srcOperatorPublicKey = _operatorPublicKeys[0]; + var dstOperatorPublicKey = _operatorPublicKeys[1]; + var srcValidatorAddress = _validatorAddresses[0]; + var dstValidatorAddress = _validatorAddresses[1]; + var delegatorAddress = _delegatorAddresses[0]; + var states = _states; + var srcValidatorNCG = _defaultNCG; + var dstValidatorNCG = _defaultNCG; + var delegatorNCG = _defaultNCG; + var redelegationShare = new FungibleAssetValue( + Asset.Share, + delegatorNCG.MajorUnit, + delegatorNCG.MinorUnit); + + // Delegate 100 NCG by src operator + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: srcOperatorPublicKey, + governanceToken: srcValidatorNCG); + // Delegate 100 NCG by dst operator + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: dstOperatorPublicKey, + governanceToken: dstValidatorNCG); + states = Update(states, blockIndex: 1); + + // Delgate 100 NCG by delegator to src validator + states = Delegate( + states: states, + blockIndex: 2, + delegatorAddress: delegatorAddress, + validatorAddress: srcValidatorAddress, + governanceToken: delegatorNCG); + states = Update(states, blockIndex: 2); + var consensusTokenBeforeInfraction = GetPower(states, srcValidatorAddress); + + // Redelegate 100 NCG from src validator to dst validator + states = Redelegate( + states: states, + blockIndex: 3, + delegatorAddress: delegatorAddress, + srcValidatorAddress: srcValidatorAddress, + dstValidatorAddress: dstValidatorAddress, + share: redelegationShare); + states = Update(states, blockIndex: 3); + + // 300 NCG in bonded pool + var bondedPoolNCG = states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken); + // 0 NCG in unbonded pool + var unbondedPoolNCG = states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken); + // 100 consensus token in src validator + var srcConsensusToken = GetPower(states, srcValidatorAddress); + // 200 consensus token in dst validator + var dstConsensusToken = GetPower(states, dstValidatorAddress); + + // 100 share in src validator + var srcShare = GetShare(states, srcValidatorAddress); + // 200 share in dst validator + var dstShare = GetShare(states, dstValidatorAddress); + + if (jailed) + { + states = Jail( + states: states, + validatorAddress: srcValidatorAddress); + } + + // Slash src validator at height 2 + states = SlashCtrl.Slash( + world: states, + actionContext: new ActionContext { PreviousState = states, BlockIndex = 4, }, + validatorAddress: srcValidatorAddress, + infractionHeight: 2, + power: consensusTokenBeforeInfraction.RawValue, + slashFactor: slashFactor, + nativeTokens: NativeTokens); + + var expectedSrcConsensusToken = SlashAsset(srcConsensusToken, slashFactor); + var expectedDstConsensusToken = + Asset.ConsensusFromGovernance(dstValidatorNCG) + + SlashAsset(Asset.ConsensusFromGovernance(delegatorNCG), slashFactor); + var expectedSrcShare = srcShare; + var expectedDstShare = + ValidatorCtrl.ShareFromConsensusToken(states, dstValidatorAddress, expectedDstConsensusToken); + var expectedBondedPoolNCG = SlashAsset(srcValidatorNCG + delegatorNCG, slashFactor) + dstValidatorNCG; + var expectedUnbondedPoolNCG = new FungibleAssetValue(Asset.GovernanceToken, 0, 0); + var expectedCommunityPoolNCG = srcValidatorNCG + dstValidatorNCG + delegatorNCG - expectedBondedPoolNCG; + + var redelegation = RedelegateCtrl.GetRedelegation(states, delegatorAddress, srcValidatorAddress, dstValidatorAddress); + var redelegationEntry = RedelegateCtrl.GetRedelegationEntry(states, redelegation.RedelegationEntryAddresses[0]); + + Assert.NotNull(redelegation); + Assert.NotNull(redelegationEntry); + + var actualSrcConsensusToken = GetPower(states, srcValidatorAddress); + var actualDstConsensusToken = GetPower(states, dstValidatorAddress); + var actualSrcShare = GetShare(states, srcValidatorAddress); + var actualDstShare = GetShare(states, dstValidatorAddress); + var actualBondedPoolNCG = states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken); + var actualUnbondedPoolNCG = states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken); + var actualCommunityPoolNCG = states.GetBalance(ReservedAddress.CommunityPool, Asset.GovernanceToken); + + Assert.Equal(expectedSrcShare, actualSrcShare); + Assert.Equal(expectedDstShare, actualDstShare); + Assert.Equal(expectedSrcConsensusToken, actualSrcConsensusToken); + Assert.Equal(expectedDstConsensusToken, actualDstConsensusToken); + Assert.Equal(expectedBondedPoolNCG, actualBondedPoolNCG); + Assert.Equal(expectedUnbondedPoolNCG, actualUnbondedPoolNCG); + Assert.Equal(expectedCommunityPoolNCG, actualCommunityPoolNCG); + + _states = states; + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Slash_AfterRedelegating_Test(bool jailed) + { + const int value = 100; + var governanceToken = new FungibleAssetValue(Asset.GovernanceToken, value, 0); + var slashFactor = _slashFactor; + + var srcOperatorPublicKey = _operatorPublicKeys[0]; + var dstOperatorPublicKey = _operatorPublicKeys[1]; + var srcValidatorAddress = _validatorAddresses[0]; + var dstValidatorAddress = _validatorAddresses[1]; + var delegatorAddress = _delegatorAddresses[0]; + var states = _states; + var srcValidatorNCG = governanceToken; + var dstValidatorNCG = governanceToken; + var delegatorNCG = governanceToken; + + // Delegate 100 NCG by src operator + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: srcOperatorPublicKey, + governanceToken: srcValidatorNCG); + // Delegate 100 NCG by dst operator + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: dstOperatorPublicKey, + governanceToken: dstValidatorNCG); + states = Update(states, blockIndex: 1); + + // Delgate 100 NCG by delegator to src validator + states = Delegate( + states: states, + blockIndex: 2, + delegatorAddress: delegatorAddress, + validatorAddress: srcValidatorAddress, + governanceToken: delegatorNCG); + states = Update(states, blockIndex: 2); + + // Redelegate 100 NCG from src validator to dst validator + states = Redelegate( + states: states, + blockIndex: 3, + delegatorAddress: delegatorAddress, + srcValidatorAddress: srcValidatorAddress, + dstValidatorAddress: dstValidatorAddress, + share: new FungibleAssetValue(Asset.Share, value, 0)); + states = Update(states, blockIndex: 3); + + var consensusTokenBeforeInfraction = GetPower(states, srcValidatorAddress); + // 300 NCG in bonded pool + var bondedPoolNCG = states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken); + // 0 NCG in unbonded pool + var unbondedPoolNCG = states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken); + // 100 consensus token in src validator + var srcConsensusToken = GetPower(states, srcValidatorAddress); + // 200 consensus token in dst validator + var dstConsensusToken = GetPower(states, dstValidatorAddress); + + // 100 share in src validator + var srcShare = GetShare(states, srcValidatorAddress); + // 200 share in dst validator + var dstShare = GetShare(states, dstValidatorAddress); + + if (jailed) + { + states = Jail( + states: states, + validatorAddress: srcValidatorAddress); + } + + // Slash src validator at height 4 + states = SlashCtrl.Slash( + world: states, + actionContext: new ActionContext { PreviousState = states, BlockIndex = 4, }, + validatorAddress: srcValidatorAddress, + infractionHeight: 4, + power: consensusTokenBeforeInfraction.RawValue, + slashFactor: slashFactor, + nativeTokens: NativeTokens); + + // Expect 95 ConsensusToken in src validator + var expectedSrcConsensusToken = SlashAsset(srcConsensusToken, slashFactor); + // Expect 200 ConsensusToken in src validator + var expectedDstConsensusToken = + Asset.ConsensusFromGovernance(dstValidatorNCG) + + Asset.ConsensusFromGovernance(delegatorNCG); + // Expect 100 Share in src validator + var expectedSrcShare = srcShare; + // Expect 200 Share in dst validator + var expectedDstShare = + ValidatorCtrl.ShareFromConsensusToken(states, dstValidatorAddress, expectedDstConsensusToken); + // Expect 295 NCG in bonded pool + var expectedBondedPoolNCG = SlashAsset(srcValidatorNCG, slashFactor) + delegatorNCG + dstValidatorNCG; + // Expect 0 NCG in unbonded pool + var expectedUnbondedPoolNCG = new FungibleAssetValue(Asset.GovernanceToken, 0, 0); + // Expect 5 NCG in community pool + var expectedCommunityPoolNCG = srcValidatorNCG + dstValidatorNCG + delegatorNCG - expectedBondedPoolNCG; + + var redelegation = RedelegateCtrl.GetRedelegation(states, delegatorAddress, srcValidatorAddress, dstValidatorAddress); + var redelegationEntry = RedelegateCtrl.GetRedelegationEntry(states, redelegation.RedelegationEntryAddresses[0]); + + Assert.NotNull(redelegation); + Assert.NotNull(redelegationEntry); + + var actualSrcConsensusToken = GetPower(states, srcValidatorAddress); + var actualDstConsensusToken = GetPower(states, dstValidatorAddress); + var actualSrcShare = GetShare(states, srcValidatorAddress); + var actualDstShare = GetShare(states, dstValidatorAddress); + var actualBondedPoolNCG = states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken); + var actualUnbondedPoolNCG = states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken); + var actualCommunityPoolNCG = states.GetBalance(ReservedAddress.CommunityPool, Asset.GovernanceToken); + + Assert.Equal(expectedSrcShare, actualSrcShare); + Assert.Equal(expectedDstShare, actualDstShare); + Assert.Equal(expectedSrcConsensusToken, actualSrcConsensusToken); + Assert.Equal(expectedDstConsensusToken, actualDstConsensusToken); + Assert.Equal(expectedBondedPoolNCG, actualBondedPoolNCG); + Assert.Equal(expectedUnbondedPoolNCG, actualUnbondedPoolNCG); + Assert.Equal(expectedCommunityPoolNCG, actualCommunityPoolNCG); + + _states = states; + } + + [Fact] + public void Slash_InvalidValidatorAddress_FailTest() + { + var states = _states; + var validatorAddress = CreateAddress(); + + Assert.Throws(() => + { + SlashCtrl.Slash( + world: states, + actionContext: new ActionContext { PreviousState = states, }, + validatorAddress: validatorAddress, + infractionHeight: 2, + power: 100, + slashFactor: 20, + nativeTokens: NativeTokens); + }); + } + + [Fact] + public void Slash_NegativeSlashFactor_FailTest() + { + var states = _states; + var operatorPublicKey = _operatorPublicKeys[0]; + var validatorAddress = _validatorAddresses[0]; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: new FungibleAssetValue(Asset.GovernanceToken, 100, 0)); + + Assert.Throws(() => + { + SlashCtrl.Slash( + world: states, + actionContext: new ActionContext { PreviousState = states, }, + validatorAddress: validatorAddress, + infractionHeight: 2, + power: 100, + slashFactor: -1, + nativeTokens: NativeTokens); + }); + } + + [Fact] + public void Slash_FutureBlockHeight_FailTest() + { + var validatorNCG = _defaultNCG; + var consensusToken = Asset.ConsensusFromGovernance(validatorNCG); + var slashFactor = _slashFactor; + + var operatorPublicKey = _operatorPublicKeys[0]; + var validatorAddress = _validatorAddresses[0]; + var states = _states; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: validatorNCG); + states = Update( + states: states, + blockIndex: 1); + + Assert.Throws(() => + { + SlashCtrl.Slash( + world: states, + actionContext: new ActionContext { PreviousState = states, BlockIndex = 2, }, + validatorAddress: validatorAddress, + infractionHeight: 3, + power: consensusToken.RawValue, + slashFactor: slashFactor, + nativeTokens: NativeTokens); + }); + } + + [Fact] + public void Unjail_Test() + { + var validatorNCG = _defaultNCG; + var operatorPublicKey = _operatorPublicKeys[0]; + var validatorAddress = _validatorAddresses[0]; + var states = _states; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: validatorNCG); + states = Update( + states: states, + blockIndex: 1); + states = Jail( + states: states, + validatorAddress: validatorAddress); + + states = SlashCtrl.Unjail( + world: states, + actionContext: new ActionContext { PreviousState = states, BlockIndex = 2, }, + validatorAddress: validatorAddress); + + var actualValidator = ValidatorCtrl.GetValidator(states, validatorAddress); + Assert.False(actualValidator!.Jailed); + + _states = states; + } + + [Fact] + public void Unjail_NotPromotedValidator_FailTest() + { + var states = _states; + var validatorAddress = _validatorAddresses[0]; + + Assert.Throws(() => + { + SlashCtrl.Unjail( + world: states, + actionContext: new ActionContext { PreviousState = states, BlockIndex = 2, }, + validatorAddress: validatorAddress); + }); + } + + [Fact] + public void Unjail_NotJailedValidator_FailTest() + { + var validatorNCG = _defaultNCG; + var operatorPublicKey = _operatorPublicKeys[0]; + var validatorAddress = _validatorAddresses[0]; + var states = _states; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: validatorNCG); + + Assert.Throws(() => + { + SlashCtrl.Unjail( + world: states, + actionContext: new ActionContext { PreviousState = states, BlockIndex = 2, }, + validatorAddress: validatorAddress); + }); + } + + [Fact] + public void Unjail_Tombstoned_FailTest() + { + var validatorNCG = _defaultNCG; + var operatorPublicKey = _operatorPublicKeys[0]; + var validatorAddress = _validatorAddresses[0]; + var states = _states; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: validatorNCG); + states = Tombstone( + states: states, + validatorAddress: validatorAddress); + + Assert.Throws(() => + { + SlashCtrl.Unjail( + world: states, + actionContext: new ActionContext { PreviousState = states, BlockIndex = 2, }, + validatorAddress: validatorAddress); + }); + } + + [Fact] + public void Unjail_StillJailed_FailTest() + { + var validatorNCG = _defaultNCG; + var operatorPublicKey = _operatorPublicKeys[0]; + var validatorAddress = _validatorAddresses[0]; + var states = _states; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: validatorNCG); + states = Update( + states: states, + blockIndex: 1); + states = JailUntil( + states: states, + validatorAddress: validatorAddress, + blockHeight: long.MaxValue); + + Assert.Throws(() => + { + SlashCtrl.Unjail( + world: states, + actionContext: new ActionContext { PreviousState = states, BlockIndex = 1, }, + validatorAddress: validatorAddress); + }); + } + + [Fact] + public void Unjail_WithoutDelegation_FailTest() + { + throw new NotImplementedException(); + } + + [Fact] + public void Unjail_PowerIsLessThanMinimum_FailTest() + { + var validatorNCG = + Asset.GovernanceFromConsensus(Validator.MinSelfDelegation) + + new FungibleAssetValue(Asset.GovernanceToken, 0, 1); + var operatorPublicKey = _operatorPublicKeys[0]; + var validatorAddress = _validatorAddresses[0]; + var states = _states; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: validatorNCG); + states = Update( + states: states, + blockIndex: 1); + states = Slash( + states: states, + blockIndex: 2, + validatorAddress: validatorAddress, + infractionHeight: 1, + power: Asset.ConsensusFromGovernance(validatorNCG).RawValue, + slashFactor: 20); + states = JailUntil( + states: states, + validatorAddress: validatorAddress, + blockHeight: long.MaxValue); + + Assert.Throws(() => + { + SlashCtrl.Unjail( + world: states, + actionContext: new ActionContext { PreviousState = states, BlockIndex = 2, }, + validatorAddress: validatorAddress); + }); + } + + private static FungibleAssetValue SlashAsset(FungibleAssetValue value, BigInteger factor) + { + var (amount, _) = value.DivRem(factor); + return value - amount; + } + + private static FungibleAssetValue GetPower(IWorldState worldState, Address validatorAddress) + { + return worldState.GetBalance( + address: validatorAddress, + currency: Asset.ConsensusToken); + } + + private static FungibleAssetValue GetShare(IWorldState worldState, Address validatorAddress) + { + var validator = ValidatorCtrl.GetValidator(worldState, validatorAddress)!; + return validator.DelegatorShares; + } + + private static FungibleAssetValue GetShare( + IWorldState worldState, + Address validatorAddress, + FungibleAssetValue consensusToken) + { + var share = ValidatorCtrl.ShareFromConsensusToken( + worldState, + validatorAddress, + consensusToken); + return share ?? new FungibleAssetValue(Asset.Share); + } + } +} diff --git a/.Lib9c.Tests/Action/DPoS/Control/ValidatorCtrlTest.cs b/.Lib9c.Tests/Action/DPoS/Control/ValidatorCtrlTest.cs index f16c88ba05..0af6b5ff0d 100644 --- a/.Lib9c.Tests/Action/DPoS/Control/ValidatorCtrlTest.cs +++ b/.Lib9c.Tests/Action/DPoS/Control/ValidatorCtrlTest.cs @@ -17,6 +17,9 @@ public class ValidatorCtrlTest : PoSTest private readonly Address _operatorAddress; private readonly Address _validatorAddress; private readonly ImmutableHashSet _nativeTokens; + private readonly FungibleAssetValue _governanceToken + = new FungibleAssetValue(Asset.GovernanceToken, 100, 0); + private IWorld _states; public ValidatorCtrlTest() @@ -120,5 +123,154 @@ public void BalanceTest(int mintAmount, int selfDelegateAmount) Asset.Share * selfDelegateAmount, ValidatorCtrl.GetValidator(_states, _validatorAddress)!.DelegatorShares); } + + [Fact] + public void JailTest() + { + var governanceToken = _governanceToken; + var states = _states; + var operatorPublicKey = _operatorPublicKey; + var validatorAddress = _validatorAddress; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: governanceToken); + + // Test before jailing + var validator1 = ValidatorCtrl.GetValidator(states, validatorAddress)!; + var powerIndex1 = ValidatorPowerIndexCtrl.GetValidatorPowerIndex(states)!; + Assert.False(validator1.Jailed); + Assert.Contains( + powerIndex1.ValidatorAddresses, + address => address.Equals(validatorAddress)); + + // Jail + states = ValidatorCtrl.Jail( + states, + validatorAddress: validatorAddress); + + // Test after jailing + var validator2 = ValidatorCtrl.GetValidator(states, validatorAddress)!; + var powerIndex2 = ValidatorPowerIndexCtrl.GetValidatorPowerIndex(states)!; + + Assert.True(validator2.Jailed); + Assert.DoesNotContain( + powerIndex2.ValidatorAddresses, + address => address.Equals(validatorAddress)); + } + + [Fact] + public void Jail_NotPromotedValidator_FailTest() + { + Assert.Throws(() => + { + ValidatorCtrl.Jail( + world: _states, + validatorAddress: _validatorAddress); + }); + } + + [Fact] + public void Jail_JailedValidator_FailTest() + { + var governanceToken = _governanceToken; + var states = _states; + var operatorPublicKey = _operatorPublicKey; + var validatorAddress = _validatorAddress; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: governanceToken); + + // Jail + states = ValidatorCtrl.Jail( + states, + validatorAddress: validatorAddress); + + Assert.Throws(() => + { + ValidatorCtrl.Jail( + world: states, + validatorAddress: validatorAddress); + }); + } + + [Fact] + public void UnjailTest() + { + var governanceToken = _governanceToken; + var states = _states; + var operatorPublicKey = _operatorPublicKey; + var validatorAddress = _validatorAddress; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: governanceToken); + + states = ValidatorCtrl.Jail( + states, + validatorAddress: _validatorAddress); + + // Test before unjailing + var validator1 = ValidatorCtrl.GetValidator(states, validatorAddress)!; + var powerIndex1 = ValidatorPowerIndexCtrl.GetValidatorPowerIndex(states)!; + + Assert.True(validator1.Jailed); + Assert.DoesNotContain( + powerIndex1.ValidatorAddresses, + address => address.Equals(_validatorAddress)); + + // Unjail + states = ValidatorCtrl.Unjail( + states, + validatorAddress: validatorAddress); + + // Test after unjailing + var validator2 = ValidatorCtrl.GetValidator(states, validatorAddress)!; + var powerIndex2 = ValidatorPowerIndexCtrl.GetValidatorPowerIndex(states)!; + Assert.False(validator2.Jailed); + Assert.Contains( + powerIndex2.ValidatorAddresses, + address => address.Equals(validatorAddress)); + } + + [Fact] + public void Unjail_NotPromotedValidator_FailTest() + { + Assert.Throws(() => + { + ValidatorCtrl.Unjail( + world: _states, + validatorAddress: _validatorAddress); + }); + } + + [Fact] + public void Unjail_NotJailedValidator_FailTest() + { + var governanceToken = _governanceToken; + var states = _states; + var operatorPublicKey = _operatorPublicKey; + var validatorAddress = _validatorAddress; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: governanceToken); + + Assert.Throws(() => + { + ValidatorCtrl.Unjail( + world: states, + validatorAddress: validatorAddress); + }); + } } } diff --git a/.Lib9c.Tests/Action/DPoS/Control/ValidatorSigningInfoCtrlTest.cs b/.Lib9c.Tests/Action/DPoS/Control/ValidatorSigningInfoCtrlTest.cs new file mode 100644 index 0000000000..2737fc152f --- /dev/null +++ b/.Lib9c.Tests/Action/DPoS/Control/ValidatorSigningInfoCtrlTest.cs @@ -0,0 +1,218 @@ +namespace Lib9c.Tests.Action.DPoS.Control +{ + using System; + using Libplanet.Action.State; + using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume.Action.DPoS.Control; + using Nekoyume.Action.DPoS.Exception; + using Nekoyume.Action.DPoS.Misc; + using Nekoyume.Action.DPoS.Model; + using Xunit; + + public class ValidatorSigningInfoCtrlTest : PoSTest + { + private readonly PublicKey _operatorPublicKey; + private readonly Address _operatorAddress; + private readonly Address _delegatorAddress; + private readonly Address _validatorAddress; + private readonly FungibleAssetValue _governanceToken + = new FungibleAssetValue(Asset.GovernanceToken, 100, 0); + + private IWorld _states; + + public ValidatorSigningInfoCtrlTest() + { + _operatorPublicKey = new PrivateKey().PublicKey; + _operatorAddress = _operatorPublicKey.Address; + _delegatorAddress = CreateAddress(); + _validatorAddress = Validator.DeriveAddress(_operatorAddress); + _states = InitializeStates(); + } + + [Fact] + public void SetTest() + { + var signingInfo = new ValidatorSigningInfo + { + Address = _validatorAddress, + }; + + _states = ValidatorSigningInfoCtrl.SetSigningInfo( + world: _states, + signingInfo: signingInfo); + } + + [Fact] + public void GetTest() + { + var signingInfo1 = ValidatorSigningInfoCtrl.GetSigningInfo(_states, _validatorAddress); + Assert.Null(signingInfo1); + + var signingInfo2 = new ValidatorSigningInfo + { + Address = _validatorAddress, + }; + _states = ValidatorSigningInfoCtrl.SetSigningInfo( + world: _states, + signingInfo: signingInfo2); + + var signingInfo3 = ValidatorSigningInfoCtrl.GetSigningInfo(_states, _validatorAddress); + Assert.NotNull(signingInfo3); + Assert.Equal(_validatorAddress, signingInfo3.Address); + Assert.Equal(signingInfo2, signingInfo3); + } + + [Fact] + public void Tombstone_Test() + { + var governanceToken = _governanceToken; + var states = _states; + var operatorPublicKey = _operatorPublicKey; + var validatorAddress = _validatorAddress; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: governanceToken); + + states = ValidatorSigningInfoCtrl.Tombstone(states, validatorAddress); + + Assert.True(ValidatorSigningInfoCtrl.IsTombstoned(states, validatorAddress)); + } + + [Fact] + public void Tombstone_NotPromotedValidator_FailTest() + { + var states = _states; + var validatorAddress = _validatorAddress; + + Assert.Throws(() => + { + ValidatorSigningInfoCtrl.Tombstone(states, validatorAddress); + }); + } + + [Fact] + public void Tombstone_TombstonedValidator_FailTest() + { + var governanceToken = _governanceToken; + var states = _states; + var operatorPublicKey = _operatorPublicKey; + var validatorAddress = _validatorAddress; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: governanceToken); + + states = ValidatorSigningInfoCtrl.Tombstone(states, validatorAddress); + + Assert.Throws(() => + { + ValidatorSigningInfoCtrl.Tombstone(states, validatorAddress); + }); + } + + [Fact] + public void JailUtil_Test() + { + var governanceToken = _governanceToken; + var states = _states; + var operatorPublicKey = _operatorPublicKey; + var validatorAddress = _validatorAddress; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: governanceToken); + + states = ValidatorSigningInfoCtrl.JailUntil( + world: states, + validatorAddress: validatorAddress, + blockHeight: 2); + + var signingInfo = ValidatorSigningInfoCtrl.GetSigningInfo(states, validatorAddress)!; + + Assert.NotNull(signingInfo); + Assert.Equal(2, signingInfo.JailedUntil); + } + + [Fact] + public void JailUtil_NotPromotedValidator_FailTest() + { + var states = _states; + var validatorAddress = _validatorAddress; + + Assert.Throws(() => + { + ValidatorSigningInfoCtrl.JailUntil(states, validatorAddress, 2); + }); + } + + [Fact] + public void JailUtil_NegativeBlockHeight_FailTest() + { + var governanceToken = _governanceToken; + var states = _states; + var operatorPublicKey = _operatorPublicKey; + var validatorAddress = _validatorAddress; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: governanceToken); + + Assert.Throws(() => + { + ValidatorSigningInfoCtrl.JailUntil(states, validatorAddress, -1); + }); + } + + [Fact] + public void JailUtil_Twice_Test() + { + var governanceToken = _governanceToken; + var states = _states; + var operatorPublicKey = _operatorPublicKey; + var validatorAddress = _validatorAddress; + + states = Promote( + states: states, + blockIndex: 1, + operatorPublicKey: operatorPublicKey, + governanceToken: governanceToken); + + states = ValidatorSigningInfoCtrl.JailUntil( + world: states, + validatorAddress: validatorAddress, + blockHeight: 2); + + // Set block height to greater than current + states = ValidatorSigningInfoCtrl.JailUntil( + world: states, + validatorAddress: validatorAddress, + blockHeight: 3); + + var signingInfo1 = ValidatorSigningInfoCtrl.GetSigningInfo(states, validatorAddress)!; + + Assert.NotNull(signingInfo1); + Assert.Equal(3, signingInfo1.JailedUntil); + + // Set block height to lower than current + states = ValidatorSigningInfoCtrl.JailUntil( + world: states, + validatorAddress: validatorAddress, + blockHeight: 1); + + var signingInfo2 = ValidatorSigningInfoCtrl.GetSigningInfo(states, validatorAddress)!; + + Assert.NotNull(signingInfo2); + Assert.Equal(1, signingInfo2.JailedUntil); + } + } +} diff --git a/.Lib9c.Tests/Action/DPoS/PoSTest.cs b/.Lib9c.Tests/Action/DPoS/PoSTest.cs index c2b1d1b4ca..0db78dcdbd 100644 --- a/.Lib9c.Tests/Action/DPoS/PoSTest.cs +++ b/.Lib9c.Tests/Action/DPoS/PoSTest.cs @@ -1,10 +1,20 @@ namespace Lib9c.Tests.Action.DPoS { + using System.Collections.Immutable; + using System.Net.NetworkInformation; + using System.Numerics; using Libplanet.Action.State; using Libplanet.Crypto; + using Libplanet.Types.Assets; + using Nekoyume.Action.DPoS.Control; + using Nekoyume.Action.DPoS.Misc; + using Nekoyume.Module; public class PoSTest { + protected static readonly ImmutableHashSet NativeTokens = ImmutableHashSet.Create( + Asset.GovernanceToken, Asset.ConsensusToken, Asset.Share); + protected static IWorld InitializeStates() { return new World(new MockWorldState()); @@ -15,5 +25,140 @@ protected static Address CreateAddress() PrivateKey privateKey = new PrivateKey(); return privateKey.Address; } + + protected static IWorld Promote(IWorld states, long blockIndex, PublicKey operatorPublicKey, FungibleAssetValue governanceToken) + { + var operatorAddress = operatorPublicKey.Address; + states = states.MintAsset( + context: new ActionContext { PreviousState = states, BlockIndex = blockIndex }, + recipient: operatorAddress, + value: governanceToken); + states = ValidatorCtrl.Create( + states, + new ActionContext { PreviousState = states, BlockIndex = blockIndex }, + operatorAddress, + operatorPublicKey, + governanceToken, + NativeTokens); + return states; + } + + protected static IWorld Delegate( + IWorld states, + long blockIndex, + Address delegatorAddress, + Address validatorAddress, + FungibleAssetValue governanceToken) + { + states = states.MintAsset( + context: new ActionContext { PreviousState = states, BlockIndex = blockIndex }, + recipient: delegatorAddress, + value: governanceToken); + states = DelegateCtrl.Execute( + states: states, + ctx: new ActionContext { PreviousState = states, BlockIndex = blockIndex }, + delegatorAddress: delegatorAddress, + validatorAddress: validatorAddress, + governanceToken: governanceToken, + nativeTokens: NativeTokens); + return states; + } + + protected static IWorld Undelegate( + IWorld states, + long blockIndex, + Address delegatorAddress, + Address validatorAddress, + FungibleAssetValue share) + { + states = UndelegateCtrl.Execute( + states: states, + ctx: new ActionContext { PreviousState = states, BlockIndex = blockIndex }, + delegatorAddress: delegatorAddress, + validatorAddress: validatorAddress, + share: share, + nativeTokens: NativeTokens); + return states; + } + + protected static IWorld Redelegate( + IWorld states, + long blockIndex, + Address delegatorAddress, + Address srcValidatorAddress, + Address dstValidatorAddress, + FungibleAssetValue share) + { + states = RedelegateCtrl.Execute( + states: states, + ctx: new ActionContext { PreviousState = states, BlockIndex = blockIndex }, + delegatorAddress: delegatorAddress, + srcValidatorAddress: srcValidatorAddress, + dstValidatorAddress: dstValidatorAddress, + redelegatingShare: share, + nativeTokens: NativeTokens); + return states; + } + + protected static IWorld Update( + IWorld states, + long blockIndex) + { + states = ValidatorSetCtrl.Update( + states: states, + ctx: new ActionContext { PreviousState = states, BlockIndex = blockIndex, }); + return states; + } + + protected static IWorld Jail( + IWorld states, + Address validatorAddress) + { + states = ValidatorCtrl.Jail( + world: states, + validatorAddress: validatorAddress); + return states; + } + + protected static IWorld JailUntil( + IWorld states, + Address validatorAddress, + long blockHeight) + { + states = ValidatorCtrl.JailUntil( + world: states, + validatorAddress: validatorAddress, + blockHeight: blockHeight); + return states; + } + + protected static IWorld Slash( + IWorld states, + long blockIndex, + Address validatorAddress, + long infractionHeight, + BigInteger power, + BigInteger slashFactor) + { + states = SlashCtrl.Slash( + world: states, + actionContext: new ActionContext { PreviousState = states, BlockIndex = blockIndex, }, + validatorAddress: validatorAddress, + infractionHeight: infractionHeight, + power: power, + slashFactor: slashFactor, + nativeTokens: NativeTokens); + return states; + } + + protected static IWorld Tombstone( + IWorld states, + Address validatorAddress) + { + states = ValidatorCtrl.Tombstone( + world: states, + validatorAddress: validatorAddress); + return states; + } } }