Skip to content

Commit

Permalink
Set attribute fee (#2916)
Browse files Browse the repository at this point in the history
* Set attribute fee

* Update src/Neo/SmartContract/Native/PolicyContract.cs

Co-authored-by: Anna Shaleva <shaleva.ann@nspcc.ru>

* Apply suggestions from code review

Co-authored-by: Anna Shaleva <shaleva.ann@nspcc.ru>

* Fix VerifyStateDependent

---------

Co-authored-by: Anna Shaleva <shaleva.ann@nspcc.ru>
  • Loading branch information
shargon and AnnaShaleva committed Oct 10, 2023
1 parent fdc4389 commit f5e257c
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 13 deletions.
5 changes: 5 additions & 0 deletions src/Neo/Network/P2P/Payloads/Conflicts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}
5 changes: 4 additions & 1 deletion src/Neo/Network/P2P/Payloads/Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions src/Neo/Network/P2P/Payloads/TransactionAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -92,5 +93,7 @@ public void Serialize(BinaryWriter writer)
/// <param name="tx">The <see cref="Transaction"/> that contains the attribute.</param>
/// <returns><see langword="true"/> if the verification passes; otherwise, <see langword="false"/>.</returns>
public virtual bool Verify(DataCache snapshot, Transaction tx) => true;

public virtual long CalculateNetworkFee(DataCache snapshot, Transaction tx) => NativeContract.Policy.GetAttributeFee(snapshot, (byte)Type);
}
}
40 changes: 39 additions & 1 deletion src/Neo/SmartContract/Native/PolicyContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -36,11 +37,21 @@ public sealed class PolicyContract : NativeContract
/// </summary>
public const uint DefaultFeePerByte = 1000;

/// <summary>
/// The default fee for attribute.
/// </summary>
public const uint DefaultAttributeFee = 0;

/// <summary>
/// The maximum execution fee factor that the committee can set.
/// </summary>
public const uint MaxExecFeeFactor = 100;

/// <summary>
/// The maximum fee for attribute that the committee can set.
/// </summary>
public const uint MaxAttributeFee = 10_0000_0000;

/// <summary>
/// The maximum storage price that the committee can set.
/// </summary>
Expand All @@ -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()
{
Expand Down Expand Up @@ -96,6 +108,22 @@ public uint GetStoragePrice(DataCache snapshot)
return (uint)(BigInteger)snapshot[CreateStorageKey(Prefix_StoragePrice)];
}

/// <summary>
/// Gets the fee for attribute.
/// </summary>
/// <param name="snapshot">The snapshot used to read data.</param>
/// <param name="attributeType">Attribute type</param>
/// <returns>The fee for attribute.</returns>
[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;
}

/// <summary>
/// Determines whether the specified account is blocked.
/// </summary>
Expand All @@ -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)
{
Expand Down
18 changes: 11 additions & 7 deletions src/Neo/Wallets/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down
70 changes: 68 additions & 2 deletions tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
using System;
using System.Linq;
using System.Numerics;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Neo.IO;
Expand All @@ -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
{
Expand All @@ -33,6 +34,71 @@ public void Check_Default()
var ret = NativeContract.Policy.Call(snapshot, "getFeePerByte");
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetInteger().Should().Be(1000);

ret = NativeContract.Policy.Call(snapshot, "getAttributeFee", new ContractParameter(ContractParameterType.Integer) { Value = (BigInteger)(byte)TransactionAttributeType.Conflicts });
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetInteger().Should().Be(PolicyContract.DefaultAttributeFee);

Assert.ThrowsException<InvalidOperationException>(() => 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<InvalidOperationException>(() =>
{
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<VM.Types.Integer>();
ret.GetInteger().Should().Be(0);

// With signature, wrong value
UInt160 committeeMultiSigAddr = NativeContract.NEO.GetCommitteeAddress(snapshot);
Assert.ThrowsException<ArgumentOutOfRangeException>(() =>
{
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<VM.Types.Integer>();
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<VM.Types.Integer>();
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<VM.Types.Integer>();
ret.GetInteger().Should().Be(0);
}

[TestMethod]
Expand Down

0 comments on commit f5e257c

Please sign in to comment.