public static void GenerateNFTDummyScript(string symbol, string name, string description, string jsonURL, string imgURL, out byte[] script, out ContractInterface abi) { if (!jsonURL.EndsWith("\\")) { jsonURL += '\\'; } if (!imgURL.EndsWith("\\")) { imgURL += '\\'; } var sb = new StringBuilder(); GenerateStringScript(sb, "getName", name); GenerateStringScript(sb, "getDescription", description); GenerateStringScript(sb, "getInfoURL", jsonURL); GenerateStringScript(sb, "getImageURL", imgURL); var asm = sb.ToString().Split('\n'); DebugInfo debugInfo; Dictionary <string, int> labels; script = CodeGen.Assembler.AssemblerUtils.BuildScript(asm, "dummy", out debugInfo, out labels); var standardABI = GetNFTStandard(); var methods = standardABI.Methods.Select(x => new ContractMethod(x.name, x.returnType, labels[x.name], x.parameters)); abi = new ContractInterface(methods, Enumerable.Empty <ContractEvent>()); }
private static VMObject ExecuteScript(Chain chain, byte[] script, ContractInterface abi, string methodName, params object[] args) { var method = abi.FindMethod(methodName); if (method == null) { throw new Exception("ABI is missing: " + method.name); } var changeSet = new StorageChangeSetContext(chain.Storage); var oracle = chain.Nexus.GetOracleReader(); var vm = new RuntimeVM(-1, script, (uint)method.offset, chain, Address.Null, Timestamp.Now, null, changeSet, oracle, ChainTask.Null, true); //var vm = new GasMachine(script, (uint)method.offset); // TODO maybe this needs to be in inverted order? foreach (var arg in args) { vm.Stack.Push(VMObject.FromObject(arg)); } var result = vm.Execute(); if (result == ExecutionState.Halt) { return(vm.Stack.Pop()); } throw new Exception("Script execution failed for: " + method.name); }
public TokenInfo(string symbol, string name, Address owner, BigInteger maxSupply, int decimals, TokenFlags flags, byte[] script, ContractInterface ABI) { Throw.IfNullOrEmpty(symbol, nameof(symbol)); Throw.IfNullOrEmpty(name, nameof(name)); Throw.If(decimals < 0, "decimals can't be negative"); Throw.If(flags == TokenFlags.None, "token must have flags set"); Throw.If(script == null || script.Length == 0, "token script can't be empty"); Throw.If(maxSupply < 0, "negative supply"); Throw.If(maxSupply == 0 && flags.HasFlag(TokenFlags.Finite), "finite requires a supply"); Throw.If(maxSupply > 0 && !flags.HasFlag(TokenFlags.Finite), "infinite requires no supply"); if (!flags.HasFlag(TokenFlags.Fungible)) { Throw.If(flags.HasFlag(TokenFlags.Divisible), "non-fungible token must be indivisible"); } if (flags.HasFlag(TokenFlags.Divisible)) { Throw.If(decimals <= 0, "divisible token must have decimals"); } else { Throw.If(decimals > 0, "indivisible token can't have decimals"); } this.Symbol = symbol; this.Name = name; this.Owner = owner; this.Flags = flags; this.Decimals = decimals; this.MaxSupply = maxSupply; this.Script = script; this.ABI = ABI; }
private static ExecutionState Nexus_CreateToken(RuntimeVM vm) { vm.ExpectStackSize(7); var owner = vm.PopAddress(); var symbol = vm.PopString("symbol"); var name = vm.PopString("name"); var maxSupply = vm.PopNumber("maxSupply"); var decimals = (int)vm.PopNumber("decimals"); var flags = vm.PopEnum <TokenFlags>("flags"); var script = vm.PopBytes("script"); ContractInterface abi; if (vm.ProtocolVersion >= 4) { var abiBytes = vm.PopBytes("abi bytes"); abi = ContractInterface.FromBytes(abiBytes); } else { abi = new ContractInterface(); } vm.CreateToken(owner, symbol, name, maxSupply, decimals, flags, script, abi); return(ExecutionState.Running); }
public override ContractInterface GenerateCode(CodeGenerator output) { var abi = base.GenerateCode(output); var nftStandard = NFTUtils.GetNFTStandard(); // convert ABI parameters var methods = new List <ContractMethod>(); foreach (var method in abi.Methods) { if (nftStandard.HasMethod(method.name)) { var convertedMethod = new ContractMethod(method.name, method.returnType, method.offset, new[] { new ContractParameter("tokenID", VMType.Number) }); methods.Add(convertedMethod); } else { methods.Add(method); } } abi = new ContractInterface(methods, abi.Events); return(abi); }
public CustomContract(string name, byte[] script, ContractInterface abi) : base() { Throw.IfNull(script, nameof(script)); this.Script = script; _name = name; this.ABI = abi; }
public void UnserializeData(BinaryReader reader) { this.MintCount = reader.ReadBigInteger(); this.MaxSupply = reader.ReadBigInteger(); this.Mode = (TokenSeriesMode)reader.ReadByte(); this.Script = reader.ReadByteArray(); var bytes = reader.ReadByteArray(); this.ABI = ContractInterface.FromBytes(bytes); this.ROM = reader.ReadByteArray(); }
public void UnserializeData(BinaryReader reader) { Symbol = reader.ReadVarString(); Name = reader.ReadVarString(); Owner = reader.ReadAddress(); Flags = (TokenFlags)reader.ReadUInt32(); Decimals = reader.ReadInt32(); MaxSupply = reader.ReadBigInteger(); Script = reader.ReadByteArray(); var abiBytes = reader.ReadByteArray(); this.ABI = ContractInterface.FromBytes(abiBytes); }
private static ExecutionState Constructor_ABI(RuntimeVM vm) { return(Constructor_Object <byte[], ContractInterface>(vm, bytes => { Throw.If(bytes == null, "invalid abi"); using (var stream = new MemoryStream(bytes)) { using (var reader = new BinaryReader(stream)) { return ContractInterface.Unserialize(reader); } } })); }
private static ContractInterface CreateInterfaceContract(ServiceDef owner, Type type) { if (!interfacedefs.TryGetValue(type, out ContractInterface contract)) { var mm = type.GetMethods().Where(_m => _m.GetCustomAttributes <ContractAttribute>().Any()); contract = new ContractInterface() { Owner = owner, Type = type }; contract.Methods = mm.Select(_m => CreateMethodContract(contract, _m)).ToArray(); interfacedefs[type] = contract; } return(contract); }
protected override void ProcessABI(ContractInterface abi, DebugInfo debugInfo) { base.ProcessABI(abi, debugInfo); // here we lookup the script start offset for each method based on debug info obtained from the assembler foreach (var abiMethod in abi.Methods) { var method = this.Methods[abiMethod.name]; abiMethod.offset = debugInfo.FindOffset([email protected]); if (abiMethod.offset < 0) { throw new Exception("Could not calculate script offset for method: " + abiMethod.name); } } }
private static ExecutionState Nexus_CreateTokenSeries(RuntimeVM vm) { vm.ExpectStackSize(5); var from = vm.PopAddress(); var symbol = vm.PopString("symbol"); var seriesID = vm.PopNumber("series ID"); var maxSupply = vm.PopNumber("max supply"); var mode = vm.PopEnum <TokenSeriesMode>("mode"); var script = vm.PopBytes("script"); var abiBytes = vm.PopBytes("abi bytes"); var abi = ContractInterface.FromBytes(abiBytes); vm.CreateTokenSeries(symbol, from, seriesID, maxSupply, mode, script, abi); return(ExecutionState.Running); }
private static VMObject ExecuteScript(byte[] script, ContractInterface abi, string methodName) { var method = abi.FindMethod(methodName); if (method == null) { throw new Exception("ABI is missing: " + methodName); } var vm = new GasMachine(script, (uint)method.offset); var result = vm.Execute(); if (result == ExecutionState.Halt) { return(vm.Stack.Pop()); } throw new Exception("Script execution failed for: " + methodName); }
public void RegisterScript(Address target, byte[] script, byte[] abiBytes) { Runtime.Expect(target.IsUser, "must be user address"); Runtime.Expect(target != Runtime.GenesisAddress, "address must not be genesis"); Runtime.Expect(Runtime.IsWitness(target), "invalid witness"); var stake = Runtime.GetStake(target); Runtime.Expect(stake >= UnitConversion.GetUnitValue(DomainSettings.StakingTokenDecimals), "must have something staked"); Runtime.Expect(script.Length < 1024, "invalid script length"); Runtime.Expect(!_scriptMap.ContainsKey(target), "address already has a script"); var abi = ContractInterface.FromBytes(abiBytes); Runtime.Expect(abi.MethodCount > 0, "unexpected empty contract abi"); var witnessTriggerName = AccountTrigger.OnWitness.ToString(); if (abi.HasMethod(witnessTriggerName)) { var witnessCheck = Runtime.InvokeTrigger(false, script, NativeContractKind.Account, abi, witnessTriggerName, Address.Null) != TriggerResult.Failure; Runtime.Expect(!witnessCheck, "script does not handle OnWitness correctly, case #1"); witnessCheck = Runtime.InvokeTrigger(false, script, NativeContractKind.Account, abi, witnessTriggerName, target) != TriggerResult.Failure; Runtime.Expect(witnessCheck, "script does not handle OnWitness correctly, case #2"); } _scriptMap.Set(target, script); _abiMap.Set(target, abiBytes); var constructor = abi.FindMethod(SmartContract.ConstructorName); if (constructor != null) { Runtime.CallContext(target.Text, constructor, target); } // TODO? Runtime.Notify(EventKind.AddressRegister, target, script); }
private static void ValidateABI(RuntimeVM vm, string contractName, ContractInterface abi, bool isNative) { var offsets = new HashSet <int>(); var names = new HashSet <string>(); foreach (var method in abi.Methods) { vm.Expect(ValidationUtils.IsValidMethod(method.name, method.returnType), "invalid method: " + method.name); var normalizedName = method.name.ToLower(); vm.Expect(!names.Contains(normalizedName), $"duplicated method name in {contractName}: {normalizedName}"); names.Add(normalizedName); if (!isNative) { vm.Expect(method.offset >= 0, $"invalid offset in {contractName} contract abi for method {method.name}"); vm.Expect(!offsets.Contains(method.offset), $"duplicated offset in {contractName} contract abi for method {method.name}"); offsets.Add(method.offset); } } }
public SmartContract GetContractByName(StorageContext storage, string name) { if (Nexus.IsNativeContract(name) || ValidationUtils.IsValidTicker(name)) { return(Nexus.GetContractByName(storage, name)); } var address = SmartContract.GetAddressForName(name); var scriptKey = GetContractKey(address, "script"); if (!storage.Has(scriptKey)) { return(null); } var script = storage.Get(scriptKey); var abiKey = GetContractKey(address, "abi"); var abiBytes = storage.Get(abiKey); var abi = ContractInterface.FromBytes(abiBytes); return(new CustomContract(name, script, abi)); }
private static ContractMethod CreateMethodContract(ContractInterface owner, MethodInfo m) { /* MethodInfo taskm = null; * * if (!m.ReturnType.IsDerived(typeof(Task))) * { * taskm = owner.Owner.Type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(_m => _m.Name == m.Name && _m.ReturnType.IsDerived(typeof(Task))).SingleOrDefault(); * * if (taskm != null) * { * if (!taskm.GetParameters().SequenceEqual(m.GetParameters(), new cmp_ParameterInfo_type())) * { * throw new ReflectionError($"error with parameters for {m.Name} for private task method"); * } * if (m.ReturnType != typeof(void)) * { * if (taskm.ReturnType.GetGenericArguments().Single()!=m.ReturnType) * { * throw new ReflectionError($"error with return type for {m.Name} for private task method"); * } * } * } * else * { * taskm = m; * } * }*/ var r = new ContractMethod() { Owner = owner, MethodInfo = m }; r.ReturnContract = new ContractReturn(m.ReturnType); r.Parameters = m.GetParameters().Select(_p => CreateParameterContract(r, _p)).ToArray(); return(r); }
public bool DeployContractScript(StorageContext storage, Address contractOwner, string name, Address contractAddress, byte[] script, ContractInterface abi) { var scriptKey = GetContractKey(contractAddress, "script"); if (storage.Has(scriptKey)) { return(false); } storage.Put(scriptKey, script); var ownerBytes = contractOwner.ToByteArray(); var ownerKey = GetContractKey(contractAddress, "owner"); storage.Put(ownerKey, ownerBytes); var abiBytes = abi.ToByteArray(); var abiKey = GetContractKey(contractAddress, "abi"); storage.Put(abiKey, abiBytes); var nameBytes = Encoding.ASCII.GetBytes(name); var nameKey = GetContractKey(contractAddress, "name"); storage.Put(nameKey, nameBytes); var contractList = new StorageList(GetContractListKey(), storage); contractList.Add <Address>(contractAddress); return(true); }
public static void FetchProperty(StorageContext storage, Chain chain, string methodName, byte[] script, ContractInterface abi, Action <string, VMObject> callback) { if (abi.HasMethod(methodName)) { var result = ExecuteScript(storage, chain, script, abi, methodName); string propName = methodName; if (propName.StartsWith("is")) { propName = propName.Substring(2); } else if (propName.StartsWith("get")) { propName = propName.Substring(3); } callback(propName, result); } }
private static void DeployOrUpgrade(string[] args, SpookSettings settings, NexusAPI api, BigInteger minFee, bool isUpgrade) { if (args.Length != 1) { throw new CommandException("Invalid number of arguments, expected file name"); } DoChecks(api); var fileName = args[0]; if (!File.Exists(fileName)) { throw new CommandException("Provided file does not exist"); } if (!FileExistsCaseSensitive(fileName)) { throw new CommandException("Provided file case does not match real file name case"); } var extension = ScriptModule.ScriptExtension; if (!fileName.EndsWith(extension)) { throw new CommandException($"Provided file is not a compiled {extension} script"); } var abiFile = fileName.Replace(extension, ".abi"); if (!File.Exists(abiFile)) { throw new CommandException($"No ABI file {abiFile} that matches provided script file"); } var contractName = Path.GetFileNameWithoutExtension(fileName); var contractScript = File.ReadAllBytes(fileName); var abiBytes = File.ReadAllBytes(abiFile); var abi = ContractInterface.FromBytes(abiBytes); var sb = new ScriptBuilder(); int nexusVersion = 0; try { var nexusDetails = api.Execute("getNexus", new object[] { false }); var root = LunarLabs.Parser.JSON.JSONReader.ReadFromString(nexusDetails); var governance = root["governance"]; var entry = governance.Children.Where(x => x.GetNode("name").Value == "nexus.protocol.version").FirstOrDefault(); entry = entry.GetNodeByIndex(1); nexusVersion = Int32.Parse(entry.Value); } catch (Exception e) { Console.WriteLine(e); nexusVersion = -1; } if (nexusVersion <= 1) { throw new CommandException("Failed to obtain nexus version via API"); } bool isToken = ValidationUtils.IsValidTicker(contractName); var availableFlags = Enum.GetValues(typeof(TokenFlags)).Cast <TokenFlags>().ToArray(); sb.AllowGas(Keys.Address, Address.Null, minFee, 9999); if (isUpgrade) { // check for modification in flags if (isToken) { var symbol = contractName; var resultStr = api.Execute("getToken", new[] { symbol, "false" }); logger.Debug($"{resultStr}"); //2021.08.27 sfichera: Fixed api obj deserialization. //dynamic apiResult = System.Text.Json.JsonSerializer.Deserialize<TokenResult>(resultStr); dynamic apiResult = JsonConvert.DeserializeObject <TokenResult>(resultStr); if (apiResult is TokenResult) { var oldToken = (TokenResult)apiResult; var oldFlags = TokenFlags.None; var splitFlags = oldToken.flags.Split(','); foreach (var entry in splitFlags) { TokenFlags flag; if (Enum.TryParse <TokenFlags>(entry, true, out flag)) { oldFlags |= flag; } } foreach (var flag in availableFlags) { var propName = "is" + flag; if (abi.HasMethod(propName)) { var isSet = ExecuteScript(contractScript, abi, propName).AsBool(); var wasSet = oldFlags.HasFlag(flag); if (isSet != wasSet) { throw new CommandException($"Detected '{flag}' flag change: {wasSet} => {isSet}"); } } } } else { throw new CommandException("could not find any deployed token contract for " + symbol); } } sb.CallInterop("Runtime.UpgradeContract", Keys.Address, contractName, contractScript, abiBytes); } else if (isToken) { if (!abi.HasMethod("getName")) { throw new CommandException("token contract is missing required 'name' property"); } string symbol = null; if (nexusVersion < 6) { symbol = contractName; } else { if (abi.HasMethod("getSymbol")) { symbol = ExecuteScript(contractScript, abi, "getSymbol").AsString(); } if (string.IsNullOrEmpty(symbol)) { throw new CommandException("token contract 'symbol' property is either missing or returning an empty value"); } } var name = ExecuteScript(contractScript, abi, "getName").AsString(); if (string.IsNullOrEmpty(name)) { throw new CommandException("token contract 'name' property is either missing or returning an empty value"); } BigInteger maxSupply = abi.HasMethod("getMaxSupply") ? ExecuteScript(contractScript, abi, "getMaxSupply").AsNumber() : 0; BigInteger decimals = abi.HasMethod("getDecimals") ? ExecuteScript(contractScript, abi, "getDecimals").AsNumber() : 0; TokenFlags flags = TokenFlags.None; foreach (var flag in availableFlags) { var propName = "is" + flag; if (abi.HasMethod(propName) && ExecuteScript(contractScript, abi, propName).AsBool()) { flags |= flag; } } if (nexusVersion < 6) { sb.CallInterop("Nexus.CreateToken", Keys.Address, symbol, name, maxSupply, decimals, flags, contractScript, abiBytes); } else { sb.CallInterop("Nexus.CreateToken", Keys.Address, contractScript, abiBytes); } contractName = symbol; } else { sb.CallInterop("Runtime.DeployContract", Keys.Address, contractName, contractScript, abiBytes); } sb.SpendGas(Keys.Address); var script = sb.EndScript(); if (!isUpgrade) { var upgradeTrigger = AccountContract.GetTriggerForABI(AccountTrigger.OnUpgrade); if (abi.Implements(upgradeTrigger)) { logger.Message($"{contractName} implements proper triggers, and can be upgraded later."); } else { logger.Warning($"{contractName} does not implements proper triggers, can't be upgraded later."); } } var hash = ExecuteTransaction(settings, api, script, ProofOfWork.Minimal, Keys); if (hash != Hash.Null) { var expectedEvent = isUpgrade ? EventKind.ContractUpgrade : (isToken ? EventKind.TokenCreate : EventKind.ContractDeploy); var expectedEventStr = expectedEvent.ToString(); var events = GetTransactionEvents(hash); if (events.Any(x => x.kind == expectedEventStr)) { var contractAddress = SmartContract.GetAddressForName(contractName); string action = isUpgrade ? "Upgraded" : "Deployed"; logger.Message($"{action} {contractName} at {contractAddress}"); } else { throw new CommandException("Transaction was confirmed but deployment event is missing!"); } } }
private static ExecutionState Runtime_UpgradeContract(RuntimeVM vm) { var tx = vm.Transaction; Throw.IfNull(tx, nameof(tx)); var pow = tx.Hash.GetDifficulty(); vm.Expect(pow >= (int)ProofOfWork.Minimal, "expected proof of work"); vm.ExpectStackSize(1); var from = vm.PopAddress(); vm.Expect(from.IsUser, "address must be user"); vm.Expect(vm.IsStakeMaster(from), "needs to be master"); vm.Expect(vm.IsWitness(from), "invalid witness"); var contractName = vm.PopString("contractName"); var contractAddress = SmartContract.GetAddressForName(contractName); var deployed = vm.Chain.IsContractDeployed(vm.Storage, contractAddress); vm.Expect(deployed, $"{contractName} does not exist"); byte[] script; ContractInterface abi; bool isNative = Nexus.IsNativeContract(contractName); vm.Expect(!isNative, "cannot upgrade native contract"); bool isToken = ValidationUtils.IsValidTicker(contractName); script = vm.PopBytes("contractScript"); var abiBytes = vm.PopBytes("contractABI"); abi = ContractInterface.FromBytes(abiBytes); var fuelCost = vm.GetGovernanceValue(Nexus.FuelPerContractDeployTag); // governance value is in usd fiat, here convert from fiat to fuel amount fuelCost = vm.GetTokenQuote(DomainSettings.FiatTokenSymbol, DomainSettings.FuelTokenSymbol, fuelCost); // burn the "cost" tokens vm.BurnTokens(DomainSettings.FuelTokenSymbol, from, fuelCost); // ABI validation ValidateABI(vm, contractName, abi, isNative); SmartContract oldContract; if (isToken) { oldContract = vm.Nexus.GetTokenContract(vm.Storage, contractName); } else { oldContract = vm.Chain.GetContractByName(vm.Storage, contractName); } vm.Expect(oldContract != null, "could not fetch previous contract"); vm.Expect(abi.Implements(oldContract.ABI), "new abi does not implement all methods of previous abi"); vm.Expect(vm.InvokeTrigger(false, script, contractName, abi, AccountTrigger.OnUpgrade.ToString(), from) == TriggerResult.Success, "OnUpgrade trigger failed"); if (isToken) { vm.Nexus.UpgradeTokenContract(vm.RootStorage, contractName, script, abi); } else { vm.Chain.UpgradeContract(vm.Storage, contractName, script, abi); } vm.Notify(EventKind.ContractUpgrade, from, contractName); return(ExecutionState.Running); }
private static ExecutionState Runtime_DeployContract(RuntimeVM vm) { var tx = vm.Transaction; Throw.IfNull(tx, nameof(tx)); var pow = tx.Hash.GetDifficulty(); vm.Expect(pow >= (int)ProofOfWork.Minimal, "expected proof of work"); vm.ExpectStackSize(1); var from = vm.PopAddress(); vm.Expect(from.IsUser, "address must be user"); if (vm.Nexus.HasGenesis) { //Runtime.Expect(org != DomainSettings.ValidatorsOrganizationName, "cannot deploy contract via this organization"); vm.Expect(vm.IsStakeMaster(from), "needs to be master"); } vm.Expect(vm.IsWitness(from), "invalid witness"); var contractName = vm.PopString("contractName"); var contractAddress = SmartContract.GetAddressForName(contractName); var deployed = vm.Chain.IsContractDeployed(vm.Storage, contractAddress); // TODO if (vm.ProtocolVersion >= 2) { vm.Expect(!deployed, $"{contractName} is already deployed"); } else if (deployed) { return(ExecutionState.Running); } byte[] script; ContractInterface abi; bool isNative = Nexus.IsNativeContract(contractName); if (isNative) { if (contractName == "validator" && vm.GenesisAddress == Address.Null) { vm.Nexus.Initialize(from); } script = new byte[] { (byte)Opcode.RET }; var contractInstance = vm.Nexus.GetNativeContractByAddress(contractAddress); abi = contractInstance.ABI; } else { if (ValidationUtils.IsValidTicker(contractName)) { throw new VMException(vm, "use createToken instead for this kind of contract"); } else { vm.Expect(ValidationUtils.IsValidIdentifier(contractName), "invalid contract name"); } var isReserved = ValidationUtils.IsReservedIdentifier(contractName); if (isReserved && vm.IsWitness(vm.GenesisAddress)) { isReserved = false; } vm.Expect(!isReserved, $"name '{contractName}' reserved by system"); script = vm.PopBytes("contractScript"); var abiBytes = vm.PopBytes("contractABI"); abi = ContractInterface.FromBytes(abiBytes); var fuelCost = vm.GetGovernanceValue(Nexus.FuelPerContractDeployTag); // governance value is in usd fiat, here convert from fiat to fuel amount fuelCost = vm.GetTokenQuote(DomainSettings.FiatTokenSymbol, DomainSettings.FuelTokenSymbol, fuelCost); // burn the "cost" tokens vm.BurnTokens(DomainSettings.FuelTokenSymbol, from, fuelCost); } // ABI validation ValidateABI(vm, contractName, abi, isNative); var success = vm.Chain.DeployContractScript(vm.Storage, from, contractName, contractAddress, script, abi); vm.Expect(success, $"deployment of {contractName} failed"); var constructor = abi.FindMethod(SmartContract.ConstructorName); if (constructor != null) { vm.CallContext(contractName, constructor, from); } vm.Notify(EventKind.ContractDeploy, from, contractName); return(ExecutionState.Running); }
public TokenSeries(BigInteger mintCount, BigInteger maxSupply, TokenSeriesMode mode, byte[] script, ContractInterface ABI, byte[] ROM) { MintCount = mintCount; MaxSupply = maxSupply; Mode = mode; Script = script; this.ABI = ABI; SetROM(ROM); }
private static void DeployOrUpgrade(string[] args, NexusAPI api, BigInteger minFee, bool isUpgrade) { if (args.Length != 1) { throw new CommandException("Invalid number of arguments, expected file name"); } DoChecks(api); var fileName = args[0]; if (!File.Exists(fileName)) { throw new CommandException("Provided file does not exist"); } var extension = ScriptModule.ScriptExtension; if (!fileName.EndsWith(extension)) { throw new CommandException($"Provided file is not a compiled {extension} script"); } var abiFile = fileName.Replace(extension, ".abi"); if (!File.Exists(abiFile)) { throw new CommandException($"No ABI file {abiFile} that matches provided script file"); } var contractName = Path.GetFileNameWithoutExtension(fileName); var contractScript = File.ReadAllBytes(fileName); var abiBytes = File.ReadAllBytes(abiFile); var abi = ContractInterface.FromBytes(abiBytes); var sb = new ScriptBuilder(); bool isToken = ValidationUtils.IsValidTicker(contractName); var availableFlags = Enum.GetValues(typeof(TokenFlags)).Cast <TokenFlags>().ToArray(); sb.AllowGas(Keys.Address, Address.Null, minFee, 9999); if (isUpgrade) { // check for modification in flags if (isToken) { var symbol = contractName; var resultStr = api.Execute("getToken", new[] { symbol, "false" }); dynamic apiResult = JsonConvert.DeserializeObject <TokenResult>(resultStr); if (apiResult is TokenResult) { var oldToken = (TokenResult)apiResult; var oldFlags = TokenFlags.None; var splitFlags = oldToken.flags.Split(','); foreach (var entry in splitFlags) { TokenFlags flag; if (Enum.TryParse <TokenFlags>(entry, true, out flag)) { oldFlags |= flag; } } foreach (var flag in availableFlags) { var propName = "is" + flag; if (abi.HasMethod(propName)) { var isSet = ExecuteScript(contractScript, abi, propName).AsBool(); var wasSet = oldFlags.HasFlag(flag); if (isSet != wasSet) { throw new CommandException($"Detected '{flag}' flag change: {wasSet} => {isSet}"); } } } } else { throw new CommandException("could not find any deployed token contract for " + symbol); } } sb.CallInterop("Runtime.UpgradeContract", Keys.Address, contractName, contractScript, abiBytes); } else if (isToken) { if (!abi.HasMethod("getName")) { throw new CommandException("token contract is missing required 'name' property"); } var symbol = contractName; var name = ExecuteScript(contractScript, abi, "getName").AsString(); if (string.IsNullOrEmpty(name)) { throw new CommandException("token contract 'name' property is returning an empty value"); } BigInteger maxSupply = abi.HasMethod("getMaxSupply") ? ExecuteScript(contractScript, abi, "getMaxSupply").AsNumber() : 0; BigInteger decimals = abi.HasMethod("getDecimals") ? ExecuteScript(contractScript, abi, "getDecimals").AsNumber() : 0; TokenFlags flags = TokenFlags.None; foreach (var flag in availableFlags) { var propName = "is" + flag; if (abi.HasMethod(propName) && ExecuteScript(contractScript, abi, propName).AsBool()) { flags |= flag; } } sb.CallInterop("Nexus.CreateToken", Keys.Address, symbol, name, maxSupply, decimals, flags, contractScript, abiBytes); contractName = symbol; } else { sb.CallInterop("Runtime.DeployContract", Keys.Address, contractName, contractScript, abiBytes); } sb.SpendGas(Keys.Address); var script = sb.EndScript(); if (!isUpgrade) { var upgradeTrigger = AccountContract.GetTriggerForABI(AccountTrigger.OnUpgrade); if (abi.Implements(upgradeTrigger)) { logger.Message($"{contractName} implements proper triggers, and can be upgraded later."); } else { logger.Warning($"{contractName} does not implements proper triggers, can't be upgraded later."); } } var hash = ExecuteTransaction(api, script, ProofOfWork.Minimal, Keys); if (hash != Hash.Null) { var expectedEvent = isUpgrade ? EventKind.ContractUpgrade : (isToken ? EventKind.TokenCreate : EventKind.ContractDeploy); var expectedEventStr = expectedEvent.ToString(); var events = GetTransactionEvents(hash); if (events.Any(x => x.kind == expectedEventStr)) { var contractAddress = SmartContract.GetAddressForName(contractName); string action = isUpgrade ? "Upgraded" : "Deployed"; logger.Message($"{action} {contractName} at {contractAddress}"); } else { throw new CommandException("Transaction was confirmed but deployment event is missing!"); } } }
public override ContractInterface GenerateCode(CodeGenerator output) { foreach (var evt in Events.Values) { evt.Validate(); } if (this.Kind == ModuleKind.Token) { bool hasName = false; foreach (var method in this.Methods.Values) { string name; if (method.Name.StartsWith("get")) { name = method.Name.Substring(3); switch (name) { case "Name": hasName = true; ExpectMethodType(method, VarKind.String); break; case "MaxSupply": ExpectMethodType(method, VarKind.Number); break; } } else { if (method.Name.StartsWith("is") && method.Name.Length > 2 && char.IsUpper(method.Name[2])) { ExpectMethodType(method, VarKind.Bool); } } } if (!hasName) { throw new CompilerException($"token {this.Name} is missing property 'name'"); } } this.Scope.Enter(output); /*{ * var reg = Parser.Instance.AllocRegister(output, this, "methodName"); * output.AppendLine(this, $"POP {reg}"); * foreach (var entry in Methods.Values) * { * output.AppendLine(this, $"LOAD r0, \"{entry.Name}\""); * output.AppendLine(this, $"EQUAL r0, {reg}"); * output.AppendLine(this, $"JMPIF r0, @{entry.GetEntryLabel()}"); * } * Parser.Instance.DeallocRegister(reg); * output.AppendLine(this, "THROW \"unknown method was called\""); * }*/ foreach (var entry in Methods.Values) { entry.GenerateCode(output); } this.Scope.Leave(output); var methods = Methods.Values.Where(x => [email protected]).Select(x => x.GetABI()); var events = Events.Values.Select(x => x.GetABI()); var abi = new ContractInterface(methods, events); return(abi); }
protected virtual void ProcessABI(ContractInterface abi, DebugInfo debugInfo) { // do nothing }
public void UpgradeContract(StorageContext storage, string name, byte[] script, ContractInterface abi) { if (Nexus.IsNativeContract(name) || ValidationUtils.IsValidTicker(name)) { throw new ChainException($"Cannot upgrade this type of contract: {name}"); } if (!IsContractDeployed(storage, name)) { throw new ChainException($"Cannot upgrade non-existing contract: {name}"); } var address = SmartContract.GetAddressForName(name); var scriptKey = GetContractKey(address, "script"); storage.Put(scriptKey, script); var abiKey = GetContractKey(address, "abi"); var abiBytes = abi.ToByteArray(); storage.Put(abiBytes, abiBytes); }
public Transaction GenerateToken(PhantasmaKeys owner, string symbol, string name, BigInteger totalSupply, int decimals, TokenFlags flags, byte[] tokenScript = null, Dictionary <string, int> labels = null, IEnumerable <ContractMethod> customMethods = null) { var version = Nexus.GetGovernanceValue(Nexus.RootStorage, Nexus.NexusProtocolVersionTag); if (labels == null) { labels = new Dictionary <string, int>(); } if (tokenScript == null) { // small script that restricts minting of tokens to transactions where the owner is a witness var addressStr = Base16.Encode(owner.Address.ToByteArray()); string[] scriptString; if (version >= 4) { scriptString = new string[] { $"alias r3, $result", $"alias r4, $owner", $"@{AccountTrigger.OnMint}: nop", $"load $owner 0x{addressStr}", "push $owner", "extcall \"Address()\"", "extcall \"Runtime.IsWitness\"", "pop $result", $"jmpif $result, @end", $"load r0 \"invalid witness\"", $"throw r0", $"@end: ret" }; } else { scriptString = new string[] { $"alias r1, $triggerMint", $"alias r2, $currentTrigger", $"alias r3, $result", $"alias r4, $owner", $@"load $triggerMint, ""{AccountTrigger.OnMint}""", $"pop $currentTrigger", $"equal $triggerMint, $currentTrigger, $result", $"jmpif $result, @mintHandler", $"jmp @end", $"@mintHandler: nop", $"load $owner 0x{addressStr}", "push $owner", "extcall \"Address()\"", "extcall \"Runtime.IsWitness\"", "pop $result", $"jmpif $result, @end", $"load r0 \"invalid witness\"", $"throw r0", $"@end: ret" }; } DebugInfo debugInfo; tokenScript = AssemblerUtils.BuildScript(scriptString, "GenerateToken", out debugInfo, out labels); } var sb = ScriptUtils. BeginScript(). AllowGas(owner.Address, Address.Null, MinimumFee, 9999); if (version >= 4) { var triggerMap = new Dictionary <AccountTrigger, int>(); var onMintLabel = AccountTrigger.OnMint.ToString(); if (labels.ContainsKey(onMintLabel)) { triggerMap[AccountTrigger.OnMint] = labels[onMintLabel]; } var methods = AccountContract.GetTriggersForABI(triggerMap); if (customMethods != null) { methods = methods.Concat(customMethods); } var abi = new ContractInterface(methods, Enumerable.Empty <ContractEvent>()); var abiBytes = abi.ToByteArray(); sb.CallInterop("Nexus.CreateToken", owner.Address, symbol, name, totalSupply, decimals, flags, tokenScript, abiBytes); } else { sb.CallInterop("Nexus.CreateToken", owner.Address, symbol, name, totalSupply, decimals, flags, tokenScript); } if (!flags.HasFlag(TokenFlags.Fungible)) { ContractInterface nftABI; byte[] nftScript; NFTUtils.GenerateNFTDummyScript(symbol, name, name, "http://simulator/nft/*", "http://simulator/img/*", out nftScript, out nftABI); sb.CallInterop("Nexus.CreateTokenSeries", owner.Address, symbol, new BigInteger(0), totalSupply, TokenSeriesMode.Unique, nftScript, nftABI.ToByteArray()); } sb.SpendGas(owner.Address); var script = sb.EndScript(); var tx = MakeTransaction(owner, ProofOfWork.Minimal, Nexus.RootChain, script); return(tx); }
static byte[] GenCreateToken() { Console.Write("Token symbol? "); string symbol = Console.ReadLine(); if (!ValidationUtils.IsValidTicker(symbol)) { Console.Write("Invalid token symbol"); return(null); } Console.Write("Token name? "); string name = Console.ReadLine(); TokenFlags flags = TokenFlags.Transferable; var possibleValues = new[] { TokenFlags.Burnable, TokenFlags.Divisible, TokenFlags.Finite, TokenFlags.Fungible }; foreach (var val in possibleValues) { if (FetchAnswer($"Is {symbol} {val}?")) { flags |= val; } } int decimals; if (flags.HasFlag(TokenFlags.Divisible)) { Console.Write($"How many decimals {symbol} has?: "); if (!int.TryParse(Console.ReadLine(), out decimals) || decimals < 0 || decimals > 18) { Console.Write("Invalid decimals"); return(null); } } else { decimals = 0; } BigInteger maxSupply; if (flags.HasFlag(TokenFlags.Finite)) { Console.Write($"What is the max supply of {symbol}?: "); decimal val; if (!decimal.TryParse(Console.ReadLine(), out val) || val <= 0) { Console.Write("Invalid decimals"); return(null); } maxSupply = UnitConversion.ToBigInteger(val, decimals); } else { maxSupply = 0; } var labels = new Dictionary <string, int>(); var addressStr = Base16.Encode(signerKeys.Address.ToByteArray()); string[] scriptString; scriptString = new string[] { $"alias r3, $result", $"alias r4, $owner", $"@{AccountTrigger.OnMint}: nop", $"load $owner 0x{addressStr}", "push $owner", "extcall \"Address()\"", "extcall \"Runtime.IsWitness\"", "pop $result", $"jmpif $result, @end", $"load r0 \"invalid witness\"", $"throw r0", $"@end: ret" }; DebugInfo debugInfo; var tokenScript = AssemblerUtils.BuildScript(scriptString, "GenerateToken", out debugInfo, out labels); var sb = ScriptUtils. BeginScript(). AllowGas(signerKeys.Address, Address.Null, MinimumFee, 9999); var triggerMap = new Dictionary <AccountTrigger, int>(); var onMintLabel = AccountTrigger.OnMint.ToString(); if (labels.ContainsKey(onMintLabel)) { triggerMap[AccountTrigger.OnMint] = labels[onMintLabel]; } var methods = AccountContract.GetTriggersForABI(triggerMap); var abi = new ContractInterface(methods, Enumerable.Empty <ContractEvent>()); var abiBytes = abi.ToByteArray(); sb.CallInterop("Nexus.CreateToken", signerKeys.Address, symbol, name, maxSupply, decimals, flags, tokenScript, abiBytes); if (!flags.HasFlag(TokenFlags.Fungible)) { Console.Write("NFT deployment not supported yet"); return(null); } sb.SpendGas(signerKeys.Address); var script = sb.EndScript(); return(script); }