public static void ExecuteScript(string[] args, SpookSettings settings, NexusAPI api, BigInteger minFee) { 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 (!fileName.EndsWith(".tx")) { throw new CommandException($"Provided file is not a compiled transaction script"); } var txScript = File.ReadAllBytes(fileName); var sb = new ScriptBuilder(); sb.AllowGas(Keys.Address, Address.Null, minFee, 9999); sb.EmitRaw(txScript); sb.SpendGas(Keys.Address); var script = sb.EndScript(); var hash = ExecuteTransaction(settings, api, script, ProofOfWork.Minimal, Keys); }
public static void Migrate(string[] args, SpookSettings settings, NexusAPI api, BigInteger minFee) { if (args.Length != 1) { throw new CommandException("Invalid number of arguments, expected new wif"); } DoChecks(api); var newWIF = args[0]; var newKeys = PhantasmaKeys.FromWIF(newWIF); var expectedLimit = 800; var sb = new ScriptBuilder(); sb.AllowGas(Keys.Address, Address.Null, minFee, expectedLimit); sb.CallContract("validator", "Migrate", Keys.Address, newKeys.Address); sb.SpendGas(Keys.Address); var script = sb.EndScript(); var hash = ExecuteTransaction(settings, api, script, ProofOfWork.None, Keys /*, newKeys*/); if (hash != Hash.Null) { logger.Message($"Migrated to " + newKeys.Address); Keys = newKeys; } }
public static void Airdrop(string[] args, NexusAPI api, BigInteger minFee) { if (args.Length != 1) { throw new CommandException("Invalid number of arguments, expected airdrop filename"); } DoChecks(api); var fileName = args[0]; if (!File.Exists(fileName)) { throw new CommandException("Airdrop file does not exist"); } var lines = File.ReadAllLines(fileName); var sb = new ScriptBuilder(); var expectedLimit = 100 + 600 * lines.Length; var expectedGas = UnitConversion.ToDecimal(expectedLimit * minFee, DomainSettings.FuelTokenDecimals); logger.Message($"This airdrop will require at least {expectedGas} {DomainSettings.FuelTokenSymbol}"); sb.AllowGas(Keys.Address, Address.Null, minFee, expectedLimit); int addressCount = 0; foreach (var line in lines) { var temp = line.Split(','); if (!Address.IsValidAddress(temp[0])) { continue; } addressCount++; var target = Address.FromText(temp[0]); var symbol = temp[1]; var amount = BigInteger.Parse(temp[2]); sb.TransferTokens(symbol, Keys.Address, target, amount); } sb.SpendGas(Keys.Address); var script = sb.EndScript(); logger.Message($"Sending airdrop to {addressCount} addresses..."); ExecuteTransaction(api, script, ProofOfWork.None, Keys); }
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!"); } } }
public static void Airdrop(string[] args, SpookSettings settings, NexusAPI api, BigInteger minFee) { if (args.Length != 1) { throw new CommandException("Invalid number of arguments, expected airdrop filename"); } DoChecks(api); var fileName = args[0]; if (!File.Exists(fileName)) { throw new CommandException("Airdrop file does not exist"); } var lines = File.ReadAllLines(fileName); var sb = new ScriptBuilder(); var expectedLimit = 100 + 600 * lines.Length; var expectedGas = UnitConversion.ToDecimal(expectedLimit * minFee, DomainSettings.FuelTokenDecimals); logger.Message($"This airdrop will require at least {expectedGas} {DomainSettings.FuelTokenSymbol}"); sb.AllowGas(Keys.Address, Address.Null, minFee, expectedLimit); Dictionary <string, TokenResult> tokens = new Dictionary <string, TokenResult>(); int addressCount = 0; foreach (var line in lines) { var temp = line.Split(','); if (!Address.IsValidAddress(temp[0])) { continue; } addressCount++; var target = Address.FromText(temp[0]); var symbol = temp[1]; decimal val; if (!Decimal.TryParse(temp[2], NumberStyles.Any, new CultureInfo("en-US"), out val) || val <= 0) { throw new CommandException($"Invalid amount {val} for {target}"); } var token = tokens.ContainsKey(symbol) ? tokens[symbol] : FetchTokenInfo(api, symbol); tokens[symbol] = token; var amount = UnitConversion.ToBigInteger(val, token.decimals); sb.TransferTokens(symbol, Keys.Address, target, amount); } sb.SpendGas(Keys.Address); var script = sb.EndScript(); logger.Message($"Sending airdrop to {addressCount} addresses..."); ExecuteTransaction(settings, api, script, ProofOfWork.None, Keys); }
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!"); } } }