/// <summary> /// Creates an InvocationTransactions. Serves to invoke a contract on the blockchain. /// It need the contract script hash, operation and operation arguments. /// This can be used for the "mintTokens" NEP5 method for example. /// </summary> /// <param name="contractScriptHash"></param> /// <param name="operation"></param> /// <param name="args"></param> /// <param name="outputs"></param> /// <param name="fee"></param> /// <param name="attributes"></param> /// <returns></returns> public override async Task <Transaction> CallContract(string contractScriptHash, string operation, object[] args, IEnumerable <TransferOutput> outputs = null, decimal fee = 0, List <TransactionAttribute> attributes = null) { if (string.IsNullOrEmpty(contractScriptHash)) { throw new ArgumentNullException(nameof(contractScriptHash)); } if (string.IsNullOrEmpty(operation)) { throw new ArgumentNullException(nameof(operation)); } var script = Utils.GenerateScript(contractScriptHash, operation, args); if (attributes == null) { attributes = new List <TransactionAttribute>(); } attributes.Add(new TransactionAttribute { Usage = TransactionAttributeUsage.Script, Data = AddressScriptHash.ToArray() }); var tx = new InvocationTransaction { Version = 1, Script = script, Attributes = attributes.ToArray(), Inputs = new CoinReference[0], Outputs = outputs == null ? new TransactionOutput[0] : outputs.Where(p => p.IsGlobalAsset).Select(p => p.ToTxOutput()).ToArray(), Witnesses = new Witness[0] }; var gasConsumed = await EstimateGasAsync(tx.Script.ToHexString()); tx.Gas = InvocationTransaction.GetGas(Fixed8.FromDecimal(gasConsumed)); tx = MakeTransaction(tx, AddressScriptHash, null, Fixed8.FromDecimal(fee)); var success = await SignAndSendTransaction(tx); return(success ? tx : null); }
public Transaction MakeTransaction(List <TransactionAttribute> attributes, IEnumerable <TransferOutput> outputs, UInt160 from = null, UInt160 change_address = null, Fixed8 fee = default(Fixed8)) { var cOutputs = outputs.Where(p => !p.IsGlobalAsset).GroupBy(p => new { AssetId = (UInt160)p.AssetId, Account = p.ScriptHash }, (k, g) => new { k.AssetId, Value = g.Aggregate(BigInteger.Zero, (x, y) => x + y.Value.Value), k.Account }).ToArray(); Transaction tx; if (attributes == null) { attributes = new List <TransactionAttribute>(); } if (cOutputs.Length == 0) { tx = new ContractTransaction(); } else { UInt160[] accounts = from == null?GetAccounts().Where(p => !p.Lock && !p.WatchOnly).Select(p => p.ScriptHash).ToArray() : new[] { from }; HashSet <UInt160> sAttributes = new HashSet <UInt160>(); using (ScriptBuilder sb = new ScriptBuilder()) { foreach (var output in cOutputs) { byte[] script; using (ScriptBuilder sb2 = new ScriptBuilder()) { foreach (UInt160 account in accounts) { sb2.EmitAppCall(output.AssetId, "balanceOf", account); } sb2.Emit(OpCode.DEPTH, OpCode.PACK); script = sb2.ToArray(); } ApplicationEngine engine = ApplicationEngine.Run(script); if (engine.State.HasFlag(VMState.FAULT)) { return(null); } var balances = ((IEnumerable <StackItem>)(VMArray) engine.ResultStack.Pop()).Reverse().Zip(accounts, (i, a) => new { Account = a, Value = i.GetBigInteger() }).ToArray(); BigInteger sum = balances.Aggregate(BigInteger.Zero, (x, y) => x + y.Value); if (sum < output.Value) { return(null); } if (sum != output.Value) { balances = balances.OrderByDescending(p => p.Value).ToArray(); BigInteger amount = output.Value; int i = 0; while (balances[i].Value <= amount) { amount -= balances[i++].Value; } if (amount == BigInteger.Zero) { balances = balances.Take(i).ToArray(); } else { balances = balances.Take(i).Concat(new[] { balances.Last(p => p.Value >= amount) }).ToArray(); } sum = balances.Aggregate(BigInteger.Zero, (x, y) => x + y.Value); } sAttributes.UnionWith(balances.Select(p => p.Account)); for (int i = 0; i < balances.Length; i++) { BigInteger value = balances[i].Value; if (i == 0) { BigInteger change = sum - output.Value; if (change > 0) { value -= change; } } sb.EmitAppCall(output.AssetId, "transfer", balances[i].Account, output.Account, value); sb.Emit(OpCode.THROWIFNOT); } } byte[] nonce = new byte[8]; rand.NextBytes(nonce); sb.Emit(OpCode.RET, nonce); tx = new InvocationTransaction { Version = 1, Script = sb.ToArray() }; } attributes.AddRange(sAttributes.Select(p => new TransactionAttribute { Usage = TransactionAttributeUsage.Script, Data = p.ToArray() })); } tx.Attributes = attributes.ToArray(); tx.Inputs = new CoinReference[0]; tx.Outputs = outputs.Where(p => p.IsGlobalAsset).Select(p => p.ToTxOutput()).ToArray(); tx.Witnesses = new Witness[0]; if (tx is InvocationTransaction itx) { ApplicationEngine engine = ApplicationEngine.Run(itx.Script, itx); if (engine.State.HasFlag(VMState.FAULT)) { return(null); } tx = new InvocationTransaction { Version = itx.Version, Script = itx.Script, Gas = InvocationTransaction.GetGas(engine.GasConsumed), Attributes = itx.Attributes, Inputs = itx.Inputs, Outputs = itx.Outputs }; } tx = MakeTransaction(tx, from, change_address, fee); return(tx); }
/// <summary> /// 发送invoke交易 /// </summary> /// <param name="script">合约执行脚本</param> /// <param name="gas_consumed">手续费</param> /// <param name="check_witness_address">见证者地址并作为输入地址,可选参数</param> /// <returns></returns> private JObject SendInvokeScript(JArray _params) { if (wallet == null || walletTimeLock.IsLocked()) { throw new RpcException(-400, "Access denied"); } else { byte[] script = _params[0].AsString().HexToBytes(); Fixed8 gas_consumed = Fixed8.Parse(_params[1].AsString()); if (gas_consumed < Fixed8.Zero) { throw new RpcException(-32602, "Invalid params"); } UInt160 check_witness_address = _params.Count >= 3 ? _params[2].AsString().ToScriptHash() : null; InvocationTransaction tx = null; gas_consumed = InvocationTransaction.GetGas(gas_consumed); tx = new InvocationTransaction { Version = 1, Script = script, Gas = gas_consumed }; List <TransactionAttribute> attributes = new List <TransactionAttribute>(); byte[] timeStamp = System.Text.ASCIIEncoding.ASCII.GetBytes(DateTime.UtcNow.ToString("yyyyMMddHHmmssfff")); byte[] nonce = new byte[8]; rand.NextBytes(nonce); attributes.Add( new TransactionAttribute() { Usage = TransactionAttributeUsage.Remark, Data = timeStamp.Concat(nonce).ToArray() }); if (check_witness_address != null) { attributes.Add( new TransactionAttribute() { Usage = TransactionAttributeUsage.Script, Data = check_witness_address.ToArray() }); } tx.Attributes = attributes.ToArray(); tx = wallet.MakeTransaction(tx, from: check_witness_address); if (tx == null) { throw new RpcException(-300, "Insufficient funds"); } ContractParametersContext context = new ContractParametersContext(tx); wallet.Sign(context); if (context.Completed) { tx.Witnesses = context.GetWitnesses(); if (tx.Size > Transaction.MaxTransactionSize) { throw new RpcException(-301, "The size of the free transaction must be less than 102400 bytes"); } wallet.ApplyTransaction(tx); system.LocalNode.Tell(new LocalNode.Relay { Inventory = tx }); return(tx.ToJson()); } else { return(context.ToJson()); } } }
/// <summary> /// Transfer NEP5 tokens. /// </summary> /// <param name="attributes"></param> /// <param name="outputs"></param> /// <param name="changeAddress"></param> /// <param name="fee"></param> /// <returns></returns> public override async Task <Transaction> TransferNep5(List <TransactionAttribute> attributes, IEnumerable <TransferOutput> outputs, UInt160 changeAddress = null, decimal fee = 0) { InvocationTransaction tx; var cOutputs = outputs.Where(p => !p.IsGlobalAsset).GroupBy(p => new { AssetId = (UInt160)p.AssetId, Account = p.ScriptHash }, (k, g) => new { k.AssetId, Value = g.Aggregate(BigInteger.Zero, (x, y) => x + y.Value.Value), k.Account }).ToArray(); if (cOutputs.Length == 0) { return(null); } var nep5Balances = await TransactionBuilderHelper.GetNep5Balances(AddressScriptHash.ToAddress(), _restService); using (ScriptBuilder sb = new ScriptBuilder()) { foreach (var output in cOutputs) { var nep5Balance = nep5Balances.SingleOrDefault(x => x.AssetHash == output.AssetId.ToString().Remove(0, 2)); if (nep5Balance == null) { throw new WalletException($"Not enough balance of: {output.AssetId} "); } sb.EmitAppCall(output.AssetId, Nep5Methods.transfer.ToString(), AddressScriptHash, output.Account, output.Value); sb.Emit(OpCode.THROWIFNOT); } byte[] nonce = GenerateNonce(8); sb.Emit(OpCode.RET, nonce); tx = new InvocationTransaction { Version = 1, Script = sb.ToArray() }; } if (attributes == null) { attributes = new List <TransactionAttribute>(); } attributes.Add(new TransactionAttribute { Usage = TransactionAttributeUsage.Script, Data = AddressScriptHash.ToArray() }); tx.Attributes = attributes.ToArray(); tx.Inputs = new CoinReference[0]; tx.Outputs = outputs.Where(p => p.IsGlobalAsset).Select(p => p.ToTxOutput()).ToArray(); tx.Witnesses = new Witness[0]; var gasConsumed = await EstimateGasAsync(tx.Script.ToHexString()); //todo add gas limit tx.Gas = InvocationTransaction.GetGas(Fixed8.FromDecimal(gasConsumed)); tx = MakeTransaction(tx, AddressScriptHash, changeAddress, Fixed8.FromDecimal(fee)); var success = await SignAndSendTransaction(tx); return(success ? tx : null); }