From d9e90c68acc166f0bafc843df0bbc3be0d348a79 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Mon, 18 Sep 2023 20:31:56 +0300 Subject: [PATCH] Policy: introduce fee for Conflicts attribute This commit implements Conflicts attribute policy described in https://github.com/neo-project/neo/issues/2907#issuecomment-1722506014. Signed-off-by: Anna Shaleva --- src/Neo/Network/P2P/Payloads/Transaction.cs | 4 +- .../SmartContract/Native/PolicyContract.cs | 31 ++++++++++ src/Neo/Wallets/Wallet.cs | 1 + .../SmartContract/Native/UT_PolicyContract.cs | 61 +++++++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/Neo/Network/P2P/Payloads/Transaction.cs b/src/Neo/Network/P2P/Payloads/Transaction.cs index 24581e2b18d..574f8954d74 100644 --- a/src/Neo/Network/P2P/Payloads/Transaction.cs +++ b/src/Neo/Network/P2P/Payloads/Transaction.cs @@ -368,7 +368,9 @@ public virtual VerifyResult VerifyStateDependent(ProtocolSettings settings, Data foreach (TransactionAttribute attribute in Attributes) if (!attribute.Verify(snapshot, this)) return VerifyResult.InvalidAttribute; - long net_fee = NetworkFee - Size * NativeContract.Policy.GetFeePerByte(snapshot); + + long conflictsFee = GetAttributes().Count() * Signers.Count() * NativeContract.Policy.GetConflictsFee(snapshot); + long net_fee = NetworkFee - Size * NativeContract.Policy.GetFeePerByte(snapshot) - conflictsFee; if (net_fee < 0) return VerifyResult.InsufficientFunds; if (net_fee > MaxVerificationGas) net_fee = MaxVerificationGas; diff --git a/src/Neo/SmartContract/Native/PolicyContract.cs b/src/Neo/SmartContract/Native/PolicyContract.cs index 536bc65691e..f565cc9fbc7 100644 --- a/src/Neo/SmartContract/Native/PolicyContract.cs +++ b/src/Neo/SmartContract/Native/PolicyContract.cs @@ -31,6 +31,11 @@ public sealed class PolicyContract : NativeContract /// public const uint DefaultStoragePrice = 100000; + /// + /// The default fee for Conflicts attribute per signer. + /// + public const uint DefaultConflictsFee = 0; + /// /// The default network fee per byte of transactions. /// @@ -46,10 +51,16 @@ public sealed class PolicyContract : NativeContract /// public const uint MaxStoragePrice = 10000000; + /// + /// The maximum fee for Conflicts attribute per signer that the committee can set. + /// + public const uint MaxConflictsFee = 10_0000_0000; + private const byte Prefix_BlockedAccount = 15; private const byte Prefix_FeePerByte = 10; private const byte Prefix_ExecFeeFactor = 18; private const byte Prefix_StoragePrice = 19; + private const byte Prefix_ConflictsFee = 20; internal PolicyContract() { @@ -60,6 +71,7 @@ internal override ContractTask Initialize(ApplicationEngine engine) engine.Snapshot.Add(CreateStorageKey(Prefix_FeePerByte), new StorageItem(DefaultFeePerByte)); engine.Snapshot.Add(CreateStorageKey(Prefix_ExecFeeFactor), new StorageItem(DefaultExecFeeFactor)); engine.Snapshot.Add(CreateStorageKey(Prefix_StoragePrice), new StorageItem(DefaultStoragePrice)); + engine.Snapshot.Add(CreateStorageKey(Prefix_ConflictsFee), new StorageItem(DefaultConflictsFee)); return ContractTask.CompletedTask; } @@ -96,6 +108,17 @@ public uint GetStoragePrice(DataCache snapshot) return (uint)(BigInteger)snapshot[CreateStorageKey(Prefix_StoragePrice)]; } + /// + /// Gets the fee for Conflicts attribute per signer. + /// + /// The snapshot used to read data. + /// The fee for Conflicts attribute per signer. + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)] + public uint GetConflictsFee(DataCache snapshot) + { + return (uint)(BigInteger)snapshot[CreateStorageKey(Prefix_ConflictsFee)]; + } + /// /// Determines whether the specified account is blocked. /// @@ -132,6 +155,14 @@ private void SetStoragePrice(ApplicationEngine engine, uint value) engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_StoragePrice)).Set(value); } + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] + private void SetConflictsFee(ApplicationEngine engine, uint value) + { + if (value > MaxConflictsFee) throw new ArgumentOutOfRangeException(nameof(value)); + if (!CheckCommittee(engine)) throw new InvalidOperationException(); + engine.Snapshot.GetAndChange(CreateStorageKey(Prefix_ConflictsFee)).Set(value); + } + [ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.States)] private bool BlockAccount(ApplicationEngine engine, UInt160 account) { diff --git a/src/Neo/Wallets/Wallet.cs b/src/Neo/Wallets/Wallet.cs index 60a3b608912..0a5a0dfc551 100644 --- a/src/Neo/Wallets/Wallet.cs +++ b/src/Neo/Wallets/Wallet.cs @@ -661,6 +661,7 @@ public long CalculateNetworkFee(DataCache snapshot, Transaction tx) } } networkFee += size * NativeContract.Policy.GetFeePerByte(snapshot); + networkFee += tx.GetAttributes().Count() * tx.Signers.Count() * NativeContract.Policy.GetConflictsFee(snapshot); return networkFee; } diff --git a/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs b/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs index 6730dd88255..64a44ae7be3 100644 --- a/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs +++ b/tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs @@ -33,6 +33,10 @@ public void Check_Default() var ret = NativeContract.Policy.Call(snapshot, "getFeePerByte"); ret.Should().BeOfType(); ret.GetInteger().Should().Be(1000); + + ret = NativeContract.Policy.Call(snapshot, "getConflictsFee"); + ret.Should().BeOfType(); + ret.GetInteger().Should().Be(0); } [TestMethod] @@ -174,6 +178,63 @@ public void Check_SetStoragePrice() ret.GetInteger().Should().Be(300300); } + [TestMethod] + public void Check_SetConflictsFee() + { + var snapshot = _snapshot.CreateSnapshot(); + + // Fake blockchain + Block block = new() + { + Header = new Header + { + Index = 1000, + PrevHash = UInt256.Zero + } + }; + + // Without signature + Assert.ThrowsException(() => + { + NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(), block, + "setConflictsFee", new ContractParameter(ContractParameterType.Integer) { Value = 100500 }); + }); + + var ret = NativeContract.Policy.Call(snapshot, "getConflictsFee"); + 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, + "setConflictsFee", new ContractParameter(ContractParameterType.Integer) { Value = 11_0000_0000 }); + }); + + ret = NativeContract.Policy.Call(snapshot, "getConflictsFee"); + ret.Should().BeOfType(); + ret.GetInteger().Should().Be(0); + + // Proper set + ret = NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block, + "setConflictsFee", new ContractParameter(ContractParameterType.Integer) { Value = 300300 }); + ret.IsNull.Should().BeTrue(); + + ret = NativeContract.Policy.Call(snapshot, "getConflictsFee"); + ret.Should().BeOfType(); + ret.GetInteger().Should().Be(300300); + + // Set to zero + ret = NativeContract.Policy.Call(snapshot, new Nep17NativeContractExtensions.ManualWitness(committeeMultiSigAddr), block, + "setConflictsFee", new ContractParameter(ContractParameterType.Integer) { Value = 0 }); + ret.IsNull.Should().BeTrue(); + + ret = NativeContract.Policy.Call(snapshot, "getConflictsFee"); + ret.Should().BeOfType(); + ret.GetInteger().Should().Be(0); + } + [TestMethod] public void Check_BlockAccount() {