From 9b27efb6c1c2179eb130e348aaac99569565e700 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 4 Jul 2024 20:58:39 +1000 Subject: [PATCH 01/72] Move new methods to clean PR --- .../org/onflow/flow/sdk/AsyncFlowAccessApi.kt | 6 ++ .../org/onflow/flow/sdk/FlowAccessApi.kt | 7 ++ .../flow/sdk/impl/AsyncFlowAccessApiImpl.kt | 68 ++++++++++++ .../onflow/flow/sdk/impl/FlowAccessApiImpl.kt | 44 ++++++++ src/main/kotlin/org/onflow/flow/sdk/models.kt | 12 +++ .../org/onflow/flow/sdk/FlowAccessApiTest.kt | 80 ++++++++++++++ .../sdk/impl/AsyncFlowAccessApiImplTest.kt | 101 ++++++++++++++++++ .../flow/sdk/impl/FlowAccessApiImplTest.kt | 74 +++++++++++++ 8 files changed, 392 insertions(+) diff --git a/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt b/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt index eb5bd94..3276899 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt @@ -49,4 +49,10 @@ interface AsyncFlowAccessApi { fun getNetworkParameters(): CompletableFuture> fun getLatestProtocolStateSnapshot(): CompletableFuture> + + fun getTransactionsByBlockId(id: FlowId): CompletableFuture>> + + fun getTransactionResultsByBlockId(id: FlowId): CompletableFuture>> + + fun getExecutionResultByBlockId(id: FlowId): CompletableFuture> } diff --git a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt b/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt index 7dab538..84e22a2 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt @@ -53,4 +53,11 @@ interface FlowAccessApi { fun getNetworkParameters(): AccessApiCallResponse fun getLatestProtocolStateSnapshot(): AccessApiCallResponse + + fun getTransactionsByBlockId(id: FlowId): AccessApiCallResponse> + + fun getTransactionResultsByBlockId(id: FlowId): AccessApiCallResponse> + + fun getExecutionResultByBlockId(id: FlowId): AccessApiCallResponse + } diff --git a/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt b/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt index 0daadc4..748ee6e 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt @@ -513,6 +513,74 @@ class AsyncFlowAccessApiImpl( CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get latest protocol state snapshot", e)) } } + + override fun getTransactionsByBlockId(id: FlowId): CompletableFuture>> { + return try { + completableFuture( + try { + api.getTransactionsByBlockID( + Access.GetTransactionsByBlockIDRequest.newBuilder().setBlockId(id.byteStringValue).build() + ) + } catch (e: Exception) { + return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get transactions by block ID", e)) + } + ).handle { response, ex -> + if (ex != null) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get transactions by block ID", ex) + } else { + FlowAccessApi.AccessApiCallResponse.Success(response.transactionsList.map { FlowTransaction.of(it) }) + } + } + } catch (e: Exception) { + CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get transactions by block ID", e)) + } + } + + override fun getTransactionResultsByBlockId(id: FlowId): CompletableFuture>> { + return try { + completableFuture( + try { + api.getTransactionResultsByBlockID( + Access.GetTransactionsByBlockIDRequest.newBuilder().setBlockId(id.byteStringValue).build() + ) + } catch (e: Exception) { + return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get transaction results by block ID", e)) + } + ).handle { response, ex -> + if (ex != null) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get transaction results by block ID", ex) + } else { + FlowAccessApi.AccessApiCallResponse.Success(response.transactionResultsList.map { FlowTransactionResult.of(it) }) + } + } + } catch (e: Exception) { + CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get transaction results by block ID", e)) + } + } + + override fun getExecutionResultByBlockId(id: FlowId): CompletableFuture> { + return try { + completableFuture( + try { + api.getExecutionResultByID(Access.GetExecutionResultByIDRequest.newBuilder().setId(id.byteStringValue).build()) + } catch (e: Exception) { + return CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get execution result by block ID", e)) + } + ).handle { response, ex -> + if (ex != null) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get execution result by block ID", ex) + } else { + if (response.hasExecutionResult()) { + FlowAccessApi.AccessApiCallResponse.Success(ExecutionResult.of(response)) + } else { + FlowAccessApi.AccessApiCallResponse.Error("Execution result not found") + } + } + } + } catch (e: Exception) { + CompletableFuture.completedFuture(FlowAccessApi.AccessApiCallResponse.Error("Failed to get execution result by block ID", e)) + } + } } fun completableFuture(future: ListenableFuture): CompletableFuture { diff --git a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt b/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt index 2fbf415..7b0578e 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt @@ -335,4 +335,48 @@ class FlowAccessApiImpl( FlowAccessApi.AccessApiCallResponse.Error("Failed to get latest protocol state snapshot", e) } } + + + override fun getTransactionsByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse> { + return try { + val ret = api.getTransactionsByBlockID( + Access.GetTransactionsByBlockIDRequest.newBuilder() + .setBlockId(id.byteStringValue) + .build() + ) + FlowAccessApi.AccessApiCallResponse.Success(ret.transactionsList.map { FlowTransaction.of(it) }) + } catch (e: Exception) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get transactions by block ID", e) + } + } + + override fun getTransactionResultsByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse> { + return try { + val ret = api.getTransactionResultsByBlockID( + Access.GetTransactionsByBlockIDRequest.newBuilder() + .setBlockId(id.byteStringValue) + .build() + ) + FlowAccessApi.AccessApiCallResponse.Success(ret.transactionResultsList.map { FlowTransactionResult.of(it) }) + } catch (e: Exception) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get transaction results by block ID", e) + } + } + + override fun getExecutionResultByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse { + return try { + val ret = api.getExecutionResultByID( + Access.GetExecutionResultByIDRequest.newBuilder() + .setId(id.byteStringValue) + .build() + ) + if (ret.hasExecutionResult()) { + FlowAccessApi.AccessApiCallResponse.Success(ExecutionResult.of(ret)) + } else { + FlowAccessApi.AccessApiCallResponse.Error("Execution result not found") + } + } catch (e: Exception) { + FlowAccessApi.AccessApiCallResponse.Error("Failed to get execution result by block ID", e) + } + } } diff --git a/src/main/kotlin/org/onflow/flow/sdk/models.kt b/src/main/kotlin/org/onflow/flow/sdk/models.kt index 502f8a4..cdb6d54 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/models.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/models.kt @@ -675,6 +675,18 @@ data class FlowBlock( } } +data class ExecutionResult( + val id: FlowId, + val parentId: FlowId +) : Serializable { + companion object { + fun of(grpcExecutionResult: Access.ExecutionResultByIDResponse) = ExecutionResult( + id = FlowId.of(grpcExecutionResult.executionResult.blockId.toByteArray()), + parentId = FlowId.of(grpcExecutionResult.executionResult.previousResultId.toByteArray()) + ) + } +} + data class FlowCollectionGuarantee( val id: FlowId, val signatures: List diff --git a/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt b/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt index 463ef6c..463df83 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt @@ -243,4 +243,84 @@ class FlowAccessApiTest { assertEquals(FlowAccessApi.AccessApiCallResponse.Success(snapshot), result) } + + @Test + fun `Test getTransactionsByBlockId`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + val transactions = listOf(FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance())) + `when`(flowAccessApi.getTransactionsByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(transactions)) + + val result = flowAccessApi.getTransactionsByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(transactions), result) + } + + @Test + fun `Test getTransactionsByBlockId with multiple results`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + + val transaction1 = FlowTransaction(FlowScript("script1"), emptyList(), FlowId.of("01".toByteArray()), 123L, FlowTransactionProposalKey(FlowAddress("02"), 1, 123L), FlowAddress("02"), emptyList()) + + val transaction2 = FlowTransaction(FlowScript("script2"), emptyList(), FlowId.of("02".toByteArray()), 456L, FlowTransactionProposalKey(FlowAddress("03"), 2, 456L), FlowAddress("03"), emptyList()) + + val transactions = listOf(transaction1, transaction2) + + `when`(flowAccessApi.getTransactionsByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(transactions)) + + val result = flowAccessApi.getTransactionsByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(transactions), result) + + assertEquals(2, transactions.size) + assertEquals(transaction1, transactions[0]) + assertEquals(transaction2, transactions[1]) + } + + @Test + fun `Test getTransactionResultsByBlockId`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + val transactionResults = listOf(FlowTransactionResult.of(Access.TransactionResultResponse.getDefaultInstance())) + `when`(flowAccessApi.getTransactionResultsByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(transactionResults)) + + val result = flowAccessApi.getTransactionResultsByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(transactionResults), result) + } + + @Test + fun `Test getTransactionResultsByBlockId with multiple results`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + + val transactionResult1 = FlowTransactionResult(FlowTransactionStatus.SEALED, 1, "message1", emptyList()) + + val transactionResult2 = FlowTransactionResult(FlowTransactionStatus.SEALED, 2, "message2", emptyList()) + + val transactions = listOf(transactionResult1, transactionResult2) + + `when`(flowAccessApi.getTransactionResultsByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(transactions)) + + val result = flowAccessApi.getTransactionResultsByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(transactions), result) + + assertEquals(2, FlowAccessApi.AccessApiCallResponse.Success(transactions).data.size) + assertEquals(transactionResult1, FlowAccessApi.AccessApiCallResponse.Success(transactions).data[0]) + assertEquals(transactionResult2, FlowAccessApi.AccessApiCallResponse.Success(transactions).data[1]) + } + + @Test + fun `Test getExecutionResultByBlockId`() { + val flowAccessApi = mock(FlowAccessApi::class.java) + val blockId = FlowId("01") + val executionResult = ExecutionResult.of(Access.ExecutionResultByIDResponse.getDefaultInstance()) + `when`(flowAccessApi.getExecutionResultByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(executionResult)) + + val result = flowAccessApi.getExecutionResultByBlockId(blockId) + + assertEquals(FlowAccessApi.AccessApiCallResponse.Success(executionResult), result) + } } diff --git a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt b/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt index 154e7a5..4b88598 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt @@ -5,20 +5,30 @@ import com.google.common.util.concurrent.SettableFuture import com.google.protobuf.ByteString import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.mock import org.mockito.Mockito.`when` import org.onflow.flow.sdk.* import org.onflow.protobuf.access.Access import org.onflow.protobuf.access.AccessAPIGrpc +import org.onflow.protobuf.entities.ExecutionResultOuterClass import org.onflow.protobuf.entities.TransactionOuterClass import java.math.BigDecimal import java.time.LocalDateTime +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException class AsyncFlowAccessApiImplTest { private val api = mock(AccessAPIGrpc.AccessAPIFutureStub::class.java) private val asyncFlowAccessApi = AsyncFlowAccessApiImpl(api) + companion object { + val BLOCK_ID_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1) + val PARENT_ID_BYTES = byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2) + } + private fun setupFutureMock(response: T): ListenableFuture { val future: ListenableFuture = SettableFuture.create() (future as SettableFuture).set(response) @@ -305,4 +315,95 @@ class AsyncFlowAccessApiImplTest { result as FlowAccessApi.AccessApiCallResponse.Success assertEquals(mockFlowSnapshot, result.data) } + + @Test + fun `test getTransactionsByBlockId`() { + val blockId = FlowId("01") + val transactions = listOf(FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance())) + val response = Access.TransactionsResponse.newBuilder().addAllTransactions(transactions.map { it.builder().build() }).build() + `when`(api.getTransactionsByBlockID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getTransactionsByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(transactions, result.data) + } + + @Test + fun `test getTransactionsByBlockId with multiple results`() { + val blockId = FlowId("01") + val transaction1 = FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance()) + val transaction2 = FlowTransaction.of(TransactionOuterClass.Transaction.newBuilder().setReferenceBlockId(ByteString.copyFromUtf8("02")).build()) + val transactions = listOf(transaction1, transaction2) + val response = Access.TransactionsResponse.newBuilder().addAllTransactions(transactions.map { it.builder().build() }).build() + `when`(api.getTransactionsByBlockID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getTransactionsByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(2, result.data.size) + assertEquals(transaction1, result.data[0]) + assertEquals(transaction2, result.data[1]) + } + + @Test + fun `test getTransactionResultsByBlockId`() { + val blockId = FlowId("01") + val transactionResults = listOf(FlowTransactionResult.of(Access.TransactionResultResponse.getDefaultInstance())) + val response = Access.TransactionResultsResponse.newBuilder().addAllTransactionResults(transactionResults.map { it.builder().build() }).build() + `when`(api.getTransactionResultsByBlockID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getTransactionResultsByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(transactionResults, result.data) + } + + @Test + fun `test getTransactionResultsByBlockId with multiple results`() { + val blockId = FlowId("01") + val transactionResult1 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus(TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(1).setErrorMessage("message1").build()) + val transactionResult2 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus(TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(2).setErrorMessage("message2").build()) + val transactionResults = listOf(transactionResult1, transactionResult2) + val response = Access.TransactionResultsResponse.newBuilder().addAllTransactionResults(transactionResults.map { it.builder().build() }).build() + `when`(api.getTransactionResultsByBlockID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getTransactionResultsByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(2, result.data.size) + assertEquals(transactionResult1, result.data[0]) + assertEquals(transactionResult2, result.data[1]) + } + + @Test + fun `test getExecutionResultByBlockId`() { + val blockId = FlowId("01") + val executionResult = ExecutionResult(FlowId("01"), FlowId("02")) + val response = Access.ExecutionResultByIDResponse.newBuilder().setExecutionResult(ExecutionResultOuterClass.ExecutionResult.newBuilder().setBlockId(ByteString.copyFrom(BLOCK_ID_BYTES)).setPreviousResultId(ByteString.copyFrom(PARENT_ID_BYTES)).build()).build() + `when`(api.getExecutionResultByID(any())).thenReturn(setupFutureMock(response)) + + val result = asyncFlowAccessApi.getExecutionResultByBlockId(blockId).get() + assert(result is FlowAccessApi.AccessApiCallResponse.Success) + result as FlowAccessApi.AccessApiCallResponse.Success + assertEquals(executionResult, result.data) + } + + @Test + fun `test getTransactionsByBlockId timeout exception`() { + val blockId = FlowId("01") + val future: ListenableFuture = SettableFuture.create() + `when`(api.getTransactionsByBlockID(any())).thenReturn(future) + + val executor = Executors.newSingleThreadExecutor() + executor.submit { + assertThrows { + asyncFlowAccessApi.getTransactionsByBlockId(blockId).get(1, TimeUnit.SECONDS) + } + } + + executor.shutdown() + executor.awaitTermination(2, TimeUnit.SECONDS) + } + } diff --git a/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt b/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt index 04f21fb..5c89714 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt @@ -10,6 +10,7 @@ import org.mockito.ArgumentMatchers.any import org.mockito.Mockito.* import org.onflow.protobuf.access.Access import org.onflow.protobuf.access.AccessAPIGrpc +import org.onflow.protobuf.entities.ExecutionResultOuterClass import org.onflow.protobuf.entities.TransactionOuterClass import java.io.ByteArrayOutputStream import java.io.PrintStream @@ -310,6 +311,79 @@ class FlowAccessApiImplTest { assertResultSuccess(result) { assertEquals(mockFlowSnapshot, it) } } + @Test + fun `Test getTransactionsByBlockId`() { + val blockId = FlowId("01") + val transactions = listOf(FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance())) + val response = Access.TransactionsResponse.newBuilder().addAllTransactions(transactions.map { it.builder().build() }).build() + + `when`(mockApi.getTransactionsByBlockID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getTransactionsByBlockId(blockId) + assertResultSuccess(result) { assertEquals(transactions, it) } + } + + @Test + fun `Test getTransactionsByBlockId with multiple results`() { + val blockId = FlowId("01") + val transaction1 = FlowTransaction.of(TransactionOuterClass.Transaction.getDefaultInstance()) + val transaction2 = FlowTransaction.of(TransactionOuterClass.Transaction.newBuilder().setReferenceBlockId(ByteString.copyFromUtf8("02")).build()) + val transactions = listOf(transaction1, transaction2) + val response = Access.TransactionsResponse.newBuilder().addAllTransactions(transactions.map { it.builder().build() }).build() + + `when`(mockApi.getTransactionsByBlockID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getTransactionsByBlockId(blockId) + assertResultSuccess(result) { + assertEquals(2, it.size) + assertEquals(transaction1, it[0]) + assertEquals(transaction2, it[1]) + } + } + + @Test + fun `Test getTransactionResultsByBlockId`() { + val blockId = FlowId("01") + val transactionResults = listOf(FlowTransactionResult.of(Access.TransactionResultResponse.getDefaultInstance())) + val response = Access.TransactionResultsResponse.newBuilder().addAllTransactionResults(transactionResults.map { it.builder().build() }).build() + + `when`(mockApi.getTransactionResultsByBlockID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getTransactionResultsByBlockId(blockId) + assertResultSuccess(result) { assertEquals(transactionResults, it) } + } + + @Test + fun `Test getTransactionResultsByBlockId with multiple results`() { + val blockId = FlowId("01") + val transactionResult1 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus (TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(1).setErrorMessage("message1").build()) + val transactionResult2 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus(TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(2).setErrorMessage("message2").build()) + val transactionResults = listOf(transactionResult1, transactionResult2) + val response = Access.TransactionResultsResponse.newBuilder().addAllTransactionResults(transactionResults.map { it.builder().build() }).build() + + `when`(mockApi.getTransactionResultsByBlockID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getTransactionResultsByBlockId(blockId) + assertResultSuccess(result) { + assertEquals(2, it.size) + assertEquals(transactionResult1, it[0]) + assertEquals(transactionResult2, it[1]) + } + } + + @Test + fun `Test getExecutionResultByBlockId`() { + val blockId = FlowId("01") + val executionResult = ExecutionResult(FlowId("01"), FlowId("02")) + val response = Access.ExecutionResultByIDResponse.newBuilder().setExecutionResult(ExecutionResultOuterClass.ExecutionResult.newBuilder().setBlockId(blockId.byteStringValue).setPreviousResultId((FlowId("02").byteStringValue)).build()).build() + + `when`(mockApi.getExecutionResultByID(any())).thenReturn(response) + + val result = flowAccessApiImpl.getExecutionResultByBlockId(blockId) + assertResultSuccess(result) { assertEquals(executionResult, it) } + } + + private fun assertResultSuccess(result: FlowAccessApi.AccessApiCallResponse, assertions: (T) -> Unit) { when (result) { is FlowAccessApi.AccessApiCallResponse.Success -> assertions(result.data) From 5f25d22a921285bd977afb54c95e919c3ae6c99b Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 4 Jul 2024 21:04:21 +1000 Subject: [PATCH 02/72] Move new methods to clean PR --- src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt | 1 - src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt b/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt index 84e22a2..2dc7a69 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt @@ -59,5 +59,4 @@ interface FlowAccessApi { fun getTransactionResultsByBlockId(id: FlowId): AccessApiCallResponse> fun getExecutionResultByBlockId(id: FlowId): AccessApiCallResponse - } diff --git a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt b/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt index 7b0578e..9b85d3c 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt @@ -336,7 +336,6 @@ class FlowAccessApiImpl( } } - override fun getTransactionsByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse> { return try { val ret = api.getTransactionsByBlockID( From 7b686171d0b0df934e1c21bd6eee2e06fd3e7f14 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 6 Jul 2024 20:00:14 +1000 Subject: [PATCH 03/72] Add missing fields --- .../org/onflow/flow/sdk/AsyncFlowAccessApi.kt | 2 +- .../org/onflow/flow/sdk/FlowAccessApi.kt | 2 +- .../flow/sdk/impl/AsyncFlowAccessApiImpl.kt | 4 +- .../onflow/flow/sdk/impl/FlowAccessApiImpl.kt | 4 +- src/main/kotlin/org/onflow/flow/sdk/models.kt | 104 +++++++++++++++++- .../org/onflow/flow/sdk/FlowAccessApiTest.kt | 2 +- .../sdk/impl/AsyncFlowAccessApiImplTest.kt | 44 +++++++- .../flow/sdk/impl/FlowAccessApiImplTest.kt | 16 ++- 8 files changed, 156 insertions(+), 22 deletions(-) diff --git a/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt b/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt index 3276899..4b6ce90 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt @@ -54,5 +54,5 @@ interface AsyncFlowAccessApi { fun getTransactionResultsByBlockId(id: FlowId): CompletableFuture>> - fun getExecutionResultByBlockId(id: FlowId): CompletableFuture> + fun getExecutionResultByBlockId(id: FlowId): CompletableFuture> } diff --git a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt b/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt index 2dc7a69..6ffb564 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt @@ -58,5 +58,5 @@ interface FlowAccessApi { fun getTransactionResultsByBlockId(id: FlowId): AccessApiCallResponse> - fun getExecutionResultByBlockId(id: FlowId): AccessApiCallResponse + fun getExecutionResultByBlockId(id: FlowId): AccessApiCallResponse } diff --git a/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt b/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt index 748ee6e..405e3ac 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt @@ -558,7 +558,7 @@ class AsyncFlowAccessApiImpl( } } - override fun getExecutionResultByBlockId(id: FlowId): CompletableFuture> { + override fun getExecutionResultByBlockId(id: FlowId): CompletableFuture> { return try { completableFuture( try { @@ -571,7 +571,7 @@ class AsyncFlowAccessApiImpl( FlowAccessApi.AccessApiCallResponse.Error("Failed to get execution result by block ID", ex) } else { if (response.hasExecutionResult()) { - FlowAccessApi.AccessApiCallResponse.Success(ExecutionResult.of(response)) + FlowAccessApi.AccessApiCallResponse.Success(FlowExecutionResult.of(response)) } else { FlowAccessApi.AccessApiCallResponse.Error("Execution result not found") } diff --git a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt b/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt index 9b85d3c..9363142 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt @@ -362,7 +362,7 @@ class FlowAccessApiImpl( } } - override fun getExecutionResultByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse { + override fun getExecutionResultByBlockId(id: FlowId): FlowAccessApi.AccessApiCallResponse { return try { val ret = api.getExecutionResultByID( Access.GetExecutionResultByIDRequest.newBuilder() @@ -370,7 +370,7 @@ class FlowAccessApiImpl( .build() ) if (ret.hasExecutionResult()) { - FlowAccessApi.AccessApiCallResponse.Success(ExecutionResult.of(ret)) + FlowAccessApi.AccessApiCallResponse.Success(FlowExecutionResult.of(ret)) } else { FlowAccessApi.AccessApiCallResponse.Error("Execution result not found") } diff --git a/src/main/kotlin/org/onflow/flow/sdk/models.kt b/src/main/kotlin/org/onflow/flow/sdk/models.kt index cdb6d54..e135dc1 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/models.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/models.kt @@ -675,14 +675,106 @@ data class FlowBlock( } } -data class ExecutionResult( - val id: FlowId, - val parentId: FlowId +data class FlowChunk( + val collectionIndex: Int, + val startState: ByteArray, + val eventCollection: ByteArray, + val blockId: FlowId, + val totalComputationUsed: Long, + val numberOfTransactions: Int, + val index: Long, + val endState: ByteArray, + val executionDataId: FlowId, + val stateDeltaCommitment: ByteArray, +) : Serializable { + companion object { + fun of(grpcExecutionResult: ExecutionResultOuterClass.Chunk) = FlowChunk( + collectionIndex = grpcExecutionResult.collectionIndex, + startState = grpcExecutionResult.startState.toByteArray(), + eventCollection = grpcExecutionResult.eventCollection.toByteArray(), + blockId = FlowId.of(grpcExecutionResult.blockId.toByteArray()), + totalComputationUsed = grpcExecutionResult.totalComputationUsed, + numberOfTransactions = grpcExecutionResult.numberOfTransactions, + index = grpcExecutionResult.index, + endState = grpcExecutionResult.endState.toByteArray(), + executionDataId = FlowId.of(grpcExecutionResult.executionDataId.toByteArray()), + stateDeltaCommitment = grpcExecutionResult.stateDeltaCommitment.toByteArray() + ) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FlowChunk) return false + + if (collectionIndex != other.collectionIndex) return false + if (!startState.contentEquals(other.startState)) return false + if (!eventCollection.contentEquals(other.eventCollection)) return false + if (blockId != other.blockId) return false + if (totalComputationUsed != other.totalComputationUsed) return false + if (numberOfTransactions != other.numberOfTransactions) return false + if (index != other.index) return false + if (!endState.contentEquals(other.endState)) return false + if (executionDataId != other.executionDataId) return false + if (!stateDeltaCommitment.contentEquals(other.stateDeltaCommitment)) return false + + return true + } + + override fun hashCode(): Int { + var result = collectionIndex + result = 31 * result + startState.contentHashCode() + result = 31 * result + eventCollection.contentHashCode() + result = 31 * result + blockId.hashCode() + result = 31 * result + totalComputationUsed.hashCode() + result = 31 * result + numberOfTransactions + result = 31 * result + index.hashCode() + result = 31 * result + endState.contentHashCode() + result = 31 * result + executionDataId.hashCode() + result = 31 * result + stateDeltaCommitment.contentHashCode() + return result + } +} + +data class FlowServiceEvent( + val type: String, + val payload: ByteArray, +) : Serializable { + companion object { + fun of(grpcExecutionResult: ExecutionResultOuterClass.ServiceEvent) = FlowServiceEvent( + type = grpcExecutionResult.type, + payload = grpcExecutionResult.payload.toByteArray(), + ) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is FlowServiceEvent) return false + + if (type != other.type) return false + if (!payload.contentEquals(other.payload)) return false + + return true + } + + override fun hashCode(): Int { + var result = type.hashCode() + result = 31 * result + payload.contentHashCode() + return result + } +} + +data class FlowExecutionResult( + val blockId: FlowId, + val previousResultId: FlowId, + val chunks: List, + val serviceEvents: List, ) : Serializable { companion object { - fun of(grpcExecutionResult: Access.ExecutionResultByIDResponse) = ExecutionResult( - id = FlowId.of(grpcExecutionResult.executionResult.blockId.toByteArray()), - parentId = FlowId.of(grpcExecutionResult.executionResult.previousResultId.toByteArray()) + fun of(grpcExecutionResult: Access.ExecutionResultByIDResponse) = FlowExecutionResult( + blockId = FlowId.of(grpcExecutionResult.executionResult.blockId.toByteArray()), + previousResultId = FlowId.of(grpcExecutionResult.executionResult.previousResultId.toByteArray()), + chunks = grpcExecutionResult.executionResult.chunksList.map { FlowChunk.of(it) }, + serviceEvents = grpcExecutionResult.executionResult.serviceEventsList.map { FlowServiceEvent.of(it) }, ) } } diff --git a/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt b/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt index 463df83..4e37df7 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt @@ -316,7 +316,7 @@ class FlowAccessApiTest { fun `Test getExecutionResultByBlockId`() { val flowAccessApi = mock(FlowAccessApi::class.java) val blockId = FlowId("01") - val executionResult = ExecutionResult.of(Access.ExecutionResultByIDResponse.getDefaultInstance()) + val executionResult = FlowExecutionResult.of(Access.ExecutionResultByIDResponse.getDefaultInstance()) `when`(flowAccessApi.getExecutionResultByBlockId(blockId)).thenReturn(FlowAccessApi.AccessApiCallResponse.Success(executionResult)) val result = flowAccessApi.getExecutionResultByBlockId(blockId) diff --git a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt b/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt index 4b88598..2a5a5c8 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt @@ -179,7 +179,7 @@ class AsyncFlowAccessApiImplTest { val accountResponse = Access.GetAccountResponse.newBuilder().setAccount(flowAccount.builder().build()).build() `when`(api.getAccount(any())).thenReturn(setupFutureMock(accountResponse)) - val result = asyncFlowAccessApi.getAccountByAddress(flowAddress).get() + val result = asyncFlowAccessApi.getAccountAtLatestBlock(flowAddress).get() assert(result is FlowAccessApi.AccessApiCallResponse.Success) result as FlowAccessApi.AccessApiCallResponse.Success assertEquals(flowAccount.address, result.data.address) @@ -379,8 +379,45 @@ class AsyncFlowAccessApiImplTest { @Test fun `test getExecutionResultByBlockId`() { val blockId = FlowId("01") - val executionResult = ExecutionResult(FlowId("01"), FlowId("02")) - val response = Access.ExecutionResultByIDResponse.newBuilder().setExecutionResult(ExecutionResultOuterClass.ExecutionResult.newBuilder().setBlockId(ByteString.copyFrom(BLOCK_ID_BYTES)).setPreviousResultId(ByteString.copyFrom(PARENT_ID_BYTES)).build()).build() + + val chunks = listOf(FlowChunk(collectionIndex = 1, startState = ByteArray(0), eventCollection = ByteArray(0), blockId = FlowId("01"), totalComputationUsed = 1000L, numberOfTransactions = 10, index = 1L, endState = ByteArray(0), executionDataId = FlowId("02"), stateDeltaCommitment = ByteArray(0))) + + val serviceEvents = listOf(FlowServiceEvent(type = "ServiceEventType", payload = ByteArray(0))) + + val executionResult = FlowExecutionResult(blockId = FlowId("01"), previousResultId = FlowId("02"), chunks = chunks, serviceEvents = serviceEvents) + + val grpcChunks = chunks.map { + ExecutionResultOuterClass.Chunk.newBuilder() + .setCollectionIndex(it.collectionIndex) + .setStartState(ByteString.copyFrom(it.startState)) + .setEventCollection(ByteString.copyFrom(it.eventCollection)) + .setBlockId(ByteString.copyFrom(it.blockId.bytes)) + .setTotalComputationUsed(it.totalComputationUsed) + .setNumberOfTransactions(it.numberOfTransactions) + .setIndex(it.index) + .setEndState(ByteString.copyFrom(it.endState)) + .setExecutionDataId(ByteString.copyFrom(it.executionDataId.bytes)) + .setStateDeltaCommitment(ByteString.copyFrom(it.stateDeltaCommitment)) + .build() + } + + val grpcServiceEvents = serviceEvents.map { + ExecutionResultOuterClass.ServiceEvent.newBuilder() + .setType(it.type) + .setPayload(ByteString.copyFrom(it.payload)) + .build() + } + + val response = Access.ExecutionResultByIDResponse.newBuilder() + .setExecutionResult( + ExecutionResultOuterClass.ExecutionResult.newBuilder() + .setBlockId(ByteString.copyFrom(BLOCK_ID_BYTES)) + .setPreviousResultId(ByteString.copyFrom(PARENT_ID_BYTES)) + .addAllChunks(grpcChunks) + .addAllServiceEvents(grpcServiceEvents) + .build() + ).build() + `when`(api.getExecutionResultByID(any())).thenReturn(setupFutureMock(response)) val result = asyncFlowAccessApi.getExecutionResultByBlockId(blockId).get() @@ -405,5 +442,4 @@ class AsyncFlowAccessApiImplTest { executor.shutdown() executor.awaitTermination(2, TimeUnit.SECONDS) } - } diff --git a/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt b/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt index 5c89714..1a2b2ef 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt @@ -356,7 +356,7 @@ class FlowAccessApiImplTest { @Test fun `Test getTransactionResultsByBlockId with multiple results`() { val blockId = FlowId("01") - val transactionResult1 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus (TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(1).setErrorMessage("message1").build()) + val transactionResult1 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus(TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(1).setErrorMessage("message1").build()) val transactionResult2 = FlowTransactionResult.of(Access.TransactionResultResponse.newBuilder().setStatus(TransactionOuterClass.TransactionStatus.SEALED).setStatusCode(2).setErrorMessage("message2").build()) val transactionResults = listOf(transactionResult1, transactionResult2) val response = Access.TransactionResultsResponse.newBuilder().addAllTransactionResults(transactionResults.map { it.builder().build() }).build() @@ -374,16 +374,22 @@ class FlowAccessApiImplTest { @Test fun `Test getExecutionResultByBlockId`() { val blockId = FlowId("01") - val executionResult = ExecutionResult(FlowId("01"), FlowId("02")) - val response = Access.ExecutionResultByIDResponse.newBuilder().setExecutionResult(ExecutionResultOuterClass.ExecutionResult.newBuilder().setBlockId(blockId.byteStringValue).setPreviousResultId((FlowId("02").byteStringValue)).build()).build() + val grpcExecutionResult = ExecutionResultOuterClass.ExecutionResult.newBuilder() + .setBlockId(ByteString.copyFromUtf8("01")) + .setPreviousResultId(ByteString.copyFromUtf8("02")) + .addChunks(ExecutionResultOuterClass.Chunk.newBuilder().build()) + .addServiceEvents(ExecutionResultOuterClass.ServiceEvent.newBuilder().build()) + .build() + val response = Access.ExecutionResultByIDResponse.newBuilder().setExecutionResult(grpcExecutionResult).build() `when`(mockApi.getExecutionResultByID(any())).thenReturn(response) val result = flowAccessApiImpl.getExecutionResultByBlockId(blockId) - assertResultSuccess(result) { assertEquals(executionResult, it) } + assertResultSuccess(result) { + assertEquals(FlowExecutionResult.of(response), it) + } } - private fun assertResultSuccess(result: FlowAccessApi.AccessApiCallResponse, assertions: (T) -> Unit) { when (result) { is FlowAccessApi.AccessApiCallResponse.Success -> assertions(result.data) From b6f17f4c76f1fc80a0613e462f8b661a15248f77 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 6 Jul 2024 20:09:02 +1000 Subject: [PATCH 04/72] Add missing fields --- .../org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt b/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt index 2a5a5c8..5645677 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt @@ -179,7 +179,7 @@ class AsyncFlowAccessApiImplTest { val accountResponse = Access.GetAccountResponse.newBuilder().setAccount(flowAccount.builder().build()).build() `when`(api.getAccount(any())).thenReturn(setupFutureMock(accountResponse)) - val result = asyncFlowAccessApi.getAccountAtLatestBlock(flowAddress).get() + val result = asyncFlowAccessApi.getAccountByAddress(flowAddress).get() assert(result is FlowAccessApi.AccessApiCallResponse.Success) result as FlowAccessApi.AccessApiCallResponse.Success assertEquals(flowAccount.address, result.data.address) From b59c1dcb99f63caf1c5afcda0b2c211080b643d4 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 11 Jul 2024 01:53:10 +1000 Subject: [PATCH 05/72] Setting up multi-module project structure --- .gitignore | 2 +- README.md | 4 +- kotlin-example/build.gradle.kts | 51 +++++++ kotlin-example/flow.json | 31 ++++ kotlin-example/src/main/flow.json | 13 ++ .../kotlin/org/onflow/examples/kotlin/App.kt | 142 ++++++++++++++++++ .../src/main/resources/create_account.cdc | 6 + .../src/main/resources/transfer_flow.cdc | 29 ++++ .../org/onflow/examples/kotlin/AppTest.kt | 56 +++++++ build.gradle.kts => sdk/build.gradle.kts | 0 {flow => sdk/flow}/NonFungibleToken.cdc | 0 {flow => sdk/flow}/flow.json | 0 .../flow/sdk/ExposeAccountKeyIssueTest.kt | 0 .../onflow/flow/sdk/IntegrationTestUtils.kt | 0 .../flow/sdk/cadence/JsonCadenceTest.kt | 0 .../extensions/ProjectTestExtensionsTest.kt | 0 .../flow/sdk/extensions/TestExtensionsTest.kt | 0 .../transaction/TransactionCreationTest.kt | 0 .../transaction/TransactionDecodingTest.kt | 0 .../transaction/TransactionIntegrationTest.kt | 0 .../sdk/transaction/TransactionSigningTest.kt | 0 .../resources/cadence/NothingContract.cdc | 0 .../org/onflow/flow/sdk/AddressRegistry.kt | 0 .../org/onflow/flow/sdk/AsyncFlowAccessApi.kt | 0 .../main/kotlin/org/onflow/flow/sdk/Flow.kt | 0 .../org/onflow/flow/sdk/FlowAccessApi.kt | 0 .../org/onflow/flow/sdk/FlowException.kt | 0 .../sdk/cadence/json-cadence-marshalling.kt | 0 .../onflow/flow/sdk/cadence/json-cadence.kt | 0 .../org/onflow/flow/sdk/crypto/Crypto.kt | 0 .../kotlin/org/onflow/flow/sdk/domain-tags.kt | 0 .../main/kotlin/org/onflow/flow/sdk/errors.kt | 0 .../kotlin/org/onflow/flow/sdk/extensions.kt | 0 .../flow/sdk/impl/AsyncFlowAccessApiImpl.kt | 0 .../onflow/flow/sdk/impl/FlowAccessApiImpl.kt | 0 .../main/kotlin/org/onflow/flow/sdk/models.kt | 0 .../kotlin/org/onflow/flow/sdk/script-dsl.kt | 0 .../org/onflow/flow/sdk/transaction-dsl.kt | 0 .../onflow/flow/sdk/AddressRegistryTest.kt | 0 .../org/onflow/flow/sdk/DomainTagTest.kt | 0 .../org/onflow/flow/sdk/FlowAccessApiTest.kt | 0 .../kotlin/org/onflow/flow/sdk/ScriptTest.kt | 0 .../kotlin/org/onflow/flow/sdk/TestUtils.kt | 0 ...JsonCadenceBuilderTypeSerializationTest.kt | 0 .../JsonCadenceBuilderAddressFieldTest.kt | 0 .../JsonCadenceBuilderArrayFieldTest.kt | 0 .../JsonCadenceBuilderBooleanFieldTest.kt | 0 .../fields/JsonCadenceBuilderFieldTest.kt | 0 .../JsonCadenceBuilderOptionalFieldTest.kt | 0 .../JsonCadenceBuilderStringFieldTest.kt | 0 .../fields/JsonCadenceBuilderVoidFieldTest.kt | 0 ...sonCadenceBuilderCompositeAttributeTest.kt | 0 .../JsonCadenceBuilderCompositeValueTest.kt | 0 .../JsonCadenceBuilderContractFieldTest.kt | 0 .../JsonCadenceBuilderEnumFieldTest.kt | 0 .../JsonCadenceBuilderEventFieldTest.kt | 0 .../JsonCadenceBuilderResourceFieldTest.kt | 0 .../JsonCadenceBuilderStructFieldTest.kt | 0 .../JsonCadenceBuilderNumberFieldTest.kt | 0 ...JsonCadenceBuilderUFix64NumberFieldTest.kt | 0 ...JsonCadenceBuilderUInt64NumberFieldTest.kt | 0 .../JsonCadenceBuilderUInt8NumberFieldTest.kt | 0 .../path/JsonCadenceBuilderPathFieldTest.kt | 0 .../path/JsonCadenceBuilderPathValueTest.kt | 0 .../org/onflow/flow/sdk/crypto/CryptoTest.kt | 0 .../sdk/impl/AsyncFlowAccessApiImplTest.kt | 0 .../flow/sdk/impl/FlowAccessApiImplTest.kt | 0 .../flow/sdk/models/FlowAccountKeyTest.kt | 0 .../onflow/flow/sdk/models/FlowAccountTest.kt | 0 .../onflow/flow/sdk/models/FlowAddressTest.kt | 0 .../flow/sdk/models/FlowArgumentTest.kt | 0 .../onflow/flow/sdk/models/FlowBlockTest.kt | 0 .../onflow/flow/sdk/models/FlowCodeTest.kt | 0 .../sdk/models/FlowCollectionGuaranteeTest.kt | 0 .../flow/sdk/models/FlowCollectionTest.kt | 0 .../flow/sdk/models/FlowEventPayloadTest.kt | 0 .../onflow/flow/sdk/models/FlowEventTest.kt | 0 .../org/onflow/flow/sdk/models/FlowIdTest.kt | 0 .../flow/sdk/models/FlowPublicKeyTest.kt | 0 .../flow/sdk/models/FlowScriptResponseTest.kt | 0 .../onflow/flow/sdk/models/FlowScriptTest.kt | 0 .../flow/sdk/models/FlowSignatureTest.kt | 0 .../models/FlowTransactionProposalKeyTest.kt | 0 .../sdk/models/FlowTransactionResultTest.kt | 0 .../models/FlowTransactionSignatureTest.kt | 0 .../sdk/models/FlowTransactionStatusTest.kt | 0 .../flow/sdk/models/FlowTransactionTest.kt | 0 .../flow/sdk/models/HashAlgorithmTest.kt | 0 .../flow/sdk/models/SignatureAlgorithmTest.kt | 0 .../org/onflow/flow/sdk/models/SignerTest.kt | 0 .../sdk/test/AbstractFlowEmulatorExtension.kt | 0 .../sdk/test/FlowEmulatorProjectExtension.kt | 0 .../sdk/test/FlowEmulatorTestExtension.kt | 0 .../org/onflow/flow/sdk/test/FlowTestUtil.kt | 0 settings.gradle.kts | 1 + 95 files changed, 332 insertions(+), 3 deletions(-) create mode 100644 kotlin-example/build.gradle.kts create mode 100644 kotlin-example/flow.json create mode 100644 kotlin-example/src/main/flow.json create mode 100644 kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt create mode 100644 kotlin-example/src/main/resources/create_account.cdc create mode 100644 kotlin-example/src/main/resources/transfer_flow.cdc create mode 100644 kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt rename build.gradle.kts => sdk/build.gradle.kts (100%) rename {flow => sdk/flow}/NonFungibleToken.cdc (100%) rename {flow => sdk/flow}/flow.json (100%) rename {src => sdk/src}/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt (100%) rename {src => sdk/src}/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt (100%) rename {src => sdk/src}/intTest/org/onflow/flow/sdk/cadence/JsonCadenceTest.kt (100%) rename {src => sdk/src}/intTest/org/onflow/flow/sdk/extensions/ProjectTestExtensionsTest.kt (100%) rename {src => sdk/src}/intTest/org/onflow/flow/sdk/extensions/TestExtensionsTest.kt (100%) rename {src => sdk/src}/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt (100%) rename {src => sdk/src}/intTest/org/onflow/flow/sdk/transaction/TransactionDecodingTest.kt (100%) rename {src => sdk/src}/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt (100%) rename {src => sdk/src}/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt (100%) rename {src => sdk/src}/intTest/resources/cadence/NothingContract.cdc (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/AddressRegistry.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/Flow.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/FlowException.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/cadence/json-cadence-marshalling.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/cadence/json-cadence.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/crypto/Crypto.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/domain-tags.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/errors.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/extensions.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/models.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/script-dsl.kt (100%) rename {src => sdk/src}/main/kotlin/org/onflow/flow/sdk/transaction-dsl.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/AddressRegistryTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/DomainTagTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/TestUtils.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/JsonCadenceBuilderTypeSerializationTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderAddressFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderArrayFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderBooleanFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderOptionalFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderStringFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderVoidFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderCompositeAttributeTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderCompositeValueTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderContractFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderEnumFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderEventFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderResourceFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderStructFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderNumberFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUFix64NumberFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUInt64NumberFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUInt8NumberFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/path/JsonCadenceBuilderPathFieldTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/cadence/fields/path/JsonCadenceBuilderPathValueTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowAccountKeyTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowAccountTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowAddressTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowArgumentTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowBlockTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowCodeTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowCollectionGuaranteeTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowCollectionTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowEventPayloadTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowEventTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowIdTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowPublicKeyTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowScriptResponseTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowScriptTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowSignatureTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionProposalKeyTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionResultTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionSignatureTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionStatusTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/HashAlgorithmTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/SignatureAlgorithmTest.kt (100%) rename {src => sdk/src}/test/kotlin/org/onflow/flow/sdk/models/SignerTest.kt (100%) rename {src => sdk/src}/testFixtures/kotlin/org/onflow/flow/sdk/test/AbstractFlowEmulatorExtension.kt (100%) rename {src => sdk/src}/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowEmulatorProjectExtension.kt (100%) rename {src => sdk/src}/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowEmulatorTestExtension.kt (100%) rename {src => sdk/src}/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt (100%) diff --git a/.gitignore b/.gitignore index 9ef8199..5926953 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ build .gradle .idea -gradle.properties +sdk/gradle.properties diff --git a/README.md b/README.md index 3da24cf..0d728ff 100644 --- a/README.md +++ b/README.md @@ -167,8 +167,8 @@ Also, the following annotations are available in tests as helpers: - `@FlowTestAccount` - used to automatically create an account in the emulator and inject a `TestAccount` instance containing the new account's credentials. -See [ProjectTestExtensionsTest](src/test/kotlin/com/nftco/flow/sdk/ProjectTestExtensionsTest.kt) and -[TestExtensionsTest](src/test/kotlin/com/nftco/flow/sdk/TestExtensionsTest.kt) for examples. +See [ProjectTestExtensionsTest](sdk/src/test/kotlin/com/nftco/flow/sdk/ProjectTestExtensionsTest.kt) and +[TestExtensionsTest](sdk/src/test/kotlin/com/nftco/flow/sdk/TestExtensionsTest.kt) for examples. ## Contribute to this SDK diff --git a/kotlin-example/build.gradle.kts b/kotlin-example/build.gradle.kts new file mode 100644 index 0000000..494e1d3 --- /dev/null +++ b/kotlin-example/build.gradle.kts @@ -0,0 +1,51 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + kotlin("jvm") version "1.9.22" + java + application +} + +val FLOW_JVM_SDK_VERSION = "1.0.1" +val USE_KOTLIN_APP = project.findProperty("USE_KOTLIN_APP") == "true" + +tasks.withType { + sourceCompatibility = JavaVersion.VERSION_21.toString() + targetCompatibility = JavaVersion.VERSION_21.toString() +} + +tasks.withType { + kotlinOptions.apply { + jvmTarget = JavaVersion.VERSION_21.toString() + freeCompilerArgs = listOf("-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn") + } +} + +repositories { + mavenCentral() + mavenLocal() + maven { url = uri("https://jitpack.io") } + maven { url = uri("https://dl.bintray.com/ethereum/maven/") } +} + +dependencies { + implementation("org.onflow:flow-jvm-sdk:$FLOW_JVM_SDK_VERSION") + + // Use JUnit Jupiter Engine for testing. + testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0") + testImplementation("org.junit.jupiter:junit-jupiter-engine:5.7.0") +} + +application { + // Define the main class for the application. + mainClass.set(if (USE_KOTLIN_APP) { + "org.onflow.examples.kotlin.App" + } else { + "org.onflow.examples.java.App" + }) +} + +tasks.test { + // Use junit platform for unit tests. + useJUnitPlatform() +} diff --git a/kotlin-example/flow.json b/kotlin-example/flow.json new file mode 100644 index 0000000..45f4c45 --- /dev/null +++ b/kotlin-example/flow.json @@ -0,0 +1,31 @@ +{ + "emulators": { + "default": { + "port": 3569, + "serviceAccount": "emulator-account" + } + }, + "contracts": {}, + "networks": { + "emulator": { + "host": "127.0.0.1:3569", + "chain": "flow-emulator" + }, + "mainnet": { + "host": "access.mainnet.nodes.onflow.org:9000", + "chain": "flow-mainnet" + }, + "testnet": { + "host": "access.devnet.nodes.onflow.org:9000", + "chain": "flow-testnet" + } + }, + "accounts": { + "emulator-account": { + "address": "f8d6e0586b0a20c7", + "keys": "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17", + "chain": "flow-emulator" + } + }, + "deployments": {} +} diff --git a/kotlin-example/src/main/flow.json b/kotlin-example/src/main/flow.json new file mode 100644 index 0000000..c331b96 --- /dev/null +++ b/kotlin-example/src/main/flow.json @@ -0,0 +1,13 @@ +{ + "networks": { + "emulator": "127.0.0.1:3569", + "mainnet": "access.mainnet.nodes.onflow.org:9000", + "testnet": "access.devnet.nodes.onflow.org:9000" + }, + "accounts": { + "emulator-account": { + "address": "f8d6e0586b0a20c7", + "key": "8d76d7fc7cc0ff807cc56c935955ba88071a5181fbee186c704c14eb534093c4" + } + } +} \ No newline at end of file diff --git a/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt new file mode 100644 index 0000000..c82bad0 --- /dev/null +++ b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt @@ -0,0 +1,142 @@ +package org.onflow.examples.kotlin + +import org.onflow.flow.sdk.* +import org.onflow.flow.sdk.cadence.AddressField +import org.onflow.flow.sdk.cadence.StringField +import org.onflow.flow.sdk.cadence.UFix64NumberField +import org.onflow.flow.sdk.crypto.Crypto +import java.math.BigDecimal + +internal class App(host: String, port: Int, privateKeyHex: String) { + + private val accessAPI = Flow.newAccessApi(host, port) + private val privateKey = Crypto.decodePrivateKey(privateKeyHex) + + private val latestBlockID: FlowId get() = accessAPI.getLatestBlockHeader().id + + private fun getAccount(address: FlowAddress): FlowAccount = accessAPI.getAccountAtLatestBlock(address)!! + + fun getAccountBalance(address: FlowAddress): BigDecimal { + val account = getAccount(address) + return account.balance + } + + private fun getAccountKey(address: FlowAddress, keyIndex: Int): FlowAccountKey { + val account = getAccount(address) + return account.keys[keyIndex] + } + + private fun getTransactionResult(txID: FlowId): FlowTransactionResult { + val txResult = accessAPI.getTransactionResultById(txID)!! + if (txResult.errorMessage.isNotEmpty()) { + throw Exception(txResult.errorMessage) + } + return txResult + } + + private fun waitForSeal(txID: FlowId): FlowTransactionResult { + var txResult: FlowTransactionResult + while (true) { + txResult = getTransactionResult(txID) + if (txResult.status == FlowTransactionStatus.SEALED) { + return txResult + } + Thread.sleep(1000) + } + } + + private fun getAccountCreatedAddress(txResult: FlowTransactionResult): FlowAddress { + val addressHex = txResult + .events[0] + .event + .value!! + .fields[0] + .value + .value as String + return FlowAddress(addressHex.substring(2)) + } + + private fun loadScript(name: String): ByteArray + = javaClass.classLoader.getResourceAsStream(name)!!.use { it.readAllBytes() } + + fun createAccount(payerAddress: FlowAddress, publicKeyHex: String): FlowAddress { + + // find payer account + val payerAccountKey = getAccountKey(payerAddress, 0) + + // create a public key for the new account + val newAccountPublicKey = FlowAccountKey( + publicKey = FlowPublicKey(publicKeyHex), + signAlgo = SignatureAlgorithm.ECDSA_P256, + hashAlgo = HashAlgorithm.SHA3_256, + weight = 1000 + ) + + // create transaction + var tx = FlowTransaction( + script = FlowScript(loadScript("create_account.cdc")), + arguments = listOf( + FlowArgument(StringField(newAccountPublicKey.encoded.bytesToHex())) + ), + referenceBlockId = latestBlockID, + gasLimit = 100, + proposalKey = FlowTransactionProposalKey( + address = payerAddress, + keyIndex = payerAccountKey.id, + sequenceNumber = payerAccountKey.sequenceNumber.toLong() + ), + payerAddress = payerAddress, + authorizers = listOf( + payerAddress + ) + ) + + // sign the transaction + val signer = Crypto.getSigner(privateKey, payerAccountKey.hashAlgo) + tx = tx.addEnvelopeSignature(payerAddress, payerAccountKey.id, signer) + + // send the transaction + val txID = accessAPI.sendTransaction(tx) + + // wait for transaction to be sealed + val txResult = waitForSeal(txID) + return getAccountCreatedAddress(txResult) + } + + fun transferTokens(senderAddress: FlowAddress, recipientAddress: FlowAddress, amount: BigDecimal) { + if (amount.scale() != 8) { + throw Exception("FLOW amount must have exactly 8 decimal places of precision (e.g. 10.00000000)") + } + val senderAccountKey = getAccountKey(senderAddress, 0) + + // create transaction + var tx = FlowTransaction( + script = FlowScript(loadScript("transfer_flow.cdc")), + arguments = listOf( + FlowArgument(UFix64NumberField(amount.toPlainString())), + FlowArgument(AddressField(recipientAddress.base16Value)) + ), + referenceBlockId = latestBlockID, + gasLimit = 100, + proposalKey = FlowTransactionProposalKey( + address = senderAddress, + keyIndex = senderAccountKey.id, + sequenceNumber = senderAccountKey.sequenceNumber.toLong() + ), + payerAddress = senderAddress, + authorizers = listOf( + senderAddress + ) + ) + + // sign the transaction + val signer = Crypto.getSigner(privateKey, senderAccountKey.hashAlgo) + tx = tx.addEnvelopeSignature(senderAddress, senderAccountKey.id, signer) + + // send the transaction + val txID = accessAPI.sendTransaction(tx) + + // wait for transaction to be sealed + waitForSeal(txID) + } +} diff --git a/kotlin-example/src/main/resources/create_account.cdc b/kotlin-example/src/main/resources/create_account.cdc new file mode 100644 index 0000000..d05afdc --- /dev/null +++ b/kotlin-example/src/main/resources/create_account.cdc @@ -0,0 +1,6 @@ +transaction(publicKey: String) { + prepare(signer: AuthAccount) { + let account = AuthAccount(payer: signer) + account.addPublicKey(publicKey.decodeHex()) + } +} diff --git a/kotlin-example/src/main/resources/transfer_flow.cdc b/kotlin-example/src/main/resources/transfer_flow.cdc new file mode 100644 index 0000000..a94a2fb --- /dev/null +++ b/kotlin-example/src/main/resources/transfer_flow.cdc @@ -0,0 +1,29 @@ +import FungibleToken from 0xee82856bf20e2aa6 +import FlowToken from 0x0ae53cb6e3f42a79 + +transaction(amount: UFix64, to: Address) { + + // The Vault resource that holds the tokens that are being transferred + let sentVault: @FungibleToken.Vault + + prepare(signer: AuthAccount) { + // Get a reference to the signer's stored vault + let vaultRef = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) + ?? panic("Could not borrow reference to the owner's Vault!") + + // Withdraw tokens from the signer's stored vault + self.sentVault <- vaultRef.withdraw(amount: amount) + } + + execute { + + // Get a reference to the recipient's Receiver + let receiverRef = getAccount(to) + .getCapability(/public/flowTokenReceiver) + .borrow<&{FungibleToken.Receiver}>() + ?? panic("Could not borrow receiver reference to the recipient's Vault") + + // Deposit the withdrawn tokens in the recipient's receiver + receiverRef.deposit(from: <-self.sentVault) + } +} diff --git a/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt new file mode 100644 index 0000000..7bed493 --- /dev/null +++ b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt @@ -0,0 +1,56 @@ +package org.onflow.examples.kotlin + +import java.math.BigDecimal +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.onflow.flow.sdk.FlowAddress +import org.onflow.flow.sdk.crypto.Crypto + + +val serviceAccountAddress: FlowAddress = FlowAddress("f8d6e0586b0a20c7") +const val servicePrivateKeyHex = "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17" + +internal class AppTest { + + private var userPrivateKeyHex: String = "" + private var userPublicKeyHex: String = "" + + @BeforeEach + fun setupUser() { + val keyPair = Crypto.generateKeyPair() + userPrivateKeyHex = keyPair.private.hex + userPublicKeyHex = keyPair.public.hex + } + + @Test + fun `Can create an account`() { + val app = App("localhost", 3569, servicePrivateKeyHex) + + // service account address + app.createAccount(serviceAccountAddress, userPublicKeyHex) + } + + @Test + fun `Can transfer tokens`() { + val app = App("localhost", 3569, servicePrivateKeyHex) + + // service account address + val recipient: FlowAddress = app.createAccount(serviceAccountAddress, userPublicKeyHex) + + // FLOW amounts always have 8 decimal places + val amount = BigDecimal("10.00000001") + val balance1 = app.getAccountBalance(recipient) + app.transferTokens(serviceAccountAddress, recipient, amount) + val balance2 = app.getAccountBalance(recipient) + Assertions.assertEquals(balance1.add(amount), balance2) + } + + @Test + fun `Can get an account balance`() { + val app = App("localhost", 3569, servicePrivateKeyHex) + val balance = app.getAccountBalance(serviceAccountAddress) + println(balance) + } + +} diff --git a/build.gradle.kts b/sdk/build.gradle.kts similarity index 100% rename from build.gradle.kts rename to sdk/build.gradle.kts diff --git a/flow/NonFungibleToken.cdc b/sdk/flow/NonFungibleToken.cdc similarity index 100% rename from flow/NonFungibleToken.cdc rename to sdk/flow/NonFungibleToken.cdc diff --git a/flow/flow.json b/sdk/flow/flow.json similarity index 100% rename from flow/flow.json rename to sdk/flow/flow.json diff --git a/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt similarity index 100% rename from src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt rename to sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt diff --git a/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt b/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt similarity index 100% rename from src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt rename to sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt diff --git a/src/intTest/org/onflow/flow/sdk/cadence/JsonCadenceTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/cadence/JsonCadenceTest.kt similarity index 100% rename from src/intTest/org/onflow/flow/sdk/cadence/JsonCadenceTest.kt rename to sdk/src/intTest/org/onflow/flow/sdk/cadence/JsonCadenceTest.kt diff --git a/src/intTest/org/onflow/flow/sdk/extensions/ProjectTestExtensionsTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/extensions/ProjectTestExtensionsTest.kt similarity index 100% rename from src/intTest/org/onflow/flow/sdk/extensions/ProjectTestExtensionsTest.kt rename to sdk/src/intTest/org/onflow/flow/sdk/extensions/ProjectTestExtensionsTest.kt diff --git a/src/intTest/org/onflow/flow/sdk/extensions/TestExtensionsTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/extensions/TestExtensionsTest.kt similarity index 100% rename from src/intTest/org/onflow/flow/sdk/extensions/TestExtensionsTest.kt rename to sdk/src/intTest/org/onflow/flow/sdk/extensions/TestExtensionsTest.kt diff --git a/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt similarity index 100% rename from src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt rename to sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt diff --git a/src/intTest/org/onflow/flow/sdk/transaction/TransactionDecodingTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionDecodingTest.kt similarity index 100% rename from src/intTest/org/onflow/flow/sdk/transaction/TransactionDecodingTest.kt rename to sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionDecodingTest.kt diff --git a/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt similarity index 100% rename from src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt rename to sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt diff --git a/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt similarity index 100% rename from src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt rename to sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt diff --git a/src/intTest/resources/cadence/NothingContract.cdc b/sdk/src/intTest/resources/cadence/NothingContract.cdc similarity index 100% rename from src/intTest/resources/cadence/NothingContract.cdc rename to sdk/src/intTest/resources/cadence/NothingContract.cdc diff --git a/src/main/kotlin/org/onflow/flow/sdk/AddressRegistry.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/AddressRegistry.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/AddressRegistry.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/AddressRegistry.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/AsyncFlowAccessApi.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/Flow.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/Flow.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/Flow.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/Flow.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/FlowAccessApi.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/FlowException.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/FlowException.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/FlowException.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/FlowException.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/cadence/json-cadence-marshalling.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/cadence/json-cadence-marshalling.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/cadence/json-cadence-marshalling.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/cadence/json-cadence-marshalling.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/cadence/json-cadence.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/cadence/json-cadence.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/cadence/json-cadence.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/cadence/json-cadence.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/crypto/Crypto.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/crypto/Crypto.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/crypto/Crypto.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/crypto/Crypto.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/domain-tags.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/domain-tags.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/domain-tags.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/domain-tags.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/errors.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/errors.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/errors.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/errors.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/extensions.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/extensions.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/extensions.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/extensions.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImpl.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImpl.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/models.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/models.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/models.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/script-dsl.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/script-dsl.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/script-dsl.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/script-dsl.kt diff --git a/src/main/kotlin/org/onflow/flow/sdk/transaction-dsl.kt b/sdk/src/main/kotlin/org/onflow/flow/sdk/transaction-dsl.kt similarity index 100% rename from src/main/kotlin/org/onflow/flow/sdk/transaction-dsl.kt rename to sdk/src/main/kotlin/org/onflow/flow/sdk/transaction-dsl.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/AddressRegistryTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/AddressRegistryTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/AddressRegistryTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/AddressRegistryTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/DomainTagTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/DomainTagTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/DomainTagTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/DomainTagTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/FlowAccessApiTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/TestUtils.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/TestUtils.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/TestUtils.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/TestUtils.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/JsonCadenceBuilderTypeSerializationTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/JsonCadenceBuilderTypeSerializationTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/JsonCadenceBuilderTypeSerializationTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/JsonCadenceBuilderTypeSerializationTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderAddressFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderAddressFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderAddressFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderAddressFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderArrayFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderArrayFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderArrayFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderArrayFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderBooleanFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderBooleanFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderBooleanFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderBooleanFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderOptionalFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderOptionalFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderOptionalFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderOptionalFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderStringFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderStringFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderStringFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderStringFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderVoidFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderVoidFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderVoidFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/JsonCadenceBuilderVoidFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderCompositeAttributeTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderCompositeAttributeTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderCompositeAttributeTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderCompositeAttributeTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderCompositeValueTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderCompositeValueTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderCompositeValueTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderCompositeValueTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderContractFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderContractFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderContractFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderContractFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderEnumFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderEnumFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderEnumFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderEnumFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderEventFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderEventFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderEventFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderEventFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderResourceFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderResourceFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderResourceFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderResourceFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderStructFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderStructFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderStructFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/composite/JsonCadenceBuilderStructFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderNumberFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderNumberFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderNumberFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderNumberFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUFix64NumberFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUFix64NumberFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUFix64NumberFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUFix64NumberFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUInt64NumberFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUInt64NumberFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUInt64NumberFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUInt64NumberFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUInt8NumberFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUInt8NumberFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUInt8NumberFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/number/JsonCadenceBuilderUInt8NumberFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/path/JsonCadenceBuilderPathFieldTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/path/JsonCadenceBuilderPathFieldTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/path/JsonCadenceBuilderPathFieldTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/path/JsonCadenceBuilderPathFieldTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/path/JsonCadenceBuilderPathValueTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/path/JsonCadenceBuilderPathValueTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/cadence/fields/path/JsonCadenceBuilderPathValueTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/fields/path/JsonCadenceBuilderPathValueTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/impl/AsyncFlowAccessApiImplTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/impl/FlowAccessApiImplTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowAccountKeyTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAccountKeyTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowAccountKeyTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAccountKeyTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowAccountTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAccountTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowAccountTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAccountTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowAddressTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAddressTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowAddressTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowAddressTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowArgumentTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowArgumentTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowArgumentTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowArgumentTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowBlockTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowCodeTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowCodeTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowCodeTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowCodeTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowCollectionGuaranteeTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowCollectionGuaranteeTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowCollectionGuaranteeTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowCollectionGuaranteeTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowCollectionTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowCollectionTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowCollectionTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowCollectionTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowEventPayloadTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowEventPayloadTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowEventPayloadTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowEventPayloadTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowEventTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowEventTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowEventTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowEventTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowIdTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowIdTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowIdTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowIdTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowPublicKeyTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowPublicKeyTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowPublicKeyTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowPublicKeyTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowScriptResponseTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowScriptResponseTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowScriptResponseTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowScriptResponseTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowScriptTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowScriptTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowScriptTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowScriptTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowSignatureTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowSignatureTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowSignatureTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowSignatureTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionProposalKeyTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionProposalKeyTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionProposalKeyTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionProposalKeyTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionResultTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionResultTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionResultTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionResultTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionSignatureTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionSignatureTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionSignatureTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionSignatureTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionStatusTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionStatusTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionStatusTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionStatusTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/FlowTransactionTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/HashAlgorithmTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/HashAlgorithmTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/HashAlgorithmTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/HashAlgorithmTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/SignatureAlgorithmTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/SignatureAlgorithmTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/SignatureAlgorithmTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/SignatureAlgorithmTest.kt diff --git a/src/test/kotlin/org/onflow/flow/sdk/models/SignerTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/models/SignerTest.kt similarity index 100% rename from src/test/kotlin/org/onflow/flow/sdk/models/SignerTest.kt rename to sdk/src/test/kotlin/org/onflow/flow/sdk/models/SignerTest.kt diff --git a/src/testFixtures/kotlin/org/onflow/flow/sdk/test/AbstractFlowEmulatorExtension.kt b/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/AbstractFlowEmulatorExtension.kt similarity index 100% rename from src/testFixtures/kotlin/org/onflow/flow/sdk/test/AbstractFlowEmulatorExtension.kt rename to sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/AbstractFlowEmulatorExtension.kt diff --git a/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowEmulatorProjectExtension.kt b/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowEmulatorProjectExtension.kt similarity index 100% rename from src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowEmulatorProjectExtension.kt rename to sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowEmulatorProjectExtension.kt diff --git a/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowEmulatorTestExtension.kt b/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowEmulatorTestExtension.kt similarity index 100% rename from src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowEmulatorTestExtension.kt rename to sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowEmulatorTestExtension.kt diff --git a/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt b/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt similarity index 100% rename from src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt rename to sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt diff --git a/settings.gradle.kts b/settings.gradle.kts index d3c7737..20da3ab 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,3 +14,4 @@ pluginManagement { } rootProject.name="flow-jvm-sdk" +include("sdk", "kotlin-example") From cadd0d65fa096eb945c5acfef4376807b32a65e0 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 11 Jul 2024 02:10:44 +1000 Subject: [PATCH 06/72] Setting up multi-module project structure --- .../workflows/ci-examples-pull-request.yml | 39 ++++++++++ ....yml => ci-sdk-manual-publish-release.yml} | 0 ...yml => ci-sdk-manual-publish-snapshot.yml} | 0 ...release.yml => ci-sdk-publish-release.yml} | 0 ...apshot.yml => ci-sdk-publish-snapshot.yml} | 0 ...ll-request.yml => ci-sdk-pull-request.yml} | 0 build.gradle.kts | 74 +++++++++++++++++++ kotlin-example/build.gradle.kts | 14 +++- sdk/build.gradle.kts | 53 ++----------- 9 files changed, 130 insertions(+), 50 deletions(-) create mode 100644 .github/workflows/ci-examples-pull-request.yml rename .github/workflows/{ci-manual-publish-release.yml => ci-sdk-manual-publish-release.yml} (100%) rename .github/workflows/{ci-manual-publish-snapshot.yml => ci-sdk-manual-publish-snapshot.yml} (100%) rename .github/workflows/{ci-publish-release.yml => ci-sdk-publish-release.yml} (100%) rename .github/workflows/{ci-publish-snapshot.yml => ci-sdk-publish-snapshot.yml} (100%) rename .github/workflows/{ci-pull-request.yml => ci-sdk-pull-request.yml} (100%) create mode 100644 build.gradle.kts diff --git a/.github/workflows/ci-examples-pull-request.yml b/.github/workflows/ci-examples-pull-request.yml new file mode 100644 index 0000000..b4a4afd --- /dev/null +++ b/.github/workflows/ci-examples-pull-request.yml @@ -0,0 +1,39 @@ +name: Build Pull Request +on: pull_request + +jobs: + build: + name: Build pull request + runs-on: ubuntu-latest + env: + JAVA_OPTS: -Xmx2g -Dorg.gradle.daemon=false + #services: + # flow-emulator: + # image: gcr.io/flow-container-registry/emulator + # env: + # FLOW_VERBOSE: true + # FLOW_PORT: 3569 + # FLOW_INTERVAL: 5s + # FLOW_PERSIST: false + # ports: + # - 3569:3569 + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Java + uses: actions/setup-java@v2 + with: + java-version: '21' + java-package: jdk + distribution: 'adopt' + + - name: Install flow emulator + run: sh -ci "$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)" + + - name: Make gradle executable + run: chmod +x ./gradlew + + - name: Build + id: build + run: ./gradlew --warning-mode all check build -x test diff --git a/.github/workflows/ci-manual-publish-release.yml b/.github/workflows/ci-sdk-manual-publish-release.yml similarity index 100% rename from .github/workflows/ci-manual-publish-release.yml rename to .github/workflows/ci-sdk-manual-publish-release.yml diff --git a/.github/workflows/ci-manual-publish-snapshot.yml b/.github/workflows/ci-sdk-manual-publish-snapshot.yml similarity index 100% rename from .github/workflows/ci-manual-publish-snapshot.yml rename to .github/workflows/ci-sdk-manual-publish-snapshot.yml diff --git a/.github/workflows/ci-publish-release.yml b/.github/workflows/ci-sdk-publish-release.yml similarity index 100% rename from .github/workflows/ci-publish-release.yml rename to .github/workflows/ci-sdk-publish-release.yml diff --git a/.github/workflows/ci-publish-snapshot.yml b/.github/workflows/ci-sdk-publish-snapshot.yml similarity index 100% rename from .github/workflows/ci-publish-snapshot.yml rename to .github/workflows/ci-sdk-publish-snapshot.yml diff --git a/.github/workflows/ci-pull-request.yml b/.github/workflows/ci-sdk-pull-request.yml similarity index 100% rename from .github/workflows/ci-pull-request.yml rename to .github/workflows/ci-sdk-pull-request.yml diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..74a5898 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,74 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +// Configuration variables +val defaultGroupId = "org.onflow" +val defaultVersion = "1.0.1" + +// Helper function to get properties +fun getProp(name: String, defaultValue: String? = null): String? { + return project.findProperty("flow.$name")?.toString()?.trim()?.ifBlank { null } + ?: project.findProperty(name)?.toString()?.trim()?.ifBlank { null } + ?: defaultValue +} + +plugins { + kotlin("jvm") version "1.9.22" apply false + id("org.jetbrains.dokka") version "1.9.10" apply false + id("org.jmailen.kotlinter") version "4.2.0" apply false + id("kotlinx-serialization") version "1.8.0" apply false + id("com.vanniktech.maven.publish") version "0.28.0" apply false +} + +buildscript { + repositories { + google() + mavenCentral() + } + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.22") + } +} + +allprojects { + repositories { + google() + mavenCentral() + maven { url = uri("https://jitpack.io") } + maven { url = uri("https://dl.bintray.com/ethereum/maven/") } + } +} + +subprojects { + apply(plugin = "org.jetbrains.kotlin.jvm") + apply(plugin = "org.jetbrains.dokka") + apply(plugin = "org.jmailen.kotlinter") + apply(plugin = "kotlinx-serialization") + apply(plugin = "com.vanniktech.maven.publish") + + group = getProp("groupId", defaultGroupId)!! + version = when { + getProp("version") !in setOf("unspecified", null) -> getProp("version")!! + getProp("snapshotDate") != null -> "${defaultVersion.replace("-SNAPSHOT", "")}.${getProp("snapshotDate")!!}-SNAPSHOT" + else -> defaultVersion + } + + tasks.withType { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_20.toString() + freeCompilerArgs = listOf("-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn") + } + } + + dependencies { + "api"("org.jetbrains.kotlin:kotlin-reflect:1.9.22") + "dokkaHtmlPlugin"("org.jetbrains.dokka:kotlin-as-java-plugin:1.9.10") + } + + tasks.named("compileTestKotlin") { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_20.toString() + freeCompilerArgs = listOf("-Xjvm-default=all", "-opt-in=com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview") + allWarningsAsErrors = false + } + } +} diff --git a/kotlin-example/build.gradle.kts b/kotlin-example/build.gradle.kts index 494e1d3..89ca5c2 100644 --- a/kotlin-example/build.gradle.kts +++ b/kotlin-example/build.gradle.kts @@ -1,13 +1,19 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - kotlin("jvm") version "1.9.22" java application } -val FLOW_JVM_SDK_VERSION = "1.0.1" -val USE_KOTLIN_APP = project.findProperty("USE_KOTLIN_APP") == "true" +// Helper function to get properties +fun getProp(name: String, defaultValue: String? = null): String? { + return project.findProperty("flow.$name")?.toString()?.trim()?.ifBlank { null } + ?: project.findProperty(name)?.toString()?.trim()?.ifBlank { null } + ?: defaultValue +} + +val FLOW_JVM_SDK_VERSION = "1.0.1" +val USE_KOTLIN_APP = project.findProperty("USE_KOTLIN_APP") == "true" tasks.withType { sourceCompatibility = JavaVersion.VERSION_21.toString() @@ -15,7 +21,7 @@ tasks.withType { } tasks.withType { - kotlinOptions.apply { + kotlinOptions { jvmTarget = JavaVersion.VERSION_21.toString() freeCompilerArgs = listOf("-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn") } diff --git a/sdk/build.gradle.kts b/sdk/build.gradle.kts index adff76c..7bd5951 100644 --- a/sdk/build.gradle.kts +++ b/sdk/build.gradle.kts @@ -1,43 +1,25 @@ import com.vanniktech.maven.publish.SonatypeHost import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -// configuration variables -val defaultGroupId = "org.onflow" -val defaultVersion = "1.0.1" - -// other variables - -fun getProp(name: String, defaultValue: String? = null): String? { - return project.findProperty("flow.$name")?.toString()?.trim()?.ifBlank { null } - ?: project.findProperty(name)?.toString()?.trim()?.ifBlank { null } - ?: defaultValue -} - -group = getProp("groupId", defaultGroupId)!! -version = when { - getProp("version") !in setOf("unspecified", null) -> { getProp("version")!! } - getProp("snapshotDate") != null -> { "${defaultVersion.replace("-SNAPSHOT", "")}.${getProp("snapshotDate")!!}-SNAPSHOT" } - else -> { defaultVersion } -} - plugins { - id("org.jetbrains.dokka") version "1.9.10" - kotlin("jvm") version "1.9.22" idea jacoco signing `java-library` `java-test-fixtures` `maven-publish` - id ("com.vanniktech.maven.publish") version "0.28.0" - id("org.jmailen.kotlinter") version "4.2.0" - id("kotlinx-serialization") version "1.8.0" +} + +// Helper function to get properties +fun getProp(name: String, defaultValue: String? = null): String? { + return project.findProperty("flow.$name")?.toString()?.trim()?.ifBlank { null } + ?: project.findProperty(name)?.toString()?.trim()?.ifBlank { null } + ?: defaultValue } repositories { gradlePluginPortal() mavenCentral() - maven { url = uri("https://jitpack.io") } } dependencies { @@ -45,13 +27,9 @@ dependencies { dokkaHtmlPlugin("org.jetbrains.dokka:kotlin-as-java-plugin:1.9.10") api("org.onflow:flow:1.0.0") - api("com.github.TrustedDataFramework:java-rlp:1.1.20") - api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1") - api("org.bouncycastle:bcpkix-jdk18on:1.76") - api(platform("com.fasterxml.jackson:jackson-bom:2.16.1")) api("com.fasterxml.jackson.core:jackson-core") api("com.fasterxml.jackson.module:jackson-module-kotlin") @@ -64,21 +42,6 @@ dependencies { testFixturesImplementation("org.mockito:mockito-inline:3.11.2") } -tasks.withType { - kotlinOptions.apply { - jvmTarget = JavaVersion.VERSION_20.toString() - freeCompilerArgs = listOf("-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn") - } -} - -tasks.named("compileTestKotlin") { - kotlinOptions.apply { - jvmTarget = JavaVersion.VERSION_20.toString() - freeCompilerArgs = listOf("-Xjvm-default=all", "-opt-in=com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview") - allWarningsAsErrors = false - } -} - sourceSets { create("intTest") { compileClasspath += sourceSets.main.get().output @@ -123,7 +86,6 @@ java { } tasks { - test { useJUnitPlatform() testLogging { @@ -209,4 +171,3 @@ signing { } sign(publishing.publications) } - From 36d195459456b600d9e40257dbfd94ae1c77468f Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 12 Jul 2024 21:15:01 +1000 Subject: [PATCH 07/72] Updates to GH actions workflows --- .github/workflows/ci-examples-pull-request.yml | 4 ++-- .github/workflows/ci-sdk-manual-publish-release.yml | 5 +++-- .github/workflows/ci-sdk-manual-publish-snapshot.yml | 5 +++-- .github/workflows/ci-sdk-publish-release.yml | 7 +++++-- .github/workflows/ci-sdk-publish-snapshot.yml | 7 +++++-- .github/workflows/ci-sdk-pull-request.yml | 6 ++++-- 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci-examples-pull-request.yml b/.github/workflows/ci-examples-pull-request.yml index b4a4afd..a3f08c9 100644 --- a/.github/workflows/ci-examples-pull-request.yml +++ b/.github/workflows/ci-examples-pull-request.yml @@ -32,8 +32,8 @@ jobs: run: sh -ci "$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)" - name: Make gradle executable - run: chmod +x ./gradlew + run: chmod +x ./kotlin-example/gradlew - name: Build id: build - run: ./gradlew --warning-mode all check build -x test + run: cd kotlin-example && ./gradlew --warning-mode all check build -x test diff --git a/.github/workflows/ci-sdk-manual-publish-release.yml b/.github/workflows/ci-sdk-manual-publish-release.yml index 2aadc4d..53ed349 100644 --- a/.github/workflows/ci-sdk-manual-publish-release.yml +++ b/.github/workflows/ci-sdk-manual-publish-release.yml @@ -38,11 +38,11 @@ jobs: run: sh -ci "$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)" - name: Make gradle executable - run: chmod +x ./gradlew + run: chmod +x ./sdk/gradlew - name: Build id: build - run: ./gradlew --warning-mode all check build -x test -x integrationTest + run: cd sdk && ./gradlew --warning-mode all check build -x test -x integrationTest - name: Publish release env: @@ -53,6 +53,7 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.FLOW_JVM_SDK_SIGNING_PASSWORD }} run: | + cd sdk if [[ "${{ secrets.FLOW_JVM_SDK_CICD_PUBLISH_ENABLED }}" != "true" ]]; then exit 0; diff --git a/.github/workflows/ci-sdk-manual-publish-snapshot.yml b/.github/workflows/ci-sdk-manual-publish-snapshot.yml index eb99d07..05b997e 100644 --- a/.github/workflows/ci-sdk-manual-publish-snapshot.yml +++ b/.github/workflows/ci-sdk-manual-publish-snapshot.yml @@ -38,11 +38,11 @@ jobs: run: sh -ci "$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)" - name: Make gradle executable - run: chmod +x ./gradlew + run: chmod +x ./sdk/gradlew - name: Build id: build - run: ./gradlew --warning-mode all check build -x test -x integrationTest + run: cd sdk && && ./gradlew --warning-mode all check build -x test -x integrationTest - name: Publish snapshot env: @@ -53,6 +53,7 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.FLOW_JVM_SDK_SIGNING_PASSWORD }} run: | + cd sdk if [[ "${{ secrets.FLOW_JVM_SDK_CICD_PUBLISH_ENABLED }}" != "true" ]]; then exit 0; diff --git a/.github/workflows/ci-sdk-publish-release.yml b/.github/workflows/ci-sdk-publish-release.yml index dec4b6f..46970a7 100644 --- a/.github/workflows/ci-sdk-publish-release.yml +++ b/.github/workflows/ci-sdk-publish-release.yml @@ -37,16 +37,17 @@ jobs: run: sh -ci "$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)" - name: Make gradle executable - run: chmod +x ./gradlew + run: chmod +x ./sdk/gradlew - name: Build id: build - run: ./gradlew --warning-mode all check build -x test -x integrationTest + run: cd sdk && ./gradlew --warning-mode all check build -x test -x integrationTest - name: Run Unit Tests id: unit_test if: ${{ steps.build.outcome == 'success' }} run: | + cd sdk export PATH="$HOME/.local/bin:$PATH" ./gradlew --no-daemon --max-workers=2 --warning-mode=all test --continue --stacktrace @@ -62,6 +63,7 @@ jobs: - name: Run Integration Tests if: ${{ steps.unit_test.outcome == 'success' }} run: | + cd sdk export PATH="$HOME/.local/bin:$PATH" ./gradlew --no-daemon --max-workers=2 --warning-mode=all integrationTest --continue --stacktrace @@ -83,6 +85,7 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.FLOW_JVM_SDK_SIGNING_PASSWORD }} run: | + cd sdk if [[ "${{ secrets.FLOW_JVM_SDK_CICD_PUBLISH_ENABLED }}" != "true" ]]; then exit 0; diff --git a/.github/workflows/ci-sdk-publish-snapshot.yml b/.github/workflows/ci-sdk-publish-snapshot.yml index d5daa7a..4c32a04 100644 --- a/.github/workflows/ci-sdk-publish-snapshot.yml +++ b/.github/workflows/ci-sdk-publish-snapshot.yml @@ -38,16 +38,17 @@ jobs: run: sh -ci "$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)" - name: Make gradle executable - run: chmod +x ./gradlew + run: chmod +x ./sdk/gradlew - name: Build id: build - run: ./gradlew --warning-mode all check build -x test -x integrationTest + run: cd sdk && ./gradlew --warning-mode all check build -x test -x integrationTest - name: Run Unit Tests id: unit_test if: ${{ steps.build.outcome == 'success' }} run: | + cd sdk export PATH="$HOME/.local/bin:$PATH" ./gradlew --no-daemon --max-workers=2 --warning-mode=all test --continue --stacktrace @@ -63,6 +64,7 @@ jobs: - name: Run Integration Tests if: ${{ steps.unit_test.outcome == 'success' }} run: | + cd sdk export PATH="$HOME/.local/bin:$PATH" ./gradlew --no-daemon --max-workers=2 --warning-mode=all integrationTest --continue --stacktrace @@ -84,6 +86,7 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.FLOW_JVM_SDK_SIGNING_PASSWORD }} run: | + cd sdk if [[ "${{ secrets.FLOW_JVM_SDK_CICD_PUBLISH_ENABLED }}" != "true" ]]; then exit 0; diff --git a/.github/workflows/ci-sdk-pull-request.yml b/.github/workflows/ci-sdk-pull-request.yml index 5384424..3676e73 100644 --- a/.github/workflows/ci-sdk-pull-request.yml +++ b/.github/workflows/ci-sdk-pull-request.yml @@ -32,16 +32,17 @@ jobs: run: sh -ci "$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)" - name: Make gradle executable - run: chmod +x ./gradlew + run: chmod +x ./sdk/gradlew - name: Build id: build - run: ./gradlew --warning-mode all check build -x test -x integrationTest + run: cd sdk && ./gradlew --warning-mode all check build -x test -x integrationTest - name: Run Unit Tests id: unit_test if: ${{ steps.build.outcome == 'success' }} run: | + cd sdk export PATH="$HOME/.local/bin:$PATH" ./gradlew --no-daemon --max-workers=2 --warning-mode=all test --continue --stacktrace @@ -57,6 +58,7 @@ jobs: - name: Run Integration Tests if: ${{ steps.unit_test.outcome == 'success' }} run: | + cd sdk export PATH="$HOME/.local/bin:$PATH" ./gradlew --no-daemon --max-workers=2 --warning-mode=all integrationTest --continue --stacktrace From 3e3e9a5333fbac93bf0d6ae66c9a210fc6a24c24 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 12 Jul 2024 21:26:15 +1000 Subject: [PATCH 08/72] Linting kotlin-example --- NOTICE | 2 +- kotlin-example/README.md | 19 +++++++++++++++++++ .../kotlin/org/onflow/examples/kotlin/App.kt | 9 +++------ .../org/onflow/examples/kotlin/AppTest.kt | 11 ++++------- sdk/README.md | 1 + RELEASES.md => sdk/RELEASES.md | 0 6 files changed, 28 insertions(+), 14 deletions(-) create mode 100644 kotlin-example/README.md create mode 100644 sdk/README.md rename RELEASES.md => sdk/RELEASES.md (100%) diff --git a/NOTICE b/NOTICE index d248f18..6b31661 100644 --- a/NOTICE +++ b/NOTICE @@ -1,4 +1,4 @@ Flow JVM SDK -Copyright 2019-2021 Dapper Labs, Inc. +Copyright 2019-2024 Dapper Labs, Inc. This product includes software developed at Dapper Labs, Inc. (https://www.dapperlabs.com/). diff --git a/kotlin-example/README.md b/kotlin-example/README.md new file mode 100644 index 0000000..d2112ae --- /dev/null +++ b/kotlin-example/README.md @@ -0,0 +1,19 @@ +# Java Flow Client Example + +Example Java application using the [Flow JVM SDK](https://github.com/onflow/flow-jvm-sdk) to interact with Flow blockchain. + +## Usage + +First, install the [Flow CLI](https://docs.onflow.org/flow-cli). + +Start the [Flow Emulator](https://docs.onflow.org/emulator) in the main directory of this repository: + +```sh +flow emulator start -v +``` + +Then, in a separate terminal, run the application with Gradle: + +```sh +./gradlew test -i +``` diff --git a/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt index c82bad0..a0e2bcb 100644 --- a/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt +++ b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt @@ -8,7 +8,6 @@ import org.onflow.flow.sdk.crypto.Crypto import java.math.BigDecimal internal class App(host: String, port: Int, privateKeyHex: String) { - private val accessAPI = Flow.newAccessApi(host, port) private val privateKey = Crypto.decodePrivateKey(privateKeyHex) @@ -56,11 +55,9 @@ internal class App(host: String, port: Int, privateKeyHex: String) { return FlowAddress(addressHex.substring(2)) } - private fun loadScript(name: String): ByteArray - = javaClass.classLoader.getResourceAsStream(name)!!.use { it.readAllBytes() } + private fun loadScript(name: String): ByteArray = javaClass.classLoader.getResourceAsStream(name)!!.use { it.readAllBytes() } fun createAccount(payerAddress: FlowAddress, publicKeyHex: String): FlowAddress { - // find payer account val payerAccountKey = getAccountKey(payerAddress, 0) @@ -93,7 +90,7 @@ internal class App(host: String, port: Int, privateKeyHex: String) { // sign the transaction val signer = Crypto.getSigner(privateKey, payerAccountKey.hashAlgo) - tx = tx.addEnvelopeSignature(payerAddress, payerAccountKey.id, signer) + tx = tx.addEnvelopeSignature(payerAddress, payerAccountKey.id, signer) // send the transaction val txID = accessAPI.sendTransaction(tx) @@ -131,7 +128,7 @@ internal class App(host: String, port: Int, privateKeyHex: String) { // sign the transaction val signer = Crypto.getSigner(privateKey, senderAccountKey.hashAlgo) - tx = tx.addEnvelopeSignature(senderAddress, senderAccountKey.id, signer) + tx = tx.addEnvelopeSignature(senderAddress, senderAccountKey.id, signer) // send the transaction val txID = accessAPI.sendTransaction(tx) diff --git a/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt index 7bed493..927bf08 100644 --- a/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt +++ b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt @@ -7,20 +7,18 @@ import org.junit.jupiter.api.Test import org.onflow.flow.sdk.FlowAddress import org.onflow.flow.sdk.crypto.Crypto - val serviceAccountAddress: FlowAddress = FlowAddress("f8d6e0586b0a20c7") -const val servicePrivateKeyHex = "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17" +const val servicePrivateKeyHex = "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17" internal class AppTest { - private var userPrivateKeyHex: String = "" private var userPublicKeyHex: String = "" @BeforeEach - fun setupUser() { + fun setupUser() { val keyPair = Crypto.generateKeyPair() - userPrivateKeyHex = keyPair.private.hex - userPublicKeyHex = keyPair.public.hex + userPrivateKeyHex = keyPair.private.hex + userPublicKeyHex = keyPair.public.hex } @Test @@ -52,5 +50,4 @@ internal class AppTest { val balance = app.getAccountBalance(serviceAccountAddress) println(balance) } - } diff --git a/sdk/README.md b/sdk/README.md new file mode 100644 index 0000000..1bc8871 --- /dev/null +++ b/sdk/README.md @@ -0,0 +1 @@ +# to-do : add SDK-specific README \ No newline at end of file diff --git a/RELEASES.md b/sdk/RELEASES.md similarity index 100% rename from RELEASES.md rename to sdk/RELEASES.md From 56bc4bc40738a9f55dd58f0054dfdedc8e1cec4f Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 12 Jul 2024 21:28:15 +1000 Subject: [PATCH 09/72] Debugging GH actions --- .github/workflows/ci-sdk-pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-sdk-pull-request.yml b/.github/workflows/ci-sdk-pull-request.yml index 3676e73..670c8e7 100644 --- a/.github/workflows/ci-sdk-pull-request.yml +++ b/.github/workflows/ci-sdk-pull-request.yml @@ -32,7 +32,7 @@ jobs: run: sh -ci "$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)" - name: Make gradle executable - run: chmod +x ./sdk/gradlew + run: chmod +x ./gradlew - name: Build id: build From e15455af9f9ff471e194f9cfb0ad95cd2f5f52c3 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 12 Jul 2024 21:33:26 +1000 Subject: [PATCH 10/72] Debugging GH actions --- .github/workflows/ci-sdk-pull-request.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-sdk-pull-request.yml b/.github/workflows/ci-sdk-pull-request.yml index 670c8e7..ac20ed5 100644 --- a/.github/workflows/ci-sdk-pull-request.yml +++ b/.github/workflows/ci-sdk-pull-request.yml @@ -36,15 +36,14 @@ jobs: - name: Build id: build - run: cd sdk && ./gradlew --warning-mode all check build -x test -x integrationTest + run: ./gradlew --warning-mode all check build -x test -x integrationTest - name: Run Unit Tests id: unit_test if: ${{ steps.build.outcome == 'success' }} run: | - cd sdk export PATH="$HOME/.local/bin:$PATH" - ./gradlew --no-daemon --max-workers=2 --warning-mode=all test --continue --stacktrace + ./gradlew --no-daemon --max-workers=2 --warning-mode=all :sdk:test --continue --stacktrace - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v1.18 @@ -58,9 +57,8 @@ jobs: - name: Run Integration Tests if: ${{ steps.unit_test.outcome == 'success' }} run: | - cd sdk export PATH="$HOME/.local/bin:$PATH" - ./gradlew --no-daemon --max-workers=2 --warning-mode=all integrationTest --continue --stacktrace + ./gradlew --no-daemon --max-workers=2 --warning-mode=all :sdk:integrationTest --continue --stacktrace - name: Publish Integration Test Results uses: EnricoMi/publish-unit-test-result-action@v1.18 From 9073a9ff5d6e4616ebdf6a33743d67e474e819c2 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 15:20:41 +1000 Subject: [PATCH 11/72] Debugging GH actions --- ...equest.yml => ci-kotlin-examples-pull-request.yml} | 6 +++--- .github/workflows/ci-sdk-manual-publish-release.yml | 5 ++--- .github/workflows/ci-sdk-manual-publish-snapshot.yml | 5 ++--- .github/workflows/ci-sdk-publish-release.yml | 11 ++++------- .github/workflows/ci-sdk-publish-snapshot.yml | 11 ++++------- .github/workflows/ci-sdk-pull-request.yml | 2 +- 6 files changed, 16 insertions(+), 24 deletions(-) rename .github/workflows/{ci-examples-pull-request.yml => ci-kotlin-examples-pull-request.yml} (84%) diff --git a/.github/workflows/ci-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml similarity index 84% rename from .github/workflows/ci-examples-pull-request.yml rename to .github/workflows/ci-kotlin-examples-pull-request.yml index a3f08c9..bb0f2fc 100644 --- a/.github/workflows/ci-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -1,4 +1,4 @@ -name: Build Pull Request +name: Build Kotlin Examples Pull Request on: pull_request jobs: @@ -32,8 +32,8 @@ jobs: run: sh -ci "$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)" - name: Make gradle executable - run: chmod +x ./kotlin-example/gradlew + run: chmod +x ./gradlew - name: Build id: build - run: cd kotlin-example && ./gradlew --warning-mode all check build -x test + run: ./gradlew --warning-mode all check build -x test diff --git a/.github/workflows/ci-sdk-manual-publish-release.yml b/.github/workflows/ci-sdk-manual-publish-release.yml index 53ed349..d83d775 100644 --- a/.github/workflows/ci-sdk-manual-publish-release.yml +++ b/.github/workflows/ci-sdk-manual-publish-release.yml @@ -42,7 +42,7 @@ jobs: - name: Build id: build - run: cd sdk && ./gradlew --warning-mode all check build -x test -x integrationTest + run: ./gradlew --warning-mode all check build -x test -x integrationTest - name: Publish release env: @@ -53,7 +53,6 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.FLOW_JVM_SDK_SIGNING_PASSWORD }} run: | - cd sdk if [[ "${{ secrets.FLOW_JVM_SDK_CICD_PUBLISH_ENABLED }}" != "true" ]]; then exit 0; @@ -61,4 +60,4 @@ jobs: ./gradlew \ -Psigning.key="${{ secrets.FLOW_JVM_SDK_SIGNING_KEY }}" \ -Psigning.password="${{ secrets.FLOW_JVM_SDK_SIGNING_PASSWORD }}" \ - publishAndReleaseToMavenCentral --no-configuration-cache + :sdk:publishAndReleaseToMavenCentral --no-configuration-cache diff --git a/.github/workflows/ci-sdk-manual-publish-snapshot.yml b/.github/workflows/ci-sdk-manual-publish-snapshot.yml index 05b997e..061bf27 100644 --- a/.github/workflows/ci-sdk-manual-publish-snapshot.yml +++ b/.github/workflows/ci-sdk-manual-publish-snapshot.yml @@ -42,7 +42,7 @@ jobs: - name: Build id: build - run: cd sdk && && ./gradlew --warning-mode all check build -x test -x integrationTest + run: ./gradlew --warning-mode all check build -x test -x integrationTest - name: Publish snapshot env: @@ -53,7 +53,6 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.FLOW_JVM_SDK_SIGNING_PASSWORD }} run: | - cd sdk if [[ "${{ secrets.FLOW_JVM_SDK_CICD_PUBLISH_ENABLED }}" != "true" ]]; then exit 0; @@ -61,4 +60,4 @@ jobs: ./gradlew \ -Psigning.key="${{ secrets.FLOW_JVM_SDK_SIGNING_KEY }}" \ -Psigning.password="${{ secrets.FLOW_JVM_SDK_SIGNING_PASSWORD }}" \ - publishToMavenCentral --no-configuration-cache + :sdk:publishToMavenCentral --no-configuration-cache diff --git a/.github/workflows/ci-sdk-publish-release.yml b/.github/workflows/ci-sdk-publish-release.yml index 46970a7..bac6832 100644 --- a/.github/workflows/ci-sdk-publish-release.yml +++ b/.github/workflows/ci-sdk-publish-release.yml @@ -41,15 +41,14 @@ jobs: - name: Build id: build - run: cd sdk && ./gradlew --warning-mode all check build -x test -x integrationTest + run: ./gradlew --warning-mode all check build -x test -x integrationTest - name: Run Unit Tests id: unit_test if: ${{ steps.build.outcome == 'success' }} run: | - cd sdk export PATH="$HOME/.local/bin:$PATH" - ./gradlew --no-daemon --max-workers=2 --warning-mode=all test --continue --stacktrace + ./gradlew --no-daemon --max-workers=2 --warning-mode=all :sdk:test --continue --stacktrace - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v1.18 @@ -63,9 +62,8 @@ jobs: - name: Run Integration Tests if: ${{ steps.unit_test.outcome == 'success' }} run: | - cd sdk export PATH="$HOME/.local/bin:$PATH" - ./gradlew --no-daemon --max-workers=2 --warning-mode=all integrationTest --continue --stacktrace + ./gradlew --no-daemon --max-workers=2 --warning-mode=all :sdk:integrationTest --continue --stacktrace - name: Publish Integration Test Results uses: EnricoMi/publish-unit-test-result-action@v1.18 @@ -85,7 +83,6 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.FLOW_JVM_SDK_SIGNING_PASSWORD }} run: | - cd sdk if [[ "${{ secrets.FLOW_JVM_SDK_CICD_PUBLISH_ENABLED }}" != "true" ]]; then exit 0; @@ -93,5 +90,5 @@ jobs: ./gradlew \ -Psigning.key="${{ secrets.FLOW_JVM_SDK_SIGNING_KEY }}" \ -Psigning.password="${{ secrets.FLOW_JVM_SDK_SIGNING_PASSWORD }}" \ - publishAndReleaseToMavenCentral --no-configuration-cache + :sdk:publishAndReleaseToMavenCentral --no-configuration-cache diff --git a/.github/workflows/ci-sdk-publish-snapshot.yml b/.github/workflows/ci-sdk-publish-snapshot.yml index 4c32a04..9017ff5 100644 --- a/.github/workflows/ci-sdk-publish-snapshot.yml +++ b/.github/workflows/ci-sdk-publish-snapshot.yml @@ -42,15 +42,14 @@ jobs: - name: Build id: build - run: cd sdk && ./gradlew --warning-mode all check build -x test -x integrationTest + run: ./gradlew --warning-mode all check build -x test -x integrationTest - name: Run Unit Tests id: unit_test if: ${{ steps.build.outcome == 'success' }} run: | - cd sdk export PATH="$HOME/.local/bin:$PATH" - ./gradlew --no-daemon --max-workers=2 --warning-mode=all test --continue --stacktrace + ./gradlew --no-daemon --max-workers=2 --warning-mode=all :sdk:test --continue --stacktrace - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v1.18 @@ -64,9 +63,8 @@ jobs: - name: Run Integration Tests if: ${{ steps.unit_test.outcome == 'success' }} run: | - cd sdk export PATH="$HOME/.local/bin:$PATH" - ./gradlew --no-daemon --max-workers=2 --warning-mode=all integrationTest --continue --stacktrace + ./gradlew --no-daemon --max-workers=2 --warning-mode=all :sdk:integrationTest --continue --stacktrace - name: Publish Integration Test Results uses: EnricoMi/publish-unit-test-result-action@v1.18 @@ -86,7 +84,6 @@ jobs: ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.FLOW_JVM_SDK_SIGNING_PASSWORD }} run: | - cd sdk if [[ "${{ secrets.FLOW_JVM_SDK_CICD_PUBLISH_ENABLED }}" != "true" ]]; then exit 0; @@ -94,4 +91,4 @@ jobs: ./gradlew \ -Psigning.key="${{ secrets.FLOW_JVM_SDK_SIGNING_KEY }}" \ -Psigning.password="${{ secrets.FLOW_JVM_SDK_SIGNING_PASSWORD }}" \ - publishToMavenCentral --no-configuration-cache + :sdk:publishToMavenCentral --no-configuration-cache diff --git a/.github/workflows/ci-sdk-pull-request.yml b/.github/workflows/ci-sdk-pull-request.yml index ac20ed5..6e16078 100644 --- a/.github/workflows/ci-sdk-pull-request.yml +++ b/.github/workflows/ci-sdk-pull-request.yml @@ -1,4 +1,4 @@ -name: Build Pull Request +name: Build SDK Pull Request on: pull_request jobs: From c1195e6e2382f114370d40c718fd15b7203b6864 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 15:32:19 +1000 Subject: [PATCH 12/72] Debugging GH actions --- .github/workflows/ci-kotlin-examples-pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index bb0f2fc..1efdc67 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -36,4 +36,4 @@ jobs: - name: Build id: build - run: ./gradlew --warning-mode all check build -x test + run: ./gradlew --warning-mode all check :kotlin-example:build -x test -x integrationTest From 78b9ff479a6200332bf95b8a7c5588f179ad8a02 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 20:48:32 +1000 Subject: [PATCH 13/72] Debugging GH actions --- .../ci-kotlin-examples-pull-request.yml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index 1efdc67..5b54642 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -37,3 +37,23 @@ jobs: - name: Build id: build run: ./gradlew --warning-mode all check :kotlin-example:build -x test -x integrationTest + + - name: Start emulator + id: start_emulator + run: flow emulator start -v + + - name: Run Unit Tests + id: unit_test + if: ${{ steps.build.outcome == 'success' }} + run: | + export PATH="$HOME/.local/bin:$PATH" + ./gradlew --no-daemon --max-workers=2 --warning-mode=all :kotlin-example:test -i --continue --stacktrace + + - name: Publish Unit Test Results + uses: EnricoMi/publish-unit-test-result-action@v1.18 + if: always() + with: + check_name: "Unit Test Results" + files: "**/test-results/test/**/*.xml" + seconds_between_github_writes: 5 + secondary_rate_limit_wait_seconds: 120 From efde813a987002115c1f9f7f42174fe7798e6f42 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 21:11:09 +1000 Subject: [PATCH 14/72] Debugging GH actions --- .github/workflows/ci-kotlin-examples-pull-request.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index 5b54642..925dc18 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -40,7 +40,10 @@ jobs: - name: Start emulator id: start_emulator - run: flow emulator start -v + run: | + flow init + flow emulator start -v + - name: Run Unit Tests id: unit_test From cc41ae5e6658459be49626e41299b1667c2d852d Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 21:15:02 +1000 Subject: [PATCH 15/72] Debugging GH actions --- .github/workflows/ci-kotlin-examples-pull-request.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index 925dc18..e0d4fc3 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -40,9 +40,12 @@ jobs: - name: Start emulator id: start_emulator + if: ${{ steps.build.outcome == 'success' }} run: | flow init flow emulator start -v + export PATH="$HOME/.local/bin:$PATH" + ./gradlew --no-daemon --max-workers=2 --warning-mode=all :kotlin-example:test -i --continue --stacktrace - name: Run Unit Tests From ab2ec53bc61538e52d65651e3d40a4668640ba3d Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 21:24:30 +1000 Subject: [PATCH 16/72] Debugging GH actions --- .../ci-kotlin-examples-pull-request.yml | 60 ++++++++++++++++++- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index e0d4fc3..2d6846b 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -44,8 +44,6 @@ jobs: run: | flow init flow emulator start -v - export PATH="$HOME/.local/bin:$PATH" - ./gradlew --no-daemon --max-workers=2 --warning-mode=all :kotlin-example:test -i --continue --stacktrace - name: Run Unit Tests @@ -54,6 +52,64 @@ jobs: run: | export PATH="$HOME/.local/bin:$PATH" ./gradlew --no-daemon --max-workers=2 --warning-mode=all :kotlin-example:test -i --continue --stacktrace +name: Build Kotlin Examples Pull Request +on: pull_request + +jobs: + build: + name: Build pull request + runs-on: ubuntu-latest + env: + JAVA_OPTS: -Xmx2g -Dorg.gradle.daemon=false + #services: + # flow-emulator: + # image: gcr.io/flow-container-registry/emulator + # env: + # FLOW_VERBOSE: true + # FLOW_PORT: 3569 + # FLOW_INTERVAL: 5s + # FLOW_PERSIST: false + # ports: + # - 3569:3569 + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Java + uses: actions/setup-java@v2 + with: + java-version: '21' + java-package: jdk + distribution: 'adopt' + + - name: Install flow emulator + run: sh -ci "$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)" + + - name: Make gradle executable + run: chmod +x ./gradlew + + - name: Build + id: build + run: ./gradlew --warning-mode all check :kotlin-example:build -x test -x integrationTest + + - name: Wait for emulator to start + id: wait_for_emulator + if: ${{ steps.start_emulator.outcome == 'success' }} + run: sleep 15 + + - name: Run Unit Tests + id: unit_test + if: ${{ steps.wait_for_emulator.outcome == 'success' }} + run: ./gradlew --no-daemon --max-workers=2 --warning-mode=all :kotlin-example:test -i --continue --stacktrace + + - name: Publish Unit Test Results + uses: EnricoMi/publish-unit-test-result-action@v1.18 + if: always() + with: + check_name: "Unit Test Results" + files: "**/test-results/test/**/*.xml" + seconds_between_github_writes: 5 + secondary_rate_limit_wait_seconds: 120 - name: Publish Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v1.18 From 78897004bf20bbad7a8f839829c3fbe8258a5856 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 21:26:40 +1000 Subject: [PATCH 17/72] Debugging GH actions --- .../ci-kotlin-examples-pull-request.yml | 63 ------------------- 1 file changed, 63 deletions(-) diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index 2d6846b..0c0d7d1 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -1,60 +1,6 @@ name: Build Kotlin Examples Pull Request on: pull_request -jobs: - build: - name: Build pull request - runs-on: ubuntu-latest - env: - JAVA_OPTS: -Xmx2g -Dorg.gradle.daemon=false - #services: - # flow-emulator: - # image: gcr.io/flow-container-registry/emulator - # env: - # FLOW_VERBOSE: true - # FLOW_PORT: 3569 - # FLOW_INTERVAL: 5s - # FLOW_PERSIST: false - # ports: - # - 3569:3569 - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Setup Java - uses: actions/setup-java@v2 - with: - java-version: '21' - java-package: jdk - distribution: 'adopt' - - - name: Install flow emulator - run: sh -ci "$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)" - - - name: Make gradle executable - run: chmod +x ./gradlew - - - name: Build - id: build - run: ./gradlew --warning-mode all check :kotlin-example:build -x test -x integrationTest - - - name: Start emulator - id: start_emulator - if: ${{ steps.build.outcome == 'success' }} - run: | - flow init - flow emulator start -v - - - - name: Run Unit Tests - id: unit_test - if: ${{ steps.build.outcome == 'success' }} - run: | - export PATH="$HOME/.local/bin:$PATH" - ./gradlew --no-daemon --max-workers=2 --warning-mode=all :kotlin-example:test -i --continue --stacktrace -name: Build Kotlin Examples Pull Request -on: pull_request - jobs: build: name: Build pull request @@ -110,12 +56,3 @@ jobs: files: "**/test-results/test/**/*.xml" seconds_between_github_writes: 5 secondary_rate_limit_wait_seconds: 120 - - - name: Publish Unit Test Results - uses: EnricoMi/publish-unit-test-result-action@v1.18 - if: always() - with: - check_name: "Unit Test Results" - files: "**/test-results/test/**/*.xml" - seconds_between_github_writes: 5 - secondary_rate_limit_wait_seconds: 120 From b51bd0a0f2d12490c15b4611b85af53ef8f256c8 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 21:28:44 +1000 Subject: [PATCH 18/72] Debugging GH actions --- .github/workflows/ci-kotlin-examples-pull-request.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index 0c0d7d1..6147431 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -38,6 +38,13 @@ jobs: id: build run: ./gradlew --warning-mode all check :kotlin-example:build -x test -x integrationTest + - name: Start emulator + id: start_emulator + if: ${{ steps.build.outcome == 'success' }} + run: | + flow init + nohup flow emulator start -v > flow-emulator.log 2>&1 & + - name: Wait for emulator to start id: wait_for_emulator if: ${{ steps.start_emulator.outcome == 'success' }} From 269900f561d9d9f7c67dc323831c8e8d42ee1002 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 21:31:50 +1000 Subject: [PATCH 19/72] Debugging GH actions --- .github/workflows/ci-kotlin-examples-pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index 6147431..21c1eac 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -42,7 +42,7 @@ jobs: id: start_emulator if: ${{ steps.build.outcome == 'success' }} run: | - flow init + cd ./kotlin-example nohup flow emulator start -v > flow-emulator.log 2>&1 & - name: Wait for emulator to start From 2edee7e0b52c83a35c553080c471222282c7bbf4 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 21:35:36 +1000 Subject: [PATCH 20/72] Debugging GH actions --- .../ci-kotlin-examples-pull-request.yml | 1 - flow.json | 31 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 flow.json diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index 21c1eac..0829b05 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -42,7 +42,6 @@ jobs: id: start_emulator if: ${{ steps.build.outcome == 'success' }} run: | - cd ./kotlin-example nohup flow emulator start -v > flow-emulator.log 2>&1 & - name: Wait for emulator to start diff --git a/flow.json b/flow.json new file mode 100644 index 0000000..45f4c45 --- /dev/null +++ b/flow.json @@ -0,0 +1,31 @@ +{ + "emulators": { + "default": { + "port": 3569, + "serviceAccount": "emulator-account" + } + }, + "contracts": {}, + "networks": { + "emulator": { + "host": "127.0.0.1:3569", + "chain": "flow-emulator" + }, + "mainnet": { + "host": "access.mainnet.nodes.onflow.org:9000", + "chain": "flow-mainnet" + }, + "testnet": { + "host": "access.devnet.nodes.onflow.org:9000", + "chain": "flow-testnet" + } + }, + "accounts": { + "emulator-account": { + "address": "f8d6e0586b0a20c7", + "keys": "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17", + "chain": "flow-emulator" + } + }, + "deployments": {} +} From 7d420d866fe34e469491ee6fd7f3414ceabaf15e Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 21:43:26 +1000 Subject: [PATCH 21/72] Debugging GH actions --- .github/workflows/ci-kotlin-examples-pull-request.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index 0829b05..6147431 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -42,6 +42,7 @@ jobs: id: start_emulator if: ${{ steps.build.outcome == 'success' }} run: | + flow init nohup flow emulator start -v > flow-emulator.log 2>&1 & - name: Wait for emulator to start From a30ba90bc4b0dd056ab438f1486d47ac5af23aa4 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 21:57:47 +1000 Subject: [PATCH 22/72] Setting up java-example module --- java-example/src/README.md | 19 +++ java-example/src/build.gradle.kts | 57 +++++++ java-example/src/flow.json | 31 ++++ java-example/src/main/flow.json | 13 ++ .../java/org/onflow/examples/java/App.java | 159 ++++++++++++++++++ .../src/main/resources/create_account.cdc | 6 + .../src/main/resources/transfer_flow.cdc | 29 ++++ .../org/onflow/examples/java/AppTest.java | 66 ++++++++ settings.gradle.kts | 2 +- 9 files changed, 381 insertions(+), 1 deletion(-) create mode 100644 java-example/src/README.md create mode 100644 java-example/src/build.gradle.kts create mode 100644 java-example/src/flow.json create mode 100644 java-example/src/main/flow.json create mode 100644 java-example/src/main/java/org/onflow/examples/java/App.java create mode 100644 java-example/src/main/resources/create_account.cdc create mode 100644 java-example/src/main/resources/transfer_flow.cdc create mode 100644 java-example/src/test/java/org/onflow/examples/java/AppTest.java diff --git a/java-example/src/README.md b/java-example/src/README.md new file mode 100644 index 0000000..d2112ae --- /dev/null +++ b/java-example/src/README.md @@ -0,0 +1,19 @@ +# Java Flow Client Example + +Example Java application using the [Flow JVM SDK](https://github.com/onflow/flow-jvm-sdk) to interact with Flow blockchain. + +## Usage + +First, install the [Flow CLI](https://docs.onflow.org/flow-cli). + +Start the [Flow Emulator](https://docs.onflow.org/emulator) in the main directory of this repository: + +```sh +flow emulator start -v +``` + +Then, in a separate terminal, run the application with Gradle: + +```sh +./gradlew test -i +``` diff --git a/java-example/src/build.gradle.kts b/java-example/src/build.gradle.kts new file mode 100644 index 0000000..89ca5c2 --- /dev/null +++ b/java-example/src/build.gradle.kts @@ -0,0 +1,57 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + java + application +} + +// Helper function to get properties +fun getProp(name: String, defaultValue: String? = null): String? { + return project.findProperty("flow.$name")?.toString()?.trim()?.ifBlank { null } + ?: project.findProperty(name)?.toString()?.trim()?.ifBlank { null } + ?: defaultValue +} + +val FLOW_JVM_SDK_VERSION = "1.0.1" +val USE_KOTLIN_APP = project.findProperty("USE_KOTLIN_APP") == "true" + +tasks.withType { + sourceCompatibility = JavaVersion.VERSION_21.toString() + targetCompatibility = JavaVersion.VERSION_21.toString() +} + +tasks.withType { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_21.toString() + freeCompilerArgs = listOf("-Xjsr305=strict", "-opt-in=kotlin.RequiresOptIn") + } +} + +repositories { + mavenCentral() + mavenLocal() + maven { url = uri("https://jitpack.io") } + maven { url = uri("https://dl.bintray.com/ethereum/maven/") } +} + +dependencies { + implementation("org.onflow:flow-jvm-sdk:$FLOW_JVM_SDK_VERSION") + + // Use JUnit Jupiter Engine for testing. + testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.0") + testImplementation("org.junit.jupiter:junit-jupiter-engine:5.7.0") +} + +application { + // Define the main class for the application. + mainClass.set(if (USE_KOTLIN_APP) { + "org.onflow.examples.kotlin.App" + } else { + "org.onflow.examples.java.App" + }) +} + +tasks.test { + // Use junit platform for unit tests. + useJUnitPlatform() +} diff --git a/java-example/src/flow.json b/java-example/src/flow.json new file mode 100644 index 0000000..45f4c45 --- /dev/null +++ b/java-example/src/flow.json @@ -0,0 +1,31 @@ +{ + "emulators": { + "default": { + "port": 3569, + "serviceAccount": "emulator-account" + } + }, + "contracts": {}, + "networks": { + "emulator": { + "host": "127.0.0.1:3569", + "chain": "flow-emulator" + }, + "mainnet": { + "host": "access.mainnet.nodes.onflow.org:9000", + "chain": "flow-mainnet" + }, + "testnet": { + "host": "access.devnet.nodes.onflow.org:9000", + "chain": "flow-testnet" + } + }, + "accounts": { + "emulator-account": { + "address": "f8d6e0586b0a20c7", + "keys": "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17", + "chain": "flow-emulator" + } + }, + "deployments": {} +} diff --git a/java-example/src/main/flow.json b/java-example/src/main/flow.json new file mode 100644 index 0000000..c331b96 --- /dev/null +++ b/java-example/src/main/flow.json @@ -0,0 +1,13 @@ +{ + "networks": { + "emulator": "127.0.0.1:3569", + "mainnet": "access.mainnet.nodes.onflow.org:9000", + "testnet": "access.devnet.nodes.onflow.org:9000" + }, + "accounts": { + "emulator-account": { + "address": "f8d6e0586b0a20c7", + "key": "8d76d7fc7cc0ff807cc56c935955ba88071a5181fbee186c704c14eb534093c4" + } + } +} \ No newline at end of file diff --git a/java-example/src/main/java/org/onflow/examples/java/App.java b/java-example/src/main/java/org/onflow/examples/java/App.java new file mode 100644 index 0000000..0dc07ea --- /dev/null +++ b/java-example/src/main/java/org/onflow/examples/java/App.java @@ -0,0 +1,159 @@ +package org.onflow.examples.java; + +import org.bouncycastle.util.encoders.Hex; +import org.onflow.flow.sdk.cadence.AddressField; +import org.onflow.flow.sdk.cadence.StringField; +import org.onflow.flow.sdk.cadence.UFix64NumberField; +import org.onflow.flow.sdk.crypto.Crypto; +import org.onflow.flow.sdk.crypto.PrivateKey; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public final class App { + + private final FlowAccessApi accessAPI; + private final PrivateKey privateKey; + + public App(String host, int port, String privateKeyHex) { + this.accessAPI = Flow.newAccessApi(host, port); + this.privateKey = Crypto.decodePrivateKey(privateKeyHex); + } + + public FlowAddress createAccount(FlowAddress payerAddress, String publicKeyHex) { + FlowAccountKey payerAccountKey = this.getAccountKey(payerAddress); + FlowAccountKey newAccountPublicKey = new FlowAccountKey( + 0, + new FlowPublicKey(publicKeyHex), + SignatureAlgorithm.ECDSA_P256, + HashAlgorithm.SHA2_256, + 1, + 0, + false); + + FlowTransaction tx = new FlowTransaction( + new FlowScript(Objects.requireNonNull(loadScript("create_account.cdc"))), + List.of(new FlowArgument(new StringField(Hex.toHexString(newAccountPublicKey.getEncoded())))), + this.getLatestBlockID(), + 100L, + new FlowTransactionProposalKey( + payerAddress, + payerAccountKey.getId(), + payerAccountKey.getSequenceNumber()), + payerAddress, + List.of(payerAddress), + new ArrayList<>(), + new ArrayList<>()); + + Signer signer = Crypto.getSigner(this.privateKey, payerAccountKey.getHashAlgo()); + tx = tx.addPayloadSignature(payerAddress, 0, signer); + tx = tx.addEnvelopeSignature(payerAddress, 0, signer); + + FlowId txID = this.accessAPI.sendTransaction(tx); + FlowTransactionResult txResult = this.waitForSeal(txID); + + return this.getAccountCreatedAddress(txResult); + } + + public void transferTokens(FlowAddress senderAddress, FlowAddress recipientAddress, BigDecimal amount) throws Exception { + // exit early + if (amount.scale() != 8) { + throw new Exception("FLOW amount must have exactly 8 decimal places of precision (e.g. 10.00000000)"); + } + + FlowAccountKey senderAccountKey = this.getAccountKey(senderAddress); + FlowTransaction tx = new FlowTransaction( + new FlowScript(Objects.requireNonNull(loadScript("transfer_flow.cdc"))), + Arrays.asList( + new FlowArgument(new UFix64NumberField(amount.toPlainString())), + new FlowArgument(new AddressField(recipientAddress.getBase16Value()))), + this.getLatestBlockID(), + 100L, + new FlowTransactionProposalKey( + senderAddress, + senderAccountKey.getId(), + senderAccountKey.getSequenceNumber()), + senderAddress, + List.of(senderAddress), + new ArrayList<>(), + new ArrayList<>()); + + Signer signer = Crypto.getSigner(this.privateKey, senderAccountKey.getHashAlgo()); + tx = tx.addEnvelopeSignature(senderAddress, senderAccountKey.getId(), signer); + + FlowId txID = this.accessAPI.sendTransaction(tx); + this.waitForSeal(txID); + } + + public FlowAccount getAccount(FlowAddress address) { + return this.accessAPI.getAccountAtLatestBlock(address); + } + + public BigDecimal getAccountBalance(FlowAddress address) { + FlowAccount account = this.getAccount(address); + return account.getBalance(); + } + + private FlowId getLatestBlockID() { + return this.accessAPI.getLatestBlockHeader(true).getId(); + } + + private FlowAccountKey getAccountKey(FlowAddress address) { + FlowAccount account = this.getAccount(address); + return account.getKeys().getFirst(); + } + + private FlowTransactionResult getTransactionResult(FlowId txID) { + return this.accessAPI.getTransactionResultById(txID); + } + + private FlowTransactionResult waitForSeal(FlowId txID) { + FlowTransactionResult txResult; + + while(true) { + txResult = this.getTransactionResult(txID); + if (txResult.getStatus().equals(FlowTransactionStatus.SEALED)) { + return txResult; + } + + try { + Thread.sleep(1000L); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + + private FlowAddress getAccountCreatedAddress(FlowTransactionResult txResult) { + if (!txResult.getStatus().equals(FlowTransactionStatus.SEALED) + || !txResult.getErrorMessage().isEmpty()) { + return null; + } + + String rez = Objects.requireNonNull(Objects.requireNonNull(txResult + .getEvents() + .getFirst() + .getEvent() + .getValue()) + .getFields()[0] + .getValue() + .getValue()).toString(); + return new FlowAddress(rez.substring(2)); + } + + private byte[] loadScript(String name) { + try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(name)) { + assert is != null; + return is.readAllBytes(); + } catch (IOException e) { + e.printStackTrace(); + } + + return null; + } +} diff --git a/java-example/src/main/resources/create_account.cdc b/java-example/src/main/resources/create_account.cdc new file mode 100644 index 0000000..d05afdc --- /dev/null +++ b/java-example/src/main/resources/create_account.cdc @@ -0,0 +1,6 @@ +transaction(publicKey: String) { + prepare(signer: AuthAccount) { + let account = AuthAccount(payer: signer) + account.addPublicKey(publicKey.decodeHex()) + } +} diff --git a/java-example/src/main/resources/transfer_flow.cdc b/java-example/src/main/resources/transfer_flow.cdc new file mode 100644 index 0000000..a94a2fb --- /dev/null +++ b/java-example/src/main/resources/transfer_flow.cdc @@ -0,0 +1,29 @@ +import FungibleToken from 0xee82856bf20e2aa6 +import FlowToken from 0x0ae53cb6e3f42a79 + +transaction(amount: UFix64, to: Address) { + + // The Vault resource that holds the tokens that are being transferred + let sentVault: @FungibleToken.Vault + + prepare(signer: AuthAccount) { + // Get a reference to the signer's stored vault + let vaultRef = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) + ?? panic("Could not borrow reference to the owner's Vault!") + + // Withdraw tokens from the signer's stored vault + self.sentVault <- vaultRef.withdraw(amount: amount) + } + + execute { + + // Get a reference to the recipient's Receiver + let receiverRef = getAccount(to) + .getCapability(/public/flowTokenReceiver) + .borrow<&{FungibleToken.Receiver}>() + ?? panic("Could not borrow receiver reference to the recipient's Vault") + + // Deposit the withdrawn tokens in the recipient's receiver + receiverRef.deposit(from: <-self.sentVault) + } +} diff --git a/java-example/src/test/java/org/onflow/examples/java/AppTest.java b/java-example/src/test/java/org/onflow/examples/java/AppTest.java new file mode 100644 index 0000000..4400553 --- /dev/null +++ b/java-example/src/test/java/org/onflow/examples/java/AppTest.java @@ -0,0 +1,66 @@ +package org.onflow.examples.java; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.onflow.flow.sdk.FlowAddress; +import org.onflow.flow.sdk.crypto.Crypto; +import org.onflow.flow.sdk.crypto.KeyPair; + +import java.math.BigDecimal; + +import static org.junit.jupiter.api.Assertions.*; + +class AppTest { + + public static final String SERVICE_PRIVATE_KEY_HEX = "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17"; + + private final FlowAddress serviceAccountAddress = new FlowAddress("f8d6e0586b0a20c7"); + private String userPublicKeyHex; + + @BeforeEach + void setupUser() { + KeyPair keyPair = Crypto.generateKeyPair(); + this.userPublicKeyHex = keyPair.getPublic().getHex(); + } + + @Test + void createAccount() { + + App app = new App("localhost", 3569, SERVICE_PRIVATE_KEY_HEX); + + // service account address + + FlowAddress account = app.createAccount(serviceAccountAddress, this.userPublicKeyHex); + assertNotNull(account); + } + + @Test + void transferTokens() throws Exception { + + App app = new App("localhost", 3569, SERVICE_PRIVATE_KEY_HEX); + + // service account address + var sender = serviceAccountAddress; + var recipient = app.createAccount(sender, this.userPublicKeyHex); + + // FLOW amounts always have 8 decimal places + var amount = new BigDecimal("10.00000001"); + + var balance1 = app.getAccountBalance(recipient); + + app.transferTokens(sender, recipient, amount); + + var balance2 = app.getAccountBalance(recipient); + + assertEquals(balance1.add(amount), balance2); + } + + @Test + void getAccountBalance() { + + App app = new App("localhost", 3569, SERVICE_PRIVATE_KEY_HEX); + var balance = app.getAccountBalance(serviceAccountAddress); + + System.out.println(balance); + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 20da3ab..c8048a2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -14,4 +14,4 @@ pluginManagement { } rootProject.name="flow-jvm-sdk" -include("sdk", "kotlin-example") +include("sdk", "kotlin-example", "java-example") From 44641ed472da405aa11d086c87e70362073c8291 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 22:28:26 +1000 Subject: [PATCH 23/72] Setting up java-example module --- java-example/{src => }/build.gradle.kts | 0 java-example/src/README.md | 19 ------------ java-example/src/flow.json | 31 ------------------- java-example/src/main/flow.json | 13 -------- .../java/org/onflow/examples/java/App.java | 15 ++++----- .../src/main/resources/create_account.cdc | 6 ---- .../src/main/resources/transfer_flow.cdc | 29 ----------------- 7 files changed, 8 insertions(+), 105 deletions(-) rename java-example/{src => }/build.gradle.kts (100%) delete mode 100644 java-example/src/README.md delete mode 100644 java-example/src/flow.json delete mode 100644 java-example/src/main/flow.json delete mode 100644 java-example/src/main/resources/create_account.cdc delete mode 100644 java-example/src/main/resources/transfer_flow.cdc diff --git a/java-example/src/build.gradle.kts b/java-example/build.gradle.kts similarity index 100% rename from java-example/src/build.gradle.kts rename to java-example/build.gradle.kts diff --git a/java-example/src/README.md b/java-example/src/README.md deleted file mode 100644 index d2112ae..0000000 --- a/java-example/src/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Java Flow Client Example - -Example Java application using the [Flow JVM SDK](https://github.com/onflow/flow-jvm-sdk) to interact with Flow blockchain. - -## Usage - -First, install the [Flow CLI](https://docs.onflow.org/flow-cli). - -Start the [Flow Emulator](https://docs.onflow.org/emulator) in the main directory of this repository: - -```sh -flow emulator start -v -``` - -Then, in a separate terminal, run the application with Gradle: - -```sh -./gradlew test -i -``` diff --git a/java-example/src/flow.json b/java-example/src/flow.json deleted file mode 100644 index 45f4c45..0000000 --- a/java-example/src/flow.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "emulators": { - "default": { - "port": 3569, - "serviceAccount": "emulator-account" - } - }, - "contracts": {}, - "networks": { - "emulator": { - "host": "127.0.0.1:3569", - "chain": "flow-emulator" - }, - "mainnet": { - "host": "access.mainnet.nodes.onflow.org:9000", - "chain": "flow-mainnet" - }, - "testnet": { - "host": "access.devnet.nodes.onflow.org:9000", - "chain": "flow-testnet" - } - }, - "accounts": { - "emulator-account": { - "address": "f8d6e0586b0a20c7", - "keys": "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17", - "chain": "flow-emulator" - } - }, - "deployments": {} -} diff --git a/java-example/src/main/flow.json b/java-example/src/main/flow.json deleted file mode 100644 index c331b96..0000000 --- a/java-example/src/main/flow.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "networks": { - "emulator": "127.0.0.1:3569", - "mainnet": "access.mainnet.nodes.onflow.org:9000", - "testnet": "access.devnet.nodes.onflow.org:9000" - }, - "accounts": { - "emulator-account": { - "address": "f8d6e0586b0a20c7", - "key": "8d76d7fc7cc0ff807cc56c935955ba88071a5181fbee186c704c14eb534093c4" - } - } -} \ No newline at end of file diff --git a/java-example/src/main/java/org/onflow/examples/java/App.java b/java-example/src/main/java/org/onflow/examples/java/App.java index 0dc07ea..7ff75f7 100644 --- a/java-example/src/main/java/org/onflow/examples/java/App.java +++ b/java-example/src/main/java/org/onflow/examples/java/App.java @@ -1,12 +1,5 @@ package org.onflow.examples.java; -import org.bouncycastle.util.encoders.Hex; -import org.onflow.flow.sdk.cadence.AddressField; -import org.onflow.flow.sdk.cadence.StringField; -import org.onflow.flow.sdk.cadence.UFix64NumberField; -import org.onflow.flow.sdk.crypto.Crypto; -import org.onflow.flow.sdk.crypto.PrivateKey; - import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; @@ -15,6 +8,14 @@ import java.util.List; import java.util.Objects; +import org.bouncycastle.util.encoders.Hex; +import org.onflow.flow.sdk.*; +import org.onflow.flow.sdk.cadence.AddressField; +import org.onflow.flow.sdk.cadence.StringField; +import org.onflow.flow.sdk.cadence.UFix64NumberField; +import org.onflow.flow.sdk.crypto.Crypto; +import org.onflow.flow.sdk.crypto.PrivateKey; + public final class App { private final FlowAccessApi accessAPI; diff --git a/java-example/src/main/resources/create_account.cdc b/java-example/src/main/resources/create_account.cdc deleted file mode 100644 index d05afdc..0000000 --- a/java-example/src/main/resources/create_account.cdc +++ /dev/null @@ -1,6 +0,0 @@ -transaction(publicKey: String) { - prepare(signer: AuthAccount) { - let account = AuthAccount(payer: signer) - account.addPublicKey(publicKey.decodeHex()) - } -} diff --git a/java-example/src/main/resources/transfer_flow.cdc b/java-example/src/main/resources/transfer_flow.cdc deleted file mode 100644 index a94a2fb..0000000 --- a/java-example/src/main/resources/transfer_flow.cdc +++ /dev/null @@ -1,29 +0,0 @@ -import FungibleToken from 0xee82856bf20e2aa6 -import FlowToken from 0x0ae53cb6e3f42a79 - -transaction(amount: UFix64, to: Address) { - - // The Vault resource that holds the tokens that are being transferred - let sentVault: @FungibleToken.Vault - - prepare(signer: AuthAccount) { - // Get a reference to the signer's stored vault - let vaultRef = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) - ?? panic("Could not borrow reference to the owner's Vault!") - - // Withdraw tokens from the signer's stored vault - self.sentVault <- vaultRef.withdraw(amount: amount) - } - - execute { - - // Get a reference to the recipient's Receiver - let receiverRef = getAccount(to) - .getCapability(/public/flowTokenReceiver) - .borrow<&{FungibleToken.Receiver}>() - ?? panic("Could not borrow receiver reference to the recipient's Vault") - - // Deposit the withdrawn tokens in the recipient's receiver - receiverRef.deposit(from: <-self.sentVault) - } -} From b769cd9b33c7f9d2d2244ceb822e3024c3da7252 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 22:46:39 +1000 Subject: [PATCH 24/72] Functional flow.json --- .../ci-kotlin-examples-pull-request.yml | 1 - flow.json | 18 +++-------- java-example/README.md | 19 ++++++++++++ kotlin-example/flow.json | 31 ------------------- 4 files changed, 23 insertions(+), 46 deletions(-) create mode 100644 java-example/README.md delete mode 100644 kotlin-example/flow.json diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index 6147431..0829b05 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -42,7 +42,6 @@ jobs: id: start_emulator if: ${{ steps.build.outcome == 'success' }} run: | - flow init nohup flow emulator start -v > flow-emulator.log 2>&1 & - name: Wait for emulator to start diff --git a/flow.json b/flow.json index 45f4c45..ea34e2b 100644 --- a/flow.json +++ b/flow.json @@ -5,20 +5,10 @@ "serviceAccount": "emulator-account" } }, - "contracts": {}, "networks": { - "emulator": { - "host": "127.0.0.1:3569", - "chain": "flow-emulator" - }, - "mainnet": { - "host": "access.mainnet.nodes.onflow.org:9000", - "chain": "flow-mainnet" - }, - "testnet": { - "host": "access.devnet.nodes.onflow.org:9000", - "chain": "flow-testnet" - } + "emulator": "127.0.0.1:3569", + "mainnet": "access.mainnet.nodes.onflow.org:9000", + "testnet": "access.devnet.nodes.onflow.org:9000" }, "accounts": { "emulator-account": { @@ -28,4 +18,4 @@ } }, "deployments": {} -} +} \ No newline at end of file diff --git a/java-example/README.md b/java-example/README.md new file mode 100644 index 0000000..d2112ae --- /dev/null +++ b/java-example/README.md @@ -0,0 +1,19 @@ +# Java Flow Client Example + +Example Java application using the [Flow JVM SDK](https://github.com/onflow/flow-jvm-sdk) to interact with Flow blockchain. + +## Usage + +First, install the [Flow CLI](https://docs.onflow.org/flow-cli). + +Start the [Flow Emulator](https://docs.onflow.org/emulator) in the main directory of this repository: + +```sh +flow emulator start -v +``` + +Then, in a separate terminal, run the application with Gradle: + +```sh +./gradlew test -i +``` diff --git a/kotlin-example/flow.json b/kotlin-example/flow.json deleted file mode 100644 index 45f4c45..0000000 --- a/kotlin-example/flow.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "emulators": { - "default": { - "port": 3569, - "serviceAccount": "emulator-account" - } - }, - "contracts": {}, - "networks": { - "emulator": { - "host": "127.0.0.1:3569", - "chain": "flow-emulator" - }, - "mainnet": { - "host": "access.mainnet.nodes.onflow.org:9000", - "chain": "flow-mainnet" - }, - "testnet": { - "host": "access.devnet.nodes.onflow.org:9000", - "chain": "flow-testnet" - } - }, - "accounts": { - "emulator-account": { - "address": "f8d6e0586b0a20c7", - "keys": "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17", - "chain": "flow-emulator" - } - }, - "deployments": {} -} From 15211841825ce2d9be5f3295538d213dc092e297 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 13 Jul 2024 22:48:05 +1000 Subject: [PATCH 25/72] Create GH action for java examples --- .../ci-java-examples-pull-request.yml | 64 +++++++++++++++++++ .../ci-kotlin-examples-pull-request.yml | 4 +- 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci-java-examples-pull-request.yml diff --git a/.github/workflows/ci-java-examples-pull-request.yml b/.github/workflows/ci-java-examples-pull-request.yml new file mode 100644 index 0000000..c1524cc --- /dev/null +++ b/.github/workflows/ci-java-examples-pull-request.yml @@ -0,0 +1,64 @@ +name: Build Java Examples Pull Request +on: pull_request + +jobs: + build: + name: Build pull request + runs-on: ubuntu-latest + env: + JAVA_OPTS: -Xmx2g -Dorg.gradle.daemon=false + #services: + # flow-emulator: + # image: gcr.io/flow-container-registry/emulator + # env: + # FLOW_VERBOSE: true + # FLOW_PORT: 3569 + # FLOW_INTERVAL: 5s + # FLOW_PERSIST: false + # ports: + # - 3569:3569 + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Java + uses: actions/setup-java@v2 + with: + java-version: '21' + java-package: jdk + distribution: 'adopt' + + - name: Install flow emulator + run: sh -ci "$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)" + + - name: Make gradle executable + run: chmod +x ./gradlew + + - name: Build + id: build + run: ./gradlew --warning-mode all check :java-example:build -x test -x integrationTest + + - name: Start emulator + id: start_emulator + if: ${{ steps.build.outcome == 'success' }} + run: | + nohup flow emulator start -v > flow-emulator.log 2>&1 & + + - name: Wait for emulator to start + id: wait_for_emulator + if: ${{ steps.start_emulator.outcome == 'success' }} + run: sleep 15 + + - name: Run Java Example Unit Tests + id: unit_test + if: ${{ steps.wait_for_emulator.outcome == 'success' }} + run: ./gradlew --no-daemon --max-workers=2 --warning-mode=all :java-example:test -i --continue --stacktrace + + - name: Publish Java Example Unit Test Results + uses: EnricoMi/publish-unit-test-result-action@v1.18 + if: always() + with: + check_name: "Unit Test Results" + files: "**/test-results/test/**/*.xml" + seconds_between_github_writes: 5 + secondary_rate_limit_wait_seconds: 120 diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index 0829b05..baa19f7 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -49,12 +49,12 @@ jobs: if: ${{ steps.start_emulator.outcome == 'success' }} run: sleep 15 - - name: Run Unit Tests + - name: Run Kotlin Example Unit Tests id: unit_test if: ${{ steps.wait_for_emulator.outcome == 'success' }} run: ./gradlew --no-daemon --max-workers=2 --warning-mode=all :kotlin-example:test -i --continue --stacktrace - - name: Publish Unit Test Results + - name: Publish Kotlin Example Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v1.18 if: always() with: From 27214dbde012112949c1172694572a9692fd41f2 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 17 Jul 2024 01:13:55 +1000 Subject: [PATCH 26/72] Fix java example tests --- java-example/build.gradle.kts | 8 ++++ .../java/org/onflow/examples/java/App.java | 43 +++++++++++-------- .../src/main/resources/create_account.cdc | 6 +++ .../src/main/resources/transfer_flow.cdc | 29 +++++++++++++ kotlin-example/src/main/flow.json | 13 ------ .../kotlin/org/onflow/examples/kotlin/App.kt | 1 + 6 files changed, 69 insertions(+), 31 deletions(-) create mode 100644 java-example/src/main/resources/create_account.cdc create mode 100644 java-example/src/main/resources/transfer_flow.cdc delete mode 100644 kotlin-example/src/main/flow.json diff --git a/java-example/build.gradle.kts b/java-example/build.gradle.kts index 89ca5c2..e92c8ce 100644 --- a/java-example/build.gradle.kts +++ b/java-example/build.gradle.kts @@ -55,3 +55,11 @@ tasks.test { // Use junit platform for unit tests. useJUnitPlatform() } + +sourceSets { + main { + resources { + srcDir("src/main/resources") + } + } +} diff --git a/java-example/src/main/java/org/onflow/examples/java/App.java b/java-example/src/main/java/org/onflow/examples/java/App.java index 7ff75f7..427c036 100644 --- a/java-example/src/main/java/org/onflow/examples/java/App.java +++ b/java-example/src/main/java/org/onflow/examples/java/App.java @@ -15,9 +15,10 @@ import org.onflow.flow.sdk.cadence.UFix64NumberField; import org.onflow.flow.sdk.crypto.Crypto; import org.onflow.flow.sdk.crypto.PrivateKey; +import java.util.logging.Logger; public final class App { - + private static final Logger logger = Logger.getLogger(App.class.getName()); private final FlowAccessApi accessAPI; private final PrivateKey privateKey; @@ -26,13 +27,18 @@ public App(String host, int port, String privateKeyHex) { this.privateKey = Crypto.decodePrivateKey(privateKeyHex); } + private boolean isValidHex(String hex) { + return hex.matches("[0-9a-fA-F]+"); + } + public FlowAddress createAccount(FlowAddress payerAddress, String publicKeyHex) { - FlowAccountKey payerAccountKey = this.getAccountKey(payerAddress); + FlowAccountKey payerAccountKey = getAccountKey(payerAddress, 0); + FlowAccountKey newAccountPublicKey = new FlowAccountKey( 0, new FlowPublicKey(publicKeyHex), SignatureAlgorithm.ECDSA_P256, - HashAlgorithm.SHA2_256, + HashAlgorithm.SHA3_256, 1, 0, false); @@ -51,14 +57,15 @@ public FlowAddress createAccount(FlowAddress payerAddress, String publicKeyHex) new ArrayList<>(), new ArrayList<>()); - Signer signer = Crypto.getSigner(this.privateKey, payerAccountKey.getHashAlgo()); - tx = tx.addPayloadSignature(payerAddress, 0, signer); - tx = tx.addEnvelopeSignature(payerAddress, 0, signer); + Signer signer = Crypto.getSigner(privateKey, payerAccountKey.getHashAlgo()); + tx = tx.addEnvelopeSignature(payerAddress, payerAccountKey.getId(), signer); + FlowId txID = accessAPI.sendTransaction(tx); - FlowId txID = this.accessAPI.sendTransaction(tx); - FlowTransactionResult txResult = this.waitForSeal(txID); + FlowTransactionResult txResult = waitForSeal(txID); - return this.getAccountCreatedAddress(txResult); + FlowAddress newAddress = getAccountCreatedAddress(txResult); + + return newAddress; } public void transferTokens(FlowAddress senderAddress, FlowAddress recipientAddress, BigDecimal amount) throws Exception { @@ -67,7 +74,7 @@ public void transferTokens(FlowAddress senderAddress, FlowAddress recipientAddre throw new Exception("FLOW amount must have exactly 8 decimal places of precision (e.g. 10.00000000)"); } - FlowAccountKey senderAccountKey = this.getAccountKey(senderAddress); + FlowAccountKey senderAccountKey = this.getAccountKey(senderAddress, 0); FlowTransaction tx = new FlowTransaction( new FlowScript(Objects.requireNonNull(loadScript("transfer_flow.cdc"))), Arrays.asList( @@ -104,9 +111,9 @@ private FlowId getLatestBlockID() { return this.accessAPI.getLatestBlockHeader(true).getId(); } - private FlowAccountKey getAccountKey(FlowAddress address) { - FlowAccount account = this.getAccount(address); - return account.getKeys().getFirst(); + private FlowAccountKey getAccountKey(FlowAddress address, int keyIndex) { + FlowAccount account = getAccount(address); + return account.getKeys().get(keyIndex); } private FlowTransactionResult getTransactionResult(FlowId txID) { @@ -148,13 +155,13 @@ private FlowAddress getAccountCreatedAddress(FlowTransactionResult txResult) { } private byte[] loadScript(String name) { - try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(name)) { - assert is != null; + try (InputStream is = getClass().getClassLoader().getResourceAsStream(name)) { + if (is == null) { + throw new IOException("Script " + name + " not found."); + } return is.readAllBytes(); } catch (IOException e) { - e.printStackTrace(); + throw new RuntimeException("Failed to load script " + name, e); } - - return null; } } diff --git a/java-example/src/main/resources/create_account.cdc b/java-example/src/main/resources/create_account.cdc new file mode 100644 index 0000000..d05afdc --- /dev/null +++ b/java-example/src/main/resources/create_account.cdc @@ -0,0 +1,6 @@ +transaction(publicKey: String) { + prepare(signer: AuthAccount) { + let account = AuthAccount(payer: signer) + account.addPublicKey(publicKey.decodeHex()) + } +} diff --git a/java-example/src/main/resources/transfer_flow.cdc b/java-example/src/main/resources/transfer_flow.cdc new file mode 100644 index 0000000..a94a2fb --- /dev/null +++ b/java-example/src/main/resources/transfer_flow.cdc @@ -0,0 +1,29 @@ +import FungibleToken from 0xee82856bf20e2aa6 +import FlowToken from 0x0ae53cb6e3f42a79 + +transaction(amount: UFix64, to: Address) { + + // The Vault resource that holds the tokens that are being transferred + let sentVault: @FungibleToken.Vault + + prepare(signer: AuthAccount) { + // Get a reference to the signer's stored vault + let vaultRef = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) + ?? panic("Could not borrow reference to the owner's Vault!") + + // Withdraw tokens from the signer's stored vault + self.sentVault <- vaultRef.withdraw(amount: amount) + } + + execute { + + // Get a reference to the recipient's Receiver + let receiverRef = getAccount(to) + .getCapability(/public/flowTokenReceiver) + .borrow<&{FungibleToken.Receiver}>() + ?? panic("Could not borrow receiver reference to the recipient's Vault") + + // Deposit the withdrawn tokens in the recipient's receiver + receiverRef.deposit(from: <-self.sentVault) + } +} diff --git a/kotlin-example/src/main/flow.json b/kotlin-example/src/main/flow.json deleted file mode 100644 index c331b96..0000000 --- a/kotlin-example/src/main/flow.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "networks": { - "emulator": "127.0.0.1:3569", - "mainnet": "access.mainnet.nodes.onflow.org:9000", - "testnet": "access.devnet.nodes.onflow.org:9000" - }, - "accounts": { - "emulator-account": { - "address": "f8d6e0586b0a20c7", - "key": "8d76d7fc7cc0ff807cc56c935955ba88071a5181fbee186c704c14eb534093c4" - } - } -} \ No newline at end of file diff --git a/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt index a0e2bcb..a4eaf28 100644 --- a/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt +++ b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt @@ -69,6 +69,7 @@ internal class App(host: String, port: Int, privateKeyHex: String) { weight = 1000 ) + println(loadScript("create_account.cdc")) // create transaction var tx = FlowTransaction( script = FlowScript(loadScript("create_account.cdc")), From eb77e0e64b2b978520e2d447bc7bbdbaa0ed6446 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 17 Jul 2024 01:40:19 +1000 Subject: [PATCH 27/72] Fix java example tests --- .../ci-java-examples-pull-request.yml | 2 +- flow.json | 16 ++++------ .../java/org/onflow/examples/java/App.java | 31 +++++++++---------- .../org/onflow/examples/java/AppTest.java | 14 +++++++-- 4 files changed, 34 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci-java-examples-pull-request.yml b/.github/workflows/ci-java-examples-pull-request.yml index c1524cc..7823d87 100644 --- a/.github/workflows/ci-java-examples-pull-request.yml +++ b/.github/workflows/ci-java-examples-pull-request.yml @@ -42,7 +42,7 @@ jobs: id: start_emulator if: ${{ steps.build.outcome == 'success' }} run: | - nohup flow emulator start -v > flow-emulator.log 2>&1 & + nohup flow emulator start -v --persist > flow-emulator.log 2>&1 & - name: Wait for emulator to start id: wait_for_emulator diff --git a/flow.json b/flow.json index ea34e2b..8545585 100644 --- a/flow.json +++ b/flow.json @@ -1,10 +1,4 @@ { - "emulators": { - "default": { - "port": 3569, - "serviceAccount": "emulator-account" - } - }, "networks": { "emulator": "127.0.0.1:3569", "mainnet": "access.mainnet.nodes.onflow.org:9000", @@ -13,9 +7,11 @@ "accounts": { "emulator-account": { "address": "f8d6e0586b0a20c7", - "keys": "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17", - "chain": "flow-emulator" + "key": "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17" + }, + "test-account-2": { + "address": "01cf0e2f2f715450", + "key": "0b7a67f924600d526bd94e565ce7cb4832e486fb7128db8d5696961686fad1c8" } - }, - "deployments": {} + } } \ No newline at end of file diff --git a/java-example/src/main/java/org/onflow/examples/java/App.java b/java-example/src/main/java/org/onflow/examples/java/App.java index 427c036..9b5ebe8 100644 --- a/java-example/src/main/java/org/onflow/examples/java/App.java +++ b/java-example/src/main/java/org/onflow/examples/java/App.java @@ -15,10 +15,8 @@ import org.onflow.flow.sdk.cadence.UFix64NumberField; import org.onflow.flow.sdk.crypto.Crypto; import org.onflow.flow.sdk.crypto.PrivateKey; -import java.util.logging.Logger; public final class App { - private static final Logger logger = Logger.getLogger(App.class.getName()); private final FlowAccessApi accessAPI; private final PrivateKey privateKey; @@ -27,10 +25,6 @@ public App(String host, int port, String privateKeyHex) { this.privateKey = Crypto.decodePrivateKey(privateKeyHex); } - private boolean isValidHex(String hex) { - return hex.matches("[0-9a-fA-F]+"); - } - public FlowAddress createAccount(FlowAddress payerAddress, String publicKeyHex) { FlowAccountKey payerAccountKey = getAccountKey(payerAddress, 0); @@ -63,9 +57,7 @@ public FlowAddress createAccount(FlowAddress payerAddress, String publicKeyHex) FlowTransactionResult txResult = waitForSeal(txID); - FlowAddress newAddress = getAccountCreatedAddress(txResult); - - return newAddress; + return getAccountCreatedAddress(txResult); } public void transferTokens(FlowAddress senderAddress, FlowAddress recipientAddress, BigDecimal amount) throws Exception { @@ -143,15 +135,22 @@ private FlowAddress getAccountCreatedAddress(FlowTransactionResult txResult) { return null; } - String rez = Objects.requireNonNull(Objects.requireNonNull(txResult - .getEvents() - .getFirst() - .getEvent() - .getValue()) + System.out.println(txResult + .getEvents() + .get(0) + .getEvent() + .getValue() + .getFields()[0]); + + String addressHex = (String) txResult + .getEvents() + .get(0) + .getEvent() + .getValue() .getFields()[0] .getValue() - .getValue()).toString(); - return new FlowAddress(rez.substring(2)); + .getValue(); + return new FlowAddress(addressHex.substring(2)); } private byte[] loadScript(String name) { diff --git a/java-example/src/test/java/org/onflow/examples/java/AppTest.java b/java-example/src/test/java/org/onflow/examples/java/AppTest.java index 4400553..f13ccef 100644 --- a/java-example/src/test/java/org/onflow/examples/java/AppTest.java +++ b/java-example/src/test/java/org/onflow/examples/java/AppTest.java @@ -5,16 +5,19 @@ import org.onflow.flow.sdk.FlowAddress; import org.onflow.flow.sdk.crypto.Crypto; import org.onflow.flow.sdk.crypto.KeyPair; +import java.util.logging.Level; +import java.util.logging.Logger; import java.math.BigDecimal; import static org.junit.jupiter.api.Assertions.*; class AppTest { - + private static final Logger logger = Logger.getLogger(AppTest.class.getName()); public static final String SERVICE_PRIVATE_KEY_HEX = "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17"; private final FlowAddress serviceAccountAddress = new FlowAddress("f8d6e0586b0a20c7"); + private final FlowAddress testRecipientAddress = new FlowAddress("0x01cf0e2f2f715450"); private String userPublicKeyHex; @BeforeEach @@ -41,17 +44,24 @@ void transferTokens() throws Exception { // service account address var sender = serviceAccountAddress; - var recipient = app.createAccount(sender, this.userPublicKeyHex); + var recipient = testRecipientAddress; + + System.out.println(recipient); + assertNotNull(recipient, "Account creation should return a non-null address"); // FLOW amounts always have 8 decimal places var amount = new BigDecimal("10.00000001"); var balance1 = app.getAccountBalance(recipient); + logger.log(Level.INFO, "Initial balance of recipient: {0}", balance1); + app.transferTokens(sender, recipient, amount); var balance2 = app.getAccountBalance(recipient); + logger.log(Level.INFO, "Balance of recipient after transfer: {0}", balance2); + assertEquals(balance1.add(amount), balance2); } From 743df77963c6f6924bd2e3ea8597c67967a8d2c8 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 17 Jul 2024 01:42:38 +1000 Subject: [PATCH 28/72] Fix java example tests --- java-example/build.gradle.kts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/java-example/build.gradle.kts b/java-example/build.gradle.kts index e92c8ce..89ca5c2 100644 --- a/java-example/build.gradle.kts +++ b/java-example/build.gradle.kts @@ -55,11 +55,3 @@ tasks.test { // Use junit platform for unit tests. useJUnitPlatform() } - -sourceSets { - main { - resources { - srcDir("src/main/resources") - } - } -} From f1f37f64a02a5d790ac2d39490eaeb6857b4c52a Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 17 Jul 2024 01:49:11 +1000 Subject: [PATCH 29/72] Fix java example tests --- java-example/build.gradle.kts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/java-example/build.gradle.kts b/java-example/build.gradle.kts index 89ca5c2..0955618 100644 --- a/java-example/build.gradle.kts +++ b/java-example/build.gradle.kts @@ -55,3 +55,7 @@ tasks.test { // Use junit platform for unit tests. useJUnitPlatform() } + +tasks.withType { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} From 75718045c5760945037f79f116afcf47826bb5a4 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 17 Jul 2024 02:19:14 +1000 Subject: [PATCH 30/72] Fix kotlin example tests --- .github/workflows/ci-kotlin-examples-pull-request.yml | 2 +- .../src/main/java/org/onflow/examples/java/App.java | 7 ------- .../test/java/org/onflow/examples/java/AppTest.java | 10 ---------- kotlin-example/build.gradle.kts | 4 ++++ .../test/kotlin/org/onflow/examples/kotlin/AppTest.kt | 3 ++- 5 files changed, 7 insertions(+), 19 deletions(-) diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index baa19f7..29d73c4 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -42,7 +42,7 @@ jobs: id: start_emulator if: ${{ steps.build.outcome == 'success' }} run: | - nohup flow emulator start -v > flow-emulator.log 2>&1 & + nohup flow emulator start -v --persist > flow-emulator.log 2>&1 & - name: Wait for emulator to start id: wait_for_emulator diff --git a/java-example/src/main/java/org/onflow/examples/java/App.java b/java-example/src/main/java/org/onflow/examples/java/App.java index 9b5ebe8..d304b28 100644 --- a/java-example/src/main/java/org/onflow/examples/java/App.java +++ b/java-example/src/main/java/org/onflow/examples/java/App.java @@ -135,13 +135,6 @@ private FlowAddress getAccountCreatedAddress(FlowTransactionResult txResult) { return null; } - System.out.println(txResult - .getEvents() - .get(0) - .getEvent() - .getValue() - .getFields()[0]); - String addressHex = (String) txResult .getEvents() .get(0) diff --git a/java-example/src/test/java/org/onflow/examples/java/AppTest.java b/java-example/src/test/java/org/onflow/examples/java/AppTest.java index f13ccef..e62b5d0 100644 --- a/java-example/src/test/java/org/onflow/examples/java/AppTest.java +++ b/java-example/src/test/java/org/onflow/examples/java/AppTest.java @@ -5,7 +5,6 @@ import org.onflow.flow.sdk.FlowAddress; import org.onflow.flow.sdk.crypto.Crypto; import org.onflow.flow.sdk.crypto.KeyPair; -import java.util.logging.Level; import java.util.logging.Logger; import java.math.BigDecimal; @@ -13,7 +12,6 @@ import static org.junit.jupiter.api.Assertions.*; class AppTest { - private static final Logger logger = Logger.getLogger(AppTest.class.getName()); public static final String SERVICE_PRIVATE_KEY_HEX = "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17"; private final FlowAddress serviceAccountAddress = new FlowAddress("f8d6e0586b0a20c7"); @@ -42,26 +40,18 @@ void transferTokens() throws Exception { App app = new App("localhost", 3569, SERVICE_PRIVATE_KEY_HEX); - // service account address var sender = serviceAccountAddress; var recipient = testRecipientAddress; - System.out.println(recipient); - assertNotNull(recipient, "Account creation should return a non-null address"); - // FLOW amounts always have 8 decimal places var amount = new BigDecimal("10.00000001"); var balance1 = app.getAccountBalance(recipient); - logger.log(Level.INFO, "Initial balance of recipient: {0}", balance1); - app.transferTokens(sender, recipient, amount); var balance2 = app.getAccountBalance(recipient); - logger.log(Level.INFO, "Balance of recipient after transfer: {0}", balance2); - assertEquals(balance1.add(amount), balance2); } diff --git a/kotlin-example/build.gradle.kts b/kotlin-example/build.gradle.kts index 89ca5c2..0955618 100644 --- a/kotlin-example/build.gradle.kts +++ b/kotlin-example/build.gradle.kts @@ -55,3 +55,7 @@ tasks.test { // Use junit platform for unit tests. useJUnitPlatform() } + +tasks.withType { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} diff --git a/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt index 927bf08..a3f6402 100644 --- a/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt +++ b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt @@ -8,6 +8,7 @@ import org.onflow.flow.sdk.FlowAddress import org.onflow.flow.sdk.crypto.Crypto val serviceAccountAddress: FlowAddress = FlowAddress("f8d6e0586b0a20c7") +val testRecipientAddress: FlowAddress = FlowAddress("0x01cf0e2f2f715450") const val servicePrivateKeyHex = "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17" internal class AppTest { @@ -34,7 +35,7 @@ internal class AppTest { val app = App("localhost", 3569, servicePrivateKeyHex) // service account address - val recipient: FlowAddress = app.createAccount(serviceAccountAddress, userPublicKeyHex) + val recipient: FlowAddress = testRecipientAddress // FLOW amounts always have 8 decimal places val amount = BigDecimal("10.00000001") From f9fea7542da6e6b48f3aa55c8f5d2c3851aee596 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 17 Jul 2024 14:50:23 +1000 Subject: [PATCH 31/72] Updating documentation --- README.md | 157 ------------------------------------------------- sdk/README.md | 159 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 158 insertions(+), 158 deletions(-) diff --git a/README.md b/README.md index 0d728ff..7abf31d 100644 --- a/README.md +++ b/README.md @@ -13,163 +13,6 @@ At the moment, this SDK includes the following features: - [x] Marshalling & unmarshalling of [JSON-Cadence](https://docs.onflow.org/cadence/json-cadence-spec/) - [x] DSL for creating, signing, and sending transactions and scripts -## Installation - -Use the configuration below to add this -SDK to your project using Maven or Gradle. - -### Maven - -```xml - - - - jitpack.io - https://jitpack.io - - - - - org.onflow - flow-jvm-sdk - [VERSION HERE] - -``` - -### Gradle - -```groovy -repositories { - ... - /* - the following repository is required because the trusted data framework - is not available on maven central. - */ - maven { url 'https://jitpack.io' } -} - -dependencies { - api("org.onflow:flow-jvm-sdk:[VERSION HERE]") -} -``` - -### Gradle (with test extensions) - -```groovy -plugins { - ... - id("java-test-fixtures") -} - -repositories { - ... - /* - the following repository is required because the trusted data framework - is not available on maven central. - */ - maven { url 'https://jitpack.io' } -} - -dependencies { - api("org.onflow:flow-jvm-sdk:[VERSION HERE]") - testFixturesApi(testFixtures("org.onflow:flow-jvm-sdk:[VERSION HERE]")) -} -``` - -The jitpack.io repository is necessary to access some of the dependencies of this library that are not available on Maven Central. - -## Example usage - -Check out the [example repository](https://github.com/onflow/flow-java-client-example) for an example -of how to use this SDK in a Java application. - -## Integration tests - -Tests annotated with `FlowEmulatorTest` depend on the [Flow Emulator](https://github.com/onflow/flow-emulator), which is part of the [Flow CLI](https://github.com/onflow/flow-cli) to be installed on your machine. - -The`FlowEmulatorTest` extension may be used by consumers of this library as well to streamline unit tests that interact -with the FLOW blockchian. The `FlowEmulatorTest` extension uses the local flow emulator to prepare the test environment -for unit and integration tests. For example: - -Setup dependency on the SDK: -```gradle -plugins { - id("java-test-fixtures") -} - -repositories { - /* - the following repository is required because the trusted data framework - is not available on maven central. - */ - maven { url 'https://jitpack.io' } -} - -dependencies { - api("org.onflow:flow-jvm-sdk:[VERSION HERE]") - - // this allows for using the test extension - testFixturesApi(testFixtures("org.onflow:flow-jvm-sdk:[VERSION HERE]")) -} -``` - -Write your blockchain tests: -```kotlin -@FlowEmulatorTest -class TransactionTest { - - @FlowTestClient - lateinit var accessAPI: FlowAccessApi - - @FlowServiceAccountCredentials - lateinit var serviceAccount: TestAccount - - @Test - fun `Test something on the emnulator`() { - val result = accessAPI.simpleFlowTransaction( - serviceAccount.flowAddress, - serviceAccount.signer - ) { - script { - """ - transaction(publicKey: String) { - prepare(signer: AuthAccount) { - let account = AuthAccount(payer: signer) - account.addPublicKey(publicKey.decodeHex()) - } - } - """ - } - arguments { - arg { string(newAccountPublicKey.encoded.bytesToHex()) } - } - }.sendAndWaitForSeal() - .throwOnError() - assertThat(result.status).isEqualTo(FlowTransactionStatus.SEALED) - } - -} -``` - -There are two ways to test using the emulator: - -- `@FlowEmulatorProjectTest` - this uses a `flow.json` file that has your configuration in it -- `@FlowEmulatorTest` - this creates a fresh and temporary flow configuration for each test - -Also, the following annotations are available in tests as helpers: - -- `@FlowTestClient` - used to inject a `FlowAccessApi` or `AsyncFlowAccessApi` into your tests -- `@FlowServiceAccountCredentials` - used to inject a `TestAccount` instance into your tests that contain - the flow service account credentials -- `@FlowTestAccount` - used to automatically create an account in the emulator and inject a `TestAccount` instance - containing the new account's credentials. - -See [ProjectTestExtensionsTest](sdk/src/test/kotlin/com/nftco/flow/sdk/ProjectTestExtensionsTest.kt) and -[TestExtensionsTest](sdk/src/test/kotlin/com/nftco/flow/sdk/TestExtensionsTest.kt) for examples. - ## Contribute to this SDK This project is in the very early phase; all contributions are welcomed. diff --git a/sdk/README.md b/sdk/README.md index 1bc8871..dfff062 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -1 +1,158 @@ -# to-do : add SDK-specific README \ No newline at end of file +# SDK Setup + +## Installation + +Use the configuration below to add this +SDK to your project using Maven or Gradle. + +### Maven + +```xml + + + + jitpack.io + https://jitpack.io + + + + + org.onflow + flow-jvm-sdk + [VERSION HERE] + +``` + +### Gradle + +```groovy +repositories { + ... + /* + the following repository is required because the trusted data framework + is not available on maven central. + */ + maven { url 'https://jitpack.io' } +} + +dependencies { + api("org.onflow:flow-jvm-sdk:[VERSION HERE]") +} +``` + +### Gradle (with test extensions) + +```groovy +plugins { + ... + id("java-test-fixtures") +} + +repositories { + ... + /* + the following repository is required because the trusted data framework + is not available on maven central. + */ + maven { url 'https://jitpack.io' } +} + +dependencies { + api("org.onflow:flow-jvm-sdk:[VERSION HERE]") + testFixturesApi(testFixtures("org.onflow:flow-jvm-sdk:[VERSION HERE]")) +} +``` + +The jitpack.io repository is necessary to access some of the dependencies of this library that are not available on Maven Central. + +## Example usage + +Check out the [kotlin-example](../kotlin-example) and [java-example](../java-example) modules in this repository for examples +of how to use this SDK in your Kotlin or Java application. + +## Integration tests + +Tests annotated with `FlowEmulatorTest` depend on the [Flow Emulator](https://github.com/onflow/flow-emulator), which is part of the [Flow CLI](https://github.com/onflow/flow-cli) to be installed on your machine. + +The`FlowEmulatorTest` extension may be used by consumers of this library as well to streamline unit tests that interact +with the FLOW blockchian. The `FlowEmulatorTest` extension uses the local flow emulator to prepare the test environment +for unit and integration tests. For example: + +Setup dependency on the SDK: +```gradle +plugins { + id("java-test-fixtures") +} + +repositories { + /* + the following repository is required because the trusted data framework + is not available on maven central. + */ + maven { url 'https://jitpack.io' } +} + +dependencies { + api("org.onflow:flow-jvm-sdk:[VERSION HERE]") + + // this allows for using the test extension + testFixturesApi(testFixtures("org.onflow:flow-jvm-sdk:[VERSION HERE]")) +} +``` + +Write your blockchain tests: +```kotlin +@FlowEmulatorTest +class TransactionTest { + + @FlowTestClient + lateinit var accessAPI: FlowAccessApi + + @FlowServiceAccountCredentials + lateinit var serviceAccount: TestAccount + + @Test + fun `Test something on the emnulator`() { + val result = accessAPI.simpleFlowTransaction( + serviceAccount.flowAddress, + serviceAccount.signer + ) { + script { + """ + transaction(publicKey: String) { + prepare(signer: AuthAccount) { + let account = AuthAccount(payer: signer) + account.addPublicKey(publicKey.decodeHex()) + } + } + """ + } + arguments { + arg { string(newAccountPublicKey.encoded.bytesToHex()) } + } + }.sendAndWaitForSeal() + .throwOnError() + assertThat(result.status).isEqualTo(FlowTransactionStatus.SEALED) + } + +} +``` + +There are two ways to test using the emulator: + +- `@FlowEmulatorProjectTest` - this uses a `flow.json` file that has your configuration in it +- `@FlowEmulatorTest` - this creates a fresh and temporary flow configuration for each test + +Also, the following annotations are available in tests as helpers: + +- `@FlowTestClient` - used to inject a `FlowAccessApi` or `AsyncFlowAccessApi` into your tests +- `@FlowServiceAccountCredentials` - used to inject a `TestAccount` instance into your tests that contain + the flow service account credentials +- `@FlowTestAccount` - used to automatically create an account in the emulator and inject a `TestAccount` instance + containing the new account's credentials. + +See [ProjectTestExtensionsTest](/src/intTest/org/onflow/flow/sdk/extensions/ProjectTestExtensionsTest.kt) and +[TestExtensionsTest](/src/intTest/org/onflow/flow/sdk/extensions/TestExtensionsTest.kt) for examples. \ No newline at end of file From 36d8b11d68064f2772352c8eeef0b0f65ca70941 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 17 Jul 2024 14:52:42 +1000 Subject: [PATCH 32/72] Updating documentation --- README.md | 2 ++ java-example/README.md | 4 ++-- kotlin-example/README.md | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7abf31d..564d658 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ At the moment, this SDK includes the following features: - [x] Marshalling & unmarshalling of [JSON-Cadence](https://docs.onflow.org/cadence/json-cadence-spec/) - [x] DSL for creating, signing, and sending transactions and scripts +## Repository modules + ## Contribute to this SDK This project is in the very early phase; all contributions are welcomed. diff --git a/java-example/README.md b/java-example/README.md index d2112ae..6b8f50e 100644 --- a/java-example/README.md +++ b/java-example/README.md @@ -1,6 +1,6 @@ -# Java Flow Client Example +# JVM SDK - Java Example -Example Java application using the [Flow JVM SDK](https://github.com/onflow/flow-jvm-sdk) to interact with Flow blockchain. +Example Java application using the [Flow JVM SDK](https://github.com/onflow/flow-jvm-sdk) to interact with the Flow blockchain. ## Usage diff --git a/kotlin-example/README.md b/kotlin-example/README.md index d2112ae..5df32db 100644 --- a/kotlin-example/README.md +++ b/kotlin-example/README.md @@ -1,6 +1,6 @@ -# Java Flow Client Example +# JVM SDK - Kotlin Example -Example Java application using the [Flow JVM SDK](https://github.com/onflow/flow-jvm-sdk) to interact with Flow blockchain. +Example Kotlin application using the [Flow JVM SDK](https://github.com/onflow/flow-jvm-sdk) to interact with the Flow blockchain. ## Usage From 93fe558f818e70b8c4de4549eec28daca58bded5 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 17 Jul 2024 17:18:23 +1000 Subject: [PATCH 33/72] Updating documentation --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 564d658..1707ae2 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,17 @@ At the moment, this SDK includes the following features: ## Repository modules +This repository is organized as a multi-module project, consisting of the following modules: + +### SDK +The core module that includes all the necessary tools and libraries to interact with the Flow blockchain. This module provides the main functionalities such as transaction preparation, signing, and interaction with the Flow Access API. + +### Java Example +This module contains example implementations demonstrating how to use the Flow JVM SDK in a Java application. It includes sample code for various use cases, making it easier for developers to understand and integrate the SDK into their Java projects. + +### Kotlin Example +Similar to the Java Example module, this module provides sample implementations in Kotlin. It showcases how to leverage the SDK's capabilities in a Kotlin environment. + ## Contribute to this SDK This project is in the very early phase; all contributions are welcomed. From ce46f1e4b1d8f9c2f55876e869de1af37ca36166 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 17 Jul 2024 17:34:10 +1000 Subject: [PATCH 34/72] Updating documentation --- .../test/java/org/onflow/examples/java/AppTest.java | 11 +++-------- .../src/main/kotlin/org/onflow/examples/kotlin/App.kt | 1 - .../test/kotlin/org/onflow/examples/kotlin/AppTest.kt | 10 +++++----- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/java-example/src/test/java/org/onflow/examples/java/AppTest.java b/java-example/src/test/java/org/onflow/examples/java/AppTest.java index e62b5d0..19a39c3 100644 --- a/java-example/src/test/java/org/onflow/examples/java/AppTest.java +++ b/java-example/src/test/java/org/onflow/examples/java/AppTest.java @@ -5,7 +5,6 @@ import org.onflow.flow.sdk.FlowAddress; import org.onflow.flow.sdk.crypto.Crypto; import org.onflow.flow.sdk.crypto.KeyPair; -import java.util.logging.Logger; import java.math.BigDecimal; @@ -15,7 +14,7 @@ class AppTest { public static final String SERVICE_PRIVATE_KEY_HEX = "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17"; private final FlowAddress serviceAccountAddress = new FlowAddress("f8d6e0586b0a20c7"); - private final FlowAddress testRecipientAddress = new FlowAddress("0x01cf0e2f2f715450"); + private final FlowAddress testRecipientAddress = new FlowAddress("01cf0e2f2f715450"); private String userPublicKeyHex; @BeforeEach @@ -29,8 +28,6 @@ void createAccount() { App app = new App("localhost", 3569, SERVICE_PRIVATE_KEY_HEX); - // service account address - FlowAddress account = app.createAccount(serviceAccountAddress, this.userPublicKeyHex); assertNotNull(account); } @@ -40,7 +37,6 @@ void transferTokens() throws Exception { App app = new App("localhost", 3569, SERVICE_PRIVATE_KEY_HEX); - var sender = serviceAccountAddress; var recipient = testRecipientAddress; // FLOW amounts always have 8 decimal places @@ -48,7 +44,7 @@ void transferTokens() throws Exception { var balance1 = app.getAccountBalance(recipient); - app.transferTokens(sender, recipient, amount); + app.transferTokens(serviceAccountAddress, recipient, amount); var balance2 = app.getAccountBalance(recipient); @@ -60,7 +56,6 @@ void getAccountBalance() { App app = new App("localhost", 3569, SERVICE_PRIVATE_KEY_HEX); var balance = app.getAccountBalance(serviceAccountAddress); - - System.out.println(balance); + assertNotNull(balance); } } diff --git a/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt index a4eaf28..a0e2bcb 100644 --- a/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt +++ b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt @@ -69,7 +69,6 @@ internal class App(host: String, port: Int, privateKeyHex: String) { weight = 1000 ) - println(loadScript("create_account.cdc")) // create transaction var tx = FlowTransaction( script = FlowScript(loadScript("create_account.cdc")), diff --git a/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt index a3f6402..006f221 100644 --- a/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt +++ b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt @@ -1,14 +1,14 @@ package org.onflow.examples.kotlin -import java.math.BigDecimal import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.onflow.flow.sdk.FlowAddress import org.onflow.flow.sdk.crypto.Crypto +import java.math.BigDecimal val serviceAccountAddress: FlowAddress = FlowAddress("f8d6e0586b0a20c7") -val testRecipientAddress: FlowAddress = FlowAddress("0x01cf0e2f2f715450") +val testRecipientAddress: FlowAddress = FlowAddress("01cf0e2f2f715450") const val servicePrivateKeyHex = "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17" internal class AppTest { @@ -26,8 +26,8 @@ internal class AppTest { fun `Can create an account`() { val app = App("localhost", 3569, servicePrivateKeyHex) - // service account address - app.createAccount(serviceAccountAddress, userPublicKeyHex) + val account = app.createAccount(serviceAccountAddress, userPublicKeyHex) + Assertions.assertNotNull(account) } @Test @@ -49,6 +49,6 @@ internal class AppTest { fun `Can get an account balance`() { val app = App("localhost", 3569, servicePrivateKeyHex) val balance = app.getAccountBalance(serviceAccountAddress) - println(balance) + Assertions.assertNotNull(balance) } } From 5dabb4d5a27b5a29bcc1e8967dda0ca0b24f0ba7 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 17 Jul 2024 17:38:19 +1000 Subject: [PATCH 35/72] Updating documentation --- flowdb/000000.vlog | Bin 0 -> 479980 bytes flowdb/000006.sst | Bin 0 -> 99729 bytes flowdb/KEYREGISTRY | 1 + flowdb/MANIFEST | Bin 0 -> 124 bytes 4 files changed, 1 insertion(+) create mode 100644 flowdb/000000.vlog create mode 100644 flowdb/000006.sst create mode 100644 flowdb/KEYREGISTRY create mode 100644 flowdb/MANIFEST diff --git a/flowdb/000000.vlog b/flowdb/000000.vlog new file mode 100644 index 0000000000000000000000000000000000000000..41505512e5894cc54df6371254a852204a205692 GIT binary patch literal 479980 zcmeEv34GmEm4DK*)FJ{dEQaN^vZToxQ_h4-*eaB@{*Ud&hq)2kJ9A5 zd(S=hoO91T_ndRjZD>$GH*~Hz>>VR_yzyh_fA}k_8!npFkP9-yL2>)8bgmL?9~j#{ zkSh%C+`c2o4(}*$F9yTeQW;-oGYeYN!IDLT10BJlp~da#&eIomF6vy|G1S@K(bmz~ zv1CE(BtOrppTB5C4!E!P@sF-<=$>IP7~GN04+puzFtP9leyV!)&R;$6)DJ8eEM$U~ z^_Bc^b|4q@7j_1D1M`Z8-_E=0R~cxN&u3_9rXy%wvb1BMHQnAi*txKMad-yp znDZn*X8eX%Upao&mE(6+&;0$v-#>rn?9`XD>vM(O>qZNMI|}xF2={ z7kvJvS69)kVXLOctlwSx(zf9n%9VHO4;rhPN+p{azxjo$A9&4|RyQo0oKj}+1t)44 z3k`x-G#qmK6ECa_$WK1>sb8(UzW)st$bk;X@9uub37~Szex!0+`;@5s?dtf0N1urQ zw>C6wJ!pRK|EXU8OP`DlhI|cOf4qJdaQ#!`enhrCJtbsoiNvTAiS9yvC_CJEVAGPz zetCUy?hB6bC)0eu!e2f1wx7L!^Nmmc=-KikU%2zukM8{Z?YF*k%@_agRZpLG+_SHF zWY_y|Ir{S-zRpbg!0Mi3*PngPd8NweXl|@0KUfUXrJy$`4hH#hdN>%rbLSk4ym<2TQ)7TlL2*5fxH zov9YxhilI|qjv`eL)#BsH0S@;C2BEwJ=KZYzkX}}BCv18e&pVww)(ku`4!{e8h@zj zFX<6-O%v?g?n>yd7X5j{OLUfv8ZFHV{yllc4%Gb*|v^> zj?+7Wj?N{m(6~dLOFK`mdol|bruuheOR2#^zFbTXmQ$HvD4P#TsdPS-&4c|z>A@g1 zR4AtM(6Ffzcn^`7jc(;as!|I0&p?n$L+kR_QFJI2M*wbQq>z{95S0`FLsUw$*{`RZ zO6N)iH&Ch=lnNDyVXC|%jW5+m1%OIJ3R8pWJiF#!; zj3BF#RUj}h`xk>;T9DY09R(tzl>s+rEz!MzHkP_%_UsgX7NXB+y1aw4QiB3=1T}-W z?U2oL{BSFfa&WLv$(IF75|Ehh2y&R9bR;Udpq$zW0-vL2-JKpy4`g%M@>m}xjMdo- zRF1=+ekV=bFO7Yd56Zg>MbR>u45(bH3B|QM zD;TTUN>kl$V#mzpv*l(Ach6eWe}2kCZhp#*(G|wV|H^|s-^ji9**s8(vo}AZkmB$s z{6?YCk_+;~p!uroKJSZVu_4Ud=4vuQhh$QKmfDftWfTRM$6%pY z#15n96GnA!2x=+F4Yg!5sTGj_OshEa}g%Me_94;uLUx9y)rIgAOg)NP_ z4)rp1BT}CFq+>|qk$r4GR8fjM*cj{dXnrzYVrotK1WvWCm{pul#F9}m;sv-YWF*N-q!H~#Jz_kR6 z1B)P+E$sjsv8G^5MAplhgmdvG{&SbdwvED??Vq4!R zt)OM8t~^W+?(dgG;O|XXdBZ`Ot!M>A;LTfR?SoY^i3ls~d_)cAxzicTuLRoA*i%Lo z_@M15rv^dMk#ru`Q6kww#HWTFO^=bIBQO((7GSf?<^>~i0Y*?cNM~q3Qmho`)Id~; z7Rv}NlqjevdKy)I9A^PX+K?VCDFd-*t;j?=KW0?4&>%Z9nhQp7CR1ivWJJez!JWb_ zLP)98a2@?XADU5WN-&^{P)wX1-8eArL_F0bEgbCF(nM?8SR=I&HFIO0m)eys8u_Ix z!4hAVxtMLvvq1&Up&*0&I8C>=b(0F3ngWC}vzXGk%3qVZ@#OwO0W!N>1twt@nMKi|}EB zK z5X3h>7|f+?Qh39(gLlh(poCbQUqppJTwG)pj__KnjiV)7ws;%j4#=Q=9wVnh+*J|k z)~19JT8lvJ)Y=VyaT%UIh{0|xBg(Q>-nXzc3K%m7dYa3DSewc+YKB#~I-(NPHy*wk z*X0XRs=p{75+W#uK*>;gb;Ndj(fl0goQWK3*x?saUBQxENR?+ATVhNArq}w|2nADy z$1M(I@9e#^+gq=GYIVbk$wM`>f3p0OKl;t7V+$0t?bxZdw>t0l^2hhQXL>I5c@D^~ z53jjmbwlUm$XGwTLdHS(#EcK!IXyH^c0gL6x#XDD4O<%3bID+)eet3ttsP6+mJYVHcPv`EWQZZi)=VZC$fPrE ztpjbHW|W5A%?F>ix*>Z^{T}yNJhP*%J?LCiw^v<<*iQJh{^cMOx4ZQNJ;x$)@zBir zahWt0**b;z>U~!}GrKAGt0hZb72No`hWlKIvGSpPzHXnV*093#)cNa^aT6Z~M=W_RfFevLE%m^s45z@4RmLXI5{!zH7zJ7hL+t zZ|*yG<4?!_rD^StA6W71m1{rs^lw{NnXuFBYB)AB{%o%iDNU54suC~JI!=e57+H$7 zJiS_EWTj_qKjRI$L%i9ss##x9+?5>+x|H`l-t4*6+}Uyv>KBVU-e5^}n8P7@760z= zs*B>rznxc=$gXzRuMdJ!yv=NdC4KPp+ICek@Bqe}HCA)J_K!DM{IXx)`|If~#3r*u zu4wq|QOEX756DRl$ZKEyB82TL3-+UMw|CUP=n!aU2zVAEz-Klz-Sd%^mt6XWC*JJ` zOCN{rH{XxH{U7d~9*`vt$TyGv(A{-y3uf8qTn8nY4$f=Zw*Fyy$TT`2 z=}&HZ_v(gA&Z?h-L9n!a>5`6tq4w6GJ)Q1Y*xA~-q@%rK5FyD$ONJ1dT+(^^lA%Sd zb@TD}J)d5E$8TTv(Bpew{ABkxA*#nuw&);(J7ry(`FYS6D zv*6OhzW(kX|jW*M9kq*WY@>-rgU*X!)C; zUUS6kUwraMH@qTv`(3@WfB(S;UUJO#Ma}2l`t_e4zh%bx@BX)W$ECmh;oEk7`?U8r zUiRj%yy)k1{k>09GALt z>Fh|5i8njGy61R}*2Y3!oj!qfI>(c1DH+9+s|AbV28r#);U4BqqXvevszrj4w5<{} zc#UR9dd2Cll>N5Sq|J`dBO7T0ERt9buOVxcnT7*X z8!7RrL7Y>n5)g-vZ(W{8qRekSYL(9IuF)M&Eb!v9zBi+vY z@ELc!cXdNyU$mTWRo1^#sT1bGUp(fLr>B9v<1 z)qC9C4PXB9Cwp7AjDIuN^r@en(e=IiSFHH#ntvU41~f4T0EAC!%1(;vH`g|@Fgak=3i9ZRmo?O_Od>VK|<3PK&vFS?Pe}c9M$Jn zchYxRbCY>twc^phxh+AfnG?{eRI+4(rZ`(t(8k|>ch_Cj3o|>7+WF0=e|X<*s~gr$ z!JViQ_NfMykedj`ezr0bfm$y>^c^9&>+T8K%)%|1>6F^;5Hi zl~?6k%BH)2x|r z`<_0zOqyd194pEf=U}|$g%^AkwEyD%yeQB*SpNnVQ@L`SG?s(%Y$kj(b7s>g{_@Oe ze|X0=fAIzH=+j;`mi|?C`BOI@_Uzu48l>cfE1wD?9(=9)D#wpYX;1yyS_4-}RM~Z~VXSTrvBkSH16h>r>CW>K9vo^v{di zI$DFde;hmfeJ4NXi;IjhJFq%_QzOo}4Gl-$@ulP6*nmH0%AXhA`rzBY!9Ncizk~nG zkWUwky?RB8e;y!zKKG1{Pn_TI=xe_8FQCiK`;#tVD@pcC5JN$^Ub+PmNi7 z&pP^+KmMh4v6*dSbu@b58UJck?2m~~Lf@(WCgLXw#Ul31@goy0Nr$N;cHB~^Oa5Xm zvzbo*dLRGy)9M0Z*OjsE4{|`R|5hV(0MyF5uid62no>EO7$Nn9+3g6AxYX`+wg4q%RYPpC6>2`tGr>efpi7?tSW4l@n(f z*_dH&=^auX9!TfXNEpz%bn8K52m?;%*530S-7i1?^=m=6x9!*Cz&`Iy;ow|Cybgzi zkG=XM&p-4HA1e8KQ*Xn@SKWBq?wh-3z46aqC}02fubz7T5g+@^)m<-q#ZgyhF6uer zUrL*X`qz{{@$Rv?e_C|Z9cN$two`xe!H-<_C75bE^S?C2Ww#Rm(W+XGba{0%a2t;IqXfkIi+R@ses;&$CdBfG!(?fC9En6(+j6@kdWZq=)ikc;9z? z_nUjBC)y`E(Qf*~4=-HZuyk^jV)arA<}k>q6M5e0!bQ+Zykl`O7ov0%AqbY>mgcS>Jo`r*Igm+_pdMnAUx{ zXBRmgC>|W=!~NR@d!^>IAKw^`9_b zedRMVny&xOkFuj5SoMt%&(HbP*WaCe-pI-SwDJB$e|%ZVGiyZY%ciqNn>EVh&2#&Z z&sETbR_=GJ+R!x=sBK?eG5)Yzsx}tu$a=q?LnZWAiym3|yJgHx!C;Wx6%@7o{Z~b1 zR~Q*H^tb-_yKQwT4ZGpF!Vw%UR#?blU!r1hH9Pco>cEI4^6X1bdiL-)J9Vb( zY8+YJp}idm22|(zgC6)?UBr#C`Cv!f@7_N%2jX6_U-yQgsVsc$&5tiksX-xYs%3+O z7D30x$BuaL*QMh>7g7<|mAf}*Zn=2y$Im-z+w$e>|1Czx!>c2b;U8qW4OhS7V>izANigfk z{uTEZXMOrkGj;fKaRqe?OXiH<)X@0CUz|L()YI4`r~jlAOf8}47- z&;)TqGIpsPD&}LPvf07yQ`g z$Kgx=a_MIuy8hN#55Mn}H~eDOGv70OnGrpMo5T5`*DQGgJMdJZld(DPOLX$Uj~;j9 zmbz>RvoGqraKp4K8X7-z`bWUEi}$0^)H($=|J@0X z-}}3>RzniH_NOFFt<)7`MF)>Rcu-^0PlwNb-l5-l>N20vv)(iQ=xd(-)^mSg7~EJL zztR@Q`t4)iecBApyy7NnSUEP0dVeOUQ7(L+q3qOBD;h76&eE;UL7kB(_#lh$Q;HJ(I!^%UdBg*ll zw)zCaskJ&R!6n$}H#by=gM5Z1rBE$uu)0%qE%m0eNYcubaMf)5+6$@|4aTZb#iCAa zRpf9X4M4$as9rdz3Rj)moq$5SmocM9e@-T-0?&oKn^^pa^Pi{ z@cz6@(Ka>Dk-1CJGFdgf*5j*7>N*ZtX)&kXcDE6~bnG{8#eDZ`s{T|Rq&ii9bmo(% zuWmTgdSq%=pS1I_(dJNMp%>(8dH3(Xyt?7~#wR=WL4aM^x5XW}Uoq6#Ddpd6nN57U zWJ=rI!c%9bPDTBm#x@eq|AfUDrzrH7lWqv=7^DMRraRGxw_ z5M@&;qr4Zp1D9n-kk4+Lq9B}9-Qcx_1lo<6a)1S1S(RgOgP_!0 zZ0^AJf(Qv)YPv-)0RHHJ|BYg^OJyo7k7g*VF1sQpZ9XT>tHpBrwNPv!oYDIFaV(MpxXjJM6a<62`DQYN&1wMQPc1`mFF}fi zq=#8CPH^x#gOwZvCEHxR>&}rVO#hS5Vvq@ZC$ z7ICXut}^4|`8w1Ylfr&GP??BwWO`mWh?LSY z6~_=6=#@R9EM)T-s1(eH_~Qp)P{;tEvj!7u3!x4~sl>i0g)x|A;;hxtTogurq&OYH zJ2(Rd!*mQ#C5MFWlSzz~c|%}Mu?|@YQ+o>HVyQ1mEVOoV z$Ot9{xaL4hEvZ)cw5m5c9SYFbabVb}Npt4f&0=%lT>LhoeY7IfvPx1-Du$Lw+}s)8 zH#fGNUX%qS)kS2@%Fw_EL2{Owq&yHAF1bFJ${VpD90M_DRqR#rs_kfA?NG>_T1P@L zLn{np1kA1rZ&jN`VZ8RII8|l3G192;pmcLJ53$l=U=qh`$$(kJl#-> zJZ6`xIZh%Uu`;0qo6P}#9@H2L@6OYe*EGXbZ5(a)cj;7QbHY%BP+Gw(YO4#NADlXn z$pDu5H?>I$ft6At$Uu;yx%M0AYN{_|#Zh>$(_#Mto4P9vPl=%t+ zx3;vVs9gl8Q7mRZGX*HR^)w({day22O<4besyM2^s<*_c#LY-6IFO7%8GyjCYP9}x zQ!s@3Gbk{oiPjt*!*PUNThNZ^s0!1|Po{$KY~o8owyBAaV{ z;8oG0=I9dPcWy2Y@fQ87omzPHOSLscDs6E_+k+Ga?s8S^t)^&tq$N7oYtVT)MIdjM zB$S3!jdP@COY@<}fxW39hYIN7^$Li(sX*Xz#C2f-xsjaT^oz%5cxM*-tECxIM30x9kCV8M);91H$MR7Mo znQ$dJ{94kZqp;ta^(SLd`g@#pokZfT>PvM=jGXv$MJi!e1cp>?N>pJ++o&bkrwlNQ zI#f-#a&v-)(c-2WCP^D{(SYgctWWTgCS?U*rtM8BI$JX-t>n#g`o-x=QJQp8SkzNQ zn|m_#ICOhV346Q?HSrV{oA3v-8pSDzuh7JgOSU~*Rm$R&;N}A>g-(Zo^x#e~AFi_t z64`kHG!$Om!TwZEjb($Jcw15>7~&d&g&*Q+YT;CAA#fZAv1Q|22Z8w3la98lY_rbp7xkb3sZLs?q-%dcb+rA!WIY%pR>nx$96`oXdWZ$(1wq}? z!=srBm$g@M(_ijvx>ZRPz%5e^+-#m#C|#)dZtj8{Kxo`{?l?Y=OvyX6ZI z&fw^boES(6IBiPd<0##dvXkcognG(`P3!r|fg75)t@Tw4I#9eb3lY=w*4v$S@!^2U zHttyk&Q(uv!vN+yl(ft-@0M0aA1y+*TUaI*rp^c;5OdSJ;CtiE^SO2hp4brUr>$i= z**?IS&N*XsDZi54KePFYuBeW7#-i7+X^1C4Nf~X&p1+ngr%@9tsmtKMMwvgc%?-tm zmA#TIv@~eUZ`CRSlhx2cTxsk{f1-I5;U`4Vdwz! zwGJ!LdC0J+q5e=oGf5mQuB44^ptpG~(2+ewSpfB`C(_Yugqz{}p63=0q!13r93EG~ zqGRFV+O}vZHVun9HZY6%6m44DPtUmK_;xVQ-qy=rGsx2uxGfKV(_p#TfLAltUDa96 zv$AC7MtRgo`-XtGVaEl*+EUO<^!K%SmXAem5-O^F8&D9LX-(}Mpmk~#qQ*eeQo;<% z<3-t*g+`xO$`u(6d8P2k1l#dPaL_g{sIn_)UcNy5WpKrz47n{Ist2T@B%lnH_WosX z!{tbAdB?t_d+wLqmg~ax7!&GKnT&UrYLak`E8*60av8^Hq&$?v8>(%1Z{Lwf=c?l( zp~+ZX5V}a1-f#y}xu$ce?hqG-e*D^-rdb#8Xb0rrZ1GRi!{k^8jAqH zH`sE8bmq)-X~+4gOJ3Uc((#pz-(0@-A2&SsnY+3=fBT$6zS_F&(aMas-+AOu7QM4z zK=);b*PH&y*?mPC8x~d469GZkG0xvq_8^7YCZ%==3pI5)U`nDLsf9L{~R_s{31n+Fh zZ&RsHi@?8VHvg7!D)R6I40b7akLOAd-RH9|ysAjsRKK}lJzfKqF$MzEJ!mvg?-!US zij*-|O6P(WHR!rDJP7JjT|gQJ5qu82Tu8$@H%6nmjAx-x6v;%=acsdOA}UC&5fBw_f)L7#q=l-m3w?D;7~w?G8`4`YfnDW z=1bdPkA5=(wlfwQJn`ua>d4|efqohx%O~uQ;9e!q&r`u3Oq;Pbeoc!dAX`RosK6Em z;+yEVgm`!1L$<{hoh@WEA4#ZE05ZcNsvIy36LL0c2oPRTTz%PsL=$}Pn#u$)%uoe^ za~_%lIpL5&ns+9)S^x zWJSh4k7J}mG$TP(_45q!LGY-MAs^@{FXNc!gp_O1OaTMHO2cauW!#5K6$UQEZ}7LB z?W!GbkY{s>a)=_wN_aXB{L;)(vc;M8ll{6hJ3Vb7?&{VgH3-7R1s@vHHH_v^t}8@| zeJBaUwelUtLM_!~s;UYj`Z*gS6*gDHMTiCN?EII_#I1+!Sr~FIMA1-vKIP;$6abQZqlze2bpGQ3`h&)!yH;8 z{Wgojzzv={dM0)~n~&R>2WR8s&4;d`ke34G|1>^IzOJ}i`kGJl%TsPjYQizZx=Oq} zADb8e;$!#Bqq5EGfM&14*8ws@0iPvnm5NRvIkL9GE=|QddNf$hj2cNPuc)Z&y;#<} zgwHZU>3t;kGfgxI_4OYP${u)Pr-QF>z(IqsDGareNV`Xj<^0tm25A(CvF!nxVC6 zrB^gvR3ggg&O&@jW_+7NA}U+B_l2jj6~PYRqlgsRt$` z3u>i?C*#PRO!9uf7a0<1_8@DEORNbcrm`rE9Zvcx!8GQVHc#!0V`gg9K;@DLnH&Sf zEFguaj^JMdBM*e6y?Y1C-wy_cYdbXN`)^OgS`UyjmZ5uJOI8W7w zY`_+5=Z7!IoRC7(IkYg!^tal=l>AmaGRj3{3?c`f|`vvyhcmgDUFeL@Gm8g(CX*R2R+(nj`{C zsIDwwA0V)hQRXv7f-?=z3pf`l62r*l&WN+-?(|s6y$I=FV-kyy@jN%C#ghzBsEIFJ< z^odFJGv_HK;vCRmsTO+3%nM_6&Zs`lx`Ien&RbIFG3&aJ$9W$t9>O1FunGbZVM`S9 zm$WmN6DSa34(fO83a+WajK(63@Sup-su)cUoBJ&epH&lJY;DK=g*4OId; zTy30f6Ax|i-`629(Lj#4#nqZ#p#;uiMJCJFj*j%RM_-t=bEEd}QhXm>al~NsW znJ8^9WVGXusoatDX5GvaAbA=Za0`>v5_zJ#seQ~oX{~@PU%-D|Y{Q$9Qj+?4S8w=4 zIQ1YyLr5m364ac$w3sBZIgUzX0Xw+xAQ82JK}#F9GeBFy1cHRA$%PuruoLX!(=X>r#(k;HWM#V^vR4iDl+;CdNR|!P#dYC!*wSs-PF~+o9q_r@eRK3M%nh;J z6KWC&{>5?xAAT(4@<&G&&_DqDIZ#P<-dFQd3mjsbNAV5;hEKwS`ygZu4>!V)m=LLH zEe??$>PWi+43Dhj7zT$3B2zW{^JY;&A^SG5la;Hm%zDH%dI~=LH98$4DeHm6u4lSz zXMXhHcAk2b+F5y6#N>8@iE5dRNJCr_;=-{2+8AenBm|%(Jo~NWm26E`6W#EXLVonc z4>1tqYp_ksiKd9{9Veuy~XEl6cTa`^pQlLMzERlV3zcR^)L>&L6K&o_j2Qqq|b z^9M$R6&5ZRQ`oW+I)*=ju@&h_PO>X;#EFrE!Ir9F-SS)e+^mC)$HKKIme9;k8UdVQ zx!Kare9nKp(OMLpIBm%PX$fC-7f5Vs*Q9SZ=QXeizOB(!6?njxD%Dd(IeawvJ0jbB zyJ1;sZnp~76}6|_mm|a|L0K$eW5U_J9h=;)YytUdB$vwH8we2BF*-UQ-Viuq0xgXl z>S!J!tEcz~pdP&%>dy{ZT&KQ|)FpTc0tJPz5j265p-{{;XEXZob%+?`X?_Z!S`eKR zrqs3YPbvwV)Qc2U9h>l4#J=;Jqj*PRQ+z((h+MdhmMUqWXk+DY8FpLsQG`_qYzom? zRHoV%x}w38XpACJcM3VydNbk5Bg^^YNdfg#RN>j9%T$yd5Bdz^A(p4FQK?7m)m8MK zwI(7<#H=>87!eIXz*SWQC!yzgPNiE)`yPq3F&E6LvRGO@(}$Vo_QiG62r2ybGMTn{4t=FbKqo7h}rcM;}_yFp;$d&F`6SANvxqhP;eNG82UQ# zF4siBIiU~)HZ}ufbD^)?bzDzp8`cy9)TsyzYbJ3tMrdg0HvwNlh}h#CNnpRJiIfvW zIpHU}Ky9N{3CKbIL%7+-A{QW&MZzQB@&)K-zldF5lQbe5&w)`c={vB^#|8=+Y*YoX z0VPkeFeAbNMQ(1|SjTyhiDt@$O-`7|3A({Xmq1Zy zM&8F>1cEG!q6sx%Oh}XP<;8X;!&LY7B0Gs#+Qkq< zo1$iJvX9gUY5nG@IV%`U!9jHcGgC)#{>BkCE(%`UJ$&}`)ILQ{c2TonR%k_O^&kcpLmMC5fn^gbdT#Y-y2beAc}p<~&{;KeE*e%P_o}bws$D zeWbJr<$n}0*?=fxmt}*f6`y^^H7rLVbi-dwjr9ysn?Af*IBMm)53lfrpqCl!G*7D?aW&Ez0Evg;rLV2D<^p_?)G-v2C>mD zm9NWj6)wHuZwlT*$+PxB9ORB0lLZZV4TrHF@!T9Z2WC1QBiv& zQ+rz?HDd#qSRGd{eDrZVhVQ`nCk-Wt9|FuTm8e|lpDoi>x*IIOQjbfrzO;5YB<8x#-kq$~+I7gUKfxVK6!7chmGHjwbYGfC72?q^%SRy6>eR4uDeFlWRFSGMM^K9SH z>@-@TrVhPTh%eaY5d|=jaOlOjZ}Q;*nYxI$)fti!`|&gcCDOMpS&8(SzPN+|O(rq6 zb;#ZJUm!Mi;98R7iM4I}-yz?2@Krj(78P?X?bKUQCs9OkNl(rTX`mHcu}#!T^p1+c z6v6W{d5s|2@nbF{e{F`7tJO&T-T~S3!imdhN`0focCzC1L0YZiTthv1oc~Z{T$u(bMx6vWP#zbTvjTlrBBs{LHYN`KuS*d#jke;RvaGSg-Sh0o+lMwTij z+M-Uz%4BOs#ZD{uT*eczF+$?y3(O!bwa%rJv9%&OToN$}*NWg!G*jgMKgZTR_5HBpu)Z7r`}|;d}@IoZFjnuu^&jD&rmT4aQ()} z0*k>}wh6UHDaSJI++~GD)q@+$ALA#8e=Z%)(~@puy0P zDs})w%HqUF#(1P6iA5?TOJ+uY(;t)TfEu(ivSbOIq)-e;eJ5&0U92?yYsjL$1Hhbr zqaJ*}gB{hdm6d96XFv%)l*#|sd%{Hep(df~We7Y7iYLkss6z-e9q>MojEvPO67u-9aA@N{B?EFp%f5Go12GFlAzB4?9oLU=S-B(XGa-@t#_@I1r9f@nIFdFp2^GaW{qZ-YAI%+) z>Wc}DMy@nN(!8Q2)4HNCxPbd;rWaRPi?Id=wC0Hzr`Fz#MwKFi*EBn=&?|$+Y91Zb zQ3nhe#hsxjYU=*Dk!=-pHDfA)wjv%Cu^M}2@|B$bzf@L~i}lD$4RRWJ@i|X3Vgh%O z-d}jiFn&zxk!ZpR`5E&dSvQrqi4vSh;RZN;TKNb$Mwnve&-#ClA1UsxnIOH$7A=mQS{8*?u~DjRtSueFrEJ=_$`uY>Pu&xzNJLF z#zL>lnY6z>7;8J=` z+U->uO=PcER;Yae5>_wm)`|Qe(S=`QJNp~MP2wyamb4oa7+!;RhT(RFa@UEi6FJWsrERzpJt0WU2 zVk2wcaM_IeH`Pt1M1n&5vX9&CxSnHzryeF@h!&$cH8=C~jAJCSBN(ShOpvB}RJ87E zxJbMdT8d%B?P1M_X*)P-=*YimAYTJbQ^0D_Hc7xdeMP;IaK{Ys6zUM>lD}bv2gVQff6$dLUKH$UA0lGS9l&dU0!6u%wteJ3XM! zNQk&2Z(H;)gE>q*vW{VGHpB6mMy!!j>d zQB^MNXsY<uo6?m!6?l71UE`#Y}T5_#SO%bJ* zFR^T!gS&6CirPs)-40~oD06he z3fsvG*}?MUgv>evu36--TfwmMIc8^JIER$RE+v<->efibnOHn8aZ79O%IN)HKgZG_ zvNf%4WV-hoJ;V)0DnmE2V3}XK+70Jca)hgT)a46wo^MhY?_HnJ?c$KusBsavo8jd` zu~(Hlb`oWf#{?bC_!?xGhDqqGjA&ECHkGPez|$I{E3y1=Ilq5^sR>O#}T%vabaa)Ti?KX&KSeH@UvyPZd$-Q`P7%eANmWh^Rx` z%u%ckYDF(%Cac5**jUYerGf;>n-=pT0+^hq2SJ!LJx}&WuX4Dji6_m|1LlSow%#k| zi(&18PoA&t)tscN+^tp%ggPtoldPKR>sz*4bC~f;=47$xEp{c@96}?R&`PT(QDeds zVO{JAWHbu|wiAn&IQv4ehh=fJ<;Vync0|av+%{Azj8FCA$qNu(SKSS>jLs^`n7i+@zn9S}xi3#3bs#}t%%(0k6VsEB1>$y!VDoFwqHb%{S^uM*_Ci(LT zK{<*p^Qi0ZjvQ0dhWn#d-}X`z6r|oSl!>I|Pu07IJ(x#DFjXL!o%!kv@{ClSJ{nYV z*UE?ibhHAcj3l!!sL$eZ-7E**u=Mk~qsV^C78ZD2$*nKm)6+Y4(=)S(w>IO*aM2A% zv^tDiHF+6YS8=f0jlG>32Q5M?CL*gLa7pOPXL-GG@f<5#m0_(}Gp_yybA=U&ywP=` zhAw3@IE|ustl1JKT^u{1o(FUyvU$y-W9P(&q)|conwRFSWlrN0qHPlQ^)eEg7m8xn zD6GR@B;lzjqz@qB=GvywGA?HYS-YTGrMyG8^|wkL!OH%_auo%*Dy)C*WKMx9JFao`}={HDp$=T0%*P zX?E}~qS8c$+>n=Cc2E<^YZXBvNnUDTqv#!$l?hPNQaL$Rx}igi3P8(e8l~3r!>~I= z0)2~y>0l*Riotuo77J-*9@W$!gv=36G|%I@)0G?)36h$5gz)^5Y7o8$6RMZuS88=U zUm13|cw?1Xygvj#Pd>wYR5@M5*~(bTf@!?B8`DGEQf6Li)hbl>$Y%$e=XOhSER(GO z6`I;v7$Ax=MC2{gLHqK|T%(Hi&Mv-g=H06sO0ybJbQW9LcBu-m9ZyOQP(Jj-lH1K0 zVS6^Spfw#VSu{A%5iA;7+@9_{ePQ(R(Yl|Z)bB{n7`x+j?_S-oYKFlAkPHzyV2pa&&lgLuJ6AGdW43FLf0MPGKWt(Lg|uNy54?r6aSp2hM2PMk9un;yAnU1jFm ze*gZ4S6|t%RTFjAGsmsHum93_KJ`xhL1Q&jsbn+bH@|4zZ4Z44^E-Dx&Tmm`{qwv0 zit%raKUDQ+nDC11o|ka0ky3QZ!&+K{llIOGirzI`enUgUMSu9>A2FS;S@D^iUqP_6 zed&^pfuZ)+pgo=Li1Dj+QT_9I^_9=eXu9X>M~*x6=QDrnv*7UKUj3$v5B%nvF5G-? z@3vRI@7xa{Uvd1a)$#kPjd}Zp7HjI8%dDC`RTWgjJ|j@m(WX^sC1WGZ1~^vH*_p0aFp z!$nh2N3pfn+1lCOae8M*M_VV}M6I8XYMm_o?4N)9&8dphlJwGn&Y|{2!P0>xiSvavo{*%S& z$T^2y1ZJL7zp9&DHMG~y%loc;rm^YYPd(?5p4rduXt>YQ&L8~Z&eNaWJ^u>B!^UcK zR+>aA&pVF=%6Fqvt z_=88Ed0^A0pZBGmN1geX+kIr4k3HpxKdrju&=X$v?Ne_*?{jbY?FYW~(W^i9zSb|_ ze*Ew^estQSJMWr*&c8jUCw+vO)PdFUn`VsP@#2|B4&Q}e8}Vyn!^<1+>jC_AN7Lo_ zbtZmo*nI#S95{YQ!%g%0Zdl#0#%_)^Z7XAK;Fao3;M=mEd3D*zt(VsI4!aTE@H^~R z-E-xt=?U;GBS2R)yy6dEf7A4U9OHnz@#w3u%~Xc=qj0nhOlcK_O}r7xdUp=x`hw!Q z-D2k1zgpxl0W`jLVd@|&*a9{r23-$^!h1vUu|R9nZ+{?YWmHLxBT#^6F&YkU&*$1U-seUm)>*M z!T7ViW-)2ieOeV>E^V(z zAQowRNndNxSIaow3}uHaMTTh1Kyj$2R!jcaI0mvp?fA_N)loUqMn;kiMn)2WdgR~V z`QGV?M~f4WcingVLv@|p3_+k>@c26N>!T;Pr|#)Le|5v&$6D)Ol0nq$l7lF0zV={o zN5^2tP$vw)jwKySI_uZl(|zz+Y!H>|-MS}wLY^IdjG1el#p+Dh0`8G@yC6Y0s;io@ z3?cKLn|div?ypNGkQZH0=lY?5W&Gd>7QX;pit0gU!KSn}kcZ#-s&Mif!gUU75~%PX zyGdoAcwmz;m7mVQ0Ija>;x;kLbm-b_Zf>O9TK!c7zC0-dC5kZ1x*@ow;bKvVP4iLs zCm5_S{u2H|yMZR=04F<|F2d7*5h1Wo@-hpP4~DbCA1E0!mQchH2NY_(58G98Vsip20?F36#Gc2o{K5}Gde za1PXDHUAh*65+rtFZnlJniC&@K4Jc_^?-U33Jzelw=cP=2HJHjh{m3AV|(SlVq;0S zs3q@u5aaC;R1(xsOiQYk2|CFwKeEiSIYyEY#;()UdBep@zR0{%D8dhw8WUwTe4kUgsLbOL z!mL|A)J60uvZ(v(NJ-M_Et=(K!(?`|FnvRl+*h`Q5NWBON#TBf4VH1bnsKtA5Ur6i zTCADOoJhl>QM(Nyr7%z>K@gJ24OVh2p{<*_Y$6RNL}gF-IaWWo?{nU-t3QJ4!~P{r ziizYxjTY2{*gLYrELWJu+mOh<;PAT0QAmE6gqa{Kwv9nUo|hivlabgn3VGg?M|~3f zZdt{WBQQO94?ak(-?D+oLhS>c5}j3&o+_3Ei5BZ+2c&)`QnrOMTzrebww)9?D-Tmb zF4oHqXppll5|FcvCE}bw+Zu|=*hv$>p6B*UN~LVO-T+vG-d`3E+cbfs=#ubhylVnd z=$i1U?HWoWB%`{Kn`~_R@GIgFlu~6t+y2uy4j6fs+v9Bn{Uico3uur+(I)-XC zc?GH8U1i2xFGmI>;Mrg*M8XTYZeka!{;CN|+e(HXvK*7BT`wB@UQiVIuoGGTGBBsac z9C6|!t!^|u#*>%ufIO_^nZ^oYNqU{Bc5keC1sCCPsU3qEA{ExZ#;?SVI}_565H7c2 zaXV9medNznw{rBbk`yO`|1su{j3!_0W%>p z5k$bVgOTce5a%_O(91HYhf*Ok0p4`faK$1fZg>$LOIsE4S06H2QL34{3jU@u@Ks#% zXeLXhCA==Y)t4X(mUv15lA`t_iamNOp*M9w$xf(lGIGg}StWnlH3uz(%+Zv?W8|pV72nv~sI3M}=fw?;GAJo!KV&653^h zHmZkuo7bx^FUK47kGn8{pwgTPN;sl8>(P@WxYjgg7l_-kcw8wo6}PYIQ^gN(qMxq;+C^7;>mZR|F0a3+8i% zW=0&nWTg-fL zw~q)Yo5X}Psb6G!XOj?rWfowJwjyNpMb(n>fN@=FZ+Jwiq|z9_9!8#pJd5r7-{ji} z8ZxR`H4_>zHH=kj9hqOeosr5tP99bKJ=+yt-QYf z4GT`xVW@)~khAXi%`J%QUEEN2+TY}1y}>Dk^_T{5bZaCJz{T`-Q>jU`szv^5!eEU} z_k8cNCvJV=8}9c*eX~wze9s-v-qv}Y3E?$X5eJKh%oYx8Ja+BshD-i*-M-`nB*d8( zFCJK2f0EC_Q&Y1~oqcLbUm#H4+|gozn;D%6!$CgabrI}3>C|wsP#I0_a0Abu!+5&)0)Q;C&~Ud91r2oF4u04RT0^l{PAEGQ})kX_up;W?I|{V_2} zs%ewiIou7W2DS}ERt*J8GJ1D-`3V}32JtG;T2vF(^w%}m)!`zQ9zt+Y5aa^OvdE>1 zcBoUVR6zCVRj3Se5ewD(L-Gsqq`{Yw0l>qYv37zYE}!#vwYExl98A_WEUCdVuX|=i zYH>Lfb1&030-|x&o=Os{3x;q(R@eZF;0I700#L5%lRCg>VM45%@~m&@!IWFE??1C? zga8x)uiLV2V}GCdWZz-0P>v5e+SY}_@F0oo^r=7`DvC(JK!&EVs7`!+NbWD=V~CAL zY63v;NgL5t{15WvtG6^u9{be=`&dAHT|UF{WKp}dxh%D{uUv#ST(xagEmY|H@b?Rh z1z!u)3Wd+;D9Wut(kZ3Ltk)EA-Wg0gg+$HMv(~(ZFrNdJq20{~7Nh+k4k4x+N$5Uj zWB;bE?*7!4u1!5%YtCM`KV>95qUCcxWF!~lhp|g*?FGtVA5(rPIncT#3{uB6u_Y^w zQfVuS_N-v+Y=OHBRmjRtuzKqLMv>NSlT zz{0z`4O>${(mA;5@SL`&q`_Pow;eO70c_M5OJ4u7^st_4RPBWl#L>DezDNoKRgZ1R zv`#!5riC2>Ctv~58agyFOca)DlS*EO6K9_?ETHIR^B4^VvqQ*+z&m90*m!#;{cnXz z5qlIja50*Iy+z{!Tmrvo%yV5*D+X?ZTY|{Of?0!XJR~LASz^9X?9|@eOfET7LLGgQaCgXP8wRycw(4KEIic)_oZul# zC0nRV5;I*Uo&XlkvY1$IN#`m7G?6kgpt_klLtDc*{2(3?3&~PMg%aQ-2MEf8h>Wr; zh5I5RWe{u_mZOxamUSN1vo)c?YEdqzbg`Ho^T1L(=xlcCV@y|#B#VVjBf2)tUx!_h z-DS<#My=qj*xBvIlf5oZgQABhDa^4LSvAE;ITbSDY@o{GijlO21xg8o7>=aNh!rrP z%L4^IFgnRDBTFbtYccYLt}y#>7$G@L<_7`fq-Bz*D&*Hk>_UjEopM}Yci=rfvGko1 z_qTFR#bQPg7IVD@m8OQ6q=7;mV55Nd3IFX_Y&r7R-)*t&_Vst|UyCepw?!N^4_g#R z0I1aR4yDPIfkvh8ZyZMHD-olq#@$t1GhT`;O%w6^^2V)@+_dyI5U)vH?R!PJ5aKdj zSwNs$)Z z-0@?5{1A}rXlBdB=qxv-iG9G@k~7ewV7P+M19YZkV6()-RTa46aBRS8#g<#bVMP6v z9w^|LDtnKz4QoL(h(%0Dhnct>!)V71PR|3&*@*X0^?^54{bEUB8<@GGT#CZcHi?Dx z`$1AqP@JU!#iiXjAiQ1bfCpw9M~w{|joF<;6+j?0#iw9o7@km^X1T`_ZlRz+(vs0J z*3km(Y{X3T*=)Xe1E}!?cLEe=0D>#n#P9%|wj4=jzBtWsroFOI@0N>F}b|#mEF-LlD(4LaE zsPxOT^Pn?X9B7%vhTzQ4TxcQLNLc%co)3SrmI5S`wT{(>Rm+eL*MxDgQw}((%$n(t zPI?ww70skx8LUw|3B4r{EE%EAnndg&XxN_1#ljf;fEIxCq8Fh82agpxxMLt#V!iVW zIh@vI{Wy*Wl8U{{l7zN5SK=-Pxg9%#d@oL{6wbK5G-p??UVGpmE|e&S3wC6#0{i#XW21O)NxE2+a96em|q3xk4#po=s`iUC3$=iw<4;)5PyYOxq^oFw^z4QzAf#$KAoQ_a+2oLWogh}fdXICdZmJEbKTZzt6vnOas z_7oR}=MHujh*?P#-iV1T>9@vL?D?bqTh^5#tfS`iSJfONI#jJ^rb~S$XKyM?UHU+0 z#n3A6&JG6Q6-F6%h?~`nZOI^l0CKmnCpL=t2CubgcUr2g#;O;@RRCZFHxHGYDqk^< zW4wzn0#gFsq>CyrC$t;Teb4DLHOq#g5P3nImdjrJ0f@4v+QKvO#9l94C_7jjJjEf( z1<$9PgDCzQl3E2;+RSo?)QW|PJO%8!uzr-SJ0hY^3{Z*`i@vPgaiRWHo_YjF)rfhh z$HBg@90OcIX>BmMP9d=D0!>Y|2I%5rz{bUaTviR+m>(=Y?7C@bp%pZCTEyuU=19Ac zEHA9htbVF@f`JJXgPVa3?@`#!$cXEFmT@N5K#Z_-Wtl!3D~X^fHjhHoWaO}${}Z-d z_)Ykeh%89*X!wW??`E(EG;DR29>IWcQK_K>kn8fiJ0pPlB<&`x&VKC9A4Pld2w3^e zm*HM=9YQ_^eUh@^;dek_O2Jb9r7GnfqG1}{Ix8bO>BC*$e8sZNGtoAr>`S9~X1lZK@c z8m716`y>FZG2tlW(r63>{yV^uHZdAR)(9I?zZf~}bIWrw4ku44ys!w~tPEbbFk3e` z3ZBa@5EgpfLA~ZIf@`Ex34TN}49jJu*5bM0k2m}}?mx}(NzeCijM@}LjXwt5G}t}e zu5*xB)!fGOvhu#|Bg4TFkHA^ciJHtYc}bF7o}QAF0_=iN|A?%a*+=>s$M06p(&gH4 z?6N=;T_oalbIuV2n0FyKq-Y0M;r|B z@hjK`@j)@`Aw8t*lTt-;M`?MeGzE*evdEQh%6?0%XbwOv$;O;Nr7Vf6UT&{5h0A>y zrB4meZ{RmWtJ^<%14NvqcmJy_zoP2%#TM)R(#g> z85XAw6NYUxu9{>_wI?KX>7}(=%t=aD2{e;E9u#$#L|?^-qhCC`EB#U zgVzv39rbFFtXh?G8B0ALQYjpyyrpvF?4~!fL^eopFPod6+AU@TtYK_)EvY^jWJm!< zTo(auI`D8o)(Q$q1(-TBkv^tf*+Qk1lgJI|3&Fy@3Q3Pxs2eRzLKEUHiCN593;~C* zQQv#ygD)x>{rJ{xR^Z9ugK7e0L7TT)bna zINIo6#7;|&hPs7D?YL}(Jibp9|DCv(1qocSDiwPn7gDlcsH7o*b?9OU%G$T+~U|k{$Qcyd- zZ&`GWbi-Bv1~LD#Y^3h?k(r1go==ArLI@|yv_8zh34)walh1@DnFDyh{bWI1Y2Gds zAHJHj&Hxh$h24$#c_?lyej|kN!XtV-i43lDeIN$Pg>GracEaLyHPXkDXW&$&HzrvI zFir-TSIOueg7iwO8LJ5^MQ2W0MA*3xt(hm9SMdY6Qim?30@F*W6^Hqp`0io>zuKHl zu-Y9*2!)J-L0K-~MjxD6*m>tm1{7|g12sv%!j6pnlyXSe(3I&?A#YJJa)j>(15KBx zUi8TF*Q71kqbba(b(4@P%WF5T8q%Kk7mqujF%gS2#-&uyO1x>>k0-MfPBBMWe<*=I zMH)%JmAj2PkBSRad*LLnO5P3;DA6Pdxp?J{i|V|#J>i&Yx0_Yl=BHSk!5lHYFR;6> zDS&|?OWp*iTo3KpquG|p4#|yH+P*j+Pz;zDqKqm<3|MaEf^n(Qc_pV=%XrF~#l<*7 zuvX(|PV;4WZ_zeD%_x<+$RG1&8OFxEo`Q_Zv8LcUSEso1v6*UbjGcg%euFRPZ@;6o z(+vq;x=H%vrK|w}h@iZYK1p%LE*D1KLKdHy17io#Feo#L#NhWBfOsU*b*mg~wJ)wozRARG+IK!c=( z52zAmbf#fX$%_qc*S3m{I9k~!a{SQ<8?95za8c&mHvehr38xmez(os~pe>dU?n`^d zGH_5ylfrCj5STJhA)t)PAfSNJq}3S+Iwu_4CA>yljBz>&B%^ITyV(WbYf#32Nh(4b zjuJ7~Wf+eRXNd6)zk*3iGlsac&8&T5Co4-%GI%37_)K}ltU0b6@i>1U?(q37(6k!nsa;#*sg`=CQtZSRKVa1`xOR55)=>~G-1u5O(i|L!9J(OONOwp)t zYy^SVhrM%%nv(1;1&h+64rxf`_a?iaC=D=)RQ5(9?f7E^)NYlQ6Z|%j8(uef&%*8P zUx_{SV1BQdM-f~U4P&lpne`k|Z_F&WT87EnQ8vV*r`u7JmEn)3_V<}GZWr_R<~g8f__&@zK^ ziBZv3VY2a@jSG43Tc-|0mN+DYiwgm!lI~aVEE7a-=BqG!U4Cy^-9%fW@ogX0YQ8tRUR*8Uf z8JAMc`4w%g>e!@a?UPtQb0;-qYHL+wN;Q{PEDrU;W@>z=UHz%E*LC&vrkXGVkg1^LAHUC)PJuRrU~;p3I8Mj~UMp z?X=SA3zSZGdU+F$kA;!c8n`V0rMoa97ciNs&YUEu(t!ZCdzD9SfH7%PILTa@wFAL6 z=Xw{^v`1)O;=<@ReD&qchU7d|Xk@ND=1Z-Ktw?Dc6I)@7$g9qR{f~Ex`+UhzWT)_G z2_WGW=%@&6GUrmvN86~ddax1F(vX8H?vvBsLC==?SdIV)mJF7`tr1qS5q&6ekCJF< z&*)S33i)Hk(t%DoN+i`I#nNrsbEi-@hc`G+!6$PW+FDI9OLdMcLeJHDiJ3Xa(h^U4 z)Nqw>Yjb9*_%djPC7U=N+X#}&xn^VWl56k7oYYFyvZmIIKsE>qkv1YpT)D1{vz1@_ zKb%C8C&q7Meh1QQs*=wWhBfWX&`^Qat6{$L|4mLXVGF02O#ApJz{U=A39LJPr9Ixz zh_kgAMVylXD^Cq8FVZtMvBw5d;x7!j^5MH?g~3ih&aydfNZUER-R)G;E_(F`v@$jL9!FVgmNL{!=jdKb{ z^5SQcERT8{MhQG>|&5>SN-IQy~+@ME~vGm{yIVSEj)u` z?WfOX%xKzi_tMXHUwimZeR>=o+>-y&KR?<2qtCs+GTwCNr9Utgf@YZa@D8gE>(YmO zIj8ZO_q^)wwlA%VaQ0CPQ()-`N4VuDfB%`)4NL92De9`}L~k1E3=s$7J=^X$ye>Rq zg`PYH-t_2){!kZ?*koonOdh>&(E>=p{Q9L}(rQs{Q+p9nJsS1kK@Cj{KYG~APrPX6 zCwwM5p}+dbi<|a0rbAPb*LpEMqW@E0M266!J6<|Dy<^dmp^gE(baMKV zp((t@`1_tuufF5AuY2h6y)S;U`=VDqxbmdn;`@TvJg|4x>ux;ktq&iteE6~3m)?8m zTR(Zr*|V3uwsprTS3gpGll^chJEwE|vkPa;tj_$|<(I$Xo_U?qle1PQ zXNT_^`N{NvEOtQFXSYMcXOF3$urZa@(bgVJ;n5gfUhTL~>0f5)e;Z!^yJvnez4;vK z=JU?C&Hk6^0ZBO^8y^40YgacErtq#}>>)f~+Uj2siWD=q_g!=v)@7!CrcA0qTBq=g zl-);+s@%bd8Y}eeGQYHl(O_QFz;jyYrisnV^NSopP#A4Q~0zU*Es^i#t0*rSZ=CWz*g}pMB+ehqBv5Q6bOl ztXgIpyLLVP^13!I`^H;r<68d2pN|D8rts?GUyOWEnWx^MdCl}qesmvzw9I!l=3m2^1Xzi+G za4RI?qnQUZedytJXWjLK^^HCWQb*nVsi!Xa*WkVlNA}J5(zcIZa>bhdEuTL6n!c~R zW9iMeetPJ=hBOCMcM0r5vA0;*Gv>}Hcm{3eo|AYy3E{A%P(ij&vE+BE?%1V2jaOov zQ5}t1>3m>q&+)Y(^AYRnZhrvp1SXy9L+i~SyYuAL4S#p%Kkf@l6KX&%>1^#7!skot zX6|3-wJ`$~EDFD$tm@me?up9hY_ErXcCent^sh;gr-@-o&05_qy4TL04Zl-Ad|OB} zS&+k3AS5=0>eC_DC7;G^0F@e>%MJyDV}r{MeXLw->0N07zLr7N|%`s&+ z59jS7RoSVuj7=^L)nb{_AJRRwuJ@eoGgBLlLui73amzqAM%<&Ng-E6@UBDj{YDr73 zwET4`{+1G-3-Fuq8D~-p(BJf$5}u*P7?k6-%}b?F*5<69jc25K&+O`3#}7Bcq2UBW z$Yx$zP#FP~6TwrLOx2m*V9LgI=l5$#P)sJgGX3~cMXM#%rQ+J)j>xS2MuJ0%QPE|> zES=mgopnoiNWcUFs1S#x6T^gwS{c4)2c4?>L?p!o6D0$e@I+wLvo<0rQ3RMxs^Yiu zwPFg&z3jq;W?dYSjzh>MF;X**$!BO(84))!RJxIrf;ZJEvH2m`ERka!+ao}Qmm0Yj zKJdVZcKf_k-@5+Iy^ee5vGT1Wxw>(r(6_-1UVgT#*i<-C>m$GeKWo+cBoGcRtP=(y zFcN}+1Z4cpYm*GHAq?rtrBHXNws;LRqgyUh^5`%LuH%W$>m}k>YBD zX;YFBelLz!kc8}(gzB)0{yN*2VWj{8K_vyk4V9L#)C+;I5o(PFMkRM`?(Xk7XQOo$ z+wW!nkab_i!Re?Ea)Z4(h76D_G-SzP}&kA3Y5ka3{&bC^1A zJn6LsooW!9kaMUlJPp9OCou_Ny^FFkU3NInl!cliw$8sep_M2z4@2T*>-eealoq_& zifR40`+p)9OiomTD-BGY9fGJqfMdE^Ire_VJahzDu?uVuZ9o)?-O4=R6+d!O|9NhA zi~cQ%f4Vp>_@(xEn1)#4-290f*?`Kw*p?74R7MIipoa_C1$K-m>?|6T;N(@|_^!t2 zlY+b^13p2hG8i)_Rx0q48!e)SjAh0%s5MuBE9;M$j0DrhNL~m%r*@29TWd$6wWc$J zF>~@7ma>#k_iNyZpsVziNO2}H64EOFgmwzwYJg-F@XA4vHoDHk7M)2RmV&etaLN@> z0YvyLu#F-M;VqdQWQMx=X~-kDTUg48sU$dw#x=T_omz!kh#;SAvu%=N8eS|$7n(Nh zb+lO&Ya>JH3@eHSm$T45(a4*4ZxSa0=Pm#QGC?Xx+|S$&*LqsiH zrqSp!i2k*;$kwGsZw~JeoMm*YF=A*8KC@?qauiJ`rC=NeWL9gWD;18ZQH_ zb&EPt;K@YH6jco&6DqxouF3_&1&al#GFTiNg%-+X2a4&UD&d?*ErC4?ba#g{Tc06s zyST9>6&N&C8>zmeHy>ikN53()9vB%jS=7y7SuU6X?(QtIL3%3#XW=88iG^8n8GGvw zR;b#B?9~U{vP{c^cd90F!u^{EEvE>N1_j zW|~t44Y*=(*kr9+S-Bfbm(VRgr5ltDiJR1>eiFO~w&VxrLSRhCi^zI>{mHf^35?u| z%<&e?rr0Xnac0B`hfw)rAhV;{k>X%ID@RrlHBBOd5bR>vSE+GW&aRml6g)vw0)*{L z+l^@uJ$iDGWJdKpuDMPOSYG+UqTY=gOf>g(J;Bj5F5B~tui53}B#?Y!z7e)bb%uGj zKD(!5ahwVYx_erOA!5#I+}0qU zb!VoqJMX0&QQ-SomqF4j6)gWUwva$gKT!l62Blt5OC<-!CtoNIr}Np1l>&woSPM=a z8)zSjBUbA;H%sDYeR%o_2Iv!zrC1oi_EEycd#W~TqlM_BR*-m`>M$1LXrHV6P2JAl z1Qm|VS6N)z#)NX`-oercp|MGsx>R<2SS(Sir(rf1kdZ9sSZy1#9)WdgK@sAjmwHOe zJ3EtZ+E1k988~|NNc_7(JhfTO*-t5KC|#qeWdWPp)L0vm%(x8tNuB{&;1yY-a^b|G zq{AB8O;XYtBWleZqxl}p@r6C)JSL%z9wlW9AhGy#y*RW z?^ww8>HrFxEZ$J_4XyNVwuzSto>*oeDEE6?3NK3ryCVYW=LOpt`Gmu;uGbVsR^#aH z1VbxQ+N~qmU!sw-tzwmSJg#Waz%K%N%1FwNV^Q?>pG4xQ(WI3fTmEw6rrffs!56#0 zOU)Mz)?D?l!xK?((gF<7-;F%!z_8LqEkmm|Lo<7}Z{kQ!_So1Phq9u@kE-6~CclTu%{9h7nVz?LS z^>9GMEGpdPF)#mAMrgH_dh+XNc^b!=Bh>^0(D>DAD3xFwCJoZ3Y{f2RO4`v#F#cE? zPB5yNa3H-pq1zrHQqq9N<6wuf9pXb+y<{|wkWt2)n1bELQ?Df#g2P*6Z5!9;`e#GL$7qh=S~`y7lNQTJByGW*0HP#oT}hCW#V!&Gw6A_WLcO{F3i# z(szEU9y}8luR}@%pPJmFDW6!nP%uTcx(4BSg!+cRnh1!QYN)&UFtKBd4ytSH<9O$Z z$OIr0eQklMJ35|TI}=ZGs<_PsbPNmCT9k&mz$h%^YNq)P%~dH%W`jBC3v9g$pn=%; z^YoG2H8=Wm8QgRHRIMtm0rQQrzCF#V-5cQqAQ_F_wBF^dv>K|{4S^xGuif`osCNL2 z@5@X_9b2?(P&wFS-KxETBa2b5)~I}=^|l+%T%4my2!j=Et7=RDfb_N#f>T1@v}3Z3 z)RK@}R|TEICF+821ms>*22}bOXnl$#d}=AGMD+bM)6$K*)t|vOnJzVmyWysL zMlPaJI{J|EMVJ0a0hOqz%0sg@OA2Z{Bpq=UT1Lq)vGqmD^Wswox-x(bBO=uCClBAT zXMb4bsH2|(Q?rkE4x3xO`o$>tYU#m_EM#j&Y|#g*)Jn~_B!COT^^j91pB#dh1P&$3 zg{3&BOHm~s!RC1h52={qlfr~CC6AG%7|>J#qwGt&2DomGq%e-~DL*PKA+tH3mmIwfLhi~mKXF{(Uj|WcIVa%B+MeFWRt6O;Z-?v$ z`?Xw41wn@4b-W~$Eg%r66vdxa!HW}lr5GcM(CrffSv6FKu-XEt7}WfsaJ9!N39dxy z^dgtv9b_syW^*t_2++K(T@ED|;6$mTyLK0`=u?W*%&ztnC*9xfIXr1fT~~XUGf@+@ zjD)cAq(kj6Exi%y9InPLaDlhoanC9Nb?=>If0a%?<|acZIrs6Pr|Yjv$w(sy+U!B3 z##VeK$1IhF>Ym0md#8f6j;ct^Zl%E8-*)r#`clN>`kS9R1J2jGA{tcV0#^QN(dnx? zS4yo)m9ng7rzT>OjwwT>a0DT~B;DxQ(iT1@CqpS~4<;k+k7m~fMujycH|Y3JOD*I4 z|FQQjFqU6wURdw!EDV!?mM9XN-4JdM>`b-CUAEiqwx`o$yWMTiwszcZ-0dBZv3hP- z-Riz_b={hKtK9CLnPk_yC@X@15FtS1DMdUI5<(CUi6n}M6d*_>kziv&fXyNh2>}9u z4N8E`@AtjV^MCKH>S~X7cWP%{UH6{HcfR-e&Ue1k*C}fc=Kgqp7$|{+aCj-7xsHSt z`D7$CiP|fE{tiyilu0~hNKFu!K_mcEjTGVUaYtTaDT`)RQxex?SnCdrKd%k0FSgx_iZI4B3Ie#c`r3J6%tm}vOn{bF)Mt&2S zY#N@+w>8wNA2-vO$FkAX+3^EpUQv!x<; zR>|H}Vwq;U%j#EmCI=5DNNfJ#$$Tb3Bjf}>idkM3W;0d~2z!Kr+pML+c`wXL#v}56 zFvH#lFVEZI3yu_aw~%r}uSwIX5L1>CS}|s+jW7EMcEV)k$U*GM9>aB(a8MID=@}2P z$g3)>x$VJ}q8QG=H05@z9aKuR7$CSDQ9m)Va@1y1R=VYI@+C;c5PqaZMH;B2hG^|F zK^}K5h`-o#@APebAGgn2LcY11&?_gA;Kf_ST|dH(dk+W0wx!@`GA!v44u9-_;J63H z*z`3oap~ts_Qs~h-0n!qCx9DozbEfH$6l})L}H(E*kt=gfW($w`Ha}WO<>`jRIC{L z;uyu5$L4v3^R}h+7Kr=;2Z^5TzM?IFCzl(V-t+7D3N&lX4u6zWaebWrAo#R)VH;0|S zC9H+~fYNOkSZo>QVD}o=>5fafKEQVVgGF%daX7oFk7*%i)r+cB8t$>4a94-a9Eb{7*j(BRzUJgoPc7qVs0m3T7>2I*HZAP^vg82ya%MwIMOa(h|w+kXiZnPg+G?~DCkvW ztQ3%&7CW>6gE=h-ouQ&W)ZK)hR>0#Z$J%9`{TG~~Ihl<=i8phLEYz%mpMq*LD;2H< z5|4T+mUW3oLGyJBC}xVb!tr&ukD_sSat)nCr`0a>!i!TN;M2~KH4^JI)|X8I4jAW_ zYT^mkwrK0D`UzogIoiLEQv~}0KA#E1`-I>6V*&2~}a z*q&|65&4$+M@$sPWd^&(z;OsnPCCdd**brhl+7FgvXxM-4lwwA&a%~UQ5hd9m8a^; zvIV!uk`sgwoa~BUD!&&^Krh2Xbj{aU25P?zBy)Ixtu~Kp@X8^V?msEzZ|IJqT-x#G z{0;Z?Mt#90nT>L1($~)&yi-|ZT;4gyD=6pi_=&TXXTZfkJJ?e#Vh4y|OeBm&>V+J4 z3bT4^Z>MKglv}aUMGc%%7ZHF2@9@zJ=$;YwY_P%SpFsjvWd5u;mhqV0lb+L@kJCAa zCvcy+<}cQJMtWRhepMMsuN@m#aqp@pL)^cXYt6I(M=speoRmGSUmkf?+J~b-lH+|> z-uM(*l`9jW9C%L})DxY+5CFmm4KrXE!L2D=RVU-!#SDcpcp)7*eAbmXRPvTbYt4{} zJevPq z(2QuSpx}h{LNHCy8T^2cs*V-WNhUIq`3@ppQHO$Qj#g+s3+t#sHPI^8l>ic&WtO{W zw$?tg=>bhF(0NnX+9e08P^gn zdcrWSDD>M}s`$ms?o>}jWbD+WX4Tw~43%zIjm;}JPd%3dq5yYiC9AmW@d;YkS3Ey| z=Nxm^y;8Ly>0)_h7W_RU34Kdk!N5Kx!l7rCR~L4L2kYTcy@U$S z4xAXX`#PCJBn{OY9!Xhy-rIA-sgmzbZ`@9FYb#&Js9ck|)p0)qGMg`RGjP*JH!>xW zNxVeFoR$4^B)Q|cf{s{cu}AyXyKKUc=$wAzqy{xHlt$qi4y;DZc(*X5>&ATwV$wv4 zM0=h(ykMS`WchUk(ov^M**UOJS;mkY$dL^D4?@B*E*Kh(!r^rM4=+mg1fjnf3c`RZ{g_U;uVJt zqq;$RHZ3KwD}io}d{Pvo{C=tXDlNtl_(Ott8fV~h3EWTQM%LX<9F~R9Ej#Q>YK>Ea zbrIeYF3q&A`|sx9$srYA2B(&GKrO)Ia~8x9N$%-XssnK*a_i_L>e5RD=cw_isNATC z`s&F?lWEND$is46HGf`u3Dnt}Zf(RCDCyCV$V4y*?A9TV*^uRq2b7_A@iEhkjXB1sDc%B0M1D+VF4Bc_KRCozGrKn+^z510{VYVD zZAT#PN-G^iuZFLMGtnJTbc}Mk&Ou&%k z_r^2ZRq!}@XqeA4EOjSLbmKVu>CYeLQ}06Zb)2^AAg^>*RQAq~90)|_&ApB=o>YmY zOAI3^Cd{)E?i_Bt3vW#w&hX#po~y;9E}6(dw7DV0CA}V7q22u8dW0asS%GcnDD1FC zX^%(S0&d3Bwb<`24yLH*VTbh52Q6qbVzak7-McZLAr%s}ph~{TXc(=1cZYCByDLmM zvq`Y`1g2G8tX(IG@hW+ zPIhB2(bN6g=`u5``2oiKxuuw1r@kt+&m@!;+ic@Adf>nIN-=LVktX{^l}(~yw9R-sry)fl!Sc`QWSH~4aSnUK5_cvS1m?xLidNMlmD(T;1t@d7+V z@&-V=Fp@EF&cKqV zxD0A#VK(RE2T1Hctgnu7w!n=5c6CutxJmff(0?*L&5u?DOn5%%uDfeSVpYQ|vK}iR(_j}!NaIdd_CKS`tzT7qPmGJO^-(st; zS8_L=8HmyPfW6-~2)Lm`?raK`quxe6-5e6c9s^fZ2>S#efyC0~H}~x5AW4MAwzzn! zH_voF`e314T(UdDevDDG@cG+TT(C#rq0b_u{`NbWrV%o?mzxW*a0aZq<#g-#?f@^`7{b_{(67G^BTIA}jO@iQs$zqmOqEe;bYwM;`*VgA4$mrOw^dO4W0;T) zkML+LlAk(Vw2Hx6&3AZ4SOTF@UT(Gzd>{adW*se1%HEot92`VVo=G>}SA(Bd!hKBO zZ=6d2jh=h>55j(U~7?Qyf$l$yV^@R=>=-p#XnbaU)Mn6xXE zfh^NacBRStK^O#ysk+1O?W%=sv;IXc8+_i=Wo)I*#>TrfYyw)=$=JMokH@mhjYE|T zn>pFyOqqjdTZ-@r831;(kOxHI_M}~BTZ~tW^ybBQ!^@H@@VRX$*BLW+Hkwm-P#Q-; z^~$^VV#B@Vz%yjP2j?}`!lzGC(Mbfd=7`n?9Q5g|Bi*wYqr{p(n++1x>VO!j7dg@L zmRWVn>QILYJ&S|S4tF(i%=?RD*<#S51-!nZt3%*BVCR_jF^;5tJCiLoZKyIjbEm*v zh>izVm-<&lL}()MME84RKJ=8sV}$A`A}^z?YXTf3B!9?^0FTi5 z=p`Q(qd39Q<8)4G9gAV^tQLxULz|1q0bs2P;1%O$*n8Z5mOZEvcukI$YSRyo;mHwj=5rq?v{%r;S;eKkHmDu zN>97#N<2YALCZCm6-jcC1tn`?_exOmh**t|cJN?$jgOCw(rSbpo7GoYNs+ZN__eJQ zL>`sl(RdL(N_=@3alt;>ATH^)(YcpU6NUXP5P*T?Vj&`qAtVvG!ugN&4Yx!`NbRU8JC)W;2FR2fgT#>*Px#3t0HK5HZXTse>& z>gcN%3wZ-&OT}N^I7g+rd{;aeZBn;Lk;J$4(B>VWW+34_!xj#Sv%jd|w3>hqx4A1N zyU(ObW&=^D1|Th_6NUBg+$b+6TL|5v$2c}h&S*+;P6wj&NEi1GcXkk0WXX36A?qF+ zIC3yEV-6&%vu@Zkd}}tW_`xL*VU;P^j`3mC73HHd_x{@6_T+tK;AbuX&Xl{vaTyBv ziaaf~NY%J7$=8)u&iLQ7eE{jeAG|7uLkI&(W&pD;-S#(RsHoco#9geff3wuo#l*20 zP&huHLIWrbtKiaS@PAc(pHBWfX`lx_Ic2jGMh@Xfd(@Mk2I+6-fW7nfbX)45*%=cK z_4zE~uJ($x5TN1@@YrR)m~MG643^u_QXTCMUPy2!NuS2RHWyj*at;0N4A0ELH`l&W zEFaYviYan1!A`}WecTgD%u%$&=J=7=g=3ZjW{k}*6T!>9*Ol0qdjG{pqJQuj`5pjl z5{|VU1K4J{Zx~1bj{Q)aDUG(3kr;x6C-w2vXg&z#$;*&U6d(3uHetp+4av3E%L!(V3G7ydHQNJJSV9?u{NIGyyt``sP=qHFSHHbU9oB zMlGNgGYe+_9DN;d#!9Gnr;rM1ZzhV-z0?A$fjf`e5%Ks&`6LBmCyNK;eR&uW^>rbA zaxG}hb983RiIteKCdvw)#AdX^jTmH*550$RV1`-S4^@2Fu-Ti)ES%dj#UB3_J4VM$ zirPc6m|kd`J@{W%gnrE6Sj`_-q?yNFH=u&_lZ?(LLxaw!44DbSEvQV7?ApZuJB}6* zcONhpoqE;IxdEy?VKmx@BWBM_xjOZ6?EFdE5lBtc7y>fOz_?;#XC4;bYs<`x&op>t zXede{`0&u>)@=Xbr6X4yV?;8&S7a#UJ|^y*iT=e%C!j6{>%^GI!I2U0f2ipJC& z473b23;S!z7iR_Zwr7{1%xZCcuH&|#VI;Ml*qI2tIwjsd@(!Xt&I*D{d#C|V3s8g*dHyqtC%_@x?b zF@GT&O00FZ#zgof!vxB3%N$OPQ&KS5aXzS*Q%94s)&{PVsKXRSencA#is?)#wZDFOc5ra)n=DL!S zHUYEcK=-h}`RU*F=YGo7>^y(&!ZR11zi|HS(<^>{R)7B4DgF86 zANtGx`S-qj>cvku1X|y9cXpRh-}s5y%cl;X z^ns4ClylFOz(4cc`pfx=AN%kxeemI5_|%7g;TN6yzCZlhFZ&aJ?f3q=PoDaTA3OE7 zt@PCI@qhfIAOA;x^5s((S15u*OfB3}1v#~4@(zKd?viKMOqvK)uSEoV#{_py)Uj$SAxlf)TS3P?KApuwY_>X<~ z-+b@`&I-FV9f8dw>^dI_DKa;cF%fI~Zed*=@;?{#tz4f<# z>-YSjKXTz0|Bj#fWB=2?_uhAJ{ec(1_pkoJfB(A=|Kl^C`?G)V?7y@5M{dvmpTD+$ zYWHvczCXM9TmJBC|J~pJjo<%|e*B;Qy7A!|CQO~uaEceV%hx0eEJ^5kWb2d z_-8)&#GGowz_S(~y#K4T+hxspk+YWBJhSuY_#Fq@v24?wvFg~J@%xGy89Kz?22V~K znBV!eVm5yN!Fz&Ip2bdT*k(Jh4B6Koy#M_=zI-NrjQPe0mR8`s|Ghd@ujvQx|0@NY zhM5x70mRq})36mG_UmbX+duo%t1qAW!PSE0oR$R3Olw+Wl8^uXU;BOAkdHqf!>-o4 zPmd?(pWV81Ve;(GbLYm-f8nWG>pp!X`S_VneBU4c7k=;?f9=w*|0g-yfA??x=ia&e zcYpf-`~83Mr~mrD`r;pa<}W!Lf8v9$1v%fGY)z)9*7)I{`+z*S7hn7k3)g=5mp}N2 zAO5c&eE1hXpa8nQnNoua+Gp{vO=#j<>8&ra}IL4}aI60J$HYAi2+-Tc6zKMt;xP5C6jV{J?j8-;e*uPyFK_ z`@g^E7w4*NirzJK&v|H=>l`oHyOzVna%z{|hwkN(sD?3cgtn|}M>`IG<0Z+heQ zZ~VpAe`@+uf9fxv{;Pk-Ro-`f@Zo>>i4XtFQ~33r`s+LN*C+MYcj4Es`pKUFHGk)K z{EvU&J0D@?;{HL{&rP1YaA6B-;ra929Xxa4&V?^rm|S@NIl6~i_`mZP`1u#;EUt~4 zr=HNb2Gwq3lJqO7H-I3Zyk0rDwo$Kt@vc4B^(iJb%YoDw>;LNNSOPd+q%4Ts@) zLILNiGrZ`HT)DZVyRjo4kH&Glb;Id8PT5i50Pl*ULL@wFOedlKhoc8$q>SL?YCK}9 z0Wza(1EaZy*Cefkmsk+2gpo*x)@r#C%V705>w~x@sxNHnDsN8ob~DZ~0Nzvl4~+GE zg8%5s7o#^{y?picR|UBH_y+Ii;!egcuEFdAB7n?SOK`AjW!i8s;V4O%;$%-e_32Mf z@9)o$nNU~^JMn*iGI`c@*nCLc4-1~sMTNo1<-+QG~Vs62b0hg^Ce0r^&Ou7ZqX2@YID#+&@( zC5?3uZk_WgazgHo@wVjJ6Gy2io|yBz=h<^d10XP$R{iFL#cZ};1OFa%gJs{pe4q^kzCuq=5S?U4qpN4pC?|G98I3nh!ZWvZ?KV4;nJ zo=mt+X(P_4u`co&38*m1D7hF!yF_GVUN++SWW$f09(A2A30p@yg#giCnu%*y0|d@c zw!~7um{y)O)c}2V@QfoXeImWp>Zn1Ad64RLUuiTle@W&gw$;S(2p!Wy0d3=*@vt|Z z_bBCJ?oVjSaL9fS+vDhOc|sr!^`IJ0v5h>;Ve2?wh8caxT7a6ofdkJ4s_BU_hE|U8 z<`dRoV0C6D$oR0x;uBl7NGbY8`I7gziSN)~aXm=(OsNDtfSN1g{qddY?)2axq2WSh zO|s3CmC-Md&r!8BD;7mhBUFKh(0mB{?~Hkaj)~Cf2=ny@K#BV$_dFd7jNN*WGCDQQf-;2eGprkq zH2Z(;Z~|?Ezq6i&diUJ041~r~8jz*dXF6vV{93%VNd7^rmq=#A!@aedk{&?Wbtu#~ zBxR4UG2#!)UMxM>O*SK}lX)#tR8kj5uNOv>pP83Jk`gkT?y9>gF=yD4;fbSDIv0BC zDIauOwb89v2|{Dr=&RZknJ!5q#(1;ILRR|f`#2zKh6!+!QGE%#%%q`$62|Vt8>~3l z-E*pY&0>Y#HvizAUXRPWyD~N~ErV0FbZACO4E0ZQEE%L|)E%kByDT*>)s){6m_{mZ zL2ra$h)7ZDC{yAtK~_wp%(~d76`qu!g|etA!@1bmwpUE|R#*}@e6}040nN|>HSua7{l-Gj=Oov`=9PT55a14<1|Em{evlP3ZF02BG{9{1Ynl~`L?&ASNDIxJK_ zXu!KDEPrX-EP%Vs4al~qJ97CKruX4K97$?j0YF-ifdi3^UZJ~CZAWg%p2jy~6jUNJ z*7hC)#xgR$0v`Gps;VlfH05Wt5U5~;Vihlet83wpBH z=aZ6%Z8$-6_c#oxwp#2$fE4@XI)-x-2z%5leIMW2Tz}{u$0tg#C z)!89a(o6nO_Tp_w)hoPX@FZn4W4jo!;dViv zrASuAli)gT1vKM0aIS3wL1b8&!Ic!J@ZqaYGJ&_l&5P-LaZt|E=V2OBf z^L^V$8cQ+KxtMKDWz$BG71hzZ;S1~U+=o;&-Hi7e?Gi4|N zTbyM~UO#3aGde^~%VJ3I`nA>`)d~j6MDMP$)asN~vxzl{FJ#+kf{5{GI*jYZ5N2pC zriLA4L{o-R^#jKltI0m7_D_L>yh+ia#JmAy6*N>PsBi^%I6lmXa78TfUN$k%O=|Kq zbq*&hq@M%GRbnul*zG<+Mng-xDAx@{WSc;R{}!RP)}6vgLUSEggbo9?vup}M z3Mve~L?Hu~h=%u4^yP9o^VZEWB%Q zX-ahp!swHw4L678j}R@PB6fn>z4iKD+o&NbcQL#mg7o_k?;$h721IX+B$+mBTe z|5_#q40ubx+0Y{zKbgUv{^i&5d>FHkok9w3XI$$Daf=U=Dl1S;dGL0Y(gHQ$pU7~V zcl321Y+B7i})ZxCmRof|4my+29suvOpU z#}&W=y|S=e!|F@z`6iP@<4@A@1y-`orw5Qy_D*QYx;{EmIEh|p?7xnuyfC`tCd9$` zd{D1REso3_{X7AUVW-I{7S2E2td8oxK5?#eAdi5MG!OMi>-W$FL?lRrvJw*w{& zj80<1h!+WYLZ|1uiY&ZW=#hQ8Bo=&y)Z8y9%aK7{6M$yXwWrDE{K>dMB?yAL4gT$a z{&_nBEtykCin#ElvpJO=_>fGcN~z*FLbDph6SsrJ)iMMr)}Xo&?bUYh#;I293;dgL z7b|q&qS;IsK1A~k;R85_`f7(R&y6W0k5ob6LoR?KU@IFfpVF+W^)Wu_PS8jBFe!9M zW8pqU#8w~Pf2)W$-~wf_(XD!xuGWmu2>A# z{-OqY`l6z5mxoNbUOKCzG;LD2jev%}aH#z^R;{lCgV<`Y5g9cO4vQ@YKz%5wcLc=1 z?Lul7vx~?jdrX+x4O=yG-O)g`o(*&in4BNO<{jD6iAM8zZ^>G7^V0cd*uVs7yw?DG z_RLEnTjlc_Z(1M#yD=+BHCEQN2PyANsL@@*5FR6i#uBYZRUr9HCupeK%X}o zJyB6P%R_9i*x>L5Q}Nb`x9L{hLi{|xG67NCnwa!F-rD^4kIDhFY%?eGIo*8(X<3?B zG%|dXgZcC>A`P&1*czc;z_u0-l+gDA5516QZFOQ3cczow?X=yA!<#3h|aW-mF-zYN9OSYg0O(e`^Q^H8TeV#^03M9x=4DZM$Du zABauY58>zzprfi*<}(bm1~V}%+UR_ClP)z|JNGr8SGe|h^WVTsezKq{*S%Rk+C)|c zyffoQmG@Q93F)$}Vq@0_s920UNYi1=4Sll$6~(l~PbH%rW9~u<#fM{YmC+Q2w_`}p z$&$DU%>DEr*$XE_sWet|`=Du47UwBBR5OITYp;CYGGQV`ltO1*5mZlEv7%?jawzWM zI9bwcpROo;lq`;g!jANM9!#;vkdiBDjgOwcQ3cYah8o=pjk{}z&9m-af+W--+^Rza z#g5-I%P|?*ga!Lltka%IIOkY!4Ym~-g_H*H`m)?xgXxn47btZ0yCg`%U z1f|Lj1=nzK{Q2x>yR^WZNAIK*Kg}lq_v8TJXfsO3Y}KtHZ)5&kg6##U+L{R~`ms4^ z_~qH`-I{FT(Ti)f*q>}o;jZ!>4%E~GDvG?3JPm~7@y9?A3!|=GFxm2Bh@uo#q@<#oMV$90pDDD{0kP-N$>24;^p%u5u19^VI8ubg897XDa$g(8> zt45e(Zpt^eWdD3^4|jMGYQ(YJ*?eBKp}9W-PwZGY$t&(y_`3~Jq9=EbjhkyEpn59SRE=` z{q6Eo@-3q|H}D6Vy~fIAYDk+m-*F(ua)k`mKy64BSL z4xOMjkH-Go5~*1Hk>sk2k_^!zcp4E4AwsRHLbXtB+{7oaeu$JbUq!zGePe-r94hRw zGX?tS0vW4cyj|1#9YnfMC+1s+=k3&IsYOY}ivfj9~$x}p!X7?Thr>f(aXk#AW} zl6)CX7ntQA7@@H)y4rW#Lu*~x=l;7%Ii%1}nO2WEj?sYaV5im>oUFzx{p zb&uZ4POm%PoGtvC-U8p019(lcYL^%=aplwlDMrHR32rSh8bS&wYIqG2_vaPd^k0%H zHR4g;AoJim1~Hl;K#MDbs$@z>G+@=U>1G6(vUDmIZxgC(SLL~n-mKyMDVkQ?B-X)} zcz$ae3ZkxAfNe&Ip+I=iXmBS5m;s8arAfaq5`Wo?`)XP zh!Z_10lbl*o}pbFO*DshHQt+UZS?d{8nzsA6Az^2BV>LaKQ;v9L9VPty~$FkZ1!1U zdQFTIw;tOt))O*43I;1#Szl4Lbob~x{EHR%4&MgB^@e#r{)x#k^ zz*>ejrY^Q0uEVlfPzEz&fCfrV`uJ@zXKCzlEaA~ivMmobSPm(UXq5@siCbn{Zf%{L z1&7gFX`rL)fQ{yiZl|MdVXEWPBqKxCnuCe$j6O#)pxcSx3g%n2*Xr_SX*L6(rmb7g z*6?yQX3SoFA8tFnwG70h{L*DWauU74Xr>*I9us?J7f*WN4M*-eC&LY zhkv4`J*_p3eZuzrji^&W1bf|DxkyI8n*NKY^#`(Azd+0E!qedeG#4BVGdkV;)8y~Q zc*%zAI8@^s(``S7bN7!{8zpr1CI=5@b3%qd2XZoTF;Tal`B(B3jDKtkDL&JXhG%&{ z$toYLNOAe_;NE4uWeCHZ{Y6J00477Jf|i3=_r7s}rF)1t!1q+5rf2dusSrBxUW7o1 z1`E{^87Ts)QO^z@nvWWkq!z$N`EX9c61H6u3XZ_dMF6QERY`i`&996(r3Gi9+=1|| zV(mz(DVzne84l2kz^s1Ol8-RK+pwpWEtaiEF~mJsuymeD_9YJ!b2vzljhDYn?EJzO z^bPvaOU~JN4gsG;CksA;u6ol7T#IIG!a>-CP-Ak|x}(;z=j~=zyFtM?tInmeE{9jD z<>7hrTtNonvt89KH#k(jsCbBq-25u5?n^<5HxZ)WG@+l(H&I^ZhsL`|DEP)V*6dlo zA5_JVh+8vA!UV3uUN4GO$bKMjsSo$WpCSFC1WK9@T8hrriJ&AqQ*ET2R2mpag^PMr z(-KHQGwNHAKx_aBfy=?jYTdeb)+-OWh9&^~VTU*NkQ3)L1er5k7QUU{cO(Jnp;=@M zT!a6iiVvQSTpZ&1bjrr?;qv_==i*janFD2;@VYemqt1IwV^to)G}1`aSqU8G4sLX7 z2#0JN147568UQjJqp%T_qZV@6;tTi8y+pr27luw%44C4$9!m^uC{%j{k7RgU!$P=l zoS4LGsWO%84jM^)TBa10Il$Bz%o}FtjMW2a$~KW--pxN!r6F^t@flhZordd&2)h*n zmXNTeWgK8_- z!ev_d^+IVh%9tNy=F5pIny)UIQ>^r2w$KI*rNSu?2%)$pLW%-syoQ;{C?`kSh`veE zJk2K>Tonc-N(zrOGDn&pUx8QtP|ZN{eq&}%9GGH#>_lRGZA+g|G+#)q2M8KfEz|RV zDvp%~FHy)*6SEv_$~!QZ&e|mOZZ3+;6{By;@Hh1a9c!&uZVm?oDfq0roFNeRS%JD* zm$5m2#2zI;NO;@mDIYONT7FFiEBzFXBVJa?nyLm;#lm34-v(0j^&EOqBqv2Hm53UpqxSzcT zxh|XY@sq4u&Fy^lDt#?3%kjCroGURsCdnU({Dr{uHDx~%rl;WgPv#2pqmJvCwV)?- zS?f2`4U%*easQ0*OtM^?%UJ^t4fL9z7mf+mRsRg`9sVbW(h!I=?l#iaaMPRPqZroS z0-KlIzQ-i+!DfeZmeWJ76%MI+SG!hwoQWyyQkZ9#wexJmIimZz3uODMWyOs% zOhj`}*74mu3%9Zey@sWFbGC z9eM%-IJ!O+4+K&?=n%L)JA&WLq6ft|V5HX(3>YmA;W`Nr#C%qwRq~SDgw#wV8zNL9 zLtS6;Q`mi`au(C9ZD8z8<+KIvpXZyKg57E39+(|gC%N)oUbG7>H2c+8g^nkN>z`gw zLw8a2lh5f)7}vL0#kCRWI_|-_mR&DYuI4dJTDT`_6?k@_)1~@&j{1$DWKG35MU$lI zU{+2FLY8dE2}(!u1Y8J*+JBR|OVhlP(3Qo`30-~lD1^pPJ#QWeqrJeS~3G`R&J!6ClTwv2ER@O4L9@-Agce zgb=~-zEbjT%1LBP>Ds4qGvNh|Q_7;o5@^#!Jz8BUi8P|H0+^H+K248G-cX!9oF zW1RyXM+<+SpoMq!QY2t%!F?G6$U+k||iF>?c2j*{|1 zcLGHAX?@m4c=-cf(zSuvnEuMR7Eh}1&*PP_vq8b2+}4}RV=lTKwAKo~Ag(}#|Bw{g zUSf_XRmyI~T;%(l_$>tHFp7Yb*1#d>@1FZ-cs5-y_fxIp|CQ&^J+F^LADzo|++t}F zd+(gjaFWsxR04Or&9+~~#%33x*Ho#AFGRRY1a(zuvY?kME+6JK+{P@`SEH}W1L;~~ zPf$A*nbHd$m!@=!^-`rve49`kDoi`m>bN>8ze%Ys{q2v)ThAvJGg^*SxKI(K3@VT} z{AUS|C+3Fa+XP{o1e_jsQ{+YYNP$v^y1m2SO#vWO9KaKdWg(8Q;=@=~o6J_Wt1o&h zY}J-{@HEpiOSN7HE+OCrvfl1qx5C&{m$~-XN|ov*u}CJ4^ab)udwwoQj|1_Llu4rj z5Bd$%^NfKqg0x2FNDOgA@gglE*=A{#N#`g2CNiem!s@U@MgNw!Atu^3m1(4s<3(M+ z`Z|<+=*01B2a#H_D7;czOv3iU3(q685 zD%wEii*v>G$%AMbOEG48k%rNpd@;_VQFBjY(J9%A8{ZZT*oDuE=0A^gWU%SaH7)}TV6M%9LY>+-mb zQh^e}3nnh$|V^OKGhJ12b zcwI^(f+q2%JW0Qd8jzN*PXOmGzgGCXUMHzGq6&`(wkFSC{h0gwu~OZt%#*@Bf2Th70Y~LV+KT6<&9X1EC99XUV(IK(MLf(?#mF7t%GyYr_RvxdeurRw2bb*ClNnUcFAbASWH zcYG*L98H$_>&v$nrG#@(H}B1=_+{)|tlpLBoW9H^1HrET1B8DGBF~tRR^EsWylpMO z*f3CD4HAY5HMn#WYvD496C@Mu1M@fG(^ghr)lsh>N{GO93139CVy;K32%pdA{|3cv z<2HJ+1Yn4E4sE}VFTmE_4InGl)1?Z>%1F@E_0zpT8R}48Bn~{j7CX;ANCO2EzlpgA z3GG%P7`2isBPg~)%t3Zc0K0_L%t-0@bki7s~sB#UXy;sUu~?X?t%^?A+_#S zm1l?jIQkE*O+PsMqo)+fpV1_=d_|+0#TpJDRE*C4F@>*TK+5Z-wPxj50JBs6ljMS* zo|s)(fwlsIp$*~VP!>mlD`>k?W6UQH#&c%iJ#LNRB>}&!F+MID0|pTV!I*%{$eg*` zw$pH5+1UbHQNARrkIO30V6%#B9z>v6HlIa@oQHz>a#6u#+|{7V-sccnyMKSO&9^h< zdg0ElUf#E8fpMZtIZ(WxBa0SwIF+kl-=$_H9^~5Wd=sxaiFZf(AL|ahQ#xz!gYdVm7?~w6QVt1s|Apzw>c8 zoHnD{KM2Qhz0ERCbwbH7At#R`JXrgVA4_+$Ay2I+gR5M}4Dvr4JWl1a5>$uKf1kXI zc_a`95p}|f;%GnxJ(p++QwUOv-ZVw68mKzM-mQCHz-_M-B}nOX99PET6Fy^X=~Ger zI8IabgbQ(%?x;6YC!`c`;&~!x1!Tj(Hvce%Aj>WGk=KDtTU)&*_G1TlL=qez7Rnvj zN5y8t0K_7fu#|^sld&0KEIL}%gIGsCkMhaX=u93l&QTKagqZ9Xs^y{XGs+_yE3gn@ z{EB3?HKI7wKjIKtZ1R>yv^?EQXDa1|-3MicUp`J7Ho%#ymU*IDaVjtLD!NHlt zvyTi8cpN6ICUuVgU~FZsPJD(D7)AiT#o?DO+@(%w)q2InwZ7|)c|`3(6b@<_etGhG zy$klqEFO%&N4!h^a@O$cCw90VHx=se->Q>oXI!O@++q-Zo5I|HO0G3jTHZDc;KKV5*P9>cn^$A;o1;}^#Cs62l}ejnuZld`7G zT4e(=4_ED|>wBy$%|%yS#B~1?VdsKh9*LC`8y9W{s_@VF=3~l4;Z9jTgdNCEfQ155 zTTipFkdcofAo`jJl8D33@Q$uqBY%#V#{{rUcv;faf`s&=L_NQj;e+giA>|e0Uv^hI zv=6sxuxKeUFl9CQ-hGP;V>{p9`kjF4h}5zI>)r3q)6G_4iR_+SrvU6#Pqkx{h<&IB zSm7^>PE!Jf`f^$Ejnos9*aKifFuvr|`!cSu41BLUIC?s`)r99#`X?Kygol-q5l%@N zl;CX0^8u3c$=AA%!>c;5h<7Hq?dzrp><`AIrR4b)y89O*0pRC@$!4#_M=&|j!ynj^ z-eP?_io7^1rIR4QzM>y|V(+1LWo@dZeAs1R^bF&=OL29*{!+Zhi7v$lU3V#ktoU(O zoulBpqLs2SK*4enw;LSos>@)y`+~3Y>ZjxY|3MwCgZYdlt9B=M8O>~nQ@Sp~*Xn4c zN*}(+?8_x!ORMf}^E6`Sc`4?EqSmPjC;Czvl;jg_>N_BToPjacMn{`?Lon>pqX0Xy zl-;b%isj$>wrB@QnA`oj?+q2o3x6Md8U5A1+ptx*&Imwj5UMMU#2*d#@)rn^0!k*Q z>eP!qzB$EB4SAq??=J2i$(8UFm*>#@G55Q?k8N;^N-V!g0@SBA8BTJe_10L~nZs_D zABiL8b!3CX8&WH*ODi16tURRGd;Krz>1pC2bk+3taV@@JWk2S4at(HXUW zSAUWFcJ+6U=-0_`AGJ%@Ujux_kItPN0%2YMr|$3m)A~#B9wMnJ+jo_(lswWQ5Y}A+ zHKmxDqa1pYr`b^^pT}OFIe1o?&`~IEE)pyxJtE`O) z5@F#y0DP0LAe8pS{4-&e<-A-+rfL0?+V4`Wr;UC{b~FHne2P zi5# zNYYG>qJt13mlR5A_AQ1A#wUXK(~;Ha5g_&^voHJ7yKGc}vNs~W=#NG?w=))A$}ai( ztCds{*ltTiV_VHa0FoWzU;HUQshF=WWCD&3+-FUPln${wlAXD07fI)PMB{~2G$9*f z5j0?(2)2JesDsdVRnJzV9L**wdiB5S>Y_c06GIMWFqSN>(kvCdGvW|}kEIe3BJnC* z&cYv!Nj@Tqa{v|U3f;uF@E5P7CYqF#_FV!MHEkCJP zxA8c@>mk5dTiZJ$slbv8c2SU<57AUnbdl0Io?w82jB&D^@ax5MC$b~{y6%WC;t7wf zd(*wiWv#P8S^j3_+ZnXINNP-GKznh4*z-rdHQUnbEDPV)rwx3u1R1vHZ!IPW53r~@ z)l8W`q30;v1@X;IpNGK=o|bRT+sKY9U;dxha{a55wTpYbJ zo9$*;qvG^DkT0_IwPDvM$aEl^8juMYdPrj<&{;^)fJ>hxyhUONNLhVy>{skvoPjOGL-)<=<2rNjEPV;YgD3ZgYM3eZX zv=46SGk|?vg<>9s2Vz1AB&^8@MW=6b!x6Zlz+9^izO*|-<;&Q&(b)$VX`(Q)*n2zA zIIX1gKHLBP08-2w*RN;jkH4QbZTOMw zowx2yw%%uuoa~CVp3ulF*y|VQ$Q) z@8On%53{d-9(@>z$HXY|*T5j({PZ_3UVP)HUp{sCub;wGfXEQO{oZ)@aB}<3!`pYb zExV0Z1n%BDxb4rl+@5Zqotz)PaOe4*bI(p*xO4uw7j~XH_uSLv&-t_E-|b&Nt3Utj z)SvmWpZUc1jeo;0{r&TuzxloR>(tw)^yj<3`)~iJfBwSs>;LkvzWXa5{IU0+_$9x` zzWBrk3*oT$kt&M^*73{&vwc4N*&Bc3_y6&K@Z!ID>$h6FPkyksv1;?_b3X*d`>Q~ZTorG;&VlSmVPG(lqp11 z`MLuhhG5-03K>j4Qla<9a|u_|FOa6lC_k7X7Q3lX(wj#;^}{>&sqy4_4C(CJ4=N9F zOu7&5_lI91g1dbfPoJA)kH&{vjDnB$?~NA|o95O%q~iPGyiOx$4P3$2>^>$>75X;$ zbO@XlzOVjZijA*0KO@dBQ@){3A^UUJ5AifD?o#oE13Zzzg+GZjT>A&d_rzt)&uWqj z1L;*n-8EtYI7j!9>OI}}TZN-Lhlpk!7)I~S@Tj{2t%w#d?qGG>2$&?S9ULNuEmrfS zQNf`>z}WxE_eBYr0yNn+h{7#g$dUvBE^l0%u>uIme0O}{L3k@Iv=%|=4<0TUkjIJu zfxfhb#|>h3=yrdiMT0;>4!N7gE(^ZlB6ujVR5C9f+zZlA5Ws~SgX?Yj0Ll39;NEPG zM*_G8y=kL_Fb2^Zb-1s93oqdL#o^A*bZa`tUV^`MR@agg7{IgqkE=-=?dG6f!`7h)Rc}>FG)%M8S zU70`J$9prMrYjRVs8wNbDukP!dP08sI_%Z=fQZFNn{bu*;kZEN-oXeg_3Hj?>)yt} z>|nfmQ#6tX@abvbL(Sa2F@88ZJn*m7)kwq07`IaE#f|ZFTgUV+Ps*aX`t*s`{7irE z;M-T=jZe1yGdls|85dQ{NLSM?f?ujXxgEJMdUEG4AoFtX3!Y9MR}9iTC0>N?i)hx2 z>9r&55=#Y;_Eu~jt7}|yc!b_Ko1p;aby}7~O&nSt^yR&696~{|k35KSi_-sS)0cNv zzcM|zw>=*}*gN7PbbckkamQ#LO?qp88{Tb;PwZGOd!SvpE#ucv)q_kMErvMPY{_g+ zBI@n*TJmCyRUF$H%cWsR2&%CM`1#QX9_IFw8oNWgM;Uqx#gm%(0$;tTlrg zAE@Rl?kB%CoxlsbB72cF*#7VU_XD?RsPCdQ!{ou0{DUE`gvt8KWb*FrLp4e4x7;|P z16dc|)a%$4-#@&MMC{1}|M$hw*TppWbl=QGm|J@FhNjzqQk)^#BzWd3T`g+4$dtGD z+SDbZ)w;H~wR^}0+RUAQRQ>_spT(WnO}~e;dQYFoWOEc zI6It77V0Jvz1lZtS2s?8n1sWA@Nj>k-lTc_u`+m>7#4SW;aQ^cF92BpzjHY1SRE4U zX^IUX^N7&z!^`6Sajtu&+(C{7;8`GuB+!gK>>;9L*oldFO3ayDG#0nt z<7S)sOh;qc&?%2rxWuMWd6LYPF+6Wn#R{C|wu=n86)`*|8NiZj=ZMc@gP1S6 z6br@dn_Ly7qtX=R($kb&zmr6TLN+cU${siv(bd+CKtM>G0UQ*^aW&zhD?7m%;+;99 zg=}!}s~UwsV^#7{r>@l!sEn>@k{N@29C)G15g==bRVhN}o<{>@Rgt)P7As_;mt6Gb zXaZB){6+-xE{@W_i^iswW#gF*I6S(oa5fB!B2#xeOLj%@?DuDj>A_?~F~pPKcxPO8 zk-k&15p2Um_N0*;W22h~c$83l!%E*F-=a!O&lYpb77KRt3P#`5#|>=*M}_C=^>AzdVmGRoh1&0#Bo) z7cIp+C+P7d^7RhPY}N0Z#%c)_e+HWW;N;=*rY046yMRw<^~TjVz}WK{5QzWXm@O8t zstl=Z+?d@s~K-dPEvJN;5&ll zYep)I&scI^}tHRC3yDSMVJt)wxp`P%1)=c zo$f(NQ?|G`=y!S^x$^mE^JfAlyy!igw)hm4PaWaRAM`-KWbFFw;N-vl_S1l!d zt!}?UV#Mx%dLw!gnq(Bqx^S7jDZfb1a98o3p=O!cD4W=V2ESL7 z7TPBdMVlt&BKVUZ@bd3|slw^@=I&(gE{}M>IJ)p0o*=#si%%A`kHs*+B0PHz2M?&) zGupWD9Kz=BbF0AfFSw*<+5&cMKzeh!_#y7#IQ+G%8`Eu^g@1;uFGdXW$wYEUAqGx8 zc~+O)Mitoy@Y!)(Q%$_qyc~b=_H-Lpzok}_js;-kndz+c-RUpEpCW0i`O| zlZ1`QN={F?V-cM4Z9D{? zz6d*703f+Rq7lC+`m+5p;lk`F25CN9)=d$gy5R4&dB&Nz%^UpOHY^+mfKryZljVfJ zC56eqUS3x$TZBg@BNelC)R30$cQHro_uj>nf0Y(!c$zG%C3bza@_KS)n&#r@V178! z6Cx6dKgXXLsO?RPk56>MM4vQU`mC9%K&#-t@|akNtNv)bINk0!6hHdkY@nM|McW*O zsJq3D(WVAhUS)2gP!C=H+Ql*`3x|`Z%*KYAT=C$a%N?d zUHKdnxwcaMI&rKK%fMkgy1cnw}#;0oGwtBEKb6CR>%Ue^k}MNs)L(Lmv!mv z=>%r5gd)h+MD>BAP$a&S$<(LkznHns=-^q2NovOX9VEM&I+4M6k^wi7ed(X_24=<_ z{FbIS#gzjB|4JQKJ<48)bvw4 zwQ4XxuR9cR2OYLKu%IPNxx2M2NE`|+R8soxzv@1+tEoL5vhq$vswA4jueBwsn>x(= zEp05YleOX}ZM%k>0bSOq`Fhz1;PRo~_^R!hIKS+cK5zTvqEPnAzj3oT)IaQJZ;1GE z)hHoDy;0)J^+thNDG;x#(Lr#V#T=rc^|<);#_;ck#|YtBl>O7^S}M9>vS<)eH!wha zKfHkYRI(gtNNC&f3TZ6|DZYxHl?}a$_$!}=lH}O5&!eKj&JPv{mn?!qn6rM>y%4ED z)^#wCCI2Ss<%W-WHe2o{-$Y)g?HXIKU=nl4V!0$DlQV*LoegVdES=`{UNzGZeXlLX zFBib^ss$8($^&Ai*J6miH(xBN>s;j{N9&BMOOQgu(Z3&v#9kU|tBAQOCxd9VO9fX=yZr9J7gT#Orw9iq}L$>O=TXXburbJ}kH z6q)6Y?~3b$^5ZZfijA!siVSiR%HXFTr=V(%Jr~Mc5B0zoi`mwcw{9fUDBpz0 zXzYAT@2gs20Oaj3$chfOJ-ivL#O*oG!19ZS$JHkcbjV#Tpu7nlDOipEL zPng1Jp;RO}FEqr8EE-90RE`K#ugZ*40^^jfnN^u2rfd__eSK8-IUgQ>%DJcga1V8; zd2W))0pPSQ+L6Es#yUPka_T?T<-p?mm>2ItYWrps)mmFRqY&^(Josj zHTa4^awzo^i6*B@CG_}X?7lbV!kC#HqW0S{Wnw{plB1;m$~+2>@UhAU)jzW0)Zr03 zKU&;Fu@4>%Fx91q^#xUgV`wBS;CIzMfhAGDfyfYD?i9ocRxB-0s8T2VKsyzr6^%lU zREx(PF)!Ab&#; zIhU;($eDII-d^948zvl8qud=%I%9GI zMi$bqUxPS4|K zs0K)z4a%~F&K=awLbhtLqO_W}D^zr%N!jg0p${p`VAaIh0#wUaJg@^SVe_VR*N8Aiwr1Tqfpo<*C(3Hx_}~4 z3(ts0d$;H@s1%u>`Yy$2nVRK4vHP zZuMAn)4WmYfI|zqy2DdMeapKrm;syNtqJ$NGu*M;na*(!?#={V(~S@68yk}`an4^X z2oj77ti^g@t{e7iH-i<$yG+~ZwcglgYoH34IadvI8UJF7^aOX^_QhkauO0_CVk*?( zz3^lw727QinzgLa2X-=#@5%Zaep4MGswM#QIyvEd7!L+dR2Q!~as$t>@@bm7nrL%M z10)q2WpvQmp(_oQefnsg5v^CWLF`*EJU|4zWaqmQP|k{)|zv z1*Klwd!`N3icW}>snO?)DLGWrBe8ieR>v;5U#`rDBlD(vYY0kn&H?UYtBtyO*tuV4 zuJGRM@b10v8p1g^UGBy!MGACXhY^Y>$pVFiW;n)vFFRsf=-*&^`P?Sb{Osv`G<(1l zCh=9o4J$JTNsk#oMRF`DXoY5;ltWobv4y82TifX!DI0`<5O=;2P%Ryhbtsm)dPo7c z+MgbM;~NdpI-ADn;!DbOx4?6+!*N5QyVqv(H<9SL0lrgRezAAgifH8dK;QpAgAi_= zgZyQXDAq(d2|MVDEd&cTZ!CqucP4=4+q*0{69S=`j^7dTCh8pRszPIeA> zCCN2Dj9bnz^d`D&*=2*)HuPkv)9Ujh1RH|$l!1^9i7bBm!R(T*QoAeeQNjQvZ5E@tgJm#Y-ra378crbU;22Qd zDv=Ly0qtdl6>;%w+d(p)s5AAHJ9x%rau5#n_~?p~T;Mw8v1qey zAtC#RT-YsNJ|qU4oYIG-C52<)T>Pr11Q{g#qw)2Vg0i*6$@#2&K_}Tch0{xp!vgv$ zfCUsGCIXT%l1tLMiz?Q09kx1Fp{MKdXDljso?R)}JR4tcRnaxh)U_Y0mgYH8^>I`8 zjGg3{FSM`uJ6SGGC6l%i9Kn~TD0V0xK1u+|_3`v#Tj`n|mXTHZdas831Uw3#-px1N zpyRs~V01nF6+=zbft^#ucuUE^=}r`5=S^5{(+v*X>Cp~>ClccL3SOy~PM>@fzNtLTEYWk^Ot4C$QQnQPxh^DH!!p6s*JmaA%^(>BQ)oxrA6%oEcIT!G%Ln~ez z7F&wFSZkBiRqyJ^c?nlT9In>3CNQB{nsiL+xK z|Ap>QOc!(4I0)t1M5F4Ku2fn-uR=(@1v_z_?t22q<+?gmJ$bJ?eKeKoUhcP5Y9wGk zs;#dXY<*UAVzjK-4~u@(9B8_Y9A>iUu2_mJub1*Q|HzLPZAJr}UhB6wF7_C`O?ZH} z3O6~`wH`W$E0pQrIabZuW!R^4@K|?9vars%W8J&8t&i>4(U`-z<|I#Y>Qyy3$hGQ~uO5oE#=&9q>zZuJqr`bMOy|&&Lrk=J@p@TRH9Ey3^>W&nPUN?OvMb%!jIvZ4MqfsG>?*5{uy_SYF7IwLb0_UvJ;bYc>=lknWdzv9hKhTh zoRB|0PVjUzjHhz+I4R>ki4m6**U{B1bk~M|Id3$u`OvD~21}C~F0{+4x@SO9ByMhv z`Pw{uy}3QHh`gsU(>d3>-NU z)>?0ez|BWP;H^bgyA&?D2Y7?gu06~pWZqu~5(|#L1mS}f7$^`@x*s^+!7&V>>(Wi? zs%2vBgpkHE^;I`E4tlULtq;#oi`ik2l&_WIB``V314SY@c-S+O8&7;8@DLP#EHP%* z#4;SgQ3=FA!82JEQ3bj$HglEHA%W@Qs}vCD^OSbz^#zDW0gS7Y2P+{GR(VKreE4b| z6~*|D7AVFZ-b#$}5K5WK>JZ^sUWC4o5CkeN4tMY>;uNof;WZs;g?nE>f7p1)n^D`{6NO2`XXB{J$Xmu#tL*nNj*5`70l~^hu2T>D-|=qrO220g++5x zUt-Eb@m|TKx_v6ZZR+3~@5&1|GIq&a9O$K&@buf>bZg@?cI7pGSFp1GBbQ6SJFwcB_%V`_J*`N0H0)Dh3yvO^xqP zc3NYrcFy!gJ7EYb7aL9~L5*)}m%c> zO(}&X#b6&yp}(_4x{CKHnw8uHfFfn!DKZz5k^M1hND;=vTf*}ho;sLp;|&KU;RvA7 z*Chp&jxXCvzKkcvLR|1_@Y^U!0&#t(HsBNz0loWpgB|K`?#{;Bmwbp9>*pk<+j6bj zrsmA6dEZA~Ak8})nfM3#`E+>p5>0Oc=j-K!4SF)AT&P3*!kkV}4Kq{~Edps%++7Ro zk}^`|4W>b!OSI^xs4+=H+{b&li+e(oEO~u@hG7r)5Adj$YHfnd!SFG`*`|WT+s|@Y zm+EpCHw|zhPGDZlb`Id|OpsDC-Qok*8q=$Q;+vH7@qNBz$lMVW$(~BC#TZ#A_rcUu z(2jzlD`v~zr$(~!z3J9HjzK(>uSnw)lW5&AIXM5_A;QiY1?sZC7&BgIBym{|6H#Af^l&*NnuUK%~K`E(ndWY?4ye>=ZnaZSqUmiiKYm^R2_W*T{DCzBNMxegL4^GNO@+Vt2CF|zFcy2yl zrDrj|GWK+aVvc;cniE50pmM_lOdgkLW8PHQ#U%zXJ!l5HgS}E#(|kgC1#{zzF&~sp zy@RO<4q&3AbXhZUn;hVdU3T3lTKARNY`39_EzT-ul&r91Kq}h}RuQPMJv3Z}-Bt_(fQ`$EiDmIz8ts7Rl5Z|YBRSqDrPjb+ zw1Wub7MYdW7S&Q>E+2#H2YTb;C^(jc_-46LCkImH=vJthzH~*|(7(2k^`TICEG?GH zW*N@;r|cKhNqsA@${`jT)qof48P*rg$HQns%Hwg`H5fD+LvY(%u9SIBzCTlJ9+1i} zf)L1^q(@$Hn%chD8RZ)t@S5C_*@{a6?gS){kA^xD=&3#&&(Vf0pQiEwG;LXX2)j0hnW6*!+A%(iB` z6gKfJDT8ny+U0M``68znxi?Y_Xz$(FA)2%kIFQ|kJ>C9Hw8d;R8N;z~_mxkJz{!{& z?y(+$d_0-&rXu>TAY>rEkR!uNi9B?Zk++i9;d*vW`6xxX_8mm}cg61@N{95vbw&9` zC0#@xVdeVdjV)`}@i;gUo7$$eJ5f_GN5`%vrGnsSa<5N=!Es=O1*Azf`Oki#$1<&u zBZ8gZ)W`sPAqFK?As)KFTHm8|B@Pn+UlpW47$M0a2jmY1=@mg*^R8oWfkRgmd$%kB zK&>v{R*d>%&E$sQZh_eTBWEhtgXO5gLdk;UL$BImUj=*bU(P3s8pmar%b!&!XgJzc?xx0ecZmD}`1Fwgi z&aq}8Zi?uVl705VI?dQ&t;yCUmsC7dU$vrR2^le5tPm(6{&Cq8`&KhM-Q`ht#e`H} z8QJ3V!RP)Qxl}}G9a=3LM77z~6!T>QcnY5m@HhvC+K4fmWM#h)j5=whJouF<#j|rzxJr5?+yZ3m3dTWfJ zkhoSNZE#Z<)iGtnB1-pB-l)5bOhRnbujrtNH8dRtb7%-Sprf`ln1IMpuOpMY&$C42*OVnOXOE~N0`@)7P0FVKOJ91VLl@Nx7d3+e%s4sO^v{gsh` z{vZHGdB{#ERUjeTbdDY3oq;5!Hs2O5sGI>Rxf3yK!n@kp) z*xur|eE|=Z6Z#MD0H6>9A{I8YpHN|d`ivS{H=E#+`j-xHyR$8Z#c<-SdGoW`Pz694 zl?fw=^GYg2iRn?O6a$O-bH5_4X5;H-(}O+Du0|-s0wSV{NWk1q%|Su>rUeir2L{ul zrzM{FYnV_PRaj+z8(-d1=2?@E7r8HLZC=Aw8VsXvcWXKgZukVbDq8_`ybfxKUV%@o z_0C4Geff>Ah|JoY#ackMk`hC9Q5NlwN^=%$|GI1UK$P0={9S47)x@QC%27)WbWZFY zr|Ap|KQDaQTbwvV^IrZt$97}Ss`)pG55<0DC>AKHvg#_9q+g`dg+S9gG6>=`5&o0f z=T-B`#;vaTO;nJEs%|AE!q1EYDCzj7a(;uu=7UUeABfr2o}Druws_&bdiI=ET@fwjRiyL@UWyCkHT#(x?EjjI=R1p@bEeYWPCk~p4)*PsF5YQk#w39 zG&vYJ>e(m;Y>nhqoS4pM8rn>DlGu52glcPh7QNz84*q{Z&z6O73Zfy%C1d1xj0PEN znKLThGZ@QJK?UUTX>9dIt4QJPgjJ`wN2$ILE4G|^gOkdGE3ih`<M;pkrNo4V3RWoTSm~m!SLMb$C7`9X>`V(ei;;kY;xfny2+(3kdGlneOP+Yvmqs? zK!mu}%R)EA%#2iuPEY7@lonmo2WyfR`W%D3S-C7qt|mB?){W|f`a@3=fXg~5Vko;( z!8xgO1+dtwJh>q#ctFCj!aGtBGEZQTlX0eGDfV>TM^Xo=E9mu7q_A(HY2`rbD06H7?e(gdjSg7nG4n@^$|Fe!OQ zOIKcvz`B@4rsFz_54V&Wba}-l*+tartXvb$m3T9|?9jEG22$w!A|lQ0UxRfKCOISr zJEU_1vWBnSMK=Vl)cAxDqex}E+?YZICh2y3T6w$%jArwtNMn2-n{(TDJ+>WJ0}j`; zT0C)0VWpU_d@{E?cO%8gKALZ3!A3-h13eH1Nk?qSV zdk6sanpy~+=sEkG;i^hFw7jC~%HhG8kw+c7sOG%xr=WGtcuVmIL)3AV2ViKmr7u^t zQ&)c%cB;jJn)Cp9c#75ZTupK{G-)yiKmT3si2NJl`!FP%$h0#m`Aw%p?<)3UeY2xZ z6M)v#`E>p}+a&u(4I~-vm*cu&oO&qPJZ&P$32A{F^_QhHT>(bT?;N0^Ih0VR$~les@ZXk;U|W1EF$M&1rp0GMHV$^ zx8Kl6OFZV$X?Szf-4)8n1w2v#I-;x5)a&ArN;Uw2?|M2X;kg#5FA++9l||RSv8vr~ ze8b#kacNisoeG&{1Yg)Ve`d4>CTa%lZx4+dLvWAn0fzq3&92T)n;?+@rpesHZmVDq zq5y1#O?O94aefo$Ivk>!gsQ?NB7P;u1;>#)^?^=FeowF z`U*bNN9-Wjv`4YXC*u_yW8zlUiAw$?bL41jKRT|_j}b!{RlM!oP`+Xd(K9RO+9ErMa zAz9Sr=eM-0Z28jbX~?d*KRw{h5k|cvr<69Es#XSRDqL`ie5eN#^wUf!##gdhT$1jf zSuq@&_pNvtLx&AKa6HWjV|UA~gtcG~++WnVIpE^A`cRvh00}1>jDR>mh>`t3gNss# zo9iz9f}Oq8U`_-OI`A!vUenbCS9d8wuyi z(aXms6TIVPiHoU}xG4uWugKZlt%v&)34)q^qFcjbhDRLXbT6H`$rEf(2hD*kn$53fyJGi~0?7TZzpmH(FW*&IEE*Ja2#T1I!PM>(Myn+#5+Mw7qN zv`g#ZwuP_BRJj(}n5nIw0k^pKVZ)R+l%_$q)`h=^gkY-p-h1B+_R5(;cEo3mQOrod}VVG9+o_O-G7xH<`Rwyto zw?~^!j_i?$0}Xp*32Em%5_vG?YWkQottL(fm=>A=8DXgmIV6XagkQJ#oD*osK_D&o z^D*2IJ)*il8G`{-7Q3WIi?#NG=*(;e_Ox5!Q@OYdS`BuFY^9AfSBpvDD$B$rT*u6usP!{@H z*{_4%;TpySlgVW+F=-**L_Soj4#tm=$d- zfzE0P;A{JwxA{HmL}%N=YqxjX8SYF#e;#V12MQhCL-`i`Yx4eN>ri57fK%q+r8^|1 z*(i28!8|D>Fx$z7tffG+cm&rc4}=u?VPE%oUEuZ>^SD)CZ=69oR!Zx>A?xk-lX=BK z)J92ZXB^0=95@7VIN?1?3Bbl}6+ZtPEJ$hbJNB|h-;q!PQuNUSaTo(H@U)w{NVvfsMSvsx&T$VK|LvkJF zHf-c;*@j$K5OgWbKCs1xjl~A~_`h*~zeL0!eXan>+c*18u_t*bZW94y#6>igZ^0ia@NR5eq(d{4=ed-B!}1 znOf+fqXq_s`8+%_rl@3zo)CEG6k!t0YLVb`YHgwZm?6V4@+MLSs3N%d$oYG1_AYvx zV~Rpxz*zZ>lmojMAQ653^BJ_vS0`^ZKwWv%;Av5~&r}bkm&jK>D5vbR1JQQ1&COZD z71nw-FNP<76d#DF6rUWCTZSMLrYQyvG*-NW#D5-9%=RF5Gd^f7#k^>$G!eWb^&%ZZ+2Bj&etT#vPmF#k ztMF-%pXT`hONSL8X0-7&BkMP<5`8*z`L$9hJ5?6tX>OG6O%5K+=2AdSQ8@8hq$TGq znMSIfEkOs(cvft3of%ayZS`cF%dC2~z?~9#Lqv@-*bsGMq#h#~%S%_8iMS5R3NG3G z8k5*@(H=MQ$;n`q*&$Lq#lj1fRUP8Hhcbga63VkJd;<0~ggR%-u+F37?QKq_fu#+; zovD>Ty?lCfR;Qz3?H5 zt|EHDiekP)O!b!w;6lu}sc|{Or4`RlkEQ7;JoY8K6D^(ZLgcJA zQ0Vpmz7%y$MpNttaYsbFUNdI!+w^La`*A(Mu%QhBD-)TQLYFQ(uL^E`c3rTX-2Be;gp>#Jljuqq>+Rg2`bpL$~qS zj^-(WKuj%L&`W2u)I(+g2T%-G=Bpf>^#Q(WRdNcCaew>}p$;HiE;_8hI7hL{HWx<% zG(XvtkFa2QA|*pMtp;z`d&L}ye;Og}v23jq(~i>k_B*x+87p}$^aGaEh^aXa=x}V< zn;7b;lz^jHWVX&{rM)~v)(p<}HII^B4b zRG;E57ujFf2l2}B_V{2dCHSN=YYfk~de~-=I=#msZtBkU27q{SbbIt}?vC5Y7g0rN zwws4GvW;l5F)JvyWGluc4-x0u9(Km4ek^Dqs(=+17DN5{9R09tzVPzKfqk0bdK+tM zcADMNXM1(OWq76x zT)8*dde@7_j_%@_1w0ZYZ%>F6aBHmBbr&qJ3=M$0j3~-Bx;J^B!FwJ~!si#vak#fw zuMGl+JrC>9+4V_gz%pNT4>!gSQR{)n89@u_>z?}*Wl_MP*CA@xt|AyX-P=DzU6SzG zDJ_+HyYuj1@2!@>F+8>#K3(Q_}HL512;_SuW0 z^Uw2PfxOiho^M+{^YpnhxnL9V{Bh-m3-rmE;}^*&87F70Bb1Xdc{&&x+%Y?U%Yzi- zJBY*;*tU&owmGlxYp@oaw>8^;c!}*A?k$HqL#vSL!)JhhNzP5MLtq)c-1&e3w>oj; zmMB8aePD>NI>S8iD_(kW2Ew7w4SRbBB8SC&Wh|j>5nH(bwmRIngDEssj z$$pfqImr`DlZ1JHgQ*`*WMlqti7RW=J47~OuReXoMjUcago^y%@pS;cjnw3T6MptJ zPp%syY}`SetTaS9B7nqt1={7E#5RFJA_dCjmK2PEX7<;1oPH=hrs_l<)S5waA^A_k zEV}vHMyhj4I@4mrz{vOxvjk}eY^zq!cscE17dq& z=br7Jb23~q2={6`158rRGyJ?j3a*kmVNn1YX9q#r4H z9Aof&L2^Vma>dc&5G5p;P*sfwpkr!2g(CoVOz zj4EjW<~jbK{2ofGDQIv+;#k#l)3n-QAWlw0cr9W`31wJ7fanr)({sUsODZ^0Zg~Z{ zl8hcoV+5Y7S{Y{R zLwus4Kpxe*5-wMlDrcVGzWR<7s6IzO;ZxLnaRcWx4n*EF>uchY1LU1N;`l|9jC|A? z2Pz{^csFz|i;w@GoUlBGl7v+reIf%x?KnHFq3!uAu`pv63%F4`k_DkW^cRuHm6ZPdXF_=T3pb#&i%U1Dd=15{4biqutW4$p}-c3{A^H${RhVQjUSt zdt?Y#6&IZ%JP~ppaTM3QR2cp>E)!ug1*+i*DZ5>~L)9z~392t5WR<6+ z9lFqn;j^#C*Hxr^7}9l1CI;L=Zg!sG$l?r=zz)Q)F4Hf?qyHbNSkf(Xi5#7VFtj{;$T~yjXBbHL| zJ*xikzT)-HRF*F%18SX9QM&E$H}-J0sQrzXMMA9aMw$%h&ZaF3QQnzPO53bsxsV+k z`2)c4`oVe5B14y><}nQ*7xL4Nh?@NdkUzj*evhE~mHP=+ZUOLs&3m$DEZmlFFrsuKwUB=MHH0!jr%$iTfPiqj|r zCtd~a)Q6^n-f=Kj+k2G(Ag18A2`xR8v4E=S`Ws&j$v6zjT-gXlGAgZmLGh?It~5xJ zS3)UO%CLR%)jpadxov0%l-7=s7pf7(aLxk?Swwal+9$AsPU@To+)QC;@j$s37xd*2 zV_HD5U%Us_!Cz%xqoIH}4r*7JQ9b6IS36NFzpWp&iG$bdca9T5h%BrKj+1C2j30Eb zmYG>me8wLcpT+Q5#Qk8LK8(?$_R9%;hCgi~%RmH(X(uekNCbxQCqZERNpl;2`cLa- zSIp)9$cgVVvJCHO?smRl$_w{OBFVIg(r{BEqCb$UN$C&7dZMwUdPT2DPe{B@ri5Vt za4?A^1pP3keWJo5}>5IIW(k{w})AEk>23!uZjdboE~f4ItO z<{C!EiqiU7^6JD>=1XyRYns4b8HQCBKd8FiY8l!ItX9vBioquv!i?`#e==DxLSRfD|LfA-2(b~^MLJxHBB z$c7h=Ii)4kzL$c8l@Wp>*HOx^T!wUTbft5EWLfEGI+4s3qhTwW%_f_Zk*Jl5O(p&h z`cZSrh3tQaT9N#>?$C$F0x5k*Ru4)sT}WEtL^_!ZXQSbKsyP~WLJ*5q57ws5TOQx? zSpSxv^dJ5DZ=OD7WqrfltH>GIiYl8(AvmFRt>dpI{ds89rJ>M^pSrNCCG>^5pUoNs z_{mtZn2aYAQ7fKI=99%#Do&qRsdy@yj3i^Jcs!Cy;E4=AsUG8`o2hgW)_+@3`IizS zX7m-PC$cbdnb@I>&qjs?<$B_}6f`4BTcmrDd@DvIUGiJd4|R?hc@sI3XzNeVw=jr& zQe`^IRFP`t(BNhX#D+DcK6Hns?%8E#I!QdBX3{Q7d6*vw4=gTtvSB6MujuBouB*L! z{Y)bBqnXN~a-j@oGLDDyIOC7b_P|{!6FbCtSPAm-P&<@MfgA}kC8RXE;gx1gwA$>u~QH>yWC@8^GWZY0&_Cg09Q{KX9H81 z#H98qG%RNOSlr4Y-<>wDvfKht5w2b%9g1%King9dAT>>Izd17NZ)7MjjiN8lA&TG^ zbeGnN9^`~U9-YXz)XiTQD&9!&5_m-b-fmSxBHQ2xF6)l3& zh$6I3K2zLJWXs4?8v&!RtE1#}t0A4B54YsZtuSnIeTIU0BsX$|23sT#RCZ^`y(JE( z27+`qoF+Eko(ucGy#q%$Ue)l(NVfnB@4z~x9;tF@{kqx*H=`YCE4qLWGjKkTm_vbN zyBOtFcJ7W*Z0|;<4G`p+!zF3lGmtA!uS3sKq6S$=y%712b^{I}r5gA&Mot@@#-0hw zOEUea{spTf!A4tP;}FtLKcFz>7J>Q!{R^A39)#8;4}2u8K{{F=m6Jd>mcMBj^dlPS ztY%A5?|saJDm?D_%=?-`2A)>G>W74tVhbPTye2=Sa^}cw_IOxlbab2~PA=hI5CVUi zgO@fM7%^Qtjxp`&;r?X~!R?h&z8!gt1tW=;Lr5gDL>{HWE9I{_)_K(-)X6iO#^Z`@6>GY-EqxtjX~N(xib08LlSr z*0xA&TTzZQ31VRCB=xfM1@SP?uU3kvNn5K_i(0`C@YB#cY9%lf1w6)*})){VV@yF_2o5J1$TL+ z)uaRotgewFrJmzpvAbEDxX>`u5ELZv$Vfd48c0- zre!r9vOt`GNR~7Qb@nv1B>@fVn1^T%s;fV_1<5*1NQxMyX?bbnztemm2oiOQZ3VB9 z#=?!qXmY;ygKmElr1!h9Yi&UI}7Sz+Ds5+7yEy;W$8S4%W-iOhC;( zFnMgf z_iA-|XPkU5Y$5kni-Q%*(QheM_iSJ}$>@VK&$~_>DRjA25wa~gB+6zFRAY!|Ru1H0 z<7YnTRd#pM5LpD*~7Hph6vB?jHs-%NDQ_a&wNIW6%eRTlu%HNx_Tyc5%-SNkP z8=664f`&7XKLwT#EqKIMIgwX+OMQmoGtg&twoBdWrvD5sg+(qw{zJMQ(&gzs1Z+7e zNjulmedHXX<#eHfZ+L3qM$QLSp+$=U(x&sQ&YN<80^%*^2L|^^yTYyxVm>FkAWIO5 z^lUjpQXZib7;Lcc?93(2t+XJ91OabY7IIkqwW4DIElN|!2TWLvVF~km9?Qqnd2I6t zJ)svPMpcu_WzZ7_-4KA{x*r+ON>MN@A1c%buPychyI^OBXvpb`ZC}@H@r)Ehu&iBB zlK~9kQRyqn#^81IG^uH2t_p!@{%CZm4_Wc@S1Hv{oaN`#Qt53diffuY8diA~S-w+N zE1vE*+(g&n71;D|&6z>@8X0JiFRLF)1_=GdAwS3xm~+zc zAYa=s6u!%5aY360l4^i59mN&jKp?9`1JSB7Vf;z94bojPL@O4|9t)eL$^IkO@io{7 zZ$SZr!mo;GPktqPo+S_2wkc|(f&-L}sHp|7f}bdnr-%w8u)FmLe*R!Q$~|J#Qr)~y zsL#ScITju-I%ix=G$0hP(^p~N?*t`M8YEyegrsx(W-EP2KfcUYz4$UwEw4{23T2`~ z)0JLeU8vo1+w?mu>(t^&#OdE%sP%>3DJ(FSS877-hY12X!23>orkt<7zH^X^sNO_A z{bq5huxX^|4U-TtQEe!hT}fQo)k~H|LpO!%oon+#0eu!EQ1J|fJcA2}>U+I!O~y^rc_RxGET2znB%bK;sN{kdzCt6G2_%Z7)bqaXd1=@1}m*FG4u z*aojoBbnv!0ve$C`aOKkkpwM+x4xm4X=9qd= z+H2w8FpLG$3NX}cVF2Yh$GShpnmMBSxT z@L`^m?d!0w42hf%F*n`Br#@kwx>1O}e(|6O z04lRu)AVHZ8d!k2jQRCunyWsZzjDo3`x0S{m)4h=VkXM7(t#zL^$4t1HHApSV(6>M z!s4}bEAt$dHEQG(LZ> zSo$3FMP?C7aFGL7xD@~t-ggQLRT&!TC3sUfg;dD~Sbq3OI4{~ze*)J#kU-asT8F3+ z&DnDxFQf`-_&Ar1{Yq*GwTgwZTCWHN^U}$N6d87ERV3D^z>K^Nq@^5Z?qG2u{(w~w zs0FLoPU~*(RP{jcA!H899j!%y8}f&sYzPEQzP*H7ny@tKDv+E~;9O&cnlSLBKu@;4 zyQyJ@sAdMiDF_W3j;uB;JY*OM;42XD7ZI*ntId%PERbx~!|(>Wgmx30{%hI`R`(R= z9yKjQy)+cF@O(Zq+rh--434*g$h{^3f-2uO#J{645X39lUvmLSF?6m zO0G?J{5e(PT+c{&W<|Z+(Jl5!9o;+_7XdwzLM9;59$OKov7}+6bKMzbjhalwX!a{k z3X~Yrz*Rwjk_=>JXeaftqz06>*O&MW*~^L}+@2D0;ln;2~0`}^~ z9Zq2f2v;X_lqgaqRPTnV4vZM{(4ysRTHMTD)z!QHB&}rInvYWuJH;bE&*B3_rce#q zqDoynABGoBEK%wosU%9Afv!m~O0hBOgiIT$NoOzndEL5>0r zx6(zL2nz5DRe|}4-f9;qy9AlAH3=k~|LiuyukZ-b`Am7lq}B5kPKJe*=C?1SwnnQkX|4gW#97p9XbcQ1bkv?dPC=NzaJz5=#3AOZ+c&GYznZ{ z5$Mw5{YFzwlD(N1;F`g~e>18)6#W+7d2Gp}VoLE?mKo@^)bN)i5 z5jq+nkj^Q6I+B5e89*lsvC=9i&q8-%?bO37TF(@7Vrd#G079yc)YIsU5}pm#dFUG& z8|>^n7U@Gh1mTi=9u89R)r9FamYL=;r^oT^Fg-4!(gG+HOvF)7@UBuajb*ZQ&e-Mf zF_I|)%@;_{nL^N*UH__x!v@z+OH-aBkBA|1@pc)ZPbHS8%Q_2w?w0$H34Z+OFhgDTND%)HRAQdIMwIv_OaTUSb1igAU>kHYyUk{e` zbMU-Ko-NPzVm=q!)0;IgqtuV+h*C$taK*r%Uo4{S9u9CgNNH5Iz92dT*V>w?^DvxO z>P6(7L=3$HjY2%mzKLvKZwY}zlw9thk|(mCES^z@q(&{tFVjIQ6cPy{!;EW4rEXs! z6rbCecyEY>U&6_f60NS^WH`V;}63gd{PA7GRf=c4&I%XiWxDIP4Fh*ocj^IoA!K z&GsC&kV{gb492lAARGZ7CVPfndLRWL>RJSJC_qg#%p9nSSi1JMzSjoabDD) zGEX&rdbw0+m#e0tDoK^7e!22WEeBd;R;__({i|bPIJ;Jg)(5->arSI*s=R94jnftCGgOeT;sT4*{zM?wm7-M?K_ZW53v{^1Y_Bt>(Q+TaWbpngnCdIOURgEumE9LJfKmg7fSZ?DW_U>smsq zYgvK2z(NWa|4|HpN&uviiK=6zat$Ni9<%-?)K!>LJq|0@RfvYGhvM8bw>;6m<EJu|dK0vNhn|1$cRs{rW@b=8CF*=~0Uh;Y0nP|rG(3P7mc zY$IKmI?t?8zck^m1?Vb)RxF4S)Qy~Q)#smlX3_`WY%o`}=gQvh(Q8g?&3&%l*xDTA=eR@XkobJC`5W_p6rB%*xEI7CDuY)|zB}7hb8&=YKl+!K1Mw-9t&bHB>JF zmkH8U6=T}98CTN32_0g@hzV~s?6%?cvv#}Pu_60sb5C9Jy-ywd@{$RM?RC(b&)XZa zLY~k>kcGa!_QIB{MmF7WYN{oau8cgzOH-vfXrpYWop$L4%2rp8vWheuO$~tC)%JE1 zqUO$8OukCF$?Nv?ekOWZji{S&@qTa3J^iICCz@Cs_fqkJ-;Fxx#Jd&t>J-LyJ@&B^ z8>%`G6+HStJj}+~(rbSEN=xWdb@!Y*7}odZ;-Jt>K9%5;CXtFJBJt|a%KGL*8|n|O zKeVCY_-rSl@$9yt<_g?i5!OMzgzFQq8sU&$1540uZq!{uhUMzMQa3u%=W!RXy(l{G zkVOF%rLPMuG4K`*%_X-)q?+#4%MlSqB_V)f)?A|p7HbdP(IV@S63UG+!~jhK&Xe2V zWRqjyIT`}5BGe+1k5vIC3vKu?hG>yX^-~ccBAXa%(Ctg5l?tDkQm8OT17!oOKLmh^ z$PvDkPCf#Q5&_uK?V%yz&J%0nK%$tBK*_2KpH4XC3TB9Vd^yxw?_?t!DC0V(v#CPl zBGe7;YUo0Y3C&{(3#?KaS{`kY0W!xJ2yA-Ro=;=73^GO9hW$Km1tulfv2DncrR^5u zif}FYOsKb#-XG*1!DiG*W(!NkCn&#TISl~2Kt-#mK{w-<2AxmIAsA6Wttf!63(6=% z0XG3leJk3~tOj@{7lR}~n0Pp6yFe}UGA3Hfl{N4O{HNqUe&7pVBn<1D~`*|LaGjK?J0-omjdYQV{+P%K3 zx8#$XC)WXl+X;C`4DnCBvi|4KTfA^a>*9u!W-OdLW9D(Q7pZTCc)}x8Dj$QFNu zSPPqnF7PZ2iP>eUIyu!ip+WML*%4;0u1$li9p*GufEC;eCNN@baH5g<$HViwV3V-h zzP%Gl3*5~;XvsnsWNhFT+l)_?=M<`%4n4hQ5TbAtX{XqLqE=KGlv}#gp2tQ4QjIAG znnbCNS+MNrKy<1=h#(m)%g5^`1*iZncM3N}D0PvzKyY_2_=uy7N)YP@=g|>C?RI3_ zB{8g?o`i~imTvd)>KUmj@Xwd`WjE+NhGMC8k`s8s5=$eM*F!a%m*dh0xrC6P0#G3Y zY2#Jy6;@A7W4N9arWpcrsnmo*g2zA7gv$>qlC4D7fChP6ka=o6P9qJ|rixs^BG~&Y& zSgfhHQ8e6IX9A{ZLENUMp(RZkfSowEvp15oBlbfs0^CBUb^g3Xi)YMRyd8uNF0ht5 z`QW2UVzTf%R|BAfOYFB|KjG6PSwY?^b}AVb7hT=8>!Hz-)RQL>9~fUcpVZs6&_bk) z5^Lxjk^;^xYlMO)s}u2VH;KC*G#AoesLq(&Ky^w8Y~_W+!0q72oQdG<{+uj;B;3i5`C<*V&$Sd6Y>aRMzN4&+_@0iZ7Ivd zfGnda*_8xCWrbAFNaFEWx`j)5s4SqXljsVg1c*L5aHwLNDL0k$fAkswsZl>r*%01^ zch>dE9EBoE^4I}W*_B8w$=0nUZ7P7+s*v(a9Kl5C#A8AUr{8Qq zgWF}hnh8NF3QoDsYGHi%$c6F9T&{`7%4KnDlH(V^D$hsNc+H3y7rB!_l60qsgqcI? zww?ohpdlZY%UP6XUMU(>!j4Zi2jZZi*aOqKGoZ1Z;}U0)QlYMvqP7Ic&tL`m+sz0l zZHkVu%w>YR1uAGo@PQ-l&T8$d9Opz!KG7!~r~avfhgP81s9+Ie(!|F@`GLW^n4*$Z za484p95O;QM=$%T8LuNMV`sY%a@0sxoptmT{qWbsOL9Pj9*w_jHl}Cw%@y&i;&n== z(+j5O2or0CaRgCi4A;b!f>rTYdQ|-wA_T*ke;p==a?IdNKCt5n0x;(Yju-h+Ct=`e zqewjg#194TnDZSMEiE)U@Yuaa2n~Uf6b38!^AWg49tu`iNxJj>(=c+!aTs>KTTDLS92w14lwf)S&e7%LulB zXpyIfEm(**XwfwLVr4|D)hFYw)kF99?lUM4?i)sz=LG|0#zPut`r5z{{YffJu>-d2 z{7wMW-d(^pd*(ztXdKnacw=lb7w$rAma^Xq?Q7b}<30sEhLzG@^H5$6qEqGwHAr-F z!=cVOoi7kb7#g=HakHHACVe0}NDUS<;(4?lpv>_uQh_Aw5x10}#|2>Y-^1+ZocL;> z>~+`1xL-A=biVCenj~k;r=K$RgJ{ZRBqWo_W?;e#CG;yKN5l4)t_3{|y^Dhdx`i;C zaKiK{7lxEeM3rs_kF_q3@+{g-;vmcA&Lb9zBV|b0R4o9Jyg?c&ePwsVg-@~zTM5?_ z`+4vsVnJ;sJF(LNJ1IrXlJY3UOi(aj;^MAQLHa%&FJQ4S@c9)CEovFyMCUi;^YYLa zwj~}C*f&DAiW}Q|&`_6wC01O-UDJ32!3GImSzh|}@FBWUOdTj}m&mD>PHa$3 z4jp^r$;qaX=|@tSd|hKqd}&@;r%#+mihR|qOz0xcqhy&=;o&+#IHX$*hqPPp>G^H) zR5DTMv*bq;n5WIsd(Jr(dOYf;Z+Ty(z;_fyhbug2>#=RZli>hNlI=oBq%an2po!QmbelDhs+7BjtS^N zb`jaAHI|2|^ipxn721t#sKCGD=9*G~siE-FDT->RIRU0Jg0M&7?xCk#QdB}ZqaZxA z_K12k6lNs8rW2lBM(mNM?Wa*~=|%(54|aeV8Xj9Hm5SM=eN=|a#hXI45_~S%PHm%5 za?2}NG=K-M^r4~$(MXFS7T~QV=_zayb>o$uH-F^j>xb<9r*WDjsbp z;B^|xhyl@sq(y4O8yN(>TNCY6FcSdvyK9t_1UZN?XkX{fD*wJya;Oo#pBe&0JtW19 zF}q`rHkLnc>Gnp0DV}IZdro665;sRtZ%tuW%FTt=q)A75DJ1=B2&A9Rc88I&hTy?b zQt~m$iPr1|by7g|5boRuhFm8f6na{Eu!oza%`Gg|v$`NhSV~A(+D<8oK$do+TETSs zbzh)SN*O034v&&#sR(g`0n&dOXhGa@LHbLllLE8{~Q1CRMQ@?3mF1 zj6D;4!ygc*EGPUfhw5NB9tj!3&qSVnmm5+#bofvwPZCaW1b<~IDDpqzI2E2I7WE{M zu=`Rp>S>8i+Te#26~XoQbXgH~J)4-Rz`>Bn*%b)Jtl)SkRJ>eYJMMUR%`V$7D?OmJ zEb|N|co$<*$@I1x4T?554pwZhQKeiNLL4euvy}@qqH~4zz&ul<9fzd9q1(v$;MH4h zi&)GB1GiUr;I#}_a~hRP)uh1PR5$K~0wx2>O5!?3mb(xzE~^o4@30KYKu2QAXjF&) zWKx^jI~^D!~}BTq-6qgT&V;N_Q;+w#nXx*?9td?__a(LdGiNJilM#PQI;D#Lpr==>!a1ik7yeoD9IhTi%M=yy+C5q*@ zlpD$>E;T5Vz1}EToLmq_W>pa0eL*I$XadsaX@o^eRU@ri7@AAV%%%3+F_9oI8VQ9L z0%9U+zT<^;qPX2V6+zAlJTo{D1K&g!N_>!aoIS%dmjhR7XlXil7UAc;wee;zTbmLJ zIEO_dhjnu&Q?uCLvYuThodHqXpv`Mm~Pk4;eZTZ8d3( z4yAmV4S-sz@3Ne5H*s;4LZb%WJ}LYsJc)>6?kv<2mk?H*j25J(k?)w35RME^r<0TC z00YUwB@q2uvt>SKig76m4Z2Csdc3&;aTZJ7JtEecO%-kI0aYy<8o2~t5i3|=vSJ^F zs*o6jmPb_9Nu=Vy2&D?c6mH?&paIzsh3{5yinQ>V%5@%yj$z=uM7u=NWe|(A>qRKym(!oTksvWH`r)I&7nuIVs6_b<~om2{; zMz&D5d6_^hODx*UtWPS8`^?qcsySb7Q29gCUM$HFv0+fz93xTMR;zPeGNVHnd9#+{ zU0v|d;3)bP)H>JJe2n}aP(RKSQ4nDmHG! zQRO;Zt78S{E|{5Sq)w6_QYTLmE8vWWtDkexoFq*vBmgyRN;#e05SFT`eeMVamBxc) zWS67=U~)x1L&=k|nt@!*PCIpUwg-#D4P}GSh~Mc$dq_?@6KPag${-eO?S#}IN5TNM z8a@Z62<#dPb8*#Wl|-x(-((5{M5W$idYQC7@fO$`WWRt%!0*mWg7m0D1A>ix)OxVnHT!ArkTerT895cqRV3GhyzBKV;1Xe6@`1LNeNZk zk!cuuA}X0pnz-69$P#fBI)-J1x921_R*tY&*MyYo_M*@F_3$n>t-N}xFe}@e6|X7s z3Pf1}*PPos7g=4nJw)lfi?b_0DRYjSfAZ8;kvpjGg>3jG~vB!+|THtRy>SfMyl#3DK;DR2gLVY`e_%q_JSz4N%4Z_ zW~yc+`IYyi-6}X4!Als55u}BsXafX5EC|l;D3MzRNyQ3W`qyo&gAbe{ie8pS+!BL* zZBLmR?Z_k1h7(D2jguBrB!?%Js~|zuQ0ODS5F^i#s5r@1ep~=~V3AAfSt5U7>wf7( zTRWlwv`mmdAWjJbQ|8kXB%bv%c-ky(g5S~Kb!sesvUgIrZFzB;SJ<{ye29cO)X;NG zP182nPYcsRQ-JqTHI#TzGLvWR3Ji)ad7PEX&EgoGNPwRd#y+qv(My=9!eL44kkCwS z02MehV@S~iEP8BnCe*_s90-qt111GQbuLo_0^4t@C-t(*BD#FfQnFOQWg0jqX$0Oc zIWg);dd@q;4l~$;n*wY;S-DKpuN8bBuFOZCeD20@ZpH%9(6;OLn|e zr}5iKfXC1?#W~p{q!Ou3oR&zq0HcZkA6?QAe+yd-SEcC^1OtXc(Vkqu;xdFpsFQ3f zqDI&3LYsG4i+aTb4$BeP)1m~U%BmA*F_d2sG@$y%$`wM`ovq;$!T^;FW%*!`24B_S zfnZcXl|!B^kpW|fQW$_E)?Fo3(iF7WmX=HgzCbB2NSZIofMw1|pg{_Q99r7|(eR2IwXj`7QJoquW>uaA-T~ zM2h0KYA;)7oVa-I{CQ{#0Vu?A0x^#pu&6b`V}V%ln_8?qXqsV`uHl-kOugQGEa0`f z7h8l88x>CAs%jOc-BPz|@8n`zi3jI8j6H(d`K%h;=J6h<1SfGRAL7$Zc_i_IQ?Aky z<%2~2dSHVEX?pr0sz8vPB%EeMkQJwi(=hC|fxd;SU+u_$C$tjr>5UVkh)BGkN@K<* z`b0?go^m-aG=K!K2@>63eoA_$4HR)n4<|VBV*cgy?5n8^$doxgQ8tuSOH%(jcZptX z6??Bp_=rmK#slcCY%-Ijw}d@^PSEH&NgQP$aZPl8q5tRM2}iR4&1R zs|j5k-l6?>F_4h@!pv75HYo?#%+EGNn`8F!NDi&aori=J1xgT72DXaU=xm>yC?JX?bLgvNBGcSmZjIYU z9HG%Un?nRRJIc7QJg3x6*D-kt<+u~DlCPw_WIJ*QiHne{50{TZIqq2@NmclaaN75l za83cA9hB)n**M&44MG)5!1-o53)0cKg>B8_&URVM*03r;OHA|x=|Y(Sj0&8%F^7v> zDEBHPw1k(onTzHrCoq=crZR;HIs@!azoqBx48FPqnZ2fI6<-qmp-+8deiG;8TZ!`wAUh2EzhTf!PbQx0qkRf9*dnQYuE zL29e*3Z)(q!&Ky82N=1n;%Gt(L__H_7Aaw1`(KUBV;Ha~c|i&wVR0=92cTHO1cH{A z>b;-k6?iV$(;Y9AUmgUwjs_V<`?B`J)QXjSVzsST6hBgw(MLqF zNcbySb*&ixPe+cN@YdLStq~hHJ#3b<5iIbFLio#ry6(`G2Di{B8O5 zV_#NREJTEP9l&JE1$2-x2?)Eh>eC0$Vyf5uG z@zhrp73!B?lKEMn18tK)I%k@LA$7IAG?q zuS>?I7ukMEj>evq0l!%4#w82PB~Jw&tkkXhbp=jZ>Q)^Ft9^TJj|=F9(hF)8)S}yA zWC_oxg$#F6IwTBaH7xMKF>f}T%hVW_C5FjSFCqtKrCe3v7fLg_#y*Yy8!IpHeNBa0OJR1ua{d;3s9&qk9@L0F-EI51|$cHnzEp zg$AUJ`d2%y!pgBG*_d4uxMHftl%r{(L+&*B9aFSix#qhCtgXxtQUi9SI7JL2i$tOV z(b=5`b{e@0K_@5Nz@xk|85vT@7aO{UH`lwJbilZ` zgPB2ZhFRQwTEoG%#1cGXFH!=^qM!_}rpuD}WrZ3oSpC>Z5hr%4-aca7sunAABMe-f zEQ^>y$RUxP^b`@c28F}vCz>DQqauVG$HRJXs^r_Kqa)!|sanX}DWFJ@Q}0nMq5efu zA4mG&wgU&5$2VR2(3co#TrnI^2(>^Dpi7S#_{T#yXYIG!1zFpB=mpGo8x2kZzp%;d zR50k8hG05$6)~Qstr6SIaMGU!rHW(-hz~&-vi5+<=z_9{LTuh5UdF;@;}TV{z@b&@ z$;v=GT}Uk+U9po3YI6qW2*<;E0^2#GKcZFPZLub_GBe&Lt%DlZ>c$Cr7JGRH*>F25 z$pME?RbwjW1W!o1+|-6yOkpw^K`P#?ACOvw_v8%<Y*4i0jCerTZuMQn)iU7#*nMpm^Jx*|RniNg# z4$ZpM-N?(*?E+mJDf6V{{D@>A+Y#+25HCYzdEzIR&S|7bfmHRbDoTbrtNQ_mC%bn5Ru1Ro*65BbAAewT2Ev zp&$5+;}u%I1F1o9CODbfg*^|Ci(?xsDm~tm${}*XrBOx41lv*^&+(Eq8f z5wSdzn-d~%8n{S~=gGF0iiGxt6Ib~!Isox*`D^&s76HBI?a*ZRY0b3E!&z}TEV z=(A>S&O_2s2rO)4sgJ_wL{e?pPRM+sJ&|H*$AW}y&g-J%LXj&)T_ld-a|)tQ2TGuB z&kz`7ISZ6=v`A8-8Se8zhkAt*WO!Ju(osaRr(rgGPv~eAeGVFAMvXWnX>}y8;lonf zh~zEFpCW>xb`D_bvBXevB!YIsiCo7?&K}{7CJh}>*<0)alSOHw|d=buNz0lTdPW_2R#%x5<_h_966?|0!JuBO)Fh1%VLrc zs|`oTwNs5_t%#SUA~&sTA`%euFhoLvf~p}L@nm^&F$S(45gojo05rf!DK)8q$_nBn zEnztRXf!xwG!a3QL|LQ?br^;RskZtd#gy1Gr>O;t2BI5!iSi4WDkcq&Ge22;QH<@Nm+42eC309G%EhZ@7XR963%zC^QS?{ki4i&Gb?7A?+d3NoTGKmI*tC$=H&Ik{xVtkr8OnhXBmQu)5+n6mfm9Trzkhhr{5}m2P-cU+8yU} zqrC=xj@&OZ9Q-dXpsEeT*Er~V45QkFLWQ?@D4#f%@&FM;Vq$9NH*VK6em1aFfZgm= zfVYo6=%JQSM(2qLBd4RZf^tR7^bnntpdyjR7+-&3VoT_gJ5--c;EvBXb20(a1h}Oh zn(HNsNej*O3P>oZzU|RwyTjJ`4=tTr6>fAdtOI4syOH*~&U-j=9(8tlHeLP7EO)Cw zURgYuh+6R^r9z-#V1}C?CK6~Nn9L>dS=D(&&4(UZUw;5D$ym(q)RcV%-%8>*49qk{ zaI>g5Q-8n#4a?P+{M~YWH_3>()zd&aIw{=cWV0f|A%rTm262H`M4Azbi_?N=l922|xcM7$Y= z?8RQLb@clB*;GVCGPS^0X)QGp5q;a8i(oWCg=&|jM(SmS4&fTtdOz9LShXmzgVm_f zrOho?DSImGm*{4LC~}L*)y-^xLjG+8@!8EKw}YHDbb&%HX@R)9*S&m-(F9lAz*{bZ zarT03WaI7-kd5-h8d+310-4`bE2Y~)A3ikGp{y2xVz~Eu&CeJ{K+is`dfE=#IG1Pv z(Qyo2R~uqHiRFWcs9dJFsZAG*^I2*H19OwSfP@0{CIj*DFUEbu9wd!e_1Vo^a#DGBFa$4`*>>qmQ0Owysg}PEcM`{T#YnwJuA1Lv=h5oX+D6lb1~N=%6!P zro&zl-(sM83{F5QqOf-tE1IeLS}Cd%TWha+ili{0!^xtGA$#fx0Mzdnkwf_LEo_(K zmM+Wcp4wZQO8?@*r0f;}t^r^y1So9+DKGW(71or8Jm^M29+9%l*x^I!q>OdQP!k;e2j%Y07RRl3XivAK=Z*3F5PD-u9zA=Vl9%!(v=nw!j zmAJZX7x9*O0L@$LsD=`P#R2GX6WB2rRp>Cs| z6vxzh&{7^(CMmm%1eO?(lHt@-{4Xts^SP*>!y+Ct9 zjlRoR09l9>(B^4A5ah|$PP8?~U*hF9_mA8*8v zRluvK^}tK{>Tp&-8uggaD%s`0fJOsjyBX16J=I# zr1rGu+B@V$Mt`m!(Lr!@LiD0DN_RUhvkN7QH795cD#=E( zIIn}KNQr$zjwoC!5u&i9Fh|K2kkNV8|Df;5L|bAoR!)>`stX|Hi;h7^kU>bY27y^1T(Djo6?9!3{EqdYWRJM*#&^Q-8Q~>MHIHE3uW}5g zc7YWG4LnCdc-t|TfgFO<*4rEd(eWpx1J$h+swk#bkOr?u=yR*GC?}7j1HJ-Gr1e9+ zMmo?*U=d`r9L_>_LD8kP5E9G)gBs84LYem21;zlEGVUBB5DpZ9*-KZ+j)s0!?Sh1= z#H%8tNS(Lbnt1aZ0O{;hYSn2kiN=zums$r?k%HYZhTg)e*Fju?tENVs7+U_e}*6mBR6jD142jF53sz6P0Ip&#*z&f9?#G`os?tkXPC($kZa z{D0+n&vkl0aauMCd+*r-V!%hLmdt;Pa* zvQ)t^LUGmV2lJ(`I6fJzrziE{Mqq+V!B8k2;U-@rPfQgMFhxA_VCz_1blDnRIW z#XoW^ncF3JM)|e$BBe2^{#O68%IYE`Qr>X&SnQyEw6Yne$e9^a4?-(4lmSQ()&$8> z!4G2XP`9Ka$QFQN4?P6LxD@T2N~QCZsyayJ;@s$840;kOv%!p1>KQ_UC-%drk=^T5 zj_F|nu~81DzI8_c%g878P%1beX4EJe5E@_oOq!YP$Wl73DtTbyMp28^nxX@7vdwyKP%J5?&!ZuU*)YH;WP`-LrJgUbx z2j$&CwiX)gncyww>yjY&5{ zll~TZS#O)2dq{_hFJll)gJ2CgtX!o!Km-9R*OslnfywQVCtfwx4k%rueDe^1Ns_*z zbFTyk>s_tH=kox(}KT|3%~> zm*Uuha&m_tDkBQ9-%u;*EEZZbrci_mQ;uu8EN9gKJ~u&?6w;G!!sQYO>=aez)2O8R z=m&#CcrmmnI?~xmWF%kJlW1?L&)c4l9B3uM%>askl!Rq6(xwBkJx`s48ih)*6QD}x zfV`KG>ate~@p zJ4_KJFF+Ax851U$7G;-B!dqD)w6}-JqTH$+0cavap#a*q43+7OWM1J-2Jwk!aGKge zm3NgwG}#868Yq%v2vnsHD=KtPL78ttBQ{9UM=W{Gz;F|lV_pnA01|RH$jn?`vXGV> zk}^TEF`?GJv9s-=I7$bsuRiAKz82jajfb5-)yHsg;eU&>azHm_Hnhs?uQU2!xAm;! z=%@3uCP#dJMBRj+mQv0A%X*G5O9+fRWWQfb`TkY6Kdedy)b$sr*u<8rLRY?k8^REET#_etCjl>A61 zjSQfAgj+;JQq_ZTk6T2nVi6$<;hfOw{A=Wx3CBNoee)N8o!>EZ&zYemauMOleUI#T z?gbzDb?iq!IppRAQ!kqRoh4&$Twj;C^qGy1KDIi4$eoiP`o$0ad(=%&o%`6k7jNj@ zT~Ym*eySB{nPq{t+|ysTx(F$yN(u1;{VR>~JX_vfpyY-GVDJ78v$~J>fxY_Auxkiy z?eAjww#Ze@#GI8M=xp~NAS*jf}v3Fgk z`ZjXO=`9CihLMUq%cc`~@@`bEp11o$b-lYb?b8yvvF_;+Ar57^>(*2vmB0Rrl)bj3%1=;nK-gnBQ8zzyLVA8Znv)3R@G-=8t zW~)ilHcVgM`GhAzxK1~UpjC9U5>q>G->r=2S3(jKHrJvvvv3Gcm(KoNnJ=3F={U?>n+Jr zMpKiSc%+ai6q1olDwU2|v3T4{bkLUyCO5RNe<`MCMv5lPJc*Rj=vwxj$^#|C7u9_`z9yk6yfW&DpO$cj{-e z(cU}$@#c9S={x?4FLZ6qE^Ykc(p@7fUR-y3-~8X6^MMO)?fTTx8)yFTm9`)MV8c_F zT@^lf#K``Uf7-O^A1A;4&6dy+`UEN~XX<{vQ*-m`#S5lF-$n#*8PbE7g%w^l*R?#e z+gEUubytrT&vEKL$->d95n?NBs&tv1!C11T4J+^?Mof6CVYdyhpS9cVrdGDg{@L79 zmwfM22fw^z!eM(I^yc&GoEp);LYf_0ZVxFfcjT|8Z+-$CRD=4^oJwHZGs%h-oFWRV z>VW%Q@eh9iZXeK_RGE`JH0MEXMb*g$d1jhHZT&&?fwYq{zS2hPqMvsZXz`hPb5 z`YVa8J8UeSYvYWMahC2XLbBz)emdVf>7?Z^csI8EwtwrE-}P^KtbfZ-`j4)>dz-iX zhM#~!cgPDOQ%Lv3wRQ}=i2NoNWRa(e*N=LAO1{I||0eo@uIuGAb|yljl3 z3rL}DJ=t%7Z7a(&J&GUOVxX}HtnFwCrEeQ4j{+nsBM6U@c2)D)r1jaGUkc_xe75*RrSjesy4C%n6_QQ2y*U zo(n(qmnR?EVUPC3Xp04byQ*v5glISlPDxFTBo{{#(_-;y(RfoTg0m_4(KFyahapd` z^G<1yOj7orl~OH0&DplfPnXot=;=XCaJF>|=vfiP>5JQ(J&w=Rb4vTT%35S0CckaP zLJmmxY-?j8?CR~=cE(YC=#PV7h1Ik9YK@+D-92Pmhh0mLR<>ooFK&8Z_MgUI@}&=6 zdRJfF!*fb!jyd?HdDpJp|E7ySeC;3FR?(Y4f`qCIm&!#YmYL|Bva%Z7oblErBU(Ze z>Ovhj<9d1>B@Gp^)rB6M{I!fJyrB4|ZOjge09`{V7#hkk)WY-E-!)x(Tn`|KbZ9zWwm=rV%x6%;V2qe#E^? z40noKPQJT9oxqaR>ps8X+IcOZQVmCtXECNGQZ>WN`A2R%@42&q%(X*_%*qCbBAObH z{Duk7-`UcAr1Qw1x%=-gxb3K?ztw-o=w~il{*Ry9$CrKN?;WZ;=j-a&-*d06fBs`r zRhFOHEiJy)eTcU=*M+)I_!2O;q6W-mtwb!JOIopFJeo~qyq>|K!JMr&jjWsSop*mT z_R5PscaC}FkDGPxFW33d6|>z1Au3LSXmfuC#%eXH*3 z_s#ZRt*kpz`+`x!gCD6 z(`7%79K8Mb^Ud|`wHZMkU!Ey?25bb~{_1BRdw%2IFSf2e?a}E6Ss%N{`t-va$9?u| zJAdgnqYhv3`&-j@ec(&qzwx;GX`czV9dh=Q-Jieue{WfG!#VBO^|Vh+Eqk|_ji9)xq*zuY8=sidJc%`k4hS*mig^oI2KJfSJ?GZiS$=Usma6KTP=2SSf4 z6Nc??CZRg4%d+*-+UUX4+Qj(TOIku7{mX<9(mhnkSE+%-!cah>`CJB$iDWFDOy#rb zNID(0U{1#psY0rlvGUQFl}VjvyU>NN-TLENsYn915NnrIHdZ|#s!P@OVQC@Mq8+` z=ptO%=|o>d@$W{;9U6}~8+!Oy+%ejs60*1?E@^0I0i2vdLC8QcVR60RsmKN|e!Kl8YTp#tj?p0&}Ykx-& zlYYV#wGPo;0U2EdZD>0x+rx!3_Bv$e_8HwP`Z&|P z|J3?(ZBFR!&aSul0DeK~{=?3qK%f1M^f%6JlEmRdBY4;==7BSI^qC{cDdw8~%t_>0 zbT}6E7Wuqd2D%08&N*e2>KSR z9Q1W0@Y`fM@ZI2-qmgLf+ws7`qw#p)+mXPxBe7V}+tGLhoMnREP6vJ)i3e_GCJ^BX zP-_4oM3RAD2Ll$32je6W2zMkFi10)(PJ$^V6^N5aG8nMny^I7?VFfY^-mOd^LgT@E z84tu*@X(RKcY{Yp6e5U-qOo8aiw3VM9Y`gS3Um;-tZ4A6Be7tC5xD9|FmFWCf%2jP zjYVRCZ&%prR3PBV;N40G;v^ammeAqAwV$_QB9XMxQA2l8HUO*+8=X$MFwH%##0*f zuFOI+T#6WXo9gJ&jVo0-HQa>StZkZ=!nK|4fUftA71Es3-?rgXRysrI`vSu#2rlgv&~lpKw-PZPicAd%U>9A?SqXo`)BTP z;a~U3-S*}uzx48V&bwp%Nnf9NUGM6aw%LWh9{#IqKXcl{@1FR|f1P#t3rAn~&o^#; zX8Hx)mmb-C=-o%2wUXv!N4d2w^wD20ro;*zORF64ROx9tv0`)GB_ppvKd1J+YH;AW zOl5?+qgLgTEOro1cwI;CD!s3xloz=7&Bwpj5=zyu9--4K(|2=S=HQ3n6kk0kKQhJT zi(GRiRcmZM6dG~)b#r4?#l%#6P&-xY)H2}QU()dcGP=H4_xP^wmlr4L@Zx}@z-4YX zPOs^TQI9A;&Z*<`nz{X5h<5(rxVy zTa}a}<;F^|@iuH#dNEeXni#e!-85Fg)Ce@chOJ5;ri}y|F~e4+AIgTUO3sn(`4_b+ zU9r$HgO5+0vv)BuFoQxXubb-*+j!4&l&Jmbk?)u88b`3aVxo4rXn8e>+WK9k?4I(r zR!RMJ-C6+h*Ma$_tF7qj`8-B?nMCd0lF!TDG;KX@m8!KnB0p;IRQQQgPj5j=$;Ya< zNIemGPl{-=`jjAj*-Tkzb|Q&S{_Xsqedt#^9}qdyOf1>u_$LmXF#WK-cY658ed9lN z-nTca1QRp=RwS34`P?RzkW-g@_d6dY#yS6eW1Mg`j5D-19NyIgH(qj~Xt?=y(5BWv`ESbSPx6JKf5M zfJO|SfL5;m<}EFu?0+5T$WRgYiibE+<*LGQr4>Efip=*tpQ?cbG{b8W7?vV*Jxn;g z2Tufp+KS;TYZb4o)qvk&X_w9=;_2dhBkkly3@6A2U9kLjqE?>^tV3}&`y`LLmAfIZ zi^N9!r{>Oz)enc9rcDZpVGSoT24&?1r;}93FpCT)GI~N`k>FU^a3Z6RTfG9A4JR^s zlB$Oj8NG}boGd$>$mqj(!->R=&EVHeI#!GnEGrw&#Ud%x2}s9tg}4Oa ztr4FJO}uTpU#^X+>4UB%+cA+rU#{KfLuaP9d$^~nMGkqmXW|PJKB>b!Z}mQ~8|q$+ zQem#L*_-MnpI^C}x6TaJjTrHlIUgEO;R}rgXW@%cvhc+SUD?4Xe9=DRiKnucteYO6 zciQ6i<0e0G`9IH_y*~N9YbLdP`_Wy}m)sCg_#!ba9zo%YR66w^Q~2V-))NwsHvZ^a z*H1hpzuyC2?c3%4x0inDpwo`-dSmI8`#1dZqQMuwc&_z=-}u+G$Uyy0Kocm$qv>2W zo6lyWkwiKfi=gjIrjSZR!ufo$kSL(_U?P!^r&Hl#Hc`Ms#b`L6$;6Z#@YJ-pb^O@x zw>?of@uu9)U5~H0W5lgHPTP`-J@S)@&ByPPZaw|UCrX!Y!8O;Lc5nm{M4^pbCY%YU za>-;G^Trd29BOKWi>YuSl}zOd;V8b($FhYqYGlOk9sqnhWGCt`|<1gHZqlEdY5ax;cyX4!t}UD#1FI z*2JHRIV;@3Ri%Cb+ELWZ6vftMnH6=pmQr1;Zj=kB!%!72*5!f;jy2SF0OeV5g;zY> zlq=x`Uwin$a}*8*M4ry;l)mKY;+h3UJ#ch)|hQTUstufrnRz;pRQ#t_T15tLKNVpBIX)JV(%Vkc)cF^7eFNZ%NFmwyz1_wPscOp2lY34{Fx9%umCnId@U*(8hXw|9Q`XasBy}LpG=)inw&FZnH#?JD@J|%xPaow~|h2wjcDV z51^k{(f*;mt5WB4SEbU?JH&TY7Uuu{p`RvWkFP!D&RcI@w0Paj@1J+o+QY8-$Caa> zy8HSgM~&P(a`LlVf8F#_$Cvw7Y&~>J-E*hxd+`C!*0)c;r2mX(AH6T(xU2G`*XADZ z9u=9>T8z9$GGW?&56(auTV>5kW%zjRV6a;C{M}cdPstRY9bi6AI*%8AviyE&S&V@4qv6 z_S1t7G9RuS`)#L)si!t@?acRu2d_dtbN0t$9EOM^n`x-*5ob12|F8^JN|vT)r6=hc z*0`%TkgUks$)JYK(7dA##8ZYDQe~D60YjGc_MY*bnUA0hW@VMlEQ861-rtUV1_d;? zbLmEX+y(haecXowFWvZ&uPnGixeoW->jRT7Xsha$v8QySx~TD1f8B`ROg!}YG7j#y zVkb0&j;tU%GjS^xi>Iw*oi66H`E)jiDrRtO$1+)zPR%5< zi9|dVjwK_7L?R`9Id2ThPA>6Z<7JG2$j*!Z82{V>zrN!3Ur*orrCZmX`_{M4`_bm^ zk(V9(!WkzXH8T6y$zJ_+Sawpm5A<7*-T}W;tcvt6kL;{{C(6psr$4x6#deo~$~*kV zc9Vc>Q+r=&OTfBYuY-8n&XrjjDiecb$){s-i` zeaj?kLr;o3r~#mv8}t@Fc=?R z<<=7Ki4X6M8jjszA0C35xo{RiOVp95e5P$H@5lNWb`c z_L;v;zP0d`zRN%T%s)T>ixH3R`|@Rg#Cxc$q~VBnhsE*Z;fVKe#GCqd4M)61M##TO#CwEK#JkdAIK&a} z5x0fzTfXM{w{QIH`aRCSWaGVG{^d_f4=x!yYRe;!Tr_v#<^Olcp*tfOF;_qc!GZ#p90b+?Ur^*xGsYrR57y!Ywv>_Y3iDk9!x z{ef?s38Qe+XWn_ZOfFeEmrlm>i5%5>%x5!M+@vc+au#~Iz`>V6n*wBPMS^8Em5rtfg;YMC&1T|>Y$2P>XTapK2p&jA!s%2Z8qMS0L`*esdELXn zmwap5hF5>M@}c;XKl;@bUw-U@V}88gnD_?|{QKm+Z~XDTe?4>VmqtIZu2LK!l|{~H zDpyQE$m9xe%@hzf$QQB@EQLh67|-IXa6B7}qkeb17>;Hl*=!+4ne#{#jc25Ad3`vJ zu)X34l{S4ijxdm&7)TtU_MIpjN4O)A`RaS5uxME)6_$O+-gV4%Rr&YIDlD&`1%+i) zV%qqg?e15#R})YxYzY0Tuh01o(pUzbh*-Hz?|b6Je0|u7`JN@g)Xw&gZMn7Vr3~~s zACd!j*hMw$qLQ~1hFw&{E~+MdwdUW%MK#jLMRjlU|D4}-<4#k5{lW+1f4qCdH%_|v z*?Xry^ZE-H|M`=@zT^DAY#Cw~)m^*JzPe@cv#s&dZ`$jXKfQUvchCC5&kH~J`*q{D zUb)xRr8^GJegH8TF~IWMKoKjeS!T7AKW4}>2$ZDQ(+7ahIt)xZ1vJDm;}RpxVV zK8Aqyn2N~+Wf^^;H~YqGetG9DA8O2w>znk`1CAJcU_X7v zw6B*<8H*=zJ>H5V11z2hBeOZ5FN70C_)4vO%*w@b@mxHO@J2kBOXC)ODr&_t@l-aJ zNrdxOG@-&BuaBB~|KddzvIkxt)i(YYldF=hWs{wxM4Z+%;fXS??$=XW!rR`0Blfie z%3p?wMDuYg8IO`jI3CM|EubKhN)(dtOu_4#$i~oxIub9&Q*hGeqM2|qlCaX5a5h)S zC4dI$h<(lJh#fBNyM}uP1}71_;|e~(5Kn>d5B=i#nUDXX^`@tv-DBzUeMWro8=w5b z{G%TF$mB7*|LOBL{PwiF2jzzK?)J0ht}E}4tubIDX9VNv^cPll&*{z^%Y9{ zenelv@vlqp9(9xSK|pogB&)8(8x5{Xn+)vr776Yf9o$_x5!?bi9Q<`Muvc~@71-E0 z5(zBo84U+^%MNZNpAPPo9f;51X4Vzz*(L+m6iEg)%C0bT;LsI%qNf58k_h}Z9=H*~ zRY;?ez!gV=H!2zpB(Pwv2?neJ*F>Tf79HHLvO>r6V73Z=IT{OwEE7!Jfec-#k$Z4q z;tJ#&4W!>lIFJS-$>8RVkw8%s3x1iJ$U~y5;!p14!@)(rV}UaV%h%w&3YOfc6dD9| zg|A`K2fbaXgKglusXz)$2MUBlAaO^7w>eS)WAQ)=j0S=o3Ery;l0A~Du&zL)1a5RN zX-}di6fSkm(+D^Y6`j48_4H6c z|M=&N5t$lWQ)KG3b2u_J+WPs6KZ8H5^2=rYX=HNP}FVx^up+Vm*8A|MsR6PsF0YuCx~&!MJ-t@< zvi8E%%8Ra83(a2iJ!fvKo6vRVeuw|;gvqyu?m08GL?(@m+q3Npf4=9Ky>7f*eo)un zsf!+R)xjKk9&@tM;CLJXN{;Z##HmO)2{|CA8Jj$=P}^*QJLep_AXzna!wHUHL2KYvoPukZCU zu6b$Y#tUD1r+M%phMlio7l$CKT!dUvtu913QpteLqp$z;9p_Xf&C14Jl{9;>E&Uk; zUn|Q+J(5rGRSjl+yA`SCH`YBpVqk546P3hZMLAhj^i_MY6+#D-lsN1GuN-0c+l9YJ zX73m4zP;=FMHk7be!d{*{tr8w*yDMk%iI}uHcj)iS{ZgWb@rz1-rE3O!r2{l4#e49rf z@%k}G;g;{*A#wsFin!aJNM#bmSh$iEqs{o&kNRQDw2rI)yYqpm3wPc9ABSJD`)-GQ zE8eu@Bj5i+c*h@p;**zkBwxIHpQrBJ{ddK0bWc2Q&7^7hmV590_Gic6azgqySN(n8 zyRW$Ekr5;7Hf_4#mER-nwXeJS#kgC?dwo(hxMu>cy!rT~-fGxw!|P}5b~{{I+K;o# z{@L79mwfM22fw^z!eM(I^yc%*pEII=MK0Ts?aW(SZVxGc)X1CWZ#!_i=UnS;tI-EH z$ein&Lr!`D-;8*uyDM)ps|q-A^Cc<@ncILK}6i zePusxa}uF8{9h3cPb(FNoI_M48Bawa?a+`339zwLJc-bLvLbQW3`CC5F?)Zj@m>_j z`s!#$@~LPEHB?!rWo{pya&<5DQ_ALh+e@8|!lQMpwSHQ|qTX(FTcUsG>upordAhRet?p^E8Is*{M?Tzy2*-jxx(bFV?a9%@CXbefM_cO}C!_1m z#`-edYBry8z1@VSh;k~qbUYkGm4<90Yb8-~C0a;CBe4ihqF6o~i6kQNC@Lq!;!z9b z9%8wq6^Vtz`CK|>A#)e+#qd%-6+tP8d?J!4 z&g7_nSh|oz*@$qYkWZn=bGQ%-qnvZtXqY4LIB976-;DCPYE{X-J;Yb7p1A)H53X3U z^@1N?(Y5!q^h#^&mw$U*vE#yvzM3l?_~Cayv@~}9KWBdS2bsTLdDnB_J$T%_@q3QB z-WOi@uH&lJ^#}eL?JWBq(7xcx+*moPQbRc6+%vb0m~ieziyjTV zICE#mY25qn%dc(RYi0f`*Ix9?nP=U8k$oC>>FCN#c`V=8zvZ6(I(O9NxBXkU{H}k? zWBprx(tosysoD46-D<(+Ex+L>U+O=5K#t`OROc7(tBC9b))v{3$bHsq=z^DaNGM-cScL*A*(wxdeiBb{pzO`t~tCm zdgFxSA8!e@?^V6x>fr#YK9#D0>SYk_gW})NDEV*QL0kX);g- zh3`?>4^3(k?+w?!@$J13fvREEei5k6qx;Xe@(ccTj>7qHDs5#^5;mPJSVi1iNft9j zP)#Zuj~8r9r_NC&{mc5&>ieqdSFB}% z-LDv%dEeYUPkw*3{rG>pwvU>2_(6uZo#8tay^h>c?7c}LKk1UMFYC^+%0FD=BS3LOaKp@k`H2# zmTtcq@7Ns6>X93!-5ZL3q-$jOQc}w}s?HRRB>+ zf&bL2I(Po#f)<#u+qaK}twshopKW_{WZi`Cy!)H6S6=kFb4){b+^lLvzHi=F<{I^D>Fh^;a!|Q*Hk!|d3#gH%eNYS^6_oF9-GoMZelI9Go5qNW{w_Ol7ich76v>95D% z^y;1ZaJ_)p2;JpJ9n58L(Zaie!QSerCK1VJvI*$h@pwF3%qQ~UL;~EO$;YC_Y`PH5<`aobPU-~PJcj1Nmi?c3?VW8M zbvHk>b;E5>9XF=$^?&U6u_yny<_Aq5Jo~;)fB)Q=-EIUQuC3mU;;t*|jsI25wbZm+ zBOeaD;o(ai|9zF>!9Djn_}Fi*A)8^Ky?~RI;(R2UM=LRj z*JOBN^H2M#Itg|1v%7?qJ_&C=;&k+$dtZu%Yd#z@=k1tH2SgKB6K@T)&;8(eYe4pR zExe^oXJwBM_qne>gB=lbI}6r2Phpwb+3O?6zWUeq$cwCLUV4!y&g=i$Ew~(3S*bP4 z7HaNoe&ZLJ?QX)@YuG*}%m?la(N&MiDALl-$I-4)*8Rk(J5&hoY#X!jg@bX^>7zI8 z;1l9c;1*nj-0N^J7L&<*7(U2C1a+ixxm=+T&xhfUFQ$=Bg&1=oYsFG<$ww{ZLnaWr z4j02pSt=9aKXugj_ElY_?%oDRysm*<5L=XOVb);$q-5+sHe)eD0{oO&1&G=XbosIU+U}5TMfN! z+1`e>Qb(Z&zv9o*N~@E8#i!YBt08A0521mlDq7v$R)L42lmlHrby`DPwd&ch7^TTz~rApDw=q>h9m1x9hQYO}usU(w*;l z`pE}xyLifsC+c22Y3%b`Zr=IG`IjHQ=<59*n$!1*r(b>h@h6_@x_xHJk@fP8qux27 zLi3lQIvSic2m5O*A3@+(Npb6^^3IHhRDnEEL$uM6>9tjjs#&d?p&9@~MSXE}4mj3zo1QwAo`&{#ooH_@r`d@ecNGYtk_}Qt)rtq`Rx6Fy>QxBzIOi; zD~^4koVE!~G7^rXYgH8KLg`qnn9t`5sZ^nu%O-LZ*v=+X;an=6Ln=rtnh&P|qJ<2Q zST>W-AU{%So39RQo5SrahTB=R_pWbj7S`c#$JKwUj;mvQw9P4BfAZp^uiEy^Oya7&Z93q+;ryt|G4cti>yuQ-{1bg zvMukF8gBl`tmod``pAhBv-cIxJ?_q_$;%QS{>rn*ANjxco%TTHlEml@o4>dD$Zy~3 z&^A9f`j5!!%>HerxB%}^Z;!`kLR8r%(}#&sn{!0YKB);2r(tVzMu z#@!)7pPWQb>($zKqO7(#{`pR1yVjBl?gkiFm8wYvS0(9RmA>PVd&pmOD!6suJ=bja zv<4fj0C%wa-o%Iz69#v^F!u3|->Q&m-D7ti^uxhKeNuyts17LV^J)#;J#5(5X4u!x z;5Q#U0WCuZ9+n?G5e+CTUkecpJ0xmo!Baa0U6XyPc|h4!T7){Yt9FsuRb&0Kt12nC zm9neS8C;5u|EFYE?N&GA(LX)-bkq4SF8#w<7hZF5%f9ztxbNta?@2fdpB&?R3=S0+bfl60#uH=L&vVWTAjUn?p629gSR0WrE(>{viY5_p2PW_ zV{iNL2sBjN&I!3yp>PNja{tzK>DfTnctt0*XR*F|uF7I<3mx*v`;@$+(KcksJ7YIp zc1+?4|_Fo-)oArSQ-lNC4W5V z2k_&M2u;jo3oESdiM{JO6`+xD<9pCLhNYuq4tj zirHv1o{gl!?7okLv$!XQ^o(dCjA!6g5336jTZg^o!(Q`Yulcaoyy5VvJyu8Y-!4aE ztdG|`y~CTY{rHZnAO78vmi-p)@!30e|M87Kf9CFW>93rA^2jwG34L^kz2<*wTG@ZY z&PP3d-jkO;J@1UgFI;g(a^JoW-Er5bOW&G){JCGd<1eYRzQ1JtQy+QbGp8-N_w5g4 z@_Syf@3U7ww)K;}-FF^5D}0*6YkvRkZ=o+_6<37xiK9+0bwz0Fr-Uv#Zm-ndx%Wx8 zO3OoT>yYVI<2HRW`{=Kri_}*eJZ#-w*J5Nt_04evj5Lwo3V{7$rRUE8pX`n zm$5`BibB+g=&WNHbH|;<)@Ww3l%<7Ul3rU8)hHz-e_L5nQ3^v6MZ9DuO3VLumvipi z?KyWdly^W{^z)MvyXp*HZGpQ`Rcd;`w zxibEH4mv(Y+w&O9)VUY+J=vv(1hHT{9#T29;PT;x`?ZcouY7bzx1j0}dfQ#=5Vtt$ z5Y@cZA&ia-zUmN68qTKSWC{axW9Fu*Lku`ri+X=xY1+*0*p|YLCwiXk9Jgn1{@Jr1 zd~*B1XXhF*tI22INHj&G$!S-}*N5(!!oWB`bQC=#%g7*>{WMWAFAqezUG8IVS$ zRUj&fpy+@D<(uF{6@)h_?Lp~*Z#7yR`{$p0{Tk(5^@w%kT$|-SIlczukc~G2<#Wj8 z&NW9~?77CTjf78;iEE#~mC()+9qNvDH!Na91@ zDkB||F4(uk1`bJM4D`&rSulCmEwfudIt^y@3wyI;cb^Lvu|9kD1%#HpUCd$;%Dp~7 z{}p#L7Xv$P7HQULU@UB4QHpf4fT9%2>;kGYsJGE3&p3}XN5yGm%g{Er=t!7JClRAkVL2fA`!i-@bF_vyk{$6YCHT2A+)F;n`@HsqbrD6Cd>*iH|sMiH~L_ zQC0qGp0YFXM73U*{_2q=jjYYJ;(0F5MSn(+Dn& zq*Hi`#_*d3f7x`#vs6{(sX4u*UoXl_UhLZP|N8eYe*gEWCx4fiWiK|qCG@;UcGo%} zg_9T^l?4i?xxkSsXYj3^TH?m-`UxDg1F-{l-=uqH1P%SB^xy&maIgdKSgGMxOua=L zUF^W$Ka$$z^;7Roo#QxId9wq*@W-x=Z@w_-wDyrhm)rVhC#HQ-*Td8tnJ*N_oji95 zL;))pbLkur1Z5{bG#1x33_MI3$m}O6^((+6~}X!K*$8Hg6glN0C*Ct zv_J+}#1&B3$0>zWL5UyZ2^C{yP%OZBIA}4f;8fnf5I5$7{s#a%va-2vZmS~5q9_O) zN5WOX#fb$ZD?{OspjN>8i3N>Po>x?wQXo4ma~S9mGgkK!ic>_$XsH^W^@1yQWKu}s z6Op|y(9}CTMD_xYw2-FaTRr9Vk9Q!*&donZ!-X+}>^hDfDMhQU=Qfoh~ zL-fGXRh4eAX!YVp5{_tfKZ$sW)=J@u)*H|g!b|tv7R}1x>V2eKWP>@nSCh~+hDf<6 z$$h{g1%0(VA7AY5b#@J0&F7ip4*mYoa z&Fkhiucv!$dH$+LK9~pY%5AD^AZDL>0p$UW7bfdOt+=d`gO{zT-sgg}s0=;T8T7`l9lV5;|Zp&Gbl~1xz5UJ`!;`79|I4A(C@5GqM#6cueHy4e2CFR`Utl z`*Buh2oPwnzF=x!{_W%(VYKxd_HVYb*+^Ka_>K>!2(sKO-)b8G7|CS)$5UiQGRsOg zWmIyjR`GF#@rCh2?iX@Sft-U2f=Yq&Ax~pq!OaVl#Nw1d(6}PfR=Ej^1xrM*P*>pp zWCjP$4v$GR!7;K-VggtZ^Sp`)93v8-H9>>t8V6`a;Pa3iB?`cUATTR2h*UVpf9Bb5 zd;3@>6%MnGoY|5&MzQ1;chg9^!+KfFIp$m;3kKo!4960ozdHn8wR_@ZKcbAtSF$DL zq5R>ZVyWhZ_H6`b2s7<_=B>Trhu!wdvmd@XaLmg&ZW_9hvd{gDY?Bw|u29DV0Li?WjSqCU$2V`R9frG|a z8cIkK!!uCOVnl|9S%%V+-^dYbarTaVhp2V)(%K$1mo<5N``wq$y>;=H(Y?lWe`9OC zg}qNc-fu zfhkJ5C&&C$lyu!{L`B8cbzloBX&8euQ)Z2{ZXSa5w+?;n4@y}vkj;Z$W7hM@kwRZM z3w>G1i^0r2cz^5ALns)3Yb}BzWg8UWE`JOLhm-zzlw{7zQlz|0bGAdAn!tGys`nJe z!b&RfB9Qz@l~E~%5;z{CpqekrG*9y!xMzWrsHA{C6$A22g2IDr6;5b?4-c}LQEll< zaZyJga2cPMd=j|IAD$i?-+~2rltnvz8B<8zi;5uu#t`r*FWlKbO!dZI8DDQLP;R3J zI9Bk4CW83=tPdx(JU1Zm?%P^i{aHhp#gt@-LY4q~g0i(y8g(>Qx%}iM=;VL@>mSnZhbXlC*OI zT7Ag+D3X#{pjgU2FDctr5}mETVe>F6`>y0-*@2R>-6drkO1f#&?}VATk7#k(9@{5R zmrNLTzSdofhr76z6}bFYiuThbIr@*@>loe&XiUCqV;sNSuW{2+p!s5aD`?xvHuin= z;9<(>!J1E9`MaV2uu0Q8MwxXdKI6N=BRJS{`z-&%!XC`A{~7RDM#m^t4^E%C5FN*7 z?~t~b6vu6d?H&X1U9psK=Qb;_`#D5P_@Ud=wEH?bzxXXMukvG6)I-5tAESI6n7$}| zpkq{r%Gx5~a~|3_PHThVv~Rj}Ie@Qg5vWOX!9_Owl?8jrU@oEuOL5#&MKOnA zSqz{h-j>DUxE>dO0PgGcOso=Z0>R5@w@U!XyBi*B^N&V`B|gy17rFZaPBeVEE09@` zgog9+<4n({Lv^5r@k*SZpQ*{3o}c2Z4#ZE#*5$xjR-wApxVE`CU!m$S3)CByQ}c|z7B`8hdRqs!JNMoV2sJux{dYG3-o*05k2$A{17zDuyIU!M!LB%~`5 z!BZlyvXDn3z@rXK@+7Mp9ma~V6%Yv$$Ke6O+Eebl zD{?#Hf!SJ1SL@JRuN5IqW)v(itBh6;&MTOix@P5T`3`5a z#!toW`^}SQ7t#|X;(@85=si&0fzO1`685VL=QM{K-&lLxrxNPOowb1LuzKKn z7q%Xolr0PgpRFXT%e)hOuebN%9BXP%$nx^m^;?D3J=$ZvHHao(w;=M1!=~?{9sBP< zf=K_aUYm%lo?%=)4^l z9{$ihwGK@`TbNSxQc}j!iRJj>iJp9MCWVB0GRNO6gz^6tv_1FLQ-7_Vw{qpH_nGh) zieqBbf^i3?6_q~nP-)NUBSR;Qe}N^a-fI`UY_-Jzu}zGXR7K=?RfK{tl=~Sd&f}cQ z;ZUh37%23!iY!_Ahk{I#6u>Pg*z2;I56F3s+J({c{kArH+WE4y2-m*a2biOOrMS%D zIc7_q$t9I+vOW+@*eFHe2##h%mB1825SV!F!&>TH`=I+7DL%eDNJOyFzS2x~mRD<2 zo*5t^_SB#i4q^j&q^6qs4pB_Dl)VX(hUR>Wq?q!C2zjO<<}5iE{t5qSP4`>>39kaa z#3U+!I5F9NN>wZtMYdha3NFco^}=?oG*d^HuBF&swg098OvPaVVS=XJZ^f%Xv*1^( zcnWBUtvz;>%AL6;D{)+rO$7q@wCb1@YddVO^ifnmx!6CPBAP80^T>8B@6*Q6-l7i4 z)-g{rr8+q1PNQO5g8|hN7kAG+rW_$VQ%X+iY|hV;P1)u=ll^j@O*4-KwN1BmfYBXtHA4V*1igDHBQx`c$QEH_vNqTDzwH2?Li>*^qZBDW zPtj)F(H0*=Yf+@koJ^QS=z~^eaT!_ofUW9dAKVNjuag~5l$>XF!iKEBkpk~^7IFkJ zGb>_g@o%-Xi1UJNGJ5oe8QWA;>G1IEdyl?f{n6}4KRrJ;d32Aey%TGW-SOek&9}E& zRM5ZC;H_J@I%mgX6L$PDrC>>oJ&pOfjRwtnV{YG1|LoSf$r%@HQ`c>ChhCeyhI1F= zs1@J&6j=pwfqrC*wBo5=i_voQE~mT^qnd$0NyQf1%5^HRGD&E~wzXaR@LFmyy5;sO zD*}Y0xutDV%a=(5fTKBkA2zL!_@eZEL$>+SgdiA2rg$3Q9SS3WJOsw#JTBuB#xab- ziL%Tv1Se30sxSm73sW*^H?bs6vx>sYI45dnqWCBN({ZK`q0{p?`S7~F088N31}f}? zIX+Noy!Ej|uXZkw1(MPqbb&ffjXG z-jz7=p<%FC5I8yv(6>C!qQ?_CoH&PEehmyp41~pyV}kr$yaF`r1K;iWM09N``Z4-? ziIO<;sJ2o;wYUB^{>>0*!MGtj%kTq1;kV9(g5OGy(1Lx_f&-89Y6H%k3_(>S3?YP4 z6>&t$&3fdl_Ma&)`cf4MR~Fm{5anN|DgwZFFxn&p8B+w3Qxw+9i54Y=XBdnZao|TQ zkURl&mn1=13?51tA>b-NkT~Av)G2~K2+6Us3Xpjb<9S*UXkOxYAWka+!H~eSW=R1w zMZmWca*}WrnE@GeC{Kv2N{d{DsfxsVsk|GN(!yMM=ce>;_~Sd@pCA2R^%>>|r!<_l ziu`Nep~Pc5f0?^)$=wZGw*2Dv5nYSBj(>8^L*LEYe)Lpkze{62`DnxGO9%D~Uy8fm zaH+iaS<|KT8dcu)vZSN(e%FLvukQAX*mkJ>vUO$KtJK#k@0yp#x#H?oj=omiFM6zg z5f^$aT%mvEe&2?m)ewe7IT)=5?Xe!oW}|9blOA8WE(E$_80I!89XM@rBU$+CorT}> z)7rt_0ke`q5fTA#Sd4{|H$ku>O+k{9v6_KEeSnl@7UZ=!iNWFjSs5O6B*`-H{DTjI z36@?M_W;ZqZ7caLyX*O7?4z%EuEsvF7oN>7%408h7| z*~?*A4uWDY|ErdRe{>0X#(-@g#u2pja~J{$Y5nX7lt|gEqP6@ZWiwA%Wpwfs?Rgq0 zn|Y3Bq-@qbJI|^T>VVEAMapJCUUjd`LT&|B<-sFmGmjBNSEnLnGtU7-)}JC}GuLIx zU+ZV1bDt-2w||2Jko&4&H5b2*abZ+*X?vII#yPzla*_0GT!=wXoHyzkH-AA_V*O-+}`x42$8NvCsD%Sk#_Utr13g&})b zyM(c{k#U?D*<6ks@r8>1t43gj`%h?fWjK#vlc0JT^!vNXPhm zl+)F!5op{#pZr7D#&uel3`>U}a)DaR=~Wet&j<;^HYX~BifTZTxE{0Sq4sDbiCb>& zY?8Pe2IQ}OzWL@#I9@~MG5;JNCn-+6sga5sRa zmkGc0(#ylP2S#W07_(sMtoq)=N@rcOrTbl9o#0OARUNit=96=7RgQ1R+if0KH|nk? zdxy+vv3>nBT;atv@68^x=(Xn(Qx-}yCZ!E)aXG5S<~`3Zuf6KhYKOZ*m6{#@_DlX# zA9rtyU~%=Vx&5m7wOHwksU~PzMI)uUJhYkPyV?de%XF z=4Li!2mhUF8|LaohZhV15_DSs4$&_FAl)M&{+=3psZgLBa9C8aayvZ%h!H(xp zCdW}KC?L}+O-UG!sXSm`FqXhsj^;r%g`)rtqu_!pDvAU^QBI`ITGV&jF(k~RX|KxQaOUASok)04bZeCL%sv-cR;ZY*lpL= zPOiEa;HS||9cJL?u;C9+r6zT4aOrO9V(%Sy|L_C5U`yTRL;vsV^wQ>&($dHsPycss zox;s?`nK)zf1A(LX}qFVi*0qwglWw25nWFlJJ#C;_<8xW31j^_ndy%==gABnZIk1t zwTCT3g?fzWhDm1wR8$X9_UPU&zv+mg`L}-ePAK5f&>!i+fJYsV`cXF%cr@@G!RVbY z?uVGJSh0M4H-U=jFqT(hpLk8YPA71^t6|gas!_D-nR7K@w_nRJZLKFLyd*l#PAgwn zs4*aY_qct+CZRHW+MkNnBPajs}eoUQs|v z7ld?W3S%B&8PLM2SQ*@&PQpl>QbBi?ArJx{WcY@U#n55oT0`B_;WnF0_0iEBE( z5kW5wXc3(1MItF?cwdW^2|;| zs4iC%;Iix`zv!M|^bqt4Hd=4i^r}4!4VVe6K7-!PVyrf^DGsG`kk|qheMN@pS2GpR z$WKZepj)~?s>o`+=_NEA%KOfDlCV);=HD4$_VdBD74%}zx-J}2#e_k=OCqtFEK|Kw%R~#1_e?!5Jy2HoRN((kZCeN0^>*(F(luCM60q8NDUkg*(XS|q6QOZkgaBr z&Bl3jfS?7SaI_vA&LbO6kVqsu`q~uw(iHNZ!5QQ)gUdT|zzu}#Fyyz? z8RXk6GEl(85Y=mR5E3LZgwcx>TExlV&O$G(=y^tm7=cy|q|l>@ZZ>kX2m(11;5Uhy zC<47O<7k=@f#z))$V;IgWH@>iG?0@cDD_i(bxW=WOr^iEjJpe;-_kEft%ESl&5p>BY zOu+uui%$Q52L|l+=yYJfZavBFz{A9>?$Z7PR`$1(s$Iw3zr7|V4-4Sql=0j z+x|U}GfJbU1nI#UdOav8A;8e;K@lN9-$Hv2Pwf=t3B|XW6JN@ee$< z@@ezd2MS8wSgK)1V{R;x#*IW@SwH1nD0l}G5t=X?M8wf4-Oq=jwG;X(8I<%^Agg2~ zUv5Wxd#Ub(BVXS8tP&V;5V>av6a^n!X>L}cU&?~|x@b=oYsLHG8dW*|X}FJ!&r3cz zl-gMdyV&+ezc%eUtVLU+HQtTe-td`}6BDoR-5Hb;*1ziRyn&Rk0^h+*n@z5^Dq+1{ zH#n+O=*v4&3Cj^Ek$RI?B`kDO)IfYbQg3omAws5VBlRYC@t8=x$%|%Fq~7FZP$Kmv zHyxo!y~$JU3|)4N)SEo-D`YY1y40IKq3g#4mu}Dm8z9|a`F$PfgJOLar(5P6POu(# zCI^|aHIlwCX5RV*;nx!ijx*$$kidI?8K9{;rC?B*ASKbNSxpfF%ctB20IR;{VM=z2 z?SFIi26;zW4L9d!=Q*KN`tS9-FLM-Z6U#|8Vkr*M11MUC8@-2v3ARG0gbI$+l+{qr zqbTYD`psy!=u7V0m8nlRxM!f!zuSQ$BWg{bo?t%p!X2v;jKBZP%GqLD7Rr zZ7$vD!M8i;!8N_ngFk&NYxn7CO!EG1PcM5cY5%O#M`uTmem!Ycwz~AE2~Uo`-#0y& zB+^JSg`t51$=xjU;Hk@Y4z&CdGuhPm+d3Nu{_*XXOZyF4{pu5Q9zIp){g{b&E(o0- z+=r+#)K|?9e9;+>5^z}+aU7R<6%5g37OY8RFeM>Xf*=%{lOk>;SKrrS60>c%V!f_yzNUGI?9cqg-O)DbD3$)CO z6tn;buN3$!L$HD-b#lgoPhxZ*JQ+V`@ikJN(C-Ud6(TAsCAP&E*%eN=bG8CeM-C(1 z?v0EmldluqPVZ{i=yo-W#x26$KC@tc0Mpz*DfQDin)j3P>^#IL5%L2OpC~>q{&NtFg*yi>nXqh5nV| zGKc4wEqSK<^0PBC#Vp0P?=e}2&xDNv7zY8+3=vp*3Lywgy!K&jo1}fveIpegpXVgE zBn!5EQku!WEoyDbGpCsxIP_MgvGMUH_>-Dy>N`X+Jt_cG&ji?YbG}7VOnF0uJkt;} zpxfb}@SoPLnf0IWs$fw}qLP`NVY2;{s#q+FY`avJl4r^V^(ou6(o7v)x|U*l)&83~ z79|HRlboBGk*!#uP5F649O!y$yR6N!{(+|550@06S@0`LwzXYmTYKzZyffEimAX-6 zQ-QVh`uD8uu)WepQBCbl_7A6sW{brvV&yr2q<~)=Aa-K~ykAyBk%WU6nn$*hPV@G$iyH6WX+jLt8 z7~P>d9$JecW#(kU zEJ7dpD3X#{pjcc+7CvBm(LT5tN}glplJm^=U$tJ}`X>e6>n!95VrEum-st31dzH#L zzS`B@zNO96Sl95;K3&)$*?3OuE%clmXuIAy+XT7IkK@>3i!p6vv$e^}gnz+QmA?l3 z+jW4n7hrHwWF^O(o0;c`uXb%*XHlD>!uTQg3%NZqhiB$BlruY7GV+JR;OCa4-{}-M z!ONXubafHOS*I{P)%ii7l(-*C^v`Pql)_=w+s$mr9HUrri@RxsyJ5X7W@xXF1&ou| zGaMh*-yH(4cF&fTQBm2CC?oQ%_u@nO!$rkX!;2h=(IFztawPkc&VBX${f&3@Ydr4K z!1Hg;DST4uQ16kRmZoK|H@N1lJ3h3-+-JzZ4FN1Q+`$(H(kY4D|nuH-i5?p^ZP!xK4ze@cBW>(x_1ID zM_6CQIWRx?Rgdi^gh^r2MxVXgv>(pfxUZ{X#H+@+Y4KJr^90sz&H}&Dv>kn+-Nar)%{)++*Q2N&@OZ6jfL>PQkXELchL-tDoh#E*JwCXn!n2=#bv%sOPxgaV@4||iV7UDHTrp3-;JT}? z)vw`BAh}fVBiyfX^Aa$jJ;J-zc<-_nFws%8(!qN?&3 zq||5$_)C<^a|AfNtBgvrtiV$$sB;P!FAy|Ikt8q4R^3h+(7_6=QdZRtP15SLM+t&l z)vVP!)o)#xUYNFIcUneXtEZLsHdk8yYvRa+#y#1>gwsb>nGB9(Bq@UKC&;o&&_Wg( z0nYKHAaF7b9`P(o0ntijXc3cWP$tDy1{YaK5#kCZL+fQ6)1G9JBbf~?SOH05qe=Hn z5=V|?Hccx%ldq+boHceYNhd!l;epSD&m-BEDgx9m+ZZoN<&R_@OVF|@bv}e=M0LYw zgwHMd>B;&Qz1HSug+rkfsEow(je#FQ6{t=J5blw55X~_n21RiRv`jp(A07p&z$5cH@Ds7~W`VX$7*7Q7@Wf zia^CmVF?oT01Tlz8iS|f86<~!Z=zL_OsOX9FwbW8WJe4Th7#Q=~qtoTpT8x<2 z&hFZ?z_FG#kL@zZN}sAdV|WG!VpO^zn$J=WQjF@y`2uTe40 z{iY-enk;w*5^{A6Q)%TBa;IK&tFYA0YGW>}@Y0mCGl-g87)w*TtE2$aAs_)GkF_&! z0i*K0{52-=MD1?9-dWQq-9^Bt=Bb9Q#&f~@7{9zFC`^n#ByO0PrVdO@ZEu(u;ibvR&HLo1PHCIo>$$oqv#WP1>vyq1 zlTLBR+4s^LmgXZcF=QG+r$7ypVECH_6O(`Sp^sCaX;Qkr$C-h*4ES%I4F?*Ou3rD+ z;_f30pD$qAz8g9wX6`--)X%)@d*UP{V+n;-!F*C>DT;zjts-DhexNZ-1lA=^2`Z;@ zEFma@LU0nGl~kE!IH*w56rt&Ro~k|Mlb;J9SG&NQ->AsZI7#86z|bI%A1E)=0tEvlNhD8@5+=bE zfuAVD!Wb$%CW#d6_GBIq3b-J$40z~Lq6qstih%JJIoR~E6ef~5M&YEw0h<#(CD1rr z6s8-e0MJ0nvI>+S3WHfzVInZh<~Pa#-&Wy(ag{gPRztUsWm0%S{p|S zPd&ZnhXyllJ^1pS_uYPL;ikbZ$*G}@WH8V4LuXJwBWlSh4UGxM#1+Rn%?|D&nph!R zWZvaT8k|CAI@> zL2_n8kF}tr_X5dlcvh4D)PXBOQ)cyxhDe#!NnBQ?2&d0aIE5W}2i0kVJBvd$oFI@PXmE3&&Cv+tFB_~x z1~)B_R6IrB(qMdo8c(#sAv(GlAa0@)>lrzD3AdvuQ>1%r$q^xFi9wEaW7&>_Ey3;=YK(F&Hx@7nx1;k@m3BjnEJngGUEPuc0>D3Ls%LmMR~^t`xZA|eSj z6a?(w6s=w!x#7rKd&!%*Zz(kF=(K*CO-qgJ)fJgGCo>3IZn|g#vByZZ?)zAiyLNdTl$p+)ga>@!h@b!&>5MjaLus~YV;0c6-ReH_weAN)?S*n`{%+&zjPik@Y@b8 zlrh_s=XMoVn>M%h%srLbWgPs7+fsAp%F-V3X;ZNwcTU)Ed2!JX9}IYVV&>A^%%<$% zzf*0vsMjJ9_*# z58dL|Wb5*J9bA)5rguvHiI(?y5iN-p#dSPnN`? zi<{2+qOGwUsbauxrzHgl?m$r^fYKxby9tvBj%RR86~P2jW)$#Gq*+Db6%bVA2@Et{ zRY6rX@z#?GvrOMK@=mD7fP}^pGK-5m34hZPc-?{19VPNCAzEc(F~O>dBJhep69AJ3 z@)b*fPAdx>Dng|7+scDbZ!{A3@*jXWpLf+W(4i$+PNGB#xN$tja2!pl0#4GD!l*D3 zV1KE=AI?e%lLU^JBtpQ!M~@;vDpp}MxtWtbm~;{MsEB(M*l|Tv&myX4|7`cD+FppI zbywVS?CSi>T^>w7QRTf})s?0XT)ARdwk&yD(?1KIS=IN>FcV8#B@2}j{L;Uy`NaxXJhx}4Qiej`W3!-WJuo` zAI)z3N`VWpG_&2(`LOA%VR*vusUduFic>>4VMoGlt|D?A&pEU*x7J z(cGaZk>E{H!symTC`zztU~)&19LG{Oi=sqKl4Wn=49o6Kl}|SR$g=W6&Z5fka~HK( zQjn2cIBwzHkA|)&alHSaR0BP`lL;%%eA&ss=OA3$Sy$|8#ClHG(AOc$-qiKoizohQ zjR(F`MAqzU<8u)BO!%zjZLRs=MUcwN&~_KbbvJi=V9(q>?)QTLr3vcPnTm~0T>kf- zstad4Ke4>7RJAVKKfC(z-sx{U)BnTr%y~lABl$TwS)cOJ4URPFyS3rf!rS+b zX5M{t;Xq~b$N&SvjhsV8TDsWh#Rbz?gvNGS@|AZo8s;_*ZPi=;>5 zF(R>vuu9#v>4Su<*!RtcXXfwRTPeS5N=gHDW`8EL_v3do?^o^VcAh)i1xLR;>?`dE zNkEywaDoG!OmiuD0U(tQeIq!kn1yStMH4yNq~7sydP4u=g;Nu1*x!+5FwHH{qDE20@XN35l?3 zpfM6giUgx7IM1-O1UhyY1uAwlp%4lV+G#vP^CbLb)kni+ke>t35>_Sk+t&4%5uwQ0 z?!Bb6upcr4U$jGde%^ExxobMUo{6^zMa~yRF6fE4nJ98W4^jlcXqWEvn)`9v6nV-0 zcu?emoroKdBImv*gzmd(PI$;X2HSfBirhH;>FHer;TlPcc^Lqwa^OE)T8u!;S+Phd zAsVTCV!&uX|1ghKs{-X?6txdcEg=Zxwb5!-XgVT}ruh(P85tTWtOJM@WS^0p0>}^Q z%Q#xoheoy=XOY)NEB@ddawd%=(a_pq1ckmhiEKEIR!l>yY}wR#V3qKpC1}(yG^@}^ zl8Q%?XAC%=I4Gl{8jid+&Z5H*d2Ir%0A>^_Xz4v1ee?0?NI{1<7^5S{j6vSm0G&g9 zn?TE28PKPZ4Wp2>SwBNd53-Stp*vw)v2@Y z4IMFeVM7COWA$#26t41=4^qd!&K^moD4C>G4wD!{5^zSLaFwPc7Uxuvm2nA7rJ#%_ zs5IE3h;WiuftJH7I50=0TDw~{%L0qzbEi3F7;1zaIjl43EH#3fp! zC0@cPt}BEh1HtPp#o1TQFdqZ>QSDE9p$r)H5WPyX?i`0|i0%eoxc z^U|D_+s<9uceG{w76rH0jcYJeePqhU!h82;Re7YxOZ7im{804X>2oT*lRxyy6Wn)` zNmms6k3V+5ZhUrq8MQsDs!xh4jI;VuoDb5MBJ^z$(wD-q|A{1zD_UPIl00_Td5R>D zJ=DM=$>XFnk3lg=`5EM(&{Y`&NxDe#*vow2k>s&wkfL+2k>s(LI5N0#k;M@_l05bs zc-%jSpX`^Oke+C8>Ip%9;nkuyFNLBu7W#%ZC>KU8(f6+a>tMD&E;eo9(6bK4g)#8r z#WyuSs~L)AZ0G@mvW#`a$<2x->Q}kp=#Ek5kjf$$OEUx`0nC{+pt<0#hvlfK*w6b8 z{Ry&{I_%yjVL@M2G?0FS-Lb!vJ4R z-9LfyE)K;BB+v5#1M*KC$uhVk&@{*KGB5H#{89*>5jl>;M2Qh3cpr49fL{WiQ$01t5}qbOf7Yy_}fIvCf++6a^K%@fMQ^g6+OtOcNS`;> z{tAV|0G=@J(tO@1cE7(jgYfIBKGhw)UG=}%&LSB2ky?>62%LilkIj&C?Hu?D63c89 z6+Lmj#?Ea>@5$Qpc*m&B+d}9xgA)oH`2MA?BStD|vGUuv<0F00!A}tQu9tRx`~Nh7 Bm8}2( literal 0 HcmV?d00001 diff --git a/flowdb/000006.sst b/flowdb/000006.sst new file mode 100644 index 0000000000000000000000000000000000000000..c39a1b219f6961a596fbc048bb2d70daf303cf5a GIT binary patch literal 99729 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 literal 0 HcmV?d00001 diff --git a/flowdb/KEYREGISTRY b/flowdb/KEYREGISTRY new file mode 100644 index 0000000..afd3786 --- /dev/null +++ b/flowdb/KEYREGISTRY @@ -0,0 +1 @@ + ìöV"}C*«ô§Hello Badger \ No newline at end of file diff --git a/flowdb/MANIFEST b/flowdb/MANIFEST new file mode 100644 index 0000000000000000000000000000000000000000..40204a43ad7a3d085e9d1ed1b85050a1906d76a8 GIT binary patch literal 124 zcmZ=tNiSkxU|{@C44!73E^#V3J@2l8gc%F_r`QnGb>T%s{q~ sww;|FP=*C8&ny5^#RO6H>|7rwP!%gk726rDN}voISQRT+6^j5P041mlrvLx| literal 0 HcmV?d00001 From a6b402a10f44890986a87cfed2bacbf8a2eeebc8 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 17 Jul 2024 17:44:10 +1000 Subject: [PATCH 36/72] Updating GH actions --- .github/workflows/ci-java-examples-pull-request.yml | 4 ++-- .github/workflows/ci-kotlin-examples-pull-request.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci-java-examples-pull-request.yml b/.github/workflows/ci-java-examples-pull-request.yml index 7823d87..9f90d14 100644 --- a/.github/workflows/ci-java-examples-pull-request.yml +++ b/.github/workflows/ci-java-examples-pull-request.yml @@ -58,7 +58,7 @@ jobs: uses: EnricoMi/publish-unit-test-result-action@v1.18 if: always() with: - check_name: "Unit Test Results" - files: "**/test-results/test/**/*.xml" + check_name: "Java Example Unit Test Results" + files: "**/test-results/java-test/**/*.xml" seconds_between_github_writes: 5 secondary_rate_limit_wait_seconds: 120 diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index 29d73c4..0c143b6 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -58,7 +58,7 @@ jobs: uses: EnricoMi/publish-unit-test-result-action@v1.18 if: always() with: - check_name: "Unit Test Results" - files: "**/test-results/test/**/*.xml" + check_name: "Kotlin Example Unit Test Results" + files: "**/test-results/kotlin-test/**/*.xml" seconds_between_github_writes: 5 secondary_rate_limit_wait_seconds: 120 From ca994086282bd43e8bc633a25d22e6da2e3074ba Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 17 Jul 2024 18:00:26 +1000 Subject: [PATCH 37/72] Updating GH actions --- .github/workflows/ci-java-examples-pull-request.yml | 2 +- .github/workflows/ci-kotlin-examples-pull-request.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci-java-examples-pull-request.yml b/.github/workflows/ci-java-examples-pull-request.yml index 9f90d14..f421963 100644 --- a/.github/workflows/ci-java-examples-pull-request.yml +++ b/.github/workflows/ci-java-examples-pull-request.yml @@ -59,6 +59,6 @@ jobs: if: always() with: check_name: "Java Example Unit Test Results" - files: "**/test-results/java-test/**/*.xml" + files: "**/test-results/test/**/*.xml" seconds_between_github_writes: 5 secondary_rate_limit_wait_seconds: 120 diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml index 0c143b6..3960354 100644 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ b/.github/workflows/ci-kotlin-examples-pull-request.yml @@ -59,6 +59,6 @@ jobs: if: always() with: check_name: "Kotlin Example Unit Test Results" - files: "**/test-results/kotlin-test/**/*.xml" + files: "**/test-results/test/**/*.xml" seconds_between_github_writes: 5 secondary_rate_limit_wait_seconds: 120 From 83dd72decaece74b407ea1011fe729b7df640b19 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 18 Jul 2024 17:43:27 +1000 Subject: [PATCH 38/72] Setting up script loader for Cadence scripts --- .../kotlin/org/onflow/flow/sdk/ScriptTest.kt | 79 ++----------------- .../kotlin/org/onflow/flow/sdk/TestUtils.kt | 2 + sdk/src/test/resources/domain_tags.cdc | 40 ++++++++++ sdk/src/test/resources/hello_world.cdc | 3 + .../resources/import_export_arguments.cdc | 22 ++++++ 5 files changed, 75 insertions(+), 71 deletions(-) create mode 100644 sdk/src/test/resources/domain_tags.cdc create mode 100644 sdk/src/test/resources/hello_world.cdc create mode 100644 sdk/src/test/resources/import_export_arguments.cdc diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt index 7352a58..b8a95a3 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt @@ -7,7 +7,9 @@ import org.onflow.flow.sdk.test.FlowTestClient import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.Test +import org.onflow.flow.sdk.TestUtils.loadScript import java.math.BigDecimal +import java.nio.charset.StandardCharsets @JsonCadenceConversion(TestClassConverterJson::class) open class TestClass( @@ -50,13 +52,10 @@ class ScriptTest { @Test fun `Can execute a script`() { + val loadedScript = String(loadScript("hello_world.cdc"), StandardCharsets.UTF_8) val result = accessAPI.simpleFlowScript { script { - """ - pub fun main(): String { - return "Hello World" - } - """ + loadedScript } } @@ -76,33 +75,11 @@ class ScriptTest { @Test fun `Can input and export arguments`() { val address = "e467b9dd11fa00df" + val loadedScript = String(loadScript("import_export_arguments.cdc"), StandardCharsets.UTF_8) val result = accessAPI.simpleFlowScript { script { - """ - pub struct TestClass { - pub let address: Address - pub let balance: UFix64 - pub let hashAlgorithm: HashAlgorithm - pub let isValid: Bool - - init(address: Address, balance: UFix64, hashAlgorithm: HashAlgorithm, isValid: Bool) { - self.address = address - self.balance = balance - self.hashAlgorithm = hashAlgorithm - self.isValid = isValid - } - } - - pub fun main(address: Address): TestClass { - return TestClass( - address: address, - balance: UFix64(1234), - hashAlgorithm: HashAlgorithm.SHA3_256, - isValid: true - ) - } - """ + loadedScript } arg { address(address) } } @@ -159,51 +136,11 @@ class ScriptTest { ) } } + val loadedScript = String(loadScript("domain_tags.cdc"), StandardCharsets.UTF_8) val result = accessAPI.simpleFlowScript { script { - """ - import Crypto - - pub fun main( - rawPublicKeys: [String], - weights: [UFix64], - signatures: [String], - message: String, - ): Bool { - - var i = 0 - let keyList = Crypto.KeyList() - for rawPublicKey in rawPublicKeys { - keyList.add( - PublicKey( - publicKey: rawPublicKey.decodeHex(), - signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 - ), - hashAlgorithm: HashAlgorithm.SHA3_256, - weight: weights[i], - ) - i = i + 1 - } - - i = 0 - let signatureSet: [Crypto.KeyListSignature] = [] - for signature in signatures { - signatureSet.append( - Crypto.KeyListSignature( - keyIndex: i, - signature: signature.decodeHex() - ) - ) - i = i + 1 - } - - return keyList.verify( - signatureSet: signatureSet, - signedData: message.decodeHex(), - ) - } - """ + loadedScript } arg { publicKeys } arg { weights } diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/TestUtils.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/TestUtils.kt index 53133c9..0fcd723 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/TestUtils.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/TestUtils.kt @@ -7,4 +7,6 @@ object TestUtils { private const val MAINNET_HOSTNAME = "access.mainnet.nodes.onflow.org" private const val TESTNET_HOSTNAME = "access.devnet.nodes.onflow.org" + + fun loadScript(name: String): ByteArray = javaClass.classLoader.getResourceAsStream(name)!!.use { it.readAllBytes() } } diff --git a/sdk/src/test/resources/domain_tags.cdc b/sdk/src/test/resources/domain_tags.cdc new file mode 100644 index 0000000..8ce0560 --- /dev/null +++ b/sdk/src/test/resources/domain_tags.cdc @@ -0,0 +1,40 @@ +import Crypto + +pub fun main( + rawPublicKeys: [String], + weights: [UFix64], + signatures: [String], + message: String, +): Bool { + + var i = 0 + let keyList = Crypto.KeyList() + for rawPublicKey in rawPublicKeys { + keyList.add( + PublicKey( + publicKey: rawPublicKey.decodeHex(), + signatureAlgorithm: SignatureAlgorithm.ECDSA_P256 + ), + hashAlgorithm: HashAlgorithm.SHA3_256, + weight: weights[i], + ) + i = i + 1 + } + + i = 0 + let signatureSet: [Crypto.KeyListSignature] = [] + for signature in signatures { + signatureSet.append( + Crypto.KeyListSignature( + keyIndex: i, + signature: signature.decodeHex() + ) + ) + i = i + 1 + } + + return keyList.verify( + signatureSet: signatureSet, + signedData: message.decodeHex(), + ) +} \ No newline at end of file diff --git a/sdk/src/test/resources/hello_world.cdc b/sdk/src/test/resources/hello_world.cdc new file mode 100644 index 0000000..223aca5 --- /dev/null +++ b/sdk/src/test/resources/hello_world.cdc @@ -0,0 +1,3 @@ +pub fun main(): String { + return "Hello World" +} \ No newline at end of file diff --git a/sdk/src/test/resources/import_export_arguments.cdc b/sdk/src/test/resources/import_export_arguments.cdc new file mode 100644 index 0000000..be2ccfa --- /dev/null +++ b/sdk/src/test/resources/import_export_arguments.cdc @@ -0,0 +1,22 @@ +pub struct TestClass { + pub let address: Address + pub let balance: UFix64 + pub let hashAlgorithm: HashAlgorithm + pub let isValid: Bool + + init(address: Address, balance: UFix64, hashAlgorithm: HashAlgorithm, isValid: Bool) { + self.address = address + self.balance = balance + self.hashAlgorithm = hashAlgorithm + self.isValid = isValid + } +} + +pub fun main(address: Address): TestClass { + return TestClass( + address: address, + balance: UFix64(1234), + hashAlgorithm: HashAlgorithm.SHA3_256, + isValid: true + ) +} \ No newline at end of file From a01d9f685b9b0b7df9834c6043dded3266a96ac6 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 18 Jul 2024 18:26:09 +1000 Subject: [PATCH 39/72] Setting up script loader for Cadence scripts --- .../onflow/flow/sdk/IntegrationTestUtils.kt | 2 ++ .../transaction/TransactionCreationTest.kt | 24 +++++++------------ .../transaction/TransactionDecodingTest.kt | 6 ++++- .../sdk/transaction/TransactionSigningTest.kt | 11 ++++----- .../cadence/expose_account_key_issue_1.cdc | 0 .../cadence/expose_account_key_issue_2.cdc | 0 .../cadence/transaction_creation.cdc | 6 +++++ ...ransaction_creation_simple_transaction.cdc | 6 +++++ .../transaction_decoding_precompute_txid.cdc | 1 + .../transaction_signing_byte_arrays.cdc | 5 ++++ sdk/src/test/resources/hello_world.cdc | 2 +- 11 files changed, 38 insertions(+), 25 deletions(-) create mode 100644 sdk/src/intTest/resources/cadence/expose_account_key_issue_1.cdc create mode 100644 sdk/src/intTest/resources/cadence/expose_account_key_issue_2.cdc create mode 100644 sdk/src/intTest/resources/cadence/transaction_creation.cdc create mode 100644 sdk/src/intTest/resources/cadence/transaction_creation_simple_transaction.cdc create mode 100644 sdk/src/intTest/resources/cadence/transaction_decoding_precompute_txid.cdc create mode 100644 sdk/src/intTest/resources/cadence/transaction_signing_byte_arrays.cdc diff --git a/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt b/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt index ddba64c..eec171e 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt @@ -3,6 +3,8 @@ package org.onflow.flow.sdk import org.onflow.flow.sdk.cadence.AddressField object IntegrationTestUtils { + fun loadScript(name: String): ByteArray = javaClass.classLoader.getResourceAsStream(name)!!.use { it.readAllBytes() } +} fun newMainnetAccessApi(): FlowAccessApi = Flow.newAccessApi(MAINNET_HOSTNAME) fun newTestnetAccessApi(): FlowAccessApi = Flow.newAccessApi(TESTNET_HOSTNAME) diff --git a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt index 713e22f..e55ad21 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt @@ -9,6 +9,8 @@ import org.onflow.flow.sdk.test.FlowServiceAccountCredentials import org.onflow.flow.sdk.test.FlowTestClient import org.onflow.flow.sdk.test.TestAccount import org.junit.jupiter.api.Test +import org.onflow.flow.sdk.IntegrationTestUtils.loadScript +import java.nio.charset.StandardCharsets @FlowEmulatorTest class TransactionCreationTest { @@ -57,16 +59,11 @@ class TransactionCreationTest { weight = 1000 ) + val loadedScript = String(loadScript("cadence/transaction_creation.cdc"), StandardCharsets.UTF_8) + val tx = flowTransaction { script { - """ - transaction(publicKey: String) { - prepare(signer: AuthAccount) { - let account = AuthAccount(payer: signer) - account.addPublicKey(publicKey.decodeHex()) - } - } - """ + loadedScript } arguments { @@ -111,16 +108,11 @@ class TransactionCreationTest { weight = 1000 ) + val loadedScript = String(loadScript("cadence/transaction_creation_simple_transaction.cdc"), StandardCharsets.UTF_8) + val transactionResult = accessAPI.simpleFlowTransaction(serviceAccount.flowAddress, serviceAccount.signer) { script { - """ - transaction(publicKey: String) { - prepare(signer: AuthAccount) { - let account = AuthAccount(payer: signer) - account.addPublicKey(publicKey.decodeHex()) - } - } - """ + loadedScript } arguments { diff --git a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionDecodingTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionDecodingTest.kt index 5324805..5590aed 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionDecodingTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionDecodingTest.kt @@ -4,6 +4,8 @@ import org.onflow.flow.sdk.* import org.assertj.core.api.Assertions.assertThat import org.onflow.flow.sdk.test.FlowEmulatorTest import org.junit.jupiter.api.Test +import org.onflow.flow.sdk.IntegrationTestUtils.loadScript +import java.nio.charset.StandardCharsets @FlowEmulatorTest class TransactionDecodingTest { @@ -52,8 +54,10 @@ class TransactionDecodingTest { val proposerAddress = "f8d6e0586b0a20c7" val payerAddress = "ee82856bf20e2aa6" + val loadedScript = String(loadScript("cadence/transaction_decoding_precompute_txid.cdc"), StandardCharsets.UTF_8) + var testTx = FlowTransaction( - script = FlowScript("transaction { execute { log(\"Hello, World!\") } }"), + script = FlowScript(loadedScript), arguments = emptyList(), referenceBlockId = FlowId.of(byteArrayOf(1, 2).copyOf(32)), gasLimit = 42, diff --git a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt index c84b891..acb8718 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt @@ -11,6 +11,8 @@ import org.onflow.flow.sdk.test.FlowServiceAccountCredentials import org.onflow.flow.sdk.test.FlowTestClient import org.onflow.flow.sdk.test.TestAccount import org.junit.jupiter.api.Test +import org.onflow.flow.sdk.IntegrationTestUtils.loadScript +import java.nio.charset.StandardCharsets import kotlin.random.Random @FlowEmulatorTest @@ -46,15 +48,10 @@ class TransactionSigningTest { @Test fun `Byte arrays are properly handled`() { + val loadedScript = String(loadScript("cadence/transaction_signing_byte_arrays.cdc"), StandardCharsets.UTF_8) accessAPI.simpleFlowTransaction(serviceAccount.flowAddress, serviceAccount.signer) { script { - """ - transaction(bytes: [UInt8]) { - prepare(signer: AuthAccount) { - log(bytes) - } - } - """.trimIndent() + loadedScript } arguments { arg { byteArray(Random.nextBytes(2048)) } diff --git a/sdk/src/intTest/resources/cadence/expose_account_key_issue_1.cdc b/sdk/src/intTest/resources/cadence/expose_account_key_issue_1.cdc new file mode 100644 index 0000000..e69de29 diff --git a/sdk/src/intTest/resources/cadence/expose_account_key_issue_2.cdc b/sdk/src/intTest/resources/cadence/expose_account_key_issue_2.cdc new file mode 100644 index 0000000..e69de29 diff --git a/sdk/src/intTest/resources/cadence/transaction_creation.cdc b/sdk/src/intTest/resources/cadence/transaction_creation.cdc new file mode 100644 index 0000000..416f7b5 --- /dev/null +++ b/sdk/src/intTest/resources/cadence/transaction_creation.cdc @@ -0,0 +1,6 @@ +transaction(publicKey: String) { + prepare(signer: AuthAccount) { + let account = AuthAccount(payer: signer) + account.addPublicKey(publicKey.decodeHex()) + } +} \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/transaction_creation_simple_transaction.cdc b/sdk/src/intTest/resources/cadence/transaction_creation_simple_transaction.cdc new file mode 100644 index 0000000..416f7b5 --- /dev/null +++ b/sdk/src/intTest/resources/cadence/transaction_creation_simple_transaction.cdc @@ -0,0 +1,6 @@ +transaction(publicKey: String) { + prepare(signer: AuthAccount) { + let account = AuthAccount(payer: signer) + account.addPublicKey(publicKey.decodeHex()) + } +} \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/transaction_decoding_precompute_txid.cdc b/sdk/src/intTest/resources/cadence/transaction_decoding_precompute_txid.cdc new file mode 100644 index 0000000..ebf9f80 --- /dev/null +++ b/sdk/src/intTest/resources/cadence/transaction_decoding_precompute_txid.cdc @@ -0,0 +1 @@ +transaction { execute { log("Hello, World!") } } \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/transaction_signing_byte_arrays.cdc b/sdk/src/intTest/resources/cadence/transaction_signing_byte_arrays.cdc new file mode 100644 index 0000000..e10f0f9 --- /dev/null +++ b/sdk/src/intTest/resources/cadence/transaction_signing_byte_arrays.cdc @@ -0,0 +1,5 @@ +transaction(bytes: [UInt8]) { + prepare(signer: AuthAccount) { + log(bytes) + } +} \ No newline at end of file diff --git a/sdk/src/test/resources/hello_world.cdc b/sdk/src/test/resources/hello_world.cdc index 223aca5..1ceec42 100644 --- a/sdk/src/test/resources/hello_world.cdc +++ b/sdk/src/test/resources/hello_world.cdc @@ -1,3 +1,3 @@ pub fun main(): String { return "Hello World" -} \ No newline at end of file + } \ No newline at end of file From 6adb589c71bb75b39dc5af5762efb5ac8818f161 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 18 Jul 2024 18:40:40 +1000 Subject: [PATCH 40/72] Setting up script loader for Cadence scripts --- .../flow/sdk/ExposeAccountKeyIssueTest.kt | 60 +++---------------- .../cadence/expose_account_key_issue_1.cdc | 29 +++++++++ .../cadence/expose_account_key_issue_2.cdc | 12 ++++ .../cadence/expose_account_key_issue_3.cdc | 5 ++ .../org/onflow/flow/sdk/test/FlowTestUtil.kt | 36 ++--------- .../resources/test_utils_create_account.cdc | 29 +++++++++ 6 files changed, 88 insertions(+), 83 deletions(-) create mode 100644 sdk/src/intTest/resources/cadence/expose_account_key_issue_3.cdc create mode 100644 sdk/src/testFixtures/resources/test_utils_create_account.cdc diff --git a/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt index 0ffea60..64539f6 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt @@ -6,7 +6,9 @@ import org.onflow.flow.sdk.test.FlowServiceAccountCredentials import org.onflow.flow.sdk.test.FlowTestClient import org.onflow.flow.sdk.test.TestAccount import org.junit.jupiter.api.Assertions.* +import org.onflow.flow.sdk.IntegrationTestUtils.loadScript import java.math.BigDecimal +import java.nio.charset.StandardCharsets @FlowEmulatorTest class ExposeAccountKeyIssueTest { @@ -37,42 +39,13 @@ class ExposeAccountKeyIssueTest { val pair1 = Crypto.generateKeyPair(signatureAlgorithm1) val signer1 = Crypto.getSigner(pair1.private, hashAlgorithm1) + val loadedScript1 = String(loadScript("cadence/expose_account_key_issue_1.cdc"), StandardCharsets.UTF_8) val createAccountResult = flow.simpleFlowTransaction( serviceAccount.flowAddress, serviceAccount.signer ) { script { - """ - import FlowToken from 0xFLOWTOKEN - import FungibleToken from 0xFUNGIBLETOKEN - - transaction(startingBalance: UFix64, publicKey: String, signatureAlgorithm: UInt8, hashAlgorithm: UInt8) { - prepare(signer: AuthAccount) { - - let newAccount = AuthAccount(payer: signer) - - newAccount.keys.add( - publicKey: PublicKey( - publicKey: publicKey.decodeHex(), - signatureAlgorithm: SignatureAlgorithm(rawValue: signatureAlgorithm)! - ), - hashAlgorithm: HashAlgorithm(rawValue: hashAlgorithm)!, - weight: UFix64(1000) - ) - - let provider = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) - ?? panic("Could not borrow FlowToken.Vault reference") - - let newVault = newAccount - .getCapability(/public/flowTokenReceiver) - .borrow<&{FungibleToken.Receiver}>() - ?? panic("Could not borrow FungibleToken.Receiver reference") - - let coin <- provider.withdraw(amount: startingBalance) - newVault.deposit(from: <- coin) - } - } - """ + loadedScript1 } arguments { arg { ufix64(startingBalance) } @@ -97,22 +70,10 @@ class ExposeAccountKeyIssueTest { val pair2 = Crypto.generateKeyPair(signatureAlgorithm2) val signer2 = Crypto.getSigner(pair2.private, hashAlgorithm2) + val loadedScript2 = String(loadScript("cadence/expose_account_key_issue_2.cdc"), StandardCharsets.UTF_8) val addKeyResult = flow.simpleFlowTransaction(newAccountAddress, signer1) { script { - """ - transaction(publicKey: String, signatureAlgorithm: UInt8, hashAlgorithm: UInt8, weight: UFix64) { - prepare(signer: AuthAccount) { - signer.keys.add( - publicKey: PublicKey( - publicKey: publicKey.decodeHex(), - signatureAlgorithm: SignatureAlgorithm(rawValue: signatureAlgorithm)! - ), - hashAlgorithm: HashAlgorithm(rawValue: hashAlgorithm)!, - weight: weight - ) - } - } - """ + loadedScript2 } arguments { arg { string(pair2.public.hex) } @@ -132,16 +93,11 @@ class ExposeAccountKeyIssueTest { assertFalse(updatedAccount.keys[0].revoked) assertFalse(updatedAccount.keys[1].revoked) + val loadedScript3 = String(loadScript("cadence/expose_account_key_issue_3.cdc"), StandardCharsets.UTF_8) // Remove the second key val removeKeyResult = flow.simpleFlowTransaction(newAccountAddress, signer1) { script { - """ - transaction(index: Int) { - prepare(signer: AuthAccount) { - signer.keys.revoke(keyIndex: index) ?? panic("Key not found to revoke") - } - } - """ + loadedScript3 } arguments { arg { int(1) } diff --git a/sdk/src/intTest/resources/cadence/expose_account_key_issue_1.cdc b/sdk/src/intTest/resources/cadence/expose_account_key_issue_1.cdc index e69de29..bfbbc3a 100644 --- a/sdk/src/intTest/resources/cadence/expose_account_key_issue_1.cdc +++ b/sdk/src/intTest/resources/cadence/expose_account_key_issue_1.cdc @@ -0,0 +1,29 @@ +import FlowToken from 0xFLOWTOKEN +import FungibleToken from 0xFUNGIBLETOKEN + +transaction(startingBalance: UFix64, publicKey: String, signatureAlgorithm: UInt8, hashAlgorithm: UInt8) { + prepare(signer: AuthAccount) { + + let newAccount = AuthAccount(payer: signer) + + newAccount.keys.add( + publicKey: PublicKey( + publicKey: publicKey.decodeHex(), + signatureAlgorithm: SignatureAlgorithm(rawValue: signatureAlgorithm)! + ), + hashAlgorithm: HashAlgorithm(rawValue: hashAlgorithm)!, + weight: UFix64(1000) + ) + + let provider = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) + ?? panic("Could not borrow FlowToken.Vault reference") + + let newVault = newAccount + .getCapability(/public/flowTokenReceiver) + .borrow<&{FungibleToken.Receiver}>() + ?? panic("Could not borrow FungibleToken.Receiver reference") + + let coin <- provider.withdraw(amount: startingBalance) + newVault.deposit(from: <- coin) + } +} \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/expose_account_key_issue_2.cdc b/sdk/src/intTest/resources/cadence/expose_account_key_issue_2.cdc index e69de29..4bed02b 100644 --- a/sdk/src/intTest/resources/cadence/expose_account_key_issue_2.cdc +++ b/sdk/src/intTest/resources/cadence/expose_account_key_issue_2.cdc @@ -0,0 +1,12 @@ +transaction(publicKey: String, signatureAlgorithm: UInt8, hashAlgorithm: UInt8, weight: UFix64) { + prepare(signer: AuthAccount) { + signer.keys.add( + publicKey: PublicKey( + publicKey: publicKey.decodeHex(), + signatureAlgorithm: SignatureAlgorithm(rawValue: signatureAlgorithm)! + ), + hashAlgorithm: HashAlgorithm(rawValue: hashAlgorithm)!, + weight: weight + ) + } +} \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/expose_account_key_issue_3.cdc b/sdk/src/intTest/resources/cadence/expose_account_key_issue_3.cdc new file mode 100644 index 0000000..f20fcd3 --- /dev/null +++ b/sdk/src/intTest/resources/cadence/expose_account_key_issue_3.cdc @@ -0,0 +1,5 @@ +transaction(index: Int) { + prepare(signer: AuthAccount) { + signer.keys.revoke(keyIndex: index) ?? panic("Key not found to revoke") + } +} \ No newline at end of file diff --git a/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt b/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt index 862d3d1..c607e13 100644 --- a/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt +++ b/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt @@ -7,9 +7,12 @@ import org.onflow.flow.sdk.impl.FlowAccessApiImpl import java.io.File import java.io.IOException import java.math.BigDecimal +import java.nio.charset.StandardCharsets import kotlin.io.path.createTempDirectory object FlowTestUtil { + private fun loadScript(name: String): ByteArray = javaClass.classLoader.getResourceAsStream(name)!!.use { it.readAllBytes() } + @JvmStatic @JvmOverloads fun deployContracts( @@ -76,43 +79,14 @@ object FlowTestUtil { hashAlgo: HashAlgorithm, balance: BigDecimal = BigDecimal(0.01) ): FlowAccessApi.AccessApiCallResponse { + val loadedScript = String(loadScript("test_utils_create_account.cdc"), StandardCharsets.UTF_8) val transactionResult = api.simpleFlowTransaction( address = serviceAccount.flowAddress, signer = serviceAccount.signer, keyIndex = serviceAccount.keyIndex ) { script { - """ - import FlowToken from 0xFLOWTOKEN - import FungibleToken from 0xFUNGIBLETOKEN - - transaction(startingBalance: UFix64, publicKey: String, signatureAlgorithm: UInt8, hashAlgorithm: UInt8) { - prepare(signer: AuthAccount) { - - let newAccount = AuthAccount(payer: signer) - - let provider = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) - ?? panic("Could not borrow FlowToken.Vault reference") - - let newVault = newAccount - .getCapability(/public/flowTokenReceiver) - .borrow<&{FungibleToken.Receiver}>() - ?? panic("Could not borrow FungibleToken.Receiver reference") - - let coin <- provider.withdraw(amount: startingBalance) - newVault.deposit(from: <- coin) - - newAccount.keys.add( - publicKey: PublicKey( - publicKey: publicKey.decodeHex(), - signatureAlgorithm: SignatureAlgorithm(rawValue: signatureAlgorithm)! - ), - hashAlgorithm: HashAlgorithm(rawValue: hashAlgorithm)!, - weight: UFix64(1000) - ) - } - } - """ + loadedScript } gasLimit(1000) arguments { diff --git a/sdk/src/testFixtures/resources/test_utils_create_account.cdc b/sdk/src/testFixtures/resources/test_utils_create_account.cdc new file mode 100644 index 0000000..7443f8f --- /dev/null +++ b/sdk/src/testFixtures/resources/test_utils_create_account.cdc @@ -0,0 +1,29 @@ +import FlowToken from 0xFLOWTOKEN +import FungibleToken from 0xFUNGIBLETOKEN + +transaction(startingBalance: UFix64, publicKey: String, signatureAlgorithm: UInt8, hashAlgorithm: UInt8) { + prepare(signer: AuthAccount) { + + let newAccount = AuthAccount(payer: signer) + + let provider = signer.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault) + ?? panic("Could not borrow FlowToken.Vault reference") + + let newVault = newAccount + .getCapability(/public/flowTokenReceiver) + .borrow<&{FungibleToken.Receiver}>() + ?? panic("Could not borrow FungibleToken.Receiver reference") + + let coin <- provider.withdraw(amount: startingBalance) + newVault.deposit(from: <- coin) + + newAccount.keys.add( + publicKey: PublicKey( + publicKey: publicKey.decodeHex(), + signatureAlgorithm: SignatureAlgorithm(rawValue: signatureAlgorithm)! + ), + hashAlgorithm: HashAlgorithm(rawValue: hashAlgorithm)!, + weight: UFix64(1000) + ) + } +} \ No newline at end of file From a85db2b9030c323de15cfaa98402e3025823e77d Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 18 Jul 2024 18:46:04 +1000 Subject: [PATCH 41/72] Refactor --- .../org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt | 6 +++--- .../onflow/flow/sdk/transaction/TransactionCreationTest.kt | 5 ++--- .../onflow/flow/sdk/transaction/TransactionDecodingTest.kt | 2 +- .../onflow/flow/sdk/transaction/TransactionSigningTest.kt | 2 +- .../expose_account_key_issue_1.cdc | 0 .../expose_account_key_issue_2.cdc | 0 .../expose_account_key_issue_3.cdc | 0 .../{ => transaction_creation}/transaction_creation.cdc | 0 .../transaction_creation_simple_transaction.cdc | 0 .../transaction_decoding_precompute_txid.cdc | 0 .../transaction_signing_byte_arrays.cdc | 0 11 files changed, 7 insertions(+), 8 deletions(-) rename sdk/src/intTest/resources/cadence/{ => expose_account_key_issue}/expose_account_key_issue_1.cdc (100%) rename sdk/src/intTest/resources/cadence/{ => expose_account_key_issue}/expose_account_key_issue_2.cdc (100%) rename sdk/src/intTest/resources/cadence/{ => expose_account_key_issue}/expose_account_key_issue_3.cdc (100%) rename sdk/src/intTest/resources/cadence/{ => transaction_creation}/transaction_creation.cdc (100%) rename sdk/src/intTest/resources/cadence/{ => transaction_creation}/transaction_creation_simple_transaction.cdc (100%) rename sdk/src/intTest/resources/cadence/{ => transaction_decoding}/transaction_decoding_precompute_txid.cdc (100%) rename sdk/src/intTest/resources/cadence/{ => transaction_signing}/transaction_signing_byte_arrays.cdc (100%) diff --git a/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt index 64539f6..764bf4b 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt @@ -39,7 +39,7 @@ class ExposeAccountKeyIssueTest { val pair1 = Crypto.generateKeyPair(signatureAlgorithm1) val signer1 = Crypto.getSigner(pair1.private, hashAlgorithm1) - val loadedScript1 = String(loadScript("cadence/expose_account_key_issue_1.cdc"), StandardCharsets.UTF_8) + val loadedScript1 = String(loadScript("cadence/expose_account_key_issue/expose_account_key_issue_1.cdc"), StandardCharsets.UTF_8) val createAccountResult = flow.simpleFlowTransaction( serviceAccount.flowAddress, serviceAccount.signer @@ -70,7 +70,7 @@ class ExposeAccountKeyIssueTest { val pair2 = Crypto.generateKeyPair(signatureAlgorithm2) val signer2 = Crypto.getSigner(pair2.private, hashAlgorithm2) - val loadedScript2 = String(loadScript("cadence/expose_account_key_issue_2.cdc"), StandardCharsets.UTF_8) + val loadedScript2 = String(loadScript("cadence/expose_account_key_issue/expose_account_key_issue_2.cdc"), StandardCharsets.UTF_8) val addKeyResult = flow.simpleFlowTransaction(newAccountAddress, signer1) { script { loadedScript2 @@ -93,7 +93,7 @@ class ExposeAccountKeyIssueTest { assertFalse(updatedAccount.keys[0].revoked) assertFalse(updatedAccount.keys[1].revoked) - val loadedScript3 = String(loadScript("cadence/expose_account_key_issue_3.cdc"), StandardCharsets.UTF_8) + val loadedScript3 = String(loadScript("cadence/expose_account_key_issue/expose_account_key_issue_3.cdc"), StandardCharsets.UTF_8) // Remove the second key val removeKeyResult = flow.simpleFlowTransaction(newAccountAddress, signer1) { script { diff --git a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt index e55ad21..2047b85 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt @@ -2,7 +2,6 @@ package org.onflow.flow.sdk.transaction import org.onflow.flow.sdk.* import org.onflow.flow.sdk.crypto.Crypto -import org.onflow.flow.sdk.IntegrationTestUtils.transaction import org.assertj.core.api.Assertions.assertThat import org.onflow.flow.sdk.test.FlowEmulatorTest import org.onflow.flow.sdk.test.FlowServiceAccountCredentials @@ -59,7 +58,7 @@ class TransactionCreationTest { weight = 1000 ) - val loadedScript = String(loadScript("cadence/transaction_creation.cdc"), StandardCharsets.UTF_8) + val loadedScript = String(loadScript("cadence/transaction_creation/transaction_creation.cdc"), StandardCharsets.UTF_8) val tx = flowTransaction { script { @@ -108,7 +107,7 @@ class TransactionCreationTest { weight = 1000 ) - val loadedScript = String(loadScript("cadence/transaction_creation_simple_transaction.cdc"), StandardCharsets.UTF_8) + val loadedScript = String(loadScript("cadence/transaction_creation/transaction_creation_simple_transaction.cdc"), StandardCharsets.UTF_8) val transactionResult = accessAPI.simpleFlowTransaction(serviceAccount.flowAddress, serviceAccount.signer) { script { diff --git a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionDecodingTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionDecodingTest.kt index 5590aed..ddd4031 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionDecodingTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionDecodingTest.kt @@ -54,7 +54,7 @@ class TransactionDecodingTest { val proposerAddress = "f8d6e0586b0a20c7" val payerAddress = "ee82856bf20e2aa6" - val loadedScript = String(loadScript("cadence/transaction_decoding_precompute_txid.cdc"), StandardCharsets.UTF_8) + val loadedScript = String(loadScript("cadence/transaction_decoding/transaction_decoding_precompute_txid.cdc"), StandardCharsets.UTF_8) var testTx = FlowTransaction( script = FlowScript(loadedScript), diff --git a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt index acb8718..3ce5589 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt @@ -48,7 +48,7 @@ class TransactionSigningTest { @Test fun `Byte arrays are properly handled`() { - val loadedScript = String(loadScript("cadence/transaction_signing_byte_arrays.cdc"), StandardCharsets.UTF_8) + val loadedScript = String(loadScript("cadence/transaction_signing/transaction_signing_byte_arrays.cdc"), StandardCharsets.UTF_8) accessAPI.simpleFlowTransaction(serviceAccount.flowAddress, serviceAccount.signer) { script { loadedScript diff --git a/sdk/src/intTest/resources/cadence/expose_account_key_issue_1.cdc b/sdk/src/intTest/resources/cadence/expose_account_key_issue/expose_account_key_issue_1.cdc similarity index 100% rename from sdk/src/intTest/resources/cadence/expose_account_key_issue_1.cdc rename to sdk/src/intTest/resources/cadence/expose_account_key_issue/expose_account_key_issue_1.cdc diff --git a/sdk/src/intTest/resources/cadence/expose_account_key_issue_2.cdc b/sdk/src/intTest/resources/cadence/expose_account_key_issue/expose_account_key_issue_2.cdc similarity index 100% rename from sdk/src/intTest/resources/cadence/expose_account_key_issue_2.cdc rename to sdk/src/intTest/resources/cadence/expose_account_key_issue/expose_account_key_issue_2.cdc diff --git a/sdk/src/intTest/resources/cadence/expose_account_key_issue_3.cdc b/sdk/src/intTest/resources/cadence/expose_account_key_issue/expose_account_key_issue_3.cdc similarity index 100% rename from sdk/src/intTest/resources/cadence/expose_account_key_issue_3.cdc rename to sdk/src/intTest/resources/cadence/expose_account_key_issue/expose_account_key_issue_3.cdc diff --git a/sdk/src/intTest/resources/cadence/transaction_creation.cdc b/sdk/src/intTest/resources/cadence/transaction_creation/transaction_creation.cdc similarity index 100% rename from sdk/src/intTest/resources/cadence/transaction_creation.cdc rename to sdk/src/intTest/resources/cadence/transaction_creation/transaction_creation.cdc diff --git a/sdk/src/intTest/resources/cadence/transaction_creation_simple_transaction.cdc b/sdk/src/intTest/resources/cadence/transaction_creation/transaction_creation_simple_transaction.cdc similarity index 100% rename from sdk/src/intTest/resources/cadence/transaction_creation_simple_transaction.cdc rename to sdk/src/intTest/resources/cadence/transaction_creation/transaction_creation_simple_transaction.cdc diff --git a/sdk/src/intTest/resources/cadence/transaction_decoding_precompute_txid.cdc b/sdk/src/intTest/resources/cadence/transaction_decoding/transaction_decoding_precompute_txid.cdc similarity index 100% rename from sdk/src/intTest/resources/cadence/transaction_decoding_precompute_txid.cdc rename to sdk/src/intTest/resources/cadence/transaction_decoding/transaction_decoding_precompute_txid.cdc diff --git a/sdk/src/intTest/resources/cadence/transaction_signing_byte_arrays.cdc b/sdk/src/intTest/resources/cadence/transaction_signing/transaction_signing_byte_arrays.cdc similarity index 100% rename from sdk/src/intTest/resources/cadence/transaction_signing_byte_arrays.cdc rename to sdk/src/intTest/resources/cadence/transaction_signing/transaction_signing_byte_arrays.cdc From f0d2c9d01bd3841b47ee486a970c3e58a8b5d45d Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 18 Jul 2024 20:05:48 +1000 Subject: [PATCH 42/72] Refactor --- .../flow/sdk/ExposeAccountKeyIssueTest.kt | 14 ++-- .../onflow/flow/sdk/IntegrationTestUtils.kt | 3 +- .../transaction/TransactionCreationTest.kt | 13 ++-- .../transaction/TransactionIntegrationTest.kt | 71 ++++++++++--------- .../sdk/transaction/TransactionSigningTest.kt | 7 +- .../kotlin/org/onflow/flow/sdk/ScriptTest.kt | 2 +- 6 files changed, 56 insertions(+), 54 deletions(-) diff --git a/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt index 764bf4b..d717c8c 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt @@ -55,10 +55,10 @@ class ExposeAccountKeyIssueTest { } }.sendAndWaitForSeal() - val createAccountResultData = IntegrationTestUtils.handleResult(createAccountResult, "Failed to create account") - val newAccountAddress = IntegrationTestUtils.getAccountAddressFromResult(createAccountResultData) + val createAccountResultData = handleResult(createAccountResult, "Failed to create account") + val newAccountAddress = getAccountAddressFromResult(createAccountResultData) - val account = IntegrationTestUtils.getAccount(flow, newAccountAddress) + val account = getAccount(flow, newAccountAddress) assertEquals(1, account.keys.size) assertEquals(pair1.public.hex, account.keys[0].publicKey.base16Value) @@ -83,9 +83,9 @@ class ExposeAccountKeyIssueTest { } }.sendAndWaitForSeal() - IntegrationTestUtils.handleResult(addKeyResult, "Failed to add key") + handleResult(addKeyResult, "Failed to add key") - val updatedAccount = IntegrationTestUtils.getAccount(flow, newAccountAddress) + val updatedAccount = getAccount(flow, newAccountAddress) assertEquals(2, updatedAccount.keys.size) assertEquals(pair1.public.hex, updatedAccount.keys[0].publicKey.base16Value) @@ -104,9 +104,9 @@ class ExposeAccountKeyIssueTest { } }.sendAndWaitForSeal() - IntegrationTestUtils.handleResult(removeKeyResult, "Failed to remove key") + handleResult(removeKeyResult, "Failed to remove key") - val finalAccount = IntegrationTestUtils.getAccount(flow, newAccountAddress) + val finalAccount = getAccount(flow, newAccountAddress) assertEquals(2, finalAccount.keys.size) assertEquals(pair1.public.hex, finalAccount.keys[0].publicKey.base16Value) diff --git a/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt b/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt index eec171e..79bb599 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt @@ -4,7 +4,6 @@ import org.onflow.flow.sdk.cadence.AddressField object IntegrationTestUtils { fun loadScript(name: String): ByteArray = javaClass.classLoader.getResourceAsStream(name)!!.use { it.readAllBytes() } -} fun newMainnetAccessApi(): FlowAccessApi = Flow.newAccessApi(MAINNET_HOSTNAME) fun newTestnetAccessApi(): FlowAccessApi = Flow.newAccessApi(TESTNET_HOSTNAME) @@ -41,6 +40,6 @@ object IntegrationTestUtils { fun getAccount(api: FlowAccessApi, address: FlowAddress): FlowAccount { val result = api.getAccountAtLatestBlock(address) - return handleResult(result, "Failed to get account at latest block") as FlowAccount + return handleResult(result, "Failed to get account at latest block") } } diff --git a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt index 2047b85..7e8d8c2 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionCreationTest.kt @@ -8,7 +8,10 @@ import org.onflow.flow.sdk.test.FlowServiceAccountCredentials import org.onflow.flow.sdk.test.FlowTestClient import org.onflow.flow.sdk.test.TestAccount import org.junit.jupiter.api.Test +import org.onflow.flow.sdk.IntegrationTestUtils.getAccount +import org.onflow.flow.sdk.IntegrationTestUtils.handleResult import org.onflow.flow.sdk.IntegrationTestUtils.loadScript +import org.onflow.flow.sdk.IntegrationTestUtils.transaction import java.nio.charset.StandardCharsets @FlowEmulatorTest @@ -48,7 +51,7 @@ class TransactionCreationTest { @Test fun `Can create an account using the transaction DSL`() { val latestBlockId = getLatestBlockId(accessAPI) - val payerAccount = IntegrationTestUtils.getAccount(accessAPI, serviceAccount.flowAddress) + val payerAccount = getAccount(accessAPI, serviceAccount.flowAddress) val newAccountKeyPair = Crypto.generateKeyPair(SignatureAlgorithm.ECDSA_P256) val newAccountPublicKey = FlowAccountKey( @@ -89,9 +92,9 @@ class TransactionCreationTest { } } - val txID = IntegrationTestUtils.handleResult(accessAPI.sendTransaction(tx), "Failed to send transaction") + val txID = handleResult(accessAPI.sendTransaction(tx), "Failed to send transaction") - val result = IntegrationTestUtils.handleResult(waitForSeal(accessAPI, txID), "Failed to wait for seal") + val result = handleResult(waitForSeal(accessAPI, txID), "Failed to wait for seal") assertThat(result).isNotNull assertThat(result.status).isEqualTo(FlowTransactionStatus.SEALED) @@ -119,13 +122,13 @@ class TransactionCreationTest { } }.sendAndWaitForSeal() - val result = IntegrationTestUtils.handleResult(transactionResult, "Failed to create account") + val result = handleResult(transactionResult, "Failed to create account") assertThat(result.status).isEqualTo(FlowTransactionStatus.SEALED) } private fun getLatestBlockId(api: FlowAccessApi): FlowId { val result = api.getLatestBlockHeader() - return IntegrationTestUtils.handleResult(result, "Failed to get latest block header").id + return handleResult(result, "Failed to get latest block header").id } } diff --git a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt index 8b8a447..6c653f0 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionIntegrationTest.kt @@ -5,14 +5,17 @@ import org.onflow.flow.sdk.test.FlowEmulatorTest import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import org.junit.jupiter.api.fail +import org.onflow.flow.sdk.IntegrationTestUtils.handleResult +import org.onflow.flow.sdk.IntegrationTestUtils.newMainnetAccessApi +import org.onflow.flow.sdk.IntegrationTestUtils.newTestnetAccessApi @FlowEmulatorTest class TransactionIntegrationTest { @Test fun wut() { val account = try { - IntegrationTestUtils.handleResult( - IntegrationTestUtils.newTestnetAccessApi().getAccountAtLatestBlock(FlowAddress("0x6bd3869f2631beb3")), + handleResult( + newTestnetAccessApi().getAccountAtLatestBlock(FlowAddress("0x6bd3869f2631beb3")), "Failed to get account" ) } catch (e: Exception) { @@ -23,16 +26,16 @@ class TransactionIntegrationTest { @Test fun `Can connect to mainnet`() { - val accessAPI = IntegrationTestUtils.newMainnetAccessApi() + val accessAPI = newMainnetAccessApi() try { - IntegrationTestUtils.handleResult(accessAPI.ping(), "Failed to ping") + handleResult(accessAPI.ping(), "Failed to ping") } catch (e: Exception) { fail("Failed to ping mainnet: ${e.message}") } val address = FlowAddress("e467b9dd11fa00df") val account = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getAccountAtLatestBlock(address), "Failed to get account" ) @@ -47,9 +50,9 @@ class TransactionIntegrationTest { @Test fun `Can get network parameters`() { - val accessAPI = IntegrationTestUtils.newMainnetAccessApi() + val accessAPI = newMainnetAccessApi() val networkParams = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getNetworkParameters(), "Failed to get network parameters" ) @@ -62,9 +65,9 @@ class TransactionIntegrationTest { @Test fun `Can get latest protocol state snapshot`() { - val accessAPI = IntegrationTestUtils.newMainnetAccessApi() + val accessAPI = newMainnetAccessApi() val snapshot = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getLatestProtocolStateSnapshot(), "Failed to get latest protocol state snapshot" ) @@ -77,11 +80,11 @@ class TransactionIntegrationTest { @Test fun `Can parse events`() { - val accessApi = IntegrationTestUtils.newMainnetAccessApi() + val accessApi = newMainnetAccessApi() // https://flowscan.org/transaction/8c2e9d37a063240f236aa181e1454eb62991b42302534d4d6dd3839c2df0ef14 val tx = try { - IntegrationTestUtils.handleResult( + handleResult( accessApi.getTransactionById(FlowId("8c2e9d37a063240f236aa181e1454eb62991b42302534d4d6dd3839c2df0ef14")), "Failed to get transaction" ) @@ -92,7 +95,7 @@ class TransactionIntegrationTest { assertThat(tx).isNotNull val results = try { - IntegrationTestUtils.handleResult( + handleResult( accessApi.getTransactionResultById(FlowId("8c2e9d37a063240f236aa181e1454eb62991b42302534d4d6dd3839c2df0ef14")), "Failed to get transaction results" ) @@ -119,10 +122,10 @@ class TransactionIntegrationTest { @Test fun `Can get block header by id`() { - val accessAPI = IntegrationTestUtils.newMainnetAccessApi() + val accessAPI = newMainnetAccessApi() val latestBlock = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getLatestBlock(true), "Failed to get latest block" ) @@ -133,7 +136,7 @@ class TransactionIntegrationTest { assertThat(latestBlock).isNotNull val blockHeaderById = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getBlockHeaderById(latestBlock.id), "Failed to get block header by ID" ) @@ -146,10 +149,10 @@ class TransactionIntegrationTest { @Test fun `Can get block header by height`() { - val accessAPI = IntegrationTestUtils.newMainnetAccessApi() + val accessAPI = newMainnetAccessApi() val latestBlock = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getLatestBlock(true), "Failed to get latest block" ) @@ -160,7 +163,7 @@ class TransactionIntegrationTest { assertThat(latestBlock).isNotNull val blockHeader = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getBlockHeaderByHeight(latestBlock.height), "Failed to get block header by height" ) @@ -174,10 +177,10 @@ class TransactionIntegrationTest { @Test fun `Can get latest block`() { - val accessAPI = IntegrationTestUtils.newMainnetAccessApi() + val accessAPI = newMainnetAccessApi() val latestBlock = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getLatestBlock(true), "Failed to get latest block" ) @@ -190,10 +193,10 @@ class TransactionIntegrationTest { @Test fun `Can get block by id`() { - val accessAPI = IntegrationTestUtils.newMainnetAccessApi() + val accessAPI = newMainnetAccessApi() val latestBlock = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getLatestBlock(true), "Failed to get latest block" ) @@ -204,7 +207,7 @@ class TransactionIntegrationTest { assertThat(latestBlock).isNotNull val blockById = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getBlockById(latestBlock.id), "Failed to get block by ID" ) @@ -218,10 +221,10 @@ class TransactionIntegrationTest { @Test fun `Can get block by height`() { - val accessAPI = IntegrationTestUtils.newMainnetAccessApi() + val accessAPI = newMainnetAccessApi() val latestBlock = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getLatestBlock(true), "Failed to get latest block" ) @@ -232,7 +235,7 @@ class TransactionIntegrationTest { assertThat(latestBlock).isNotNull val blockByHeight = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getBlockByHeight(latestBlock.height), "Failed to get block by height" ) @@ -246,11 +249,11 @@ class TransactionIntegrationTest { @Test fun `Can get account by address`() { - val accessAPI = IntegrationTestUtils.newMainnetAccessApi() + val accessAPI = newMainnetAccessApi() val address = FlowAddress("18eb4ee6b3c026d2") val account = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getAccountByAddress(address), "Failed to get account by address" ) @@ -264,11 +267,11 @@ class TransactionIntegrationTest { @Test fun `Can get account by address at latest block`() { - val accessAPI = IntegrationTestUtils.newMainnetAccessApi() + val accessAPI = newMainnetAccessApi() val address = FlowAddress("18eb4ee6b3c026d2") val account = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getAccountAtLatestBlock(address), "Failed to get account at latest block" ) @@ -282,10 +285,10 @@ class TransactionIntegrationTest { @Test fun `Can get account by block height`() { - val accessAPI = IntegrationTestUtils.newMainnetAccessApi() + val accessAPI = newMainnetAccessApi() val latestBlock = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getLatestBlock(true), "Failed to get latest block" ) @@ -294,7 +297,7 @@ class TransactionIntegrationTest { } val blockHeader = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getBlockHeaderByHeight(latestBlock.height), "Failed to get block header by height" ) @@ -304,7 +307,7 @@ class TransactionIntegrationTest { val address = FlowAddress("18eb4ee6b3c026d2") val account = try { - IntegrationTestUtils.handleResult( + handleResult( accessAPI.getAccountByBlockHeight(address, blockHeader.height), "Failed to get account by block height" ) diff --git a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt index 3ce5589..2fa5650 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/transaction/TransactionSigningTest.kt @@ -1,17 +1,14 @@ package org.onflow.flow.sdk.transaction -import org.onflow.flow.sdk.FlowAccessApi -import org.onflow.flow.sdk.FlowAddress -import org.onflow.flow.sdk.IntegrationTestUtils.transaction -import org.onflow.flow.sdk.bytesToHex import org.onflow.flow.sdk.crypto.Crypto -import org.onflow.flow.sdk.simpleFlowTransaction import org.onflow.flow.sdk.test.FlowEmulatorTest import org.onflow.flow.sdk.test.FlowServiceAccountCredentials import org.onflow.flow.sdk.test.FlowTestClient import org.onflow.flow.sdk.test.TestAccount import org.junit.jupiter.api.Test +import org.onflow.flow.sdk.* import org.onflow.flow.sdk.IntegrationTestUtils.loadScript +import org.onflow.flow.sdk.IntegrationTestUtils.transaction import java.nio.charset.StandardCharsets import kotlin.random.Random diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt index b8a95a3..aa06d06 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt @@ -140,7 +140,7 @@ class ScriptTest { val result = accessAPI.simpleFlowScript { script { - loadedScript + loadedScript } arg { publicKeys } arg { weights } From 7bd366a0d2c135921439222629f8019c37b050c3 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 18 Jul 2024 20:06:32 +1000 Subject: [PATCH 43/72] Refactor --- .../intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt index d717c8c..034909e 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/ExposeAccountKeyIssueTest.kt @@ -6,6 +6,9 @@ import org.onflow.flow.sdk.test.FlowServiceAccountCredentials import org.onflow.flow.sdk.test.FlowTestClient import org.onflow.flow.sdk.test.TestAccount import org.junit.jupiter.api.Assertions.* +import org.onflow.flow.sdk.IntegrationTestUtils.getAccount +import org.onflow.flow.sdk.IntegrationTestUtils.getAccountAddressFromResult +import org.onflow.flow.sdk.IntegrationTestUtils.handleResult import org.onflow.flow.sdk.IntegrationTestUtils.loadScript import java.math.BigDecimal import java.nio.charset.StandardCharsets From e8122ac96b4803d87b864d9b6ed87fb28c4e3b29 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 18 Jul 2024 20:38:05 +1000 Subject: [PATCH 44/72] Update JSON Cadence tests to load from external snippets --- .../flow/sdk/cadence/JsonCadenceTest.kt | 184 ++++-------------- .../cadence/json_cadence/decode_array.cdc | 3 + .../cadence/json_cadence/decode_boolean.cdc | 3 + .../json_cadence/decode_complex_dict.cdc | 31 +++ .../cadence/json_cadence/decode_enum.cdc | 9 + .../cadence/json_cadence/decode_optional.cdc | 3 + .../json_cadence/decode_optional_2.cdc | 3 + .../cadence/json_cadence/decode_reference.cdc | 6 + .../cadence/json_cadence/decode_resource.cdc | 12 ++ .../cadence/json_cadence/decode_struct.cdc | 16 ++ .../cadence/json_cadence/decode_ufix64.cdc | 3 + 11 files changed, 122 insertions(+), 151 deletions(-) create mode 100644 sdk/src/intTest/resources/cadence/json_cadence/decode_array.cdc create mode 100644 sdk/src/intTest/resources/cadence/json_cadence/decode_boolean.cdc create mode 100644 sdk/src/intTest/resources/cadence/json_cadence/decode_complex_dict.cdc create mode 100644 sdk/src/intTest/resources/cadence/json_cadence/decode_enum.cdc create mode 100644 sdk/src/intTest/resources/cadence/json_cadence/decode_optional.cdc create mode 100644 sdk/src/intTest/resources/cadence/json_cadence/decode_optional_2.cdc create mode 100644 sdk/src/intTest/resources/cadence/json_cadence/decode_reference.cdc create mode 100644 sdk/src/intTest/resources/cadence/json_cadence/decode_resource.cdc create mode 100644 sdk/src/intTest/resources/cadence/json_cadence/decode_struct.cdc create mode 100644 sdk/src/intTest/resources/cadence/json_cadence/decode_ufix64.cdc diff --git a/sdk/src/intTest/org/onflow/flow/sdk/cadence/JsonCadenceTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/cadence/JsonCadenceTest.kt index 2754c92..69e1b4e 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/cadence/JsonCadenceTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/cadence/JsonCadenceTest.kt @@ -2,10 +2,13 @@ package org.onflow.flow.sdk.cadence import kotlinx.serialization.Serializable import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.onflow.flow.sdk.* +import java.nio.charset.StandardCharsets class JsonCadenceTest { + @Serializable data class StorageInfo( val capacity: Int, @@ -37,11 +40,26 @@ class JsonCadenceTest { val rawValue: Int ) - private val flow = IntegrationTestUtils.newMainnetAccessApi() + private lateinit var flow: FlowAccessApi + + @BeforeEach + fun setup() { + flow = IntegrationTestUtils.newMainnetAccessApi() + } + + private fun loadScriptContent(path: String): String { + return String(IntegrationTestUtils.loadScript(path), StandardCharsets.UTF_8) + } + + private fun executeScript(scriptPath: String): FlowAccessApi.AccessApiCallResponse { + val loadedScript = loadScriptContent(scriptPath) + return flow.simpleFlowScript { + script { loadedScript } + } + } @Test fun `Can parse new JSON Cadence`() { - val flow = IntegrationTestUtils.newMainnetAccessApi() val tx = when (val transactionResult = flow.getTransactionResultById(FlowId("273f68ffe175a0097db60bc7cf5e92c5a775d189af3f5636f5432c1206be771a"))) { is FlowAccessApi.AccessApiCallResponse.Success -> transactionResult.data is FlowAccessApi.AccessApiCallResponse.Error -> throw IllegalStateException("Failed to get transaction result: ${transactionResult.message}", transactionResult.throwable) @@ -53,15 +71,7 @@ class JsonCadenceTest { @Test fun decodeOptional() { - val result = flow.simpleFlowScript { - script { - """ - pub fun main(): Bool? { - return nil - } - """.trimIndent() - } - } + val result = executeScript("cadence/json_cadence/decode_optional.cdc") val data = when (result) { is FlowAccessApi.AccessApiCallResponse.Success -> result.data.jsonCadence.decode() @@ -73,15 +83,7 @@ class JsonCadenceTest { @Test fun decodeOptional2() { - val result = flow.simpleFlowScript { - script { - """ - pub fun main(): Bool? { - return true - } - """.trimIndent() - } - } + val result = executeScript("cadence/json_cadence/decode_optional_2.cdc") val data = when (result) { is FlowAccessApi.AccessApiCallResponse.Success -> result.data.jsonCadence.decode() @@ -93,15 +95,8 @@ class JsonCadenceTest { @Test fun decodeBoolean() { - val result = flow.simpleFlowScript { - script { - """ - pub fun main(): Bool { - return true - } - """.trimIndent() - } - } + val result = executeScript("cadence/json_cadence/decode_boolean.cdc") + val data = when (result) { is FlowAccessApi.AccessApiCallResponse.Success -> result.data.jsonCadence.decode() is FlowAccessApi.AccessApiCallResponse.Error -> throw IllegalStateException("Failed to execute script: ${result.message}", result.throwable) @@ -112,15 +107,7 @@ class JsonCadenceTest { @Test fun decodeArray() { - val result = flow.simpleFlowScript { - script { - """ - pub fun main(): [UInt64] { - return [1,3,4,5] - } - """.trimIndent() - } - } + val result = executeScript("cadence/json_cadence/decode_array.cdc") val data = when (result) { is FlowAccessApi.AccessApiCallResponse.Success -> result.data.jsonCadence.decode>() @@ -133,15 +120,7 @@ class JsonCadenceTest { @Test fun decodeUFix64() { - val result = flow.simpleFlowScript { - script { - """ - pub fun main(): UFix64 { - return 0.789111 - } - """.trimIndent() - } - } + val result = executeScript("cadence/json_cadence/decode_ufix64.cdc") val data = when (result) { is FlowAccessApi.AccessApiCallResponse.Success -> result.data.jsonCadence.decode() @@ -153,30 +132,9 @@ class JsonCadenceTest { @Test fun decodeStruct() { + val loadedScript = loadScriptContent("cadence/json_cadence/decode_struct.cdc") val result = flow.simpleFlowScript { - script { - """ - pub struct StorageInfo { - pub let capacity: Int - pub let used: Int - pub let available: Int - - init(capacity: Int, used: Int, available: Int) { - self.capacity = capacity - self.used = used - self.available = available - } - } - - pub fun main(addr: Address): [StorageInfo] { - let acct = getAccount(addr) - return [StorageInfo(capacity: 1, - used: 2, - available: 3)] - } - """.trimIndent() - } - + script { loadedScript } arg { address("0x84221fe0294044d7") } } @@ -192,43 +150,9 @@ class JsonCadenceTest { @Test fun decodeComplexDict() { + val loadedScript = loadScriptContent("cadence/json_cadence/decode_complex_dict.cdc") val result = flow.simpleFlowScript { - script { - """ - pub struct StorageInfo { - pub let capacity: UInt64 - pub let used: UInt64 - pub let available: UInt64 - pub let foo: Foo - - init(capacity: UInt64, used: UInt64, available: UInt64, foo: Foo) { - self.capacity = capacity - self.used = used - self.available = available - self.foo = foo - } - } - - pub struct Foo { - pub let bar: Int - - init(bar: Int) { - self.bar = bar - } - } - - pub fun main(addr: Address): {String: [StorageInfo]} { - let acct = getAccount(addr) - - let foo = Foo(bar: 1) - return {"test": [StorageInfo(capacity: acct.storageCapacity, - used: acct.storageUsed, - available: acct.storageCapacity - acct.storageUsed, - foo: foo)]} - } - """.trimIndent() - } - + script { loadedScript } arg { address("0x84221fe0294044d7") } } @@ -242,24 +166,7 @@ class JsonCadenceTest { @Test fun decodeResource() { - val result = flow.simpleFlowScript { - script { - """ - pub resource SomeResource { - pub var value: Int - - init(value: Int) { - self.value = value - } - } - - pub fun main(): @SomeResource { - let newResource <- create SomeResource(value: 20) - return <-newResource - } - """.trimIndent() - } - } + val result = executeScript("cadence/json_cadence/decode_resource.cdc") val decodedResource = when (result) { is FlowAccessApi.AccessApiCallResponse.Success -> result.data.jsonCadence.decode() @@ -272,21 +179,7 @@ class JsonCadenceTest { @Test fun decodeEnum() { - val result = flow.simpleFlowScript { - script { - """ - pub enum Color: UInt8 { - pub case red - pub case green - pub case blue - } - - pub fun main() : Color { - return Color.red - } - """.trimIndent() - } - } + val result = executeScript("cadence/json_cadence/decode_enum.cdc") val decodedEnum = when (result) { is FlowAccessApi.AccessApiCallResponse.Success -> result.data.jsonCadence.decode() @@ -299,18 +192,7 @@ class JsonCadenceTest { @Test fun decodeReference() { - val result = flow.simpleFlowScript { - script { - """ - pub let hello = "Hello" - pub let helloRef: &String = &hello as &String - - pub fun main(): &String { - return helloRef - } - """.trimIndent() - } - } + val result = executeScript("cadence/json_cadence/decode_reference.cdc") val decodedReference = when (result) { is FlowAccessApi.AccessApiCallResponse.Success -> result.data.jsonCadence.decodeToAny() diff --git a/sdk/src/intTest/resources/cadence/json_cadence/decode_array.cdc b/sdk/src/intTest/resources/cadence/json_cadence/decode_array.cdc new file mode 100644 index 0000000..ae557cd --- /dev/null +++ b/sdk/src/intTest/resources/cadence/json_cadence/decode_array.cdc @@ -0,0 +1,3 @@ +pub fun main(): [UInt64] { + return [1,3,4,5] +} \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/json_cadence/decode_boolean.cdc b/sdk/src/intTest/resources/cadence/json_cadence/decode_boolean.cdc new file mode 100644 index 0000000..3a14b6a --- /dev/null +++ b/sdk/src/intTest/resources/cadence/json_cadence/decode_boolean.cdc @@ -0,0 +1,3 @@ +pub fun main(): Bool { + return true +} \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/json_cadence/decode_complex_dict.cdc b/sdk/src/intTest/resources/cadence/json_cadence/decode_complex_dict.cdc new file mode 100644 index 0000000..b7c67f4 --- /dev/null +++ b/sdk/src/intTest/resources/cadence/json_cadence/decode_complex_dict.cdc @@ -0,0 +1,31 @@ +pub struct StorageInfo { + pub let capacity: UInt64 + pub let used: UInt64 + pub let available: UInt64 + pub let foo: Foo + + init(capacity: UInt64, used: UInt64, available: UInt64, foo: Foo) { + self.capacity = capacity + self.used = used + self.available = available + self.foo = foo + } +} + +pub struct Foo { + pub let bar: Int + + init(bar: Int) { + self.bar = bar + } +} + +pub fun main(addr: Address): {String: [StorageInfo]} { + let acct = getAccount(addr) + + let foo = Foo(bar: 1) + return {"test": [StorageInfo(capacity: acct.storageCapacity, + used: acct.storageUsed, + available: acct.storageCapacity - acct.storageUsed, + foo: foo)]} +} \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/json_cadence/decode_enum.cdc b/sdk/src/intTest/resources/cadence/json_cadence/decode_enum.cdc new file mode 100644 index 0000000..8e95e64 --- /dev/null +++ b/sdk/src/intTest/resources/cadence/json_cadence/decode_enum.cdc @@ -0,0 +1,9 @@ +pub enum Color: UInt8 { + pub case red + pub case green + pub case blue +} + +pub fun main() : Color { + return Color.red +} \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/json_cadence/decode_optional.cdc b/sdk/src/intTest/resources/cadence/json_cadence/decode_optional.cdc new file mode 100644 index 0000000..e1af171 --- /dev/null +++ b/sdk/src/intTest/resources/cadence/json_cadence/decode_optional.cdc @@ -0,0 +1,3 @@ +pub fun main(): Bool? { + return nil +} \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/json_cadence/decode_optional_2.cdc b/sdk/src/intTest/resources/cadence/json_cadence/decode_optional_2.cdc new file mode 100644 index 0000000..89bfb54 --- /dev/null +++ b/sdk/src/intTest/resources/cadence/json_cadence/decode_optional_2.cdc @@ -0,0 +1,3 @@ +pub fun main(): Bool? { + return true +} \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/json_cadence/decode_reference.cdc b/sdk/src/intTest/resources/cadence/json_cadence/decode_reference.cdc new file mode 100644 index 0000000..dc3439f --- /dev/null +++ b/sdk/src/intTest/resources/cadence/json_cadence/decode_reference.cdc @@ -0,0 +1,6 @@ +pub let hello = "Hello" +pub let helloRef: &String = &hello as &String + +pub fun main(): &String { + return helloRef +} \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/json_cadence/decode_resource.cdc b/sdk/src/intTest/resources/cadence/json_cadence/decode_resource.cdc new file mode 100644 index 0000000..6aa2825 --- /dev/null +++ b/sdk/src/intTest/resources/cadence/json_cadence/decode_resource.cdc @@ -0,0 +1,12 @@ +pub resource SomeResource { + pub var value: Int + + init(value: Int) { + self.value = value + } +} + +pub fun main(): @SomeResource { + let newResource <- create SomeResource(value: 20) + return <-newResource +} \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/json_cadence/decode_struct.cdc b/sdk/src/intTest/resources/cadence/json_cadence/decode_struct.cdc new file mode 100644 index 0000000..24cfef4 --- /dev/null +++ b/sdk/src/intTest/resources/cadence/json_cadence/decode_struct.cdc @@ -0,0 +1,16 @@ +pub struct StorageInfo { + pub let capacity: Int + pub let used: Int + pub let available: Int + + init(capacity: Int, used: Int, available: Int) { + self.capacity = capacity + self.used = used + self.available = available + } +} + +pub fun main(addr: Address): [StorageInfo] { + let acct = getAccount(addr) + return [StorageInfo(capacity: 1, used: 2, available: 3)] +} \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/json_cadence/decode_ufix64.cdc b/sdk/src/intTest/resources/cadence/json_cadence/decode_ufix64.cdc new file mode 100644 index 0000000..2e3dabe --- /dev/null +++ b/sdk/src/intTest/resources/cadence/json_cadence/decode_ufix64.cdc @@ -0,0 +1,3 @@ +pub fun main(): UFix64 { + return 0.789111 +} \ No newline at end of file From f91293affad7372763f0eed3174686ab7d7d4587 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 18 Jul 2024 22:38:54 +1000 Subject: [PATCH 45/72] Update JSON Cadence tests to load from external snippets --- .../sdk/extensions/ProjectTestExtensionsTest.kt | 4 ++-- .../flow/sdk/extensions/TestExtensionsTest.kt | 15 ++++++--------- .../cadence/test_extensions/ContractInterface.cdc | 1 + .../cadence/test_extensions/ContractSuccessor.cdc | 2 ++ .../cadence/test_extensions/EmptyContract.cdc | 1 + .../{ => test_extensions}/NothingContract.cdc | 0 .../test/kotlin/org/onflow/flow/sdk/ScriptTest.kt | 6 +++--- .../test/resources/{ => cadence}/domain_tags.cdc | 0 .../test/resources/{ => cadence}/hello_world.cdc | 0 .../{ => cadence}/import_export_arguments.cdc | 0 .../org/onflow/flow/sdk/test/FlowTestUtil.kt | 2 +- .../{ => cadence}/test_utils_create_account.cdc | 0 12 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 sdk/src/intTest/resources/cadence/test_extensions/ContractInterface.cdc create mode 100644 sdk/src/intTest/resources/cadence/test_extensions/ContractSuccessor.cdc create mode 100644 sdk/src/intTest/resources/cadence/test_extensions/EmptyContract.cdc rename sdk/src/intTest/resources/cadence/{ => test_extensions}/NothingContract.cdc (100%) rename sdk/src/test/resources/{ => cadence}/domain_tags.cdc (100%) rename sdk/src/test/resources/{ => cadence}/hello_world.cdc (100%) rename sdk/src/test/resources/{ => cadence}/import_export_arguments.cdc (100%) rename sdk/src/testFixtures/resources/{ => cadence}/test_utils_create_account.cdc (100%) diff --git a/sdk/src/intTest/org/onflow/flow/sdk/extensions/ProjectTestExtensionsTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/extensions/ProjectTestExtensionsTest.kt index 88f1774..02ec525 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/extensions/ProjectTestExtensionsTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/extensions/ProjectTestExtensionsTest.kt @@ -44,7 +44,7 @@ class ProjectTestExtensionsTest { contracts = [ FlowTestContractDeployment( name = "NothingContract", - codeClasspathLocation = "/cadence/NothingContract.cdc", + codeClasspathLocation = "/cadence/test_extensions/NothingContract.cdc", arguments = [ TestContractArg("name", "The Name"), TestContractArg("description", "The Description"), @@ -74,7 +74,7 @@ class ProjectTestExtensionsTest { ), FlowTestContractDeployment( name = "NothingContract", - codeClasspathLocation = "/cadence/NothingContract.cdc", + codeClasspathLocation = "/cadence/test_extensions/NothingContract.cdc", arguments = [ TestContractArg("name", "The Name"), TestContractArg("description", "The Description"), diff --git a/sdk/src/intTest/org/onflow/flow/sdk/extensions/TestExtensionsTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/extensions/TestExtensionsTest.kt index 8fcd245..2c70ac5 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/extensions/TestExtensionsTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/extensions/TestExtensionsTest.kt @@ -35,7 +35,7 @@ class TestExtensionsTest { contracts = [ FlowTestContractDeployment( name = "NothingContract", - codeClasspathLocation = "/cadence/NothingContract.cdc", + codeClasspathLocation = "/cadence/test_extensions/NothingContract.cdc", arguments = [ TestContractArg("name", "The Name"), TestContractArg("description", "The Description"), @@ -51,7 +51,7 @@ class TestExtensionsTest { contracts = [ FlowTestContractDeployment( name = "EmptyContract", - code = "pub contract EmptyContract { init() { } }" + codeClasspathLocation = "/cadence/test_extensions/EmptyContract.cdc", ) ] ) @@ -65,7 +65,7 @@ class TestExtensionsTest { ), FlowTestContractDeployment( name = "NothingContract", - codeClasspathLocation = "/cadence/NothingContract.cdc", + codeClasspathLocation = "/cadence/test_extensions/NothingContract.cdc", arguments = [ TestContractArg("name", "The Name"), TestContractArg("description", "The Description"), @@ -73,7 +73,7 @@ class TestExtensionsTest { ), FlowTestContractDeployment( name = "EmptyContract", - code = "pub contract EmptyContract { init() { } }" + codeClasspathLocation = "/cadence/test_extensions/EmptyContract.cdc", ) ] ) @@ -88,14 +88,11 @@ class TestExtensionsTest { contracts = [ FlowTestContractDeployment( name = "ContractInterface", - code = "pub contract interface ContractInterface { }" + codeClasspathLocation = "/cadence/test_extensions/ContractInterface.cdc", ), FlowTestContractDeployment( name = "ContractSuccessor", - code = """ - import ContractInterface from 0xCONTRACTINTERFACE - pub contract ContractSuccessor : ContractInterface { init() { } } - """ + codeClasspathLocation = "/cadence/test_extensions/ContractSuccessor.cdc", ), ] ) diff --git a/sdk/src/intTest/resources/cadence/test_extensions/ContractInterface.cdc b/sdk/src/intTest/resources/cadence/test_extensions/ContractInterface.cdc new file mode 100644 index 0000000..a0de7f7 --- /dev/null +++ b/sdk/src/intTest/resources/cadence/test_extensions/ContractInterface.cdc @@ -0,0 +1 @@ +pub contract interface ContractInterface { } \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/test_extensions/ContractSuccessor.cdc b/sdk/src/intTest/resources/cadence/test_extensions/ContractSuccessor.cdc new file mode 100644 index 0000000..059fc9f --- /dev/null +++ b/sdk/src/intTest/resources/cadence/test_extensions/ContractSuccessor.cdc @@ -0,0 +1,2 @@ +import ContractInterface from 0xCONTRACTINTERFACE +pub contract ContractSuccessor : ContractInterface { init() { } } \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/test_extensions/EmptyContract.cdc b/sdk/src/intTest/resources/cadence/test_extensions/EmptyContract.cdc new file mode 100644 index 0000000..4db1fce --- /dev/null +++ b/sdk/src/intTest/resources/cadence/test_extensions/EmptyContract.cdc @@ -0,0 +1 @@ +pub contract EmptyContract { init() { } } \ No newline at end of file diff --git a/sdk/src/intTest/resources/cadence/NothingContract.cdc b/sdk/src/intTest/resources/cadence/test_extensions/NothingContract.cdc similarity index 100% rename from sdk/src/intTest/resources/cadence/NothingContract.cdc rename to sdk/src/intTest/resources/cadence/test_extensions/NothingContract.cdc diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt index aa06d06..399dd91 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/ScriptTest.kt @@ -52,7 +52,7 @@ class ScriptTest { @Test fun `Can execute a script`() { - val loadedScript = String(loadScript("hello_world.cdc"), StandardCharsets.UTF_8) + val loadedScript = String(loadScript("cadence/hello_world.cdc"), StandardCharsets.UTF_8) val result = accessAPI.simpleFlowScript { script { loadedScript @@ -75,7 +75,7 @@ class ScriptTest { @Test fun `Can input and export arguments`() { val address = "e467b9dd11fa00df" - val loadedScript = String(loadScript("import_export_arguments.cdc"), StandardCharsets.UTF_8) + val loadedScript = String(loadScript("cadence/import_export_arguments.cdc"), StandardCharsets.UTF_8) val result = accessAPI.simpleFlowScript { script { @@ -136,7 +136,7 @@ class ScriptTest { ) } } - val loadedScript = String(loadScript("domain_tags.cdc"), StandardCharsets.UTF_8) + val loadedScript = String(loadScript("cadence/domain_tags.cdc"), StandardCharsets.UTF_8) val result = accessAPI.simpleFlowScript { script { diff --git a/sdk/src/test/resources/domain_tags.cdc b/sdk/src/test/resources/cadence/domain_tags.cdc similarity index 100% rename from sdk/src/test/resources/domain_tags.cdc rename to sdk/src/test/resources/cadence/domain_tags.cdc diff --git a/sdk/src/test/resources/hello_world.cdc b/sdk/src/test/resources/cadence/hello_world.cdc similarity index 100% rename from sdk/src/test/resources/hello_world.cdc rename to sdk/src/test/resources/cadence/hello_world.cdc diff --git a/sdk/src/test/resources/import_export_arguments.cdc b/sdk/src/test/resources/cadence/import_export_arguments.cdc similarity index 100% rename from sdk/src/test/resources/import_export_arguments.cdc rename to sdk/src/test/resources/cadence/import_export_arguments.cdc diff --git a/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt b/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt index c607e13..ecd6e07 100644 --- a/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt +++ b/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt @@ -79,7 +79,7 @@ object FlowTestUtil { hashAlgo: HashAlgorithm, balance: BigDecimal = BigDecimal(0.01) ): FlowAccessApi.AccessApiCallResponse { - val loadedScript = String(loadScript("test_utils_create_account.cdc"), StandardCharsets.UTF_8) + val loadedScript = String(loadScript("cadence/test_utils_create_account.cdc"), StandardCharsets.UTF_8) val transactionResult = api.simpleFlowTransaction( address = serviceAccount.flowAddress, signer = serviceAccount.signer, diff --git a/sdk/src/testFixtures/resources/test_utils_create_account.cdc b/sdk/src/testFixtures/resources/cadence/test_utils_create_account.cdc similarity index 100% rename from sdk/src/testFixtures/resources/test_utils_create_account.cdc rename to sdk/src/testFixtures/resources/cadence/test_utils_create_account.cdc From e48867ac6c4f185b10e4330ee3068e2161d2ca05 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 18 Jul 2024 22:42:05 +1000 Subject: [PATCH 46/72] Linting --- sdk/src/intTest/org/onflow/flow/sdk/cadence/JsonCadenceTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/src/intTest/org/onflow/flow/sdk/cadence/JsonCadenceTest.kt b/sdk/src/intTest/org/onflow/flow/sdk/cadence/JsonCadenceTest.kt index 69e1b4e..be041ee 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/cadence/JsonCadenceTest.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/cadence/JsonCadenceTest.kt @@ -8,7 +8,6 @@ import org.onflow.flow.sdk.* import java.nio.charset.StandardCharsets class JsonCadenceTest { - @Serializable data class StorageInfo( val capacity: Int, From 0ede39329c7e80f213a544d82cc430e43328fa70 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Thu, 18 Jul 2024 23:05:43 +1000 Subject: [PATCH 47/72] Debugging tests --- .../resources/cadence/test_utils_create_account.cdc | 0 .../kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt | 6 +++++- 2 files changed, 5 insertions(+), 1 deletion(-) rename sdk/src/{testFixtures => intTest}/resources/cadence/test_utils_create_account.cdc (100%) diff --git a/sdk/src/testFixtures/resources/cadence/test_utils_create_account.cdc b/sdk/src/intTest/resources/cadence/test_utils_create_account.cdc similarity index 100% rename from sdk/src/testFixtures/resources/cadence/test_utils_create_account.cdc rename to sdk/src/intTest/resources/cadence/test_utils_create_account.cdc diff --git a/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt b/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt index ecd6e07..8a19359 100644 --- a/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt +++ b/sdk/src/testFixtures/kotlin/org/onflow/flow/sdk/test/FlowTestUtil.kt @@ -11,7 +11,11 @@ import java.nio.charset.StandardCharsets import kotlin.io.path.createTempDirectory object FlowTestUtil { - private fun loadScript(name: String): ByteArray = javaClass.classLoader.getResourceAsStream(name)!!.use { it.readAllBytes() } + private fun loadScript(name: String): ByteArray { + val resource = javaClass.classLoader.getResourceAsStream(name) + ?: throw IllegalArgumentException("Script file $name not found") + return resource.use { it.readAllBytes() } + } @JvmStatic @JvmOverloads From f4238a2e194f06bee65c690ff6adedeb6588e033 Mon Sep 17 00:00:00 2001 From: Jerome P Date: Thu, 18 Jul 2024 18:58:25 -0700 Subject: [PATCH 48/72] Minor typo --- sdk/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/README.md b/sdk/README.md index dfff062..19bd9df 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -78,7 +78,7 @@ of how to use this SDK in your Kotlin or Java application. Tests annotated with `FlowEmulatorTest` depend on the [Flow Emulator](https://github.com/onflow/flow-emulator), which is part of the [Flow CLI](https://github.com/onflow/flow-cli) to be installed on your machine. The`FlowEmulatorTest` extension may be used by consumers of this library as well to streamline unit tests that interact -with the FLOW blockchian. The `FlowEmulatorTest` extension uses the local flow emulator to prepare the test environment +with the FLOW blockchain. The `FlowEmulatorTest` extension uses the local flow emulator to prepare the test environment for unit and integration tests. For example: Setup dependency on the SDK: From 0f58b070da6064a4f3017c00dc1575451b0714bc Mon Sep 17 00:00:00 2001 From: Lea Lobanov <44328396+lealobanov@users.noreply.github.com> Date: Fri, 19 Jul 2024 22:46:36 +1000 Subject: [PATCH 49/72] Update NOTICE Co-authored-by: j pimmel --- NOTICE | 1 - 1 file changed, 1 deletion(-) diff --git a/NOTICE b/NOTICE index 6b31661..bc1c80b 100644 --- a/NOTICE +++ b/NOTICE @@ -1,4 +1,3 @@ Flow JVM SDK -Copyright 2019-2024 Dapper Labs, Inc. This product includes software developed at Dapper Labs, Inc. (https://www.dapperlabs.com/). From 5ae4a45d4f30db33b735332cec7ac762a579d206 Mon Sep 17 00:00:00 2001 From: Lea Lobanov <44328396+lealobanov@users.noreply.github.com> Date: Fri, 19 Jul 2024 22:47:14 +1000 Subject: [PATCH 50/72] Update NOTICE Co-authored-by: j pimmel --- NOTICE | 1 - 1 file changed, 1 deletion(-) diff --git a/NOTICE b/NOTICE index bc1c80b..1dd280a 100644 --- a/NOTICE +++ b/NOTICE @@ -1,3 +1,2 @@ Flow JVM SDK -This product includes software developed at Dapper Labs, Inc. (https://www.dapperlabs.com/). From 688473e1690593a56ef9096216567992faf60997 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 19 Jul 2024 22:53:59 +1000 Subject: [PATCH 51/72] Delete NOTICE file --- NOTICE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NOTICE b/NOTICE index 1dd280a..a5cc7b2 100644 --- a/NOTICE +++ b/NOTICE @@ -1,2 +1,2 @@ -Flow JVM SDK +Flow JVM SDK From 202207ec079d11d288905b16f7d24dc83ef4cfc4 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 19 Jul 2024 22:54:57 +1000 Subject: [PATCH 52/72] Delete NOTICE file --- NOTICE | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 NOTICE diff --git a/NOTICE b/NOTICE deleted file mode 100644 index a5cc7b2..0000000 --- a/NOTICE +++ /dev/null @@ -1,2 +0,0 @@ - -Flow JVM SDK From ba14488875db29b37e53ee45c2205fe34cd4eb78 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 19 Jul 2024 22:57:17 +1000 Subject: [PATCH 53/72] Refactor class names --- .../java/{App.java => AccessAPIConnector.java} | 4 ++-- ...ppTest.java => AccessAPIConnectorTest.java} | 18 +++++++++--------- .../kotlin/{App.kt => AccessAPIConnector.kt} | 2 +- .../{AppTest.kt => AccessAPIConnectorTest.kt} | 18 +++++++++--------- 4 files changed, 21 insertions(+), 21 deletions(-) rename java-example/src/main/java/org/onflow/examples/java/{App.java => AccessAPIConnector.java} (98%) rename java-example/src/test/java/org/onflow/examples/java/{AppTest.java => AccessAPIConnectorTest.java} (61%) rename kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/{App.kt => AccessAPIConnector.kt} (98%) rename kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/{AppTest.kt => AccessAPIConnectorTest.kt} (64%) diff --git a/java-example/src/main/java/org/onflow/examples/java/App.java b/java-example/src/main/java/org/onflow/examples/java/AccessAPIConnector.java similarity index 98% rename from java-example/src/main/java/org/onflow/examples/java/App.java rename to java-example/src/main/java/org/onflow/examples/java/AccessAPIConnector.java index d304b28..047b93c 100644 --- a/java-example/src/main/java/org/onflow/examples/java/App.java +++ b/java-example/src/main/java/org/onflow/examples/java/AccessAPIConnector.java @@ -16,11 +16,11 @@ import org.onflow.flow.sdk.crypto.Crypto; import org.onflow.flow.sdk.crypto.PrivateKey; -public final class App { +public final class AccessAPIConnector { private final FlowAccessApi accessAPI; private final PrivateKey privateKey; - public App(String host, int port, String privateKeyHex) { + public AccessAPIConnector(String host, int port, String privateKeyHex) { this.accessAPI = Flow.newAccessApi(host, port); this.privateKey = Crypto.decodePrivateKey(privateKeyHex); } diff --git a/java-example/src/test/java/org/onflow/examples/java/AppTest.java b/java-example/src/test/java/org/onflow/examples/java/AccessAPIConnectorTest.java similarity index 61% rename from java-example/src/test/java/org/onflow/examples/java/AppTest.java rename to java-example/src/test/java/org/onflow/examples/java/AccessAPIConnectorTest.java index 19a39c3..710621e 100644 --- a/java-example/src/test/java/org/onflow/examples/java/AppTest.java +++ b/java-example/src/test/java/org/onflow/examples/java/AccessAPIConnectorTest.java @@ -10,7 +10,7 @@ import static org.junit.jupiter.api.Assertions.*; -class AppTest { +class AccessAPIConnectorTest { public static final String SERVICE_PRIVATE_KEY_HEX = "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17"; private final FlowAddress serviceAccountAddress = new FlowAddress("f8d6e0586b0a20c7"); @@ -26,27 +26,27 @@ void setupUser() { @Test void createAccount() { - App app = new App("localhost", 3569, SERVICE_PRIVATE_KEY_HEX); + AccessAPIConnector accessAPIConnector = new AccessAPIConnector("localhost", 3569, SERVICE_PRIVATE_KEY_HEX); - FlowAddress account = app.createAccount(serviceAccountAddress, this.userPublicKeyHex); + FlowAddress account = accessAPIConnector.createAccount(serviceAccountAddress, this.userPublicKeyHex); assertNotNull(account); } @Test void transferTokens() throws Exception { - App app = new App("localhost", 3569, SERVICE_PRIVATE_KEY_HEX); + AccessAPIConnector accessAPIConnector = new AccessAPIConnector("localhost", 3569, SERVICE_PRIVATE_KEY_HEX); var recipient = testRecipientAddress; // FLOW amounts always have 8 decimal places var amount = new BigDecimal("10.00000001"); - var balance1 = app.getAccountBalance(recipient); + var balance1 = accessAPIConnector.getAccountBalance(recipient); - app.transferTokens(serviceAccountAddress, recipient, amount); + accessAPIConnector.transferTokens(serviceAccountAddress, recipient, amount); - var balance2 = app.getAccountBalance(recipient); + var balance2 = accessAPIConnector.getAccountBalance(recipient); assertEquals(balance1.add(amount), balance2); } @@ -54,8 +54,8 @@ void transferTokens() throws Exception { @Test void getAccountBalance() { - App app = new App("localhost", 3569, SERVICE_PRIVATE_KEY_HEX); - var balance = app.getAccountBalance(serviceAccountAddress); + AccessAPIConnector accessAPIConnector = new AccessAPIConnector("localhost", 3569, SERVICE_PRIVATE_KEY_HEX); + var balance = accessAPIConnector.getAccountBalance(serviceAccountAddress); assertNotNull(balance); } } diff --git a/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/AccessAPIConnector.kt similarity index 98% rename from kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt rename to kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/AccessAPIConnector.kt index a0e2bcb..ad46948 100644 --- a/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/App.kt +++ b/kotlin-example/src/main/kotlin/org/onflow/examples/kotlin/AccessAPIConnector.kt @@ -7,7 +7,7 @@ import org.onflow.flow.sdk.cadence.UFix64NumberField import org.onflow.flow.sdk.crypto.Crypto import java.math.BigDecimal -internal class App(host: String, port: Int, privateKeyHex: String) { +internal class AccessAPIConnector(host: String, port: Int, privateKeyHex: String) { private val accessAPI = Flow.newAccessApi(host, port) private val privateKey = Crypto.decodePrivateKey(privateKeyHex) diff --git a/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AccessAPIConnectorTest.kt similarity index 64% rename from kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt rename to kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AccessAPIConnectorTest.kt index 006f221..2c3b5d4 100644 --- a/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AppTest.kt +++ b/kotlin-example/src/test/kotlin/org/onflow/examples/kotlin/AccessAPIConnectorTest.kt @@ -11,7 +11,7 @@ val serviceAccountAddress: FlowAddress = FlowAddress("f8d6e0586b0a20c7") val testRecipientAddress: FlowAddress = FlowAddress("01cf0e2f2f715450") const val servicePrivateKeyHex = "a2f983853e61b3e27d94b7bf3d7094dd756aead2a813dd5cf738e1da56fa9c17" -internal class AppTest { +internal class AccessAPIConnectorTest { private var userPrivateKeyHex: String = "" private var userPublicKeyHex: String = "" @@ -24,31 +24,31 @@ internal class AppTest { @Test fun `Can create an account`() { - val app = App("localhost", 3569, servicePrivateKeyHex) + val accessAPIConnector = AccessAPIConnector("localhost", 3569, servicePrivateKeyHex) - val account = app.createAccount(serviceAccountAddress, userPublicKeyHex) + val account = accessAPIConnector.createAccount(serviceAccountAddress, userPublicKeyHex) Assertions.assertNotNull(account) } @Test fun `Can transfer tokens`() { - val app = App("localhost", 3569, servicePrivateKeyHex) + val accessAPIConnector = AccessAPIConnector("localhost", 3569, servicePrivateKeyHex) // service account address val recipient: FlowAddress = testRecipientAddress // FLOW amounts always have 8 decimal places val amount = BigDecimal("10.00000001") - val balance1 = app.getAccountBalance(recipient) - app.transferTokens(serviceAccountAddress, recipient, amount) - val balance2 = app.getAccountBalance(recipient) + val balance1 = accessAPIConnector.getAccountBalance(recipient) + accessAPIConnector.transferTokens(serviceAccountAddress, recipient, amount) + val balance2 = accessAPIConnector.getAccountBalance(recipient) Assertions.assertEquals(balance1.add(amount), balance2) } @Test fun `Can get an account balance`() { - val app = App("localhost", 3569, servicePrivateKeyHex) - val balance = app.getAccountBalance(serviceAccountAddress) + val accessAPIConnector = AccessAPIConnector("localhost", 3569, servicePrivateKeyHex) + val balance = accessAPIConnector.getAccountBalance(serviceAccountAddress) Assertions.assertNotNull(balance) } } From 880952ce463e49cdc3d497e9e3adf2b41d014710 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 19 Jul 2024 23:05:44 +1000 Subject: [PATCH 54/72] Refactor class names --- flowdb/000000.vlog | Bin 479980 -> 0 bytes flowdb/000006.sst | Bin 99729 -> 0 bytes flowdb/KEYREGISTRY | 1 - flowdb/MANIFEST | Bin 124 -> 0 bytes 4 files changed, 1 deletion(-) delete mode 100644 flowdb/000000.vlog delete mode 100644 flowdb/000006.sst delete mode 100644 flowdb/KEYREGISTRY delete mode 100644 flowdb/MANIFEST diff --git a/flowdb/000000.vlog b/flowdb/000000.vlog deleted file mode 100644 index 41505512e5894cc54df6371254a852204a205692..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 479980 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%0A>^_Xz4v1ee?0?NI{1<7^5S{j6vSm0G&g9 zn?TE28PKPZ4Wp2>SwBNd53-Stp*vw)v2@Y z4IMFeVM7COWA$#26t41=4^qd!&K^moD4C>G4wD!{5^zSLaFwPc7Uxuvm2nA7rJ#%_ zs5IE3h;WiuftJH7I50=0TDw~{%L0qzbEi3F7;1zaIjl43EH#3fp! zC0@cPt}BEh1HtPp#o1TQFdqZ>QSDE9p$r)H5WPyX?i`0|i0%eoxc z^U|D_+s<9uceG{w76rH0jcYJeePqhU!h82;Re7YxOZ7im{804X>2oT*lRxyy6Wn)` zNmms6k3V+5ZhUrq8MQsDs!xh4jI;VuoDb5MBJ^z$(wD-q|A{1zD_UPIl00_Td5R>D zJ=DM=$>XFnk3lg=`5EM(&{Y`&NxDe#*vow2k>s&wkfL+2k>s(LI5N0#k;M@_l05bs zc-%jSpX`^Oke+C8>Ip%9;nkuyFNLBu7W#%ZC>KU8(f6+a>tMD&E;eo9(6bK4g)#8r z#WyuSs~L)AZ0G@mvW#`a$<2x->Q}kp=#Ek5kjf$$OEUx`0nC{+pt<0#hvlfK*w6b8 z{Ry&{I_%yjVL@M2G?0FS-Lb!vJ4R z-9LfyE)K;BB+v5#1M*KC$uhVk&@{*KGB5H#{89*>5jl>;M2Qh3cpr49fL{WiQ$01t5}qbOf7Yy_}fIvCf++6a^K%@fMQ^g6+OtOcNS`;> z{tAV|0G=@J(tO@1cE7(jgYfIBKGhw)UG=}%&LSB2ky?>62%LilkIj&C?Hu?D63c89 z6+Lmj#?Ea>@5$Qpc*m&B+d}9xgA)oH`2MA?BStD|vGUuv<0F00!A}tQu9tRx`~Nh7 Bm8}2( diff --git a/flowdb/000006.sst b/flowdb/000006.sst deleted file mode 100644 index c39a1b219f6961a596fbc048bb2d70daf303cf5a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99729 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 diff --git a/flowdb/KEYREGISTRY b/flowdb/KEYREGISTRY deleted file mode 100644 index afd3786..0000000 --- a/flowdb/KEYREGISTRY +++ /dev/null @@ -1 +0,0 @@ - ìöV"}C*«ô§Hello Badger \ No newline at end of file diff --git a/flowdb/MANIFEST b/flowdb/MANIFEST deleted file mode 100644 index 40204a43ad7a3d085e9d1ed1b85050a1906d76a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124 zcmZ=tNiSkxU|{@C44!73E^#V3J@2l8gc%F_r`QnGb>T%s{q~ sww;|FP=*C8&ny5^#RO6H>|7rwP!%gk726rDN}voISQRT+6^j5P041mlrvLx| From d37c3e2a03c32a09cc08e9bc4256bd9b9e2b8b4a Mon Sep 17 00:00:00 2001 From: Lea Lobanov <44328396+lealobanov@users.noreply.github.com> Date: Fri, 19 Jul 2024 23:07:15 +1000 Subject: [PATCH 55/72] Update sdk/src/test/kotlin/org/onflow/flow/sdk/TestUtils.kt Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- sdk/src/test/kotlin/org/onflow/flow/sdk/TestUtils.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/TestUtils.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/TestUtils.kt index 0fcd723..9872c40 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/TestUtils.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/TestUtils.kt @@ -8,5 +8,5 @@ object TestUtils { private const val MAINNET_HOSTNAME = "access.mainnet.nodes.onflow.org" private const val TESTNET_HOSTNAME = "access.devnet.nodes.onflow.org" - fun loadScript(name: String): ByteArray = javaClass.classLoader.getResourceAsStream(name)!!.use { it.readAllBytes() } + fun loadScript(name: String): ByteArray = javaClass.classLoader.getResourceAsStream(name)?.use { it.readAllBytes() } ?: throw IllegalArgumentException("Script not found: $name") } From 920b5069733f48884d4cc2dae99791b465e42782 Mon Sep 17 00:00:00 2001 From: Lea Lobanov <44328396+lealobanov@users.noreply.github.com> Date: Fri, 19 Jul 2024 23:09:09 +1000 Subject: [PATCH 56/72] Update sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt b/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt index 79bb599..c30c0fc 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt @@ -3,7 +3,9 @@ package org.onflow.flow.sdk import org.onflow.flow.sdk.cadence.AddressField object IntegrationTestUtils { - fun loadScript(name: String): ByteArray = javaClass.classLoader.getResourceAsStream(name)!!.use { it.readAllBytes() } + fun loadScript(name: String): ByteArray = + javaClass.classLoader.getResourceAsStream(name)?.use { it.readAllBytes() } + ?: throw IllegalArgumentException("Script not found: $name") fun newMainnetAccessApi(): FlowAccessApi = Flow.newAccessApi(MAINNET_HOSTNAME) fun newTestnetAccessApi(): FlowAccessApi = Flow.newAccessApi(TESTNET_HOSTNAME) From 88089d8aa4c030d4202c21c6c7b83e52c239e677 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 19 Jul 2024 23:38:49 +1000 Subject: [PATCH 57/72] Debugging examples tests --- .github/workflows/ci-java-examples-pull-request.yml | 8 +++++++- java-example/build.gradle.kts | 7 +------ kotlin-example/build.gradle.kts | 7 +------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci-java-examples-pull-request.yml b/.github/workflows/ci-java-examples-pull-request.yml index f421963..13b70c7 100644 --- a/.github/workflows/ci-java-examples-pull-request.yml +++ b/.github/workflows/ci-java-examples-pull-request.yml @@ -36,7 +36,7 @@ jobs: - name: Build id: build - run: ./gradlew --warning-mode all check :java-example:build -x test -x integrationTest + run: ./gradlew --warning-mode all check build -x test -x integrationTest - name: Start emulator id: start_emulator @@ -54,6 +54,12 @@ jobs: if: ${{ steps.wait_for_emulator.outcome == 'success' }} run: ./gradlew --no-daemon --max-workers=2 --warning-mode=all :java-example:test -i --continue --stacktrace + - name: Run Kotlin Example Unit Tests + id: unit_test_2 + if: ${{ steps.wait_for_emulator.outcome == 'success' }} + run: ./gradlew --no-daemon --max-workers=2 --warning-mode=all :kotlin-example:test -i --continue --stacktrace + + - name: Publish Java Example Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v1.18 if: always() diff --git a/java-example/build.gradle.kts b/java-example/build.gradle.kts index 0955618..d4e795a 100644 --- a/java-example/build.gradle.kts +++ b/java-example/build.gradle.kts @@ -13,7 +13,6 @@ fun getProp(name: String, defaultValue: String? = null): String? { } val FLOW_JVM_SDK_VERSION = "1.0.1" -val USE_KOTLIN_APP = project.findProperty("USE_KOTLIN_APP") == "true" tasks.withType { sourceCompatibility = JavaVersion.VERSION_21.toString() @@ -44,11 +43,7 @@ dependencies { application { // Define the main class for the application. - mainClass.set(if (USE_KOTLIN_APP) { - "org.onflow.examples.kotlin.App" - } else { - "org.onflow.examples.java.App" - }) + mainClass.set("org.onflow.examples.java.AccessAPIConnector") } tasks.test { diff --git a/kotlin-example/build.gradle.kts b/kotlin-example/build.gradle.kts index 0955618..fdf91ad 100644 --- a/kotlin-example/build.gradle.kts +++ b/kotlin-example/build.gradle.kts @@ -13,7 +13,6 @@ fun getProp(name: String, defaultValue: String? = null): String? { } val FLOW_JVM_SDK_VERSION = "1.0.1" -val USE_KOTLIN_APP = project.findProperty("USE_KOTLIN_APP") == "true" tasks.withType { sourceCompatibility = JavaVersion.VERSION_21.toString() @@ -44,11 +43,7 @@ dependencies { application { // Define the main class for the application. - mainClass.set(if (USE_KOTLIN_APP) { - "org.onflow.examples.kotlin.App" - } else { - "org.onflow.examples.java.App" - }) + mainClass.set("org.onflow.examples.kotlin.AccessAPIConnector") } tasks.test { From c99ff4710dc23e415172ad99b5012b5f961755d6 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 19 Jul 2024 23:42:39 +1000 Subject: [PATCH 58/72] Debugging examples tests --- sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt b/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt index c30c0fc..9c0923f 100644 --- a/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt +++ b/sdk/src/intTest/org/onflow/flow/sdk/IntegrationTestUtils.kt @@ -3,9 +3,9 @@ package org.onflow.flow.sdk import org.onflow.flow.sdk.cadence.AddressField object IntegrationTestUtils { - fun loadScript(name: String): ByteArray = - javaClass.classLoader.getResourceAsStream(name)?.use { it.readAllBytes() } - ?: throw IllegalArgumentException("Script not found: $name") + fun loadScript(name: String): ByteArray = + javaClass.classLoader.getResourceAsStream(name)?.use { it.readAllBytes() } + ?: throw IllegalArgumentException("Script not found: $name") fun newMainnetAccessApi(): FlowAccessApi = Flow.newAccessApi(MAINNET_HOSTNAME) fun newTestnetAccessApi(): FlowAccessApi = Flow.newAccessApi(TESTNET_HOSTNAME) From 7adc8d90f98f52cf6cae46bdd93ad020e09b07cf Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 19 Jul 2024 23:50:20 +1000 Subject: [PATCH 59/72] Debugging examples tests, update git ignore --- ...quest.yml => ci-examples-pull-request.yml} | 5 +- .../ci-kotlin-examples-pull-request.yml | 64 ------------------- .gitignore | 1 + 3 files changed, 3 insertions(+), 67 deletions(-) rename .github/workflows/{ci-java-examples-pull-request.yml => ci-examples-pull-request.yml} (95%) delete mode 100644 .github/workflows/ci-kotlin-examples-pull-request.yml diff --git a/.github/workflows/ci-java-examples-pull-request.yml b/.github/workflows/ci-examples-pull-request.yml similarity index 95% rename from .github/workflows/ci-java-examples-pull-request.yml rename to .github/workflows/ci-examples-pull-request.yml index 13b70c7..0d4a2fe 100644 --- a/.github/workflows/ci-java-examples-pull-request.yml +++ b/.github/workflows/ci-examples-pull-request.yml @@ -1,4 +1,4 @@ -name: Build Java Examples Pull Request +name: Build SDK Examples Pull Request on: pull_request jobs: @@ -59,8 +59,7 @@ jobs: if: ${{ steps.wait_for_emulator.outcome == 'success' }} run: ./gradlew --no-daemon --max-workers=2 --warning-mode=all :kotlin-example:test -i --continue --stacktrace - - - name: Publish Java Example Unit Test Results + - name: Publish SDK Examples Unit Test Results uses: EnricoMi/publish-unit-test-result-action@v1.18 if: always() with: diff --git a/.github/workflows/ci-kotlin-examples-pull-request.yml b/.github/workflows/ci-kotlin-examples-pull-request.yml deleted file mode 100644 index 3960354..0000000 --- a/.github/workflows/ci-kotlin-examples-pull-request.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Build Kotlin Examples Pull Request -on: pull_request - -jobs: - build: - name: Build pull request - runs-on: ubuntu-latest - env: - JAVA_OPTS: -Xmx2g -Dorg.gradle.daemon=false - #services: - # flow-emulator: - # image: gcr.io/flow-container-registry/emulator - # env: - # FLOW_VERBOSE: true - # FLOW_PORT: 3569 - # FLOW_INTERVAL: 5s - # FLOW_PERSIST: false - # ports: - # - 3569:3569 - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Setup Java - uses: actions/setup-java@v2 - with: - java-version: '21' - java-package: jdk - distribution: 'adopt' - - - name: Install flow emulator - run: sh -ci "$(curl -fsSL https://storage.googleapis.com/flow-cli/install.sh)" - - - name: Make gradle executable - run: chmod +x ./gradlew - - - name: Build - id: build - run: ./gradlew --warning-mode all check :kotlin-example:build -x test -x integrationTest - - - name: Start emulator - id: start_emulator - if: ${{ steps.build.outcome == 'success' }} - run: | - nohup flow emulator start -v --persist > flow-emulator.log 2>&1 & - - - name: Wait for emulator to start - id: wait_for_emulator - if: ${{ steps.start_emulator.outcome == 'success' }} - run: sleep 15 - - - name: Run Kotlin Example Unit Tests - id: unit_test - if: ${{ steps.wait_for_emulator.outcome == 'success' }} - run: ./gradlew --no-daemon --max-workers=2 --warning-mode=all :kotlin-example:test -i --continue --stacktrace - - - name: Publish Kotlin Example Unit Test Results - uses: EnricoMi/publish-unit-test-result-action@v1.18 - if: always() - with: - check_name: "Kotlin Example Unit Test Results" - files: "**/test-results/test/**/*.xml" - seconds_between_github_writes: 5 - secondary_rate_limit_wait_seconds: 120 diff --git a/.gitignore b/.gitignore index 5926953..2939781 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ build .gradle .idea sdk/gradle.properties +flowdb \ No newline at end of file From 0d669577ec6ce8f6f73287317760b3c293570154 Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Fri, 19 Jul 2024 23:58:34 +1000 Subject: [PATCH 60/72] Debugging examples tests, update git ignore --- .github/workflows/ci-examples-pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-examples-pull-request.yml b/.github/workflows/ci-examples-pull-request.yml index 0d4a2fe..602992f 100644 --- a/.github/workflows/ci-examples-pull-request.yml +++ b/.github/workflows/ci-examples-pull-request.yml @@ -63,7 +63,7 @@ jobs: uses: EnricoMi/publish-unit-test-result-action@v1.18 if: always() with: - check_name: "Java Example Unit Test Results" + check_name: "SDK Examples Unit Test Results" files: "**/test-results/test/**/*.xml" seconds_between_github_writes: 5 secondary_rate_limit_wait_seconds: 120 From f92cf39dec99937e62490a43f550f9b0b1ad812d Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Sat, 20 Jul 2024 20:32:39 +1000 Subject: [PATCH 61/72] Move inline JSON to standalone files --- ...JsonCadenceBuilderTypeSerializationTest.kt | 101 ++++-------------- sdk/src/test/resources/json/invalid.json | 1 + .../resources/json/invalid_capability.json | 3 + .../json/invalid_constant_sized_array.json | 3 + .../resources/json/invalid_dictionary.json | 4 + sdk/src/test/resources/json/invalid_enum.json | 6 ++ .../test/resources/json/invalid_function.json | 5 + .../test/resources/json/invalid_optional.json | 3 + .../resources/json/invalid_reference.json | 5 + .../resources/json/invalid_restriction.json | 5 + .../json/invalid_variable_sized_array.json | 3 + sdk/src/test/resources/json/missing_kind.json | 1 + sdk/src/test/resources/json/missing_type.json | 1 + .../json/missing_type_composite.json | 6 ++ .../json/missing_type_id_composite.json | 6 ++ sdk/src/test/resources/json/unknown_kind.json | 4 + .../test/resources/json/unknown_kind_2.json | 1 + 17 files changed, 76 insertions(+), 82 deletions(-) create mode 100644 sdk/src/test/resources/json/invalid.json create mode 100644 sdk/src/test/resources/json/invalid_capability.json create mode 100644 sdk/src/test/resources/json/invalid_constant_sized_array.json create mode 100644 sdk/src/test/resources/json/invalid_dictionary.json create mode 100644 sdk/src/test/resources/json/invalid_enum.json create mode 100644 sdk/src/test/resources/json/invalid_function.json create mode 100644 sdk/src/test/resources/json/invalid_optional.json create mode 100644 sdk/src/test/resources/json/invalid_reference.json create mode 100644 sdk/src/test/resources/json/invalid_restriction.json create mode 100644 sdk/src/test/resources/json/invalid_variable_sized_array.json create mode 100644 sdk/src/test/resources/json/missing_kind.json create mode 100644 sdk/src/test/resources/json/missing_type.json create mode 100644 sdk/src/test/resources/json/missing_type_composite.json create mode 100644 sdk/src/test/resources/json/missing_type_id_composite.json create mode 100644 sdk/src/test/resources/json/unknown_kind.json create mode 100644 sdk/src/test/resources/json/unknown_kind_2.json diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/JsonCadenceBuilderTypeSerializationTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/JsonCadenceBuilderTypeSerializationTest.kt index f539e63..e52edfe 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/JsonCadenceBuilderTypeSerializationTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/cadence/JsonCadenceBuilderTypeSerializationTest.kt @@ -6,6 +6,8 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertThrows import org.junit.jupiter.api.Test +import org.onflow.flow.sdk.TestUtils +import java.nio.charset.StandardCharsets class JsonCadenceBuilderTypeSerializationTest { private val objectMapper: ObjectMapper = jacksonObjectMapper() @@ -309,7 +311,7 @@ class JsonCadenceBuilderTypeSerializationTest { // Exception handling @Test fun `test decode with invalid JSON`() { - val invalidJson = "{invalid: json}" + val invalidJson = String(TestUtils.loadScript("json/invalid.json"), StandardCharsets.UTF_8) assertThrows(Exception::class.java) { objectMapper.readValue(invalidJson, CadenceType::class.java) } @@ -317,7 +319,7 @@ class JsonCadenceBuilderTypeSerializationTest { @Test fun `test decode with missing kind field in generic Cadence type`() { - val invalidJson = "{\"type\": \"String\"}" + val invalidJson = String(TestUtils.loadScript("json/missing_kind.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJson, CadenceType::class.java) } @@ -325,7 +327,7 @@ class JsonCadenceBuilderTypeSerializationTest { @Test fun `test decode with missing kind field in simple type`() { - val invalidJson = "{\"type\": \"String\"}" + val invalidJson = String(TestUtils.loadScript("json/missing_kind.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJson, SimpleType::class.java) } @@ -333,103 +335,57 @@ class JsonCadenceBuilderTypeSerializationTest { @Test fun `test decode with missing type field in complex type`() { - val invalidJsonOptional = """ - { - "kind": "Optional" - } - """.trimIndent() + val invalidJsonOptional = String(TestUtils.loadScript("json/invalid_optional.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJsonOptional, OptionalType::class.java) } - val invalidJsonVariableSizedArray = """ - { - "kind": "VariableSizedArray" - } - """.trimIndent() + val invalidJsonVariableSizedArray = String(TestUtils.loadScript("json/invalid_variable_sized_array.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJsonVariableSizedArray, VariableSizedArrayType::class.java) } - val invalidJsonConstantSizedArray = """ - { - "kind": "ConstantSizedArray" - } - """.trimIndent() + val invalidJsonConstantSizedArray = String(TestUtils.loadScript("json/invalid_constant_sized_array.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJsonConstantSizedArray, ConstantSizedArrayType::class.java) } // Missing value field - val invalidJsonDictionary = """ - { - "kind": "Dictionary", - "key": {"kind": "String"} - } - """.trimIndent() + val invalidJsonDictionary = String(TestUtils.loadScript("json/invalid_dictionary.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJsonDictionary, DictionaryType::class.java) } // Missing typeID field - val invalidJsonFunction = """ - { - "kind": "Function", - "parameters": [], - "`return`": {"kind": "String"} - } - """.trimIndent() + val invalidJsonFunction = String(TestUtils.loadScript("json/invalid_function.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJsonFunction, FunctionType::class.java) } - val invalidJsonReference = """ - { - "kind": "Reference", - "typeID": "id", - "authorized": true - } - """.trimIndent() + val invalidJsonReference = String(TestUtils.loadScript("json/invalid_reference.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJsonReference, ReferenceType::class.java) } - val invalidJsonRestriction = """ - { - "kind": "Restriction", - "typeID": "id", - "restrictions": [] - } - """.trimIndent() + val invalidJsonRestriction = String(TestUtils.loadScript("json/invalid_restriction.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJsonRestriction, RestrictionType::class.java) } - val invalidJsonCapability = """ - { - "kind": "Capability" - } - """.trimIndent() + val invalidJsonCapability = String(TestUtils.loadScript("json/invalid_capability.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJsonCapability, CapabilityType::class.java) } - val invalidJsonEnum = """ - { - "kind": "Enum", - "typeID": "id", - "initializers": [], - "fields": [] - } - """.trimIndent() + val invalidJsonEnum = String(TestUtils.loadScript("json/invalid_enum.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJsonEnum, EnumType::class.java) @@ -438,7 +394,7 @@ class JsonCadenceBuilderTypeSerializationTest { @Test fun `test decode with partial Cadence type and missing type field`() { - val invalidJson = "{\"kind\": \"PartialCadenceType\"}" + val invalidJson = String(TestUtils.loadScript("json/missing_type.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJson, PartialCadenceType::class.java) } @@ -446,14 +402,7 @@ class JsonCadenceBuilderTypeSerializationTest { @Test fun `test decode with missing type field in composite type`() { - val invalidJson = """ - { - "kind": "Struct", - "typeID": "id", - "initializers": [], - "fields":[] - } - """.trimIndent() + val invalidJson = String(TestUtils.loadScript("json/missing_type_composite.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJson, CompositeType::class.java) } @@ -461,14 +410,7 @@ class JsonCadenceBuilderTypeSerializationTest { @Test fun `test decode with missing typeID field in composite type`() { - val invalidJson = """ - { - "kind": "Struct", - "type": "Composite", - "initializers": [], - "fields":[] - } - """.trimIndent() + val invalidJson = String(TestUtils.loadScript("json/missing_type_id_composite.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJson, CompositeType::class.java) } @@ -476,12 +418,7 @@ class JsonCadenceBuilderTypeSerializationTest { @Test fun `test decode with unknown CadenceType kind`() { - val invalidJson = """ - { - "kind": "UnknownKind", - "type": {} - } - """.trimIndent() + val invalidJson = String(TestUtils.loadScript("json/unknown_kind.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJson, CadenceType::class.java) } @@ -489,7 +426,7 @@ class JsonCadenceBuilderTypeSerializationTest { @Test fun `test decode with unknown CadenceType kind 2`() { - val invalidJson = "{\"kind\": \"UnknownKind\", \"type\": \"\"}" + val invalidJson = String(TestUtils.loadScript("json/unknown_kind_2.json"), StandardCharsets.UTF_8) assertThrows(MismatchedInputException::class.java) { objectMapper.readValue(invalidJson, CadenceType::class.java) } diff --git a/sdk/src/test/resources/json/invalid.json b/sdk/src/test/resources/json/invalid.json new file mode 100644 index 0000000..f2e6346 --- /dev/null +++ b/sdk/src/test/resources/json/invalid.json @@ -0,0 +1 @@ +{invalid: json} \ No newline at end of file diff --git a/sdk/src/test/resources/json/invalid_capability.json b/sdk/src/test/resources/json/invalid_capability.json new file mode 100644 index 0000000..8d4a1aa --- /dev/null +++ b/sdk/src/test/resources/json/invalid_capability.json @@ -0,0 +1,3 @@ +{ + "kind": "Capability" +} \ No newline at end of file diff --git a/sdk/src/test/resources/json/invalid_constant_sized_array.json b/sdk/src/test/resources/json/invalid_constant_sized_array.json new file mode 100644 index 0000000..11c17d3 --- /dev/null +++ b/sdk/src/test/resources/json/invalid_constant_sized_array.json @@ -0,0 +1,3 @@ +{ + "kind": "ConstantSizedArray" +} \ No newline at end of file diff --git a/sdk/src/test/resources/json/invalid_dictionary.json b/sdk/src/test/resources/json/invalid_dictionary.json new file mode 100644 index 0000000..2b236a5 --- /dev/null +++ b/sdk/src/test/resources/json/invalid_dictionary.json @@ -0,0 +1,4 @@ +{ + "kind": "Dictionary", + "key": {"kind": "String"} +} \ No newline at end of file diff --git a/sdk/src/test/resources/json/invalid_enum.json b/sdk/src/test/resources/json/invalid_enum.json new file mode 100644 index 0000000..25ba14c --- /dev/null +++ b/sdk/src/test/resources/json/invalid_enum.json @@ -0,0 +1,6 @@ +{ + "kind": "Enum", + "typeID": "id", + "initializers": [], + "fields": [] +} \ No newline at end of file diff --git a/sdk/src/test/resources/json/invalid_function.json b/sdk/src/test/resources/json/invalid_function.json new file mode 100644 index 0000000..288debc --- /dev/null +++ b/sdk/src/test/resources/json/invalid_function.json @@ -0,0 +1,5 @@ +{ + "kind": "Function", + "parameters": [], + "`return`": {"kind": "String"} +} \ No newline at end of file diff --git a/sdk/src/test/resources/json/invalid_optional.json b/sdk/src/test/resources/json/invalid_optional.json new file mode 100644 index 0000000..afe532b --- /dev/null +++ b/sdk/src/test/resources/json/invalid_optional.json @@ -0,0 +1,3 @@ +{ + "kind": "Optional" +} \ No newline at end of file diff --git a/sdk/src/test/resources/json/invalid_reference.json b/sdk/src/test/resources/json/invalid_reference.json new file mode 100644 index 0000000..e371d88 --- /dev/null +++ b/sdk/src/test/resources/json/invalid_reference.json @@ -0,0 +1,5 @@ +{ + "kind": "Reference", + "typeID": "id", + "authorized": true +} \ No newline at end of file diff --git a/sdk/src/test/resources/json/invalid_restriction.json b/sdk/src/test/resources/json/invalid_restriction.json new file mode 100644 index 0000000..531a8cc --- /dev/null +++ b/sdk/src/test/resources/json/invalid_restriction.json @@ -0,0 +1,5 @@ +{ + "kind": "Restriction", + "typeID": "id", + "restrictions": [] +} \ No newline at end of file diff --git a/sdk/src/test/resources/json/invalid_variable_sized_array.json b/sdk/src/test/resources/json/invalid_variable_sized_array.json new file mode 100644 index 0000000..d9db542 --- /dev/null +++ b/sdk/src/test/resources/json/invalid_variable_sized_array.json @@ -0,0 +1,3 @@ +{ + "kind": "VariableSizedArray" +} \ No newline at end of file diff --git a/sdk/src/test/resources/json/missing_kind.json b/sdk/src/test/resources/json/missing_kind.json new file mode 100644 index 0000000..a69e853 --- /dev/null +++ b/sdk/src/test/resources/json/missing_kind.json @@ -0,0 +1 @@ +{"type": "String"} \ No newline at end of file diff --git a/sdk/src/test/resources/json/missing_type.json b/sdk/src/test/resources/json/missing_type.json new file mode 100644 index 0000000..48b5537 --- /dev/null +++ b/sdk/src/test/resources/json/missing_type.json @@ -0,0 +1 @@ +{"kind": "PartialCadenceType"} \ No newline at end of file diff --git a/sdk/src/test/resources/json/missing_type_composite.json b/sdk/src/test/resources/json/missing_type_composite.json new file mode 100644 index 0000000..797a0d1 --- /dev/null +++ b/sdk/src/test/resources/json/missing_type_composite.json @@ -0,0 +1,6 @@ +{ + "kind": "Struct", + "typeID": "id", + "initializers": [], + "fields":[] +} \ No newline at end of file diff --git a/sdk/src/test/resources/json/missing_type_id_composite.json b/sdk/src/test/resources/json/missing_type_id_composite.json new file mode 100644 index 0000000..0d03813 --- /dev/null +++ b/sdk/src/test/resources/json/missing_type_id_composite.json @@ -0,0 +1,6 @@ +{ + "kind": "Struct", + "type": "Composite", + "initializers": [], + "fields":[] +} \ No newline at end of file diff --git a/sdk/src/test/resources/json/unknown_kind.json b/sdk/src/test/resources/json/unknown_kind.json new file mode 100644 index 0000000..631b902 --- /dev/null +++ b/sdk/src/test/resources/json/unknown_kind.json @@ -0,0 +1,4 @@ +{ + "kind": "UnknownKind", + "type": {} +} \ No newline at end of file diff --git a/sdk/src/test/resources/json/unknown_kind_2.json b/sdk/src/test/resources/json/unknown_kind_2.json new file mode 100644 index 0000000..d72a72d --- /dev/null +++ b/sdk/src/test/resources/json/unknown_kind_2.json @@ -0,0 +1 @@ +{"kind": "UnknownKind", "type": ""} \ No newline at end of file From 2dfff0615f206478a76ff6a075e3f6115edea655 Mon Sep 17 00:00:00 2001 From: Tarak Ben Youssef Date: Tue, 23 Jul 2024 12:52:57 -0600 Subject: [PATCH 62/72] add KMAC test with 48-bytes output-size --- .../org/onflow/flow/sdk/crypto/CryptoTest.kt | 44 ++++++++++++------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt b/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt index 3796e79..dd3c516 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt @@ -155,25 +155,39 @@ internal class CryptoTest { fun `Sanity check KMAC128`() { val input = byteArrayOf(0x00, 0x01, 0x02, 0x03) val expected = listOf( + listOf( hexStringToByteArray("E5780B0D3EA6F7D3A429C5706AA43A00FADBD7D49628839E3187243F456EE14E"), hexStringToByteArray("3B1FBA963CD8B0B59E8C1A6D71888B7143651AF8BA0A7070C0979E2811324AA5") - ) + ), + listOf( + hexStringToByteArray("4f5967393bd357c13cf1b0aff13c2abe075dd68edee33d6b8cb06f5b2a4d5232c11b439f3c20b20a4f04b0549d9caa10"), + hexStringToByteArray("99d9364ab5d2b748cb843b27f5a4f4fb06ea87306fa141a676cbb4b39c5c5f12d36b7b76aeea81c4f5876e16e6783e72") + ) + ) val key = hexStringToByteArray("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F") - val customizers = listOf("".toByteArray(), "My Tagged Application".toByteArray()) - val outputSize = 32 + val customizers = listOf( + "".toByteArray(), + "My Tagged Application".toByteArray() + ) + val outputSize = listOf( + 32, + 48 + ) - customizers.forEachIndexed { index, customizer -> - // Test full input processing - val hasher1 = HasherImpl(HashAlgorithm.KMAC128, key, customizer, outputSize) - val hash1 = hasher1.hash(input) - assertArrayEquals(expected[index], hash1) - - // Test incremental input processing - val hasher2 = HasherImpl(HashAlgorithm.KMAC128, key, customizer, outputSize) - hasher2.update(input, 0, 2) - hasher2.update(input, 2, input.size - 2) - val hash2 = hasher2.doFinal(outputSize) - assertArrayEquals(expected[index], hash2) + outputSize.forEachIndexed { indexSize, size -> + customizers.forEachIndexed { index, customizer -> + // Test full input processing + val hasher1 = HasherImpl(HashAlgorithm.KMAC128, key, customizer, size) + val hash1 = hasher1.hash(input) + assertArrayEquals(expected[indexSize][index], hash1) + + // Test incremental input processing + val hasher2 = HasherImpl(HashAlgorithm.KMAC128, key, customizer, size) + hasher2.update(input, 0, 2) + hasher2.update(input, 2, input.size - 2) + val hash2 = hasher2.doFinal(size) + assertArrayEquals(expected[indexSize][index], hash2) + } } } From db86f91d6e0792185f1c9ccfe7cc459e976e5e31 Mon Sep 17 00:00:00 2001 From: Tarak Ben Youssef Date: Tue, 23 Jul 2024 12:54:45 -0600 Subject: [PATCH 63/72] use KMAC128 minimum output size as hash size --- src/main/kotlin/org/onflow/flow/sdk/models.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/onflow/flow/sdk/models.kt b/src/main/kotlin/org/onflow/flow/sdk/models.kt index ea671e9..9e7a088 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/models.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/models.kt @@ -80,7 +80,7 @@ enum class HashAlgorithm( UNKNOWN("unknown", -1, -1, 0), SHA2_256("SHA-256", 256, 1, 1), SHA3_256("SHA3-256", 256, 3, 2), - KMAC128("KMAC128", 128, 5, 3), + KMAC128("KMAC128", 256, 5, 3), KECCAK256("KECCAK256", 256, 6, 4); From 42665560cee9f5c639a26afb7a4a3921c54b5ce1 Mon Sep 17 00:00:00 2001 From: Tarak Ben Youssef Date: Tue, 23 Jul 2024 13:16:06 -0600 Subject: [PATCH 64/72] remove old failing test of KMAC128 with 48-bytes output --- .../org/onflow/flow/sdk/crypto/CryptoTest.kt | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt b/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt index dd3c516..b074fef 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt @@ -170,7 +170,9 @@ internal class CryptoTest { "My Tagged Application".toByteArray() ) val outputSize = listOf( + // test vector for 32 bytes is taken from https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/Kmac_samples.pdf 32, + // test vector for 48 bytes in generated by other trusted libraries 48 ) @@ -229,21 +231,6 @@ internal class CryptoTest { assertEquals("Output size must be at least 256 bits (32 bytes)", exception.message) } - @Test - fun `Test output size longer than 32 bytes`() { - // wip: using sample 4 from https://csrc.nist.gov/CSRC/media/Projects/Cryptographic-Standards-and-Guidelines/documents/examples/Kmac_samples.pdf - val input = byteArrayOf(0x00, 0x01, 0x02, 0x03) - val key = hexStringToByteArray("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F") - val customizer = "My Tagged Application".toByteArray() - val outputSize = 64 - - val expectedHash = hexStringToByteArray("20C570C31346F703C9AC36C61C03CB64C3970D0CFC787E9B79599D273A68D2F7F69D4CC3DE9D104A351689F27CF6F5951F0103F33F4F24871024D9C27773A8DD") - - val hasher = HasherImpl(HashAlgorithm.KMAC128, key, customizer, outputSize) - val hash = hasher.hash(input) - assertArrayEquals(hash, expectedHash) - } - private fun hexStringToByteArray(hexString: String): ByteArray { val len = hexString.length val data = ByteArray(len / 2) From 4048c1e61b2250e6863208e48499d0116ba86e96 Mon Sep 17 00:00:00 2001 From: Jerome P Date: Tue, 23 Jul 2024 12:25:22 -0700 Subject: [PATCH 65/72] Fix kotlin link build error --- src/main/kotlin/org/onflow/flow/sdk/models.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/org/onflow/flow/sdk/models.kt b/src/main/kotlin/org/onflow/flow/sdk/models.kt index 9e7a088..ef4c8b4 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/models.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/models.kt @@ -77,13 +77,13 @@ enum class HashAlgorithm( val code: Int, val index: Int ) { + UNKNOWN("unknown", -1, -1, 0), SHA2_256("SHA-256", 256, 1, 1), SHA3_256("SHA3-256", 256, 3, 2), KMAC128("KMAC128", 256, 5, 3), KECCAK256("KECCAK256", 256, 6, 4); - companion object { @JvmStatic fun fromCode(code: Int): HashAlgorithm = entries.find { it.code == code } ?: UNKNOWN From 6bcdbb0cc139d8f00bffc2e37ddb0089b89c0c47 Mon Sep 17 00:00:00 2001 From: Jerome P Date: Tue, 23 Jul 2024 12:27:55 -0700 Subject: [PATCH 66/72] Fix kotlin link build error --- src/main/kotlin/org/onflow/flow/sdk/models.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/kotlin/org/onflow/flow/sdk/models.kt b/src/main/kotlin/org/onflow/flow/sdk/models.kt index ef4c8b4..0e72ca9 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/models.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/models.kt @@ -77,7 +77,6 @@ enum class HashAlgorithm( val code: Int, val index: Int ) { - UNKNOWN("unknown", -1, -1, 0), SHA2_256("SHA-256", 256, 1, 1), SHA3_256("SHA3-256", 256, 3, 2), From 3156357b24c2f3ed088661d7be8ce47a3a8ee36d Mon Sep 17 00:00:00 2001 From: Jerome P Date: Tue, 23 Jul 2024 12:35:41 -0700 Subject: [PATCH 67/72] Fix kotlin link build errors --- .../kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt b/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt index b074fef..3948663 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt @@ -82,7 +82,6 @@ internal class CryptoTest { assertEquals(expectedLength, normalizedSignature.size) } - @Test fun `Test extractRS`() { val keyPair = Crypto.generateKeyPair() @@ -156,14 +155,14 @@ internal class CryptoTest { val input = byteArrayOf(0x00, 0x01, 0x02, 0x03) val expected = listOf( listOf( - hexStringToByteArray("E5780B0D3EA6F7D3A429C5706AA43A00FADBD7D49628839E3187243F456EE14E"), - hexStringToByteArray("3B1FBA963CD8B0B59E8C1A6D71888B7143651AF8BA0A7070C0979E2811324AA5") + hexStringToByteArray("E5780B0D3EA6F7D3A429C5706AA43A00FADBD7D49628839E3187243F456EE14E"), + hexStringToByteArray("3B1FBA963CD8B0B59E8C1A6D71888B7143651AF8BA0A7070C0979E2811324AA5") ), listOf( - hexStringToByteArray("4f5967393bd357c13cf1b0aff13c2abe075dd68edee33d6b8cb06f5b2a4d5232c11b439f3c20b20a4f04b0549d9caa10"), - hexStringToByteArray("99d9364ab5d2b748cb843b27f5a4f4fb06ea87306fa141a676cbb4b39c5c5f12d36b7b76aeea81c4f5876e16e6783e72") - ) + hexStringToByteArray("4f5967393bd357c13cf1b0aff13c2abe075dd68edee33d6b8cb06f5b2a4d5232c11b439f3c20b20a4f04b0549d9caa10"), + hexStringToByteArray("99d9364ab5d2b748cb843b27f5a4f4fb06ea87306fa141a676cbb4b39c5c5f12d36b7b76aeea81c4f5876e16e6783e72") ) + ) val key = hexStringToByteArray("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F") val customizers = listOf( "".toByteArray(), From fdf3e0a5a842e969b46abaacfa981cfd52ec2486 Mon Sep 17 00:00:00 2001 From: Tarak Ben Youssef Date: Tue, 23 Jul 2024 18:32:02 -0600 Subject: [PATCH 68/72] update HashImpl to enforce key and custmizer aren't used outside of KMAC --- .../org/onflow/flow/sdk/crypto/Crypto.kt | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/org/onflow/flow/sdk/crypto/Crypto.kt b/src/main/kotlin/org/onflow/flow/sdk/crypto/Crypto.kt index fe9c94b..cfb53d5 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/crypto/Crypto.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/crypto/Crypto.kt @@ -162,22 +162,37 @@ internal class HasherImpl( private val hashAlgo: HashAlgorithm, private val key: ByteArray? = null, private val customizer: ByteArray? = null, - private val outputSize: Int = 32 + private val outputSize: Int = 0 ) : Hasher { private var kmac: KMAC? = null init { - if (outputSize < 32) { - throw IllegalArgumentException("Output size must be at least 256 bits (32 bytes)") - } - if (hashAlgo == HashAlgorithm.KMAC128) { + if (outputSize < 32) { + throw IllegalArgumentException("KMAC128 output size must be at least 32 bytes") + } + if (key == null || key.size < 16) { throw IllegalArgumentException("KMAC128 requires a key of at least 16 bytes") } kmac = KMAC(128, customizer) kmac!!.init(KeyParameter(key)) + } else if (hashAlgo == HashAlgorithm.KECCAK256 || + hashAlgo == HashAlgorithm.SHA3_256 || + hashAlgo == HashAlgorithm.SHA2_256) { + if (key != null) { + throw IllegalArgumentException("Key must be null") + } + if (customizer != null) { + throw IllegalArgumentException("Customizer must be null") + } + if (outputSize != 32) { + throw IllegalArgumentException("Output size must be 32 bytes") + } + } else { + throw IllegalArgumentException("Unsupported hash algorithm: ${hashAlgo.algorithm}") } + } override fun hash(bytes: ByteArray): ByteArray { From 06ed3d11eeb3b02da98bcb31ae8abef09a005fa0 Mon Sep 17 00:00:00 2001 From: Tarak Ben Youssef Date: Tue, 23 Jul 2024 18:32:42 -0600 Subject: [PATCH 69/72] extend tests for invalid values of keys, customizers and output sizes for all algos --- .../org/onflow/flow/sdk/crypto/CryptoTest.kt | 83 +++++++++++++++---- 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt b/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt index 3948663..8b8e86e 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt @@ -193,41 +193,96 @@ internal class CryptoTest { } @Test - fun `Test short key length`() { + fun `Test invalid keys`() { + val exceptionString = "Key must be null" val input = byteArrayOf(0x00, 0x01, 0x02, 0x03) val key = hexStringToByteArray("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F") + // SHA2-256 + var exception = assertThrows(IllegalArgumentException::class.java) { + HasherImpl(HashAlgorithm.SHA2_256, key, null).hash(input) + } + assertEquals(exceptionString, exception.message) + // SHA3-256 + exception = assertThrows(IllegalArgumentException::class.java) { + HasherImpl(HashAlgorithm.SHA3_256, key, null).hash(input) + } + assertEquals(exceptionString, exception.message) + // KECCAK-256 + exception = assertThrows(IllegalArgumentException::class.java) { + HasherImpl(HashAlgorithm.KECCAK256, key, null).hash(input) + } + assertEquals(exceptionString, exception.message) + + // KMAC128 val customizer = "".toByteArray() val outputSize = 32 - val exception = assertThrows(IllegalArgumentException::class.java) { + exception = assertThrows(IllegalArgumentException::class.java) { HasherImpl(HashAlgorithm.KMAC128, key.sliceArray(0..<15), customizer, outputSize).hash(input) } assertEquals("KMAC128 requires a key of at least 16 bytes", exception.message) + + exception = assertThrows(IllegalArgumentException::class.java) { + HasherImpl(HashAlgorithm.KMAC128, null, customizer, outputSize).hash(input) + } + assertEquals("KMAC128 requires a key of at least 16 bytes", exception.message) } @Test - fun `Test nil output size`() { + fun `Test invalid customizers`() { + val exceptionString = "Customizer must be null" val input = byteArrayOf(0x00, 0x01, 0x02, 0x03) - val key = hexStringToByteArray("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F") - val customizer = "".toByteArray() - - val exception = assertThrows(IllegalArgumentException::class.java) { - HasherImpl(HashAlgorithm.KMAC128, key, customizer, 0).hash(input) + val customizer = byteArrayOf(0x00) + // SHA2-256 + var exception = assertThrows(IllegalArgumentException::class.java) { + HasherImpl(HashAlgorithm.SHA2_256, null, customizer).hash(input) + } + assertEquals(exceptionString, exception.message) + // SHA3-256 + exception = assertThrows(IllegalArgumentException::class.java) { + HasherImpl(HashAlgorithm.SHA3_256, null, customizer).hash(input) + } + assertEquals(exceptionString, exception.message) + // KECCAK-256 + exception = assertThrows(IllegalArgumentException::class.java) { + HasherImpl(HashAlgorithm.KECCAK256, null, customizer).hash(input) } - assertEquals("Output size must be at least 256 bits (32 bytes)", exception.message) + assertEquals(exceptionString, exception.message) } @Test - fun `Test non-nil shorter output size than 32 bytes`() { + fun `Test invalid output sizes`() { + val exceptionString = "Output size must be 32 bytes" val input = byteArrayOf(0x00, 0x01, 0x02, 0x03) + // SHA2-256 + var exception = assertThrows(IllegalArgumentException::class.java) { + HasherImpl(HashAlgorithm.SHA2_256, null, null, 40).hash(input) + } + assertEquals(exceptionString, exception.message) + // SHA3-256 + exception = assertThrows(IllegalArgumentException::class.java) { + HasherImpl(HashAlgorithm.SHA3_256, null, null, 40).hash(input) + } + assertEquals(exceptionString, exception.message) + // KECCAK-256 + exception = assertThrows(IllegalArgumentException::class.java) { + HasherImpl(HashAlgorithm.KECCAK256, null, null, 40).hash(input) + } + assertEquals(exceptionString, exception.message) + + // KMAC128 val key = hexStringToByteArray("404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F") val customizer = "".toByteArray() - val outputSize = 16 - val exception = assertThrows(IllegalArgumentException::class.java) { - HasherImpl(HashAlgorithm.KMAC128, key, customizer, outputSize).hash(input) + exception = assertThrows(IllegalArgumentException::class.java) { + HasherImpl(HashAlgorithm.KMAC128, key, customizer, 0).hash(input) + } + assertEquals("KMAC128 output size must be at least 32 bytes", exception.message) + + exception = assertThrows(IllegalArgumentException::class.java) { + HasherImpl(HashAlgorithm.KMAC128, key, customizer, 16).hash(input) } - assertEquals("Output size must be at least 256 bits (32 bytes)", exception.message) + assertEquals("KMAC128 output size must be at least 32 bytes", exception.message) } private fun hexStringToByteArray(hexString: String): ByteArray { From 8891a0a7816d9cc17f294f5944318d51f3148c3a Mon Sep 17 00:00:00 2001 From: Tarak Ben Youssef Date: Tue, 23 Jul 2024 18:36:09 -0600 Subject: [PATCH 70/72] remove obsolete hash tests and add test for invalid hasher input --- .../org/onflow/flow/sdk/crypto/CryptoTest.kt | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt b/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt index 8b8e86e..75fd754 100644 --- a/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt +++ b/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt @@ -99,26 +99,13 @@ internal class CryptoTest { } @Test - fun `Hasher implementation`() { - val hasher = HasherImpl(HashAlgorithm.SHA3_256) - val hashedBytes = hasher.hash("test".toByteArray()) - assertNotNull(hashedBytes) - } - - @Test - fun `Hasher implementation for Keccak-256`() { - val hasher = HasherImpl(HashAlgorithm.KECCAK256) - val hashedBytes = hasher.hash("test".toByteArray()) - assertNotNull(hashedBytes) + fun `Invalid Hasher input`() { + val exception = assertThrows(IllegalArgumentException::class.java) { + HasherImpl(HashAlgorithm.UNKNOWN) + } + assertEquals("Unsupported hash algorithm: unknown", exception.message) } - @Test - fun `Hasher implementation for KMAC128`() { - val key = "thisKeyIsAtLeast16Bytes".toByteArray() - val hasher = HasherImpl(HashAlgorithm.KMAC128, key) - val hashedBytes = hasher.hash("test".toByteArray()) - assertNotNull(hashedBytes) - } @Test fun `Sanity check SHA3_256`() { From 090bb7d85fa9554cd0249fc9b128f4640b112c70 Mon Sep 17 00:00:00 2001 From: Tarak Ben Youssef Date: Tue, 23 Jul 2024 19:03:21 -0600 Subject: [PATCH 71/72] fix the default output size --- src/main/kotlin/org/onflow/flow/sdk/crypto/Crypto.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/onflow/flow/sdk/crypto/Crypto.kt b/src/main/kotlin/org/onflow/flow/sdk/crypto/Crypto.kt index cfb53d5..a79ea41 100644 --- a/src/main/kotlin/org/onflow/flow/sdk/crypto/Crypto.kt +++ b/src/main/kotlin/org/onflow/flow/sdk/crypto/Crypto.kt @@ -162,7 +162,7 @@ internal class HasherImpl( private val hashAlgo: HashAlgorithm, private val key: ByteArray? = null, private val customizer: ByteArray? = null, - private val outputSize: Int = 0 + private val outputSize: Int = 32 ) : Hasher { private var kmac: KMAC? = null @@ -186,7 +186,7 @@ internal class HasherImpl( if (customizer != null) { throw IllegalArgumentException("Customizer must be null") } - if (outputSize != 32) { + if (outputSize != (hashAlgo.outputSize/8)) { throw IllegalArgumentException("Output size must be 32 bytes") } } else { From a09aaff2690222abc06dfaca196fdc8f905a990e Mon Sep 17 00:00:00 2001 From: Lea Lobanov Date: Wed, 24 Jul 2024 19:44:29 +0700 Subject: [PATCH 72/72] Linting build --- sdk/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt b/sdk/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt index 75fd754..8749b15 100644 --- a/sdk/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt +++ b/sdk/src/test/kotlin/org/onflow/flow/sdk/crypto/CryptoTest.kt @@ -106,7 +106,6 @@ internal class CryptoTest { assertEquals("Unsupported hash algorithm: unknown", exception.message) } - @Test fun `Sanity check SHA3_256`() { val input = "test".toByteArray()