diff --git a/src/Neo/Network/P2P/Payloads/Transaction.cs b/src/Neo/Network/P2P/Payloads/Transaction.cs
index 4e59c1b9c5..cae0944bd1 100644
--- a/src/Neo/Network/P2P/Payloads/Transaction.cs
+++ b/src/Neo/Network/P2P/Payloads/Transaction.cs
@@ -339,11 +339,11 @@ public virtual VerifyResult VerifyStateDependent(ProtocolSettings settings, Data
return VerifyResult.InvalidAttribute;
attributesFee += attribute.CalculateNetworkFee(snapshot, this);
}
- long netFeeDatoshi = NetworkFee - (Size * NativeContract.Policy.GetFeePerByte(snapshot)) - attributesFee;
+ var netFeeDatoshi = NetworkFee - (Size * NativeContract.Policy.GetFeePerByte(snapshot)) - attributesFee;
if (netFeeDatoshi < 0) return VerifyResult.InsufficientFunds;
if (netFeeDatoshi > MaxVerificationGas) netFeeDatoshi = MaxVerificationGas;
- uint execFeeFactor = NativeContract.Policy.GetExecFeeFactor(snapshot);
+ var execFeeFactor = NativeContract.Policy.GetExecFeeFactor(settings, snapshot, height);
for (int i = 0; i < hashes.Length; i++)
{
if (IsSignatureContract(Witnesses[i].VerificationScript.Span) && IsSingleSignatureInvocationScript(Witnesses[i].InvocationScript, out var _))
diff --git a/src/Neo/SmartContract/ApplicationEngine.Contract.cs b/src/Neo/SmartContract/ApplicationEngine.Contract.cs
index 05562604fc..bc914030e8 100644
--- a/src/Neo/SmartContract/ApplicationEngine.Contract.cs
+++ b/src/Neo/SmartContract/ApplicationEngine.Contract.cs
@@ -124,7 +124,7 @@ internal protected UInt160 CreateStandardAccount(ECPoint pubKey)
long fee = IsHardforkEnabled(Hardfork.HF_Aspidochelone)
? CheckSigPrice
: 1 << 8;
- AddFee(fee * ExecFeeFactor);
+ AddFee(fee * _execFeeFactor);
return Contract.CreateSignatureRedeemScript(pubKey).ToScriptHash();
}
@@ -141,7 +141,7 @@ internal protected UInt160 CreateMultisigAccount(int m, ECPoint[] pubKeys)
long fee = IsHardforkEnabled(Hardfork.HF_Aspidochelone)
? CheckSigPrice * pubKeys.Length
: 1 << 8;
- AddFee(fee * ExecFeeFactor);
+ AddFee(fee * _execFeeFactor);
return Contract.CreateMultiSigRedeemScript(m, pubKeys).ToScriptHash();
}
diff --git a/src/Neo/SmartContract/ApplicationEngine.Crypto.cs b/src/Neo/SmartContract/ApplicationEngine.Crypto.cs
index b8fdb62ba0..e006fb3176 100644
--- a/src/Neo/SmartContract/ApplicationEngine.Crypto.cs
+++ b/src/Neo/SmartContract/ApplicationEngine.Crypto.cs
@@ -69,7 +69,7 @@ protected internal bool CheckMultisig(byte[][] pubkeys, byte[][] signatures)
if (n == 0) throw new ArgumentException("pubkeys array cannot be empty.");
if (m == 0) throw new ArgumentException("signatures array cannot be empty.");
if (m > n) throw new ArgumentException($"signatures count ({m}) cannot be greater than pubkeys count ({n}).");
- AddFee(CheckSigPrice * n * ExecFeeFactor);
+ AddFee(CheckSigPrice * n * _execFeeFactor);
try
{
for (int i = 0, j = 0; i < m && j < n;)
diff --git a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs
index 2b813338d0..aca7af2b75 100644
--- a/src/Neo/SmartContract/ApplicationEngine.Runtime.cs
+++ b/src/Neo/SmartContract/ApplicationEngine.Runtime.cs
@@ -324,7 +324,7 @@ protected internal BigInteger GetRandom()
buffer = nonceData = Cryptography.Helper.Murmur128(nonceData, ProtocolSettings.Network);
price = 1 << 4;
}
- AddFee(price * ExecFeeFactor);
+ AddFee(price * _execFeeFactor);
return new BigInteger(buffer, isUnsigned: true);
}
@@ -449,7 +449,7 @@ protected internal void BurnGas(long datoshi)
{
if (datoshi <= 0)
throw new InvalidOperationException("GAS must be positive.");
- AddFee(datoshi);
+ AddFee(datoshi * FeeFactor);
}
///
diff --git a/src/Neo/SmartContract/ApplicationEngine.Storage.cs b/src/Neo/SmartContract/ApplicationEngine.Storage.cs
index 36c3769893..db1190afaa 100644
--- a/src/Neo/SmartContract/ApplicationEngine.Storage.cs
+++ b/src/Neo/SmartContract/ApplicationEngine.Storage.cs
@@ -256,7 +256,7 @@ protected internal void Put(StorageContext context, byte[] key, byte[] value)
else
newDataSize = (item.Value.Length - 1) / 4 + 1 + value.Length - item.Value.Length;
}
- AddFee(newDataSize * StoragePrice);
+ AddFee(newDataSize * StoragePrice * FeeFactor);
item.Value = value;
}
diff --git a/src/Neo/SmartContract/ApplicationEngine.cs b/src/Neo/SmartContract/ApplicationEngine.cs
index e3b10b746d..2d66654fee 100644
--- a/src/Neo/SmartContract/ApplicationEngine.cs
+++ b/src/Neo/SmartContract/ApplicationEngine.cs
@@ -66,15 +66,19 @@ public partial class ApplicationEngine : ExecutionEngine
private static Dictionary? services;
// Total amount of GAS spent to execute.
- // In the unit of datoshi, 1 datoshi = 1e-8 GAS, 1 GAS = 1e8 datoshi
- private readonly long _feeAmount;
+ // In the unit of picoGAS, 1 picoGAS = 1e-12 GAS
+ private readonly BigInteger _feeAmount;
+ private BigInteger _feeConsumed;
+ // Decimals for fee calculation
+ public const uint FeeFactor = 10000;
private Dictionary? states;
private readonly DataCache originalSnapshotCache;
private List? notifications;
private List? disposables;
private readonly Dictionary invocationCounter = new();
private readonly Dictionary contractTasks = new();
- internal readonly uint ExecFeeFactor;
+ // In the unit of picoGAS, 1 picoGAS = 1e-12 GAS
+ private readonly BigInteger _execFeeFactor;
// In the unit of datoshi, 1 datoshi = 1e-8 GAS
internal readonly uint StoragePrice;
private byte[] nonceData;
@@ -132,19 +136,29 @@ public partial class ApplicationEngine : ExecutionEngine
/// In the unit of datoshi, 1 datoshi = 1e-8 GAS, 1 GAS = 1e8 datoshi
///
[Obsolete("This property is deprecated. Use FeeConsumed instead.")]
- public long GasConsumed { get; protected set; } = 0;
+ public long GasConsumed => FeeConsumed;
+
+ ///
+ /// Exec Fee Factor. In the unit of datoshi, 1 datoshi = 1e-8 GAS
+ ///
+ internal long ExecFeeFactor => (long)_execFeeFactor.DivideCeiling(FeeFactor);
///
/// GAS spent to execute.
/// In the unit of datoshi, 1 datoshi = 1e-8 GAS, 1 GAS = 1e8 datoshi
///
- public long FeeConsumed { get; protected set; } = 0;
+ public long FeeConsumed => (long)_feeConsumed.DivideCeiling(FeeFactor);
+
+ ///
+ /// Exec Fee Factor. In the unit of picoGAS, 1 picoGAS = 1e-12 GAS
+ ///
+ internal BigInteger ExecFeePicoFactor => _execFeeFactor;
///
/// The remaining GAS that can be spent in order to complete the execution.
/// In the unit of datoshi, 1 datoshi = 1e-8 GAS, 1 GAS = 1e8 datoshi
///
- public long GasLeft => _feeAmount - FeeConsumed;
+ public long GasLeft => (long)((_feeAmount - _feeConsumed) / FeeFactor);
///
/// The exception that caused the execution to terminate abnormally. This field could be if no exception is thrown.
@@ -206,17 +220,31 @@ protected ApplicationEngine(
originalSnapshotCache = snapshotCache;
PersistingBlock = persistingBlock;
ProtocolSettings = settings;
- _feeAmount = gas;
+ _feeAmount = gas * FeeFactor; // PicoGAS
Diagnostic = diagnostic;
nonceData = container is Transaction tx ? tx.Hash.ToArray()[..16] : new byte[16];
if (snapshotCache is null || persistingBlock?.Index == 0)
{
- ExecFeeFactor = PolicyContract.DefaultExecFeeFactor;
+ _execFeeFactor = PolicyContract.DefaultExecFeeFactor * FeeFactor; // Add fee decimals
StoragePrice = PolicyContract.DefaultStoragePrice;
}
else
{
- ExecFeeFactor = NativeContract.Policy.GetExecFeeFactor(snapshotCache);
+ var persistingIndex = persistingBlock?.Index ?? NativeContract.Ledger.CurrentIndex(snapshotCache);
+
+ if (settings == null || !settings.IsHardforkEnabled(Hardfork.HF_Faun, persistingIndex))
+ {
+ // The values doesn't have the decimals stored
+ _execFeeFactor = NativeContract.Policy.GetExecFeeFactor(this) * FeeFactor;
+ }
+ else
+ {
+ // The values have the decimals stored starting from OnPersist of Faun's block.
+ _execFeeFactor = NativeContract.Policy.GetExecPicoFeeFactor(this);
+ if (trigger == TriggerType.OnPersist && persistingIndex > 0 && !settings.IsHardforkEnabled(Hardfork.HF_Faun, persistingIndex - 1))
+ _execFeeFactor *= FeeFactor;
+ }
+
StoragePrice = NativeContract.Policy.GetStoragePrice(snapshotCache);
}
@@ -297,13 +325,11 @@ protected static void OnSysCall(ExecutionEngine engine, Instruction instruction)
///
/// Adds GAS to and checks if it has exceeded the maximum limit.
///
- /// The amount of GAS, in the unit of datoshi, 1 datoshi = 1e-8 GAS, to be added.
- protected internal void AddFee(long datoshi)
+ /// The amount of GAS, in the unit of picoGAS, 1 picoGAS = 1e-12 GAS, to be added.
+ protected internal void AddFee(BigInteger picoGas)
{
-#pragma warning disable CS0618 // Type or member is obsolete
- FeeConsumed = GasConsumed = checked(FeeConsumed + datoshi);
-#pragma warning restore CS0618 // Type or member is obsolete
- if (FeeConsumed > _feeAmount)
+ _feeConsumed = _feeConsumed + picoGas;
+ if (_feeConsumed > _feeAmount)
throw new InvalidOperationException("Insufficient GAS.");
}
@@ -657,7 +683,7 @@ internal protected void ValidateCallFlags(CallFlags requiredCallFlags)
protected virtual void OnSysCall(InteropDescriptor descriptor)
{
ValidateCallFlags(descriptor.RequiredCallFlags);
- AddFee(descriptor.FixedPrice * ExecFeeFactor);
+ AddFee(descriptor.FixedPrice * _execFeeFactor);
object?[] parameters = new object?[descriptor.Parameters.Count];
for (int i = 0; i < parameters.Length; i++)
@@ -671,7 +697,7 @@ protected virtual void OnSysCall(InteropDescriptor descriptor)
protected override void PreExecuteInstruction(Instruction instruction)
{
Diagnostic?.PreExecuteInstruction(instruction);
- AddFee(ExecFeeFactor * OpCodePriceTable[(byte)instruction.OpCode]);
+ AddFee(_execFeeFactor * OpCodePriceTable[(byte)instruction.OpCode]);
}
protected override void PostExecuteInstruction(Instruction instruction)
diff --git a/src/Neo/SmartContract/Native/ContractManagement.cs b/src/Neo/SmartContract/Native/ContractManagement.cs
index d71771189e..f4c2ccd03f 100644
--- a/src/Neo/SmartContract/Native/ContractManagement.cs
+++ b/src/Neo/SmartContract/Native/ContractManagement.cs
@@ -267,10 +267,10 @@ private async ContractTask Deploy(ApplicationEngine engine, byte[
if (manifest.Length == 0)
throw new ArgumentException($"Manifest length cannot be zero.");
- engine.AddFee(Math.Max(
- engine.StoragePrice * (nefFile.Length + manifest.Length),
- GetMinimumDeploymentFee(engine.SnapshotCache)
- ));
+ // In the unit of picoGAS, 1 picoGAS = 1e-12 GAS
+ engine.AddFee(BigInteger.Max(engine.StoragePrice * (nefFile.Length + manifest.Length),
+ GetMinimumDeploymentFee(engine.SnapshotCache))
+ * ApplicationEngine.FeeFactor);
NefFile nef = nefFile.AsSerializable();
ContractManifest parsedManifest = ContractManifest.Parse(manifest);
@@ -336,7 +336,7 @@ private ContractTask Update(ApplicationEngine engine, byte[] nefFile, byte[] man
if (nefFile is null && manifest is null)
throw new ArgumentException("NEF file and manifest cannot both be null.");
- engine.AddFee(engine.StoragePrice * ((nefFile?.Length ?? 0) + (manifest?.Length ?? 0)));
+ engine.AddFee(engine.StoragePrice * ApplicationEngine.FeeFactor * ((nefFile?.Length ?? 0) + (manifest?.Length ?? 0)));
var contractState = engine.SnapshotCache.GetAndChange(CreateStorageKey(Prefix_Contract, engine.CallingScriptHash!))
?? throw new InvalidOperationException($"Updating Contract Does Not Exist: {engine.CallingScriptHash}");
diff --git a/src/Neo/SmartContract/Native/NativeContract.cs b/src/Neo/SmartContract/Native/NativeContract.cs
index dc514468a4..7453ceb947 100644
--- a/src/Neo/SmartContract/Native/NativeContract.cs
+++ b/src/Neo/SmartContract/Native/NativeContract.cs
@@ -432,8 +432,10 @@ internal async void Invoke(ApplicationEngine engine, byte version)
var state = context.GetState();
if (!state.CallFlags.HasFlag(method.RequiredCallFlags))
throw new InvalidOperationException($"Cannot call this method with the flag {state.CallFlags}.");
- // In the unit of datoshi, 1 datoshi = 1e-8 GAS
- engine.AddFee(method.CpuFee * engine.ExecFeeFactor + method.StorageFee * engine.StoragePrice);
+ // In the unit of picoGAS, 1 picoGAS = 1e-12 GAS
+ engine.AddFee(
+ (method.CpuFee * engine.ExecFeePicoFactor) +
+ (method.StorageFee * engine.StoragePrice * ApplicationEngine.FeeFactor));
List