diff --git a/src/Neo/Network/P2P/Payloads/Conflicts.cs b/src/Neo/Network/P2P/Payloads/Conflicts.cs
index db00b03259..f8ef364365 100644
--- a/src/Neo/Network/P2P/Payloads/Conflicts.cs
+++ b/src/Neo/Network/P2P/Payloads/Conflicts.cs
@@ -43,5 +43,10 @@ public override bool Verify(DataCache snapshot, Transaction tx)
// on-chain transaction.
return !NativeContract.Ledger.ContainsTransaction(snapshot, Hash);
}
+
+ public override long CalculateNetworkFee(DataCache snapshot, Transaction tx)
+ {
+ return tx.Signers.Length * base.CalculateNetworkFee(snapshot, tx);
+ }
}
}
diff --git a/src/Neo/Network/P2P/Payloads/Transaction.cs b/src/Neo/Network/P2P/Payloads/Transaction.cs
index 24581e2b18..73ae25bac8 100644
--- a/src/Neo/Network/P2P/Payloads/Transaction.cs
+++ b/src/Neo/Network/P2P/Payloads/Transaction.cs
@@ -365,10 +365,13 @@ public virtual VerifyResult VerifyStateDependent(ProtocolSettings settings, Data
if (NativeContract.Policy.IsBlocked(snapshot, hash))
return VerifyResult.PolicyFail;
if (!(context?.CheckTransaction(this, conflictsList, snapshot) ?? true)) return VerifyResult.InsufficientFunds;
+ long attributesFee = 0;
foreach (TransactionAttribute attribute in Attributes)
if (!attribute.Verify(snapshot, this))
return VerifyResult.InvalidAttribute;
- long net_fee = NetworkFee - Size * NativeContract.Policy.GetFeePerByte(snapshot);
+ else
+ attributesFee += attribute.CalculateNetworkFee(snapshot, this);
+ long net_fee = NetworkFee - (Size * NativeContract.Policy.GetFeePerByte(snapshot)) - attributesFee;
if (net_fee < 0) return VerifyResult.InsufficientFunds;
if (net_fee > MaxVerificationGas) net_fee = MaxVerificationGas;
diff --git a/src/Neo/Network/P2P/Payloads/TransactionAttribute.cs b/src/Neo/Network/P2P/Payloads/TransactionAttribute.cs
index da8620a95c..0da87a94f4 100644
--- a/src/Neo/Network/P2P/Payloads/TransactionAttribute.cs
+++ b/src/Neo/Network/P2P/Payloads/TransactionAttribute.cs
@@ -8,12 +8,13 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.
+using System;
+using System.IO;
using Neo.IO;
using Neo.IO.Caching;
using Neo.Json;
using Neo.Persistence;
-using System;
-using System.IO;
+using Neo.SmartContract.Native;
namespace Neo.Network.P2P.Payloads
{
@@ -92,5 +93,7 @@ public void Serialize(BinaryWriter writer)
/// The that contains the attribute.
/// if the verification passes; otherwise, .
public virtual bool Verify(DataCache snapshot, Transaction tx) => true;
+
+ public virtual long CalculateNetworkFee(DataCache snapshot, Transaction tx) => NativeContract.Policy.GetAttributeFee(snapshot, (byte)Type);
}
}
diff --git a/src/Neo/SmartContract/Native/PolicyContract.cs b/src/Neo/SmartContract/Native/PolicyContract.cs
index 536bc65691..c754a15a93 100644
--- a/src/Neo/SmartContract/Native/PolicyContract.cs
+++ b/src/Neo/SmartContract/Native/PolicyContract.cs
@@ -10,9 +10,10 @@
#pragma warning disable IDE0051
-using Neo.Persistence;
using System;
using System.Numerics;
+using Neo.Network.P2P.Payloads;
+using Neo.Persistence;
namespace Neo.SmartContract.Native
{
@@ -36,11 +37,21 @@ public sealed class PolicyContract : NativeContract
///
public const uint DefaultFeePerByte = 1000;
+ ///
+ /// The default fee for attribute.
+ ///
+ public const uint DefaultAttributeFee = 0;
+
///
/// The maximum execution fee factor that the committee can set.
///
public const uint MaxExecFeeFactor = 100;
+ ///
+ /// The maximum fee for attribute that the committee can set.
+ ///
+ public const uint MaxAttributeFee = 10_0000_0000;
+
///
/// The maximum storage price that the committee can set.
///
@@ -50,6 +61,7 @@ public sealed class PolicyContract : NativeContract
private const byte Prefix_FeePerByte = 10;
private const byte Prefix_ExecFeeFactor = 18;
private const byte Prefix_StoragePrice = 19;
+ private const byte Prefix_AttributeFee = 20;
internal PolicyContract()
{
@@ -96,6 +108,22 @@ public uint GetStoragePrice(DataCache snapshot)
return (uint)(BigInteger)snapshot[CreateStorageKey(Prefix_StoragePrice)];
}
+ ///
+ /// Gets the fee for attribute.
+ ///
+ /// The snapshot used to read data.
+ /// Attribute type
+ /// The fee for attribute.
+ [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)]
+ public uint GetAttributeFee(DataCache snapshot, byte attributeType)
+ {
+ if (!Enum.IsDefined(typeof(TransactionAttributeType), attributeType)) throw new InvalidOperationException();
+ StorageItem entry = snapshot.TryGet(CreateStorageKey(Prefix_AttributeFee).Add(attributeType));
+ if (entry == null) return DefaultAttributeFee;
+
+ return (uint)(BigInteger)entry;
+ }
+
///
/// Determines whether the specified account is blocked.
///
@@ -108,6 +136,16 @@ public bool IsBlocked(DataCache snapshot, UInt160 account)
return snapshot.Contains(CreateStorageKey(Prefix_BlockedAccount).Add(account));
}
+ [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)]
+ private void SetAttributeFee(ApplicationEngine engine, byte attributeType, uint value)
+ {
+ if (!Enum.IsDefined(typeof(TransactionAttributeType), attributeType)) throw new InvalidOperationException();
+ if (value > MaxAttributeFee) throw new ArgumentOutOfRangeException(nameof(value));
+ if (!CheckCommittee(engine)) throw new InvalidOperationException();
+
+ engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_AttributeFee).Add(attributeType), () => new StorageItem(DefaultAttributeFee)).Set(value);
+ }
+
[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)]
private void SetFeePerByte(ApplicationEngine engine, long value)
{
diff --git a/src/Neo/Wallets/Wallet.cs b/src/Neo/Wallets/Wallet.cs
index 7ca5aa8b7b..d908530987 100644
--- a/src/Neo/Wallets/Wallet.cs
+++ b/src/Neo/Wallets/Wallet.cs
@@ -8,6 +8,13 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
using Neo.Cryptography;
using Neo.IO;
using Neo.Network.P2P.Payloads;
@@ -17,13 +24,6 @@
using Neo.VM;
using Neo.Wallets.NEP6;
using Org.BouncyCastle.Crypto.Generators;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Numerics;
-using System.Security.Cryptography;
-using System.Security.Cryptography.X509Certificates;
-using System.Text;
using static Neo.SmartContract.Helper;
using static Neo.Wallets.Helper;
using ECPoint = Neo.Cryptography.ECC.ECPoint;
@@ -661,6 +661,10 @@ public long CalculateNetworkFee(DataCache snapshot, Transaction tx, long maxExec
// 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;
}
diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs
index 6730dd8825..6793c27614 100644
--- a/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs
+++ b/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs
@@ -1,3 +1,6 @@
+using System;
+using System.Linq;
+using System.Numerics;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.IO;
@@ -6,8 +9,6 @@
using Neo.SmartContract;
using Neo.SmartContract.Native;
using Neo.UnitTests.Extensions;
-using System;
-using System.Linq;
namespace Neo.UnitTests.SmartContract.Native
{
@@ -33,6 +34,71 @@ public void Check_Default()
var ret = NativeContract.Policy.Call(snapshot, "getFeePerByte");
ret.Should().BeOfType();
ret.GetInteger().Should().Be(1000);
+
+ ret = NativeContract.Policy.Call(snapshot, "getAttributeFee", new ContractParameter(ContractParameterType.Integer) { Value = (BigInteger)(byte)TransactionAttributeType.Conflicts });
+ ret.Should().BeOfType();
+ ret.GetInteger().Should().Be(PolicyContract.DefaultAttributeFee);
+
+ Assert.ThrowsException(() => NativeContract.Policy.Call(snapshot, "getAttributeFee", new ContractParameter(ContractParameterType.Integer) { Value = (BigInteger)byte.MaxValue }));
+ }
+
+ [TestMethod]
+ public void Check_SetAttributeFee()
+ {
+ var snapshot = _snapshot.CreateSnapshot();
+
+ // Fake blockchain
+ Block block = new()
+ {
+ Header = new Header
+ {
+ Index = 1000,
+ PrevHash = UInt256.Zero
+ }
+ };
+
+ var attr = new ContractParameter(ContractParameterType.Integer) { Value = (BigInteger)(byte)TransactionAttributeType.Conflicts };
+
+ // Without signature
+ Assert.ThrowsException(() =>
+ {
+ NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(), block,
+ "setAttributeFee", attr, new ContractParameter(ContractParameterType.Integer) { Value = 100500 });
+ });
+
+ var ret = NativeContract.Policy.Call(snapshot, "getAttributeFee", attr);
+ ret.Should().BeOfType();
+ ret.GetInteger().Should().Be(0);
+
+ // With signature, wrong value
+ UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot);
+ Assert.ThrowsException(() =>
+ {
+ NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block,
+ "setAttributeFee", attr, new ContractParameter(ContractParameterType.Integer) { Value = 11_0000_0000 });
+ });
+
+ ret = NativeContract.Policy.Call(snapshot, "getAttributeFee", attr);
+ ret.Should().BeOfType();
+ ret.GetInteger().Should().Be(0);
+
+ // Proper set
+ ret = NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block,
+ "setAttributeFee", attr, new ContractParameter(ContractParameterType.Integer) { Value = 300300 });
+ ret.IsNull.Should().BeTrue();
+
+ ret = NativeContract.Policy.Call(snapshot, "getAttributeFee", attr);
+ ret.Should().BeOfType();
+ ret.GetInteger().Should().Be(300300);
+
+ // Set to zero
+ ret = NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block,
+ "setAttributeFee", attr, new ContractParameter(ContractParameterType.Integer) { Value = 0 });
+ ret.IsNull.Should().BeTrue();
+
+ ret = NativeContract.Policy.Call(snapshot, "getAttributeFee", attr);
+ ret.Should().BeOfType();
+ ret.GetInteger().Should().Be(0);
}
[TestMethod]