diff --git a/Mainnet/DividendToken/DividendToken.Tests/AddressExtensions.cs b/Mainnet/DividendToken/DividendToken.Tests/AddressExtensions.cs
new file mode 100644
index 00000000..28365d9f
--- /dev/null
+++ b/Mainnet/DividendToken/DividendToken.Tests/AddressExtensions.cs
@@ -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);
+ }
+ }
+}
diff --git a/Mainnet/DividendToken/DividendToken.Tests/DividendToken.Tests.csproj b/Mainnet/DividendToken/DividendToken.Tests/DividendToken.Tests.csproj
new file mode 100644
index 00000000..ffc22b61
--- /dev/null
+++ b/Mainnet/DividendToken/DividendToken.Tests/DividendToken.Tests.csproj
@@ -0,0 +1,20 @@
+
+
+
+ Exe
+ netcoreapp3.1
+ DividendTokenContract.Tests
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
diff --git a/Mainnet/DividendToken/DividendToken.Tests/DividendTokenTests.cs b/Mainnet/DividendToken/DividendToken.Tests/DividendTokenTests.cs
new file mode 100644
index 00000000..f56035e6
--- /dev/null
+++ b/Mainnet/DividendToken/DividendToken.Tests/DividendTokenTests.cs
@@ -0,0 +1,212 @@
+using Moq;
+using Stratis.SmartContracts;
+using Xunit;
+
+namespace DividendTokenContract.Tests
+{
+ public class DividendTokenTests
+ {
+ private readonly IPersistentState state;
+
+ private readonly Mock mContractState;
+ private readonly Mock mContractLogger;
+ private readonly Mock 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();
+ this.mContractLogger = new Mock();
+ this.mTransactionExecutor = new Mock();
+ 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));
+ }
+
+ ///
+ /// 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)
+ ///
+ [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($"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());
+ }
+
+ ///
+ /// GetTotalDividends should to return Withdrawable + Withdrawn dividends
+ ///
+ [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());
+ }
+ }
+}
diff --git a/Mainnet/DividendToken/DividendToken.Tests/InMemoryState.cs b/Mainnet/DividendToken/DividendToken.Tests/InMemoryState.cs
new file mode 100644
index 00000000..53e14132
--- /dev/null
+++ b/Mainnet/DividendToken/DividendToken.Tests/InMemoryState.cs
@@ -0,0 +1,83 @@
+using Stratis.SmartContracts;
+using System;
+using System.Collections.Generic;
+
+namespace DividendTokenContract.Tests
+{
+ public class InMemoryState : IPersistentState
+ {
+ private readonly Dictionary storage = new Dictionary();
+ public bool IsContractResult { get; set; }
+ public void Clear(string key) => storage.Remove(key);
+
+ public T GetValue(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(key);
+
+ public T[] GetArray(string key) => GetValue(key);
+
+ public bool GetBool(string key) => GetValue(key);
+
+ public byte[] GetBytes(byte[] key) => throw new NotImplementedException();
+
+ public byte[] GetBytes(string key) => GetValue(key);
+
+ public char GetChar(string key) => GetValue(key);
+
+ public int GetInt32(string key) => GetValue(key);
+
+ public long GetInt64(string key) => GetValue(key);
+
+ public string GetString(string key) => GetValue(key);
+
+ public T GetStruct(string key)
+ where T : struct => GetValue(key);
+
+ public uint GetUInt32(string key) => GetValue(key);
+
+ public ulong GetUInt64(string key) => GetValue(key);
+
+ public UInt128 GetUInt128(string key) => GetValue(key);
+
+ public UInt256 GetUInt256(string key) => GetValue(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(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);
+ }
+}
\ No newline at end of file
diff --git a/Mainnet/DividendToken/DividendToken.Tests/TransferResult.cs b/Mainnet/DividendToken/DividendToken.Tests/TransferResult.cs
new file mode 100644
index 00000000..a20cd46f
--- /dev/null
+++ b/Mainnet/DividendToken/DividendToken.Tests/TransferResult.cs
@@ -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 };
+ }
+}
diff --git a/Mainnet/DividendToken/DividendToken.sln b/Mainnet/DividendToken/DividendToken.sln
new file mode 100644
index 00000000..fff0a409
--- /dev/null
+++ b/Mainnet/DividendToken/DividendToken.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30204.135
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DividendToken", "DividendToken\DividendToken.csproj", "{3AD529F2-7163-4BBE-80BB-F2D7D54B2DFA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DividendToken.Tests", "DividendToken.Tests\DividendToken.Tests.csproj", "{3B5AF108-6986-4E17-9A01-EB297D6F0C15}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3AD529F2-7163-4BBE-80BB-F2D7D54B2DFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3AD529F2-7163-4BBE-80BB-F2D7D54B2DFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3AD529F2-7163-4BBE-80BB-F2D7D54B2DFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3AD529F2-7163-4BBE-80BB-F2D7D54B2DFA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3B5AF108-6986-4E17-9A01-EB297D6F0C15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3B5AF108-6986-4E17-9A01-EB297D6F0C15}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3B5AF108-6986-4E17-9A01-EB297D6F0C15}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3B5AF108-6986-4E17-9A01-EB297D6F0C15}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {BA15305F-3348-4660-BF5B-3D0CD51F0FFD}
+ EndGlobalSection
+EndGlobal
diff --git a/Mainnet/DividendToken/DividendToken/DividendToken.cs b/Mainnet/DividendToken/DividendToken/DividendToken.cs
new file mode 100644
index 00000000..749760a8
--- /dev/null
+++ b/Mainnet/DividendToken/DividendToken/DividendToken.cs
@@ -0,0 +1,305 @@
+using Stratis.SmartContracts;
+using Stratis.SmartContracts.Standards;
+
+[Deploy]
+public class DividendToken : SmartContract, IStandardToken256
+{
+ public ulong Dividends
+ {
+ get => State.GetUInt64(nameof(this.Dividends));
+ private set => State.SetUInt64(nameof(this.Dividends), value);
+ }
+
+ private Account GetAccount(Address address) => State.GetStruct($"Account:{address}");
+
+ private void SetAccount(Address address, Account account) => State.SetStruct($"Account:{address}", account);
+
+
+ public DividendToken(ISmartContractState state, UInt256 totalSupply, string name, string symbol, byte decimals)
+ : base(state)
+ {
+ this.TotalSupply = totalSupply;
+ this.Name = name;
+ this.Symbol = symbol;
+ this.Decimals = decimals;
+ this.SetBalance(Message.Sender, totalSupply);
+ }
+
+
+ public override void Receive()
+ {
+ DistributeDividends();
+ }
+
+ ///
+ /// It is advised that deposit amount should to be evenly divided by total supply,
+ /// otherwise small amount of satoshi may lost(burn)
+ ///
+ public void DistributeDividends()
+ {
+ Dividends += Message.Value;
+ }
+
+ public bool TransferTo(Address to, UInt256 amount)
+ {
+ UpdateAccount(Message.Sender);
+ UpdateAccount(to);
+
+ return TransferTokensTo(to, amount);
+ }
+
+ public bool TransferFrom(Address from, Address to, UInt256 amount)
+ {
+ UpdateAccount(from);
+ UpdateAccount(to);
+
+ return TransferTokensFrom(from, to, amount);
+ }
+
+ private Account UpdateAccount(Address address)
+ {
+ var account = GetAccount(address);
+ var newDividends = GetNewDividends(address, account);
+
+ if (newDividends > 0)
+ {
+ account.DividendBalance += newDividends;
+ account.CreditedDividends = Dividends;
+ SetAccount(address, account);
+ }
+
+ return account;
+ }
+
+ private UInt256 GetWithdrawableDividends(Address address, Account account)
+ {
+ return account.DividendBalance + GetNewDividends(address, account); //Delay divide by TotalSupply to final stage for avoid decimal value loss.
+ }
+
+ private UInt256 GetNewDividends(Address address, Account account)
+ {
+ var notCreditedDividends = checked(Dividends - account.CreditedDividends);
+ return GetBalance(address) * notCreditedDividends;
+ }
+
+ ///
+ /// Get Withdrawable dividends
+ ///
+ ///
+ public ulong GetDividends() => GetDividends(Message.Sender);
+
+ ///
+ /// Get Withdrawable dividends
+ ///
+ ///
+ ///
+ public ulong GetDividends(Address address)
+ {
+ var account = GetAccount(address);
+
+ var withdrawable = GetWithdrawableDividends(address, account) / TotalSupply;
+
+ return (ulong)withdrawable;
+ }
+
+ ///
+ /// Get the all divididends since beginning (Withdrawable Dividends + Withdrawn Dividends)
+ ///
+ ///
+ public ulong GetTotalDividends() => GetTotalDividends(Message.Sender);
+
+ ///
+ /// Get the all divididends since beginning (Withdrawable Dividends + Withdrawn Dividends)
+ ///
+ ///
+ ///
+ public ulong GetTotalDividends(Address address)
+ {
+ var account = GetAccount(address);
+ var withdrawable = GetWithdrawableDividends(address, account) / TotalSupply;
+ return (ulong)withdrawable + account.WithdrawnDividends;
+ }
+
+ ///
+ /// Withdraws all dividends
+ ///
+ public void Withdraw()
+ {
+ var account = UpdateAccount(Message.Sender);
+ var balance = (ulong)(account.DividendBalance / TotalSupply);
+
+ Assert(balance > 0, "The account has no dividends.");
+
+ account.WithdrawnDividends += balance;
+
+ account.DividendBalance %= TotalSupply;
+
+ SetAccount(Message.Sender, account);
+
+ var transfer = Transfer(Message.Sender, balance);
+
+ Assert(transfer.Success, "Transfer failed.");
+ }
+
+ public struct Account
+ {
+ ///
+ /// Withdrawable Dividend Balance. Exact value should to divided by
+ ///
+ public UInt256 DividendBalance;
+
+ public ulong WithdrawnDividends;
+
+ ///
+ /// Dividends computed and added to
+ ///
+
+ public ulong CreditedDividends;
+ }
+
+ #region StandardToken code is inlined
+
+
+ public string Symbol
+ {
+ get => State.GetString(nameof(this.Symbol));
+ private set => State.SetString(nameof(this.Symbol), value);
+ }
+
+ public string Name
+ {
+ get => State.GetString(nameof(this.Name));
+ private set => State.SetString(nameof(this.Name), value);
+ }
+
+ ///
+ public UInt256 TotalSupply
+ {
+ get => State.GetUInt256(nameof(this.TotalSupply));
+ private set => State.SetUInt256(nameof(this.TotalSupply), value);
+ }
+
+ public byte Decimals
+ {
+ get => State.GetBytes(nameof(Decimals))[0];
+ private set => State.SetBytes(nameof(Decimals), new[] { value });
+ }
+
+
+ ///
+ public UInt256 GetBalance(Address address)
+ {
+ return State.GetUInt256($"Balance:{address}");
+ }
+
+ private void SetBalance(Address address, UInt256 value)
+ {
+ State.SetUInt256($"Balance:{address}", value);
+ }
+
+ ///
+ private bool TransferTokensTo(Address to, UInt256 amount)
+ {
+ if (amount == 0)
+ {
+ Log(new TransferLog { From = Message.Sender, To = to, Amount = 0 });
+
+ return true;
+ }
+
+ UInt256 senderBalance = GetBalance(Message.Sender);
+
+ if (senderBalance < amount)
+ {
+ return false;
+ }
+
+ SetBalance(Message.Sender, senderBalance - amount);
+
+ SetBalance(to, checked(GetBalance(to) + amount));
+
+ Log(new TransferLog { From = Message.Sender, To = to, Amount = amount });
+
+ return true;
+ }
+
+ ///
+ private bool TransferTokensFrom(Address from, Address to, UInt256 amount)
+ {
+ if (amount == 0)
+ {
+ Log(new TransferLog { From = from, To = to, Amount = 0 });
+
+ return true;
+ }
+
+ UInt256 senderAllowance = Allowance(from, Message.Sender);
+ UInt256 fromBalance = GetBalance(from);
+
+ if (senderAllowance < amount || fromBalance < amount)
+ {
+ return false;
+ }
+
+ SetApproval(from, Message.Sender, senderAllowance - amount);
+
+ SetBalance(from, fromBalance - amount);
+
+ SetBalance(to, checked(GetBalance(to) + amount));
+
+ Log(new TransferLog { From = from, To = to, Amount = amount });
+
+ return true;
+ }
+
+ ///
+ public bool Approve(Address spender, UInt256 currentAmount, UInt256 amount)
+ {
+ if (Allowance(Message.Sender, spender) != currentAmount)
+ {
+ return false;
+ }
+
+ SetApproval(Message.Sender, spender, amount);
+
+ Log(new ApprovalLog { Owner = Message.Sender, Spender = spender, Amount = amount, OldAmount = currentAmount });
+
+ return true;
+ }
+
+ private void SetApproval(Address owner, Address spender, UInt256 value)
+ {
+ State.SetUInt256($"Allowance:{owner}:{spender}", value);
+ }
+
+ ///
+ public UInt256 Allowance(Address owner, Address spender)
+ {
+ return State.GetUInt256($"Allowance:{owner}:{spender}");
+ }
+
+ public struct TransferLog
+ {
+ [Index]
+ public Address From;
+
+ [Index]
+ public Address To;
+
+ public UInt256 Amount;
+ }
+
+ public struct ApprovalLog
+ {
+ [Index]
+ public Address Owner;
+
+ [Index]
+ public Address Spender;
+
+ public UInt256 OldAmount;
+
+ public UInt256 Amount;
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/Mainnet/DividendToken/DividendToken/DividendToken.csproj b/Mainnet/DividendToken/DividendToken/DividendToken.csproj
new file mode 100644
index 00000000..8733aff9
--- /dev/null
+++ b/Mainnet/DividendToken/DividendToken/DividendToken.csproj
@@ -0,0 +1,11 @@
+
+
+
+ netcoreapp2.1
+ 8.0
+
+
+
+
+
+
diff --git a/Mainnet/DividendToken/README.MD b/Mainnet/DividendToken/README.MD
new file mode 100644
index 00000000..db698d8e
--- /dev/null
+++ b/Mainnet/DividendToken/README.MD
@@ -0,0 +1,11 @@
+# Dividend Token Contract
+
+**Contract Hash**
+```
+41a37d258e324a6c51ec2099a1a20546c696fecf2a649bd6e6cd8add835d46ce
+```
+
+**Contract Byte Code**
+```
+4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000504500004C0102001B3479CF0000000000000000E00022200B013000001400000002000000000000B2320000002000000040000000000010002000000002000004000000000000000400000000000000006000000002000000000000030040850000100000100000000010000010000000000000100000000000000000000000603200004F000000000000000000000000000000000000000000000000000000004000000C000000443200001C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000080000000000000000000000082000004800000000000000000000002E74657874000000B8120000002000000014000000020000000000000000000000000000200000602E72656C6F6300000C00000000400000000200000016000000000000000000000000000040000042000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000094320000000000004800000002000500AC250000980C000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004602280600000A72010000706F0700000A2A4A02280600000A7201000070036F0800000A2A7202280600000A7215000070038C08000001280900000A6F0100002B2A7602280600000A7215000070038C08000001280900000A046F0200002B2AC20203280C00000A0204281700000602052815000006020E0428130000060202280D00000A6F0E00000A0428190000062A1E0228070000062A660202280100000602280D00000A6F0F00000AD728020000062A8E0202280D00000A6F0E00000A280A000006260203280A00000626020304281A0000062A6A0203280A000006260204280A0000062602030405281B0000062A0000133003003C00000001000011020328030000060A020306280C0000060B07166A36241200067B0100000407D77D0100000412000228010000067D030000040203062804000006062A42047B01000004020304280C000006D72A000000133002001800000002000011022801000006047B03000004DB0A0203281800000606D92A4A0202280D00000A6F0E00000A280E0000062A00133003001800000003000011020328030000060A020306280B0000060228160000065C2A4A0202280D00000A6F0E00000A28100000062A00133003001F00000003000011020328030000060A020306280B000006067B02000004D70228160000065C2A001330030093000000040000110202280D00000A6F0E00000A280A0000060A067B010000040228160000065C0B067B010000040228160000065E0C0207166AFE03722D000070281000000A1200067B02000004067B01000004D708DB7D020000041200087D010000040202280D00000A6F0E00000A0628040000060202280D00000A6F0E00000A07281100000A0D02096F1200000A7269000070281000000A2A4602280600000A728B0000706F1300000A2A4A02280600000A728B000070036F1400000A2A4602280600000A72990000706F1300000A2A4A02280600000A7299000070036F1400000A2A4602280600000A72A30000706F0700000A2A4A02280600000A72A3000070036F0800000A2A7202280600000A72BB000070038C08000001280900000A6F0700000A2A7602280600000A72BB000070038C08000001280900000A046F0800000A2A00000013300400A600000005000011042D34021201FE1504000002120102280D00000A6F0E00000A7D040000041201037D050000041201166A7D0600000407280300002B172A0202280D00000A6F0E00000A28180000060A06043402162A0202280D00000A6F0E00000A0604DB281900000602030203281800000604D72819000006021201FE1504000002120102280D00000A6F0E00000A7D040000041201037D050000041201047D0600000407280300002B172A000013300500AA00000006000011052D2A021202FE15040000021202037D040000041202047D050000041202166A7D0600000408280300002B172A020302280D00000A6F0E00000A281E0000060A020328180000060B0605370407053402162A020302280D00000A6F0E00000A0605DB281D00000602030705DB281900000602040204281800000605D72819000006021202FE15040000021202037D040000041202047D050000041202057D0600000408280300002B172A00001330040065000000070000110202280D00000A6F0E00000A03281E000006042E02162A0202280D00000A6F0E00000A0305281D000006021200FE1505000002120002280D00000A6F0E00000A7D070000041200037D080000041200057D0A0000041200047D0900000406280400002B172A8E02280600000A72D3000070038C08000001048C08000001281600000A056F0800000A2A8A02280600000A72D3000070038C08000001048C08000001281600000A6F0700000A2A42534A4201000100000000000C00000076342E302E33303331390000000005006C00000058050000237E0000C40500006C04000023537472696E677300000000300A0000F800000023555300280B0000100000002347554944000000380B00006001000023426C6F6200000000000000020000015717A201090A000000FA0133001600000100000010000000050000000A0000001E0000002700000001000000160000000800000007000000010000000400000008000000010000000300000003000000040000000000A3010100000000000600EE00410306002D0141030600DA0070020F00610300000A001D018C030A00AA038C030E000C0220030A007C038C030A009B008C030A00D3038C0306009100D2010A000E018C030A00AF008C0306007801D2010600B803D2010A0065008C03000000001500000000000100010001001000FE0100001900010001000A011000FF0300002D0001001F000A0110008B0100002D0004001F000A0110007F0100002D0007001F0006003500B6000600F002B60006009F02B6000600F401B90006003402B90006002004B60006005E02B90006004502B90006000F04B60006002004B600502000000000860883024A00010062200000000081089102BD0001007520000000008100F103C20002009220000000008100FC03C9000300B0200000000086186A02D1000500E12000000000C6005B0106000900E920000000008600CA0206000900032100000000E6011B02DA000900272100000000E601D901E1000B004421000000008100E303C2000E008C21000000008100B102EA000F00A0210000000081001003EA001100C42100000000860003034A001300D8210000000086000303F2001300FC21000000008600DE024A0014001022000000008600DE02F20014003C22000000008600350406001500DB22000000008608B501F8001500ED22000000008108C001FC00150000230000000086087A00F800160012230000000081088300FC001600252300000000E6093E044A00170037230000000081084E04BD0017004A2300000000E6014500F2001800672300000000810050000101190088230000000081002602DA001B003C24000000008100E601E1001D00F42400000000E6016301080120006525000000008100970110012300892500000000E6015B001901260000000100550100000100840300000100840300000200070400000100D400000002005E04000003008C0000000400CB0100000100370200000200270400000100F90100000200370200000300270400000100840300000100840300000200070400000100840300000200070400000100840300000100840300000100550100000100550100000100550100000100840300000100840300000200550100000100370200000200270400000100F901000002003702000003002704000001004D02000002001904000003002704000001006402000002004D02000003005501000001006402000002004D0202001D0009006A02010011006A02060019006A020A0029006A02060061006A0206003100C000100069000100150069000B001A007100A30320006900BF0326006900C903320031006A023A0031006E00400081003A02450081004B014A0031002E046600310055026C0051007003740069006B017800690075017D003100930189007100A303A6002E000B0029012E00130032012E001B005101430023005A0181002B005A01A1002B005A01E1002B005A0101012B005A014E00540058005D00830095009C00020001000000160321010000C4012501000087002501000052042101020001000300010002000300020012000500010013000500020014000700010015000700020016000900010017000900048000000000000000000000000000000000AA030000040000000000000000000000AD001E000000000001000200010000000000000000008C0300000000010000000000000000000000000020030000000003000200040002000500020015002D0017002D002B0090002B00A10000000047657455496E7436340053657455496E743634003C4D6F64756C653E0053797374656D2E507269766174652E436F72654C6962004469766964656E6442616C616E63650047657442616C616E63650053657442616C616E636500416C6C6F77616E636500494D657373616765006765745F4D657373616765006765745F4E616D65007365745F4E616D65006E616D650056616C7565547970650049536D617274436F6E74726163745374617465004950657273697374656E745374617465006765745F50657273697374656E7453746174650073746174650044656275676761626C6541747472696275746500436F6D70696C6174696F6E52656C61786174696F6E7341747472696275746500496E646578417474726962757465004465706C6F794174747269627574650052756E74696D65436F6D7061746962696C697479417474726962757465006765745F56616C75650076616C7565005265636569766500417070726F766500476574537472696E6700536574537472696E6700417070726F76616C4C6F67005472616E736665724C6F6700536574417070726F76616C00536D617274436F6E74726163742E646C6C006765745F53796D626F6C007365745F53796D626F6C0073796D626F6C0053797374656D005472616E7366657246726F6D005472616E73666572546F6B656E7346726F6D0066726F6D004469766964656E64546F6B656E00495374616E64617264546F6B656E005472616E73666572546F005472616E73666572546F6B656E73546F00746F006765745F53656E646572005370656E646572007370656E646572005472616E73666572004F776E6572006F776E6572002E63746F720053797374656D2E446961676E6F7374696373006765745F4469766964656E6473007365745F4469766964656E64730043726564697465644469766964656E647300476574576974686472617761626C654469766964656E647300446973747269627574654469766964656E647300476574546F74616C4469766964656E64730057697468647261776E4469766964656E6473004765744469766964656E6473004765744E65774469766964656E647300537472617469732E536D617274436F6E7472616374732E5374616E64617264730053797374656D2E52756E74696D652E436F6D70696C6572536572766963657300446562756767696E674D6F646573006765745F537563636573730041646472657373006164647265737300537472617469732E536D617274436F6E74726163747300466F726D617400536D617274436F6E7472616374004F626A656374004765745374727563740053657453747275637400495472616E73666572526573756C74005570646174654163636F756E74004765744163636F756E74005365744163636F756E74006163636F756E74004F6C64416D6F756E740063757272656E74416D6F756E7400616D6F756E7400417373657274005769746864726177006765745F546F74616C537570706C79007365745F546F74616C537570706C7900746F74616C537570706C7900000000134400690076006900640065006E006400730000174100630063006F0075006E0074003A007B0030007D00003B54006800650020006100630063006F0075006E007400200068006100730020006E006F0020006400690076006900640065006E00640073002E0000215400720061006E00730066006500720020006600610069006C00650064002E00000D530079006D0062006F006C0000094E0061006D006500001754006F00740061006C0053007500700070006C0079000017420061006C0061006E00630065003A007B0030007D00002341006C006C006F00770061006E00630065003A007B0030007D003A007B0031007D00000043222F817C95CB4498A926937E6400A20004200101080320000105200101111104200012350420010B0E052002010E0B0500020E0E1C063001011E000E040A01110C07300102010E1E00052001011225042000124104200011210320000B050702110C0B0307010B040701110C080704110C0B0B122905200201020E072002122911210B032000020420010E0E052002010E0E0507020B111006300101011E00040A0111100607030B0B11100407011114040A0111140600030E0E1C1C087CEC85D7BEA7798E02060B03061121042001010B062001110C1121072002011121110C0820040112250B0E0E0620020211210B08200302112111210B0720020B1121110C0520010B11210320000E042001010E0620020111210B0720030211210B0B08200301112111210B0720020B112111210328000B0328000E0801000800000000001E01000100540216577261704E6F6E457863657074696F6E5468726F77730108010002000000000004010000000000000000000000000000000010000000000000000000000000000000883200000000000000000000A232000000200000000000000000000000000000000000000000000094320000000000000000000000005F436F72446C6C4D61696E006D73636F7265652E646C6C0000000000FF250020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000C000000B43200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+```
diff --git a/Testnet/DividendToken/DividendToken.Tests/DividendToken.Tests.csproj b/Testnet/DividendToken/DividendToken.Tests/DividendToken.Tests.csproj
new file mode 100644
index 00000000..be593740
--- /dev/null
+++ b/Testnet/DividendToken/DividendToken.Tests/DividendToken.Tests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Exe
+ netcoreapp3.1
+ DividendTokenContract.Tests
+
+
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
diff --git a/Testnet/DividendToken/DividendToken.Tests/DividendTokenTests.cs b/Testnet/DividendToken/DividendToken.Tests/DividendTokenTests.cs
new file mode 100644
index 00000000..98b908a2
--- /dev/null
+++ b/Testnet/DividendToken/DividendToken.Tests/DividendTokenTests.cs
@@ -0,0 +1,212 @@
+using Moq;
+using Stratis.SmartContracts;
+using Stratis.SmartContracts.CLR;
+using Xunit;
+
+namespace DividendTokenContract.Tests
+{
+ public class DividendTokenTests
+ {
+ private readonly IPersistentState persistentState;
+
+ private readonly Mock mContractState;
+ private readonly Mock mContractLogger;
+ private readonly Mock mTransactionExecutor;
+
+ private readonly Address owner;
+ private readonly Address tokenHolder;
+ private readonly Address contract;
+
+ private readonly string name;
+ private readonly string symbol;
+ private readonly UInt256 totalSupply;
+ private readonly uint decimals;
+
+ public DividendTokenTests()
+ {
+ this.persistentState = new InMemoryState();
+ this.mContractState = new Mock();
+ this.mContractLogger = new Mock();
+ this.mTransactionExecutor = new Mock();
+ this.mContractState.Setup(s => s.PersistentState).Returns(this.persistentState);
+ this.mContractState.Setup(s => s.ContractLogger).Returns(this.mContractLogger.Object);
+ this.mContractState.Setup(s => s.InternalTransactionExecutor).Returns(this.mTransactionExecutor.Object);
+ this.owner = "0x0000000000000000000000000000000000000001".HexToAddress();
+ this.tokenHolder = "0x0000000000000000000000000000000000000002".HexToAddress();
+ this.contract = "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;
+
+ this.mContractState.Setup(m => m.Message).Returns(new Message(this.contract, this.owner, dividend));
+
+ var contract = new DividendToken(this.mContractState.Object, this.totalSupply, this.name, this.symbol, this.decimals);
+
+ Assert.True(contract.TransferTo(this.tokenHolder, 100));
+
+ contract.Receive();
+
+ Assert.Equal(dividend, contract.Dividends);
+ Assert.Equal(100ul, contract.GetDividends(this.tokenHolder));
+ Assert.Equal(100ul, contract.GetTotalDividends(this.tokenHolder));
+ Assert.Equal(900ul, contract.GetDividends(this.owner));
+ Assert.Equal(900ul, contract.GetTotalDividends(this.owner));
+ }
+
+ [Fact]
+ public void Multiple_Deposited_Dividend_Should_Be_Distributed_Equaly()
+ {
+ var dividend = 1000ul;
+
+ this.mContractState.Setup(m => m.Message).Returns(new Message(this.contract, this.owner, dividend));
+
+ var contract = new DividendToken(this.mContractState.Object, this.totalSupply, this.name, this.symbol, this.decimals);
+
+ Assert.True(contract.TransferTo(this.tokenHolder, 100));
+
+ contract.Receive();
+
+ Assert.True(contract.TransferTo(this.tokenHolder, 100));
+
+ contract.Receive();
+
+ Assert.True(contract.TransferTo(this.tokenHolder, 100));
+
+ Assert.Equal(2 * dividend, contract.Dividends);
+ Assert.Equal(300ul, contract.GetDividends(this.tokenHolder));
+ Assert.Equal(300ul, contract.GetTotalDividends(this.tokenHolder));
+
+ Assert.Equal(1700ul, contract.GetDividends(this.owner));
+ Assert.Equal(1700ul, contract.GetTotalDividends(this.owner));
+ }
+
+ [Fact]
+ public void Cumulative_Dividends_Should_Be_Distributed_Equaly()
+ {
+ var dividend = 1000ul;
+
+ this.mContractState.Setup(m => m.Message).Returns(new Message(this.contract, this.owner, dividend));
+
+ var contract = new DividendToken(this.mContractState.Object, this.totalSupply, this.name, this.symbol, this.decimals);
+
+ Assert.True(contract.TransferTo(this.tokenHolder, 100));
+
+ contract.Receive();
+ contract.Receive();
+
+ Assert.Equal(2 * dividend, contract.Dividends);
+ Assert.Equal(2 * 100ul, contract.GetDividends(this.tokenHolder));
+ Assert.Equal(2 * 900ul, contract.GetDividends(this.owner));
+ }
+
+ ///
+ /// 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)
+ ///
+ [Fact]
+ public void Cumulative_Deposits_Should_Be_Distributed_Equaly_When_Dividends_Have_Decimal_Value()
+ {
+ var dividend = 500ul;
+
+ this.mContractState.Setup(m => m.Message).Returns(new Message(this.contract, this.owner, dividend));
+
+ var contract = new DividendToken(this.mContractState.Object, this.totalSupply, this.name, this.symbol, this.decimals);
+
+ Assert.True(contract.TransferTo(this.tokenHolder, 1));
+
+ contract.Receive();
+
+ Assert.Equal(dividend, contract.Dividends);
+ Assert.Equal(0ul, contract.GetDividends(this.tokenHolder));
+ Assert.Equal(499ul, contract.GetDividends(this.owner));
+
+ contract.Receive();
+
+ Assert.Equal(2 * dividend, contract.Dividends);
+ Assert.Equal(1ul, contract.GetDividends(this.tokenHolder));
+ Assert.Equal(999ul, contract.GetDividends(this.owner));
+ }
+
+ [Fact]
+ public void Deposited_Dividend_Should_Be_Withdrawable()
+ {
+ var dividend = 500ul;
+
+ this.mContractState.Setup(m => m.Message).Returns(new Message(this.contract, this.owner, dividend));
+ this.mContractState.Setup(m => m.GetBalance).Returns(() => dividend);
+ this.mTransactionExecutor.Setup(m => m.Transfer(this.mContractState.Object, this.tokenHolder, 5)).Returns(TransferResult.Transferred(true));
+
+ var contract = new DividendToken(this.mContractState.Object, this.totalSupply, this.name, this.symbol, this.decimals);
+
+ Assert.True(contract.TransferTo(this.tokenHolder, 11));
+
+ contract.Receive();
+
+ this.mContractState.Setup(m => m.Message).Returns(new Message(this.contract, this.tokenHolder, 0));
+
+ contract.Withdraw();
+
+ this.mTransactionExecutor.Verify(s => s.Transfer(this.mContractState.Object, this.tokenHolder, 5), Times.Once);
+ Assert.Equal(0ul, contract.GetDividends());
+ var account = this.persistentState.GetStruct($"Account:{this.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;
+
+ this.mContractState.Setup(m => m.Message).Returns(new Message(this.contract, this.owner, dividend));
+ this.mContractState.Setup(m => m.GetBalance).Returns(() => dividend);
+ this.mTransactionExecutor.Setup(m => m.Transfer(this.mContractState.Object, this.tokenHolder, 100)).Returns(TransferResult.Transferred(true));
+
+ var contract = new DividendToken(this.mContractState.Object, this.totalSupply, this.name, this.symbol, this.decimals);
+
+ Assert.True(contract.TransferTo(this.tokenHolder, 100));
+
+ this.mContractState.Setup(m => m.Message).Returns(new Message(this.contract, this.tokenHolder, dividend));
+ contract.Receive();
+
+ Assert.Equal(100ul, contract.GetDividends());
+ }
+
+ ///
+ /// GetTotalDividends should to return Withdrawable + Withdrawn dividends
+ ///
+ [Fact]
+ public void GetTotalDividends_Returns_Current_Sender_TotalDividends()
+ {
+ var dividend = 1000ul;
+
+ this.mContractState.Setup(m => m.Message).Returns(new Message(this.contract, this.owner, dividend));
+ this.mContractState.Setup(m => m.GetBalance).Returns(() => dividend);
+ this.mTransactionExecutor.Setup(m => m.Transfer(this.mContractState.Object, this.tokenHolder, 100)).Returns(TransferResult.Transferred(true));
+
+ var contract = new DividendToken(this.mContractState.Object, this.totalSupply, this.name, this.symbol, this.decimals);
+
+ Assert.True(contract.TransferTo(this.tokenHolder, 100));
+
+ contract.Receive();
+
+ this.mContractState.Setup(m => m.Message).Returns(new Message(this.contract, this.tokenHolder, 0));
+
+ contract.Withdraw();
+
+ this.mTransactionExecutor.Verify(s => s.Transfer(this.mContractState.Object, this.tokenHolder, 100), Times.Once);
+ Assert.Equal(0ul, contract.GetDividends());
+ Assert.Equal(100ul, contract.GetTotalDividends());
+ }
+ }
+}
diff --git a/Testnet/DividendToken/DividendToken.Tests/InMemoryState.cs b/Testnet/DividendToken/DividendToken.Tests/InMemoryState.cs
new file mode 100644
index 00000000..cc1cbffa
--- /dev/null
+++ b/Testnet/DividendToken/DividendToken.Tests/InMemoryState.cs
@@ -0,0 +1,82 @@
+using NBitcoin;
+using Stratis.SmartContracts;
+using System;
+using System.Collections.Generic;
+
+namespace DividendTokenContract.Tests
+{
+ public class InMemoryState : IPersistentState
+ {
+ private readonly Dictionary storage = new Dictionary();
+
+ public void Clear(string key) => this.storage.Remove(key);
+
+ public T GetValue(string key) => (T)this.storage.GetValueOrDefault(key, default(T));
+
+ public Address GetAddress(string key) => this.GetValue(key);
+
+ public T[] GetArray(string key) => this.GetValue(key);
+
+ public bool GetBool(string key) => this.GetValue(key);
+
+ public byte[] GetBytes(byte[] key) => throw new NotImplementedException();
+
+ public byte[] GetBytes(string key) => this.GetValue(key);
+
+ public char GetChar(string key) => this.GetValue(key);
+
+ public int GetInt32(string key) => this.GetValue(key);
+
+ public long GetInt64(string key) => this.GetValue(key);
+
+ public string GetString(string key) => this.GetValue(key);
+
+ public T GetStruct(string key)
+ where T : struct => this.GetValue(key);
+
+ public uint GetUInt32(string key) => this.GetValue(key);
+
+ public ulong GetUInt64(string key) => this.GetValue(key);
+
+ public UInt128 GetUInt128(string key) => this.GetValue(key);
+
+ public UInt256 GetUInt256(string key) => this.GetValue(key);
+
+ public bool IsContract(Address address)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void SetAddress(string key, Address value) => this.storage.AddOrReplace(key, value);
+
+ public void SetArray(string key, Array a) => this.storage.AddOrReplace(key, a);
+
+ public void SetBool(string key, bool value) => this.storage.AddOrReplace(key, value);
+
+ public void SetBytes(byte[] key, byte[] value)
+ {
+ throw new NotImplementedException();
+ }
+
+ public void SetBytes(string key, byte[] value) => this.storage.AddOrReplace(key, value);
+
+ public void SetChar(string key, char value) => this.storage.AddOrReplace(key, value);
+
+ public void SetInt32(string key, int value) => this.storage.AddOrReplace(key, value);
+
+ public void SetInt64(string key, long value) => this.storage.AddOrReplace(key, value);
+
+ public void SetString(string key, string value) => this.storage.AddOrReplace(key, value);
+
+ public void SetStruct(string key, T value)
+ where T : struct => this.storage.AddOrReplace(key, value);
+
+ public void SetUInt32(string key, uint value) => this.storage.AddOrReplace(key, value);
+
+ public void SetUInt64(string key, ulong value) => this.storage.AddOrReplace(key, value);
+
+ public void SetUInt128(string key, UInt128 value) => this.storage.AddOrReplace(key, value);
+
+ public void SetUInt256(string key, UInt256 value) => this.storage.AddOrReplace(key, value);
+ }
+}
diff --git a/Testnet/DividendToken/DividendToken.sln b/Testnet/DividendToken/DividendToken.sln
new file mode 100644
index 00000000..fff0a409
--- /dev/null
+++ b/Testnet/DividendToken/DividendToken.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30204.135
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DividendToken", "DividendToken\DividendToken.csproj", "{3AD529F2-7163-4BBE-80BB-F2D7D54B2DFA}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DividendToken.Tests", "DividendToken.Tests\DividendToken.Tests.csproj", "{3B5AF108-6986-4E17-9A01-EB297D6F0C15}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3AD529F2-7163-4BBE-80BB-F2D7D54B2DFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3AD529F2-7163-4BBE-80BB-F2D7D54B2DFA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3AD529F2-7163-4BBE-80BB-F2D7D54B2DFA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3AD529F2-7163-4BBE-80BB-F2D7D54B2DFA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3B5AF108-6986-4E17-9A01-EB297D6F0C15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3B5AF108-6986-4E17-9A01-EB297D6F0C15}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3B5AF108-6986-4E17-9A01-EB297D6F0C15}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3B5AF108-6986-4E17-9A01-EB297D6F0C15}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {BA15305F-3348-4660-BF5B-3D0CD51F0FFD}
+ EndGlobalSection
+EndGlobal
diff --git a/Testnet/DividendToken/DividendToken/DividendToken.cs b/Testnet/DividendToken/DividendToken/DividendToken.cs
new file mode 100644
index 00000000..2592979d
--- /dev/null
+++ b/Testnet/DividendToken/DividendToken/DividendToken.cs
@@ -0,0 +1,304 @@
+using Stratis.SmartContracts;
+using Stratis.SmartContracts.Standards;
+
+[Deploy]
+public class DividendToken : SmartContract, IStandardToken
+{
+
+ public ulong Dividends
+ {
+ get => PersistentState.GetUInt64(nameof(this.Dividends));
+ private set => PersistentState.SetUInt64(nameof(this.Dividends), value);
+ }
+
+ private Account GetAccount(Address address) => PersistentState.GetStruct($"Account:{address}");
+
+ private void SetAccount(Address address, Account account) => PersistentState.SetStruct($"Account:{address}", account);
+
+
+ public DividendToken(ISmartContractState state, UInt256 totalSupply, string name, string symbol,uint decimals)
+ : base(state)
+ {
+ this.TotalSupply = totalSupply;
+ this.Name = name;
+ this.Symbol = symbol;
+ this.Decimals = decimals;
+ this.SetBalance(Message.Sender, totalSupply);
+ }
+
+
+ public override void Receive()
+ {
+ DistributeDividends();
+ }
+
+ ///
+ /// It is advised that deposit amount should to be evenly divided by total supply,
+ /// otherwise small amount of satoshi may lost(burn)
+ ///
+ public void DistributeDividends()
+ {
+ Dividends += Message.Value;
+ }
+
+ public bool TransferTo(Address to, UInt256 amount)
+ {
+ UpdateAccount(Message.Sender);
+ UpdateAccount(to);
+
+ return TransferTokensTo(to, amount);
+ }
+
+ public bool TransferFrom(Address from, Address to, UInt256 amount)
+ {
+ UpdateAccount(from);
+ UpdateAccount(to);
+
+ return TransferTokensFrom(from, to, amount);
+ }
+
+ private Account UpdateAccount(Address address)
+ {
+ var account = GetAccount(address);
+ var newDividends = GetNewDividends(address, account);
+
+ if (newDividends > 0)
+ {
+ account.DividendBalance += newDividends;
+ account.CreditedDividends = Dividends;
+ SetAccount(address, account);
+ }
+
+ return account;
+ }
+
+ private UInt256 GetWithdrawableDividends(Address address, Account account)
+ {
+ return account.DividendBalance + GetNewDividends(address, account); //Delay divide by TotalSupply to final stage for avoid decimal value loss.
+ }
+
+ private UInt256 GetNewDividends(Address address, Account account)
+ {
+ var notCreditedDividends = checked(Dividends - account.CreditedDividends);
+ return GetBalance(address) * notCreditedDividends;
+ }
+
+ ///
+ /// Get Withdrawable dividends
+ ///
+ ///
+ public ulong GetDividends() => GetDividends(Message.Sender);
+
+ ///
+ /// Get Withdrawable dividends
+ ///
+ ///
+ ///
+ public ulong GetDividends(Address address)
+ {
+ var account = GetAccount(address);
+
+ return GetWithdrawableDividends(address, account) / TotalSupply;
+ }
+
+ ///
+ /// Get the all divididends since beginning (Withdrawable Dividends + Withdrawn Dividends)
+ ///
+ ///
+ public ulong GetTotalDividends() => GetTotalDividends(Message.Sender);
+
+ ///
+ /// Get the all divididends since beginning (Withdrawable Dividends + Withdrawn Dividends)
+ ///
+ ///
+ ///
+ public ulong GetTotalDividends(Address address)
+ {
+ var account = GetAccount(address);
+ var withdrawable = GetWithdrawableDividends(address, account) / TotalSupply;
+ return withdrawable + account.WithdrawnDividends;
+ }
+
+ ///
+ /// Withdraws all dividends
+ ///
+ public void Withdraw()
+ {
+ var account = UpdateAccount(Message.Sender);
+ var balance = account.DividendBalance / TotalSupply;
+
+ Assert(balance > 0, "The account has no dividends.");
+
+ account.WithdrawnDividends += balance;
+
+ account.DividendBalance %= TotalSupply;
+
+ SetAccount(Message.Sender, account);
+
+ var transfer = Transfer(Message.Sender, balance);
+
+ Assert(transfer.Success, "Transfer failed.");
+ }
+
+ public struct Account
+ {
+ ///
+ /// Withdrawable Dividend Balance. Exact value should to divided by
+ ///
+ public UInt256 DividendBalance;
+
+ public ulong WithdrawnDividends;
+
+ ///
+ /// Dividends computed and added to
+ ///
+
+ public ulong CreditedDividends;
+ }
+
+ #region StandardToken code is inlined
+
+
+ public string Symbol
+ {
+ get => PersistentState.GetString(nameof(this.Symbol));
+ private set => PersistentState.SetString(nameof(this.Symbol), value);
+ }
+
+ public string Name
+ {
+ get => PersistentState.GetString(nameof(this.Name));
+ private set => PersistentState.SetString(nameof(this.Name), value);
+ }
+
+ ///
+ public UInt256 TotalSupply
+ {
+ get => PersistentState.GetUInt256(nameof(this.TotalSupply));
+ private set => PersistentState.SetUInt256(nameof(this.TotalSupply), value);
+ }
+
+ public uint Decimals
+ {
+ get => PersistentState.GetUInt32(nameof(Decimals));
+ private set => PersistentState.SetUInt32(nameof(Decimals), value);
+ }
+
+
+ ///
+ public UInt256 GetBalance(Address address)
+ {
+ return PersistentState.GetUInt256($"Balance:{address}");
+ }
+
+ private void SetBalance(Address address, UInt256 value)
+ {
+ PersistentState.SetUInt256($"Balance:{address}", value);
+ }
+
+ ///
+ private bool TransferTokensTo(Address to, UInt256 amount)
+ {
+ if (amount == 0)
+ {
+ Log(new TransferLog { From = Message.Sender, To = to, Amount = 0 });
+
+ return true;
+ }
+
+ UInt256 senderBalance = GetBalance(Message.Sender);
+
+ if (senderBalance < amount)
+ {
+ return false;
+ }
+
+ SetBalance(Message.Sender, senderBalance - amount);
+
+ SetBalance(to, checked(GetBalance(to) + amount));
+
+ Log(new TransferLog { From = Message.Sender, To = to, Amount = amount });
+
+ return true;
+ }
+
+ ///
+ private bool TransferTokensFrom(Address from, Address to, UInt256 amount)
+ {
+ if (amount == 0)
+ {
+ Log(new TransferLog { From = from, To = to, Amount = 0 });
+
+ return true;
+ }
+
+ UInt256 senderAllowance = Allowance(from, Message.Sender);
+ UInt256 fromBalance = GetBalance(from);
+
+ if (senderAllowance < amount || fromBalance < amount)
+ {
+ return false;
+ }
+
+ SetApproval(from, Message.Sender, senderAllowance - amount);
+
+ SetBalance(from, fromBalance - amount);
+
+ SetBalance(to, checked(GetBalance(to) + amount));
+
+ Log(new TransferLog { From = from, To = to, Amount = amount });
+
+ return true;
+ }
+
+ ///
+ public bool Approve(Address spender, UInt256 currentAmount, UInt256 amount)
+ {
+ if (Allowance(Message.Sender, spender) != currentAmount)
+ {
+ return false;
+ }
+
+ SetApproval(Message.Sender, spender, amount);
+
+ Log(new ApprovalLog { Owner = Message.Sender, Spender = spender, Amount = amount, OldAmount = currentAmount });
+
+ return true;
+ }
+
+ private void SetApproval(Address owner, Address spender, UInt256 value)
+ {
+ PersistentState.SetUInt256($"Allowance:{owner}:{spender}", value);
+ }
+
+ ///
+ public UInt256 Allowance(Address owner, Address spender)
+ {
+ return PersistentState.GetUInt256($"Allowance:{owner}:{spender}");
+ }
+
+ public struct TransferLog
+ {
+ [Index]
+ public Address From;
+
+ [Index]
+ public Address To;
+
+ public UInt256 Amount;
+ }
+
+ public struct ApprovalLog
+ {
+ [Index]
+ public Address Owner;
+
+ [Index]
+ public Address Spender;
+
+ public UInt256 OldAmount;
+
+ public UInt256 Amount;
+ }
+ #endregion
+}
\ No newline at end of file
diff --git a/Testnet/DividendToken/DividendToken/DividendToken.csproj b/Testnet/DividendToken/DividendToken/DividendToken.csproj
new file mode 100644
index 00000000..227885dc
--- /dev/null
+++ b/Testnet/DividendToken/DividendToken/DividendToken.csproj
@@ -0,0 +1,12 @@
+
+
+
+ netcoreapp2.1
+
+ 8.0
+
+
+
+
+
+
diff --git a/Testnet/DividendToken/README.MD b/Testnet/DividendToken/README.MD
new file mode 100644
index 00000000..db698d8e
--- /dev/null
+++ b/Testnet/DividendToken/README.MD
@@ -0,0 +1,11 @@
+# Dividend Token Contract
+
+**Contract Hash**
+```
+41a37d258e324a6c51ec2099a1a20546c696fecf2a649bd6e6cd8add835d46ce
+```
+
+**Contract Byte Code**
+```
+4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000800000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000504500004C0102001B3479CF0000000000000000E00022200B013000001400000002000000000000B2320000002000000040000000000010002000000002000004000000000000000400000000000000006000000002000000000000030040850000100000100000000010000010000000000000100000000000000000000000603200004F000000000000000000000000000000000000000000000000000000004000000C000000443200001C0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000200000080000000000000000000000082000004800000000000000000000002E74657874000000B8120000002000000014000000020000000000000000000000000000200000602E72656C6F6300000C00000000400000000200000016000000000000000000000000000040000042000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000094320000000000004800000002000500AC250000980C000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004602280600000A72010000706F0700000A2A4A02280600000A7201000070036F0800000A2A7202280600000A7215000070038C08000001280900000A6F0100002B2A7602280600000A7215000070038C08000001280900000A046F0200002B2AC20203280C00000A0204281700000602052815000006020E0428130000060202280D00000A6F0E00000A0428190000062A1E0228070000062A660202280100000602280D00000A6F0F00000AD728020000062A8E0202280D00000A6F0E00000A280A000006260203280A00000626020304281A0000062A6A0203280A000006260204280A0000062602030405281B0000062A0000133003003C00000001000011020328030000060A020306280C0000060B07166A36241200067B0100000407D77D0100000412000228010000067D030000040203062804000006062A42047B01000004020304280C000006D72A000000133002001800000002000011022801000006047B03000004DB0A0203281800000606D92A4A0202280D00000A6F0E00000A280E0000062A00133003001800000003000011020328030000060A020306280B0000060228160000065C2A4A0202280D00000A6F0E00000A28100000062A00133003001F00000003000011020328030000060A020306280B000006067B02000004D70228160000065C2A001330030093000000040000110202280D00000A6F0E00000A280A0000060A067B010000040228160000065C0B067B010000040228160000065E0C0207166AFE03722D000070281000000A1200067B02000004067B01000004D708DB7D020000041200087D010000040202280D00000A6F0E00000A0628040000060202280D00000A6F0E00000A07281100000A0D02096F1200000A7269000070281000000A2A4602280600000A728B0000706F1300000A2A4A02280600000A728B000070036F1400000A2A4602280600000A72990000706F1300000A2A4A02280600000A7299000070036F1400000A2A4602280600000A72A30000706F0700000A2A4A02280600000A72A3000070036F0800000A2A7202280600000A72BB000070038C08000001280900000A6F0700000A2A7602280600000A72BB000070038C08000001280900000A046F0800000A2A00000013300400A600000005000011042D34021201FE1504000002120102280D00000A6F0E00000A7D040000041201037D050000041201166A7D0600000407280300002B172A0202280D00000A6F0E00000A28180000060A06043402162A0202280D00000A6F0E00000A0604DB281900000602030203281800000604D72819000006021201FE1504000002120102280D00000A6F0E00000A7D040000041201037D050000041201047D0600000407280300002B172A000013300500AA00000006000011052D2A021202FE15040000021202037D040000041202047D050000041202166A7D0600000408280300002B172A020302280D00000A6F0E00000A281E0000060A020328180000060B0605370407053402162A020302280D00000A6F0E00000A0605DB281D00000602030705DB281900000602040204281800000605D72819000006021202FE15040000021202037D040000041202047D050000041202057D0600000408280300002B172A00001330040065000000070000110202280D00000A6F0E00000A03281E000006042E02162A0202280D00000A6F0E00000A0305281D000006021200FE1505000002120002280D00000A6F0E00000A7D070000041200037D080000041200057D0A0000041200047D0900000406280400002B172A8E02280600000A72D3000070038C08000001048C08000001281600000A056F0800000A2A8A02280600000A72D3000070038C08000001048C08000001281600000A6F0700000A2A42534A4201000100000000000C00000076342E302E33303331390000000005006C00000058050000237E0000C40500006C04000023537472696E677300000000300A0000F800000023555300280B0000100000002347554944000000380B00006001000023426C6F6200000000000000020000015717A201090A000000FA0133001600000100000010000000050000000A0000001E0000002700000001000000160000000800000007000000010000000400000008000000010000000300000003000000040000000000A3010100000000000600EE00410306002D0141030600DA0070020F00610300000A001D018C030A00AA038C030E000C0220030A007C038C030A009B008C030A00D3038C0306009100D2010A000E018C030A00AF008C0306007801D2010600B803D2010A0065008C03000000001500000000000100010001001000FE0100001900010001000A011000FF0300002D0001001F000A0110008B0100002D0004001F000A0110007F0100002D0007001F0006003500B6000600F002B60006009F02B6000600F401B90006003402B90006002004B60006005E02B90006004502B90006000F04B60006002004B600502000000000860883024A00010062200000000081089102BD0001007520000000008100F103C20002009220000000008100FC03C9000300B0200000000086186A02D1000500E12000000000C6005B0106000900E920000000008600CA0206000900032100000000E6011B02DA000900272100000000E601D901E1000B004421000000008100E303C2000E008C21000000008100B102EA000F00A0210000000081001003EA001100C42100000000860003034A001300D8210000000086000303F2001300FC21000000008600DE024A0014001022000000008600DE02F20014003C22000000008600350406001500DB22000000008608B501F8001500ED22000000008108C001FC00150000230000000086087A00F800160012230000000081088300FC001600252300000000E6093E044A00170037230000000081084E04BD0017004A2300000000E6014500F2001800672300000000810050000101190088230000000081002602DA001B003C24000000008100E601E1001D00F42400000000E6016301080120006525000000008100970110012300892500000000E6015B001901260000000100550100000100840300000100840300000200070400000100D400000002005E04000003008C0000000400CB0100000100370200000200270400000100F90100000200370200000300270400000100840300000100840300000200070400000100840300000200070400000100840300000100840300000100550100000100550100000100550100000100840300000100840300000200550100000100370200000200270400000100F901000002003702000003002704000001004D02000002001904000003002704000001006402000002004D02000003005501000001006402000002004D0202001D0009006A02010011006A02060019006A020A0029006A02060061006A0206003100C000100069000100150069000B001A007100A30320006900BF0326006900C903320031006A023A0031006E00400081003A02450081004B014A0031002E046600310055026C0051007003740069006B017800690075017D003100930189007100A303A6002E000B0029012E00130032012E001B005101430023005A0181002B005A01A1002B005A01E1002B005A0101012B005A014E00540058005D00830095009C00020001000000160321010000C4012501000087002501000052042101020001000300010002000300020012000500010013000500020014000700010015000700020016000900010017000900048000000000000000000000000000000000AA030000040000000000000000000000AD001E000000000001000200010000000000000000008C0300000000010000000000000000000000000020030000000003000200040002000500020015002D0017002D002B0090002B00A10000000047657455496E7436340053657455496E743634003C4D6F64756C653E0053797374656D2E507269766174652E436F72654C6962004469766964656E6442616C616E63650047657442616C616E63650053657442616C616E636500416C6C6F77616E636500494D657373616765006765745F4D657373616765006765745F4E616D65007365745F4E616D65006E616D650056616C7565547970650049536D617274436F6E74726163745374617465004950657273697374656E745374617465006765745F50657273697374656E7453746174650073746174650044656275676761626C6541747472696275746500436F6D70696C6174696F6E52656C61786174696F6E7341747472696275746500496E646578417474726962757465004465706C6F794174747269627574650052756E74696D65436F6D7061746962696C697479417474726962757465006765745F56616C75650076616C7565005265636569766500417070726F766500476574537472696E6700536574537472696E6700417070726F76616C4C6F67005472616E736665724C6F6700536574417070726F76616C00536D617274436F6E74726163742E646C6C006765745F53796D626F6C007365745F53796D626F6C0073796D626F6C0053797374656D005472616E7366657246726F6D005472616E73666572546F6B656E7346726F6D0066726F6D004469766964656E64546F6B656E00495374616E64617264546F6B656E005472616E73666572546F005472616E73666572546F6B656E73546F00746F006765745F53656E646572005370656E646572007370656E646572005472616E73666572004F776E6572006F776E6572002E63746F720053797374656D2E446961676E6F7374696373006765745F4469766964656E6473007365745F4469766964656E64730043726564697465644469766964656E647300476574576974686472617761626C654469766964656E647300446973747269627574654469766964656E647300476574546F74616C4469766964656E64730057697468647261776E4469766964656E6473004765744469766964656E6473004765744E65774469766964656E647300537472617469732E536D617274436F6E7472616374732E5374616E64617264730053797374656D2E52756E74696D652E436F6D70696C6572536572766963657300446562756767696E674D6F646573006765745F537563636573730041646472657373006164647265737300537472617469732E536D617274436F6E74726163747300466F726D617400536D617274436F6E7472616374004F626A656374004765745374727563740053657453747275637400495472616E73666572526573756C74005570646174654163636F756E74004765744163636F756E74005365744163636F756E74006163636F756E74004F6C64416D6F756E740063757272656E74416D6F756E7400616D6F756E7400417373657274005769746864726177006765745F546F74616C537570706C79007365745F546F74616C537570706C7900746F74616C537570706C7900000000134400690076006900640065006E006400730000174100630063006F0075006E0074003A007B0030007D00003B54006800650020006100630063006F0075006E007400200068006100730020006E006F0020006400690076006900640065006E00640073002E0000215400720061006E00730066006500720020006600610069006C00650064002E00000D530079006D0062006F006C0000094E0061006D006500001754006F00740061006C0053007500700070006C0079000017420061006C0061006E00630065003A007B0030007D00002341006C006C006F00770061006E00630065003A007B0030007D003A007B0031007D00000043222F817C95CB4498A926937E6400A20004200101080320000105200101111104200012350420010B0E052002010E0B0500020E0E1C063001011E000E040A01110C07300102010E1E00052001011225042000124104200011210320000B050702110C0B0307010B040701110C080704110C0B0B122905200201020E072002122911210B032000020420010E0E052002010E0E0507020B111006300101011E00040A0111100607030B0B11100407011114040A0111140600030E0E1C1C087CEC85D7BEA7798E02060B03061121042001010B062001110C1121072002011121110C0820040112250B0E0E0620020211210B08200302112111210B0720020B1121110C0520010B11210320000E042001010E0620020111210B0720030211210B0B08200301112111210B0720020B112111210328000B0328000E0801000800000000001E01000100540216577261704E6F6E457863657074696F6E5468726F77730108010002000000000004010000000000000000000000000000000010000000000000000000000000000000883200000000000000000000A232000000200000000000000000000000000000000000000000000094320000000000000000000000005F436F72446C6C4D61696E006D73636F7265652E646C6C0000000000FF250020001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000000C000000B43200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+```