Exemple #1
0
        /// <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);
        }
Exemple #2
0
        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);
        }
Exemple #3
0
        /// <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());
                }
            }
        }
Exemple #4
0
        /// <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);
        }