Skip to content
Open
42 changes: 42 additions & 0 deletions Mainnet/DividendToken/DividendToken.Tests/AddressExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Stratis.SmartContracts;
using System;
using System.Collections.Generic;
using System.Text;

namespace DividendTokenContract.Tests
{
public static class AddressExtensions
{
private static byte[] HexStringToBytes(string val)
{
if (val.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
val = val.Substring(2);

byte[] ret = new byte[val.Length / 2];
for (int i = 0; i < val.Length; i = i + 2)
{
string hexChars = val.Substring(i, 2);
ret[i / 2] = byte.Parse(hexChars, System.Globalization.NumberStyles.HexNumber);
}
return ret;
}

public static Address HexToAddress(this string hexString)
{
// uint160 only parses a big-endian hex string
var result = HexStringToBytes(hexString);
return CreateAddress(result);
}

private static Address CreateAddress(byte[] bytes)
{
uint pn0 = BitConverter.ToUInt32(bytes, 0);
uint pn1 = BitConverter.ToUInt32(bytes, 4);
uint pn2 = BitConverter.ToUInt32(bytes, 8);
uint pn3 = BitConverter.ToUInt32(bytes, 12);
uint pn4 = BitConverter.ToUInt32(bytes, 16);

return new Address(pn0, pn1, pn2, pn3, pn4);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace>DividendTokenContract.Tests</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
<PackageReference Include="Moq" Version="4.14.2" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DividendToken\DividendToken.csproj" />
</ItemGroup>
</Project>
212 changes: 212 additions & 0 deletions Mainnet/DividendToken/DividendToken.Tests/DividendTokenTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
using Moq;
using Stratis.SmartContracts;
using Xunit;

namespace DividendTokenContract.Tests
{
public class DividendTokenTests
{
private readonly IPersistentState state;

private readonly Mock<ISmartContractState> mContractState;
private readonly Mock<IContractLogger> mContractLogger;
private readonly Mock<IInternalTransactionExecutor> mTransactionExecutor;

private readonly Address owner;
private readonly Address tokenHolder;
private readonly Address currentContract;

private readonly string name;
private readonly string symbol;
private readonly UInt256 totalSupply;
private readonly byte decimals;

public DividendTokenTests()
{
this.state = new InMemoryState();
this.mContractState = new Mock<ISmartContractState>();
this.mContractLogger = new Mock<IContractLogger>();
this.mTransactionExecutor = new Mock<IInternalTransactionExecutor>();
this.mContractState.Setup(s => s.PersistentState).Returns(state);
this.mContractState.Setup(s => s.ContractLogger).Returns(mContractLogger.Object);
this.mContractState.Setup(s => s.InternalTransactionExecutor).Returns(mTransactionExecutor.Object);
this.owner = "0x0000000000000000000000000000000000000001".HexToAddress();
this.tokenHolder = "0x0000000000000000000000000000000000000002".HexToAddress();
this.currentContract = "0x0000000000000000000000000000000000000003".HexToAddress();
this.name = "Test Token";
this.symbol = "TST";
this.totalSupply = 1_000;
this.decimals = 0;
}


[Fact]
public void Deposited_Dividend_Should_Be_Distributed_Equaly()
{
var dividend = 1000ul;

mContractState.Setup(m => m.Message).Returns(new Message(currentContract, owner, dividend));

var contract = new DividendToken(mContractState.Object, totalSupply, name, symbol, decimals);

Assert.True(contract.TransferTo(tokenHolder, 100));

contract.Receive();

Assert.Equal(dividend, contract.Dividends);
Assert.Equal(100ul, contract.GetDividends(tokenHolder));
Assert.Equal(100ul, contract.GetTotalDividends(tokenHolder));
Assert.Equal(900ul, contract.GetDividends(owner));
Assert.Equal(900ul, contract.GetTotalDividends(owner));
}

[Fact]
public void Multiple_Deposited_Dividend_Should_Be_Distributed_Equaly()
{
var dividend = 1000ul;

mContractState.Setup(m => m.Message).Returns(new Message(currentContract, owner, dividend));

var contract = new DividendToken(mContractState.Object, totalSupply, name, symbol, decimals);

Assert.True(contract.TransferTo(tokenHolder, 100));

contract.Receive();

Assert.True(contract.TransferTo(tokenHolder, 100));

contract.Receive();

Assert.True(contract.TransferTo(tokenHolder, 100));

Assert.Equal(2 * dividend, contract.Dividends);
Assert.Equal(300ul, contract.GetDividends(tokenHolder));
Assert.Equal(300ul, contract.GetTotalDividends(tokenHolder));

Assert.Equal(1700ul, contract.GetDividends(owner));
Assert.Equal(1700ul, contract.GetTotalDividends(owner));
}

[Fact]
public void Cumulative_Dividends_Should_Be_Distributed_Equaly()
{
var dividend = 1000ul;

mContractState.Setup(m => m.Message).Returns(new Message(currentContract, owner, dividend));

var contract = new DividendToken(mContractState.Object, totalSupply, name, symbol, decimals);

Assert.True(contract.TransferTo(tokenHolder, 100));

contract.Receive();
contract.Receive();

Assert.Equal(2 * dividend, contract.Dividends);
Assert.Equal(2 * 100ul, contract.GetDividends(tokenHolder));
Assert.Equal(2 * 900ul, contract.GetDividends(owner));
}

/// <summary>
/// In the case of dividend per token is 1.5 satoshi,
/// address that holds 1 token will have 1 dividend at first deposit despite actual earned amount is 1.5 satoshi
/// because satoshi unit doesn't support decimal points. Still 0.5 satoshi is accounted in the account
/// But after second deposit by same rate (0.5 satoshi per token), the user will be able to have 1 satoshi (0.5 + 0.5)
/// </summary>
[Fact]
public void Cumulative_Deposits_Should_Be_Distributed_Equaly_When_Dividends_Have_Decimal_Value()
{
var dividend = 500ul;

mContractState.Setup(m => m.Message).Returns(new Message(currentContract, owner, dividend));

var contract = new DividendToken(mContractState.Object, totalSupply, name, symbol, decimals);

Assert.True(contract.TransferTo(tokenHolder, 1));

contract.Receive();

Assert.Equal(dividend, contract.Dividends);
Assert.Equal(0ul, contract.GetDividends(tokenHolder));
Assert.Equal(499ul, contract.GetDividends(owner));

contract.Receive();

Assert.Equal(2 * dividend, contract.Dividends);
Assert.Equal(1ul, contract.GetDividends(tokenHolder));
Assert.Equal(999ul, contract.GetDividends(owner));
}

[Fact]
public void Deposited_Dividend_Should_Be_Withdrawable()
{
var dividend = 500ul;

mContractState.Setup(m => m.Message).Returns(new Message(currentContract, owner, dividend));
mContractState.Setup(m => m.GetBalance).Returns(() => dividend);
mTransactionExecutor.Setup(m => m.Transfer(mContractState.Object, tokenHolder, 5)).Returns(TransferResult.Succeed(true));

var contract = new DividendToken(mContractState.Object, totalSupply, name, symbol, decimals);

Assert.True(contract.TransferTo(tokenHolder, 11));

contract.Receive();

mContractState.Setup(m => m.Message).Returns(new Message(currentContract, tokenHolder, 0));

contract.Withdraw();

mTransactionExecutor.Verify(s => s.Transfer(mContractState.Object, tokenHolder, 5), Times.Once);
Assert.Equal(0ul, contract.GetDividends());
var account = state.GetStruct<DividendToken.Account>($"Account:{tokenHolder}");
Assert.Equal((UInt256)500, account.DividendBalance);
Assert.Equal(5ul, account.WithdrawnDividends);
Assert.Equal(dividend, account.CreditedDividends);
}

[Fact]
public void GetDividends_Returns_Current_Sender_Dividends()
{
var dividend = 1000ul;

mContractState.Setup(m => m.Message).Returns(new Message(currentContract, owner, dividend));
mContractState.Setup(m => m.GetBalance).Returns(() => dividend);
mTransactionExecutor.Setup(m => m.Transfer(mContractState.Object, tokenHolder, 100)).Returns(TransferResult.Succeed(true));

var contract = new DividendToken(mContractState.Object, totalSupply, name, symbol, decimals);

Assert.True(contract.TransferTo(tokenHolder, 100));

mContractState.Setup(m => m.Message).Returns(new Message(currentContract, tokenHolder, dividend));
contract.Receive();

Assert.Equal(100ul, contract.GetDividends());
}

/// <summary>
/// GetTotalDividends should to return Withdrawable + Withdrawn dividends
/// </summary>
[Fact]
public void GetTotalDividends_Returns_Current_Sender_TotalDividends()
{
var dividend = 1000ul;

mContractState.Setup(m => m.Message).Returns(new Message(currentContract, owner, dividend));
mContractState.Setup(m => m.GetBalance).Returns(() => dividend);
mTransactionExecutor.Setup(m => m.Transfer(mContractState.Object, tokenHolder, 100)).Returns(TransferResult.Succeed(true));

var contract = new DividendToken(mContractState.Object, totalSupply, name, symbol, decimals);

Assert.True(contract.TransferTo(tokenHolder, 100));

contract.Receive();

mContractState.Setup(m => m.Message).Returns(new Message(currentContract, tokenHolder, 0));

contract.Withdraw();

mTransactionExecutor.Verify(s => s.Transfer(mContractState.Object, tokenHolder, 100), Times.Once);
Assert.Equal(0ul, contract.GetDividends());
Assert.Equal(100ul, contract.GetTotalDividends());
}
}
}
83 changes: 83 additions & 0 deletions Mainnet/DividendToken/DividendToken.Tests/InMemoryState.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using Stratis.SmartContracts;
using System;
using System.Collections.Generic;

namespace DividendTokenContract.Tests
{
public class InMemoryState : IPersistentState
{
private readonly Dictionary<string, object> storage = new Dictionary<string, object>();
public bool IsContractResult { get; set; }
public void Clear(string key) => storage.Remove(key);

public T GetValue<T>(string key) => (T)storage.GetValueOrDefault(key, default(T));

public void AddOrReplace(string key, object value)
{
if (!storage.TryAdd(key, value))
storage[key] = value;
}
public Address GetAddress(string key) => GetValue<Address>(key);

public T[] GetArray<T>(string key) => GetValue<T[]>(key);

public bool GetBool(string key) => GetValue<bool>(key);

public byte[] GetBytes(byte[] key) => throw new NotImplementedException();

public byte[] GetBytes(string key) => GetValue<byte[]>(key);

public char GetChar(string key) => GetValue<char>(key);

public int GetInt32(string key) => GetValue<int>(key);

public long GetInt64(string key) => GetValue<long>(key);

public string GetString(string key) => GetValue<string>(key);

public T GetStruct<T>(string key)
where T : struct => GetValue<T>(key);

public uint GetUInt32(string key) => GetValue<uint>(key);

public ulong GetUInt64(string key) => GetValue<ulong>(key);

public UInt128 GetUInt128(string key) => GetValue<UInt128>(key);

public UInt256 GetUInt256(string key) => GetValue<UInt256>(key);

public bool IsContract(Address address) => IsContractResult;

public void SetAddress(string key, Address value) => AddOrReplace(key, value);

public void SetArray(string key, Array a) => AddOrReplace(key, a);

public void SetBool(string key, bool value) => AddOrReplace(key, value);

public void SetBytes(byte[] key, byte[] value)
{
throw new NotImplementedException();
}

public void SetBytes(string key, byte[] value) => AddOrReplace(key, value);

public void SetChar(string key, char value) => AddOrReplace(key, value);

public void SetInt32(string key, int value) => AddOrReplace(key, value);

public void SetInt64(string key, long value) => AddOrReplace(key, value);

public void SetString(string key, string value) => AddOrReplace(key, value);

public void SetStruct<T>(string key, T value)
where T : struct => AddOrReplace(key, value);

public void SetUInt32(string key, uint value) => AddOrReplace(key, value);

public void SetUInt64(string key, ulong value) => AddOrReplace(key, value);

public void SetUInt128(string key, UInt128 value) => AddOrReplace(key, value);

public void SetUInt256(string key, UInt256 value) => AddOrReplace(key, value);
}
}
19 changes: 19 additions & 0 deletions Mainnet/DividendToken/DividendToken.Tests/TransferResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Stratis.SmartContracts;
using System;
using System.Collections.Generic;
using System.Text;

namespace DividendTokenContract.Tests
{
public class TransferResult : ITransferResult
{
public object ReturnValue { get; private set; }

public bool Success { get; private set; }

public static TransferResult Failed() => new TransferResult { Success = false };


public static TransferResult Succeed(object returnValue) => new TransferResult { Success = true, ReturnValue = returnValue };
}
}
Loading