Skip to content

Commit

Permalink
Implement NEP-17 (neo-project#2024)
Browse files Browse the repository at this point in the history
* If exists

* Call onPayment if to it's a smart contract

* Increase cost in transfer

* Remove Mint check

* return

* Remove extra args

* Drop result

* Clean code

* Method.Exists

* Rename

* protected

* Update ApplicationEngine.Contract.cs

* Fix merge

* Add Name in Extra

* Name in manifest

* Fix UT

* dotnet format

* Remove Method.Exists

* Clean code

* Move filed `Name`

* Rename

* Update null checks

* Fix CallFromNativeContract parameters

* Update AssetDescriptor.cs

* Fix UT

* format

* Shargon's suggestion

* Update src/neo/SmartContract/Native/Tokens/Nep17Token.cs

Co-authored-by: Luchuan <luchuan@ngd.neo.org>

* Fix

Co-authored-by: Erik Zhang <erik@neo.org>
Co-authored-by: Luchuan <luchuan@ngd.neo.org>
  • Loading branch information
3 people authored and cloud8little committed Jan 24, 2021
1 parent ae9889a commit 07d9101
Show file tree
Hide file tree
Showing 11 changed files with 61 additions and 25 deletions.
8 changes: 8 additions & 0 deletions src/neo/SmartContract/Manifest/ContractManifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ public int Size
/// </summary>
public UInt160 Hash => Abi.Hash;

/// <summary>
/// Contract name
/// </summary>
public string Name { get; set; }

/// <summary>
/// A group represents a set of mutually trusted contracts. A contract will trust and allow any contract in the same group to invoke it, and the user interface will not give any warnings.
/// </summary>
Expand Down Expand Up @@ -110,6 +115,7 @@ public JObject ToJson()
{
return new JObject
{
["name"] = Name,
["groups"] = Groups.Select(u => u.ToJson()).ToArray(),
["supportedstandards"] = SupportedStandards.Select(u => new JString(u)).ToArray(),
["abi"] = Abi.ToJson(),
Expand All @@ -128,6 +134,7 @@ public ContractManifest Clone()
{
return new ContractManifest
{
Name = Name,
Groups = Groups.Select(p => p.Clone()).ToArray(),
SupportedStandards = SupportedStandards[..],
Abi = Abi.Clone(),
Expand Down Expand Up @@ -156,6 +163,7 @@ public void Deserialize(BinaryReader reader)

private void DeserializeFromJson(JObject json)
{
Name = json["name"].AsString();
Groups = ((JArray)json["groups"]).Select(u => ContractGroup.FromJson(u)).ToArray();
SupportedStandards = ((JArray)json["supportedstandards"]).Select(u => u.AsString()).ToArray();
Abi = ContractAbi.FromJson(json["abi"]);
Expand Down
1 change: 1 addition & 0 deletions src/neo/SmartContract/Native/NativeContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ protected NativeContract()
}
this.Manifest = new ContractManifest
{
Name = Name,
Groups = System.Array.Empty<ContractGroup>(),
SupportedStandards = new string[0],
Abi = new ContractAbi()
Expand Down
2 changes: 1 addition & 1 deletion src/neo/SmartContract/Native/Tokens/GasToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Neo.SmartContract.Native.Tokens
{
public sealed class GasToken : Nep5Token<AccountState>
public sealed class GasToken : Nep17Token<AccountState>
{
public override int Id => -2;
public override string Name => "GAS";
Expand Down
2 changes: 1 addition & 1 deletion src/neo/SmartContract/Native/Tokens/NeoToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

namespace Neo.SmartContract.Native.Tokens
{
public sealed class NeoToken : Nep5Token<NeoToken.NeoAccountState>
public sealed class NeoToken : Nep17Token<NeoToken.NeoAccountState>
{
public override int Id => -1;
public override string Name => "NEO";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

namespace Neo.SmartContract.Native.Tokens
{
public abstract class Nep5Token<TState> : NativeContract
public abstract class Nep17Token<TState> : NativeContract
where TState : AccountState, new()
{
[ContractMethod(0, CallFlags.None)]
Expand All @@ -22,11 +22,11 @@ public abstract class Nep5Token<TState> : NativeContract
protected const byte Prefix_TotalSupply = 11;
protected const byte Prefix_Account = 20;

protected Nep5Token()
protected Nep17Token()
{
this.Factor = BigInteger.Pow(10, Decimals);

Manifest.SupportedStandards = new[] { "NEP-5" };
Manifest.SupportedStandards = new[] { "NEP-17" };

var events = new List<ContractEventDescriptor>(Manifest.Abi.Events)
{
Expand Down Expand Up @@ -67,7 +67,7 @@ internal protected virtual void Mint(ApplicationEngine engine, UInt160 account,
state.Balance += amount;
storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_TotalSupply), () => new StorageItem(BigInteger.Zero));
storage.Add(amount);
engine.SendNotification(Hash, "Transfer", new Array { StackItem.Null, account.ToArray(), amount });
PostTransfer(engine, null, account, amount);
}

internal protected virtual void Burn(ApplicationEngine engine, UInt160 account, BigInteger amount)
Expand All @@ -85,7 +85,7 @@ internal protected virtual void Burn(ApplicationEngine engine, UInt160 account,
state.Balance -= amount;
storage = engine.Snapshot.Storages.GetAndChange(CreateStorageKey(Prefix_TotalSupply));
storage.Add(-amount);
engine.SendNotification(Hash, "Transfer", new Array { account.ToArray(), StackItem.Null, amount });
PostTransfer(engine, account, null, amount);
}

[ContractMethod(0_01000000, CallFlags.AllowStates)]
Expand All @@ -104,7 +104,7 @@ public virtual BigInteger BalanceOf(StoreView snapshot, UInt160 account)
return storage.GetInteroperable<TState>().Balance;
}

[ContractMethod(0_08000000, CallFlags.AllowModifyStates)]
[ContractMethod(0_09000000, CallFlags.AllowModifyStates)]
protected virtual bool Transfer(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount)
{
if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount));
Expand Down Expand Up @@ -143,12 +143,28 @@ protected virtual bool Transfer(ApplicationEngine engine, UInt160 from, UInt160
state_to.Balance += amount;
}
}
engine.SendNotification(Hash, "Transfer", new Array { from.ToArray(), to.ToArray(), amount });
PostTransfer(engine, from, to, amount);
return true;
}

protected virtual void OnBalanceChanging(ApplicationEngine engine, UInt160 account, TState state, BigInteger amount)
{
}

private void PostTransfer(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount)
{
// Send notification

engine.SendNotification(Hash, "Transfer",
new Array { from?.ToArray() ?? StackItem.Null, to?.ToArray() ?? StackItem.Null, amount });

// Check if it's a wallet or smart contract

if (to is null || engine.Snapshot.Contracts.TryGet(to) is null) return;

// Call onPayment method if exists (NEP-17)

engine.CallFromNativeContract(() => { }, to, "onPayment", from.ToArray(), amount);
}
}
}
11 changes: 8 additions & 3 deletions src/neo/Wallets/AssetDescriptor.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Neo.Ledger;
using Neo.Persistence;
using Neo.SmartContract;
using Neo.VM;
using System;
Expand All @@ -12,17 +14,20 @@ public class AssetDescriptor

public AssetDescriptor(UInt160 asset_id)
{
using SnapshotView snapshot = Blockchain.Singleton.GetSnapshot();
var contract = snapshot.Contracts.TryGet(asset_id);
if (contract is null) throw new ArgumentException();

byte[] script;
using (ScriptBuilder sb = new ScriptBuilder())
{
sb.EmitAppCall(asset_id, "decimals");
sb.EmitAppCall(asset_id, "name");
script = sb.ToArray();
}
using ApplicationEngine engine = ApplicationEngine.Run(script, gas: 3_000_000);
using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, gas: 0_02000000);
if (engine.State.HasFlag(VMState.FAULT)) throw new ArgumentException();
this.AssetId = asset_id;
this.AssetName = engine.ResultStack.Pop().GetString();
this.AssetName = contract.Manifest.Name;
this.Decimals = (byte)engine.ResultStack.Pop().GetInteger();
}

Expand Down
2 changes: 1 addition & 1 deletion tests/neo.UnitTests/Ledger/UT_ContractState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public void TestDeserialize()
public void TestGetSize()
{
ISerializable newContract = contract;
newContract.Size.Should().Be(218);
newContract.Size.Should().Be(240);
}

[TestMethod]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class UT_ContractManifest
[TestMethod]
public void ParseFromJson_Default()
{
var json = @"{""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":null}";
var json = @"{""name"":""testManifest"",""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":null}";
var manifest = ContractManifest.Parse(json);

Assert.AreEqual(manifest.ToString(), json);
Expand All @@ -22,7 +22,7 @@ public void ParseFromJson_Default()
[TestMethod]
public void ParseFromJson_Permissions()
{
var json = @"{""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""0x0000000000000000000000000000000000000000"",""methods"":[""method1"",""method2""]}],""trusts"":[],""safemethods"":[],""extra"":null}";
var json = @"{""name"":""testManifest"",""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""0x0000000000000000000000000000000000000000"",""methods"":[""method1"",""method2""]}],""trusts"":[],""safemethods"":[],""extra"":null}";
var manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

Expand All @@ -41,7 +41,7 @@ public void ParseFromJson_Permissions()
[TestMethod]
public void ParseFromJson_SafeMethods()
{
var json = @"{""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[""balanceOf""],""extra"":null}";
var json = @"{""name"":""testManifest"",""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[""balanceOf""],""extra"":null}";
var manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

Expand All @@ -53,7 +53,7 @@ public void ParseFromJson_SafeMethods()
[TestMethod]
public void ParseFromJson_Trust()
{
var json = @"{""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001""],""safemethods"":[],""extra"":null}";
var json = @"{""name"":""testManifest"",""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[""0x0000000000000000000000000000000000000001""],""safemethods"":[],""extra"":null}";
var manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

Expand All @@ -65,7 +65,7 @@ public void ParseFromJson_Trust()
[TestMethod]
public void ParseFromJson_Groups()
{
var json = @"{""groups"":[{""pubkey"":""03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"",""signature"":""QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ==""}],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":null}";
var json = @"{""name"":""testManifest"",""groups"":[{""pubkey"":""03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c"",""signature"":""QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ==""}],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":null}";
var manifest = ContractManifest.Parse(json);
Assert.AreEqual(manifest.ToString(), json);

Expand All @@ -77,7 +77,7 @@ public void ParseFromJson_Groups()
[TestMethod]
public void ParseFromJson_Extra()
{
var json = @"{""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":{""key"":""value""}}";
var json = @"{""name"":""testManifest"",""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":{""key"":""value""}}";
var manifest = ContractManifest.Parse(json);
Assert.AreEqual(json, json);
Assert.AreEqual("value", manifest.Extra["key"].AsString(), false);
Expand Down Expand Up @@ -110,7 +110,7 @@ public void TestGetHash()
public void TestGetSize()
{
var temp = TestUtils.CreateDefaultManifest(UInt160.Zero);
Assert.AreEqual(212, temp.Size);
Assert.AreEqual(234, temp.Size);
}

[TestMethod]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace Neo.UnitTests.SmartContract.Native.Tokens
{
[TestClass]
public class UT_Nep5Token : TestKit
public class UT_Nep17Token : TestKit
{
[TestInitialize]
public void TestSetup()
Expand All @@ -18,7 +18,13 @@ public void TestSetup()
}

protected const byte Prefix_TotalSupply = 11;
private static readonly TestNep5Token test = new TestNep5Token();
private static readonly TestNep17Token test = new TestNep17Token();

[TestMethod]
public void TestName()
{
Assert.AreEqual(test.Name, test.Manifest.Name);
}

[TestMethod]
public void TestTotalSupply()
Expand Down Expand Up @@ -71,11 +77,11 @@ public StorageKey CreateStorageKey(byte prefix, byte[] key = null)
}
}

public class TestNep5Token : Nep5Token<NeoToken.NeoAccountState>
public class TestNep17Token : Nep17Token<NeoToken.NeoAccountState>
{
public override int Id => 0x10000005;

public override string Name => "testNep5Token";
public override string Name => "testNep17Token";

public override string Symbol => throw new NotImplementedException();

Expand Down
1 change: 1 addition & 0 deletions tests/neo.UnitTests/TestUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public static ContractManifest CreateDefaultManifest(UInt160 hash)
{
return new ContractManifest()
{
Name = "testManifest",
Groups = new ContractGroup[0],
SupportedStandards = Array.Empty<string>(),
Abi = new ContractAbi()
Expand Down
1 change: 0 additions & 1 deletion tests/neo.UnitTests/Wallets/UT_AssetDescriptor.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.Persistence;
using Neo.SmartContract.Native;
using System;

Expand Down

0 comments on commit 07d9101

Please sign in to comment.