diff --git a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt index 13aecf1be..fa581a468 100644 --- a/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt +++ b/modules/block/src/main/kotlin/jp/co/soramitsu/iroha2/Serde.kt @@ -33,6 +33,7 @@ import jp.co.soramitsu.iroha2.generated.DomainId import jp.co.soramitsu.iroha2.generated.EvaluatesTo import jp.co.soramitsu.iroha2.generated.ExecutorMode import jp.co.soramitsu.iroha2.generated.Expression +import jp.co.soramitsu.iroha2.generated.Fixed import jp.co.soramitsu.iroha2.generated.GrantExpr import jp.co.soramitsu.iroha2.generated.Hash import jp.co.soramitsu.iroha2.generated.HashValue @@ -75,7 +76,6 @@ import jp.co.soramitsu.iroha2.generated.TriggerId import jp.co.soramitsu.iroha2.generated.TriggerOfTriggeringFilterBox import jp.co.soramitsu.iroha2.generated.Value import java.io.ByteArrayOutputStream -import java.math.BigInteger import kotlin.reflect.KClass import kotlin.reflect.full.createInstance import kotlin.reflect.full.memberProperties @@ -777,10 +777,10 @@ private fun mintBurnSerialize( } private fun NumericValue.formatAsString() = when (this) { - is NumericValue.U32 -> this.u32 - is NumericValue.U64 -> this.u64 - is NumericValue.U128 -> this.u128 - is NumericValue.Fixed -> this.fixed.fixedPointOfI64 + is NumericValue.U32 -> "${this.u32}_u32" + is NumericValue.U64 -> "${this.u64}_u64" + is NumericValue.U128 -> "${this.u128}_u128" + is NumericValue.Fixed -> "${this.fixed.fixedPointOfI64}_fx" }.toString() private fun NumericValue.format() = when (this) { @@ -1135,11 +1135,12 @@ private fun deserializeMetadata(p: JsonParser, mapper: ObjectMapper): Metadata { } private fun String.toNumericValue(): NumericValue { - val number = BigInteger(this) - return when { - number >= BigInteger.ZERO && number <= "4294967295".toBigInteger() -> NumericValue.U32(number.toLong()) - number <= "18446744073709551615".toBigInteger() -> NumericValue.U64(number) - number <= "340282366920938463463374607431768211455".toBigInteger() -> NumericValue.U128(number) + val (number, type) = this.split('_') + return when (type) { + NumericValue.U32::class.simpleName?.lowercase() -> NumericValue.U32(number.toLong()) + NumericValue.U64::class.simpleName?.lowercase() -> NumericValue.U64(number.toBigInteger()) + NumericValue.U128::class.simpleName?.lowercase() -> NumericValue.U128(number.toBigInteger()) + "fx" -> NumericValue.Fixed(Fixed(number.toBigDecimal())) else -> throw IllegalArgumentException("Number out of range") } } diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extensions.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extensions.kt index 6e8c0cb13..0d8dd148c 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extensions.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/Extensions.kt @@ -114,12 +114,14 @@ fun String.asValue() = Value.String(this) fun Int.asValue() = Value.Numeric(NumericValue.U32(this.toLong())) -fun Long.asValue() = Value.Numeric(NumericValue.U128(BigInteger.valueOf(this))) - -fun BigInteger.asValue() = Value.Numeric(NumericValue.U128(this)) +fun Double.asValue() = Value.Numeric(NumericValue.Fixed(Fixed(this.toBigDecimal()))) fun BigDecimal.asValue() = Value.Numeric(NumericValue.Fixed(Fixed(this))) +fun Long.asValue() = Value.Numeric(NumericValue.U64(BigInteger.valueOf(this))) + +fun BigInteger.asValue() = Value.Numeric(NumericValue.U128(this)) + fun Boolean.asValue() = Value.Bool(this) fun AccountId.asValue() = Value.Id(IdBox.AccountId(this)) @@ -298,6 +300,7 @@ inline fun T.asValue() = when (this) { is String -> this.asValue() is Long -> this.asValue() is Int -> this.asValue() + is Double -> this.asValue() is BigInteger -> this.asValue() is BigDecimal -> this.asValue() is Boolean -> this.asValue() diff --git a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/Instructions.kt b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/Instructions.kt index 90ec94668..d52723bf0 100644 --- a/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/Instructions.kt +++ b/modules/client/src/main/kotlin/jp/co/soramitsu/iroha2/transaction/Instructions.kt @@ -375,7 +375,9 @@ object Instructions { /** * Execute a trigger */ - fun executeTrigger(triggerId: TriggerId) = InstructionExpr.ExecuteTrigger(ExecuteTriggerExpr(triggerId.evaluatesTo())) + fun executeTrigger(triggerId: TriggerId) = InstructionExpr.ExecuteTrigger( + ExecuteTriggerExpr(triggerId.evaluatesTo()), + ) /** * Mint an asset of the [AssetValueType.Quantity] asset value type @@ -513,6 +515,19 @@ object Instructions { } } + /** + * Revoke an account the [Permissions.CanSetKeyValueInDomain] permission + */ + fun grantSetKeyValueDomain(domainId: DomainId, target: AccountId): InstructionExpr { + return grantSome( + target, + PermissionToken( + definitionId = Permissions.CanSetKeyValueInDomain.type, + payload = domainId.asJsonString().asStringWithJson(), + ).asValue(), + ) + } + /** * Revoke an account the [Permissions.CanSetKeyValueInDomain] permission */ @@ -552,7 +567,10 @@ object Instructions { ), ) - private inline fun revokeSome(accountId: AccountId, permissionToken: () -> PermissionToken): InstructionExpr.Revoke { + private inline fun revokeSome( + accountId: AccountId, + permissionToken: () -> PermissionToken, + ): InstructionExpr.Revoke { return InstructionExpr.Revoke( RevokeExpr( destinationId = accountId.evaluatesTo(), diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/InstructionsTest.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/InstructionsTest.kt index 88c41edf8..ac963a53d 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/InstructionsTest.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/InstructionsTest.kt @@ -12,6 +12,7 @@ import jp.co.soramitsu.iroha2.generated.AssetDefinitionId import jp.co.soramitsu.iroha2.generated.AssetId import jp.co.soramitsu.iroha2.generated.AssetValue import jp.co.soramitsu.iroha2.generated.AssetValueType +import jp.co.soramitsu.iroha2.generated.DomainId import jp.co.soramitsu.iroha2.generated.IdBox import jp.co.soramitsu.iroha2.generated.Metadata import jp.co.soramitsu.iroha2.generated.Name @@ -44,6 +45,7 @@ import jp.co.soramitsu.iroha2.testengine.NewAccountWithMetadata import jp.co.soramitsu.iroha2.testengine.NewDomainWithMetadata import jp.co.soramitsu.iroha2.testengine.RubbishToTestMultipleGenesis import jp.co.soramitsu.iroha2.testengine.StoreAssetWithMetadata +import jp.co.soramitsu.iroha2.testengine.WithDomainTransferredToBob import jp.co.soramitsu.iroha2.testengine.WithIroha import jp.co.soramitsu.iroha2.testengine.WithIrohaManual import jp.co.soramitsu.iroha2.testengine.XorAndValAssets @@ -411,6 +413,24 @@ class InstructionsTest : IrohaTest() { } } + @Test + @WithIroha([DefaultGenesis::class]) + fun `domain metadata set key value with permissions`(): Unit = runBlocking { + val domainId = DomainId(randomAlphabetic(10).asName()) + client.tx(BOB_ACCOUNT_ID, BOB_KEYPAIR) { + registerDomain(domainId) + grantPermissionToken( + Permissions.CanSetKeyValueInDomain, + domainId.asJsonString(), + ALICE_ACCOUNT_ID, + ) + } + + client.tx(ALICE_ACCOUNT_ID, ALICE_KEYPAIR) { + setKeyValue(domainId, randomAlphabetic(10).asName(), randomAlphabetic(10).asValue()) + } + } + @Test @WithIroha([DefaultGenesis::class]) @Feature("Accounts") @@ -1051,6 +1071,25 @@ class InstructionsTest : IrohaTest() { } } + @Test + @WithIroha([WithDomainTransferredToBob::class]) + @Feature("Domains") + @Story("Account transfers domain ownership") + @SdkTestId("transfer_domain_ownership_in_genesis") + fun `transfer domain ownership in genesis`(): Unit = runBlocking { + val key = randomAlphabetic(5).asName() + val value = randomAlphabetic(5).asValue() + client.tx(BOB_ACCOUNT_ID, BOB_KEYPAIR) { + setKeyValue(WithDomainTransferredToBob.DOMAIN_ID, key, value) + } + val extractedValue = QueryBuilder.findDomainById(WithDomainTransferredToBob.DOMAIN_ID) + .account(ALICE_ACCOUNT_ID) + .buildSigned(ALICE_KEYPAIR) + .let { query -> client.sendQuery(query) } + .metadata.map[key] + assertEquals(value.string, extractedValue?.cast()?.string) + } + @Test @WithIroha([DefaultGenesis::class]) @Feature("Domains") @@ -1058,36 +1097,38 @@ class InstructionsTest : IrohaTest() { @SdkTestId("transfer_domain_ownership") fun `transfer domain ownership`(): Unit = runBlocking { val domainId = "Kingdom".asDomainId() + client.tx(ALICE_ACCOUNT_ID, ALICE_KEYPAIR) { registerDomain(domainId) } - client.sendTransaction { - account(super.account) - registerDomain(domainId) - buildSigned(super.keyPair) - }.also { d -> - withTimeout(txTimeout) { d.await() } + assertFailsWith(TransactionRejectedException::class) { + client.tx(BOB_ACCOUNT_ID, BOB_KEYPAIR) { + setKeyValue(domainId, randomAlphabetic(5).asName(), randomAlphabetic(5).asValue()) + } } var kingdomDomainOwnedBy = QueryBuilder.findDomainById(domainId) - .account(super.account) - .buildSigned(super.keyPair) - .let { query -> - client.sendQuery(query) - }.ownedBy + .account(ALICE_ACCOUNT_ID) + .buildSigned(ALICE_KEYPAIR) + .let { query -> client.sendQuery(query) }.ownedBy assertEquals(ALICE_ACCOUNT_ID, kingdomDomainOwnedBy) - client.tx { - transferDomainOwnership( - ALICE_ACCOUNT_ID, - IdBox.DomainId(domainId), - BOB_ACCOUNT_ID, - ) + client.tx(ALICE_ACCOUNT_ID, ALICE_KEYPAIR) { + transferDomainOwnership(ALICE_ACCOUNT_ID, IdBox.DomainId(domainId), BOB_ACCOUNT_ID) } kingdomDomainOwnedBy = QueryBuilder.findDomainById(domainId) - .account(super.account) - .buildSigned(super.keyPair) - .let { query -> - client.sendQuery(query) - }.ownedBy + .account(ALICE_ACCOUNT_ID) + .buildSigned(ALICE_KEYPAIR) + .let { query -> client.sendQuery(query) }.ownedBy assertEquals(BOB_ACCOUNT_ID, kingdomDomainOwnedBy) + + val key = randomAlphabetic(5).asName() + val value = randomAlphabetic(5).asValue() + client.tx(BOB_ACCOUNT_ID, BOB_KEYPAIR) { setKeyValue(domainId, key, value) } + + val extractedValue = QueryBuilder.findDomainById(domainId) + .account(ALICE_ACCOUNT_ID) + .buildSigned(ALICE_KEYPAIR) + .let { query -> client.sendQuery(query) } + .metadata.map[key] + assertEquals(value.string, extractedValue?.cast()?.string) } @Test diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/QueriesTest.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/QueriesTest.kt index 46a54f0bc..3799c164a 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/QueriesTest.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/QueriesTest.kt @@ -26,12 +26,14 @@ import jp.co.soramitsu.iroha2.query.QueryBuilder import jp.co.soramitsu.iroha2.testengine.ALICE_ACCOUNT_ID import jp.co.soramitsu.iroha2.testengine.ALICE_ACCOUNT_NAME import jp.co.soramitsu.iroha2.testengine.ALICE_KEYPAIR +import jp.co.soramitsu.iroha2.testengine.AliceAndBobHasPermissionToMintPublicKeys import jp.co.soramitsu.iroha2.testengine.AliceCanMintXor import jp.co.soramitsu.iroha2.testengine.AliceHas100XorAndPermissionToBurn import jp.co.soramitsu.iroha2.testengine.AliceHasRoleWithAccessToBobsMetadata import jp.co.soramitsu.iroha2.testengine.AliceWithTestAssets import jp.co.soramitsu.iroha2.testengine.BOB_ACCOUNT_ID import jp.co.soramitsu.iroha2.testengine.BOB_ACCOUNT_NAME +import jp.co.soramitsu.iroha2.testengine.BOB_KEYPAIR import jp.co.soramitsu.iroha2.testengine.DEFAULT_ASSET_DEFINITION_ID import jp.co.soramitsu.iroha2.testengine.DEFAULT_ASSET_ID import jp.co.soramitsu.iroha2.testengine.DEFAULT_DOMAIN_ID @@ -64,6 +66,33 @@ import kotlin.test.assertTrue @Permission("no_permission_required") class QueriesTest : IrohaTest() { + @Test + @WithIroha([AliceAndBobHasPermissionToMintPublicKeys::class]) + @Feature("Accounts") + @Story("Account sets key value pair") + @Permission("CanSetKeyValueInUserAsset") + @SdkTestId("query_permission_tokens_by_accountId") + fun `query permission tokens by accountId`(): Unit = runBlocking { + val permissionsBefore = QueryBuilder.findPermissionTokensByAccountId(BOB_ACCOUNT_ID) + .account(BOB_ACCOUNT_ID) + .buildSigned(BOB_KEYPAIR) + .let { query -> client.sendQuery(query) } + assertEquals(1, permissionsBefore.size) + + client.tx(ALICE_ACCOUNT_ID, ALICE_KEYPAIR) { + grantPermissionToken( + Permissions.CanSetKeyValueInUserAccount, + ALICE_ACCOUNT_ID.asJsonString(), + BOB_ACCOUNT_ID, + ) + } + val permissionsAfter = QueryBuilder.findPermissionTokensByAccountId(BOB_ACCOUNT_ID) + .account(BOB_ACCOUNT_ID) + .buildSigned(BOB_KEYPAIR) + .let { query -> client.sendQuery(query) } + assertEquals(2, permissionsAfter.size) + } + @Test @WithIroha([NewAccountWithMetadata::class]) @Feature("Accounts") diff --git a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/Genesis.kt b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/Genesis.kt index 31f63b841..bc42aebd2 100644 --- a/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/Genesis.kt +++ b/modules/client/src/test/kotlin/jp/co/soramitsu/iroha2/testengine/Genesis.kt @@ -1,5 +1,6 @@ package jp.co.soramitsu.iroha2.testengine +import jp.co.soramitsu.iroha2.ACCOUNT_ID_DELIMITER import jp.co.soramitsu.iroha2.Genesis import jp.co.soramitsu.iroha2.Permissions import jp.co.soramitsu.iroha2.asAccountId @@ -15,6 +16,7 @@ import jp.co.soramitsu.iroha2.generated.AssetDefinitionId import jp.co.soramitsu.iroha2.generated.AssetId import jp.co.soramitsu.iroha2.generated.AssetValueType import jp.co.soramitsu.iroha2.generated.DomainId +import jp.co.soramitsu.iroha2.generated.IdBox import jp.co.soramitsu.iroha2.generated.InstructionExpr import jp.co.soramitsu.iroha2.generated.Metadata import jp.co.soramitsu.iroha2.generated.PermissionToken @@ -26,6 +28,9 @@ import jp.co.soramitsu.iroha2.toIrohaPublicKey import jp.co.soramitsu.iroha2.transaction.Instructions import org.apache.commons.lang3.RandomStringUtils.randomAlphabetic import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils +import java.math.BigDecimal +import java.math.BigInteger +import kotlin.random.Random.Default.nextDouble /** * Create a default genesis where there is just one domain with only Alice and Bob in it @@ -42,6 +47,21 @@ open class AliceCanUpgradeExecutor : Genesis( ), ) +open class WithDomainTransferredToBob : Genesis( + rawGenesisBlock( + Instructions.registerDomain(DOMAIN_ID), + Instructions.transferDomainOwnership( + "$GENESIS$ACCOUNT_ID_DELIMITER$GENESIS".asAccountId(), + IdBox.DomainId(DOMAIN_ID), + BOB_ACCOUNT_ID, + ), + ), +) { + companion object { + val DOMAIN_ID = randomAlphabetic(10).asDomainId() + } +} + open class AliceCanUnregisterAnyPeer : Genesis( rawGenesisBlock( Instructions.grantPermissionToken( @@ -369,8 +389,17 @@ open class FatGenesis : Genesis( Instructions.grantRole(ROLE_ID, ALICE_ACCOUNT_ID), Instructions.mintAsset(AssetId(DEFAULT_ASSET_DEFINITION_ID, BOB_ACCOUNT_ID), 100), Instructions.burnAsset(AssetId(DEFAULT_ASSET_DEFINITION_ID, BOB_ACCOUNT_ID), 100), - Instructions.setKeyValue(ASSET_ID, randomAlphabetic(10).asName(), 100.asValue()), + Instructions.setKeyValue(ASSET_ID, randomAlphabetic(10).asName(), Int.MAX_VALUE.asValue()), + Instructions.setKeyValue(ASSET_ID, randomAlphabetic(10).asName(), (Int.MAX_VALUE * 10L).asValue()), + Instructions.setKeyValue(ASSET_ID, randomAlphabetic(10).asName(), nextDouble().asValue()), + Instructions.setKeyValue(ASSET_ID, randomAlphabetic(10).asName(), BigDecimal(nextDouble()).asValue()), + Instructions.setKeyValue( + ASSET_ID, + randomAlphabetic(10).asName(), + (BigInteger.valueOf(Long.MAX_VALUE) * BigInteger.valueOf(2)).asValue(), + ), Instructions.setKeyValue(ASSET_ID, randomAlphabetic(10).asName(), randomAlphabetic(10).asValue()), + Instructions.setKeyValue(DEFAULT_DOMAIN_ID, randomAlphabetic(10).asName(), randomAlphabetic(10).asValue()), ), ) { companion object { diff --git a/modules/test-tools/src/main/resources/executor.wasm b/modules/test-tools/src/main/resources/executor.wasm old mode 100755 new mode 100644 index ffe306074..544c9e29d Binary files a/modules/test-tools/src/main/resources/executor.wasm and b/modules/test-tools/src/main/resources/executor.wasm differ diff --git a/modules/test-tools/src/main/resources/genesis2.json b/modules/test-tools/src/main/resources/genesis2.json index 52789f6f0..be860aa3d 100644 --- a/modules/test-tools/src/main/resources/genesis2.json +++ b/modules/test-tools/src/main/resources/genesis2.json @@ -130,7 +130,7 @@ "Name" : "id" }, "value" : { - "String" : "123" + "String" : "123_u32" } } }, { @@ -139,7 +139,7 @@ "key" : { "Name" : "wt" }, - "value" : "123" + "value" : "123_u32" } }, { "SetKeyValue" : { @@ -167,7 +167,7 @@ "key" : { "Name" : "gh" }, - "value" : "123" + "value" : "123_u32" } }, { "SetKeyValue" : { @@ -175,7 +175,7 @@ "key" : { "Name" : "ef" }, - "value" : "1234" + "value" : "1234_u32" } }, { "SetKeyValue" : { @@ -183,7 +183,7 @@ "key" : { "Name" : "cd" }, - "value" : "123" + "value" : "123_u32" } }, { "SetKeyValue" : { diff --git a/modules/test-tools/src/main/resources/genesis3.json b/modules/test-tools/src/main/resources/genesis3.json index f098d1c4a..9de5e5121 100644 --- a/modules/test-tools/src/main/resources/genesis3.json +++ b/modules/test-tools/src/main/resources/genesis3.json @@ -156,7 +156,7 @@ "key" : { "Name" : "key2" }, - "value" : "123" + "value" : "123_u32" } },{ "Mint" : {