Skip to content

Commit 72eb3e6

Browse files
shargonerikzhangTommo-L
authored
Implement NEP-17 (#2024)
* 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 <[email protected]> * Fix Co-authored-by: Erik Zhang <[email protected]> Co-authored-by: Luchuan <[email protected]>
1 parent 54eff7e commit 72eb3e6

File tree

11 files changed

+61
-25
lines changed

11 files changed

+61
-25
lines changed

src/neo/SmartContract/Manifest/ContractManifest.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ public int Size
3434
/// </summary>
3535
public UInt160 Hash => Abi.Hash;
3636

37+
/// <summary>
38+
/// Contract name
39+
/// </summary>
40+
public string Name { get; set; }
41+
3742
/// <summary>
3843
/// 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.
3944
/// </summary>
@@ -110,6 +115,7 @@ public JObject ToJson()
110115
{
111116
return new JObject
112117
{
118+
["name"] = Name,
113119
["groups"] = Groups.Select(u => u.ToJson()).ToArray(),
114120
["supportedstandards"] = SupportedStandards.Select(u => new JString(u)).ToArray(),
115121
["abi"] = Abi.ToJson(),
@@ -128,6 +134,7 @@ public ContractManifest Clone()
128134
{
129135
return new ContractManifest
130136
{
137+
Name = Name,
131138
Groups = Groups.Select(p => p.Clone()).ToArray(),
132139
SupportedStandards = SupportedStandards[..],
133140
Abi = Abi.Clone(),
@@ -156,6 +163,7 @@ public void Deserialize(BinaryReader reader)
156163

157164
private void DeserializeFromJson(JObject json)
158165
{
166+
Name = json["name"].AsString();
159167
Groups = ((JArray)json["groups"]).Select(u => ContractGroup.FromJson(u)).ToArray();
160168
SupportedStandards = ((JArray)json["supportedstandards"]).Select(u => u.AsString()).ToArray();
161169
Abi = ContractAbi.FromJson(json["abi"]);

src/neo/SmartContract/Native/NativeContract.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ protected NativeContract()
6262
}
6363
this.Manifest = new ContractManifest
6464
{
65+
Name = Name,
6566
Groups = System.Array.Empty<ContractGroup>(),
6667
SupportedStandards = new string[0],
6768
Abi = new ContractAbi()

src/neo/SmartContract/Native/Tokens/GasToken.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace Neo.SmartContract.Native.Tokens
66
{
7-
public sealed class GasToken : Nep5Token<AccountState>
7+
public sealed class GasToken : Nep17Token<AccountState>
88
{
99
public override int Id => -2;
1010
public override string Name => "GAS";

src/neo/SmartContract/Native/Tokens/NeoToken.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
namespace Neo.SmartContract.Native.Tokens
1717
{
18-
public sealed class NeoToken : Nep5Token<NeoToken.NeoAccountState>
18+
public sealed class NeoToken : Nep17Token<NeoToken.NeoAccountState>
1919
{
2020
public override int Id => -1;
2121
public override string Name => "NEO";

src/neo/SmartContract/Native/Tokens/Nep5Token.cs renamed to src/neo/SmartContract/Native/Tokens/Nep17Token.cs

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
namespace Neo.SmartContract.Native.Tokens
1212
{
13-
public abstract class Nep5Token<TState> : NativeContract
13+
public abstract class Nep17Token<TState> : NativeContract
1414
where TState : AccountState, new()
1515
{
1616
[ContractMethod(0, CallFlags.None)]
@@ -22,11 +22,11 @@ public abstract class Nep5Token<TState> : NativeContract
2222
protected const byte Prefix_TotalSupply = 11;
2323
protected const byte Prefix_Account = 20;
2424

25-
protected Nep5Token()
25+
protected Nep17Token()
2626
{
2727
this.Factor = BigInteger.Pow(10, Decimals);
2828

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

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

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

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

107-
[ContractMethod(0_08000000, CallFlags.AllowModifyStates)]
107+
[ContractMethod(0_09000000, CallFlags.AllowModifyStates)]
108108
protected virtual bool Transfer(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount)
109109
{
110110
if (amount.Sign < 0) throw new ArgumentOutOfRangeException(nameof(amount));
@@ -143,12 +143,28 @@ protected virtual bool Transfer(ApplicationEngine engine, UInt160 from, UInt160
143143
state_to.Balance += amount;
144144
}
145145
}
146-
engine.SendNotification(Hash, "Transfer", new Array { from.ToArray(), to.ToArray(), amount });
146+
PostTransfer(engine, from, to, amount);
147147
return true;
148148
}
149149

150150
protected virtual void OnBalanceChanging(ApplicationEngine engine, UInt160 account, TState state, BigInteger amount)
151151
{
152152
}
153+
154+
private void PostTransfer(ApplicationEngine engine, UInt160 from, UInt160 to, BigInteger amount)
155+
{
156+
// Send notification
157+
158+
engine.SendNotification(Hash, "Transfer",
159+
new Array { from?.ToArray() ?? StackItem.Null, to?.ToArray() ?? StackItem.Null, amount });
160+
161+
// Check if it's a wallet or smart contract
162+
163+
if (to is null || engine.Snapshot.Contracts.TryGet(to) is null) return;
164+
165+
// Call onPayment method if exists (NEP-17)
166+
167+
engine.CallFromNativeContract(() => { }, to, "onPayment", from.ToArray(), amount);
168+
}
153169
}
154170
}

src/neo/Wallets/AssetDescriptor.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using Neo.Ledger;
2+
using Neo.Persistence;
13
using Neo.SmartContract;
24
using Neo.VM;
35
using System;
@@ -12,17 +14,20 @@ public class AssetDescriptor
1214

1315
public AssetDescriptor(UInt160 asset_id)
1416
{
17+
using SnapshotView snapshot = Blockchain.Singleton.GetSnapshot();
18+
var contract = snapshot.Contracts.TryGet(asset_id);
19+
if (contract is null) throw new ArgumentException();
20+
1521
byte[] script;
1622
using (ScriptBuilder sb = new ScriptBuilder())
1723
{
1824
sb.EmitAppCall(asset_id, "decimals");
19-
sb.EmitAppCall(asset_id, "name");
2025
script = sb.ToArray();
2126
}
22-
using ApplicationEngine engine = ApplicationEngine.Run(script, gas: 3_000_000);
27+
using ApplicationEngine engine = ApplicationEngine.Run(script, snapshot, gas: 0_02000000);
2328
if (engine.State.HasFlag(VMState.FAULT)) throw new ArgumentException();
2429
this.AssetId = asset_id;
25-
this.AssetName = engine.ResultStack.Pop().GetString();
30+
this.AssetName = contract.Manifest.Name;
2631
this.Decimals = (byte)engine.ResultStack.Pop().GetInteger();
2732
}
2833

tests/neo.UnitTests/Ledger/UT_ContractState.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public void TestDeserialize()
7272
public void TestGetSize()
7373
{
7474
ISerializable newContract = contract;
75-
newContract.Size.Should().Be(218);
75+
newContract.Size.Should().Be(240);
7676
}
7777

7878
[TestMethod]

tests/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class UT_ContractManifest
1111
[TestMethod]
1212
public void ParseFromJson_Default()
1313
{
14-
var json = @"{""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":null}";
14+
var json = @"{""name"":""testManifest"",""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":null}";
1515
var manifest = ContractManifest.Parse(json);
1616

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

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

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

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

@@ -77,7 +77,7 @@ public void ParseFromJson_Groups()
7777
[TestMethod]
7878
public void ParseFromJson_Extra()
7979
{
80-
var json = @"{""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":{""key"":""value""}}";
80+
var json = @"{""name"":""testManifest"",""groups"":[],""supportedstandards"":[],""abi"":{""hash"":""0x0000000000000000000000000000000000000000"",""methods"":[],""events"":[]},""permissions"":[{""contract"":""*"",""methods"":""*""}],""trusts"":[],""safemethods"":[],""extra"":{""key"":""value""}}";
8181
var manifest = ContractManifest.Parse(json);
8282
Assert.AreEqual(json, json);
8383
Assert.AreEqual("value", manifest.Extra["key"].AsString(), false);
@@ -110,7 +110,7 @@ public void TestGetHash()
110110
public void TestGetSize()
111111
{
112112
var temp = TestUtils.CreateDefaultManifest(UInt160.Zero);
113-
Assert.AreEqual(212, temp.Size);
113+
Assert.AreEqual(234, temp.Size);
114114
}
115115

116116
[TestMethod]

tests/neo.UnitTests/SmartContract/Native/Tokens/UT_Nep5Token.cs renamed to tests/neo.UnitTests/SmartContract/Native/Tokens/UT_Nep17Token.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
namespace Neo.UnitTests.SmartContract.Native.Tokens
1010
{
1111
[TestClass]
12-
public class UT_Nep5Token : TestKit
12+
public class UT_Nep17Token : TestKit
1313
{
1414
[TestInitialize]
1515
public void TestSetup()
@@ -18,7 +18,13 @@ public void TestSetup()
1818
}
1919

2020
protected const byte Prefix_TotalSupply = 11;
21-
private static readonly TestNep5Token test = new TestNep5Token();
21+
private static readonly TestNep17Token test = new TestNep17Token();
22+
23+
[TestMethod]
24+
public void TestName()
25+
{
26+
Assert.AreEqual(test.Name, test.Manifest.Name);
27+
}
2228

2329
[TestMethod]
2430
public void TestTotalSupply()
@@ -71,11 +77,11 @@ public StorageKey CreateStorageKey(byte prefix, byte[] key = null)
7177
}
7278
}
7379

74-
public class TestNep5Token : Nep5Token<NeoToken.NeoAccountState>
80+
public class TestNep17Token : Nep17Token<NeoToken.NeoAccountState>
7581
{
7682
public override int Id => 0x10000005;
7783

78-
public override string Name => "testNep5Token";
84+
public override string Name => "testNep17Token";
7985

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

tests/neo.UnitTests/TestUtils.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public static ContractManifest CreateDefaultManifest(UInt160 hash)
2222
{
2323
return new ContractManifest()
2424
{
25+
Name = "testManifest",
2526
Groups = new ContractGroup[0],
2627
SupportedStandards = Array.Empty<string>(),
2728
Abi = new ContractAbi()

0 commit comments

Comments
 (0)