Skip to content

Commit

Permalink
Add fields to OP rpc; fix Ecotone gas issue; update pivots (#7180)
Browse files Browse the repository at this point in the history
  • Loading branch information
flcl42 authored Jun 20, 2024
1 parent 1a1d594 commit 0480d7a
Show file tree
Hide file tree
Showing 16 changed files with 218 additions and 47 deletions.
3 changes: 2 additions & 1 deletion src/Nethermind/Chains/base-mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"canyonTimestamp": "0x65a01e91",
"ecotoneTimestamp": "0x65f23e01",
"l1FeeRecipient": "0x420000000000000000000000000000000000001A",
"l1BlockAddress": "0x4200000000000000000000000000000000000015"
"l1BlockAddress": "0x4200000000000000000000000000000000000015",
"canyonBaseFeeChangeDenominator": "250"
}
}
},
Expand Down
3 changes: 2 additions & 1 deletion src/Nethermind/Chains/op-mainnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"canyonTimestamp": "0x65a01e91",
"ecotoneTimestamp": "0x65f23e01",
"l1FeeRecipient": "0x420000000000000000000000000000000000001A",
"l1BlockAddress": "0x4200000000000000000000000000000000000015"
"l1BlockAddress": "0x4200000000000000000000000000000000000015",
"canyonBaseFeeChangeDenominator": "250"
}
}
},
Expand Down
1 change: 0 additions & 1 deletion src/Nethermind/Nethermind.Facade/Eth/BlockForRpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
using Nethermind.Core.Crypto;
using Nethermind.Core.Specs;
using Nethermind.Int256;
using Nethermind.JsonRpc.Data;
using Nethermind.Serialization.Json;
using Nethermind.Serialization.Rlp;
using System.Text.Json.Serialization;
Expand Down
7 changes: 0 additions & 7 deletions src/Nethermind/Nethermind.Facade/Eth/TransactionForRpc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using Nethermind.Core.Eip2930;
using Nethermind.Core.Extensions;
using Nethermind.Int256;
using Nethermind.JsonRpc.Data;

namespace Nethermind.Facade.Eth;

Expand All @@ -24,12 +23,6 @@ public TransactionForRpc(Transaction transaction) : this(null, null, null, trans

public TransactionForRpc(Hash256? blockHash, long? blockNumber, int? txIndex, Transaction transaction, UInt256? baseFee = null)
{
if (transaction.Type == TxType.DepositTx)
{
SourceHash = transaction.SourceHash;
Mint = transaction.Mint;
IsSystemTx = transaction.IsOPSystemTransaction;
}
Hash = transaction.Hash;
Nonce = transaction.Nonce;
BlockHash = blockHash;
Expand Down
8 changes: 4 additions & 4 deletions src/Nethermind/Nethermind.JsonRpc/Modules/Eth/EthRpcModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ public ResultWrapper<BlockForRpc> eth_getBlockByNumber(BlockParameter blockParam
return GetBlock(blockParameter, returnFullTransactionObjects);
}

private ResultWrapper<BlockForRpc> GetBlock(BlockParameter blockParameter, bool returnFullTransactionObjects)
protected virtual ResultWrapper<BlockForRpc?> GetBlock(BlockParameter blockParameter, bool returnFullTransactionObjects)
{
SearchResult<Block> searchResult = _blockFinder.SearchForBlock(blockParameter, true);
if (searchResult.IsError)
Expand All @@ -362,7 +362,7 @@ private ResultWrapper<BlockForRpc> GetBlock(BlockParameter blockParameter, bool
_blockchainBridge.RecoverTxSenders(block);
}

return ResultWrapper<BlockForRpc>.Success(block is null
return ResultWrapper<BlockForRpc?>.Success(block is null
? null
: new BlockForRpc(block, returnFullTransactionObjects, _specProvider));
}
Expand Down Expand Up @@ -680,7 +680,7 @@ public ResultWrapper<ulong> eth_chainId()
}
}

private void RecoverTxSenderIfNeeded(Transaction transaction)
protected void RecoverTxSenderIfNeeded(Transaction transaction)
{
transaction.SenderAddress ??= _blockchainBridge.RecoverTxSender(transaction);
}
Expand Down Expand Up @@ -713,7 +713,7 @@ private static IEnumerable<FilterLog> GetLogs(IEnumerable<FilterLog> logs, Cance
: null);
}

private static ResultWrapper<TResult> GetFailureResult<TResult, TSearch>(SearchResult<TSearch> searchResult, bool isTemporary) where TSearch : class =>
protected static ResultWrapper<TResult> GetFailureResult<TResult, TSearch>(SearchResult<TSearch> searchResult, bool isTemporary) where TSearch : class =>
ResultWrapper<TResult>.Fail(searchResult, isTemporary && searchResult.ErrorCode == ErrorCodes.ResourceNotFound);

private static ResultWrapper<TResult> GetFailureResult<TResult>(ResourceNotFoundException exception, bool isTemporary) =>
Expand Down
2 changes: 1 addition & 1 deletion src/Nethermind/Nethermind.Optimism.Test/GasCostTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class GasCostTests
{
[TestCaseSource(nameof(FjordL1CostCalculationTestCases))]
public UInt256 Fjord_l1cost_should_match(UInt256 fastLzSize, UInt256 l1BaseFee, UInt256 blobBaseFee, UInt256 l1BaseFeeScalar, UInt256 l1BlobBaseFeeScalar) =>
OPL1CostHelper.ComputeL1CostFjord(fastLzSize, l1BaseFee, blobBaseFee, l1BaseFeeScalar, l1BlobBaseFeeScalar);
OPL1CostHelper.ComputeL1CostFjord(fastLzSize, l1BaseFee, blobBaseFee, l1BaseFeeScalar, l1BlobBaseFeeScalar, out _);

public static IEnumerable FjordL1CostCalculationTestCases
{
Expand Down
27 changes: 16 additions & 11 deletions src/Nethermind/Nethermind.Optimism/L1BlockGasInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,31 @@

namespace Nethermind.Optimism;

public readonly struct L1TxGasInfo(UInt256? l1Fee, UInt256? l1GasPrice, UInt256? l1GasUsed, string? l1FeeScalar)
public readonly struct L1TxGasInfo(UInt256? l1Fee, UInt256? l1GasPrice, UInt256? l1GasUsed, string? l1FeeScalar, UInt256? l1BaseFeeScalar = null, UInt256? l1BlobBaseFee = null, UInt256? l1BlobBaseFeeScalar = null)
{
public UInt256? L1Fee { get; } = l1Fee;
public UInt256? L1GasPrice { get; } = l1GasPrice;
public UInt256? L1GasUsed { get; } = l1GasUsed;
public string? L1FeeScalar { get; } = l1FeeScalar;

public UInt256? L1BaseFeeScalar { get; } = l1BaseFeeScalar;
public UInt256? L1BlobBaseFee { get; } = l1BlobBaseFee;
public UInt256? L1BlobBaseFeeScalar { get; } = l1BlobBaseFeeScalar;
}

public readonly struct L1BlockGasInfo
{
private readonly UInt256? _l1GasPrice;
private readonly UInt256 _l1BlobBaseFee;
private readonly UInt256 _l1BaseFeeScalar;
private readonly UInt256 _l1BlobBaseFeeScalar;
private readonly UInt256? _l1BlobBaseFee;
private readonly UInt256? _l1BaseFeeScalar;
private readonly UInt256? _l1BlobBaseFeeScalar;
private readonly UInt256 _l1BaseFee;
private readonly UInt256 _overhead;
private readonly UInt256 _feeScalar;
private readonly string? _feeScalarDecimal;
private readonly bool _isFjord;
private readonly bool _isEcotone;
private readonly bool _isRegolith;
private readonly bool _isPostRegolith;

private static readonly byte[] BedrockL1AttributesSelector = [0x01, 0x5d, 0x8e, 0xb9];
private readonly IOptimismSpecHelper _specHelper;
Expand All @@ -48,6 +52,7 @@ public L1BlockGasInfo(Block block, IOptimismSpecHelper specHelper)
Memory<byte> data = depositTx.Data.Value;

_isFjord = _specHelper.IsFjord(block.Header);
_isPostRegolith = _specHelper.IsRegolith(block.Header);

if (_isFjord || (_isEcotone = (_specHelper.IsEcotone(block.Header) && !data[0..4].Span.SequenceEqual(BedrockL1AttributesSelector))))
{
Expand All @@ -63,7 +68,6 @@ public L1BlockGasInfo(Block block, IOptimismSpecHelper specHelper)
}
else
{
_isRegolith = true;
if (data.Length < 4 + 32 * 8)
{
return;
Expand All @@ -88,20 +92,21 @@ public readonly L1TxGasInfo GetTxGasInfo(Transaction tx)
if (_isFjord)
{
UInt256 fastLzSize = OPL1CostHelper.ComputeFlzCompressLen(tx);
l1Fee = OPL1CostHelper.ComputeL1CostFjord(fastLzSize, _l1GasPrice.Value, _l1BlobBaseFee, _l1BaseFeeScalar, _l1BlobBaseFeeScalar);
l1Fee = OPL1CostHelper.ComputeL1CostFjord(fastLzSize, _l1GasPrice.Value, _l1BlobBaseFee!.Value, _l1BaseFeeScalar!.Value, _l1BlobBaseFeeScalar!.Value, out UInt256 estimatedSize);
l1GasUsed = OPL1CostHelper.ComputeGasUsedFjord(estimatedSize);
}
else if (_isEcotone)
{
l1GasUsed = OPL1CostHelper.ComputeDataGas(tx, _isRegolith);
l1Fee = OPL1CostHelper.ComputeL1CostEcotone(l1GasUsed.Value, _l1GasPrice.Value, _l1BlobBaseFee, _l1BaseFeeScalar, _l1BlobBaseFeeScalar);
l1GasUsed = OPL1CostHelper.ComputeDataGas(tx, _isPostRegolith);
l1Fee = OPL1CostHelper.ComputeL1CostEcotone(l1GasUsed.Value, _l1GasPrice.Value, _l1BlobBaseFee!.Value, _l1BaseFeeScalar!.Value, _l1BlobBaseFeeScalar!.Value);
}
else
{
l1GasUsed = OPL1CostHelper.ComputeDataGas(tx, _isRegolith) + _overhead;
l1GasUsed = OPL1CostHelper.ComputeDataGas(tx, _isPostRegolith) + _overhead;
l1Fee = OPL1CostHelper.ComputeL1CostPreEcotone(l1GasUsed.Value, _l1BaseFee, _feeScalar);
}
}

return new L1TxGasInfo(l1Fee, _l1GasPrice, l1GasUsed, _feeScalarDecimal);
return new L1TxGasInfo(l1Fee, _l1GasPrice, l1GasUsed, _feeScalarDecimal, _l1BaseFeeScalar, _l1BlobBaseFee, _l1BlobBaseFeeScalar);
}
}
9 changes: 5 additions & 4 deletions src/Nethermind/Nethermind.Optimism/OPL1CostHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// SPDX-License-Identifier: LGPL-3.0-only

using System;
using System.Buffers;
using System.Linq;
using System.Runtime.CompilerServices;
using Nethermind.Core;
Expand Down Expand Up @@ -62,7 +61,7 @@ public UInt256 ComputeL1Cost(Transaction tx, BlockHeader header, IWorldState wor

uint fastLzSize = ComputeFlzCompressLen(tx);

return ComputeL1CostFjord(fastLzSize, l1BaseFee, blobBaseFee, l1BaseFeeScalar, l1BlobBaseFeeScalar);
return ComputeL1CostFjord(fastLzSize, l1BaseFee, blobBaseFee, l1BaseFeeScalar, l1BlobBaseFeeScalar, out _);
}

UInt256 dataGas = ComputeDataGas(tx, _opSpecHelper.IsRegolith(header));
Expand Down Expand Up @@ -112,7 +111,7 @@ public static UInt256 ComputeDataGas(Transaction tx, bool isRegolith)
// l1FeeScaled = baseFeeScalar * l1BaseFee * 16 + blobFeeScalar * l1BlobBaseFee
// estimatedSize = max(minTransactionSize, intercept + fastlzCoef * fastlzSize)
// l1Cost = estimatedSize * l1FeeScaled / 1e12
public static UInt256 ComputeL1CostFjord(UInt256 fastLzSize, UInt256 l1BaseFee, UInt256 blobBaseFee, UInt256 l1BaseFeeScalar, UInt256 l1BlobBaseFeeScalar)
public static UInt256 ComputeL1CostFjord(UInt256 fastLzSize, UInt256 l1BaseFee, UInt256 blobBaseFee, UInt256 l1BaseFeeScalar, UInt256 l1BlobBaseFeeScalar, out UInt256 estimatedSize)
{
UInt256 l1FeeScaled = l1BaseFeeScalar * l1BaseFee * PrecisionMultiplier + l1BlobBaseFeeScalar * blobBaseFee;
UInt256 fastLzCost = L1CostFastlzCoef * fastLzSize;
Expand All @@ -126,7 +125,7 @@ public static UInt256 ComputeL1CostFjord(UInt256 fastLzSize, UInt256 l1BaseFee,
fastLzCost -= L1CostInterceptNeg;
}

var estimatedSize = UInt256.Max(MinTransactionSizeScaled, fastLzCost);
estimatedSize = UInt256.Max(MinTransactionSizeScaled, fastLzCost);
return estimatedSize * l1FeeScaled / FjordDivisor;
}

Expand Down Expand Up @@ -242,4 +241,6 @@ uint setNextHash(uint ip, ref Span<uint> ht)
}
return FlzCompressLen(encoded);
}

internal static UInt256 ComputeGasUsedFjord(UInt256 estimatedSize) => estimatedSize * GasCostOf.TxDataNonZeroEip2028 / BasicDivisor;
}
24 changes: 24 additions & 0 deletions src/Nethermind/Nethermind.Optimism/Rpc/IOptimismEthRpcModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
using Nethermind.Core.Crypto;
using Nethermind.JsonRpc;
using Nethermind.Blockchain.Find;
using Nethermind.Int256;
using System.Threading.Tasks;
using Nethermind.Facade.Eth;

namespace Nethermind.Optimism.Rpc;

Expand All @@ -23,4 +26,25 @@ public interface IOptimismEthRpcModule : IEthRpcModule
IsSharable = true,
ExampleResponse = "{\"transactionHash\":\"0x80757153e93d1b475e203406727b62a501187f63e23b8fa999279e219ee3be71\",\"transactionIndex\":\"0x7\",\"blockHash\":\"0x42def051b21038905cd2a2bc28d460a94df2249466847f0e1bcb4be4eb21891a\",\"blockNumber\":\"0x4e3f39\",\"cumulativeGasUsed\":\"0x62c9d\",\"gasUsed\":\"0xe384\",\"effectiveGasPrice\":\"0x12a05f200\",\"from\":\"0x0afe0a94415e8974052e7e6cfab19ee1c2ef4f69\",\"to\":\"0x19e8c84d4943e58b035626b064cfc76ee13ee6cb\",\"contractAddress\":null,\"logs\":[{\"removed\":false,\"logIndex\":\"0x0\",\"transactionIndex\":\"0x7\",\"transactionHash\":\"0x80757153e93d1b475e203406727b62a501187f63e23b8fa999279e219ee3be71\",\"blockHash\":\"0x42def051b21038905cd2a2bc28d460a94df2249466847f0e1bcb4be4eb21891a\",\"blockNumber\":\"0x4e3f39\",\"address\":\"0x2ac3c1d3e24b45c6c310534bc2dd84b5ed576335\",\"data\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"topics\":[\"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef\",\"0x00000000000000000000000019e8c84d4943e58b035626b064cfc76ee13ee6cb\",\"0x00000000000000000000000028078300a459a9e136f872285654cdc74463041e\"]},{\"removed\":false,\"logIndex\":\"0x1\",\"transactionIndex\":\"0x7\",\"transactionHash\":\"0x80757153e93d1b475e203406727b62a501187f63e23b8fa999279e219ee3be71\",\"blockHash\":\"0x42def051b21038905cd2a2bc28d460a94df2249466847f0e1bcb4be4eb21891a\",\"blockNumber\":\"0x4e3f39\",\"address\":\"0x19e8c84d4943e58b035626b064cfc76ee13ee6cb\",\"data\":\"0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000007735940000000000000000000000000000000000000000000000000000000000000000000\",\"topics\":[\"0x950494fc3642fae5221b6c32e0e45765c95ebb382a04a71b160db0843e74c99f\",\"0x0000000000000000000000000afe0a94415e8974052e7e6cfab19ee1c2ef4f69\",\"0x00000000000000000000000028078300a459a9e136f872285654cdc74463041e\",\"0x0000000000000000000000000afe0a94415e8974052e7e6cfab19ee1c2ef4f69\"]}],\"logsBloom\":\"0x00000000000000000000000000000000000000000000000020000000000000800000000000000000000400000000000000000000000000000000000000002000000000000000000000000008000000000000000000000000000000000000000000000002002000000000000000000000000000000000000000000812000000000000000000000000000001000000000000000000000008000400008000000000000000000000000000000000000000000000000000000000800000000000000000000002000000000000000000000000000000000000100000000000000000002000000000000000000000000010000000000000000000000400000000020000\",\"status\":\"0x1\",\"type\":\"0x0\"}")]
new ResultWrapper<OptimismReceiptForRpc?> eth_getTransactionReceipt([JsonRpcParameter(ExampleValue = "[\"0x80757153e93d1b475e203406727b62a501187f63e23b8fa999279e219ee3be71\"]")] Hash256 txHashData);

[JsonRpcMethod(IsImplemented = true,
Description = "Retrieves a transaction by hash",
IsSharable = true,
ExampleResponse = "{\"hash\":\"0xabca23910646013d608ec671de099447ab60b2b7159ad8319c3c088e8d9ea0fa\",\"nonce\":\"0x1a\",\"blockHash\":\"0xcb6756f69e0469acd5e5bb77966be580786ec2c11de85c9ddfd75257010e34f8\",\"blockNumber\":\"0x4dfbc7\",\"transactionIndex\":\"0xb\",\"from\":\"0xe1e7ab1c643dbe5b24739fdf2a5c7c193b54dd99\",\"to\":\"0x0b10e304088b2ba2b2acfd2f72573faad31a13a5\",\"value\":\"0x0\",\"gasPrice\":\"0x2540be400\",\"gas\":\"0xb4a4\",\"data\":\"0x095ea7b300000000000000000000000092c1576845703089cf6c0788379ed81f75f45dd500000000000000000000000000000000000000000000000000000002540be400\",\"input\":\"0x095ea7b300000000000000000000000092c1576845703089cf6c0788379ed81f75f45dd500000000000000000000000000000000000000000000000000000002540be400\",\"type\":\"0x0\",\"v\":\"0x2d\",\"s\":\"0x496d72d435ead8a8a9a865b14d6a102c1a9f848681d050dbbf11c522c612235\",\"r\":\"0xc8350e831203fecc8bff41f5cf858ac1d121e4b4d9e59c1137cc9440516ca9fd\"}")]
new Task<ResultWrapper<OptimismTransactionForRpc?>> eth_getTransactionByHash(
[JsonRpcParameter(ExampleValue = "\"0xabca23910646013d608ec671de099447ab60b2b7159ad8319c3c088e8d9ea0fa\"")] Hash256 transactionHash);

[JsonRpcMethod(IsImplemented = true,
Description = "Retrieves a transaction by block hash and index",
IsSharable = true,
ExampleResponse = "{\"hash\":\"0xb87ec4c8cb36a06f49cdd93c2e9f63e0b7db9af07a605c8bcf1fbe705162344e\",\"nonce\":\"0x5d\",\"blockHash\":\"0xfe47fb3539ccce9d19a032473effdd6ce19e3c921bbae2746152ccf82ceef48e\",\"blockNumber\":\"0x4dfc90\",\"transactionIndex\":\"0x2\",\"from\":\"0xaa9a0f962e433755c843175488fe088fccf8526f\",\"to\":\"0x074b24cef703f17fe123fa1b82081055775b7004\",\"value\":\"0x0\",\"gasPrice\":\"0x2540be401\",\"gas\":\"0x130ab\",\"data\":\"0x428dc451000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000005d3c0f4ca5ee99f8e8f59ff9a5fab04f6a7e007f0000000000000000000000009d233a907e065855d2a9c7d4b552ea27fb2e5a36000000000000000000000000cbe56b00d173a26a5978ce90db2e33622fd95a28\",\"input\":\"0x428dc451000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000005d3c0f4ca5ee99f8e8f59ff9a5fab04f6a7e007f0000000000000000000000009d233a907e065855d2a9c7d4b552ea27fb2e5a36000000000000000000000000cbe56b00d173a26a5978ce90db2e33622fd95a28\",\"type\":\"0x0\",\"v\":\"0x2e\",\"s\":\"0x696f6db060a6dd30435a7f592506ba3213f81cf4704e211a1a45a99f8984189a\",\"r\":\"0x7e07076186e38b68cb7e4f68a04258a5744c5a2ad1a7153456ee662a07902954\"}")]
new ResultWrapper<OptimismTransactionForRpc?> eth_getTransactionByBlockHashAndIndex(
[JsonRpcParameter(ExampleValue = "[\"0xfe47fb3539ccce9d19a032473effdd6ce19e3c921bbae2746152ccf82ceef48e\",\"0x2\"]")] Hash256 blockHash, UInt256 positionIndex);

[JsonRpcMethod(IsImplemented = true,
Description = "Retrieves a transaction by block number and index",
IsSharable = true,
ExampleResponse = "{\"hash\":\"0xfd320a4949990929f64b52041c58a74c8ce13289b3d6853bd8073b0580aa031a\",\"nonce\":\"0x5b\",\"blockHash\":\"0xd779e1a5ce8f34544d66d219bb3e5331a7b280fae89a36d7d52813a23e1ca1e3\",\"blockNumber\":\"0x4dfdd8\",\"transactionIndex\":\"0x8\",\"from\":\"0xadb540569e2db497bd973c141b0b63be98461e40\",\"to\":\"0x074b24cef703f17fe123fa1b82081055775b7004\",\"value\":\"0x0\",\"gasPrice\":\"0x12a05f200\",\"gas\":\"0x927c0\",\"data\":\"0x428dc451000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000005d3c0f4ca5ee99f8e8f59ff9a5fab04f6a7e007f0000000000000000000000009d233a907e065855d2a9c7d4b552ea27fb2e5a36000000000000000000000000cbe56b00d173a26a5978ce90db2e33622fd95a28\",\"input\":\"0x428dc451000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000030000000000000000000000005d3c0f4ca5ee99f8e8f59ff9a5fab04f6a7e007f0000000000000000000000009d233a907e065855d2a9c7d4b552ea27fb2e5a36000000000000000000000000cbe56b00d173a26a5978ce90db2e33622fd95a28\",\"type\":\"0x0\",\"v\":\"0x2e\",\"s\":\"0x37b90a929884787df717c87258f0434e2f115ce2fbb4bfc230322112fa9d5bbc\",\"r\":\"0x5222eff9e16b5c3e9e8901d9c45fc8e0f9cf774e8a56546a504025ef67ceefec\"}")]
new ResultWrapper<OptimismTransactionForRpc?> eth_getTransactionByBlockNumberAndIndex(
[JsonRpcParameter(ExampleValue = "[\"5111256\",\"0x8\"]")] BlockParameter blockParameter, UInt256 positionIndex);
}
Loading

0 comments on commit 0480d7a

Please sign in to comment.