From 73cea0173d14b8c29dfb6f7d0b0792aff4c75d5b Mon Sep 17 00:00:00 2001 From: Christopher Schuchardt Date: Thu, 26 Sep 2024 22:48:11 -0400 Subject: [PATCH] [`Add`] Transaction Builder (#3477) * Added Builders with tests * Added SignerBuilder and started WitnessRuleBuilder * Added `WitnessConditionBuilder` with tests * Added more logic * Fixed `SignerBuilder` class * Code touch ups * Added more tests * Update src/Neo/Builders/TransactionBuilder.cs * Fixed `And` `Or` and `Not` conditions * Fixed Memory leak * Added error message for Witness scripts --------- Co-authored-by: Jimmy Co-authored-by: Shargon Co-authored-by: NGD Admin <154295625+NGDAdmin@users.noreply.github.com> --- src/Neo/Builders/AndConditionBuilder.cs | 90 +++++++ src/Neo/Builders/OrConditionBuilder.cs | 90 +++++++ src/Neo/Builders/SignerBuilder.cs | 73 ++++++ .../Builders/TransactionAttributesBuilder.cs | 74 ++++++ src/Neo/Builders/TransactionBuilder.cs | 116 +++++++++ src/Neo/Builders/WitnessBuilder.cs | 79 +++++++ src/Neo/Builders/WitnessConditionBuilder.cs | 131 +++++++++++ src/Neo/Builders/WitnessRuleBuilder.cs | 44 ++++ .../Builders/UT_SignerBuilder.cs | 101 ++++++++ .../UT_TransactionAttributesBuilder.cs | 92 ++++++++ .../Builders/UT_TransactionBuilder.cs | 201 ++++++++++++++++ .../Builders/UT_WitnessBuilder.cs | 91 ++++++++ .../Builders/UT_WitnessConditionBuilder.cs | 220 ++++++++++++++++++ .../Builders/UT_WitnessRuleBuilder.cs | 69 ++++++ 14 files changed, 1471 insertions(+) create mode 100644 src/Neo/Builders/AndConditionBuilder.cs create mode 100644 src/Neo/Builders/OrConditionBuilder.cs create mode 100644 src/Neo/Builders/SignerBuilder.cs create mode 100644 src/Neo/Builders/TransactionAttributesBuilder.cs create mode 100644 src/Neo/Builders/TransactionBuilder.cs create mode 100644 src/Neo/Builders/WitnessBuilder.cs create mode 100644 src/Neo/Builders/WitnessConditionBuilder.cs create mode 100644 src/Neo/Builders/WitnessRuleBuilder.cs create mode 100644 tests/Neo.UnitTests/Builders/UT_SignerBuilder.cs create mode 100644 tests/Neo.UnitTests/Builders/UT_TransactionAttributesBuilder.cs create mode 100644 tests/Neo.UnitTests/Builders/UT_TransactionBuilder.cs create mode 100644 tests/Neo.UnitTests/Builders/UT_WitnessBuilder.cs create mode 100644 tests/Neo.UnitTests/Builders/UT_WitnessConditionBuilder.cs create mode 100644 tests/Neo.UnitTests/Builders/UT_WitnessRuleBuilder.cs diff --git a/src/Neo/Builders/AndConditionBuilder.cs b/src/Neo/Builders/AndConditionBuilder.cs new file mode 100644 index 0000000000..a75a52227a --- /dev/null +++ b/src/Neo/Builders/AndConditionBuilder.cs @@ -0,0 +1,90 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// AndConditionBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads.Conditions; +using System; + +namespace Neo.Builders +{ + public sealed class AndConditionBuilder + { + private readonly AndCondition _condition = new() { Expressions = [] }; + + private AndConditionBuilder() { } + + public static AndConditionBuilder CreateEmpty() + { + return new AndConditionBuilder(); + } + + public AndConditionBuilder And(Action config) + { + var acb = new AndConditionBuilder(); + config(acb); + + _condition.Expressions = [.. _condition.Expressions, acb.Build()]; + + return this; + } + + public AndConditionBuilder Or(Action config) + { + var ocb = OrConditionBuilder.CreateEmpty(); + config(ocb); + + _condition.Expressions = [.. _condition.Expressions, ocb.Build()]; + + return this; + } + + public AndConditionBuilder Boolean(bool expression) + { + _condition.Expressions = [.. _condition.Expressions, new BooleanCondition { Expression = expression }]; + return this; + } + + public AndConditionBuilder CalledByContract(UInt160 hash) + { + _condition.Expressions = [.. _condition.Expressions, new CalledByContractCondition { Hash = hash }]; + return this; + } + + public AndConditionBuilder CalledByEntry() + { + _condition.Expressions = [.. _condition.Expressions, new CalledByEntryCondition()]; + return this; + } + + public AndConditionBuilder CalledByGroup(ECPoint publicKey) + { + _condition.Expressions = [.. _condition.Expressions, new CalledByGroupCondition { Group = publicKey }]; + return this; + } + + public AndConditionBuilder Group(ECPoint publicKey) + { + _condition.Expressions = [.. _condition.Expressions, new GroupCondition() { Group = publicKey }]; + return this; + } + + public AndConditionBuilder ScriptHash(UInt160 scriptHash) + { + _condition.Expressions = [.. _condition.Expressions, new ScriptHashCondition() { Hash = scriptHash }]; + return this; + } + + public AndCondition Build() + { + return _condition; + } + } +} diff --git a/src/Neo/Builders/OrConditionBuilder.cs b/src/Neo/Builders/OrConditionBuilder.cs new file mode 100644 index 0000000000..6b8ca2a0e1 --- /dev/null +++ b/src/Neo/Builders/OrConditionBuilder.cs @@ -0,0 +1,90 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// OrConditionBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads.Conditions; +using System; + +namespace Neo.Builders +{ + public sealed class OrConditionBuilder + { + private readonly OrCondition _condition = new() { Expressions = [] }; + + private OrConditionBuilder() { } + + public static OrConditionBuilder CreateEmpty() + { + return new OrConditionBuilder(); + } + + public OrConditionBuilder And(Action config) + { + var acb = AndConditionBuilder.CreateEmpty(); + config(acb); + + _condition.Expressions = [.. _condition.Expressions, acb.Build()]; + + return this; + } + + public OrConditionBuilder Or(Action config) + { + var acb = new OrConditionBuilder(); + config(acb); + + _condition.Expressions = [.. _condition.Expressions, acb.Build()]; + + return this; + } + + public OrConditionBuilder Boolean(bool expression) + { + _condition.Expressions = [.. _condition.Expressions, new BooleanCondition { Expression = expression }]; + return this; + } + + public OrConditionBuilder CalledByContract(UInt160 hash) + { + _condition.Expressions = [.. _condition.Expressions, new CalledByContractCondition { Hash = hash }]; + return this; + } + + public OrConditionBuilder CalledByEntry() + { + _condition.Expressions = [.. _condition.Expressions, new CalledByEntryCondition()]; + return this; + } + + public OrConditionBuilder CalledByGroup(ECPoint publicKey) + { + _condition.Expressions = [.. _condition.Expressions, new CalledByGroupCondition { Group = publicKey }]; + return this; + } + + public OrConditionBuilder Group(ECPoint publicKey) + { + _condition.Expressions = [.. _condition.Expressions, new GroupCondition() { Group = publicKey }]; + return this; + } + + public OrConditionBuilder ScriptHash(UInt160 scriptHash) + { + _condition.Expressions = [.. _condition.Expressions, new ScriptHashCondition() { Hash = scriptHash }]; + return this; + } + + public OrCondition Build() + { + return _condition; + } + } +} diff --git a/src/Neo/Builders/SignerBuilder.cs b/src/Neo/Builders/SignerBuilder.cs new file mode 100644 index 0000000000..76b70f1efe --- /dev/null +++ b/src/Neo/Builders/SignerBuilder.cs @@ -0,0 +1,73 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// SignerBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using System; + +namespace Neo.Builders +{ + public sealed class SignerBuilder + { + private readonly Signer _signer = new Signer() + { + Account = UInt160.Zero, + AllowedContracts = [], + AllowedGroups = [], + Rules = [], + Scopes = WitnessScope.None, + }; + + private SignerBuilder() { } + + public static SignerBuilder CreateEmpty() + { + return new SignerBuilder(); + } + + public SignerBuilder Account(UInt160 scriptHash) + { + _signer.Account = scriptHash; + return this; + } + + public SignerBuilder AllowContract(UInt160 contractHash) + { + _signer.AllowedContracts = [.. _signer.AllowedContracts, contractHash]; + return this; + } + + public SignerBuilder AllowGroup(ECPoint publicKey) + { + _signer.AllowedGroups = [.. _signer.AllowedGroups, publicKey]; + return this; + } + + public SignerBuilder AddWitnessScope(WitnessScope scope) + { + _signer.Scopes |= scope; + return this; + } + + public SignerBuilder AddWitnessRule(WitnessRuleAction action, Action config) + { + var rb = WitnessRuleBuilder.Create(action); + config(rb); + _signer.Rules = [.. _signer.Rules, rb.Build()]; + return this; + } + + public Signer Build() + { + return _signer; + } + } +} diff --git a/src/Neo/Builders/TransactionAttributesBuilder.cs b/src/Neo/Builders/TransactionAttributesBuilder.cs new file mode 100644 index 0000000000..9eb4ae99e3 --- /dev/null +++ b/src/Neo/Builders/TransactionAttributesBuilder.cs @@ -0,0 +1,74 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionAttributesBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using System; +using System.Linq; + +namespace Neo.Builders +{ + public sealed class TransactionAttributesBuilder + { + private TransactionAttribute[] _attributes = []; + + private TransactionAttributesBuilder() { } + + public static TransactionAttributesBuilder CreateEmpty() + { + return new TransactionAttributesBuilder(); + } + + public TransactionAttributesBuilder AddConflict(Action config) + { + var conflicts = new Conflicts(); + config(conflicts); + _attributes = [.. _attributes, conflicts]; + return this; + } + + public TransactionAttributesBuilder AddOracleResponse(Action config) + { + var oracleResponse = new OracleResponse(); + config(oracleResponse); + _attributes = [.. _attributes, oracleResponse]; + return this; + } + + public TransactionAttributesBuilder AddHighPriority() + { + if (_attributes.Any(a => a is HighPriorityAttribute)) + throw new InvalidOperationException("HighPriority already exists in the attributes."); + + var highPriority = new HighPriorityAttribute(); + _attributes = [.. _attributes, highPriority]; + return this; + } + + public TransactionAttributesBuilder AddNotValidBefore(uint block) + { + if (_attributes.Any(a => a is NotValidBefore b && b.Height == block)) + throw new InvalidOperationException($"Block {block} already exists in the attributes."); + + var validUntilBlock = new NotValidBefore() + { + Height = block + }; + + _attributes = [.. _attributes, validUntilBlock]; + return this; + } + + public TransactionAttribute[] Build() + { + return _attributes; + } + } +} diff --git a/src/Neo/Builders/TransactionBuilder.cs b/src/Neo/Builders/TransactionBuilder.cs new file mode 100644 index 0000000000..2a1a7a9424 --- /dev/null +++ b/src/Neo/Builders/TransactionBuilder.cs @@ -0,0 +1,116 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// TransactionBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using Neo.VM; +using System; + +namespace Neo.Builders +{ + public sealed class TransactionBuilder + { + private readonly Transaction _tx = new() + { + Script = new[] { (byte)OpCode.RET }, + Attributes = [], + Signers = [], + Witnesses = [], + }; + + private TransactionBuilder() { } + + public static TransactionBuilder CreateEmpty() + { + return new TransactionBuilder(); + } + + public TransactionBuilder Version(byte version) + { + _tx.Version = version; + return this; + } + + public TransactionBuilder Nonce(uint nonce) + { + _tx.Nonce = nonce; + return this; + } + + public TransactionBuilder SystemFee(uint systemFee) + { + _tx.SystemFee = systemFee; + return this; + } + + public TransactionBuilder NetworkFee(uint networkFee) + { + _tx.NetworkFee = networkFee; + return this; + } + + public TransactionBuilder ValidUntil(uint blockIndex) + { + _tx.ValidUntilBlock = blockIndex; + return this; + } + + public TransactionBuilder AttachSystem(Action config) + { + using var sb = new ScriptBuilder(); + config(sb); + _tx.Script = sb.ToArray(); + return this; + } + + public TransactionBuilder AttachSystem(byte[] script) + { + _tx.Script = script; + return this; + } + + public TransactionBuilder AddAttributes(Action config) + { + var ab = TransactionAttributesBuilder.CreateEmpty(); + config(ab); + _tx.Attributes = ab.Build(); + return this; + } + + public TransactionBuilder AddWitness(Action config) + { + var wb = WitnessBuilder.CreateEmpty(); + config(wb); + _tx.Witnesses = [.. _tx.Witnesses, wb.Build()]; + return this; + } + + public TransactionBuilder AddWitness(Action config) + { + var wb = WitnessBuilder.CreateEmpty(); + config(wb, _tx); + _tx.Witnesses = [.. _tx.Witnesses, wb.Build()]; + return this; + } + + public TransactionBuilder AddSigner(Action config) + { + var wb = SignerBuilder.CreateEmpty(); + config(wb, _tx); + _tx.Signers = [.. _tx.Signers, wb.Build()]; + return this; + } + + public Transaction Build() + { + return _tx; + } + } +} diff --git a/src/Neo/Builders/WitnessBuilder.cs b/src/Neo/Builders/WitnessBuilder.cs new file mode 100644 index 0000000000..e555195688 --- /dev/null +++ b/src/Neo/Builders/WitnessBuilder.cs @@ -0,0 +1,79 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using Neo.VM; +using System; + +namespace Neo.Builders +{ + public sealed class WitnessBuilder + { + private byte[] _invocationScript = []; + private byte[] _verificationScript = []; + + private WitnessBuilder() { } + + public static WitnessBuilder CreateEmpty() + { + return new WitnessBuilder(); + } + + public WitnessBuilder AddInvocation(Action config) + { + if (_invocationScript.Length > 0) + throw new InvalidOperationException("Invocation script already exists."); + + using var sb = new ScriptBuilder(); + config(sb); + _invocationScript = sb.ToArray(); + return this; + } + + public WitnessBuilder AddInvocation(byte[] bytes) + { + if (_invocationScript.Length > 0) + throw new InvalidOperationException("Invocation script already exists."); + + _invocationScript = bytes; + return this; + } + + public WitnessBuilder AddVerification(Action config) + { + if (_verificationScript.Length > 0) + throw new InvalidOperationException("Verification script already exists."); + + using var sb = new ScriptBuilder(); + config(sb); + _verificationScript = sb.ToArray(); + return this; + } + + public WitnessBuilder AddVerification(byte[] bytes) + { + if (_verificationScript.Length > 0) + throw new InvalidOperationException("Verification script already exists."); + + _verificationScript = bytes; + return this; + } + + public Witness Build() + { + return new Witness() + { + InvocationScript = _invocationScript, + VerificationScript = _verificationScript, + }; + } + } +} diff --git a/src/Neo/Builders/WitnessConditionBuilder.cs b/src/Neo/Builders/WitnessConditionBuilder.cs new file mode 100644 index 0000000000..41cfc05163 --- /dev/null +++ b/src/Neo/Builders/WitnessConditionBuilder.cs @@ -0,0 +1,131 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessConditionBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads.Conditions; +using System; + +namespace Neo.Builders +{ + public sealed class WitnessConditionBuilder + { + WitnessCondition _condition; + + private WitnessConditionBuilder() { } + + private WitnessConditionBuilder(WitnessCondition condition) + { + _condition = condition; + } + + public static WitnessConditionBuilder Create() + { + return new WitnessConditionBuilder(); + } + + public WitnessConditionBuilder And(Action config) + { + var acb = AndConditionBuilder.CreateEmpty(); + config(acb); + + _condition = acb.Build(); + + return this; + } + + public WitnessConditionBuilder Boolean(bool expression) + { + var condition = new BooleanCondition() { Expression = expression }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder CalledByContract(UInt160 hash) + { + var condition = new CalledByContractCondition() { Hash = hash }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder CalledByEntry() + { + var condition = new CalledByEntryCondition(); + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder CalledByGroup(ECPoint publicKey) + { + var condition = new CalledByGroupCondition() { Group = publicKey }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder Group(ECPoint publicKey) + { + var condition = new GroupCondition() { Group = publicKey }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder Not(Action config) + { + var wcb = new WitnessConditionBuilder(); + config(wcb); + + var condition = new NotCondition() + { + Expression = wcb.Build() + }; + + _condition = condition; + + return this; + } + + public WitnessConditionBuilder Or(Action config) + { + var ocb = OrConditionBuilder.CreateEmpty(); + config(ocb); + + _condition = ocb.Build(); + + return this; + } + + public WitnessConditionBuilder ScriptHash(UInt160 scriptHash) + { + var condition = new ScriptHashCondition() { Hash = scriptHash }; + + _condition = condition; + + return this; + } + + public WitnessCondition Build() + { + if (_condition is null) + return new BooleanCondition() { Expression = true }; + + return _condition; + } + } +} diff --git a/src/Neo/Builders/WitnessRuleBuilder.cs b/src/Neo/Builders/WitnessRuleBuilder.cs new file mode 100644 index 0000000000..7c05a524d6 --- /dev/null +++ b/src/Neo/Builders/WitnessRuleBuilder.cs @@ -0,0 +1,44 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// WitnessRuleBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Neo.Network.P2P.Payloads; +using System; + +namespace Neo.Builders +{ + public sealed class WitnessRuleBuilder + { + private readonly WitnessRule _rule = new(); + + private WitnessRuleBuilder(WitnessRuleAction action) + { + _rule.Action = action; + } + + public static WitnessRuleBuilder Create(WitnessRuleAction action) + { + return new WitnessRuleBuilder(action); + } + + public WitnessRuleBuilder AddCondition(Action config) + { + var cb = WitnessConditionBuilder.Create(); + config(cb); + _rule.Condition = cb.Build(); + return this; + } + + public WitnessRule Build() + { + return _rule; + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_SignerBuilder.cs b/tests/Neo.UnitTests/Builders/UT_SignerBuilder.cs new file mode 100644 index 0000000000..7d79f7428c --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_SignerBuilder.cs @@ -0,0 +1,101 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_SignerBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Builders; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads.Conditions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_SignerBuilder + { + [TestMethod] + public void TestCreateEmpty() + { + var sb = SignerBuilder.CreateEmpty(); + Assert.IsNotNull(sb); + } + + [TestMethod] + public void TestAccount() + { + var signer = SignerBuilder.CreateEmpty() + .Account(UInt160.Zero) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(UInt160.Zero, signer.Account); + } + + [TestMethod] + public void TestAllowContract() + { + var signer = SignerBuilder.CreateEmpty() + .AllowContract(UInt160.Zero) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(1, signer.AllowedContracts.Length); + Assert.AreEqual(UInt160.Zero, signer.AllowedContracts[0]); + } + + [TestMethod] + public void TestAllowGroup() + { + var myPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var signer = SignerBuilder.CreateEmpty() + .AllowGroup(myPublicKey) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(1, signer.AllowedGroups.Length); + Assert.AreEqual(myPublicKey, signer.AllowedGroups[0]); + } + + [TestMethod] + public void TestAddWitnessScope() + { + var signer = SignerBuilder.CreateEmpty() + .AddWitnessScope(WitnessScope.Global) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(WitnessScope.Global, signer.Scopes); + } + + [TestMethod] + public void TestAddWitnessRule() + { + var signer = SignerBuilder.CreateEmpty() + .AddWitnessRule(WitnessRuleAction.Allow, rb => + { + rb.AddCondition(cb => + { + cb.ScriptHash(UInt160.Zero); + }); + }) + .Build(); + + Assert.IsNotNull(signer); + Assert.AreEqual(1, signer.Rules.Length); + Assert.AreEqual(WitnessRuleAction.Allow, signer.Rules[0].Action); + Assert.IsInstanceOfType(signer.Rules[0].Condition); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_TransactionAttributesBuilder.cs b/tests/Neo.UnitTests/Builders/UT_TransactionAttributesBuilder.cs new file mode 100644 index 0000000000..d9ade210ea --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_TransactionAttributesBuilder.cs @@ -0,0 +1,92 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_TransactionAttributesBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Builders; +using Neo.Network.P2P.Payloads; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_TransactionAttributesBuilder + { + [TestMethod] + public void TestCreateEmpty() + { + var builder = TransactionAttributesBuilder.CreateEmpty(); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void TestConflict() + { + var attr = TransactionAttributesBuilder.CreateEmpty() + .AddConflict(c => c.Hash = UInt256.Zero) + .Build(); + + Assert.IsNotNull(attr); + Assert.AreEqual(1, attr.Length); + Assert.IsInstanceOfType(attr[0]); + Assert.AreEqual(UInt256.Zero, ((Conflicts)attr[0]).Hash); + } + + [TestMethod] + public void TestOracleResponse() + { + var attr = TransactionAttributesBuilder.CreateEmpty() + .AddOracleResponse(c => + { + c.Id = 1ul; + c.Code = OracleResponseCode.Success; + c.Result = new byte[] { 0x01, 0x02, 0x03 }; + }) + .Build(); + + Assert.IsNotNull(attr); + Assert.AreEqual(1, attr.Length); + Assert.IsInstanceOfType(attr[0]); + Assert.AreEqual(1ul, ((OracleResponse)attr[0]).Id); + Assert.AreEqual(OracleResponseCode.Success, ((OracleResponse)attr[0]).Code); + CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03 }, ((OracleResponse)attr[0]).Result.ToArray()); + } + + [TestMethod] + public void TestHighPriority() + { + var attr = TransactionAttributesBuilder.CreateEmpty() + .AddHighPriority() + .Build(); + + Assert.IsNotNull(attr); + Assert.AreEqual(1, attr.Length); + Assert.IsInstanceOfType(attr[0]); + } + + [TestMethod] + public void TestNotValidBefore() + { + var attr = TransactionAttributesBuilder.CreateEmpty() + .AddNotValidBefore(10u) + .Build(); + + Assert.IsNotNull(attr); + Assert.AreEqual(1, attr.Length); + Assert.IsInstanceOfType(attr[0]); + Assert.AreEqual(10u, ((NotValidBefore)attr[0]).Height); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_TransactionBuilder.cs b/tests/Neo.UnitTests/Builders/UT_TransactionBuilder.cs new file mode 100644 index 0000000000..17eb5796ad --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_TransactionBuilder.cs @@ -0,0 +1,201 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_TransactionBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Builders; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads.Conditions; +using Neo.SmartContract; +using Neo.SmartContract.Native; +using Neo.VM; +using Neo.Wallets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_TransactionBuilder + { + [TestMethod] + public void TestCreateEmpty() + { + var builder = TransactionBuilder.CreateEmpty(); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void TestEmptyTransaction() + { + var tx = TransactionBuilder.CreateEmpty() + .Build(); + + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestVersion() + { + byte expectedVersion = 1; + var tx = TransactionBuilder.CreateEmpty() + .Version(expectedVersion) + .Build(); + + Assert.AreEqual(expectedVersion, tx.Version); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestNonce() + { + var expectedNonce = (uint)Random.Shared.Next(); + var tx = TransactionBuilder.CreateEmpty() + .Nonce(expectedNonce) + .Build(); + + Assert.AreEqual(expectedNonce, tx.Nonce); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestSystemFee() + { + var expectedSystemFee = (uint)Random.Shared.Next(); + var tx = TransactionBuilder.CreateEmpty() + .SystemFee(expectedSystemFee) + .Build(); + + Assert.AreEqual(expectedSystemFee, tx.SystemFee); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestNetworkFee() + { + var expectedNetworkFee = (uint)Random.Shared.Next(); + var tx = TransactionBuilder.CreateEmpty() + .NetworkFee(expectedNetworkFee) + .Build(); + + Assert.AreEqual(expectedNetworkFee, tx.NetworkFee); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestValidUntilBlock() + { + var expectedValidUntilBlock = (uint)Random.Shared.Next(); + var tx = TransactionBuilder.CreateEmpty() + .ValidUntil(expectedValidUntilBlock) + .Build(); + + Assert.AreEqual(expectedValidUntilBlock, tx.ValidUntilBlock); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestAttachScript() + { + byte[] expectedScript = [(byte)OpCode.NOP]; + var tx = TransactionBuilder.CreateEmpty() + .AttachSystem(sb => sb.Emit(OpCode.NOP)) + .Build(); + + CollectionAssert.AreEqual(expectedScript, tx.Script.ToArray()); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestTransactionAttributes() + { + var tx = TransactionBuilder.CreateEmpty() + .AddAttributes(ab => ab.AddHighPriority()) + .Build(); + + Assert.AreEqual(1, tx.Attributes.Length); + Assert.IsInstanceOfType(tx.Attributes[0]); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestWitness() + { + var tx = TransactionBuilder.CreateEmpty() + .AddWitness(wb => + { + // Contract signature + wb.AddInvocation([]); + wb.AddVerification([]); + }) + .Build(); + + Assert.AreEqual(1, tx.Witnesses.Length); + Assert.AreEqual(0, tx.Witnesses[0].InvocationScript.Length); + Assert.AreEqual(0, tx.Witnesses[0].VerificationScript.Length); + Assert.IsNotNull(tx.Hash); + } + + [TestMethod] + public void TestWitnessWithTransactionParameter() + { + var tx = TransactionBuilder.CreateEmpty() + .AddWitness((wb, tx) => + { + // Checks to make sure the transaction is hash able + // NOTE: transaction can be used for signing here + Assert.IsNotNull(tx.Hash); + }) + .Build(); + } + + [TestMethod] + public void TestSigner() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + + var tx = TransactionBuilder.CreateEmpty() + .AddSigner((sb, tx) => + { + sb.Account(expectedContractHash); + sb.AllowContract(expectedContractHash); + sb.AllowGroup(expectedPublicKey); + sb.AddWitnessScope(WitnessScope.WitnessRules); + sb.AddWitnessRule(WitnessRuleAction.Deny, wrb => + { + wrb.AddCondition(cb => + { + cb.ScriptHash(expectedContractHash); + }); + }); + }) + .Build(); + + Assert.IsNotNull(tx.Hash); + Assert.AreEqual(1, tx.Signers.Length); + Assert.AreEqual(expectedContractHash, tx.Signers[0].Account); + Assert.AreEqual(1, tx.Signers[0].AllowedContracts.Length); + Assert.AreEqual(expectedContractHash, tx.Signers[0].AllowedContracts[0]); + Assert.AreEqual(1, tx.Signers[0].AllowedGroups.Length); + Assert.AreEqual(expectedPublicKey, tx.Signers[0].AllowedGroups[0]); + Assert.AreEqual(WitnessScope.WitnessRules, tx.Signers[0].Scopes); + Assert.AreEqual(1, tx.Signers[0].Rules.Length); + Assert.AreEqual(WitnessRuleAction.Deny, tx.Signers[0].Rules[0].Action); + Assert.IsNotNull(tx.Signers[0].Rules[0].Condition); + Assert.IsInstanceOfType(tx.Signers[0].Rules[0].Condition); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_WitnessBuilder.cs b/tests/Neo.UnitTests/Builders/UT_WitnessBuilder.cs new file mode 100644 index 0000000000..1b73a69124 --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_WitnessBuilder.cs @@ -0,0 +1,91 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_WitnessBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Builders; +using System; +using System.Buffers; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_WitnessBuilder + { + [TestMethod] + public void TestCreateEmpty() + { + var wb = WitnessBuilder.CreateEmpty(); + Assert.IsNotNull(wb); + } + + [TestMethod] + public void TestAddInvocationWithScriptBuilder() + { + var witness = WitnessBuilder.CreateEmpty() + .AddInvocation(sb => + { + sb.Emit(VM.OpCode.NOP); + sb.Emit(VM.OpCode.NOP); + sb.Emit(VM.OpCode.NOP); + }) + .Build(); + + Assert.IsNotNull(witness); + Assert.AreEqual(3, witness.InvocationScript.Length); + CollectionAssert.AreEqual(new byte[] { 0x21, 0x21, 0x21 }, witness.InvocationScript.ToArray()); + } + + [TestMethod] + public void TestAddInvocation() + { + var witness = WitnessBuilder.CreateEmpty() + .AddInvocation(new byte[] { 0x01, 0x02, 0x03 }) + .Build(); + + Assert.IsNotNull(witness); + Assert.AreEqual(3, witness.InvocationScript.Length); + CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03 }, witness.InvocationScript.ToArray()); + } + + [TestMethod] + public void TestAddVerificationWithScriptBuilder() + { + var witness = WitnessBuilder.CreateEmpty() + .AddVerification(sb => + { + sb.Emit(VM.OpCode.NOP); + sb.Emit(VM.OpCode.NOP); + sb.Emit(VM.OpCode.NOP); + }) + .Build(); + + Assert.IsNotNull(witness); + Assert.AreEqual(3, witness.VerificationScript.Length); + CollectionAssert.AreEqual(new byte[] { 0x21, 0x21, 0x21 }, witness.VerificationScript.ToArray()); + } + + [TestMethod] + public void TestAddVerification() + { + var witness = WitnessBuilder.CreateEmpty() + .AddVerification(new byte[] { 0x01, 0x02, 0x03 }) + .Build(); + + Assert.IsNotNull(witness); + Assert.AreEqual(3, witness.VerificationScript.Length); + CollectionAssert.AreEqual(new byte[] { 0x01, 0x02, 0x03 }, witness.VerificationScript.ToArray()); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_WitnessConditionBuilder.cs b/tests/Neo.UnitTests/Builders/UT_WitnessConditionBuilder.cs new file mode 100644 index 0000000000..4378aa2bfa --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_WitnessConditionBuilder.cs @@ -0,0 +1,220 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_WitnessConditionBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Builders; +using Neo.Cryptography.ECC; +using Neo.Network.P2P.Payloads.Conditions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_WitnessConditionBuilder + { + [TestMethod] + public void TestAndCondition() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .And(and => + { + and.CalledByContract(expectedContractHash); + and.CalledByGroup(expectedPublicKey); + }) + .Build(); + + var actual = condition as AndCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(2, actual.Expressions.Length); + Assert.IsInstanceOfType(actual.Expressions[0]); + Assert.IsInstanceOfType(actual.Expressions[1]); + Assert.AreEqual(expectedContractHash, (actual.Expressions[0] as CalledByContractCondition).Hash); + Assert.AreEqual(expectedPublicKey, (actual.Expressions[1] as CalledByGroupCondition).Group); + } + + [TestMethod] + public void TestOrCondition() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .Or(or => + { + or.CalledByContract(expectedContractHash); + or.CalledByGroup(expectedPublicKey); + }) + .Build(); + + var actual = condition as OrCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(2, actual.Expressions.Length); + Assert.IsInstanceOfType(actual.Expressions[0]); + Assert.IsInstanceOfType(actual.Expressions[1]); + Assert.AreEqual(expectedContractHash, (actual.Expressions[0] as CalledByContractCondition).Hash); + Assert.AreEqual(expectedPublicKey, (actual.Expressions[1] as CalledByGroupCondition).Group); + } + + [TestMethod] + public void TestBoolean() + { + var condition = WitnessConditionBuilder.Create() + .Boolean(true) + .Build(); + + var actual = condition as BooleanCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.IsTrue(actual.Expression); + } + + [TestMethod] + public void TestCalledByContract() + { + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .CalledByContract(expectedContractHash) + .Build(); + + var actual = condition as CalledByContractCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(expectedContractHash, actual.Hash); + } + + [TestMethod] + public void TestCalledByEntry() + { + var condition = WitnessConditionBuilder.Create() + .CalledByEntry() + .Build(); + + var actual = condition as CalledByEntryCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + } + + [TestMethod] + public void TestCalledByGroup() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var condition = WitnessConditionBuilder.Create() + .CalledByGroup(expectedPublicKey) + .Build(); + + var actual = condition as CalledByGroupCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(expectedPublicKey, actual.Group); + } + + [TestMethod] + public void TestGroup() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var condition = WitnessConditionBuilder.Create() + .Group(expectedPublicKey) + .Build(); + + var actual = condition as GroupCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(expectedPublicKey, actual.Group); + } + + [TestMethod] + public void TestScriptHash() + { + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .ScriptHash(expectedContractHash) + .Build(); + + var actual = condition as ScriptHashCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.AreEqual(expectedContractHash, actual.Hash); + } + + [TestMethod] + public void TestNotConditionWithAndCondition() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .Not(not => + { + not.And(and => + { + and.CalledByContract(expectedContractHash); + and.CalledByGroup(expectedPublicKey); + }); + }) + .Build(); + + var actual = condition as NotCondition; + var actualAndCondition = actual.Expression as AndCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.IsInstanceOfType(actual.Expression); + Assert.AreEqual(2, actualAndCondition.Expressions.Length); + Assert.IsInstanceOfType(actualAndCondition.Expressions[0]); + Assert.IsInstanceOfType(actualAndCondition.Expressions[1]); + Assert.AreEqual(expectedContractHash, (actualAndCondition.Expressions[0] as CalledByContractCondition).Hash); + Assert.AreEqual(expectedPublicKey, (actualAndCondition.Expressions[1] as CalledByGroupCondition).Group); + } + + [TestMethod] + public void TestNotConditionWithOrCondition() + { + var expectedPublicKey = ECPoint.Parse("021821807f923a3da004fb73871509d7635bcc05f41edef2a3ca5c941d8bbc1231", ECCurve.Secp256r1); + var expectedContractHash = UInt160.Zero; + var condition = WitnessConditionBuilder.Create() + .Not(not => + { + not.Or(or => + { + or.CalledByContract(expectedContractHash); + or.CalledByGroup(expectedPublicKey); + }); + }) + .Build(); + + var actual = condition as NotCondition; + var actualOrCondition = actual.Expression as OrCondition; + + Assert.IsNotNull(actual); + Assert.IsInstanceOfType(condition); + Assert.IsInstanceOfType(actual.Expression); + Assert.AreEqual(2, actualOrCondition.Expressions.Length); + Assert.IsInstanceOfType(actualOrCondition.Expressions[0]); + Assert.IsInstanceOfType(actualOrCondition.Expressions[1]); + Assert.AreEqual(expectedContractHash, (actualOrCondition.Expressions[0] as CalledByContractCondition).Hash); + Assert.AreEqual(expectedPublicKey, (actualOrCondition.Expressions[1] as CalledByGroupCondition).Group); + } + } +} diff --git a/tests/Neo.UnitTests/Builders/UT_WitnessRuleBuilder.cs b/tests/Neo.UnitTests/Builders/UT_WitnessRuleBuilder.cs new file mode 100644 index 0000000000..1b87e2c52f --- /dev/null +++ b/tests/Neo.UnitTests/Builders/UT_WitnessRuleBuilder.cs @@ -0,0 +1,69 @@ +// Copyright (C) 2015-2024 The Neo Project. +// +// UT_WitnessRuleBuilder.cs file belongs to the neo project and is free +// software distributed under the MIT software license, see the +// accompanying file LICENSE in the main directory of the +// repository or http://www.opensource.org/licenses/mit-license.php +// for more details. +// +// Redistribution and use in source and binary forms with or without +// modifications are permitted. + +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Builders; +using Neo.Network.P2P.Payloads; +using Neo.Network.P2P.Payloads.Conditions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Neo.UnitTests.Builders +{ + [TestClass] + public class UT_WitnessRuleBuilder + { + [TestMethod] + public void TestCreate() + { + var builder = WitnessRuleBuilder.Create(WitnessRuleAction.Allow); + + Assert.IsNotNull(builder); + } + + [TestMethod] + public void TestCondition() + { + var rule = WitnessRuleBuilder.Create(WitnessRuleAction.Allow) + .AddCondition(wcb => + { + wcb.ScriptHash(UInt160.Zero); + }).Build(); + + Assert.IsNotNull(rule.Condition); + Assert.AreEqual(WitnessRuleAction.Allow, rule.Action); + Assert.IsInstanceOfType(rule.Condition); + Assert.AreEqual(UInt160.Zero, ((ScriptHashCondition)rule.Condition).Hash); + } + + [TestMethod] + public void TestCondition2() + { + var rule = WitnessRuleBuilder.Create(WitnessRuleAction.Allow) + .AddCondition(wcb => + { + wcb.And(and => + { + and.ScriptHash(UInt160.Zero); + }); + }).Build(); + + Assert.IsNotNull(rule.Condition); + Assert.AreEqual(WitnessRuleAction.Allow, rule.Action); + Assert.IsInstanceOfType(rule.Condition); + Assert.IsInstanceOfType((rule.Condition as AndCondition).Expressions[0]); + Assert.AreEqual(UInt160.Zero, ((rule.Condition as AndCondition).Expressions[0] as ScriptHashCondition).Hash); + } + } +}