diff --git a/.github/workflows/push-to-github-registry.yml b/.github/workflows/push-to-github-registry.yml index fa33398..3a1564f 100644 --- a/.github/workflows/push-to-github-registry.yml +++ b/.github/workflows/push-to-github-registry.yml @@ -13,7 +13,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Create nuget package run: dotnet pack --configuration Release -o out - name: Publish Nuget to GitHub registry diff --git a/CHANGELOG.md b/CHANGELOG.md index 8971072..908f6f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,94 @@ All notable changes to this project will be documented in this file. The format [comment]: <> (Fixed: any bug fixes) [comment]: <> (Security: in case of vulnerabilities) +## [3.0.0-beta3] + +### Added + +* The `TransactionBuilder` now accepts amount as `ulong` type for the `Payment()` method. +* New `GlobalStateKey.FromValidatorKey()` to get the bid address key of a validator. +* New `GetNodeVersion()` in the main client class as a convenient method to check node version. +* New `GetValidatorBid()` method to recover the bid information of a validator. + +### Changed + +* `MessageKey` contains an `AddressableEntity` property instead of a `HashAddr` to represent the related contract entity. +* `GetAuctionInfo()` now uses the new `state_get_auction_info_v2` RPC method in Casper 2.0. + +### Fixed + +* [#99](https://github.com/make-software/casper-net-sdk/issues/99) SSE Listener emits the same event 3 times when listening to EventType.All +* Fixed the `TransactionV1.ValidateHashes()` method to properly verify the hashes. + +## [3.0.0-beta2] + +### Added + +* Added `GetDelegatorReward` and `GetValidatorReward` methods to the RPC client. [PR#76](https://github.com/make-software/casper-net-sdk/pull/76) and [PR#80](https://github.com/make-software/casper-net-sdk/pull/80). +* Added `GetStatePackage` method to the RPC client. [PR#82](https://github.com/make-software/casper-net-sdk/pull/82). +* Added `ProtocolVersion` to Get Node Status RPC response. [PR#77](https://github.com/make-software/casper-net-sdk/pull/77). +* Added a `TransactionBuilder` to build `TransactionV1` transactions for the different type of invocations ( to mint/auction system contracts, user stored contracts) and session deployments. [PR#79](https://github.com/make-software/casper-net-sdk/pull/79). + +### Changed + +* The `Transaction` type is now an abstraction for `TransactionV1` and `Deploy` transaction models. [PR#79](https://github.com/make-software/casper-net-sdk/pull/79). +* Updated `TransactionV1` JSON and bytes serialization in alignment to `casper-node v2.0.0-rc5`. [PR-84](https://github.com/make-software/casper-net-sdk/pull/84) and [PR-85](https://github.com/make-software/casper-net-sdk/pull/85) +* Updated `MessageKey` key type in alignment to `casper-node v2.0.0-rc5`. [PR-86](https://github.com/make-software/casper-net-sdk/pull/86) +* `EntryPointPayment.SelfOnly` constant replaced with `EntryPointPayment.DirectInvocationOnly`.[PR#87](https://github.com/make-software/casper-net-sdk/pull/87). + +### Fixed + +* Fixed a multi-thread bug when using the RPCLoggingHandler in the RPC client class. [PR#81](https://github.com/make-software/casper-net-sdk/pull/81). +* v3.0.0-beta1 did not parse correctly `Step` event emmitted by nodes in Casper v1.x. [PR#78](https://github.com/make-software/casper-net-sdk/pull/78). + +## [3.0.0-beta1] + +This version is compatible with Casper node v2.0.0-rc3 and Casper node v1.5.6. + +### Added + +* New `GetEntity()` method added to the RPC client. +* New `QueryBalanceDetails()` method added to the RPC client. +* New `PutTransaction()` and `GetTransaction()` methods added to the RPC client. +* New `TransactionV1` class to model the new type of transactions in Condor. +* New global state keys added to the SDK: `AddressableEntityKey`, `BalanceHoldKey`, `BidAddrKey`, `BlockGlobalAddrKey`, `ByteCodeKey`, `EntryPointKey`, `MessageKey`, `NamedKeyKey` and `PackageKey`. +* New `AddressableEntity` class added. It may represent an account, a stored smart contract, or a system smart contract. +* New properties in the `StoredValue` class that can be retrieved with `QueryGlobalState()` method: `BidKind`, `Unbonding`, `AddressableEntity`, `Package`, `ByteCode`, `MessageTopicSummary`, `Message`, `NamedKey`, `Reservation`, and `EntryPoint`. +* New classes to represent data from the global state: `BidKind`, `UnbondingPurse`, `Package` and `ByteCode`. +* New `Message` class to contain data for native events included in the `TransactionAccepted` event from the SSE channel. +* Added `TransactionAccepted`, `TransactionProcessed`, and `TransactionExpired` events to the list of events emitted by a node through the SSE interface. + +### Changed + +* The `Block` class has changed to abstract the developer from the versioned block records returned from the network in Condor version. Refer to the migration guide for more information. +* For blocks produced in Casper 2.0, the `Block` instance contains the previous switch block hash in the `LastSwitchBlockHash` property. +* Ther `EraEnd` class has changed to abstract the developer from the versioned records returned from the network. More info in the migration guide. +* The `Transfer` class contained in the response for a `GetBlockTransfers()` call or in a `ExecutionResult` object has changed to abstract from the versioned transfer records returned from the network. Refer to the migration guide for more information. +* The input argument for the `QueryBalance()` method in the RPC client is any object from a class implementing the `IPurseIdentifier` interface. `PublicKey`, `AccountHashKey`, `URef`, and `AddressableEntity` classes implement this interface. +* The type to refer to block heights is now `ulong` across all the SDK. In previous version there was a mix of `ulong` and `int`. +* When using the `GetNodeStatus()` method with a Casper 2.0 node, the response contains the hash for the latest switch block in the `LatestSwitchBlockHash` property. +* `GetDeploy()` response has changed and now contains a `ExecutionInfo`object when the deploy has been processed instead a list of `ExecutionResult` objects. The execution info itself contains block information and a result object. +* Starting with this version of the SDK, only public keys are checksummed with the CEP-57 standard. The rest of the keys and hashes are not checksummed anymore. +* In the `StoredValue` class, `Transfer` has been renamed to `LegacyTransfer`. +* `DeployApproval` class has been renamed to `Approval` and is used for `Deploy` as well as for the new `TransactionV1` model. +* `ActionThresholds` class has now new `UpgradeManagement` property. +* The `EntryPoint` class has new entry point types and a new property `EntryPointPayment`. Both apply when working with Casper 2.0 only. +* `Step` event from SSE contains a list of `Transform` objects instead of a `ExecutionEffect` instance. +* `FinalitySignature` event contains `BlockHeight` and `ChainNameHash` value when connected to a Casper 2.0 node. +* `DeployProcessed` event from SSE contains a `ExecutionResultV1` object instead of a `ExecutionResult` object. + +### Deprecated + +* `Proposer.isSystem` property is marked as Obsolete. Use `IsSystem` with capital `I` instead. + +### Removed + +* `GetAccountBalance()` method in the RPC client has been removed. Use `QueryBalance()` instead. `GetBalance()` method exists to use `state_get_balance` from the RPC interface if needed. + +### Security + +* BouncyCastle package updated to 2.4.0 version. + ## [2.4.0] ### Changed @@ -94,6 +182,9 @@ This new type permits to parse correctly the value `"00"` used for system blocks ### Added * Initial release of Casper .NET SDK. +[3.0.0-beta3]: https://github.com/make-software/casper-net-sdk/releases/tag/v3.0.0-beta3 +[3.0.0-beta2]: https://github.com/make-software/casper-net-sdk/releases/tag/v3.0.0-beta2 +[3.0.0-beta1]: https://github.com/make-software/casper-net-sdk/releases/tag/v3.0.0-beta1 [2.3.0]: https://github.com/make-software/casper-net-sdk/releases/tag/v2.3.0 [2.2.0]: https://github.com/make-software/casper-net-sdk/releases/tag/v2.2.0 [2.1.0]: https://github.com/make-software/casper-net-sdk/releases/tag/v2.1.0 diff --git a/Casper.Network.SDK.Test/AssertExtensions.cs b/Casper.Network.SDK.Test/AssertExtensions.cs new file mode 100644 index 0000000..e04b538 --- /dev/null +++ b/Casper.Network.SDK.Test/AssertExtensions.cs @@ -0,0 +1,34 @@ +using NUnit.Framework; +using Org.BouncyCastle.Utilities.Encoders; + +namespace NetCasperTest +{ + public static class AssertExtensions + { + public static void IsHash(string maybeHash) + { + try + { + Assert.AreEqual(32, Hex.Decode(maybeHash).Length); + } + catch + { + Assert.Fail($"Cannot decode a hash from '{maybeHash}'"); + } + } + + public static void IsValidHex(string maybeHex, uint bytesLength = 0) + { + try + { + var bytes = Hex.Decode(maybeHex); + if(bytesLength > 0) + Assert.AreEqual(bytesLength, bytes.Length); + } + catch + { + Assert.Fail($"Cannot decode hex bytes from '{maybeHex}'"); + } + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/CEP57ChecksumTest.cs b/Casper.Network.SDK.Test/CEP57ChecksumTest.cs index 0b05f28..f657e44 100644 --- a/Casper.Network.SDK.Test/CEP57ChecksumTest.cs +++ b/Casper.Network.SDK.Test/CEP57ChecksumTest.cs @@ -24,59 +24,6 @@ public void CEP57LongByteArray() Assert.AreEqual(longHex.ToLower(), encodedHex); } - [Test] - public void CEP57DecodeEncode() - { - int result; - - var hash = "66B754c5E2981B41D41af39B88C8583bD08EF176eB1a81F56b8F395685805968"; - var bytes = CEP57Checksum.Decode(hash, out result); - Assert.AreEqual(CEP57Checksum.ValidChecksum, result); - - GlobalStateKey key = new HashKey(bytes); - Assert.AreEqual($"hash-{hash}", key.ToString()); - - hash = "eA1D6C19ccAeb35Ae717065c250E0F7F6Dc64AC3c6494a797E0b33A23CA1f1b9"; - bytes = CEP57Checksum.Decode(hash, out result); - Assert.AreEqual(CEP57Checksum.ValidChecksum, result); - Assert.AreEqual(hash, CEP57Checksum.Encode(bytes)); - - key = new TransferKey(bytes); - Assert.AreEqual($"transfer-{hash}", key.ToString()); - - hash = "98d945f5324F865243B7c02C0417AB6eaC361c5c56602FD42ced834a1Ba201B6"; - bytes = CEP57Checksum.Decode(hash, out result); - Assert.AreEqual(CEP57Checksum.ValidChecksum, result); - Assert.AreEqual(hash, CEP57Checksum.Encode(bytes)); - - key = new DeployInfoKey(bytes); - Assert.AreEqual($"deploy-{hash}", key.ToString()); - - hash = "8cf5E4aCF51f54Eb59291599187838Dc3BC234089c46fc6cA8AD17e762aE4401"; - bytes = CEP57Checksum.Decode(hash, out result); - Assert.AreEqual(CEP57Checksum.ValidChecksum, result); - Assert.AreEqual(hash, CEP57Checksum.Encode(bytes)); - - key = new BalanceKey(bytes); - Assert.AreEqual($"balance-{hash}", key.ToString()); - - hash = "010c3Fe81B7b862E50C77EF9A958a05BfA98444F26f96f23d37A13c96244cFB7"; - bytes = CEP57Checksum.Decode(hash, out result); - Assert.AreEqual(CEP57Checksum.ValidChecksum, result); - Assert.AreEqual(hash, CEP57Checksum.Encode(bytes)); - - key = new BidKey(bytes); - Assert.AreEqual($"bid-{hash}", key.ToString()); - - hash = "98d945f5324F865243B7c02C0417AB6eaC361c5c56602FD42ced834a1Ba201B6"; - bytes = CEP57Checksum.Decode(hash, out result); - Assert.AreEqual(CEP57Checksum.ValidChecksum, result); - Assert.AreEqual(hash, CEP57Checksum.Encode(bytes)); - - key = new WithdrawKey(bytes); - Assert.AreEqual($"withdraw-{hash}", key.ToString()); - } - [Test] public void CEP57HasChecksum() { diff --git a/Casper.Network.SDK.Test/CLValueJsonDeserializerTest.cs b/Casper.Network.SDK.Test/CLValueJsonDeserializerTest.cs index 4862944..364026c 100644 --- a/Casper.Network.SDK.Test/CLValueJsonDeserializerTest.cs +++ b/Casper.Network.SDK.Test/CLValueJsonDeserializerTest.cs @@ -191,7 +191,7 @@ public void SerializeU512CLValue() var doc = System.Text.Json.JsonDocument.Parse(json); Assert.AreEqual(3, doc.RootElement.EnumerateObject().Count()); Assert.AreEqual("U512", doc.RootElement.GetProperty("cl_type").GetString()); - Assert.AreEqual("05005550B405", doc.RootElement.GetProperty("bytes").GetString()); + Assert.AreEqual("05005550b405", doc.RootElement.GetProperty("bytes").GetString().ToLower()); Assert.AreEqual("24500000000", doc.RootElement.GetProperty("parsed").GetString()); clValue = CLValue.U512(15000000000); @@ -541,7 +541,7 @@ public void SerializeKeyCLValue() Assert.IsNotEmpty(json); Assert.IsTrue(json.Contains(@"""cl_type"":""Key""")); Assert.IsTrue(json.Contains(@"""bytes"":""08b192")); - Assert.IsTrue(json.Contains(@"""parsed"":{""Withdraw"":""withdraw-B192")); + Assert.IsTrue(json.Contains(@"""parsed"":{""Withdraw"":""withdraw-b192")); gsKey = GlobalStateKey.FromString( "uref-e48935c79e96c490c01e1e8800de5ec5f4a857a57db0dcffed1e1e2b5d29b5e4-007"); @@ -550,9 +550,9 @@ public void SerializeKeyCLValue() Assert.IsNotEmpty(json); Assert.IsTrue(json.Contains(@"""cl_type"":""Key""")); Assert.IsTrue( - json.Contains(@"""bytes"":""02e48935c79E96C490c01e1E8800dE5Ec5F4A857A57dB0DcFfED1e1E2b5d29b5E407")); + json.Contains(@"""bytes"":""02e48935c79e96c490c01e1e8800de5ec5f4a857a57db0dcffed1e1e2b5d29b5e407")); Assert.IsTrue(json.Contains( - @"""parsed"":{""URef"":""uref-E48935C79E96c490c01e1E8800de5Ec5F4A857A57db0dcffEd1E1E2B5D29b5E4-007")); + @"""parsed"":{""URef"":""uref-e48935c79e96c490c01e1e8800de5ec5f4a857a57db0dcffed1e1e2b5d29b5e4-007")); gsKey = GlobalStateKey.FromString("era-2685"); clValue = CLValue.Key(gsKey); diff --git a/Casper.Network.SDK.Test/Casper.Network.SDK.Test.csproj b/Casper.Network.SDK.Test/Casper.Network.SDK.Test.csproj index 2747f70..cbbea81 100644 --- a/Casper.Network.SDK.Test/Casper.Network.SDK.Test.csproj +++ b/Casper.Network.SDK.Test/Casper.Network.SDK.Test.csproj @@ -32,5 +32,7 @@ PreserveNewest + + diff --git a/Casper.Network.SDK.Test/DeployItemByteSerializerTest.cs b/Casper.Network.SDK.Test/DeployItemByteSerializerTest.cs index 69a2b67..4658738 100644 --- a/Casper.Network.SDK.Test/DeployItemByteSerializerTest.cs +++ b/Casper.Network.SDK.Test/DeployItemByteSerializerTest.cs @@ -40,7 +40,7 @@ public void StoredContractByHashSerialization() new StoredContractByHashDeployItem(hash.ToHexString(), "counter_inc"); - Assert.AreEqual(CEP57Checksum.Encode(hash.RawBytes), storedContract.Hash); + Assert.AreEqual(Hex.ToHexString(hash.RawBytes), storedContract.Hash); Assert.AreEqual("counter_inc", storedContract.EntryPoint); Assert.IsNotNull(storedContract.RuntimeArgs); Assert.AreEqual(0, storedContract.RuntimeArgs.Count); @@ -71,7 +71,7 @@ public void StoredVersionedContractByHashSerialization() var storedContract = new StoredVersionedContractByHashDeployItem(hash.ToHexString(), 1, "counter_inc"); - Assert.AreEqual(CEP57Checksum.Encode(hash.RawBytes), storedContract.Hash); + Assert.AreEqual(Hex.ToHexString(hash.RawBytes), storedContract.Hash); Assert.AreEqual(1, storedContract.Version); Assert.AreEqual("counter_inc", storedContract.EntryPoint); Assert.IsNotNull(storedContract.RuntimeArgs); diff --git a/Casper.Network.SDK.Test/DeployJsonSerializerTest.cs b/Casper.Network.SDK.Test/DeployJsonSerializerTest.cs index a672517..d02c4c4 100644 --- a/Casper.Network.SDK.Test/DeployJsonSerializerTest.cs +++ b/Casper.Network.SDK.Test/DeployJsonSerializerTest.cs @@ -12,7 +12,7 @@ public class DeployJsonSerializer private string deploy1 = TestContext.CurrentContext.TestDirectory + "/TestData/transfer-deploy.json"; private string signer1 = "017b8058863Aad49c7b89c77019ceF3a4D863BDf1c0c61499776F94b18465810F7"; private string signature1 = - "012a0c5896aB9D6Cf029268Cf454Ba9D42bcD40Bd909C984890F326739EeF13201d1d2a9a95938B2a966F2E650D1bDd80931F0374e3A92403e025B806Aa1065109"; + "012a0c5896ab9d6cf029268cf454ba9d42bcd40bd909c984890f326739eef13201d1d2a9a95938b2a966f2e650d1bdd80931f0374e3a92403e025b806aa1065109"; [Test] public void LoadDeployFromFileTest() diff --git a/Casper.Network.SDK.Test/GlobalStateKeyTest.cs b/Casper.Network.SDK.Test/GlobalStateKeyTest.cs index 6a9ed65..11f01a9 100644 --- a/Casper.Network.SDK.Test/GlobalStateKeyTest.cs +++ b/Casper.Network.SDK.Test/GlobalStateKeyTest.cs @@ -1,7 +1,9 @@ using System; using System.Linq; +using System.Runtime.InteropServices.JavaScript; using System.Text.Json; using Casper.Network.SDK.Types; +using Casper.Network.SDK.Utils; using NUnit.Framework; using Org.BouncyCastle.Utilities.Encoders; @@ -407,7 +409,7 @@ public void InvalidPrefixTest() [Test] public void InvalidKeyIdentifierTest() { - var bytes = Hex.Decode("0F00"); + var bytes = Hex.Decode("3F00"); var ex = Assert.Catch(() => { GlobalStateKey.FromBytes(bytes); @@ -415,47 +417,223 @@ public void InvalidKeyIdentifierTest() Assert.IsNotNull(ex); Assert.IsTrue(ex.Message.StartsWith("Unknown key identifier")); } + + [Test] + public void HashKeyJsonDeserializeTest() + { + const string json = @"{""key"":""hash-98d945f5324F865243B7c02C0417AB6eaC361c5c56602FD42ced834a1Ba201B6"",""kind"":""Read""}"; + var op = JsonSerializer.Deserialize(json); + Assert.IsNotNull(op); + + const string invalidJson = @"{""key"":""hash-aaaaa5f5324F865243B7c02C0417AB6eaC361c5c56602FD42ced834a1Ba201B6"",""kind"":""Read""}"; + var ex = Assert.Catch(() => JsonSerializer.Deserialize(invalidJson)); + Assert.IsNotNull(ex); + Assert.IsTrue(ex.Message.Contains("checksum mismatch")); + } + + [Test] + public void AddressableEntityKeyTest() + { + const string ENTITY_PREFIX = "entity-"; + + var entityAddr = "0101010101010101010101010101010101010101010101010101010101010101"; + + var entities = new EntityKindEnum[] { EntityKindEnum.System, EntityKindEnum.Account, EntityKindEnum.Contract }; + foreach (var entityKind in entities) + { + var entityKey = $"{ENTITY_PREFIX}{entityKind.ToString().ToLower()}-{entityAddr}"; + + var key = GlobalStateKey.FromString(entityKey) as AddressableEntityKey; + Assert.IsNotNull(key); + Assert.AreEqual(KeyIdentifier.AddressableEntity, key.KeyIdentifier); + + Assert.AreEqual(entityKind, key.Kind); + Assert.AreEqual($"{ENTITY_PREFIX}{entityKind.ToString().ToLower()}-"+entityAddr, key.ToString().ToLower()); + } + } [Test] - public void CEP57ChecksumValidationTest() + public void ByteCodeKeyTest() { - var hashKey = "hash-98d945f5324F865243B7c02C0417AB6eaC361c5c56602FD42ced834a1Ba201B6"; + var addr = "0101010101010101010101010101010101010101010101010101010101010101"; - var key = GlobalStateKey.FromString(hashKey); - Assert.IsNotNull(key); + var kinds = new ByteCodeKind[] { ByteCodeKind.Empty, ByteCodeKind.V1CasperWasm }; + foreach (var kind in kinds) + { + var keyStr = $"{kind.ToKeyPrefix()}{addr}"; - var invalidHashKey = "hash-98D945F5324F865243B7c02C0417AB6eaC361c5c56602FD42ced834a1Ba201B6"; - var ex = Assert.Catch(() => + var key = GlobalStateKey.FromString(keyStr); + Assert.IsNotNull(key); + Assert.AreEqual(KeyIdentifier.ByteCode, key.KeyIdentifier); + + var byteCodeKey = key as ByteCodeKey; + Assert.IsNotNull(byteCodeKey); + + Assert.AreEqual(kind, byteCodeKey.Kind); + Assert.AreEqual(keyStr, key.ToString().ToLower()); + Assert.AreEqual(keyStr, byteCodeKey.ToString().ToLower()); + } + } + + [Test] + public void NamedKeyKeyTest() + { + const string NAMEDKEY_PREFIX = "named-key-"; + const string ENTITY_PREFIX = "entity-"; + + var entityAddr = "0101010101010101010101010101010101010101010101010101010101010101"; + var namedKeyAddr = "0202020202020202020202020202020202020202020202020202020202020202"; + + var entities = new EntityKindEnum[] { EntityKindEnum.System, EntityKindEnum.Account, EntityKindEnum.Contract }; + foreach (var entityKind in entities) { - GlobalStateKey.FromString(invalidHashKey); - }); - Assert.IsNotNull(ex); - Assert.IsTrue(ex.Message.StartsWith("Global State Key checksum mismatch.")); + var namedKeyKey = $"{NAMEDKEY_PREFIX}{ENTITY_PREFIX}{entityKind.ToString().ToLower()}-{entityAddr}-{namedKeyAddr}"; - var urefKey = "uref-98d945f5324F865243B7c02C0417AB6eaC361c5c56602FD42ced834a1Ba201B6-007"; - key = GlobalStateKey.FromString(urefKey); - Assert.IsNotNull(key); + var key = GlobalStateKey.FromString(namedKeyKey); + Assert.IsNotNull(key); + Assert.AreEqual(KeyIdentifier.NamedKey, key.KeyIdentifier); + + var namedKey = key as NamedKeyKey; + Assert.IsNotNull(namedKey); + + var entity = namedKey.AddressableEntity; + Assert.IsNotNull(entity); + Assert.AreEqual(KeyIdentifier.AddressableEntity, entity.KeyIdentifier); + Assert.AreEqual(entityKind, entity.Kind); + Assert.AreEqual($"{ENTITY_PREFIX}{entityKind.ToString().ToLower()}-"+entityAddr, entity.ToString().ToLower()); + } + } + + [Test] + public void BalanceHoldKeyTest() + { + const string NAMEDKEY_PREFIX = "balance-hold-"; + + var tags = new BalanceHoldTag[] { BalanceHoldTag.Gas, BalanceHoldTag.Processing }; - var invalidURefKey = "uref-98D945F5324F865243B7c02C0417AB6eaC361c5c56602FD42ced834a1Ba201B6-007"; - ex = Assert.Catch(() => + var uref = "0101010101010101010101010101010101010101010101010101010101010101"; + UInt64 blockTime = DateUtils.ToEpochTime(DateTime.Now); + + foreach (var tag in tags) { - GlobalStateKey.FromString(invalidURefKey); - }); - Assert.IsNotNull(ex); - Assert.IsTrue(ex.Message.StartsWith("URef checksum mismatch.")); + byte[] timeBytes = BitConverter.GetBytes(blockTime); + if (!BitConverter.IsLittleEndian) + Array.Reverse(timeBytes); + + var keyStr = + $"{NAMEDKEY_PREFIX}{Hex.ToHexString(new byte[] {(byte)tag})}{uref}{Hex.ToHexString(timeBytes)}"; + + var key = GlobalStateKey.FromString(keyStr); + Assert.IsNotNull(key); + Assert.AreEqual(KeyIdentifier.BalanceHold, key.KeyIdentifier); + + var balanceHoldKey = key as BalanceHoldKey; + Assert.IsNotNull(balanceHoldKey); + Assert.AreEqual(tag, balanceHoldKey.Tag); + Assert.AreEqual(blockTime, balanceHoldKey.BlockTime); + Assert.AreEqual($"uref-{uref}-000", balanceHoldKey.URef.ToString().ToLower()); + Assert.AreEqual(keyStr, balanceHoldKey.ToString().ToLower()); + } } [Test] - public void HashKeyJsonDeserializeTest() + public void MessageTopicKeyTest() { - const string json = @"{""key"":""hash-98d945f5324F865243B7c02C0417AB6eaC361c5c56602FD42ced834a1Ba201B6"",""kind"":""Read""}"; - var op = JsonSerializer.Deserialize(json); - Assert.IsNotNull(op); - - const string invalidJson = @"{""key"":""hash-aaaaa5f5324F865243B7c02C0417AB6eaC361c5c56602FD42ced834a1Ba201B6"",""kind"":""Read""}"; - var ex = Assert.Catch(() => JsonSerializer.Deserialize(invalidJson)); - Assert.IsNotNull(ex); - Assert.IsTrue(ex.Message.Contains("checksum mismatch")); + var hashAddr = "entity-contract-55d4a6915291da12afded37fa5bc01f0803a2f0faf6acb7ec4c7ca6ab76f3330"; + var topicStr = "5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1"; + var msgKeyStr = $"message-topic-{hashAddr}-{topicStr}"; + + var key = GlobalStateKey.FromString(msgKeyStr); + Assert.IsNotNull(key); + Assert.AreEqual(KeyIdentifier.Message, key.KeyIdentifier); + + var messageKey = key as MessageKey; + Assert.IsNotNull(messageKey); + Assert.IsNotNull(messageKey.AddressableEntity); + Assert.AreEqual(hashAddr, messageKey.AddressableEntity.ToString()); + Assert.AreEqual(topicStr, messageKey.TopicHash); + Assert.IsFalse(messageKey.Index.HasValue); + Assert.AreEqual(msgKeyStr, messageKey.ToString().ToLower()); + Assert.AreEqual(msgKeyStr, key.ToString().ToLower()); + } + + [Test] + public void MessageIndexKeyTest() + { + var hashAddr = "entity-contract-55d4a6915291da12afded37fa5bc01f0803a2f0faf6acb7ec4c7ca6ab76f3330"; + var topicStr = "5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1"; + var indexStr = "0f"; + var msgKeyStr = $"message-{hashAddr}-{topicStr}-{indexStr}"; + + var key = GlobalStateKey.FromString(msgKeyStr); + Assert.IsNotNull(key); + Assert.AreEqual(KeyIdentifier.Message, key.KeyIdentifier); + + var messageKey = key as MessageKey; + Assert.IsNotNull(messageKey); + Assert.IsNotNull(messageKey.AddressableEntity); + Assert.AreEqual(hashAddr, messageKey.AddressableEntity.ToString()); + Assert.AreEqual(topicStr, messageKey.TopicHash); + Assert.IsTrue(messageKey.Index.HasValue); + Assert.AreEqual(15, messageKey.Index.Value); + Assert.AreEqual(msgKeyStr, messageKey.ToString().ToLower()); + Assert.AreEqual(msgKeyStr, key.ToString().ToLower()); + } + + [Test] + public void BidAddrKeyTest() + { + { + var unifiedBidAddrKeyStr = "bid-addr-002f3fb80d362ad0a922f446915a259c9aaec9ba99292b3e50ff2359c458007309"; + var key = GlobalStateKey.FromString(unifiedBidAddrKeyStr); + Assert.IsNotNull(key); + Assert.AreEqual(KeyIdentifier.BidAddr, key.KeyIdentifier); + + var bidAddrKey = key as BidAddrKey; + Assert.AreEqual(BidAddrTag.Unified, bidAddrKey.Tag); + Assert.AreEqual("account-hash-2f3fb80d362ad0a922f446915a259c9aaec9ba99292b3e50ff2359c458007309", bidAddrKey.Unified.ToString().ToLower()); + Assert.IsNull(bidAddrKey.DelegatorAccount); + Assert.AreEqual(0, bidAddrKey.EraId); + + } + { + var validatorBidAddrKeyStr = "bid-addr-012f3fb80d362ad0a922f446915a259c9aaec9ba99292b3e50ff2359c458007309"; + var key = GlobalStateKey.FromString(validatorBidAddrKeyStr); + Assert.IsNotNull(key); + Assert.AreEqual(KeyIdentifier.BidAddr, key.KeyIdentifier); + + var bidAddrKey = key as BidAddrKey; + Assert.AreEqual(BidAddrTag.Validator, bidAddrKey.Tag); + Assert.AreEqual("account-hash-2f3fb80d362ad0a922f446915a259c9aaec9ba99292b3e50ff2359c458007309", bidAddrKey.Validator.ToString().ToLower()); + Assert.IsNull(bidAddrKey.DelegatorAccount); + Assert.AreEqual(0, bidAddrKey.EraId); + } + { + var delegatorBidAddrKeyStr = "bid-addr-022f3fb80d362ad0a922f446915a259c9aaec9ba99292b3e50ff2359c4580073099fa1fc0808d3a5b9ea9f3af4ca7c8c3655568fdf378d8afdf8a7e56e58abbfd4"; + var key = GlobalStateKey.FromString(delegatorBidAddrKeyStr); + Assert.IsNotNull(key); + Assert.AreEqual(KeyIdentifier.BidAddr, key.KeyIdentifier); + + var bidAddrKey = key as BidAddrKey; + Assert.AreEqual(BidAddrTag.DelegatedAccount, bidAddrKey.Tag); + Assert.AreEqual("account-hash-2f3fb80d362ad0a922f446915a259c9aaec9ba99292b3e50ff2359c458007309", bidAddrKey.Validator.ToString().ToLower()); + Assert.AreEqual("account-hash-9fa1fc0808d3a5b9ea9f3af4ca7c8c3655568fdf378d8afdf8a7e56e58abbfd4", bidAddrKey.DelegatorAccount.ToString().ToLower()); + Assert.AreEqual(0, bidAddrKey.EraId); + + } + { + var creditBidAddrKeyStr = + "bid-addr-04520037cd249ccbcfeb0b9feae07d8d4f7d922cf88adc4f3e8691f9d34ccc8d097f00000000000000"; + var key = GlobalStateKey.FromString(creditBidAddrKeyStr); + Assert.IsNotNull(key); + Assert.AreEqual(KeyIdentifier.BidAddr, key.KeyIdentifier); + + var bidAddrKey = key as BidAddrKey; + Assert.AreEqual(BidAddrTag.Credit, bidAddrKey.Tag); + Assert.AreEqual("account-hash-520037cd249ccbcfeb0b9feae07d8d4f7d922cf88adc4f3e8691f9d34ccc8d09", bidAddrKey.Validator.ToString().ToLower()); + Assert.IsNull(bidAddrKey.DelegatorAccount); + Assert.AreEqual(127, bidAddrKey.EraId); + } } } } diff --git a/Casper.Network.SDK.Test/NctlContractTest.cs b/Casper.Network.SDK.Test/NctlContractTest.cs index df520d2..1405f73 100644 --- a/Casper.Network.SDK.Test/NctlContractTest.cs +++ b/Casper.Network.SDK.Test/NctlContractTest.cs @@ -50,7 +50,8 @@ public async Task WaitContractDeploymentTest() var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120)); var getResponse = await _client.GetDeploy(_contractDeployHash, tokenSource.Token); - var execResult = getResponse.Parse().ExecutionResults.First(); + var execInfo = getResponse.Parse().ExecutionInfo; + var execResult = (ExecutionResultV1)execInfo.ExecutionResult; Assert.IsTrue(execResult.IsSuccess); Assert.AreEqual(64, execResult.BlockHash.Length); Assert.IsNull(execResult.ErrorMessage); @@ -78,7 +79,7 @@ public async Task QueryContractKeysTest() var contractWasmKey = GlobalStateKey.FromString(contractInfo.ContractWasmHash); var rpcResponse4 = await _client.QueryGlobalState(contractWasmKey); var contractWasmInfo = rpcResponse4.Parse().StoredValue.ContractWasm; - Assert.IsFalse(string.IsNullOrWhiteSpace(contractWasmInfo)); + Assert.IsFalse(string.IsNullOrWhiteSpace(contractWasmInfo.Bytes)); } [Test, Order(4)] @@ -102,7 +103,8 @@ public async Task CallContractByNameTest() var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120)); var getResponse = await _client.GetDeploy(deployHash, tokenSource.Token); - var execResult = getResponse.Parse().ExecutionResults.First(); + var execInfo = getResponse.Parse().ExecutionInfo; + var execResult = (ExecutionResultV1)execInfo.ExecutionResult; Assert.IsTrue(execResult.IsSuccess); } @@ -127,7 +129,8 @@ public async Task CallContractByHashTest() var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120)); var getResponse = await _client.GetDeploy(deployHash, tokenSource.Token); - var execResult = getResponse.Parse().ExecutionResults.First(); + var execInfo = getResponse.Parse().ExecutionInfo; + var execResult = (ExecutionResultV1)execInfo.ExecutionResult; Assert.IsTrue(execResult.IsSuccess); _blockHash = execResult.BlockHash; Assert.IsNotNull(_blockHash); diff --git a/Casper.Network.SDK.Test/NctlGetXTest.cs b/Casper.Network.SDK.Test/NctlGetXTest.cs index 1dda357..34a28c9 100644 --- a/Casper.Network.SDK.Test/NctlGetXTest.cs +++ b/Casper.Network.SDK.Test/NctlGetXTest.cs @@ -47,7 +47,7 @@ public async Task GetAccountTest() try { var block = (await _client.GetBlock()).Parse().Block; - var blockHeight = (int) block.Header.Height; + var blockHeight = block.Height; var blockHash = block.Hash; var stateRootHash = await _client.GetStateRootHash(blockHash); @@ -75,46 +75,31 @@ public async Task GetAccountTest() var accountInfo6 = response6.Parse(); Assert.AreEqual(accountInfo.Account.AccountHash.ToString(), accountInfo6.Account.AccountHash.ToString()); - var resp = await _client.GetAccountBalance(_faucetKey.PublicKey, stateRootHash); + var resp = await _client.GetBalance(accountInfo.Account.MainPurse.ToString(), stateRootHash); var accountBalance = resp.Parse(); - Assert.IsTrue(accountBalance.BalanceValue > 0); - - resp = await _client.GetAccountBalance(accountInfo.Account.MainPurse, stateRootHash); - var accountBalanceCompare = resp.Parse(); - Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); - - resp = await _client.GetAccountBalance(accountInfo.Account.MainPurse.ToString(), stateRootHash); - accountBalanceCompare = resp.Parse(); - Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); - resp = await _client.GetAccountBalance(new AccountHashKey(_faucetKey.PublicKey), stateRootHash); - accountBalanceCompare = resp.Parse(); - Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); - - - resp = await _client.GetAccountBalance(_faucetKey.PublicKey, blockHeight); - accountBalanceCompare = resp.Parse(); + var respq = await _client.QueryBalance(_faucetKey.PublicKey, (ulong)blockHeight); + var accountBalanceCompare = respq.Parse(); Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); - resp = await _client.GetAccountBalance(accountInfo.Account.MainPurse, blockHeight); - accountBalanceCompare = resp.Parse(); + respq = await _client.QueryBalance(accountInfo.Account.MainPurse, (ulong)blockHeight); + accountBalanceCompare = respq.Parse(); Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); - resp = await _client.GetAccountBalance(new AccountHashKey(_faucetKey.PublicKey), blockHeight); - accountBalanceCompare = resp.Parse(); + respq = await _client.QueryBalance(new AccountHashKey(_faucetKey.PublicKey), (ulong)blockHeight); + accountBalanceCompare = respq.Parse(); Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); - - resp = await _client.GetAccountBalanceWithBlockHash(_faucetKey.PublicKey, blockHash); - accountBalanceCompare = resp.Parse(); + respq = await _client.QueryBalance(_faucetKey.PublicKey, blockHash); + accountBalanceCompare = respq.Parse(); Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); - resp = await _client.GetAccountBalanceWithBlockHash(accountInfo.Account.MainPurse, blockHash); - accountBalanceCompare = resp.Parse(); + respq = await _client.QueryBalance(accountInfo.Account.MainPurse, blockHash); + accountBalanceCompare = respq.Parse(); Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); - resp = await _client.GetAccountBalanceWithBlockHash(new AccountHashKey(_faucetKey.PublicKey), blockHash); - accountBalanceCompare = resp.Parse(); + respq = await _client.QueryBalance(new AccountHashKey(_faucetKey.PublicKey), blockHash); + accountBalanceCompare = respq.Parse(); Assert.AreEqual(accountBalance.BalanceValue, accountBalanceCompare.BalanceValue); } catch (RpcClientException e) @@ -139,7 +124,7 @@ public async Task GetAccountTest() { var key1 = KeyPair.CreateNew(KeyAlgo.ED25519); - await _client.GetAccountBalance(key1.PublicKey); + await _client.QueryBalance(key1.PublicKey); Assert.Fail("Exception expected"); } catch (RpcClientException e) @@ -162,13 +147,13 @@ public async Task GetBlockTest() var result2 = response2.Parse(); Assert.IsNotNull(result2.Block.Hash); - Assert.AreEqual(result2.Block.Body.Proposer.IsSystem, result2.Block.Body.Proposer.isSystem); + Assert.AreEqual(result2.Block.Proposer.IsSystem, result2.Block.Proposer.IsSystem); var response3 = await _client.GetBlock(result2.Block.Hash); var result3 = response3.Parse(); Assert.AreEqual(result2.Block.Hash, result3.Block.Hash); - var response4 = await _client.GetBlock((int)result2.Block.Header.Height); + var response4 = await _client.GetBlock(result2.Block.Height); var result4 = response4.Parse(); Assert.AreEqual(result2.Block.Hash, result4.Block.Hash); @@ -176,14 +161,14 @@ public async Task GetBlockTest() var result5 = response5.Parse(); Assert.AreEqual(0, result5.Transfers.Count); - var response6 = await _client.GetBlockTransfers((int)result2.Block.Header.Height); + var response6 = await _client.GetBlockTransfers(result2.Block.Height); var result6 = response6.Parse(); Assert.AreEqual(0, result6.Transfers.Count); var hash1 = await _client.GetStateRootHash(result2.Block.Hash); Assert.AreEqual(32*2, hash1.Length); - var hash2 = await _client.GetStateRootHash((int)result2.Block.Header.Height); + var hash2 = await _client.GetStateRootHash(result2.Block.Height); Assert.AreEqual(hash1, hash2); var hash3 = await _client.GetStateRootHash(); @@ -216,8 +201,8 @@ public async Task GetSystemBlockProposerTest() var response = await _client.GetBlock(0); var result = response.Parse(); Assert.IsNotNull(result.Block.Hash); - Assert.IsTrue(result.Block.Body.Proposer.IsSystem); - Assert.AreEqual(result.Block.Body.Proposer.IsSystem, result.Block.Body.Proposer.isSystem); + Assert.IsTrue(result.Block.Proposer.IsSystem); + Assert.AreEqual(result.Block.Proposer.IsSystem, result.Block.Proposer.IsSystem); } catch (RpcClientException e) { @@ -276,7 +261,7 @@ public async Task GetEraSummaryTest() var result3 = response3.Parse(); Assert.IsNotNull(result3.Block.Hash); - var response4 = await _client.GetEraSummary((int)result3.Block.Header.Height); + var response4 = await _client.GetEraSummary(result3.Block.Height); var result4 = response4.Parse(); Assert.IsTrue(result4.EraSummary.EraId > 0); Assert.IsNotNull(result4.EraSummary.StoredValue.EraInfo); diff --git a/Casper.Network.SDK.Test/NctlMyDictContractTest.cs b/Casper.Network.SDK.Test/NctlMyDictContractTest.cs index 5e3a2e2..8248bea 100644 --- a/Casper.Network.SDK.Test/NctlMyDictContractTest.cs +++ b/Casper.Network.SDK.Test/NctlMyDictContractTest.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -8,7 +7,6 @@ using Casper.Network.SDK.Types; using Casper.Network.SDK.Utils; using NUnit.Framework; -using Org.BouncyCastle.Utilities.Encoders; namespace NetCasperTest { @@ -49,9 +47,10 @@ public async Task WaitContractDeploymentTest() var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120)); var getResponse = await _client.GetDeploy(_contractDeployHash, tokenSource.Token); - var execResult = getResponse.Parse().ExecutionResults.First(); + var execInfo = getResponse.Parse().ExecutionInfo; + var execResult = execInfo.ExecutionResult; Assert.IsTrue(execResult.IsSuccess); - Assert.AreEqual(64, execResult.BlockHash.Length); + AssertExtensions.IsHash(execInfo.BlockHash); Assert.IsNull(execResult.ErrorMessage); } @@ -81,7 +80,8 @@ public async Task CallVersionedContractByNameTest() var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120)); var getResponse = await _client.GetDeploy(deployHash, tokenSource.Token); - var execResult = getResponse.Parse().ExecutionResults.First(); + var execInfo = getResponse.Parse().ExecutionInfo; + var execResult = execInfo.ExecutionResult; Assert.IsTrue(execResult.IsSuccess); } @@ -123,7 +123,8 @@ public async Task CallVersionedContractByHashTest() var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120)); var getResponse = await _client.GetDeploy(deployHash, tokenSource.Token); - var execResult = getResponse.Parse().ExecutionResults.First(); + var execInfo = getResponse.Parse().ExecutionInfo; + var execResult = execInfo.ExecutionResult; Assert.IsTrue(execResult.IsSuccess); } diff --git a/Casper.Network.SDK.Test/NctlTransferTest.cs b/Casper.Network.SDK.Test/NctlTransferTest.cs index 9b231c9..f86e94e 100644 --- a/Casper.Network.SDK.Test/NctlTransferTest.cs +++ b/Casper.Network.SDK.Test/NctlTransferTest.cs @@ -50,13 +50,15 @@ public async Task WaitTransferExecutionTest() var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120)); var getResponse = await _client.GetDeploy(_transferDeployHash, tokenSource.Token); - var execResult = getResponse.Parse().ExecutionResults.First(); - Assert.IsTrue(execResult.IsSuccess); - + var execInfo = getResponse.Parse().ExecutionInfo; + var execResult = (ExecutionResultV1)execInfo.ExecutionResult; + Assert.IsTrue(execResult.ErrorMessage == null); + + var t = execResult.Transfers.First(); _transferKey = execResult.Transfers.First(); Assert.IsNotNull(_transferKey.ToString()); - _transferBlockHash = execResult.BlockHash; + _transferBlockHash = execInfo.BlockHash; Assert.IsNotNull(_transferBlockHash); } @@ -66,12 +68,13 @@ public async Task QueryTransferKey() Assert.IsNotNull(_transferKey, "This test must run after WaitTransferExecutionTest"); var getResponse = await _client.QueryGlobalState(_transferKey); + Console.WriteLine(getResponse.Result.GetRawText()); var transfer = getResponse.Parse().StoredValue.Transfer; Assert.AreEqual(_transferAmount, transfer.Amount); var getResponse2 = await _client.QueryGlobalStateWithBlockHash(_transferKey, _transferBlockHash); var transfer2 = getResponse.Parse().StoredValue.Transfer; - Assert.AreEqual(transfer.DeployHash, transfer2.DeployHash); + Assert.AreEqual(transfer.TransactionHash.Deploy, transfer2.TransactionHash.Deploy); } [Test, Order(4)] @@ -95,7 +98,7 @@ public async Task CatchFailedTransferTest() var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120)); var getResponse = await _client.GetDeploy(deploy.Hash, tokenSource.Token); - var execResult = getResponse.Parse().ExecutionResults.First(); + var execResult = getResponse.Parse().ExecutionInfo.ExecutionResult; Assert.IsFalse(execResult.IsSuccess); Assert.IsFalse(string.IsNullOrWhiteSpace(execResult.ErrorMessage)); } @@ -131,7 +134,7 @@ public async Task GetBlockTransfersTest() var response = await _client.GetBlockTransfers(_transferBlockHash); var result = response.Parse(); Assert.AreEqual(1, result.Transfers.Count); - Assert.AreEqual(_transferDeployHash, result.Transfers[0].DeployHash); + Assert.AreEqual(_transferDeployHash, result.Transfers[0].TransactionHash.Deploy); } [Test, Order(8)] @@ -157,8 +160,8 @@ public async Task DelegateTokens() var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120)); var getResponse = await _client.GetDeploy(deployHash, tokenSource.Token); - - var execResult = getResponse.Parse().ExecutionResults.First(); + Console.WriteLine(getResponse.Result.GetRawText()); + var execResult = getResponse.Parse().ExecutionInfo.ExecutionResult; Assert.IsTrue(execResult.IsSuccess); } @@ -186,7 +189,7 @@ public async Task UndelegateTokens() var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120)); var getResponse = await _client.GetDeploy(deployHash, tokenSource.Token); - var execResult = getResponse.Parse().ExecutionResults.First(); + var execResult = getResponse.Parse().ExecutionInfo.ExecutionResult; Assert.IsTrue(execResult.IsSuccess); } } diff --git a/Casper.Network.SDK.Test/RPCResponses/GetAuctionInfoResultTest.cs b/Casper.Network.SDK.Test/RPCResponses/GetAuctionInfoResultTest.cs new file mode 100644 index 0000000..cb402dd --- /dev/null +++ b/Casper.Network.SDK.Test/RPCResponses/GetAuctionInfoResultTest.cs @@ -0,0 +1,83 @@ +using System.IO; +using System.Linq; +using System.Numerics; +using Casper.Network.SDK.JsonRpc.ResultTypes; +using Casper.Network.SDK.Types; +using NUnit.Framework; +using Org.BouncyCastle.Utilities.Encoders; + +namespace NetCasperTest.RPCResponses +{ + public class GetAuctionInfoResultTest + { + [Test] + public void GetBlockResultTest_v156() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-auction-info-v156.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("1.5.6", result.ApiVersion); + Assert.AreEqual("436d8e272b759838e6416d915efb3adc3a102489b8db1c0ce731043116c6f645", result.AuctionState.StateRootHash); + Assert.AreEqual(3192405, result.AuctionState.BlockHeight); + Assert.AreEqual(1, result.AuctionState.EraValidators.Count); + Assert.AreEqual(13801, result.AuctionState.EraValidators[0].EraId); + Assert.AreEqual(5, result.AuctionState.EraValidators[0].ValidatorWeights.Count); + Assert.AreEqual("020377bc3ad54b5505971e001044ea822a3f6f307f8dc93fa45a05b7463c0a053bed", result.AuctionState.EraValidators[0].ValidatorWeights[4].PublicKey.ToString().ToLower()); + Assert.AreEqual(BigInteger.Parse("10567495110201092"), result.AuctionState.EraValidators[0].ValidatorWeights[4].Weight); + + Assert.AreEqual(6, result.AuctionState.Bids.Count); + Assert.AreEqual(3, result.AuctionState.Bids.Count(b => b.Validator != null)); + Assert.AreEqual(3, result.AuctionState.Bids.Count(b => b.Delegator != null)); + Assert.AreEqual("01001b79b9a6e13d2b96e916f7fa7dff40496ba5188479263ca0fb2ccf8b714305", result.AuctionState.Bids[4].Validator.PublicKey.ToString().ToLower()); + Assert.AreEqual(BigInteger.Parse("908982507030"), result.AuctionState.Bids[4].Validator.StakedAmount); + Assert.AreEqual("01001b79b9a6e13d2b96e916f7fa7dff40496ba5188479263ca0fb2ccf8b714305", result.AuctionState.Bids[5].Delegator.ValidatorPublicKey.ToString().ToLower()); + Assert.AreEqual("018b34b15e023844531621cb52d42e216a2ea56034f0f40bf7cee566c32eae4f83", result.AuctionState.Bids[5].Delegator.DelegatorKind.PublicKey.ToString().ToLower()); + Assert.AreEqual(BigInteger.Parse("30268476029"), result.AuctionState.Bids[5].Delegator.StakedAmount); + Assert.AreEqual("uref-401f87167d733d8dd7d3efbf135a91ccd42fffb77b02d4a0075b963f14f1fbb4-007", result.AuctionState.Bids[5].Delegator.BondingPurse.ToString()); + } + + [Test] + public void GetBlockResultTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-auction-info-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + Assert.AreEqual("c8228d6d6d45151766901ba3579461847a17db15a66ce9ef6ae2f3e3abffd132", result.AuctionState.StateRootHash); + Assert.AreEqual(3480973, result.AuctionState.BlockHeight); + Assert.AreEqual(3, result.AuctionState.EraValidators.Count); + Assert.AreEqual(14912, result.AuctionState.EraValidators[2].EraId); + Assert.AreEqual(4, result.AuctionState.EraValidators[2].ValidatorWeights.Count); + Assert.AreEqual("017536433a73f7562526f3e9fcb8d720428ae2d28788a9909f3c6f637a9d848a4b", result.AuctionState.EraValidators[2].ValidatorWeights[3].PublicKey.ToString().ToLower()); + Assert.AreEqual(BigInteger.Parse("2030445261010189498"), result.AuctionState.EraValidators[2].ValidatorWeights[3].Weight); + + Assert.AreEqual(5, result.AuctionState.Bids.Count); + + Assert.IsNotNull(result.AuctionState.Bids[0].Validator); + Assert.AreEqual("01358a7e107668ae2eb092dcfbeb97d2ec3cc8354d2a77bc8f232fff6630a826c3", result.AuctionState.Bids[0].Validator.PublicKey.ToString().ToLower()); + Assert.AreEqual("uref-027d909fa0818f8b426b905795f608a6301168476b8013d7b3f682786796096f-007", result.AuctionState.Bids[0].Validator.BondingPurse.ToString()); + Assert.AreEqual(500000000000, result.AuctionState.Bids[0].Validator.MinimumDelegationAmount); + Assert.AreEqual(1000000000000000000, result.AuctionState.Bids[0].Validator.MaximumDelegationAmount); + Assert.AreEqual(3, result.AuctionState.Bids[0].Validator.ReservedSlots); + Assert.AreEqual(1, result.AuctionState.Bids[0].Validator.DelegationRate); + Assert.AreEqual(BigInteger.Parse("2500000000"), result.AuctionState.Bids[0].Validator.StakedAmount); + Assert.AreEqual(true, result.AuctionState.Bids[0].Validator.Inactive); + + Assert.IsNotNull(result.AuctionState.Bids[3].Delegator); + Assert.AreEqual("01032146b0b9de01e26aaec7b0d1769920de94681dbd432c3530bfe591752ded6c", result.AuctionState.Bids[3].Delegator.ValidatorPublicKey.ToString().ToLower()); + Assert.AreEqual("uref-d34ee21f5fe61feee2d9f15e0e369367aba62ba1b689e59c1e6ddc581ca99fdf-007", result.AuctionState.Bids[3].Delegator.BondingPurse.ToString()); + Assert.AreEqual(BigInteger.Parse("1676515877735"), result.AuctionState.Bids[3].Delegator.StakedAmount); + Assert.IsNull(result.AuctionState.Bids[3].Delegator.DelegatorKind.PublicKey); + Assert.AreEqual("8af7b77811970792f98b806779dfc0d1a9fef5bad205c6be8bb884210d7d323c", result.AuctionState.Bids[3].Delegator.DelegatorKind.Purse); + + Assert.IsNotNull(result.AuctionState.Bids[4].Unbond); + Assert.AreEqual("0106ca7c39cd272dbf21a86eeb3b36b7c26e2e9b94af64292419f7862936bca2ca", result.AuctionState.Bids[4].Unbond.Validator.ToString().ToLower()); + Assert.AreEqual(1, result.AuctionState.Bids[4].Unbond.Eras.Count); + Assert.AreEqual("uref-a3c42d782f460513f2e1ee5569208d9efa80b34adf9736e011fdf1f5ca91bc08-007", result.AuctionState.Bids[4].Unbond.Eras[0].BondingPurse.ToString()); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/RPCResponses/GetBlockResultTest.cs b/Casper.Network.SDK.Test/RPCResponses/GetBlockResultTest.cs new file mode 100644 index 0000000..16ee124 --- /dev/null +++ b/Casper.Network.SDK.Test/RPCResponses/GetBlockResultTest.cs @@ -0,0 +1,86 @@ +using System.IO; +using System.Linq; +using Casper.Network.SDK.JsonRpc.ResultTypes; +using Casper.Network.SDK.Types; +using NUnit.Framework; +using Org.BouncyCastle.Utilities.Encoders; + +namespace NetCasperTest.RPCResponses +{ + public class GetBlockResultTest + { + [Test] + public void GetBlockResultTest_v156() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-block-result-v156.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("1.5.6", result.ApiVersion); + Assert.AreEqual(3, result.Proofs.Count); + Assert.IsNotNull(result.Proofs[1].PublicKey); + AssertExtensions.IsValidHex(result.Proofs[1].Signature.ToString(), 65); + Assert.AreEqual(1, result.Block.Version); + AssertExtensions.IsHash(result.Block.Hash); + Assert.AreEqual(2947381, result.Block.Height); + Assert.AreEqual(13670, result.Block.EraId); + Assert.IsNull(result.Block.EraEnd); + Assert.IsNull(result.Block.RewardedSignatures); + Assert.AreEqual(26, result.Block.Transactions.Count); + Assert.AreEqual(25, result.Block.Transactions + .Where(t => t.Category==TransactionCategory.Large).ToList().Count); + Assert.AreEqual("31849e17f715273ad7032d51534c5b6029dd0dec1e01c225c50083752a750219", + result.Block.Transactions.First(t => t.Category==TransactionCategory.Mint).Hash); + + var blockV1 = (BlockV1)result.Block; + AssertExtensions.IsHash(blockV1.Hash); + Assert.AreEqual(2947381, blockV1.Header.Height); + Assert.AreEqual(13670, blockV1.Header.EraId); + Assert.IsNull(blockV1.Header.EraEnd); + Assert.AreEqual(1, blockV1.Body.TransferHashes.Count); + Assert.AreEqual(25, blockV1.Body.DeployHashes.Count); + } + + [Test] + public void GetBlockResultTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-block-result-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + Assert.AreEqual(5, result.Proofs.Count); + Assert.IsNotNull(result.Proofs[1].PublicKey); + AssertExtensions.IsValidHex(result.Proofs[1].Signature.ToString(), 65); + Assert.AreEqual(2, result.Block.Version); + AssertExtensions.IsHash(result.Block.Hash); + Assert.AreEqual(1551, result.Block.Height); + Assert.AreEqual(141, result.Block.EraId); + Assert.IsNotNull(result.Block.EraEnd); + var eraEnd = result.Block.EraEnd; + Assert.IsTrue(eraEnd.NextEraValidatorWeights.Count > 1); + Assert.IsNotNull(eraEnd.NextEraValidatorWeights[1].PublicKey); + Assert.IsNotNull(eraEnd.NextEraValidatorWeights[1].Weight); + Assert.IsTrue(eraEnd.Rewards.Count > 1); + Assert.AreEqual(1, result.Block.Transactions.Count); + Assert.AreEqual(TransactionCategory.InstallUpgrade, result.Block.Transactions[0].Category); + Assert.IsNotEmpty(result.Block.Transactions[0].Hash); + + var blockV2 = (BlockV2)result.Block; + AssertExtensions.IsHash(blockV2.Hash); + Assert.AreEqual(1551, blockV2.Header.Height); + Assert.AreEqual(141, blockV2.Header.EraId); + Assert.IsNotNull(blockV2.Header.EraEnd); + eraEnd = blockV2.Header.EraEnd; + Assert.IsTrue(eraEnd.NextEraValidatorWeights.Count > 1); + Assert.IsNotNull(eraEnd.NextEraValidatorWeights[1].PublicKey); + Assert.IsNotNull(eraEnd.NextEraValidatorWeights[1].Weight); + Assert.IsTrue(eraEnd.Rewards.Count > 1); + Assert.AreEqual(1, blockV2.Body.Transactions.Count); + Assert.AreEqual(TransactionCategory.InstallUpgrade, blockV2.Body.Transactions[0].Category); + Assert.IsNotEmpty(blockV2.Body.Transactions[0].Hash); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/RPCResponses/GetDeployResultTest.cs b/Casper.Network.SDK.Test/RPCResponses/GetDeployResultTest.cs new file mode 100644 index 0000000..be2ae0d --- /dev/null +++ b/Casper.Network.SDK.Test/RPCResponses/GetDeployResultTest.cs @@ -0,0 +1,79 @@ +using System.IO; +using System.Linq; +using Casper.Network.SDK.JsonRpc.ResultTypes; +using Casper.Network.SDK.Types; +using NUnit.Framework; +using Org.BouncyCastle.Utilities.Encoders; + +namespace NetCasperTest.RPCResponses +{ + public class GetDeployResultTest + { + [Test] + public void GetDeployResultTest_Version1_Success() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-deploy-result-version1-success.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + + var deploy = result.Deploy; + AssertExtensions.IsHash(deploy.Hash); + AssertExtensions.IsValidHex(deploy.Header.Account.ToAccountHex(), 33); + Assert.AreEqual(1, deploy.Payment.RuntimeArgs.Count); + Assert.AreEqual(6, deploy.Session.RuntimeArgs.Count); + Assert.AreEqual(1, deploy.Approvals.Count); + AssertExtensions.IsHash(result.ExecutionInfo.BlockHash); + Assert.AreEqual(23, result.ExecutionInfo.BlockHeight); + Assert.IsTrue(result.ExecutionInfo.ExecutionResult.Effect.Count > 0); + Assert.IsNull(result.ExecutionInfo.ExecutionResult.ErrorMessage); + } + + [Test] + public void GetDeployResultTest_Version1_Failure() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-deploy-result-version1-failure.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + + var deploy = result.Deploy; + AssertExtensions.IsHash(deploy.Hash); + AssertExtensions.IsValidHex(deploy.Header.Account.ToAccountHex(), 33); + Assert.AreEqual(1, deploy.Payment.RuntimeArgs.Count); + Assert.AreEqual(2, deploy.Session.RuntimeArgs.Count); + Assert.AreEqual(1, deploy.Approvals.Count); + AssertExtensions.IsHash(result.ExecutionInfo.BlockHash); + Assert.AreEqual(122, result.ExecutionInfo.BlockHeight); + Assert.IsTrue(result.ExecutionInfo.ExecutionResult.Effect.Count > 0); + Assert.AreEqual("User error: 60001", result.ExecutionInfo.ExecutionResult.ErrorMessage); + Assert.AreEqual("31057410", result.ExecutionInfo.ExecutionResult.Cost.ToString()); + } + + [Test] + public void GetDeployResultTest_Version2_Success() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-deploy-result-version2-success.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + + var deploy = result.Deploy; + AssertExtensions.IsHash(deploy.Hash); + AssertExtensions.IsValidHex(deploy.Header.Account.ToAccountHex(), 33); + Assert.AreEqual(1, deploy.Payment.RuntimeArgs.Count); + Assert.AreEqual(2, deploy.Session.RuntimeArgs.Count); + Assert.AreEqual(1, deploy.Approvals.Count); + AssertExtensions.IsHash(result.ExecutionInfo.BlockHash); + Assert.AreEqual(1964, result.ExecutionInfo.BlockHeight); + Assert.IsTrue(result.ExecutionInfo.ExecutionResult.Effect.Count > 0); + Assert.IsNull(result.ExecutionInfo.ExecutionResult.ErrorMessage); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/RPCResponses/GetEntityResultTest.cs b/Casper.Network.SDK.Test/RPCResponses/GetEntityResultTest.cs new file mode 100644 index 0000000..e8f810f --- /dev/null +++ b/Casper.Network.SDK.Test/RPCResponses/GetEntityResultTest.cs @@ -0,0 +1,67 @@ +using System.IO; +using Casper.Network.SDK.JsonRpc.ResultTypes; +using Casper.Network.SDK.Types; +using NUnit.Framework; + +namespace NetCasperTest.RPCResponses +{ + public class GetEntityResultTest + { + [Test] + public void GetEntityAccountTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-entity-account-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + + AssertExtensions.IsValidHex(result.MerkleProof); + + Assert.IsNotNull(result.Entity.Package); + Assert.IsNotNull(result.Entity.MainPurse); + Assert.AreEqual("byte-code-0000000000000000000000000000000000000000000000000000000000000000", result.Entity.ByteCodeHash); + Assert.IsNotEmpty(result.Entity.EntityKind.Account.ToHexString()); + Assert.AreEqual(1, result.Entity.ActionThresholds.KeyManagement); + Assert.AreEqual(1, result.Entity.ActionThresholds.Deployment); + Assert.AreEqual(1, result.Entity.ActionThresholds.UpgradeManagement); + Assert.AreEqual(0, result.NamedKeys.Count); + Assert.AreEqual(0, result.EntryPoints.Count); + } + + [Test] + public void GetEntityContractTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-entity-contract-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + + AssertExtensions.IsValidHex(result.MerkleProof); + + Assert.IsNotNull(result.Entity.Package); + Assert.IsNotNull(result.Entity.MainPurse); + Assert.AreEqual("byte-code-85def61e3ee02e10a1e845cfb8e8b2d9640a18f605333158027a24ed8569d895", result.Entity.ByteCodeHash); + Assert.AreEqual(TransactionRuntime.VmCasperV1().ToString(), result.Entity.EntityKind.SmartContract.ToString()); + Assert.AreEqual(1, result.Entity.ActionThresholds.KeyManagement); + Assert.AreEqual(1, result.Entity.ActionThresholds.Deployment); + Assert.AreEqual(1, result.Entity.ActionThresholds.UpgradeManagement); + Assert.AreEqual(11, result.NamedKeys.Count); + Assert.AreEqual("balances", result.NamedKeys[1].Name); + Assert.AreEqual("uref-96cd0453fb2e1d063c9438c158c0d804d0121a96f3423046150ee355cfadefb6-007", result.NamedKeys[1].Key.ToString().ToLower()); + + Assert.IsTrue(result.EntryPoints.Count > 0); + Assert.AreEqual("allowance", result.EntryPoints[1].V1CasperVm.Name); + Assert.AreEqual(2, result.EntryPoints[1].V1CasperVm.Args.Count); + Assert.AreEqual("spender", result.EntryPoints[1].V1CasperVm.Args[1].Name); + Assert.AreEqual(CLType.Key, result.EntryPoints[1].V1CasperVm.Args[1].CLType.Type); + Assert.AreEqual(CLType.U256, result.EntryPoints[1].V1CasperVm.Ret.Type); + Assert.IsTrue(result.EntryPoints[1].V1CasperVm.Access.IsPublic); + Assert.AreEqual(EntryPointType.Called, result.EntryPoints[1].V1CasperVm.EntryPointType); + Assert.AreEqual(EntryPointPayment.Caller, result.EntryPoints[1].V1CasperVm.EntryPointPayment); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/RPCResponses/GetNodeStatusResultTest.cs b/Casper.Network.SDK.Test/RPCResponses/GetNodeStatusResultTest.cs new file mode 100644 index 0000000..bbe2f32 --- /dev/null +++ b/Casper.Network.SDK.Test/RPCResponses/GetNodeStatusResultTest.cs @@ -0,0 +1,33 @@ +using System.IO; +using Casper.Network.SDK.JsonRpc.ResultTypes; +using Casper.Network.SDK.Types; +using NUnit.Framework; +using Org.BouncyCastle.Utilities.Encoders; + +namespace NetCasperTest.RPCResponses +{ + public class GetNodeStatusResultTest + { + [Test] + public void GetBlockResultTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/info-get-status-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("1.2.3", result.ApiVersion); + Assert.AreEqual("5.4.3", result.ProtocolVersion); + Assert.AreEqual(4, result.Peers.Count); + Assert.IsNotNull(result.Peers[1].NodeId); + Assert.IsNotNull(result.Peers[1].Address); + Assert.IsTrue(result.BuildVersion.StartsWith("2.0.0-")); + Assert.AreEqual("2.0.0-f803ee53d", result.BuildVersion); + Assert.AreEqual("casper-net-1", result.ChainspecName); + Assert.AreEqual("7ca5855e14e936019193d07055fb3390e21ec8cb75724ea756b4eac84e1b5fd7", result.StartingStateRootHash.ToLower()); + Assert.AreEqual("01fed662dc7f1f7af43ad785ba07a8cc05b7a96f9ee69613cfde43bc56bec1140b", result.OurPublicSigningKey.ToString().ToLower()); + Assert.AreEqual("643f9d06cebd07dffef47b7bfa70cc014496ff904561a78a1b5e8123247d3231", result.LatestSwitchBlockHash.ToLower()); + Assert.AreEqual("89ceb389e0e8244d8f82afacedaccb42d256831c3a036b06c0770323905d97d1", result.LastAddedBlockInfo.Hash.ToLower()); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/RPCResponses/GetPackageResultTest.cs b/Casper.Network.SDK.Test/RPCResponses/GetPackageResultTest.cs new file mode 100644 index 0000000..a8691ac --- /dev/null +++ b/Casper.Network.SDK.Test/RPCResponses/GetPackageResultTest.cs @@ -0,0 +1,52 @@ +using System.IO; +using Casper.Network.SDK.JsonRpc.ResultTypes; +using Casper.Network.SDK.Types; +using NUnit.Framework; + +namespace NetCasperTest.RPCResponses +{ + public class GetPackageResultTest + { + [Test] + public void GetPackageResultContractPackageTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-package-result-contract-package-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + Assert.IsNotNull(result.ContractPackage); + Assert.IsNull(result.Package); + Assert.AreEqual("uref-6fc684fea74b278cbb18b546a6d9242b810ce58a2ff05d17493b19aa08f540e0-007", result.ContractPackage.AccessKey.ToString()); + Assert.AreEqual(2, result.ContractPackage.Versions.Count); + Assert.AreEqual(2, result.ContractPackage.Versions[0].ProtocolVersionMajor); + Assert.AreEqual(1, result.ContractPackage.Versions[0].Version); + Assert.AreEqual(2, result.ContractPackage.DisabledVersions[0].ProtocolVersionMajor); + Assert.AreEqual(4, result.ContractPackage.DisabledVersions[0].Version); + Assert.AreEqual("contract-25aa2d3cc62a302746c08ae885454d6e8a9c8609aaa7468b24284e5d29c5d2f1", result.ContractPackage.Versions[0].Hash); + Assert.AreEqual(LockStatus.Unlocked, result.ContractPackage.LockStatus); + + var package = Package.FromContractPackage(result.ContractPackage); + Assert.AreEqual("entity-contract-25aa2d3cc62a302746c08ae885454d6e8a9c8609aaa7468b24284e5d29c5d2f1", package.Versions[0].AddressableEntity.ToString()); + } + [Test] + public void GetPackageResultPackageTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-package-result-package-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + Assert.IsNull(result.ContractPackage); + Assert.IsNotNull(result.Package); + Assert.AreEqual(1, result.Package.Versions.Count); + Assert.AreEqual(2, result.Package.Versions[0].EntityVersion.ProtocolVersionMajor); + Assert.AreEqual(1, result.Package.Versions[0].EntityVersion.Version); + Assert.AreEqual("entity-contract-e51af99d88fd26a282de00271c49a6c256232b344aa7907d2c8603b2bd5217c9", result.Package.Versions[0].AddressableEntity.ToString()); + Assert.AreEqual(EntityKindEnum.Contract, result.Package.Versions[0].AddressableEntity.Kind); + Assert.AreEqual(LockStatus.Unlocked, result.Package.LockStatus); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/RPCResponses/GetRewardResultTest.cs b/Casper.Network.SDK.Test/RPCResponses/GetRewardResultTest.cs new file mode 100644 index 0000000..b1f73bb --- /dev/null +++ b/Casper.Network.SDK.Test/RPCResponses/GetRewardResultTest.cs @@ -0,0 +1,24 @@ +using System.IO; +using Casper.Network.SDK.JsonRpc.ResultTypes; +using NUnit.Framework; + +namespace NetCasperTest.RPCResponses +{ + public class GetRewardResultTest + { + [Test] + public void GetRewardResultTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/info-get-reward-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + Assert.AreEqual(13, result.EraId); + Assert.AreEqual(1, result.DelegationRate); + Assert.AreEqual("62559062048560", result.Amount.ToString()); + Assert.AreEqual("0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f", result.SwitchBlockHash); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/RPCResponses/GetTransactionTest.cs b/Casper.Network.SDK.Test/RPCResponses/GetTransactionTest.cs new file mode 100644 index 0000000..f29905e --- /dev/null +++ b/Casper.Network.SDK.Test/RPCResponses/GetTransactionTest.cs @@ -0,0 +1,180 @@ +using System.IO; +using Casper.Network.SDK.JsonRpc.ResultTypes; +using Casper.Network.SDK.Types; +using NUnit.Framework; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Utilities.Encoders; + +namespace NetCasperTest.RPCResponses +{ + public class GetTransactionTest + { + [Test] + public void GetTransactionWithDeployHashTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-transaction-deploy-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + + Assert.IsNotNull(result.Transaction); + var deploy = (Deploy)result.Transaction; + Assert.NotNull(deploy); + Assert.AreEqual(deploy.Hash, result.Transaction.Hash); + Assert.AreEqual(deploy.Header.ChainName, result.Transaction.ChainName); + Assert.AreEqual(deploy.Header.Timestamp, result.Transaction.Timestamp); + Assert.AreEqual(deploy.Header.Account, result.Transaction.InitiatorAddr.PublicKey); + Assert.AreEqual("c5cf594dc6d244358fddd6461e93703653bb07a0d9fa957c4a4b5fdc5bc9968d", deploy.Header.BodyHash); + Assert.IsNull(result.Transaction.InitiatorAddr.AccountHash); + Assert.IsTrue(result.Transaction.Invocation is Transaction.NativeTransactionInvocation); + Assert.AreEqual(NativeEntryPoint.Transfer, (result.Transaction.Invocation as Transaction.NativeTransactionInvocation)!.Type); + Assert.IsTrue(result.Transaction.Scheduling is StandardTransactionScheduling); + Assert.AreEqual(deploy.Approvals.Count, result.Transaction.Approvals.Count); + Assert.AreEqual(deploy.Approvals[0].Signature, result.Transaction.Approvals[0].Signature); + + AssertExtensions.IsHash(result.ExecutionInfo.BlockHash); + Assert.IsTrue(result.ExecutionInfo.BlockHeight > 0); + Assert.IsNotNull(result.ExecutionInfo.ExecutionResult); + Assert.AreEqual(1, result.ExecutionInfo.ExecutionResult.CurrentGasPrice); + Assert.AreEqual("30000", result.ExecutionInfo.ExecutionResult.Refund.ToString()); + } + + [Test] + public void GetDeployWithDeployHashTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-deploy-deploy-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + + var deploy = result.Deploy; + Assert.NotNull(deploy); + + var transaction = (Transaction)deploy; + Assert.IsNotNull(transaction); + + Assert.AreEqual(deploy.Hash, transaction.Hash); + Assert.AreEqual(deploy.Header.ChainName, transaction.ChainName); + Assert.AreEqual(deploy.Header.Timestamp, transaction.Timestamp); + Assert.AreEqual(deploy.Header.Account, transaction.InitiatorAddr.PublicKey); + Assert.AreEqual("c5cf594dc6d244358fddd6461e93703653bb07a0d9fa957c4a4b5fdc5bc9968d", deploy.Header.BodyHash); + Assert.IsNull(transaction.InitiatorAddr.AccountHash); + Assert.IsTrue(transaction.Invocation is Transaction.NativeTransactionInvocation); + Assert.AreEqual(NativeEntryPoint.Transfer, (transaction.Invocation as Transaction.NativeTransactionInvocation)!.Type); + Assert.IsTrue(transaction.Scheduling is StandardTransactionScheduling); + Assert.AreEqual(deploy.Approvals.Count, transaction.Approvals.Count); + Assert.AreEqual(deploy.Approvals[0].Signature, transaction.Approvals[0].Signature); + + AssertExtensions.IsHash(result.ExecutionInfo.BlockHash); + Assert.IsTrue(result.ExecutionInfo.BlockHeight > 0); + Assert.IsNotNull(result.ExecutionInfo.ExecutionResult); + } + + [Test] + public void GetTransactionWithSessionTransactionTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-transaction-session-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + + var transaction = result.Transaction; + Assert.NotNull(transaction); + + var transactionV1 = (TransactionV1)transaction; + Assert.IsNotNull(transactionV1); + + Assert.AreEqual(transactionV1.Hash, transaction.Hash); + Assert.AreEqual(transactionV1.Payload.ChainName, transaction.ChainName); + Assert.AreEqual(transactionV1.Payload.Timestamp, transaction.Timestamp); + Assert.AreEqual(transactionV1.Payload.InitiatorAddr.PublicKey, transaction.InitiatorAddr.PublicKey); + Assert.IsNull(transaction.InitiatorAddr.AccountHash); + Assert.IsTrue(transaction.Invocation is Transaction.SessionTransactionInvocation); + Assert.AreEqual("01020304", Hex.ToHexString((transaction.Invocation as Transaction.SessionTransactionInvocation)!.Wasm)); + Assert.IsTrue(transaction.Scheduling is StandardTransactionScheduling); + Assert.IsTrue(transaction.PricingMode is PaymentLimitedPricingMode); + Assert.AreEqual(1, ((PaymentLimitedPricingMode)transaction.PricingMode).GasPriceTolerance); + Assert.AreEqual(transactionV1.Approvals.Count, transaction.Approvals.Count); + Assert.AreEqual(transactionV1.Approvals[0].Signature, transaction.Approvals[0].Signature); + + AssertExtensions.IsHash(result.ExecutionInfo.BlockHash); + Assert.IsTrue(result.ExecutionInfo.BlockHeight > 0); + Assert.IsNotNull(result.ExecutionInfo.ExecutionResult); + } + + [Test] + public void GetTransactionWithStoredTransactionTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-transaction-stored-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + + var transaction = result.Transaction; + Assert.NotNull(transaction); + + var transactionV1 = (TransactionV1)transaction; + Assert.IsNotNull(transactionV1); + + Assert.AreEqual(transactionV1.Hash, transaction.Hash); + Assert.AreEqual(transactionV1.Payload.ChainName, transaction.ChainName); + Assert.AreEqual(transactionV1.Payload.Timestamp, transaction.Timestamp); + Assert.AreEqual(transactionV1.Payload.InitiatorAddr.AccountHash, transaction.InitiatorAddr.AccountHash); + Assert.AreEqual("01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a", transaction.InitiatorAddr.PublicKey.ToString().ToLower()); + Assert.IsNull(transaction.InitiatorAddr.AccountHash); + Assert.IsTrue(transaction.Invocation is Transaction.StoredTransactionInvocation); + Assert.AreEqual("transfer", (transaction.Invocation as Transaction.StoredTransactionInvocation)!.EntryPoint); + Assert.IsTrue(transaction.Scheduling is StandardTransactionScheduling); + Assert.AreEqual(transactionV1.Approvals.Count, transaction.Approvals.Count); + Assert.AreEqual(transactionV1.Approvals[0].Signature, transaction.Approvals[0].Signature); + + AssertExtensions.IsHash(result.ExecutionInfo.BlockHash); + Assert.IsTrue(result.ExecutionInfo.BlockHeight > 0); + Assert.IsNotNull(result.ExecutionInfo.ExecutionResult); + Assert.AreEqual(2, result.ExecutionInfo.ExecutionResult.CurrentGasPrice); + Assert.AreEqual("2123123123", result.ExecutionInfo.ExecutionResult.Refund.ToString()); + } + + [Test] + public void GetTransactionWithStoredDeployTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/get-transaction-deploy-stored-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + + var transaction = result.Transaction; + Assert.NotNull(transaction); + + var deploy = (Deploy)transaction; + Assert.IsNotNull(deploy); + + Assert.AreEqual(deploy.Hash, transaction.Hash); + Assert.AreEqual("8d18c7a727317c54fb8ac21ba2e02ec8f4d24d0f4cb4183d3df05afe86ba614c", deploy.Header.BodyHash); + Assert.AreEqual(deploy.Header.ChainName, transaction.ChainName); + Assert.AreEqual(deploy.Header.Timestamp, transaction.Timestamp); + Assert.AreEqual(deploy.Header.Account, transaction.InitiatorAddr.PublicKey); + Assert.AreEqual("0106ed45915392c02b37136618372ac8dde8e0e3b8ee6190b2ca6db539b354ede4", transaction.InitiatorAddr.PublicKey.ToString().ToLower()); + Assert.IsNull(transaction.InitiatorAddr.AccountHash); + Assert.IsTrue(transaction.Invocation is Transaction.StoredTransactionInvocation); + Assert.AreEqual("transfer", (transaction.Invocation as Transaction.StoredTransactionInvocation)!.EntryPoint); + Assert.IsTrue(transaction.Scheduling is StandardTransactionScheduling); + Assert.AreEqual(deploy.Approvals.Count, transaction.Approvals.Count); + Assert.AreEqual(deploy.Approvals[0].Signature, transaction.Approvals[0].Signature); + + AssertExtensions.IsHash(result.ExecutionInfo.BlockHash); + Assert.IsTrue(result.ExecutionInfo.BlockHeight > 0); + Assert.IsNotNull(result.ExecutionInfo.ExecutionResult); + } + } +} diff --git a/Casper.Network.SDK.Test/RPCResponses/QueryBalanceDetailsResultTest.cs b/Casper.Network.SDK.Test/RPCResponses/QueryBalanceDetailsResultTest.cs new file mode 100644 index 0000000..099062f --- /dev/null +++ b/Casper.Network.SDK.Test/RPCResponses/QueryBalanceDetailsResultTest.cs @@ -0,0 +1,27 @@ +using System.IO; +using Casper.Network.SDK.JsonRpc.ResultTypes; +using NUnit.Framework; + +namespace NetCasperTest.RPCResponses +{ + public class QueryBalanceDetailsResultTest + { + [Test] + public void GetBlockResultTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/query-balance-details-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + Assert.AreEqual(7, result.Holds.Count); + Assert.IsTrue(result.Holds[1].Amount > 0); + Assert.IsTrue(result.Holds[1].Time > 0); + Assert.IsFalse(string.IsNullOrWhiteSpace(result.Holds[1].Proof)); + Assert.IsTrue(result.TotalBalance > 0); + Assert.IsTrue(result.AvailableBalance > 0); + Assert.IsFalse(string.IsNullOrWhiteSpace(result.TotalBalanceProof)); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/RPCResponses/QueryBalanceResultTest.cs b/Casper.Network.SDK.Test/RPCResponses/QueryBalanceResultTest.cs new file mode 100644 index 0000000..59ba094 --- /dev/null +++ b/Casper.Network.SDK.Test/RPCResponses/QueryBalanceResultTest.cs @@ -0,0 +1,21 @@ +using System.IO; +using Casper.Network.SDK.JsonRpc.ResultTypes; +using NUnit.Framework; + +namespace NetCasperTest.RPCResponses +{ + public class GetBalanceResultTest + { + [Test] + public void GetBlockResultTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/query-balance-v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + Assert.AreEqual("1576039612769", result.BalanceValue.ToString()); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/RPCResponses/QueryGlobalStateResultTest.cs b/Casper.Network.SDK.Test/RPCResponses/QueryGlobalStateResultTest.cs new file mode 100644 index 0000000..6addb97 --- /dev/null +++ b/Casper.Network.SDK.Test/RPCResponses/QueryGlobalStateResultTest.cs @@ -0,0 +1,30 @@ +using System.IO; +using System.Numerics; +using Casper.Network.SDK.JsonRpc.ResultTypes; +using NUnit.Framework; + +namespace NetCasperTest.RPCResponses +{ + public class QueryGlobalStateResultTest + { + [Test] + public void LegacyTransferTest_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/legacy_transfer_v200.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + Assert.AreEqual("2.0.0", result.ApiVersion); + Assert.IsNotNull(result.StoredValue.Transfer); + var transfer = result.StoredValue.Transfer; + Assert.AreEqual(new BigInteger(2500000000000), transfer.Amount); + Assert.AreEqual("cd91f138e82ddce5dfbb99c6bbf3f47caca439b81d7f43702cbebfa99bacbfd0", transfer.TransactionHash.Deploy); + Assert.AreEqual("account-hash-87516c22bca9a14179ebbbe646c8f911153fe53626126c1ba24293517c2e04a2", transfer.From.AccountHash.ToString()); + Assert.AreEqual("account-hash-1265caa7cd80f31f882ab6f5623d89741c77cf3a36a309b54ed55fdc0be227c9", transfer.To.ToString()); + Assert.AreEqual("uref-c003c853c6477137225914c7610cf2c30ed594480c1fccd6dfd58a633522edc6-007", transfer.Source.ToString()); + Assert.AreEqual("uref-c74ad99f2bca136c1e64b1a14bf576696363c25701d66d87adca6dbdb147b39d-004", transfer.Target.ToString()); + Assert.IsFalse(string.IsNullOrWhiteSpace(result.MerkleProof)); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/RpcResultTest.cs b/Casper.Network.SDK.Test/RpcResultTest.cs index 1d42b19..4dfd35a 100644 --- a/Casper.Network.SDK.Test/RpcResultTest.cs +++ b/Casper.Network.SDK.Test/RpcResultTest.cs @@ -41,29 +41,29 @@ public void SuccessExecutionResultDeserializerTest() JsonSerializerOptions options = new JsonSerializerOptions() { WriteIndented = true, - Converters = {new ExecutionResult.ExecutionResultConverter()} + Converters = {new ExecutionResultV1.ExecutionResultV1Converter()} }; - var results = JsonSerializer.Deserialize(json, options); - Assert.IsNotNull(results); - Assert.IsTrue(results.IsSuccess); - Assert.IsNull(results.ErrorMessage); - Assert.AreEqual(System.Numerics.BigInteger.Parse("42905260"), results.Cost); - Assert.AreEqual(0, results.Transfers.Count); + var resultsV1 = JsonSerializer.Deserialize(json, options); + Assert.IsNotNull(resultsV1); + Assert.IsTrue(resultsV1.IsSuccess); + Assert.IsNull(resultsV1.ErrorMessage); + Assert.AreEqual(System.Numerics.BigInteger.Parse("42905260"), resultsV1.Cost); + Assert.AreEqual(0, resultsV1.Transfers.Count); - Assert.AreEqual(10, results.Effect.Operations.Count); + Assert.AreEqual(10, resultsV1.Effect.Operations.Count); Assert.AreEqual("hash-efef434ae3cea598a8871ac40ac5d5b00f0f4babe8f7b49a05f87d203d1c8391", - results.Effect.Operations[0].Key.ToString().ToLower()); - Assert.AreEqual(OpKind.Read, results.Effect.Operations[0].Kind); + resultsV1.Effect.Operations[0].Key.ToString().ToLower()); + Assert.AreEqual(OpKind.Read, resultsV1.Effect.Operations[0].Kind); Assert.AreEqual("balance-d86ed76303a691d12cf121e4b4cb4fd875484f28a33c2edf14aad56c01c8c601", - results.Effect.Operations[9].Key.ToString().ToLower()); - Assert.AreEqual(OpKind.Write, results.Effect.Operations[9].Kind); + resultsV1.Effect.Operations[9].Key.ToString().ToLower()); + Assert.AreEqual(OpKind.Write, resultsV1.Effect.Operations[9].Kind); - Assert.AreEqual(10, results.Effect.Transforms.Count); + Assert.AreEqual(10, resultsV1.Effect.Transforms.Count); Assert.AreEqual("hash-98eca31f263ff3176518b6dcbb6af54c7469b60192749feee073d20618a389e6", - results.Effect.Transforms[0].Key.ToString().ToLower() + resultsV1.Effect.Transforms[0].Key.ToString().ToLower() ); Assert.AreEqual("uref-a3a79be7bd922c0846ed406b01f2430dd6ba367a2703b8d75109a9f41fdc336b-000", - results.Effect.Transforms[9].Key.ToString().ToLower() + resultsV1.Effect.Transforms[9].Key.ToString().ToLower() ); } @@ -76,19 +76,19 @@ public void FailureExecutionResultDeserializerTest() JsonSerializerOptions options = new JsonSerializerOptions() { WriteIndented = true, - Converters = {new ExecutionResult.ExecutionResultConverter()} + Converters = {new ExecutionResultV1.ExecutionResultV1Converter()} }; - var results = JsonSerializer.Deserialize(json, options); - Assert.IsNotNull(results); - Assert.IsFalse(results.IsSuccess); - Assert.IsNotNull(results.ErrorMessage); + var resultsV1 = JsonSerializer.Deserialize(json, options); + Assert.IsNotNull(resultsV1); + Assert.IsFalse(resultsV1.IsSuccess); + Assert.IsNotNull(resultsV1.ErrorMessage); Assert.AreEqual( "Wasm preprocessing error: Encountered operation forbidden by gas rules. Consult instruction -> metering config map", - results.ErrorMessage); - Assert.AreEqual(BigInteger.Zero, results.Cost); - Assert.AreEqual(0, results.Transfers.Count); - Assert.AreEqual(0, results.Effect.Operations.Count); - Assert.AreEqual(0, results.Effect.Transforms.Count); + resultsV1.ErrorMessage); + Assert.AreEqual(BigInteger.Zero, resultsV1.Cost); + Assert.AreEqual(0, resultsV1.Transfers.Count); + Assert.AreEqual(0, resultsV1.Effect.Operations.Count); + Assert.AreEqual(0, resultsV1.Effect.Transforms.Count); } [Test] diff --git a/Casper.Network.SDK.Test/SSETypesTest.cs b/Casper.Network.SDK.Test/SSETypesTest.cs new file mode 100644 index 0000000..20a71c0 --- /dev/null +++ b/Casper.Network.SDK.Test/SSETypesTest.cs @@ -0,0 +1,101 @@ +using System.IO; +using System.Text.Json; +using Casper.Network.SDK.SSE; +using NUnit.Framework; + +namespace NetCasperTest +{ + public class SSETypesTest + { + [Test] + public void FinalitySignatureV1Test() + { + string testFile = TestContext.CurrentContext.TestDirectory + "/TestData/finality_signature_v156.json"; + var json = File.ReadAllText(testFile); + + var doc = System.Text.Json.JsonDocument.Parse(json); + + json = doc.RootElement.GetProperty("FinalitySignature").GetRawText(); + // Assert.AreEqual(3, doc.RootElement.EnumerateObject().Count()); + // Assert.AreEqual("U512", ); + var value = JsonSerializer.Deserialize(json); + Assert.IsNotNull(value); + + Assert.AreEqual(1, value.Version); + Assert.AreEqual("d800de72aa40c6df064c714d3a6d8b6fab73f68742d8468b84efd6616bbb10bb", value.BlockHash); + Assert.AreEqual(0, value.BlockHeight); + Assert.AreEqual(13859, value.EraId); + Assert.IsNull(value.ChainNameHash); + Assert.AreEqual( + "01221b61b83b889898363501c7defd7baa6729989d8fce2dfffea4632017fd46cc34b88bc85fa570a6e9ada829c67b9fdaa78e8dee2f07d346bcad0010e1d3df0f", + value.Signature.ToHexString().ToLowerInvariant()); + Assert.AreEqual("01b71b2d746681b4e0f44ef137d72ee0d42122b08ef569dc65bb0395cb624f99e5", + value.PublicKey.ToString().ToLowerInvariant()); + + var v1 = (FinalitySignatureV1)value; + + Assert.AreEqual("d800de72aa40c6df064c714d3a6d8b6fab73f68742d8468b84efd6616bbb10bb", v1.BlockHash); + Assert.AreEqual(13859, v1.EraId); + Assert.AreEqual( + "01221b61b83b889898363501c7defd7baa6729989d8fce2dfffea4632017fd46cc34b88bc85fa570a6e9ada829c67b9fdaa78e8dee2f07d346bcad0010e1d3df0f", + v1.Signature.ToHexString().ToLowerInvariant()); + Assert.AreEqual("01b71b2d746681b4e0f44ef137d72ee0d42122b08ef569dc65bb0395cb624f99e5", + v1.PublicKey.ToString().ToLowerInvariant()); + } + + [Test] + public void FinalitySignatureV2Test() + { + string testFile = TestContext.CurrentContext.TestDirectory + "/TestData/finality_signature_v2.json"; + var json = File.ReadAllText(testFile); + + var doc = System.Text.Json.JsonDocument.Parse(json); + + json = doc.RootElement.GetProperty("FinalitySignature").GetRawText(); + // Assert.AreEqual(3, doc.RootElement.EnumerateObject().Count()); + // Assert.AreEqual("U512", ); + var value = JsonSerializer.Deserialize(json); + Assert.IsNotNull(value); + + Assert.AreEqual(2, value.Version); + Assert.AreEqual("13a5603c1dad7d4e4d2ce81313c35043172e1535363c3ec428f559dff5704ea5", value.BlockHash); + Assert.AreEqual(54, value.BlockHeight); + Assert.AreEqual(5, value.EraId); + Assert.AreEqual("8a09603fc862b15412b60a050d71f69c57601b6da7382dd56b9a3f300822bb75", value.ChainNameHash); + Assert.AreEqual( + "01a12e3601b4d5c82177231910adaabf50d8a52416e756efee1694ee7534ae16fdb59a7370d564715615736074850b9255ee246c8ffa2502c167c6ef8a86b06504", + value.Signature.ToHexString().ToLowerInvariant()); + Assert.AreEqual("01fed662dc7f1f7af43ad785ba07a8cc05b7a96f9ee69613cfde43bc56bec1140b", + value.PublicKey.ToString().ToLowerInvariant()); + } + + [Test] + public void TransactionProcessedTest() + { + string testFile = TestContext.CurrentContext.TestDirectory + + "/TestData/sse-transaction-processed-v200.json"; + var json = File.ReadAllText(testFile); + + var value = JsonSerializer.Deserialize(json); + Assert.IsNotNull(value); + + Assert.AreEqual("9749ce1dc5dbd0d9a611088f934fd81c2c8429dbab0a3a7d281359be0a92d29a", + value.TransactionHash.Version1); + Assert.AreEqual("01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a", + value.InitiatorAddr.PublicKey.ToString().ToLower()); + Assert.AreEqual("0dabde3e8b065e734247b7d5328ac18317af9842f0141ffe41173df15efd97a8", value.BlockHash); + Assert.IsTrue(value.ExecutionResult.Effect.Count > 0); + + Assert.AreEqual(1, value.Messages.Count); + var message = value.Messages[0]; + Assert.AreEqual("9038763d3066f0217047263ebd48dc7c839fadfdde141f5b990866563655b44a", + message.HashAddr); + Assert.AreEqual("dummy-data", message.MessagePayload.String); + Assert.IsNull(message.MessagePayload.Bytes); + Assert.AreEqual("events", message.TopicName); + Assert.AreEqual("5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1", message.TopicNameHash); + Assert.AreEqual(1, message.TopicIndex); + Assert.AreEqual(2, message.BlockIndex); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/ServerEventsClientTest.cs b/Casper.Network.SDK.Test/ServerEventsClientTest.cs index 9c06fe8..bba60af 100644 --- a/Casper.Network.SDK.Test/ServerEventsClientTest.cs +++ b/Casper.Network.SDK.Test/ServerEventsClientTest.cs @@ -103,7 +103,8 @@ private async Task MakeTransfer() var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120)); var getResponse = await _client.GetDeploy(deploy.Hash, tokenSource.Token); - var execResult = getResponse.Parse().ExecutionResults.First(); + var execInfo = getResponse.Parse().ExecutionInfo; + var execResult = (ExecutionResultV1)execInfo.ExecutionResult; Assert.IsTrue(execResult.IsSuccess); } @@ -167,10 +168,10 @@ public async Task SSEListen() await MakeTransfer(); int retries = 0; - while(nDeployProcessed == 0 && retries++ < 5) + while(nDeployProcessed == 0 && retries++ < 3) Thread.Sleep(5000); - sse.StopListening().Wait(); + await sse.StopListening(); Assert.IsTrue(nApiVersion > 0); Assert.IsTrue(nBlocks > 0); diff --git a/Casper.Network.SDK.Test/StoredValueTest.cs b/Casper.Network.SDK.Test/StoredValueTest.cs new file mode 100644 index 0000000..0954e44 --- /dev/null +++ b/Casper.Network.SDK.Test/StoredValueTest.cs @@ -0,0 +1,44 @@ +using System.IO; +using Casper.Network.SDK.Types; +using NUnit.Framework; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace NetCasperTest +{ + public class StoredValueTest + { + [Test] + public void SeigniorageAllocationsTest_V200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/seigniorage_allocation_v200.json"); + + var storedValue = JsonSerializer.Deserialize(json); + Assert.IsNotNull(storedValue); + Assert.IsNotNull(storedValue.EraInfo); + Assert.AreEqual(20, storedValue.EraInfo.SeigniorageAllocations.Count); + Assert.IsTrue(storedValue.EraInfo.SeigniorageAllocations[0].IsDelegator); + Assert.AreEqual("018b46617b2b97e633b36530f2964b3f4c15916235910a2737e83d4fa2c7fad542", storedValue.EraInfo.SeigniorageAllocations[0].DelegatorKind.PublicKey.ToString().ToLower()); + Assert.AreEqual("01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6", storedValue.EraInfo.SeigniorageAllocations[0].ValidatorPublicKey.ToString().ToLower()); + Assert.AreEqual("2515330120214391", storedValue.EraInfo.SeigniorageAllocations[0].Amount.ToString()); + Assert.IsFalse(storedValue.EraInfo.SeigniorageAllocations[1].IsDelegator); + } + + [Test] + public void SeigniorageAllocationsTest_V158() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/seigniorage_allocation_v158.json"); + + var storedValue = JsonSerializer.Deserialize(json); + Assert.IsNotNull(storedValue); + Assert.IsNotNull(storedValue.EraInfo); + Assert.AreEqual(10, storedValue.EraInfo.SeigniorageAllocations.Count); + Assert.IsTrue(storedValue.EraInfo.SeigniorageAllocations[0].IsDelegator); + Assert.AreEqual("018b46617b2b97e633b36530f2964b3f4c15916235910a2737e83d4fa2c7fad542", storedValue.EraInfo.SeigniorageAllocations[0].DelegatorKind.PublicKey.ToString().ToLower()); + Assert.AreEqual("01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6", storedValue.EraInfo.SeigniorageAllocations[0].ValidatorPublicKey.ToString().ToLower()); + Assert.AreEqual("4414692857142857", storedValue.EraInfo.SeigniorageAllocations[0].Amount.ToString()); + Assert.IsFalse(storedValue.EraInfo.SeigniorageAllocations[1].IsDelegator); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/block_added_v2.json b/Casper.Network.SDK.Test/TestData/block_added_v2.json new file mode 100644 index 0000000..9a33854 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/block_added_v2.json @@ -0,0 +1,44 @@ +{ + "BlockAdded": { + "block_hash": "13a5603c1dad7d4e4d2ce81313c35043172e1535363c3ec428f559dff5704ea5", + "block": { + "Version2": { + "hash": "13a5603c1dad7d4e4d2ce81313c35043172e1535363c3ec428f559dff5704ea5", + "header": { + "parent_hash": "8d14ccfde187f71c0d0b6a2e51298e851521a52e6df21ff8c572412477498106", + "state_root_hash": "a8a33e0b5a36950eda1ef918cb959a6bb0a56aeca356b6d670003d041bb348e9", + "body_hash": "337a4c9e510e01e142a19e5d81203bdc43e59a4f9039288c01f7b89370e1d104", + "random_bit": false, + "accumulated_seed": "b86abf8486be8b34621ac2d519886e894e1bef647b556e42a2d4c8b3104270a1", + "era_end": null, + "timestamp": "2024-05-22T07:22:36.684Z", + "era_id": 5, + "height": 54, + "protocol_version": "2.0.0", + "proposer": "01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6", + "current_gas_price": 1, + "last_switch_block_hash": "1d0103963e72d14e760106267d28f4f20ef2eb08dc8a0101e5f18e6f18859af0" + }, + "body": { + "transactions": { + "0": [], + "1": [], + "2": [], + "3": [] + }, + "rewarded_signatures": [ + [ + 248 + ], + [ + 0 + ], + [ + 0 + ] + ] + } + } + } + } +} diff --git a/Casper.Network.SDK.Test/TestData/deploy-testing-effects-v156.json b/Casper.Network.SDK.Test/TestData/deploy-testing-effects-v156.json new file mode 100644 index 0000000..4dc2e8f --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/deploy-testing-effects-v156.json @@ -0,0 +1,235 @@ +{ + "api_version": "1.5.7", + "deploy": { + "hash": "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f", + "header": { + "account": "0203000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f", + "timestamp": "2020-01-01T00:00:00.000Z", + "ttl": "30m", + "gas_price": 1, + "body_hash": "0101010101010101010101010101010101010101010101010101010101010101", + "dependencies": [], + "chain_name": "casper-test" + }, + "payment": { + "ModuleBytes": { + "module_bytes": "", + "args": [ + [ + "amount", + { + "cl_type": "U512", + "bytes": "050010a5d4e8", + "parsed": "1000000000000" + } + ] + ] + } + }, + "session": { + "ModuleBytes": { + "module_bytes": "0001020304", + "args": [ + [ + "decimals", + { + "cl_type": "U8", + "bytes": "12", + "parsed": 18 + } + ] + ] + } + }, + "approvals": [ + { + "signer": "0203000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f", + "signature": "02000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f" + } + ] + }, + "execution_results": [ + { + "block_hash": "0202020202020202020202020202020202020202020202020202020202020202", + "result": { + "Success": { + "effect": { + "operations": [], + "transforms": [ + { + "key": "account-hash-0303030303030303030303030303030303030303030303030303030303030303", + "transform": "Identity" + }, + { + "key": "hash-0303030303030303030303030303030303030303030303030303030303030303", + "transform": "Identity" + }, + { + "key": "balance-0303030303030303030303030303030303030303030303030303030303030303", + "transform": { + "WriteCLValue": { + "cl_type": "U512", + "bytes": "050010a5d4e8", + "parsed": "1000000000000" + } + } + }, + { + "key": "balance-0909090909090909090909090909090909090909090909090909090909090909", + "transform": { + "AddUInt64": 555 + } + }, + { + "key": "balance-0303030303030303030303030303030303030303030303030303030303030303", + "transform": { + "AddUInt512": "1000000000000" + } + }, + { + "key": "uref-0303030303030303030303030303030303030303030303030303030303030303-000", + "transform": { + "WriteCLValue": { + "cl_type": "String", + "bytes": "030000004a4a4a", + "parsed": "JJJ" + } + } + }, + { + "key": "uref-0303030303030303030303030303030303030303030303030303030303030303-000", + "transform": { + "WriteCLValue": { + "cl_type": "U8", + "bytes": "12", + "parsed": 18 + } + } + }, + { + "key": "uref-0303030303030303030303030303030303030303030303030303030303030303-000", + "transform": { + "WriteCLValue": { + "cl_type": "Unit", + "bytes": "", + "parsed": null + } + } + }, + { + "key": "hash-0505050505050505050505050505050505050505050505050505050505050505", + "transform": "WriteContractPackage" + }, + { + "key": "account-hash-0505050505050505050505050505050505050505050505050505050505050505", + "transform": { + "AddKeys": [ + { + "name": "cep18_contract_package_JJJ", + "key": "hash-0505050505050505050505050505050505050505050505050505050505050505" + } + ] + } + }, + { + "key": "hash-0707070707070707070707070707070707070707070707070707070707070707", + "transform": "WriteContractWasm" + }, + { + "key": "hash-0808080808080808080808080808080808080808080808080808080808080808", + "transform": "WriteContract" + }, + { + "key": "hash-0909090909090909090909090909090909090909090909090909090909090909", + "transform": "WriteContractPackage" + }, + { + "key": "transfer-0101010101010101010101010101010101010101010101010101010101010101", + "transform": { + "WriteTransfer": { + "deploy_hash": "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f", + "from": "account-hash-0101010101010101010101010101010101010101010101010101010101010101", + "to": "account-hash-0202020202020202020202020202020202020202020202020202020202020202", + "source": "uref-0101010101010101010101010101010101010101010101010101010101010101-007", + "target": "uref-0202020202020202020202020202020202020202020202020202020202020202-004", + "amount": "777777777", + "gas": "0", + "id": 9 + } + } + }, + { + "key": "deploy-000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f", + "transform": { + "WriteDeployInfo": { + "deploy_hash": "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f", + "transfers": [ + "transfer-0101010101010101010101010101010101010101010101010101010101010101" + ], + "from": "account-hash-0101010101010101010101010101010101010101010101010101010101010101", + "source": "uref-0101010101010101010101010101010101010101010101010101010101010101-007", + "gas": "555555555555" + } + } + }, + { + "key": "bid-0101010101010101010101010101010101010101010101010101010101010101", + "transform": { + "WriteBid": { + "validator_public_key": "010808080808080808080808080808080808080808080808080808080808080808", + "bonding_purse": "uref-0808080808080808080808080808080808080808080808080808080808080808-007", + "staked_amount": "1000000000000", + "delegation_rate": 10, + "vesting_schedule": { + "initial_release_timestamp_millis": 1722581889000, + "locked_amounts": [ + "1300000000000000", + "1200000000000000", + "0" + ] + }, + "delegators": { + + "02030909090909090909090909090909090909090909090909090909090909090909": { + "delegator_public_key": "02030909090909090909090909090909090909090909090909090909090909090909", + "staked_amount": "4000000000000", + "bonding_purse": "uref-0909090909090909090909090909090909090909090909090909090909090909-007", + "validator_public_key": "010808080808080808080808080808080808080808080808080808080808080808", + "vesting_schedule": null + }, + "02030a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a": { + "delegator_public_key": "02030a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a", + "staked_amount": "5000000000000", + "bonding_purse": "uref-0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a-007", + "validator_public_key": "010808080808080808080808080808080808080808080808080808080808080808", + "vesting_schedule": null + } + }, + "inactive": false + } + } + }, + { + "key": "unbond-0101010101010101010101010101010101010101010101010101010101010101", + "transform": { + "WriteUnbonding": [ + { + "bonding_purse": "uref-0808080808080808080808080808080808080808080808080808080808080808-007", + "validator_public_key": "010808080808080808080808080808080808080808080808080808080808080808", + "unbonder_public_key": "010808080808080808080808080808080808080808080808080808080808080808", + "era_of_creation": 10, + "amount": "600600600600600", + "new_validator": null + } + ] + } + } + ] + }, + "transfers": [], + "cost": "600600600" + } + } + } + ] +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/finality_signature_v156.json b/Casper.Network.SDK.Test/TestData/finality_signature_v156.json new file mode 100644 index 0000000..25a0d75 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/finality_signature_v156.json @@ -0,0 +1,8 @@ +{ + "FinalitySignature": { + "block_hash": "d800de72aa40c6df064c714d3a6d8b6fab73f68742d8468b84efd6616bbb10bb", + "era_id": 13859, + "signature": "01221b61b83b889898363501c7defd7baa6729989d8fce2dfffea4632017fd46cc34b88bc85fa570a6e9ada829c67b9fdaa78e8dee2f07d346bcad0010e1d3df0f", + "public_key": "01b71b2d746681b4e0f44ef137d72ee0d42122b08ef569dc65bb0395cb624f99e5" + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/finality_signature_v2.json b/Casper.Network.SDK.Test/TestData/finality_signature_v2.json new file mode 100644 index 0000000..6d94fee --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/finality_signature_v2.json @@ -0,0 +1,12 @@ +{ + "FinalitySignature": { + "V2": { + "block_hash": "13a5603c1dad7d4e4d2ce81313c35043172e1535363c3ec428f559dff5704ea5", + "block_height": 54, + "era_id": 5, + "chain_name_hash": "8a09603fc862b15412b60a050d71f69c57601b6da7382dd56b9a3f300822bb75", + "signature": "01a12e3601b4d5c82177231910adaabf50d8a52416e756efee1694ee7534ae16fdb59a7370d564715615736074850b9255ee246c8ffa2502c167c6ef8a86b06504", + "public_key": "01fed662dc7f1f7af43ad785ba07a8cc05b7a96f9ee69613cfde43bc56bec1140b" + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/get-auction-info-v156.json b/Casper.Network.SDK.Test/TestData/get-auction-info-v156.json new file mode 100644 index 0000000..b47f441 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-auction-info-v156.json @@ -0,0 +1,87 @@ +{ + "api_version": "1.5.6", + "auction_state": { + "state_root_hash": "436d8e272b759838e6416d915efb3adc3a102489b8db1c0ce731043116c6f645", + "block_height": 3192405, + "era_validators": [ + { + "era_id": 13801, + "validator_weights": [ + { + "public_key": "01ee3d205fe314de0739fac7264c97af5ccc4cffec391cae5bcb6f87e2dc2df079", + "weight": "1324008752218119" + }, + { + "public_key": "01f0c453c8f47ce5348227b59da3555f7e487477aab5f254f62216ad85df475c74", + "weight": "24633294030105" + }, + { + "public_key": "01f340df2c32f25391e8f7924a99e93cab3a6f230ff7af1cacbfc070772cbebd94", + "weight": "11456292357472638" + }, + { + "public_key": "01fe19d0fd010dfecc4f2a812c396bef932cd8f11b3855cc5ec3f6bd9545d58894", + "weight": "24228828948987" + }, + { + "public_key": "020377bc3ad54b5505971e001044ea822a3f6f307f8dc93fa45a05b7463c0a053bed", + "weight": "10567495110201092" + } + ] + } + ], + "bids": [ + { + "public_key": "01001b79b9a6e13d2b96e916f7fa7dff40496ba5188479263ca0fb2ccf8b714305", + "bid": { + "bonding_purse": "uref-68f12244cf9e37759aa78e3146c431cc4577fc122272989b9f9ebf2e8f27d741-007", + "staked_amount": "908982507030", + "delegation_rate": 10, + "delegators": [ + { + "public_key": "018b34b15e023844531621cb52d42e216a2ea56034f0f40bf7cee566c32eae4f83", + "staked_amount": "30268476029", + "bonding_purse": "uref-401f87167d733d8dd7d3efbf135a91ccd42fffb77b02d4a0075b963f14f1fbb4-007", + "delegatee": "01001b79b9a6e13d2b96e916f7fa7dff40496ba5188479263ca0fb2ccf8b714305" + } + ], + "inactive": true + } + }, + { + "public_key": "01001b79b9a6e13d2b96e916f7fa7dff40496ba5188479263ca0fb2ccf8b714305", + "bid": { + "bonding_purse": "uref-68f12244cf9e37759aa78e3146c431cc4577fc122272989b9f9ebf2e8f27d741-007", + "staked_amount": "908982507030", + "delegation_rate": 10, + "delegators": [ + { + "public_key": "018b34b15e023844531621cb52d42e216a2ea56034f0f40bf7cee566c32eae4f83", + "staked_amount": "30268476029", + "bonding_purse": "uref-401f87167d733d8dd7d3efbf135a91ccd42fffb77b02d4a0075b963f14f1fbb4-007", + "delegatee": "01001b79b9a6e13d2b96e916f7fa7dff40496ba5188479263ca0fb2ccf8b714305" + } + ], + "inactive": true + } + }, + { + "public_key": "01001b79b9a6e13d2b96e916f7fa7dff40496ba5188479263ca0fb2ccf8b714305", + "bid": { + "bonding_purse": "uref-68f12244cf9e37759aa78e3146c431cc4577fc122272989b9f9ebf2e8f27d741-007", + "staked_amount": "908982507030", + "delegation_rate": 10, + "delegators": [ + { + "public_key": "018b34b15e023844531621cb52d42e216a2ea56034f0f40bf7cee566c32eae4f83", + "staked_amount": "30268476029", + "bonding_purse": "uref-401f87167d733d8dd7d3efbf135a91ccd42fffb77b02d4a0075b963f14f1fbb4-007", + "delegatee": "01001b79b9a6e13d2b96e916f7fa7dff40496ba5188479263ca0fb2ccf8b714305" + } + ], + "inactive": true + } + } + ] + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/get-auction-info-v200.json b/Casper.Network.SDK.Test/TestData/get-auction-info-v200.json new file mode 100644 index 0000000..511922f --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-auction-info-v200.json @@ -0,0 +1,153 @@ +{ + "api_version": "2.0.0", + "auction_state": { + "state_root_hash": "c8228d6d6d45151766901ba3579461847a17db15a66ce9ef6ae2f3e3abffd132", + "block_height": 3480973, + "era_validators": [ + { + "era_id": 14910, + "validator_weights": [ + { + "public_key": "01032146b0b9de01e26aaec7b0d1769920de94681dbd432c3530bfe591752ded6c", + "weight": "2030345838471710063" + }, + { + "public_key": "0126d4637eb0c0769274f03a696df1112383fa621c9f73f57af4c5c0fbadafa8cf", + "weight": "2030227732934792089" + }, + { + "public_key": "0140afe8f752e5ff100e0189c080bc207e8805b3e5e82f792ec608de2f11f39f6c", + "weight": "2030338910449323803" + }, + { + "public_key": "017536433a73f7562526f3e9fcb8d720428ae2d28788a9909f3c6f637a9d848a4b", + "weight": "2030266847164599460" + } + ] + }, + { + "era_id": 14911, + "validator_weights": [ + { + "public_key": "01032146b0b9de01e26aaec7b0d1769920de94681dbd432c3530bfe591752ded6c", + "weight": "2030438564141544419" + }, + { + "public_key": "0126d4637eb0c0769274f03a696df1112383fa621c9f73f57af4c5c0fbadafa8cf", + "weight": "2030321963971741685" + }, + { + "public_key": "0140afe8f752e5ff100e0189c080bc207e8805b3e5e82f792ec608de2f11f39f6c", + "weight": "2030431133219240217" + }, + { + "public_key": "017536433a73f7562526f3e9fcb8d720428ae2d28788a9909f3c6f637a9d848a4b", + "weight": "2030357058487126289" + } + ] + }, + { + "era_id": 14912, + "validator_weights": [ + { + "public_key": "01032146b0b9de01e26aaec7b0d1769920de94681dbd432c3530bfe591752ded6c", + "weight": "2030529783588751162" + }, + { + "public_key": "0126d4637eb0c0769274f03a696df1112383fa621c9f73f57af4c5c0fbadafa8cf", + "weight": "2030418207085001407" + }, + { + "public_key": "0140afe8f752e5ff100e0189c080bc207e8805b3e5e82f792ec608de2f11f39f6c", + "weight": "2030524865448355568" + }, + { + "public_key": "017536433a73f7562526f3e9fcb8d720428ae2d28788a9909f3c6f637a9d848a4b", + "weight": "2030445261010189498" + } + ] + } + ], + "bids": [ + { + "public_key": "01358a7e107668ae2eb092dcfbeb97d2ec3cc8354d2a77bc8f232fff6630a826c3", + "bid": { + "Validator": { + "validator_public_key": "01358a7e107668ae2eb092dcfbeb97d2ec3cc8354d2a77bc8f232fff6630a826c3", + "bonding_purse": "uref-027d909fa0818f8b426b905795f608a6301168476b8013d7b3f682786796096f-007", + "staked_amount": "2500000000", + "delegation_rate": 1, + "vesting_schedule": null, + "inactive": true, + "minimum_delegation_amount": 500000000000, + "maximum_delegation_amount": 1000000000000000000, + "reserved_slots": 3 + } + } + }, + { + "public_key": "020234b6ebc6d9c826964a692951e4e7e0c1e7db2683ebbd4b9b209ad6ca5224f02c", + "bid": { + "Validator": { + "validator_public_key": "020234b6ebc6d9c826964a692951e4e7e0c1e7db2683ebbd4b9b209ad6ca5224f02c", + "bonding_purse": "uref-4a15ba727136c9cb324af202412f3a2cb0d53cd58913647c6e34336f80758f54-007", + "staked_amount": "1000000000", + "delegation_rate": 1, + "vesting_schedule": null, + "inactive": true, + "minimum_delegation_amount": 500000000000, + "maximum_delegation_amount": 1000000000000000000, + "reserved_slots": 0 + } + } + }, + { + "public_key": "01032146b0b9de01e26aaec7b0d1769920de94681dbd432c3530bfe591752ded6c", + "bid": { + "Delegator": { + "delegator_kind": { + "Purse": "1cd4fc41e3f750e52024f54b1ac3a757027baad64d47ffb6d4e968967618740b" + }, + "staked_amount": "6435861748906", + "bonding_purse": "uref-30c3f9163e14b9b00c967daaecdba61d9a7a25f454b9f23d556200e1ab847955-007", + "validator_public_key": "01032146b0b9de01e26aaec7b0d1769920de94681dbd432c3530bfe591752ded6c", + "vesting_schedule": null + } + } + }, + { + "public_key": "01032146b0b9de01e26aaec7b0d1769920de94681dbd432c3530bfe591752ded6c", + "bid": { + "Delegator": { + "delegator_kind": { + "Purse": "8af7b77811970792f98b806779dfc0d1a9fef5bad205c6be8bb884210d7d323c" + }, + "staked_amount": "1676515877735", + "bonding_purse": "uref-d34ee21f5fe61feee2d9f15e0e369367aba62ba1b689e59c1e6ddc581ca99fdf-007", + "validator_public_key": "01032146b0b9de01e26aaec7b0d1769920de94681dbd432c3530bfe591752ded6c", + "vesting_schedule": null + } + } + }, + { + "public_key": "0106ca7c39cd272dbf21a86eeb3b36b7c26e2e9b94af64292419f7862936bca2ca", + "bid": { + "Unbond": { + "validator_public_key": "0106ca7c39cd272dbf21a86eeb3b36b7c26e2e9b94af64292419f7862936bca2ca", + "unbond_kind": { + "DelegatedPublicKey": "0154161886e8bcfea05f79efd7e8bfef2ed5a36c18d7d86d748fb21236f9e06d03" + }, + "eras": [ + { + "bonding_purse": "uref-a3c42d782f460513f2e1ee5569208d9efa80b34adf9736e011fdf1f5ca91bc08-007", + "era_of_creation": 17243, + "amount": "500000000000", + "new_validator": null + } + ] + } + } + } + ] + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/get-block-result-v156.json b/Casper.Network.SDK.Test/TestData/get-block-result-v156.json new file mode 100644 index 0000000..0545bf1 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-block-result-v156.json @@ -0,0 +1,65 @@ +{ + "api_version": "1.5.6", + "block": { + "hash": "36115bdee7ace197d200fc1b61ebd69e689bc1bbe0b7664f3cb55f66bf4590ed", + "header": { + "parent_hash": "8c870bfceb9522f8fb183b7c57443d3735b69afe377a0d4ebd94196fa8d80771", + "state_root_hash": "c354134eb17e4f73411c1950e7a3b566f4900b10ff351c1b1f097a096f8d9b90", + "body_hash": "577a030327479c1451ae5f882e4b79cc3570d9cb967b8e72f78006dba1a475f2", + "random_bit": true, + "accumulated_seed": "ce636433c38c9d1754fd3586f97437997773f5cfaa21d355ef5780f7bd42960e", + "era_end": null, + "timestamp": "2024-05-15T09:42:12.736Z", + "era_id": 13670, + "height": 2947381, + "protocol_version": "1.5.6" + }, + "body": { + "proposer": "01d2b6b397578ed3d46ebd94525a96b787d767494d94496a68b645a8bc21b66806", + "deploy_hashes": [ + "2dcf4d1b6990e725bf2a5075c360faa74a6f330c74afdf5dd06b09dbe3c16133", + "92b2545727df4f1bf6876cb6ce1b8cc9bf435b46f4d33c8fd057c457b7cc0adb", + "18d00684d17198141735abc4620466eac958da162caa76294ab531a60dfa9135", + "9eb5dd22e9785b0356b9c7ce172dd9c0d41d143e926690b9a6c40e9e628e5055", + "13fe9581cc4985b4cf1e1cdc5d759b4de3fcc07485742a0f67f86a1534919418", + "48abbe7f5c0ff6bee97a268d087ea4922f56e187ef5cd1bf6f581c39e5fb9791", + "307ad4761d2f350b3b01ed3b9d37feb05a5cc20ec38ae0f3a69280d3162fa676", + "81b8b21e4e573db18f24360596c5492fc1612d4daf96c8dbde3604cbb6cd4aa2", + "8c44f729fcd5b68904acb5bee80f3f900984e50ad8e88731540ee2a44e1bba12", + "543fd268a3340a1f94e3eb2bd282c427e16b82b0c400f1f674e6978366b1ad8d", + "7f0d7e260c9e325dd81a0300ecb3d8809b41c030187df8105e6ba954d9853918", + "2956b8b0ef0fd987ab18b0e3b9ee8dd1b2e1dafe6d5425b1e5a449347d1e75a6", + "a10be1850ae501564ada0075701242062f68270df51612697aa962ca0c037313", + "0b8a9067db06fb58a1d1d56a561bcc1906cd18c834a6d9de7d14dbe99be5259c", + "b47f979afa2c5cea563ab949bd6fc80032787e93530576dde233cb77eca936d9", + "3ff8291a5042b8c70ab8044e5f0ed177b32e8f6ddb6be9c7c39542a9a0e8a14b", + "23b4735ecce1923677be79251e6e76e4b5c6659f2b0d14a2fd16a81d8833163c", + "335ee7c3405d73129444f986700e1b7244069618b6f30a30ef7c3054bdd19c6c", + "378db7e92e65cd84dd6a61c7f9c01acb45bac7817fbdb23a600d5301d9a9f9eb", + "1cb75e19e48405ce3df81c7e6ce7158bc97c7579ad4d8b77f1faf0f070a00a29", + "800f1ecb41fc5f1b00775bd96967090f0a29fae0c4e2daff1ec1c7fb2f3261d9", + "550888c9b56f345093d5210ed2f69c284ff05f2a5a04c7f6e483edf0616fdcc1", + "05d4fba4fa790e3058f3893b6c9b5731be4c92710262401b82fd3781cf618b12", + "7e44ecfeabe496dae657958e870777ad6f677b01686428b3f7aa02518d0a6c05", + "fbfcb832613adcb5ed2d3125d95ae20e19499113435696c4d0c40ddd7312aaf3" + ], + "transfer_hashes": [ + "31849e17f715273ad7032d51534c5b6029dd0dec1e01c225c50083752a750219" + ] + }, + "proofs": [ + { + "public_key": "01000e6fce753895c0d08d5d6af62db4e9b0d070f10e69e2c6badf977b29bbeeee", + "signature": "011836119151750cc8847e145d1472ae6ee68c107d32cb838087a1f9f3ad2089a8f9697141563018df611bda7eb05ec34abe708043d28b2edcd025520153051108" + }, + { + "public_key": "0100a8faa48e4b20966105c610d8a5f80c4248b337686c51213297d88afe0ff84e", + "signature": "013baa9370748764e8020f9e07c2f43e1bebc470b4742cf020507ad7d41aa8a24bda12c026bd561911874a2c46fc2aaea2aba727bcea20766031972e4c3d666a08" + }, + { + "public_key": "01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80", + "signature": "0127e72f44e3027a3393a4720e6c1f441d8ad3a343372897ec7e88b30bbc2c62c882c246fa5077aa1eeb0c3e1ca105dd1b753a1b0be109cbe1fadc96cf8196820f" + } + ] + } +} diff --git a/Casper.Network.SDK.Test/TestData/get-block-result-v200.json b/Casper.Network.SDK.Test/TestData/get-block-result-v200.json new file mode 100644 index 0000000..a84015a --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-block-result-v200.json @@ -0,0 +1,115 @@ +{ + "api_version": "2.0.0", + "block_with_signatures": { + "block": { + "Version2": { + "hash": "8c1042f7e78c58229b9cf8579a2a5094e68332646b433bd785650d2a4f607421", + "header": { + "parent_hash": "b877088ae6272d77fc1b4b936331d9cef4c2b6e7ea2e8854b5ee8e93c438cadd", + "state_root_hash": "972ff7268bee4e6a11c0b1c615dfcc1ed89f13de2f19c1b85a0d9dcf95774eef", + "body_hash": "2f6218cdaa056e391fc9fb37f2dae71eefa3ca03e91d7fc4455bb6a4cd01ac5b", + "random_bit": false, + "accumulated_seed": "dacf81815b307edc02802bb8afee014cd2681749efeaab5a4f5e0a1240e26663", + "era_end": { + "equivocators": [], + "inactive_validators": [], + "next_era_validator_weights": [ + { + "validator": "01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6", + "weight": "1457007299027515530" + }, + { + "validator": "0190664e16a17594ed2d0e3c279c4cf5894e8db0da15e3b91c938562a1caae32ab", + "weight": "2462811880650517518" + }, + { + "validator": "01c867ff3cf1d4e4e68fc00922fdcb740304def196e223091dee62012f444b9eba", + "weight": "684867946138376005" + }, + { + "validator": "01f58b94526d280881f79744effebc555426190950d5dfdd2f8aaf10ceaec010c6", + "weight": "1500458509683331484" + }, + { + "validator": "01fed662dc7f1f7af43ad785ba07a8cc05b7a96f9ee69613cfde43bc56bec1140b", + "weight": "1604854364500260181" + } + ], + "rewards": { + "01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6": [ + "3787954951771805", + "378861119683192" + ], + "0190664e16a17594ed2d0e3c279c4cf5894e8db0da15e3b91c938562a1caae32ab": [ + "18392670705238418", + "638104246294617" + ], + "01c867ff3cf1d4e4e68fc00922fdcb740304def196e223091dee62012f444b9eba": [ + "1760738793374719", + "176836068103231" + ], + "01f58b94526d280881f79744effebc555426190950d5dfdd2f8aaf10ceaec010c6": [ + "9901127257757664", + "391012293027695" + ], + "01fed662dc7f1f7af43ad785ba07a8cc05b7a96f9ee69613cfde43bc56bec1140b": [ + "18657508291857411", + "915186272891264" + ] + }, + "next_era_gas_price": 1 + }, + "timestamp": "2024-06-10T13:44:09.123Z", + "era_id": 141, + "height": 1551, + "protocol_version": "2.0.0", + "proposer": "0190664e16a17594ed2d0e3c279c4cf5894e8db0da15e3b91c938562a1caae32ab", + "current_gas_price": 1, + "last_switch_block_hash": "45216a334c0ef0e886ca2671d5795150e8bc0f1a1a47719112d7289dbb5e5dfd" + }, + "body": { + "transactions": { + "2": [ + { + "Version1": "82bedd65735508daeb26e9132f24bd8861bcece1120ee945a3362ef6526e3884" + } + ] + }, + "rewarded_signatures": [ + [ + 248 + ], + [ + 0 + ], + [ + 0 + ] + ] + } + } + }, + "proofs": [ + { + "public_key": "01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6", + "signature": "01bceb8d8ebdec1065d0a9614b601b2621a3f127bfad6dce4c5d106d7fdabf98e0e5824309fb907c51071060dce62cc445330b94298dc2cccc4a49cb16e9150103" + }, + { + "public_key": "0190664e16a17594ed2d0e3c279c4cf5894e8db0da15e3b91c938562a1caae32ab", + "signature": "01682c1cf7770ced1b9e764db6223c4b0cb349b14979ea67c89e5af9b5e325f0c2a56b7844e136c349475888b334acf9ba803b0c64a86146db4d6801222a336a0a" + }, + { + "public_key": "01c867ff3cf1d4e4e68fc00922fdcb740304def196e223091dee62012f444b9eba", + "signature": "0108ddabc990a3b01628f52bdcbbc200d2086e9c5c3e8e6c1ce1e6aa28d4cf41183161a0ebb72243d56dc6a6deff028b16c10970b84bdd8fee5c8cea73b8fcf201" + }, + { + "public_key": "01f58b94526d280881f79744effebc555426190950d5dfdd2f8aaf10ceaec010c6", + "signature": "01713456c93fb75ebd05fcbc6c3eab28e04a19da43bb056d935a9af6a7781487468bc2127c8c49fe794fcb61aae6552893281d91fd6ea9be8c8530c51d41ff1508" + }, + { + "public_key": "01fed662dc7f1f7af43ad785ba07a8cc05b7a96f9ee69613cfde43bc56bec1140b", + "signature": "01113063714eed166f636b4437bc839fa4c26d59fc3fdf8268f3ee6466e559521edad9737637c5918adb713b4281e2f761e595189be086fcabd50d945d8d39f106" + } + ] + } +} diff --git a/Casper.Network.SDK.Test/TestData/get-deploy-deploy-v200.json b/Casper.Network.SDK.Test/TestData/get-deploy-deploy-v200.json new file mode 100644 index 0000000..058c77e --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-deploy-deploy-v200.json @@ -0,0 +1,197 @@ +{ + "api_version": "2.0.0", + "deploy": { + "hash": "53363985887173c70955bee232e280429f53ab208e2397b19a5e761215734e56", + "header": { + "account": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a", + "timestamp": "2024-07-17T07:00:19.247Z", + "ttl": "30m", + "gas_price": 1, + "body_hash": "c5cf594dc6d244358fddd6461e93703653bb07a0d9fa957c4a4b5fdc5bc9968d", + "dependencies": [], + "chain_name": "casper-net-1" + }, + "payment": { + "ModuleBytes": { + "module_bytes": "", + "args": [ + [ + "amount", + { + "cl_type": "U512", + "bytes": "0100", + "parsed": "0" + } + ] + ] + } + }, + "session": { + "Transfer": { + "args": [ + [ + "amount", + { + "cl_type": "U512", + "bytes": "0500ba1dd205", + "parsed": "25000000000" + } + ], + [ + "target", + { + "cl_type": { + "ByteArray": 32 + }, + "bytes": "6bccb64a7904b7217dd4ed7d9e4163785fe2133d45d390250b18f9442917bc0a", + "parsed": "6bccb64a7904b7217dd4ed7d9e4163785fe2133d45d390250b18f9442917bc0a" + } + ], + [ + "id", + { + "cl_type": { + "Option": "U64" + }, + "bytes": "010100000000000000", + "parsed": 1 + } + ] + ] + } + }, + "approvals": [ + { + "signer": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a", + "signature": "01a42c794b4f9f1d344a41ec67f0a01a3f5c18b7a04c8948118d172993137d9db526c0e2512b89c45fc100d6cab9c562cccc295d6b0fd667a0c9d211cae88c570b" + } + ] + }, + "execution_info": { + "block_hash": "4ab00f303c62fe665fabf1112fc08b995c31b62b7af13390b417d8ba483597d4", + "block_height": 74, + "execution_result": { + "Version2": { + "initiator": { + "PublicKey": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a" + }, + "error_message": null, + "limit": "10000", + "consumed": "10000", + "cost": "10000", + "payment": [], + "transfers": [ + { + "Version2": { + "transaction_hash": { + "Deploy": "53363985887173c70955bee232e280429f53ab208e2397b19a5e761215734e56" + }, + "from": { + "AccountHash": "account-hash-9fa1fc0808d3a5b9ea9f3af4ca7c8c3655568fdf378d8afdf8a7e56e58abbfd4" + }, + "to": "account-hash-6bccb64a7904b7217dd4ed7d9e4163785fe2133d45d390250b18f9442917bc0a", + "source": "uref-8b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce85142-007", + "target": "uref-98735896568d7b44005778ed909e83457e8923c76b2a23bcec09643cc384cfce-004", + "amount": "25000000000", + "gas": "0", + "id": 1 + } + } + ], + "size_estimate": 367, + "effects": [ + { + "key": "balance-hold-018b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce85142d0f57dbf90010000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "021027", + "parsed": "10000" + } + } + } + }, + { + "key": "account-hash-9fa1fc0808d3a5b9ea9f3af4ca7c8c3655568fdf378d8afdf8a7e56e58abbfd4", + "kind": "Identity" + }, + { + "key": "balance-hold-008b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce8514256207dbf90010000", + "kind": "Identity" + }, + { + "key": "balance-hold-018b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce85142d0f57dbf90010000", + "kind": "Identity" + }, + { + "key": "balance-8b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce85142", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "0e005e6be5ec5ac138938d44c64d31", + "parsed": "999999999999999999999875000000000" + } + } + } + }, + { + "key": "balance-98735896568d7b44005778ed909e83457e8923c76b2a23bcec09643cc384cfce", + "kind": { + "AddUInt512": "25000000000" + } + }, + { + "key": "balance-hold-018b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce85142d0f57dbf90010000", + "kind": { + "Prune": "balance-hold-018b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce85142d0f57dbf90010000" + } + }, + { + "key": "balance-hold-008b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce85142d0f57dbf90010000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "021027", + "parsed": "10000" + } + } + } + }, + { + "key": "entity-system-8499396a854bbe7e172a5ab675caf3c7b944f57e43cee5b7563971678f8650b2", + "kind": "Identity" + }, + { + "key": "entity-system-989625d28e779749e3cacc7367403dd459c8e7082937436f16d5dbff7aec8292", + "kind": "Identity" + }, + { + "key": "entity-system-da2faf76ab117ec370d10f994e9407ff014ab974bedc50e58425a9e35428dd0c", + "kind": "Identity" + }, + { + "key": "bid-addr-01dcd0c38c46c9d5c7083aa1a46b430e8e460f97f8f0bf8444776ac925187acfcc", + "kind": "Identity" + }, + { + "key": "bid-addr-04dcd0c38c46c9d5c7083aa1a46b430e8e460f97f8f0bf8444776ac925187acfcc0900000000000000", + "kind": { + "Write": { + "BidKind": { + "Credit": { + "validator_public_key": "01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6", + "era_id": 9, + "amount": "10000" + } + } + } + } + } + ] + } + } + } +} diff --git a/Casper.Network.SDK.Test/TestData/get-deploy-result-version1-failure.json b/Casper.Network.SDK.Test/TestData/get-deploy-result-version1-failure.json new file mode 100644 index 0000000..7baf554 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-deploy-result-version1-failure.json @@ -0,0 +1,245 @@ +{ + "api_version": "2.0.0", + "deploy": { + "hash": "f21957f06b05720988672e625d9900f4d787524d05e4dbb30b04db18625aeac7", + "header": { + "account": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a", + "timestamp": "2024-06-28T10:22:35.711Z", + "ttl": "3m", + "gas_price": 1, + "body_hash": "3cd24b57053a21605a0569edcd0da556782db67d5ee0529dedecc9e1271d34d6", + "dependencies": [], + "chain_name": "casper-net-1" + }, + "payment": { + "ModuleBytes": { + "module_bytes": "", + "args": [ + [ + "amount", + { + "cl_type": "U512", + "bytes": "0500f2052a01", + "parsed": "5000000000" + } + ] + ] + } + }, + "session": { + "StoredVersionedContractByHash": { + "hash": "2f5f35e499d33f7bbb03f6657c12c3bee0ca2eebd9cfe177fb5cc2dbb6378feb", + "version": null, + "entry_point": "transfer", + "args": [ + [ + "recipient", + { + "cl_type": "Key", + "bytes": "006bccb64a7904b7217dd4ed7d9e4163785fe2133d45d390250b18f9442917bc0a", + "parsed": "account-hash-6bccb64a7904b7217dd4ed7d9e4163785fe2133d45d390250b18f9442917bc0a" + } + ], + [ + "amount", + { + "cl_type": "U256", + "bytes": "02d430", + "parsed": "12500" + } + ] + ] + } + }, + "approvals": [ + { + "signer": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a", + "signature": "0188dd1e8400c8515c42df0184e11316b6eee94194a9be41b9bee9b4ce48304643a5bbb7cf5511649597f46c41c7a543882a09900244f65b82abad2bf906e9b20b" + } + ] + }, + "execution_info": { + "block_hash": "fee17570df0ee6c77ba19cc4194490f1f939ba7823a6a3e664c5599dc7067879", + "block_height": 122, + "execution_result": { + "Version1": { + "Failure": { + "effect": { + "operations": [], + "transforms": [ + { + "key": "account-hash-6174cf2e6f8fed1715c9a3bace9c50bfe572eecb763b0ed3f644532616452008", + "transform": "Identity" + }, + { + "key": "hash-2f5f35e499d33f7bbb03f6657c12c3bee0ca2eebd9cfe177fb5cc2dbb6378feb", + "transform": "Identity" + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "hash-d6513eb8e21953badd438f0e3cb01fa4c47f879cd6ecd28a25bd327cb0d0a0e2", + "transform": "Identity" + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "hash-c3d73ae472263ea840378de7487c67b3b598dcd281d7851a429e783b25d4e959", + "transform": "Identity" + }, + { + "key": "hash-1499242da9799e647edf8e0903e75776fe5c1df56a26de15d028422ef1f2bf93", + "transform": "Identity" + }, + { + "key": "hash-c3d73ae472263ea840378de7487c67b3b598dcd281d7851a429e783b25d4e959", + "transform": "Identity" + }, + { + "key": "balance-f34ede14ef9d3adc031b37ae4473f6818f289b786083ce589343cf52a8f59841", + "transform": "Identity" + }, + { + "key": "balance-a1e58c31ab6ef541735d5a13ee768c72031b5042c3b128e45119cf1087b3c93d", + "transform": "Identity" + }, + { + "key": "balance-f34ede14ef9d3adc031b37ae4473f6818f289b786083ce589343cf52a8f59841", + "transform": { + "WriteCLValue": { + "cl_type": "U512", + "bytes": "0e000efad5085bc138938d44c64d31", + "parsed": "999999999999999999999995000000000" + } + } + }, + { + "key": "balance-a1e58c31ab6ef541735d5a13ee768c72031b5042c3b128e45119cf1087b3c93d", + "transform": { + "AddUInt512": "5000000000" + } + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "hash-d6513eb8e21953badd438f0e3cb01fa4c47f879cd6ecd28a25bd327cb0d0a0e2", + "transform": "Identity" + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "balance-a1e58c31ab6ef541735d5a13ee768c72031b5042c3b128e45119cf1087b3c93d", + "transform": "Identity" + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "account-hash-9fa1fc0808d3a5b9ea9f3af4ca7c8c3655568fdf378d8afdf8a7e56e58abbfd4", + "transform": "Identity" + }, + { + "key": "hash-c3d73ae472263ea840378de7487c67b3b598dcd281d7851a429e783b25d4e959", + "transform": "Identity" + }, + { + "key": "hash-1499242da9799e647edf8e0903e75776fe5c1df56a26de15d028422ef1f2bf93", + "transform": "Identity" + }, + { + "key": "hash-c3d73ae472263ea840378de7487c67b3b598dcd281d7851a429e783b25d4e959", + "transform": "Identity" + }, + { + "key": "balance-a1e58c31ab6ef541735d5a13ee768c72031b5042c3b128e45119cf1087b3c93d", + "transform": "Identity" + }, + { + "key": "balance-f34ede14ef9d3adc031b37ae4473f6818f289b786083ce589343cf52a8f59841", + "transform": "Identity" + }, + { + "key": "balance-a1e58c31ab6ef541735d5a13ee768c72031b5042c3b128e45119cf1087b3c93d", + "transform": { + "WriteCLValue": { + "cl_type": "U512", + "bytes": "045419d004", + "parsed": "80746836" + } + } + }, + { + "key": "balance-f34ede14ef9d3adc031b37ae4473f6818f289b786083ce589343cf52a8f59841", + "transform": { + "AddUInt512": "4919253164" + } + }, + { + "key": "hash-c3d73ae472263ea840378de7487c67b3b598dcd281d7851a429e783b25d4e959", + "transform": "Identity" + }, + { + "key": "hash-1499242da9799e647edf8e0903e75776fe5c1df56a26de15d028422ef1f2bf93", + "transform": "Identity" + }, + { + "key": "hash-c3d73ae472263ea840378de7487c67b3b598dcd281d7851a429e783b25d4e959", + "transform": "Identity" + }, + { + "key": "balance-a1e58c31ab6ef541735d5a13ee768c72031b5042c3b128e45119cf1087b3c93d", + "transform": "Identity" + }, + { + "key": "balance-59621579e9cd67523b65d2ac87fe05993be7dfd6ba677fd1b4200e64a53f9d16", + "transform": "Identity" + }, + { + "key": "balance-a1e58c31ab6ef541735d5a13ee768c72031b5042c3b128e45119cf1087b3c93d", + "transform": { + "WriteCLValue": { + "cl_type": "U512", + "bytes": "00", + "parsed": "0" + } + } + }, + { + "key": "balance-59621579e9cd67523b65d2ac87fe05993be7dfd6ba677fd1b4200e64a53f9d16", + "transform": { + "AddUInt512": "80746836" + } + } + ] + }, + "transfers": [], + "cost": "31057410", + "error_message": "User error: 60001" + } + } + } + } +} + + diff --git a/Casper.Network.SDK.Test/TestData/get-deploy-result-version1-success.json b/Casper.Network.SDK.Test/TestData/get-deploy-result-version1-success.json new file mode 100644 index 0000000..4ce6f3d --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-deploy-result-version1-success.json @@ -0,0 +1,632 @@ +{ + "api_version": "2.0.0", + "deploy": { + "hash": "cdbb52d946ce58dccde35ac17a5820c0ee6bdddcf80d0e5e22572d2be6bffed6", + "header": { + "account": "0184f6d260f4ee6869ddb36affe15456de6ae045278fa2f467bb677561ce0dad55", + "timestamp": "2024-06-28T10:15:36.909Z", + "ttl": "30m", + "gas_price": 1, + "body_hash": "467d34798eb4075f9518a82f7a8eb959290f00ac5fb305c3428f63508082ba39", + "dependencies": [], + "chain_name": "casper-net-1" + }, + "payment": { + "ModuleBytes": { + "module_bytes": "", + "args": [ + [ + "amount", + { + "cl_type": "U512", + "bytes": "050088526a74", + "parsed": "500000000000" + } + ] + ] + } + }, + "session": { + "ModuleBytes": { + "module_bytes": "0061736d0100000141073602b803200141b8036a10ee808080002102200141b8036a0002100000", + "args": [ + [ + "name", + { + "cl_type": "String", + "bytes": "0b000000434c49434b542054657374", + "parsed": "CLICKT Test" + } + ], + [ + "symbol", + { + "cl_type": "String", + "bytes": "06000000434c49434b54", + "parsed": "CLICKT" + } + ], + [ + "decimals", + { + "cl_type": "U8", + "bytes": "03", + "parsed": 3 + } + ], + [ + "total_supply", + { + "cl_type": "U256", + "bytes": "0400ca9a3b", + "parsed": "1000000000" + } + ], + [ + "events_mode", + { + "cl_type": "U8", + "bytes": "01", + "parsed": 1 + } + ], + [ + "enable_mint_burn", + { + "cl_type": "U8", + "bytes": "00", + "parsed": 0 + } + ] + ] + } + }, + "approvals": [ + { + "signer": "0184f6d260f4ee6869ddb36affe15456de6ae045278fa2f467bb677561ce0dad55", + "signature": "01cddbf5fc583f7ebf2293837e8f306ecb3650ad8df3760010048e3a7ad75640dcce3e5b1c990cd596f60b01570fb963514c0af083a29b81194f708a777f15910b" + } + ] + }, + "execution_info": { + "block_hash": "63dcce7f471030454da37a84e35c62fac863ee73aea0a9bf159e973ecc3e2e7d", + "block_height": 23, + "execution_result": { + "Version1": { + "Success": { + "effect": { + "operations": [], + "transforms": [ + { + "key": "account-hash-6174cf2e6f8fed1715c9a3bace9c50bfe572eecb763b0ed3f644532616452008", + "transform": "Identity" + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "hash-d6513eb8e21953badd438f0e3cb01fa4c47f879cd6ecd28a25bd327cb0d0a0e2", + "transform": "Identity" + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "hash-c3d73ae472263ea840378de7487c67b3b598dcd281d7851a429e783b25d4e959", + "transform": "Identity" + }, + { + "key": "hash-1499242da9799e647edf8e0903e75776fe5c1df56a26de15d028422ef1f2bf93", + "transform": "Identity" + }, + { + "key": "hash-c3d73ae472263ea840378de7487c67b3b598dcd281d7851a429e783b25d4e959", + "transform": "Identity" + }, + { + "key": "balance-bc174dc2398daae9d676470292db5f34c5c4aafb059ee08543e1d725667a7c1e", + "transform": "Identity" + }, + { + "key": "balance-a1e58c31ab6ef541735d5a13ee768c72031b5042c3b128e45119cf1087b3c93d", + "transform": "Identity" + }, + { + "key": "balance-bc174dc2398daae9d676470292db5f34c5c4aafb059ee08543e1d725667a7c1e", + "transform": { + "WriteCLValue": { + "cl_type": "U512", + "bytes": "0e0078ad95955ac138938d44c64d31", + "parsed": "999999999999999999999500000000000" + } + } + }, + { + "key": "balance-a1e58c31ab6ef541735d5a13ee768c72031b5042c3b128e45119cf1087b3c93d", + "transform": { + "AddUInt512": "500000000000" + } + }, + { + "key": "uref-db1891fef9155d6ef077d8a93d5e3edcba5eea0d8298163fb9872e133268711e-000", + "transform": { + "WriteCLValue": { + "cl_type": "String", + "bytes": "0b000000434c49434b542054657374", + "parsed": "CLICKT Test" + } + } + }, + { + "key": "uref-e8d05c77d4bf5d46001bad5828c27d90b5da6e4a151496e46af1cc706efd48dd-000", + "transform": { + "WriteCLValue": { + "cl_type": "String", + "bytes": "06000000434c49434b54", + "parsed": "CLICKT" + } + } + }, + { + "key": "uref-21fe1e848187fdd960698ad8b7986d5c0e08de270b6757e7635c9d86e44e1f9e-000", + "transform": { + "WriteCLValue": { + "cl_type": "U8", + "bytes": "03", + "parsed": 3 + } + } + }, + { + "key": "uref-18556a7a9e30862aa6f649f184febae6edf1ffe038403ef85a8ff78ad97307b9-000", + "transform": { + "WriteCLValue": { + "cl_type": "U256", + "bytes": "0400ca9a3b", + "parsed": "1000000000" + } + } + }, + { + "key": "uref-3d67308514d1a0c447a07f088b165d54aa13f601f9e58791e059bc6344cd84f9-000", + "transform": { + "WriteCLValue": { + "cl_type": "U8", + "bytes": "01", + "parsed": 1 + } + } + }, + { + "key": "uref-5c12c6fa083d7a2340379cb9a56d07915f186d76624c7223c478a679962cecf4-000", + "transform": { + "WriteCLValue": { + "cl_type": "U8", + "bytes": "00", + "parsed": 0 + } + } + }, + { + "key": "uref-783e3cbee2b8acb3e58da315fdfdc809faf98f0c03d4de7b7d3fd1ec10a7bd6e-000", + "transform": { + "WriteCLValue": { + "cl_type": "Unit", + "bytes": "", + "parsed": null + } + } + }, + { + "key": "hash-2f5f35e499d33f7bbb03f6657c12c3bee0ca2eebd9cfe177fb5cc2dbb6378feb", + "transform": "WriteContractPackage" + }, + { + "key": "account-hash-56befc13a6fd62e18f361700a5e08f966901c34df8041b36ec97d54d605c23de", + "transform": { + "AddKeys": [ + { + "name": "cep18_contract_package_CLICKT Test", + "key": "hash-2f5f35e499d33f7bbb03f6657c12c3bee0ca2eebd9cfe177fb5cc2dbb6378feb" + } + ] + } + }, + { + "key": "account-hash-56befc13a6fd62e18f361700a5e08f966901c34df8041b36ec97d54d605c23de", + "transform": { + "AddKeys": [ + { + "name": "cep18_contract_package_access_CLICKT Test", + "key": "uref-783e3cbee2b8acb3e58da315fdfdc809faf98f0c03d4de7b7d3fd1ec10a7bd6e-007" + } + ] + } + }, + { + "key": "hash-2f5f35e499d33f7bbb03f6657c12c3bee0ca2eebd9cfe177fb5cc2dbb6378feb", + "transform": "Identity" + }, + { + "key": "hash-906aa4db02430ae0894f6996c3dcb423013d9878037ad84a46c6f6ac78b7d5b1", + "transform": "WriteContractWasm" + }, + { + "key": "hash-3ec4d7e8ec755ba06be9ee21dd0ffa69028fb92fbe4316e880bb417a13898d01", + "transform": "WriteContract" + }, + { + "key": "hash-2f5f35e499d33f7bbb03f6657c12c3bee0ca2eebd9cfe177fb5cc2dbb6378feb", + "transform": "WriteContractPackage" + }, + { + "key": "account-hash-56befc13a6fd62e18f361700a5e08f966901c34df8041b36ec97d54d605c23de", + "transform": { + "AddKeys": [ + { + "name": "cep18_contract_hash_CLICKT Test", + "key": "hash-3ec4d7e8ec755ba06be9ee21dd0ffa69028fb92fbe4316e880bb417a13898d01" + } + ] + } + }, + { + "key": "uref-09377f91048ec8d1b15df45c151e55683d8b93a779c5f2cef634d3ca1543b192-000", + "transform": { + "WriteCLValue": { + "cl_type": "U32", + "bytes": "01000000", + "parsed": 1 + } + } + }, + { + "key": "account-hash-56befc13a6fd62e18f361700a5e08f966901c34df8041b36ec97d54d605c23de", + "transform": { + "AddKeys": [ + { + "name": "cep18_contract_version_CLICKT Test", + "key": "uref-09377f91048ec8d1b15df45c151e55683d8b93a779c5f2cef634d3ca1543b192-007" + } + ] + } + }, + { + "key": "hash-3ec4d7e8ec755ba06be9ee21dd0ffa69028fb92fbe4316e880bb417a13898d01", + "transform": "Identity" + }, + { + "key": "hash-2f5f35e499d33f7bbb03f6657c12c3bee0ca2eebd9cfe177fb5cc2dbb6378feb", + "transform": "Identity" + }, + { + "key": "hash-906aa4db02430ae0894f6996c3dcb423013d9878037ad84a46c6f6ac78b7d5b1", + "transform": "Identity" + }, + { + "key": "hash-3ec4d7e8ec755ba06be9ee21dd0ffa69028fb92fbe4316e880bb417a13898d01", + "transform": { + "AddKeys": [ + { + "name": "package_hash", + "key": "hash-2f5f35e499d33f7bbb03f6657c12c3bee0ca2eebd9cfe177fb5cc2dbb6378feb" + } + ] + } + }, + { + "key": "uref-ae50771df34a40472b425ddd98c874a0836888f3bd22e01842f7bfddb3549c6f-000", + "transform": { + "WriteCLValue": { + "cl_type": "Unit", + "bytes": "", + "parsed": null + } + } + }, + { + "key": "hash-3ec4d7e8ec755ba06be9ee21dd0ffa69028fb92fbe4316e880bb417a13898d01", + "transform": { + "AddKeys": [ + { + "name": "allowances", + "key": "uref-ae50771df34a40472b425ddd98c874a0836888f3bd22e01842f7bfddb3549c6f-007" + } + ] + } + }, + { + "key": "uref-aeae29762c24430dd4e6dd0ff93994abd53c43907103a27bfe7d3881fdcebbc0-000", + "transform": { + "WriteCLValue": { + "cl_type": "Unit", + "bytes": "", + "parsed": null + } + } + }, + { + "key": "hash-3ec4d7e8ec755ba06be9ee21dd0ffa69028fb92fbe4316e880bb417a13898d01", + "transform": { + "AddKeys": [ + { + "name": "balances", + "key": "uref-aeae29762c24430dd4e6dd0ff93994abd53c43907103a27bfe7d3881fdcebbc0-007" + } + ] + } + }, + { + "key": "dictionary-3242adf99a374c325bbc98d29323805a5a0c7d81c0b55731027111bc1b220869", + "transform": { + "WriteCLValue": { + "cl_type": "Any", + "bytes": "050000000400ca9a3b0720000000aeae29762c24430dd4e6dd0ff93994abd53c43907103a27bfe7d3881fdcebbc02c0000004146612b2f424f6d2f574c686a7a5958414b58676a355a7041634e4e2b4151624e7579583155316758435065", + "parsed": null + } + } + }, + { + "key": "uref-304e0283e0bd6fbb04dd97af1c6caea28cbd7a8937fcf46724052142cfe3f603-000", + "transform": { + "WriteCLValue": { + "cl_type": "Unit", + "bytes": "", + "parsed": null + } + } + }, + { + "key": "hash-3ec4d7e8ec755ba06be9ee21dd0ffa69028fb92fbe4316e880bb417a13898d01", + "transform": { + "AddKeys": [ + { + "name": "security_badges", + "key": "uref-304e0283e0bd6fbb04dd97af1c6caea28cbd7a8937fcf46724052142cfe3f603-007" + } + ] + } + }, + { + "key": "dictionary-fbde437d8e6d72b1eecd9e776d5a54c70216381905dca0ef5e589cf6d708d5d1", + "transform": { + "WriteCLValue": { + "cl_type": "Any", + "bytes": "01000000000320000000304e0283e0bd6fbb04dd97af1c6caea28cbd7a8937fcf46724052142cfe3f6032c0000004146612b2f424f6d2f574c686a7a5958414b58676a355a7041634e4e2b4151624e7579583155316758435065", + "parsed": null + } + } + }, + { + "key": "uref-3d67308514d1a0c447a07f088b165d54aa13f601f9e58791e059bc6344cd84f9-000", + "transform": "Identity" + }, + { + "key": "uref-83fe09fd54cc79ce67e513bdef50297936fdcef11fa7886f984a373aa2fa000c-000", + "transform": { + "WriteCLValue": { + "cl_type": "Unit", + "bytes": "", + "parsed": null + } + } + }, + { + "key": "hash-3ec4d7e8ec755ba06be9ee21dd0ffa69028fb92fbe4316e880bb417a13898d01", + "transform": { + "AddKeys": [ + { + "name": "__events", + "key": "uref-83fe09fd54cc79ce67e513bdef50297936fdcef11fa7886f984a373aa2fa000c-007" + } + ] + } + }, + { + "key": "uref-c241213422d3c9adb0afa8437ea3dab8c69dd863c0cf44536d7586538545b482-000", + "transform": { + "WriteCLValue": { + "cl_type": "U32", + "bytes": "00000000", + "parsed": 0 + } + } + }, + { + "key": "hash-3ec4d7e8ec755ba06be9ee21dd0ffa69028fb92fbe4316e880bb417a13898d01", + "transform": { + "AddKeys": [ + { + "name": "__events_length", + "key": "uref-c241213422d3c9adb0afa8437ea3dab8c69dd863c0cf44536d7586538545b482-007" + } + ] + } + }, + { + "key": "uref-8ed3033a37c622f23a4c1689c29a3b773bec3a8ac8a583e866c3d196a7b2e8a4-000", + "transform": { + "WriteCLValue": { + "cl_type": { + "Map": { + "key": "String", + "value": { + "List": { + "Tuple2": [ + "String", + "Any" + ] + } + } + } + }, + "bytes": "08000000040000004275726e02000000050000006f776e65720b06000000616d6f756e74070e0000004368616e67655365637572697479020000000500000061646d696e0b0e0000007365635f6368616e67655f6d6170110b03110000004465637265617365416c6c6f77616e636504000000050000006f776e65720b070000007370656e6465720b09000000616c6c6f77616e63650707000000646563725f62790711000000496e637265617365416c6c6f77616e636504000000050000006f776e65720b070000007370656e6465720b09000000616c6c6f77616e63650706000000696e635f627907040000004d696e740200000009000000726563697069656e740b06000000616d6f756e74070c000000536574416c6c6f77616e636503000000050000006f776e65720b070000007370656e6465720b09000000616c6c6f77616e636507080000005472616e73666572030000000600000073656e6465720b09000000726563697069656e740b06000000616d6f756e74070c0000005472616e7366657246726f6d04000000070000007370656e6465720b050000006f776e65720b09000000726563697069656e740b06000000616d6f756e7407", + "parsed": null + } + } + }, + { + "key": "hash-3ec4d7e8ec755ba06be9ee21dd0ffa69028fb92fbe4316e880bb417a13898d01", + "transform": { + "AddKeys": [ + { + "name": "__events_schema", + "key": "uref-8ed3033a37c622f23a4c1689c29a3b773bec3a8ac8a583e866c3d196a7b2e8a4-007" + } + ] + } + }, + { + "key": "uref-6533dbce8d122ee0310a51dc4416e1a594277ec8625f1df54cbc1dcccae27c4b-000", + "transform": { + "WriteCLValue": { + "cl_type": "String", + "bytes": "03000000312e31", + "parsed": "1.1" + } + } + }, + { + "key": "hash-3ec4d7e8ec755ba06be9ee21dd0ffa69028fb92fbe4316e880bb417a13898d01", + "transform": { + "AddKeys": [ + { + "name": "__events_ces_version", + "key": "uref-6533dbce8d122ee0310a51dc4416e1a594277ec8625f1df54cbc1dcccae27c4b-007" + } + ] + } + }, + { + "key": "deploy-cdbb52d946ce58dccde35ac17a5820c0ee6bdddcf80d0e5e22572d2be6bffed6", + "transform": { + "WriteDeployInfo": { + "deploy_hash": "cdbb52d946ce58dccde35ac17a5820c0ee6bdddcf80d0e5e22572d2be6bffed6", + "transfers": [], + "from": "account-hash-56befc13a6fd62e18f361700a5e08f966901c34df8041b36ec97d54d605c23de", + "source": "uref-bc174dc2398daae9d676470292db5f34c5c4aafb059ee08543e1d725667a7c1e-007", + "gas": "239918433115" + } + } + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "hash-d6513eb8e21953badd438f0e3cb01fa4c47f879cd6ecd28a25bd327cb0d0a0e2", + "transform": "Identity" + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "balance-a1e58c31ab6ef541735d5a13ee768c72031b5042c3b128e45119cf1087b3c93d", + "transform": "Identity" + }, + { + "key": "hash-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "transform": "Identity" + }, + { + "key": "account-hash-56befc13a6fd62e18f361700a5e08f966901c34df8041b36ec97d54d605c23de", + "transform": "Identity" + }, + { + "key": "hash-c3d73ae472263ea840378de7487c67b3b598dcd281d7851a429e783b25d4e959", + "transform": "Identity" + }, + { + "key": "hash-1499242da9799e647edf8e0903e75776fe5c1df56a26de15d028422ef1f2bf93", + "transform": "Identity" + }, + { + "key": "hash-c3d73ae472263ea840378de7487c67b3b598dcd281d7851a429e783b25d4e959", + "transform": "Identity" + }, + { + "key": "balance-a1e58c31ab6ef541735d5a13ee768c72031b5042c3b128e45119cf1087b3c93d", + "transform": "Identity" + }, + { + "key": "balance-bc174dc2398daae9d676470292db5f34c5c4aafb059ee08543e1d725667a7c1e", + "transform": "Identity" + }, + { + "key": "balance-a1e58c31ab6ef541735d5a13ee768c72031b5042c3b128e45119cf1087b3c93d", + "transform": { + "WriteCLValue": { + "cl_type": "U512", + "bytes": "05900f467738", + "parsed": "242519248784" + } + } + }, + { + "key": "balance-bc174dc2398daae9d676470292db5f34c5c4aafb059ee08543e1d725667a7c1e", + "transform": { + "AddUInt512": "257480751216" + } + }, + { + "key": "hash-c3d73ae472263ea840378de7487c67b3b598dcd281d7851a429e783b25d4e959", + "transform": "Identity" + }, + { + "key": "hash-1499242da9799e647edf8e0903e75776fe5c1df56a26de15d028422ef1f2bf93", + "transform": "Identity" + }, + { + "key": "hash-c3d73ae472263ea840378de7487c67b3b598dcd281d7851a429e783b25d4e959", + "transform": "Identity" + }, + { + "key": "balance-a1e58c31ab6ef541735d5a13ee768c72031b5042c3b128e45119cf1087b3c93d", + "transform": "Identity" + }, + { + "key": "balance-59621579e9cd67523b65d2ac87fe05993be7dfd6ba677fd1b4200e64a53f9d16", + "transform": "Identity" + }, + { + "key": "balance-a1e58c31ab6ef541735d5a13ee768c72031b5042c3b128e45119cf1087b3c93d", + "transform": { + "WriteCLValue": { + "cl_type": "U512", + "bytes": "00", + "parsed": "0" + } + } + }, + { + "key": "balance-59621579e9cd67523b65d2ac87fe05993be7dfd6ba677fd1b4200e64a53f9d16", + "transform": { + "AddUInt512": "242519248784" + } + } + ] + }, + "transfers": [], + "cost": "239918433115" + } + } + } + } +} + + diff --git a/Casper.Network.SDK.Test/TestData/get-deploy-result-version2-success.json b/Casper.Network.SDK.Test/TestData/get-deploy-result-version2-success.json new file mode 100644 index 0000000..6b31829 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-deploy-result-version2-success.json @@ -0,0 +1,225 @@ +{ + "api_version": "2.0.0", + "deploy": { + "hash": "8738a51ca5c656e1c3eea5d4473fd07f92d6bb7dfd2d75d8d8dc59331545096c", + "header": { + "account": "0184f6d260f4ee6869ddb36affe15456de6ae045278fa2f467bb677561ce0dad55", + "timestamp": "2024-06-28T13:05:19.722Z", + "ttl": "3m", + "gas_price": 1, + "body_hash": "3cd24b57053a21605a0569edcd0da556782db67d5ee0529dedecc9e1271d34d6", + "dependencies": [], + "chain_name": "casper-net-1" + }, + "payment": { + "ModuleBytes": { + "module_bytes": "", + "args": [ + [ + "amount", + { + "cl_type": "U512", + "bytes": "0500f2052a01", + "parsed": "5000000000" + } + ] + ] + } + }, + "session": { + "StoredVersionedContractByHash": { + "hash": "2f5f35e499d33f7bbb03f6657c12c3bee0ca2eebd9cfe177fb5cc2dbb6378feb", + "version": null, + "entry_point": "transfer", + "args": [ + [ + "recipient", + { + "cl_type": "Key", + "bytes": "006bccb64a7904b7217dd4ed7d9e4163785fe2133d45d390250b18f9442917bc0a", + "parsed": "account-hash-6bccb64a7904b7217dd4ed7d9e4163785fe2133d45d390250b18f9442917bc0a" + } + ], + [ + "amount", + { + "cl_type": "U256", + "bytes": "02d430", + "parsed": "12500" + } + ] + ] + } + }, + "approvals": [ + { + "signer": "0184f6d260f4ee6869ddb36affe15456de6ae045278fa2f467bb677561ce0dad55", + "signature": "01c5caded9fd67da7232ad55b1fe3f6781ee5212c3c71b755866d63543d014a2501dece11e3f3c1073b919dcaf41b09d1709b3da7caae07bbbb4eb2d01876c7c0a" + } + ] + }, + "execution_info": { + "block_hash": "638d6a36843f153f173810569ca41129228d49e9f786bb96cd59c0215c2101b6", + "block_height": 1964, + "execution_result": { + "Version2": { + "initiator": { + "PublicKey": "0184f6d260f4ee6869ddb36affe15456de6ae045278fa2f467bb677561ce0dad55" + }, + "error_message": null, + "limit": "500000000000", + "consumed": "537061384", + "cost": "500000000000", + "payment": [], + "transfers": [], + "size_estimate": 392, + "effects": [ + { + "key": "balance-hold-01bc174dc2398daae9d676470292db5f34c5c4aafb059ee08543e1d725667a7c1e1d4ef35e90010000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "050088526a74", + "parsed": "500000000000" + } + } + } + }, + { + "key": "package-2f5f35e499d33f7bbb03f6657c12c3bee0ca2eebd9cfe177fb5cc2dbb6378feb", + "kind": "Identity" + }, + { + "key": "entity-contract-3ec4d7e8ec755ba06be9ee21dd0ffa69028fb92fbe4316e880bb417a13898d01", + "kind": "Identity" + }, + { + "key": "package-2f5f35e499d33f7bbb03f6657c12c3bee0ca2eebd9cfe177fb5cc2dbb6378feb", + "kind": "Identity" + }, + { + "key": "entry-point-v1-entity-contract-3ec4d7e8ec755ba06be9ee21dd0ffa69028fb92fbe4316e880bb417a13898d01-3820ce25e54df0470fb738e3e0f63ee50b2719cf2680da2bdb579e21aebc8f63", + "kind": "Identity" + }, + { + "key": "byte-code-v1-wasm-906aa4db02430ae0894f6996c3dcb423013d9878037ad84a46c6f6ac78b7d5b1", + "kind": "Identity" + }, + { + "key": "dictionary-3242adf99a374c325bbc98d29323805a5a0c7d81c0b55731027111bc1b220869", + "kind": "Identity" + }, + { + "key": "dictionary-5f00faae08f256fdbbcab53942ae89f0961dbc28142c49d3f6fc7efb827887cf", + "kind": "Identity" + }, + { + "key": "dictionary-3242adf99a374c325bbc98d29323805a5a0c7d81c0b55731027111bc1b220869", + "kind": { + "Write": { + "CLValue": { + "cl_type": "Any", + "bytes": "050000000484379a3b0720000000aeae29762c24430dd4e6dd0ff93994abd53c43907103a27bfe7d3881fdcebbc02c0000004146612b2f424f6d2f574c686a7a5958414b58676a355a7041634e4e2b4151624e7579583155316758435065", + "parsed": null + } + } + } + }, + { + "key": "dictionary-5f00faae08f256fdbbcab53942ae89f0961dbc28142c49d3f6fc7efb827887cf", + "kind": { + "Write": { + "CLValue": { + "cl_type": "Any", + "bytes": "03000000027c920720000000aeae29762c24430dd4e6dd0ff93994abd53c43907103a27bfe7d3881fdcebbc02c0000004147764d746b7035424c636866645474665a35425933686634684d3952644f514a5173592b5551704637774b", + "parsed": null + } + } + } + }, + { + "key": "uref-3d67308514d1a0c447a07f088b165d54aa13f601f9e58791e059bc6344cd84f9-000", + "kind": "Identity" + }, + { + "key": "uref-c241213422d3c9adb0afa8437ea3dab8c69dd863c0cf44536d7586538545b482-000", + "kind": "Identity" + }, + { + "key": "dictionary-b8404730e4b5686f96941958a9aabab4ee4997b2333cee681e96ed986d159107", + "kind": { + "Write": { + "CLValue": { + "cl_type": "Any", + "bytes": "5b000000570000000e0000006576656e745f5472616e736665720056befc13a6fd62e18f361700a5e08f966901c34df8041b36ec97d54d605c23de006bccb64a7904b7217dd4ed7d9e4163785fe2133d45d390250b18f9442917bc0a02d4300e032000000083fe09fd54cc79ce67e513bdef50297936fdcef11fa7886f984a373aa2fa000c0100000032", + "parsed": null + } + } + } + }, + { + "key": "uref-c241213422d3c9adb0afa8437ea3dab8c69dd863c0cf44536d7586538545b482-000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U32", + "bytes": "03000000", + "parsed": 3 + } + } + } + }, + { + "key": "balance-hold-01bc174dc2398daae9d676470292db5f34c5c4aafb059ee08543e1d725667a7c1e1d4ef35e90010000", + "kind": { + "Prune": "balance-hold-01bc174dc2398daae9d676470292db5f34c5c4aafb059ee08543e1d725667a7c1e1d4ef35e90010000" + } + }, + { + "key": "balance-hold-00bc174dc2398daae9d676470292db5f34c5c4aafb059ee08543e1d725667a7c1e1d4ef35e90010000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "050088526a74", + "parsed": "500000000000" + } + } + } + }, + { + "key": "entity-system-96d9230e1a4e2154023d3d8f581a9f0503bae015907aec8debdac83054e3c019", + "kind": "Identity" + }, + { + "key": "entity-system-c3d73ae472263ea840378de7487c67b3b598dcd281d7851a429e783b25d4e959", + "kind": "Identity" + }, + { + "key": "entity-system-bd8cb7d0da04ad619b8981e0ffa13ea80c3fa6deb8c56fbff435e922e4d92774", + "kind": "Identity" + }, + { + "key": "bid-addr-011c50d14ca563d5afe0399a3da010bac01383f502bc7cce774cc03d858a94cf06", + "kind": "Identity" + }, + { + "key": "bid-addr-041c50d14ca563d5afe0399a3da010bac01383f502bc7cce774cc03d858a94cf06ba00000000000000", + "kind": { + "Write": { + "BidKind": { + "Credit": { + "validator_public_key": "01f58b94526d280881f79744effebc555426190950d5dfdd2f8aaf10ceaec010c6", + "era_id": 186, + "amount": "500000000000" + } + } + } + } + } + ] + } + } + } +} diff --git a/Casper.Network.SDK.Test/TestData/get-entity-account-v200.json b/Casper.Network.SDK.Test/TestData/get-entity-account-v200.json new file mode 100644 index 0000000..8e080fa --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-entity-account-v200.json @@ -0,0 +1,30 @@ +{ + "api_version": "2.0.0", + "entity": { + "AddressableEntity": { + "entity": { + "protocol_version": "2.0.0", + "entity_kind": { + "Account": "account-hash-65a3d53119035ffe8560a67e355a80b2edaf2673fbd2d1d90b70a033b1566213" + }, + "package_hash": "package-8117d677f8d08045b0db10c9efe309a801c39c3c41e86bc801883c6343fbe318", + "byte_code_hash": "byte-code-0000000000000000000000000000000000000000000000000000000000000000", + "main_purse": "uref-7decb5dcf734bace39f398bc19a3f371ac5ffe68c23f7fa8c2abaf7ae2edd1ad-007", + "associated_keys": [ + { + "account_hash": "account-hash-65a3d53119035ffe8560a67e355a80b2edaf2673fbd2d1d90b70a033b1566213", + "weight": 1 + } + ], + "action_thresholds": { + "deployment": 1, + "upgrade_management": 1, + "key_management": 1 + } + }, + "named_keys": [], + "entry_points": [] + } + }, + "merkle_proof": "01000000110165a3d53119035ffe8560a67e355a80b2edaf2673fbd2d1d90b70a033b15662130d8117d677f8d08045b0db10c9efe309a801c39c3c41e86bc801883c6343fbe31800000000000000000000000000000000000000000000000000000000000000000200000000000000000000007decb5dcf734bace39f398bc19a3f371ac5ffe68c23f7fa8c2abaf7ae2edd1ad070100000065a3d53119035ffe8560a67e355a80b2edaf2673fbd2d1d90b70a033b156621301010101000000000165a3d53119035ffe8560a67e355a80b2edaf2673fbd2d1d90b70a033b1566213030000000065140000001c004140e396af85425c4d13c428fff8c94a6f7bb5dec7fd618b4e23b3b58369721822007ab457a5e6805d0fdeb823c09ab40dff1d4941951ab0b2645861aeb8e4d6b2d02b00f0f334d6a39190dbc16682ec95db4553df44d11558952b172106082bac5e0aa52f00b7dacae2754a0970a96016fa199d5ad254701873fee1cb50436cd2288b5b83a13d01c02bcc20002435fa0760dfcae0f0b3d65909f0907169ef07fc33ceee4a93ff813e00379d62bdd28b3cc340a34484ad14325f06a21b3c34d15b45ed1b5ae23cdc70e15200f060fe324c53b5e1746c06d158b1919596c3ce71a5fe3b9ff115e137415f23d75300c1603481f9cd5dfa0d86f68321cc1cba0baf160efa55e19de1ca3516468617bc5600d31eb794b67f631b10b1f5372991c4ea7367fc05ccc8e4b2bdf147d1b353f7c3610095fc5e8e2f88cf31f67b30c74e507cbfbc3370750d98834204cca8d5f43e1b4a6b0032d4fcc51712e24553680440cb511aa1a91a1fa554c9135926eb3566931e2ace87008b6dd1ababcce7cc20691e6c0e87fef231b7c9c342a43ab7b2b935b39dd4848f8800c53304e96293c383d723e2d1ba7a2b0a3f6df67643f5c45363d19a88d561163f9f0017d4c50c86f70a2ea288b1deafe78eb99986a312e750f1de6c3d75daefb83d1dc20079f01d2ea83a6ea636b1c24e9ddc7ed3487a96c5f829ffc64a2a8f8f1dd00855cd001f40c6887abe655291469455b0d84c7cd58b74d896ed63e2b58d488906d054d8db00fabbb7ee9e93dcb60b3d4f70ab3c3fde9172dcdbf9ca90bd686b2632e73485afdc00e16ac8017d72f86f6b2c9fa52d6e177efa53c89c9d734ab1c64206e6005fd2eae200a13d5697094dbcef9b9bea3a8a59f0ce57a48445ade810658f7a127eb4f9e31dff00d60902a31f311443a0490c83cc48591c64da565901940683bc4cd43fea3a8d070001010000000001329fa50131086d2de5e18cdb8487b1e27a2e04ce7a574f185d22862019271fa100110d000000000106797c4406a0a246f352ba5592920ed0b3e214c688e080962ca74d36d7a1a99002012ec2b919aac0633305c4259420ce41a8b3a530e1b7dbbdce3c60c72cd1968c9506011ff8296cf60a3a7469992155c753f1dcb063a1db770cb5fe4e4f591d5ad421dd0a0029b220b5550023f51a78e121017aebf0089c690ab74adff840c31392ce2a59370b00a4c3b00132e152ef5fe45d3a7c56254b8b1651565b7c16e34a7b0abf6969ae820d0010fa1f5eb43fa8f8d7d286d8e5394ce7362095bd049788c3f8eb82f997e675240e0043d39741a6e7b395eace3b1fbbbd5bbf288f7a788e80745a2e2bb0f2173a8b4f0f013853ed3060a42737482e66540def898bdbf8534e7b24e9999a5af9fe6671ba0e100159f9269ebe85ad359217e196d018cacb941a04bb7f8e251f0c73435fc83c277c1200693fff58318fbd94674eb111fbc49ea8ab66ece1cd614b0008d387f228d778ee14015a38f15c34cc79868b4445af51125e24c100d0cf719b1eef5202458d74c146751500dd70cbafab73cb6edfb05345ed175bd858f2985199a9fa113e6d7710facf8bed17012e0cdc4b06694b367a198ff7f1676e8fd104c958d902b2e27b7117dd1572e234" +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/get-entity-contract-v200.json b/Casper.Network.SDK.Test/TestData/get-entity-contract-v200.json new file mode 100644 index 0000000..584f4af --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-entity-contract-v200.json @@ -0,0 +1,379 @@ +{ + "api_version": "2.0.0", + "entity": { + "AddressableEntity": { + "entity": { + "protocol_version": "2.0.0", + "entity_kind": { + "SmartContract": "VmCasperV1" + }, + "package_hash": "package-7a25b7c52f6c69b8d229ae50c821c5edf4e0c2bd3ac73ec52386ed42440ce5c6", + "byte_code_hash": "byte-code-85def61e3ee02e10a1e845cfb8e8b2d9640a18f605333158027a24ed8569d895", + "main_purse": "uref-d3df027aba457c3e0a657148427f90c5a6fcdbe33b0c841ee91aacd63838cbba-007", + "associated_keys": [ + { + "account_hash": "account-hash-9fa1fc0808d3a5b9ea9f3af4ca7c8c3655568fdf378d8afdf8a7e56e58abbfd4", + "weight": 1 + } + ], + "action_thresholds": { + "deployment": 1, + "upgrade_management": 1, + "key_management": 1 + } + }, + "named_keys": [ + { + "name": "allowances", + "key": "uref-2573b5cca61e31f7b602f135a9abbe096367f5c07f593d006ba10134d3698aa4-007" + }, + { + "name": "balances", + "key": "uref-96cd0453fb2e1d063c9438c158c0d804d0121a96f3423046150ee355cfadefb6-007" + }, + { + "name": "contract_hash", + "key": "entity-contract-9bd4c534c8055bc82e44a9f8c02b485008ee28108f79c8556b57dc9f5a8ef63d" + }, + { + "name": "decimals", + "key": "uref-da8081ab755640837c90d9088579f9d7e29c09c3ba0295f59b9ee166ba0bf374-007" + }, + { + "name": "enable_mint_burn", + "key": "uref-cc1accfee71597d884d2e22f7cb166f53cdf001e568a6d32ed74877d80472fca-007" + }, + { + "name": "events_mode", + "key": "uref-c537df8ce12730db7a9b74f5338b2121751d5813c65e648b7bba9a6223f5353c-007" + }, + { + "name": "name", + "key": "uref-bd0fc79a60c201f454f6840e3ed2a9f81f1ea55a7d5f59b08df5f0450b5dc9e6-007" + }, + { + "name": "package_hash", + "key": "package-7a25b7c52f6c69b8d229ae50c821c5edf4e0c2bd3ac73ec52386ed42440ce5c6" + }, + { + "name": "security_badges", + "key": "uref-f0ac8d1883c4be33bf902b44f1227b6f1fccfcb9bfa491eb13ab2d18ffeeca5e-007" + }, + { + "name": "symbol", + "key": "uref-5dde41a496dd0b1c55b44ca8fbeeb4d17669a019067533ec0ace3571c9b82b8b-007" + }, + { + "name": "total_supply", + "key": "uref-007bc04abf7d45a8e2748373899ead7691fe5349d9bcd5d95740d602a9245c4b-007" + } + ], + "entry_points": [ + { + "V1CasperVm": { + "name": "migrate_user_allowance_keys", + "args": [ + { + "name": "events", + "cl_type": "Bool" + }, + { + "name": "revert", + "cl_type": "Bool" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "allowance", + "args": [ + { + "name": "owner", + "cl_type": "Key" + }, + { + "name": "spender", + "cl_type": "Key" + } + ], + "ret": "U256", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "migrate_user_balance_keys", + "args": [ + { + "name": "events", + "cl_type": "Bool" + }, + { + "name": "revert", + "cl_type": "Bool" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "mint", + "args": [ + { + "name": "owner", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "transfer", + "args": [ + { + "name": "recipient", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "transfer_from", + "args": [ + { + "name": "owner", + "cl_type": "Key" + }, + { + "name": "recipient", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "approve", + "args": [ + { + "name": "spender", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "init", + "args": [], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "name", + "args": [], + "ret": "String", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "burn", + "args": [ + { + "name": "owner", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "change_security", + "args": [], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "increase_allowance", + "args": [ + { + "name": "spender", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "decrease_allowance", + "args": [ + { + "name": "spender", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "change_events_mode", + "args": [ + { + "name": "events_mode", + "cl_type": "U8" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "decimals", + "args": [], + "ret": "U8", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "symbol", + "args": [], + "ret": "String", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "total_supply", + "args": [], + "ret": "U256", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "migrate_sec_keys", + "args": [ + { + "name": "events", + "cl_type": "Bool" + }, + { + "name": "revert", + "cl_type": "Bool" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + }, + { + "V1CasperVm": { + "name": "balance_of", + "args": [ + { + "name": "address", + "cl_type": "Key" + } + ], + "ret": "U256", + "access": "Public", + "entry_point_type": "Called", + "entry_point_payment": "Caller" + } + } + ] + } + }, + "merkle_proof": "0100000011029bd4c534c8055bc82e44a9f8c02b485008ee28108f79c8556b57dc9f5a8ef63d0d7a25b7c52f6c69b8d229ae50c821c5edf4e0c2bd3ac73ec52386ed42440ce5c685def61e3ee02e10a1e845cfb8e8b2d9640a18f605333158027a24ed8569d895020000000000000000000000d3df027aba457c3e0a657148427f90c5a6fcdbe33b0c841ee91aacd63838cbba07010000009fa1fc0808d3a5b9ea9f3af4ca7c8c3655568fdf378d8afdf8a7e56e58abbfd40101010101000000060000006576656e74735721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e10200020000000002020000000001329fa50131086d2de5e18cdb8487b1e27a2e04ce7a574f185d22862019271fa1010109b9c1523ad187123214b99094869646952ecf4f56f83c168adb43c690f8c7090011100000000001e9602fcc1c2dd571ad440b6157fb44ae056257301ae8ebbe64ed156289a18ed2020161620e026b0fb697da2be845771ddadeaa555a8c421b5c06f1c2209f582cda8c060166119f72425746e2133a0a8d67dfd34cd0d89db2e86e37a112c5089bf5b9524e09011d429bda3bf002784d0ea8ba8cd94ca26534c2102a088daeece5f28608a6ad240a0029b220b5550023f51a78e121017aebf0089c690ab74adff840c31392ce2a59370b00a6503cbee1ef52651ad311cd7d32d727d1c5349afa428b0109ae30e0e69aed2f0d0010fa1f5eb43fa8f8d7d286d8e5394ce7362095bd049788c3f8eb82f997e675240e0043d39741a6e7b395eace3b1fbbbd5bbf288f7a788e80745a2e2bb0f2173a8b4f0f015ba4f65f5b54cab5eab7171a7218f09c4cb20cf3a5127b7491ff9230b1ecf2ea10016997f28e86b4a18d9f74c494e44cc58a3aecec712e5f332917b6ec751f7a42711201dc991a88ba67859da4efb3d13273933b7d6e2e1cd3826e81a966b967c8cf54ee1301cfce4655aaa43c05dc69a3ebb887808e9d04b01d9c4bff5913f80c1136b83ffd1401c0956fb9a4815604a8e12528088273a2bdc7961cdc7480a6fa1c54bad136fb501501f912293449cf99294a99e4d774b1c2f8fb7f8760cd9c5625a60de980de7c3e381601483cc5eb620c842e555ffa3b69feff61910c79740010b80f8d30d6bc73d6b06417012e40a254b1d0a5136f9713b4af327548efed7ff2d2177446f14c57c103b4c4b9" +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/get-package-result-contract-package-v200.json b/Casper.Network.SDK.Test/TestData/get-package-result-contract-package-v200.json new file mode 100644 index 0000000..683e54b --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-package-result-contract-package-v200.json @@ -0,0 +1,26 @@ +{ + "api_version": "2.0.0", + "package": { + "ContractPackage": { + "access_key": "uref-6fc684fea74b278cbb18b546a6d9242b810ce58a2ff05d17493b19aa08f540e0-007", + "versions": [ + { + "protocol_version_major": 2, + "contract_version": 1, + "contract_hash": "contract-25aa2d3cc62a302746c08ae885454d6e8a9c8609aaa7468b24284e5d29c5d2f1" + }, + { + "protocol_version_major": 2, + "contract_version": 4, + "contract_hash": "contract-2222222222222222222222222222222222222222222222222222222222222222" + } + ], + "disabled_versions": [ + [2,4] + ], + "groups": [], + "lock_status": "Unlocked" + } + }, + "merkle_proof": "010000000116aa0d0ad73d3534ba543a574f2a521fd770e4cb436ca0741e8a02eae8fdbf08046fc684fea74b278cbb18b546a6d9242b810ce58a2ff05d17493b19aa08f540e00701000000020000000100000025aa2d3cc62a302746c08ae885454d6e8a9c8609aaa7468b24284e5d29c5d2f1000000000000000000020000000016080000002500309ea1908b14a258eb4246b0b4cf2181f9a079e97571bb5cf5edf2cc6b1dd26848001b5a6e381c3fd5e9983555281b928e75ee6e6b17eb4ca779581b2073313e9e7d5300162d2614d188e62d6b8d8148b9455feedb784c39230ef2df6610f4706da05a466300d25c1b5580816475df74470a0c397baf8c4c5ea1016f9742414fc145d048aca3a600f91a5d4a604d79b244b83c89716caf516e75e951f4e32995101916fdeea30721ae00a77ba4e92715b5285ff5bbda6cfc1a75fb6160d9f8c237abe9c7eef3aa0d448abb0032729b6868c15a80e2315f291020073922c81a445f3220e6214262823e7690a7e3000264f63a177c0d3bdf4501f85fc99ef7a25335f15cf01c7e5092e101eb13c8810001090000000001a3595f9802a8c4ecc4f74cbbcf6ea4cd484fb643ee6ed49f6d6a8ad4816f123002013ba7feb38dc3c94f0fc6dd8eefa88b87e0a6d0fb6372d8c166c0861812efa0970601fbbff6ca6374c82315ce70bd07ad0b6e503aab81fb9eedf4eee2806cdb44f5310a0062e02bef4f516c7bf650d0e7a7f92c3278515b193eb2067cea0b40f8208626d40b00b7aac0f3f6692862b38c63e0df2a381945f48c00f7dba20994dde801b102e3eb0d004cc57d085abe7a34c1c509178c52296cedf995770f0023a195cbf3b713494a9c0e0043d39741a6e7b395eace3b1fbbbd5bbf288f7a788e80745a2e2bb0f2173a8b4f0f01951506a8635a859363abd1c96f88d1b972d2b8be5b8620c9e01d9cf0933a477b1500cb808b7d2846894f16ea158ab0df1e92b8ce3bf9e19d1e06e66662f424906cb9" +} diff --git a/Casper.Network.SDK.Test/TestData/get-package-result-package-v200.json b/Casper.Network.SDK.Test/TestData/get-package-result-package-v200.json new file mode 100644 index 0000000..7ef2028 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-package-result-package-v200.json @@ -0,0 +1,20 @@ +{ + "api_version": "2.0.0", + "package": { + "Package": { + "versions": [ + { + "entity_version_key": { + "protocol_version_major": 2, + "entity_version": 1 + }, + "entity_addr": "entity-contract-e51af99d88fd26a282de00271c49a6c256232b344aa7907d2c8603b2bd5217c9" + } + ], + "disabled_versions": [], + "groups": [], + "lock_status": "Unlocked" + } + }, + "merkle_proof": "010000000116aa0d0ad73d3534ba543a574f2a521fd770e4cb436ca0741e8a02eae8fdbf08046fc684fea74b278cbb18b546a6d9242b810ce58a2ff05d17493b19aa08f540e00701000000020000000100000025aa2d3cc62a302746c08ae885454d6e8a9c8609aaa7468b24284e5d29c5d2f1000000000000000000020000000016080000002500309ea1908b14a258eb4246b0b4cf2181f9a079e97571bb5cf5edf2cc6b1dd26848001b5a6e381c3fd5e9983555281b928e75ee6e6b17eb4ca779581b2073313e9e7d5300162d2614d188e62d6b8d8148b9455feedb784c39230ef2df6610f4706da05a466300d25c1b5580816475df74470a0c397baf8c4c5ea1016f9742414fc145d048aca3a600f91a5d4a604d79b244b83c89716caf516e75e951f4e32995101916fdeea30721ae00a77ba4e92715b5285ff5bbda6cfc1a75fb6160d9f8c237abe9c7eef3aa0d448abb0032729b6868c15a80e2315f291020073922c81a445f3220e6214262823e7690a7e3000264f63a177c0d3bdf4501f85fc99ef7a25335f15cf01c7e5092e101eb13c8810001090000000001a3595f9802a8c4ecc4f74cbbcf6ea4cd484fb643ee6ed49f6d6a8ad4816f123002013ba7feb38dc3c94f0fc6dd8eefa88b87e0a6d0fb6372d8c166c0861812efa0970601fbbff6ca6374c82315ce70bd07ad0b6e503aab81fb9eedf4eee2806cdb44f5310a0062e02bef4f516c7bf650d0e7a7f92c3278515b193eb2067cea0b40f8208626d40b00b7aac0f3f6692862b38c63e0df2a381945f48c00f7dba20994dde801b102e3eb0d004cc57d085abe7a34c1c509178c52296cedf995770f0023a195cbf3b713494a9c0e0043d39741a6e7b395eace3b1fbbbd5bbf288f7a788e80745a2e2bb0f2173a8b4f0f01951506a8635a859363abd1c96f88d1b972d2b8be5b8620c9e01d9cf0933a477b1500cb808b7d2846894f16ea158ab0df1e92b8ce3bf9e19d1e06e66662f424906cb9" +} diff --git a/Casper.Network.SDK.Test/TestData/get-transaction-deploy-stored-v200.json b/Casper.Network.SDK.Test/TestData/get-transaction-deploy-stored-v200.json new file mode 100644 index 0000000..54766a3 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-transaction-deploy-stored-v200.json @@ -0,0 +1,282 @@ +{ + "api_version": "2.0.0", + "transaction": { + "Deploy": { + "hash": "7958e3156a2770aec31587d61bcbc92073492d6d4fc8c99a7fd5718e534dee31", + "header": { + "account": "0106ed45915392c02b37136618372ac8dde8e0e3b8ee6190b2ca6db539b354ede4", + "timestamp": "2024-07-17T09:12:04.578Z", + "ttl": "3m", + "gas_price": 1, + "body_hash": "8d18c7a727317c54fb8ac21ba2e02ec8f4d24d0f4cb4183d3df05afe86ba614c", + "dependencies": [], + "chain_name": "casper-net-1" + }, + "payment": { + "ModuleBytes": { + "module_bytes": "", + "args": [ + [ + "amount", + { + "cl_type": "U512", + "bytes": "05001417c668", + "parsed": "450000000000" + } + ] + ] + } + }, + "session": { + "StoredVersionedContractByName": { + "name": "cep18_contract_package_CLICK1 Test", + "version": null, + "entry_point": "transfer", + "args": [ + [ + "recipient", + { + "cl_type": "Key", + "bytes": "00ffaa73331421de42fd71a28824e1606a1ca7fa754f91f501f0cf56015f7284cd", + "parsed": "account-hash-ffaa73331421de42fd71a28824e1606a1ca7fa754f91f501f0cf56015f7284cd" + } + ], + [ + "amount", + { + "cl_type": "U256", + "bytes": "02d430", + "parsed": "12500" + } + ] + ] + } + }, + "approvals": [ + { + "signer": "0106ed45915392c02b37136618372ac8dde8e0e3b8ee6190b2ca6db539b354ede4", + "signature": "01bcc798d081a34bdce01167b4b37a713f13da89eaf4e7382c6757646cb36da97dfa43c60498860ecbabaf2c68867b459f625b6006bec12b84ab0ea94c5a892a00" + } + ] + } + }, + "execution_info": { + "block_hash": "827d030f98782c1e68da7f6be464681821ab6a49c134364ac1b3fdbed236b25f", + "block_height": 2123, + "execution_result": { + "Version2": { + "initiator": { + "PublicKey": "0106ed45915392c02b37136618372ac8dde8e0e3b8ee6190b2ca6db539b354ede4" + }, + "error_message": null, + "limit": "500000000000", + "consumed": "760483530", + "cost": "500000000000", + "payment": [], + "transfers": [], + "size_estimate": 398, + "effects": [ + { + "key": "balance-hold-016f8eee1d8ea5eee794ffe69a9008c9c8620eea284ee2c49bf686ae8eee0422155198f6bf90010000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "050088526a74", + "parsed": "500000000000" + } + } + } + }, + { + "key": "package-ab86f3895a5b0d8cd15113ec159f425037aceb9bb1b464affd9db54b592accf4", + "kind": "Identity" + }, + { + "key": "entity-contract-d8850e3227ce97cdb21a3e266fe360a196ec6c97058ee1ea242da0d03aa51c58", + "kind": "Identity" + }, + { + "key": "package-ab86f3895a5b0d8cd15113ec159f425037aceb9bb1b464affd9db54b592accf4", + "kind": "Identity" + }, + { + "key": "entry-point-v1-entity-contract-d8850e3227ce97cdb21a3e266fe360a196ec6c97058ee1ea242da0d03aa51c58-3820ce25e54df0470fb738e3e0f63ee50b2719cf2680da2bdb579e21aebc8f63", + "kind": "Identity" + }, + { + "key": "byte-code-v1-wasm-800e35cfd87b0f36479afdaf68725cd7ef0f503e8d541ed33afad4a18c7fa6eb", + "kind": "Identity" + }, + { + "key": "dictionary-ede600b099900d357796a3e23eecb1e5277bdfb93297c36f0c039d2c3a5c6557", + "kind": "Identity" + }, + { + "key": "dictionary-ede600b099900d357796a3e23eecb1e5277bdfb93297c36f0c039d2c3a5c6557", + "kind": { + "Write": { + "CLValue": { + "cl_type": "Any", + "bytes": "0800000007162ac5a47e8d030720000000f032be9ae9bd295ec0403aa886d7247d27c296751176bd88d3b02c996626fd053000000045514869587777376d477171476d7946376a5672365a7a5449506f66664f72356b6f6f2f765146647352386b44773d3d", + "parsed": null + } + } + } + }, + { + "key": "dictionary-7a57f65f11b9d64d61137b5ad8212378b357078550a2104fdecd6e355c26e052", + "kind": { + "Write": { + "CLValue": { + "cl_type": "Any", + "bytes": "0300000002d4300720000000f032be9ae9bd295ec0403aa886d7247d27c296751176bd88d3b02c996626fd052c00000041502b71637a4d55496435432f5847696943546859476f63702f7031543548314166445056674666636f544e", + "parsed": null + } + } + } + }, + { + "key": "uref-08f9c3e110c4d5cbc6e8ce71dd0cd535e4f41b34fe88c26cf3cd222004373db3-000", + "kind": "Identity" + }, + { + "key": "message-topic-entity-contract-d8850e3227ce97cdb21a3e266fe360a196ec6c97058ee1ea242da0d03aa51c58-5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1", + "kind": "Identity" + }, + { + "key": "message-entity-contract-d8850e3227ce97cdb21a3e266fe360a196ec6c97058ee1ea242da0d03aa51c58-5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1-1", + "kind": { + "Prune": "message-entity-contract-d8850e3227ce97cdb21a3e266fe360a196ec6c97058ee1ea242da0d03aa51c58-5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1-1" + } + }, + { + "key": "message-entity-contract-d8850e3227ce97cdb21a3e266fe360a196ec6c97058ee1ea242da0d03aa51c58-5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1-2", + "kind": { + "Prune": "message-entity-contract-d8850e3227ce97cdb21a3e266fe360a196ec6c97058ee1ea242da0d03aa51c58-5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1-2" + } + }, + { + "key": "block-message-count-00000000000000000000000000000000000000000000000000000000000000", + "kind": "Identity" + }, + { + "key": "message-entity-contract-d8850e3227ce97cdb21a3e266fe360a196ec6c97058ee1ea242da0d03aa51c58-5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1-0", + "kind": { + "Write": { + "Message": "message-checksum-4e8fa8422d6022df02a2d41568e25ebfbc9d7d098f8c31f42e4493523627f0f4" + } + } + }, + { + "key": "message-topic-entity-contract-d8850e3227ce97cdb21a3e266fe360a196ec6c97058ee1ea242da0d03aa51c58-5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1", + "kind": { + "Write": { + "MessageTopic": { + "message_count": 1, + "blocktime": 1721207527505 + } + } + } + }, + { + "key": "block-message-count-00000000000000000000000000000000000000000000000000000000000000", + "kind": { + "Write": { + "CLValue": { + "cl_type": { + "Tuple2": [ + "U64", + "U64" + ] + }, + "bytes": "5198f6bf900100000100000000000000", + "parsed": [ + 1721207527505, + 1 + ] + } + } + } + }, + { + "key": "uref-ab3e2fffdfad1bea4c6cea416dc7b820f769dc1ce9071d524d63e704ff0c3ae4-000", + "kind": "Identity" + }, + { + "key": "dictionary-d16f0a36780c0a7f72457a751a5f79125b1a3c492cb86456adeff5b0111d2640", + "kind": { + "Write": { + "CLValue": { + "cl_type": "Any", + "bytes": "5c000000580000000e0000006576656e745f5472616e736665721101e25f0c3b986aaa1a6c85ee356be99cd320fa1f7ceaf9928a3fbd015db11f240f00ffaa73331421de42fd71a28824e1606a1ca7fa754f91f501f0cf56015f7284cd02d4300e03200000009d1bc561e4c28250e3595b12b78989409f69ad336a8e398b4e9f6b91b3c283170100000037", + "parsed": null + } + } + } + }, + { + "key": "uref-ab3e2fffdfad1bea4c6cea416dc7b820f769dc1ce9071d524d63e704ff0c3ae4-000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U32", + "bytes": "08000000", + "parsed": 8 + } + } + } + }, + { + "key": "balance-hold-016f8eee1d8ea5eee794ffe69a9008c9c8620eea284ee2c49bf686ae8eee0422155198f6bf90010000", + "kind": { + "Prune": "balance-hold-016f8eee1d8ea5eee794ffe69a9008c9c8620eea284ee2c49bf686ae8eee0422155198f6bf90010000" + } + }, + { + "key": "balance-hold-006f8eee1d8ea5eee794ffe69a9008c9c8620eea284ee2c49bf686ae8eee0422155198f6bf90010000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "050088526a74", + "parsed": "500000000000" + } + } + } + }, + { + "key": "entity-system-8499396a854bbe7e172a5ab675caf3c7b944f57e43cee5b7563971678f8650b2", + "kind": "Identity" + }, + { + "key": "entity-system-989625d28e779749e3cacc7367403dd459c8e7082937436f16d5dbff7aec8292", + "kind": "Identity" + }, + { + "key": "entity-system-da2faf76ab117ec370d10f994e9407ff014ab974bedc50e58425a9e35428dd0c", + "kind": "Identity" + }, + { + "key": "bid-addr-011c50d14ca563d5afe0399a3da010bac01383f502bc7cce774cc03d858a94cf06", + "kind": "Identity" + }, + { + "key": "bid-addr-041c50d14ca563d5afe0399a3da010bac01383f502bc7cce774cc03d858a94cf06c400000000000000", + "kind": { + "Write": { + "BidKind": { + "Credit": { + "validator_public_key": "01f58b94526d280881f79744effebc555426190950d5dfdd2f8aaf10ceaec010c6", + "era_id": 196, + "amount": "500000000000" + } + } + } + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/get-transaction-deploy-v200.json b/Casper.Network.SDK.Test/TestData/get-transaction-deploy-v200.json new file mode 100644 index 0000000..069c579 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-transaction-deploy-v200.json @@ -0,0 +1,201 @@ +{ + "api_version": "2.0.0", + "transaction": { + "Deploy": { + "hash": "53363985887173c70955bee232e280429f53ab208e2397b19a5e761215734e56", + "header": { + "account": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a", + "timestamp": "2024-07-17T07:00:19.247Z", + "ttl": "30m", + "gas_price": 1, + "body_hash": "c5cf594dc6d244358fddd6461e93703653bb07a0d9fa957c4a4b5fdc5bc9968d", + "dependencies": [], + "chain_name": "casper-net-1" + }, + "payment": { + "ModuleBytes": { + "module_bytes": "", + "args": [ + [ + "amount", + { + "cl_type": "U512", + "bytes": "0100", + "parsed": "0" + } + ] + ] + } + }, + "session": { + "Transfer": { + "args": [ + [ + "amount", + { + "cl_type": "U512", + "bytes": "0500ba1dd205", + "parsed": "25000000000" + } + ], + [ + "target", + { + "cl_type": { + "ByteArray": 32 + }, + "bytes": "6bccb64a7904b7217dd4ed7d9e4163785fe2133d45d390250b18f9442917bc0a", + "parsed": "6bccb64a7904b7217dd4ed7d9e4163785fe2133d45d390250b18f9442917bc0a" + } + ], + [ + "id", + { + "cl_type": { + "Option": "U64" + }, + "bytes": "010100000000000000", + "parsed": 1 + } + ] + ] + } + }, + "approvals": [ + { + "signer": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a", + "signature": "01a42c794b4f9f1d344a41ec67f0a01a3f5c18b7a04c8948118d172993137d9db526c0e2512b89c45fc100d6cab9c562cccc295d6b0fd667a0c9d211cae88c570b" + } + ] + } + }, + "execution_info": { + "block_hash": "4ab00f303c62fe665fabf1112fc08b995c31b62b7af13390b417d8ba483597d4", + "block_height": 74, + "execution_result": { + "Version2": { + "initiator": { + "PublicKey": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a" + }, + "error_message": null, + "limit": "10000", + "consumed": "10000", + "cost": "10000", + "refund": "30000", + "current_price": 1, + "payment": [], + "transfers": [ + { + "Version2": { + "transaction_hash": { + "Deploy": "53363985887173c70955bee232e280429f53ab208e2397b19a5e761215734e56" + }, + "from": { + "AccountHash": "account-hash-9fa1fc0808d3a5b9ea9f3af4ca7c8c3655568fdf378d8afdf8a7e56e58abbfd4" + }, + "to": "account-hash-6bccb64a7904b7217dd4ed7d9e4163785fe2133d45d390250b18f9442917bc0a", + "source": "uref-8b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce85142-007", + "target": "uref-98735896568d7b44005778ed909e83457e8923c76b2a23bcec09643cc384cfce-004", + "amount": "25000000000", + "gas": "0", + "id": 1 + } + } + ], + "size_estimate": 367, + "effects": [ + { + "key": "balance-hold-018b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce85142d0f57dbf90010000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "021027", + "parsed": "10000" + } + } + } + }, + { + "key": "account-hash-9fa1fc0808d3a5b9ea9f3af4ca7c8c3655568fdf378d8afdf8a7e56e58abbfd4", + "kind": "Identity" + }, + { + "key": "balance-hold-008b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce8514256207dbf90010000", + "kind": "Identity" + }, + { + "key": "balance-hold-018b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce85142d0f57dbf90010000", + "kind": "Identity" + }, + { + "key": "balance-8b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce85142", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "0e005e6be5ec5ac138938d44c64d31", + "parsed": "999999999999999999999875000000000" + } + } + } + }, + { + "key": "balance-98735896568d7b44005778ed909e83457e8923c76b2a23bcec09643cc384cfce", + "kind": { + "AddUInt512": "25000000000" + } + }, + { + "key": "balance-hold-018b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce85142d0f57dbf90010000", + "kind": { + "Prune": "balance-hold-018b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce85142d0f57dbf90010000" + } + }, + { + "key": "balance-hold-008b3e0e98047445553ad20dae8fb539358e4421b49348e7ee8061e3ad5ce85142d0f57dbf90010000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "021027", + "parsed": "10000" + } + } + } + }, + { + "key": "entity-system-8499396a854bbe7e172a5ab675caf3c7b944f57e43cee5b7563971678f8650b2", + "kind": "Identity" + }, + { + "key": "entity-system-989625d28e779749e3cacc7367403dd459c8e7082937436f16d5dbff7aec8292", + "kind": "Identity" + }, + { + "key": "entity-system-da2faf76ab117ec370d10f994e9407ff014ab974bedc50e58425a9e35428dd0c", + "kind": "Identity" + }, + { + "key": "bid-addr-01dcd0c38c46c9d5c7083aa1a46b430e8e460f97f8f0bf8444776ac925187acfcc", + "kind": "Identity" + }, + { + "key": "bid-addr-04dcd0c38c46c9d5c7083aa1a46b430e8e460f97f8f0bf8444776ac925187acfcc0900000000000000", + "kind": { + "Write": { + "BidKind": { + "Credit": { + "validator_public_key": "01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6", + "era_id": 9, + "amount": "10000" + } + } + } + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/get-transaction-session-v200.json b/Casper.Network.SDK.Test/TestData/get-transaction-session-v200.json new file mode 100644 index 0000000..60a108d --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-transaction-session-v200.json @@ -0,0 +1,883 @@ +{ + "api_version": "2.0.0", + "transaction": { + "Version1": { + "hash": "22ef28d7e341c79d868e1be407ec4af4f25c267c0094201ba511045b3af191f6", + "payload": { + "initiator_addr": { + "PublicKey": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a" + }, + "timestamp": "2024-12-18T09:03:15.790Z", + "ttl": "30m", + "chain_name": "casper-net-1", + "pricing_mode": { + "PaymentLimited": { + "payment_amount": 1000000000000, + "gas_price_tolerance": 1, + "standard_payment": true + } + }, + "fields": { + "args": { + "Named": [ + [ + "name", + { + "cl_type": "String", + "bytes": "0b000000434c49434b542054657374", + "parsed": "CLICKT Test" + } + ], + [ + "symbol", + { + "cl_type": "String", + "bytes": "06000000434c49434b54", + "parsed": "CLICKT" + } + ], + [ + "decimals", + { + "cl_type": "U8", + "bytes": "09", + "parsed": 9 + } + ], + [ + "total_supply", + { + "cl_type": "U256", + "bytes": "070080c6a47e8d03", + "parsed": "1000000000000000" + } + ], + [ + "events_mode", + { + "cl_type": "U8", + "bytes": "02", + "parsed": 2 + } + ], + [ + "enable_mint_burn", + { + "cl_type": "U8", + "bytes": "01", + "parsed": 1 + } + ] + ] + }, + "entry_point": "Call", + "scheduling": "Standard", + "target": { + "Session": { + "is_install_upgrade": true, + "module_bytes": "01020304", + "runtime": "VmCasperV1" + } + } + } + }, + "approvals": [ + { + "signer": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a", + "signature": "013bb82f647fcb53a4fa01871e43100bbee1a90370480361a96f426280b73c07adee7a7116b6a433f0173b8a2403d95c3fa84fbcb28f74535f6a598721d299300a" + } + ] + } + }, + "execution_info": { + "block_hash": "856ce7684bc53fc76e27d91a9dfda2c158fd18a69789fecbdf98535d3bddadf0", + "block_height": 72, + "execution_result": { + "Version2": { + "initiator": { + "PublicKey": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a" + }, + "error_message": null, + "limit": "1000000000000", + "consumed": "314102340989", + "cost": "1000000000000", + "transfers": [], + "size_estimate": 278442, + "effects": [ + { + "key": "balance-hold-01e542fb9f5dc6d06d849e2ac3ad6cc5cd4b7c71392dc8759c0898f4ab04439520bee001d993010000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "050010a5d4e8", + "parsed": "1000000000000" + } + } + } + }, + { + "key": "uref-6702ca24ad114bf8fe2e7e2169461b98e11f12ad7ad191d6e4b6594183f64ecf-000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "String", + "bytes": "0b000000434c49434b542054657374", + "parsed": "CLICKT Test" + } + } + } + }, + { + "key": "uref-644d8cce1ad2d0fb9a783a72e9542a4edc3e88e62f13e55d3d6a2899cd205619-000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "String", + "bytes": "06000000434c49434b54", + "parsed": "CLICKT" + } + } + } + }, + { + "key": "uref-127a8c723a6df941dae0d26a9c181c26f1a80f86eaeb12eed786b4c9715b5b55-000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U8", + "bytes": "09", + "parsed": 9 + } + } + } + }, + { + "key": "uref-411a53953e73ddd23a115ad96e7ccb53e1867ec4beec03e62b8c7aa400be56fe-000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U256", + "bytes": "070080c6a47e8d03", + "parsed": "1000000000000000" + } + } + } + }, + { + "key": "uref-a0cb023abb00bf87f9898b5fdce36569d5b87b7891e2b5023e663a5bc78134e0-000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U8", + "bytes": "02", + "parsed": 2 + } + } + } + }, + { + "key": "uref-c09b022180e6a9221dfefe6ae62c6c8a93e09031742f284ffb64c5c1ddd23395-000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U8", + "bytes": "01", + "parsed": 1 + } + } + } + }, + { + "key": "uref-d8a4e0ed98096e0df3263828e46c1f6758255853d3abcb32ebaf0776483a46bb-000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "Unit", + "bytes": "", + "parsed": null + } + } + } + }, + { + "key": "hash-976b060d3ae59125f3521c5dd16ca5f55c9862b37a9effa05c3770a9a4d7b870", + "kind": { + "Write": { + "ContractPackage": { + "access_key": "uref-d8a4e0ed98096e0df3263828e46c1f6758255853d3abcb32ebaf0776483a46bb-007", + "versions": [], + "disabled_versions": [], + "groups": [], + "lock_status": "Unlocked" + } + } + } + }, + { + "key": "account-hash-9fa1fc0808d3a5b9ea9f3af4ca7c8c3655568fdf378d8afdf8a7e56e58abbfd4", + "kind": { + "AddKeys": [ + { + "name": "cep18_contract_package_CLICKT Test", + "key": "package-976b060d3ae59125f3521c5dd16ca5f55c9862b37a9effa05c3770a9a4d7b870" + } + ] + } + }, + { + "key": "account-hash-9fa1fc0808d3a5b9ea9f3af4ca7c8c3655568fdf378d8afdf8a7e56e58abbfd4", + "kind": { + "AddKeys": [ + { + "name": "cep18_contract_package_access_CLICKT Test", + "key": "uref-d8a4e0ed98096e0df3263828e46c1f6758255853d3abcb32ebaf0776483a46bb-007" + } + ] + } + }, + { + "key": "hash-976b060d3ae59125f3521c5dd16ca5f55c9862b37a9effa05c3770a9a4d7b870", + "kind": "Identity" + }, + { + "key": "message-topic-entity-contract-783ac85c80ca97c0a3f699fedbde796c548e3cd0f8d015c3f3158090272eebd4-5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1", + "kind": { + "Write": { + "MessageTopic": { + "message_count": 0, + "blocktime": 1734512599230, + "topic_name": "events" + } + } + } + }, + { + "key": "hash-feb66b9d1f45bf0a4c11c2048c2b5c839f191128f883ad24d1c2508fc6ad285d", + "kind": { + "Write": { + "ContractWasm": { + "bytes": "01020304" + } + } + } + }, + { + "key": "hash-783ac85c80ca97c0a3f699fedbde796c548e3cd0f8d015c3f3158090272eebd4", + "kind": { + "Write": { + "Contract": { + "contract_package_hash": "contract-package-976b060d3ae59125f3521c5dd16ca5f55c9862b37a9effa05c3770a9a4d7b870", + "contract_wasm_hash": "contract-wasm-feb66b9d1f45bf0a4c11c2048c2b5c839f191128f883ad24d1c2508fc6ad285d", + "named_keys": [ + { + "name": "decimals", + "key": "uref-127a8c723a6df941dae0d26a9c181c26f1a80f86eaeb12eed786b4c9715b5b55-007" + }, + { + "name": "enable_mint_burn", + "key": "uref-c09b022180e6a9221dfefe6ae62c6c8a93e09031742f284ffb64c5c1ddd23395-007" + }, + { + "name": "events_mode", + "key": "uref-a0cb023abb00bf87f9898b5fdce36569d5b87b7891e2b5023e663a5bc78134e0-007" + }, + { + "name": "name", + "key": "uref-6702ca24ad114bf8fe2e7e2169461b98e11f12ad7ad191d6e4b6594183f64ecf-007" + }, + { + "name": "symbol", + "key": "uref-644d8cce1ad2d0fb9a783a72e9542a4edc3e88e62f13e55d3d6a2899cd205619-007" + }, + { + "name": "total_supply", + "key": "uref-411a53953e73ddd23a115ad96e7ccb53e1867ec4beec03e62b8c7aa400be56fe-007" + } + ], + "entry_points": [ + { + "name": "allowance", + "entry_point": { + "name": "allowance", + "args": [ + { + "name": "owner", + "cl_type": "Key" + }, + { + "name": "spender", + "cl_type": "Key" + } + ], + "ret": "U256", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "approve", + "entry_point": { + "name": "approve", + "args": [ + { + "name": "spender", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "balance_of", + "entry_point": { + "name": "balance_of", + "args": [ + { + "name": "address", + "cl_type": "Key" + } + ], + "ret": "U256", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "burn", + "entry_point": { + "name": "burn", + "args": [ + { + "name": "owner", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "change_events_mode", + "entry_point": { + "name": "change_events_mode", + "args": [ + { + "name": "events_mode", + "cl_type": "U8" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "change_security", + "entry_point": { + "name": "change_security", + "args": [], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "condor", + "entry_point": { + "name": "condor", + "args": [], + "ret": "String", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "decimals", + "entry_point": { + "name": "decimals", + "args": [], + "ret": "U8", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "decrease_allowance", + "entry_point": { + "name": "decrease_allowance", + "args": [ + { + "name": "spender", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "increase_allowance", + "entry_point": { + "name": "increase_allowance", + "args": [ + { + "name": "spender", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "init", + "entry_point": { + "name": "init", + "args": [], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "migrate_sec_keys", + "entry_point": { + "name": "migrate_sec_keys", + "args": [], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "migrate_user_allowance_keys", + "entry_point": { + "name": "migrate_user_allowance_keys", + "args": [], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "migrate_user_balance_keys", + "entry_point": { + "name": "migrate_user_balance_keys", + "args": [], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "mint", + "entry_point": { + "name": "mint", + "args": [ + { + "name": "owner", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "name", + "entry_point": { + "name": "name", + "args": [], + "ret": "String", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "symbol", + "entry_point": { + "name": "symbol", + "args": [], + "ret": "String", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "total_supply", + "entry_point": { + "name": "total_supply", + "args": [], + "ret": "U256", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "transfer", + "entry_point": { + "name": "transfer", + "args": [ + { + "name": "recipient", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "transfer_from", + "entry_point": { + "name": "transfer_from", + "args": [ + { + "name": "owner", + "cl_type": "Key" + }, + { + "name": "recipient", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + } + ], + "protocol_version": "2.0.0" + } + } + } + }, + { + "key": "hash-976b060d3ae59125f3521c5dd16ca5f55c9862b37a9effa05c3770a9a4d7b870", + "kind": { + "Write": { + "ContractPackage": { + "access_key": "uref-d8a4e0ed98096e0df3263828e46c1f6758255853d3abcb32ebaf0776483a46bb-007", + "versions": [ + { + "protocol_version_major": 2, + "contract_version": 1, + "contract_hash": "contract-783ac85c80ca97c0a3f699fedbde796c548e3cd0f8d015c3f3158090272eebd4" + } + ], + "disabled_versions": [], + "groups": [], + "lock_status": "Unlocked" + } + } + } + }, + { + "key": "account-hash-9fa1fc0808d3a5b9ea9f3af4ca7c8c3655568fdf378d8afdf8a7e56e58abbfd4", + "kind": { + "AddKeys": [ + { + "name": "cep18_contract_hash_CLICKT Test", + "key": "entity-contract-783ac85c80ca97c0a3f699fedbde796c548e3cd0f8d015c3f3158090272eebd4" + } + ] + } + }, + { + "key": "uref-e2e2f2ee35996daeb226a3d756bc1bd245e635c2f66ea6c0acd15859bc543519-000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U32", + "bytes": "01000000", + "parsed": 1 + } + } + } + }, + { + "key": "account-hash-9fa1fc0808d3a5b9ea9f3af4ca7c8c3655568fdf378d8afdf8a7e56e58abbfd4", + "kind": { + "AddKeys": [ + { + "name": "cep18_contract_version_CLICKT Test", + "key": "uref-e2e2f2ee35996daeb226a3d756bc1bd245e635c2f66ea6c0acd15859bc543519-007" + } + ] + } + }, + { + "key": "uref-11aa02dd78243651d098b056c7a3761c9ba2024da11df432b350c36d6bc15d33-000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "String", + "bytes": "06000000636f6e646f72", + "parsed": "condor" + } + } + } + }, + { + "key": "account-hash-9fa1fc0808d3a5b9ea9f3af4ca7c8c3655568fdf378d8afdf8a7e56e58abbfd4", + "kind": { + "AddKeys": [ + { + "name": "condor", + "key": "uref-11aa02dd78243651d098b056c7a3761c9ba2024da11df432b350c36d6bc15d33-007" + } + ] + } + }, + { + "key": "hash-783ac85c80ca97c0a3f699fedbde796c548e3cd0f8d015c3f3158090272eebd4", + "kind": "Identity" + }, + { + "key": "hash-976b060d3ae59125f3521c5dd16ca5f55c9862b37a9effa05c3770a9a4d7b870", + "kind": "Identity" + }, + { + "key": "hash-feb66b9d1f45bf0a4c11c2048c2b5c839f191128f883ad24d1c2508fc6ad285d", + "kind": "Identity" + }, + { + "key": "hash-783ac85c80ca97c0a3f699fedbde796c548e3cd0f8d015c3f3158090272eebd4", + "kind": { + "AddKeys": [ + { + "name": "package_hash", + "key": "package-976b060d3ae59125f3521c5dd16ca5f55c9862b37a9effa05c3770a9a4d7b870" + } + ] + } + }, + { + "key": "hash-783ac85c80ca97c0a3f699fedbde796c548e3cd0f8d015c3f3158090272eebd4", + "kind": { + "AddKeys": [ + { + "name": "contract_hash", + "key": "entity-contract-783ac85c80ca97c0a3f699fedbde796c548e3cd0f8d015c3f3158090272eebd4" + } + ] + } + }, + { + "key": "uref-5014b553c6c195c6327fdbdf4c6f411cd1ad99aecaa1add06d6459b9452d66f5-000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "Unit", + "bytes": "", + "parsed": null + } + } + } + }, + { + "key": "hash-783ac85c80ca97c0a3f699fedbde796c548e3cd0f8d015c3f3158090272eebd4", + "kind": { + "AddKeys": [ + { + "name": "allowances", + "key": "uref-5014b553c6c195c6327fdbdf4c6f411cd1ad99aecaa1add06d6459b9452d66f5-007" + } + ] + } + }, + { + "key": "uref-85a731470d19fb7c4986f85f8bc889aec3bd578c5e3f499ce8ff3311fde230dc-000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "Unit", + "bytes": "", + "parsed": null + } + } + } + }, + { + "key": "hash-783ac85c80ca97c0a3f699fedbde796c548e3cd0f8d015c3f3158090272eebd4", + "kind": { + "AddKeys": [ + { + "name": "balances", + "key": "uref-85a731470d19fb7c4986f85f8bc889aec3bd578c5e3f499ce8ff3311fde230dc-007" + } + ] + } + }, + { + "key": "dictionary-02af2d6b9eb8e3c6c5e7403f44e01766ef86516bc40e674b360ea6d1c8ae21af", + "kind": { + "Write": { + "CLValue": { + "cl_type": "Any", + "bytes": "08000000070080c6a47e8d03072000000085a731470d19fb7c4986f85f8bc889aec3bd578c5e3f499ce8ff3311fde230dc2c000000414a2b682f4167493036573536703836394d70386a445a56566f2f664e34324b2f66696e3557355971372f55", + "parsed": null + } + } + } + }, + { + "key": "uref-f9afa20f6ac96ed3aac01ddeecfef4067aee7ad0b90ba12ce76a02cf29557817-000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "Unit", + "bytes": "", + "parsed": null + } + } + } + }, + { + "key": "hash-783ac85c80ca97c0a3f699fedbde796c548e3cd0f8d015c3f3158090272eebd4", + "kind": { + "AddKeys": [ + { + "name": "security_badges", + "key": "uref-f9afa20f6ac96ed3aac01ddeecfef4067aee7ad0b90ba12ce76a02cf29557817-007" + } + ] + } + }, + { + "key": "dictionary-a083f799ef84f75134c43548f59d20a3fb41c9993ba5295b000c1541c929ae98", + "kind": { + "Write": { + "CLValue": { + "cl_type": "Any", + "bytes": "01000000000320000000f9afa20f6ac96ed3aac01ddeecfef4067aee7ad0b90ba12ce76a02cf295578172c000000414a2b682f4167493036573536703836394d70386a445a56566f2f664e34324b2f66696e3557355971372f55", + "parsed": null + } + } + } + }, + { + "key": "uref-a0cb023abb00bf87f9898b5fdce36569d5b87b7891e2b5023e663a5bc78134e0-000", + "kind": "Identity" + }, + { + "key": "message-topic-entity-contract-783ac85c80ca97c0a3f699fedbde796c548e3cd0f8d015c3f3158090272eebd4-5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1", + "kind": "Identity" + }, + { + "key": "message-entity-contract-783ac85c80ca97c0a3f699fedbde796c548e3cd0f8d015c3f3158090272eebd4-5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1-0", + "kind": { + "Write": { + "Message": "message-checksum-4f9c3b0e96c2fd77a8a0d11c123c23ee4251c183707307205ae02ecd29c3385e" + } + } + }, + { + "key": "message-topic-entity-contract-783ac85c80ca97c0a3f699fedbde796c548e3cd0f8d015c3f3158090272eebd4-5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1", + "kind": { + "Write": { + "MessageTopic": { + "message_count": 1, + "blocktime": 1734512599230, + "topic_name": "events" + } + } + } + }, + { + "key": "block-message-count-00000000000000000000000000000000000000000000000000000000000000", + "kind": { + "Write": { + "CLValue": { + "cl_type": { + "Tuple2": [ + "U64", + "U64" + ] + }, + "bytes": "bee001d9930100000100000000000000", + "parsed": [ + 1734512599230, + 1 + ] + } + } + } + }, + { + "key": "balance-hold-01e542fb9f5dc6d06d849e2ac3ad6cc5cd4b7c71392dc8759c0898f4ab04439520bee001d993010000", + "kind": { + "Prune": "balance-hold-01e542fb9f5dc6d06d849e2ac3ad6cc5cd4b7c71392dc8759c0898f4ab04439520bee001d993010000" + } + }, + { + "key": "balance-hold-00e542fb9f5dc6d06d849e2ac3ad6cc5cd4b7c71392dc8759c0898f4ab04439520bee001d993010000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "050010a5d4e8", + "parsed": "1000000000000" + } + } + } + }, + { + "key": "bid-addr-01dcd0c38c46c9d5c7083aa1a46b430e8e460f97f8f0bf8444776ac925187acfcc", + "kind": "Identity" + }, + { + "key": "bid-addr-04dcd0c38c46c9d5c7083aa1a46b430e8e460f97f8f0bf8444776ac925187acfcc0800000000000000", + "kind": { + "Write": { + "BidKind": { + "Credit": { + "validator_public_key": "01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6", + "era_id": 8, + "amount": "1000000000000" + } + } + } + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/get-transaction-stored-v200.json b/Casper.Network.SDK.Test/TestData/get-transaction-stored-v200.json new file mode 100644 index 0000000..283646a --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/get-transaction-stored-v200.json @@ -0,0 +1,135 @@ +{ + "api_version": "2.0.0", + "transaction": { + "Version1": { + "hash": "173251b375edd42e3ee43a05ff7d1996380c59192a785884a8cfdd574d86aa9a", + "payload": { + "initiator_addr": { + "PublicKey": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a" + }, + "timestamp": "2024-12-18T10:27:48.633Z", + "ttl": "30m", + "chain_name": "casper-net-1", + "pricing_mode": { + "PaymentLimited": { + "payment_amount": 3000000000, + "gas_price_tolerance": 1, + "standard_payment": true + } + }, + "fields": { + "args": { + "Named": [ + [ + "recipient", + { + "cl_type": "Key", + "bytes": "00e25f0c3b986aaa1a6c85ee356be99cd320fa1f7ceaf9928a3fbd015db11f240f", + "parsed": "account-hash-e25f0c3b986aaa1a6c85ee356be99cd320fa1f7ceaf9928a3fbd015db11f240f" + } + ], + [ + "amount", + { + "cl_type": "U256", + "bytes": "02a861", + "parsed": "25000" + } + ] + ] + }, + "entry_point": { + "Custom": "transfer" + }, + "scheduling": "Standard", + "target": { + "Stored": { + "id": { + "ByPackageName": { + "name": "cep18_contract_package_CLICKT Test", + "version": null + } + }, + "runtime": "VmCasperV1" + } + } + } + }, + "approvals": [ + { + "signer": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a", + "signature": "01b39caf891bbac7902078222651a6158bc7aa387355b8d37c70289f58330bc86ab920afcf51be5a523d2be30427150c228ba08aafb45ba927f762a270bdd8f006" + } + ] + } + }, + "execution_info": { + "block_hash": "5fd0ee0a8929245148a87cfe57355bf3762dbeabdef91155e1e13f23954d3f92", + "block_height": 1400, + "execution_result": { + "Version2": { + "initiator": { + "PublicKey": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a" + }, + "error_message": "ApiError::Formatting [18]", + "limit": "3000000000", + "consumed": "1241925", + "cost": "3000000000", + "refund": "2123123123", + "current_price": 2, + "transfers": [], + "size_estimate": 591, + "effects": [ + { + "key": "balance-hold-01e542fb9f5dc6d06d849e2ac3ad6cc5cd4b7c71392dc8759c0898f4ab04439520b1414fd993010000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "04005ed0b2", + "parsed": "3000000000" + } + } + } + }, + { + "key": "balance-hold-01e542fb9f5dc6d06d849e2ac3ad6cc5cd4b7c71392dc8759c0898f4ab04439520b1414fd993010000", + "kind": { + "Prune": "balance-hold-01e542fb9f5dc6d06d849e2ac3ad6cc5cd4b7c71392dc8759c0898f4ab04439520b1414fd993010000" + } + }, + { + "key": "balance-hold-00e542fb9f5dc6d06d849e2ac3ad6cc5cd4b7c71392dc8759c0898f4ab04439520b1414fd993010000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "04005ed0b2", + "parsed": "3000000000" + } + } + } + }, + { + "key": "bid-addr-01520037cd249ccbcfeb0b9feae07d8d4f7d922cf88adc4f3e8691f9d34ccc8d09", + "kind": "Identity" + }, + { + "key": "bid-addr-04520037cd249ccbcfeb0b9feae07d8d4f7d922cf88adc4f3e8691f9d34ccc8d098100000000000000", + "kind": { + "Write": { + "BidKind": { + "Credit": { + "validator_public_key": "0190664e16a17594ed2d0e3c279c4cf5894e8db0da15e3b91c938562a1caae32ab", + "era_id": 129, + "amount": "3000000000" + } + } + } + } + } + ] + } + } + } +} diff --git a/Casper.Network.SDK.Test/TestData/info-get-reward-v200.json b/Casper.Network.SDK.Test/TestData/info-get-reward-v200.json new file mode 100644 index 0000000..c00ffca --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/info-get-reward-v200.json @@ -0,0 +1 @@ +{"api_version":"2.0.0","reward_amount":"62559062048560","era_id":13,"delegation_rate":1, "switch_block_hash": "0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f"} diff --git a/Casper.Network.SDK.Test/TestData/info-get-status-v200.json b/Casper.Network.SDK.Test/TestData/info-get-status-v200.json new file mode 100644 index 0000000..fdc26ab --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/info-get-status-v200.json @@ -0,0 +1,48 @@ +{ + "api_version": "1.2.3", + "protocol_version": "5.4.3", + "peers": [ + { + "node_id": "tls:0eae..e79e", + "address": "127.0.0.1:22103" + }, + { + "node_id": "tls:151f..dab7", + "address": "127.0.0.1:22104" + }, + { + "node_id": "tls:3e34..67cc", + "address": "127.0.0.1:22102" + }, + { + "node_id": "tls:64d8..4609", + "address": "127.0.0.1:22105" + } + ], + "build_version": "2.0.0-f803ee53d", + "chainspec_name": "casper-net-1", + "starting_state_root_hash": "7ca5855e14e936019193d07055fb3390e21ec8cb75724ea756b4eac84e1b5fd7", + "last_added_block_info": { + "hash": "89ceb389e0e8244d8f82afacedaccb42d256831c3a036b06c0770323905d97d1", + "timestamp": "2024-06-11T16:32:33.703Z", + "era_id": 688, + "height": 7540, + "state_root_hash": "2177c9546bd31b428f0862e2845e46376e4d66fe6935b3e9eba33be67692a6b9", + "creator": "01c867ff3cf1d4e4e68fc00922fdcb740304def196e223091dee62012f444b9eba" + }, + "our_public_signing_key": "01fed662dc7f1f7af43ad785ba07a8cc05b7a96f9ee69613cfde43bc56bec1140b", + "round_length": "4s 96ms", + "next_upgrade": null, + "uptime": "7h 59m", + "reactor_state": "Validate", + "last_progress": "2024-06-11T07:51:41.458Z", + "available_block_range": { + "low": 0, + "high": 7540 + }, + "block_sync": { + "historical": null, + "forward": null + }, + "latest_switch_block_hash": "643f9d06cebd07dffef47b7bfa70cc014496ff904561a78a1b5e8123247d3231" +} diff --git a/Casper.Network.SDK.Test/TestData/legacy_transfer_v200.json b/Casper.Network.SDK.Test/TestData/legacy_transfer_v200.json new file mode 100644 index 0000000..ac31e79 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/legacy_transfer_v200.json @@ -0,0 +1,17 @@ +{ + "api_version": "2.0.0", + "block_header": null, + "stored_value": { + "Transfer": { + "deploy_hash": "cd91f138e82ddce5dfbb99c6bbf3f47caca439b81d7f43702cbebfa99bacbfd0", + "from": "account-hash-87516c22bca9a14179ebbbe646c8f911153fe53626126c1ba24293517c2e04a2", + "to": "account-hash-1265caa7cd80f31f882ab6f5623d89741c77cf3a36a309b54ed55fdc0be227c9", + "source": "uref-c003c853c6477137225914c7610cf2c30ed594480c1fccd6dfd58a633522edc6-007", + "target": "uref-c74ad99f2bca136c1e64b1a14bf576696363c25701d66d87adca6dbdb147b39d-004", + "amount": "2500000000000", + "gas": "0", + "id": null + } + }, + "merkle_proof": "0100000003c74ad99f2bca136c1e64b1a14bf576696363c25701d66d87adca6dbdb147b39d05cd91f138e82ddce5dfbb99c6bbf3f47caca439b81d7f43702cbebfa99bacbfd087516c22bca9a14179ebbbe646c8f911153fe53626126c1ba24293517c2e04a2011265caa7cd80f31f882ab6f5623d89741c77cf3a36a309b54ed55fdc0be227c9c003c853c6477137225914c7610cf2c30ed594480c1fccd6dfd58a633522edc607c74ad99f2bca136c1e64b1a14bf576696363c25701d66d87adca6dbdb147b39d040600a89c13460200000200000000c7080000000400a8a2c8abb44aa2e88169f0b9354b1db7fc7300fc5cbb860c52802dfeca0a39d70d000a3d7f57c86a55ffcf45899071404663e9c85b3c26644d0861724809836edff00e00f04d9ff40974591c3de3bb5e3355b60fd3b7f0703eb28213904a31ba58f1dc9514003edffabc50e557526ff9c01b1fa581cf560f8b1b57d5ee00ea072d55e9b2b1a0310050992ed0b3b63bcc2f33ad04c203de33b9a91c32abb4ee7b17c59cdf99f899df8600768b619b425f98445824cfeff827dbbb6cd4a598e222b630a455b892a077bdd7ef012460796877e7106f67da2e5d3206d97a020d1e1f442403d714ffd3a1d7eb0bdcf400507226381d48af14f08767414cbbb2479c0637e892c93dd332f4eb9623f05c5100031100000000017da10bd7dcd1d89149ff1765dcbbad7b57231b2ab227d8af7dda4f4c7eda3c390100b2c12a9e61379047b4bfbe9beb64a7782f4b6b87647459e34ea487f189c738d80201536a37fd00820c285355734a3dfcf094407487d4530609979a8d4722bece6a5604014c76c9f2caf55d8962aa32b3fdd5f887bad4f92d2a12728962bac8dd03f5e7be06012790a10653f15923b90ceaefbeb22ace1ed083ee4b36a8cdd2f4605562a6e7930a0032bd6991fdc3e5d108acb5e9860f87e521b11ebe8744722bdd8b8febedd495620b0010d358322b226099302b72fc1dcee15ef2c5182633078f9b261cecbcf38ba0490c01ed825da87ad90573bc714a1c312e749a66a5fb11643d09aa13505ad3523ec01f0d004d7d61e4bccc295f9e749e699c686ea72bda64f9c262966edb281efbaaed25e60e0043d39741a6e7b395eace3b1fbbbd5bbf288f7a788e80745a2e2bb0f2173a8b4f0f015fda27549535d581e35954d9fec577972a6b3921b4bea74ace58f971bc44ef37100149d5e7bfe2e0b8b01d44f53adf40b77d391b9d008d66be8ca6e8982058d4db8e110162e3c98c88cd12b4ba47f3c59158b5aef048eecab639ac055c69025388a2020e120188c216f76544d3c1fbefc85148e5c62b31901da4a5e2680c526ac8662281ad8f140140d93937739fa8ca0fce0cd9dfcc6fc440b1400b6c730978b2e26a277bc397041500637d03dd4f755905fe5ef5757294211766db21e29ebb7cc3d29c494bc335e1ea1701e77b15a40bde97cdd4200b3c02b4683314d794f95fb9d0eaab1041187feb13f7" +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/query-balance-details-v200.json b/Casper.Network.SDK.Test/TestData/query-balance-details-v200.json new file mode 100644 index 0000000..7bb9585 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/query-balance-details-v200.json @@ -0,0 +1,43 @@ +{ + "api_version": "2.0.0", + "total_balance": "999999999999999999999937500000000", + "available_balance": "999999999999999999997937499950000", + "total_balance_proof": "010000000642848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc1118298000f0000000e00afb572fb5ac138938d44c64d3108020000000042250000000c006fc8c4863bd73ab8a33c9ff46c2b3f0611c312b23ba9de8ffcaaaf940cfb9b770e004d419e30142fdd2da42df93349891f54190a21a93b4b9fdfff8222fbb6348c5610009c65771c2232c1bb0270b247ba6af09fab7c2cb9d78b42de94daf097d4117a381100d79c23c82f5f12519e80d22af2233fd9f7b4c31b5bab900fba7af37b096da98b13009c2e309b248e6e247b4beaa917058da8b189bf03f26c95fbf394ba57c34e228f1a01960ca68f3138a882eae83e2e6554c8068634fac02fa1c2a805fa5cb15d542aae250039058b91411598a541d2108b61cb2d1369ff65c6d3fa9c1639ea73b74b3fa5872f00f2b4b0a07af15aa6cd303e317d89046aecb0033f2d06f78fef2c7c7d33775394320047991ecf1aad64080af41fbaff781ed6785b5fa2969490fce17595d1497c6c6b340065f0cc526a2b698a0010df3f8a5ecf3737d9c6564e6eb14774345951fded5634480055ca4b5de7e379e1f04ceb66af55e4229e4f169254395596630ed2728c136c4d5500cbe030b09d1bab294d7cdf5292d8ccda7e658b480b6bfe9b267a729b5889099e5a00090adb5ca41d14b879ac98eac90ebf2f38351b92a773566de6b6cadc56833b6d5c00f56647832ce5d0d67ab576a82876e789dd2e3a11831f9840092610375f0df7bc5d00890df43f743ad953f2674e54bfce9566115641417f82d403ff5642fdabf497f05e00b1e6b13b15e0f02b8a598455294eb75d655901b786f987680cae98260702adc06500ad6ad1b49663fd803a310bdea94fff8229c22060c40d6823f050baef83e3e61f78007309f745531c9ad99c7b9f9fe041c1d184c580b58ed5cd728adcf4837b6a82d084009375af1ee9a11c5db3da73ce8ec276711348114f6d19163e6b52c37825c33d979000ad5d8113481ddb4c7aa481b835aaae649ebc96d67996a9337447697183cc62149300e78ca9a054b151c8a0b131bb3e939e30199ac470e203f9e1bf808fd56f70253a94005f4da3f8f8f0374fae31ed80b2578f650d7e086363320c75fd61fdc1f160506a9700dd4c8aec0a4fc59f700e025f46c3f34a3a33c21129acf6204c176ddf675cc50d9d00d9e73751b542ab74c1133c6e088f01ae2eb0a50220a71ca031abe2b3907f26f79f006c7cfbc2ce48f82099a2aa20f24e4042229d680b16c758dd1c13b93683ec12ebaf0014a4115c7583af3bedfe0ce175e2f6f3397245cc504395880281fe2f6cf8ffccb0002d119c4c7f5df86abd27bcb6c8c8df59472b29f10998cf218ec4154dd88860beb4007712bc1e8849c4ab9c35b2318e343300fb2a99bc61ebd4fd6d92dc015ba6f31fc2008c1c60be05141f92bfeee3248f795f28e2acecaf56647c1431e5ced19fb31660c600f9cdb7cd33095d2b2214b0c36899b64117020647956569539312fc64d4065c87cb003e48890fb892d90bf93a24e0ea0a31009de583dfb36967d8835d36664149dedcd70036b0e024421d549a488abf7a9b037551eb0abe4540cbb511ced645917b9e7494df00775b78815785d04dade5d176e254d388b43364a9d55415d729456e0c62a016dde7001a779ff7aef2aa185ee689bbd9a35a705918613bb82b097c1da88d9cd9bcd2fff20056fb81de43335796fc3e0bc88d994dc2406ff8629e0471af40ef732e1b1f2560f60049266acf8839c7510626367ae6c392b022ee3174b439e4a9d0a2dd7851efbb84ff005878c5f671a0892e62413e48a48e237604721f6e8f6870baeaaa06c002436a8c0006100000000001a3df3924154642743df7c89de30c3fbfcc2e7ccdb487e0693929743d613a6cb9020128db81db5621fdc570cc8593aababa461ed83022ac4ee5b5fd83143d99850a1109018a849a8b1ac084049b6de037f9ede90f7e6fcc9796422d28a883ed18e05b7ac70a00f81b23926e2d382e007c479b17e69957fcb3b8a73ec7a788639842dca0abcb3b0b008a016711ddca3eb1ced4444adad1ad6f35db320391975e4e8a84a4a87c8771530d0099151e39326a7661d8ac045a627c821811c63737a1debdc07eeca10c66fd27190e0043d39741a6e7b395eace3b1fbbbd5bbf288f7a788e80745a2e2bb0f2173a8b4f0f016b6764c3b069c008262b9ce74a646d5eede153788bad31653db0ad3113e18e3b1001bbbc076ba1e3d1805ff8c0fd75ebf6e6fde7c661c61d9a44eefb562611ca5c4e1101afafe9d12e30e14ad5fd86304491349176f318c5030e6155a893b0e0e8cf63451201a45a7290887763d97fcd47cf082a8b600bade1bd985cdb1a4e7e6f9c3f2e8a9813013b990454904bc08790426c0ff1b32284c8f9f2a3079735f2cc7b2c2aaa7741b91401b5ff2d6942b82c900d243d810b8464374488ae76f5e82549686e9179fdcce60c1501cc236f5394ce85c69e0010fbc6b0585a46226dd8e8135ae15419ba630db445551601dfa3660910818c6e2d27c0c2dd0bfc1fde32a13bc23aa04c8039c3e49dc85ec0170109ecc9f38fcff21f7ac414e7a0e48a81141d642f4498621aab3b816703e47ef1", + "holds": [ + { + "time": 1718092399578, + "amount": "1000000000000", + "proof": "01000000160042848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc1118298da834906900100000006000000050010a5d4e8080300000000da0600000007009c05d308ffbb5c59f120419b9b4ef1277f92b835c8b17262a18d51c61fbf756b460043dd210f11337ffb1290e171db7d06697ed944a7d3adf00bcb5d003b4ccadc9e6b00e7b2b80cb816e6576d998902739e2bd55a9bd1d575c189ed5dfc1336c581d64c9000c002bf557c16b76a64de47e4895424f55a53f05119336df0835591bbcb4b7c24b00094b7d84caa17d084746b0a35ff9e80d9e0b1aef6a32d7da883b3f7f378c20bc5b80034af2ffdbf7cfb287b47271b16ace78311e11966e77561bff4fb435db314dcd901210000000042848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc11182980016100000000001a3df3924154642743df7c89de30c3fbfcc2e7ccdb487e0693929743d613a6cb9020128db81db5621fdc570cc8593aababa461ed83022ac4ee5b5fd83143d99850a11060132e10c19cbe5ea8b7ebf42b91833e92d5ec81605a5bff5ef7990eddf7e68746e09018a849a8b1ac084049b6de037f9ede90f7e6fcc9796422d28a883ed18e05b7ac70a00f81b23926e2d382e007c479b17e69957fcb3b8a73ec7a788639842dca0abcb3b0b008a016711ddca3eb1ced4444adad1ad6f35db320391975e4e8a84a4a87c8771530d0099151e39326a7661d8ac045a627c821811c63737a1debdc07eeca10c66fd27190e0043d39741a6e7b395eace3b1fbbbd5bbf288f7a788e80745a2e2bb0f2173a8b4f0f016b6764c3b069c008262b9ce74a646d5eede153788bad31653db0ad3113e18e3b1001bbbc076ba1e3d1805ff8c0fd75ebf6e6fde7c661c61d9a44eefb562611ca5c4e1101afafe9d12e30e14ad5fd86304491349176f318c5030e6155a893b0e0e8cf63451201a45a7290887763d97fcd47cf082a8b600bade1bd985cdb1a4e7e6f9c3f2e8a9813013b990454904bc08790426c0ff1b32284c8f9f2a3079735f2cc7b2c2aaa7741b91401b5ff2d6942b82c900d243d810b8464374488ae76f5e82549686e9179fdcce60c1501cc236f5394ce85c69e0010fbc6b0585a46226dd8e8135ae15419ba630db44555170109ecc9f38fcff21f7ac414e7a0e48a81141d642f4498621aab3b816703e47ef1" + }, + { + "time": 1718092584368, + "amount": "1000000000000", + "proof": "01000000160042848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc1118298b0554c06900100000006000000050010a5d4e8080300000000b00600000007009c05d308ffbb5c59f120419b9b4ef1277f92b835c8b17262a18d51c61fbf756b460043dd210f11337ffb1290e171db7d06697ed944a7d3adf00bcb5d003b4ccadc9e6b00e7b2b80cb816e6576d998902739e2bd55a9bd1d575c189ed5dfc1336c581d64c9000c002bf557c16b76a64de47e4895424f55a53f05119336df0835591bbcb4b7c24b80034af2ffdbf7cfb287b47271b16ace78311e11966e77561bff4fb435db314dcd9da0094a48357dce8f67e158331f999e6480792bae25325054617e9938c2e55ee7f1901210000000042848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc11182980016100000000001a3df3924154642743df7c89de30c3fbfcc2e7ccdb487e0693929743d613a6cb9020128db81db5621fdc570cc8593aababa461ed83022ac4ee5b5fd83143d99850a11060132e10c19cbe5ea8b7ebf42b91833e92d5ec81605a5bff5ef7990eddf7e68746e09018a849a8b1ac084049b6de037f9ede90f7e6fcc9796422d28a883ed18e05b7ac70a00f81b23926e2d382e007c479b17e69957fcb3b8a73ec7a788639842dca0abcb3b0b008a016711ddca3eb1ced4444adad1ad6f35db320391975e4e8a84a4a87c8771530d0099151e39326a7661d8ac045a627c821811c63737a1debdc07eeca10c66fd27190e0043d39741a6e7b395eace3b1fbbbd5bbf288f7a788e80745a2e2bb0f2173a8b4f0f016b6764c3b069c008262b9ce74a646d5eede153788bad31653db0ad3113e18e3b1001bbbc076ba1e3d1805ff8c0fd75ebf6e6fde7c661c61d9a44eefb562611ca5c4e1101afafe9d12e30e14ad5fd86304491349176f318c5030e6155a893b0e0e8cf63451201a45a7290887763d97fcd47cf082a8b600bade1bd985cdb1a4e7e6f9c3f2e8a9813013b990454904bc08790426c0ff1b32284c8f9f2a3079735f2cc7b2c2aaa7741b91401b5ff2d6942b82c900d243d810b8464374488ae76f5e82549686e9179fdcce60c1501cc236f5394ce85c69e0010fbc6b0585a46226dd8e8135ae15419ba630db44555170109ecc9f38fcff21f7ac414e7a0e48a81141d642f4498621aab3b816703e47ef1" + }, + { + "time": 1718110526059, + "amount": "10000", + "proof": "01000000160042848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc11182986b1a5e079001000000030000000210270803000000006b0600000007009c05d308ffbb5c59f120419b9b4ef1277f92b835c8b17262a18d51c61fbf756b460043dd210f11337ffb1290e171db7d06697ed944a7d3adf00bcb5d003b4ccadc9e9000c002bf557c16b76a64de47e4895424f55a53f05119336df0835591bbcb4b7c24b00094b7d84caa17d084746b0a35ff9e80d9e0b1aef6a32d7da883b3f7f378c20bc5b80034af2ffdbf7cfb287b47271b16ace78311e11966e77561bff4fb435db314dcd9da0094a48357dce8f67e158331f999e6480792bae25325054617e9938c2e55ee7f1901210000000042848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc11182980016100000000001a3df3924154642743df7c89de30c3fbfcc2e7ccdb487e0693929743d613a6cb9020128db81db5621fdc570cc8593aababa461ed83022ac4ee5b5fd83143d99850a11060132e10c19cbe5ea8b7ebf42b91833e92d5ec81605a5bff5ef7990eddf7e68746e09018a849a8b1ac084049b6de037f9ede90f7e6fcc9796422d28a883ed18e05b7ac70a00f81b23926e2d382e007c479b17e69957fcb3b8a73ec7a788639842dca0abcb3b0b008a016711ddca3eb1ced4444adad1ad6f35db320391975e4e8a84a4a87c8771530d0099151e39326a7661d8ac045a627c821811c63737a1debdc07eeca10c66fd27190e0043d39741a6e7b395eace3b1fbbbd5bbf288f7a788e80745a2e2bb0f2173a8b4f0f016b6764c3b069c008262b9ce74a646d5eede153788bad31653db0ad3113e18e3b1001bbbc076ba1e3d1805ff8c0fd75ebf6e6fde7c661c61d9a44eefb562611ca5c4e1101afafe9d12e30e14ad5fd86304491349176f318c5030e6155a893b0e0e8cf63451201a45a7290887763d97fcd47cf082a8b600bade1bd985cdb1a4e7e6f9c3f2e8a9813013b990454904bc08790426c0ff1b32284c8f9f2a3079735f2cc7b2c2aaa7741b91401b5ff2d6942b82c900d243d810b8464374488ae76f5e82549686e9179fdcce60c1501cc236f5394ce85c69e0010fbc6b0585a46226dd8e8135ae15419ba630db44555170109ecc9f38fcff21f7ac414e7a0e48a81141d642f4498621aab3b816703e47ef1" + }, + { + "time": 1718110593680, + "amount": "10000", + "proof": "01000000160042848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc111829890225f07900100000003000000021027080300000000900600000007009c05d308ffbb5c59f120419b9b4ef1277f92b835c8b17262a18d51c61fbf756b460043dd210f11337ffb1290e171db7d06697ed944a7d3adf00bcb5d003b4ccadc9e6b00e7b2b80cb816e6576d998902739e2bd55a9bd1d575c189ed5dfc1336c581d64cb00094b7d84caa17d084746b0a35ff9e80d9e0b1aef6a32d7da883b3f7f378c20bc5b80034af2ffdbf7cfb287b47271b16ace78311e11966e77561bff4fb435db314dcd9da0094a48357dce8f67e158331f999e6480792bae25325054617e9938c2e55ee7f1901210000000042848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc11182980016100000000001a3df3924154642743df7c89de30c3fbfcc2e7ccdb487e0693929743d613a6cb9020128db81db5621fdc570cc8593aababa461ed83022ac4ee5b5fd83143d99850a11060132e10c19cbe5ea8b7ebf42b91833e92d5ec81605a5bff5ef7990eddf7e68746e09018a849a8b1ac084049b6de037f9ede90f7e6fcc9796422d28a883ed18e05b7ac70a00f81b23926e2d382e007c479b17e69957fcb3b8a73ec7a788639842dca0abcb3b0b008a016711ddca3eb1ced4444adad1ad6f35db320391975e4e8a84a4a87c8771530d0099151e39326a7661d8ac045a627c821811c63737a1debdc07eeca10c66fd27190e0043d39741a6e7b395eace3b1fbbbd5bbf288f7a788e80745a2e2bb0f2173a8b4f0f016b6764c3b069c008262b9ce74a646d5eede153788bad31653db0ad3113e18e3b1001bbbc076ba1e3d1805ff8c0fd75ebf6e6fde7c661c61d9a44eefb562611ca5c4e1101afafe9d12e30e14ad5fd86304491349176f318c5030e6155a893b0e0e8cf63451201a45a7290887763d97fcd47cf082a8b600bade1bd985cdb1a4e7e6f9c3f2e8a9813013b990454904bc08790426c0ff1b32284c8f9f2a3079735f2cc7b2c2aaa7741b91401b5ff2d6942b82c900d243d810b8464374488ae76f5e82549686e9179fdcce60c1501cc236f5394ce85c69e0010fbc6b0585a46226dd8e8135ae15419ba630db44555170109ecc9f38fcff21f7ac414e7a0e48a81141d642f4498621aab3b816703e47ef1" + }, + { + "time": 1718110630663, + "amount": "10000", + "proof": "01000000160042848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc111829807b35f079001000000030000000210270803000000000706000000460043dd210f11337ffb1290e171db7d06697ed944a7d3adf00bcb5d003b4ccadc9e6b00e7b2b80cb816e6576d998902739e2bd55a9bd1d575c189ed5dfc1336c581d64c9000c002bf557c16b76a64de47e4895424f55a53f05119336df0835591bbcb4b7c24b00094b7d84caa17d084746b0a35ff9e80d9e0b1aef6a32d7da883b3f7f378c20bc5b80034af2ffdbf7cfb287b47271b16ace78311e11966e77561bff4fb435db314dcd9da0094a48357dce8f67e158331f999e6480792bae25325054617e9938c2e55ee7f1901210000000042848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc11182980016100000000001a3df3924154642743df7c89de30c3fbfcc2e7ccdb487e0693929743d613a6cb9020128db81db5621fdc570cc8593aababa461ed83022ac4ee5b5fd83143d99850a11060132e10c19cbe5ea8b7ebf42b91833e92d5ec81605a5bff5ef7990eddf7e68746e09018a849a8b1ac084049b6de037f9ede90f7e6fcc9796422d28a883ed18e05b7ac70a00f81b23926e2d382e007c479b17e69957fcb3b8a73ec7a788639842dca0abcb3b0b008a016711ddca3eb1ced4444adad1ad6f35db320391975e4e8a84a4a87c8771530d0099151e39326a7661d8ac045a627c821811c63737a1debdc07eeca10c66fd27190e0043d39741a6e7b395eace3b1fbbbd5bbf288f7a788e80745a2e2bb0f2173a8b4f0f016b6764c3b069c008262b9ce74a646d5eede153788bad31653db0ad3113e18e3b1001bbbc076ba1e3d1805ff8c0fd75ebf6e6fde7c661c61d9a44eefb562611ca5c4e1101afafe9d12e30e14ad5fd86304491349176f318c5030e6155a893b0e0e8cf63451201a45a7290887763d97fcd47cf082a8b600bade1bd985cdb1a4e7e6f9c3f2e8a9813013b990454904bc08790426c0ff1b32284c8f9f2a3079735f2cc7b2c2aaa7741b91401b5ff2d6942b82c900d243d810b8464374488ae76f5e82549686e9179fdcce60c1501cc236f5394ce85c69e0010fbc6b0585a46226dd8e8135ae15419ba630db44555170109ecc9f38fcff21f7ac414e7a0e48a81141d642f4498621aab3b816703e47ef1" + }, + { + "time": 1718114549574, + "amount": "10000", + "proof": "01000000160042848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc1118298467f9b07900100000003000000021027080300000000460600000007009c05d308ffbb5c59f120419b9b4ef1277f92b835c8b17262a18d51c61fbf756b6b00e7b2b80cb816e6576d998902739e2bd55a9bd1d575c189ed5dfc1336c581d64c9000c002bf557c16b76a64de47e4895424f55a53f05119336df0835591bbcb4b7c24b00094b7d84caa17d084746b0a35ff9e80d9e0b1aef6a32d7da883b3f7f378c20bc5b80034af2ffdbf7cfb287b47271b16ace78311e11966e77561bff4fb435db314dcd9da0094a48357dce8f67e158331f999e6480792bae25325054617e9938c2e55ee7f1901210000000042848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc11182980016100000000001a3df3924154642743df7c89de30c3fbfcc2e7ccdb487e0693929743d613a6cb9020128db81db5621fdc570cc8593aababa461ed83022ac4ee5b5fd83143d99850a11060132e10c19cbe5ea8b7ebf42b91833e92d5ec81605a5bff5ef7990eddf7e68746e09018a849a8b1ac084049b6de037f9ede90f7e6fcc9796422d28a883ed18e05b7ac70a00f81b23926e2d382e007c479b17e69957fcb3b8a73ec7a788639842dca0abcb3b0b008a016711ddca3eb1ced4444adad1ad6f35db320391975e4e8a84a4a87c8771530d0099151e39326a7661d8ac045a627c821811c63737a1debdc07eeca10c66fd27190e0043d39741a6e7b395eace3b1fbbbd5bbf288f7a788e80745a2e2bb0f2173a8b4f0f016b6764c3b069c008262b9ce74a646d5eede153788bad31653db0ad3113e18e3b1001bbbc076ba1e3d1805ff8c0fd75ebf6e6fde7c661c61d9a44eefb562611ca5c4e1101afafe9d12e30e14ad5fd86304491349176f318c5030e6155a893b0e0e8cf63451201a45a7290887763d97fcd47cf082a8b600bade1bd985cdb1a4e7e6f9c3f2e8a9813013b990454904bc08790426c0ff1b32284c8f9f2a3079735f2cc7b2c2aaa7741b91401b5ff2d6942b82c900d243d810b8464374488ae76f5e82549686e9179fdcce60c1501cc236f5394ce85c69e0010fbc6b0585a46226dd8e8135ae15419ba630db44555170109ecc9f38fcff21f7ac414e7a0e48a81141d642f4498621aab3b816703e47ef1" + }, + { + "time": 1718124588984, + "amount": "10000", + "proof": "01000000160042848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc1118298b8af3408900100000003000000021027080300000000b80600000007009c05d308ffbb5c59f120419b9b4ef1277f92b835c8b17262a18d51c61fbf756b460043dd210f11337ffb1290e171db7d06697ed944a7d3adf00bcb5d003b4ccadc9e6b00e7b2b80cb816e6576d998902739e2bd55a9bd1d575c189ed5dfc1336c581d64c9000c002bf557c16b76a64de47e4895424f55a53f05119336df0835591bbcb4b7c24b00094b7d84caa17d084746b0a35ff9e80d9e0b1aef6a32d7da883b3f7f378c20bc5da0094a48357dce8f67e158331f999e6480792bae25325054617e9938c2e55ee7f1901210000000042848ec03c268eb7b69fe729a834db06dc3b182055fb00e5d4a066cfc11182980016100000000001a3df3924154642743df7c89de30c3fbfcc2e7ccdb487e0693929743d613a6cb9020128db81db5621fdc570cc8593aababa461ed83022ac4ee5b5fd83143d99850a11060132e10c19cbe5ea8b7ebf42b91833e92d5ec81605a5bff5ef7990eddf7e68746e09018a849a8b1ac084049b6de037f9ede90f7e6fcc9796422d28a883ed18e05b7ac70a00f81b23926e2d382e007c479b17e69957fcb3b8a73ec7a788639842dca0abcb3b0b008a016711ddca3eb1ced4444adad1ad6f35db320391975e4e8a84a4a87c8771530d0099151e39326a7661d8ac045a627c821811c63737a1debdc07eeca10c66fd27190e0043d39741a6e7b395eace3b1fbbbd5bbf288f7a788e80745a2e2bb0f2173a8b4f0f016b6764c3b069c008262b9ce74a646d5eede153788bad31653db0ad3113e18e3b1001bbbc076ba1e3d1805ff8c0fd75ebf6e6fde7c661c61d9a44eefb562611ca5c4e1101afafe9d12e30e14ad5fd86304491349176f318c5030e6155a893b0e0e8cf63451201a45a7290887763d97fcd47cf082a8b600bade1bd985cdb1a4e7e6f9c3f2e8a9813013b990454904bc08790426c0ff1b32284c8f9f2a3079735f2cc7b2c2aaa7741b91401b5ff2d6942b82c900d243d810b8464374488ae76f5e82549686e9179fdcce60c1501cc236f5394ce85c69e0010fbc6b0585a46226dd8e8135ae15419ba630db44555170109ecc9f38fcff21f7ac414e7a0e48a81141d642f4498621aab3b816703e47ef1" + } + ] +} diff --git a/Casper.Network.SDK.Test/TestData/query-balance-v200.json b/Casper.Network.SDK.Test/TestData/query-balance-v200.json new file mode 100644 index 0000000..473fb81 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/query-balance-v200.json @@ -0,0 +1,4 @@ +{ + "api_version": "2.0.0", + "balance": "1576039612769" +} diff --git a/Casper.Network.SDK.Test/TestData/seigniorage_allocation_v158.json b/Casper.Network.SDK.Test/TestData/seigniorage_allocation_v158.json new file mode 100644 index 0000000..bb0b1af --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/seigniorage_allocation_v158.json @@ -0,0 +1,71 @@ +{ + "EraInfo": { + "seigniorage_allocations": [ + { + "Delegator": { + "delegator_public_key": "018b46617b2b97e633b36530f2964b3f4c15916235910a2737e83d4fa2c7fad542", + "validator_public_key": "01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6", + "amount": "4414692857142857" + } + }, + { + "Validator": { + "validator_public_key": "01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6", + "amount": "4585307142857143" + } + }, + { + "Delegator": { + "delegator_public_key": "0197b79d1a1351f8fb922b9f7f556d2bbfdba5105df9eaa6caa07804c703a641ed", + "validator_public_key": "0190664e16a17594ed2d0e3c279c4cf5894e8db0da15e3b91c938562a1caae32ab", + "amount": "4414692857142857" + } + }, + { + "Validator": { + "validator_public_key": "0190664e16a17594ed2d0e3c279c4cf5894e8db0da15e3b91c938562a1caae32ab", + "amount": "4585307142857143" + } + }, + { + "Delegator": { + "delegator_public_key": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a", + "validator_public_key": "01c867ff3cf1d4e4e68fc00922fdcb740304def196e223091dee62012f444b9eba", + "amount": "4414692857142857" + } + }, + { + "Validator": { + "validator_public_key": "01c867ff3cf1d4e4e68fc00922fdcb740304def196e223091dee62012f444b9eba", + "amount": "4585307142857143" + } + }, + { + "Delegator": { + "delegator_public_key": "0106ed45915392c02b37136618372ac8dde8e0e3b8ee6190b2ca6db539b354ede4", + "validator_public_key": "01f58b94526d280881f79744effebc555426190950d5dfdd2f8aaf10ceaec010c6", + "amount": "4414692857142857" + } + }, + { + "Validator": { + "validator_public_key": "01f58b94526d280881f79744effebc555426190950d5dfdd2f8aaf10ceaec010c6", + "amount": "4585307142857143" + } + }, + { + "Delegator": { + "delegator_public_key": "0184f6d260f4ee6869ddb36affe15456de6ae045278fa2f467bb677561ce0dad55", + "validator_public_key": "01fed662dc7f1f7af43ad785ba07a8cc05b7a96f9ee69613cfde43bc56bec1140b", + "amount": "4414692857142857" + } + }, + { + "Validator": { + "validator_public_key": "01fed662dc7f1f7af43ad785ba07a8cc05b7a96f9ee69613cfde43bc56bec1140b", + "amount": "4585307142857143" + } + } + ] + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/seigniorage_allocation_v200.json b/Casper.Network.SDK.Test/TestData/seigniorage_allocation_v200.json new file mode 100644 index 0000000..6b27c35 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/seigniorage_allocation_v200.json @@ -0,0 +1,156 @@ +{ + "EraInfo": { + "seigniorage_allocations": [ + { + "Delegator": { + "delegator_kind": { + "PublicKey": "018b46617b2b97e633b36530f2964b3f4c15916235910a2737e83d4fa2c7fad542" + }, + "validator_public_key": "01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6", + "amount": "2515330120214391" + } + }, + { + "Validator": { + "validator_public_key": "01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6", + "amount": "2728720430156545" + } + }, + { + "Delegator": { + "delegator_kind": { + "PublicKey": "018b46617b2b97e633b36530f2964b3f4c15916235910a2737e83d4fa2c7fad542" + }, + "validator_public_key": "01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6", + "amount": "109303520813010" + } + }, + { + "Validator": { + "validator_public_key": "01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6", + "amount": "118554941151112" + } + }, + { + "Delegator": { + "delegator_kind": { + "PublicKey": "0197b79d1a1351f8fb922b9f7f556d2bbfdba5105df9eaa6caa07804c703a641ed" + }, + "validator_public_key": "0190664e16a17594ed2d0e3c279c4cf5894e8db0da15e3b91c938562a1caae32ab", + "amount": "8599696498056110" + } + }, + { + "Validator": { + "validator_public_key": "0190664e16a17594ed2d0e3c279c4cf5894e8db0da15e3b91c938562a1caae32ab", + "amount": "9377950843219784" + } + }, + { + "Delegator": { + "delegator_kind": { + "PublicKey": "0197b79d1a1351f8fb922b9f7f556d2bbfdba5105df9eaa6caa07804c703a641ed" + }, + "validator_public_key": "0190664e16a17594ed2d0e3c279c4cf5894e8db0da15e3b91c938562a1caae32ab", + "amount": "285067736921916" + } + }, + { + "Validator": { + "validator_public_key": "0190664e16a17594ed2d0e3c279c4cf5894e8db0da15e3b91c938562a1caae32ab", + "amount": "310701366981535" + } + }, + { + "Delegator": { + "delegator_kind": { + "PublicKey": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a" + }, + "validator_public_key": "01c867ff3cf1d4e4e68fc00922fdcb740304def196e223091dee62012f444b9eba", + "amount": "5976757455713484" + } + }, + { + "Validator": { + "validator_public_key": "01c867ff3cf1d4e4e68fc00922fdcb740304def196e223091dee62012f444b9eba", + "amount": "6492754998004464" + } + }, + { + "Delegator": { + "delegator_kind": { + "PublicKey": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a" + }, + "validator_public_key": "01c867ff3cf1d4e4e68fc00922fdcb740304def196e223091dee62012f444b9eba", + "amount": "162277940193805" + } + }, + { + "Validator": { + "validator_public_key": "01c867ff3cf1d4e4e68fc00922fdcb740304def196e223091dee62012f444b9eba", + "amount": "176125500882714" + } + }, + { + "Delegator": { + "delegator_kind": { + "PublicKey": "0106ed45915392c02b37136618372ac8dde8e0e3b8ee6190b2ca6db539b354ede4" + }, + "validator_public_key": "01f58b94526d280881f79744effebc555426190950d5dfdd2f8aaf10ceaec010c6", + "amount": "6111063397723576" + } + }, + { + "Validator": { + "validator_public_key": "01f58b94526d280881f79744effebc555426190950d5dfdd2f8aaf10ceaec010c6", + "amount": "6660504858490961" + } + }, + { + "Delegator": { + "delegator_kind": { + "PublicKey": "0106ed45915392c02b37136618372ac8dde8e0e3b8ee6190b2ca6db539b354ede4" + }, + "validator_public_key": "01f58b94526d280881f79744effebc555426190950d5dfdd2f8aaf10ceaec010c6", + "amount": "183204228041446" + } + }, + { + "Validator": { + "validator_public_key": "01f58b94526d280881f79744effebc555426190950d5dfdd2f8aaf10ceaec010c6", + "amount": "199637730476608" + } + }, + { + "Delegator": { + "delegator_kind": { + "PublicKey": "0184f6d260f4ee6869ddb36affe15456de6ae045278fa2f467bb677561ce0dad55" + }, + "validator_public_key": "01fed662dc7f1f7af43ad785ba07a8cc05b7a96f9ee69613cfde43bc56bec1140b", + "amount": "2170319328593039" + } + }, + { + "Validator": { + "validator_public_key": "01fed662dc7f1f7af43ad785ba07a8cc05b7a96f9ee69613cfde43bc56bec1140b", + "amount": "2366902069827651" + } + }, + { + "Delegator": { + "delegator_kind": { + "PublicKey": "0184f6d260f4ee6869ddb36affe15456de6ae045278fa2f467bb677561ce0dad55" + }, + "validator_public_key": "01fed662dc7f1f7af43ad785ba07a8cc05b7a96f9ee69613cfde43bc56bec1140b", + "amount": "217749920954248" + } + }, + { + "Validator": { + "validator_public_key": "01fed662dc7f1f7af43ad785ba07a8cc05b7a96f9ee69613cfde43bc56bec1140b", + "amount": "237377113583604" + } + } + ] + } +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/sse-transaction-processed-v200.json b/Casper.Network.SDK.Test/TestData/sse-transaction-processed-v200.json new file mode 100644 index 0000000..612c53c --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/sse-transaction-processed-v200.json @@ -0,0 +1,86 @@ +{ + "transaction_hash": { + "Version1": "9749ce1dc5dbd0d9a611088f934fd81c2c8429dbab0a3a7d281359be0a92d29a" + }, + "initiator_addr": { + "PublicKey": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a" + }, + "timestamp": "2024-12-18T11:32:13.181Z", + "ttl": "30m", + "block_hash": "0dabde3e8b065e734247b7d5328ac18317af9842f0141ffe41173df15efd97a8", + "execution_result": { + "Version2": { + "initiator": { + "PublicKey": "01a5a5b7328118681638be3e06c8749609280dba4c9daf9aeb3d3464b8839b018a" + }, + "error_message": "ApiError::Formatting [18]", + "limit": "3000000000", + "consumed": "1241925", + "cost": "3000000000", + "transfers": [], + "size_estimate": 591, + "effects": [ + { + "key": "balance-hold-012cbc4e6c6edcb446920db63ec8c3d2e0e4e5922391ecf2a5e66d8208bbe394a473378ad993010000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "04005ed0b2", + "parsed": "3000000000" + } + } + } + }, + { + "key": "balance-hold-012cbc4e6c6edcb446920db63ec8c3d2e0e4e5922391ecf2a5e66d8208bbe394a473378ad993010000", + "kind": { + "Prune": "balance-hold-012cbc4e6c6edcb446920db63ec8c3d2e0e4e5922391ecf2a5e66d8208bbe394a473378ad993010000" + } + }, + { + "key": "balance-hold-002cbc4e6c6edcb446920db63ec8c3d2e0e4e5922391ecf2a5e66d8208bbe394a473378ad993010000", + "kind": { + "Write": { + "CLValue": { + "cl_type": "U512", + "bytes": "04005ed0b2", + "parsed": "3000000000" + } + } + } + }, + { + "key": "bid-addr-01520037cd249ccbcfeb0b9feae07d8d4f7d922cf88adc4f3e8691f9d34ccc8d09", + "kind": "Identity" + }, + { + "key": "bid-addr-04520037cd249ccbcfeb0b9feae07d8d4f7d922cf88adc4f3e8691f9d34ccc8d093400000000000000", + "kind": { + "Write": { + "BidKind": { + "Credit": { + "validator_public_key": "0190664e16a17594ed2d0e3c279c4cf5894e8db0da15e3b91c938562a1caae32ab", + "era_id": 52, + "amount": "3000000000" + } + } + } + } + } + ] + } + }, + "messages": [ + { + "hash_addr": "9038763d3066f0217047263ebd48dc7c839fadfdde141f5b990866563655b44a", + "message": { + "String": "dummy-data" + }, + "topic_name": "events", + "topic_name_hash": "5721a6d9d7a9afe5dfdb35276fb823bed0f825350e4d865a5ec0110c380de4e1", + "topic_index": 1, + "block_index": 2 + } + ] +} \ No newline at end of file diff --git a/Casper.Network.SDK.Test/TestData/write_contract_transform_v2.json b/Casper.Network.SDK.Test/TestData/write_contract_transform_v2.json new file mode 100644 index 0000000..3103967 --- /dev/null +++ b/Casper.Network.SDK.Test/TestData/write_contract_transform_v2.json @@ -0,0 +1,452 @@ +{ + "key": "hash-94b21891ae273b17eeb6a1899a52ab952bcc4da2e19626563f88d6cf7ab6a2bd", + "kind": { + "Write": { + "Contract": { + "contract_package_hash": "contract-package-b71675d8cf701d9bc584cb5152706873110e5158b004fb966ed28be49c66b39a", + "contract_wasm_hash": "contract-wasm-a9fb7ec293465829432a8e543a2c2d5bba6d622c512af7e0cf204f6f536a1cbf", + "named_keys": [ + { + "name": "__events", + "key": "uref-821900c35dcc7bedfa01d4901621990552d80870228b4d9a37e47364ae7d12a6-007" + }, + { + "name": "__events_ces_version", + "key": "uref-993d25549280a839ef18c81d1211e86766ff29e7b7685912bc0e4e4a4964d886-007" + }, + { + "name": "__events_length", + "key": "uref-c640355c74023b0eb6025d376285235767ffe73d9be57d83576e647aa720eacc-007" + }, + { + "name": "__events_schema", + "key": "uref-5bf9cb0884874cc7d80e5bcab54c5100bfc8773908b85099ab569b32bd6bd72d-007" + }, + { + "name": "state", + "key": "uref-c5ae802a50fb72194d3c543805bab1a612186bdf2cc5d62758595694a1928fff-007" + } + ], + "entry_points": [ + { + "name": "add_to_the_pool", + "entry_point": { + "name": "add_to_the_pool", + "args": [], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "allowance", + "entry_point": { + "name": "allowance", + "args": [ + { + "name": "owner", + "cl_type": "Key" + }, + { + "name": "spender", + "cl_type": "Key" + } + ], + "ret": "U256", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "approve", + "entry_point": { + "name": "approve", + "args": [ + { + "name": "spender", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "balance_of", + "entry_point": { + "name": "balance_of", + "args": [ + { + "name": "account", + "cl_type": "Key" + } + ], + "ret": "U256", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "burn", + "entry_point": { + "name": "burn", + "args": [ + { + "name": "owner", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "change_security", + "entry_point": { + "name": "change_security", + "args": [ + { + "name": "admin_list", + "cl_type": { + "List": "Key" + } + }, + { + "name": "minter_list", + "cl_type": { + "List": "Key" + } + }, + { + "name": "none_list", + "cl_type": { + "List": "Key" + } + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "claim", + "entry_point": { + "name": "claim", + "args": [ + { + "name": "receipt_id", + "cl_type": "U32" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "decimals", + "entry_point": { + "name": "decimals", + "args": [], + "ret": "U8", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "decrease_allowance", + "entry_point": { + "name": "decrease_allowance", + "args": [ + { + "name": "spender", + "cl_type": "Key" + }, + { + "name": "decr_by", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "grant_role", + "entry_point": { + "name": "grant_role", + "args": [ + { + "name": "role", + "cl_type": { + "ByteArray": 32 + } + }, + { + "name": "address", + "cl_type": "Key" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "has_role", + "entry_point": { + "name": "has_role", + "args": [ + { + "name": "role", + "cl_type": { + "ByteArray": 32 + } + }, + { + "name": "address", + "cl_type": "Key" + } + ], + "ret": "Bool", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "increase_allowance", + "entry_point": { + "name": "increase_allowance", + "args": [ + { + "name": "spender", + "cl_type": "Key" + }, + { + "name": "inc_by", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "init", + "entry_point": { + "name": "init", + "args": [ + { + "name": "validator_address", + "cl_type": "PublicKey" + }, + { + "name": "claim_time", + "cl_type": "U64" + } + ], + "ret": "Unit", + "access": { + "Groups": [ + "constructor_group" + ] + }, + "entry_point_type": "Called" + } + }, + { + "name": "mint", + "entry_point": { + "name": "mint", + "args": [ + { + "name": "owner", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "name", + "entry_point": { + "name": "name", + "args": [], + "ret": "String", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "remove_from_the_pool", + "entry_point": { + "name": "remove_from_the_pool", + "args": [ + { + "name": "amount", + "cl_type": "U512" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "revoke_role", + "entry_point": { + "name": "revoke_role", + "args": [ + { + "name": "role", + "cl_type": { + "ByteArray": 32 + } + }, + { + "name": "address", + "cl_type": "Key" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "stake", + "entry_point": { + "name": "stake", + "args": [], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "staked_cspr", + "entry_point": { + "name": "staked_cspr", + "args": [], + "ret": "U512", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "symbol", + "entry_point": { + "name": "symbol", + "args": [], + "ret": "String", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "total_supply", + "entry_point": { + "name": "total_supply", + "args": [], + "ret": "U256", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "transfer", + "entry_point": { + "name": "transfer", + "args": [ + { + "name": "recipient", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "transfer_from", + "entry_point": { + "name": "transfer_from", + "args": [ + { + "name": "owner", + "cl_type": "Key" + }, + { + "name": "recipient", + "cl_type": "Key" + }, + { + "name": "amount", + "cl_type": "U256" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "unstake", + "entry_point": { + "name": "unstake", + "args": [ + { + "name": "scspr_amount", + "cl_type": "U256" + } + ], + "ret": "U32", + "access": "Public", + "entry_point_type": "Called" + } + }, + { + "name": "withdraw_from_the_pool", + "entry_point": { + "name": "withdraw_from_the_pool", + "args": [ + { + "name": "amount", + "cl_type": "U512" + } + ], + "ret": "Unit", + "access": "Public", + "entry_point_type": "Called" + } + } + ], + "protocol_version": "2.0.0" + } + } + } +} diff --git a/Casper.Network.SDK.Test/TransferDeployTest.cs b/Casper.Network.SDK.Test/TransferDeployTest.cs index 1ef8f00..51b6e75 100644 --- a/Casper.Network.SDK.Test/TransferDeployTest.cs +++ b/Casper.Network.SDK.Test/TransferDeployTest.cs @@ -41,7 +41,7 @@ public void Transfer1() var deploy = new Deploy(header, payment, session); - deploy.AddApproval(new DeployApproval() + deploy.AddApproval(new Approval() { Signature = Signature.FromHexString("012dbf03817a51794a8e19e0724884075e6d1fbec326b766ecfa6658b41f81290da85e23b24e88b1c8d9761185c961daee1adab0649912a6477bcd2e69bd91bd08"), Signer = PublicKey.FromHexString("01027c04a0210afdf4a83328d57e8c2a12247a86d872fb53367f22a84b1b53d2a9") diff --git a/Casper.Network.SDK.Test/TransformsTest.cs b/Casper.Network.SDK.Test/TransformsTest.cs new file mode 100644 index 0000000..2526c76 --- /dev/null +++ b/Casper.Network.SDK.Test/TransformsTest.cs @@ -0,0 +1,88 @@ +using System.IO; +using System.Linq; +using System.Numerics; +using System.Text.Json; +using Casper.Network.SDK.JsonRpc.ResultTypes; +using Casper.Network.SDK.Types; +using NUnit.Framework; + +namespace NetCasperTest +{ + public class TransformsTest + { + [Test] + public void TransformsTest_v156() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/deploy-testing-effects-v156.json"); + + var result = RpcResult.Parse(json); + Assert.IsNotNull(result); + + var effects = result.ExecutionInfo.ExecutionResult.Effect; + Assert.AreEqual(2, effects.Count(t => t.Kind is IdentityTransformKind)); + + Assert.AreEqual(4, effects.Count(t => t.Kind is WriteTransformKind kind && + kind.Value.CLValue is not null)); + Assert.AreEqual(1, effects.Count(t => t.Kind is WriteTransformKind kind && + kind.Value.CLValue is not null && + kind.Value.CLValue.TypeInfo.Type == CLType.String && + kind.Value.CLValue.ToString().Equals("JJJ"))); + + var addU64Kind = + effects.FirstOrDefault(t => t.Kind is AddUInt64TransformKind).Kind as AddUInt64TransformKind; + Assert.AreEqual(555, addU64Kind.Value); + var addU512Kind = + effects.FirstOrDefault(t => t.Kind is AddUInt512TransformKind).Kind as AddUInt512TransformKind; + Assert.AreEqual(new BigInteger(1000000000000), addU512Kind.Value); + + var addKeysKind = + effects.FirstOrDefault(t => t.Kind is AddKeysTransformKind).Kind as AddKeysTransformKind; + Assert.AreEqual(1, addKeysKind.Value.Count); + Assert.AreEqual("cep18_contract_package_JJJ", addKeysKind.Value[0].Name); + Assert.AreEqual("hash-0505050505050505050505050505050505050505050505050505050505050505", addKeysKind.Value[0].Key.ToString()); + + Assert.AreEqual(2, effects.Count(t => t.Kind is WriteContractPackageLegacyTransformKind)); + Assert.AreEqual(1, effects.Count(t => t.Kind is WriteContractPackageLegacyTransformKind && + t.Key.ToString().Equals("hash-0505050505050505050505050505050505050505050505050505050505050505"))); + Assert.AreEqual(1, effects.Count(t => t.Kind is WriteContractLegacyTransformKind)); + Assert.AreEqual(1, effects.Count(t => t.Kind is WriteContractWasmLegacyTransformKind)); + + Assert.AreEqual(1, effects.Count(t => t.Kind is WriteTransformKind kind && + kind.Value.DeployInfo is not null)); + Assert.AreEqual(1, effects.Count(t => t.Kind is WriteTransformKind kind && + kind.Value.Transfer is not null)); + Assert.AreEqual(1, effects.Count(t => t.Kind is WriteTransformKind kind && + kind.Value.Bid is not null)); + Assert.AreEqual(1, effects.Count(t => t.Kind is WriteTransformKind kind && + kind.Value.Unbonding is not null)); + } + + [Test] + public void WriteContractTransform_v200() + { + string json = File.ReadAllText(TestContext.CurrentContext.TestDirectory + + "/TestData/write_contract_transform_v2.json"); + + var options = new JsonSerializerOptions() + { + WriteIndented = false, + Converters = + { + new Transform.TransformConverter(), + new StoredValue.StoredValueConverter(), + } + }; + var transform = JsonSerializer.Deserialize(json, options); + + Assert.IsNotNull(transform); + Assert.IsNotNull(transform.Key); + var writeTransform = transform.Kind as WriteTransformKind; + Assert.IsNotNull(writeTransform); + Assert.IsNotNull(writeTransform.Value); + var contract = writeTransform.Value.Contract; + Assert.IsNotNull(contract); + Assert.IsTrue(contract.EntryPoints.Count > 0); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/ByteSerializers/BaseByteSerializer.cs b/Casper.Network.SDK/ByteSerializers/BaseByteSerializer.cs index 26698e1..c7be481 100644 --- a/Casper.Network.SDK/ByteSerializers/BaseByteSerializer.cs +++ b/Casper.Network.SDK/ByteSerializers/BaseByteSerializer.cs @@ -12,6 +12,13 @@ protected static void WriteInteger(MemoryStream ms, int value) ms.Write(bytes); } + protected static void WriteUShort(MemoryStream ms, ushort value) + { + var bytes = BitConverter.GetBytes(value); + if(!BitConverter.IsLittleEndian) Array.Reverse(bytes); + ms.Write(bytes); + } + protected static void WriteUInteger(MemoryStream ms, uint value) { var bytes = BitConverter.GetBytes(value); @@ -36,6 +43,7 @@ protected static void WriteBytes(MemoryStream ms, byte[] value) ms.Write(value); } + protected static void WriteString(MemoryStream ms, string value) { var valueBytes = System.Text.Encoding.UTF8.GetBytes(value); @@ -44,5 +52,17 @@ protected static void WriteString(MemoryStream ms, string value) ms.Write(lenBytes); ms.Write(valueBytes); } + + protected static void WriteMaybeUInteger(MemoryStream ms, uint? maybeValue) + { + WriteByte(ms, (byte)(maybeValue.HasValue ? 0x01 : 0x00)); + + if (maybeValue.HasValue) + { + var bytes = BitConverter.GetBytes(maybeValue.Value); + if(!BitConverter.IsLittleEndian) Array.Reverse(bytes); + ms.Write(bytes); + } + } } } \ No newline at end of file diff --git a/Casper.Network.SDK/ByteSerializers/CalltableSerialization.cs b/Casper.Network.SDK/ByteSerializers/CalltableSerialization.cs new file mode 100644 index 0000000..c73cdd1 --- /dev/null +++ b/Casper.Network.SDK/ByteSerializers/CalltableSerialization.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Casper.Network.SDK.Types; + +namespace Casper.Network.SDK.ByteSerializers +{ + public class Field + { + public readonly ushort Index; + public readonly uint Offset; + public readonly byte[] Value; + + public Field(ushort index, uint offset, byte[] value) + { + this.Index = index; + this.Offset = offset; + this.Value = value; + } + + public static int SerializedVecSize(int numberOfFields) + { + return sizeof(uint) + sizeof(uint) * sizeof(ushort); + } + } + + public class CalltableSerialization : BaseByteSerializer + { + private readonly List _fields = new List(); + private int _currentOffset; + + public CalltableSerialization() + { + _fields = new List(); + } + + public CalltableSerialization AddField(int index, CLValue value) + { + // var serializer = new CLValueByteSerializer(); + // var bytes = serializer.ToBytes(value); + return AddField(index, value.Bytes); + } + + public CalltableSerialization AddField(int index, byte[] value) + { + if (_fields.Count != index) { + throw new Exception("Add fields in correct index order."); + } + + _fields.Add(new Field((ushort)index, (uint)_currentOffset, value)); + + _currentOffset += value.Length; + + return this; + } + + public byte[] GetBytes() + { + var calltable_bytes = new MemoryStream(); + var payload_bytes = new MemoryStream(); + WriteUInteger(calltable_bytes, (uint)_fields.Count); + foreach (var field in _fields) + { + WriteUShort(calltable_bytes, field.Index); + WriteUInteger(calltable_bytes, field.Offset); + payload_bytes.Write(field.Value, 0, field.Value.Length); + } + WriteUInteger(calltable_bytes, (uint)payload_bytes.Length); + payload_bytes.Seek(0, SeekOrigin.Begin); + payload_bytes.CopyTo(calltable_bytes); + + return calltable_bytes.ToArray(); + } + } +} diff --git a/Casper.Network.SDK/ByteSerializers/DeployApprovalByteSerializer.cs b/Casper.Network.SDK/ByteSerializers/DeployApprovalByteSerializer.cs index b9f62d4..d3e6762 100644 --- a/Casper.Network.SDK/ByteSerializers/DeployApprovalByteSerializer.cs +++ b/Casper.Network.SDK/ByteSerializers/DeployApprovalByteSerializer.cs @@ -3,9 +3,9 @@ namespace Casper.Network.SDK.ByteSerializers { - public class DeployApprovalByteSerializer : BaseByteSerializer, IByteSerializer + public class DeployApprovalByteSerializer : BaseByteSerializer, IByteSerializer { - public byte[] ToBytes(DeployApproval source) + public byte[] ToBytes(Approval source) { var ms = new MemoryStream(); WriteBytes(ms, source.Signer.GetBytes()); diff --git a/Casper.Network.SDK/ByteSerializers/LittleEndianConverter.cs b/Casper.Network.SDK/ByteSerializers/LittleEndianConverter.cs new file mode 100644 index 0000000..ac4db5f --- /dev/null +++ b/Casper.Network.SDK/ByteSerializers/LittleEndianConverter.cs @@ -0,0 +1,27 @@ +using System; + +namespace Casper.Network.SDK.ByteSerializers +{ + public class LittleEndianConverter + { + public static byte[] GetBytes(int value) + { + var bytes = BitConverter.GetBytes(value); + if (!BitConverter.IsLittleEndian) + Array.Reverse(bytes); + return bytes; + } + + public static int ToInt32(byte[] bytes, int startIndex = 0) + { + if (!BitConverter.IsLittleEndian) + { + var temp = new byte[4]; + Array.Copy(bytes, startIndex, temp, 0, 4); + Array.Reverse(temp); // Convert to native order + return BitConverter.ToInt32(temp, 0); + } + return BitConverter.ToInt32(bytes, startIndex); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Casper.Network.SDK.csproj b/Casper.Network.SDK/Casper.Network.SDK.csproj index b7032c3..b26c171 100644 --- a/Casper.Network.SDK/Casper.Network.SDK.csproj +++ b/Casper.Network.SDK/Casper.Network.SDK.csproj @@ -4,9 +4,9 @@ net8.0;netstandard2.0 9.0 CS1591 - 2.4.0.0 - 2.4.0 - 2.4.0 + 3.0.0.0 + 3.0.0-beta3 + 3.0.0-beta3 Casper.Network.SDK make-software https://github.com/make-software/casper-net-sdk @@ -24,9 +24,9 @@ - - - + + + diff --git a/Casper.Network.SDK/Compat/EnumCompat.cs b/Casper.Network.SDK/Compat/EnumCompat.cs index cae5ad5..15d902e 100644 --- a/Casper.Network.SDK/Compat/EnumCompat.cs +++ b/Casper.Network.SDK/Compat/EnumCompat.cs @@ -7,6 +7,11 @@ public static TEnum Parse(this String value) where TEnum : struct { return (TEnum)Enum.Parse(typeof(TEnum), value); } + + public static bool TryParse(this String value, out TEnum e) where TEnum : struct + { + return Enum.TryParse( value, true, out e); + } public static string GetName(TEnum value) where TEnum : Enum { diff --git a/Casper.Network.SDK/Converters/BidsListConverter.cs b/Casper.Network.SDK/Converters/BidsListConverter.cs index fc4cd35..9072740 100644 --- a/Casper.Network.SDK/Converters/BidsListConverter.cs +++ b/Casper.Network.SDK/Converters/BidsListConverter.cs @@ -6,27 +6,30 @@ namespace Casper.Network.SDK.Converters { - public class BidsListConverter : JsonConverter> + public class BidKindsListConverter : JsonConverter> { - public override List Read( + const ulong DefaultMinimumDelegationAmount = 500l * 1_000_000_000l; + const ulong DefaultMaximumDelegationAmount = 1_000_000_000 * 1_000_000_000l; + + public override List Read( ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + var bids = new List(); + if (reader.TokenType != JsonTokenType.StartArray) - throw new JsonException("Array token expected to deserialize a list of Bids"); + throw new JsonException("StartArray token expected to deserialize a list of Bids"); reader.Read(); // Start array - List bids = new List(); - - while (reader.TokenType == JsonTokenType.StartObject) + while (reader.TokenType != JsonTokenType.EndArray) { reader.Read(); - + string publicKey = null; - Bid bid = null; - + BidKind bid = null; + while (reader.TokenType == JsonTokenType.PropertyName) { var property = reader.GetString(); @@ -38,25 +41,73 @@ public override List Read( reader.Read(); break; case "bid": - bid = JsonSerializer.Deserialize(ref reader, options); - reader.Read(); // end object + try + { + using (JsonDocument document = JsonDocument.ParseValue(ref reader)) + { + if (document.RootElement.TryGetProperty("bonding_purse", out var bondingPurse)) + { + var unifiedBid = + JsonSerializer.Deserialize(document.RootElement.GetRawText()); + bid = new BidKind() + { + Unified = unifiedBid, + }; + } + else + { + bid = JsonSerializer.Deserialize(document.RootElement.GetRawText()); + } + + reader.Read(); // read end of object + } + } + catch (Exception e) + { + throw new JsonException(e.Message); + } + break; default: throw new JsonException($"Unexpected property '{property}' deserializing Bid"); } } - if (bid != null && bid.PublicKey == null && publicKey != null) - bid = new Bid + if (bid?.Unified != null) + { + // Convert Unified bids to 1 Validator BidKind and 1 Delegator BidKind per delegator included + bids.Add(new BidKind() + { + Validator = new ValidatorBid() + { + PublicKey = bid.Unified.PublicKey ?? PublicKey.FromHexString(publicKey), + BondingPurse = bid.Unified.BondingPurse, + DelegationRate = bid.Unified.DelegationRate, + StakedAmount = bid.Unified.StakedAmount, + MinimumDelegationAmount = DefaultMinimumDelegationAmount, + MaximumDelegationAmount = DefaultMaximumDelegationAmount, + ReservedSlots = 0, + Inactive = bid.Unified.Inactive, + } + }); + + foreach (var delegator in bid.Unified.Delegators) { - BondingPurse = bid.BondingPurse, - DelegationRate = bid.DelegationRate, - Delegators = bid.Delegators, - Inactive = bid.Inactive, - StakedAmount = bid.StakedAmount, - PublicKey = PublicKey.FromHexString(publicKey), - }; - bids.Add(bid); + bids.Add(new BidKind() + { + Delegator = new DelegatorBid() + { + VestingSchedule = delegator.VestingSchedule, + ValidatorPublicKey = delegator.ValidatorPublicKey, + StakedAmount = delegator.StakedAmount, + BondingPurse = delegator.BondingPurse, + DelegatorKind = new DelegatorKind() { PublicKey = delegator.DelegatorPublicKey }, + } + }); + } + } + else + bids.Add(bid); if (reader.TokenType != JsonTokenType.EndObject) throw new JsonException("End object token expected while deserializing a list of Bids"); @@ -68,7 +119,7 @@ public override List Read( public override void Write( Utf8JsonWriter writer, - List value, + List value, JsonSerializerOptions options) { throw new NotImplementedException("Write method for Bid not yet implemented"); diff --git a/Casper.Network.SDK/Converters/HexBytesConverter.cs b/Casper.Network.SDK/Converters/HexBytesConverter.cs index b9dded7..94f2e67 100644 --- a/Casper.Network.SDK/Converters/HexBytesConverter.cs +++ b/Casper.Network.SDK/Converters/HexBytesConverter.cs @@ -19,26 +19,4 @@ public override void Write( JsonSerializerOptions options) => writer.WriteStringValue(Org.BouncyCastle.Utilities.Encoders.Hex.ToHexString(bytes)); } - - public class HexBytesWithChecksumConverter : JsonConverter - { - public override byte[] Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) - { - var hex = reader.GetString(); - var bytes = CEP57Checksum.Decode(hex, out var checksumResult); - if (checksumResult == CEP57Checksum.InvalidChecksum) - throw new JsonException("Wrong checksum in hexadecimal string."); - - return bytes; - } - - public override void Write( - Utf8JsonWriter writer, - byte[] bytes, - JsonSerializerOptions options) => - writer.WriteStringValue(CEP57Checksum.Encode(bytes)); - } } \ No newline at end of file diff --git a/Casper.Network.SDK/Converters/NamedArgsListConverter.cs b/Casper.Network.SDK/Converters/NamedArgsListConverter.cs new file mode 100644 index 0000000..bfdc0a9 --- /dev/null +++ b/Casper.Network.SDK/Converters/NamedArgsListConverter.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Casper.Network.SDK.Converters +{ + public class NamedArgsListConverter : JsonConverter> + { + public override List Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException("Array/Object start token expected to deserialize a list of NamedArgs"); + + reader.Read(); // Start object + + var named = reader.GetString(); + reader.Read(); + if (named != "Named") + throw new JsonException("Named property expected"); + + reader.Read(); // Start array + + List list = new List(); + + if (reader.TokenType == JsonTokenType.EndObject) + return list; //this is an empty list, just return... + + if (reader.TokenType == JsonTokenType.EndArray) + { + //this is an empty list, skip end array item and return... + reader.Read(); + return list; + } + + var tConverter = Activator.CreateInstance(typeof(TConverter)) as JsonConverter; + + if (reader.TokenType is JsonTokenType.StartArray or JsonTokenType.StartObject or JsonTokenType.PropertyName) + { + while (reader.TokenType == JsonTokenType.StartObject || + reader.TokenType == JsonTokenType.StartArray || + (tConverter is IDeserializeAsList && reader.TokenType == JsonTokenType.PropertyName)) + { + var element = JsonSerializer.Deserialize(ref reader, new JsonSerializerOptions() + { + Converters = { tConverter } + }); + reader.Read(); // end object/array + + list.Add(element); + } + } + else + { + while (reader.TokenType != JsonTokenType.EndArray) + { + var element = JsonSerializer.Deserialize(ref reader, new JsonSerializerOptions() + { + Converters = { tConverter } + }); + list.Add(element); + reader.Read(); + } + } + + reader.Read(); //read end object + return list; + } + + public override void Write( + Utf8JsonWriter writer, + List value, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WritePropertyName("Named"); + writer.WriteStartArray(); + var tConverter = Activator.CreateInstance(typeof(TConverter)) as JsonConverter; + var elOptions = new JsonSerializerOptions() + { + WriteIndented = options.WriteIndented, + Converters = { tConverter } + }; + foreach (var element in value) + { + JsonSerializer.Serialize(writer, element, elOptions); + } + + writer.WriteEndArray(); + writer.WriteEndObject(); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/ICasperClient.cs b/Casper.Network.SDK/ICasperClient.cs index 985b2d4..68ee6db 100644 --- a/Casper.Network.SDK/ICasperClient.cs +++ b/Casper.Network.SDK/ICasperClient.cs @@ -10,7 +10,7 @@ public interface ICasperClient { Task GetStateRootHash(string blockHash = null); - Task GetStateRootHash(int blockHeight); + Task GetStateRootHash(ulong blockHeight); Task> GetNodeStatus(); @@ -18,16 +18,32 @@ public interface ICasperClient Task> GetAuctionInfo(string blockHash = null); - Task> GetAuctionInfo(int blockHeight); + Task> GetAuctionInfo(ulong blockHeight); Task> GetAccountInfo(PublicKey publicKey, string blockHash = null); Task> GetAccountInfo(string publicKey, string blockHash = null); - Task> GetAccountInfo(PublicKey publicKey, int blockHeight); + Task> GetAccountInfo(PublicKey publicKey, ulong blockHeight); - Task> GetAccountInfo(string publicKey, int blockHeight); + Task> GetAccountInfo(string publicKey, ulong blockHeight); + Task> GetAccountInfo(AccountHashKey accountHash, string blockHash = null); + + Task> GetAccountInfo(AccountHashKey accountHash, ulong blockHeight); + + Task> GetEntity(IEntityIdentifier entityIdentifier, string blockHash = null); + + Task> GetEntity(IEntityIdentifier entityIdentifier, ulong blockHeight); + + Task> GetEntity(string entityAddr, string blockHash = null); + + Task> GetEntity(string entityAddr, ulong blockHeight); + + Task> GetPackage(string packageHash, string blockHash = null); + + Task> QueryGlobalState(string key, ulong height, + string path = null); Task> QueryGlobalState(string key, string stateRootHash = null, string path = null); @@ -40,32 +56,68 @@ Task> QueryGlobalStateWithBlockHash(string k Task> QueryGlobalStateWithBlockHash(GlobalStateKey key, string blockHash, string path = null); - Task> GetAccountBalance(string purseURef, + Task> GetBalance(string purseURef, string stateRootHash = null); - Task> GetAccountBalance(URef purseURef, - string stateRootHash = null); + Task> QueryBalance(IPurseIdentifier purseIdentifier, + string blockHash = null); - Task> GetAccountBalance(PublicKey publicKey, - string stateRootHash = null); + Task> QueryBalance(IPurseIdentifier purseIdentifier, + ulong blockHeight); + Task> QueryBalanceWithStateRootHash( + IPurseIdentifier purseIdentifier, string stateRootHash); + + Task> QueryBalanceDetails(IPurseIdentifier purseIdentifier, + string blockHash = null); + + Task> QueryBalanceDetails(IPurseIdentifier purseIdentifier, + ulong blockHeight); + + Task> QueryBalanceDetailsWithStateRootHash( + IPurseIdentifier purseIdentifier, string stateRootHash); + Task> PutDeploy(Deploy deploy); Task> GetDeploy(string deployHash, CancellationToken cancellationToken = default(CancellationToken)); + + Task> GetDeploy(string deployHash, + bool finalizedApprovals, + CancellationToken cancellationToken = default(CancellationToken)); + + Task> PutTransaction(TransactionV1 transaction); + + Task> GetTransaction(TransactionHash transactionHash, + bool finalizedApprovals, + CancellationToken cancellationToken = default(CancellationToken)); + + Task> GetTransaction(TransactionHash transactionHash, + CancellationToken cancellationToken = default(CancellationToken)); + + Task> GetTransaction(string transactionV1Hash, + bool finalizedApprovals, + CancellationToken cancellationToken = default(CancellationToken)); + + Task> GetTransaction(string transactionV1Hash, + CancellationToken cancellationToken = default(CancellationToken)); Task> GetBlock(string blockHash = null); - Task> GetBlock(int blockHeight); + Task> GetBlock(ulong blockHeight); Task> GetBlockTransfers(string blockHash = null); - Task> GetBlockTransfers(int blockHeight); + Task> GetBlockTransfers(ulong blockHeight); Task> GetEraInfoBySwitchBlock(string blockHash = null); - Task> GetEraInfoBySwitchBlock(int blockHeight); + Task> GetEraInfoBySwitchBlock(ulong blockHeight); + Task> GetEraSummary(string blockHash = null); + + Task> GetEraSummary(ulong blockHeight); + Task> GetDictionaryItem(string dictionaryItem, string stateRootHash = null); Task> GetDictionaryItemByAccount(string accountKey, string dictionaryName, @@ -79,6 +131,27 @@ Task> GetDictionaryItemByURef(string seedUR Task> GetValidatorChanges(); + Task> GetValidatorReward(PublicKey validator, string blockHash = null); + + Task> GetValidatorReward(PublicKey validator, ulong blockHeight); + + Task> GetValidatorRewardWithEraId(PublicKey validator, ulong eraId); + + Task> GetDelegatorReward(PublicKey validator, PublicKey delegator, + string blockHash = null); + + Task> GetDelegatorReward(PublicKey validator, PublicKey delegator, + ulong blockHeight); + + Task> GetDelegatorRewardWithEraId(PublicKey validator, PublicKey delegator, + ulong eraId); + Task GetRpcSchema(); + + Task> GetChainspec(); + + Task> SpeceulativeExecution(Deploy deploy, string stateRootHash = null); + + Task> SpeceulativeExecutionWithBlockHash(Deploy deploy, string blockHash = null); } } diff --git a/Casper.Network.SDK/JsonRpc/CasperMethods.cs b/Casper.Network.SDK/JsonRpc/CasperMethods.cs index 7dfe949..6e7db89 100644 --- a/Casper.Network.SDK/JsonRpc/CasperMethods.cs +++ b/Casper.Network.SDK/JsonRpc/CasperMethods.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; -using Casper.Network.SDK.JsonRpc; using Casper.Network.SDK.Types; -using Org.BouncyCastle.Utilities.Encoders; namespace Casper.Network.SDK.JsonRpc { @@ -20,7 +18,7 @@ public GetStateRootHash(string blockHash = null) : base("chain_get_state_root_ha /// Returns the state root hash at a given Block /// /// Block height for which the state root is queried. - public GetStateRootHash(int height) : base("chain_get_state_root_hash", height) + public GetStateRootHash(ulong height) : base("chain_get_state_root_hash", height) { } } @@ -59,7 +57,26 @@ public GetAuctionInfo(string blockHash) : base("state_get_auction_info", blockHa /// Returns the bids and validators at a given block. /// /// Block height for which the auction info is queried. - public GetAuctionInfo(int height) : base("state_get_auction_info", height) + public GetAuctionInfo(ulong height) : base("state_get_auction_info", height) + { + } + } + + public class GetAuctionInfoV2 : RpcMethod + { + /// + /// Returns the bids and validators at a given block. + /// + /// Block hash for which the auction info is queried. Null for the most recent auction info. + public GetAuctionInfoV2(string blockHash) : base("state_get_auction_info_v2", blockHash) + { + } + + /// + /// Returns the bids and validators at a given block. + /// + /// Block height for which the auction info is queried. + public GetAuctionInfoV2(ulong height) : base("state_get_auction_info_v2", height) { } } @@ -81,7 +98,7 @@ public GetAccountInfo(PublicKey publicKey, string blockHash = null) : base("stat /// /// The public key of the account. /// A block height for which the information of the account is queried. - public GetAccountInfo(PublicKey publicKey, int height) : base("state_get_account_info", height) + public GetAccountInfo(PublicKey publicKey, ulong height) : base("state_get_account_info", height) { this.Parameters.Add("account_identifier", publicKey); } @@ -101,7 +118,7 @@ public GetAccountInfo(AccountHashKey accountHash, string blockHash = null) : bas /// /// The account hash of the account. /// A block height for which the information of the account is queried. - public GetAccountInfo(AccountHashKey accountHash, int height) : base("state_get_account_info", height) + public GetAccountInfo(AccountHashKey accountHash, ulong height) : base("state_get_account_info", height) { // this.Parameters.Add("account_identifier", Hex.ToHexString(accountHash.GetBytes())); this.Parameters.Add("account_identifier", accountHash.ToString()); @@ -124,12 +141,55 @@ public GetAccountInfo(string publicKey, string blockHash = null) : base("state_g /// The public key of the account. /// A block height for which the information of the account is queried. [Obsolete("For Casper node v1.5.5 or newer use the new method signature with PublicKey or AccountHashKey", false)] - public GetAccountInfo(string publicKey, int height) : base("state_get_account_info", height) + public GetAccountInfo(string publicKey, ulong height) : base("state_get_account_info", height) { this.Parameters.Add("public_key", publicKey); } } + public class GetEntity : RpcMethod + { + /// + /// Returns an AddressableEntity from the network for a Block from the network + /// + /// A PublicKey, an AccoountHashKey, or an AddressableEntityKey + /// A a block identifier by hash or key. Null for the latest block + public GetEntity(IEntityIdentifier entityIdentifier, IBlockIdentifier blockIdentifier = null) : base("state_get_entity") + { + this.Parameters = new Dictionary + { + { "entity_identifier", entityIdentifier.GetEntityIdentifier() } + }; + + if(blockIdentifier != null) + this.Parameters.Add("block_identifier", blockIdentifier.GetBlockIdentifier()); + } + + /// + /// Returns an AddressableEntity from the network for a Block from the network + /// + /// A string with an addressable entity key. + /// A a block identifier by hash or key. Null for the latest block + public GetEntity(string addressableEntity, IBlockIdentifier blockIdentifier = null) + : this(new AddressableEntityKey(addressableEntity), blockIdentifier) + { + } + } + + public class GetPackage : RpcMethod + { + public GetPackage(IPackageIdentifier packageIdentifier, IBlockIdentifier blockIdentifier = null) : base("state_get_package") + { + this.Parameters = new Dictionary + { + { "package_identifier", packageIdentifier.GetPackageIdentifier() } + }; + + if(blockIdentifier != null) + this.Parameters.Add("block_identifier", blockIdentifier.GetBlockIdentifier()); + } + } + public class GetItem : RpcMethod { /// @@ -181,51 +241,48 @@ public GetBalance(string purseURef, string stateRootHash) : base("state_get_bala { this.Parameters = new Dictionary { - {"state_root_hash", stateRootHash}, - {"purse_uref", purseURef} - }; - } - - public GetBalance(URef uref, StateIdentifier stateIdentifier) : base("query_balance") - { - Dictionary mainPurse = new Dictionary - { - {"purse_uref", uref.ToString()} - }; - this.Parameters = new Dictionary - { - {"purse_identifier", mainPurse} + { "state_root_hash", stateRootHash }, + { "purse_uref", purseURef } }; - this.Parameters.Add("state_identifier", stateIdentifier.GetParam()); } + } - public GetBalance(AccountHashKey key, StateIdentifier stateIdentifier) : base("query_balance") + public class QueryBalance : RpcMethod + { + /// + /// Query for balance information using a purse identifier and a state identifier + /// + /// The identifier to obtain the purse corresponding to balance query. + /// The identifier for the state used for the query, if none is passed, the latest block will be used. + public QueryBalance(IPurseIdentifier purseIdentifier, StateIdentifier stateIdentifier = null) : base("query_balance") { - Dictionary mainPurse = new Dictionary - { - {"main_purse_under_account_hash", key.ToString()} - }; this.Parameters = new Dictionary { - {"purse_identifier", mainPurse} + {"purse_identifier", purseIdentifier.GetPurseIdentifier()}, }; - this.Parameters.Add("state_identifier", stateIdentifier.GetParam()); + if(stateIdentifier != null) + this.Parameters.Add("state_identifier", stateIdentifier.GetParam()); } + } - public GetBalance(PublicKey key, StateIdentifier stateIdentifier) : base("query_balance") + public class QueryBalanceDetails : RpcMethod + { + /// + /// Query for full balance information using a purse identifier and a state identifier + /// + /// The identifier to obtain the purse corresponding to balance query. + /// The identifier for the state used for the query, if none is passed, the latest block will be used. + public QueryBalanceDetails(IPurseIdentifier purseIdentifier, StateIdentifier stateIdentifier = null) : base("query_balance_details") { - Dictionary mainPurse = new Dictionary - { - {"main_purse_under_public_key", key.ToString()} - }; this.Parameters = new Dictionary { - {"purse_identifier", mainPurse} + {"purse_identifier", purseIdentifier.GetPurseIdentifier()}, }; - this.Parameters.Add("state_identifier", stateIdentifier.GetParam()); + if(stateIdentifier != null) + this.Parameters.Add("state_identifier", stateIdentifier.GetParam()); } } - + public class PutDeploy : RpcMethod { /// @@ -261,6 +318,65 @@ public GetDeploy(string deployHash, bool finalizedApprovals = false) : base("inf this.Parameters.Add("finalized_approvals", true); } } + + + public class PutTransaction : RpcMethod + { + /// + /// Sends a Transaction to the network for its execution. + /// + /// The Transactionv1 object. + public PutTransaction(TransactionV1 transaction) : base("account_put_transaction") + { + var txParameter = new Dictionary(); + txParameter.Add("Version1", transaction); + + this.Parameters = new Dictionary + { + { + "transaction", txParameter + } + }; + } + + /// + /// Sends a Transaction to the network for its execution. + /// + /// The Deploy object. + public PutTransaction(Deploy transaction) : base("account_put_transaction") + { + var txParameter = new Dictionary(); + txParameter.Add("Deploy", transaction); + + this.Parameters = new Dictionary + { + { + "transaction", txParameter + } + }; + } + } + + public class GetTransaction : RpcMethod + { + public GetTransaction(TransactionHash transactionHash, bool finalizedApprovals = false) : base("info_get_transaction") + { + var hashDict = new Dictionary(); + if(transactionHash.Deploy != null) + hashDict.Add("Deploy", transactionHash.Deploy); + if(transactionHash.Version1 != null) + hashDict.Add("Version1", transactionHash.Version1); + + this.Parameters = new Dictionary + { + { + "transaction_hash", hashDict + }, + }; + if (finalizedApprovals) + this.Parameters.Add("finalized_approvals", true); + } + } public class GetBlock : RpcMethod { @@ -276,7 +392,7 @@ public GetBlock(string blockHash) : base("chain_get_block", blockHash) /// Retrieves a Block from the network by its height number. /// /// Height of the block to retrieve. - public GetBlock(int height) : base("chain_get_block", height) + public GetBlock(ulong height) : base("chain_get_block", height) { } } @@ -295,7 +411,7 @@ public GetBlockTransfers(string blockHash) : base("chain_get_block_transfers", b /// Retrieves all transfers for a Block from the network /// /// Height of the block to retrieve the transfers from. - public GetBlockTransfers(int height) : base("chain_get_block_transfers", height) + public GetBlockTransfers(ulong height) : base("chain_get_block_transfers", height) { } } @@ -314,7 +430,7 @@ public GetEraInfoBySwitchBlock(string blockHash) : base("chain_get_era_info_by_s /// Retrieves an EraInfo from the network given a switch block. /// /// Block height of a switch block. - public GetEraInfoBySwitchBlock(int height) : base("chain_get_era_info_by_switch_block", height) + public GetEraInfoBySwitchBlock(ulong height) : base("chain_get_era_info_by_switch_block", height) { } } @@ -333,7 +449,7 @@ public GetEraSummary(string blockHash) : base("chain_get_era_summary", blockHash /// Retrieves current era info from the network given a block height /// /// Block height. - public GetEraSummary(int height) : base("chain_get_era_summary", height) + public GetEraSummary(ulong height) : base("chain_get_era_summary", height) { } } @@ -464,6 +580,90 @@ public GetValidatorChanges() : base("info_get_validator_changes") } } + public class GetValidatorReward : RpcMethod + { + /// + /// Returns the reward for a given era and a validator + /// + /// The public key of the validator. + /// The identifier for the state used for the query, if none is passed, the latest block will be used. + public GetValidatorReward(PublicKey validator, IBlockIdentifier blockIdentifier = null) : base("info_get_reward") + { + this.Parameters = new Dictionary + { + { "validator", validator.ToString() } + }; + + if (blockIdentifier != null) + this.Parameters.Add("era_identifier", new Dictionary + { + { "Block", blockIdentifier.GetBlockIdentifier() } + }); + } + + /// + /// Returns the reward for a given era and a validator + /// + /// The public key of the validator. + /// Id of the Era to retrieve the rewards from. + public GetValidatorReward(PublicKey validator, ulong eraId ) : base("info_get_reward") + { + this.Parameters = new Dictionary + { + { "validator", validator.ToString() }, + { "era_identifier", new Dictionary + { + { "Era", eraId } + } + } + }; + } + } + + public class GetDelegatorReward : RpcMethod + { + /// + /// Returns the reward for a given era and a delegator + /// + /// The public key of the validator. + /// The public key of the delegator. + /// The identifier for the state used for the query, if none is passed, the latest block will be used. + public GetDelegatorReward(PublicKey validator, PublicKey delegator, IBlockIdentifier blockIdentifier = null) : base("info_get_reward") + { + this.Parameters = new Dictionary + { + { "validator", validator.ToString() }, + { "delegator", delegator.ToString() } + }; + + if (blockIdentifier != null) + this.Parameters.Add("era_identifier", new Dictionary + { + { "Block", blockIdentifier.GetBlockIdentifier() } + }); + } + + /// + /// Returns the reward for a given era and a delegator + /// + /// The public key of the validator. + /// The public key of the delegator. + /// Id of the Era to retrieve the rewards from. + public GetDelegatorReward(PublicKey validator, PublicKey delegator, ulong eraId ) : base("info_get_reward") + { + this.Parameters = new Dictionary + { + { "validator", validator.ToString() }, + { "delegator", delegator.ToString() }, + { "era_identifier", new Dictionary + { + { "Era", eraId } + } + } + }; + } + } + public class GetRpcSchema : RpcMethod { /// diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/GetBalanceResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/GetBalanceResult.cs index db10152..b51cfba 100644 --- a/Casper.Network.SDK/JsonRpc/ResultTypes/GetBalanceResult.cs +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/GetBalanceResult.cs @@ -1,16 +1,12 @@ -using System; using System.Numerics; -using System.Text.Json; using System.Text.Json.Serialization; using Casper.Network.SDK.Converters; -using Casper.Network.SDK.Utils; namespace Casper.Network.SDK.JsonRpc.ResultTypes { /// /// Result for "state_get_balance" RPC response. /// - [JsonConverter(typeof(BalanceResultConverter))] public class GetBalanceResult : RpcResult { /// @@ -26,60 +22,4 @@ public class GetBalanceResult : RpcResult [JsonPropertyName("merkle_proof")] public string MerkleProof { get; init; } } - - public class BalanceResultConverter : JsonConverter - { - public override GetBalanceResult Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) - { - string api_version = null; - BigInteger balance = default; - string merkle_proof = null; - - reader.Read(); - - while (reader.TokenType == JsonTokenType.PropertyName) - { - var property = reader.GetString(); - reader.Read(); - switch (property) - { - case "api_version": - api_version = reader.GetString(); - reader.Read(); - break; - case "balance": - case "balance_value": - balance = BigInteger.Parse(reader.GetString()); - reader.Read(); - break; - case "merkle_proof": - merkle_proof = reader.GetString(); - reader.Read(); - break; - default: - throw new JsonException($"Unexpected property '{property}' deserializing QueryBalanceResult"); - } - } - - reader.Read(); - - return new GetBalanceResult - { - ApiVersion = api_version, - BalanceValue = balance, - MerkleProof = merkle_proof, - }; - } - - public override void Write( - Utf8JsonWriter writer, - GetBalanceResult ttlInMillis, - JsonSerializerOptions options) - { - throw new NotImplementedException(); - } - } } diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/GetBlockResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/GetBlockResult.cs index 1547c4f..f1625bf 100644 --- a/Casper.Network.SDK/JsonRpc/ResultTypes/GetBlockResult.cs +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/GetBlockResult.cs @@ -1,17 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; using System.Text.Json.Serialization; using Casper.Network.SDK.Types; namespace Casper.Network.SDK.JsonRpc.ResultTypes { + internal class BlockV1Compat + { + [JsonPropertyName("hash")] + public string Hash { get; init; } + + [JsonPropertyName("header")] + public BlockHeaderV1 Header { get; init; } + + [JsonPropertyName("body")] + public BlockBodyV1 Body { get; init; } + + [JsonPropertyName("proofs")] + public List Proofs { get; init; } + } + + /// + /// A JSON-friendly representation of a block and the signatures for that block + /// + internal class BlockWithSignatures + { + /// + /// The block. + /// + [JsonPropertyName("block")] + [JsonConverter(typeof(Block.BlockConverter))] + public Block Block { get; init; } + + /// + /// The proofs of the block, i.e. a collection of validators' signatures of the block hash. + /// + [JsonPropertyName("proofs")] + public List Proofs { get; init; } + } + + internal class GetBlockResultCompat : RpcResult + { + /// + /// The block, if found. + /// + [JsonPropertyName("block_with_signatures")] + public BlockWithSignatures BlockWithSignatures { get; init; } + + [JsonPropertyName("block")] + public BlockV1Compat BlockV1 { get; init; } + } + /// /// Result for "chain_get_block" RPC response. /// + [JsonConverter(typeof(GetBlockResultConverter))] public class GetBlockResult : RpcResult { /// - /// The block, if found. + /// The block. /// [JsonPropertyName("block")] + [JsonConverter(typeof(Block.BlockConverter))] public Block Block { get; init; } + + /// + /// The proofs of the block, i.e. a collection of validators' signatures of the block hash. + /// + [JsonPropertyName("proofs")] + public List Proofs { get; init; } + + public class GetBlockResultConverter : JsonConverter + { + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert == typeof(GetBlockResult); + } + + public override GetBlockResult Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + try + { + var resultCompat = JsonSerializer.Deserialize(ref reader, options); + + if (resultCompat.BlockV1 != null) + { + var blockV1 = new BlockV1 + { + Hash = resultCompat.BlockV1.Hash, + Header = resultCompat.BlockV1.Header, + Body = resultCompat.BlockV1.Body, + }; + return new GetBlockResult() + { + ApiVersion = resultCompat.ApiVersion, + Block = (Block)blockV1, + Proofs = resultCompat.BlockV1.Proofs, + }; + } + return new GetBlockResult() + { + ApiVersion = resultCompat.ApiVersion, + Block = resultCompat.BlockWithSignatures.Block, + Proofs = resultCompat.BlockWithSignatures.Proofs, + }; + } + catch (Exception e) + { + throw new JsonException(e.Message); + } + } + + public override void Write( + Utf8JsonWriter writer, + GetBlockResult block, + JsonSerializerOptions options) + { + throw new JsonException($"not yet implemented"); + } + } } -} \ No newline at end of file +} diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/GetBlockTransfersResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/GetBlockTransfersResult.cs index b3e6399..7658cb7 100644 --- a/Casper.Network.SDK/JsonRpc/ResultTypes/GetBlockTransfersResult.cs +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/GetBlockTransfersResult.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Text.Json.Serialization; +using Casper.Network.SDK.Converters; using Casper.Network.SDK.Types; namespace Casper.Network.SDK.JsonRpc.ResultTypes @@ -19,6 +20,7 @@ public class GetBlockTransfersResult : RpcResult /// The block's transfers /// [JsonPropertyName("transfers")] + [JsonConverter(typeof(GenericListConverter))] public List Transfers { get; init; } } } \ No newline at end of file diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/GetDeployResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/GetDeployResult.cs index bf2fa2a..9622793 100644 --- a/Casper.Network.SDK/JsonRpc/ResultTypes/GetDeployResult.cs +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/GetDeployResult.cs @@ -1,14 +1,13 @@ +using System; using System.Collections.Generic; +using System.Text.Json; using System.Text.Json.Serialization; using Casper.Network.SDK.Converters; using Casper.Network.SDK.Types; namespace Casper.Network.SDK.JsonRpc.ResultTypes { - /// - /// Result for "info_get_deploy" RPC response. - /// - public class GetDeployResult : RpcResult + public class GetDeployResultCompat : RpcResult { /// /// The deploy. @@ -16,23 +15,89 @@ public class GetDeployResult : RpcResult [JsonPropertyName("deploy")] public Deploy Deploy { get; init; } + /// + /// Execution info, if available. + /// + [JsonPropertyName("execution_info")] + public ExecutionInfo ExecutionInfo { get; init; } + /// /// The map of block hash to execution result. /// [JsonPropertyName("execution_results")] - [JsonConverter(typeof(GenericListConverter))] - public List ExecutionResults { get; init; } - + [JsonConverter(typeof(ExecutionResultV1.ExecutionResultV1Converter))] + public ExecutionResultV1 ExecutionResult { get; init; } + } + + /// + /// Result for "info_get_deploy" RPC response. + /// + [JsonConverter(typeof(GetDeployResultConverter))] + public class GetDeployResult : RpcResult + { /// - /// The hash of this deploy's block. + /// The deploy. /// - [JsonPropertyName("block_hash")] - public string BlockHash { get; init; } - + [JsonPropertyName("deploy")] + public Deploy Deploy { get; init; } + /// - /// The height of this deploy's block. + /// Execution info, if available. /// - [JsonPropertyName("block_height")] - public ulong BlockHeight { get; init; } + [JsonPropertyName("execution_info")] + public ExecutionInfo ExecutionInfo { get; init; } + + public class GetDeployResultConverter : JsonConverter + { + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert == typeof(GetDeployResult); + } + + public override GetDeployResult Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + try + { + var resultCompat = JsonSerializer.Deserialize(ref reader, options); + + if (resultCompat.ExecutionResult != null) + { + return new GetDeployResult() + { + ApiVersion = resultCompat.ApiVersion, + Deploy = resultCompat.Deploy, + ExecutionInfo = new ExecutionInfo + { + BlockHash = resultCompat.ExecutionResult.BlockHash, + BlockHeight = 0, + ExecutionResult = (ExecutionResult)resultCompat.ExecutionResult, + }, + }; + } + + return new GetDeployResult() + { + ApiVersion = resultCompat.ApiVersion, + Deploy = resultCompat.Deploy, + ExecutionInfo = resultCompat.ExecutionInfo, + }; + } + catch (Exception e) + { + throw new JsonException(e.Message); + } + } + + public override void Write( + Utf8JsonWriter writer, + GetDeployResult block, + JsonSerializerOptions options) + { + throw new JsonException($"not yet implemented"); + } + } } -} +} \ No newline at end of file diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/GetEntityResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/GetEntityResult.cs new file mode 100644 index 0000000..14ed152 --- /dev/null +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/GetEntityResult.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; +using Casper.Network.SDK.Types; + +namespace Casper.Network.SDK.JsonRpc.ResultTypes +{ + /// + /// Result for \"state_get_entity\" RPC response. + /// + [JsonConverter(typeof(GetEntityResultConverter))] + public class GetEntityResult : RpcResult + { + /// + /// An addressable entity. + /// + public AddressableEntity Entity { get; init; } + + /// + /// Array of named keys present in the entity. + /// + public List NamedKeys { get; init; } + + /// + /// Array of entry points defined in the entity + /// + public List EntryPoints { get; init; } + + /// + /// A legacy account. + /// + public Account LegacyAccount { get; init; } + + /// + /// The merkle proof. + /// + public string MerkleProof { get; init; } + + public class GetEntityResultConverter : JsonConverter + { + public override GetEntityResult Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + string api_version = null; + AddressableEntity entity = null; + var namedKeys = new List(); + var entryPoints = new List(); + Account legacyAccount = null; + string merkle_proof = null; + uint skippedEntityWrapperCount = 0; + reader.Read(); + + while (reader.TokenType == JsonTokenType.PropertyName) + { + var property = reader.GetString(); + reader.Read(); + switch (property) + { + case "api_version": + api_version = reader.GetString(); + break; + case "entity": + // parse only if we already read AddressableEntity property. Otherwise, this is a wrapper for an AnddressableEntity or a LegacyAccount + if (++skippedEntityWrapperCount >= 2) + entity = JsonSerializer.Deserialize(ref reader, options); + break; + case "AddressableEntity": + // continue, this is a wrapper for entity (the actual AddressableEntity), named_keys and entry_points. + ++skippedEntityWrapperCount; + break; + case "named_keys": + namedKeys = JsonSerializer.Deserialize>(ref reader, options); + break; + case "entry_points": + entryPoints = JsonSerializer.Deserialize>(ref reader, options); + break; + case "LegacyAccount": + if (reader.TokenType != JsonTokenType.Null) + legacyAccount = JsonSerializer.Deserialize(ref reader, options); + reader.Read(); + break; + case "merkle_proof": + merkle_proof = reader.GetString(); + break; + } + + reader.Read(); + + while (reader.TokenType == JsonTokenType.EndObject && --skippedEntityWrapperCount > 0) + reader.Read(); // skip entity wrapper EndObject token and continue parsing GetEntityResult properties + } + + reader.Read(); // skip outer type EndObject token + + if(entity == null && legacyAccount == null) + throw new JsonException($"Could not deserialize GetEntityResult."); + + return new GetEntityResult() + { + ApiVersion = api_version, + Entity = entity, + NamedKeys = namedKeys, + EntryPoints = entryPoints, + LegacyAccount = legacyAccount, + MerkleProof = merkle_proof + }; + } + + public override void Write( + Utf8JsonWriter writer, + GetEntityResult value, + JsonSerializerOptions options) + { + throw new NotImplementedException("Write method for GetEntityResult not yet implemented"); + } + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/GetNodeStatusResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/GetNodeStatusResult.cs index 528b70f..aa56686 100644 --- a/Casper.Network.SDK/JsonRpc/ResultTypes/GetNodeStatusResult.cs +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/GetNodeStatusResult.cs @@ -80,5 +80,16 @@ public class GetNodeStatusResult : RpcResult /// The status of the block synchronizer builders. /// [JsonPropertyName("block_sync")] public BlockSynchronizerStatus BlockSync { get; init; } + + /// + /// The hash of the latest switch block. + /// + [JsonPropertyName("latest_switch_block_hash")] public string LatestSwitchBlockHash { get; init; } + + /// + /// The protocol version running in the node + /// + [JsonPropertyName("protocol_version")] + public string ProtocolVersion { get; init; } } } diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/GetPackageResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/GetPackageResult.cs new file mode 100644 index 0000000..9043a30 --- /dev/null +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/GetPackageResult.cs @@ -0,0 +1,83 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Casper.Network.SDK.Types; + +namespace Casper.Network.SDK.JsonRpc.ResultTypes +{ + internal class PackageCompat : RpcResult + { + [JsonPropertyName("Package")] + public Package Package { get; init; } + + [JsonPropertyName("ContractPackage")] + public ContractPackage ContractPackage { get; init; } + } + + internal class GetPackageResultCompat : RpcResult + { + [JsonPropertyName("package")] + public PackageCompat Package { get; init; } + + [JsonPropertyName("merkle_proof")] + public string MerkleProof { get; init; } + } + + [JsonConverter(typeof(GetPackageResultConverter))] + public class GetPackageResult : RpcResult + { + /// + /// A legacy Contract Package. + /// + public ContractPackage ContractPackage { get; init; } + + /// + /// An Addressable Entity package. + /// + public Package Package { get; init; } + + /// + /// The merkle proof. + /// + public string MerkleProof { get; init; } + + public class GetPackageResultConverter : JsonConverter + { + public override bool CanConvert(Type typeToConvert) + { + return typeToConvert == typeof(GetPackageResult); + } + + public override GetPackageResult Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + try + { + var resultCompat = JsonSerializer.Deserialize(ref reader, options); + + return new GetPackageResult() + { + ApiVersion = resultCompat.ApiVersion, + ContractPackage = resultCompat.Package.ContractPackage, + Package = resultCompat.Package.Package, + MerkleProof = resultCompat.MerkleProof, + }; + } + catch (Exception e) + { + throw new JsonException(e.Message); + } + } + + public override void Write( + Utf8JsonWriter writer, + GetPackageResult block, + JsonSerializerOptions options) + { + throw new JsonException($"not implemented"); + } + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/GetRewardResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/GetRewardResult.cs new file mode 100644 index 0000000..f958775 --- /dev/null +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/GetRewardResult.cs @@ -0,0 +1,37 @@ +using System.Numerics; +using System.Text.Json.Serialization; +using Casper.Network.SDK.Converters; + +namespace Casper.Network.SDK.JsonRpc.ResultTypes +{ + /// + /// Result for \"info_get_reward\" RPC response. + /// + public class GetRewardResult : RpcResult + { + /// + /// The era for which the reward was calculated. + /// + [JsonPropertyName("era_id")] + public ulong EraId { get; init; } + + /// + /// The reward amount. + /// + [JsonPropertyName("reward_amount")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger Amount { get; init; } + + /// + /// The delegation rate of the validator. + /// + [JsonPropertyName("delegation_rate")] + public uint DelegationRate { get; init; } + + /// + /// The switch block hash at which the reward was distributed. + /// + [JsonPropertyName("switch_block_hash")] + public string SwitchBlockHash { get; init; } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/GetTransactionResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/GetTransactionResult.cs new file mode 100644 index 0000000..adc9a59 --- /dev/null +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/GetTransactionResult.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; +using Casper.Network.SDK.Types; + +namespace Casper.Network.SDK.JsonRpc.ResultTypes +{ + /// + /// Result for "info_get_deploy" RPC response. + /// + public class GetTransactionResult : RpcResult + { + /// + /// The deploy. + /// + [JsonPropertyName("transaction")] + [JsonConverter(typeof(Transaction.TransactionConverter))] + public Transaction Transaction { get; init; } + + /// + /// Execution info, if available. + /// + [JsonPropertyName("execution_info")] + public ExecutionInfo ExecutionInfo { get; init; } + } +} diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/PutTransactionResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/PutTransactionResult.cs new file mode 100644 index 0000000..21de098 --- /dev/null +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/PutTransactionResult.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; +using Casper.Network.SDK.Types; + +namespace Casper.Network.SDK.JsonRpc.ResultTypes +{ + /// + /// Result for "account_put_transaction" RPC response. + /// + public class PutTransactionResult + { + /// + /// Hex-encoded transaction hash. + /// + [JsonPropertyName("transaction_hash")] + public TransactionHash TransactionHash { get; init; } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/QueryBalanceDetailsResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/QueryBalanceDetailsResult.cs new file mode 100644 index 0000000..e20c0d0 --- /dev/null +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/QueryBalanceDetailsResult.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Text.Json.Serialization; +using Casper.Network.SDK.Converters; + +namespace Casper.Network.SDK.JsonRpc.ResultTypes +{ + /// + /// A hold. + /// + public class Hold + { + /// + /// The amount in the hold. + /// + [JsonPropertyName("amount")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger Amount { get; init; } + + /// + /// The block time at which the hold was created. + /// + [JsonPropertyName("time")] + public UInt64 Time { get; init; } + /// + /// A proof that the given value is present in the Merkle trie. + /// + [JsonPropertyName("proof")] + public string Proof { get; init; } + } + + /// + /// Result for "query_balance_details" RPC response. + /// + public class QueryBalanceDetailsResult : RpcResult + { + /// + /// The purses total balance, not considering holds. + /// + [JsonPropertyName("total_balance")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger TotalBalance { get; init; } + + /// + /// The available balance in motes (total balance - sum of all active holds). + /// + [JsonPropertyName("available_balance")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger AvailableBalance { get; init; } + + /// + /// A proof that the given value is present in the Merkle trie. + /// + [JsonPropertyName("total_balance_proof")] + public string TotalBalanceProof { get; init; } + + /// + /// Holds active at the requested point in time. + /// + [JsonPropertyName("holds")] + public List Holds { get; init; } + } +} diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/QueryBalanceResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/QueryBalanceResult.cs new file mode 100644 index 0000000..32efe92 --- /dev/null +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/QueryBalanceResult.cs @@ -0,0 +1,25 @@ +using System.Numerics; +using System.Text.Json.Serialization; +using Casper.Network.SDK.Converters; + +namespace Casper.Network.SDK.JsonRpc.ResultTypes +{ + /// + /// Result for "query_balance" RPC response. + /// + public class QueryBalanceResult : RpcResult + { + /// + /// The balance value. + /// + [JsonPropertyName("balance")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger BalanceValue { get; init; } + + /// + /// The merkle proof. + /// + [JsonPropertyName("merkle_proof")] + public string MerkleProof { get; init; } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/QueryGlobalStateResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/QueryGlobalStateResult.cs index 3b02e40..6faaf6e 100644 --- a/Casper.Network.SDK/JsonRpc/ResultTypes/QueryGlobalStateResult.cs +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/QueryGlobalStateResult.cs @@ -12,6 +12,7 @@ public class QueryGlobalStateResult : RpcResult /// The block header if a Block hash was provided. /// [JsonPropertyName("block_header")] + [JsonConverter(typeof(BlockHeader.BlockHeaderConverter))] public BlockHeader BlockHeader { get; init; } /// diff --git a/Casper.Network.SDK/JsonRpc/ResultTypes/SpeculativeExecutionResult.cs b/Casper.Network.SDK/JsonRpc/ResultTypes/SpeculativeExecutionResult.cs index b0144ca..75efd7b 100644 --- a/Casper.Network.SDK/JsonRpc/ResultTypes/SpeculativeExecutionResult.cs +++ b/Casper.Network.SDK/JsonRpc/ResultTypes/SpeculativeExecutionResult.cs @@ -18,7 +18,7 @@ public class SpeculativeExecutionResult : RpcResult /// The result of executing the Deploy. /// [JsonPropertyName("execution_result")] - [JsonConverter(typeof(ExecutionResult.ExecutionResultConverter))] - public ExecutionResult ExecutionResult { get; init; } + [JsonConverter(typeof(ExecutionResultV1.ExecutionResultV1Converter))] + public ExecutionResultV1 ExecutionResult { get; init; } } } diff --git a/Casper.Network.SDK/JsonRpc/RpcLoggingHandler.cs b/Casper.Network.SDK/JsonRpc/RpcLoggingHandler.cs index 27bbb0e..58b730a 100644 --- a/Casper.Network.SDK/JsonRpc/RpcLoggingHandler.cs +++ b/Casper.Network.SDK/JsonRpc/RpcLoggingHandler.cs @@ -12,8 +12,13 @@ namespace Casper.Network.SDK.JsonRpc /// public class RpcLoggingHandler : DelegatingHandler { - public StreamWriter LoggerStream { get; init; } - + private readonly TextWriter _loggerStream; + public TextWriter LoggerStream + { + get => _loggerStream; + init => _loggerStream = TextWriter.Synchronized(value); + } + private volatile bool _disposed; public RpcLoggingHandler(HttpMessageHandler innerHandler) diff --git a/Casper.Network.SDK/JsonRpc/RpcMethod.cs b/Casper.Network.SDK/JsonRpc/RpcMethod.cs index 3e17c8d..ba99d5b 100644 --- a/Casper.Network.SDK/JsonRpc/RpcMethod.cs +++ b/Casper.Network.SDK/JsonRpc/RpcMethod.cs @@ -76,11 +76,11 @@ public RpcMethod(string method, string blockHash) }; } - public RpcMethod(string method, int blockHeight) + public RpcMethod(string method, ulong blockHeight) { this.Method = method; - var blockIdentifier = new Dictionary + var blockIdentifier = new Dictionary { {"Height", blockHeight} }; diff --git a/Casper.Network.SDK/NetCasperClient.cs b/Casper.Network.SDK/NetCasperClient.cs index d006453..8ee0ca8 100644 --- a/Casper.Network.SDK/NetCasperClient.cs +++ b/Casper.Network.SDK/NetCasperClient.cs @@ -3,6 +3,7 @@ using System.IO; using System.Net.Http; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Casper.Network.SDK.JsonRpc; @@ -20,6 +21,8 @@ public class NetCasperClient : ICasperClient, IDisposable private readonly RpcClient _rpcClient; + private int _nodeVersion; + /// /// Create a new instance of the Casper client for a specific node in the network determined /// by the node address. Optionally, indicate a logging handler to log the requests and responses @@ -43,6 +46,20 @@ private Task> SendRpcRequestAsync(RpcMethod return _rpcClient.SendRpcRequestAsync(method); } + private async Task GetNodeVersion() + { + if (_nodeVersion > 0) + return _nodeVersion; + + var response = await this.GetNodeStatus(); + var result = response.Parse(); + _nodeVersion = result.BuildVersion.StartsWith("2") + ? 2 + : 1; + + return _nodeVersion; + } + /// /// Request the state root hash at a given Block. /// @@ -60,7 +77,7 @@ public async Task GetStateRootHash(string blockHash = null) /// /// Block height for which the state root is queried. /// - public async Task GetStateRootHash(int blockHeight) + public async Task GetStateRootHash(ulong blockHeight) { var method = new GetStateRootHash(blockHeight); var rpcResponse = await SendRpcRequestAsync(method); @@ -92,18 +109,46 @@ public async Task> GetNodePeers() /// public async Task> GetAuctionInfo(string blockHash = null) { - var method = new GetAuctionInfo(blockHash); - return await SendRpcRequestAsync(method); + var nodeVersion = await GetNodeVersion(); + RpcMethod method = null; + + if (nodeVersion == 1) + method = new GetAuctionInfo(blockHash); + else if (blockHash == null) + method = new GetAuctionInfoV2(blockHash); + else + { + var getBlockResponse = await GetBlock(blockHash); + if (getBlockResponse.Parse().Block.Version == 2) + method = new GetAuctionInfoV2(blockHash); + else + method = new GetAuctionInfo(blockHash); + } + + return await SendRpcRequestAsync(method); } /// /// Request the bids and validators at a given block. /// /// Block height for which the auction info is queried. - public async Task> GetAuctionInfo(int blockHeight) - { - var method = new GetAuctionInfo(blockHeight); - return await SendRpcRequestAsync(method); + public async Task> GetAuctionInfo(ulong blockHeight) + { + var nodeVersion = await GetNodeVersion(); + RpcMethod method = null; + + if (nodeVersion == 1) + method = new GetAuctionInfo(blockHeight); + else + { + var getBlockResponse = await GetBlock(blockHeight); + if (getBlockResponse.Parse().Block.Version == 2) + method = new GetAuctionInfoV2(blockHeight); + else + method = new GetAuctionInfo(blockHeight); + } + + return await SendRpcRequestAsync(method); } /// @@ -148,7 +193,7 @@ public async Task> GetAccountInfo(string publi /// /// The public key of the account. /// A block height for which the information of the account is queried. - public async Task> GetAccountInfo(PublicKey publicKey, int blockHeight) + public async Task> GetAccountInfo(PublicKey publicKey, ulong blockHeight) { var method = new GetAccountInfo(publicKey, blockHeight); return await SendRpcRequestAsync(method); @@ -159,7 +204,7 @@ public async Task> GetAccountInfo(PublicKey pu /// /// The account hash of the account. /// A block height for which the information of the account is queried. - public async Task> GetAccountInfo(AccountHashKey accountHash, int blockHeight) + public async Task> GetAccountInfo(AccountHashKey accountHash, ulong blockHeight) { var method = new GetAccountInfo(accountHash, blockHeight); return await SendRpcRequestAsync(method); @@ -171,12 +216,84 @@ public async Task> GetAccountInfo(AccountHashK /// The public key of the account formatted as an hex-string. /// A block height for which the information of the account is queried. [Obsolete("For Casper node v1.5.5 or newer use the new method signature with PublicKey or AccountHashKey, ", false)] - public async Task> GetAccountInfo(string publicKey, int blockHeight) + public async Task> GetAccountInfo(string publicKey, ulong blockHeight) { var method = new GetAccountInfo(publicKey, blockHeight); return await SendRpcRequestAsync(method); } + /// + /// Returns an AddressableEntity or a legacy Accountfrom the network for a Block from the network + /// + /// A PublicKey, an AccoountHashKey, or an AddressableEntityKey + /// A block hash for which the information of the entity is queried. Null for most recent information. + public async Task> GetEntity(IEntityIdentifier entityIdentifier, string blockHash = null) + { + var method = new GetEntity(entityIdentifier, blockHash != null ? new BlockIdentifier(blockHash) : null); + return await SendRpcRequestAsync(method); + } + + /// + /// Returns an AddressableEntity or a legacy Accountfrom the network for a Block from the network + /// + /// A PublicKey, an AccoountHashKey, or an AddressableEntityKey + /// A block height for which the information of the entity is queried.. + public async Task> GetEntity(IEntityIdentifier entityIdentifier, ulong blockHeight) + { + var method = new GetEntity(entityIdentifier, new BlockIdentifier(blockHeight)); + return await SendRpcRequestAsync(method); + } + + /// + /// Returns an AddressableEntity or a legacy Accountfrom the network for a Block from the network + /// + /// The entity address to get information of. + /// A block hash for which the information of the entity is queried. Null for most recent information. + public async Task> GetEntity(string entityAddr, string blockHash = null) + { + var method = new GetEntity(entityAddr, blockHash != null ? new BlockIdentifier(blockHash) : null); + return await SendRpcRequestAsync(method); + } + + /// + /// Returns an AddressableEntity or a legacy Accountfrom the network for a Block from the network + /// + /// The entity address to get information of. + /// A block height for which the information of the entity is queried.. + public async Task> GetEntity(string entityAddr, ulong blockHeight) + { + var method = new GetEntity(entityAddr, new BlockIdentifier(blockHeight)); + return await SendRpcRequestAsync(method); + } + + /// + /// Returns a Package from the network + /// + /// The entity address to get information of. + /// A block hash for which the information of the entity is queried. Null for most recent information. + public async Task> GetPackage(string packageHash, string blockHash = null) + { + var method = new GetPackage(packageHash.StartsWith("package-") + ? PackageIdentifier.FromPackageAddr(packageHash) + : PackageIdentifier.FromContractPackageHash(packageHash), + blockHash != null ? new BlockIdentifier(blockHash) : null); + return await SendRpcRequestAsync(method); + } + + /// + /// Returns a Package from the network + /// + /// The package address or contract package hash to get information of. + /// A block height for which the information of the package is queried. + public async Task> GetPackage(string packageHash, ulong blockHeight) + { + var method = new GetPackage(packageHash.StartsWith("package-") + ? PackageIdentifier.FromPackageAddr(packageHash) + : PackageIdentifier.FromContractPackageHash(packageHash), + new BlockIdentifier(blockHeight)); + return await SendRpcRequestAsync(method); + } + /// /// Request a stored value from the network. This RPC is deprecated, use `QueryGlobalState` instead. /// @@ -201,7 +318,7 @@ public async Task> QueryState(string keyHash, ListThe global state key formatted as a string to query the value from the network. /// Height of the block to check the stored value in. /// The path components starting from the key as base (use '/' as separator). - public async Task> QueryGlobalState(string key, int height, + public async Task> QueryGlobalState(string key, ulong height, string path = null) { var method = new QueryGlobalState(key, StateIdentifier.WithBlockHeight(height), path?.Split(new char[] {'/'})); @@ -265,128 +382,83 @@ public async Task> QueryGlobalStateWithBlock /// /// Purse URef formatted as a string. /// Hash of the state root. Null to get latest available. - public async Task> GetAccountBalance(string purseURef, - string stateRootHash = null) - { - if (!purseURef.StartsWith("uref-")) - { - var response = await GetAccountInfo(purseURef); - purseURef = response.Result.GetProperty("account") - .GetProperty("main_purse").GetString(); - } - - var uref = new URef(purseURef); - - var method = new GetBalance(uref, StateIdentifier.WithStateRootHash(stateRootHash)); - return await SendRpcRequestAsync(method); - } - - /// - /// Request a purse's balance from the network. - /// - /// Purse URef key. - /// Hash of the state root. Null to get latest available. - public async Task> GetAccountBalance(URef purseURef, + public async Task> GetBalance(string purseURef, string stateRootHash = null) { - var method = new GetBalance(purseURef, StateIdentifier.WithStateRootHash(stateRootHash)); + var method = new GetBalance(purseURef, stateRootHash); return await SendRpcRequestAsync(method); } /// - /// Request the balance information of an account given its account hash key. + /// Request the balance information from a PublicKey, AccountHashKey, URef or EntityAddr. /// - /// The account hash of the account to request the balance. - /// Hash of the state root. Null to get latest available. - public async Task> GetAccountBalance(AccountHashKey accountHash, - string stateRootHash = null) - { - var method = new GetBalance(accountHash, StateIdentifier.WithStateRootHash(stateRootHash)); - return await SendRpcRequestAsync(method); - } - - /// - /// Request the balance information of an account given its public key. - /// - /// The public key of the account to request the balance. - /// Hash of the state root. Null to get latest available. - public async Task> GetAccountBalance(PublicKey publicKey, - string stateRootHash = null) - { - var method = new GetBalance(publicKey, StateIdentifier.WithStateRootHash(stateRootHash)); - return await SendRpcRequestAsync(method); - } - - /// - /// Request a purse's balance from the network. - /// - /// Purse URef key. + /// A PublicKey, AccountHashKey, URef or EntityAddr to identify a purse. /// Hash of the block. Null to get latest available. - public async Task> GetAccountBalanceWithBlockHash(URef purseURef, + public async Task> QueryBalance(IPurseIdentifier purseIdentifier, string blockHash = null) { - var method = new GetBalance(purseURef, StateIdentifier.WithBlockHash(blockHash)); - return await SendRpcRequestAsync(method); + var method = new QueryBalance(purseIdentifier, blockHash != null ? StateIdentifier.WithBlockHash(blockHash) : null); + return await SendRpcRequestAsync(method); } /// - /// Request the balance information of an account given its account hash key. + /// Request the balance information from a PublicKey, AccountHashKey, URef or EntityAddr. /// - /// The account hash of the account to request the balance. - /// Hash of the block. Null to get latest available. - public async Task> GetAccountBalanceWithBlockHash(AccountHashKey accountHash, - string blockHash = null) + /// A PublicKey, AccountHashKey, URef or EntityAddr to identify a purse. + /// Height of the block. + public async Task> QueryBalance(IPurseIdentifier purseIdentifier, + ulong blockHeight) { - var method = new GetBalance(accountHash, StateIdentifier.WithBlockHash(blockHash)); - return await SendRpcRequestAsync(method); + var method = new QueryBalance(purseIdentifier, StateIdentifier.WithBlockHeight(blockHeight)); + return await SendRpcRequestAsync(method); } /// - /// Request the balance information of an account given its public key. + /// Request the balance information from a PublicKey, AccountHashKey, URef or EntityAddr. /// - /// The public key of the account to request the balance. - /// Hash of the block. Null to get latest available. - public async Task> GetAccountBalanceWithBlockHash(PublicKey publicKey, - string blockHash = null) + /// A PublicKey, AccountHashKey, URef or EntityAddr to identify a purse. + /// the state root hash. + public async Task> QueryBalanceWithStateRootHash(IPurseIdentifier purseIdentifier, + string stateRootHash) { - var method = new GetBalance(publicKey, StateIdentifier.WithBlockHash(blockHash)); - return await SendRpcRequestAsync(method); + var method = new QueryBalance(purseIdentifier, StateIdentifier.WithStateRootHash(stateRootHash)); + return await SendRpcRequestAsync(method); } /// - /// Request a purse's balance from the network. + /// Queries the balance information including total, available, and holds. /// - /// Purse URef key. - /// Height of the block. - public async Task> GetAccountBalance(URef purseURef, - int blockHeight) + /// A PublicKey, AccountHashKey, URef or EntityAddr to identify a purse. + /// Hash of the block. Null to get latest available. + public async Task> QueryBalanceDetails(IPurseIdentifier purseIdentifier, + string blockHash = null) { - var method = new GetBalance(purseURef, StateIdentifier.WithBlockHeight(blockHeight)); - return await SendRpcRequestAsync(method); + var method = new QueryBalanceDetails(purseIdentifier, blockHash != null ? StateIdentifier.WithBlockHash(blockHash) : null); + return await SendRpcRequestAsync(method); } /// - /// Request the balance information of an account given its account hash key. + /// Queries the balance information including total, available, and holds. /// - /// The account hash of the account to request the balance. + /// A PublicKey, AccountHashKey, URef or EntityAddr to identify a purse. /// Height of the block. - public async Task> GetAccountBalance(AccountHashKey accountHash, - int blockHeight) + public async Task> QueryBalanceDetails(IPurseIdentifier purseIdentifier, + ulong blockHeight) { - var method = new GetBalance(accountHash, StateIdentifier.WithBlockHeight(blockHeight)); - return await SendRpcRequestAsync(method); + var method = new QueryBalanceDetails(purseIdentifier, StateIdentifier.WithBlockHeight(blockHeight)); + return await SendRpcRequestAsync(method); } /// - /// Request the balance information of an account given its public key. + /// Queries the balance information including total, available, and holds. /// - /// The public key of the account to request the balance. - /// Height of the block. - public async Task> GetAccountBalance(PublicKey publicKey, - int blockHeight) + /// A PublicKey, AccountHashKey, URef or EntityAddr to identify a purse. + /// the state root hash. + public async Task> QueryBalanceDetailsWithStateRootHash(IPurseIdentifier purseIdentifier, + string stateRootHash) { - var method = new GetBalance(publicKey, StateIdentifier.WithBlockHeight(blockHeight)); - return await SendRpcRequestAsync(method); + var method = new QueryBalanceDetails(purseIdentifier, StateIdentifier.WithStateRootHash(stateRootHash)); + return await SendRpcRequestAsync(method); } /// @@ -402,7 +474,6 @@ public async Task> PutDeploy(Deploy deploy) var method = new PutDeploy(deploy); return await SendRpcRequestAsync(method); } - /// /// Request a Deploy object from the network by the deploy hash. @@ -440,14 +511,139 @@ public async Task> GetDeploy(string deployHash, while (!cancellationToken.IsCancellationRequested) { var response = await SendRpcRequestAsync(method); + if (!cancellationToken.CanBeCanceled) + return response; + + // Casper >= v2.0.0 processed deploy contains execution_info with data + if(response.Result.TryGetProperty("execution_info", out var executionInfo) && + executionInfo.ValueKind != JsonValueKind.Null) + return response; + + // Casper < v2.0.0 processed deploy contains execution_results with data + if(response.Result.TryGetProperty("execution_results", out var executionResults) && + executionResults.ValueKind == JsonValueKind.Array && + executionResults.GetArrayLength() > 0) + return response; + + await Task.Delay(4000); + } + + throw new TaskCanceledException("GetDeploy operation canceled"); + } + + /// + /// Send a Transaction to the network for its execution. + /// + /// The transaction object. + /// Throws an exception if the transaction is not signed. + public async Task> PutTransaction(TransactionV1 transaction) + { + if (transaction.Approvals.Count == 0) + throw new Exception("Sign the transaction before sending it to the network."); + + var method = new PutTransaction(transaction); + return await SendRpcRequestAsync(method); + } + + /// + /// Send a Transaction to the network for its execution. + /// + /// The transaction object. + /// Throws an exception if the transaction is not signed. + public async Task> PutTransaction(Transaction transaction) + { + RpcMethod method; + + if (transaction.Approvals.Count == 0) + throw new Exception("Sign the transaction before sending it to the network."); + + if(transaction.Version == TransactionVersion.Deploy) + method = new PutTransaction((Deploy)transaction); + else + method = new PutTransaction((TransactionV1)transaction); + return await SendRpcRequestAsync(method); + } + + /// + /// Request a Transaction object from the network by the transaction (or deploy) hash. + /// When a cancellation token is included this method waits until the transaction is + /// executed, i.e. until the transaction contains the execution result information. + /// + /// An v1 transaction hash or a deploy hash + /// Whether to return the transaction with the finalized approvals + /// substituted. If `false` or omitted, returns the transaction with the approvals that were originally + /// received by the node. + /// A CancellationToken. Do not include this parameter to return + /// with the first transaction object returned by the network, even it's not executed. + /// The token has cancelled the operation before the deploy has been executed. + public async Task> GetTransaction(TransactionHash transactionHash, + bool finalizedApprovals, + CancellationToken cancellationToken = default(CancellationToken)) + { + var method = new GetTransaction(transactionHash, finalizedApprovals); + + while (!cancellationToken.IsCancellationRequested) + { + var response = await SendRpcRequestAsync(method); if (!cancellationToken.CanBeCanceled || - response.Result.GetProperty("execution_results").GetArrayLength() > 0) + response.Result.GetProperty("execution_info").ValueKind != JsonValueKind.Null) return response; - await Task.Delay(10000); + await Task.Delay(4000); } throw new TaskCanceledException("GetDeploy operation canceled"); } + + /// + /// Request a Transaction object from the network by the transaction (or deploy) hash. + /// When a cancellation token is included this method waits until the transaction is + /// executed, i.e. until the transaction contains the execution result information. + /// + /// An v1 transaction hash or a deploy hash + /// A CancellationToken. Do not include this parameter to return + /// with the first transaction object returned by the network, even it's not executed. + /// The token has cancelled the operation before the deploy has been executed. + public async Task> GetTransaction(TransactionHash transactionHash, + CancellationToken cancellationToken = default(CancellationToken)) + { + return await GetTransaction(transactionHash, false, cancellationToken); + } + + /// + /// Request a Transaction object from the network by the transaction hash. + /// When a cancellation token is included this method waits until the transaction is + /// executed, i.e. until the transaction contains the execution result information. + /// + /// A TransactionV1 hash + /// Whether to return the transaction with the finalized approvals + /// substituted. If `false` or omitted, returns the transaction with the approvals that were originally + /// received by the node. + /// A CancellationToken. Do not include this parameter to return + /// with the first transaction object returned by the network, even it's not executed. + /// The token has cancelled the operation before the deploy has been executed. + public async Task> GetTransaction(string transactionV1Hash, + bool finalizedApprovals, + CancellationToken cancellationToken = default(CancellationToken)) + { + return await this.GetTransaction(new TransactionHash { Version1 = transactionV1Hash }, finalizedApprovals, + cancellationToken); + } + + /// + /// Request a Transaction object from the network by the transaction hash. + /// When a cancellation token is included this method waits until the transaction is + /// executed, i.e. until the transaction contains the execution result information. + /// + /// A TransactionV1 hash + /// A CancellationToken. Do not include this parameter to return + /// with the first transaction object returned by the network, even it's not executed. + /// The token has cancelled the operation before the deploy has been executed. + public async Task> GetTransaction(string transactionV1Hash, + CancellationToken cancellationToken = default(CancellationToken)) + { + return await this.GetTransaction(new TransactionHash { Version1 = transactionV1Hash }, false, + cancellationToken); + } /// /// Retrieves a Block from the network by its hash. @@ -463,7 +659,7 @@ public async Task> GetBlock(string blockHash = null) /// Request a Block from the network by its height number. /// /// Height of the block to retrieve. - public async Task> GetBlock(int blockHeight) + public async Task> GetBlock(ulong blockHeight) { var method = new GetBlock(blockHeight); return await SendRpcRequestAsync(method); @@ -483,7 +679,7 @@ public async Task> GetBlockTransfers(string /// Request all transfers for a Block by its height number. /// /// Height of the block to retrieve the transfers from. - public async Task> GetBlockTransfers(int blockHeight) + public async Task> GetBlockTransfers(ulong blockHeight) { var method = new GetBlockTransfers(blockHeight); return await SendRpcRequestAsync(method); @@ -505,7 +701,7 @@ public async Task> GetEraInfoBySwitch /// For a non-switch block this method returns an empty response. /// /// Block height of a switch block. - public async Task> GetEraInfoBySwitchBlock(int blockHeight) + public async Task> GetEraInfoBySwitchBlock(ulong blockHeight) { var method = new GetEraInfoBySwitchBlock(blockHeight); return await SendRpcRequestAsync(method); @@ -525,7 +721,7 @@ public async Task> GetEraSummary(string blockHa /// Request current Era Info from the network given a block hash /// /// Block height. - public async Task> GetEraSummary(int blockHeight) + public async Task> GetEraSummary(ulong blockHeight) { var method = new GetEraSummary(blockHeight); return await SendRpcRequestAsync(method); @@ -604,6 +800,75 @@ public async Task> GetValidatorChanges() return await SendRpcRequestAsync(method); } + /// + /// Returns the reward for a given era and a validator + /// + /// The public key of the validator. + /// Hash of the block to retrieve the rewards from. Null for the most recent era + public async Task> GetValidatorReward(PublicKey validator, string blockHash = null) + { + var method = new GetValidatorReward(validator, blockHash != null ? new BlockIdentifier(blockHash) : null); + return await SendRpcRequestAsync(method); + } + + /// + /// Returns the reward for a given era and a validator + /// + /// The public key of the validator. + /// Height of the block to retrieve the rewards from. + public async Task> GetValidatorReward(PublicKey validator, ulong blockHeight) + { + var method = new GetValidatorReward(validator, new BlockIdentifier(blockHeight)); + return await SendRpcRequestAsync(method); + } + + /// + /// Returns the reward for a given era and a validator + /// + /// The public key of the validator. + /// Id of the Era to retrieve the rewards from. + public async Task> GetValidatorRewardWithEraId(PublicKey validator, ulong eraId) + { + var method = new GetValidatorReward(validator, eraId); + return await SendRpcRequestAsync(method); + } + + /// + /// Returns the reward for a given era and a delegator + /// + /// The public key of the validator. + /// The public key of the delegator. + /// Hash of the block to retrieve the rewards from. Null for the most recent era + public async Task> GetDelegatorReward(PublicKey validator, PublicKey delegator, string blockHash = null) + { + var method = new GetDelegatorReward(validator, delegator, blockHash != null ? new BlockIdentifier(blockHash) : null); + return await SendRpcRequestAsync(method); + } + + /// + /// Returns the reward for a given era and a delegator + /// + /// The public key of the validator. + /// The public key of the delegator. + /// Height of the block to retrieve the rewards from. + public async Task> GetDelegatorReward(PublicKey validator, PublicKey delegator, ulong blockHeight) + { + var method = new GetDelegatorReward(validator, delegator, new BlockIdentifier(blockHeight)); + return await SendRpcRequestAsync(method); + } + + /// + /// Returns the reward for a given era and a delegator + /// + /// The public key of the validator. + /// The public key of the delegator. + /// Id of the Era to retrieve the rewards from. + public async Task> GetDelegatorRewardWithEraId(PublicKey validator, PublicKey delegator, ulong eraId) + { + var method = new GetDelegatorReward(validator, delegator, eraId); + return await SendRpcRequestAsync(method); + } + /// /// Request the RPC Json schema to the network node. /// @@ -701,6 +966,17 @@ public static async Task GetNodeMetrics(string host, int port) return await GetNodeMetrics(uriBuilder.Uri.ToString()); } + /// + /// Returns the validator bid. + /// + /// The public key of the validator. + /// Hash of the block to retrieve the rewards from. Null for the most recent era + public async Task> GetValidatorBid(PublicKey validator, string blockHash = null) + { + var bidAddr = BidAddrKey.FromValidatorKey(new AccountHashKey(validator)); + return await QueryGlobalStateWithBlockHash(bidAddr, blockHash); + } + public void Dispose() { Dispose(true); diff --git a/Casper.Network.SDK/SSE/BlockAdded.cs b/Casper.Network.SDK/SSE/BlockAdded.cs index ae96e0e..2eefdcf 100644 --- a/Casper.Network.SDK/SSE/BlockAdded.cs +++ b/Casper.Network.SDK/SSE/BlockAdded.cs @@ -1,3 +1,5 @@ +using System; +using System.Text.Json; using System.Text.Json.Serialization; using Casper.Network.SDK.Types; @@ -11,11 +13,53 @@ public class BlockAdded /// /// The Block hash. /// - [JsonPropertyName("block_hash")] public string BlockHash { get; init; } + [JsonPropertyName("block_hash")] + public string BlockHash { get; init; } /// /// The Block data. /// - [JsonPropertyName("block")] public Block Block { get; init; } + [JsonPropertyName("block")] + [JsonConverter(typeof(BlockConverter))] + public Block Block { get; init; } + + public class BlockConverter : JsonConverter + { + public override Block Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + try + { + using (JsonDocument document = JsonDocument.ParseValue(ref reader)) + { + if (document.RootElement.TryGetProperty("header", out var header)) + { + var blockV1 = JsonSerializer.Deserialize(document.RootElement.GetRawText()); + return (Block)blockV1; + } + + var blockSerializerOptions = new JsonSerializerOptions(options); + blockSerializerOptions.Converters.Add(new Block.BlockConverter()); + var block = JsonSerializer.Deserialize(document.RootElement.GetRawText(), blockSerializerOptions); + return block; + } + } + catch (Exception e) + { + throw new JsonException(e.Message); + } + } + + public override void Write( + Utf8JsonWriter writer, + Block block, + JsonSerializerOptions options) + { + throw new JsonException( + "Cannot serialize a block with this converter. Use Block.BlockConverter instead"); + } + } } } \ No newline at end of file diff --git a/Casper.Network.SDK/SSE/DeployAccepted.cs b/Casper.Network.SDK/SSE/DeployAccepted.cs index 009ab9b..d3458f6 100644 --- a/Casper.Network.SDK/SSE/DeployAccepted.cs +++ b/Casper.Network.SDK/SSE/DeployAccepted.cs @@ -12,7 +12,7 @@ public class DeployAccepted : Deploy // This is an alias of Deploy to be used in SSE. // public DeployAccepted(string hash, DeployHeader header, ExecutableDeployItem payment, - ExecutableDeployItem session, List approvals) + ExecutableDeployItem session, List approvals) : base(hash, header, payment, session, approvals) { } diff --git a/Casper.Network.SDK/SSE/DeployProcessed.cs b/Casper.Network.SDK/SSE/DeployProcessed.cs index 5e7185f..a1a09a6 100644 --- a/Casper.Network.SDK/SSE/DeployProcessed.cs +++ b/Casper.Network.SDK/SSE/DeployProcessed.cs @@ -52,7 +52,7 @@ public class DeployProcessed /// The result of executing a this Deploy. /// [JsonPropertyName("execution_result")] - [JsonConverter(typeof(ExecutionResult.ExecutionResultConverter))] - public ExecutionResult ExecutionResult { get; init; } + [JsonConverter(typeof(ExecutionResultV1.ExecutionResultV1Converter))] + public ExecutionResultV1 ExecutionResult { get; init; } } } \ No newline at end of file diff --git a/Casper.Network.SDK/SSE/FinalitySignature.cs b/Casper.Network.SDK/SSE/FinalitySignature.cs index 4748f2c..1d944c5 100644 --- a/Casper.Network.SDK/SSE/FinalitySignature.cs +++ b/Casper.Network.SDK/SSE/FinalitySignature.cs @@ -1,12 +1,14 @@ +using System; +using System.Text.Json; using System.Text.Json.Serialization; using Casper.Network.SDK.Types; namespace Casper.Network.SDK.SSE { /// - /// Server-Side event sent after each block finalization + /// A validator's signature of a block, confirming it is finalized. Produced in Casper v1.x /// - public class FinalitySignature + public class FinalitySignatureV1 { /// /// The block hash @@ -34,4 +36,199 @@ public class FinalitySignature [JsonConverter(typeof(Signature.SignatureConverter))] public Signature Signature { get; init; } } -} \ No newline at end of file + + + internal class FinalitySignatureV2 : FinalitySignatureV1 + { + /// + /// The block height + /// + [JsonPropertyName("block_height")] + public ulong BlockHeight { get; init; } + + /// + /// The hash of the chain name of the associated block. + /// + [JsonPropertyName("chain_name_hash")] + public string ChainNameHash { get; init; } + } + + internal class FinalitySignatureCompat + { + [JsonPropertyName("V1")] + public FinalitySignatureV1 Version1 { get; init; } + + [JsonPropertyName("V2")] + public FinalitySignatureV2 Version2 { get; init; } + + [JsonPropertyName("block_hash")] + public string BlockHash { get; init; } + + [JsonPropertyName("era_id")] + public ulong EraId { get; init; } + + [JsonPropertyName("public_key")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey PublicKey { get; init; } + + [JsonPropertyName("signature")] + [JsonConverter(typeof(Signature.SignatureConverter))] + public Signature Signature { get; init; } + } + + /// + /// A validator's signature of a block, confirming it is finalized. + /// + [JsonConverter(typeof(FinalitySignatureConverter))] + public class FinalitySignature + { + protected int _version; + + /// + /// Returns the version of the finality signature. + /// + public int Version + { + get { return _version; } + } + + protected FinalitySignatureV1 _finalitySignatureV1; + + public static explicit operator FinalitySignatureV1(FinalitySignature finalitySignature) + { + if(finalitySignature._version == 1) + return finalitySignature._finalitySignatureV1; + + throw new InvalidCastException("Version2 FinalitySignature cannot be converted to Version1"); + } + + public static explicit operator FinalitySignature(FinalitySignatureV1 finalitySignature) + { + return new FinalitySignature + { + _version = 1, + _finalitySignatureV1 = finalitySignature, + BlockHash = finalitySignature.BlockHash, + BlockHeight = 0, + EraId = finalitySignature.EraId, + PublicKey= finalitySignature.PublicKey, + Signature = finalitySignature.Signature, + ChainNameHash = null, + }; + } + + /// + /// The block hash + /// + [JsonPropertyName("block_hash")] + public string BlockHash { get; init; } + + /// + /// The block height + /// + [JsonPropertyName("block_height")] + public ulong BlockHeight { get; init; } + + /// + /// The hash of the chain name of the associated block. + /// + [JsonPropertyName("chain_name_hash")] + public string ChainNameHash { get; init; } + + /// + /// The block era id. + /// + [JsonPropertyName("era_id")] + public ulong EraId { get; init; } + + /// + /// Validator public key + /// + [JsonPropertyName("public_key")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey PublicKey { get; init; } + + /// + /// Validator signature + /// + [JsonPropertyName("signature")] + [JsonConverter(typeof(Signature.SignatureConverter))] + public Signature Signature { get; init; } + + /// + /// Json converter class to serialize/deserialize a FinalitySignature to/from Json + /// + public class FinalitySignatureConverter : JsonConverter + { + public override FinalitySignature Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + try + { + var fsCompat = JsonSerializer.Deserialize(ref reader, options); + if (fsCompat.BlockHash != null) + { + var v1 = new FinalitySignatureV1() + { + BlockHash = fsCompat.BlockHash, + EraId = fsCompat.EraId, + PublicKey = fsCompat.PublicKey, + Signature = fsCompat.Signature, + + }; + return (FinalitySignature)v1; + } + if (fsCompat.Version1 != null) + { + return (FinalitySignature)fsCompat.Version1; + } + if (fsCompat.Version2 != null) + { + return new FinalitySignature + { + _version = 2, + BlockHash = fsCompat.Version2.BlockHash, + BlockHeight = fsCompat.Version2.BlockHeight, + EraId = fsCompat.Version2.EraId, + PublicKey = fsCompat.Version2.PublicKey, + Signature = fsCompat.Version2.Signature, + ChainNameHash = fsCompat.Version2.ChainNameHash, + }; + } + + throw new JsonException("Cannot deserialize FinalitySignature"); + } + catch (Exception e) + { + throw new JsonException(e.Message); + } + } + + public override void Write( + Utf8JsonWriter writer, + FinalitySignature finalitySignature, + JsonSerializerOptions options) + { + switch (finalitySignature.Version) + { + case 1: + writer.WritePropertyName("V1"); + writer.WriteStartObject(); + JsonSerializer.Serialize((FinalitySignatureV1)finalitySignature, options); + writer.WriteEndObject(); + break; + case 2: + writer.WritePropertyName("V2"); + writer.WriteStartObject(); + JsonSerializer.Serialize(finalitySignature, options); + writer.WriteEndObject(); + break; + default: + throw new JsonException($"Unexpected finality signature version {finalitySignature.Version}"); + } + } + } + } +} diff --git a/Casper.Network.SDK/SSE/ISSEClient.cs b/Casper.Network.SDK/SSE/ISSEClient.cs index 1bd562d..53fc7fa 100644 --- a/Casper.Network.SDK/SSE/ISSEClient.cs +++ b/Casper.Network.SDK/SSE/ISSEClient.cs @@ -9,10 +9,14 @@ void AddEventCallback(EventType eventType, string name, EventCallback cb, bool RemoveEventCallback(EventType eventType, string name); + int NodeVersion { get; set; } + void StartListening(); Task StopListening(); + bool IsRunning(); + void Wait(); } } diff --git a/Casper.Network.SDK/SSE/SSEvent.cs b/Casper.Network.SDK/SSE/SSEvent.cs index 0e8cbab..d7a0245 100644 --- a/Casper.Network.SDK/SSE/SSEvent.cs +++ b/Casper.Network.SDK/SSE/SSEvent.cs @@ -56,6 +56,15 @@ public T Parse() if (typeof(T) == typeof(Step)) json = this.Result.GetProperty("Step").GetRawText(); + if (typeof(T) == typeof(Transaction)) + json = this.Result.GetProperty("TransactionAccepted").GetRawText(); + + if (typeof(T) == typeof(TransactionProcessed)) + json = this.Result.GetProperty("TransactionProcessed").GetRawText(); + + if (typeof(T) == typeof(TransactionExpired)) + json = this.Result.GetProperty("TransactionExpired").GetRawText(); + if (json is null) throw new Exception($"Type '{typeof(T)} not compatible with SSE event returned object."); diff --git a/Casper.Network.SDK/SSE/ServerEventsClient.cs b/Casper.Network.SDK/SSE/ServerEventsClient.cs index f3af59c..c9ea217 100644 --- a/Casper.Network.SDK/SSE/ServerEventsClient.cs +++ b/Casper.Network.SDK/SSE/ServerEventsClient.cs @@ -24,7 +24,7 @@ public enum ChannelType /// /// Channel to subscribe to only `DeployAccepted` events. /// - Sigs + Sigs, }; /// @@ -69,7 +69,19 @@ public enum EventType /// /// Add `DeployExpired` to an event callback to catch events due to an expired Deploy. /// - DeployExpired + DeployExpired, + /// + /// Add `TransactionAccepted` to an event callback to catch events due to a new Transaction accepted by the network. + /// + TransactionAccepted, + /// + /// Add `TransactionProcessed` to an event callback to catch events due to a new Transaction processed by the network. + /// + TransactionProcessed, + /// + /// Add `TransactionExpired` to an event callback to catch events due to an expired Transaction. + /// + TransactionExpired, } internal struct EventData @@ -103,6 +115,7 @@ public class ServerEventsClient : ISSEClient protected string _host; protected int _port; + protected int _nodeVersion = 1; public ServerEventsClient() { @@ -116,7 +129,10 @@ public ServerEventsClient() {EventType.Fault, ChannelType.Main}, {EventType.Step, ChannelType.Main}, {EventType.FinalitySignature, ChannelType.Sigs}, - {EventType.DeployExpired, ChannelType.Main} + {EventType.DeployExpired, ChannelType.Main}, + {EventType.TransactionAccepted, ChannelType.Main}, + {EventType.TransactionProcessed, ChannelType.Main}, + {EventType.TransactionExpired, ChannelType.Main}, }; _runningTasks = new Dictionary>(); } @@ -126,12 +142,20 @@ public ServerEventsClient() /// /// IP or domain name of the node. /// Event stream port. - public ServerEventsClient(string host, int port) : this() + /// 2 for Casper 2.x; 1 for Casper 1.x. + public ServerEventsClient(string host, int port, int nodeVersion = 2) : this() { _host = host; _port = port; + _nodeVersion = nodeVersion; } + public int NodeVersion + { + get { return _nodeVersion; } + set { _nodeVersion = value; } + } + /// /// Adds an event callback method that is called for each subscribed event emitted by the node. /// @@ -168,6 +192,17 @@ public bool RemoveEventCallback(EventType eventType, string name) private void UpdateChannels(EventType eventType, int startFrom = int.MaxValue) { + if (_nodeVersion == 2) + { + _channels = new Dictionary() + { + { ChannelType.Main, startFrom }, + }; + return; + } + + // for _nodeVersion == 1, we need to listen to different channels + // var ch = new Dictionary(); foreach (var cb in _callbacks) { @@ -233,6 +268,8 @@ public async Task StopListening() } await Task.WhenAll(tasks); + + _runningTasks.Clear(); } /// @@ -250,6 +287,11 @@ public void Wait() Task.WhenAll(tasks).Wait(); } + public bool IsRunning() + { + return _runningTasks.Count > 0; + } + /// /// Returns an instance of an HttpClient. Derived classes can override this method to get /// the client object from an HttpClientFactory, for example. @@ -276,8 +318,9 @@ private Task ListenChannelAsync(ChannelType channelType, int? startFrom, Cancell try { var uriBuilder = new UriBuilder(new Uri(client.BaseAddress + - $"events/{channelType.ToString().ToLowerInvariant()}")); - + $"events" + + (_nodeVersion == 1 ? $"/{channelType.ToString().ToLowerInvariant()}" : ""))); + if (startFrom != null && startFrom != int.MaxValue) uriBuilder.Query = $"start_from={startFrom}"; else diff --git a/Casper.Network.SDK/SSE/Step.cs b/Casper.Network.SDK/SSE/Step.cs index 0f39c31..f11f9f3 100644 --- a/Casper.Network.SDK/SSE/Step.cs +++ b/Casper.Network.SDK/SSE/Step.cs @@ -1,10 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; using System.Text.Json.Serialization; +using Casper.Network.SDK.Converters; namespace Casper.Network.SDK.Types { + internal class StepCompat + { + /// + /// The era in which the change occurred. + /// + [JsonPropertyName("era_id")] + public ulong EraId { get; init; } + + /// + /// The resulting operations (only for Casper v1.x). + /// + [JsonPropertyName("execution_effect")] + public ExecutionEffect ExecutionEffect { get; init; } + + /// + /// The effect of executing the deploy. + /// + [JsonPropertyName("execution_effects")] + [JsonConverter(typeof(GenericListConverter))] + public List Effects { get; init; } + } + /// /// The journal of execution transforms from an Era /// + [JsonConverter(typeof(StepConverter))] public class Step { /// @@ -13,10 +41,68 @@ public class Step [JsonPropertyName("era_id")] public ulong EraId { get; init; } + /// + /// The resulting operations (only for Casper v1.x). + /// + [JsonPropertyName("operations")] + public List Operations { get; init; } + /// /// The effect of executing the deploy. /// - [JsonPropertyName("execution_effect")] - public ExecutionEffect Effect { get; init; } + [JsonPropertyName("execution_effects")] + [JsonConverter(typeof(GenericListConverter))] + public List Effects { get; init; } + + public class StepConverter : JsonConverter + { + public override Step Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + var stepCompat = JsonSerializer.Deserialize(ref reader, options); + + if (stepCompat.ExecutionEffect == null) + return new Step() + { + EraId = stepCompat.EraId, + Effects = stepCompat.Effects, + }; + + return new Step() + { + EraId = stepCompat.EraId, + Operations = stepCompat.ExecutionEffect.Operations, + Effects = stepCompat.ExecutionEffect.Transforms.Select(t => (Transform)t).ToList(), + }; + } + + public override void Write( + Utf8JsonWriter writer, + Step value, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + writer.WriteNumber("era_id", value.EraId); + if (value.Operations != null && value.Operations.Count > 0) + { + writer.WritePropertyName("operations"); + JsonSerializer.Serialize(writer, value.Operations); + } + writer.WritePropertyName("execution_effects"); + + if (value.Effects == null || value.Effects.Count == 0) + { + writer.WriteStartArray(); + writer.WriteEndArray(); + } + else + { + JsonSerializer.Serialize(writer, value.Effects); + } + writer.WriteEndObject(); + } + } } } \ No newline at end of file diff --git a/Casper.Network.SDK/SSE/TransactionExpired.cs b/Casper.Network.SDK/SSE/TransactionExpired.cs new file mode 100644 index 0000000..992988d --- /dev/null +++ b/Casper.Network.SDK/SSE/TransactionExpired.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; +using Casper.Network.SDK.Types; + +namespace Casper.Network.SDK.SSE +{ + public class TransactionExpired + { + /// + /// Versioned transaction hash. + /// + [JsonPropertyName("transaction_hash")] + public TransactionHash TransactionHash { get; init; } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/SSE/TransactionProcessed.cs b/Casper.Network.SDK/SSE/TransactionProcessed.cs new file mode 100644 index 0000000..0d1df2b --- /dev/null +++ b/Casper.Network.SDK/SSE/TransactionProcessed.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; +using Casper.Network.SDK.Converters; +using Casper.Network.SDK.Types; + +namespace Casper.Network.SDK.SSE +{ + public class TransactionProcessed + { + /// + /// Versioned transaction hash. + /// + [JsonPropertyName("transaction_hash")] + public TransactionHash TransactionHash { get; init; } + + /// + /// The address of the initiator of a transaction. + /// + [JsonPropertyName("initiator_addr")] + public InitiatorAddr InitiatorAddr { get; set; } + + /// + /// The timestamp in which the Transaction was built. + /// + [JsonPropertyName("timestamp")] + public string Timestamp { get; init; } + + /// + /// The time-to-live of the Transaction. + /// + [JsonPropertyName("ttl")] + [JsonConverter(typeof(HumanizeTTLConverter))] + public ulong Ttl { get; set; } + + /// + /// The hash of the block containing this Transaction. + /// + [JsonPropertyName("block_hash")] + public string BlockHash { get; init; } + + /// + /// Versioned execution result. + /// + [JsonPropertyName("execution_result")] + [JsonConverter(typeof(ExecutionResult.ExecutionResultConverter))] + public ExecutionResult ExecutionResult { get; init; } + + /// + /// List of messages emitted in the transaction execution + /// + [JsonPropertyName("messages")] + public List Messages { get; init; } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/Account.cs b/Casper.Network.SDK/Types/Account.cs index 461499e..8ee8c4c 100644 --- a/Casper.Network.SDK/Types/Account.cs +++ b/Casper.Network.SDK/Types/Account.cs @@ -19,6 +19,12 @@ public class ActionThresholds /// [JsonPropertyName("key_management")] public uint KeyManagement { get; init; } + + /// + /// Threshold for upgrading contracts. + /// + [JsonPropertyName("upgrade_management")] + public uint UpgradeManagement { get; init; } } /// diff --git a/Casper.Network.SDK/Types/AddressableEntity.cs b/Casper.Network.SDK/Types/AddressableEntity.cs new file mode 100644 index 0000000..be99399 --- /dev/null +++ b/Casper.Network.SDK/Types/AddressableEntity.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Casper.Network.SDK.Types +{ + public enum SystemEntityType + { + /// + /// Mint contract. + /// + Mint, + + /// + /// Handle Payment contract. + /// + HandlePayment, + + /// + /// Standard Payment contract. + /// + StandardPayment, + + /// + /// Auction contract. + /// + Auction, + } + + [JsonConverter(typeof(EntityKindConverter))] + public class EntityKind + { + /// + /// Package associated with a native contract implementation. + /// + [JsonConverter(typeof(JsonStringEnumConverter))] + public SystemEntityType? System { get; init; } + + /// + /// Package associated with an Account hash. + /// + [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] + public AccountHashKey Account { get; init; } + + /// + /// Packages associated with Wasm stored on chain. + /// + public TransactionRuntime? SmartContract { get; init; } + + /// + /// Json converter class to serialize/deserialize a Block to/from Json + /// + public class EntityKindConverter : JsonConverter + { + public override EntityKind Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.StartObject) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.PropertyName) + { + var property = reader.GetString(); + reader.Read(); + EntityKind entity = null; + switch (property) + { + case "Account": + entity = new EntityKind() + { + Account = new AccountHashKey(reader.GetString()), + }; + break; + case "SmartContract": + var tag = reader.GetString(); + entity = new EntityKind() + { + SmartContract = TransactionRuntime.FromString(tag), + }; + break; + case "System": + entity = new EntityKind() + { + System = EnumCompat.Parse(reader.GetString()), + }; + break; + } + + reader.Read(); + if (entity != null) + return entity; + } + } + + throw new JsonException("Cannot deserialize EntityKind"); + } + + public override void Write( + Utf8JsonWriter writer, + EntityKind blockHeader, + JsonSerializerOptions options) + { + throw new NotImplementedException("Write method for EntityKind not yet implemented"); + } + } + } + + /// + /// A message topic. + /// + public class MessageTopic + { + /// + /// The message topic name. + /// + [JsonPropertyName("topic_name")] + public string TopicName { get; init; } + + /// + /// The hash of the name of the message topic. + /// + [JsonPropertyName("topic_name_hash")] + public string TopicNameHash { get; init; } + } + + public class AddressableEntity + { + /// + /// Casper Platform protocol version. + /// + [JsonPropertyName("protocol_version")] + public string ProtocolVersion { get; init; } + + /// + /// The type of Package. + /// + [JsonPropertyName("entity_kind")] + public EntityKind EntityKind { get; init; } + + /// + /// The hex-encoded address of the Package. + /// + [JsonPropertyName("package_hash")] + [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] + public PackageKey Package { get; init; } + + /// + /// The hash address of the contract wasm. + /// + [JsonPropertyName("byte_code_hash")] + public string ByteCodeHash { get; init; } + + [JsonPropertyName("main_purse")] + [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] + public URef MainPurse { get; init; } + + /// + /// Set of public keys allowed to provide signatures on deploys for the package + /// + [JsonPropertyName("associated_keys")] + public List AssociatedKeys { get; init; } + + /// + /// Thresholds that have to be met when executing an action of a certain type. + /// + [JsonPropertyName("action_thresholds")] + public ActionThresholds ActionThresholds { get; init; } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/DeployApproval.cs b/Casper.Network.SDK/Types/Approval.cs similarity index 85% rename from Casper.Network.SDK/Types/DeployApproval.cs rename to Casper.Network.SDK/Types/Approval.cs index ba1699b..d2727e8 100644 --- a/Casper.Network.SDK/Types/DeployApproval.cs +++ b/Casper.Network.SDK/Types/Approval.cs @@ -1,15 +1,14 @@ using System.Text.Json.Serialization; -using Casper.Network.SDK.Converters; namespace Casper.Network.SDK.Types { /// /// Signature and Public Key of the signer. /// - public class DeployApproval + public class Approval { /// - /// Signature of a deploy. + /// Signature of a deploy or transaction. /// [JsonPropertyName("signature")] [JsonConverter(typeof(Signature.SignatureConverter))] diff --git a/Casper.Network.SDK/Types/AuctionState.cs b/Casper.Network.SDK/Types/AuctionState.cs index d537dfe..1cd498c 100644 --- a/Casper.Network.SDK/Types/AuctionState.cs +++ b/Casper.Network.SDK/Types/AuctionState.cs @@ -13,9 +13,9 @@ public class AuctionState /// All bids contained within a vector. /// [JsonPropertyName("bids")] - [JsonConverter(typeof(BidsListConverter))] - public List Bids { get; init; } - + [JsonConverter(typeof(BidKindsListConverter))] + public List Bids { get; init; } + /// /// Block height. /// diff --git a/Casper.Network.SDK/Types/Bid.cs b/Casper.Network.SDK/Types/Bid.cs index 09b244a..67d8b6f 100644 --- a/Casper.Network.SDK/Types/Bid.cs +++ b/Casper.Network.SDK/Types/Bid.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Numerics; using System.Text.Json.Serialization; using Casper.Network.SDK.Converters; @@ -6,7 +7,7 @@ namespace Casper.Network.SDK.Types { /// - /// An entry in a founding validator map representing a bid. + /// An entry in the validator map. /// public class Bid { @@ -24,13 +25,10 @@ public class Bid [JsonPropertyName("delegation_rate")] public uint DelegationRate { get; init; } - /// - /// The delegators. - /// [JsonPropertyName("delegators")] - [JsonConverter(typeof(GenericListConverter))] + [JsonConverter(typeof(Delegator.PublicKeyAndDelegatorListConverter))] public List Delegators { get; init; } - + /// /// `true` if validator has been "evicted" /// @@ -45,7 +43,7 @@ public class Bid public BigInteger StakedAmount { get; init; } /// - /// Validator public key + /// Validator public key. /// [JsonPropertyName("validator_public_key")] [JsonConverter(typeof(PublicKey.PublicKeyConverter))] diff --git a/Casper.Network.SDK/Types/BidKind.cs b/Casper.Network.SDK/Types/BidKind.cs new file mode 100644 index 0000000..3ae449c --- /dev/null +++ b/Casper.Network.SDK/Types/BidKind.cs @@ -0,0 +1,170 @@ +using System.Collections.Generic; +using System.Numerics; +using System.Text.Json.Serialization; +using Casper.Network.SDK.Converters; + +namespace Casper.Network.SDK.Types +{ + /// + /// A bridge record pointing to a new `ValidatorBid` after the public key was changed. + /// + public class Bridge + { + /// + /// Previous validator public key associated with the bid. + /// + [JsonPropertyName("old_validator_public_key")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey OldValidator { get; init; } + + /// + /// New validator public key associated with the bid. + /// + [JsonPropertyName("new_validator_public_key")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey NewValidator { get; init; } + + /// + /// Era when bridge record was created. + /// + [JsonPropertyName("era_id")] + public ulong EraId { get; init; } + + } + /// + /// Validator credit record. + /// + public class ValidatorCredit + { + /// + /// The credit amount. + /// + [JsonPropertyName("amount")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger Amount { get; init; } + + /// + /// The era id the credit was created. + /// + [JsonPropertyName("era_id")] + public ulong EraId { get; init; } + + /// + /// Validator public key. + /// + [JsonPropertyName("validator_public_key")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey Validator { get; init; } + } + + public class UnbondKind + { + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey Validator { get; init; } + + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey DelegatedPublicKey { get; init; } + + public string DelegatedPurse { get; init; } + } + + public class UnbondEra + { + /// + /// The purse that was used for bonding. + /// + [JsonPropertyName("bonding_purse")] + [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] + public URef BondingPurse { get; init; } + + /// + /// Era in which this unbonding request was created. + /// + [JsonPropertyName("era_of_creation")] + public ulong EraOfCreation { get; init; } + + /// + /// Unbonding Amount + /// + [JsonPropertyName("amount")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger Amount { get; init; } + + /// + /// The validator public key to re-delegate to. + /// + [JsonPropertyName("new_validator")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey NewValidator { get; init; } + } + + + public class Unbond + { + /// + /// Validator public key. + /// + [JsonPropertyName("validator_public_key")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey Validator { get; init; } + + /// + /// Unbond kind. + /// + [JsonPropertyName("unbond_kind")] + public UnbondKind Kind { get; init; } + + [JsonPropertyName("eras")] + public List Eras { get; init; } + } + + /// + /// Auction bid variants. + /// + public class BidKind + { + /// + /// A unified record indexed on validator data, with an embedded collection of all delegator bids assigned + /// to that validator. The Unified variant is for legacy retrograde support, new instances will not be + /// created going forward. + /// + [JsonPropertyName("Unified")] + public Bid Unified { get; init; } + + /// + /// A bid record containing only validator data. + /// + [JsonPropertyName("Validator")] + public ValidatorBid Validator { get; init; } + + /// + /// A bid record containing only delegator data. + /// + [JsonPropertyName("Delegator")] + public DelegatorBid Delegator { get; init; } + + /// + /// A bridge record pointing to a new `ValidatorBid` after the public key was changed. + /// + [JsonPropertyName("Bridge")] + public Bridge Bridge { get; init; } + + /// + /// Credited amount. + /// + [JsonPropertyName("Credit")] + public ValidatorCredit Credit { get; init; } + + /// + /// Represents a validator reserving a slot for specific delegator" + /// + [JsonPropertyName("Reservation")] + public Reservation Reservation { get; init; } + + /// + /// A bid record containing Unbond information + /// + [JsonPropertyName("Unbond")] + public Unbond Unbond { get; init; } + } +} diff --git a/Casper.Network.SDK/Types/Block.cs b/Casper.Network.SDK/Types/Block.cs index 0233bfe..b880043 100644 --- a/Casper.Network.SDK/Types/Block.cs +++ b/Casper.Network.SDK/Types/Block.cs @@ -1,75 +1,303 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; namespace Casper.Network.SDK.Types { - /// + /// /// A block header /// - public class BlockHeader + public class BlockHeaderV1 { /// - /// Accumulated seed. + /// A seed needed for initializing a future era. /// [JsonPropertyName("accumulated_seed")] public string AccumulatedSeed { get; init; } /// - /// The body hash. + /// The hash of the block's body. /// [JsonPropertyName("body_hash")] public string BodyHash { get; init; } /// - /// The era end. + /// The `EraEnd` of a block if it is a switch block. /// [JsonPropertyName("era_end")] - public EraEnd EraEnd { get; init; } + public EraEndV1 EraEnd { get; init; } /// - /// The block era id. + /// The era ID in which this block was created. /// [JsonPropertyName("era_id")] public ulong EraId { get; init; } /// - /// The block height. + /// The height of this block, i.e. the number of ancestors. /// [JsonPropertyName("height")] public ulong Height { get; init; } - + /// - /// The parent hash. + /// The parent block's hash. /// [JsonPropertyName("parent_hash")] public string ParentHash { get; init; } - + /// - /// The protocol version. + /// The protocol version of the network from when this block was created. /// - [JsonPropertyName("protocol_version")] + [JsonPropertyName("protocol_version")] public string ProtocolVersion { get; init; } - + /// - /// Randomness bit. + /// A random bit needed for initializing a future era. /// [JsonPropertyName("random_bit")] public bool RandomBit { get; init; } - + /// - /// The state root hash. + /// The root hash of global state after the deploys in this block have been executed. /// [JsonPropertyName("state_root_hash")] public string StateRootHash { get; init; } + + /// + /// The timestamp from when the block was proposed. + /// + [JsonPropertyName("timestamp")] + public string Timestamp { get; init; } + } + + public class BlockHeaderV2 : BlockHeaderV1 + { + /// + /// The `EraEnd` of a block if it is a switch block. + /// + [JsonPropertyName("era_end")] + public EraEnd EraEnd { get; init; } + + /// + /// The gas price of the era. + /// + [JsonPropertyName("current_gas_price")] + public UInt16 CurrentGasPrice { get; init; } + + /// + /// Public key of the validator that proposed the block + /// + [JsonPropertyName("proposer")] + [JsonConverter(typeof(Proposer.ProposerConverter))] + public Proposer Proposer { get; init; } + + [JsonPropertyName("last_switch_block_hash")] + public string LastSwitchBlockHash { get; init; } + } + + /// + /// A block header (alias for BlockHeaderV2) + /// + public class BlockHeader + { + protected int _version; + + /// + /// Returns the version of the block. + /// + public int Version + { + get { return _version; } + } + + protected BlockHeaderV1 _blockHeaderV1; + + protected BlockHeaderV2 _blockHeaderV2; /// - /// The block timestamp. + /// A seed needed for initializing a future era. + /// + public string AccumulatedSeed { get; init; } + + /// + /// The hash of the block's body. + /// + public string BodyHash { get; init; } + + /// + /// The era ID in which this block was created. + /// + public ulong EraId { get; init; } + + /// + /// The height of this block, i.e. the number of ancestors. + /// + public ulong Height { get; init; } + + /// + /// The parent block's hash. + /// + public string ParentHash { get; init; } + + /// + /// The protocol version of the network from when this block was created. + /// + public string ProtocolVersion { get; init; } + + /// + /// A random bit needed for initializing a future era. + /// + public bool RandomBit { get; init; } + + /// + /// The root hash of global state after the deploys in this block have been executed. + /// + public string StateRootHash { get; init; } + + /// + /// The timestamp from when the block was proposed. /// - [JsonPropertyName("timestamp")] public string Timestamp { get; init; } + + /// + /// The `EraEnd` of a block if it is a switch block. + /// + public EraEnd EraEnd { get; init; } + + /// + /// The gas price of the era. + /// + public UInt16 CurrentGasPrice { get; init; } + + /// + /// Public key of the validator that proposed the block + /// + public Proposer Proposer { get; init; } + + public string LastSwitchBlockHash { get; init; } + + public class BlockHeaderConverter : JsonConverter + { + public override BlockHeader Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + try + { + using (JsonDocument document = JsonDocument.ParseValue(ref reader)) + { + if (document.RootElement.TryGetProperty("parent_hash", out var header)) + { + var blockHeaderV1 = JsonSerializer.Deserialize(document.RootElement.GetRawText()); + return (BlockHeader)blockHeaderV1; + } + if (document.RootElement.TryGetProperty("Version1", out var headerV1)) + { + var blockHeaderV1 = JsonSerializer.Deserialize(headerV1.GetRawText()); + return (BlockHeader)blockHeaderV1; + } + if (document.RootElement.TryGetProperty("Version2", out var headerV2)) + { + var blockHeaderV2 = JsonSerializer.Deserialize(headerV1.GetRawText()); + return (BlockHeader)blockHeaderV2; + } + throw new JsonException("Cannot deserialize BlockHeader"); + } + } + catch (Exception e) + { + throw new JsonException(e.Message); + } + } + + public override void Write( + Utf8JsonWriter writer, + BlockHeader blockHeader, + JsonSerializerOptions options) + { + switch (blockHeader.Version) + { + case 1: + writer.WritePropertyName("Version1"); + writer.WriteStartObject(); + JsonSerializer.Serialize((BlockHeaderV1)blockHeader, options); + writer.WriteEndObject(); + break; + case 2: + writer.WritePropertyName("Version2"); + writer.WriteStartObject(); + JsonSerializer.Serialize((BlockHeaderV2)blockHeader, options); + writer.WriteEndObject(); + break; + default: + throw new JsonException($"Unexpected block header version {blockHeader.Version}"); + } + } + } + + public static explicit operator BlockHeaderV1(BlockHeader blockHeader) + { + if(blockHeader._version == 1) + return blockHeader._blockHeaderV1; + + throw new InvalidCastException("Version2 block header cannot be converted to Version1"); + } + + public static explicit operator BlockHeaderV2(BlockHeader blockHeader) + { + if(blockHeader._version == 2) + return blockHeader._blockHeaderV2; + + throw new InvalidCastException("Version1 block header cannot be converted to Version2"); + } + + public static explicit operator BlockHeader(BlockHeaderV1 blockHeader) + { + return new BlockHeader + { + _version = 1, + _blockHeaderV1 = blockHeader, + + AccumulatedSeed = blockHeader.AccumulatedSeed, + EraId = blockHeader.EraId, + Height = blockHeader.Height, + ParentHash = blockHeader.ParentHash, + ProtocolVersion = blockHeader.ProtocolVersion, + StateRootHash = blockHeader.StateRootHash, + Timestamp = blockHeader.Timestamp, + RandomBit = blockHeader.RandomBit, + CurrentGasPrice = 1, + LastSwitchBlockHash = null, + Proposer = null, + EraEnd = BlockV1.EraEndFromV1(blockHeader), + }; + } + + public static explicit operator BlockHeader(BlockHeaderV2 blockHeader) + { + return new BlockHeader + { + _version = 2, + _blockHeaderV2 = blockHeader, + + AccumulatedSeed = blockHeader.AccumulatedSeed, + EraId = blockHeader.EraId, + Height = blockHeader.Height, + ParentHash = blockHeader.ParentHash, + ProtocolVersion = blockHeader.ProtocolVersion, + StateRootHash = blockHeader.StateRootHash, + Timestamp = blockHeader.Timestamp, + RandomBit = blockHeader.RandomBit, + CurrentGasPrice = blockHeader.CurrentGasPrice, + LastSwitchBlockHash = blockHeader.LastSwitchBlockHash, + Proposer = blockHeader.Proposer, + EraEnd = blockHeader.EraEnd, + }; + } } /// @@ -89,12 +317,12 @@ public bool isSystem get => this.IsSystem; set => this.IsSystem = value; } - + /// /// Validator's public key /// public PublicKey PublicKey { get; set; } - + /// /// Json converter class to serialize/deserialize a Proposer to/from Json /// @@ -134,63 +362,131 @@ public override void Write( } } } - + /// /// A block body /// - public class BlockBody + public class BlockBodyV1 { /// - /// List of Deploy hashes included in the block + /// The deploy hashes of the non-transfer deploys within the block. /// [JsonPropertyName("deploy_hashes")] public List DeployHashes { get; init; } - + /// /// Public key of the validator that proposed the block /// [JsonPropertyName("proposer")] [JsonConverter(typeof(Proposer.ProposerConverter))] public Proposer Proposer { get; init; } - + /// - /// List of Transfer hashes included in the block + /// The deploy hashes of the transfers within the block. /// [JsonPropertyName("transfer_hashes")] public List TransferHashes { get; init; } } /// - /// Block's finality signature. + /// A block body /// - public class BlockProof + public class BlockBodyV2 { - /// - /// Validator public key - /// - [JsonPropertyName("public_key")] - [JsonConverter(typeof(PublicKey.PublicKeyConverter))] - public PublicKey PublicKey { get; init; } + [JsonPropertyName("transactions")] + [JsonConverter(typeof(BlockTransaction.BlockTransactionConverter))] + public List Transactions { get; init; } + // public Dictionary> Transactions { get; init; } /// - /// Validator signature + /// Describes finality signatures that will be rewarded in a block. Consists of a vector of + /// `SingleBlockRewardedSignatures`, each of which describes signatures for a single ancestor block. + /// The first entry represents the signatures for the parent block, the second for the parent of the parent, + /// and so on. /// - [JsonPropertyName("signature")] - [JsonConverter(typeof(Signature.SignatureConverter))] - public Signature Signature { get; init; } + [JsonPropertyName("rewarded_signatures")] + public List> RewardedSignatures { get; init; } } /// /// A block in the network /// - public class Block + public class BlockV1 { + /// + /// Block hash + /// + [JsonPropertyName("hash")] + public string Hash { get; init; } + + /// + /// Block header + /// + [JsonPropertyName("header")] + public BlockHeaderV1 Header { get; init; } + /// /// Block body /// [JsonPropertyName("body")] - public BlockBody Body { get; init; } + public BlockBodyV1 Body { get; init; } + + internal static EraEnd EraEndFromV1(BlockHeaderV1 header) + { + Dictionary> rewards = new(); + if (header.EraEnd != null && header.EraEnd.EraReport.Rewards.Count > 0) + { + foreach (var r in header.EraEnd.EraReport.Rewards) + { + rewards.Add(r.PublicKey.ToString().ToLower(), + new List { r.Amount.ToString()}); + } + } + + EraEnd eraEnd = header.EraEnd == null ? null : new EraEnd + { + NextEraValidatorWeights = header.EraEnd.NextEraValidatorWeights, + NextEraGasPrice = 1, + Equivocators = header.EraEnd.EraReport.Equivocators, + InactiveValidators = header.EraEnd.EraReport.InactiveValidators, + Rewards = rewards, + }; + return eraEnd; + } + + internal static List BlockTransactionsFromV1(BlockBodyV1 body) + { + var txs = new List(); + + var mintTransactions = + body.TransferHashes.Select(hash => new BlockTransaction() + { + Category = TransactionCategory.Mint, + Hash = hash, + Version = TransactionVersion.Deploy, + }).ToList(); + if(mintTransactions.Count > 0) + txs.AddRange(mintTransactions); + + var standardTransactions = + body.DeployHashes.Select(hash => new BlockTransaction() + { + Category = TransactionCategory.Large, + Hash = hash, + Version = TransactionVersion.Deploy, + }).ToList(); + if(standardTransactions.Count > 0) + txs.AddRange(standardTransactions); + + return txs; + } + } + /// + /// A block in the network + /// + public class BlockV2 + { /// /// Block hash /// @@ -201,12 +497,328 @@ public class Block /// Block header /// [JsonPropertyName("header")] - public BlockHeader Header { get; init; } + public BlockHeaderV2 Header { get; init; } + + /// + /// Block body + /// + [JsonPropertyName("body")] + public BlockBodyV2 Body { get; init; } + } + + public enum TransactionCategory { + /// + /// Native mint interaction (the default). + /// + Mint = 0, + /// + /// Transfer from a Deploy transaction. + /// + [Obsolete("Use Mint instead of DeployTransfer")] + DeployTransfer = 0, + /// + /// Native auction interaction. + /// + Auction = 1, + /// + /// Install or Upgrade. + /// + InstallUpgrade = 2, + /// + /// A large Wasm based transaction. + /// + Large = 3, + /// + /// A non-native Deploy transaction. + /// + [Obsolete("Use Large instead of DeployLarge")] + DeployLarge = 3, + /// + /// A medium Wasm based transaction. + /// + Medium = 4, + /// + /// A small Wasm based transaction. + /// + Small = 5, + } + + public enum TransactionVersion + { + Deploy = 0, + TransactionV1 = 1, + } + + public class BlockTransaction + { + public TransactionCategory Category { get; init; } + public TransactionVersion Version { get; init; } + public string Hash { get; init; } + + public class BlockTransactionConverter : JsonConverter> + { + public override List Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + var txs = new List(); + + try + { + reader.Read(); // skip start object + while (reader.TokenType == JsonTokenType.PropertyName) + { + var categoryNumber = reader.GetString(); + var category = EnumCompat.Parse(categoryNumber); + reader.Read(); + var list = JsonSerializer.Deserialize>(ref reader, options); + reader.Read(); + if (list.Count > 0) + { + txs.AddRange(list.Select(t => new BlockTransaction() + { + Category = category, + Hash = t.Deploy != null ? t.Deploy : t.Version1, + Version = t.Deploy != null ? TransactionVersion.Deploy : TransactionVersion.TransactionV1, + })); + } + } + } + catch (Exception e) + { + throw new JsonException(e.Message); + } + + return txs; + } + + public override void Write( + Utf8JsonWriter writer, + List block, + JsonSerializerOptions options) + { + throw new JsonException($"Serialization of BlockTransaction not available"); + } + } + } + + public class Block + { + protected int _version; /// - /// List of proofs for this block. + /// Returns the version of the block. /// - [JsonPropertyName("proofs")] - public List Proofs { get; init; } + public int Version + { + get { return _version; } + } + + protected BlockV1 _blockV1; + + protected BlockV2 _blockV2; + + /// + /// Block hash + /// + public string Hash { get; init; } + + /// + /// A seed needed for initializing a future era. + /// + public string AccumulatedSeed { get; init; } + + /// + /// The era ID in which this block was created. + /// + public ulong EraId { get; init; } + + /// + /// The height of this block, i.e. the number of ancestors. + /// + public ulong Height { get; init; } + + /// + /// The parent block's hash. + /// + public string ParentHash { get; init; } + + /// + /// The protocol version of the network from when this block was created. + /// + public string ProtocolVersion { get; init; } + + /// + /// A random bit needed for initializing a future era. + /// + public bool RandomBit { get; init; } + + /// + /// The root hash of global state after the deploys in this block have been executed. + /// + public string StateRootHash { get; init; } + + /// + /// The timestamp from when the block was proposed. + /// + public string Timestamp { get; init; } + + /// + /// The `EraEnd` of a block if it is a switch block. + /// + public EraEnd EraEnd { get; init; } + + /// + /// The gas price of the era. + /// + public UInt16 CurrentGasPrice { get; init; } + + /// + /// Public key of the validator that proposed the block + /// + public Proposer Proposer { get; init; } + + public string LastSwitchBlockHash { get; init; } + + public List Transactions { get; init; } + + /// + /// Describes finality signatures that will be rewarded in a block. Consists of a vector of + /// `SingleBlockRewardedSignatures`, each of which describes signatures for a single ancestor block. + /// The first entry represents the signatures for the parent block, the second for the parent of the parent, + /// and so on. + /// + public List> RewardedSignatures { get; init; } + + /// + /// Json converter class to serialize/deserialize a Block to/from Json + /// + public class BlockConverter : JsonConverter + { + public override Block Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + try + { + reader.Read(); + var version = reader.GetString(); + reader.Read(); + switch (version) + { + case "Version1": + var blockv1 = JsonSerializer.Deserialize(ref reader, options); + reader.Read(); + return (Block)blockv1; + case "Version2": + var blockv2 = JsonSerializer.Deserialize(ref reader, options); + reader.Read(); + return (Block)blockv2; + default: + throw new JsonException("Expected Version1 or Version2"); + } + } + catch (Exception e) + { + throw new JsonException(e.Message); + } + } + + public override void Write( + Utf8JsonWriter writer, + Block block, + JsonSerializerOptions options) + { + switch (block.Version) + { + case 1: + writer.WritePropertyName("Version1"); + writer.WriteStartObject(); + JsonSerializer.Serialize(writer, (BlockV1)block, options); + writer.WriteEndObject(); + break; + case 2: + writer.WritePropertyName("Version2"); + writer.WriteStartObject(); + JsonSerializer.Serialize(writer, block, options); + writer.WriteEndObject(); + break; + default: + throw new JsonException($"Unexpected block version {block.Version}"); + } + } + } + + public static explicit operator BlockV1(Block block) + { + if(block._version == 1) + return block._blockV1; + + throw new InvalidCastException("Version2 block cannot be converted to Version1"); + } + + public static explicit operator BlockV2(Block block) + { + if(block._version == 2) + return block._blockV2; + + throw new InvalidCastException("Version1 block cannot be converted to Version2"); + } + + public static explicit operator Block(BlockV1 block) + { + return new Block + { + _version = 1, + _blockV1 = block, + + Hash = block.Hash, + + AccumulatedSeed = block.Header.AccumulatedSeed, + EraId = block.Header.EraId, + Height = block.Header.Height, + ParentHash = block.Header.ParentHash, + ProtocolVersion = block.Header.ProtocolVersion, + StateRootHash = block.Header.StateRootHash, + Timestamp = block.Header.Timestamp, + RandomBit = block.Header.RandomBit, + CurrentGasPrice = 1, + LastSwitchBlockHash = null, + Proposer = block.Body.Proposer, + EraEnd = BlockV1.EraEndFromV1(block.Header), + + Transactions = BlockV1.BlockTransactionsFromV1(block.Body), + RewardedSignatures = null, + }; + } + + public static explicit operator Block(BlockV2 block) + { + return new Block + { + _version = 2, + _blockV2 = block, + + Hash = block.Hash, + + AccumulatedSeed = block.Header.AccumulatedSeed, + EraId = block.Header.EraId, + Height = block.Header.Height, + ParentHash = block.Header.ParentHash, + ProtocolVersion = block.Header.ProtocolVersion, + StateRootHash = block.Header.StateRootHash, + Timestamp = block.Header.Timestamp, + RandomBit = block.Header.RandomBit, + CurrentGasPrice = block.Header.CurrentGasPrice, + LastSwitchBlockHash = block.Header.LastSwitchBlockHash, + Proposer = block.Header.Proposer, + EraEnd = block.Header.EraEnd, + + Transactions = block.Body.Transactions, + RewardedSignatures = block.Body.RewardedSignatures, + }; + } } -} +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/BlockIdentifier.cs b/Casper.Network.SDK/Types/BlockIdentifier.cs new file mode 100644 index 0000000..19412d9 --- /dev/null +++ b/Casper.Network.SDK/Types/BlockIdentifier.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; + +namespace Casper.Network.SDK.Types +{ + /// + /// A block identified by its hash or its height. + /// + public interface IBlockIdentifier + { + public Dictionary GetBlockIdentifier(); + } + + /// + /// A block identified by its hash or its height. + /// + public class BlockIdentifier : IBlockIdentifier + { + private string _hash; + private UInt64? _height; + + public BlockIdentifier(string hash) + { + _hash = hash; + } + + public BlockIdentifier(UInt64 height) + { + _height = height; + } + + /// + /// Returns a BlockIdentifier object as defined in the RPC schema for a block. + /// + public Dictionary GetBlockIdentifier() + { + if(_height.HasValue) + return new Dictionary + { + { "Height", _height.Value}, + }; + + return new Dictionary + { + { "Hash", _hash}, + }; + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/BlockProof.cs b/Casper.Network.SDK/Types/BlockProof.cs new file mode 100644 index 0000000..2ff0ea1 --- /dev/null +++ b/Casper.Network.SDK/Types/BlockProof.cs @@ -0,0 +1,24 @@ +using System.Text.Json.Serialization; + +namespace Casper.Network.SDK.Types +{ + /// + /// A validator's public key paired with a corresponding signature of a given block hash. + /// + public class BlockProof + { + /// + /// The validator's public key. + /// + [JsonPropertyName("public_key")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey PublicKey { get; init; } + + /// + /// The validator's signature. + /// + [JsonPropertyName("signature")] + [JsonConverter(typeof(Signature.SignatureConverter))] + public Signature Signature { get; init; } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/ByteCode.cs b/Casper.Network.SDK/Types/ByteCode.cs new file mode 100644 index 0000000..d7439cf --- /dev/null +++ b/Casper.Network.SDK/Types/ByteCode.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; + +namespace Casper.Network.SDK.Types +{ + /// + /// A container for contract's Wasm bytes. + /// + public class ByteCode + { + [JsonPropertyName("kind")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public ByteCodeKind Kind { get; init; } + + [JsonPropertyName("bytes")] + public string Bytes { get; init; } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/CLValue.cs b/Casper.Network.SDK/Types/CLValue.cs index 670dd23..b8b8115 100644 --- a/Casper.Network.SDK/Types/CLValue.cs +++ b/Casper.Network.SDK/Types/CLValue.cs @@ -32,7 +32,7 @@ public class CLValue /// Byte array representation of underlying data /// [JsonPropertyName("bytes")] - [JsonConverter(typeof(HexBytesWithChecksumConverter))] + [JsonConverter(typeof(HexBytesConverter))] public byte[] Bytes { get; } /// diff --git a/Casper.Network.SDK/Types/ContractPackage.cs b/Casper.Network.SDK/Types/ContractPackage.cs index 1318dcb..5c1575b 100644 --- a/Casper.Network.SDK/Types/ContractPackage.cs +++ b/Casper.Network.SDK/Types/ContractPackage.cs @@ -1,4 +1,6 @@ +using System; using System.Collections.Generic; +using System.Text.Json; using System.Text.Json.Serialization; using Casper.Network.SDK.Converters; @@ -7,6 +9,7 @@ namespace Casper.Network.SDK.Types /// /// A disabled version of a contract. /// + [JsonConverter(typeof(DisabledVersion.DisabledVersionConverter))] public class DisabledVersion { /// @@ -14,11 +17,68 @@ public class DisabledVersion /// [JsonPropertyName("contract_version")] public uint Version { get; init; } - + [JsonPropertyName("protocol_version_major")] public uint ProtocolVersionMajor { get; init; } + + public class DisabledVersionConverter : JsonConverter + { + public override DisabledVersion Read + ( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options + ) + { + if (reader.TokenType == JsonTokenType.StartObject) + { + using (JsonDocument doc = JsonDocument.ParseValue(ref reader)) + { + doc.RootElement.TryGetProperty("protocol_version_major", out var protocolVersionMajor); + doc.RootElement.TryGetProperty("contract_version", out var contractVersion); + if (!protocolVersionMajor.ValueKind.Equals(JsonValueKind.Number) || + !contractVersion.ValueKind.Equals(JsonValueKind.Number)) + throw new JsonException("Cannot deserialize StoredValue. Number expected for protocol_version_major and contract_version."); + return new DisabledVersion() + { + ProtocolVersionMajor = protocolVersionMajor.GetUInt32(), + Version = contractVersion.GetUInt32() + }; + } + } + + if (reader.TokenType != JsonTokenType.StartArray) + throw new JsonException("Cannot deserialize StoredValue. StartObject expected."); + { + using (JsonDocument doc = JsonDocument.ParseValue(ref reader)) + { + var versionsArray = JsonSerializer.Deserialize>(doc.RootElement.GetRawText()); + + if (versionsArray?.Count != 2) + throw new JsonException( + "Cannot deserialize StoredValue. Array of length 2 expected for disabled_version item."); + + return new DisabledVersion() + { + ProtocolVersionMajor = versionsArray[0], + Version = versionsArray[1] + }; + } + } + + } + + + public override void Write( + Utf8JsonWriter writer, + DisabledVersion value, + JsonSerializerOptions options) + { + throw new NotImplementedException("Write method for DisabledVersion not yet implemented"); + } + } } - + /// /// Information related to an active version of a contract. /// @@ -29,13 +89,13 @@ public class ContractVersion /// [JsonPropertyName("contract_hash")] public string Hash { get; init; } - + /// /// Contract version. /// [JsonPropertyName("contract_version")] public uint Version { get; init; } - + [JsonPropertyName("protocol_version_major")] public uint ProtocolVersionMajor { get; init; } } @@ -50,7 +110,7 @@ public class Group /// [JsonPropertyName("group")] public string Label { get; init; } - + /// /// List of URefs associated with the group label. /// @@ -67,7 +127,7 @@ public enum LockStatus Locked, Unlocked, } - + /// /// Contract definition, metadata, and security container. /// @@ -76,13 +136,13 @@ public class ContractPackage [JsonPropertyName("access_key")] [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] public URef AccessKey { get; init; } - + /// /// List of disabled versions of a contract. /// [JsonPropertyName("disabled_versions")] public List DisabledVersions { get; init; } - + /// /// Groups associate a set of URefs with a label. Entry points on a contract can be given /// a list of labels they accept and the runtime will check that a URef from at least one @@ -90,18 +150,18 @@ public class ContractPackage /// [JsonPropertyName("groups")] public List Groups { get; init; } - + /// /// List of active versions of a contract. /// [JsonPropertyName("versions")] public List Versions { get; init; } - + /// - /// The current state of node reactor. + /// The current state of the contract package. /// - [JsonPropertyName("lock_status")] + [JsonPropertyName("lock_status")] [JsonConverter(typeof(JsonStringEnumConverter))] public LockStatus LockStatus { get; init; } } -} +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/ContractWasm.cs b/Casper.Network.SDK/Types/ContractWasm.cs new file mode 100644 index 0000000..f9ddb3d --- /dev/null +++ b/Casper.Network.SDK/Types/ContractWasm.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace Casper.Network.SDK.Types +{ + /// + /// A container for contract's WASM bytes. + /// + public class ContractWasm + { + [JsonPropertyName("bytes")] + public string Bytes { get; init; } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/Delegator.cs b/Casper.Network.SDK/Types/Delegator.cs index 007c9ed..2ab5a9e 100644 --- a/Casper.Network.SDK/Types/Delegator.cs +++ b/Casper.Network.SDK/Types/Delegator.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Numerics; using System.Text.Json; using System.Text.Json.Serialization; @@ -6,6 +7,35 @@ namespace Casper.Network.SDK.Types { + internal class PublicKeyAndDelegator + { + [JsonPropertyName("delegator_public_key")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey DelegatorPublicKey { get; init; } + + [JsonPropertyName("delegator")] + public Delegator Delegator { get; init; } + } + + internal class DelegatorV1 + { + [JsonPropertyName("bonding_purse")] + [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] + public URef BondingPurse { get; init; } + + [JsonPropertyName("delegatee")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey ValidatorPublicKey { get; init; } + + [JsonPropertyName("public_key")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey DelegatorPublicKey { get; init; } + + [JsonPropertyName("staked_amount")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger StakedAmount { get; init; } + } + /// /// A delegator associated with the given validator. /// @@ -14,103 +44,112 @@ public class Delegator /// /// The purse that was used for delegating. /// + [JsonPropertyName("bonding_purse")] + [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] public URef BondingPurse { get; init; } /// /// Public key of the validator /// - public PublicKey Delegatee { get; init; } + [JsonPropertyName("validator_public_key")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey ValidatorPublicKey { get; init; } /// /// Public Key of the delegator /// - public PublicKey PublicKey { get; init; } + [JsonPropertyName("delegator_public_key")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey DelegatorPublicKey { get; init; } /// /// Amount of Casper token (in motes) delegated /// + [JsonPropertyName("staked_amount")] + [JsonConverter(typeof(BigIntegerConverter))] public BigInteger StakedAmount { get; init; } + [JsonPropertyName("vesting_schedule")] public VestingSchedule VestingSchedule { get; init; } - - public class DelegatorConverter : JsonConverter, IDeserializeAsList + + public class PublicKeyAndDelegatorListConverter : JsonConverter>, IDeserializeAsList { public bool DeserializeAsList { get { return true; } } - public override Delegator Read( + public override List Read( ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - // two possibilities called 'Delegator' and 'JsonDelegator' in the rpc schema - // An array of delegators is returned in the GetAuctionState response - // A dictionary of delegators is returned in the QueryGlobalState response for a Bid key - // This methods parses both but returns a common Delegator object in both cases + var delegators = new List(); - string delegatorPublicKey = null; - if (reader.TokenType == JsonTokenType.PropertyName) + if (reader.TokenType == JsonTokenType.StartArray) { - delegatorPublicKey = reader.GetString(); reader.Read(); - } - if (reader.TokenType != JsonTokenType.StartObject) - throw new JsonException("Could not deserialize Delegator. Start object token expected."); + while (reader.TokenType != JsonTokenType.EndArray) + { + try + { + using (JsonDocument document = JsonDocument.ParseValue(ref reader)) + { + if (document.RootElement.TryGetProperty("delegator_public_key", + out var delegatorPublicKey)) + { + var pkAndDelegator = + JsonSerializer.Deserialize(document.RootElement + .GetRawText()); + delegators.Add(pkAndDelegator.Delegator); + } + else + { + var pkAndDelegator = + JsonSerializer.Deserialize(document.RootElement.GetRawText()); + delegators.Add(new Delegator() + { + BondingPurse = pkAndDelegator.BondingPurse, + StakedAmount = pkAndDelegator.StakedAmount, + DelegatorPublicKey = pkAndDelegator.DelegatorPublicKey, + ValidatorPublicKey = pkAndDelegator.ValidatorPublicKey, + }); + } + } - reader.Read(); //start object + reader.Read(); // skip end object + } + catch (Exception e) + { + throw new JsonException("Cannot parse list of delegators. " + e.Message); + } + } - string public_key = null; - string amount = null; - string bonding_purse = null; - string validator_public_key = null; - VestingSchedule vesting_schedule = null; - - while (reader.TokenType == JsonTokenType.PropertyName) + return delegators; + } + + if (reader.TokenType == JsonTokenType.StartObject) { - var property = reader.GetString()?.ToLowerInvariant(); - reader.Read(); - - switch (property) + reader.Read(); // skip start object + + while (reader.TokenType == JsonTokenType.PropertyName) { - case "delegator_public_key": - case "public_key": - public_key = reader.GetString(); - reader.Read(); - break; - case "bonding_purse": - bonding_purse = reader.GetString(); - reader.Read(); - break; - case "staked_amount": - amount = reader.GetString(); - reader.Read(); - break; - case "validator_public_key": - case "delegatee": - validator_public_key = reader.GetString(); - reader.Read(); - break; - case "vesting_schedule": - if(reader.TokenType != JsonTokenType.Null) - vesting_schedule = JsonSerializer.Deserialize(ref reader, options); - reader.Read(); - break; + reader.Read(); // skip public key + + var delegator = + JsonSerializer.Deserialize(ref reader, options); + reader.Read(); + delegators.Add(delegator); } + + return delegators; } + + throw new JsonException("StartArray or StartObject token expected to deserialize a list of delegators."); - return new Delegator() - { - PublicKey = PublicKey.FromHexString(public_key), - StakedAmount = BigInteger.Parse(amount), - BondingPurse = new URef(bonding_purse), - Delegatee = PublicKey.FromHexString(validator_public_key), - VestingSchedule = vesting_schedule - }; } public override void Write( Utf8JsonWriter writer, - Delegator value, + List value, JsonSerializerOptions options) { throw new NotImplementedException("Write method for Delegator not yet implemented"); diff --git a/Casper.Network.SDK/Types/DelegatorBid.cs b/Casper.Network.SDK/Types/DelegatorBid.cs new file mode 100644 index 0000000..91ef6ea --- /dev/null +++ b/Casper.Network.SDK/Types/DelegatorBid.cs @@ -0,0 +1,36 @@ +using System.Numerics; +using System.Text.Json.Serialization; +using Casper.Network.SDK.Converters; + +namespace Casper.Network.SDK.Types +{ + /// + /// Represents a party delegating their stake to a validator (or \"delegatee + /// + public class DelegatorBid + { + /// + /// A Public key or Purse. Origin of the delegation. + /// + [JsonPropertyName("delegator_kind")] + public DelegatorKind DelegatorKind { get; init; } + + [JsonPropertyName("validator_public_key")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey ValidatorPublicKey { get; init; } + + /// + /// Amount of Casper token (in motes) delegated + /// + [JsonPropertyName("staked_amount")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger StakedAmount { get; init; } + + [JsonPropertyName("bonding_purse")] + [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] + public URef BondingPurse { get; init; } + + [JsonPropertyName("vesting_schedule")] + public VestingSchedule VestingSchedule { get; init; } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/DelegatorKind.cs b/Casper.Network.SDK/Types/DelegatorKind.cs new file mode 100644 index 0000000..50b380e --- /dev/null +++ b/Casper.Network.SDK/Types/DelegatorKind.cs @@ -0,0 +1,49 @@ + +using System.Collections.Generic; +using System.IO; +using System.Text.Json.Serialization; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Casper.Network.SDK.Types +{ + /// + /// Auction bid variants. Kinds of delegation bids. + /// + public class DelegatorKind + { + /// + /// Delegation from public key. + /// + [JsonPropertyName("PublicKey")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey PublicKey { get; init; } + + /// + /// Delegation from purse. + /// + [JsonPropertyName("Purse")] + public string Purse { get; init; } + + public byte[] ToBytes() + { + var ms = new MemoryStream(); + if (PublicKey != null) + { + ms.WriteByte(0x00); + ms.Write(this.PublicKey.GetBytes()); + return ms.ToArray(); + } + else // Purse + { + ms.WriteByte(0x01); + ms.Write(Hex.Decode(Purse)); + return ms.ToArray(); + } + } + + public CLValue ToCLValue() + { + return new CLValue(ToBytes(), CLType.Any); + } + } +} diff --git a/Casper.Network.SDK/Types/Deploy.cs b/Casper.Network.SDK/Types/Deploy.cs index 14b2692..655ffa3 100644 --- a/Casper.Network.SDK/Types/Deploy.cs +++ b/Casper.Network.SDK/Types/Deploy.cs @@ -6,7 +6,6 @@ using System.Text.Json.Serialization; using Casper.Network.SDK.ByteSerializers; using Casper.Network.SDK.Converters; -using Casper.Network.SDK.JsonRpc; using Casper.Network.SDK.Utils; using Org.BouncyCastle.Utilities.Encoders; @@ -18,13 +17,12 @@ public class Deploy /// List of signers and signatures for this Deploy. /// [JsonPropertyName("approvals")] - public List Approvals { get; } = new List(); + public List Approvals { get; } = new List(); /// /// A hash over the header of the deploy. /// [JsonPropertyName("hash")] - [JsonConverter(typeof(CEP57Checksum.HashWithChecksumConverter))] public string Hash { get; } /// @@ -94,7 +92,7 @@ public string SerializeToJson() [JsonConstructor] public Deploy(string hash, DeployHeader header, ExecutableDeployItem payment, - ExecutableDeployItem session, List approvals) + ExecutableDeployItem session, List approvals) { this.Hash = hash; this.Header = header; @@ -115,10 +113,10 @@ public Deploy(DeployHeader header, Ttl = header.Ttl, Dependencies = header.Dependencies, GasPrice = header.GasPrice, - BodyHash = CEP57Checksum.Encode(bodyHash), + BodyHash = Hex.ToHexString(bodyHash), ChainName = header.ChainName }; - this.Hash = CEP57Checksum.Encode(ComputeHeaderHash(this.Header)); + this.Hash = Hex.ToHexString(ComputeHeaderHash(this.Header)); this.Payment = payment; this.Session = session; } @@ -130,7 +128,7 @@ public void Sign(KeyPair keyPair) { byte[] signature = keyPair.Sign(Hex.Decode(this.Hash)); - Approvals.Add(new DeployApproval() + Approvals.Add(new Approval() { Signature = Signature.FromRawBytes(signature, keyPair.PublicKey.KeyAlgorithm), Signer = keyPair.PublicKey @@ -140,7 +138,7 @@ public void Sign(KeyPair keyPair) /// /// Adds an approval to the deploy. No check is done to the approval signature. /// - public void AddApproval(DeployApproval approval) + public void AddApproval(Approval approval) { this.Approvals.Add(approval); } @@ -157,7 +155,7 @@ public bool ValidateHashes(out string message) { message = "Computed Body Hash does not match value in deploy header. " + $"Expected: '{this.Header.BodyHash}'. " + - $"Computed: '{CEP57Checksum.Encode(computedHash)}'."; + $"Computed: '{Hex.ToHexString(computedHash)}'."; return false; } @@ -166,7 +164,7 @@ public bool ValidateHashes(out string message) { message = "Computed Hash does not match value in deploy object. " + $"Expected: '{this.Hash}'. " + - $"Computed: '{CEP57Checksum.Encode(computedHash)}'."; + $"Computed: '{Hex.ToHexString(computedHash)}'."; return false; } diff --git a/Casper.Network.SDK/Types/DeployHeader.cs b/Casper.Network.SDK/Types/DeployHeader.cs index ce218e3..4e4e037 100644 --- a/Casper.Network.SDK/Types/DeployHeader.cs +++ b/Casper.Network.SDK/Types/DeployHeader.cs @@ -47,7 +47,6 @@ public class DeployHeader /// Hash of the body part of this Deploy. /// [JsonPropertyName("body_hash")] - [JsonConverter(typeof(CEP57Checksum.HashWithChecksumConverter))] public string BodyHash { get; set; } /// diff --git a/Casper.Network.SDK/Types/EntryPoint.cs b/Casper.Network.SDK/Types/EntryPoint.cs index 71be3e9..636e28c 100644 --- a/Casper.Network.SDK/Types/EntryPoint.cs +++ b/Casper.Network.SDK/Types/EntryPoint.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; using Casper.Network.SDK.Converters; @@ -77,13 +78,50 @@ public override void Write( public enum EntryPointType { /// - /// Runs as session code + /// Runs using the calling entity's context. In v1.x this was used for both \"session\" code run using the + /// originating Account's context, and also for \"StoredSession\" code that ran in the caller's context. + /// While this made systemic sense due to the way the runtime context nesting works, this dual usage was + /// very confusing to most human beings. + /// In v2.x the renamed Caller variant is exclusively used for wasm run using the initiating account entity's + /// context. Previously installed 1.x stored session code should continue to work as the binary value matches + /// but we no longer allow such logic to be upgraded, nor do we allow new stored session to be installed. + /// + Caller, + /// + /// Runs using the called entity's context. + /// + Called, + /// + /// Extract a subset of bytecode and installs it as a new smart contract. Runs using the called entity's context. + /// + Factory, + /// + /// Casper v1.x only. Executes in the contract context. + /// + Contract, + /// + /// Casper v1.x only. Executes in the caller account context. /// Session, + } + + /// + /// Defines who pays for the execution of the entry point. + /// + public enum EntryPointPayment + { + /// + /// The caller must cover cost. + /// + Caller, /// - /// Runs within contract’s context + /// Will cover cost to execute self but not cost of any subsequent invoked contracts. /// - Contract + DirectInvocationOnly, + /// + /// Will cover cost to execute self and the cost of any subsequent invoked contracts. + /// + SelfOnward, } /// @@ -130,6 +168,13 @@ public class EntryPoint [JsonConverter(typeof(JsonStringEnumConverter))] public EntryPointType EntryPointType { get; init; } + /// + /// Specifies who pays for the invocation and execution of the entrypoint. + /// + [JsonPropertyName("entry_point_payment")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public EntryPointPayment EntryPointPayment { get; init; } + /// /// Name of the entry point /// @@ -143,4 +188,51 @@ public class EntryPoint [JsonConverter(typeof(CLTypeInfoConverter))] public CLTypeInfo Ret { get; init; } } + + public class EntryPointV2 + { + public EntryPointV2() + { + throw new NotImplementedException("V2CasperVm entry point not yet implemented"); + } + } + + public class VersionedEntryPoint + { + public EntryPoint V1CasperVm { get; init; } + + public EntryPointV2 V2CasperVm { get; init; } + } + + public class NamedEntryPoint + { + [JsonPropertyName("name")] + public string Name { get; init; } + + [JsonPropertyName("entry_point")] + public EntryPoint EntryPoint { get; init; } + + public class NamedEntryPointsConverter : JsonConverter> + { + public override List Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + var namedEntryPoints = JsonSerializer.Deserialize>(ref reader, options); + if (namedEntryPoints != null) + return namedEntryPoints.Select(e => e.EntryPoint).ToList(); + + throw new JsonException("Cannot deserialize Array_of_NamedEntryPoint."); + } + + public override void Write( + Utf8JsonWriter writer, + List value, + JsonSerializerOptions options) + { + throw new NotImplementedException("Write method for Array_of_NamedEntryPoint not yet implemented."); + } + } + } } \ No newline at end of file diff --git a/Casper.Network.SDK/Types/EraEnd.cs b/Casper.Network.SDK/Types/EraEnd.cs index 0674da2..286abe8 100644 --- a/Casper.Network.SDK/Types/EraEnd.cs +++ b/Casper.Network.SDK/Types/EraEnd.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Text.Json.Serialization; using Casper.Network.SDK.Converters; @@ -5,18 +6,18 @@ namespace Casper.Network.SDK.Types { /// - /// A validator reward + /// validator's public key paired with a measure of the value of its contribution to consensus, as a fraction of the configured maximum block reward. /// public class Reward { /// - /// Reward amount + /// The reward amount. /// [JsonPropertyName("amount")] public ulong Amount { get; init; } /// - /// Validator public key + /// The validator's public key. /// [JsonPropertyName("validator")] [JsonConverter(typeof(PublicKey.PublicKeyConverter))] @@ -24,38 +25,38 @@ public class Reward } /// - /// Equivocation and reward information to be included in the terminal block. + /// Equivocation, reward and validator inactivity information. /// public class EraReport { /// - /// List of public keys of the equivocators + /// The set of equivocators. /// [JsonPropertyName("equivocators")] [JsonConverter(typeof(GenericListConverter))] public List Equivocators { get; init; } /// - /// List of public keys of inactive validators + /// Validators that haven't produced any unit during the era. /// [JsonPropertyName("inactive_validators")] [JsonConverter(typeof(GenericListConverter))] public List InactiveValidators { get; init; } /// - /// List of validators with rewards + /// Rewards for finalization of earlier blocks. /// [JsonPropertyName("rewards")] public List Rewards { get; init; } } /// - /// Era end report and list of validator weights for next era + /// Information related to the end of an era, and validator weights for the following era. /// - public class EraEnd + public class EraEndV1 { /// - /// Era report + /// Equivocation, reward and validator inactivity information. /// [JsonPropertyName("era_report")] public EraReport EraReport { get; init; } @@ -67,4 +68,51 @@ public class EraEnd [JsonConverter(typeof(GenericListConverter))] public List NextEraValidatorWeights { get; init; } } + + + /// + /// Information related to the end of an era, and validator weights for the following era. + /// + public class EraEndV2 + { + /// + /// The set of equivocators. + /// + [JsonPropertyName("equivocators")] + [JsonConverter(typeof(GenericListConverter))] + public List Equivocators { get; init; } + + /// + /// Validators that haven't produced any unit during the era. + /// + [JsonPropertyName("inactive_validators")] + [JsonConverter(typeof(GenericListConverter))] + public List InactiveValidators { get; init; } + + /// + /// A list of validator weights for the next era + /// + [JsonPropertyName("next_era_validator_weights")] + [JsonConverter(typeof(GenericListConverter))] + public List NextEraValidatorWeights { get; init; } + + /// + /// The rewards distributed to the validators. + /// + [JsonPropertyName("rewards")] + public Dictionary> Rewards { get; init; } + + /// + /// Next Era gas price + /// + [JsonPropertyName("next_era_gas_price")] + public UInt16 NextEraGasPrice { get; init; } + } + + /// + /// Information related to the end of an era, and validator weights for the following era (alias for EraEndV2). + /// + public class EraEnd: EraEndV2 + { + } } \ No newline at end of file diff --git a/Casper.Network.SDK/Types/ExecutableDeployItem.cs b/Casper.Network.SDK/Types/ExecutableDeployItem.cs index 0524876..c4b71e6 100644 --- a/Casper.Network.SDK/Types/ExecutableDeployItem.cs +++ b/Casper.Network.SDK/Types/ExecutableDeployItem.cs @@ -78,7 +78,6 @@ public class StoredContractByHashDeployItem : ExecutableDeployItem /// Hash of the contract. /// [JsonPropertyName("hash")] - [JsonConverter(typeof(CEP57Checksum.HashWithChecksumConverter))] public string Hash { get; init; } /// @@ -161,7 +160,6 @@ public class StoredVersionedContractByHashDeployItem : ExecutableDeployItem /// Hash of the contract package. /// [JsonPropertyName("hash")] - [JsonConverter(typeof(CEP57Checksum.HashWithChecksumConverter))] public string Hash { get; init; } /// diff --git a/Casper.Network.SDK/Types/ExecutionEffect.cs b/Casper.Network.SDK/Types/ExecutionEffect.cs index 1bec2ff..9ebd592 100644 --- a/Casper.Network.SDK/Types/ExecutionEffect.cs +++ b/Casper.Network.SDK/Types/ExecutionEffect.cs @@ -19,7 +19,7 @@ public class ExecutionEffect /// The journal of execution transforms. /// [JsonPropertyName("transforms")] - [JsonConverter(typeof(GenericListConverter))] - public List Transforms { get; init; } + [JsonConverter(typeof(GenericListConverter))] + public List Transforms { get; init; } } } \ No newline at end of file diff --git a/Casper.Network.SDK/Types/ExecutionInfo.cs b/Casper.Network.SDK/Types/ExecutionInfo.cs new file mode 100644 index 0000000..a441b1b --- /dev/null +++ b/Casper.Network.SDK/Types/ExecutionInfo.cs @@ -0,0 +1,26 @@ +using System.Text.Json.Serialization; + +namespace Casper.Network.SDK.Types +{ + public class ExecutionInfo + { + /// + /// The hash of the block in which the transaction/deploy was executed. + /// + [JsonPropertyName("block_hash")] + public string BlockHash { get; init; } + + /// + /// The height of the block in which the transaction/deploy was executed. + /// + [JsonPropertyName("block_height")] + public ulong BlockHeight { get; init; } + + /// + /// The map of block hash to execution result. + /// + [JsonPropertyName("execution_result")] + [JsonConverter(typeof(ExecutionResult.ExecutionResultConverter))] + public ExecutionResult ExecutionResult { get; init; } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/ExecutionResult.cs b/Casper.Network.SDK/Types/ExecutionResult.cs index b11d41d..ea7165b 100644 --- a/Casper.Network.SDK/Types/ExecutionResult.cs +++ b/Casper.Network.SDK/Types/ExecutionResult.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Numerics; using System.Text.Json; using System.Text.Json.Serialization; @@ -7,10 +8,210 @@ namespace Casper.Network.SDK.Types { + public class ExecutionResult + { + protected int _version; + + /// + /// Returns the version of the block. + /// + public int Version + { + get { return _version; } + } + + protected ExecutionResultV1 _executionResultV1; + + public static explicit operator ExecutionResultV1(ExecutionResult executionResult) + { + if(executionResult._version == 1) + return executionResult._executionResultV1; + + throw new InvalidCastException("Version2 execution result cannot be converted to Version1"); + } + + public static explicit operator ExecutionResult(ExecutionResultV1 executionResult) + { + var v2Transfers = executionResult.Transfers.Select(key => + { + var transform = executionResult.Effect.Transforms.FirstOrDefault(tr => tr.Key.Equals(key)); + if (transform != null && + transform.Kind == TransformKindV1.WriteTransfer && + transform.Value is TransferV1 transferV1) + { + return (Transfer)transferV1; + } + return null; + }).ToList(); + + var v2Effect = executionResult.Effect.Transforms.Select(t => (Transform)t).ToList(); + + return new ExecutionResult + { + _version = 1, + _executionResultV1 = executionResult, + ErrorMessage = executionResult.ErrorMessage, + // Transfers = executionResult.Transfers, + Cost = executionResult.Cost, + Refund = 0, + CurrentGasPrice = 1, + Limit = 0, + Consumed = 0, + Effect = v2Effect, + SizeEstimate = 0, + Transfers = v2Transfers, + }; + } + + /// + /// What was the maximum allowed gas limit for this transaction?. + /// + [JsonPropertyName("limit")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger Limit { get; init; } + + /// + /// How much gas was consumed executing this transaction. + /// + [JsonPropertyName("consumed")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger Consumed { get; init; } + + /// + /// How much was paid for this transaction. + /// + [JsonPropertyName("cost")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger Cost { get; init; } + + /// + /// How much was refunded (if any) + /// + [JsonPropertyName("refund")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger Refund { get; init; } + + /// + /// The gas price of the era. + /// + [JsonPropertyName("current_price")] + public UInt16 CurrentGasPrice { get; init; } + + /// + /// If there is no error message, this execution was processed successfully. If there is an error message, this + /// execution failed to fully process for the stated reason. + /// + [JsonPropertyName("error_message")] + public string ErrorMessage { get; init; } + + /// + /// True if the transaction was completed without error. + /// + [JsonIgnore] + public bool IsSuccess + { + get { return ErrorMessage == null; } + } + + /// + /// A record of transfers performed while executing this transaction. + /// + [JsonPropertyName("transfers")] + public List Transfers { get; init; } + + /// + /// The size estimate of the transaction + /// + [JsonPropertyName("size_estimate")] + public UInt64 SizeEstimate { get; init; } + + /// + /// A log of all transforms produced during execution. + /// + [JsonPropertyName("effects")] + [JsonConverter(typeof(GenericListConverter))] + public List Effect { get; init; } + + /// + /// Json converter class to serialize/deserialize a ExecutionResult to/from Json + /// + public class ExecutionResultConverter : JsonConverter + { + public override ExecutionResult Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + try + { + reader.Read(); + var version = reader.GetString(); + reader.Read(); + switch (version) + { + case "Version1": + var erSerializerOptions = new JsonSerializerOptions(options); + erSerializerOptions.Converters.Add(new ExecutionResultV1.ExecutionResultV1Converter()); + + var erV1 = JsonSerializer.Deserialize(ref reader, erSerializerOptions); + reader.Read(); + return (ExecutionResult)erV1; + case "Version2": + var erv2 = JsonSerializer.Deserialize(ref reader, options); + reader.Read(); + return new ExecutionResult + { + _version = 2, + ErrorMessage = erv2.ErrorMessage, + Transfers = erv2.Transfers, + Cost = erv2.Cost, + Refund = erv2.Refund, + CurrentGasPrice = erv2.CurrentGasPrice, + Limit = erv2.Limit, + Consumed = erv2.Consumed, + Effect = erv2.Effect, + SizeEstimate = erv2.SizeEstimate, + }; + default: + throw new JsonException("Expected Version1 or Version2"); + } + } + catch (Exception e) + { + throw new JsonException(e.Message); + } + } + + public override void Write( + Utf8JsonWriter writer, + ExecutionResult executionResult, + JsonSerializerOptions options) + { + switch (executionResult.Version) + { + case 1: + writer.WritePropertyName("Version1"); + writer.WriteStartObject(); + JsonSerializer.Serialize((ExecutionResultV1)executionResult, options); + writer.WriteEndObject(); + break; + case 2: + writer.WritePropertyName("Version2"); + writer.WriteStartObject(); + JsonSerializer.Serialize(executionResult, options); + writer.WriteEndObject(); + break; + default: + throw new JsonException($"Unexpected execution result version {executionResult.Version}"); + } + } + } + } + /// /// The result of executing a single deploy. /// - public class ExecutionResult + public class ExecutionResultV1 { [JsonIgnore] public bool IsSuccess { get; init; } @@ -45,25 +246,30 @@ public class ExecutionResult [JsonConverter(typeof(GenericListConverter))] public List Transfers { get; init; } - public ExecutionResult() + public ExecutionResultV1() { Transfers = new List(); } - public class ExecutionResultConverter : JsonConverter + public class ExecutionResultV1Converter : JsonConverter { - public override ExecutionResult Read( + public override ExecutionResultV1 Read( ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { + var firstTokenType = reader.TokenType; + + if(firstTokenType == JsonTokenType.StartArray) //skip start array (backward compat) + reader.Read(); + if (reader.TokenType == JsonTokenType.StartObject) reader.Read(); string blockHash = null; string result = null; bool readResult = false; - ExecutionResult executionResult = null; + ExecutionResultV1 executionResult = null; while (reader.TokenType == JsonTokenType.PropertyName) { @@ -87,7 +293,7 @@ public override ExecutionResult Read( if (reader.TokenType != JsonTokenType.StartObject) throw new JsonException($"Object expected after '{result}' during ExecutionResult deserialization"); - executionResult = JsonSerializer.Deserialize(ref reader); + executionResult = JsonSerializer.Deserialize(ref reader); reader.Read(); // end Success/Failure object @@ -99,11 +305,17 @@ public override ExecutionResult Read( break; } } + + if(firstTokenType == JsonTokenType.StartArray) + reader.Read(); // skip end object + + // if (reader.TokenType == JsonTokenType.EndArray) + // reader.Read(); //skip end array (backward compat) if (executionResult == null) throw new JsonException("Could not deserialize ExecutionResult object"); - return new ExecutionResult + return new ExecutionResultV1 { BlockHash = blockHash, IsSuccess = result.Equals("Success", StringComparison.InvariantCultureIgnoreCase), @@ -116,11 +328,74 @@ public override ExecutionResult Read( public override void Write( Utf8JsonWriter writer, - ExecutionResult item, + ExecutionResultV1 item, JsonSerializerOptions options) { throw new NotImplementedException("Serialization of ExecutionResult not implemented"); } } } -} \ No newline at end of file + + internal class ExecutionResultV2 + { + /// + /// What was the maximum allowed gas limit for this transaction?. + /// + [JsonPropertyName("limit")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger Limit { get; init; } + + /// + /// How much gas was consumed executing this transaction. + /// + [JsonPropertyName("consumed")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger Consumed { get; init; } + + /// + /// How much was paid for this transaction. + /// + [JsonPropertyName("cost")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger Cost { get; init; } + + /// + /// How much was refunded (if any) + /// + [JsonPropertyName("refund")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger Refund { get; init; } + + /// + /// The gas price of the era. + /// + [JsonPropertyName("current_price")] + public UInt16 CurrentGasPrice { get; init; } + + /// + /// If there is no error message, this execution was processed successfully. If there is an error message, this + /// execution failed to fully process for the stated reason. + /// + [JsonPropertyName("error_message")] + public string ErrorMessage { get; init; } + + /// + /// A record of transfers performed while executing this transaction. + /// + [JsonPropertyName("transfers")] + public List Transfers { get; init; } + + /// + /// The size estimate of the transaction + /// + [JsonPropertyName("size_estimate")] + public UInt64 SizeEstimate { get; init; } + + /// + /// A log of all transforms produced during execution. + /// + [JsonPropertyName("effects")] + [JsonConverter(typeof(GenericListConverter))] + public List Effect { get; init; } + } +} diff --git a/Casper.Network.SDK/Types/GlobalStateKey/AddressableEntityKey.cs b/Casper.Network.SDK/Types/GlobalStateKey/AddressableEntityKey.cs new file mode 100644 index 0000000..fcd4ba4 --- /dev/null +++ b/Casper.Network.SDK/Types/GlobalStateKey/AddressableEntityKey.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; +using Casper.Network.SDK.Utils; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Casper.Network.SDK.Types +{ + public enum EntityKindEnum + { + /// + /// Package associated with a native contract implementation. + /// + System = 0, + + /// + /// Package associated with an Account hash. + /// + Account = 1, + + /// + /// Package associated with Wasm stored on chain. + /// + Contract = 2, + } + + public static class EntityKinEnumExtensions + { + public static string ToKeyPrefix(this EntityKindEnum kind) + { + switch (kind) + { + case EntityKindEnum.System: + return "entity-system-"; + case EntityKindEnum.Account: + return "entity-account-"; + case EntityKindEnum.Contract: + return "entity-contract-"; + default: + return kind.ToString(); + } + } + } + + public class AddressableEntityKey : GlobalStateKey, IEntityIdentifier, IPurseIdentifier + { + public EntityKindEnum Kind { get; init; } + + private static string GetPrefix(string key) + { + if (key.StartsWith(EntityKindEnum.System.ToKeyPrefix())) + return EntityKindEnum.System.ToKeyPrefix(); + if (key.StartsWith(EntityKindEnum.Account.ToKeyPrefix())) + return EntityKindEnum.Account.ToKeyPrefix(); + if (key.StartsWith(EntityKindEnum.Contract.ToKeyPrefix())) + return EntityKindEnum.Contract.ToKeyPrefix(); + + throw new Exception("Unexpected key prefix in NamedKeyKey: " + key); + } + + public AddressableEntityKey(string key) : base(key, GetPrefix(key)) + { + KeyIdentifier = KeyIdentifier.AddressableEntity; + var prefix = GetPrefix(key); + if (EntityKindEnum.System.ToKeyPrefix().Equals(prefix)) + Kind = EntityKindEnum.System; + else if (EntityKindEnum.Account.ToKeyPrefix().Equals(prefix)) + Kind = EntityKindEnum.Account; + else if (EntityKindEnum.Contract.ToKeyPrefix().Equals(prefix)) + Kind = EntityKindEnum.Contract; + } + + public AddressableEntityKey(byte[] key) : this( + key[0] == (byte)EntityKindEnum.System + ? EntityKindEnum.System.ToKeyPrefix() + CEP57Checksum.Encode(key.Slice(1)) + : (key[0] == (byte)EntityKindEnum.Account + ? EntityKindEnum.Account.ToKeyPrefix() + CEP57Checksum.Encode(key.Slice(1)) + : (key[0] == (byte)EntityKindEnum.Account + ? EntityKindEnum.Contract.ToKeyPrefix() + CEP57Checksum.Encode(key.Slice(1)) + : throw new Exception($"Wrong entity tag '{key[0]}' for AddressableEntityKey.")))) + { + } + + public AddressableEntityKey(BinaryReader reader) : base(null) + { + KeyIdentifier = KeyIdentifier.AddressableEntity; + var tag = reader.ReadByte(); + var addr = reader.ReadBytes(32); + Kind = (EntityKindEnum)tag; + Key = Kind.ToKeyPrefix() + Hex.ToHexString(addr); + } + + protected override byte[] _GetRawBytesFromKey(string key) + { + return this.GetBytes(); + } + + public override byte[] GetBytes() + { + var key = Key.Substring(Key.LastIndexOf('-') + 1); + var rawBytes = Hex.Decode(key); + var ms = new MemoryStream(); + ms.WriteByte((byte)this.Kind); + ms.Write(rawBytes); + + return ms.ToArray(); + } + + /// + /// Returns an EntityIdentifier object as defined in the RPC schema for an account hash key. + /// + public Dictionary GetEntityIdentifier() + { + return new Dictionary + { + { "EntityAddr", Key } + }; + } + + /// + /// Returns a PurseIdentifier object as defined in the RPC schema for an entity address + /// + public Dictionary GetPurseIdentifier() + { + return new Dictionary + { + { "main_purse_under_entity_addr", this.ToString() } + }; + } + + public class AddressableEntityKeyConverter : JsonConverter + { + public override AddressableEntityKey Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + try + { + return new AddressableEntityKey(reader.GetString()); + } + catch (Exception e) + { + throw new JsonException(e.Message); + } + } + + public override void Write( + Utf8JsonWriter writer, + AddressableEntityKey addressableEntity, + JsonSerializerOptions options) + { + writer.WriteStringValue(addressableEntity.Key); + } + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/GlobalStateKey/BalanceHoldKey.cs b/Casper.Network.SDK/Types/GlobalStateKey/BalanceHoldKey.cs new file mode 100644 index 0000000..2e28000 --- /dev/null +++ b/Casper.Network.SDK/Types/GlobalStateKey/BalanceHoldKey.cs @@ -0,0 +1,63 @@ +using System; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Casper.Network.SDK.Types +{ + public enum BalanceHoldTag + { + /// + /// Tag for gas variant. + /// + Gas = 0, + /// + /// Tag for processing variant. + /// + Processing = 1, + } + + public class BalanceHoldKey : GlobalStateKey + { + private const string KEYPREFIX = "balance-hold-"; + public static string KeyPrefix => KEYPREFIX; + + public BalanceHoldTag Tag { get; init; } + + public URef URef => new URef(RawBytes.Slice(1,33), AccessRights.NONE); + + public UInt64 BlockTime + { + get + { + var bytes = RawBytes.Slice(33); + if (!BitConverter.IsLittleEndian) + Array.Reverse(bytes); + return BitConverterExtensions.ToUInt64(bytes); + } + } + + public BalanceHoldKey(string key) : base(key, KEYPREFIX) + { + KeyIdentifier = KeyIdentifier.BalanceHold; + if (RawBytes.Length <= 0) + throw new Exception("Wrong key length."); + switch (RawBytes[0]) + { + case (byte)BalanceHoldTag.Gas: + Tag = BalanceHoldTag.Gas; + break; + case (byte)BalanceHoldTag.Processing: + Tag = BalanceHoldTag.Processing; + break; + default: + throw new Exception($"Wrong BalanceHold tag '{RawBytes[0]}'."); + } + + if (RawBytes.Length != 41) // tag + purse uref (32) + block time (8) + throw new Exception("Wrong key length for BalanceHold. Expected 41 bytes."); + } + + public BalanceHoldKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) + { + } + } +} diff --git a/Casper.Network.SDK/Types/GlobalStateKey/BidAddrKey.cs b/Casper.Network.SDK/Types/GlobalStateKey/BidAddrKey.cs new file mode 100644 index 0000000..55bd857 --- /dev/null +++ b/Casper.Network.SDK/Types/GlobalStateKey/BidAddrKey.cs @@ -0,0 +1,164 @@ +using System; +using Casper.Network.SDK.Utils; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Casper.Network.SDK.Types +{ + public enum BidAddrTag + { + /// + /// Unified BidAddr (refers to validator address). + /// + Unified = 0, //legacy type + /// + /// Validator BidAddr. + /// + Validator = 1, + /// + /// Delegator BidAddr, + /// + DelegatedAccount = 2, + /// + /// BidAddr for delegated purse bid. + /// + DelegatedPurse = 3, + /// + /// BidAddr for auction credit. + /// + Credit = 4, + /// + /// BidAddr for reserved delegation account bid. + /// + ReservedDelegationAccount = 5, + /// + /// BidAddr for reserved delegation purse bid. + /// + ReservedDelegationPurse = 6, + /// + /// BidAddr for unbonding accounts. + /// + UnbondAccount = 7, + /// + /// BidAddr for unbonding purses. + /// + UnbondPurse = 8, + } + + public class BidAddrKey : GlobalStateKey + { + public static string KEYPREFIX = "bid-addr-"; + + public BidAddrTag Tag { get; init; } + + /// + /// Unified BidAddr. + /// + public AccountHashKey Unified { get; init; } + + /// + /// The valicator address. + /// + public AccountHashKey Validator { get; init; } + + /// + /// The delegator address. + /// + public AccountHashKey DelegatorAccount { get; init; } + + /// + /// The delegator purse address. + /// + public string DelegatorPurseAddress { get; init; } + + /// + /// The era id. + /// + public ulong EraId { get; init; } + + public BidAddrKey(string key) : base(key, KEYPREFIX) + { + KeyIdentifier = KeyIdentifier.BidAddr; + var bytes = Hex.Decode(key.Substring(key.LastIndexOf('-') + 1)); + + if (bytes.Length == 0) + throw new Exception("Wrong key length."); + + switch (bytes[0]) + { + case (byte)BidAddrTag.Unified: + Tag = BidAddrTag.Unified; + if (bytes.Length != 33) + throw new Exception("Wrong key length for Unified BidAddr. Expected 33 bytes."); + Unified = new AccountHashKey(bytes.Slice(1, 33)); + break; + case (byte)BidAddrTag.Validator: + Tag = BidAddrTag.Validator; + if (bytes.Length != 33) + throw new Exception("Wrong key length for Validator BidAddr. Expected 33 bytes."); + Validator = new AccountHashKey(bytes.Slice(1, 33)); + break; + case (byte)BidAddrTag.DelegatedAccount: + Tag = BidAddrTag.DelegatedAccount; + if (bytes.Length != 65) + throw new Exception("Wrong key length for DelegatedAccount BidAddr. Expected 65 bytes."); + Validator = new AccountHashKey(bytes.Slice(1, 33)); + DelegatorAccount = new AccountHashKey(bytes.Slice(33)); + break; + case (byte)BidAddrTag.DelegatedPurse: + Tag = BidAddrTag.DelegatedPurse; + if (bytes.Length != 65) + throw new Exception("Wrong key length for DelegatedPurse BidAddr. Expected 65 bytes."); + Validator = new AccountHashKey(bytes.Slice(1, 33)); + DelegatorPurseAddress = Hex.ToHexString(bytes.Slice(33)); + break; + case (byte)BidAddrTag.Credit: + Tag = BidAddrTag.Credit; + if (bytes.Length != 41) + throw new Exception("Wrong key length for Credit BidAddr. Expected 41 bytes."); + Validator = new AccountHashKey(bytes.Slice(1, 33)); + EraId = BitConverterExtensions.ToUInt64(bytes.Slice(33)); + break; + case (byte)BidAddrTag.ReservedDelegationAccount: + Tag = BidAddrTag.ReservedDelegationAccount; + if (bytes.Length != 65) + throw new Exception("Wrong key length for ReservedDelegationAccount BidAddr. Expected 65 bytes."); + Validator = new AccountHashKey(bytes.Slice(1, 33)); + DelegatorAccount = new AccountHashKey(bytes.Slice(33)); + break; + case (byte)BidAddrTag.ReservedDelegationPurse: + Tag = BidAddrTag.ReservedDelegationPurse; + if (bytes.Length != 65) + throw new Exception("Wrong key length for ReservedDelegationPurse BidAddr. Expected 65 bytes."); + Validator = new AccountHashKey(bytes.Slice(1, 33)); + DelegatorPurseAddress = Hex.ToHexString(bytes.Slice(33)); + break; + case (byte)BidAddrTag.UnbondAccount: + Tag = BidAddrTag.UnbondAccount; + if (bytes.Length != 65) + throw new Exception("Wrong key length for UnbondAccount BidAddr. Expected 65 bytes."); + Validator = new AccountHashKey(bytes.Slice(1, 33)); + DelegatorAccount = new AccountHashKey(bytes.Slice(33)); + break; + case (byte)BidAddrTag.UnbondPurse: + Tag = BidAddrTag.UnbondPurse; + if (bytes.Length != 65) + throw new Exception("Wrong key length for UnbondPurse BidAddr. Expected 65 bytes."); + Validator = new AccountHashKey(bytes.Slice(1, 33)); + DelegatorPurseAddress = Hex.ToHexString(bytes.Slice(33)); + break; + default: + throw new Exception($"Wrong BidAddr tag '{bytes[0]}'."); + } + } + + public BidAddrKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) + { + } + + public static BidAddrKey FromValidatorKey(AccountHashKey accountHashKey) + { + return new BidAddrKey(KEYPREFIX + Hex.ToHexString(new byte[] {(byte)BidAddrTag.Validator}) + + accountHashKey.ToHexString()); + } + } +} diff --git a/Casper.Network.SDK/Types/GlobalStateKey/BlockGlobalAddrKey.cs b/Casper.Network.SDK/Types/GlobalStateKey/BlockGlobalAddrKey.cs new file mode 100644 index 0000000..35bdebf --- /dev/null +++ b/Casper.Network.SDK/Types/GlobalStateKey/BlockGlobalAddrKey.cs @@ -0,0 +1,70 @@ +using System; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Casper.Network.SDK.Types +{ + public enum BlockGlobalAddrTag + { + /// + /// Tag for block time variant. + /// + Time = 0, + /// + /// Tag for processing variant. + /// + MessageCount = 1, + } + + public static class BlockGlobalAddrTagExtensions + { + public static string ToKeyPrefix(this BlockGlobalAddrTag tag) + { + switch (tag) + { + case BlockGlobalAddrTag.Time: + return "block-time-"; + case BlockGlobalAddrTag.MessageCount: + return "block-message-count-"; + default: + return tag.ToString(); + } + } + } + + /// + /// Address for singleton values associated to specific block. These are values which are + /// calculated or set during the execution of a block such as the block timestamp, or the + /// total count of messages emitted during the execution of the block, and so on. + /// + public class BlockGlobalAddrKey : GlobalStateKey + { + public BlockGlobalAddrTag Tag { get; init; } + + private static string GetPrefix(string key) + { + if (key.StartsWith(BlockGlobalAddrTag.Time.ToKeyPrefix())) + return BlockGlobalAddrTag.Time.ToKeyPrefix(); + if (key.StartsWith(BlockGlobalAddrTag.MessageCount.ToKeyPrefix())) + return BlockGlobalAddrTag.MessageCount.ToKeyPrefix(); + + throw new Exception("Unexpected key prefix in NamedKeyKey: " + key); + } + + public BlockGlobalAddrKey(string key) : base(key, GetPrefix(key)) + { + KeyIdentifier = KeyIdentifier.BlockGlobal; + Tag = GetPrefix(key) == BlockGlobalAddrTag.Time.ToKeyPrefix() + ? BlockGlobalAddrTag.Time + : BlockGlobalAddrTag.MessageCount; + } + + public BlockGlobalAddrKey(byte[] key) : this( + key[0] == (byte)BlockGlobalAddrTag.Time + ? BlockGlobalAddrTag.Time.ToKeyPrefix() + Hex.ToHexString(key.Slice(1)) + : (key[0] == (byte)BlockGlobalAddrTag.MessageCount + ? BlockGlobalAddrTag.MessageCount.ToKeyPrefix() + Hex.ToHexString(key.Slice(1)) + : throw new Exception($"Wrong kind tag '{key[0]}' for BlockGlobalAddrKey."))) + { + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/GlobalStateKey/ByteCodeKey.cs b/Casper.Network.SDK/Types/GlobalStateKey/ByteCodeKey.cs new file mode 100644 index 0000000..e4f772a --- /dev/null +++ b/Casper.Network.SDK/Types/GlobalStateKey/ByteCodeKey.cs @@ -0,0 +1,65 @@ +using System; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Casper.Network.SDK.Types +{ + public enum ByteCodeKind { + /// + /// Empty byte code. + /// + Empty = 0, + /// + /// Byte code to be executed with the version 1 Casper execution engine. + /// + V1CasperWasm = 1, + } + + public static class ByteCodeKindExtensions + { + public static string ToKeyPrefix(this ByteCodeKind kind) + { + switch (kind) + { + case ByteCodeKind.Empty: + return "byte-code-empty-"; + case ByteCodeKind.V1CasperWasm: + return "byte-code-v1-wasm-"; + default: + return kind.ToString(); + } + } + } + + public class ByteCodeKey : GlobalStateKey + { + public ByteCodeKind Kind { get; init; } + + private static string GetPrefix(string key) + { + if (key.StartsWith(ByteCodeKind.Empty.ToKeyPrefix())) + return ByteCodeKind.Empty.ToKeyPrefix(); + if (key.StartsWith(ByteCodeKind.V1CasperWasm.ToKeyPrefix())) + return ByteCodeKind.V1CasperWasm.ToKeyPrefix(); + throw new Exception("Unexpected key prefix in ByteCodeKey: " + key); + } + + public ByteCodeKey(string key) : base(key, ByteCodeKey.GetPrefix(key)) + { + KeyIdentifier = KeyIdentifier.ByteCode; + var prefix = GetPrefix(key); + if (ByteCodeKind.Empty.ToKeyPrefix().Equals(prefix)) + Kind = ByteCodeKind.Empty; + else if (ByteCodeKind.V1CasperWasm.ToKeyPrefix().Equals(prefix)) + Kind = ByteCodeKind.V1CasperWasm; + } + + public ByteCodeKey(byte[] key) : this( + key[0] == (byte)ByteCodeKind.Empty + ? ByteCodeKind.Empty.ToKeyPrefix() + Hex.ToHexString(key.Slice(1)) + : (key[0] == (byte)ByteCodeKind.V1CasperWasm + ? ByteCodeKind.V1CasperWasm.ToKeyPrefix() + Hex.ToHexString(key.Slice(1)) + : throw new Exception($"Wrong kind tag '{key[0]}' for ByteCodeKey."))) + { + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/GlobalStateKey/EntryPointKey.cs b/Casper.Network.SDK/Types/GlobalStateKey/EntryPointKey.cs new file mode 100644 index 0000000..9865f31 --- /dev/null +++ b/Casper.Network.SDK/Types/GlobalStateKey/EntryPointKey.cs @@ -0,0 +1,47 @@ +using System; +using Casper.Network.SDK.Utils; + +namespace Casper.Network.SDK.Types +{ + public class EntryPointKey : GlobalStateKey + { + public static string ENTRYPOINT_PREFIX = "entry-point-"; + public static string V1_PREFIX = "v1-"; + public static string V2_PREFIX = "v2-"; + + public AddressableEntityKey AddressableEntity { get; init; } + + public string NameHash { get; init; } + + public UInt32 Separator { get; init; } + + public EntryPointKey(string key) : base(key) + { + KeyIdentifier = KeyIdentifier.EntryPoint; + + if (!key.StartsWith(ENTRYPOINT_PREFIX)) + throw new ArgumentException($"Key not valid. It should start with '{ENTRYPOINT_PREFIX}'."); + key = key.Substring(ENTRYPOINT_PREFIX.Length); + + if (key.StartsWith(V1_PREFIX)) + { + key = key.Substring(V1_PREFIX.Length); + var parts = key.Split('-'); + if (parts.Length != 4) + throw new Exception("Key not valid. It should have an entity address and a name hash."); + + AddressableEntity = new AddressableEntityKey($"{parts[0]}-{parts[1]}-{parts[2]}"); + NameHash = parts[3]; + } + else if (key.StartsWith(V2_PREFIX)) + { + throw new Exception($"entry-point-v2 not yet supported. {key}."); + } + } + + public EntryPointKey(byte[] key) : base(null) + { + throw new Exception($"entry-point key from bytes not yet supported. {key}."); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/GlobalStateKey.cs b/Casper.Network.SDK/Types/GlobalStateKey/GlobalStateKey.cs similarity index 73% rename from Casper.Network.SDK/Types/GlobalStateKey.cs rename to Casper.Network.SDK/Types/GlobalStateKey/GlobalStateKey.cs index aef55ce..1655fed 100644 --- a/Casper.Network.SDK/Types/GlobalStateKey.cs +++ b/Casper.Network.SDK/Types/GlobalStateKey/GlobalStateKey.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; @@ -74,14 +75,47 @@ public enum KeyIdentifier /// A `Key` variant under which a registry of checksums are stored. /// ChecksumRegistry = 0x0e, + /// + /// A `Key` under which bid information is stored. + /// + BidAddr = 0x0f, + /// + /// A `Key` under which package information is stored. + /// + Package = 0x10, + /// + /// A `Key` under which an addressable entity is stored. + /// + AddressableEntity = 0x11, + /// + /// A `Key` under which a byte code record is stored. + /// + ByteCode = 0x12, + /// + /// A `Key` under which a message is stored. + /// + Message = 0x13, + /// + /// A `Key` under which a single named key entry is stored. + /// + NamedKey = 0x14, + BlockGlobal = 0x15, + /// + /// A `Key` under which a hold on a purse balance is stored. + /// + BalanceHold = 0x16, + /// + /// A `Key` under which a entrypoint record is written. + /// + EntryPoint = 0x17, } - + /// /// Base class for the different global state keys. /// public abstract class GlobalStateKey { - protected readonly string Key; + protected string Key { get; init; } public KeyIdentifier KeyIdentifier { get; init; } @@ -108,12 +142,12 @@ protected GlobalStateKey(string key, string keyPrefix) if (checksumResult == CEP57Checksum.InvalidChecksum) throw new ArgumentException("Global State Key checksum mismatch."); - Key = keyPrefix + CEP57Checksum.Encode(bytes); + Key = keyPrefix + Hex.ToHexString(bytes); } public string ToHexString() { - return CEP57Checksum.Encode(RawBytes); + return Hex.ToHexString(RawBytes); } /// @@ -139,8 +173,12 @@ public static GlobalStateKey FromString(string value) return new TransferKey(value); if (value.StartsWith("deploy-")) return new DeployInfoKey(value); + if (value.StartsWith("balance-hold-")) + return new BalanceHoldKey(value); if (value.StartsWith("balance-")) return new BalanceKey(value); + if (value.StartsWith("bid-addr-")) + return new BidAddrKey(value); if (value.StartsWith("bid")) return new BidKey(value); if (value.StartsWith("withdraw")) @@ -159,7 +197,22 @@ public static GlobalStateKey FromString(string value) return new ChainspecRegistryKey(value); if (value.StartsWith("checksum-registry-")) return new ChecksumRegistryKey(value); - + if (value.StartsWith("package-")) + return new PackageKey(value); + if (value.StartsWith("block-message-count-")) + return new BlockGlobalAddrKey(value); + if (value.StartsWith("block-time-")) + return new BlockGlobalAddrKey(value); + if (value.StartsWith("entity-")) + return new AddressableEntityKey(value); + if (value.StartsWith("named-key-")) + return new NamedKeyKey(value); + if (value.StartsWith("byte-code-")) + return new ByteCodeKey(value); + if (value.StartsWith("message-")) + return new MessageKey(value); + if (value.StartsWith("entry-point-")) + return new EntryPointKey(value); throw new ArgumentException($"Key not valid. Unknown key prefix in \"{value}\"."); } @@ -171,21 +224,30 @@ public static GlobalStateKey FromBytes(byte[] bytes) { return bytes[0] switch { - 0x00 => new AccountHashKey("account-hash-" + CEP57Checksum.Encode(bytes.Slice(1))), - 0x01 => new HashKey("hash-" + CEP57Checksum.Encode(bytes.Slice(1))), + 0x00 => new AccountHashKey("account-hash-" + Hex.ToHexString(bytes.Slice(1))), + 0x01 => new HashKey("hash-" + Hex.ToHexString(bytes.Slice(1))), 0x02 => new URef(bytes.Slice(1)), - 0x03 => new TransferKey("transfer-" + CEP57Checksum.Encode(bytes.Slice(1))), - 0x04 => new DeployInfoKey("deploy-" + CEP57Checksum.Encode(bytes.Slice(1))), + 0x03 => new TransferKey("transfer-" + Hex.ToHexString(bytes.Slice(1))), + 0x04 => new DeployInfoKey("deploy-" + Hex.ToHexString(bytes.Slice(1))), 0x05 => new EraInfoKey("era-" + BitConverter.ToInt64(bytes, 1)), - 0x06 => new BalanceKey("balance-" + CEP57Checksum.Encode(bytes.Slice(1))), - 0x07 => new BidKey("bid-" + CEP57Checksum.Encode(bytes.Slice(1))), - 0x08 => new WithdrawKey("withdraw-" + CEP57Checksum.Encode(bytes.Slice(1))), - 0x09 => new DictionaryKey("dictionary-" + CEP57Checksum.Encode(bytes.Slice(1))), - 0x0a => new SystemContractRegistryKey("system-contract-registry-" + CEP57Checksum.Encode(bytes.Slice(1))), - 0x0b => new EraSummaryKey("era-summary-" + CEP57Checksum.Encode(bytes.Slice(1))), - 0x0c => new UnbondKey("unbond-" + CEP57Checksum.Encode(bytes.Slice(1))), - 0x0d => new ChainspecRegistryKey("chainspec-registry-" + CEP57Checksum.Encode(bytes.Slice(1))), - 0x0e => new ChecksumRegistryKey("checksum-registry-" + CEP57Checksum.Encode(bytes.Slice(1))), + 0x06 => new BalanceKey("balance-" + Hex.ToHexString(bytes.Slice(1))), + 0x07 => new BidKey("bid-" + Hex.ToHexString(bytes.Slice(1))), + 0x08 => new WithdrawKey("withdraw-" + Hex.ToHexString(bytes.Slice(1))), + 0x09 => new DictionaryKey("dictionary-" + Hex.ToHexString(bytes.Slice(1))), + 0x0a => new SystemContractRegistryKey("system-contract-registry-" + Hex.ToHexString(bytes.Slice(1))), + 0x0b => new EraSummaryKey("era-summary-" + Hex.ToHexString(bytes.Slice(1))), + 0x0c => new UnbondKey("unbond-" + Hex.ToHexString(bytes.Slice(1))), + 0x0d => new ChainspecRegistryKey("chainspec-registry-" + Hex.ToHexString(bytes.Slice(1))), + 0x0e => new ChecksumRegistryKey("checksum-registry-" + Hex.ToHexString(bytes.Slice(1))), + 0x0f => new BidAddrKey("bid-addr-" + Hex.ToHexString(bytes.Slice(1))), + 0x10 => new PackageKey("package-" + Hex.ToHexString(bytes.Slice(1))), + 0x11 => new AddressableEntityKey(bytes.Slice(1)), + 0x12 => new ByteCodeKey(bytes.Slice(1)), + 0x13 => new MessageKey(bytes.Slice(1)), + 0x14 => new NamedKeyKey(bytes.Slice(1)), + 0x15 => new BlockGlobalAddrKey(bytes.Slice(1)), + 0x16 => new BalanceHoldKey(bytes.Slice(1)), + 0x17 => new EntryPointKey(bytes.Slice(1)), _ => throw new ArgumentException($"Unknown key identifier '{bytes[0]}'") }; } @@ -207,6 +269,22 @@ public override string ToString() return Key; } + public override bool Equals(object obj) + { + //Check for null and compare run-time types. + if ((obj == null) || ! this.GetType().Equals(obj.GetType())) + { + return false; + } + + return Key.ToLowerInvariant().Equals(((GlobalStateKey) obj).Key.ToLowerInvariant()); + } + + public override int GetHashCode() + { + return Key.ToLowerInvariant().GetHashCode(); + } + /// /// Json converter class to serialize/deserialize an object derived from /// GlobalStateKey to/from Json @@ -230,7 +308,16 @@ public override bool CanConvert(Type typeToConvert) typeToConvert == typeof(EraSummaryKey) || typeToConvert == typeof(UnbondKey) || typeToConvert == typeof(ChainspecRegistryKey) || - typeToConvert == typeof(ChecksumRegistryKey); + typeToConvert == typeof(ChecksumRegistryKey) || + typeToConvert == typeof(BidAddrKey) || + typeToConvert == typeof(PackageKey) || + typeToConvert == typeof(AddressableEntityKey) || + typeToConvert == typeof(ByteCodeKey) || + typeToConvert == typeof(MessageKey) || + typeToConvert == typeof(NamedKeyKey) || + typeToConvert == typeof(BlockGlobalAddrKey) || + typeToConvert == typeof(BalanceHoldKey) || + typeToConvert == typeof(EntryPointKey); } public override JsonConverter CreateConverter( @@ -281,7 +368,7 @@ public override void Write( /// Stores an account in the global state. /// Format: 32-byte length with prefix 'account-hash-'. /// - public class AccountHashKey : GlobalStateKey + public class AccountHashKey : GlobalStateKey, IPurseIdentifier, IEntityIdentifier { public static string KEYPREFIX = "account-hash-"; @@ -294,6 +381,32 @@ public AccountHashKey(PublicKey publicKey) : base(publicKey.GetAccountHash(), KEYPREFIX) { } + + public AccountHashKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) + { + } + + /// + /// Returns a PurseIdentifier object as defined in the RPC schema for an account hash key. + /// + public Dictionary GetPurseIdentifier() + { + return new Dictionary + { + {"main_purse_under_account_hash", this.ToString()} + }; + } + + /// + /// Returns an EntityIdentifier object as defined in the RPC schema for an account hash key. + /// + public Dictionary GetEntityIdentifier() + { + return new Dictionary + { + {"AccountHash", this.ToString()} + }; + } } /// @@ -309,7 +422,7 @@ public HashKey(string key) : base(key, KEYPREFIX) KeyIdentifier = KeyIdentifier.Hash; } - public HashKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + public HashKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) { } } @@ -327,7 +440,7 @@ public TransferKey(string key) : base(key, KEYPREFIX) KeyIdentifier = KeyIdentifier.Transfer; } - public TransferKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + public TransferKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) { } } @@ -345,7 +458,7 @@ public DeployInfoKey(string key) : base(key, KEYPREFIX) KeyIdentifier = KeyIdentifier.DeployInfo; } - public DeployInfoKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + public DeployInfoKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) { } } @@ -410,7 +523,7 @@ public BalanceKey(string key) : base(key, KEYPREFIX) KeyIdentifier = KeyIdentifier.Balance; } - public BalanceKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + public BalanceKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) { } } @@ -428,7 +541,7 @@ public BidKey(string key) : base(key, KEYPREFIX) KeyIdentifier = KeyIdentifier.Bid; } - public BidKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + public BidKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) { } } @@ -446,7 +559,7 @@ public WithdrawKey(string key) : base(key, KEYPREFIX) KeyIdentifier = KeyIdentifier.Withdraw; } - public WithdrawKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + public WithdrawKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) { } } @@ -464,7 +577,7 @@ public DictionaryKey(string key) : base(key, KEYPREFIX) KeyIdentifier = KeyIdentifier.Dictionary; } - public DictionaryKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + public DictionaryKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) { } } @@ -488,7 +601,7 @@ public SystemContractRegistryKey(string key) : base(key, KEYPREFIX) KeyIdentifier = KeyIdentifier.SystemContractRegistry; } - public SystemContractRegistryKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + public SystemContractRegistryKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) { } } @@ -520,7 +633,7 @@ public UnbondKey(string key) : base(key, KEYPREFIX) KeyIdentifier = KeyIdentifier.Unbond; } - public UnbondKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + public UnbondKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) { } } @@ -544,7 +657,7 @@ public ChainspecRegistryKey(string key) : base(key, KEYPREFIX) KeyIdentifier = KeyIdentifier.ChainspecRegistry; } - public ChainspecRegistryKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + public ChainspecRegistryKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) { } } @@ -567,7 +680,7 @@ public ChecksumRegistryKey(string key) : base(key, KEYPREFIX) KeyIdentifier = KeyIdentifier.ChecksumRegistry; } - public ChecksumRegistryKey(byte[] key) : this(KEYPREFIX + CEP57Checksum.Encode(key)) + public ChecksumRegistryKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) { } } diff --git a/Casper.Network.SDK/Types/GlobalStateKey/MessageKey.cs b/Casper.Network.SDK/Types/GlobalStateKey/MessageKey.cs new file mode 100644 index 0000000..1be7f6f --- /dev/null +++ b/Casper.Network.SDK/Types/GlobalStateKey/MessageKey.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; +using Casper.Network.SDK.Utils; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Casper.Network.SDK.Types +{ + public class MessageKey : GlobalStateKey + { + private const string KEY_PREFIX = "message-"; + private const string TOPIC_PREFIX = "topic-"; + + public AddressableEntityKey AddressableEntity { get; init; } + + public string TopicHash { get; init; } + + public UInt32? Index { get; init; } + + public MessageKey(string key) : base(key) + { + KeyIdentifier = KeyIdentifier.Message; + + if (!key.StartsWith(KEY_PREFIX)) + throw new ArgumentException($"Key not valid. It should start with '{KEY_PREFIX}'."); + key = key.Substring(KEY_PREFIX.Length); + + if (key.StartsWith(TOPIC_PREFIX)) + { + key = key.Substring(TOPIC_PREFIX.Length); + var parts = key.Split('-'); + if (parts.Length != 4) + throw new Exception("Key not valid. It should have an entity address and a topic hash."); + + AddressableEntity = new AddressableEntityKey($"{parts[0]}-{parts[1]}-{parts[2]}"); + TopicHash = parts[3]; + } + else + { + var parts = key.Split('-'); + if (parts.Length != 5) + throw new Exception("Key not valid. It should have an entity address, a topic hash, and a message index."); + + AddressableEntity = new AddressableEntityKey($"{parts[0]}-{parts[1]}-{parts[2]}"); + TopicHash = parts[3]; + + if(parts[4].Length == 0) + throw new Exception("Key not valid. Expected a non-empty message index."); + Index = Convert.ToUInt32(parts[4], 16); + } + } + + public MessageKey(byte[] key) : base(null) + { + KeyIdentifier = KeyIdentifier.Message; + + var ms = new MemoryStream(key); + var reader = new BinaryReader(ms); + + AddressableEntity = new AddressableEntityKey(reader); + + var topic = reader.ReadBytes(32); + TopicHash = Hex.ToHexString(topic); + + var optionIndexTag = reader.ReadByte(); + if (optionIndexTag == 0x01) + Index = reader.ReadUInt32(); + + Key = KEY_PREFIX + + (Index.HasValue ? "" : TOPIC_PREFIX) + + AddressableEntity.ToString() + "-" + + TopicHash + + (Index.HasValue ? "-" + Index.Value.ToString("x") : ""); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/GlobalStateKey/NamedKeyKey.cs b/Casper.Network.SDK/Types/GlobalStateKey/NamedKeyKey.cs new file mode 100644 index 0000000..bf70dc2 --- /dev/null +++ b/Casper.Network.SDK/Types/GlobalStateKey/NamedKeyKey.cs @@ -0,0 +1,44 @@ +using System; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Casper.Network.SDK.Types +{ + public class NamedKeyKey : GlobalStateKey + { + private const string NAMEDKEYPREFIX = "named-key-"; + + public AddressableEntityKey AddressableEntity { get; init; } + + private static string GetPrefix(string key) + { + if (key.StartsWith(NAMEDKEYPREFIX+EntityKindEnum.System.ToKeyPrefix())) + return NAMEDKEYPREFIX+EntityKindEnum.System.ToKeyPrefix(); + if (key.StartsWith(NAMEDKEYPREFIX+EntityKindEnum.Account.ToKeyPrefix())) + return NAMEDKEYPREFIX+EntityKindEnum.Account.ToKeyPrefix(); + if (key.StartsWith(NAMEDKEYPREFIX+EntityKindEnum.Contract.ToKeyPrefix())) + return NAMEDKEYPREFIX+EntityKindEnum.Contract.ToKeyPrefix(); + + throw new Exception("Unexpected key prefix in NamedKeyKey: " + key); + } + + public NamedKeyKey(string key) : base(key, NamedKeyKey.GetPrefix(key)) + { + KeyIdentifier = KeyIdentifier.NamedKey; + + var subkey = key.Substring(NAMEDKEYPREFIX.Length); + subkey = subkey.Remove(subkey.LastIndexOf('-')); + AddressableEntity = new AddressableEntityKey(subkey); + } + + private static string _getString(byte[] key) + { + var entityAddress = new AddressableEntityKey(key.Slice(0, 33)); + var addr = Hex.ToHexString(key.Slice(33)); + return $"{NAMEDKEYPREFIX}{entityAddress}-{addr}"; + } + + public NamedKeyKey(byte[] key) : this(_getString(key)) + { + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/GlobalStateKey/PackageKey.cs b/Casper.Network.SDK/Types/GlobalStateKey/PackageKey.cs new file mode 100644 index 0000000..71b3a7c --- /dev/null +++ b/Casper.Network.SDK/Types/GlobalStateKey/PackageKey.cs @@ -0,0 +1,18 @@ +using Org.BouncyCastle.Utilities.Encoders; + +namespace Casper.Network.SDK.Types +{ + public class PackageKey : GlobalStateKey + { + public static string KEYPREFIX = "package-"; + + public PackageKey(string key) : base(key, KEYPREFIX) + { + KeyIdentifier = KeyIdentifier.Package; + } + + public PackageKey(byte[] key) : this(KEYPREFIX + Hex.ToHexString(key)) + { + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/IEntityIdentifier.cs b/Casper.Network.SDK/Types/IEntityIdentifier.cs new file mode 100644 index 0000000..289ae9c --- /dev/null +++ b/Casper.Network.SDK/Types/IEntityIdentifier.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace Casper.Network.SDK.Types +{ + public interface IEntityIdentifier + { + public Dictionary GetEntityIdentifier(); + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/IPurseIdentifier.cs b/Casper.Network.SDK/Types/IPurseIdentifier.cs new file mode 100644 index 0000000..1849501 --- /dev/null +++ b/Casper.Network.SDK/Types/IPurseIdentifier.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Casper.Network.SDK.Types +{ + /// + /// A PublicKey, AccountHashKey, AddressableEntityKey or URef object that identifies a purse. + /// For keys, the main purse under the key is identified automatically. + /// + public interface IPurseIdentifier + { + public Dictionary GetPurseIdentifier(); + } +} diff --git a/Casper.Network.SDK/Types/InitiatorAddr.cs b/Casper.Network.SDK/Types/InitiatorAddr.cs new file mode 100644 index 0000000..ecfbe37 --- /dev/null +++ b/Casper.Network.SDK/Types/InitiatorAddr.cs @@ -0,0 +1,89 @@ +using System; +using System.Text.Json.Serialization; +using Casper.Network.SDK.ByteSerializers; + +namespace Casper.Network.SDK.Types +{ + /// + /// The address of the initiator of a TransactionV1 + /// + public class InitiatorAddr + { + /// + /// The public key of the initiator + /// + [JsonPropertyName("PublicKey")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey PublicKey { get; init; } + + /// + /// The account hash derived from the public key of the initiator + /// + [JsonPropertyName("AccountHash")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] + public AccountHashKey AccountHash { get; init; } + + public InitiatorAddr() + { + } + + public InitiatorAddr(PublicKey publicKey) + { + this.PublicKey = publicKey; + } + + public InitiatorAddr(AccountHashKey accountHash) + { + this.AccountHash = accountHash; + } + + public static InitiatorAddr FromPublicKey(PublicKey publicKey) + { + return new InitiatorAddr(publicKey); + } + + public static InitiatorAddr FromAccountHash(AccountHashKey accountHashKey) + { + return new InitiatorAddr(accountHashKey); + } + + public override string ToString() + { + return PublicKey != null + ? PublicKey.ToString() + : AccountHash?.ToString(); + } + + public string ToHexString() + { + return PublicKey != null + ? PublicKey.ToString() + : AccountHash?.ToHexString(); + } + + const ushort TAG_FIELD_INDEX = 0; + const byte PUBLIC_KEY_VARIANT_TAG = 0; + const ushort PUBLIC_KEY_FIELD_INDEX = 1; + const byte ACCOUNT_HASH_VARIANT_TAG = 1; + const ushort ACCOUNT_HASH_FIELD_INDEX = 1; + + public byte[] ToBytes() + { + if(PublicKey != null) + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] { PUBLIC_KEY_VARIANT_TAG }) + .AddField(PUBLIC_KEY_FIELD_INDEX, CLValue.PublicKey(PublicKey)) + .GetBytes(); + + if(AccountHash != null) + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] { ACCOUNT_HASH_VARIANT_TAG }) + .AddField(PUBLIC_KEY_FIELD_INDEX, AccountHash.RawBytes) + .GetBytes(); + + throw new Exception("Unable to serialize initiator addr"); + } + } +} diff --git a/Casper.Network.SDK/Types/Message.cs b/Casper.Network.SDK/Types/Message.cs new file mode 100644 index 0000000..14c23e5 --- /dev/null +++ b/Casper.Network.SDK/Types/Message.cs @@ -0,0 +1,80 @@ +using System; +using System.Text.Json.Serialization; + +namespace Casper.Network.SDK.Types +{ + /// + /// Summary of a message topic that will be stored in global state. + /// + public class MessageTopicSummary + { + /// + /// Block timestamp in which these messages were emitted. + /// + [JsonPropertyName("blocktime")] + public UInt64 BlockTime { get; init; } + + /// + /// Number of messages in this topic. + /// + [JsonPropertyName("message_count")] + public UInt32 MessageCount { get; init; } + } + + /// + /// The payload of a message. + /// + public class MessagePayload + { + /// + /// Human readable string message. + /// + public string String { get; init; } + /// + /// Message represented as raw bytes. + /// + public string Bytes { get; init; } + } + + /// + /// Message that was emitted by an addressable entity during execution. + /// + public class Message + { + /// + /// The identity of the entity that produced the message. + /// + [JsonPropertyName("hash_addr")] + public string HashAddr { get; init; } + + /// + /// The payload of the message. + /// + [JsonPropertyName("message")] + public MessagePayload MessagePayload { get; init; } + + /// + /// The name of the topic on which the message was emitted on. + /// + [JsonPropertyName("topic_name")] + public string TopicName { get; init; } + + /// + /// The hash of the name of the topic. + /// + [JsonPropertyName("topic_name_hash")] + public string TopicNameHash { get; init; } + + /// + /// Message index in the topic. + /// + [JsonPropertyName("topic_index")] + public uint TopicIndex { get; init; } + + /// + /// Message index in the block. + /// + [JsonPropertyName("block_index")] + public ulong BlockIndex { get; init; } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/NamedKey.cs b/Casper.Network.SDK/Types/NamedKey.cs index f7a0db2..8d4b052 100644 --- a/Casper.Network.SDK/Types/NamedKey.cs +++ b/Casper.Network.SDK/Types/NamedKey.cs @@ -20,4 +20,22 @@ public class NamedKey [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] public GlobalStateKey Key { get; init; } } + + /// + /// A named key in an addressable entity. + /// + public class NamedKeyValue + { + /// + /// The name of the entry. + /// + [JsonPropertyName("name")] + public CLValue Name { get; init; } + + /// + /// The value of the entry: a casper `Key` type. + /// + [JsonPropertyName("named_key")] + public CLValue Key { get; init; } + } } \ No newline at end of file diff --git a/Casper.Network.SDK/Types/Package.cs b/Casper.Network.SDK/Types/Package.cs new file mode 100644 index 0000000..3715f1b --- /dev/null +++ b/Casper.Network.SDK/Types/Package.cs @@ -0,0 +1,123 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using Casper.Network.SDK.Converters; + +namespace Casper.Network.SDK.Types +{ + /// + /// Major element of `ProtocolVersion` combined with `EntityVersion`. + /// + public class EntityVersion + { + /// + /// Automatically incremented value for a contract version within a major `ProtocolVersion`. + /// + [JsonPropertyName("entity_version")] + public uint Version { get; init; } + + /// + /// Major element of `ProtocolVersion` a `ContractVersion` is compatible with. + /// + [JsonPropertyName("protocol_version_major")] + public uint ProtocolVersionMajor { get; init; } + } + + public enum PackageStatus + { + /// + /// The package is locked and cannot be versioned. + /// + Locked, + + /// + /// The package is unlocked and can be versioned. + /// + Unlocked, + } + + public class EntityVersionAndHash + { + /// + /// Major element of `ProtocolVersion` combined with `EntityVersion`. + /// + [JsonPropertyName("entity_version_key")] + public EntityVersion EntityVersion { get; init; } + + /// + /// The hex-encoded address of the addressable entity. + /// + [JsonPropertyName("entity_addr")] + [JsonConverter(typeof(AddressableEntityKey.AddressableEntityKeyConverter))] + public AddressableEntityKey AddressableEntity { get; init; } + } + + public class NamedUserGroup + { + /// + /// The hex-encoded address of the addressable entity. + /// + [JsonPropertyName("group_name")] + public string Name { get; init; } + + /// + /// List of URefs associated with the group. + /// + [JsonPropertyName("group_users")] + [JsonConverter(typeof(GenericListConverter))] + public List Users { get; init; } + } + + /// + /// Entity definition, metadata, and security container. + /// + public class Package + { + /// + /// All versions (enabled and disabled). + /// + [JsonPropertyName("versions")] + public List Versions { get; init; } + + /// + /// Collection of disabled entity versions. The runtime will not permit disabled entity versions to be executed. + /// + [JsonPropertyName("disabled_versions")] + public List DisabledVersions { get; init; } + + /// + /// Mapping maintaining the set of URefs associated with each "user group". This can be used + /// to control access to methods in a particular version of the entity. A method is callable + /// by any context which "knows" any of the URefs associated with the method's user group. + /// + [JsonPropertyName("groups")] + public List Groups { get; init; } + + /// + /// The current state of the contract package. + /// + [JsonPropertyName("lock_status")] + [JsonConverter(typeof(JsonStringEnumConverter))] + public LockStatus LockStatus { get; init; } + + public static Package FromContractPackage(ContractPackage contractPackage) + { + return new Package() + { + Versions = contractPackage.Versions.Select(v => new EntityVersionAndHash() + { + EntityVersion = new EntityVersion() + { Version = v.Version, ProtocolVersionMajor = v.ProtocolVersionMajor }, + AddressableEntity = new AddressableEntityKey("entity-contract-" + v.Hash.Replace("contract-", "")), + }).ToList(), + Groups = contractPackage.Groups.Select(g => new NamedUserGroup() + { + Name = g.Label, + Users = g.Keys + }) + .ToList(), + LockStatus = contractPackage.LockStatus, + }; + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/PackageIdentifier.cs b/Casper.Network.SDK/Types/PackageIdentifier.cs new file mode 100644 index 0000000..6abe844 --- /dev/null +++ b/Casper.Network.SDK/Types/PackageIdentifier.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; + +namespace Casper.Network.SDK.Types +{ + /// + /// Identifier of a package by its contract package hash or its addressable entity package address. + /// + public interface IPackageIdentifier + { + public Dictionary GetPackageIdentifier(); + } + + /// + /// Identifier of a package by its contract package hash or its addressable entity package address. + /// + public class PackageIdentifier : IPackageIdentifier + { + private readonly string? _contractPackageHash; + private readonly string? _packageAddr; + + private PackageIdentifier(string? contractPackageHash = null, string? packageAddr = null) + { + _contractPackageHash = contractPackageHash; + _packageAddr = packageAddr; + } + + public static PackageIdentifier FromContractPackageHash(string contractPackageHash) => + new(contractPackageHash: contractPackageHash); + + public static PackageIdentifier FromPackageAddr(string packageAddr) => + new(packageAddr: packageAddr); + + /// + /// Returns a PackageIdentifier object as defined in the RPC schema. + /// + public Dictionary GetPackageIdentifier() + { + return !string.IsNullOrWhiteSpace(_contractPackageHash) + ? new Dictionary { { "ContractPackageHash", _contractPackageHash! } } + : new Dictionary { { "PackageAddr", _packageAddr! } }; + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/Prepayment.cs b/Casper.Network.SDK/Types/Prepayment.cs new file mode 100644 index 0000000..d009802 --- /dev/null +++ b/Casper.Network.SDK/Types/Prepayment.cs @@ -0,0 +1,19 @@ +using System.Text.Json.Serialization; + +namespace Casper.Network.SDK.Types +{ + /// + /// Container for bytes recording location, type and data for a gas pre-payment. + /// + public class Prepayment + { + [JsonPropertyName("receipt")] + public string Receipt { get; init; } + + [JsonPropertyName("prepayment_kind")] + public byte PrepaymentKind { get; init; } + + [JsonPropertyName("prepayment_data")] + public string PrepaymentData { get; init; } + } +} diff --git a/Casper.Network.SDK/Types/PricingMode.cs b/Casper.Network.SDK/Types/PricingMode.cs new file mode 100644 index 0000000..4f7ab56 --- /dev/null +++ b/Casper.Network.SDK/Types/PricingMode.cs @@ -0,0 +1,253 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Casper.Network.SDK.ByteSerializers; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Casper.Network.SDK.Types +{ + public enum PricingModeType + { + /// + /// The original payment model, where the creator of the transaction specifies how much they will pay, + /// at what gas price. + /// + PaymentLimited = 0, + + /// + /// The cost of the transaction is determined by the cost table, per the transaction category. + /// + Fixed = 1, + + /// + /// The payment for this transaction was previously reserved, as proven by the receipt hash + /// (this is for future use, not currently supported by the Casper network). + /// + Prepaid = 2, + } + + public interface IPricingMode + { +#if NET7_0_OR_GREATER + public bool IsPaymentLimited => this is PaymentLimitedPricingMode; + public bool IsFixed => this is FixedPricingMode; + public bool IsPrepaid => this is PrepaidPricingMode; +#endif + + public byte[] ToBytes(); + } + + public class PaymentLimitedPricingMode : IPricingMode + { + /// + /// Payment amount. + /// + [JsonPropertyName("payment_amount")] + public ulong PaymentAmount { get; init; } + + /// + /// Standard payment. + /// + [JsonPropertyName("standard_payment")] + public bool StandardPayment { get; init; } + + /// + /// User-specified gas_price tolerance (minimum 1). This is interpreted to mean "do not include this + /// transaction in a block if the current gas price is greater than this number". + /// + [JsonPropertyName("gas_price_tolerance")] + public byte GasPriceTolerance { get; init; } + + const ushort TAG_FIELD_INDEX = 0; + const byte PAYMENT_LIMITED_VARIANT_TAG = 0; + const ushort PAYMENT_LIMITED_PAYMENT_AMOUNT_INDEX = 1; + const ushort PAYMENT_LIMITED_GAS_PRICE_TOLERANCE_INDEX = 2; + const ushort PAYMENT_LIMITED_STANDARD_PAYMENT_INDEX = 3; + + public byte[] ToBytes() + { + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, CLValue.U8(PAYMENT_LIMITED_VARIANT_TAG)) + .AddField(PAYMENT_LIMITED_PAYMENT_AMOUNT_INDEX, CLValue.U64(PaymentAmount)) + .AddField(PAYMENT_LIMITED_GAS_PRICE_TOLERANCE_INDEX, CLValue.U8(GasPriceTolerance)) + .AddField(PAYMENT_LIMITED_STANDARD_PAYMENT_INDEX, CLValue.Bool(StandardPayment)) + .GetBytes(); + } + } + + public class FixedPricingMode : IPricingMode + { + /// + /// User-specified additional computation factor (minimum 0). If "0" is provided, + /// no additional logic is applied to the computation limit. Each value above "0" + /// tells the node that it needs to treat the transaction as if it uses more gas + /// than it's serialized size indicates. Each "1" will increase the "wasm lane" + /// size bucket for this transaction by 1. So if the size of the transaction + /// indicates bucket "0" and "additional_computation_factor = 2", the transaction + /// will be treated as a "2". + /// + [JsonPropertyName("additional_computation_factor")] + public byte AdditionalComputationFactor { get; init; } + + /// + /// User-specified gas_price tolerance (minimum 1). This is interpreted to mean "do not include this + /// transaction in a block if the current gas price is greater than this number". + /// + [JsonPropertyName("gas_price_tolerance")] + public byte GasPriceTolerance { get; init; } + + const ushort TAG_FIELD_INDEX = 0; + const byte FIXED_VARIANT_TAG = 1; + const ushort FIXED_GAS_PRICE_TOLERANCE_INDEX = 1; + const ushort FIXED_ADDITIONAL_COMPUTATION_FACTOR_INDEX = 2; + + public byte[] ToBytes() + { + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, CLValue.U8(FIXED_VARIANT_TAG)) + .AddField(FIXED_GAS_PRICE_TOLERANCE_INDEX, CLValue.U8(GasPriceTolerance)) + .AddField(FIXED_ADDITIONAL_COMPUTATION_FACTOR_INDEX, CLValue.U8(AdditionalComputationFactor)) + .GetBytes(); + } + } + + public class PrepaidPricingMode : IPricingMode + { + /// + /// Pre-paid receipt in the Prepaid Pricing mode. + /// + [JsonPropertyName("receipt")] + public string Receipt { get; init; } + + const ushort TAG_FIELD_INDEX = 0; + const byte PREPAID_VARIANT_TAG = 2; + const ushort PREPAID_RECEIPT_INDEX = 1; + public byte[] ToBytes() + { + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, CLValue.U8(PREPAID_VARIANT_TAG)) + .AddField(PREPAID_RECEIPT_INDEX, Hex.Decode(Receipt)) + .GetBytes(); + } + } + + /// + /// Pricing mode of a Transaction. + /// + public class PricingMode + { + /// + /// The original payment model, where the creator of the transaction + /// specifies how much they will pay, at what gas price. + /// + /// Amount in motes to pay for the transaction. + /// Defaults to 1. Gas price tolerance admitted. + /// Defaults to true. Indicates if this is a standar payment (non-standard payments are handled via wasm code). + public static IPricingMode PaymentLimited(ulong paymentAmount, byte gasPriceTolerance = 1, bool standardPayment = true) + { + return new PaymentLimitedPricingMode() + { + StandardPayment = standardPayment, + GasPriceTolerance = gasPriceTolerance, + PaymentAmount = paymentAmount, + }; + } + + /// + /// The cost of the transaction is determined by the cost table, per the transaction category. + /// + /// Defaults to 1. Gas price tolerance admitted. + /// Defaults to 0. Each unit increases the wasm lane the transaction is executed in.. + public static IPricingMode Fixed(byte gasPriceTolerance = 1, byte additionalComputationFactor = 0) + { + return new FixedPricingMode() + { + GasPriceTolerance = gasPriceTolerance, + AdditionalComputationFactor = additionalComputationFactor, + }; + } + + /// + /// The payment for this transaction was previously pre-paid, as proven by the receipt hash. + /// + /// Pre-paid receipt. + public static IPricingMode Prepaid(string receipt) + { + return new PrepaidPricingMode() + { + Receipt = receipt, + }; + } + + public class PricingModeConverter : JsonConverter + { + public override IPricingMode Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + IPricingMode pricingMode = null; + + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException("Cannot deserialize PricingMode. StartObject expected"); + reader.Read(); + + if (reader.TokenType != JsonTokenType.PropertyName) + throw new JsonException("Cannot deserialize PricingMode. PropertyName expected"); + + if (!EnumCompat.TryParse(reader.GetString(), out PricingModeType pricingModeType)) + throw new Exception($"Unknown pricing mode '{reader.GetString()}'"); + + reader.Read(); + + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException("Cannot deserialize PricingMode. StartObject expected"); + + switch (pricingModeType) + { + case PricingModeType.PaymentLimited: + pricingMode = JsonSerializer.Deserialize(ref reader, options); + break; + case PricingModeType.Fixed: + pricingMode = JsonSerializer.Deserialize(ref reader, options); + break; + case PricingModeType.Prepaid: + pricingMode = JsonSerializer.Deserialize(ref reader, options); + break; + } + + reader.Read(); + + return pricingMode; + } + + public override void Write( + Utf8JsonWriter writer, + IPricingMode value, + JsonSerializerOptions options) + { + switch (value) + { + case PaymentLimitedPricingMode classicPricingMode: + writer.WriteStartObject(); + writer.WritePropertyName("PaymentLimited"); + JsonSerializer.Serialize(writer, classicPricingMode); + writer.WriteEndObject(); + break; + case FixedPricingMode fixedPricingMode: + writer.WriteStartObject(); + writer.WritePropertyName("Fixed"); + JsonSerializer.Serialize(writer, fixedPricingMode); + writer.WriteEndObject(); + break; + case PrepaidPricingMode reservedPricingMode: + writer.WriteStartObject(); + writer.WritePropertyName("Prepaid"); + JsonSerializer.Serialize(writer, reservedPricingMode); + writer.WriteEndObject(); + break; + } + } + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/PublicKey.cs b/Casper.Network.SDK/Types/PublicKey.cs index 7228b40..6aa8868 100644 --- a/Casper.Network.SDK/Types/PublicKey.cs +++ b/Casper.Network.SDK/Types/PublicKey.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.Json; @@ -17,7 +18,7 @@ namespace Casper.Network.SDK.Types /// /// A wrapper for a Public Key. Provides signature verification functionality. /// - public class PublicKey + public class PublicKey: IPurseIdentifier, IEntityIdentifier { /// /// Byte array without the Key algorithm identifier. @@ -264,6 +265,28 @@ public bool VerifySignature(string message, string signature) { return VerifySignature(Hex.Decode(message), Hex.Decode(signature)); } + + /// + /// Returns a PurseIdentifier object as defined in the RPC schema for a public key. + /// + public Dictionary GetPurseIdentifier() + { + return new Dictionary + { + {"main_purse_under_public_key", this.ToString()} + }; + } + + /// + /// Returns an EntityIdentifier object as defined in the RPC schema for a public key. + /// + public Dictionary GetEntityIdentifier() + { + return new Dictionary + { + {"PublicKey", this.ToString()} + }; + } #region Cast operators diff --git a/Casper.Network.SDK/Types/Reservation.cs b/Casper.Network.SDK/Types/Reservation.cs new file mode 100644 index 0000000..5ce9e51 --- /dev/null +++ b/Casper.Network.SDK/Types/Reservation.cs @@ -0,0 +1,46 @@ +using System.IO; +using System.Text.Json.Serialization; +using Casper.Network.SDK.ByteSerializers; + +namespace Casper.Network.SDK.Types +{ + /// + /// Represents a validator reserving a slot for specific delegator + /// + public class Reservation + { + /// + /// The validator public key. + /// + [JsonPropertyName("validator_public_key")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey ValidatorPublicKey { get; init; } + + /// + /// The delegator public key or purse. + /// + [JsonPropertyName("delegator_kind")] + public DelegatorKind DelegatorKind { get; init; } + + /// + /// The delegation rate. + /// + /// + [JsonPropertyName("delegation_rate")] + public uint DelegationRate { get; init; } + + public byte[] ToBytes() + { + MemoryStream ms = new MemoryStream(); + ms.Write(this.DelegatorKind.ToBytes()); + ms.Write(ValidatorPublicKey.GetBytes()); + ms.WriteByte((byte)DelegationRate); + return ms.ToArray(); + } + + public CLValue ToCLValue() + { + return new CLValue(ToBytes(), CLType.Any); + } + } +} diff --git a/Casper.Network.SDK/Types/SeigniorageAllocation.cs b/Casper.Network.SDK/Types/SeigniorageAllocation.cs index 4c1ee7f..77fbcdb 100644 --- a/Casper.Network.SDK/Types/SeigniorageAllocation.cs +++ b/Casper.Network.SDK/Types/SeigniorageAllocation.cs @@ -16,9 +16,9 @@ public class SeigniorageAllocation public bool IsDelegator { get; init; } /// - /// Public key of the delegator (null if not a delegator reward allocation). + /// Public key or purse of the delegator (null if not a delegator reward allocation). /// - public PublicKey DelegatorPublicKey { get; init; } + public DelegatorKind DelegatorKind { get; init; } /// /// Public key of the validator @@ -53,7 +53,7 @@ public override SeigniorageAllocation Read( reader.Read(); // start object - string delegatorPk = null; + DelegatorKind delegatorKind = null; string validatorPk = null; BigInteger amount = BigInteger.Zero; @@ -61,8 +61,10 @@ public override SeigniorageAllocation Read( { var field = reader.GetString(); reader.Read(); - if (field == "delegator_public_key") - delegatorPk = reader.GetString(); + if (field == "delegator_kind") + delegatorKind = JsonSerializer.Deserialize(ref reader, options); + else if (field == "delegator_public_key") + delegatorKind = new DelegatorKind() { PublicKey = PublicKey.FromHexString(reader.GetString()) }; else if (field == "validator_public_key") validatorPk = reader.GetString(); else if (field == "amount") @@ -75,7 +77,7 @@ public override SeigniorageAllocation Read( return new SeigniorageAllocation() { IsDelegator = propertyName?.ToLowerInvariant() == "delegator", - DelegatorPublicKey = delegatorPk != null ? PublicKey.FromHexString(delegatorPk) : null, + DelegatorKind = delegatorKind, ValidatorPublicKey = PublicKey.FromHexString(validatorPk), Amount = amount }; @@ -91,8 +93,8 @@ public override void Write( writer.WriteStartObject(); writer.WritePropertyName("Delegator"); writer.WriteStartObject(); - writer.WritePropertyName("delegator_public_key"); - writer.WriteStringValue(value.DelegatorPublicKey.ToString()); + writer.WritePropertyName("delegator_kind"); + JsonSerializer.Serialize(writer, value.DelegatorKind, options); writer.WritePropertyName("validator_public_key"); writer.WriteStringValue(value.ValidatorPublicKey.ToString()); writer.WritePropertyName("amount"); diff --git a/Casper.Network.SDK/Types/Signature.cs b/Casper.Network.SDK/Types/Signature.cs index a28b27d..24799de 100644 --- a/Casper.Network.SDK/Types/Signature.cs +++ b/Casper.Network.SDK/Types/Signature.cs @@ -30,11 +30,8 @@ protected Signature(byte[] rawBytes, KeyAlgo keyAlgo) /// public static Signature FromHexString(string signature) { - var rawBytes = CEP57Checksum.Decode(signature.Substring(2), out var checksumResult); + var rawBytes = Hex.Decode(signature.Substring(2)); - if (checksumResult == CEP57Checksum.InvalidChecksum) - throw new ArgumentException("Signature checksum mismatch."); - KeyAlgo algo = signature.Substring(0, 2) switch { "01" => KeyAlgo.ED25519, @@ -92,9 +89,9 @@ public byte[] GetBytes() public string ToHexString() { if (KeyAlgorithm == KeyAlgo.ED25519) - return "01" + CEP57Checksum.Encode(RawBytes); + return "01" + Hex.ToHexString(RawBytes); else - return "02" + CEP57Checksum.Encode(RawBytes); + return "02" + Hex.ToHexString(RawBytes); } public override string ToString() diff --git a/Casper.Network.SDK/Types/StateIdentifier.cs b/Casper.Network.SDK/Types/StateIdentifier.cs index c951a80..14f3eaa 100644 --- a/Casper.Network.SDK/Types/StateIdentifier.cs +++ b/Casper.Network.SDK/Types/StateIdentifier.cs @@ -6,7 +6,7 @@ public class StateIdentifier { private string _stateRootHash; private string _blockHash; - private int? _blockHeight; + private ulong? _blockHeight; private StateIdentifier() { @@ -32,7 +32,7 @@ public static StateIdentifier WithBlockHash(string blockHash) }; } - public static StateIdentifier WithBlockHeight(int blockHeight) + public static StateIdentifier WithBlockHeight(ulong blockHeight) { return new StateIdentifier { diff --git a/Casper.Network.SDK/Types/StoredValue.cs b/Casper.Network.SDK/Types/StoredValue.cs index d52c9c5..1fedfa4 100644 --- a/Casper.Network.SDK/Types/StoredValue.cs +++ b/Casper.Network.SDK/Types/StoredValue.cs @@ -9,6 +9,7 @@ namespace Casper.Network.SDK.Types /// /// A wrapper class for different types of values stored in the global state. /// + [JsonConverter(typeof(StoredValue.StoredValueConverter))] public class StoredValue { public Contract Contract { get; init; } @@ -17,7 +18,7 @@ public class StoredValue public Account Account { get; init; } - public string ContractWasm { get; init; } + public ContractWasm ContractWasm { get; init; } public ContractPackage ContractPackage { get; init; } @@ -28,9 +29,55 @@ public class StoredValue public EraInfo EraInfo { get; init; } public Bid Bid { get; init; } - - public List Withdraw { get; init; } - + + public BidKind BidKind { get; init; } + + public List Withdraw { get; init; } + + public List Unbonding { get; init; } + + /// + /// Stores an addressable entity. + /// + public AddressableEntity AddressableEntity { get; init; } + + /// + /// Stores a package. + /// + public Package Package { get; init; } + + /// + /// A record of byte code. + /// + public ByteCode ByteCode { get; init; } + + /// + /// Stores a message topic. + /// + public MessageTopicSummary MessageTopic { get; init; } + + /// + /// Stores a message checksum. + /// + public string Message { get; init; } + + /// + /// Stores a NamedKey. + /// + public NamedKeyValue NamedKey { get; init; } + + /// + /// Stores location, type and data for a gas pre-payment. + /// + public Prepayment Prepayment { get; init; } + + public EntryPoint EntryPoint { get; init; } + + /// + /// Raw bytes. Similar to a [`crate::StoredValue::CLValue`] but does not incur overhead of a [`crate::CLValue`] and [`crate::CLType`]. + /// + public string RawBytes { get; init; } + public class StoredValueConverter : JsonConverter { public override StoredValue Read @@ -52,13 +99,16 @@ JsonSerializerOptions options throw new JsonException("Cannot deserialize StoredValue. PropertyName expected."); var propertyName = reader.GetString(); + var propertyInfo = typeof(StoredValue).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); - if (propertyInfo == null) + if (propertyInfo != null) + { + reader.Read(); + var propertyValue = JsonSerializer.Deserialize(ref reader, propertyInfo.PropertyType, options); + propertyInfo.SetValue(storedValue, propertyValue); + } + else throw new JsonException($"Unknown property: {propertyName}."); - - reader.Read(); - var propertyValue = JsonSerializer.Deserialize(ref reader, propertyInfo.PropertyType, options); - propertyInfo.SetValue(storedValue, propertyValue); } return storedValue; diff --git a/Casper.Network.SDK/Types/Transaction.cs b/Casper.Network.SDK/Types/Transaction.cs new file mode 100644 index 0000000..a078ef0 --- /dev/null +++ b/Casper.Network.SDK/Types/Transaction.cs @@ -0,0 +1,348 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Casper.Network.SDK.Types +{ + using ITransactionScheduling = ITransactionV1Scheduling; + + internal class TransactionCompat + { + /// + /// The deploy. + /// + [JsonPropertyName("Deploy")] + public Deploy Deploy { get; init; } + + /// + /// A version 1 transaction. + /// + [JsonPropertyName("Version1")] + public TransactionV1 Version1 { get; init; } + } + + /// + /// A versioned wrapper for a TransactionV1 or Deploy. + /// + [JsonConverter(typeof(TransactionConverter))] + public partial class Transaction + { + protected TransactionVersion _version; + + /// + /// Returns the version of the transaction (0=Deploy, 1=TransactionV1) . + /// + public TransactionVersion Version + { + get { return _version; } + } + + private Deploy _deploy; + + private TransactionV1 _transactionV1; + + public string Hash { get; init; } + + /// + /// The address of the initiator of a transaction. + /// + public InitiatorAddr InitiatorAddr { get; set; } + + /// + /// Timestamp formatted as per RFC 3339 + /// + public ulong Timestamp { get; set; } + + /// + /// Duration of the Deploy in milliseconds (from timestamp). + /// + public ulong Ttl { get; set; } + + /// + /// Name of the chain where the deploy is executed. + /// + public string ChainName { get; set; } + + /// + /// List of signers and signatures for this transaction. + /// + public List Approvals { get; init; } + + /// + /// Pricing mode of a Transaction. + /// + public IPricingMode PricingMode { get; set; } + + public ITransactionScheduling Scheduling { get; set; } + + public ITransactionInvocation Invocation { get; init; } + + public interface ITransactionInvocation + { + List RuntimeArgs { get; init; } + + CLValue GetRuntimeArgValue(string name); + } + + public abstract class TransactionInvocation : ITransactionInvocation + { + public List RuntimeArgs { get; init; } + + public CLValue GetRuntimeArgValue(string name) + { + var runtimeArg = RuntimeArgs.FirstOrDefault(a => a.Name.Equals(name)); + return runtimeArg?.Value; + } + } + + public class NativeTransactionInvocation : TransactionInvocation + { + public NativeEntryPoint Type { get; init; } + + public string Name { get { return Type.ToString(); } } + } + + public class StoredTransactionInvocation : TransactionInvocation + { + public IInvocationTarget InvocationTarget { get; init; } + + public string EntryPoint { get; init; } + } + + public class SessionTransactionInvocation : TransactionInvocation + { + public byte[] Wasm { get; init; } + } + + /// + /// Signs the transaction with a private key and adds a new Approval to it. + /// + public void Sign(KeyPair keyPair) + { + if(_deploy is not null) + _deploy.Sign(keyPair); + + if(_transactionV1 is not null) + _transactionV1.Sign(keyPair); + } + + public class TransactionConverter : JsonConverter + { + public override Transaction Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + try + { + var compat = JsonSerializer.Deserialize(ref reader, options); + + if (compat != null && compat.Version1 != null) + { + return (Transaction)compat.Version1; + } + + if (compat != null && compat.Deploy != null) + { + return (Transaction)compat.Deploy; + } + + throw new JsonException("Cannot deserialize Transaction. Deploy or TransactionV1 not found"); + } + catch (Exception e) + { + throw new JsonException("Cannot deserialize Transaction. " + e.Message); + } + } + + public override void Write( + Utf8JsonWriter writer, + Transaction value, + JsonSerializerOptions options) + { + if (value._deploy is null && value._transactionV1 is null) + throw new JsonException("Cannot serialize empty Transaction. Deploy or TransactionV1 not found"); + + var compat = new TransactionCompat() + { + Deploy = value._deploy, + Version1 = value._transactionV1, + }; + + JsonSerializer.Serialize(writer, compat, options); + } + } + + public static explicit operator Transaction(Deploy deploy) + { + ITransactionInvocation invocation; + if (deploy.Session is TransferDeployItem transferDeployItem) + { + invocation = new NativeTransactionInvocation() + { + Type = NativeEntryPoint.Transfer, + RuntimeArgs = transferDeployItem.RuntimeArgs, + }; + } + else if (deploy.Session is ModuleBytesDeployItem moduleBytesDeployItem) + { + invocation = new SessionTransactionInvocation() + { + Wasm = moduleBytesDeployItem.ModuleBytes, + RuntimeArgs = moduleBytesDeployItem.RuntimeArgs, + }; + } + else if (deploy.Session is StoredContractByHashDeployItem storedContractByHash) + { + invocation = new StoredTransactionInvocation() + { + InvocationTarget = new ByHashInvocationTarget { Hash = storedContractByHash.Hash }, + EntryPoint = storedContractByHash.EntryPoint, + RuntimeArgs = storedContractByHash.RuntimeArgs, + }; + } + else if (deploy.Session is StoredContractByNameDeployItem storedContractByName) + { + invocation = new StoredTransactionInvocation() + { + InvocationTarget = new ByNameInvocationTarget { Name = storedContractByName.Name }, + EntryPoint = storedContractByName.EntryPoint, + RuntimeArgs = storedContractByName.RuntimeArgs, + }; + } + else if (deploy.Session is StoredVersionedContractByHashDeployItem storedVersionedContractByHash) + { + invocation = new StoredTransactionInvocation() + { + InvocationTarget = new ByPackageHashInvocationTarget + { + Hash = storedVersionedContractByHash.Hash, + Version = storedVersionedContractByHash.Version + }, + EntryPoint = storedVersionedContractByHash.EntryPoint, + RuntimeArgs = storedVersionedContractByHash.RuntimeArgs, + }; + } + else if (deploy.Session is StoredVersionedContractByNameDeployItem storedVersionedContractByName) + { + invocation = new StoredTransactionInvocation() + { + InvocationTarget = new ByPackageNameInvocationTarget + { + Name = storedVersionedContractByName.Name, + Version = storedVersionedContractByName.Version + }, + EntryPoint = storedVersionedContractByName.EntryPoint, + RuntimeArgs = storedVersionedContractByName.RuntimeArgs, + }; + } + else + { + throw new ArgumentException("No valid session object in the deploy to convert to Transaction"); + } + + IPricingMode pricingMode; + if (deploy.Payment is ModuleBytesDeployItem paymentModule) + { + var amountArg = paymentModule.RuntimeArgs.FirstOrDefault(arg => arg.Name == "amount"); + if (amountArg == null) + pricingMode = Types.PricingMode.PaymentLimited(0, (byte)deploy.Header.GasPrice, false); + else + { + var paymentAmount = (ulong)amountArg.Value.ToBigInteger(); + pricingMode = Types.PricingMode.PaymentLimited(paymentAmount, (byte)deploy.Header.GasPrice, paymentModule.ModuleBytes == null); + } + } + else + { + throw new ArgumentException("No valid payment object in the deploy to convert to Transaction"); + } + + return new Transaction() + { + _deploy = deploy, + _version = TransactionVersion.Deploy, + Hash = deploy.Hash, + InitiatorAddr = InitiatorAddr.FromPublicKey(deploy.Header.Account), + Timestamp = deploy.Header.Timestamp, + Ttl = deploy.Header.Ttl, + ChainName = deploy.Header.ChainName, + PricingMode = pricingMode, + Approvals = deploy.Approvals, + Scheduling = TransactionScheduling.Standard, + Invocation = invocation, + }; + } + + public static explicit operator Transaction(TransactionV1 transactionV1) + { + ITransactionInvocation transactionInvocation; + + if(transactionV1.Payload.Target is NativeTransactionV1Target && + transactionV1.Payload.EntryPoint is NativeTransactionV1EntryPoint nativeEntryPoint) + { + transactionInvocation = new NativeTransactionInvocation() + { + Type = nativeEntryPoint.Type, + RuntimeArgs = transactionV1.Payload.RuntimeArgs, + }; + } + else if (transactionV1.Payload.Target is StoredTransactionV1Target storedTarget && + transactionV1.Payload.EntryPoint is CustomTransactionV1EntryPoint customEntryPoint) + { + transactionInvocation = new StoredTransactionInvocation() + { + InvocationTarget = storedTarget.Id, + EntryPoint = customEntryPoint.Name, + RuntimeArgs = transactionV1.Payload.RuntimeArgs, + }; + } + else if (transactionV1.Payload.Target is SessionTransactionV1Target sessionTarget && + transactionV1.Payload.EntryPoint.Name.Equals("Call", StringComparison.InvariantCultureIgnoreCase)) + { + transactionInvocation = new SessionTransactionInvocation() + { + Wasm = sessionTarget.ModuleBytes, + RuntimeArgs = transactionV1.Payload.RuntimeArgs, + }; + } + else + { + throw new Exception("Invalid Target or EntryPoint data, or not compatible."); + } + + return new Transaction() + { + _transactionV1 = transactionV1, + _version = TransactionVersion.TransactionV1, + Hash = transactionV1.Hash, + InitiatorAddr = transactionV1.Payload.InitiatorAddr, + Timestamp = transactionV1.Payload.Timestamp, + Ttl = transactionV1.Payload.Ttl, + ChainName = transactionV1.Payload.ChainName, + PricingMode = transactionV1.Payload.PricingMode, + Approvals = transactionV1.Approvals, + Scheduling = transactionV1.Payload.Scheduling, + Invocation = transactionInvocation, + }; + } + + public static explicit operator Deploy(Transaction transaction) + { + if (transaction._version == TransactionVersion.Deploy) + return transaction._deploy; + + throw new InvalidCastException("TransactionV1 transaction cannot be converted to Deploy"); + } + + public static explicit operator TransactionV1(Transaction transaction) + { + if (transaction._version == TransactionVersion.TransactionV1) + return transaction._transactionV1; + + throw new InvalidCastException("Deploy transaction cannot be converted to TransactionV1"); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/TransactionBuilder.cs b/Casper.Network.SDK/Types/TransactionBuilder.cs new file mode 100644 index 0000000..ac9db51 --- /dev/null +++ b/Casper.Network.SDK/Types/TransactionBuilder.cs @@ -0,0 +1,641 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using Casper.Network.SDK.Utils; + +namespace Casper.Network.SDK.Types +{ + public partial class Transaction + { + [AttributeUsage(AttributeTargets.Field, Inherited = true, AllowMultiple = false)] + public class RequiredArgAttribute : Attribute + { + } + + public abstract class TransactionV1Builder where T : TransactionV1Builder + { + [RequiredArg] + protected InitiatorAddr _initiatorAddr; + [RequiredArg] + protected string _chainName; + protected DateTime? _timestamp; + protected ulong _ttl = 1800000; //30m + [RequiredArg] + protected IPricingMode _pricingMode; + [RequiredArg] + protected ITransactionV1Target _invocationTarget; + [RequiredArg] + protected ITransactionV1EntryPoint _entryPoint; + protected ITransactionV1Scheduling _scheduling = TransactionScheduling.Standard; + [RequiredArg] + protected List _runtimeArgs = new(); + + public T From(PublicKey publicKey) + { + _initiatorAddr = Types.InitiatorAddr.FromPublicKey(publicKey); + return (T)this; + } + + public T From(AccountHashKey accountHashKey) + { + _initiatorAddr = Types.InitiatorAddr.FromAccountHash(accountHashKey); + return (T)this; + } + + public T ChainName(string chainName) + { + _chainName = chainName; + return (T)this; + } + + public T Timestamp(DateTime timestamp) + { + _timestamp = timestamp; + return (T)this; + } + + public T TTL(ulong ttl) + { + _ttl = ttl; + return (T)this; + } + + public T Payment(IPricingMode pricingMode) + { + _pricingMode = pricingMode; + return (T)this; + } + + public T Payment(ulong amount, byte gasPriceTolerance = 1) + { + _pricingMode = Types.PricingMode.PaymentLimited(amount, gasPriceTolerance); + return (T)this; + } + + protected void ValidateRequiredProperties() + { + var missingProperties = this.GetType() + .GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + .Where(field => Attribute.IsDefined(field, typeof(RequiredArgAttribute)) && field.GetValue(this) == null) + .Select(field => field.Name) + .ToList(); + + if (missingProperties.Any()) + { + throw new InvalidOperationException($"The following required properties are missing: {string.Join(", ", missingProperties)}"); + } + } + + public virtual Transaction Build() + { + var payload = new TransactionV1Payload() + { + InitiatorAddr = _initiatorAddr, + Timestamp = DateUtils.ToEpochTime(_timestamp.HasValue ? _timestamp.Value : DateTime.UtcNow), + Ttl = _ttl, + ChainName = _chainName, + PricingMode = _pricingMode, + RuntimeArgs = _runtimeArgs, + Target = _invocationTarget, + EntryPoint = _entryPoint, + Scheduling = _scheduling, + }; + var transactionV1 = new TransactionV1(payload); + return (Transaction)transactionV1; + } + } + + public class NativeTransferBuilder : TransactionV1Builder + { + //specific tx properties + [RequiredArg] + private CLValue _target; + [RequiredArg] + private CLValue _amount = CLValue.U512((BigInteger)0); + private ulong? _idTransfer; + + public NativeTransferBuilder() + { + _invocationTarget = TransactionV1Target.Native; + _entryPoint = TransactionV1EntryPoint.Transfer; + } + + public NativeTransferBuilder Target(PublicKey publicKey) + { + _target = CLValue.PublicKey(publicKey); + return this; + } + + public NativeTransferBuilder Target(AccountHashKey accountHashKey) + { + _target = CLValue.ByteArray(accountHashKey.RawBytes); + return this; + } + + public NativeTransferBuilder Amount(ulong amount) + { + _amount = CLValue.U512(amount); + return this; + } + + public NativeTransferBuilder Amount(BigInteger amount) + { + _amount = CLValue.U512(amount); + return this; + } + + public NativeTransferBuilder Id(ulong id) + { + _idTransfer = id; + return this; + } + + public override Transaction Build() + { + ValidateRequiredProperties(); + + _runtimeArgs = new List(); + _runtimeArgs.Add(new NamedArg("target", _target)); + _runtimeArgs.Add(new NamedArg("amount", _amount)); + if (_idTransfer.HasValue) + { + _runtimeArgs.Add(new NamedArg("id", CLValue.Option(CLValue.U64(_idTransfer.Value)))); + } + + return base.Build(); + } + } + + public class NativeAddBidBuilder : TransactionV1Builder + { + //specific tx properties + [RequiredArg] + private CLValue _validator; + [RequiredArg] + private CLValue _amount; + [RequiredArg] + private CLValue _delegationRate; + private CLValue _minimumDelegationAmount; + private CLValue _maximumDelegationAmount; + private CLValue _reservedSlots; + + public NativeAddBidBuilder() + { + _invocationTarget = TransactionV1Target.Native; + _entryPoint = TransactionV1EntryPoint.AddBid; + } + + public NativeAddBidBuilder Validator(PublicKey publicKey) + { + _validator = CLValue.PublicKey(publicKey); + return this; + } + + public NativeAddBidBuilder Amount(ulong amount) + { + _amount = CLValue.U512(amount); + return this; + } + + public NativeAddBidBuilder Amount(BigInteger amount) + { + _amount = CLValue.U512(amount); + return this; + } + + public NativeAddBidBuilder DelegationRate(byte delegationRate) + { + _delegationRate = CLValue.U8(delegationRate); + return this; + } + + public NativeAddBidBuilder MinimumDelegationAmount(ulong minimumDelegationAmount) + { + _minimumDelegationAmount = CLValue.U64(minimumDelegationAmount); + return this; + } + + public NativeAddBidBuilder MaximumDelegationAmount(ulong maximumDelegationAmount) + { + _maximumDelegationAmount = CLValue.U64(maximumDelegationAmount); + return this; + } + + public NativeAddBidBuilder ReservedSlots(uint reservedSlots) + { + _reservedSlots = CLValue.U32(reservedSlots); + return this; + } + + public override Transaction Build() + { + ValidateRequiredProperties(); + + _runtimeArgs = new List(); + _runtimeArgs.Add(new NamedArg("public_key", _validator)); + _runtimeArgs.Add(new NamedArg("amount", _amount)); + _runtimeArgs.Add(new NamedArg("delegation_rate", _delegationRate)); + if(_minimumDelegationAmount != null) + _runtimeArgs.Add(new NamedArg("minimum_delegation_amount", _minimumDelegationAmount)); + if(_maximumDelegationAmount != null) + _runtimeArgs.Add(new NamedArg("maximum_delegation_amount", _maximumDelegationAmount)); + if(_reservedSlots != null) + _runtimeArgs.Add(new NamedArg("reserved_slots", _reservedSlots)); + return base.Build(); + } + } + + public class NativeWithdrawBidBuilder : TransactionV1Builder + { + //specific tx properties + [RequiredArg] + private CLValue _validator; + [RequiredArg] + private CLValue _amount = CLValue.U512((BigInteger)0); + + public NativeWithdrawBidBuilder() + { + _invocationTarget = TransactionV1Target.Native; + _entryPoint = TransactionV1EntryPoint.WithdrawBid; + } + + public NativeWithdrawBidBuilder Validator(PublicKey publicKey) + { + _validator = CLValue.PublicKey(publicKey); + return this; + } + + public NativeWithdrawBidBuilder Amount(ulong amount) + { + _amount = CLValue.U512(amount); + return this; + } + + public NativeWithdrawBidBuilder Amount(BigInteger amount) + { + _amount = CLValue.U512(amount); + return this; + } + + public override Transaction Build() + { + ValidateRequiredProperties(); + + _runtimeArgs = new List(); + _runtimeArgs.Add(new NamedArg("public_key", _validator)); + _runtimeArgs.Add(new NamedArg("amount", _amount)); + return base.Build(); + } + } + + public class NativeDelegateBuilder : TransactionV1Builder + { + //specific tx properties + [RequiredArg] + private CLValue _validator; + [RequiredArg] + private CLValue _amount = CLValue.U512((BigInteger)0); + + public NativeDelegateBuilder() + { + _invocationTarget = TransactionV1Target.Native; + _entryPoint = TransactionV1EntryPoint.Delegate; + } + + public NativeDelegateBuilder Validator(PublicKey publicKey) + { + _validator = CLValue.PublicKey(publicKey); + return this; + } + + public NativeDelegateBuilder Amount(ulong amount) + { + _amount = CLValue.U512(amount); + return this; + } + + public NativeDelegateBuilder Amount(BigInteger amount) + { + _amount = CLValue.U512(amount); + return this; + } + + public override Transaction Build() + { + ValidateRequiredProperties(); + + _runtimeArgs = new List(); + _runtimeArgs.Add(new NamedArg("delegator", CLValue.PublicKey(_initiatorAddr.PublicKey))); + _runtimeArgs.Add(new NamedArg("validator", _validator)); + _runtimeArgs.Add(new NamedArg("amount", _amount)); + + return base.Build(); + } + } + + public class NativeUndelegateBuilder : TransactionV1Builder + { + //specific tx properties + [RequiredArg] + private CLValue _validator; + [RequiredArg] + private CLValue _amount = CLValue.U512((BigInteger)0); + + public NativeUndelegateBuilder() + { + _invocationTarget = TransactionV1Target.Native; + _entryPoint = TransactionV1EntryPoint.Undelegate; + } + + public NativeUndelegateBuilder Validator(PublicKey publicKey) + { + _validator = CLValue.PublicKey(publicKey); + return this; + } + + public NativeUndelegateBuilder Amount(ulong amount) + { + _amount = CLValue.U512(amount); + return this; + } + + public NativeUndelegateBuilder Amount(BigInteger amount) + { + _amount = CLValue.U512(amount); + return this; + } + + public override Transaction Build() + { + ValidateRequiredProperties(); + + _runtimeArgs = new List(); + _runtimeArgs.Add(new NamedArg("delegator", CLValue.PublicKey(_initiatorAddr.PublicKey))); + _runtimeArgs.Add(new NamedArg("validator", _validator)); + _runtimeArgs.Add(new NamedArg("amount", _amount)); + + return base.Build(); + } + } + + public class NativeRedelegateBuilder : TransactionV1Builder + { + //specific tx properties + [RequiredArg] + private CLValue _validator; + [RequiredArg] + private CLValue _newValidator; + [RequiredArg] + private CLValue _amount = CLValue.U512((BigInteger)0); + + public NativeRedelegateBuilder() + { + _invocationTarget = TransactionV1Target.Native; + _entryPoint = TransactionV1EntryPoint.Redelegate; + } + + public NativeRedelegateBuilder Validator(PublicKey publicKey) + { + _validator = CLValue.PublicKey(publicKey); + return this; + } + + public NativeRedelegateBuilder NewValidator(PublicKey publicKey) + { + _newValidator = CLValue.PublicKey(publicKey); + return this; + } + + public NativeRedelegateBuilder Amount(ulong amount) + { + _amount = CLValue.U512(amount); + return this; + } + + public NativeRedelegateBuilder Amount(BigInteger amount) + { + _amount = CLValue.U512(amount); + return this; + } + + public override Transaction Build() + { + ValidateRequiredProperties(); + + _runtimeArgs = new List(); + _runtimeArgs.Add(new NamedArg("delegator", CLValue.PublicKey(_initiatorAddr.PublicKey))); + _runtimeArgs.Add(new NamedArg("validator", _validator)); + _runtimeArgs.Add(new NamedArg("new_validator", _newValidator)); + _runtimeArgs.Add(new NamedArg("amount", _amount)); + + return base.Build(); + } + } + + public class NativeActivateBidBuilder : TransactionV1Builder + { + //specific tx properties + [RequiredArg] + private CLValue _validator; + + public NativeActivateBidBuilder() + { + _invocationTarget = TransactionV1Target.Native; + _entryPoint = TransactionV1EntryPoint.ActivateBid; + } + + public NativeActivateBidBuilder Validator(PublicKey publicKey) + { + _validator = CLValue.PublicKey(publicKey); + return this; + } + + public override Transaction Build() + { + ValidateRequiredProperties(); + + _runtimeArgs = new List(); + _runtimeArgs.Add(new NamedArg("validator", _validator)); + return base.Build(); + } + } + + public class NativeChangeBidPublicKeyBuilder : TransactionV1Builder + { + //specific tx properties + [RequiredArg] + private CLValue _public_key; + [RequiredArg] + private CLValue _new_public_key; + + public NativeChangeBidPublicKeyBuilder() + { + _invocationTarget = TransactionV1Target.Native; + _entryPoint = TransactionV1EntryPoint.ChangeBidPublicKey; + } + + public NativeChangeBidPublicKeyBuilder PublicKey(PublicKey publicKey) + { + _public_key = CLValue.PublicKey(publicKey); + return this; + } + + public NativeChangeBidPublicKeyBuilder NewPublicKey(PublicKey publicKey) + { + _new_public_key = CLValue.PublicKey(publicKey); + return this; + } + + public override Transaction Build() + { + ValidateRequiredProperties(); + + _runtimeArgs = new List(); + _runtimeArgs.Add(new NamedArg("public_key", _public_key)); + _runtimeArgs.Add(new NamedArg("new_public_key", _new_public_key)); + return base.Build(); + } + } + + public class NativeAddReservationsBuilder : TransactionV1Builder + { + //specific tx properties + [RequiredArg] + private List _reservations; + + public NativeAddReservationsBuilder() + { + _invocationTarget = TransactionV1Target.Native; + _entryPoint = TransactionV1EntryPoint.AddReservations; + } + + public NativeAddReservationsBuilder Reservations(List reservations) + { + _reservations = reservations; + return this; + } + + public override Transaction Build() + { + ValidateRequiredProperties(); + + var list = _reservations.Select(r => r.ToCLValue()); + _runtimeArgs.Add(new NamedArg("reservations", CLValue.List(list.ToArray()))); + return base.Build(); + } + } + + public class NativeCancelReservationsBuilder : TransactionV1Builder + { + //specific tx properties + [RequiredArg] + private PublicKey _validator; + [RequiredArg] + private List _delegators; + + public NativeCancelReservationsBuilder() + { + _invocationTarget = TransactionV1Target.Native; + _entryPoint = TransactionV1EntryPoint.CancelReservations; + } + + public NativeCancelReservationsBuilder Validator(PublicKey validator) + { + _validator = validator; + return this; + } + + public NativeCancelReservationsBuilder Delegators(List delegators) + { + _delegators = delegators; + return this; + } + + public override Transaction Build() + { + ValidateRequiredProperties(); + + _runtimeArgs.Add(new NamedArg("validator", CLValue.PublicKey(_validator))); + var list = _delegators.Select(r => r.ToCLValue()); + _runtimeArgs.Add(new NamedArg("delegators", CLValue.List(list.ToArray()))); + return base.Build(); + } + } + + public class ContractCallBuilder : TransactionV1Builder + { + public ContractCallBuilder() + { + } + + public ContractCallBuilder ByHash(string contractHash) + { + _invocationTarget = TransactionV1Target.StoredByHash(contractHash); + return this; + } + + public ContractCallBuilder ByName(string name) + { + _invocationTarget = TransactionV1Target.StoredByName(name); + return this; + } + + public ContractCallBuilder ByPackageHash(string contractHash, UInt32? version = null) + { + _invocationTarget = TransactionV1Target.StoredByPackageHash(contractHash, version); + return this; + } + + public ContractCallBuilder ByPackageName(string name, UInt32? version = null) + { + _invocationTarget = TransactionV1Target.StoredByPackageName(name, version); + return this; + } + + public ContractCallBuilder EntryPoint(string name) + { + _entryPoint = TransactionV1EntryPoint.Custom(name); + return this; + } + + public ContractCallBuilder RuntimeArgs(List args) + { + _runtimeArgs = args; + return this; + } + } + + public class SessionBuilder : TransactionV1Builder + { + private bool _isInstallOrUpgrade = false; + + public SessionBuilder() + { + _entryPoint = TransactionV1EntryPoint.Call; + } + + public SessionBuilder Wasm(byte[] wasmBytes) + { + var target = TransactionV1Target.Session(wasmBytes); + target.IsInstallUpgrade = _isInstallOrUpgrade; + _invocationTarget = target; + return this; + } + + public SessionBuilder InstallOrUpgrade() + { + _isInstallOrUpgrade = true; + if (_invocationTarget is SessionTransactionV1Target sessionTarget) + sessionTarget.IsInstallUpgrade = true; + return this; + } + + public SessionBuilder RuntimeArgs(List args) + { + _runtimeArgs = args; + return this; + } + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/TransactionHash.cs b/Casper.Network.SDK/Types/TransactionHash.cs new file mode 100644 index 0000000..391a09e --- /dev/null +++ b/Casper.Network.SDK/Types/TransactionHash.cs @@ -0,0 +1,14 @@ +namespace Casper.Network.SDK.Types +{ + public class TransactionHash + { + public string Deploy { get; init; } + + public string Version1 { get; init; } + + public override string ToString() + { + return Deploy ?? Version1; + } + } +} diff --git a/Casper.Network.SDK/Types/TransactionV1.cs b/Casper.Network.SDK/Types/TransactionV1.cs new file mode 100644 index 0000000..c11eaed --- /dev/null +++ b/Casper.Network.SDK/Types/TransactionV1.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using Casper.Network.SDK.ByteSerializers; +using Casper.Network.SDK.Utils; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Casper.Network.SDK.Types +{ + /// + /// A unit of work sent by a client to the network, which when executed can cause global state to be altered. + /// + public class TransactionV1 + { + /// + /// A hash over the header of the transaction. + /// + [JsonPropertyName("hash")] + public string Hash { get; } + + /// + /// Payload for this transaction. + /// + [JsonPropertyName("payload")] + public TransactionV1Payload Payload { get; init; } + + /// + /// List of signers and signatures for this transaction. + /// + [JsonPropertyName("approvals")] + public List Approvals { get; } = new List(); + + /// + /// Loads and deserializes a TransactionV1 from a file. + /// + public static TransactionV1 Load(string filename) + { + var data = File.ReadAllText(filename); + return TransactionV1.Parse(data); + } + + /// + /// Parses a Transaction from a string with json. + /// + public static TransactionV1 Parse(string json) + { + try + { + var transaction = JsonSerializer.Deserialize(json); + + return transaction; + } + catch (JsonException e) + { + var message = $"The JSON value could not be converted to a TransactionV1 object. " + + $"{e.Message} Path: {e.Path} | LineNumber: {e.LineNumber} | " + + $"BytePositionInLine: {e.BytePositionInLine}."; + throw new Exception(message); + } + } + + /// + /// Saves a transaction object to a file. + /// + public void Save(string filename) + { + File.WriteAllText(filename, JsonSerializer.Serialize(this)); + } + + /// + /// Returns a json string with the transaction. + /// + public string SerializeToJson() + { + return JsonSerializer.Serialize(this); + } + + [JsonConstructor] + public TransactionV1(string hash, + TransactionV1Payload payload, + List approvals) + { + this.Hash = hash; + this.Payload = payload; + this.Approvals = approvals; + } + + public TransactionV1(TransactionV1Payload payload) + { + var payloadBytes = payload.ToBytes(); + var blake2BDigest = new Org.BouncyCastle.Crypto.Digests.Blake2bDigest(256); + + blake2BDigest.BlockUpdate(payloadBytes, 0, payloadBytes.Length); + + var hash = new byte[blake2BDigest.GetDigestSize()]; + blake2BDigest.DoFinal(hash, 0); + + this.Payload = payload; + this.Hash = Hex.ToHexString(hash); + } + + /// + /// Signs the transaction with a private key and adds a new Approval to it. + /// + public void Sign(KeyPair keyPair) + { + byte[] signature = keyPair.Sign(Hex.Decode(this.Hash)); + + Approvals.Add(new Approval() + { + Signature = Signature.FromRawBytes(signature, keyPair.PublicKey.KeyAlgorithm), + Signer = keyPair.PublicKey + }); + } + + /// + /// Adds an approval to the transaction. No check is done to the approval signature. + /// + public void AddApproval(Approval approval) + { + this.Approvals.Add(approval); + } + + /// + /// Validates the transaction hash. + /// + /// output string with a validation error message if validation fails. empty otherwise. + /// false if the validation of hash is not successful + public bool ValidateHashes(out string message) + { + var payloadBytes = this.Payload.ToBytes(); + var blake2BDigest = new Org.BouncyCastle.Crypto.Digests.Blake2bDigest(256); + + blake2BDigest.BlockUpdate(payloadBytes, 0, payloadBytes.Length); + + var hash = new byte[blake2BDigest.GetDigestSize()]; + blake2BDigest.DoFinal(hash, 0); + + var computedHash = Hex.ToHexString(hash); + if (!this.Hash.Equals(computedHash, StringComparison.InvariantCultureIgnoreCase)) + { + message = "Computed hash does not match value in transaction object." + + $"Expected: '{this.Hash}'. " + + $"Computed: '{computedHash}'."; + return false; + } + + message = ""; + return true; + } + + /// + /// Verifies the signatures in the list of approvals. + /// + /// an output string with the signer which signature could not be verified. empty if verification succeeds. + /// false if the verification of a signature fails. + public bool VerifySignatures(out string message) + { + message = string.Empty; + + var decodedHash = Hex.Decode(this.Hash); + + foreach (var approval in Approvals.Where(approval => !approval.Signer + .VerifySignature(decodedHash, approval.Signature.RawBytes))) + { + message = $"Error verifying signature with signer '{approval.Signer}'."; + return false; + } + + return true; + } + + /// + /// returns the number of bytes resulting from the binary serialization of the Deploy. + /// + public int GetTransactionSizeInBytes() + { + return this.ToBytes().Length; + } + + const ushort HASH_FIELD_INDEX = 0; + const ushort PAYLOAD_FIELD_INDEX = 1; + const ushort APPROVALS_FIELD_INDEX = 2; + + + public byte[] ToBytes() + { + // add the approvals + // + var ms = new MemoryStream(); + var count = LittleEndianConverter.GetBytes(this.Approvals.Count); + ms.Write(count, 0, count.Length); + foreach (var approval in this.Approvals) + { + var approvalSerializer = new DeployApprovalByteSerializer(); + var approvalBytes = approvalSerializer.ToBytes(approval); + ms.Write(approvalBytes, 0, approvalBytes.Length); + } + + return new CalltableSerialization() + .AddField(HASH_FIELD_INDEX, Hex.Decode(this.Hash)) + .AddField(PAYLOAD_FIELD_INDEX, this.Payload.ToBytes()) + .AddField(APPROVALS_FIELD_INDEX, ms.ToArray()) + .GetBytes(); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/TransactionV1EntryPoint.cs b/Casper.Network.SDK/Types/TransactionV1EntryPoint.cs new file mode 100644 index 0000000..50d5aa5 --- /dev/null +++ b/Casper.Network.SDK/Types/TransactionV1EntryPoint.cs @@ -0,0 +1,261 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Casper.Network.SDK.ByteSerializers; + +namespace Casper.Network.SDK.Types +{ + /// + /// Collection of entry points available for native calls to the system contracts. + /// + public enum NativeEntryPoint + { + /// + /// Used to call entry point call() in session transactions. + /// + Call = 0, + + /// + /// A non-native, arbitrary entry point. + /// + Custom = 1, + + /// + /// The `transfer` native entry point, used to transfer `Motes` from a source purse to a target purse. + /// + Transfer = 2, + + /// + /// The `add_bid` native entry point, used to create or top off a bid purse. + /// + AddBid = 3, + + /// + /// The `withdraw_bid` native entry point, used to decrease a stake. + /// + WithdrawBid = 4, + + /// + /// The `delegate` native entry point, used to add a new delegator or increase an existing delegator's stake. + /// + Delegate = 5, + + /// + /// The `undelegate` native entry point, used to reduce a delegator's stake or remove the delegator if the remaining stake is 0. + /// + Undelegate = 6, + + /// + /// The `redelegate` native entry point, used to reduce a delegator's stake or remove the delegator if + /// the remaining stake is 0, and after the unbonding delay, automatically delegate to a new validator. + /// + Redelegate = 7, + + /// + /// The `activate_bid` native entry point, used to used to reactivate an inactive bid. + /// + ActivateBid = 8, + + /// + /// The `change_bid_public_key` native entry point, used to change a bid's public key. + /// + ChangeBidPublicKey = 9, + + /// + /// The `add_reservations` native entry point, used to add delegators to validator's reserve list. + /// + AddReservations = 10, + + /// + /// The `cancel_reservations` native entry point, used to remove delegators from validator's reserve list. + /// + CancelReservations = 11, + } + + + public interface ITransactionV1EntryPoint + { + public string Name { get; } + + public byte[] ToBytes(); + } + + public class NativeTransactionV1EntryPoint : ITransactionV1EntryPoint + { + public NativeEntryPoint Type { get; init; } + + public string Name + { + get { return Type.ToString(); } + } + + public NativeTransactionV1EntryPoint(NativeEntryPoint type) + { + Type = type; + } + + public NativeTransactionV1EntryPoint(string name) + { + try + { + var nativeEntryPoint = EnumCompat.Parse(name); + Type = nativeEntryPoint; + } + catch (Exception e) + { + throw new Exception($"Invalid name for a TransactionV1NativeEntryPoint ({name})."); + } + } + + const ushort TAG_FIELD_INDEX = 0; + const byte CALL_VARIANT_TAG = 0; + const byte CUSTOM_VARIANT_TAG = 1; + const byte TRANSFER_VARIANT_TAG = 2; + const byte ADD_BID_VARIANT_TAG = 3; + const byte WITHDRAW_BID_VARIANT_TAG = 4; + const byte DELEGATE_VARIANT_TAG = 5; + const byte UNDELEGATE_VARIANT_TAG = 6; + const byte REDELEGATE_VARIANT_TAG = 7; + const byte ACTIVATE_BID_VARIANT_TAG = 8; + const byte CHANGE_BID_PUBLIC_KEY_VARIANT_TAG = 9; + const byte ADD_RESERVATIONS_VARIANT_TAG = 10; + const byte CANCEL_RESERVATIONS_VARIANT_TAG = 11; + + public byte[] ToBytes() + { + var tag = Type switch + { + NativeEntryPoint.Call => CALL_VARIANT_TAG, + NativeEntryPoint.Custom => CUSTOM_VARIANT_TAG, + NativeEntryPoint.Transfer => TRANSFER_VARIANT_TAG, + NativeEntryPoint.AddBid => ADD_BID_VARIANT_TAG, + NativeEntryPoint.Delegate => DELEGATE_VARIANT_TAG, + NativeEntryPoint.WithdrawBid => WITHDRAW_BID_VARIANT_TAG, + NativeEntryPoint.Undelegate => UNDELEGATE_VARIANT_TAG, + NativeEntryPoint.Redelegate => REDELEGATE_VARIANT_TAG, + NativeEntryPoint.ActivateBid => ACTIVATE_BID_VARIANT_TAG, + NativeEntryPoint.ChangeBidPublicKey => CHANGE_BID_PUBLIC_KEY_VARIANT_TAG, + NativeEntryPoint.AddReservations => ADD_RESERVATIONS_VARIANT_TAG, + NativeEntryPoint.CancelReservations => CANCEL_RESERVATIONS_VARIANT_TAG, + _ => throw new Exception($"Unknown NativeEntryPoint type: {Type}") + }; + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] {tag}) + .GetBytes(); + } + } + + public class CustomTransactionV1EntryPoint : ITransactionV1EntryPoint + { + public string Name { get; init; } + + public CustomTransactionV1EntryPoint(string name) + { + Name = name; + } + + const ushort TAG_FIELD_INDEX = 0; + const byte CUSTOM_VARIANT_TAG = 1; + const ushort CUSTOM_CUSTOM_INDEX = 1; + + public byte[] ToBytes() + { + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] {CUSTOM_VARIANT_TAG}) + .AddField(CUSTOM_CUSTOM_INDEX, CLValue.String(Name)) + .GetBytes(); + } + } + + /// + /// A native or custom entry point to call within a transaction. + /// + public class TransactionV1EntryPoint + { + public static ITransactionV1EntryPoint Transfer => + new NativeTransactionV1EntryPoint(NativeEntryPoint.Transfer); + + public static ITransactionV1EntryPoint AddBid => + new NativeTransactionV1EntryPoint(NativeEntryPoint.AddBid); + + public static ITransactionV1EntryPoint WithdrawBid => + new NativeTransactionV1EntryPoint(NativeEntryPoint.WithdrawBid); + + public static ITransactionV1EntryPoint Delegate => + new NativeTransactionV1EntryPoint(NativeEntryPoint.Delegate); + + public static ITransactionV1EntryPoint Undelegate => + new NativeTransactionV1EntryPoint(NativeEntryPoint.Undelegate); + + public static ITransactionV1EntryPoint Redelegate => + new NativeTransactionV1EntryPoint(NativeEntryPoint.Redelegate); + + public static ITransactionV1EntryPoint ActivateBid => + new NativeTransactionV1EntryPoint(NativeEntryPoint.ActivateBid); + + public static ITransactionV1EntryPoint ChangeBidPublicKey => + new NativeTransactionV1EntryPoint(NativeEntryPoint.ChangeBidPublicKey); + + public static ITransactionV1EntryPoint Call => + new NativeTransactionV1EntryPoint(NativeEntryPoint.Call); + + public static ITransactionV1EntryPoint AddReservations => + new NativeTransactionV1EntryPoint(NativeEntryPoint.AddReservations); + + public static ITransactionV1EntryPoint CancelReservations => + new NativeTransactionV1EntryPoint(NativeEntryPoint.CancelReservations); + + public static ITransactionV1EntryPoint Custom(string name) => + new CustomTransactionV1EntryPoint(name); + + public class TransactionEntryPointConverter : JsonConverter + { + public override ITransactionV1EntryPoint Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + return new NativeTransactionV1EntryPoint(reader.GetString()); + } + + if (reader.TokenType == JsonTokenType.StartObject) + { + reader.Read(); + if (reader.TokenType == JsonTokenType.PropertyName && + reader.GetString() == "Custom") + { + reader.Read(); + var customEntryPoint = reader.GetString(); + reader.Read(); + return new CustomTransactionV1EntryPoint(customEntryPoint); + } + } + + throw new JsonException("Cannot deserialize TransactionEntryPoint."); + } + + public override void Write( + Utf8JsonWriter writer, + ITransactionV1EntryPoint value, + JsonSerializerOptions options) + { + switch (value) + { + case NativeTransactionV1EntryPoint nativeEntryPoint: + writer.WriteStringValue(nativeEntryPoint.Name); + break; + case CustomTransactionV1EntryPoint customEntryPoint: + writer.WriteStartObject(); + writer.WritePropertyName("Custom"); + writer.WriteStringValue(customEntryPoint.Name); + writer.WriteEndObject(); + break; + default: + throw new JsonException("Cannot serialize empty transaction entry point."); + } + } + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/TransactionV1Payload.cs b/Casper.Network.SDK/Types/TransactionV1Payload.cs new file mode 100644 index 0000000..3b1dd67 --- /dev/null +++ b/Casper.Network.SDK/Types/TransactionV1Payload.cs @@ -0,0 +1,267 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Text.Json.Serialization; +using Casper.Network.SDK.ByteSerializers; +using Casper.Network.SDK.Converters; +using Casper.Network.SDK.Utils; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Casper.Network.SDK.Types +{ + public class PayloadFields : BaseByteSerializer + { + [JsonPropertyName("args")] + [JsonConverter(typeof(NamedArgsListConverter))] + public List RuntimeArgs { get; init; } + + [JsonPropertyName("entry_point")] + [JsonConverter(typeof(TransactionV1EntryPoint.TransactionEntryPointConverter))] + public ITransactionV1EntryPoint EntryPoint { get; init; } + + [JsonPropertyName("scheduling")] + [JsonConverter(typeof(TransactionV1Scheduling.TransactionV1SchedulingConverter))] + public ITransactionV1Scheduling Scheduling { get; init; } + + [JsonPropertyName("target")] + [JsonConverter(typeof(TransactionV1Target.TransactionTargetConverter))] + public ITransactionV1Target Target { get; init; } + + private readonly Dictionary _fields = new Dictionary(); + + public void AddField(ushort field, byte[] value) + { + _fields.Add(field, value); + } + + public byte[] ToBytes() + { + var fields_bytes = new MemoryStream(); + WriteInteger(fields_bytes, _fields.Count); + foreach (var field in _fields) + { + WriteUShort(fields_bytes, field.Key); + var bytes = field.Value; + WriteBytes(fields_bytes, bytes); + } + return fields_bytes.ToArray(); + } + } + + public class TransactionV1PayloadJson + { + [JsonPropertyName("initiator_addr")] + public InitiatorAddr InitiatorAddr { get; set; } + + [JsonPropertyName("timestamp")] + [JsonConverter(typeof(DateTime2EpochConverter))] + public ulong Timestamp { get; set; } + + + /// + /// Duration of the Deploy in milliseconds (from timestamp). + /// + [JsonPropertyName("ttl")] + [JsonConverter(typeof(HumanizeTTLConverter))] + public ulong Ttl { get; set; } + + /// + /// Name of the chain where the deploy is executed. + /// + [JsonPropertyName("chain_name")] + public string ChainName { get; set; } + + /// + /// Pricing mode of a Transaction. + /// + [JsonPropertyName("pricing_mode")] + [JsonConverter(typeof(PricingMode.PricingModeConverter))] + public IPricingMode PricingMode { get; set; } + + [JsonPropertyName("fields")] + public PayloadFields Fields { get; set; } + } + + /// + /// The payload portion of a TransactionV1. + /// + [JsonConverter(typeof(TransactionV1PayloadConverter))] + public class TransactionV1Payload + { + /// + /// The address of the initiator of a transaction. + /// + [JsonPropertyName("initiator_addr")] + public InitiatorAddr InitiatorAddr { get; set; } + + /// + /// Timestamp formatted as per RFC 3339 + /// + [JsonPropertyName("timestamp")] + [JsonConverter(typeof(DateTime2EpochConverter))] + public ulong Timestamp { get; set; } + + /// + /// Duration of the Deploy in milliseconds (from timestamp). + /// + [JsonPropertyName("ttl")] + [JsonConverter(typeof(HumanizeTTLConverter))] + public ulong Ttl { get; set; } + + /// + /// Name of the chain where the deploy is executed. + /// + [JsonPropertyName("chain_name")] + public string ChainName { get; set; } + + /// + /// Pricing mode of a Transaction. + /// + [JsonPropertyName("pricing_mode")] + [JsonConverter(typeof(PricingMode.PricingModeConverter))] + public IPricingMode PricingMode { get; set; } + + // [JsonPropertyName("fields")] + // public PayloadFields Fields { get; set; } + + /// + /// List of runtime arguments. + /// + [JsonIgnore] + public List RuntimeArgs { get; init; } + + /// + /// Entry point or method of the contract to call. + /// + [JsonIgnore] + public ITransactionV1EntryPoint EntryPoint { get; init; } + + /// + /// Target contract of the transaction (native, custom or session). + /// + [JsonIgnore] + public ITransactionV1Target Target { get; init; } + + /// + /// Scheduling of the transaction. + /// + [JsonIgnore] + public ITransactionV1Scheduling Scheduling { get; init; } + + public TransactionV1Payload() + { + Timestamp = DateUtils.ToEpochTime(DateTime.UtcNow); + Ttl = 1800000; + } + + public class TransactionV1PayloadConverter : JsonConverter + { + public override TransactionV1Payload Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + try + { + // reader.Read(); // skip start object + var payloadJson = JsonSerializer.Deserialize(ref reader, options); + + return new TransactionV1Payload() + { + InitiatorAddr = payloadJson.InitiatorAddr, + Timestamp = payloadJson.Timestamp, + Ttl = payloadJson.Ttl, + PricingMode = payloadJson.PricingMode, + ChainName = payloadJson.ChainName, + RuntimeArgs = payloadJson.Fields.RuntimeArgs, + Target = payloadJson.Fields.Target, + EntryPoint = payloadJson.Fields.EntryPoint, + Scheduling = payloadJson.Fields.Scheduling, + }; + } + catch (Exception e) + { + throw new JsonException(e.Message); + } + } + + public override void Write( + Utf8JsonWriter writer, + TransactionV1Payload payload, + JsonSerializerOptions options) + { + var payloadJson = new TransactionV1PayloadJson() + { + InitiatorAddr = payload.InitiatorAddr, + Timestamp = payload.Timestamp, + Ttl = payload.Ttl, + PricingMode = payload.PricingMode, + ChainName = payload.ChainName, + Fields = new PayloadFields() + { + RuntimeArgs = payload.RuntimeArgs, + Target = payload.Target, + EntryPoint = payload.EntryPoint, + Scheduling = payload.Scheduling, + } + }; + JsonSerializer.Serialize(writer, payloadJson, options); + } + } + + const ushort INITIATOR_ADDR_FIELD_INDEX = 0; + const ushort TIMESTAMP_FIELD_INDEX = 1; + const ushort TTL_FIELD_INDEX = 2; + const ushort CHAIN_NAME_FIELD_INDEX = 3; + const ushort PRICING_MODE_FIELD_INDEX = 4; + const ushort FIELDS_FIELD_INDEX = 5; + + const ushort ARGS_MAP_KEY = 0; + const ushort TARGET_MAP_KEY = 1; + const ushort ENTRY_POINT_MAP_KEY = 2; + const ushort SCHEDULING_MAP_KEY = 3; + + public byte[] ToBytes() + { + var ms = new MemoryStream(); + var namedArgSerializer = new NamedArgByteSerializer(); + ms.WriteByte(0x00); + ms.Write(BitConverter.GetBytes(RuntimeArgs.Count)); + foreach (var args in RuntimeArgs) + ms.Write(namedArgSerializer.ToBytes(args)); + + var ms2 = new MemoryStream(); + ms2.Write(BitConverter.GetBytes(ms.ToArray().Length)); + ms2.Write(ms.ToArray()); + + var fields = new PayloadFields(); + fields.AddField(ARGS_MAP_KEY, ms2.ToArray()); + + ms2 = new MemoryStream(); + ms2.Write(BitConverter.GetBytes(Target.ToBytes().Length)); + ms2.Write(Target.ToBytes()); + fields.AddField(TARGET_MAP_KEY, ms2.ToArray()); + + ms2 = new MemoryStream(); + ms2.Write(BitConverter.GetBytes(EntryPoint.ToBytes().Length)); + ms2.Write(EntryPoint.ToBytes()); + fields.AddField(ENTRY_POINT_MAP_KEY, ms2.ToArray()); + + ms2 = new MemoryStream(); + ms2.Write(BitConverter.GetBytes(Scheduling.ToBytes().Length)); + ms2.Write(Scheduling.ToBytes()); + fields.AddField(SCHEDULING_MAP_KEY, ms2.ToArray()); + + return new CalltableSerialization() + .AddField(INITIATOR_ADDR_FIELD_INDEX, InitiatorAddr.ToBytes()) + .AddField(TIMESTAMP_FIELD_INDEX, CLValue.U64(Timestamp)) + .AddField(TTL_FIELD_INDEX, CLValue.U64(Ttl)) + .AddField(CHAIN_NAME_FIELD_INDEX, CLValue.String(ChainName)) + .AddField(PRICING_MODE_FIELD_INDEX, PricingMode.ToBytes()) + .AddField(FIELDS_FIELD_INDEX, fields.ToBytes()) + .GetBytes(); + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/TransactionV1Scheduling.cs b/Casper.Network.SDK/Types/TransactionV1Scheduling.cs new file mode 100644 index 0000000..19eb2b0 --- /dev/null +++ b/Casper.Network.SDK/Types/TransactionV1Scheduling.cs @@ -0,0 +1,193 @@ +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Casper.Network.SDK.ByteSerializers; +using Casper.Network.SDK.Utils; + +namespace Casper.Network.SDK.Types +{ + public enum TransactionV1SchedulingType + { + /// + /// No special scheduling applied. + /// + Standard = 0, + /// + /// Execution should be scheduled for the specified era. + /// + FutureEra = 1, + /// + /// Execution should be scheduled for the specified timestamp or later. + /// + FutureTimestamp = 2, + } + + public interface ITransactionV1Scheduling + { + public byte[] ToBytes(); + } + + public class StandardTransactionScheduling : ITransactionV1Scheduling + { + const ushort TAG_FIELD_INDEX = 0; + const byte STANDARD_VARIANT = 0; + + public byte[] ToBytes() + { + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] { STANDARD_VARIANT }) + .GetBytes(); + } + } + + public class StandardTransactionV1Scheduling : StandardTransactionScheduling + { + } + + public class FutureEraTransactionScheduling : ITransactionV1Scheduling + { + const ushort TAG_FIELD_INDEX = 0; + const byte FUTURE_ERA_VARIANT = 1; + const ushort FUTURE_ERA_ERA_ID_INDEX = 1; + + public ulong EraId { get; init; } + + public byte[] ToBytes() + { + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] { FUTURE_ERA_VARIANT } ) + .AddField(FUTURE_ERA_ERA_ID_INDEX, CLValue.U64(EraId)) + .GetBytes(); + } + } + + public class FutureEraTransactionV1Scheduling : FutureEraTransactionScheduling + { + } + + public class FutureTimestampTransactionScheduling : ITransactionV1Scheduling + { + public ulong Timestamp { get; init; } + + const ushort TAG_FIELD_INDEX = 0; + const byte FUTURE_TIMESTAMP_VARIANT = 2; + const ushort FUTURE_TIMESTAMP_TIMESTAMP_INDEX = 1; + + public byte[] ToBytes() + { + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] { FUTURE_TIMESTAMP_VARIANT }) + .AddField(FUTURE_TIMESTAMP_TIMESTAMP_INDEX, CLValue.U64(Timestamp)) + .GetBytes(); + } + } + + public class FutureTimestampTransactionV1Scheduling : FutureTimestampTransactionScheduling + { + } + + public abstract class TransactionScheduling + { + public static ITransactionV1Scheduling Standard => + new StandardTransactionV1Scheduling(); + + public static ITransactionV1Scheduling FutureEra(ulong eraId) + { + return new FutureEraTransactionV1Scheduling() + { + EraId = eraId, + }; + } + + public static ITransactionV1Scheduling FutureTimestamp(ulong timestamp) + { + return new FutureTimestampTransactionV1Scheduling() + { + Timestamp = timestamp, + }; + } + } + + public class TransactionV1Scheduling : TransactionScheduling + { + public class TransactionV1SchedulingConverter : JsonConverter + { + public override ITransactionV1Scheduling Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + ITransactionV1Scheduling v1Scheduling; + + if (reader.TokenType == JsonTokenType.String) + { + var schedulingType = reader.GetString(); + switch (schedulingType) + { + case "Standard": + v1Scheduling = new StandardTransactionV1Scheduling(); + break; + default: + throw new JsonException( + "Cannot deserialize TransactionScheduling. Unknown scheduling type"); + } + } + else if (reader.TokenType == JsonTokenType.StartObject) + { + reader.Read(); // skip start object + var schedulingType = reader.GetString(); + reader.Read(); + switch (schedulingType) + { + case "FutureEra": + v1Scheduling = new FutureEraTransactionV1Scheduling + { + EraId = reader.GetUInt64(), + }; + break; + case "FutureTimestamp": + v1Scheduling = new FutureTimestampTransactionV1Scheduling + { + Timestamp = DateUtils.ToEpochTime(reader.GetString()), + }; + break; + default: + throw new JsonException( + "Cannot deserialize TransactionScheduling. Unknown scheduling type"); + } + + reader.Read(); + } + else + throw new JsonException("Cannot deserialize TransactionScheduling."); + + return v1Scheduling; + } + + public override void Write( + Utf8JsonWriter writer, + ITransactionV1Scheduling value, + JsonSerializerOptions options) + { + switch (value) + { + case StandardTransactionV1Scheduling: + writer.WriteStringValue("Standard"); + break; + case FutureEraTransactionV1Scheduling eraScheduling: + writer.WriteStartObject(); + writer.WriteNumber("FutureEra", eraScheduling.EraId); + writer.WriteEndObject(); + break; + case FutureTimestampTransactionV1Scheduling timestampScheduling: + writer.WriteStartObject(); + writer.WriteString("FutureTimestamp", DateUtils.ToISOString(timestampScheduling.Timestamp)); + writer.WriteEndObject(); + break; + default: + throw new JsonException("Cannot serialize due to unkown transaction scheduling type."); + } + } + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/TransactionV1Target.cs b/Casper.Network.SDK/Types/TransactionV1Target.cs new file mode 100644 index 0000000..ce6ff13 --- /dev/null +++ b/Casper.Network.SDK/Types/TransactionV1Target.cs @@ -0,0 +1,517 @@ +using System; +using System.IO; +using System.Text.Json; +using System.Text.Json.Serialization; +using Casper.Network.SDK.ByteSerializers; +using Casper.Network.SDK.Converters; +using Org.BouncyCastle.Utilities.Encoders; + +namespace Casper.Network.SDK.Types +{ + public enum InvocationTargetTag + { + ByHash = 0, + ByName = 1, + ByPackageHash = 2, + ByPackageName = 3, + } + + public class ByHashInvocationTarget : IInvocationTarget + { + public string Hash { get; init; } + + const ushort TAG_FIELD_INDEX = 0; + const byte BY_HASH_VARIANT = 0; + const ushort BY_HASH_HASH_INDEX = 1; + + public byte[] ToBytes() + { + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] { BY_HASH_VARIANT }) + .AddField(BY_HASH_HASH_INDEX, Hex.Decode(Hash)) + .GetBytes(); + } + } + + public class ByNameInvocationTarget : IInvocationTarget + { + public string Name { get; init; } + + const ushort TAG_FIELD_INDEX = 0; + const byte BY_NAME_VARIANT = 1; + const ushort BY_NAME_NAME_INDEX = 1; + + public byte[] ToBytes() + { + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] { BY_NAME_VARIANT }) + .AddField(BY_NAME_NAME_INDEX, CLValue.String(Name)) + .GetBytes(); + } + } + + public class ByPackageHashInvocationTarget : IInvocationTarget + { + [JsonPropertyName("addr")] public string Hash { get; init; } + + [JsonPropertyName("version")] public UInt32? Version { get; init; } + + const ushort TAG_FIELD_INDEX = 0; + const byte BY_PACKAGE_HASH_VARIANT = 2; + const ushort BY_PACKAGE_HASH_ADDR_INDEX = 1; + const ushort BY_PACKAGE_HASH_VERSION_INDEX = 2; + + public byte[] ToBytes() + { + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] { BY_PACKAGE_HASH_VARIANT }) + .AddField(BY_PACKAGE_HASH_ADDR_INDEX, Hex.Decode(Hash)) + .AddField(BY_PACKAGE_HASH_VERSION_INDEX, Version.HasValue + ? CLValue.Option(CLValue.U32(Version.Value)) + : CLValue.OptionNone(CLType.U32)) + .GetBytes(); + } + } + + public class ByPackageNameInvocationTarget : IInvocationTarget + { + [JsonPropertyName("name")] public string Name { get; init; } + + [JsonPropertyName("version")] public UInt32? Version { get; init; } + + const ushort TAG_FIELD_INDEX = 0; + const byte BY_PACKAGE_NAME_VARIANT = 3; + const ushort BY_PACKAGE_NAME_NAME_INDEX = 1; + const ushort BY_PACKAGE_NAME_VERSION_INDEX = 2; + + public byte[] ToBytes() + { + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] { BY_PACKAGE_NAME_VARIANT }) + .AddField(BY_PACKAGE_NAME_NAME_INDEX, CLValue.String(Name)) + .AddField(BY_PACKAGE_NAME_VERSION_INDEX, Version.HasValue + ? CLValue.Option(CLValue.U32(Version.Value)) + : CLValue.OptionNone(CLType.U32)).GetBytes(); + } + } + + [JsonConverter(typeof(InvocationTargetConverter))] + public interface IInvocationTarget + { + public class InvocationTargetConverter : JsonConverter + { + public override IInvocationTarget Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + IInvocationTarget target; + + if (reader.TokenType == JsonTokenType.StartObject) + { + reader.Read(); // skip start object + var idType = reader.GetString(); + reader.Read(); + switch (idType) + { + case "ByHash": + target = new ByHashInvocationTarget { Hash = reader.GetString() }; + reader.Read(); + break; + case "ByName": + target = new ByNameInvocationTarget { Name = reader.GetString() }; + reader.Read(); + break; + case "ByPackageHash": + target = JsonSerializer.Deserialize(ref reader, options); + break; + case "ByPackageName": + target = JsonSerializer.Deserialize(ref reader, options); + break; + default: + throw new JsonException( + "Cannot deserialize TransactionScheduling. Unknown scheduling type"); + } + + reader.Read(); // skip end object + } + else + throw new JsonException("Cannot deserialize TransactionScheduling."); + + return target; + } + + public override void Write( + Utf8JsonWriter writer, + IInvocationTarget value, + JsonSerializerOptions options) + { + writer.WriteStartObject(); + + if (value is ByHashInvocationTarget byHash) + writer.WriteString("ByHash", byHash.Hash); + else if (value is ByNameInvocationTarget byName) + writer.WriteString("ByName", byName.Name); + else if (value is ByPackageHashInvocationTarget byPackageHash) + { + writer.WritePropertyName("ByPackageHash"); + JsonSerializer.Serialize(writer, byPackageHash); + } + else if (value is ByPackageNameInvocationTarget byPackageName) + { + writer.WritePropertyName("ByPackageName"); + JsonSerializer.Serialize(writer, byPackageName); + } + else + throw new JsonException("Unknown invocation target type."); + + writer.WriteEndObject(); + } + } + + public byte[] ToBytes(); + } + + public enum TransactionTargetType + { + Native = 0, + Stored = 1, + Session = 2, + } + + public class TransactionRuntime + { + private const byte VM_CASPER_V1_TAG = 0; + private const byte VM_CASPER_V2_TAG = 1; + private byte _tag = VM_CASPER_V1_TAG; + + public static TransactionRuntime FromString(string json) + { + switch (json) + { + case "VmCasperV1": + return VmCasperV1(); + case "VmCasperV2": + return VmCasperV2(); + default: + throw new JsonException($"Unknown TransactionRuntime '{json}'"); + } + } + + public override string ToString() + { + switch (_tag) + { + case VM_CASPER_V1_TAG: + return "VmCasperV1"; + case VM_CASPER_V2_TAG: + return "VmCasperV2"; + default: + throw new JsonException($"Unknown TransactionRuntime '{_tag}'"); + } + } + + /// + /// The Casper Version 1 Virtual Machine. + /// + public static TransactionRuntime VmCasperV1() + { + return new TransactionRuntime() { _tag = VM_CASPER_V1_TAG }; + } + + /// + /// The Casper Version 2 Virtual Machine. + /// + public static TransactionRuntime VmCasperV2() + { + return new TransactionRuntime() { _tag = VM_CASPER_V2_TAG }; + } + + const ushort TAG_FIELD_INDEX = 0; + + public byte[] ToBytes() + { + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] { _tag }) + .GetBytes(); + } + } + + public interface ITransactionV1Target + { + public byte[] ToBytes(); + } + + public class NativeTransactionV1Target : ITransactionV1Target + { + const ushort TAG_FIELD_INDEX = 0; + const byte NATIVE_VARIANT = 0; + + public byte[] ToBytes() + { + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] { NATIVE_VARIANT }) + .GetBytes(); + } + } + + public class StoredTransactionV1Target : ITransactionV1Target + { + [JsonPropertyName("id")] + public IInvocationTarget Id { get; set; } + + /// + /// Targeted Casper VM version. + /// + [JsonPropertyName("runtime")] + public TransactionRuntime Runtime { get; set; } + + public StoredTransactionV1Target() + { + Runtime = TransactionRuntime.VmCasperV1(); + } + + const ushort TAG_FIELD_INDEX = 0; + const byte STORED_VARIANT = 1; + const ushort STORED_ID_INDEX = 1; + const ushort STORED_RUNTIME_INDEX = 2; + + public byte[] ToBytes() + { + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] { STORED_VARIANT }) + .AddField(STORED_ID_INDEX, Id.ToBytes()) + .AddField(STORED_RUNTIME_INDEX, Runtime.ToBytes()) + .GetBytes(); + } + } + + public class SessionTransactionV1Target : ITransactionV1Target + { + /// + /// Flag determining if the Wasm is an install/upgrade. + /// + [JsonPropertyName("is_install_upgrade")] + public bool IsInstallUpgrade { get; set; } + + /// + /// Wasm bytes for a Session transaction type. + /// + [JsonPropertyName("module_bytes")] + [JsonConverter(typeof(HexBytesConverter))] + public byte[] ModuleBytes { get; set; } + + /// + /// Targeted Casper VM version. + /// + [JsonPropertyName("runtime")] + public TransactionRuntime Runtime { get; set; } + + public SessionTransactionV1Target() + { + Runtime = TransactionRuntime.VmCasperV1(); + } + + const ushort TAG_FIELD_INDEX = 0; + const byte SESSION_VARIANT = 2; + const ushort SESSION_IS_INSTALL_INDEX = 1; + const ushort SESSION_RUNTIME_INDEX = 2; + const ushort SESSION_MODULE_BYTES_INDEX = 3; + + public byte[] ToBytes() + { + var ms = new MemoryStream(); + ms.Write(BitConverter.GetBytes(ModuleBytes.Length)); + ms.Write(ModuleBytes); + + return new CalltableSerialization() + .AddField(TAG_FIELD_INDEX, new byte[] { SESSION_VARIANT }) + .AddField(SESSION_IS_INSTALL_INDEX, new byte[] { IsInstallUpgrade ? (byte)0x01 : (byte)0x00 }) + .AddField(SESSION_RUNTIME_INDEX, Runtime.ToBytes()) + .AddField(SESSION_MODULE_BYTES_INDEX, ms.ToArray()) + .GetBytes(); + } + } + + public class TransactionV1Target + { + public static ITransactionV1Target Native => new NativeTransactionV1Target(); + + public static StoredTransactionV1Target StoredByHash(string contractHash) + { + return new StoredTransactionV1Target() + { + Id = new ByHashInvocationTarget { Hash = contractHash }, + }; + } + + public static StoredTransactionV1Target StoredByName(string name) + { + return new StoredTransactionV1Target() + { + Id = new ByNameInvocationTarget { Name = name }, + }; + } + + public static StoredTransactionV1Target StoredByPackageHash(string packageHash, UInt32? version = null) + { + return new StoredTransactionV1Target() + { + Id = new ByPackageHashInvocationTarget { Hash = packageHash, Version = version }, + }; + } + + public static StoredTransactionV1Target StoredByPackageName(string name, UInt32? version = null) + { + return new StoredTransactionV1Target() + { + Id = new ByPackageNameInvocationTarget() { Name = name, Version = version }, + }; + } + + public static SessionTransactionV1Target Session(byte[] moduleBytes) + { + return new SessionTransactionV1Target() + { + ModuleBytes = moduleBytes, + }; + } + + public class TransactionTargetConverter : JsonConverter + { + public override ITransactionV1Target Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.String) + { + var targetType = reader.GetString(); + + switch (targetType) + { + case "Native": + return new NativeTransactionV1Target(); + default: + throw new JsonException($"TransactionTargetType '{targetType}' not supported."); + } + } + + if (reader.TokenType == JsonTokenType.StartObject) + { + ITransactionV1Target transactionTarget = null; + IInvocationTarget id = null; + string module_bytes = null; + bool is_install_upgrade = false; + TransactionRuntime runtime = TransactionRuntime.VmCasperV1(); + + reader.Read(); // skip start object + var targetType = reader.GetString(); + reader.Read(); + + switch (targetType) + { + case "Stored": + reader.Read(); + while (reader.TokenType != JsonTokenType.EndObject) + { + var prop = reader.GetString(); + reader.Read(); + switch (prop) + { + case "id": + id = JsonSerializer.Deserialize(ref reader, options); + reader.Read(); + break; + case "runtime": + runtime = TransactionRuntime.FromString(reader.GetString()); + reader.Read(); // skip end object + break; + } + } + + reader.Read(); // skip end object + + transactionTarget = new StoredTransactionV1Target() + { + Id = id, + Runtime = runtime, + }; + break; + case "Session": + reader.Read(); + while (reader.TokenType != JsonTokenType.EndObject) + { + var prop = reader.GetString(); + reader.Read(); + switch (prop) + { + case "is_install_upgrade": + is_install_upgrade = reader.GetBoolean(); + reader.Read(); + break; + case "module_bytes": + module_bytes = reader.GetString(); + reader.Read(); + break; + case "runtime": + runtime = TransactionRuntime.FromString(reader.GetString()); + reader.Read(); + break; + } + } + + reader.Read(); // skip end object + + transactionTarget = new SessionTransactionV1Target() + { + IsInstallUpgrade = is_install_upgrade, + ModuleBytes = Hex.Decode(module_bytes), + Runtime = runtime, + }; + break; + default: + throw new JsonException($"TransactionTargetType '{targetType}' not supported."); + } + + return transactionTarget; + } + + throw new JsonException("Cannot deserialize TransactionTarget. PropertyName expected"); + } + + public override void Write( + Utf8JsonWriter writer, + ITransactionV1Target value, + JsonSerializerOptions options) + { + switch (value) + { + case NativeTransactionV1Target: + writer.WriteStringValue("Native"); + break; + case StoredTransactionV1Target storedTarget: + writer.WriteStartObject(); + writer.WriteStartObject("Stored"); + writer.WritePropertyName("id"); + JsonSerializer.Serialize(writer, storedTarget.Id); + writer.WriteString("runtime", storedTarget.Runtime.ToString()); + writer.WriteEndObject(); + writer.WriteEndObject(); + break; + case SessionTransactionV1Target sessionTarget: + writer.WriteStartObject(); + writer.WriteStartObject("Session"); + writer.WriteBoolean("is_install_upgrade", sessionTarget.IsInstallUpgrade); + writer.WriteString("module_bytes", Hex.ToHexString(sessionTarget.ModuleBytes)); + writer.WriteString("runtime", sessionTarget.Runtime.ToString()); + writer.WriteEndObject(); + writer.WriteEndObject(); + break; + default: + throw new JsonException("Cannot serialize empty transaction target."); + } + } + } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/Transfer.cs b/Casper.Network.SDK/Types/Transfer.cs index ef892d8..5a5794d 100644 --- a/Casper.Network.SDK/Types/Transfer.cs +++ b/Casper.Network.SDK/Types/Transfer.cs @@ -1,4 +1,6 @@ +using System; using System.Numerics; +using System.Text.Json; using System.Text.Json.Serialization; using Casper.Network.SDK.Converters; @@ -7,7 +9,7 @@ namespace Casper.Network.SDK.Types /// /// Represents a transfer from one purse to another /// - public class Transfer + public class TransferV1 { /// /// Transfer amount @@ -63,4 +65,174 @@ public class Transfer [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] public AccountHashKey To { get; init; } } + + /// + /// Represents a version 2 transfer from one purse to another + /// + [JsonConverter(typeof(TransferConverter))] + public class Transfer + { + protected int _version; + + [JsonIgnore] + public int Version + { + get { return _version; } + } + + protected TransferV1 _transferV1; + + public static explicit operator TransferV1(Transfer transfer) + { + if(transfer._version == 1) + return transfer._transferV1; + + throw new InvalidCastException("Version2 transfer cannot be converted to Version1"); + } + + public static explicit operator Transfer(TransferV1 transfer) + { + return new Transfer() + { + _version = 1, + _transferV1 = transfer, + Amount = transfer.Amount, + From = new InitiatorAddr { AccountHash = transfer.From }, + To = transfer.To, + Id = transfer.Id, + TransactionHash = new TransactionHash() { Deploy = transfer.DeployHash }, + Source = transfer.Source, + Target = transfer.Target, + Gas = transfer.Gas, + }; + } + + /// + /// Transfer amount + /// + [JsonPropertyName("amount")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger Amount { get; init; } + + /// + /// Transaction that created the transfer + /// + [JsonPropertyName("transaction_hash")] + public TransactionHash TransactionHash { get; init; } + + /// + /// Entity from which transfer was executed + /// + [JsonPropertyName("from")] + public InitiatorAddr From { get; init; } + + /// + /// Gas + /// + [JsonPropertyName("gas")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger Gas { get; init; } + + /// + /// User-defined id + /// + [JsonPropertyName("id")] + public ulong? Id { get; init; } + + /// + /// Source purse + /// + [JsonPropertyName("source")] + [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] + public URef Source { get; init; } + + /// + /// Target purse + /// + [JsonPropertyName("target")] + [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] + public URef Target { get; init; } + + /// + /// Account to which funds are transferred + /// + [JsonPropertyName("to")] + [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] + public AccountHashKey To { get; init; } + + public Transfer() + { + // this value is overriden in FromTransferV1 + _version = 2; + } + + /// + /// Json converter class to serialize/deserialize a Block to/from Json + /// + public class TransferConverter : JsonConverter + { + public override Transfer Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + try + { + using (JsonDocument doc = JsonDocument.ParseValue(ref reader)) + { + JsonElement root = doc.RootElement; + + if (root.TryGetProperty("Version1", out JsonElement v1Element)) + { + var transfer = JsonSerializer.Deserialize(v1Element.GetRawText(), options); + return transfer != null ? (Transfer)transfer : null; + } + + if (root.TryGetProperty("Version2", out JsonElement v2Element)) + { + var transfer = JsonSerializer.Deserialize(v2Element.GetRawText()); + return transfer; + } + + // try as Casper node v1.x for backward compatibility + var transferv1 = JsonSerializer.Deserialize(root.GetRawText(), options); + return transferv1 != null ? (Transfer)transferv1 : null; + } + } + catch (Exception e) + { + throw new JsonException(e.Message); + } + } + + public override void Write( + Utf8JsonWriter writer, + Transfer transfer, + JsonSerializerOptions options) + { + switch (transfer.Version) + { + case 1: + writer.WritePropertyName("Version1"); + writer.WriteStartObject(); + JsonSerializer.Serialize((TransferV1)transfer, options); + writer.WriteEndObject(); + break; + case 2: + writer.WritePropertyName("Version2"); + writer.WriteStartObject(); + JsonSerializer.Serialize(transfer, options); + writer.WriteEndObject(); + break; + default: + throw new JsonException($"Unexpected transfer version {transfer.Version}"); + } + } + } + } + + internal class TransferV2 : Transfer + { + + } } \ No newline at end of file diff --git a/Casper.Network.SDK/Types/Transform.cs b/Casper.Network.SDK/Types/Transform.cs index e9ee8e9..39bd87b 100644 --- a/Casper.Network.SDK/Types/Transform.cs +++ b/Casper.Network.SDK/Types/Transform.cs @@ -1,35 +1,133 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Numerics; using System.Text.Json; using System.Text.Json.Serialization; namespace Casper.Network.SDK.Types { - /// - /// Enumeration of transformation types used in the execution of a deploy. - /// - public enum TransformType + public enum TransformKindV2 { Identity, - WriteContractWasm, - WriteContract, - WriteContractPackage, - WriteCLValue, - WriteAccount, - WriteDeployInfo, - WriteEraInfo, - WriteTransfer, - WriteBid, - WriteWithdraw, + Write, AddInt32, AddUInt64, AddUInt128, AddUInt256, AddUInt512, AddKeys, + Prune, Failure, - WriteUnbonding, + } + + public abstract class TransformKind + { + public object Value { get; init; } + } + + public abstract class TransformKind : TransformKind + { + public new abstract T Value { get; init; } + } + + /// + /// An identity transformation that does not modify a value in the global state. Created as a result of + /// reading from the global state. + /// + public class IdentityTransformKind : TransformKind + { + public override object Value { get; init; } + } + + /// + /// Writes a new value (StoredValue) in the global state. + /// + public class WriteTransformKind : TransformKind + { + public override StoredValue Value { get; init; } + } + + /// + /// A wrapping addition of an `i32` to an existing numeric value (not necessarily an `i32`) in the global state. + /// + public class AddInt32TransformKind : TransformKind + { + public override int Value { get; init; } + } + + /// + /// A wrapping addition of a `u64` to an existing numeric value (not necessarily an `u64`) in the global state. + /// + public class AddUInt64TransformKind : TransformKind + { + public override ulong Value { get; init; } + } + + /// + /// A wrapping addition of a `U128` to an existing numeric value (not necessarily an `U128`) in the global state. + /// + public class AddUInt128TransformKind : TransformKind + { + public override BigInteger Value { get; init; } + } + + /// + /// A wrapping addition of a `U256` to an existing numeric value (not necessarily an `U256`) in the global state. + /// + public class AddUInt256TransformKind : TransformKind + { + public override BigInteger Value { get; init; } + } + + /// + /// A wrapping addition of a `U512` to an existing numeric value (not necessarily an `U512`) in the global state. + /// + public class AddUInt512TransformKind : TransformKind + { + public override BigInteger Value { get; init; } + } + + /// + /// Adds new named keys to an existing entry in the global state.\n\nThis transform assumes that the existing stored + /// value is either an Account or a Contract. + /// + public class AddKeysTransformKind : TransformKind> + { + public override List Value { get; init; } + } + + /// + /// Removes the pathing to the global state entry of the specified key. The pruned element remains reachable from + /// previously generated global state root hashes, but will not be included in the next generated global state + /// root hash and subsequent state accumulated from it. + /// + public class PruneTransformKind : TransformKind + { + public override GlobalStateKey Value { get; init; } + } + + /// + /// Represents the case where applying a transform would cause an error. + /// + public class FailureTransformKind : TransformKind + { + public override string Value { get; init; } + } + + public class WriteContractLegacyTransformKind : TransformKind + { + public override object Value { get; init; } + } + + public class WriteContractPackageLegacyTransformKind : TransformKind + { + public override object Value { get; init; } + } + + public class WriteContractWasmLegacyTransformKind : TransformKind + { + public override object Value { get; init; } } /// @@ -37,21 +135,86 @@ public enum TransformType /// public class Transform { + protected int _version; + /// - /// The formatted string of the `Key`. + /// Returns the version of the block. /// - [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] - public GlobalStateKey Key { get; init; } + public int Version + { + get { return _version; } + } + + protected TransformV1 _transformV1; + + public static explicit operator TransformV1(Transform transform) + { + if (transform._version == 1) + return transform._transformV1; + + throw new InvalidCastException("Version2 transform cannot be converted to Version1"); + } + + public static explicit operator Transform(TransformV1 transform) + { + TransformKind kind = transform.Kind switch + { + TransformKindV1.Identity => new IdentityTransformKind(), + TransformKindV1.WriteAccount => new WriteTransformKind() + { Value = new StoredValue() { Account = transform.Value as Account } }, + // TransformKindV1.WriteAddressableEntity => new WriteTransformKind() + // { Value = new StoredValue() { AddressableEntity = transform.Value as AddressableEntity } }, + TransformKindV1.WriteBid => new WriteTransformKind() + { Value = new StoredValue() { Bid = transform.Value as Bid } }, + // TransformKindV1.WriteBidKind => new WriteTransformKind() + // { Value = new StoredValue() { BidKind = transform.Value as BidKind } }, + TransformKindV1.WriteCLValue => new WriteTransformKind() + { Value = new StoredValue() { CLValue = transform.Value as CLValue } }, + TransformKindV1.WriteContract => new WriteContractLegacyTransformKind(), + TransformKindV1.WriteContractPackage => new WriteContractPackageLegacyTransformKind(), + TransformKindV1.WriteContractWasm => new WriteContractWasmLegacyTransformKind(), + TransformKindV1.WriteDeployInfo => new WriteTransformKind() + { Value = new StoredValue() { DeployInfo = transform.Value as DeployInfo } }, + TransformKindV1.WriteEraInfo => new WriteTransformKind() + { Value = new StoredValue() { EraInfo = transform.Value as EraInfo } }, + TransformKindV1.WriteTransfer => new WriteTransformKind() + { Value = new StoredValue() { Transfer = (Transfer)(transform.Value as TransferV1) } }, + TransformKindV1.WriteUnbonding => new WriteTransformKind() + { Value = new StoredValue() { Unbonding = transform.Value as List } }, + TransformKindV1.WriteWithdraw => new WriteTransformKind() + { Value = new StoredValue() { Withdraw = transform.Value as List } }, + TransformKindV1.AddInt32 => new AddInt32TransformKind() { Value = (int)transform.Value }, + TransformKindV1.AddUInt64 => new AddUInt64TransformKind() { Value = (ulong)transform.Value }, + TransformKindV1.AddUInt128 => new AddUInt128TransformKind() { Value = (BigInteger)transform.Value }, + TransformKindV1.AddUInt256 => new AddUInt256TransformKind() { Value = (BigInteger)transform.Value }, + TransformKindV1.AddUInt512 => new AddUInt512TransformKind() { Value = (BigInteger)transform.Value }, + TransformKindV1.AddKeys => new AddKeysTransformKind() { Value = transform.Value as List }, + TransformKindV1.Failure => new FailureTransformKind() { Value = (string)transform.Value }, + // TransformKindV1.Prune => new PruneTransformKind() {Value = transform.Value as GlobalStateKey }, + _ => throw new Exception("Cannot convert '" + transform.Kind + "' to TransofrmKind v2."), + }; + + return new Transform + { + _version = 1, + _transformV1 = transform, + Key = transform.Key, + Kind = kind, + }; + } /// - /// The type of transform + /// The formatted string of the `Key`. /// - public TransformType Type { get; init; } + [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] + public GlobalStateKey Key { get; init; } /// - /// Data associated to some type of transforms + /// Representation of a single transformation occurring during execution.\n\nNote that all arithmetic + /// variants of `TransformKind` are commutative which means that a given collection of them can be + /// executed in any order to produce the same end result. /// - public object Value { get; init; } + public TransformKind Kind { get; init; } public class TransformConverter : JsonConverter { @@ -66,7 +229,7 @@ public override Transform Read( reader.Read(); // start object string key = null; - TransformType? type = null; + TransformKind? kind = null; object value = null; while (reader.TokenType == JsonTokenType.PropertyName) @@ -78,82 +241,66 @@ public override Transform Read( key = reader.GetString(); reader.Read(); } - else if (field == "transform") + else if (field == "kind") { if (reader.TokenType == JsonTokenType.String) { - var stype = reader.GetString(); - if (stype != null) - type = EnumCompat.Parse(stype); + if (TransformKindV2.Identity.ToString().Equals(reader.GetString())) + kind = new IdentityTransformKind(); + else + throw new Exception("Unexpected transform kind: " + reader.GetString()); reader.Read(); } else if (reader.TokenType == JsonTokenType.StartObject) { - reader.Read(); + reader.Read(); // skip start object var stype = reader.GetString(); - if (stype != null) - type = EnumCompat.Parse(stype); + var kindv2 = EnumCompat.Parse(stype); reader.Read(); - switch (type) + switch (kindv2) { - case TransformType.WriteCLValue: - value = JsonSerializer.Deserialize(ref reader, options); - reader.Read(); // end object - break; - case TransformType.WriteAccount: - value = GlobalStateKey.FromString(reader.GetString()); - reader.Read(); // end object - break; - case TransformType.WriteDeployInfo: - value = JsonSerializer.Deserialize(ref reader, options); - reader.Read(); // end object - break; - case TransformType.WriteEraInfo: - value = JsonSerializer.Deserialize(ref reader, options); - reader.Read(); // end object - break; - case TransformType.WriteTransfer: - value = JsonSerializer.Deserialize(ref reader, options); - reader.Read(); // end object - break; - case TransformType.WriteBid: - value = JsonSerializer.Deserialize(ref reader, options); - reader.Read(); // end object - break; - case TransformType.WriteWithdraw: - value = JsonSerializer.Deserialize>(ref reader, options); + case TransformKindV2.Write: + var storedValue = JsonSerializer.Deserialize(ref reader, options); + kind = new WriteTransformKind() { Value = storedValue }; reader.Read(); // end object break; - case TransformType.AddInt32: - value = reader.GetInt32(); + case TransformKindV2.AddInt32: + kind = new AddInt32TransformKind() { Value = reader.GetInt32() }; reader.Read(); break; - case TransformType.AddUInt64: - value = reader.GetUInt64(); + case TransformKindV2.AddUInt64: + kind = new AddUInt64TransformKind() { Value = reader.GetUInt64() }; reader.Read(); break; - case TransformType.AddUInt128: - value = BigInteger.Parse(reader.GetString() ?? "0"); + case TransformKindV2.AddUInt128: + var u128 = BigInteger.Parse(reader.GetString() ?? "0"); + kind = new AddUInt128TransformKind() { Value = u128 }; reader.Read(); break; - case TransformType.AddUInt256: - value = BigInteger.Parse(reader.GetString() ?? "0"); + case TransformKindV2.AddUInt256: + var u256 = BigInteger.Parse(reader.GetString() ?? "0"); + kind = new AddUInt256TransformKind() { Value = u256 }; reader.Read(); break; - case TransformType.AddUInt512: - value = BigInteger.Parse(reader.GetString() ?? "0"); + case TransformKindV2.AddUInt512: + var u512 = BigInteger.Parse(reader.GetString() ?? "0"); + kind = new AddUInt512TransformKind() { Value = u512 }; reader.Read(); break; - case TransformType.AddKeys: - value = JsonSerializer.Deserialize>(ref reader, options); + case TransformKindV2.AddKeys: + var namedKeys = + JsonSerializer.Deserialize>(ref reader, options); + kind = new AddKeysTransformKind() { Value = namedKeys }; reader.Read(); // end array break; - case TransformType.Failure: - value = reader.GetString(); + case TransformKindV2.Prune: + var prunedKey = GlobalStateKey.FromString(reader.GetString()); + kind = new PruneTransformKind() { Value = prunedKey }; reader.Read(); break; - case TransformType.WriteUnbonding: - value = JsonSerializer.Deserialize>(ref reader, options); + case TransformKindV2.Failure: + var json = JsonSerializer.Deserialize(ref reader, options); + kind = new FailureTransformKind() { Value = json.GetRawText() }; reader.Read(); break; } @@ -163,13 +310,12 @@ public override Transform Read( } } - if (key != null & type != null) + if (key != null & kind != null) { return new Transform() { Key = GlobalStateKey.FromString(key), - Type = type ?? TransformType.Identity, - Value = value + Kind = kind, }; } @@ -181,8 +327,8 @@ public override void Write( Transform value, JsonSerializerOptions options) { - throw new NotImplementedException("Write method for Transform not yet implemented"); + throw new NotImplementedException("Write method for TransformV2 not yet implemented"); } } } -} +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/TransformV1.cs b/Casper.Network.SDK/Types/TransformV1.cs new file mode 100644 index 0000000..b653f0f --- /dev/null +++ b/Casper.Network.SDK/Types/TransformV1.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace Casper.Network.SDK.Types +{ + /// + /// Enumeration of transformation types used in the execution of a deploy. + /// + public enum TransformKindV1 + { + Identity, + WriteContractWasm, + WriteContract, + WriteContractPackage, + WriteCLValue, + WriteAccount, + WriteDeployInfo, + WriteEraInfo, + WriteTransfer, + WriteBid, + WriteWithdraw, + AddInt32, + AddUInt64, + AddUInt128, + AddUInt256, + AddUInt512, + AddKeys, + Failure, + WriteUnbonding, + WriteAddressableEntity, + Prune, + WriteBidKind, + } + + /// + /// A transformation performed while executing a deploy. + /// + public class TransformV1 + { + /// + /// The formatted string of the `Key`. + /// + [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] + public GlobalStateKey Key { get; init; } + + /// + /// The type of transform + /// + public TransformKindV1 Kind { get; init; } + + /// + /// Data associated to some type of transforms + /// + public object Value { get; init; } + + public class TransformV1Converter : JsonConverter + { + public override TransformV1 Read( + ref Utf8JsonReader reader, + Type typeToConvert, + JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + throw new JsonException("Cannot deserialize Transform. StartObject expected"); + + reader.Read(); // start object + + string key = null; + TransformKindV1? type = null; + object value = null; + + while (reader.TokenType == JsonTokenType.PropertyName) + { + var field = reader.GetString(); + reader.Read(); + if (field == "key") + { + key = reader.GetString(); + reader.Read(); + } + else if (field == "transform") + { + if (reader.TokenType == JsonTokenType.String) + { + var stype = reader.GetString(); + if (stype != null) + type = EnumCompat.Parse(stype); + reader.Read(); + } + else if (reader.TokenType == JsonTokenType.StartObject) + { + reader.Read(); + var stype = reader.GetString(); + if (stype != null) + type = EnumCompat.Parse(stype); + reader.Read(); + switch (type) + { + case TransformKindV1.WriteCLValue: + value = JsonSerializer.Deserialize(ref reader, options); + reader.Read(); // end object + break; + case TransformKindV1.WriteAccount: + value = GlobalStateKey.FromString(reader.GetString()); + reader.Read(); // end object + break; + case TransformKindV1.WriteDeployInfo: + value = JsonSerializer.Deserialize(ref reader, options); + reader.Read(); // end object + break; + case TransformKindV1.WriteEraInfo: + value = JsonSerializer.Deserialize(ref reader, options); + reader.Read(); // end object + break; + case TransformKindV1.WriteTransfer: + value = JsonSerializer.Deserialize(ref reader, options); + reader.Read(); // end object + break; + case TransformKindV1.WriteBid: + value = JsonSerializer.Deserialize(ref reader, options); + reader.Read(); // end object + break; + case TransformKindV1.WriteWithdraw: + value = JsonSerializer.Deserialize>(ref reader, options); + reader.Read(); // end object + break; + case TransformKindV1.AddInt32: + value = reader.GetInt32(); + reader.Read(); + break; + case TransformKindV1.AddUInt64: + value = reader.GetUInt64(); + reader.Read(); + break; + case TransformKindV1.AddUInt128: + value = BigInteger.Parse(reader.GetString() ?? "0"); + reader.Read(); + break; + case TransformKindV1.AddUInt256: + value = BigInteger.Parse(reader.GetString() ?? "0"); + reader.Read(); + break; + case TransformKindV1.AddUInt512: + value = BigInteger.Parse(reader.GetString() ?? "0"); + reader.Read(); + break; + case TransformKindV1.AddKeys: + value = JsonSerializer.Deserialize>(ref reader, options); + reader.Read(); // end array + break; + case TransformKindV1.Failure: + value = reader.GetString(); + reader.Read(); + break; + case TransformKindV1.WriteUnbonding: + value = JsonSerializer.Deserialize>(ref reader, options); + reader.Read(); + break; + case TransformKindV1.Prune: + value = GlobalStateKey.FromString(reader.GetString()); + reader.Read(); + break; + case TransformKindV1.WriteBidKind: + value = JsonSerializer.Deserialize(ref reader, options); + reader.Read(); + break; + default: + throw new Exception("Unkown TransformV1 kind: " + type.ToString()); + } + + reader.Read(); //end object + } + } + } + + if (key != null & type != null) + { + return new TransformV1() + { + Key = GlobalStateKey.FromString(key), + Kind = type ?? TransformKindV1.Identity, + Value = value + }; + } + + throw new JsonException("Incomplete transform"); + } + + public override void Write( + Utf8JsonWriter writer, + TransformV1 value, + JsonSerializerOptions options) + { + throw new NotImplementedException("Write method for Transform not yet implemented"); + } + } + } +} diff --git a/Casper.Network.SDK/Types/URef.cs b/Casper.Network.SDK/Types/URef.cs index e69ddc6..9792bb5 100644 --- a/Casper.Network.SDK/Types/URef.cs +++ b/Casper.Network.SDK/Types/URef.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using Casper.Network.SDK.Utils; using Org.BouncyCastle.Utilities.Encoders; @@ -10,7 +11,7 @@ namespace Casper.Network.SDK.Types /// except Account. Additionally, URefs used in contracts carry permission information /// to prevent unauthorized usage of the value stored under the key. /// - public class URef : GlobalStateKey + public class URef : GlobalStateKey, IPurseIdentifier { public static string KEYPREFIX = "uref-"; @@ -34,10 +35,6 @@ public URef(string value) : base(value) throw new ArgumentOutOfRangeException(nameof(value), "An URef object must contain a 3 digits access rights suffix."); - CEP57Checksum.Decode(parts[0], out int checksumResult); - if (checksumResult == CEP57Checksum.InvalidChecksum) - throw new ArgumentException("URef checksum mismatch."); - AccessRights = (AccessRights) uint.Parse(parts[1]); } @@ -75,7 +72,18 @@ public override byte[] GetBytes() public override string ToString() { - return KEYPREFIX + CEP57Checksum.Encode(RawBytes) + $"-{(byte) AccessRights:000}"; + return KEYPREFIX + Hex.ToHexString(RawBytes) + $"-{(byte) AccessRights:000}"; + } + + /// + /// Returns a PurseIdentifier object as defined in the RPC schema for an URef key. + /// + public Dictionary GetPurseIdentifier() + { + return new Dictionary + { + {"purse_uref", this.ToString()} + }; } } } diff --git a/Casper.Network.SDK/Types/UnbondingPurse.cs b/Casper.Network.SDK/Types/UnbondingPurse.cs index dfe3557..03ced7d 100644 --- a/Casper.Network.SDK/Types/UnbondingPurse.cs +++ b/Casper.Network.SDK/Types/UnbondingPurse.cs @@ -5,9 +5,9 @@ namespace Casper.Network.SDK.Types { /// - /// Information of an unbonding or delegation withdrawal + /// Information of a delegation withdrawal (legacy structure) /// - public class UnbondingPurse + public class WithdrawPurse { /// /// Unbonding Amount. @@ -39,6 +39,13 @@ public class UnbondingPurse [JsonPropertyName("validator_public_key")] [JsonConverter(typeof(PublicKey.PublicKeyConverter))] public PublicKey ValidatorPublicKey { get; init; } + } + + /// + /// Information of an unbonding or delegation withdrawal + /// + public class UnbondingPurse : WithdrawPurse + { /// /// The validator public key to re-delegate to. diff --git a/Casper.Network.SDK/Types/ValidatorBid.cs b/Casper.Network.SDK/Types/ValidatorBid.cs new file mode 100644 index 0000000..34e3b51 --- /dev/null +++ b/Casper.Network.SDK/Types/ValidatorBid.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text.Json.Serialization; +using Casper.Network.SDK.Converters; +using System.Numerics; + +namespace Casper.Network.SDK.Types +{ + public class ValidatorBid + { + /// + /// The purse that was used for bonding. + /// + [JsonPropertyName("bonding_purse")] + [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))] + public URef BondingPurse { get; init; } + + /// + /// The delegation rate. + /// + /// + [JsonPropertyName("delegation_rate")] + public uint DelegationRate { get; init; } + + /// + /// `true` if validator has been "evicted" + /// + [JsonPropertyName("inactive")] + public bool Inactive { get; init; } + + /// + /// The amount of tokens staked by a validator (not including delegators). + /// + [JsonPropertyName("staked_amount")] + [JsonConverter(typeof(BigIntegerConverter))] + public BigInteger StakedAmount { get; init; } + + /// + /// Validator public key. + /// + [JsonPropertyName("validator_public_key")] + [JsonConverter(typeof(PublicKey.PublicKeyConverter))] + public PublicKey PublicKey { get; init; } + + /// + /// Minimum allowed delegation amount in motes + /// + [JsonPropertyName("minimum_delegation_amount")] + public ulong MinimumDelegationAmount { get; init; } + + /// + /// Maximum allowed delegation amount in motes + /// + [JsonPropertyName("maximum_delegation_amount")] + public ulong MaximumDelegationAmount { get; init; } + + /// + /// Number of slots reserved for specific delegators + /// + [JsonPropertyName("reserved_slots")] + public uint ReservedSlots { get; init; } + } +} \ No newline at end of file diff --git a/Casper.Network.SDK/Types/ValidatorWeight.cs b/Casper.Network.SDK/Types/ValidatorWeight.cs index 1c9474d..7ba8191 100644 --- a/Casper.Network.SDK/Types/ValidatorWeight.cs +++ b/Casper.Network.SDK/Types/ValidatorWeight.cs @@ -6,7 +6,7 @@ namespace Casper.Network.SDK.Types { /// - /// A validator's weight. + /// A validator's public key paired with its weight, i.e. the total number of motes staked by it and its delegators. /// public class ValidatorWeight { diff --git a/Casper.Network.SDK/Utils/CEP57Checksum.cs b/Casper.Network.SDK/Utils/CEP57Checksum.cs index 51b6625..78f0f8c 100644 --- a/Casper.Network.SDK/Utils/CEP57Checksum.cs +++ b/Casper.Network.SDK/Utils/CEP57Checksum.cs @@ -134,27 +134,5 @@ public static byte[] Decode(string hex, out int checksumResult) return bytes; } - - public class HashWithChecksumConverter : JsonConverter - { - public override string Read( - ref Utf8JsonReader reader, - Type typeToConvert, - JsonSerializerOptions options) - { - var hex = reader.GetString(); - CEP57Checksum.Decode(hex, out var checksumResult); - if (checksumResult == CEP57Checksum.InvalidChecksum) - throw new JsonException("Wrong checksum in hexadecimal string."); - - return hex; - } - - public override void Write( - Utf8JsonWriter writer, - string hash, - JsonSerializerOptions options) => - writer.WriteStringValue(hash); - } } } \ No newline at end of file diff --git a/Casper.Network.SDK/Utils/RpcResponseExtensions.cs b/Casper.Network.SDK/Utils/RpcResponseExtensions.cs index d8a832f..234fae3 100644 --- a/Casper.Network.SDK/Utils/RpcResponseExtensions.cs +++ b/Casper.Network.SDK/Utils/RpcResponseExtensions.cs @@ -1,4 +1,6 @@ +using System.Text.Json; using Casper.Network.SDK.JsonRpc; +using Casper.Network.SDK.Types; namespace Casper.Network.SDK.Utils { @@ -14,5 +16,25 @@ public static string GetDeployHash(this RpcResponse resp throw new RpcClientException("deploy_hash property not found."); } + + /// + /// Retrieves the Transaction hash from an RpcResponse object. + /// + public static TransactionHash GetTransactionHash(this RpcResponse response) + { + if (response.Result.TryGetProperty("transaction_hash", out var el)) + { + if (el.ValueKind != JsonValueKind.Null) + { + var hash = JsonSerializer.Deserialize(el.GetRawText()); + if (hash != null) + return hash; + + throw new RpcClientException("Cannot deserialize transaction_hash property in response."); + } + } + + throw new RpcClientException("transaction_hash property not found."); + } } } \ No newline at end of file diff --git a/Docs/Articles/Casper20MigrationGuide.md b/Docs/Articles/Casper20MigrationGuide.md new file mode 100644 index 0000000..a9e152c --- /dev/null +++ b/Docs/Articles/Casper20MigrationGuide.md @@ -0,0 +1,193 @@ +# Migration from Casper .NET SDK v2.x to v3.0 + +To migrate your application from Casper .NET SDK v2.x to Casper .NET SDK v3.0, you’ll need to make several changes to your code. The good news is that version 3 of this SDK keeps compatibility with Casper v1.5.6 nodes; therefore, once you update your application, it will work before and after the Casper 2.0 upgrade. + +This guide outlines the changes necessary for your application. However, it's worth noting that it doesn't replace other documents introducing the new concepts in Casper 2.0 or migration guides. We strongly advise the reader to understand these new concepts first, as they will significantly aid in grasping the changes. + +## Blocks + +With Casper 2.0, produced blocks are stored in a new format that extends the information contained compared to old blocks. Blocks produced before the upgrade keep their original format. Thus, the SDK implements `BlockV1` and `BlockV2` classes to handle old and new block formats, respectively. + +To facilitate handling different versions, the SDK also implements the type `Block`, which can represent either a V1 or V2 type in the network. This is the type obtained by default in the RPC queries and the SSE channel and contains all the data you may want to query: + +```csharp +public class Block +{ + public int Version { get; init; } + public string Hash { get; init; } + public string AccumulatedSeed { get; init; } + public ulong EraId { get; init; } + public ulong Height { get; init; } + public string ParentHash { get; init; } + public string ProtocolVersion { get; init; } + public bool RandomBit { get; init; } + public string StateRootHash { get; init; } + public string Timestamp { get; init; } + public EraEnd EraEnd { get; init; } + public UInt16 CurrentGasPrice { get; init; } + public Proposer Proposer { get; init; } + public string LastSwitchBlockHash { get; init; } + public List Transactions { get; init; } + public List> RewardedSignatures { get; init; } +} +``` + +Note that `Block` does not have a header or body parts. + +Also, some properties may have a `null` value if they’re not part of the versioned block version. For example, `LastSwitchBlockHash` is present only for V2 blocks and `null` for V1 blocks. + +### Recovering the versioned block object + +If, for any reason, you need to work with the original format of the block, you can cast a Block into BlockV1 or BlockV2. For example: + +```csharp +if (block.Version == 2) { + var blockv2 = (BlockV2)block; + // ... +} else if (block.Version == 1) { + var blockv1 = (BlockV1)block; + // ... +} +``` + +### EraEnd + +EraEnd structure also differs for switch blocks produced before and after Casper 2.0. In most cases, you can just use the EraEnd object from the common Block class. But again, if necessary, you can recover an EraEndV1 object from a version 1 block instance: + +```csharp +if (block.Version == 1) { + var blockv1 = (BlockV1)block; + var eraEnd = blockv1.Header.EraEnd; + // ... +} +``` + +### Transactions included in a block + +Blocks produced on Casper v1.x contain a list of deploys that differentiate between native transfers and all other types of deploys. On Casper 2.0, transactions (this includes legacy deploys too) the payment amount determines the processing lane to which the transaction is sent. The chainspec of the network determines the properties of each lane: + +1. Maximum transaction size in bytes for a given transaction in a certain lane. +2. Maximum args length size in bytes for a given transaction in a certain lane. +3. Transaction gas limit size in motes for a given transaction in a certain lane. +4. The maximum number of transactions the lane can contain for one block. + +The categories (i.e., lanes) defined so far are: + +1. Mint (native CSPR transfers) transactions +2. Auction (native interaction with the Auction contract) transactions +3. Install/Upgrade transactions +4. Large transactions +5. Medium transactions +6. Small transactions + +A `Block` contains a list of transactions: + +```csharp +public List Transactions { get; init; } +``` + +The category, version (either a legacy Deploy or the new TransactionV1 type), and the hash are provided for each transaction. + +`Deploy`s in V1 blocks are categorized as `Mint` for native transfer deploys and `Large` for all the rest. The same applies to legacy deploys sent to a Casper v2.x node. + +### Block height type + +The type to represent block height is now `ulong` everywhere. `int` was used in some methods or types in the previous +version. That's not the case with Casper .NET SDK v3. + +## Transfers + +The `Transfer` class contained in the response for a `GetBlockTransfers()` call or in a `ExecutionResult` object is also a versioned object. It contains all the information related to the transfer. + +If you need to recover the original format, cast an instance to a `TransferV1` or `TransferV2` object: + +```csharp +var response = await rpcClient.GetBlockTransfers(); +var result = response.Parse(); +foreach(var transfer in result.Transfers) +{ + if (transfer.Version == 1) { + var transferv1 = (TransferV1)transfer; + // ... + } +} +``` + +### GetDeploy() + +Response from the `GetDeploy()` method has changed. Instead of a list of `ExecutionResult` objects, it now returns an instance of `ExecutionInfo` for a processed deploy. This instance contains block information and a results object. + +### Payments and costs + +For a transaction (old and new versions) processed in Casper 2.0, the execution results object contains three properties related to the gas consumed and the CSPR tokens paid: + +- `limit`: The maximum allowed gas limit for the transaction. +- `consumed`: Gas consumed executing the transaction. +- `cost`: CSPR paid/held for the transaction. + +In the NoFee model, the user does not specify any amount for payment. Instead, he must use the Fixed pricing mode with a gas price tolerance. The network chainspec defines the gas limit for each transaction category (see the ‘Transactions included in a block’ section above). Then, the network determines a gas price for each era based on the previous era's load. This price works as a multiplier for the consumed gas and relates to the gas price tolerance specified in the transaction. The transaction won't be processed if the tolerance is lower than the gas price. The gas consumed in a transaction must always be lower than the limit, or it will fail with an out-of-gas error. Finally, the cost in the no-fee model is the limit multiplied by the gas price. + +### Execution results + +The `ExecutionResult` class is a versioned object. Deploys processed before the Casper 2.0 upgrade are stored in the global state as `ExecutionResultV1`records, and deploys and transactions processed after the Casper 2.0 upgrade are stored as `ExecutionResultV2` records. + +For a processed transaction, the `GetTransaction` method always returns an `ExecutionResult` instance regardless of the version. It has the same fields as `ExecutionResultV2`. You can obtain the original structure by casting this instance to the correct version: + +```csharp +if (executionResult.Version == 2) { + var executionResultV2 = (ExecutionResultV2)executionResult; + // ... +} else if (executionResult.Version == 1) { + var executionResultV1 = (ExecutionResultV1)executionResult; + // ... +} +``` + +The Effect property contains a list of Transforms that modify the global state due to the transaction execution. Note that the ExecutionEffect type in Casper v1.x, which also contained a list of operations in addition to the transforms, has been removed in Casper 2.0 execution results. + +## Auction contract + +The auction contract has also changed in Casper 2.0. If your application tracks validator bids, rewards, and delegators, you must rework how network responses are parsed and interpreted. A complete description of the changes cannot be covered in this guide. + +## Server Sent Events + +The ServerEventsClient class listens to an event stream from a v1.x node and from v2.x. By default, the client class expects a 2.x network. To listen to a 1.x network change the `nodeVersion` property to `1`. + +### Blocks + +The BlockedAdded event contains a Block object. The original block structure, BlockV1 or BlockV2, can be obtained with the cast operator, as described above. + +### Transactions + +On Casper 2.0, the `DeployAccepted`, `DeployProcessed`, and `DeployExpired` events are replaced with the equivalent `TransactionAccepted`, `TransactionProcessed`, and `TransactionExpired` events. These are emitted for both `Deploy` and `TransactionV1` types of transactions. + +A `TransactionAccepted` event contains a `Transaction` property with either a `Deploy` or a `TransactionV1`. `TransactionProcessed` and `TransactionExpired` events contain a `TransactionHash` property with either a deploy hash or a transaction version 1 hash. + +### Finality signatures + +The `FinalitySignature` event contains an instance of the versioned `FinalitySignature` class. Version 2 of this type is an extension that contains all properties in version 1 plus the block height and the chain name hash. + +The user can obtain the original structure by casting this instance to the appropriate version: + +```csharp +if (finalitySignature.Version == 2) { + var finalitySignatureV2 = (FinalitySignatureV2)finalitySignature; + // ... +} else if (finalitySignature.Version == 1) { + var finalitySignatureV1 = (FinalitySignatureV1)finalitySignature; + // ... +} +``` + +## Other changes + +### Last switch block hash + +For a Casper 2.0 network, the `GetNodeStatus` method can be used to get the latest switch block hash. The response contains the `LatestSwitchBlockHash` property with this value. + +Also, for blocks produced in Casper 2.0, the `Block` instance contains the previous switch block hash in the `LastSwitchBlockHash` property. + +### Checksums (CEP-57) + +On SDK v3 only public keys are checksummed with the CEP-57 standard. The rest of the keys and hashes are not checksummed anymore. + diff --git a/Docs/Examples/AwaitEvents/Program.cs b/Docs/Examples/AwaitEvents/Program.cs index 4ae7724..c56775d 100644 --- a/Docs/Examples/AwaitEvents/Program.cs +++ b/Docs/Examples/AwaitEvents/Program.cs @@ -9,8 +9,8 @@ namespace Casper.NET.SDK.Examples public static class AwaitEvents { // Testnet node and port - private static string localNetHost = "52.35.59.254"; - private static int localNetPort = 9999; // use 18101 with NCTL + private static string localNetHost = "127.0.0.1"; + private static int localNetPort = 18101; static NetCasperClient casperSdk; @@ -39,7 +39,17 @@ public static void ListenEvents(int startFrom) else if (evt.EventType == EventType.BlockAdded) { var block = evt.Parse(); - Console.WriteLine("Block height: " + block.Block.Header.Height); + Console.WriteLine("Block height: " + block.Block.Height); + } + else if (evt.EventType == EventType.TransactionAccepted) + { + var transaction = evt.Parse(); + Console.WriteLine("TransactionAccepted: " + transaction.Hash); + } + else if (evt.EventType == EventType.TransactionProcessed) + { + var transaction = evt.Parse(); + Console.WriteLine("TransactionProcessed: " + transaction.TransactionHash.ToString()); } else if (evt.EventType == EventType.DeployAccepted) { diff --git a/Docs/Examples/DelegateStake/Program.cs b/Docs/Examples/DelegateStake/Program.cs deleted file mode 100644 index 3f98aa7..0000000 --- a/Docs/Examples/DelegateStake/Program.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Data; -using System.IO; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Casper.Network.SDK; -using Casper.Network.SDK.JsonRpc; -using Casper.Network.SDK.Types; -using Casper.Network.SDK.Utils; - -namespace CasperIntegrations -{ - public class DelegateStake - { - public static async Task Main(string[] args) - { - string nodeAddress = "http://52.35.59.254:7777/rpc"; - - try - { - // create an instance of the NetCasperClient that logs requests/outputs in stdout - // - var loggingHandler = new RpcLoggingHandler(new HttpClientHandler()) - { - LoggerStream = new StreamWriter(Console.OpenStandardOutput()) - }; - var casperSdk = new NetCasperClient(nodeAddress, loggingHandler); - - // load source account secret key from PEM files - // - var sourceKey = KeyPair.FromPem("./testnet1/secret_key.pem"); - - // choose a validator public key to stake the tokens - // - var validatorPK = PublicKey.FromHexString("017d96b9a63abcb61c870a4f55187a0a7ac24096bdb5fc585c12a686a4d892009e"); - - // create a hash key with the auction contract hash - // IMPORTANT: this value changes between network. Double check you - // are using the right contract hash before executing the deploy! - // - var contractHash = GlobalStateKey.FromString("hash-93d923e336b20a4c4ca14d592b60e5bd3fe330775618290104f9beb326db7ae2") as HashKey; - - // prepare a transfer deploy using the DelegateTokens template. - // Similarly, use the template UndelegateTokens to undelegate. - // - var deploy = DeployTemplates.DelegateTokens( - contractHash, - sourceKey.PublicKey, - validatorPK, - 100_000_000_000, - 3_000_000_000, - "casper-test"); - - // sign the deploy and send it to the network - // use the source account secret key for signing. - // - deploy.Sign(sourceKey); - - await casperSdk.PutDeploy(deploy); - - var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120)); - var deployResponse = await casperSdk.GetDeploy(deploy.Hash, tokenSource.Token); - - // only as an example, extract the transfer key, and retrieve the transfer - // information from the network - // - var result = deployResponse.Parse(); - - if(result.ExecutionResults[0].IsSuccess) - Console.WriteLine("Delegation successful"); - else - Console.WriteLine("Delegation completed with errors: " + result.ExecutionResults[0].ErrorMessage); - } - catch (RpcClientException e) - { - Console.WriteLine("ERROR:\n" + e.RpcError.Message); - } - catch (Exception e) - { - Console.WriteLine(e); - } - } - } -} diff --git a/Docs/Examples/GetAccountBalance/Program.cs b/Docs/Examples/GetAccountBalance/Program.cs deleted file mode 100644 index 8514fb3..0000000 --- a/Docs/Examples/GetAccountBalance/Program.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Threading.Tasks; -using Casper.Network.SDK; -using Casper.Network.SDK.JsonRpc; -using Casper.Network.SDK.Types; - -namespace Casper.NET.SDK.Examples -{ - public class GetAccountBalance - { - public static async Task Main(string[] args) - { - string nodeAddress = "http://52.35.59.254:7777/rpc"; - - var hex = "0203914289b334f57366541099a52156b149436fdb0422b3c48fe4115d0578abf690"; - var publicKey = PublicKey.FromHexString(hex); - - try - { - var casperSdk = new NetCasperClient(nodeAddress); - - // Get the balance using the account public key - // - var rpcResponse = await casperSdk.GetAccountBalance(publicKey); - Console.WriteLine("Public Key Balance: " + rpcResponse.Parse().BalanceValue); - - // Alternatively, use the main purse URef key to get the balance - // - var mainPurse = "uref-91e6278ea0ccc22eac98d1e6f2c6530f1e8a278eb1005c4b36e070b400c88301-007"; - var purseResponse = await casperSdk.GetAccountBalance(mainPurse); - Console.WriteLine("Purse Balance : " + purseResponse.Parse().BalanceValue); - } - catch (RpcClientException e) - { - Console.WriteLine("ERROR:\n" + e.RpcError.Message); - } - catch (Exception e) - { - Console.WriteLine(e); - } - } - } -} \ No newline at end of file diff --git a/Docs/Examples/GetBalance/GetBalance.csproj b/Docs/Examples/GetBalance/GetBalance.csproj new file mode 100644 index 0000000..622f8a9 --- /dev/null +++ b/Docs/Examples/GetBalance/GetBalance.csproj @@ -0,0 +1,13 @@ + + + + Exe + net8.0 + GetAccountBalance + + + + + + + diff --git a/Docs/Examples/GetBalance/Program.cs b/Docs/Examples/GetBalance/Program.cs new file mode 100644 index 0000000..f705da1 --- /dev/null +++ b/Docs/Examples/GetBalance/Program.cs @@ -0,0 +1,50 @@ +using System; +using System.Threading.Tasks; +using Casper.Network.SDK; +using Casper.Network.SDK.JsonRpc; +using Casper.Network.SDK.Types; + +namespace Casper.NET.SDK.Examples +{ + public class GetBalance + { + public static async Task Main(string[] args) + { + string nodeAddress = "http://127.0.0.1:11101/rpc"; + + var hex = "0184f6d260F4EE6869DDB36affe15456dE6aE045278FA2f467bb677561cE0daD55"; + var publicKey = PublicKey.FromHexString(hex); + + try + { + var casperSdk = new NetCasperClient(nodeAddress); + + // Get the balance using the account public key + // + var rpcResponse = await casperSdk.QueryBalance(publicKey); + Console.WriteLine("Public Key Balance : " + rpcResponse.Parse().BalanceValue); + + // Alternatively, use the main purse URef key to get the balance + // + var mainPurse = new URef("uref-0d0b57865e41b9e39170c038993997af432f66545f56838f1bf602c6d56e0e54-007"); + var purseResponse = await casperSdk.QueryBalance(mainPurse); + Console.WriteLine("Purse Balance : " + purseResponse.Parse().BalanceValue); + + // Or using an entity key + // + var entity = new AddressableEntityKey( + "entity-account-56befc13a6fd62e18f361700a5e08f966901c34df8041b36ec97d54d605c23de"); + var entityResponse = await casperSdk.QueryBalance(entity); + Console.WriteLine("Entity Balance : " + entityResponse.Parse().BalanceValue); + } + catch (RpcClientException e) + { + Console.WriteLine("ERROR:\n" + e.RpcError.Data); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + } +} \ No newline at end of file diff --git a/Docs/Examples/GetBlockTransfers/Program.cs b/Docs/Examples/GetBlockTransfers/Program.cs index 66d7adf..69fd675 100644 --- a/Docs/Examples/GetBlockTransfers/Program.cs +++ b/Docs/Examples/GetBlockTransfers/Program.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using Casper.Network.SDK; using Casper.Network.SDK.JsonRpc; -using Casper.Network.SDK.Types; namespace Casper.NET.SDK.Examples { @@ -10,23 +9,21 @@ public class GetBlockTransfers { public static async Task Main(string[] args) { - string nodeAddress = "http://52.35.59.254:7777/rpc"; - string blockHash = "c7148e1e2e115d8fba357e04be2073d721847c982dc70d5c36b5f6d3cf66331c"; - int blockHeight = 20652; + string nodeAddress = "http://127.0.0.1:11101/rpc"; + + // find a block height with transfers using a block explorer + ulong blockHeight = 222; try { var casperSdk = new NetCasperClient(nodeAddress); - //Get latest block + // Get latest block transfers var rpcResponse = await casperSdk.GetBlockTransfers(); var transfers = rpcResponse.Parse().Transfers; Console.WriteLine("Number of transfers in latest block: " + transfers.Count); - //Get block by hash - rpcResponse = await casperSdk.GetBlockTransfers(blockHash); - - // //Get block by height + // Get block transfers by block height rpcResponse = await casperSdk.GetBlockTransfers(blockHeight); transfers = rpcResponse.Parse().Transfers; @@ -35,6 +32,8 @@ public static async Task Main(string[] args) foreach (var transfer in transfers) { Console.WriteLine("Transfer amount: " + transfer.Amount); + Console.WriteLine("Transfer from: " + transfer.From); + Console.WriteLine("Transfer to: " + transfer.To); } } catch (RpcClientException e) diff --git a/Docs/Examples/DelegateStake/DelegateStake.csproj b/Docs/Examples/GetChainspec/GetChainspec.csproj similarity index 100% rename from Docs/Examples/DelegateStake/DelegateStake.csproj rename to Docs/Examples/GetChainspec/GetChainspec.csproj diff --git a/Docs/Examples/GetChainspec/Program.cs b/Docs/Examples/GetChainspec/Program.cs new file mode 100644 index 0000000..7a8eaa9 --- /dev/null +++ b/Docs/Examples/GetChainspec/Program.cs @@ -0,0 +1,96 @@ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; +using Casper.Network.SDK; +using Casper.Network.SDK.JsonRpc; + +namespace Casper.NET.SDK.Examples +{ + class Chainspec + { + private Dictionary data; + + public Chainspec(string configContent) + { + data = new Dictionary(); + ParseContent(configContent); + } + + private void ParseContent(string content) + { + string currentSection = null; + var lines = content.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (var line in lines) + { + var trimmedLine = line.Trim(); + + if (string.IsNullOrEmpty(trimmedLine) || trimmedLine.StartsWith("#")) + continue; + + if (trimmedLine.StartsWith("[") && trimmedLine.EndsWith("]")) + { + // Section header + currentSection = trimmedLine.Substring(1, trimmedLine.Length - 2); + } + else if (currentSection != null && trimmedLine.Contains("=")) + { + var keyValue = trimmedLine.Split(new[] { '=' }, 2); + if (keyValue.Length == 2) + { + var key = keyValue[0].Trim(); + var value = keyValue[1].Trim().Trim('"'); + data[currentSection + "." + key] = value; + } + } + } + } + + public string GetValue(string key) + { + return data.GetValueOrDefault(key); + } + } + + public class GetChainspec + { + public static async Task Main(string[] args) + { + string nodeAddress = "http://127.0.0.1:11101/rpc"; + + try + { + // create an instance of the NetCasperClient that logs requests/outputs in stdout + // + var loggingHandler = new RpcLoggingHandler(new HttpClientHandler()) + { + LoggerStream = new StreamWriter(Console.OpenStandardOutput()) + }; + var casperSdk = new NetCasperClient(nodeAddress, loggingHandler); + + // get node status and print API version and PK as example + // + var rpcResponse = await casperSdk.GetChainspec(); + var result = rpcResponse.Parse(); + + var chainspec = new Chainspec(result.ChainspecBytes.ChainspecAsString); + + Console.WriteLine("Consensus protocol : " + chainspec.GetValue("core.consensus_protocol")); + Console.WriteLine("Pricing mode : " + chainspec.GetValue("core.pricing_handling.type")); + Console.WriteLine("Max gas price : " + chainspec.GetValue("vacancy.max_gas_price")); + Console.WriteLine("Wasm tx lanes : " + chainspec.GetValue("transactions.v1.wasm_lanes")); + } + catch (RpcClientException e) + { + Console.WriteLine("ERROR:\n" + e.RpcError.Message); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + } +} \ No newline at end of file diff --git a/Docs/Examples/GetNodeMetrics/Program.cs b/Docs/Examples/GetNodeMetrics/Program.cs index b5da45a..8f39ac4 100644 --- a/Docs/Examples/GetNodeMetrics/Program.cs +++ b/Docs/Examples/GetNodeMetrics/Program.cs @@ -10,7 +10,7 @@ public class GetNodeMetrics { public static async Task Main(string[] args) { - string nodeAddress = "http://52.35.59.254:8888/metrics"; + string nodeAddress = "http://127.0.0.1:14101/metrics"; try { diff --git a/Docs/Examples/GetNodeStatus/Program.cs b/Docs/Examples/GetNodeStatus/Program.cs index eb3d061..50ee0bc 100644 --- a/Docs/Examples/GetNodeStatus/Program.cs +++ b/Docs/Examples/GetNodeStatus/Program.cs @@ -1,9 +1,9 @@ using System; +using System.IO; using System.Net.Http; using System.Threading.Tasks; using Casper.Network.SDK; using Casper.Network.SDK.JsonRpc; -using Casper.Network.SDK.Types; namespace Casper.NET.SDK.Examples { @@ -11,13 +11,25 @@ public class GetNodeStatus { public static async Task Main(string[] args) { - string nodeAddress = "http://52.35.59.254:7777/rpc"; - + string nodeAddress = "http://127.0.0.1:11101/rpc"; + try { - var casperSdk = new NetCasperClient(nodeAddress); + // create an instance of the NetCasperClient that logs requests/outputs in stdout + // + var loggingHandler = new RpcLoggingHandler(new HttpClientHandler()) + { + LoggerStream = new StreamWriter(Console.OpenStandardOutput()) + }; + var casperSdk = new NetCasperClient(nodeAddress, loggingHandler); + + // get node status and print API version and PK as example + // var rpcResponse = await casperSdk.GetNodeStatus(); - Console.WriteLine(rpcResponse.Result.GetRawText()); + var nodeStatus = rpcResponse.Parse(); + + Console.WriteLine("API Version : " + nodeStatus.ApiVersion); + Console.WriteLine("Node PK : " + nodeStatus.OurPublicSigningKey); } catch (RpcClientException e) { diff --git a/Docs/Examples/ListRewards/Program.cs b/Docs/Examples/ListRewards/Program.cs index 82887a0..2897861 100644 --- a/Docs/Examples/ListRewards/Program.cs +++ b/Docs/Examples/ListRewards/Program.cs @@ -4,16 +4,17 @@ using Casper.Network.SDK; using Casper.Network.SDK.JsonRpc; -namespace ListRewards +namespace Casper.NET.SDK.Examples { public static class ListRewards { - static string nodeAddress = "http://52.35.59.254:7777/rpc"; + static string nodeAddress = "http://127.0.0.1:11101/rpc"; + static NetCasperClient casperSdk; public async static Task GetEraSummary() { - var rpcResponse = await casperSdk.GetEraInfoBySwitchBlock(435_733); + var rpcResponse = await casperSdk.GetEraSummary(); var eraSummary = rpcResponse.Parse().EraSummary; Console.WriteLine("Block Hash: " + eraSummary.BlockHash); @@ -23,19 +24,30 @@ public async static Task GetEraSummary() // print the era rewards per validator // - Console.WriteLine("Validator Deleg. Rewards"); - Console.WriteLine("--------------------------------------------------------------------------------------------------------"); + Console.WriteLine("Validator"); + Console.WriteLine(" Delegator Rewards"); + Console.WriteLine("------------------------------------------------------------------------------------------------------------"); var groupedByValidator = eraInfo.SeigniorageAllocations .GroupBy(allocation => allocation.ValidatorPublicKey); foreach (var group in groupedByValidator) { - var eraRewards = group.Sum(a => (double)a.Amount); - eraRewards /= 1_000_000_000; + var rewards = group.Where(a => !a.IsDelegator).Sum(a => (double)a.Amount); + rewards /= 1_000_000_000; + + Console.WriteLine($"{group.Key} " + + $"{rewards.ToString("N9"),35} $CSPR"); - Console.WriteLine($"{group.Key} - {group.Count(),5} - " + - $"{eraRewards.ToString("N9"),20} $CSPR"); + var delegators = group.Where(a => a.IsDelegator) + .GroupBy(a => a.DelegatorKind.PublicKey); + foreach (var delegatorAllocations in delegators) + { + var delegatorRewards = delegatorAllocations.Sum(a => (double)a.Amount); + Console.WriteLine(" " + delegatorAllocations.Key + " " + + $"{delegatorRewards.ToString("N9"),32} $CSPR"); + } + Console.WriteLine(); } } diff --git a/Docs/Examples/ExecTransfer/ExecTransfer.csproj b/Docs/Examples/NativeAuction/NativeAuction.csproj similarity index 84% rename from Docs/Examples/ExecTransfer/ExecTransfer.csproj rename to Docs/Examples/NativeAuction/NativeAuction.csproj index 1dfa19e..1e4b1f6 100644 --- a/Docs/Examples/ExecTransfer/ExecTransfer.csproj +++ b/Docs/Examples/NativeAuction/NativeAuction.csproj @@ -3,6 +3,7 @@ Exe net8.0 + DelegateStake diff --git a/Docs/Examples/NativeAuction/Program.cs b/Docs/Examples/NativeAuction/Program.cs new file mode 100644 index 0000000..e760637 --- /dev/null +++ b/Docs/Examples/NativeAuction/Program.cs @@ -0,0 +1,91 @@ +using System; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Casper.Network.SDK; +using Casper.Network.SDK.JsonRpc; +using Casper.Network.SDK.Types; +using Casper.Network.SDK.Utils; + +namespace Casper.NET.SDK.Examples +{ + public class NativeAuction + { + public static async Task Main(string[] args) + { + string nodeAddress = "http://127.0.0.1:11101/rpc"; + string chainName = "casper-net-1"; + + try + { + // create an instance of the NetCasperClient that logs requests/outputs in stdout + // + var loggingHandler = new RpcLoggingHandler(new HttpClientHandler()) + { + LoggerStream = new StreamWriter(Console.OpenStandardOutput()) + }; + var casperSdk = new NetCasperClient(nodeAddress, loggingHandler); + + // load delegator account secret key from PEM files + // + var delegator = KeyPair.FromPem("./testnet1/secret_key.pem"); + + // choose a validator public key to stake the tokens + // + var validatorPK = PublicKey.FromHexString("01509254f22690fbe7fb6134be574c4fbdb060dfa699964653b99753485e518ea6"); + + // prepare a delegate transaction using the transaction builder. + // + var transaction = new Transaction.NativeDelegateBuilder() + .From(delegator.PublicKey) + .Validator(validatorPK) + .Amount(1000_000_000_000) + .ChainName(chainName) + .Build(); + + // sign the transaction and send it to the network + // use the delegator account secret key for signing. + // + transaction.Sign(delegator); + + var response = await casperSdk.PutTransaction(transaction); + + // extract the transaction hash and use it to wait (up to 2mins) for the execution results + // + var transactionHash = response.GetTransactionHash(); + + var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120)); + var transactionResponse = await casperSdk.GetTransaction(transactionHash, tokenSource.Token); + + var executionResult = transactionResponse.Parse().ExecutionInfo.ExecutionResult; + + if (executionResult.IsSuccess) + { + Console.WriteLine("Delegation successful"); + + // only as an example, extract the Bid transform, + // and print the staked amount and the bonding purse + // + var transform = executionResult.Effect.First(t => t.Kind is WriteTransformKind kind && + kind.Value.BidKind is not null); + + var bid = (transform.Kind as WriteTransformKind).Value.BidKind; + Console.WriteLine("Staked amount : " + bid.Delegator.StakedAmount); + Console.WriteLine("Bonding purse : " + bid.Delegator.BondingPurse.ToString()); + } + else + Console.WriteLine("Delegation completed with errors: " + executionResult.ErrorMessage); + } + catch (RpcClientException e) + { + Console.WriteLine("ERROR:\n" + e.RpcError.Message); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + } +} diff --git a/Docs/Examples/GetAccountBalance/GetAccountBalance.csproj b/Docs/Examples/NativeTransfer/NativeTransfer.csproj similarity index 85% rename from Docs/Examples/GetAccountBalance/GetAccountBalance.csproj rename to Docs/Examples/NativeTransfer/NativeTransfer.csproj index 1dfa19e..a36b02d 100644 --- a/Docs/Examples/GetAccountBalance/GetAccountBalance.csproj +++ b/Docs/Examples/NativeTransfer/NativeTransfer.csproj @@ -3,6 +3,7 @@ Exe net8.0 + ExecTransfer diff --git a/Docs/Examples/ExecTransfer/Program.cs b/Docs/Examples/NativeTransfer/Program.cs similarity index 61% rename from Docs/Examples/ExecTransfer/Program.cs rename to Docs/Examples/NativeTransfer/Program.cs index 88ee31d..e81f4f0 100644 --- a/Docs/Examples/ExecTransfer/Program.cs +++ b/Docs/Examples/NativeTransfer/Program.cs @@ -8,14 +8,15 @@ using Casper.Network.SDK.Types; using Casper.Network.SDK.Utils; -namespace CasperIntegrations +namespace Casper.NET.SDK.Examples { - public class ExecAccountTransfer + public class NativeTransfer { public static async Task Main(string[] args) { - string nodeAddress = "http://52.35.59.254:7777/rpc"; - + string nodeAddress = "http://127.0.0.1:11101/rpc"; + string chainName = "casper-net-1"; + try { // create an instance of the NetCasperClient that logs requests/outputs in stdout @@ -31,36 +32,35 @@ public static async Task Main(string[] args) var sourceKey = KeyPair.FromPem("./testnet1/secret_key.pem"); var targetPK = PublicKey.FromPem("./testnet2/public_key.pem"); - // prepare a transfer deploy using the StandardTransfer template. + // prepare a transfer transaction using the transaction builder. // - var deploy = DeployTemplates.StandardTransfer( - sourceKey.PublicKey, - targetPK, - 25_000_000_000, - 100_000_000, - "casper-test"); + var transaction = new Transaction.NativeTransferBuilder() + .From(sourceKey.PublicKey) + .Target(targetPK) + .Amount(25_000_000_000) + .Id(DateUtils.ToEpochTime(DateTime.Now)) + .ChainName(chainName) + .Payment(PricingMode.PaymentLimited(100_000_000, 1)) + .Build(); - // sign the deploy and send it to the network + // sign the transaction and send it to the network // use the origin account secret key for signing. // - deploy.Sign(sourceKey); + transaction.Sign(sourceKey); - var response = await casperSdk.PutDeploy(deploy); + var response = await casperSdk.PutTransaction(transaction); - // extract the deploy hash and use it to wait (up to 2mins) for the execution results + // extract the transaction hash and use it to wait (up to 2mins) for the execution results // - var deployHash = response.GetDeployHash(); + var transactionHash = response.GetTransactionHash(); var tokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(120)); - var deployResponse = await casperSdk.GetDeploy(deployHash, tokenSource.Token); + var transactionResponse = await casperSdk.GetTransaction(transactionHash, tokenSource.Token); // only as an example, extract the transfer key, and retrieve the transfer // information from the network // - var transferKey = deployResponse.Parse().ExecutionResults[0].Transfers[0]; - - var queryResponse = await casperSdk.QueryGlobalState(transferKey); - var transfer = queryResponse.Parse().StoredValue.Transfer; + var transfer = transactionResponse.Parse().ExecutionInfo.ExecutionResult.Transfers[0]; Console.WriteLine("Transfer amount: " + transfer.Amount);