diff --git a/lib/src/main/kotlin/com/swmansion/starknet/account/Account.kt b/lib/src/main/kotlin/com/swmansion/starknet/account/Account.kt index 9b963cf5f..a74960f9b 100644 --- a/lib/src/main/kotlin/com/swmansion/starknet/account/Account.kt +++ b/lib/src/main/kotlin/com/swmansion/starknet/account/Account.kt @@ -188,18 +188,19 @@ interface Account { * @param calldata constructor calldata for the contract deployment * @param salt salt used to calculate address of the new contract * @param forFeeEstimate when set to `true`, it changes the version to `2^128+version` so the signed transaction can only be used for fee estimation + * @param resourceBounds L1 and L2 resource bounds for the transaction * @return signed deploy account payload */ fun signDeployAccountV3( classHash: Felt, calldata: Calldata, salt: Felt, - l1ResourceBounds: ResourceBounds, + resourceBounds: ResourceBoundsMapping, forFeeEstimate: Boolean, ): DeployAccountTransactionV3 { val params = DeployAccountParamsV3( nonce = Felt.ZERO, - l1ResourceBounds = l1ResourceBounds, + resourceBounds = resourceBounds, ) return signDeployAccountV3(classHash, calldata, salt, params, forFeeEstimate) } @@ -212,17 +213,18 @@ interface Account { * @param classHash hash of the contract that will be deployed. Has to be declared first! * @param calldata constructor calldata for the contract deployment * @param salt salt used to calculate address of the new contract + * @param resourceBounds L1 and L2 resource bounds for the transaction * @return signed deploy account payload */ fun signDeployAccountV3( classHash: Felt, calldata: Calldata, salt: Felt, - l1ResourceBounds: ResourceBounds, + resourceBounds: ResourceBoundsMapping, ): DeployAccountTransactionV3 { val params = DeployAccountParamsV3( nonce = Felt.ZERO, - l1ResourceBounds = l1ResourceBounds, + resourceBounds = resourceBounds, ) return signDeployAccountV3(classHash, calldata, salt, params, false) } @@ -333,10 +335,10 @@ interface Account { * Execute list of calls on Starknet. * * @param calls a list of calls to be executed. - * @param l1ResourceBounds L1 resource bounds for the transaction. + * @param resourceBounds L1 and L2 resource bounds for the transaction * @return Invoke function response, containing transaction hash. */ - fun executeV3(calls: List, l1ResourceBounds: ResourceBounds): Request + fun executeV3(calls: List, resourceBounds: ResourceBoundsMapping): Request /** * Execute single call using version 1 invoke transaction. @@ -355,10 +357,10 @@ interface Account { * Execute single call on Starknet. * * @param call a call to be executed. - * @param l1ResourceBounds L1 resource bounds for the transaction. + * @param resourceBounds L1 and L2 resource bounds for the transaction * @return Invoke function response, containing transaction hash. */ - fun executeV3(call: Call, l1ResourceBounds: ResourceBounds): Request + fun executeV3(call: Call, resourceBounds: ResourceBoundsMapping): Request /** * Execute a list of calls using version 1 invoke transaction with automatically estimated fee diff --git a/lib/src/main/kotlin/com/swmansion/starknet/account/StandardAccount.kt b/lib/src/main/kotlin/com/swmansion/starknet/account/StandardAccount.kt index 9c2b9c417..0d9285d40 100644 --- a/lib/src/main/kotlin/com/swmansion/starknet/account/StandardAccount.kt +++ b/lib/src/main/kotlin/com/swmansion/starknet/account/StandardAccount.kt @@ -314,11 +314,11 @@ class StandardAccount @JvmOverloads constructor( } } - override fun executeV3(calls: List, l1ResourceBounds: ResourceBounds): Request { + override fun executeV3(calls: List, resourceBounds: ResourceBoundsMapping): Request { return getNonce().compose { nonce -> val signParams = InvokeParamsV3( nonce = nonce, - l1ResourceBounds = l1ResourceBounds, + resourceBounds = resourceBounds, ) val payload = signV3(calls, signParams, false) @@ -343,7 +343,7 @@ class StandardAccount @JvmOverloads constructor( amountMultiplier = estimateAmountMultiplier, unitPriceMultiplier = estimateUnitPriceMultiplier, ) - executeV3(calls, resourceBounds.l1Gas) + executeV3(calls, resourceBounds) } } @@ -363,7 +363,7 @@ class StandardAccount @JvmOverloads constructor( override fun executeV3(calls: List): Request { return estimateFeeV3(calls).compose { estimateFee -> val resourceBounds = estimateFee.values.first().toResourceBounds() - executeV3(calls, resourceBounds.l1Gas) + executeV3(calls, resourceBounds) } } @@ -371,8 +371,8 @@ class StandardAccount @JvmOverloads constructor( return executeV1(listOf(call), maxFee) } - override fun executeV3(call: Call, l1ResourceBounds: ResourceBounds): Request { - return executeV3(listOf(call), l1ResourceBounds) + override fun executeV3(call: Call, resourceBounds: ResourceBoundsMapping): Request { + return executeV3(listOf(call), resourceBounds) } override fun executeV1(call: Call, estimateFeeMultiplier: Double): Request { @@ -541,7 +541,7 @@ class StandardAccount @JvmOverloads constructor( private fun buildEstimateFeeV3Payload(calls: List, nonce: Felt): List { val executionParams = InvokeParamsV3( nonce = nonce, - l1ResourceBounds = ResourceBounds.ZERO, + resourceBounds = ResourceBoundsMapping.ZERO, ) val payload = signV3(calls, executionParams, true) diff --git a/lib/src/main/kotlin/com/swmansion/starknet/data/serializers/MerkleNodePolymorphicSerializer.kt b/lib/src/main/kotlin/com/swmansion/starknet/data/serializers/MerkleNodePolymorphicSerializer.kt new file mode 100644 index 000000000..5e72b167f --- /dev/null +++ b/lib/src/main/kotlin/com/swmansion/starknet/data/serializers/MerkleNodePolymorphicSerializer.kt @@ -0,0 +1,21 @@ +import com.swmansion.starknet.data.types.* +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.descriptors.elementNames +import kotlinx.serialization.json.JsonContentPolymorphicSerializer +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.jsonObject + +internal object MerkleNodePolymorphicSerializer : + JsonContentPolymorphicSerializer(NodeHashToNodeMappingItem.MerkleNode::class) { + override fun selectDeserializer(element: JsonElement): DeserializationStrategy { + val jsonElement = element.jsonObject + val binaryNodeKeys = NodeHashToNodeMappingItem.BinaryNode.serializer().descriptor.elementNames.toSet() + val edgeNodeKeys = NodeHashToNodeMappingItem.EdgeNode.serializer().descriptor.elementNames.toSet() + + return when (jsonElement.keys) { + binaryNodeKeys -> NodeHashToNodeMappingItem.BinaryNode.serializer() + edgeNodeKeys -> NodeHashToNodeMappingItem.EdgeNode.serializer() + else -> throw IllegalArgumentException("Invalid MerkleNode JSON object: $jsonElement") + } + } +} diff --git a/lib/src/main/kotlin/com/swmansion/starknet/data/serializers/MessageStatusListSerializer.kt b/lib/src/main/kotlin/com/swmansion/starknet/data/serializers/MessageStatusListSerializer.kt new file mode 100644 index 000000000..d54fa3f12 --- /dev/null +++ b/lib/src/main/kotlin/com/swmansion/starknet/data/serializers/MessageStatusListSerializer.kt @@ -0,0 +1,27 @@ +package com.swmansion.starknet.data.serializers + +import com.swmansion.starknet.data.types.MessageStatus +import com.swmansion.starknet.data.types.MessageStatusList +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.listSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder + +internal object MessageStatusListSerializer : KSerializer { + private val listSerializer = ListSerializer(MessageStatus.serializer()) + + @OptIn(ExperimentalSerializationApi::class) + override val descriptor: SerialDescriptor = listSerialDescriptor() + + override fun serialize(encoder: Encoder, value: MessageStatusList) { + encoder.encodeSerializableValue(listSerializer, value.values) + } + + override fun deserialize(decoder: Decoder): MessageStatusList { + val list = decoder.decodeSerializableValue(listSerializer) + return MessageStatusList(list) + } +} diff --git a/lib/src/main/kotlin/com/swmansion/starknet/data/types/Block.kt b/lib/src/main/kotlin/com/swmansion/starknet/data/types/Block.kt index a0bc33ee3..378fce319 100644 --- a/lib/src/main/kotlin/com/swmansion/starknet/data/types/Block.kt +++ b/lib/src/main/kotlin/com/swmansion/starknet/data/types/Block.kt @@ -46,6 +46,7 @@ sealed interface Block : StarknetResponse { val sequencerAddress: Felt val parentHash: Felt val l1GasPrice: ResourcePrice + val l2GasPrice: ResourcePrice val l1DataGasPrice: ResourcePrice val l1DataAvailabilityMode: L1DAMode val starknetVersion: String @@ -107,6 +108,9 @@ data class ProcessedBlockWithTransactions( @SerialName("l1_gas_price") override val l1GasPrice: ResourcePrice, + @SerialName("l2_gas_price") + override val l2GasPrice: ResourcePrice, + @SerialName("l1_data_gas_price") override val l1DataGasPrice: ResourcePrice, @@ -137,6 +141,9 @@ data class PendingBlockWithTransactions( @SerialName("l1_gas_price") override val l1GasPrice: ResourcePrice, + @SerialName("l2_gas_price") + override val l2GasPrice: ResourcePrice, + @SerialName("l1_data_gas_price") override val l1DataGasPrice: ResourcePrice, @@ -192,6 +199,9 @@ data class ProcessedBlockWithReceipts( @SerialName("l1_gas_price") override val l1GasPrice: ResourcePrice, + @SerialName("l2_gas_price") + override val l2GasPrice: ResourcePrice, + @SerialName("l1_data_gas_price") override val l1DataGasPrice: ResourcePrice, @@ -219,6 +229,9 @@ data class PendingBlockWithReceipts( @SerialName("l1_gas_price") override val l1GasPrice: ResourcePrice, + @SerialName("l2_gas_price") + override val l2GasPrice: ResourcePrice, + @SerialName("l1_data_gas_price") override val l1DataGasPrice: ResourcePrice, @@ -263,6 +276,9 @@ data class ProcessedBlockWithTransactionHashes( @SerialName("l1_gas_price") override val l1GasPrice: ResourcePrice, + @SerialName("l2_gas_price") + override val l2GasPrice: ResourcePrice, + @SerialName("l1_data_gas_price") override val l1DataGasPrice: ResourcePrice, @@ -290,6 +306,9 @@ data class PendingBlockWithTransactionHashes( @SerialName("l1_gas_price") override val l1GasPrice: ResourcePrice, + @SerialName("l2_gas_price") + override val l2GasPrice: ResourcePrice, + @SerialName("l1_data_gas_price") override val l1DataGasPrice: ResourcePrice, diff --git a/lib/src/main/kotlin/com/swmansion/starknet/data/types/ContractStorageKeys.kt b/lib/src/main/kotlin/com/swmansion/starknet/data/types/ContractStorageKeys.kt new file mode 100644 index 000000000..242070347 --- /dev/null +++ b/lib/src/main/kotlin/com/swmansion/starknet/data/types/ContractStorageKeys.kt @@ -0,0 +1,13 @@ +package com.swmansion.starknet.data.types + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class ContractStorageKeys( + @SerialName("contract_address") + val contractAddress: Felt, + + @SerialName("storage_keys") + val storageKeys: List, +) diff --git a/lib/src/main/kotlin/com/swmansion/starknet/data/types/MessageStatusList.kt b/lib/src/main/kotlin/com/swmansion/starknet/data/types/MessageStatusList.kt new file mode 100644 index 000000000..cf83317c3 --- /dev/null +++ b/lib/src/main/kotlin/com/swmansion/starknet/data/types/MessageStatusList.kt @@ -0,0 +1,7 @@ +package com.swmansion.starknet.data.types + +import com.swmansion.starknet.data.serializers.MessageStatusListSerializer +import kotlinx.serialization.Serializable + +@Serializable(with = MessageStatusListSerializer::class) +data class MessageStatusList(val values: List) : StarknetResponse diff --git a/lib/src/main/kotlin/com/swmansion/starknet/data/types/Params.kt b/lib/src/main/kotlin/com/swmansion/starknet/data/types/Params.kt index 74fbf6218..1b1592562 100644 --- a/lib/src/main/kotlin/com/swmansion/starknet/data/types/Params.kt +++ b/lib/src/main/kotlin/com/swmansion/starknet/data/types/Params.kt @@ -39,11 +39,9 @@ data class InvokeParamsV3 private constructor( override val nonceDataAvailabilityMode: DAMode, override val feeDataAvailabilityMode: DAMode, ) : ParamsV3() { - constructor(nonce: Felt, l1ResourceBounds: ResourceBounds) : this( + constructor(nonce: Felt, resourceBounds: ResourceBoundsMapping) : this( nonce = nonce, - resourceBounds = ResourceBoundsMapping( - l1Gas = l1ResourceBounds, - ), + resourceBounds = resourceBounds, tip = Uint64.ZERO, paymasterData = emptyList(), accountDeploymentData = emptyList(), @@ -66,11 +64,9 @@ data class DeclareParamsV3 private constructor( override val nonceDataAvailabilityMode: DAMode, override val feeDataAvailabilityMode: DAMode, ) : ParamsV3() { - constructor(nonce: Felt, l1ResourceBounds: ResourceBounds) : this( + constructor(nonce: Felt, resourceBounds: ResourceBoundsMapping) : this( nonce = nonce, - resourceBounds = ResourceBoundsMapping( - l1Gas = l1ResourceBounds, - ), + resourceBounds = resourceBounds, tip = Uint64.ZERO, paymasterData = emptyList(), accountDeploymentData = emptyList(), @@ -95,12 +91,10 @@ data class DeployAccountParamsV3 private constructor( @JvmOverloads constructor( nonce: Felt = Felt.ZERO, - l1ResourceBounds: ResourceBounds, + resourceBounds: ResourceBoundsMapping, ) : this( nonce = nonce, - resourceBounds = ResourceBoundsMapping( - l1Gas = l1ResourceBounds, - ), + resourceBounds = resourceBounds, tip = Uint64.ZERO, paymasterData = emptyList(), nonceDataAvailabilityMode = DAMode.L1, diff --git a/lib/src/main/kotlin/com/swmansion/starknet/data/types/Payloads.kt b/lib/src/main/kotlin/com/swmansion/starknet/data/types/Payloads.kt index f1b95c6e6..79b2ecbf0 100644 --- a/lib/src/main/kotlin/com/swmansion/starknet/data/types/Payloads.kt +++ b/lib/src/main/kotlin/com/swmansion/starknet/data/types/Payloads.kt @@ -47,6 +47,12 @@ internal data class GetTransactionStatusPayload( val transactionHash: Felt, ) +@Serializable +internal data class GetMessagesStatusPayload( + @SerialName("transaction_hash") + val transactionHash: NumAsHex, +) + @Serializable internal data class EstimateTransactionFeePayload( @SerialName("request") @@ -83,6 +89,21 @@ internal data class GetNoncePayload( override val blockId: BlockId, ) : PayloadWithBlockId() +@Serializable +internal data class GetStorageProofPayload constructor( + @SerialName("block_id") + val blockId: BlockId, + + @SerialName("class_hashes") + val classHashes: List? = null, + + @SerialName("contract_addresses") + val contractAddresses: List? = null, + + @SerialName("contracts_storage_keys") + val contractsStorageKeys: List? = null, +) + @Serializable internal data class GetBlockWithTransactionsPayload( @SerialName("block_id") diff --git a/lib/src/main/kotlin/com/swmansion/starknet/data/types/Resources.kt b/lib/src/main/kotlin/com/swmansion/starknet/data/types/Resources.kt index 526bb6e66..6467c053c 100644 --- a/lib/src/main/kotlin/com/swmansion/starknet/data/types/Resources.kt +++ b/lib/src/main/kotlin/com/swmansion/starknet/data/types/Resources.kt @@ -7,92 +7,27 @@ import kotlinx.serialization.Serializable @Serializable sealed class Resources { - abstract val steps: Int - abstract val memoryHoles: Int? - abstract val rangeCheckApplications: Int? - abstract val pedersenApplications: Int? - abstract val poseidonApplications: Int? - abstract val ecOpApplications: Int? - abstract val ecdsaApplications: Int? - abstract val bitwiseApplications: Int? - abstract val keccakApplications: Int? - abstract val segmentArenaApplications: Int? + abstract val l1Gas: Int + abstract val l2Gas: Int } -@Serializable -data class ComputationResources( - @SerialName("steps") - override val steps: Int, - - @SerialName("memory_holes") - override val memoryHoles: Int? = null, - - @SerialName("range_check_builtin_applications") - override val rangeCheckApplications: Int? = null, - - @SerialName("pedersen_builtin_applications") - override val pedersenApplications: Int? = null, - - @SerialName("poseidon_builtin_applications") - override val poseidonApplications: Int? = null, - - @SerialName("ec_op_builtin_applications") - override val ecOpApplications: Int? = null, - - @SerialName("ecdsa_builtin_applications") - override val ecdsaApplications: Int? = null, - - @SerialName("bitwise_builtin_applications") - override val bitwiseApplications: Int? = null, - - @SerialName("keccak_builtin_applications") - override val keccakApplications: Int? = null, - - @SerialName("segment_arena_builtin") - override val segmentArenaApplications: Int? = null, -) : Resources() - @Serializable data class ExecutionResources( - @SerialName("steps") - override val steps: Int, - - @SerialName("memory_holes") - override val memoryHoles: Int? = null, - - @SerialName("range_check_builtin_applications") - override val rangeCheckApplications: Int? = null, - - @SerialName("pedersen_builtin_applications") - override val pedersenApplications: Int? = null, - - @SerialName("poseidon_builtin_applications") - override val poseidonApplications: Int? = null, - - @SerialName("ec_op_builtin_applications") - override val ecOpApplications: Int? = null, - - @SerialName("ecdsa_builtin_applications") - override val ecdsaApplications: Int? = null, - - @SerialName("bitwise_builtin_applications") - override val bitwiseApplications: Int? = null, - - @SerialName("keccak_builtin_applications") - override val keccakApplications: Int? = null, + @SerialName("l1_gas") + override val l1Gas: Int, - @SerialName("segment_arena_builtin") - override val segmentArenaApplications: Int? = null, + @SerialName("l1_data_gas") + val l1DataGas: Int, - @SerialName("data_availability") - val dataAvailability: DataResources, + @SerialName("l2_gas") + override val l2Gas: Int, ) : Resources() @Serializable -data class DataResources( +data class InnerCallExecutionResources( @SerialName("l1_gas") - val l1Gas: Int, + override val l1Gas: Int, - @SerialName("l1_data_gas") - val l1DataGas: Int, -) + @SerialName("l2_gas") + override val l2Gas: Int, +) : Resources() diff --git a/lib/src/main/kotlin/com/swmansion/starknet/data/types/Responses.kt b/lib/src/main/kotlin/com/swmansion/starknet/data/types/Responses.kt index dd0aac7e9..d9c86c9f8 100644 --- a/lib/src/main/kotlin/com/swmansion/starknet/data/types/Responses.kt +++ b/lib/src/main/kotlin/com/swmansion/starknet/data/types/Responses.kt @@ -1,5 +1,6 @@ package com.swmansion.starknet.data.types +import MerkleNodePolymorphicSerializer import com.swmansion.starknet.data.serializers.HexToIntDeserializer import com.swmansion.starknet.data.serializers.NotSyncingResponseSerializer import com.swmansion.starknet.extensions.* @@ -10,6 +11,8 @@ import kotlinx.serialization.json.JsonNames import java.math.BigInteger import kotlin.math.roundToInt +typealias NodeHashToNodeMapping = List + @Serializable data class CallContractResponse( val result: List, @@ -43,17 +46,23 @@ data class DeployAccountResponse( @Serializable data class EstimateFeeResponse( - @SerialName("gas_consumed") - val gasConsumed: Felt, + @SerialName("l1_gas_consumed") + val l1GasConsumed: Felt, + + @SerialName("l1_gas_price") + val l1GasPrice: Felt, - @SerialName("gas_price") - val gasPrice: Felt, + @SerialName("l2_gas_consumed") + val l2GasConsumed: Felt, - @SerialName("data_gas_consumed") - val dataGasConsumed: Felt, + @SerialName("l2_gas_price") + val l2GasPrice: Felt, - @SerialName("data_gas_price") - val dataGasPrice: Felt, + @SerialName("l1_data_gas_consumed") + val l1DataGasConsumed: Felt, + + @SerialName("l1_data_gas_price") + val l1DataGasPrice: Felt, @SerialName("overall_fee") val overallFee: Felt, @@ -79,9 +88,11 @@ data class EstimateFeeResponse( /** * Convert estimated fee to resource bounds with applied multipliers. * - * Calculates max amount as maxAmount = [overallFee] / [gasPrice], unless [gasPrice] is 0, then maxAmount is 0. - * Calculates max price per unit as maxPricePerUnit = [gasPrice]. - * Then multiplies maxAmount by round([amountMultiplier] * 100%) and maxPricePerUnit by round([unitPriceMultiplier] * 100%) and performs integer division by 100 on both. + * Calculates max amount l1 as maxAmountL1 = [overallFee] / [l1GasPrice], unless [l1GasPrice] is 0, then maxAmountL1 is 0. + * Calculates max amount l2 as maxAmountL2 = [overallFee] / [l2GasPrice], unless [l2GasPrice] is 0, then maxAmountL2 is 0. + * Calculates max price per unit l1 as maxPricePerUnitL1 = [l1GasPrice]. + * Calculates max price per unit l2 as maxPricePerUnitL2 = [l2GasPrice]. + * Then multiplies maxAmountL1/L2 by round([amountMultiplier] * 100%) and maxPricePerUnitL1/L2 by round([unitPriceMultiplier] * 100%) and performs integer division by 100 on each. * * @param amountMultiplier Multiplier for max amount, defaults to 1.5. * @param unitPriceMultiplier Multiplier for max price per unit, defaults to 1.5. @@ -96,14 +107,22 @@ data class EstimateFeeResponse( require(amountMultiplier >= 0) require(unitPriceMultiplier >= 0) - val maxAmount = when (gasPrice) { + val maxAmountL1 = when (l1GasPrice) { Felt.ZERO -> Uint64.ZERO - else -> (overallFee.value / gasPrice.value).applyMultiplier(amountMultiplier).toUint64 + else -> (overallFee.value / l1GasPrice.value).applyMultiplier(amountMultiplier).toUint64 } - val maxPricePerUnit = gasPrice.value.applyMultiplier(unitPriceMultiplier).toUint128 + + val maxAmountL2 = when (l2GasPrice) { + Felt.ZERO -> Uint64.ZERO + else -> (overallFee.value / l2GasPrice.value).applyMultiplier(amountMultiplier).toUint64 + } + + val maxPricePerUnitL1 = l1GasPrice.value.applyMultiplier(unitPriceMultiplier).toUint128 + val maxPricePerUnitL2 = l2GasPrice.value.applyMultiplier(unitPriceMultiplier).toUint128 return ResourceBoundsMapping( - l1Gas = ResourceBounds(maxAmount = maxAmount, maxPricePerUnit = maxPricePerUnit), + l1Gas = ResourceBounds(maxAmount = maxAmountL1, maxPricePerUnit = maxPricePerUnitL1), + l2Gas = ResourceBounds(maxAmount = maxAmountL2, maxPricePerUnit = maxPricePerUnitL2), ) } @@ -129,8 +148,101 @@ data class GetTransactionStatusResponse( @SerialName("execution_status") val executionStatus: TransactionExecutionStatus? = null, + + @SerialName("failure_reason") + val failureReason: String? = null, ) : StarknetResponse +@Serializable +data class MessageStatus( + @SerialName("transaction_hash") + val transactionHash: Felt, + + @SerialName("finality_status") + val finalityStatus: TransactionStatus, + + @SerialName("failure_reason") + val failureReason: String? = null, +) + +@Serializable +data class StorageProof( + @SerialName("classes_proof") + val classesProof: NodeHashToNodeMapping, + + @SerialName("contracts_proof") + val contractsProof: ContractsProof, + + @SerialName("contracts_storage_proofs") + val contractsStorageProofs: List, + + @SerialName("global_roots") + val globalRoots: GlobalRoots, +) : StarknetResponse { + @Serializable + data class GlobalRoots( + @SerialName("contracts_tree_root") + val contractsTreeRoot: Felt, + + @SerialName("classes_tree_root") + val classesTreeRoot: Felt, + + @SerialName("block_hash") + val blockHash: Felt, + ) +} + +@Serializable +data class ContractsProof( + @SerialName("nodes") + val nodes: NodeHashToNodeMapping, + + @SerialName("contract_leaves_data") + val contractLeavesData: List, +) + +@Serializable +data class ContractLeafData( + @SerialName("nonce") + val nonce: Felt, + + @SerialName("class_hash") + val classHash: Felt, +) + +@Serializable +data class NodeHashToNodeMappingItem( + @SerialName("node_hash") + val nodeHash: Felt, + + @SerialName("node") + val node: MerkleNode, +) { + @Serializable(with = MerkleNodePolymorphicSerializer::class) + sealed interface MerkleNode + + @Serializable + data class BinaryNode( + @SerialName("left") + val left: Felt, + + @SerialName("right") + val right: Felt, + ) : MerkleNode + + @Serializable + data class EdgeNode( + @SerialName("path") + val path: NumAsHex, + + @SerialName("length") + val length: Int, + + @SerialName("child") + val value: Felt, + ) : MerkleNode +} + @Serializable sealed class Syncing : StarknetResponse { abstract val status: Boolean @@ -309,10 +421,9 @@ data class PendingStateUpdateResponse( ) : StateUpdate() // TODO: remove SCREAMING_SNAKE_CASE @JsonNames once devnet is updated -@Suppress("DataClassPrivateConstructor") @OptIn(ExperimentalSerializationApi::class) @Serializable -data class ResourceBoundsMapping private constructor( +data class ResourceBoundsMapping( @SerialName("l1_gas") @JsonNames("L1_GAS") val l1Gas: ResourceBounds, @@ -321,14 +432,10 @@ data class ResourceBoundsMapping private constructor( @JsonNames("L2_GAS") val l2Gas: ResourceBounds, ) { - constructor( - l1Gas: ResourceBounds, - ) : this( - // As of Starknet 0.13.0, the L2 gas is not supported - // Because of this, the L2 gas values are hardcoded to 0 - l1Gas = l1Gas, - l2Gas = ResourceBounds.ZERO, - ) + companion object { + @field:JvmField + val ZERO = ResourceBoundsMapping(ResourceBounds.ZERO, ResourceBounds.ZERO) + } } @Serializable diff --git a/lib/src/main/kotlin/com/swmansion/starknet/data/types/SimulatedTransaction.kt b/lib/src/main/kotlin/com/swmansion/starknet/data/types/SimulatedTransaction.kt index 3ffaa0edf..f5c18b4ba 100644 --- a/lib/src/main/kotlin/com/swmansion/starknet/data/types/SimulatedTransaction.kt +++ b/lib/src/main/kotlin/com/swmansion/starknet/data/types/SimulatedTransaction.kt @@ -65,7 +65,7 @@ data class FunctionInvocation( val messages: List, @SerialName("execution_resources") - val computationResources: ComputationResources, + val executionResources: InnerCallExecutionResources, ) @Serializable diff --git a/lib/src/main/kotlin/com/swmansion/starknet/deployercontract/Deployer.kt b/lib/src/main/kotlin/com/swmansion/starknet/deployercontract/Deployer.kt index f6eb92f46..3f292dca3 100644 --- a/lib/src/main/kotlin/com/swmansion/starknet/deployercontract/Deployer.kt +++ b/lib/src/main/kotlin/com/swmansion/starknet/deployercontract/Deployer.kt @@ -2,7 +2,7 @@ package com.swmansion.starknet.deployercontract import com.swmansion.starknet.data.types.Calldata import com.swmansion.starknet.data.types.Felt -import com.swmansion.starknet.data.types.ResourceBounds +import com.swmansion.starknet.data.types.ResourceBoundsMapping import com.swmansion.starknet.provider.Request import com.swmansion.starknet.provider.exceptions.RequestFailedException @@ -48,7 +48,7 @@ interface Deployer { * @param unique set whether deployed contract address should be based on account address or not * @param salt a salt to be used to calculate deployed contract address * @param constructorCalldata constructor calldata - * @param l1ResourceBounds L1 resource bounds for the transaction + * @param resourceBounds L1 and L2 resource bounds for the transaction * * @throws RequestFailedException * @@ -59,7 +59,7 @@ interface Deployer { unique: Boolean, salt: Felt, constructorCalldata: Calldata, - l1ResourceBounds: ResourceBounds, + resourceBounds: ResourceBoundsMapping, ): Request /** @@ -121,14 +121,14 @@ interface Deployer { * * @param classHash a class hash of the declared contract * @param constructorCalldata constructor calldata - * @param l1ResourceBounds L1 resource bounds for the transaction + * @param resourceBounds L1 and L2 resource bounds for the transaction * * @throws RequestFailedException * @throws SaltGenerationFailedException * * @sample starknet.deployercontract.StandardDeployerTest.testUdcDeployV3WithSpecificFeeAndDefaultParameters */ - fun deployContractV3(classHash: Felt, constructorCalldata: Calldata, l1ResourceBounds: ResourceBounds): Request + fun deployContractV3(classHash: Felt, constructorCalldata: Calldata, resourceBounds: ResourceBoundsMapping): Request /** * Deploy a contract through Universal Deployer Contract (UDC) using version 1 invoke transaction diff --git a/lib/src/main/kotlin/com/swmansion/starknet/deployercontract/StandardDeployer.kt b/lib/src/main/kotlin/com/swmansion/starknet/deployercontract/StandardDeployer.kt index 73bd9c06d..b67d75b64 100644 --- a/lib/src/main/kotlin/com/swmansion/starknet/deployercontract/StandardDeployer.kt +++ b/lib/src/main/kotlin/com/swmansion/starknet/deployercontract/StandardDeployer.kt @@ -31,11 +31,11 @@ class StandardDeployer( unique: Boolean, salt: Felt, constructorCalldata: Calldata, - l1ResourceBounds: ResourceBounds, + resourceBounds: ResourceBoundsMapping, ): Request { val call = buildDeployContractCall(classHash, unique, salt, constructorCalldata) - return account.executeV3(call, l1ResourceBounds).map { ContractDeployment(it.transactionHash) } + return account.executeV3(call, resourceBounds).map { ContractDeployment(it.transactionHash) } } override fun deployContractV1( @@ -65,9 +65,9 @@ class StandardDeployer( return deployContractV1(classHash, true, salt, constructorCalldata, maxFee) } - override fun deployContractV3(classHash: Felt, constructorCalldata: Calldata, l1ResourceBounds: ResourceBounds): Request { + override fun deployContractV3(classHash: Felt, constructorCalldata: Calldata, resourceBounds: ResourceBoundsMapping): Request { val salt = randomSalt() - return deployContractV3(classHash, true, salt, constructorCalldata, l1ResourceBounds) + return deployContractV3(classHash, true, salt, constructorCalldata, resourceBounds) } override fun deployContractV1(classHash: Felt, constructorCalldata: Calldata): Request { diff --git a/lib/src/main/kotlin/com/swmansion/starknet/provider/Provider.kt b/lib/src/main/kotlin/com/swmansion/starknet/provider/Provider.kt index 556897137..e4e81b619 100644 --- a/lib/src/main/kotlin/com/swmansion/starknet/provider/Provider.kt +++ b/lib/src/main/kotlin/com/swmansion/starknet/provider/Provider.kt @@ -321,6 +321,17 @@ interface Provider { fun getTransactionStatus(transactionHash: Felt): Request + /** + * Get L1 handler transaction data. + * + * Get L1 handler transaction data for all L1 → L2 messages sent by the given L1 transaction. + * + * @param l1TransactionHash The hash of the L1 transaction. + * + * @throws RequestFailedException + */ + fun getMessagesStatus(l1TransactionHash: NumAsHex): Request + /** * Get the contract class definition. * @@ -651,6 +662,20 @@ interface Provider { */ fun getNonce(contractAddress: Felt, blockHash: Felt): Request + /** + * Get merkle paths in one of the state tries. + * + * Get merkle paths in one of the state tries: global state, classes, individual contract. + * + * @param blockId the hash of the requested block + * @param classHashes list of class hashes for which we want to prove membership + * @param contractAddresses list of contract addresses for which we want to prove membership + * @param contractsStorageKeys list of contract address and storage keys pairs + * + * @throws RequestFailedException + */ + fun getStorageProof(blockId: BlockId, classHashes: List? = null, contractAddresses: List? = null, contractsStorageKeys: List? = null): Request + /** * Get the block synchronization status. * diff --git a/lib/src/main/kotlin/com/swmansion/starknet/provider/rpc/JsonRpcProvider.kt b/lib/src/main/kotlin/com/swmansion/starknet/provider/rpc/JsonRpcProvider.kt index 3e67f9f9f..a4e2d9184 100644 --- a/lib/src/main/kotlin/com/swmansion/starknet/provider/rpc/JsonRpcProvider.kt +++ b/lib/src/main/kotlin/com/swmansion/starknet/provider/rpc/JsonRpcProvider.kt @@ -6,6 +6,7 @@ import com.swmansion.starknet.data.serializers.BlockWithTransactionsPolymorphicS import com.swmansion.starknet.data.serializers.SyncPolymorphicSerializer import com.swmansion.starknet.data.serializers.TransactionReceiptPolymorphicSerializer import com.swmansion.starknet.data.types.* +import com.swmansion.starknet.data.types.MessageStatusList import com.swmansion.starknet.provider.Provider import com.swmansion.starknet.provider.Request import com.swmansion.starknet.service.http.* @@ -270,6 +271,18 @@ class JsonRpcProvider( return buildRequest(JsonRpcMethod.GET_TRANSACTION_STATUS, params, GetTransactionStatusResponse.serializer()) } + private fun getMessagesStatus(payload: GetMessagesStatusPayload): HttpRequest { + val params = Json.encodeToJsonElement(payload) + + return buildRequest(JsonRpcMethod.GET_MESSAGES_STATUS, params, MessageStatusListSerializer) + } + + override fun getMessagesStatus(l1TransactionHash: NumAsHex): HttpRequest { + val payload = GetMessagesStatusPayload(l1TransactionHash) + + return getMessagesStatus(payload) + } + /** * @sample starknet.account.StandardAccountTest.InvokeTest.signV1SingleCall */ @@ -646,6 +659,23 @@ class JsonRpcProvider( return getNonce(payload) } + private fun getStorageProof(payload: GetStorageProofPayload): HttpRequest { + val jsonPayload = Json.encodeToJsonElement(payload) + + return buildRequest(JsonRpcMethod.GET_STORAGE_PROOF, jsonPayload, StorageProof.serializer()) + } + + override fun getStorageProof( + blockId: BlockId, + classHashes: List?, + contractAddresses: List?, + contractsStorageKeys: List?, + ): HttpRequest { + val payload = GetStorageProofPayload(blockId, classHashes, contractAddresses, contractsStorageKeys) + + return getStorageProof(payload) + } + /** * @sample starknet.provider.ProviderTest.getSyncInformationNodeNotSyncing */ @@ -884,6 +914,7 @@ private enum class JsonRpcMethod(val methodName: String) { GET_TRANSACTION_BY_HASH("starknet_getTransactionByHash"), GET_TRANSACTION_RECEIPT("starknet_getTransactionReceipt"), GET_TRANSACTION_STATUS("starknet_getTransactionStatus"), + GET_MESSAGES_STATUS("starknet_getMessagesStatus"), DECLARE("starknet_addDeclareTransaction"), GET_EVENTS("starknet_getEvents"), GET_BLOCK_NUMBER("starknet_blockNumber"), @@ -899,6 +930,7 @@ private enum class JsonRpcMethod(val methodName: String) { GET_STATE_UPDATE("starknet_getStateUpdate"), GET_TRANSACTION_BY_BLOCK_ID_AND_INDEX("starknet_getTransactionByBlockIdAndIndex"), GET_NONCE("starknet_getNonce"), + GET_STORAGE_PROOF("starknet_getStorageProof"), DEPLOY_ACCOUNT_TRANSACTION("starknet_addDeployAccountTransaction"), SIMULATE_TRANSACTIONS("starknet_simulateTransactions"), } diff --git a/lib/src/test/kotlin/com/swmansion/starknet/data/TransactionHashCalculatorTest.kt b/lib/src/test/kotlin/com/swmansion/starknet/data/TransactionHashCalculatorTest.kt index 1ebee6ea3..f8d059ec9 100644 --- a/lib/src/test/kotlin/com/swmansion/starknet/data/TransactionHashCalculatorTest.kt +++ b/lib/src/test/kotlin/com/swmansion/starknet/data/TransactionHashCalculatorTest.kt @@ -123,6 +123,11 @@ internal class TransactionHashCalculatorTest { maxAmount = Uint64.fromHex("0x186a0"), maxPricePerUnit = Uint128.fromHex("0x5af3107a4000"), ), + // TODO: Check if these l2 can be equal to 0 + l2Gas = ResourceBounds( + maxAmount = Uint64.ZERO, + maxPricePerUnit = Uint128.ZERO, + ), ), paymasterData = emptyList(), feeDataAvailabilityMode = DAMode.L1, @@ -148,6 +153,11 @@ internal class TransactionHashCalculatorTest { maxAmount = Uint64.fromHex("0x186a0"), maxPricePerUnit = Uint128.fromHex("0x5af3107a4000"), ), + // TODO: Check if these l2 can be equal to 0 + l2Gas = ResourceBounds( + maxAmount = Uint64.ZERO, + maxPricePerUnit = Uint128.ZERO, + ), ), tip = Uint64.ZERO, paymasterData = emptyList(), @@ -172,6 +182,11 @@ internal class TransactionHashCalculatorTest { maxAmount = Uint64.fromHex("0x186a0"), maxPricePerUnit = Uint128.fromHex("0x2540be400"), ), + // TODO: Check if these l2 can be equal to 0 + l2Gas = ResourceBounds( + maxAmount = Uint64.ZERO, + maxPricePerUnit = Uint128.ZERO, + ), ), tip = Uint64.ZERO, paymasterData = emptyList(), diff --git a/lib/src/test/kotlin/network/account/AccountTest.kt b/lib/src/test/kotlin/network/account/AccountTest.kt index 06336cc3e..79f74bac5 100644 --- a/lib/src/test/kotlin/network/account/AccountTest.kt +++ b/lib/src/test/kotlin/network/account/AccountTest.kt @@ -135,8 +135,10 @@ class AccountTest { val feeEstimateRequest = provider.getEstimateFee(listOf(signedTransaction), BlockTag.LATEST, emptySet()) val feeEstimate = feeEstimateRequest.send().values.first() - assertNotEquals(Felt(0), feeEstimate.gasConsumed) - assertNotEquals(Felt(0), feeEstimate.gasPrice) + assertNotEquals(Felt(0), feeEstimate.l1GasConsumed) + assertNotEquals(Felt(0), feeEstimate.l1GasPrice) + assertNotEquals(Felt(0), feeEstimate.l2GasConsumed) + assertNotEquals(Felt(0), feeEstimate.l2GasPrice) assertNotEquals(Felt(0), feeEstimate.overallFee) } @@ -159,7 +161,7 @@ class AccountTest { val nonce = account.getNonce().send() val params = DeclareParamsV3( nonce = nonce, - l1ResourceBounds = ResourceBounds.ZERO, + resourceBounds = ResourceBoundsMapping.ZERO, ) val declareTransactionPayload = account.signDeclareV3( sierraContractDefinition = contractDefinition, @@ -171,8 +173,10 @@ class AccountTest { val feeEstimateRequest = provider.getEstimateFee(listOf(declareTransactionPayload), BlockTag.PENDING) val feeEstimate = feeEstimateRequest.send().values.first() - assertNotEquals(Felt(0), feeEstimate.gasConsumed) - assertNotEquals(Felt(0), feeEstimate.gasPrice) + assertNotEquals(Felt(0), feeEstimate.l1GasConsumed) + assertNotEquals(Felt(0), feeEstimate.l1GasPrice) + assertNotEquals(Felt(0), feeEstimate.l2GasConsumed) + assertNotEquals(Felt(0), feeEstimate.l2GasPrice) assertNotEquals(Felt(0), feeEstimate.overallFee) } @@ -265,9 +269,17 @@ class AccountTest { maxAmount = Uint64(100000), maxPricePerUnit = Uint128(2500000000000), ) + // TODO: Check if these l2 resources need to be updated once we can add tests + val l2ResourceBounds = ResourceBounds( + maxAmount = Uint64(100000), + maxPricePerUnit = Uint128(2500000000000), + ) val params = DeclareParamsV3( nonce = nonce, - l1ResourceBounds = l1ResourceBounds, + resourceBounds = ResourceBoundsMapping( + l1Gas = l1ResourceBounds, + l2Gas = l2ResourceBounds, + ), ) val declareTransactionPayload = account.signDeclareV3( contractDefinition, @@ -544,7 +556,7 @@ class AccountTest { calldata = calldata, params = DeployAccountParamsV3( nonce = Felt.ZERO, - l1ResourceBounds = ResourceBounds.ZERO, + resourceBounds = ResourceBoundsMapping.ZERO, ), forFeeEstimate = true, // BUG: (#344) this should be true, but Pathfinder and Devnet claim that using query version produce invalid signature ) @@ -569,7 +581,7 @@ class AccountTest { val params = DeployAccountParamsV3( nonce = Felt.ZERO, - l1ResourceBounds = resourceBounds.l1Gas, + resourceBounds = resourceBounds, ) val payload = deployedAccount.signDeployAccountV3( classHash = classHash, diff --git a/lib/src/test/kotlin/network/provider/ProviderTest.kt b/lib/src/test/kotlin/network/provider/ProviderTest.kt index ab5cb9d54..51a041fae 100644 --- a/lib/src/test/kotlin/network/provider/ProviderTest.kt +++ b/lib/src/test/kotlin/network/provider/ProviderTest.kt @@ -96,6 +96,11 @@ class ProviderTest { assertEquals(expectedChainId, chainId) } + @Test + fun `get storage proof`() { + // TODO + } + @Test fun `get transaction status`() { assumeTrue(NetworkConfig.isTestEnabled(requiresGas = false)) @@ -114,6 +119,11 @@ class ProviderTest { assertEquals(TransactionExecutionStatus.REVERTED, transactionStatus2.executionStatus) } + @Test + fun `get messages status`() { + // TODO + } + @Test fun `get deploy account v1 transaction`() { assumeTrue(NetworkConfig.isTestEnabled(requiresGas = false)) diff --git a/lib/src/test/kotlin/starknet/account/StandardAccountTest.kt b/lib/src/test/kotlin/starknet/account/StandardAccountTest.kt index 06b50ff0d..162b4ec3e 100644 --- a/lib/src/test/kotlin/starknet/account/StandardAccountTest.kt +++ b/lib/src/test/kotlin/starknet/account/StandardAccountTest.kt @@ -218,7 +218,7 @@ class StandardAccountTest { assertNotEquals(Felt.ZERO, feeEstimate.overallFee) assertEquals( - feeEstimate.gasPrice.value * feeEstimate.gasConsumed.value + feeEstimate.dataGasPrice.value * feeEstimate.dataGasConsumed.value, + feeEstimate.l1GasPrice.value * feeEstimate.l1GasConsumed.value + feeEstimate.l1DataGasPrice.value * feeEstimate.l1DataGasConsumed.value, feeEstimate.overallFee.value, ) } @@ -236,7 +236,7 @@ class StandardAccountTest { // docsEnd assertNotEquals(Felt.ZERO, feeEstimate.overallFee) assertEquals( - feeEstimate.gasPrice.value * feeEstimate.gasConsumed.value + feeEstimate.dataGasPrice.value * feeEstimate.dataGasConsumed.value, + feeEstimate.l1GasPrice.value * feeEstimate.l1GasConsumed.value + feeEstimate.l1DataGasPrice.value * feeEstimate.l1DataGasConsumed.value + feeEstimate.l2GasPrice.value * feeEstimate.l2GasConsumed.value, feeEstimate.overallFee.value, ) } @@ -253,7 +253,10 @@ class StandardAccountTest { ) val invokeTxV3Payload = account.signV3( call = call, - params = InvokeParamsV3(nonce.value.add(BigInteger.ONE).toFelt, ResourceBounds.ZERO), + params = InvokeParamsV3( + nonce = nonce.value.add(BigInteger.ONE).toFelt, + resourceBounds = ResourceBoundsMapping.ZERO, + ), forFeeEstimate = true, ) assertEquals(TransactionVersion.V1_QUERY, invokeTxV1Payload.version) @@ -271,7 +274,7 @@ class StandardAccountTest { feeEstimates.values.forEach { assertNotEquals(Felt.ZERO, it.overallFee) assertEquals( - it.gasPrice.value * it.gasConsumed.value + it.dataGasPrice.value * it.dataGasConsumed.value, + it.l1GasPrice.value * it.l1GasConsumed.value + it.l1DataGasPrice.value * it.l1DataGasConsumed.value + it.l2GasPrice.value * it.l2GasConsumed.value, it.overallFee.value, ) } @@ -286,7 +289,7 @@ class StandardAccountTest { assertNotEquals(Felt.ZERO, feeEstimate.overallFee) assertEquals( - feeEstimate.gasPrice.value * feeEstimate.gasConsumed.value + feeEstimate.dataGasPrice.value * feeEstimate.dataGasConsumed.value, + feeEstimate.l1GasPrice.value * feeEstimate.l1GasConsumed.value + feeEstimate.l1DataGasPrice.value * feeEstimate.l1DataGasConsumed.value + feeEstimate.l2GasPrice.value * feeEstimate.l2GasConsumed.value, feeEstimate.overallFee.value, ) } @@ -318,7 +321,7 @@ class StandardAccountTest { // docsEnd assertNotEquals(Felt.ZERO, feeEstimate.overallFee) assertEquals( - feeEstimate.gasPrice.value * feeEstimate.gasConsumed.value + feeEstimate.dataGasPrice.value * feeEstimate.dataGasConsumed.value, + feeEstimate.l1GasPrice.value * feeEstimate.l1GasConsumed.value + feeEstimate.l1DataGasPrice.value * feeEstimate.l1DataGasConsumed.value + feeEstimate.l2GasPrice.value * feeEstimate.l2GasConsumed.value, feeEstimate.overallFee.value, ) } @@ -333,7 +336,10 @@ class StandardAccountTest { val contractCasmDefinition = CasmContractDefinition(casmCode) val nonce = account.getNonce().send() - val params = DeclareParamsV3(nonce = nonce, l1ResourceBounds = ResourceBounds.ZERO) + val params = DeclareParamsV3( + nonce = nonce, + resourceBounds = ResourceBoundsMapping.ZERO, + ) val declareTransactionPayload = account.signDeclareV3( contractDefinition, contractCasmDefinition, @@ -348,7 +354,7 @@ class StandardAccountTest { // docsEnd assertNotEquals(Felt.ZERO, feeEstimate.overallFee) assertEquals( - feeEstimate.gasPrice.value * feeEstimate.gasConsumed.value + feeEstimate.dataGasPrice.value * feeEstimate.dataGasConsumed.value, + feeEstimate.l1GasPrice.value * feeEstimate.l1GasConsumed.value + feeEstimate.l1DataGasPrice.value * feeEstimate.l1DataGasConsumed.value + feeEstimate.l2GasPrice.value * feeEstimate.l2GasConsumed.value, feeEstimate.overallFee.value, ) } @@ -390,9 +396,9 @@ class StandardAccountTest { ) val response = request.send() - assertNotEquals(Felt.ZERO, response.gasPrice) + assertNotEquals(Felt.ZERO, (response.l1GasPrice.value + response.l2GasPrice.value).toFelt) assertEquals( - response.gasPrice.value * response.gasConsumed.value + response.dataGasPrice.value * response.dataGasConsumed.value, + response.l1GasPrice.value * response.l1GasConsumed.value + response.l1DataGasPrice.value * response.l1DataGasConsumed.value, response.overallFee.value, ) } @@ -462,12 +468,16 @@ class StandardAccountTest { val contractCasmDefinition = CasmContractDefinition(casmCode) val nonce = account.getNonce().send() - val params = DeclareParamsV3( - nonce = nonce, - l1ResourceBounds = ResourceBounds( + val resourceBounds = ResourceBoundsMapping( + ResourceBounds( maxAmount = Uint64(100000), maxPricePerUnit = Uint128(1000000000000), ), + ResourceBounds.ZERO, + ) + val params = DeclareParamsV3( + nonce = nonce, + resourceBounds = resourceBounds, ) val declareTransactionPayload = account.signDeclareV3( contractDefinition, @@ -585,13 +595,20 @@ class StandardAccountTest { entrypoint = "increase_balance", ) - val params = InvokeParamsV3( - nonce = account.getNonce().send(), - l1ResourceBounds = ResourceBounds( + val resourceBounds = ResourceBoundsMapping( + ResourceBounds( + maxAmount = Uint64(20000), + maxPricePerUnit = Uint128(120000000000), + ), + ResourceBounds( maxAmount = Uint64(20000), maxPricePerUnit = Uint128(120000000000), ), ) + val params = InvokeParamsV3( + nonce = account.getNonce().send(), + resourceBounds = resourceBounds, + ) val payload = account.signV3(call, params) val request = provider.invokeFunction(payload) @@ -699,7 +716,13 @@ class StandardAccountTest { maxAmount = Uint64(20000), maxPricePerUnit = Uint128(120000000000), ) - val result = account.executeV3(call, l1ResourceBounds).send() + // TODO: Check if these l2 resources need to be updated once we can add tests + val l2ResourceBounds = ResourceBounds( + maxAmount = Uint64(20000), + maxPricePerUnit = Uint128(120000000000), + ) + val resourceBounds = ResourceBoundsMapping(l1ResourceBounds, l2ResourceBounds) + val result = account.executeV3(call, resourceBounds).send() val receipt = provider.getTransactionReceipt(result.transactionHash).send() @@ -736,13 +759,21 @@ class StandardAccountTest { calldata = listOf(Felt(10)), ) - val params = InvokeParamsV3( - nonce = account.getNonce().send(), - l1ResourceBounds = ResourceBounds( + val resourceBounds = ResourceBoundsMapping( + ResourceBounds( + maxAmount = Uint64(20000), + maxPricePerUnit = Uint128(120000000000), + ), + // TODO: Check if these l2 resources need to be updated once we can add tests + ResourceBounds( maxAmount = Uint64(20000), maxPricePerUnit = Uint128(120000000000), ), ) + val params = InvokeParamsV3( + nonce = account.getNonce().send(), + resourceBounds = resourceBounds, + ) val payload = account.signV3(listOf(call, call, call), params) val response = provider.invokeFunction(payload).send() @@ -959,7 +990,7 @@ class StandardAccountTest { ) val params = DeployAccountParamsV3( nonce = Felt.ZERO, - l1ResourceBounds = ResourceBounds.ZERO, + resourceBounds = ResourceBoundsMapping.ZERO, ) val payloadForFeeEstimation = account.signDeployAccountV3( classHash = accountContractClassHash, @@ -1064,9 +1095,14 @@ class StandardAccountTest { maxAmount = Uint64(20000), maxPricePerUnit = Uint128(120000000000), ) + val l2ResourceBounds = ResourceBounds( + maxAmount = Uint64(0), + maxPricePerUnit = Uint128(0), + ) + val params = DeployAccountParamsV3( nonce = Felt.ZERO, - l1ResourceBounds = l1ResourceBounds, + resourceBounds = ResourceBoundsMapping(l1ResourceBounds, l2ResourceBounds), ) // Prefund the new account address with STRK @@ -1216,13 +1252,21 @@ class StandardAccountTest { val nonce = account.getNonce().send() val call = Call(balanceContractAddress, "increase_balance", listOf(Felt(1000))) - val params = InvokeParamsV3( - nonce = nonce, - l1ResourceBounds = ResourceBounds( + val resourceBounds = ResourceBoundsMapping( + ResourceBounds( + maxAmount = Uint64(20000), + maxPricePerUnit = Uint128(120000000000), + ), + // TODO: Check if these l2 resources need to be updated once we can add tests + ResourceBounds( maxAmount = Uint64(20000), maxPricePerUnit = Uint128(120000000000), ), ) + val params = InvokeParamsV3( + nonce = nonce, + resourceBounds = resourceBounds, + ) val invokeTx = account.signV3(call, params) @@ -1239,14 +1283,12 @@ class StandardAccountTest { val newAccount = StandardAccount(newAccountAddress, privateKey, provider, chainId) devnetClient.prefundAccountStrk(newAccountAddress) + val deployAccountTx = newAccount.signDeployAccountV3( classHash = accountContractClassHash, calldata = calldata, salt = salt, - l1ResourceBounds = ResourceBounds( - maxAmount = Uint64(20000), - maxPricePerUnit = Uint128(120000000000), - ), + resourceBounds = resourceBounds, ) val simulationFlags = setOf() @@ -1319,15 +1361,23 @@ class StandardAccountTest { val casmContractDefinition = CasmContractDefinition(casmCode) val nonce = account.getNonce().send() + val resourceBounds = ResourceBoundsMapping( + ResourceBounds( + maxAmount = Uint64(100000), + maxPricePerUnit = Uint128(1000000000000), + ), + // TODO: Check if these l2 resources need to be updated once we can add tests + ResourceBounds( + maxAmount = Uint64(100000), + maxPricePerUnit = Uint128(1000000000000), + ), + ) val declareTransactionPayload = account.signDeclareV3( contractDefinition, casmContractDefinition, DeclareParamsV3( nonce = nonce, - l1ResourceBounds = ResourceBounds( - maxAmount = Uint64(100000), - maxPricePerUnit = Uint128(1000000000000), - ), + resourceBounds = resourceBounds, ), ) @@ -1365,11 +1415,9 @@ class StandardAccountTest { "revert_reason": "Placeholder revert reason." }, "execution_resources": { - "steps": 582, - "data_availability": { - "l1_gas": "123", - "l1_data_gas": "456" - } + "l1_gas": "123", + "l1_data_gas": "456", + "l2_gas": "789" } } } @@ -1465,7 +1513,8 @@ class StandardAccountTest { ], "messages": [], "execution_resources": { - "steps": 582 + "l1_gas": "123", + "l2_gas": "456" } } ], @@ -1485,15 +1534,14 @@ class StandardAccountTest { } ], "execution_resources": { - "steps": 800 + "l1_gas": "123", + "l2_gas": "789" } }, "execution_resources": { - "steps": 1600, - "data_availability": { - "l1_gas": "123", - "l1_data_gas": "456" - } + "l1_gas": "123", + "l1_data_gas": "456", + "l2_gas": "789" } } } diff --git a/lib/src/test/kotlin/starknet/crypto/FeeUtilsTest.kt b/lib/src/test/kotlin/starknet/crypto/FeeUtilsTest.kt index 83dc66ebe..40bc90982 100644 --- a/lib/src/test/kotlin/starknet/crypto/FeeUtilsTest.kt +++ b/lib/src/test/kotlin/starknet/crypto/FeeUtilsTest.kt @@ -9,11 +9,13 @@ import org.junit.jupiter.api.assertThrows class FeeUtilsTest { companion object { val estimateFee = EstimateFeeResponse( - gasConsumed = Felt(1000), - gasPrice = Felt(100), - dataGasConsumed = Felt(200), - dataGasPrice = Felt(50), - overallFee = Felt(1000 * 100 + 200 * 50), // 110000 + l1GasConsumed = Felt(1000), + l1GasPrice = Felt(100), + l1DataGasConsumed = Felt(200), + l1DataGasPrice = Felt(50), + l2GasConsumed = Felt(1000), + l2GasPrice = Felt(100), + overallFee = Felt(1000 * 100 + 200 * 50 + 1000 * 100), // 210000 feeUnit = PriceUnit.WEI, ) } @@ -24,21 +26,21 @@ class FeeUtilsTest { fun `estimate fee to max fee - default`() { val result = estimateFee.toMaxFee() - assertEquals(result, Felt(165000)) + assertEquals(result, Felt(315000)) } @Test fun `estimate fee to max fee - specific multiplier`() { val result = estimateFee.toMaxFee(1.13) - assertEquals(result, Felt(124300)) + assertEquals(result, Felt(237300)) } @Test fun `estimate fee to max fee - 1 multiplier`() { val result = estimateFee.toMaxFee(1.0) - assertEquals(result, Felt(110000)) + assertEquals(result, Felt(210000)) } @Test @@ -56,7 +58,11 @@ class FeeUtilsTest { val result = estimateFee.toResourceBounds() val expected = ResourceBoundsMapping( l1Gas = ResourceBounds( - maxAmount = Uint64(1650), + maxAmount = Uint64(3150), + maxPricePerUnit = Uint128(150), + ), + l2Gas = ResourceBounds( + maxAmount = Uint64(3150), maxPricePerUnit = Uint128(150), ), ) @@ -68,7 +74,12 @@ class FeeUtilsTest { val result = estimateFee.toResourceBounds(1.19, 1.13) val expected = ResourceBoundsMapping( l1Gas = ResourceBounds( - maxAmount = Uint64(1309), + maxAmount = Uint64(2499), + maxPricePerUnit = Uint128(113), + ), + // TODO: Check if these l2 resources need to be updated once we can add tests + l2Gas = ResourceBounds( + maxAmount = Uint64(2499), maxPricePerUnit = Uint128(113), ), ) @@ -80,8 +91,13 @@ class FeeUtilsTest { val result = estimateFee.toResourceBounds(1.0, 1.0) val expected = ResourceBoundsMapping( l1Gas = ResourceBounds( - maxAmount = Uint64(1100), - maxPricePerUnit = Uint128(100), + maxAmount = Uint64(2499), + maxPricePerUnit = Uint128(113), + ), + // TODO: Check if these l2 resources need to be updated once we can add tests + l2Gas = ResourceBounds( + maxAmount = Uint64(2499), + maxPricePerUnit = Uint128(113), ), ) assertEquals(expected, result) diff --git a/lib/src/test/kotlin/starknet/data/types/MerkleNodeTest.kt b/lib/src/test/kotlin/starknet/data/types/MerkleNodeTest.kt new file mode 100644 index 000000000..b443b5d09 --- /dev/null +++ b/lib/src/test/kotlin/starknet/data/types/MerkleNodeTest.kt @@ -0,0 +1,81 @@ +package starknet.data.types + +import com.swmansion.starknet.data.types.NodeHashToNodeMappingItem +import kotlinx.serialization.json.Json +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +internal class MerkleNodeTest { + private val json = Json { ignoreUnknownKeys = true } + + @Test + fun `binary node`() { + val jsonString = """ + { + "left": "0x123", + "right": "0x456" + } + """.trimIndent() + + val node = json.decodeFromString(NodeHashToNodeMappingItem.MerkleNode.serializer(), jsonString) + + assertTrue(node is NodeHashToNodeMappingItem.BinaryNode) + } + + @Test + fun `binary node with missing field`() { + val jsonString = """ + { + "left": "0x123" + } + """.trimIndent() + + assertThrows("Invalid MerkleNode JSON object: {\"left\":\"0x123\"}") { + json.decodeFromString(jsonString) + } + } + + @Test + fun `edge node`() { + val jsonString = """ + { + "path": "0x123", + "length": 456, + "child": "0x789" + } + """.trimIndent() + + val node = json.decodeFromString(NodeHashToNodeMappingItem.MerkleNode.serializer(), jsonString) + assertTrue(node is NodeHashToNodeMappingItem.EdgeNode) + } + + @Test + fun `edge node with missing fields`() { + val jsonString = """ + { + "path": "0x123", + "length": 456 + } + """.trimIndent() + + assertThrows("Invalid MerkleNode JSON object: {\"path\":123,\"length\":456}") { + json.decodeFromString(jsonString) + } + } + + @Test + fun `node with mixed fields`() { + val jsonString = """ + { + "path": "0x123", + "length": 20, + "right": "0x123" + } + """.trimIndent() + + assertThrows("Invalid MerkleNode JSON object: {\"path\":10,\"length\":20,\"right\":\"0x123\"}") { + json.decodeFromString(jsonString) + } + } +} diff --git a/lib/src/test/kotlin/starknet/deployercontract/StandardDeployerTest.kt b/lib/src/test/kotlin/starknet/deployercontract/StandardDeployerTest.kt index fef791471..37294cc23 100644 --- a/lib/src/test/kotlin/starknet/deployercontract/StandardDeployerTest.kt +++ b/lib/src/test/kotlin/starknet/deployercontract/StandardDeployerTest.kt @@ -102,15 +102,22 @@ object StandardDeployerTest { @Test fun testUdcDeployV3WithSpecificResourceBounds() { val initialBalance = Felt(1000) + val resourceBounds = ResourceBoundsMapping( + l1Gas = ResourceBounds( + maxAmount = Uint64(50000), + maxPricePerUnit = Uint128(100_000_000_000), + ), + l2Gas = ResourceBounds( + maxAmount = Uint64(0), + maxPricePerUnit = Uint128(0), + ), + ) val deployment = standardDeployer.deployContractV3( classHash = balanceContractClassHash, unique = true, salt = Felt(302), constructorCalldata = listOf(initialBalance), - l1ResourceBounds = ResourceBounds( - maxAmount = Uint64(50000), - maxPricePerUnit = Uint128(100_000_000_000), - ), + resourceBounds = resourceBounds, ).send() val address = standardDeployer.findContractAddress(deployment).send() @@ -157,13 +164,21 @@ object StandardDeployerTest { @Test fun testUdcDeployV3WithSpecificFeeAndDefaultParameters() { val initialBalance = Felt(1000) - val deployment = standardDeployer.deployContractV3( - classHash = balanceContractClassHash, - constructorCalldata = listOf(initialBalance), - l1ResourceBounds = ResourceBounds( + val resourceBounds = ResourceBoundsMapping( + l1Gas = ResourceBounds( maxAmount = Uint64(50000), maxPricePerUnit = Uint128(100_000_000_000), ), + // TODO: Check if these l2 resources need to be updated once we can add tests + l2Gas = ResourceBounds( + maxAmount = Uint64(0), + maxPricePerUnit = Uint128(0), + ), + ) + val deployment = standardDeployer.deployContractV3( + classHash = balanceContractClassHash, + constructorCalldata = listOf(initialBalance), + resourceBounds = resourceBounds, ).send() val address = standardDeployer.findContractAddress(deployment).send() diff --git a/lib/src/test/kotlin/starknet/provider/ProviderTest.kt b/lib/src/test/kotlin/starknet/provider/ProviderTest.kt index 2932ffc49..bd4a5b102 100644 --- a/lib/src/test/kotlin/starknet/provider/ProviderTest.kt +++ b/lib/src/test/kotlin/starknet/provider/ProviderTest.kt @@ -86,6 +86,44 @@ class ProviderTest { assertEquals(TransactionExecutionStatus.SUCCEEDED, transactionStatus.executionStatus) } + @Test + fun getMessagesStatus() { + val mockedResponse = """ + { + "id": 0, + "jsonrpc": "2.0", + "result": [ + { + "transaction_hash": "0x123", + "finality_status": "ACCEPTED_ON_L2" + }, + { + "transaction_hash": "0x123", + "finality_status": "ACCEPTED_ON_L2", + "failure_reason": "Example failure reason" + } + ] + } + """.trimIndent() + + val httpService = mock { + on { send(any()) } doReturn HttpResponse(true, 200, mockedResponse) + } + val provider = JsonRpcProvider(rpcUrl, httpService) + val request = provider.getMessagesStatus(NumAsHex(0x123)) + val response = request.send() + + assertEquals(2, response.values.count()) + + assertEquals(Felt(0x123), response.values[0].transactionHash) + assertEquals(TransactionStatus.ACCEPTED_ON_L2, response.values[0].finalityStatus) + assertNull(response.values[0].failureReason) + + assertEquals(Felt(0x123), response.values[1].transactionHash) + assertEquals(TransactionStatus.ACCEPTED_ON_L2, response.values[1].finalityStatus) + assertNotNull(response.values[0].failureReason) + } + @Test fun callContractWithBlockNumber() { val currentNumber = provider.getBlockNumber().send().value @@ -337,19 +375,9 @@ class ProviderTest { [], "execution_resources": { - "steps": "999", - "memory_holes": "1", - "range_check_builtin_applications": "21", - "pedersen_builtin_applications": "37", - "poseidon_builtin_applications": "451", - "ec_op_builtin_applications": "123", - "ecdsa_builtin_applications": "789", - "bitwise_builtin_applications": "1", - "keccak_builtin_applications": "1", - "data_availability": { - "l1_gas": "123", - "l1_data_gas": "456" - } + "l1_gas": "123", + "l1_data_gas": "456", + "l2_gas": "789" } } } @@ -406,19 +434,9 @@ class ProviderTest { "finality_status": "ACCEPTED_ON_L2", "execution_resources": { - "steps": "999", - "memory_holes": "1", - "range_check_builtin_applications": "21", - "pedersen_builtin_applications": "37", - "poseidon_builtin_applications": "451", - "ec_op_builtin_applications": "123", - "ecdsa_builtin_applications": "789", - "bitwise_builtin_applications": "1", - "keccak_builtin_applications": "1", - "data_availability": { - "l1_gas": "123", - "l1_data_gas": "456" - } + "l1_gas": "123", + "l1_data_gas": "456", + "l2_gas": "789" } } } @@ -489,19 +507,9 @@ class ProviderTest { ], "execution_resources": { - "steps": "999", - "memory_holes": "1", - "range_check_builtin_applications": "21", - "pedersen_builtin_applications": "37", - "poseidon_builtin_applications": "451", - "ec_op_builtin_applications": "123", - "ecdsa_builtin_applications": "789", - "bitwise_builtin_applications": "1", - "keccak_builtin_applications": "1", - "data_availability": { - "l1_gas": "123", - "l1_data_gas": "456" - } + "l1_gas": "123", + "l1_data_gas": "456", + "l2_gas": "789" }, "message_hash": "0x8000000000000110000000000000000000000000000000000000011111111111" } @@ -815,6 +823,86 @@ class ProviderTest { assertNotNull(response) } + @Test + fun getStorageProof() { + val mockedResponse = """ + { + "id": 0, + "jsonrpc": "2.0", + "result": { + "classes_proof": [ + { + "node_hash": "0x123", + "node": { + "left": "0x123", + "right": "0x456" + } + }, + { + "node_hash": "0x123", + "node": { + "path": 10, + "length": 20, + "child": "0x456" + } + } + ], + "contracts_proof": { + "nodes": [ + { + "node_hash": "0x789", + "node": { + "path": 3, + "length": 5, + "child": "0xabc" + } + } + ], + "contract_leaves_data": [ + { + "nonce": "0x1", + "class_hash": "0xdef" + } + ] + }, + "contracts_storage_proofs": [ + [ + { + "node_hash": "0x456", + "node": { + "left": "0xabc", + "right": "0xdef" + } + } + ] + ], + "global_roots": { + "contracts_tree_root": "0x789", + "classes_tree_root": "0xabc", + "block_hash": "0xdef" + } + } + } + """.trimIndent() + + val httpService = mock { + on { send(any()) } doReturn HttpResponse(true, 200, mockedResponse) + } + val provider = JsonRpcProvider(rpcUrl, httpService) + + val request = provider.getStorageProof( + blockId = BlockId.Number(0), + ) + val response = request.send() + + assertNotNull(response) + + assertTrue(response.classesProof[0].node is NodeHashToNodeMappingItem.BinaryNode) + assertTrue(response.classesProof[1].node is NodeHashToNodeMappingItem.EdgeNode) + assertTrue(response.contractsProof.nodes[0].node is NodeHashToNodeMappingItem.EdgeNode) + assertTrue(response.contractsStorageProofs[0][0].node is NodeHashToNodeMappingItem.BinaryNode) + } + @Test fun `get pending block with transactions`() { // TODO (#304): We should also test for 'pending' tag, but atm they are not supported in devnet @@ -831,6 +919,11 @@ class ProviderTest { "price_in_wei": "0x2137", "price_in_fri": "0x1234" }, + "l2_gas_price": + { + "price_in_wei": "0x123", + "price_in_fri": "0x456" + }, "l1_data_gas_price": { "price_in_wei": "0x789", @@ -921,6 +1014,11 @@ class ProviderTest { "price_in_wei": "0x2137", "price_in_fri": "0x1234" }, + "l2_gas_price": + { + "price_in_wei": "0x123", + "price_in_fri": "0x456" + }, "l1_data_gas_price": { "price_in_wei": "0x789", @@ -961,11 +1059,9 @@ class ProviderTest { [], "execution_resources": { - "steps": "999", - "data_availability": { - "l1_gas": "123", - "l1_data_gas": "456" - } + "l1_gas": "123", + "l1_data_gas": "456", + "l2_gas": "789" } } }, @@ -999,11 +1095,9 @@ class ProviderTest { [], "execution_resources": { - "steps": "999", - "data_availability": { - "l1_gas": "123", - "l1_data_gas": "456" - } + "l1_gas": "123", + "l1_data_gas": "456", + "l2_gas": "789" } } } @@ -1068,6 +1162,11 @@ class ProviderTest { "price_in_wei": "0x2137", "price_in_fri": "0x1234" }, + "l2_gas_price": + { + "price_in_wei": "0x123", + "price_in_fri": "0x456" + }, "l1_data_gas_price": { "price_in_wei": "0x789", diff --git a/lib/src/test/kotlin/starknet/provider/response/JsonRpcResponseTest.kt b/lib/src/test/kotlin/starknet/provider/response/JsonRpcResponseTest.kt index a0451e899..b8fec05dd 100644 --- a/lib/src/test/kotlin/starknet/provider/response/JsonRpcResponseTest.kt +++ b/lib/src/test/kotlin/starknet/provider/response/JsonRpcResponseTest.kt @@ -24,10 +24,12 @@ class JsonRpcResponseTest { "uknown_key": "value", "result": { "unknown_primitive": "value", - "gas_consumed": "0x1234", - "gas_price": "0x5678", - "data_gas_consumed": "0xabc", - "data_gas_price": "0x789", + "l1_gas_consumed": "0x1234", + "l1_gas_price": "0x5678", + "l2_gas_consumed": "0x1111", + "l2_gas_price": "0x2222", + "l1_data_gas_consumed": "0xabc", + "l1_data_gas_price": "0x789", "overall_fee": "0x9abc", "unknown_object": {"key_1": "value_1", "key_2": "value_2"}, "unit": "FRI", @@ -44,8 +46,10 @@ class JsonRpcResponseTest { val request = provider.getEstimateMessageFee(message, BlockTag.PENDING) val response = request.send() - assertEquals(Felt.fromHex("0x1234"), response.gasConsumed) - assertEquals(Felt.fromHex("0x5678"), response.gasPrice) + assertEquals(Felt.fromHex("0x1234"), response.l1GasConsumed) + assertEquals(Felt.fromHex("0x5678"), response.l1GasPrice) + assertEquals(Felt.fromHex("0x1111"), response.l2GasConsumed) + assertEquals(Felt.fromHex("0x2222"), response.l2GasPrice) assertEquals(Felt.fromHex("0x9abc"), response.overallFee) assertEquals(PriceUnit.FRI, response.feeUnit) diff --git a/lib/src/test/kotlin/starknet/signer/SignerTest.kt b/lib/src/test/kotlin/starknet/signer/SignerTest.kt index 222ca5dc5..f410bf3d7 100644 --- a/lib/src/test/kotlin/starknet/signer/SignerTest.kt +++ b/lib/src/test/kotlin/starknet/signer/SignerTest.kt @@ -41,6 +41,11 @@ internal class SignerTest { maxAmount = Uint64(20000), maxPricePerUnit = Uint128(120000000000), ), + // TODO: Check if these l2 resources need to be updated once we can add tests + l2Gas = ResourceBounds( + maxAmount = Uint64(20000), + maxPricePerUnit = Uint128(120000000000), + ), ), )