diff --git a/templates/Template.NEP5.CSharp/NEP5.Admin.cs b/templates/Template.NEP5.CSharp/NEP5.Admin.cs deleted file mode 100644 index a64fca5d3..000000000 --- a/templates/Template.NEP5.CSharp/NEP5.Admin.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Neo.SmartContract.Framework; -using Neo.SmartContract.Framework.Services.Neo; -using System; - -namespace Template.NEP5.CSharp -{ - public partial class NEP5 : SmartContract - { - public static bool Deploy() - { - if (!Runtime.CheckWitness(Owner)) - { - return false; - } - - StorageMap contract = Storage.CurrentContext.CreateMap(StoragePrefixContract); - if (contract.Get("totalSupply") != null) - throw new Exception("Contract already deployed"); - - StorageMap balances = Storage.CurrentContext.CreateMap(StoragePrefixBalance); - balances.Put(Owner, InitialSupply); - contract.Put("totalSupply", InitialSupply); - - OnTransfer(null, Owner, InitialSupply); - return true; - } - - public static bool Migrate(byte[] script, string manifest) - { - if (!Runtime.CheckWitness(Owner)) - { - return false; - } - if (script.Length == 0 || manifest.Length == 0) - { - return false; - } - Contract.Update(script, manifest); - return true; - } - - public static bool Destroy() - { - if (!Runtime.CheckWitness(Owner)) - { - return false; - } - - Contract.Destroy(); - return true; - } - } -} diff --git a/templates/Template.NEP5.CSharp/NEP5.Crowdsale.cs b/templates/Template.NEP5.CSharp/NEP5.Crowdsale.cs index ca9a3efbe..0bbb4fae9 100644 --- a/templates/Template.NEP5.CSharp/NEP5.Crowdsale.cs +++ b/templates/Template.NEP5.CSharp/NEP5.Crowdsale.cs @@ -25,12 +25,10 @@ private static BigInteger GetTransactionAmount(object state) public static bool Mint() { - if (Runtime.InvocationCounter != 1) - throw new Exception(); + if (Runtime.InvocationCounter != 1) throw new Exception("InvocationCounter must be 1."); var notifications = Runtime.GetNotifications(); - if (notifications.Length == 0) - throw new Exception("Contribution transaction not found"); + if (notifications.Length == 0) throw new Exception("Contribution transaction not found."); BigInteger neo = 0; BigInteger gas = 0; @@ -49,27 +47,20 @@ public static bool Mint() } } - StorageMap contract = Storage.CurrentContext.CreateMap(StoragePrefixContract); - var supply = contract.Get("totalSupply"); - if (supply == null) - throw new Exception("Contract not deployed"); + var totalSupply = TotalSupplyStorage.Get(); + if (totalSupply <= 0) throw new Exception("Contract not deployed."); - var current_supply = supply.ToBigInteger(); - var avaliable_supply = MaxSupply - current_supply; + var avaliable_supply = MaxSupply - totalSupply; var contribution = (neo * TokensPerNEO) + (gas * TokensPerGAS); - if (contribution <= 0) - throw new Exception(); - if (contribution > avaliable_supply) - throw new Exception(); + if (contribution <= 0) throw new Exception("Contribution cannot be zero."); + if (contribution > avaliable_supply) throw new Exception("Insufficient supply for mint tokens."); - StorageMap balances = Storage.CurrentContext.CreateMap(StoragePrefixBalance); Transaction tx = (Transaction)ExecutionEngine.ScriptContainer; - var balance = balances.Get(tx.Sender)?.ToBigInteger() ?? 0; - balances.Put(tx.Sender, balance + contribution); - contract.Put("totalSupply", current_supply + contribution); + AssetStorage.Increase(tx.Sender, contribution); + TotalSupplyStorage.Increase(contribution); - OnTransfer(null, tx.Sender, balance + contribution); + OnTransfer(null, tx.Sender, contribution); return true; } } diff --git a/templates/Template.NEP5.CSharp/NEP5.Helpers.cs b/templates/Template.NEP5.CSharp/NEP5.Helpers.cs index 347313c59..e1d141176 100644 --- a/templates/Template.NEP5.CSharp/NEP5.Helpers.cs +++ b/templates/Template.NEP5.CSharp/NEP5.Helpers.cs @@ -5,19 +5,8 @@ namespace Template.NEP5.CSharp { public partial class NEP5 : SmartContract { - private static bool ValidateAddress(byte[] address) - { - if (address.Length != 20) - return false; - if (address.ToBigInteger() == 0) - return false; - return true; - } + private static bool ValidateAddress(byte[] address) => address.Length == 20 && address.ToBigInteger() != 0; - private static bool IsPayable(byte[] address) - { - var c = Blockchain.GetContract(address); - return c == null || c.IsPayable; - } + private static bool IsPayable(byte[] address) => Blockchain.GetContract(address)?.IsPayable ?? true; } } diff --git a/templates/Template.NEP5.CSharp/NEP5.Methods.cs b/templates/Template.NEP5.CSharp/NEP5.Methods.cs index a6933c114..cf1e039f0 100644 --- a/templates/Template.NEP5.CSharp/NEP5.Methods.cs +++ b/templates/Template.NEP5.CSharp/NEP5.Methods.cs @@ -1,5 +1,6 @@ using Neo.SmartContract.Framework; using Neo.SmartContract.Framework.Services.Neo; +using Neo.SmartContract.Framework.Services.System; using System; using System.Numerics; @@ -7,45 +8,25 @@ namespace Template.NEP5.CSharp { public partial class NEP5 : SmartContract { - public static BigInteger TotalSupply() - { - StorageMap contract = Storage.CurrentContext.CreateMap(StoragePrefixContract); - return contract.Get("totalSupply")?.ToBigInteger() ?? 0; - } + public static BigInteger TotalSupply() => TotalSupplyStorage.Get(); public static BigInteger BalanceOf(byte[] account) { - if (!ValidateAddress(account)) throw new FormatException("The parameter 'account' SHOULD be 20-byte addresses."); - - StorageMap balances = Storage.CurrentContext.CreateMap(StoragePrefixBalance); - return balances.Get(account)?.ToBigInteger() ?? 0; + if (!ValidateAddress(account)) throw new Exception("The parameters account SHOULD be 20-byte addresses."); + return AssetStorage.Get(account); } public static bool Transfer(byte[] from, byte[] to, BigInteger amount) { - if (!ValidateAddress(from)) throw new FormatException("The parameter 'from' SHOULD be 20-byte addresses."); - if (!ValidateAddress(to)) throw new FormatException("The parameters 'to' SHOULD be 20-byte addresses."); - if (!IsPayable(to)) return false; - if (amount <= 0) throw new InvalidOperationException("The parameter amount MUST be greater than 0."); - if (!Runtime.CheckWitness(from)) return false; - - StorageMap balances = Storage.CurrentContext.CreateMap(StoragePrefixBalance); - BigInteger fromAmount = balances.Get(from).ToBigInteger(); - - if (fromAmount < amount) return false; - if (amount == 0 || from == to) return true; - - if (fromAmount == amount) - { - balances.Delete(from); - } - else - { - balances.Put(from, fromAmount - amount); - } - - BigInteger toAmount = balances.Get(to)?.ToBigInteger() ?? 0; - balances.Put(to, toAmount + amount); + if (!ValidateAddress(from) || !ValidateAddress(to)) throw new Exception("The parameters from and to SHOULD be 20-byte addresses."); + if (amount <= 0) throw new Exception("The parameter amount MUST be greater than 0."); + if (!IsPayable(to)) throw new Exception("Receiver cannot receive."); + if (!Runtime.CheckWitness(from) && !from.Equals(ExecutionEngine.CallingScriptHash)) throw new Exception("No authorization."); + if (AssetStorage.Get(from) < amount) throw new Exception("Insufficient balance."); + if (from == to) return true; + + AssetStorage.Reduce(from, amount); + AssetStorage.Increase(to, amount); OnTransfer(from, to, amount); return true; diff --git a/templates/Template.NEP5.CSharp/NEP5.Owner.cs b/templates/Template.NEP5.CSharp/NEP5.Owner.cs new file mode 100644 index 000000000..03fe9af32 --- /dev/null +++ b/templates/Template.NEP5.CSharp/NEP5.Owner.cs @@ -0,0 +1,40 @@ +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Services.Neo; +using Neo.SmartContract.Framework.Services.System; +using System; + +namespace Template.NEP5.CSharp +{ + public partial class NEP5 : SmartContract + { + public static bool Deploy() + { + if (!IsOwner()) throw new Exception("No authorization."); + if (TotalSupplyStorage.Get() > 0) throw new Exception("Contract has been deployed."); + + TotalSupplyStorage.Increase(InitialSupply); + AssetStorage.Increase(Owner, InitialSupply); + + OnTransfer(null, Owner, InitialSupply); + return true; + } + + public static bool Update(byte[] script, string manifest) + { + if (!IsOwner()) throw new Exception("No authorization."); + // Check empty + if (script.Length == 0 && manifest.Length == 0) return false; + Contract.Update(script, manifest); + return true; + } + + public static bool Destroy() + { + if (!IsOwner()) throw new Exception("No authorization."); + Contract.Destroy(); + return true; + } + + private static bool IsOwner() => Runtime.CheckWitness(Owner); + } +} diff --git a/templates/Template.NEP5.CSharp/NEP5.cs b/templates/Template.NEP5.CSharp/NEP5.cs index 8ffee8c27..5f964deef 100644 --- a/templates/Template.NEP5.CSharp/NEP5.cs +++ b/templates/Template.NEP5.CSharp/NEP5.cs @@ -1,5 +1,4 @@ using Neo.SmartContract.Framework; -using Neo.SmartContract.Framework.Services.Neo; using System; using System.ComponentModel; using System.Numerics; @@ -9,17 +8,18 @@ namespace Template.NEP5.CSharp [ManifestExtra("Author", "Neo")] [ManifestExtra("Email", "dev@neo.org")] [ManifestExtra("Description", "This is a NEP5 example")] + [SupportedStandards("NEP5", "NEP10")] [Features(ContractFeatures.HasStorage | ContractFeatures.Payable)] public partial class NEP5 : SmartContract { #region Token Settings static readonly ulong MaxSupply = 10_000_000_000_000_000; static readonly ulong InitialSupply = 2_000_000_000_000_000; - static readonly byte[] Owner = new byte[] { 0xf6, 0x64, 0x43, 0x49, 0x8d, 0x38, 0x78, 0xd3, 0x2b, 0x99, 0x4e, 0x4e, 0x12, 0x83, 0xc6, 0x93, 0x44, 0x21, 0xda, 0xfe }; + static readonly byte[] Owner = "NiNmXL8FjEUEs1nfX9uHFBNaenxDHJtmuB".ToScriptHash(); static readonly ulong TokensPerNEO = 1_000_000_000; static readonly ulong TokensPerGAS = 1; - static readonly byte[] NeoToken = new byte[] { 0x89, 0x77, 0x20, 0xd8, 0xcd, 0x76, 0xf4, 0xf0, 0x0a, 0xbf, 0xa3, 0x7c, 0x0e, 0xdd, 0x88, 0x9c, 0x20, 0x8f, 0xde, 0x9b }; - static readonly byte[] GasToken = new byte[] { 0x3b, 0x7d, 0x37, 0x11, 0xc6, 0xf0, 0xcc, 0xf9, 0xb1, 0xdc, 0xa9, 0x03, 0xd1, 0xbf, 0xa1, 0xd8, 0x96, 0xf1, 0x23, 0x8c }; + static readonly byte[] NeoToken = "0xde5f57d430d3dece511cf975a8d37848cb9e0525".HexToBytes(); + static readonly byte[] GasToken = "0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc".HexToBytes(); #endregion #region Notifications @@ -27,37 +27,15 @@ public partial class NEP5 : SmartContract public static event Action OnTransfer; #endregion - #region Storage key prefixes - static readonly byte[] StoragePrefixBalance = new byte[] { 0x01, 0x01 }; - static readonly byte[] StoragePrefixContract = new byte[] { 0x02, 0x02 }; - #endregion - // When this contract address is included in the transaction signature, // this method will be triggered as a VerificationTrigger to verify that the signature is correct. // For example, this method needs to be called when withdrawing token from the contract. - public static bool Verify() - { - return Runtime.CheckWitness(Owner); - } - - public static string Name() - { - return "Token Name"; - } + public static bool Verify() => IsOwner(); - public static string Symbol() - { - return "TokenSymbol"; - } + public static string Name() => "Token Name"; - public static ulong Decimals() - { - return 8; - } + public static string Symbol() => "TokenSymbol"; - public static string[] SupportedStandards() - { - return new string[] { "NEP-5", "NEP-10" }; - } + public static ulong Decimals() => 8; } } diff --git a/templates/Template.NEP5.CSharp/Storage/AssetStorage.cs b/templates/Template.NEP5.CSharp/Storage/AssetStorage.cs new file mode 100644 index 000000000..9e5de31d3 --- /dev/null +++ b/templates/Template.NEP5.CSharp/Storage/AssetStorage.cs @@ -0,0 +1,28 @@ +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Services.Neo; +using System.Numerics; + +namespace Template.NEP5.CSharp +{ + public static class AssetStorage + { + public static readonly string mapName = "asset"; + + public static void Increase(byte[] key, BigInteger value) => Put(key, Get(key) + value); + + public static void Reduce(byte[] key, BigInteger value) + { + var oldValue = Get(key); + if (oldValue == value) + Remove(key); + else + Put(key, oldValue - value); + } + + public static void Put(byte[] key, BigInteger value) => Storage.CurrentContext.CreateMap(mapName).Put(key, value); + + public static BigInteger Get(byte[] key) => Storage.CurrentContext.CreateMap(mapName).Get(key).ToBigInteger(); + + public static void Remove(byte[] key) => Storage.CurrentContext.CreateMap(mapName).Delete(key); + } +} diff --git a/templates/Template.NEP5.CSharp/Storage/TotalSupplyStorage.cs b/templates/Template.NEP5.CSharp/Storage/TotalSupplyStorage.cs new file mode 100644 index 000000000..bd8722144 --- /dev/null +++ b/templates/Template.NEP5.CSharp/Storage/TotalSupplyStorage.cs @@ -0,0 +1,22 @@ +using Neo.SmartContract.Framework; +using Neo.SmartContract.Framework.Services.Neo; +using System.Numerics; + +namespace Template.NEP5.CSharp +{ + public static class TotalSupplyStorage + { + public static readonly string mapName = "contract"; + + public static readonly string key = "totalSupply"; + + public static void Increase(BigInteger value) => Put(Get() + value); + + public static void Reduce(BigInteger value) => Put(Get() - value); + + public static void Put(BigInteger value) => Storage.CurrentContext.CreateMap(mapName).Put(key, value); + + public static BigInteger Get() => Storage.CurrentContext.CreateMap(mapName).Get(key).ToBigInteger(); + + } +} diff --git a/templates/Template.NEP5.CSharp/Template.NEP5.CSharp.sln b/templates/Template.NEP5.CSharp/Template.NEP5.CSharp.sln deleted file mode 100644 index 0f66d4952..000000000 --- a/templates/Template.NEP5.CSharp/Template.NEP5.CSharp.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29025.244 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Template.NEP5.CSharp", "Template.NEP5.CSharp.csproj", "{A97D1F8D-1181-489D-893C-F25C019D75FF}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {A97D1F8D-1181-489D-893C-F25C019D75FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A97D1F8D-1181-489D-893C-F25C019D75FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A97D1F8D-1181-489D-893C-F25C019D75FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A97D1F8D-1181-489D-893C-F25C019D75FF}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {517EB11D-73F9-4594-88B0-0D7C0E9290C1} - EndGlobalSection -EndGlobal diff --git a/tests/Template.NEP5.UnitTests/UnitTest_NEP5.cs b/tests/Template.NEP5.UnitTests/UnitTest_NEP5.cs index 83fe98d78..8eb6e9b89 100644 --- a/tests/Template.NEP5.UnitTests/UnitTest_NEP5.cs +++ b/tests/Template.NEP5.UnitTests/UnitTest_NEP5.cs @@ -1,6 +1,5 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Neo.Compiler.MSIL.UnitTests.Utils; -using Neo.VM; using Neo.VM.Types; using System.Linq; using System.Numerics; @@ -12,8 +11,8 @@ namespace Template.NEP5.UnitTests public class UnitTest_NEP5 { private TestEngine _engine; - private static readonly byte[] _prefixBalance = { 0x01, 0x01 }; - private static readonly byte[] _prefixContract = { 0x02, 0x02 }; + private static readonly byte[] _prefixAsset = Encoding.UTF8.GetBytes("asset"); + private static readonly byte[] _prefixContract = Encoding.UTF8.GetBytes("contract"); [TestInitialize] public void Init() @@ -28,7 +27,9 @@ TestEngine CreateEngine() engine.AddEntryScript(new string[] { "../../../../../templates/Template.NEP5.CSharp/NEP5.cs", - "../../../../../templates/Template.NEP5.CSharp/NEP5.Admin.cs", + "../../../../../templates/Template.NEP5.CSharp/Storage/TotalSupplyStorage.cs", + "../../../../../templates/Template.NEP5.CSharp/Storage/AssetStorage.cs", + "../../../../../templates/Template.NEP5.CSharp/NEP5.Owner.cs", "../../../../../templates/Template.NEP5.CSharp/NEP5.Crowdsale.cs", "../../../../../templates/Template.NEP5.CSharp/NEP5.Helpers.cs", "../../../../../templates/Template.NEP5.CSharp/NEP5.Methods.cs" @@ -75,7 +76,7 @@ public void Test_totalSupply() { var engine = CreateEngine(); var hash = engine.CurrentScriptHash; - var snapshot = engine.Snapshot as TestSnapshot; + var snapshot = engine.Snapshot; snapshot.Contracts.Add(hash, new Neo.Ledger.ContractState() { @@ -109,7 +110,7 @@ public void Test_totalSupply_empty() { var engine = CreateEngine(); var hash = engine.CurrentScriptHash; - var snapshot = engine.Snapshot as TestSnapshot; + var snapshot = engine.Snapshot; snapshot.Contracts.Add(hash, new Neo.Ledger.ContractState() { @@ -132,7 +133,7 @@ public void Test_balanceOf() { var engine = CreateEngine(); var hash = engine.CurrentScriptHash; - var snapshot = engine.Snapshot as TestSnapshot; + var snapshot = engine.Snapshot; var address = new byte[] { 0xf6, 0x64, 0x43, 0x49, 0x8d, 0x38, 0x78, 0xd3, 0x2b, 0x99, 0x4e, 0x4e, 0x12, 0x83, 0xc6, 0x93, 0x44, 0x21, 0xda, 0xfe }; snapshot.Contracts.Add(hash, new Neo.Ledger.ContractState() @@ -146,7 +147,7 @@ public void Test_balanceOf() snapshot.Storages.Add(new Neo.Ledger.StorageKey() { Id = 0, - Key = _prefixBalance.Concat(address).ToArray() + Key = _prefixAsset.Concat(address).ToArray() }, new Neo.Ledger.StorageItem() { @@ -167,7 +168,7 @@ public void Test_balanceOf_empty() { var engine = CreateEngine(); var hash = engine.CurrentScriptHash; - var snapshot = engine.Snapshot as TestSnapshot; + var snapshot = engine.Snapshot; var address = new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13 }; snapshot.Contracts.Add(hash, new Neo.Ledger.ContractState() @@ -185,17 +186,5 @@ public void Test_balanceOf_empty() Assert.IsInstanceOfType(item, typeof(Integer)); Assert.AreEqual(0, item.GetInteger()); } - - [TestMethod] - public void Test_supportedStandards() - { - var result = _engine.ExecuteTestCaseStandard("supportedStandards"); - Assert.AreEqual(1, result.Count); - - var item = (Array)result.Pop(); - Assert.IsInstanceOfType(item, typeof(Array)); - Assert.AreEqual("NEP-5", item[0].GetString()); - Assert.AreEqual("NEP-10", item[1].GetString()); - } } }