diff --git a/.Lib9c.Tests/Action/ActionContext.cs b/.Lib9c.Tests/Action/ActionContext.cs index 9c157693b4..1afdbc9f7e 100644 --- a/.Lib9c.Tests/Action/ActionContext.cs +++ b/.Lib9c.Tests/Action/ActionContext.cs @@ -28,6 +28,8 @@ public class ActionContext : IActionContext public int BlockProtocolVersion { get; set; } + public BlockCommit LastCommit { get; set; } + public IWorld PreviousState { get; set; } public int RandomSeed { get; set; } diff --git a/Lib9c.DPoS.Tests/ActionContext.cs b/Lib9c.DPoS.Tests/ActionContext.cs new file mode 100644 index 0000000000..7bb511d6f9 --- /dev/null +++ b/Lib9c.DPoS.Tests/ActionContext.cs @@ -0,0 +1,60 @@ +#nullable disable + +using System.Security.Cryptography; +using Libplanet.Action; +using Libplanet.Action.State; +using Libplanet.Common; +using Libplanet.Crypto; +using Libplanet.Types.Blocks; +using Libplanet.Types.Tx; + +namespace Lib9c.DPoS.Tests +{ + public class ActionContext : IActionContext + { + private long _gasUsed; + + private IRandom _random = null; + + public BlockHash? GenesisHash { get; set; } + + public Address Signer { get; set; } + + public TxId? TxId { get; set; } + + public Address Miner { get; set; } + + public BlockHash BlockHash { get; set; } + + public long BlockIndex { get; set; } + + public int BlockProtocolVersion { get; set; } + + public BlockCommit LastCommit { get; set; } + + public IWorld PreviousState { get; set; } + + public int RandomSeed { get; set; } + + public HashDigest? PreviousStateRootHash { get; set; } + + public bool BlockAction { get; } + + public void UseGas(long gas) + { + _gasUsed += gas; + } + + public IRandom GetRandom() => _random ?? new TestRandom(RandomSeed); + + public long GasUsed() => _gasUsed; + + public long GasLimit() => 0; + + // FIXME: Temporary measure to allow inheriting already mutated IRandom. + public void SetRandom(IRandom random) + { + _random = random; + } + } +} diff --git a/Lib9c.DPoS.Tests/Control/DelegateCtrlTest.cs b/Lib9c.DPoS.Tests/Control/DelegateCtrlTest.cs new file mode 100644 index 0000000000..d0fff28aaf --- /dev/null +++ b/Lib9c.DPoS.Tests/Control/DelegateCtrlTest.cs @@ -0,0 +1,193 @@ +using System.Collections.Immutable; +using Lib9c.DPoS.Control; +using Lib9c.DPoS.Exception; +using Lib9c.DPoS.Misc; +using Lib9c.DPoS.Model; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Module; +using Xunit; + +namespace Lib9c.DPoS.Tests.Control +{ + public class DelegateCtrlTest : PoSTest + { + private readonly PublicKey _operatorPublicKey; + private readonly Address _operatorAddress; + private readonly Address _delegatorAddress; + private readonly Address _validatorAddress; + private ImmutableHashSet _nativeTokens; + private IWorld _states; + + public DelegateCtrlTest() + { + _operatorPublicKey = new PrivateKey().PublicKey; + _operatorAddress = _operatorPublicKey.Address; + _delegatorAddress = CreateAddress(); + _validatorAddress = Validator.DeriveAddress(_operatorAddress); + _nativeTokens = ImmutableHashSet.Create( + Asset.GovernanceToken, Asset.ConsensusToken, Asset.Share); + _states = InitializeStates(); + } + + [Fact] + public void InvalidCurrencyTest() + { + Initialize(500, 500, 100); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + Asset.ConsensusToken * 50); + Assert.Throws( + () => _states = DelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _validatorAddress, + Asset.ConsensusToken * 30, + _nativeTokens)); + } + + [Fact] + public void InvalidValidatorTest() + { + Initialize(500, 500, 100); + Assert.Throws( + () => _states = DelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + CreateAddress(), + Asset.GovernanceToken * 10, + _nativeTokens)); + } + + [Fact] + public void InvalidShareTest() + { + Initialize(500, 500, 100); + _states = _states.BurnAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _validatorAddress, + Asset.ConsensusToken * 100); + Assert.Throws( + () => _states = DelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _validatorAddress, + Asset.GovernanceToken * 10, + _nativeTokens)); + } + + [Theory] + [InlineData(500, 500, 100, 10)] + [InlineData(500, 500, 100, 20)] + public void BalanceTest( + int operatorMintAmount, + int delegatorMintAmount, + int selfDelegateAmount, + int delegateAmount) + { + Initialize(operatorMintAmount, delegatorMintAmount, selfDelegateAmount); + _states = DelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _validatorAddress, + Asset.GovernanceToken * delegateAmount, + _nativeTokens); + Assert.Equal( + Asset.GovernanceToken * 0, + _states.GetBalance(_validatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.ConsensusToken * 0, + _states.GetBalance(_operatorAddress, Asset.ConsensusToken)); + Assert.Equal( + Asset.ConsensusToken * 0, + _states.GetBalance(_delegatorAddress, Asset.ConsensusToken)); + Assert.Equal( + Asset.Share * 0, + _states.GetBalance(_operatorAddress, Asset.Share)); + Assert.Equal( + Asset.Share * 0, + _states.GetBalance(_delegatorAddress, Asset.Share)); + Assert.Equal( + Asset.ConsensusToken * (selfDelegateAmount + delegateAmount), + _states.GetBalance(_validatorAddress, Asset.ConsensusToken)); + Assert.Equal( + Asset.GovernanceToken * (operatorMintAmount - selfDelegateAmount), + _states.GetBalance(_operatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken * (delegatorMintAmount - delegateAmount), + _states.GetBalance(_delegatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken * (selfDelegateAmount + delegateAmount), + _states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken)); + Assert.Equal( + ValidatorCtrl.GetValidator(_states, _validatorAddress)!.DelegatorShares, + _states.GetBalance( + Delegation.DeriveAddress(_operatorAddress, _validatorAddress), Asset.Share) + + _states.GetBalance( + Delegation.DeriveAddress(_delegatorAddress, _validatorAddress), Asset.Share)); + } + + private void Initialize( + int operatorMintAmount, int delegatorMintAmount, int selfDelegateAmount) + { + _states = InitializeStates(); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _operatorAddress, + Asset.GovernanceToken * operatorMintAmount); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + Asset.GovernanceToken * delegatorMintAmount); + _states = ValidatorCtrl.Create( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _operatorAddress, + _operatorPublicKey, + Asset.GovernanceToken * selfDelegateAmount, + _nativeTokens); + } + } +} diff --git a/Lib9c.DPoS.Tests/Control/RedelegateCtrlTest.cs b/Lib9c.DPoS.Tests/Control/RedelegateCtrlTest.cs new file mode 100644 index 0000000000..862f2c0575 --- /dev/null +++ b/Lib9c.DPoS.Tests/Control/RedelegateCtrlTest.cs @@ -0,0 +1,375 @@ +using System.Collections.Immutable; +using Lib9c.DPoS.Action; +using Lib9c.DPoS.Control; +using Lib9c.DPoS.Exception; +using Lib9c.DPoS.Misc; +using Lib9c.DPoS.Model; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Module; +using Xunit; + +namespace Lib9c.DPoS.Tests.Control +{ + public class RedelegateCtrlTest : PoSTest + { + private readonly PublicKey _srcOperatorPublicKey; + private readonly PublicKey _dstOperatorPublicKey; + private readonly Address _srcOperatorAddress; + private readonly Address _dstOperatorAddress; + private readonly Address _delegatorAddress; + private readonly Address _srcValidatorAddress; + private readonly Address _dstValidatorAddress; + private readonly Address _redelegationAddress; + private ImmutableHashSet _nativeTokens; + private IWorld _states; + + public RedelegateCtrlTest() + { + _srcOperatorPublicKey = new PrivateKey().PublicKey; + _dstOperatorPublicKey = new PrivateKey().PublicKey; + _srcOperatorAddress = _srcOperatorPublicKey.Address; + _dstOperatorAddress = _dstOperatorPublicKey.Address; + _delegatorAddress = CreateAddress(); + _srcValidatorAddress = Validator.DeriveAddress(_srcOperatorAddress); + _dstValidatorAddress = Validator.DeriveAddress(_dstOperatorAddress); + _redelegationAddress = Redelegation.DeriveAddress( + _delegatorAddress, _srcValidatorAddress, _dstValidatorAddress); + _nativeTokens = ImmutableHashSet.Create( + Asset.GovernanceToken, Asset.ConsensusToken, Asset.Share); + _states = InitializeStates(); + } + + [Fact] + public void InvalidCurrencyTest() + { + Initialize(500, 500, 100, 100); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + Asset.ConsensusToken * 50); + Assert.Throws( + () => _states = RedelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _srcValidatorAddress, + _dstValidatorAddress, + Asset.ConsensusToken * 30, + _nativeTokens)); + Assert.Throws( + () => _states = RedelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _srcValidatorAddress, + _dstValidatorAddress, + Asset.GovernanceToken * 30, + _nativeTokens)); + } + + [Fact] + public void InvalidValidatorTest() + { + Initialize(500, 500, 100, 100); + Assert.Throws( + () => _states = RedelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + CreateAddress(), + _dstValidatorAddress, + Asset.Share * 10, + _nativeTokens)); + Assert.Throws( + () => _states = RedelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _srcValidatorAddress, + CreateAddress(), + Asset.Share * 10, + _nativeTokens)); + } + + [Fact] + public void MaxEntriesTest() + { + Initialize(500, 500, 100, 100); + for (long i = 0; i < 10; i++) + { + _states = RedelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = i, + }, + _delegatorAddress, + _srcValidatorAddress, + _dstValidatorAddress, + Asset.Share * 1, + _nativeTokens); + } + + Assert.Throws( + () => _states = RedelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _srcValidatorAddress, + _dstValidatorAddress, + Asset.Share * 1, + _nativeTokens)); + } + + [Fact] + public void ExceedRedelegateTest() + { + Initialize(500, 500, 100, 100); + Assert.Throws( + () => _states = RedelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _srcValidatorAddress, + _dstValidatorAddress, + Asset.Share * 101, + _nativeTokens)); + } + + [Theory] + [InlineData(500, 500, 100, 100, 100)] + [InlineData(500, 500, 100, 100, 50)] + public void CompleteRedelegationTest( + int operatorMintAmount, + int delegatorMintAmount, + int selfDelegateAmount, + int delegateAmount, + int redelegateAmount) + { + Initialize(operatorMintAmount, delegatorMintAmount, selfDelegateAmount, delegateAmount); + _states = RedelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _srcValidatorAddress, + _dstValidatorAddress, + Asset.Share * redelegateAmount, + _nativeTokens); + Assert.Single( + RedelegateCtrl.GetRedelegation(_states, _redelegationAddress)! + .RedelegationEntryAddresses); + _states = RedelegateCtrl.Complete( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1000, + }, + _redelegationAddress); + Assert.Single( + RedelegateCtrl.GetRedelegation(_states, _redelegationAddress)! + .RedelegationEntryAddresses); + _states = RedelegateCtrl.Complete( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 50400 * 5, + }, + _redelegationAddress); + Assert.Empty( + RedelegateCtrl.GetRedelegation(_states, _redelegationAddress)! + .RedelegationEntryAddresses); + } + + [Theory] + [InlineData(500, 500, 100, 100, 100)] + [InlineData(500, 500, 100, 100, 50)] + public void BalanceTest( + int operatorMintAmount, + int delegatorMintAmount, + int selfDelegateAmount, + int delegateAmount, + int redelegateAmount) + { + Initialize(operatorMintAmount, delegatorMintAmount, selfDelegateAmount, delegateAmount); + _states = RedelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _srcValidatorAddress, + _dstValidatorAddress, + Asset.Share * redelegateAmount, + _nativeTokens); + Assert.Equal( + Asset.GovernanceToken * 0, + _states.GetBalance(_srcValidatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken * 0, + _states.GetBalance(_dstValidatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.ConsensusToken * 0, + _states.GetBalance(_srcOperatorAddress, Asset.ConsensusToken)); + Assert.Equal( + Asset.ConsensusToken * 0, + _states.GetBalance(_dstOperatorAddress, Asset.ConsensusToken)); + Assert.Equal( + Asset.ConsensusToken * 0, + _states.GetBalance(_delegatorAddress, Asset.ConsensusToken)); + Assert.Equal( + Asset.Share * 0, + _states.GetBalance(_srcOperatorAddress, Asset.Share)); + Assert.Equal( + Asset.Share * 0, + _states.GetBalance(_dstOperatorAddress, Asset.Share)); + Assert.Equal( + Asset.Share * 0, + _states.GetBalance(_delegatorAddress, Asset.Share)); + Assert.Equal( + Asset.GovernanceToken * (operatorMintAmount - selfDelegateAmount), + _states.GetBalance(_srcOperatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken * (operatorMintAmount - selfDelegateAmount), + _states.GetBalance(_dstOperatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken * (delegatorMintAmount - delegateAmount), + _states.GetBalance(_delegatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken * (2 * selfDelegateAmount + delegateAmount), + _states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken)); + Assert.Equal( + ValidatorCtrl.GetValidator(_states, _srcValidatorAddress)!.DelegatorShares, + _states.GetBalance( + Delegation.DeriveAddress( + _srcOperatorAddress, _srcValidatorAddress), Asset.Share) + + _states.GetBalance( + Delegation.DeriveAddress( + _delegatorAddress, _srcValidatorAddress), Asset.Share)); + Assert.Equal( + ValidatorCtrl.GetValidator(_states, _dstValidatorAddress)!.DelegatorShares, + _states.GetBalance( + Delegation.DeriveAddress( + _dstOperatorAddress, _dstValidatorAddress), Asset.Share) + + _states.GetBalance( + Delegation.DeriveAddress( + _delegatorAddress, _dstValidatorAddress), Asset.Share)); + RedelegationEntry entry = new RedelegationEntry( + _states.GetDPoSState( + RedelegateCtrl.GetRedelegation(_states, _redelegationAddress)! + .RedelegationEntryAddresses[0])!); + Assert.Equal( + Asset.ConsensusToken * (selfDelegateAmount + delegateAmount) + - entry.UnbondingConsensusToken, + _states.GetBalance(_srcValidatorAddress, Asset.ConsensusToken)); + Assert.Equal( + Asset.ConsensusToken * selfDelegateAmount + + entry.UnbondingConsensusToken, + _states.GetBalance(_dstValidatorAddress, Asset.ConsensusToken)); + } + + private void Initialize( + int operatorMintAmount, + int delegatorMintAmount, + int selfDelegateAmount, + int delegateAmount) + { + _states = InitializeStates(); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _srcOperatorAddress, + Asset.GovernanceToken * operatorMintAmount); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _dstOperatorAddress, + Asset.GovernanceToken * operatorMintAmount); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + Asset.GovernanceToken * delegatorMintAmount); + _states = ValidatorCtrl.Create( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _srcOperatorAddress, + _srcOperatorPublicKey, + Asset.GovernanceToken * selfDelegateAmount, + _nativeTokens); + _states = ValidatorCtrl.Create( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _dstOperatorAddress, + _dstOperatorPublicKey, + Asset.GovernanceToken * selfDelegateAmount, + _nativeTokens); + _states = DelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _srcValidatorAddress, + Asset.GovernanceToken * delegateAmount, + _nativeTokens); + } + } +} diff --git a/Lib9c.DPoS.Tests/Control/UndelegateCtrlTest.cs b/Lib9c.DPoS.Tests/Control/UndelegateCtrlTest.cs new file mode 100644 index 0000000000..4bfb53041a --- /dev/null +++ b/Lib9c.DPoS.Tests/Control/UndelegateCtrlTest.cs @@ -0,0 +1,393 @@ +using System.Collections.Immutable; +using Lib9c.DPoS.Control; +using Lib9c.DPoS.Exception; +using Lib9c.DPoS.Misc; +using Lib9c.DPoS.Model; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Module; +using Xunit; + +namespace Lib9c.DPoS.Tests.Control +{ + public class UndelegateCtrlTest : PoSTest + { + private readonly PublicKey _operatorPublicKey; + private readonly Address _operatorAddress; + private readonly Address _delegatorAddress; + private readonly Address _validatorAddress; + private readonly Address _undelegationAddress; + private ImmutableHashSet _nativeTokens; + private IWorld _states; + + public UndelegateCtrlTest() + { + _operatorPublicKey = new PrivateKey().PublicKey; + _operatorAddress = _operatorPublicKey.Address; + _delegatorAddress = CreateAddress(); + _validatorAddress = Validator.DeriveAddress(_operatorAddress); + _undelegationAddress = Undelegation.DeriveAddress(_delegatorAddress, _validatorAddress); + _nativeTokens = ImmutableHashSet.Create( + Asset.GovernanceToken, Asset.ConsensusToken, Asset.Share); + _states = InitializeStates(); + } + + [Fact] + public void InvalidCurrencyTest() + { + Initialize(500, 500, 100, 100); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + Asset.ConsensusToken * 50); + Assert.Throws( + () => _states = UndelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _validatorAddress, + Asset.ConsensusToken * 30, + _nativeTokens)); + Assert.Throws( + () => _states = UndelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _validatorAddress, + Asset.GovernanceToken * 30, + _nativeTokens)); + } + + [Fact] + public void InvalidValidatorTest() + { + Initialize(500, 500, 100, 100); + Assert.Throws( + () => _states = UndelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + CreateAddress(), + Asset.Share * 10, + _nativeTokens)); + } + + [Fact] + public void MaxEntriesTest() + { + Initialize(500, 500, 100, 100); + for (long i = 0; i < 10; i++) + { + _states = UndelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = i, + }, + _delegatorAddress, + _validatorAddress, + Asset.Share * 1, + _nativeTokens); + } + + Assert.Throws( + () => _states = UndelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _validatorAddress, + Asset.Share * 1, + _nativeTokens)); + } + + [Fact] + public void ExceedUndelegateTest() + { + Initialize(500, 500, 100, 100); + Assert.Throws( + () => _states = UndelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _validatorAddress, + Asset.Share * 101, + _nativeTokens)); + } + + [Theory] + [InlineData(500, 500, 100, 100, 100)] + [InlineData(500, 500, 100, 100, 50)] + public void CompleteUnbondingTest( + int operatorMintAmount, + int delegatorMintAmount, + int selfDelegateAmount, + int delegateAmount, + int undelegateAmount) + { + Initialize(operatorMintAmount, delegatorMintAmount, selfDelegateAmount, delegateAmount); + _states = UndelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _validatorAddress, + Asset.Share * undelegateAmount, + _nativeTokens); + Assert.Single( + UndelegateCtrl.GetUndelegation(_states, _undelegationAddress)! + .UndelegationEntryAddresses); + Assert.Equal( + Asset.GovernanceToken * (delegatorMintAmount - delegateAmount), + _states.GetBalance(_delegatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken * (selfDelegateAmount + delegateAmount), + _states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken)); + _states = UndelegateCtrl.Complete( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1000, + }, + _undelegationAddress); + Assert.Single(UndelegateCtrl.GetUndelegation(_states, _undelegationAddress)! + .UndelegationEntryAddresses); + Assert.Equal( + Asset.GovernanceToken * (delegatorMintAmount - delegateAmount), + _states.GetBalance(_delegatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken * (selfDelegateAmount + delegateAmount), + _states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken)); + _states = UndelegateCtrl.Complete( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 50400 * 5, + }, + _undelegationAddress); + Assert.Empty(UndelegateCtrl.GetUndelegation(_states, _undelegationAddress)! + .UndelegationEntryAddresses); + Assert.Equal( + Asset.GovernanceToken * (delegatorMintAmount - delegateAmount + undelegateAmount), + _states.GetBalance(_delegatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken * (selfDelegateAmount + delegateAmount - undelegateAmount), + _states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken)); + } + + [Theory] + [InlineData(500, 500, 100, 100, 100, 30)] + [InlineData(500, 500, 100, 100, 50, 30)] + public void CancelUndelegateTest( + int operatorMintAmount, + int delegatorMintAmount, + int selfDelegateAmount, + int delegateAmount, + int undelegateAmount, + int cancelAmount) + { + Initialize(operatorMintAmount, delegatorMintAmount, selfDelegateAmount, delegateAmount); + _states = UndelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _validatorAddress, + Asset.Share * undelegateAmount, + _nativeTokens); + Assert.Equal( + Asset.GovernanceToken * (delegatorMintAmount - delegateAmount), + _states.GetBalance(_delegatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.ConsensusToken * (selfDelegateAmount + delegateAmount - undelegateAmount), + _states.GetBalance(_validatorAddress, Asset.ConsensusToken)); + _states = UndelegateCtrl.Cancel( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 2, + }, + _undelegationAddress, + Asset.ConsensusToken * cancelAmount, + _nativeTokens); + Assert.Equal( + Asset.GovernanceToken * (delegatorMintAmount - delegateAmount), + _states.GetBalance(_delegatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.ConsensusToken + * (selfDelegateAmount + delegateAmount - undelegateAmount + cancelAmount), + _states.GetBalance(_validatorAddress, Asset.ConsensusToken)); + _states = UndelegateCtrl.Complete( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1000, + }, + _undelegationAddress); + Assert.Equal( + Asset.GovernanceToken * (delegatorMintAmount - delegateAmount), + _states.GetBalance(_delegatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.ConsensusToken + * (selfDelegateAmount + delegateAmount - undelegateAmount + cancelAmount), + _states.GetBalance(_validatorAddress, Asset.ConsensusToken)); + _states = UndelegateCtrl.Complete( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 50400 * 5, + }, + _undelegationAddress); + Assert.Equal( + Asset.GovernanceToken + * (delegatorMintAmount - delegateAmount + undelegateAmount - cancelAmount), + _states.GetBalance(_delegatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.ConsensusToken + * (selfDelegateAmount + delegateAmount - undelegateAmount + cancelAmount), + _states.GetBalance(_validatorAddress, Asset.ConsensusToken)); + } + + [Theory] + [InlineData(500, 500, 100, 100, 100)] + [InlineData(500, 500, 100, 100, 50)] + public void BalanceTest( + int operatorMintAmount, + int delegatorMintAmount, + int selfDelegateAmount, + int delegateAmount, + int undelegateAmount) + { + Initialize(operatorMintAmount, delegatorMintAmount, selfDelegateAmount, delegateAmount); + _states = UndelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _validatorAddress, + Asset.Share * undelegateAmount, + _nativeTokens); + Assert.Equal( + Asset.GovernanceToken * 0, + _states.GetBalance(_validatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.ConsensusToken * 0, + _states.GetBalance(_operatorAddress, Asset.ConsensusToken)); + Assert.Equal( + Asset.ConsensusToken * 0, + _states.GetBalance(_delegatorAddress, Asset.ConsensusToken)); + Assert.Equal( + Asset.Share * 0, + _states.GetBalance(_operatorAddress, Asset.Share)); + Assert.Equal( + Asset.Share * 0, + _states.GetBalance(_delegatorAddress, Asset.Share)); + Assert.Equal( + Asset.GovernanceToken * (operatorMintAmount - selfDelegateAmount), + _states.GetBalance(_operatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken * (delegatorMintAmount - delegateAmount), + _states.GetBalance(_delegatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken * (selfDelegateAmount + delegateAmount), + _states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken)); + Assert.Equal( + ValidatorCtrl.GetValidator(_states, _validatorAddress)!.DelegatorShares, + _states.GetBalance( + Delegation.DeriveAddress( + _operatorAddress, _validatorAddress), Asset.Share) + + _states.GetBalance( + Delegation.DeriveAddress( + _delegatorAddress, _validatorAddress), Asset.Share)); + Assert.Equal( + Asset.ConsensusToken * (selfDelegateAmount + delegateAmount - undelegateAmount), + _states.GetBalance(_validatorAddress, Asset.ConsensusToken)); + } + + private void Initialize( + int operatorMintAmount, + int delegatorMintAmount, + int selfDelegateAmount, + int delegateAmount) + { + _states = InitializeStates(); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _operatorAddress, + Asset.GovernanceToken * operatorMintAmount); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + Asset.GovernanceToken * delegatorMintAmount); + _states = ValidatorCtrl.Create( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _operatorAddress, + _operatorPublicKey, + Asset.GovernanceToken * selfDelegateAmount, + _nativeTokens); + _states = DelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _delegatorAddress, + _validatorAddress, + Asset.GovernanceToken * delegateAmount, + _nativeTokens); + } + } +} diff --git a/Lib9c.DPoS.Tests/Control/ValidatorCtrlTest.cs b/Lib9c.DPoS.Tests/Control/ValidatorCtrlTest.cs new file mode 100644 index 0000000000..6a5a548024 --- /dev/null +++ b/Lib9c.DPoS.Tests/Control/ValidatorCtrlTest.cs @@ -0,0 +1,124 @@ +using System.Collections.Immutable; +using Lib9c.DPoS.Control; +using Lib9c.DPoS.Exception; +using Lib9c.DPoS.Misc; +using Lib9c.DPoS.Model; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Module; +using Xunit; + +namespace Lib9c.DPoS.Tests.Control +{ + public class ValidatorCtrlTest : PoSTest + { + private readonly PublicKey _operatorPublicKey; + private readonly Address _operatorAddress; + private readonly Address _validatorAddress; + private readonly ImmutableHashSet _nativeTokens; + private IWorld _states; + + public ValidatorCtrlTest() + : base() + { + _operatorPublicKey = new PrivateKey().PublicKey; + _operatorAddress = _operatorPublicKey.Address; + _validatorAddress = Validator.DeriveAddress(_operatorAddress); + _nativeTokens = ImmutableHashSet.Create( + Asset.GovernanceToken, Asset.ConsensusToken, Asset.Share); + _states = InitializeStates(); + } + + [Fact] + public void InvalidCurrencyTest() + { + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _operatorAddress, + Asset.ConsensusToken * 50); + Assert.Throws( + () => _states = ValidatorCtrl.Create( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _operatorAddress, + _operatorPublicKey, + Asset.ConsensusToken * 30, + _nativeTokens)); + } + + [Theory] + [InlineData(500, 0)] + [InlineData(500, 1000)] + public void InvalidSelfDelegateTest(int mintAmount, int selfDelegateAmount) + { + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _operatorAddress, + Asset.GovernanceToken * mintAmount); + Assert.Throws( + () => _states = ValidatorCtrl.Create( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _operatorAddress, + _operatorPublicKey, + Asset.GovernanceToken * selfDelegateAmount, + _nativeTokens)); + } + + [Theory] + [InlineData(500, 10)] + [InlineData(500, 100)] + public void BalanceTest(int mintAmount, int selfDelegateAmount) + { + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _operatorAddress, + Asset.GovernanceToken * mintAmount); + _states = ValidatorCtrl.Create( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _operatorAddress, + _operatorPublicKey, + Asset.GovernanceToken * selfDelegateAmount, + _nativeTokens); + Assert.Equal( + Asset.ConsensusToken * selfDelegateAmount, + _states.GetBalance(_validatorAddress, Asset.ConsensusToken)); + Assert.Equal( + Asset.GovernanceToken * (mintAmount - selfDelegateAmount), + _states.GetBalance(_operatorAddress, Asset.GovernanceToken)); + Assert.Equal( + Asset.Share * selfDelegateAmount, + _states.GetBalance( + Delegation.DeriveAddress(_operatorAddress, _validatorAddress), Asset.Share)); + Assert.Equal( + Asset.Share * selfDelegateAmount, + ValidatorCtrl.GetValidator(_states, _validatorAddress)!.DelegatorShares); + } + } +} diff --git a/Lib9c.DPoS.Tests/Control/ValidatorPowerIndexCtrlTest.cs b/Lib9c.DPoS.Tests/Control/ValidatorPowerIndexCtrlTest.cs new file mode 100644 index 0000000000..8929a66683 --- /dev/null +++ b/Lib9c.DPoS.Tests/Control/ValidatorPowerIndexCtrlTest.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Lib9c.DPoS.Control; +using Lib9c.DPoS.Misc; +using Lib9c.DPoS.Model; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Module; +using Xunit; + +namespace Lib9c.DPoS.Tests.Control +{ + public class ValidatorPowerIndexCtrlTest : PoSTest + { + private readonly ImmutableHashSet _nativeTokens; + private IWorld _states; + + public ValidatorPowerIndexCtrlTest() + { + List operatorPublicKeys = new List() + { + new PrivateKey().PublicKey, + new PrivateKey().PublicKey, + new PrivateKey().PublicKey, + new PrivateKey().PublicKey, + new PrivateKey().PublicKey, + }; + + List
operatorAddresses = operatorPublicKeys.Select( + pubKey => pubKey.Address).ToList(); + + _nativeTokens = ImmutableHashSet.Create( + Asset.GovernanceToken, Asset.ConsensusToken, Asset.Share); + + _states = InitializeStates(); + ValidatorAddresses = new List
(); + + var pairs = operatorAddresses.Zip(operatorPublicKeys, (addr, key) => (addr, key)); + foreach (var (addr, key) in pairs) + { + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + addr, + Asset.GovernanceToken * 100); + _states = ValidatorCtrl.Create( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + addr, + key, + Asset.GovernanceToken * 10, _nativeTokens); + ValidatorAddresses.Add(Validator.DeriveAddress(addr)); + } + } + + private List
ValidatorAddresses { get; set; } + + [Fact] + public void SortingTestDifferentToken() + { + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + ValidatorAddresses[0], + Asset.ConsensusToken * 10); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + ValidatorAddresses[1], + Asset.ConsensusToken * 30); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + ValidatorAddresses[2], + Asset.ConsensusToken * 50); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + ValidatorAddresses[3], + Asset.ConsensusToken * 40); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + ValidatorAddresses[4], + Asset.ConsensusToken * 20); + _states = ValidatorPowerIndexCtrl.Update(_states, ValidatorAddresses); + ValidatorPowerIndex validatorPowerIndex; + (_states, validatorPowerIndex) + = ValidatorPowerIndexCtrl.FetchValidatorPowerIndex(_states); + List index = validatorPowerIndex.Index.ToList(); + Assert.Equal(5, index.Count); + Assert.Equal(ValidatorAddresses[2], index[0].ValidatorAddress); + Assert.Equal(Asset.ConsensusToken * 60, index[0].ConsensusToken); + Assert.Equal(ValidatorAddresses[3], index[1].ValidatorAddress); + Assert.Equal(Asset.ConsensusToken * 50, index[1].ConsensusToken); + Assert.Equal(ValidatorAddresses[1], index[2].ValidatorAddress); + Assert.Equal(Asset.ConsensusToken * 40, index[2].ConsensusToken); + Assert.Equal(ValidatorAddresses[4], index[3].ValidatorAddress); + Assert.Equal(Asset.ConsensusToken * 30, index[3].ConsensusToken); + Assert.Equal(ValidatorAddresses[0], index[4].ValidatorAddress); + Assert.Equal(Asset.ConsensusToken * 20, index[4].ConsensusToken); + } + + [Fact] + public void SortingTestSameToken() + { + (_states, _) = ValidatorPowerIndexCtrl.FetchValidatorPowerIndex(_states); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + ValidatorAddresses[0], + Asset.ConsensusToken * 10); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + ValidatorAddresses[1], + Asset.ConsensusToken * 10); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + ValidatorAddresses[2], + Asset.ConsensusToken * 10); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + ValidatorAddresses[3], + Asset.ConsensusToken * 10); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + ValidatorAddresses[4], + Asset.ConsensusToken * 10); + _states = ValidatorPowerIndexCtrl.Update(_states, ValidatorAddresses); + ValidatorPowerIndex validatorPowerIndex; + (_states, validatorPowerIndex) + = ValidatorPowerIndexCtrl.FetchValidatorPowerIndex(_states); + List index = validatorPowerIndex.Index.ToList(); + Assert.Equal(5, index.Count); + for (int i = 0; i < index.Count - 1; i++) + { + Assert.True(((IComparable
)index[i].ValidatorAddress) + .CompareTo(index[i + 1].ValidatorAddress) > 0); + } + } + } +} diff --git a/Lib9c.DPoS.Tests/Control/ValidatorSetCtrlTest.cs b/Lib9c.DPoS.Tests/Control/ValidatorSetCtrlTest.cs new file mode 100644 index 0000000000..e8a9055f80 --- /dev/null +++ b/Lib9c.DPoS.Tests/Control/ValidatorSetCtrlTest.cs @@ -0,0 +1,221 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Lib9c.DPoS.Control; +using Lib9c.DPoS.Misc; +using Lib9c.DPoS.Model; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Nekoyume.Module; +using Xunit; + +namespace Lib9c.DPoS.Tests.Control +{ + public class ValidatorSetCtrlTest : PoSTest + { + private ImmutableHashSet _nativeTokens; + private IWorld _states; + + public ValidatorSetCtrlTest() + : base() + { + _nativeTokens = ImmutableHashSet.Create( + Asset.GovernanceToken, Asset.ConsensusToken, Asset.Share); + _states = InitializeStates(); + OperatorAddresses = new List
(); + ValidatorAddresses = new List
(); + DelegatorAddress = CreateAddress(); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + }, + DelegatorAddress, + Asset.GovernanceToken * 100000); + for (int i = 0; i < 200; i++) + { + PublicKey operatorPublicKey = new PrivateKey().PublicKey; + Address operatorAddress = operatorPublicKey.Address; + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + }, + operatorAddress, + Asset.GovernanceToken * 1000); + OperatorAddresses.Add(operatorAddress); + _states = ValidatorCtrl.Create( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + operatorAddress, + operatorPublicKey, + Asset.GovernanceToken * 1, + _nativeTokens); + ValidatorAddresses.Add(Validator.DeriveAddress(operatorAddress)); + } + } + + private List
OperatorAddresses { get; set; } + + private List
ValidatorAddresses { get; set; } + + private Address DelegatorAddress { get; set; } + + [Fact] + public void ValidatorSetTest() + { + for (int i = 0; i < 200; i++) + { + _states = DelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + DelegatorAddress, + ValidatorAddresses[i], + Asset.GovernanceToken * (i + 1), + _nativeTokens); + } + + Address validatorAddressA = ValidatorAddresses[3]; + Address validatorAddressB = ValidatorAddresses[5]; + Address delegationAddressB = Delegation.DeriveAddress( + DelegatorAddress, validatorAddressB); + + _states = DelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + DelegatorAddress, + validatorAddressA, + Asset.GovernanceToken * 200, + _nativeTokens); + + _states = DelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + DelegatorAddress, + validatorAddressB, + Asset.GovernanceToken * 300, + _nativeTokens); + + _states = ValidatorSetCtrl.Update( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }); + + ValidatorSet bondedSet; + (_states, bondedSet) = ValidatorSetCtrl.FetchBondedValidatorSet(_states); + Assert.Equal( + validatorAddressB, bondedSet.Set.ToList()[0].ValidatorAddress); + Assert.Equal( + validatorAddressA, bondedSet.Set.ToList()[1].ValidatorAddress); + Assert.Equal( + Asset.Share * (5 + 1 + 300), + _states.GetBalance(delegationAddressB, Asset.Share)); + Assert.Equal( + Asset.ConsensusToken * (1 + 5 + 1 + 300), + _states.GetBalance(ValidatorAddresses[5], Asset.ConsensusToken)); + Assert.Equal( + Asset.GovernanceToken + * (100 + (101 + 200) * 50 - 101 - 102 + 204 + 306), + _states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken + * (100 + (1 + 100) * 50 - 4 - 6 + 101 + 102), + _states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken + * (100000 - (1 + 200) * 100 - 200 - 300), + _states.GetBalance(DelegatorAddress, Asset.GovernanceToken)); + + _states = UndelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 2, + }, + DelegatorAddress, + validatorAddressB, + _states.GetBalance(delegationAddressB, Asset.Share), + _nativeTokens); + + Assert.Equal( + Asset.Share * 0, + _states.GetBalance(delegationAddressB, Asset.Share)); + Assert.Equal( + Asset.ConsensusToken * 1, + _states.GetBalance(validatorAddressB, Asset.ConsensusToken)); + Assert.Equal( + Asset.GovernanceToken + * (100 + (101 + 200) * 50 - 101 - 102 + 204 + 306 - 306), + _states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken + * (100 + (1 + 100) * 50 - 4 - 6 + 101 + 102 + 306), + _states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken + * (100000 - (1 + 200) * 100 - 200 - 300), + _states.GetBalance(DelegatorAddress, Asset.GovernanceToken)); + + _states = ValidatorSetCtrl.Update( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }); + (_states, bondedSet) = ValidatorSetCtrl.FetchBondedValidatorSet(_states); + Assert.Equal( + validatorAddressA, bondedSet.Set.ToList()[0].ValidatorAddress); + Assert.Equal( + Asset.GovernanceToken + * (100 + (101 + 200) * 50 - 101 - 102 + 204 + 306 - 306 + 102), + _states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken + * (100 + (1 + 100) * 50 - 4 - 6 + 101 + 102 + 306 - 102), + _states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken + * (100000 - (1 + 200) * 100 - 200 - 300), + _states.GetBalance(DelegatorAddress, Asset.GovernanceToken)); + + _states = ValidatorSetCtrl.Update( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 50400 * 5, + }); + + Assert.Equal( + Asset.GovernanceToken + * (100 + (1 + 100) * 50 - 4 - 6 + 101 + 102 + 306 - 102 - 306), + _states.GetBalance(ReservedAddress.UnbondedPool, Asset.GovernanceToken)); + Assert.Equal( + Asset.GovernanceToken + * (100000 - (1 + 200) * 100 - 200 - 300 + 306), + _states.GetBalance(DelegatorAddress, Asset.GovernanceToken)); + } + } +} diff --git a/Lib9c.DPoS.Tests/DistributeTest.cs b/Lib9c.DPoS.Tests/DistributeTest.cs new file mode 100644 index 0000000000..9c50484e87 --- /dev/null +++ b/Lib9c.DPoS.Tests/DistributeTest.cs @@ -0,0 +1,259 @@ +using System.Collections.Generic; +using System.Collections.Immutable; +using Lib9c.DPoS.Control; +using Lib9c.DPoS.Misc; +using Lib9c.DPoS.Model; +using Libplanet.Action.State; +using Libplanet.Crypto; +using Libplanet.Types.Assets; +using Libplanet.Types.Consensus; +using Nekoyume.Module; +using Xunit; +using Validator = Lib9c.DPoS.Model.Validator; + +namespace Lib9c.DPoS.Tests +{ + public class DistributeTest : PoSTest + { + private readonly ImmutableHashSet _nativeTokens; + private IWorld _states; + + public DistributeTest() + : base() + { + _nativeTokens = ImmutableHashSet.Create( + Asset.GovernanceToken, Asset.ConsensusToken, Asset.Share); + _states = InitializeStates(); + OperatorPrivateKeys = new List(); + OperatorPublicKeys = new List(); + OperatorAddresses = new List
(); + ValidatorAddresses = new List
(); + DelegatorAddress = CreateAddress(); + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + DelegatorAddress, + Asset.GovernanceToken * 100000); + for (int i = 0; i < 200; i++) + { + PrivateKey operatorPrivateKey = new PrivateKey(); + PublicKey operatorPublicKey = operatorPrivateKey.PublicKey; + Address operatorAddress = operatorPublicKey.Address; + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + operatorAddress, + Asset.GovernanceToken * 1000); + + OperatorPrivateKeys.Add(operatorPrivateKey); + OperatorPublicKeys.Add(operatorPublicKey); + OperatorAddresses.Add(operatorAddress); + _states = ValidatorCtrl.Create( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + operatorAddress, + operatorPublicKey, + Asset.GovernanceToken * 1, + _nativeTokens); + ValidatorAddresses.Add(Validator.DeriveAddress(operatorAddress)); + } + } + + private List OperatorPrivateKeys { get; set; } + + private List OperatorPublicKeys { get; set; } + + private List
OperatorAddresses { get; set; } + + private List
ValidatorAddresses { get; set; } + + private Address DelegatorAddress { get; set; } + + [Fact] + public void ValidatorSetTest() + { + for (int i = 0; i < 200; i++) + { + _states = DelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + DelegatorAddress, + ValidatorAddresses[i], + Asset.GovernanceToken * (i + 1), + _nativeTokens); + } + + Address validatorAddressA = ValidatorAddresses[3]; + Address validatorAddressB = ValidatorAddresses[5]; + + _states = DelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + DelegatorAddress, + validatorAddressA, + Asset.GovernanceToken * 200, + _nativeTokens); + + _states = DelegateCtrl.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + DelegatorAddress, + validatorAddressB, + Asset.GovernanceToken * 300, + _nativeTokens); + + _states = ValidatorSetCtrl.Update( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }); + + (_states, _) = ValidatorSetCtrl.FetchBondedValidatorSet(_states); + + List votes = new List() + { + new VoteMetadata( + default, + default, + default, + default, + OperatorPrivateKeys[3].PublicKey, + VoteFlag.PreCommit).Sign(OperatorPrivateKeys[3]), + new VoteMetadata( + default, + default, + default, + default, + OperatorPrivateKeys[5].PublicKey, + VoteFlag.PreCommit).Sign(OperatorPrivateKeys[5]), + }; + FungibleAssetValue blockReward = Asset.ConsensusToken * 50; + _states = _states.MintAsset( + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + ReservedAddress.RewardPool, + blockReward); + _states = AllocateReward.Execute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 1, + }, + _nativeTokens, + votes, + OperatorAddresses[3]); + + var (baseProposerReward, _) + = (blockReward * AllocateReward.BaseProposerRewardNumer) + .DivRem(AllocateReward.BaseProposerRewardDenom); + var (bonusProposerReward, _) + = (blockReward * (205 + 307) + * AllocateReward.BonusProposerRewardNumer) + .DivRem((100 + (101 + 200) * 50 - 101 - 102 + 204 + 306) + * AllocateReward.BonusProposerRewardDenom); + FungibleAssetValue proposerReward = baseProposerReward + bonusProposerReward; + FungibleAssetValue validatorRewardSum = blockReward - proposerReward; + + var (validatorRewardA, _) + = (validatorRewardSum * 205) + .DivRem(100 + (101 + 200) * 50 - 101 - 102 + 204 + 306); + var (commissionA, _) + = (validatorRewardA * Validator.CommissionNumer) + .DivRem(Validator.CommissionDenom); + var (validatorRewardB, _) + = (validatorRewardSum * 307) + .DivRem(100 + (101 + 200) * 50 - 101 - 102 + 204 + 306); + var (commissionB, _) + = (validatorRewardB * Validator.CommissionNumer) + .DivRem(Validator.CommissionDenom); + + Assert.Equal( + Asset.ConsensusToken * 0, + _states.GetBalance(ReservedAddress.RewardPool, Asset.ConsensusToken)); + + Assert.Equal( + Asset.GovernanceToken * (100 + (101 + 200) * 50 - 101 - 102 + 204 + 306), + _states.GetBalance(ReservedAddress.BondedPool, Asset.GovernanceToken)); + + Assert.Equal( + Asset.ConsensusToken * 205, + _states.GetBalance(validatorAddressA, Asset.ConsensusToken)); + + Assert.Equal( + Asset.ConsensusToken * 307, + _states.GetBalance(validatorAddressB, Asset.ConsensusToken)); + + Assert.Equal( + proposerReward + commissionA, + _states.GetBalance( + AllocateReward.RewardAddress(OperatorAddresses[3]), Asset.ConsensusToken)); + + Assert.Equal( + commissionB, + _states.GetBalance( + AllocateReward.RewardAddress(OperatorAddresses[5]), Asset.ConsensusToken)); + + Address delegationAddressA + = Delegation.DeriveAddress(DelegatorAddress, validatorAddressA); + + Assert.Equal( + Asset.ConsensusToken * 0, + _states.GetBalance( + AllocateReward.RewardAddress(DelegatorAddress), Asset.ConsensusToken)); + + var (delegatorToken, _) + = (_states.GetBalance( + ValidatorRewards.DeriveAddress(validatorAddressA, Asset.ConsensusToken), + Asset.ConsensusToken) + * _states.GetBalance( + Delegation.DeriveAddress(DelegatorAddress, validatorAddressA), + Asset.Share) + .RawValue) + .DivRem(ValidatorCtrl.GetValidator(_states, validatorAddressA)! + .DelegatorShares.RawValue); + + _states = DelegateCtrl.Distribute( + _states, + new ActionContext + { + PreviousState = _states, + BlockIndex = 5, + }, + _nativeTokens, + delegationAddressA); + + Assert.Equal( + delegatorToken, + _states.GetBalance( + AllocateReward.RewardAddress(DelegatorAddress), Asset.ConsensusToken)); + } + } +} diff --git a/Lib9c.DPoS.Tests/Lib9c.DPoS.Tests.csproj b/Lib9c.DPoS.Tests/Lib9c.DPoS.Tests.csproj new file mode 100644 index 0000000000..f3999a5277 --- /dev/null +++ b/Lib9c.DPoS.Tests/Lib9c.DPoS.Tests.csproj @@ -0,0 +1,37 @@ + + + + net6.0 + 9.0 + false + false + true + true + $(NoWarn);CS0162;CS8032;CS0618;CS0612;SYSLIB0011 + enable + .\Lib9c.Tests.ruleset + Debug;Release;DevEx + AnyCPU + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + diff --git a/Lib9c.DPoS.Tests/Model/DelegationTest.cs b/Lib9c.DPoS.Tests/Model/DelegationTest.cs new file mode 100644 index 0000000000..5dea9496ab --- /dev/null +++ b/Lib9c.DPoS.Tests/Model/DelegationTest.cs @@ -0,0 +1,23 @@ +using Lib9c.DPoS.Model; +using Xunit; + +namespace Lib9c.DPoS.Tests.Model +{ + public class DelegationTest : PoSTest + { + private readonly Delegation _delegation; + + public DelegationTest() + { + _delegation = new Delegation(CreateAddress(), CreateAddress()); + } + + [Fact] + public void MarshallingTest() + { + Delegation newDelegation + = new Delegation(_delegation.Serialize()); + Assert.Equal(_delegation, newDelegation); + } + } +} diff --git a/Lib9c.DPoS.Tests/Model/RedelegationEntryTest.cs b/Lib9c.DPoS.Tests/Model/RedelegationEntryTest.cs new file mode 100644 index 0000000000..cb6ae49b25 --- /dev/null +++ b/Lib9c.DPoS.Tests/Model/RedelegationEntryTest.cs @@ -0,0 +1,48 @@ +using Lib9c.DPoS.Exception; +using Lib9c.DPoS.Misc; +using Lib9c.DPoS.Model; +using Xunit; + +namespace Lib9c.DPoS.Tests.Model +{ + public class RedelegationEntryTest : PoSTest + { + private readonly RedelegationEntry _redelegationEntry; + + public RedelegationEntryTest() + { + _redelegationEntry = new RedelegationEntry( + CreateAddress(), + Asset.Share * 1, + Asset.ConsensusToken * 1, + Asset.Share * 1, + 1, + 1); + } + + [Fact] + public void InvalidUnbondingConsensusToken() + { + Assert.Throws( + () => _redelegationEntry.RedelegatingShare = Asset.GovernanceToken * 1); + Assert.Throws( + () => _redelegationEntry.RedelegatingShare = Asset.ConsensusToken * 1); + Assert.Throws( + () => _redelegationEntry.UnbondingConsensusToken = Asset.GovernanceToken * 1); + Assert.Throws( + () => _redelegationEntry.UnbondingConsensusToken = Asset.Share * 1); + Assert.Throws( + () => _redelegationEntry.IssuedShare = Asset.GovernanceToken * 1); + Assert.Throws( + () => _redelegationEntry.IssuedShare = Asset.ConsensusToken * 1); + } + + [Fact] + public void MarshallingTest() + { + RedelegationEntry newRedelegationEntry + = new RedelegationEntry(_redelegationEntry.Serialize()); + Assert.Equal(_redelegationEntry, newRedelegationEntry); + } + } +} diff --git a/Lib9c.DPoS.Tests/Model/RedelegationTest.cs b/Lib9c.DPoS.Tests/Model/RedelegationTest.cs new file mode 100644 index 0000000000..21cee77b7c --- /dev/null +++ b/Lib9c.DPoS.Tests/Model/RedelegationTest.cs @@ -0,0 +1,24 @@ +using Lib9c.DPoS.Model; +using Xunit; + +namespace Lib9c.DPoS.Tests.Model +{ + public class RedelegationTest : PoSTest + { + private readonly Redelegation _redelegation; + + public RedelegationTest() + { + _redelegation = new Redelegation( + CreateAddress(), CreateAddress(), CreateAddress()); + } + + [Fact] + public void MarshallingTest() + { + Redelegation newRedelegationInfo + = new Redelegation(_redelegation.Serialize()); + Assert.Equal(_redelegation, newRedelegationInfo); + } + } +} diff --git a/Lib9c.DPoS.Tests/Model/UndelegationEntryTest.cs b/Lib9c.DPoS.Tests/Model/UndelegationEntryTest.cs new file mode 100644 index 0000000000..16d5124510 --- /dev/null +++ b/Lib9c.DPoS.Tests/Model/UndelegationEntryTest.cs @@ -0,0 +1,35 @@ +using Lib9c.DPoS.Exception; +using Lib9c.DPoS.Misc; +using Lib9c.DPoS.Model; +using Xunit; + +namespace Lib9c.DPoS.Tests.Model +{ + public class UndelegationEntryTest : PoSTest + { + private readonly UndelegationEntry _undelegationEntry; + + public UndelegationEntryTest() + { + _undelegationEntry = new UndelegationEntry( + CreateAddress(), Asset.ConsensusToken * 1, 1, 1); + } + + [Fact] + public void InvalidUnbondingConsensusToken() + { + Assert.Throws( + () => _undelegationEntry.UnbondingConsensusToken = Asset.GovernanceToken * 1); + Assert.Throws( + () => _undelegationEntry.UnbondingConsensusToken = Asset.Share * 1); + } + + [Fact] + public void MarshallingTest() + { + UndelegationEntry newUndelegationEntry + = new UndelegationEntry(_undelegationEntry.Serialize()); + Assert.Equal(_undelegationEntry, newUndelegationEntry); + } + } +} diff --git a/Lib9c.DPoS.Tests/Model/UndelegationTest.cs b/Lib9c.DPoS.Tests/Model/UndelegationTest.cs new file mode 100644 index 0000000000..0806c58cf4 --- /dev/null +++ b/Lib9c.DPoS.Tests/Model/UndelegationTest.cs @@ -0,0 +1,23 @@ +using Lib9c.DPoS.Model; +using Xunit; + +namespace Lib9c.DPoS.Tests.Model +{ + public class UndelegationTest : PoSTest + { + private readonly Undelegation _undelegation; + + public UndelegationTest() + { + _undelegation = new Undelegation(CreateAddress(), CreateAddress()); + } + + [Fact] + public void MarshallingTest() + { + Undelegation newUndelegationInfo + = new Undelegation(_undelegation.Serialize()); + Assert.Equal(_undelegation, newUndelegationInfo); + } + } +} diff --git a/Lib9c.DPoS.Tests/Model/ValidatorPowerIndexTest.cs b/Lib9c.DPoS.Tests/Model/ValidatorPowerIndexTest.cs new file mode 100644 index 0000000000..acdd7a4983 --- /dev/null +++ b/Lib9c.DPoS.Tests/Model/ValidatorPowerIndexTest.cs @@ -0,0 +1,23 @@ +using Lib9c.DPoS.Model; +using Xunit; + +namespace Lib9c.DPoS.Tests.Model +{ + public class ValidatorPowerIndexTest : PoSTest + { + private readonly ValidatorPowerIndex _validatorPowerIndex; + + public ValidatorPowerIndexTest() + { + _validatorPowerIndex = new ValidatorPowerIndex(); + } + + [Fact] + public void MarshallingTest() + { + ValidatorPowerIndex newValidatorPowerIndex = new ValidatorPowerIndex( + _validatorPowerIndex.Serialize()); + Assert.Equal(_validatorPowerIndex.Index, newValidatorPowerIndex.Index); + } + } +} diff --git a/Lib9c.DPoS.Tests/Model/ValidatorPowerTest.cs b/Lib9c.DPoS.Tests/Model/ValidatorPowerTest.cs new file mode 100644 index 0000000000..0149f97279 --- /dev/null +++ b/Lib9c.DPoS.Tests/Model/ValidatorPowerTest.cs @@ -0,0 +1,38 @@ +using Lib9c.DPoS.Exception; +using Lib9c.DPoS.Misc; +using Lib9c.DPoS.Model; +using Libplanet.Crypto; +using Xunit; + +namespace Lib9c.DPoS.Tests.Model +{ + public class ValidatorPowerTest : PoSTest + { + private readonly ValidatorPower _validatorPower; + + public ValidatorPowerTest() + { + _validatorPower = new ValidatorPower( + CreateAddress(), + new PrivateKey().PublicKey, + Asset.ConsensusToken * 10); + } + + [Fact] + public void InvalidUnbondingConsensusToken() + { + Assert.Throws( + () => _validatorPower.ConsensusToken = Asset.GovernanceToken * 1); + Assert.Throws( + () => _validatorPower.ConsensusToken = Asset.Share * 1); + } + + [Fact] + public void MarshallingTest() + { + ValidatorPower newValidatorPower = new ValidatorPower( + _validatorPower.Serialize()); + Assert.Equal(_validatorPower, newValidatorPower); + } + } +} diff --git a/Lib9c.DPoS.Tests/Model/ValidatorSetTest.cs b/Lib9c.DPoS.Tests/Model/ValidatorSetTest.cs new file mode 100644 index 0000000000..189a89996f --- /dev/null +++ b/Lib9c.DPoS.Tests/Model/ValidatorSetTest.cs @@ -0,0 +1,25 @@ +using Lib9c.DPoS.Model; +using Xunit; + +namespace Lib9c.DPoS.Tests.Model +{ + public class ValidatorSetTest : PoSTest + { + private readonly ValidatorSet _validatorSet; + + public ValidatorSetTest() + { + _validatorSet = new ValidatorSet(); + } + + [Fact] + public void MarshallingTest() + { + ValidatorSet newValidatorSet = new ValidatorSet( + _validatorSet.Serialize()); + Assert.Equal( + _validatorSet.Set, + newValidatorSet.Set); + } + } +} diff --git a/Lib9c.DPoS.Tests/Model/ValidatorTest.cs b/Lib9c.DPoS.Tests/Model/ValidatorTest.cs new file mode 100644 index 0000000000..d700513315 --- /dev/null +++ b/Lib9c.DPoS.Tests/Model/ValidatorTest.cs @@ -0,0 +1,34 @@ +using Lib9c.DPoS.Exception; +using Lib9c.DPoS.Misc; +using Lib9c.DPoS.Model; +using Libplanet.Crypto; +using Xunit; + +namespace Lib9c.DPoS.Tests.Model +{ + public class ValidatorTest : PoSTest + { + private readonly Validator _validator; + + public ValidatorTest() + { + _validator = new Validator(CreateAddress(), new PrivateKey().PublicKey); + } + + [Fact] + public void InvalidShareTypeTest() + { + Assert.Throws( + () => _validator.DelegatorShares = Asset.ConsensusToken * 1); + Assert.Throws( + () => _validator.DelegatorShares = Asset.GovernanceToken * 1); + } + + [Fact] + public void MarshallingTest() + { + Validator newValidator = new Validator(_validator.Serialize()); + Assert.Equal(_validator, newValidator); + } + } +} diff --git a/Lib9c.DPoS.Tests/PoSTest.cs b/Lib9c.DPoS.Tests/PoSTest.cs new file mode 100644 index 0000000000..1c1c1f1387 --- /dev/null +++ b/Lib9c.DPoS.Tests/PoSTest.cs @@ -0,0 +1,20 @@ +using Lib9c.Tests.Action; +using Libplanet.Action.State; +using Libplanet.Crypto; + +namespace Lib9c.DPoS.Tests +{ + public class PoSTest + { + protected static IWorld InitializeStates() + { + return new World(new MockWorldState()); + } + + protected static Address CreateAddress() + { + PrivateKey privateKey = new PrivateKey(); + return privateKey.Address; + } + } +} diff --git a/Lib9c.DPoS.Tests/TestRandom.cs b/Lib9c.DPoS.Tests/TestRandom.cs new file mode 100644 index 0000000000..361c45c03b --- /dev/null +++ b/Lib9c.DPoS.Tests/TestRandom.cs @@ -0,0 +1,41 @@ +using Libplanet.Action; + +namespace Lib9c.DPoS.Tests +{ + public class TestRandom : IRandom + { + private readonly System.Random _random; + + public TestRandom(int seed = default) + { + _random = new System.Random(seed); + } + + public int Seed => 0; + + public int Next() + { + return _random.Next(); + } + + public int Next(int maxValue) + { + return _random.Next(maxValue); + } + + public int Next(int minValue, int maxValue) + { + return _random.Next(minValue, maxValue); + } + + public void NextBytes(byte[] buffer) + { + _random.NextBytes(buffer); + } + + public double NextDouble() + { + return _random.NextDouble(); + } + } +} diff --git a/Lib9c.DPoS.Tests/ValidatorPowerComparerTest.cs b/Lib9c.DPoS.Tests/ValidatorPowerComparerTest.cs new file mode 100644 index 0000000000..e38e666f3b --- /dev/null +++ b/Lib9c.DPoS.Tests/ValidatorPowerComparerTest.cs @@ -0,0 +1,39 @@ +using System; +using Lib9c.DPoS.Misc; +using Lib9c.DPoS.Model; +using Libplanet.Crypto; +using Xunit; + +namespace Lib9c.DPoS.Tests +{ + public class ValidatorPowerComparerTest : PoSTest + { + [Fact] + public void CompareDifferentTokenTest() + { + PublicKey publicKeyA = new PrivateKey().PublicKey; + PublicKey publicKeyB = new PrivateKey().PublicKey; + ValidatorPower validatorPowerA = new ValidatorPower( + publicKeyA.Address, publicKeyA, Asset.ConsensusToken * 10); + ValidatorPower validatorPowerB = new ValidatorPower( + publicKeyB.Address, publicKeyB, Asset.ConsensusToken * 11); + Assert.True(((IComparable)validatorPowerA) + .CompareTo(validatorPowerB) > 0); + } + + [Fact] + public void CompareSameTokenTest() + { + PublicKey publicKeyA = new PrivateKey().PublicKey; + PublicKey publicKeyB = new PrivateKey().PublicKey; + ValidatorPower validatorPowerA = new ValidatorPower( + publicKeyA.Address, publicKeyA, Asset.ConsensusToken * 10); + ValidatorPower validatorPowerB = new ValidatorPower( + publicKeyB.Address, publicKeyB, Asset.ConsensusToken * 10); + int sign = -((IComparable
)publicKeyA.Address) + .CompareTo(publicKeyB.Address); + Assert.True(((IComparable)validatorPowerA) + .CompareTo(validatorPowerB) == sign); + } + } +} diff --git a/Lib9c.DPoS/AssemblyInfo.cs b/Lib9c.DPoS/AssemblyInfo.cs new file mode 100644 index 0000000000..bb400a3aee --- /dev/null +++ b/Lib9c.DPoS/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Lib9c.DPoS.Tests")] diff --git a/Lib9c.sln b/Lib9c.sln index c19d8af968..35a141ef77 100644 --- a/Lib9c.sln +++ b/Lib9c.sln @@ -72,6 +72,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib9c.Plugin.Shared", ".Lib EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lib9c.DPoS", "Lib9c.DPoS\Lib9c.DPoS.csproj", "{23848A39-37DD-4B73-A108-1915E10FEE50}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lib9c.DPoS.Tests", "Lib9c.DPoS.Tests\Lib9c.DPoS.Tests.csproj", "{447DEF1C-01B0-4FC7-8444-B9DB5C26AFB7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -202,6 +204,10 @@ Global {23848A39-37DD-4B73-A108-1915E10FEE50}.Debug|Any CPU.Build.0 = Debug|Any CPU {23848A39-37DD-4B73-A108-1915E10FEE50}.Release|Any CPU.ActiveCfg = Release|Any CPU {23848A39-37DD-4B73-A108-1915E10FEE50}.Release|Any CPU.Build.0 = Release|Any CPU + {447DEF1C-01B0-4FC7-8444-B9DB5C26AFB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {447DEF1C-01B0-4FC7-8444-B9DB5C26AFB7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {447DEF1C-01B0-4FC7-8444-B9DB5C26AFB7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {447DEF1C-01B0-4FC7-8444-B9DB5C26AFB7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE