Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TestNet] Add DelegatedTransferWithMetadata method to MintableToken #99

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>

<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="Moq" Version="4.12.0" />
<PackageReference Include="Stratis.Bitcoin.Features.SmartContracts" Version="0.0.6-beta" />
<PackageReference Include="Stratis.SmartContracts.Networks" Version="0.0.1-beta" />
<PackageReference Include="Stratis.SmartContracts.CLR" Version="2.1.0-beta" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
</ItemGroup>
Expand Down
52 changes: 52 additions & 0 deletions Testnet/MintableToken/MintableToken.Tests/MintableTokenTests.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
using System.Linq;
using Moq;
using NBitcoin.DataEncoders;
using NBitcoin;
using Stratis.SmartContracts;
using Stratis.SmartContracts.CLR;
using Stratis.SmartContracts.CLR.Serialization;
using Xunit;

namespace MintableTokenTests
{
public class ChameleonNetwork : Network
{
public ChameleonNetwork(byte base58Prefix)
{
this.Base58Prefixes = new byte[][] { new byte[] { base58Prefix } };
}
}

/// <summary>
/// These tests validate the functionality that differs between the original standard token and the extended version.
/// </summary>
Expand Down Expand Up @@ -37,6 +49,9 @@ public MintableTokenTests()
this.name = "Test Token";
this.symbol = "TST";
this.decimals = 8;

var serializer = new Serializer(new ContractPrimitiveSerializerV2(null)); // new SmartContractsPoARegTest()
this.mockContractState.Setup(x => x.Serializer).Returns(serializer);
}

[Fact]
Expand Down Expand Up @@ -230,5 +245,42 @@ public void BurnWithMetadata_Records_Metadata_In_Log()

this.mockContractLogger.Verify(l => l.Log(It.IsAny<ISmartContractState>(), new MintableToken.BurnMetadata() { From = this.sender, Amount = burnAmount, Metadata = "Hello world" }));
}

[Fact]
public void CanPerformDelegatedTransfer()
{
UInt256 balance = 100;
UInt256 burnAmount = 20;

this.mockContractState.Setup(m => m.Message).Returns(new Message(this.contract, this.sender, 0));

var standardToken = new MintableToken(this.mockContractState.Object, 100_000, this.name, this.symbol, "CIRRUS", "Address");

// Setup the total supply
this.mockPersistentState.Setup(s => s.GetUInt256($"TotalSupply")).Returns(100_000);

// Setup the balance of the sender's address in persistent state
this.mockPersistentState.Setup(s => s.GetUInt256($"Balance:{this.sender}")).Returns(balance);

// Setup the balance of the recipient's address in persistent state
this.mockPersistentState.Setup(s => s.GetUInt256($"Balance:{Address.Zero}")).Returns(0);

var uniqueNumber = new UInt128("1");

var key = new Key(new HexEncoder().DecodeData("c6edd54dd0671f1415a94ad388265c4465a8b328cc51a0a1fe770d910b48b0d1"));
var address = key.PubKey.Hash.ToBytes().ToAddress();

var hexUniqueNumber = Encoders.Hex.EncodeData(uniqueNumber.ToBytes().Reverse().ToArray());

var contractAddress = this.contract.ToUint160().ToBase58Address(new ChameleonNetwork(1));
var fromAddress = address.ToUint160().ToBase58Address(new ChameleonNetwork(1));
var toAddress = this.destination.ToUint160().ToBase58Address(new ChameleonNetwork(1));

var url = $"webdemo.stratisplatform.com:7167/api/auth?uid={hexUniqueNumber}&from={fromAddress}&to={toAddress}&amount=1.23&metadata=metadata&contract={contractAddress}";
var signature = key.SignMessage(url);
byte[] signatureBytes = Encoders.Base64.DecodeData(signature);

standardToken.DelegatedTransferWithMetadata(url, signatureBytes);
}
}
}
75 changes: 67 additions & 8 deletions Testnet/MintableToken/MintableToken/MintableToken.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Stratis.SmartContracts;
using Stratis.SCL.Crypto;
using Stratis.SmartContracts;
using Stratis.SmartContracts.Standards;

/// <summary>
Expand All @@ -8,7 +9,8 @@
public class MintableToken : SmartContract, IStandardToken256, IMintable, IBurnable, IMintableWithMetadataForNetwork, IBurnableWithMetadata, IOwnable, IInterflux, IBlackList
{
/// <summary>
/// Constructor used to create a new instance of the token. Assigns the total token supply to the creator of the contract.
/// Initializes a new instance of the <see cref="MintableToken"/> class.
/// Assigns the total token supply to the creator of the contract.
/// </summary>
/// <param name="smartContractState">The execution state for the contract.</param>
/// <param name="totalSupply">The total token supply.</param>
Expand All @@ -34,6 +36,19 @@ public MintableToken(ISmartContractState smartContractState, UInt256 totalSupply
Log(new InterfluxChangedLog { PreviousInterflux = Address.Zero, NewInterflux = Message.Sender });
}

/// <summary>
/// Function to check for replays of signed transfers.
/// </summary>
/// <param name="transferID">A unique number identifying the transfer.</param>
/// <returns>True if the transfer had already been performed, false otherwise.</returns>
public bool KnownTransfer(UInt128 transferID) => State.GetBool($"Transfer:{transferID}");

/// <summary>
/// Records the <paramref name="transferID"/> of a signed transfer.
/// </summary>
/// <param name="transferID">A unique number identifying the transfer.</param>
private void SetKnownTransfer(UInt128 transferID) => State.SetBool($"Transfer:{transferID}", true);

public string Symbol
{
get => State.GetString(nameof(this.Symbol));
Expand Down Expand Up @@ -113,33 +128,39 @@ private void SetBlackListed(Address address, bool value)
}

/// <inheritdoc />
public bool TransferTo(Address to, UInt256 amount)
private bool TransferInternal(Address from, Address to, UInt256 amount)
{
BeforeTokenTransfer(Message.Sender, to);
BeforeTokenTransfer(from, to);

if (amount == 0)
{
Log(new TransferLog { From = Message.Sender, To = to, Amount = 0 });
Log(new TransferLog { From = from, To = to, Amount = 0 });

return true;
}

UInt256 senderBalance = GetBalance(Message.Sender);
UInt256 senderBalance = GetBalance(from);

if (senderBalance < amount)
{
return false;
}

SetBalance(Message.Sender, senderBalance - amount);
SetBalance(from, senderBalance - amount);

SetBalance(to, checked(GetBalance(to) + amount));

Log(new TransferLog { From = Message.Sender, To = to, Amount = amount });
Log(new TransferLog { From = from, To = to, Amount = amount });

return true;
}

/// <inheritdoc />
public bool TransferTo(Address to, UInt256 amount)
{
return TransferInternal(Message.Sender, to, amount);
}

public bool TransferToWithMetadata(Address to, UInt256 amount, string metadata)
{
Log(new MetadataLog { Metadata = metadata });
Expand Down Expand Up @@ -178,6 +199,44 @@ public bool TransferFrom(Address from, Address to, UInt256 amount)
return true;
}

public void DelegatedTransferWithMetadata(string url, byte[] signature)
{
var args = SSAS.GetURLArguments(url, new string[] { "uid", "contract", "from", "to", "amount", "metadata" });

Assert(args != null && args.Length == 6, "Invalid url.");
Assert(Serializer.ToAddress(SSAS.ParseAddress(args[1], out byte contractPrefix)) == this.Address, "Invalid 'contract' address.");

var uniqueNumber = UInt128.Parse($"0x{args[0]}");
Assert(!KnownTransfer(uniqueNumber), "The 'uid' has already been used.");

Assert(SSAS.TryGetSignerSHA256(Serializer.Serialize(url), signature, out Address signer), "Could not resolve signer.");

Address from = Serializer.ToAddress(SSAS.ParseAddress(args[2], out byte fromPrefix));
Assert(signer == from, "Signer of 'metadata' does not match 'from' address.");
Assert(fromPrefix == contractPrefix, "The 'from' address prefix is different from 'contract' address prefix.");

Address to = Serializer.ToAddress(SSAS.ParseAddress(args[3], out byte toPrefix));
Assert(toPrefix == contractPrefix, "The 'to' address prefix is different from 'contract' address prefix.");

var amount = ParseAmount(args[4]);

TransferInternal(from, to, amount);

SetKnownTransfer(uniqueNumber);

Log(new MetadataLog { Metadata = args[5] });
}

private UInt256 ParseAmount(string amount)
{
// Parse amount.
int amountDecimalIndex = amount.IndexOf('.');
int amountDecimals = amountDecimalIndex >= 0 ? amount.Length - amountDecimalIndex - 1 : 0;
Assert(amountDecimals <= 2, "Too many decimals in amount");

return UInt256.Parse(amount.PadRight(amount.Length + 8 - amountDecimals, '0').Replace(".", string.Empty));
}

private void OnlyOwner()
{
Assert(Message.Sender == Owner, "Only the owner can call this method");
Expand Down
3 changes: 2 additions & 1 deletion Testnet/MintableToken/MintableToken/MintableToken.csproj
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Stratis.SCL" Version="2.1.0-beta" />
<PackageReference Include="Stratis.SmartContracts" Version="2.0.0" />
<PackageReference Include="Stratis.SmartContracts.Standards" Version="2.0.0" />
</ItemGroup>
Expand Down
Loading