Skip to content

Commit

Permalink
Policy: introduce fee for Conflicts attribute
Browse files Browse the repository at this point in the history
This commit implements Conflicts attribute policy described in
#2907 (comment).

Signed-off-by: Anna Shaleva <[email protected]>
  • Loading branch information
AnnaShaleva committed Sep 18, 2023
1 parent 182549f commit d9e90c6
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 1 deletion.
4 changes: 3 additions & 1 deletion src/Neo/Network/P2P/Payloads/Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Conflicts>().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;
Expand Down
31 changes: 31 additions & 0 deletions src/Neo/SmartContract/Native/PolicyContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public sealed class PolicyContract : NativeContract
/// </summary>
public const uint DefaultStoragePrice = 100000;

/// <summary>
/// The default fee for Conflicts attribute per signer.
/// </summary>
public const uint DefaultConflictsFee = 0;

/// <summary>
/// The default network fee per byte of transactions.
/// </summary>
Expand All @@ -46,10 +51,16 @@ public sealed class PolicyContract : NativeContract
/// </summary>
public const uint MaxStoragePrice = 10000000;

/// <summary>
/// The maximum fee for Conflicts attribute per signer that the committee can set.
/// </summary>
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()
{
Expand All @@ -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;
}

Expand Down Expand Up @@ -96,6 +108,17 @@ public uint GetStoragePrice(DataCache snapshot)
return (uint)(BigInteger)snapshot[CreateStorageKey(Prefix_StoragePrice)];
}

/// <summary>
/// Gets the fee for Conflicts attribute per signer.
/// </summary>
/// <param name="snapshot">The snapshot used to read data.</param>
/// <returns>The fee for Conflicts attribute per signer.</returns>
[ContractMethod(CpuFee = 1 << 15, RequiredCallFlags = CallFlags.ReadStates)]
public uint GetConflictsFee(DataCache snapshot)
{
return (uint)(BigInteger)snapshot[CreateStorageKey(Prefix_ConflictsFee)];
}

/// <summary>
/// Determines whether the specified account is blocked.
/// </summary>
Expand Down Expand Up @@ -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)
{
Expand Down
1 change: 1 addition & 0 deletions src/Neo/Wallets/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ public long CalculateNetworkFee(DataCache snapshot, Transaction tx)
}
}
networkFee += size * NativeContract.Policy.GetFeePerByte(snapshot);
networkFee += tx.GetAttributes<Conflicts>().Count() * tx.Signers.Count() * NativeContract.Policy.GetConflictsFee(snapshot);
return networkFee;
}

Expand Down
61 changes: 61 additions & 0 deletions tests/Neo.UnitTests/SmartContract/Native/UT_PolicyContract.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ 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, "getConflictsFee");
ret.Should().BeOfType<VM.Types.Integer>();
ret.GetInteger().Should().Be(0);
}

[TestMethod]
Expand Down Expand Up @@ -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<InvalidOperationException>(() =>
{
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<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,
"setConflictsFee", new ContractParameter(ContractParameterType.Integer) { Value = 11_0000_0000 });
});

ret = NativeContract.Policy.Call(snapshot, "getConflictsFee");
ret.Should().BeOfType<VM.Types.Integer>();
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<VM.Types.Integer>();
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<VM.Types.Integer>();
ret.GetInteger().Should().Be(0);
}

[TestMethod]
public void Check_BlockAccount()
{
Expand Down

0 comments on commit d9e90c6

Please sign in to comment.