From 2f94fe18ea6394b2a95c55c8a464a195f28ffe8f Mon Sep 17 00:00:00 2001 From: Naomi Plasterer Date: Tue, 22 Oct 2024 13:22:06 -0700 Subject: [PATCH] add tests reproducing the SCW issues --- .../library/SmartContractWalletTest.kt | 243 +++++++++++++++++- .../org/xmtp/android/library/TestHelpers.kt | 22 +- 2 files changed, 253 insertions(+), 12 deletions(-) diff --git a/library/src/androidTest/java/org/xmtp/android/library/SmartContractWalletTest.kt b/library/src/androidTest/java/org/xmtp/android/library/SmartContractWalletTest.kt index a65d38108..2b5847c85 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/SmartContractWalletTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/SmartContractWalletTest.kt @@ -2,15 +2,32 @@ package org.xmtp.android.library import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.xmtp.android.library.messages.MessageDeliveryStatus +import org.xmtp.android.library.messages.PrivateKey +import org.xmtp.android.library.messages.PrivateKeyBuilder +import org.xmtp.android.library.messages.walletAddress @RunWith(AndroidJUnit4::class) class SmartContractWalletTest { - @Test - fun testCanCreateASCW() { + private lateinit var davonSCW: FakeSCWWallet + private lateinit var davonSCWClient: Client + private lateinit var eriSCW: FakeSCWWallet + private lateinit var eriSCWClient: Client + private lateinit var options: ClientOptions + private lateinit var boV3Wallet: PrivateKeyBuilder + private lateinit var boV3: PrivateKey + private lateinit var boV3Client: Client + + @Before + fun setUp() { val key = byteArrayOf( 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, @@ -18,19 +35,44 @@ class SmartContractWalletTest { 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F ) val context = InstrumentationRegistry.getInstrumentation().targetContext - val davonSCW = FakeSCWWallet.generate() - val options = ClientOptions( + options = ClientOptions( ClientOptions.Api(XMTPEnvironment.LOCAL, false), enableV3 = true, appContext = context, dbEncryptionKey = key ) - val davonSCWClient = runBlocking { + + // EOA + boV3Wallet = PrivateKeyBuilder() + boV3 = boV3Wallet.getPrivateKey() + boV3Client = runBlocking { + Client().createV3( + account = boV3Wallet, + options = options + ) + } + + // SCW + davonSCW = FakeSCWWallet.generate(ANVIL_TEST_PRIVATE_KEY_1) + davonSCWClient = runBlocking { + Client().createV3( + account = davonSCW, + options = options + ) + } + + // SCW + eriSCW = FakeSCWWallet.generate(ANVIL_TEST_PRIVATE_KEY_2) + eriSCWClient = runBlocking { Client().createV3( account = davonSCW, options = options ) } + } + + @Test + fun testCanBuildASCW() { val davonSCWClient2 = runBlocking { Client().buildV3( address = davonSCW.address, @@ -41,4 +83,195 @@ class SmartContractWalletTest { assertEquals(davonSCWClient.inboxId, davonSCWClient2.inboxId) } + + @Test + fun testsCanCreateGroup() { + val group1 = runBlocking { + boV3Client.conversations.newGroup( + listOf( + davonSCW.walletAddress, + eriSCW.walletAddress + ) + ) + } + val group2 = runBlocking { + davonSCWClient.conversations.newGroup( + listOf( + boV3.walletAddress, + eriSCW.walletAddress + ) + ) + } + + assertEquals( + runBlocking { group1.members().map { it.inboxId }.sorted() }, + listOf(davonSCWClient.inboxId, boV3Client.inboxId, eriSCWClient.inboxId).sorted() + ) + assertEquals( + runBlocking { group2.members().map { it.addresses.first() }.sorted() }, + listOf(davonSCWClient.address, boV3Client.address, eriSCWClient.address).sorted() + ) + } + + @Test + fun testsCanSendMessages() { + val boGroup = runBlocking { + boV3Client.conversations.newGroup( + listOf( + davonSCW.walletAddress, + eriSCW.walletAddress + ) + ) + } + runBlocking { boGroup.send("howdy") } + val messageId = runBlocking { boGroup.send("gm") } + runBlocking { boGroup.sync() } + assertEquals(boGroup.messages().first().body, "gm") + assertEquals(boGroup.messages().first().id, messageId) + assertEquals(boGroup.messages().first().deliveryStatus, MessageDeliveryStatus.PUBLISHED) + assertEquals(boGroup.messages().size, 3) + + runBlocking { davonSCWClient.conversations.syncGroups() } + val davonGroup = runBlocking { davonSCWClient.conversations.listGroups().last() } + runBlocking { davonGroup.sync() } + assertEquals(davonGroup.messages().size, 2) + assertEquals(davonGroup.messages().first().body, "gm") + runBlocking { davonGroup.send("from davon") } + + runBlocking { eriSCWClient.conversations.syncGroups() } + val eriGroup = runBlocking { davonSCWClient.findGroup(davonGroup.id) } + runBlocking { eriGroup?.sync() } + assertEquals(eriGroup?.messages()?.size, 3) + assertEquals(eriGroup?.messages()?.first()?.body, "from davon") + runBlocking { eriGroup?.send("from eri") } + } + + @Test + fun testGroupConsent() { + runBlocking { + val davonGroup = runBlocking { + davonSCWClient.conversations.newGroup( + listOf( + boV3.walletAddress, + eriSCW.walletAddress + ) + ) + } + assert(davonSCWClient.contacts.isGroupAllowed(davonGroup.id)) + assertEquals(davonGroup.consentState(), ConsentState.ALLOWED) + + davonSCWClient.contacts.denyGroups(listOf(davonGroup.id)) + assert(davonSCWClient.contacts.isGroupDenied(davonGroup.id)) + assertEquals(davonGroup.consentState(), ConsentState.DENIED) + + davonGroup.updateConsentState(ConsentState.ALLOWED) + assert(davonSCWClient.contacts.isGroupAllowed(davonGroup.id)) + assertEquals(davonGroup.consentState(), ConsentState.ALLOWED) + } + } + + @Test + fun testCanAllowAndDenyInboxId() { + runBlocking { + val davonGroup = runBlocking { + davonSCWClient.conversations.newGroup( + listOf( + boV3.walletAddress, + eriSCW.walletAddress + ) + ) + } + assert(!davonSCWClient.contacts.isInboxAllowed(boV3Client.inboxId)) + assert(!davonSCWClient.contacts.isInboxDenied(boV3Client.inboxId)) + + davonSCWClient.contacts.allowInboxes(listOf(boV3Client.inboxId)) + var caroMember = davonGroup.members().firstOrNull { it.inboxId == boV3Client.inboxId } + assertEquals(caroMember!!.consentState, ConsentState.ALLOWED) + + assert(davonSCWClient.contacts.isInboxAllowed(boV3Client.inboxId)) + assert(!davonSCWClient.contacts.isInboxDenied(boV3Client.inboxId)) + assert(davonSCWClient.contacts.isAllowed(boV3Client.address)) + assert(!davonSCWClient.contacts.isDenied(boV3Client.address)) + + davonSCWClient.contacts.denyInboxes(listOf(boV3Client.inboxId)) + caroMember = davonGroup.members().firstOrNull { it.inboxId == boV3Client.inboxId } + assertEquals(caroMember!!.consentState, ConsentState.DENIED) + + assert(!davonSCWClient.contacts.isInboxAllowed(boV3Client.inboxId)) + assert(davonSCWClient.contacts.isInboxDenied(boV3Client.inboxId)) + + davonSCWClient.contacts.allow(listOf(eriSCWClient.address)) + assert(davonSCWClient.contacts.isAllowed(eriSCWClient.address)) + assert(!davonSCWClient.contacts.isDenied(eriSCWClient.address)) + assert(davonSCWClient.contacts.isInboxAllowed(eriSCWClient.inboxId)) + assert(!davonSCWClient.contacts.isInboxDenied(eriSCWClient.inboxId)) + } + } + + @Test + fun testCanStreamAllMessages() { + val group = runBlocking { + davonSCWClient.conversations.newGroup( + listOf( + boV3.walletAddress, + eriSCW.walletAddress + ) + ) + } + val group2 = runBlocking { + boV3Client.conversations.newGroup( + listOf( + davonSCW.walletAddress, + eriSCW.walletAddress + ) + ) + } + runBlocking { davonSCWClient.conversations.syncGroups() } + + val allMessages = mutableListOf() + + val job = CoroutineScope(Dispatchers.IO).launch { + try { + davonSCWClient.conversations.streamAllGroupMessages() + .collect { message -> + allMessages.add(message) + } + } catch (e: Exception) { + } + } + Thread.sleep(1000) + runBlocking { + group.send("hi") + group2.send("hi") + } + Thread.sleep(1000) + assertEquals(2, allMessages.size) + job.cancel() + } + + @Test + fun testCanStreamGroups() { + val allMessages = mutableListOf() + + val job = CoroutineScope(Dispatchers.IO).launch { + try { + davonSCWClient.conversations.streamGroups() + .collect { message -> + allMessages.add(message.topic) + } + } catch (e: Exception) { + } + } + Thread.sleep(1000) + + runBlocking { + eriSCWClient.conversations.newGroup(listOf(boV3.walletAddress, davonSCW.walletAddress)) + boV3Client.conversations.newGroup(listOf(eriSCW.walletAddress, davonSCW.walletAddress)) + } + + Thread.sleep(1000) + assertEquals(2, allMessages.size) + job.cancel() + } + } diff --git a/library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt b/library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt index b9915d0a8..4d2453fe3 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/TestHelpers.kt @@ -54,14 +54,15 @@ class FakeWallet : SigningKey { get() = privateKey.walletAddress } -private const val ANVIL_TEST_PRIVATE_KEY = +const val ANVIL_TEST_PRIVATE_KEY_1 = "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +const val ANVIL_TEST_PRIVATE_KEY_2 = + "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" private const val ANVIL_TEST_PORT = "http://10.0.2.2:8545" class FakeSCWWallet : SigningKey { - private var web3j: Web3j = Web3j.build(HttpService(ANVIL_TEST_PORT)) - private val contractDeployerCredentials: Credentials = - Credentials.create(ANVIL_TEST_PRIVATE_KEY) + private val web3j: Web3j = Web3j.build(HttpService(ANVIL_TEST_PORT)) + private var contractDeployerCredentials: Credentials? = null var walletAddress: String = "" override val address: String @@ -73,8 +74,9 @@ class FakeSCWWallet : SigningKey { override var chainId: Long? = 31337L companion object { - fun generate(): FakeSCWWallet { + fun generate(privateKey: String): FakeSCWWallet { return FakeSCWWallet().apply { + contractDeployerCredentials = Credentials.create(privateKey) createSmartContractWallet() } } @@ -91,7 +93,7 @@ class FakeSCWWallet : SigningKey { val replaySafeHash = smartWallet.replaySafeHash(digest).send() val signature = - Sign.signMessage(replaySafeHash, contractDeployerCredentials.ecKeyPair, false) + Sign.signMessage(replaySafeHash, contractDeployerCredentials!!.ecKeyPair, false) val signatureBytes = signature.r + signature.s + signature.v val tokens = listOf( Uint(BigInteger.ZERO), @@ -119,7 +121,13 @@ class FakeSCWWallet : SigningKey { ).send() val ownerAddress = ByteArray(32) { 0 }.apply { - System.arraycopy(contractDeployerCredentials.address.hexToByteArray(), 0, this, 12, 20) + System.arraycopy( + contractDeployerCredentials!!.address.hexToByteArray(), + 0, + this, + 12, + 20 + ) } val owners = listOf(ownerAddress) val nonce = BigInteger.ZERO