Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decouple calculate fee from Wallet #3147

Merged
merged 5 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions src/Neo/Wallets/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@
using Neo.IO;
using Neo.Network.P2P;
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.VM;
using System;
using static Neo.SmartContract.Helper;

namespace Neo.Wallets
{
Expand Down Expand Up @@ -72,5 +77,90 @@ internal static byte[] XOR(byte[] x, byte[] y)
r[i] = (byte)(x[i] ^ y[i]);
return r;
}

/// <summary>
/// Calculates the network fee for the specified transaction.
/// </summary>
/// <param name="tx">The transaction to calculate.</param>
/// <param name="snapshot">The snapshot used to read data.</param>
/// <param name="settings">Thr protocol settings to use.</param>
/// <param name="accountScript">Function to retrive the script's account from a hash.</param>
/// <param name="maxExecutionCost">The maximum cost that can be spent when a contract is executed.</param>
/// <returns>The network fee of the transaction.</returns>
public static long CalculateNetworkFee(this Transaction tx, DataCache snapshot, ProtocolSettings settings, Func<UInt160, byte[]> accountScript, long maxExecutionCost = ApplicationEngine.TestModeGas)
{
UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot);

// base size for transaction: includes const_header + signers + attributes + script + hashes
int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length), index = -1;
uint exec_fee_factor = NativeContract.Policy.GetExecFeeFactor(snapshot);
long networkFee = 0;
foreach (UInt160 hash in hashes)
{
index++;
byte[] witnessScript = accountScript(hash);
byte[] invocationScript = null;

if (tx.Witnesses != null && witnessScript is null)
{
// Try to find the script in the witnesses
Witness witness = tx.Witnesses[index];
witnessScript = witness?.VerificationScript.ToArray();

if (witnessScript is null || witnessScript.Length == 0)
{
// Then it's a contract-based witness, so try to get the corresponding invocation script for it
invocationScript = witness?.InvocationScript.ToArray();
}
}

if (witnessScript is null || witnessScript.Length == 0)
{
var contract = NativeContract.ContractManagement.GetContract(snapshot, hash);
if (contract is null)
throw new ArgumentException($"The smart contract or address {hash} is not found");
var md = contract.Manifest.Abi.GetMethod("verify", -1);
if (md is null)
throw new ArgumentException($"The smart contract {contract.Hash} haven't got verify method");
if (md.ReturnType != ContractParameterType.Boolean)
throw new ArgumentException("The verify method doesn't return boolean value.");
if (md.Parameters.Length > 0 && invocationScript is null)
throw new ArgumentException("The verify method requires parameters that need to be passed via the witness' invocation script.");

// Empty verification and non-empty invocation scripts
var invSize = invocationScript?.GetVarSize() ?? Array.Empty<byte>().GetVarSize();
size += Array.Empty<byte>().GetVarSize() + invSize;

// Check verify cost
using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: settings, gas: maxExecutionCost);
engine.LoadContract(contract, md, CallFlags.ReadOnly);
if (invocationScript != null) engine.LoadScript(invocationScript, configureState: p => p.CallFlags = CallFlags.None);
if (engine.Execute() == VMState.FAULT) throw new ArgumentException($"Smart contract {contract.Hash} verification fault.");
if (!engine.ResultStack.Pop().GetBoolean()) throw new ArgumentException($"Smart contract {contract.Hash} returns false.");

maxExecutionCost -= engine.GasConsumed;
if (maxExecutionCost <= 0) throw new InvalidOperationException("Insufficient GAS.");
networkFee += engine.GasConsumed;
}
else if (IsSignatureContract(witnessScript))
{
size += 67 + witnessScript.GetVarSize();
networkFee += exec_fee_factor * SignatureContractCost();
}
else if (IsMultiSigContract(witnessScript, out int m, out int n))
{
int size_inv = 66 * m;
size += IO.Helper.GetVarSize(size_inv) + size_inv + witnessScript.GetVarSize();
networkFee += exec_fee_factor * MultiSignatureContractCost(m, n);
}
// We can support more contract types in the future.
}
networkFee += size * NativeContract.Policy.GetFeePerByte(snapshot);
foreach (TransactionAttribute attr in tx.Attributes)
{
networkFee += attr.CalculateNetworkFee(snapshot, tx);
}
return networkFee;
}
}
}
89 changes: 1 addition & 88 deletions src/Neo/Wallets/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -578,99 +578,12 @@ private Transaction MakeTransaction(DataCache snapshot, ReadOnlyMemory<byte> scr
tx.SystemFee = engine.GasConsumed;
}

tx.NetworkFee = CalculateNetworkFee(snapshot, tx, maxGas);
tx.NetworkFee = tx.CalculateNetworkFee(snapshot, ProtocolSettings, (a) => GetAccount(a)?.Contract?.Script, maxGas);
vncoelho marked this conversation as resolved.
Show resolved Hide resolved
if (value >= tx.SystemFee + tx.NetworkFee) return tx;
}
throw new InvalidOperationException("Insufficient GAS");
}

/// <summary>
/// Calculates the network fee for the specified transaction.
/// </summary>
/// <param name="snapshot">The snapshot used to read data.</param>
/// <param name="tx">The transaction to calculate.</param>
/// <param name="maxExecutionCost">The maximum cost that can be spent when a contract is executed.</param>
/// <returns>The network fee of the transaction.</returns>
public long CalculateNetworkFee(DataCache snapshot, Transaction tx, long maxExecutionCost = ApplicationEngine.TestModeGas)
{
UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot);

// base size for transaction: includes const_header + signers + attributes + script + hashes
int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length);
uint exec_fee_factor = NativeContract.Policy.GetExecFeeFactor(snapshot);
long networkFee = 0;
int index = -1;
foreach (UInt160 hash in hashes)
{
index++;
byte[] witness_script = GetAccount(hash)?.Contract?.Script;
byte[] invocationScript = null;

if (tx.Witnesses != null)
{
if (witness_script is null)
{
// Try to find the script in the witnesses
Witness witness = tx.Witnesses[index];
witness_script = witness?.VerificationScript.ToArray();

if (witness_script is null || witness_script.Length == 0)
{
// Then it's a contract-based witness, so try to get the corresponding invocation script for it
invocationScript = witness?.InvocationScript.ToArray();
}
}
}

if (witness_script is null || witness_script.Length == 0)
{
var contract = NativeContract.ContractManagement.GetContract(snapshot, hash);
if (contract is null)
throw new ArgumentException($"The smart contract or address {hash} is not found");
var md = contract.Manifest.Abi.GetMethod("verify", -1);
if (md is null)
throw new ArgumentException($"The smart contract {contract.Hash} haven't got verify method");
if (md.ReturnType != ContractParameterType.Boolean)
throw new ArgumentException("The verify method doesn't return boolean value.");
if (md.Parameters.Length > 0 && invocationScript is null)
throw new ArgumentException("The verify method requires parameters that need to be passed via the witness' invocation script.");

// Empty verification and non-empty invocation scripts
var invSize = invocationScript?.GetVarSize() ?? Array.Empty<byte>().GetVarSize();
size += Array.Empty<byte>().GetVarSize() + invSize;

// Check verify cost
using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.CreateSnapshot(), settings: ProtocolSettings, gas: maxExecutionCost);
engine.LoadContract(contract, md, CallFlags.ReadOnly);
if (invocationScript != null) engine.LoadScript(invocationScript, configureState: p => p.CallFlags = CallFlags.None);
if (engine.Execute() == VMState.FAULT) throw new ArgumentException($"Smart contract {contract.Hash} verification fault.");
if (!engine.ResultStack.Pop().GetBoolean()) throw new ArgumentException($"Smart contract {contract.Hash} returns false.");

maxExecutionCost -= engine.GasConsumed;
if (maxExecutionCost <= 0) throw new InvalidOperationException("Insufficient GAS.");
networkFee += engine.GasConsumed;
}
else if (IsSignatureContract(witness_script))
{
size += 67 + witness_script.GetVarSize();
networkFee += exec_fee_factor * SignatureContractCost();
}
else if (IsMultiSigContract(witness_script, out int m, out int n))
{
int size_inv = 66 * m;
size += IO.Helper.GetVarSize(size_inv) + size_inv + witness_script.GetVarSize();
networkFee += exec_fee_factor * MultiSignatureContractCost(m, n);
}
// We can support more contract types in the future.
}
networkFee += size * NativeContract.Policy.GetFeePerByte(snapshot);
foreach (TransactionAttribute attr in tx.Attributes)
{
networkFee += attr.CalculateNetworkFee(snapshot, tx);
}
return networkFee;
}

/// <summary>
/// Signs the <see cref="IVerifiable"/> in the specified <see cref="ContractParametersContext"/> with the wallet.
/// </summary>
Expand Down
Loading