import QtQuick 2.15 import QtTest 1.15 import "helpers/wallet_connect.js" as Testing import StatusQ.Core.Utils 0.1 import QtQuick.Controls 2.15 import Storybook 1.0 import AppLayouts.Wallet.services.dapps 1.0 import AppLayouts.Wallet.services.dapps.types 1.0 import AppLayouts.Profile.stores 1.0 import AppLayouts.Wallet.panels 1.0 import AppLayouts.Wallet.stores 1.0 as WalletStore import shared.stores 1.0 import utils 1.0 Item { id: root width: 600 height: 400 function mockActiveSession(accountsModel, networksModel, sdk, topic) { const account = accountsModel.get(1) const networks = ModelUtils.modelToFlatArray(networksModel, "chainId") const requestId = 1717149885151715 const session = JSON.parse(Testing.formatApproveSessionResponse(networks, [account.address])) const sessionProposal = JSON.parse(Testing.formatSessionProposal()) sdk.sessionProposal(sessionProposal) // Expect to have calls to getActiveSessions from service initialization const prevRequests = sdk.getActiveSessionsCallbacks.length sdk.approveSessionResult(sessionProposal.id, session, null) // Service might trigger a sessionRequest event following the getActiveSessions call const callback = sdk.getActiveSessionsCallbacks[prevRequests].callback callback({"b536a": session}) return session } function mockSessionRequestEvent(tc, sdk, accountsModel, networksModel) { const account = accountsModel.get(1) const network = networksModel.get(1) const topic = "b536a" const requestId = 1717149885151715 const session = mockActiveSession(accountsModel, networksModel, sdk, topic) const request = buildSessionRequestResolved(tc, account.address, network.chainId, topic, requestId) return {sdk, session, account, network, topic, request} } function buildSessionRequest(chainId, method, params, topic, requestId) { return JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic, requestId)) } function buildSessionRequestResolved(testCase, account, network, topic, requestId) { const requestObj = buildSessionRequest(network, Constants.personal_sign, [`"${DAppsHelpers.strToHex("hello world")}"`, `"${account}"`], topic, requestId) const requestItem = testCase.createTemporaryObject(sessionRequestComponent, root, { event: requestObj, topic, requestId: requestObj.id, method: Constants.personal_sign, accountAddress: account, chainId: network, data: "hello world", preparedData: "hello world", expirationTimestamp: (Date.now() + 10000) / 1000, sourceId: Constants.DAppConnectors.WalletConnect, dappName: "Test DApp", dappUrl: "https://test.dapp", dappIcon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAcCAYAAACdz7SqAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAM2SURBVHgBtVbNbtNAEJ7ZpBQ4pRGF9kQqWqkBRNwnwLlxI9y4NX2CiiOntE9QeINw49a8QdwT3NhKQCKaSj4WUVXmABRqe5hxE+PGTuyk5ZOSXe/ftzs/3y5CBiw/NEzw/cdAaCJAifgXdCA4QGAjggbEvbMf0LJt7aSth6lkHjW4akIG8GI2/1k5H7e7XW2PGRdHqWQU8jdoNytZIrnC7YNPupnUnxtuWF01SjhD77hqwPQosNlrxdt34OTb172xpELoKvrA1QW4EqCZRJyLEnpI7ZBQggThlGvXYVLI3HAeE88vfj85Pno/6FaDiqeoEUZlMA9bvc/7cxyxVa6/SeM5j2Tcdn/hnHsNly520s7KAyN0V17+7pWNGhHVhxYJTNLraosLi8e0kMBxT0FH00IW830oeT/ButBertjRQ5BPO1xUQ1IE2oQUHHZ0K6mdI1RzoSEdpqRg76O2lPgSElKDdz919JYMoxA95QDow7qUykWoxTo5z2YIXsGUsLV2CPD1cDu7MODiQKKnsVmI1jhFyQJvFrb6URxFQWJAYYIZSEF6tKZATitFQpehEm1PkCraWYCE+8Nt5ENBwX8EAd2NNaKQxu0ukVuCqwATQHwnjhphShMuiSAVKZ527E6bzYt78Q3SulxvcAm44K8ntXMqagmkJDUpzNwMZGsqBDqLuDXcLvkvqajcWWgm+ZUI6svlym5fsbITlh9tsgi0Ezs5//vkMtBocqSJOZw84ZrHPiXFJ6UwECx5A/FbqNXX2hAiefkzqCNRha1Wi8yJgddeCk4qHzkK1aMgdypfshYRbkTGm3z0Rs6LW0REgDXVEMuMI0TE5kDlgkv8+PjIKRYXfzPxEyH2EYzDzv7L4q1FHsvpg8Gkt186OlGp5uYXZMjzkYS8txwfQnj63//APmzDIF1yWJVrCDJgeZVfjTjCj0KicC3qlny0053FZ/k/PFnyy6P2yv1Kk1T/1eCGF/pEYCncGI6DCzIo/uGnRvg8CfzE5MEPoQGT4Pz5Uj3oxp+hMe0V4oOOrssOMfmWyMJo5X1cG2WZkYIvO2Tn85sGXwg5B5Q9kiKMas5DntPr6Oq4+/gvs8hkkbAzoC8AAAAASUVORK5CYII=" }) return requestItem } Component { id: sdkComponent WalletConnectSDKBase { property bool sdkReady: true enabled: true property var getActiveSessionsCallbacks: [] getActiveSessions: function(callback) { getActiveSessionsCallbacks.push({callback}) } property int pairCalled: 0 pair: function() { pairCalled++ } property var buildApprovedNamespacesCalls: [] buildApprovedNamespaces: function(id, params, supportedNamespaces) { buildApprovedNamespacesCalls.push({id, params, supportedNamespaces}) } property var approveSessionCalls: [] approveSession: function(sessionProposalJson, approvedNamespaces) { approveSessionCalls.push({sessionProposalJson, approvedNamespaces}) } property var acceptSessionRequestCalls: [] acceptSessionRequest: function(topic, id, signature) { acceptSessionRequestCalls.push({topic, id, signature}) } property var rejectSessionRequestCalls: [] rejectSessionRequest: function(topic, id, error) { rejectSessionRequestCalls.push({topic, id, error}) } } } Component { id: serviceComponent DAppsService { property var onApproveSessionResultTriggers: [] onApproveSessionResult: function(session, error) { onApproveSessionResultTriggers.push({session, error}) } property var onDisplayToastMessageTriggers: [] onDisplayToastMessage: function(message, type) { onDisplayToastMessageTriggers.push({message, type}) } property var onPairingValidatedTriggers: [] onPairingValidated: function(validationState) { onPairingValidatedTriggers.push({validationState}) } } } Component { id: dappsStoreComponent DAppsStore { property string dappsListReceivedJsonStr: '[]' signal dappsListReceived(string dappsJson) signal userAuthenticated(string topic, string id, string password, string pin) signal userAuthenticationFailed(string topic, string id) signal signingResult(string topic, string id, string data) signal estimatedTimeResponse(string topic, int timeCategory, bool success) signal suggestedFeesResponse(string topic, var suggestedFeesJsonObj, bool success) signal estimatedGasResponse(string topic, string gasEstimate, bool success) // By default, return no dapps in store function getDapps() { dappsListReceived(dappsListReceivedJsonStr) return true } property var addWalletConnectSessionCalls: [] function addWalletConnectSession(sessionJson) { addWalletConnectSessionCalls.push({sessionJson}) return true } property var authenticateUserCalls: [] function authenticateUser(topic, id, address) { authenticateUserCalls.push({topic, id, address}) } property var signMessageCalls: [] function signMessage(topic, id, address, password, message) { signMessageCalls.push({topic, id, address, password, message}) } property var safeSignTypedDataCalls: [] function safeSignTypedData(topic, id, address, password, message, chainId, legacy) { safeSignTypedDataCalls.push({topic, id, address, password, message, chainId, legacy}) } property var updateWalletConnectSessionsCalls: [] function updateWalletConnectSessions(activeTopicsJson) { updateWalletConnectSessionsCalls.push({activeTopicsJson}) } function requestEstimatedTime(topic, chainId, maxFeePerGas) { estimatedTimeResponse(topic, Constants.TransactionEstimatedTime.LessThanThreeMins, true) } property var mockedSuggestedFees: ({ gasPrice: 2.0, baseFee: 5.0, maxPriorityFeePerGas: 2.0, maxFeePerGasLow: 1.0, maxFeePerGasMedium: 1.1, maxFeePerGasHigh: 1.2, l1GasFee: 0.0, eip1559Enabled: true }) function requestSuggestedFees(topic, chainId) { suggestedFeesResponse(topic, mockedSuggestedFees, true) } function requestGasEstimate(topic, chainId, tx) { estimatedGasResponse(topic, "0x5208", true) } function hexToDec(hex) { if (hex.length > "0xfffffffffffff".length) { console.warn(`Beware of possible loss of precision converting ${hex}`) } return parseInt(hex, 16).toString() } } } Component { id: walletStoreComponent QtObject { readonly property ListModel filteredFlatModel: ListModel { ListElement { chainId: 1 layer: 1 } ListElement { chainId: 2 chainName: "Test Chain" iconUrl: "network/Network=Ethereum" layer: 2 } // Used by tst_balanceCheck ListElement { chainId: 11155111 layer: 1 } // Used by tst_balanceCheck ListElement { chainId: 421614 layer: 2 } } readonly property ListModel filteredFlatModelWithOnlineStat: ListModel { ListElement { chainId: 1 layer: 1 isOnline: true } ListElement { chainId: 2 chainName: "Test Chain" iconUrl: "network/Network=Ethereum" layer: 2 isOnline: true } // Used by tst_balanceCheck ListElement { chainId: 11155111 layer: 1 isOnline: true } // Used by tst_balanceCheck ListElement { chainId: 421614 layer: 2 isOnline: true } } readonly property ListModel nonWatchAccounts: ListModel { ListElement { address: "0x1" keycardAccount: false } ListElement { address: "0x2" name: "helloworld" emoji: "😋" color: "#2A4AF5" keycardAccount: false } ListElement { address: "0x3a" keycardAccount: false } // Account from GroupedAccountsAssetsModel ListElement { address: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240" keycardAccount: false } } readonly property var currencyStore: CurrenciesStore {} readonly property var walletAssetsStore: assetsStoreMock readonly property string selectedAddress: "0x1" } } Component { id: sessionRequestComponent SessionRequestResolved { } } WalletStore.WalletAssetsStore { id: assetsStoreMock // Silence warnings assetsWithFilteredBalances: ListModel {} readonly property var groupedAccountAssetsModel: groupedAccountsAssetsModel } Component { id: dappsModuleComponent DAppsModule { currenciesStore: CurrenciesStore {} groupedAccountAssetsModel: assetsStoreMock.groupedAccountAssetsModel } } TestCase { id: dappsModuleTest name: "DAppsModuleTest" // Ensure mocked GroupedAccountsAssetsModel is properly initialized when: windowShown property DAppsModule handler: null function init() { let walletStore = createTemporaryObject(walletStoreComponent, root) verify(!!walletStore) let sdk = createTemporaryObject(sdkComponent, root, { projectId: "12ab", enabled: true }) verify(!!sdk) let bcSdk = createTemporaryObject(sdkComponent, root, { projectId: "12ab", enabled: false }) verify(!!bcSdk) let store = createTemporaryObject(dappsStoreComponent, root) verify(!!store) handler = createTemporaryObject(dappsModuleComponent, root, { wcSdk: sdk, bcSdk: bcSdk, store: store, accountsModel: walletStore.nonWatchAccounts, networksModel: walletStore.filteredFlatModelWithOnlineStat }) verify(!!handler) sdk.getActiveSessionsCallbacks = [] } function test_TestAuthentication() { let td = mockSessionRequestEvent(this, handler.wcSdk, handler.accountsModel, handler.networksModel) handler.wcSdk.sessionRequestEvent(td.request.event) let request = handler.requestsModel.findById(td.request.requestId) request.accept() compare(handler.store.authenticateUserCalls.length, 1, "expected a call to store.authenticateUser") let store = handler.store store.userAuthenticated(td.topic, td.request.requestId, "hello world", "") compare(store.signMessageCalls.length, 1, "expected a call to store.signMessage") compare(store.signMessageCalls[0].message, td.request.data) } function test_onSessionRequestEventDifferentCaseForAddress() { const sdk = handler.wcSdk const testAddressUpper = "0x3A" const chainId = 2 const method = "personal_sign" const message = "hello world" const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddressUpper}"`] const topic = "b536a" const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic)) // Expect to have calls to getActiveSessions from service initialization const prevRequests = sdk.getActiveSessionsCallbacks.length mockActiveSession(handler.accountsModel, handler.networksModel, sdk, topic) sdk.sessionRequestEvent(session) compare(sdk.getActiveSessionsCallbacks.length, 1, "expected DAppsRequestHandler call sdk.getActiveSessions") } // Tests that the request is ignored if not in the current profile (don't have the PK for the address) function test_onSessionRequestEventMissingAddress() { const sdk = handler.wcSdk const testAddressUpper = "0xY" const chainId = 2 const method = "personal_sign" const message = "hello world" const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddressUpper}"`] const topic = "b536a" const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic)) mockActiveSession(handler.accountsModel, handler.networksModel, sdk, topic) sdk.getActiveSessionsCallbacks = [] sdk.sessionRequestEvent(session) compare(sdk.getActiveSessionsCallbacks.length, 0, "expected DAppsRequestHandler don't call sdk.getActiveSessions") compare(sdk.rejectSessionRequestCalls.length, 1, "expected to reject the request") } function test_balanceCheck_data() { return [{ tag: "have_enough_funds", chainId: 11155111, expect: { haveEnoughForFees: true, fee: "0.0000231" } }, { tag: "doest_have_enough_funds", chainId: 11155111, // Override the suggestedFees to a higher value maxFeePerGasM: 1000000.0, /*GWEI*/ expect: { haveEnoughForFees: false, fee: "21" } }, { tag: "check_l2_doesnt_have_enough_funds_on_l1", chainId: 421614, // Override the l1 additional fees l1GasFee: 1000000000.0, expect: { haveEnoughForFees: false, fee: "1.0000231" } }, { tag: "check_l2_doesnt_have_enough_funds_on_l2", chainId: 421614, // Override the l2 to a higher value maxFeePerGasM: 1000000.0, /*GWEI*/ // Override the l1 additional fees l1GasFee: 10.0, expect: { haveEnoughForFees: false, fee: "21.00000001" } }] } function test_balanceCheck(data) { let sdk = handler.wcSdk // Override the suggestedFees if (!!data.maxFeePerGasM) { handler.store.mockedSuggestedFees.maxFeePerGasMedium = data.maxFeePerGasM } if (!!data.l1GasFee) { handler.store.mockedSuggestedFees.l1GasFee = data.l1GasFee } let testAddress = "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240" let chainId = data.chainId let method = "eth_sendTransaction" let message = "hello world" let params = [`{ "data": "0x", "from": "${testAddress}", "to": "0x2", "value": "0x12345" }`] let topic = "b536a" let session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic)) mockActiveSession(handler.accountsModel, handler.networksModel, sdk, topic) sdk.sessionRequestEvent(session) compare(sdk.getActiveSessionsCallbacks.length, 1, "expected DAppsRequestHandler call sdk.getActiveSessions") let callback = sdk.getActiveSessionsCallbacks[0].callback callback({"b536a": JSON.parse(Testing.formatApproveSessionResponse([chainId, 7], [testAddress]))}) let request = handler.requestsModel.findById(session.id) request.setActive() verify(!!request, "expected request to be found") compare(request.fiatMaxFees.toFixed(), data.expect.fee, "expected fiatMaxFees to be set") // storybook's CurrenciesStore mock up getFiatValue returns the balance compare(request.ethMaxFees, data.expect.fee, "expected ethMaxFees to be set") verify(request.haveEnoughFunds, "expected haveEnoughFunds to be set") compare(request.haveEnoughFees, data.expect.haveEnoughForFees, "expected haveEnoughForFees to be set") verify(!!request.feesInfo, "expected feesInfo to be set") } function test_sessionRequestExpiryInTheFuture() { const sdk = handler.wcSdk const testAddressUpper = "0x3A" const chainId = 2 const method = "personal_sign" const message = "hello world" const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddressUpper}"`] const topic = "b536a" const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic)) verify(session.params.request.expiryTimestamp > Date.now() / 1000, "expected expiryTimestamp to be in the future") // Expect to have calls to getActiveSessions from service initialization const prevRequests = sdk.getActiveSessionsCallbacks.length mockActiveSession(handler.accountsModel, handler.networksModel, sdk, topic) sdk.sessionRequestEvent(session) verify(handler.requestsModel.count === 1, "expected a request to be added") const request = handler.requestsModel.findRequest(topic, session.id) verify(!!request, "expected request to be found") verify(!request.isExpired(), "expected request to not be expired") } function test_sessionRequestExpiryInThePast() { const sdk = handler.wcSdk const testAddressUpper = "0x3A" const chainId = 2 const method = "personal_sign" const message = "hello world" const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddressUpper}"`] const topic = "b536a" const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic)) session.params.request.expiryTimestamp = (Date.now() - 10000) / 1000 verify(session.params.request.expiryTimestamp < Date.now() / 1000, "expected expiryTimestamp to be in the past") mockActiveSession(handler.accountsModel, handler.networksModel, sdk, topic) sdk.sessionRequestEvent(session) verify(handler.requestsModel.count === 1, "expected a request to be added") const request = handler.requestsModel.findRequest(topic, session.id) verify(!!request, "expected request to be found") verify(request.isExpired(), "expected request to be expired") } function test_wcSignalsSessionRequestExpiry() { const sdk = handler.wcSdk const testAddressUpper = "0x3A" const chainId = 2 const method = "personal_sign" const message = "hello world" const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddressUpper}"`] const topic = "b536a" const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic)) verify(session.params.request.expiryTimestamp > Date.now() / 1000, "expected expiryTimestamp to be in the future") mockActiveSession(handler.accountsModel, handler.networksModel, sdk, topic) sdk.sessionRequestEvent(session) const request = handler.requestsModel.findRequest(topic, session.id) verify(!!request, "expected request to be found") verify(!request.isExpired(), "expected request to not be expired") sdk.sessionRequestExpired(session.id) verify(request.isExpired(), "expected request to be expired") } function test_acceptExpiredSessionRequest() { const sdk = handler.wcSdk const testAddressUpper = "0x3A" const chainId = 2 const method = "personal_sign" const message = "hello world" const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddressUpper}"`] const topic = "b536a" const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic)) session.params.request.expiryTimestamp = (Date.now() - 10000) / 1000 verify(session.params.request.expiryTimestamp < Date.now() / 1000, "expected expiryTimestamp to be in the past") mockActiveSession(handler.accountsModel, handler.networksModel, sdk, topic) sdk.sessionRequestEvent(session) verify(handler.requestsModel.count === 1, "expected a request to be added") const request = handler.requestsModel.findRequest(topic, session.id) verify(!!request, "expected request to be found") verify(request.isExpired(), "expected request to be expired") verify(sdk.rejectSessionRequestCalls.length === 0, "expected no call to sdk.rejectSessionRequest") ignoreWarning("Error: request expired") request.accept() handler.store.userAuthenticated(topic, session.id, "1234", "", message) verify(sdk.rejectSessionRequestCalls.length === 1, "expected a call to sdk.rejectSessionRequest") sdk.sessionRequestUserAnswerResult(topic, session.id, false, "") } function test_rejectExpiredSessionRequest() { const sdk = handler.wcSdk const testAddressUpper = "0x3A" const chainId = 2 const method = "personal_sign" const message = "hello world" const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddressUpper}"`] const topic = "b536a" const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic)) session.params.request.expiryTimestamp = (Date.now() - 10000) / 1000 verify(session.params.request.expiryTimestamp < Date.now() / 1000, "expected expiryTimestamp to be in the past") mockActiveSession(handler.accountsModel, handler.networksModel, sdk, topic) sdk.sessionRequestEvent(session) verify(sdk.rejectSessionRequestCalls.length === 0, "expected no call to sdk.rejectSessionRequest") ignoreWarning("Error: request expired") handler.requestsModel.findRequest(topic, session.id).accept() handler.store.userAuthenticationFailed(topic, session.id) verify(sdk.rejectSessionRequestCalls.length === 1, "expected a call to sdk.rejectSessionRequest") } function test_signFailedAuthOnExpiredRequest() { const sdk = handler.wcSdk const testAddressUpper = "0x3A" const chainId = 2 const method = "personal_sign" const message = "hello world" const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddressUpper}"`] const topic = "b536a" const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic)) session.params.request.expiryTimestamp = (Date.now() - 10000) / 1000 verify(session.params.request.expiryTimestamp < Date.now() / 1000, "expected expiryTimestamp to be in the past") mockActiveSession(handler.accountsModel, handler.networksModel, sdk, topic) sdk.sessionRequestEvent(session) verify(sdk.rejectSessionRequestCalls.length === 0, "expected no call to sdk.rejectSessionRequest") ignoreWarning("Error: request expired") handler.requestsModel.findRequest(topic, session.id).accept() handler.store.userAuthenticationFailed(topic, session.id) verify(sdk.rejectSessionRequestCalls.length === 1, "expected a call to sdk.rejectSessionRequest") } } TestCase { id: dappsServiceTest name: "DAppsService" property DAppsService service: null SignalSpy { id: connectDAppSpy target: dappsServiceTest.service signalName: "connectDApp" property var argPos: { "dappChains": 0, "dappUrl": 1, "dappName": 2, "dappIcon": 3, "key": 4, } } function populateDAppData(topic) { const dapp = { topic, name: Testing.dappName, url: Testing.dappUrl, iconUrl: Testing.dappFirstIcon, connectorId: 0, accountAddresses: [{address: "0x123"}], rawSessions: [{session: {topic}}] } findChild(service.dappsModule, "DAppsModel").model.append(dapp) } function init() { let walletStore = createTemporaryObject(walletStoreComponent, root) verify(!!walletStore) let sdk = createTemporaryObject(sdkComponent, root, { projectId: "12ab", enabled: true }) verify(!!sdk) let bcSdk = createTemporaryObject(sdkComponent, root, { projectId: "12ab", enabled: false }) let store = createTemporaryObject(dappsStoreComponent, root) verify(!!store) let dappsModuleObj = createTemporaryObject(dappsModuleComponent, root, { wcSdk: sdk, bcSdk: bcSdk, store: store, accountsModel: walletStore.nonWatchAccounts, networksModel: walletStore.filteredFlatModelWithOnlineStat }) service = createTemporaryObject(serviceComponent, root, { dappsModule: dappsModuleObj, selectedAddress: "", accountsModel: walletStore.nonWatchAccounts }) verify(!!service) } function cleanup() { connectDAppSpy.clear() } function testSetupPair(sessionProposalPayload) { let sdk = service.dappsModule.wcSdk let accountsModel = service.dappsModule.accountsModel let networksModel = service.dappsModule.networksModel let store = service.dappsModule.store service.pair("wc:12ab@1?bridge=https%3A%2F%2Fbridge.walletconnect.org&key=12ab") compare(sdk.pairCalled, 1, "expected a call to sdk.pair") sdk.sessionProposal(JSON.parse(sessionProposalPayload)) compare(sdk.buildApprovedNamespacesCalls.length, 1, "expected a call to sdk.buildApprovedNamespaces") var args = sdk.buildApprovedNamespacesCalls[0] verify(!!args.supportedNamespaces, "expected supportedNamespaces to be set") // All calls to SDK are expected as events to be made by the wallet connect SDK let chainsForApproval = args.supportedNamespaces.eip155.chains let networksArray = ModelUtils.modelToArray(networksModel).map(entry => entry.chainId) verify(networksArray.every(chainId => chainsForApproval.some(eip155Chain => eip155Chain === `eip155:${chainId}`)), "expect all the networks to be present") // We test here all accounts for one chain only, we have separate tests to validate that all accounts are present let allAccountsForApproval = args.supportedNamespaces.eip155.accounts let accountsArray = ModelUtils.modelToArray(accountsModel).map(entry => entry.address) verify(accountsArray.every(address => allAccountsForApproval.some(eip155Address => eip155Address === `eip155:${networksArray[0]}:${address}`)), "expect at least all accounts for the first chain to be present" ) return {sdk, store, networksArray, accountsArray, networksModel, accountsModel} } function test_TestPairing() { const {sdk, store, networksArray, accountsArray, networksModel, accountsModel} = testSetupPair(Testing.formatSessionProposal()) compare(sdk.buildApprovedNamespacesCalls.length, 1, "expected a call to sdk.buildApprovedNamespaces") let allApprovedNamespaces = JSON.parse(Testing.formatBuildApprovedNamespacesResult(networksArray, accountsArray)) sdk.buildApprovedNamespacesResult(sdk.buildApprovedNamespacesCalls[0].id, allApprovedNamespaces, "") compare(connectDAppSpy.count, 1, "expected a call to service.connectDApp") let connectArgs = connectDAppSpy.signalArguments[0] compare(connectArgs[connectDAppSpy.argPos.dappChains], networksArray, "expected all provided networks (walletStore.filteredFlatModel) for the dappChains") compare(connectArgs[connectDAppSpy.argPos.dappUrl], Testing.dappUrl, "expected dappUrl to be set") compare(connectArgs[connectDAppSpy.argPos.dappName], Testing.dappName, "expected dappName to be set") compare(connectArgs[connectDAppSpy.argPos.dappIcon], Testing.dappFirstIcon, "expected dappIcon to be set") verify(!!connectArgs[connectDAppSpy.argPos.key], "expected key to be set") let selectedAccount = accountsModel.get(1).address service.approvePairSession(connectArgs[connectDAppSpy.argPos.key], connectArgs[connectDAppSpy.argPos.dappChains], selectedAccount) compare(sdk.buildApprovedNamespacesCalls.length, 2, "expected a call to sdk.buildApprovedNamespaces") const approvedArgs = sdk.buildApprovedNamespacesCalls[1] verify(!!approvedArgs.supportedNamespaces, "expected supportedNamespaces to be set") // We test here that only one account for all chains is provided let accountsForApproval = approvedArgs.supportedNamespaces.eip155.accounts compare(accountsForApproval.length, networksArray.length, "expect only one account per chain") compare(accountsForApproval[0], `eip155:${networksArray[0]}:${selectedAccount}`) compare(accountsForApproval[1], `eip155:${networksArray[1]}:${selectedAccount}`) let approvedNamespaces = JSON.parse(Testing.formatBuildApprovedNamespacesResult(networksArray, [selectedAccount])) sdk.buildApprovedNamespacesResult(approvedArgs.id, approvedNamespaces, "") compare(sdk.approveSessionCalls.length, 1, "expected a call to sdk.approveSession") verify(!!sdk.approveSessionCalls[0].sessionProposalJson, "expected sessionProposalJson to be set") verify(!!sdk.approveSessionCalls[0].approvedNamespaces, "expected approvedNamespaces to be set") let finalApprovedNamespaces = JSON.parse(Testing.formatApproveSessionResponse(networksArray, [selectedAccount.address])) sdk.approveSessionResult(connectArgs[connectDAppSpy.argPos.key], finalApprovedNamespaces, "") verify(store.addWalletConnectSessionCalls.length === 1) verify(store.addWalletConnectSessionCalls[0].sessionJson, "expected sessionJson to be set") verify(service.onApproveSessionResultTriggers.length === 1) verify(service.onApproveSessionResultTriggers[0].session, "expected session to be set") compare(service.onDisplayToastMessageTriggers.length, 1, "expected a success message to be displayed") verify(service.onDisplayToastMessageTriggers[0].type !== Constants.ephemeralNotificationType.danger, "expected no error") verify(service.onDisplayToastMessageTriggers[0].message, "expected message to be set") } function test_TestPairingUnsupportedNetworks() { const {sdk, store} = testSetupPair(Testing.formatSessionProposal()) const approvedArgs = sdk.buildApprovedNamespacesCalls[0] sdk.buildApprovedNamespacesResult(approvedArgs.id, {}, "Non conforming namespaces. approve() namespaces chains don't satisfy required namespaces") compare(connectDAppSpy.count, 0, "expected not to have calls to service.connectDApp") compare(service.onPairingValidatedTriggers.length, 1, "expected a call to service.onPairingValidated") compare(service.onPairingValidatedTriggers[0].validationState, Pairing.errors.unsupportedNetwork, "expected unsupportedNetwork state error") } function test_SessionRequestMainFlow() { // All calls to SDK are expected as events to be made by the wallet connect SDK const sdk = service.dappsModule.wcSdk const walletStore = service.walletRootStore const store = service.store const testAddress = "0x3a" const chainId = "2" const method = "personal_sign" const message = "hello world" const params = [`"${DAppsHelpers.strToHex(message)}"`, `"${testAddress}"`] const topic = "b536a" const session = JSON.parse(Testing.formatSessionRequest(chainId, method, params, topic)) populateDAppData(topic) sdk.sessionRequestEvent(session) const request = service.sessionRequestsModel.findById(session.id) verify(!!request, "expected request to be found") compare(request.topic, topic, "expected topic to be set") compare(request.method, method, "expected method to be set") compare(request.event, session, "expected event to be the one sent by the sdk") compare(request.dappName, Testing.dappName, "expected dappName to be set") compare(request.dappUrl, Testing.dappUrl, "expected dappUrl to be set") compare(request.dappIcon, Testing.dappFirstIcon, "expected dappIcon to be set") verify(!!request.accountAddress, "expected account to be set") compare(request.accountAddress, testAddress, "expected look up of the right account") verify(!!request.chainId, "expected network to be set") compare(request.chainId, chainId, "expected look up of the right network") verify(!!request.data, "expected data to be set") compare(request.data.message, message, "expected message to be set") } // TODO #14757: add tests with multiple session requests coming in; validate that authentication is serialized and in order // function tst_SessionRequestQueueMultiple() { // } } TestCase { name: "ServiceHelpers" function test_extractChainsAndAccountsFromApprovedNamespaces() { const res = DAppsHelpers.extractChainsAndAccountsFromApprovedNamespaces(JSON.parse(`{ "eip155": { "accounts": [ "eip155:1:0x1", "eip155:1:0x2", "eip155:2:0x1", "eip155:2:0x2" ], "chains": [ "eip155:1", "eip155:2" ], "events": [ "accountsChanged", "chainChanged" ], "methods": [ "eth_sendTransaction", "personal_sign" ] } }`)) verify(res.chains.length === 2) verify(res.accounts.length === 2) verify(res.chains[0] === 1) verify(res.chains[1] === 2) verify(res.accounts[0] === "0x1") verify(res.accounts[1] === "0x2") } readonly property ListModel chainsModel: ListModel { ListElement { chainId: 1 } ListElement { chainId: 2 } } readonly property ListModel accountsModel: ListModel { ListElement { address: "0x1" } ListElement { address: "0x2" } } function test_buildSupportedNamespacesFromModels() { const methods = ["eth_sendTransaction", "personal_sign"] const resStr = DAppsHelpers.buildSupportedNamespacesFromModels(chainsModel, accountsModel, methods) const jsonObj = JSON.parse(resStr) verify(jsonObj.hasOwnProperty("eip155")) const eip155 = jsonObj.eip155 verify(eip155.hasOwnProperty("chains")) const chains = eip155.chains verify(chains.length === 2) verify(chains[0] === "eip155:1") verify(chains[1] === "eip155:2") verify(eip155.hasOwnProperty("accounts")) const accounts = eip155.accounts verify(accounts.length === 4) for (let chainI = 0; chainI < chainsModel.count; chainI++) { for (let accountI = 0; accountI < chainsModel.count; accountI++) { var found = false for (const entry of accounts) { if(entry === `eip155:${chainsModel.get(chainI).chainId}:${accountsModel.get(accountI).address}`) { found = true break } } verify(found, `found ${accountsModel.get(accountI).address} for chain ${chainsModel.get(chainI).chainId}`) } } verify(eip155.hasOwnProperty("methods")) verify(eip155.methods.length > 0) verify(eip155.hasOwnProperty("events")) compare(eip155.events.length, 5) } function test_getAccountsInSession() { const account1 = accountsModel.get(0) const account2 = accountsModel.get(1) const chainIds = [chainsModel.get(0).chainId, chainsModel.get(1).chainId] const oneAccountSession = JSON.parse(Testing.formatApproveSessionResponse(chainIds, [account2.address])) const twoAccountsSession = JSON.parse(Testing.formatApproveSessionResponse(chainIds, ['0x03acc', account1.address])) const duplicateAccountsSession = JSON.parse(Testing.formatApproveSessionResponse(chainIds, ['0x83acb', '0x83acb'])) const res = DAppsHelpers.getAccountsInSession(oneAccountSession) compare(res.length, 1, "expected the only account to be returned") compare(res[0], account2.address, "expected the only account to be the one in the session") const res2 = DAppsHelpers.getAccountsInSession(twoAccountsSession) compare(res2.length, 2, "expected the two accounts to be returned") compare(res2[0], '0x03acc', "expected the first account to be the one in the session") compare(res2[1], account1.address, "expected the second account to be the one in the session") const res3 = DAppsHelpers.getAccountsInSession(duplicateAccountsSession) compare(res3.length, 1, "expected the only account to be returned") compare(res3[0], '0x83acb', "expected the duplicated account") } } Component { id: componentUnderTest DAppsWorkflow { loginType: Constants.LoginType.Password model: ListModel {} accountsModel: ListModel {} networksModel: ListModel {} sessionRequestsModel: SessionRequestsModel {} selectedAccountAddress: "" formatBigNumber: (number, symbol, noSymbolOption) => number + symbol } } // TODO #15151: this TestCase if placed before ServiceHelpers was not run with `when: windowShown`. Check if related to the CI crash TestCase { id: dappsWorkflowTest name: "DAppsWorkflow" when: windowShown property DAppsWorkflow controlUnderTest: null SignalSpy { id: pairWCReadySpy target: dappsWorkflowTest.controlUnderTest signalName: "pairWCReady" } SignalSpy { id: disconnectRequestedSpy target: dappsWorkflowTest.controlUnderTest signalName: "disconnectRequested" } SignalSpy { id: pairingRequestedSpy target: dappsWorkflowTest.controlUnderTest signalName: "pairingRequested" } SignalSpy { id: pairingValidationRequestedSpy target: dappsWorkflowTest.controlUnderTest signalName: "pairingValidationRequested" } SignalSpy { id: connectionAcceptedSpy target: dappsWorkflowTest.controlUnderTest signalName: "connectionAccepted" } SignalSpy { id: connectionDeclinedSpy target: dappsWorkflowTest.controlUnderTest signalName: "connectionDeclined" } SignalSpy { id: signRequestAcceptedSpy target: dappsWorkflowTest.controlUnderTest signalName: "signRequestAccepted" } SignalSpy { id: signRequestRejectedSpy target: dappsWorkflowTest.controlUnderTest signalName: "signRequestRejected" } function init() { controlUnderTest = createTemporaryObject(componentUnderTest, root) verify(!!controlUnderTest) } function cleanup() { pairWCReadySpy.clear() disconnectRequestedSpy.clear() pairingRequestedSpy.clear() pairingValidationRequestedSpy.clear() connectionAcceptedSpy.clear() connectionDeclinedSpy.clear() signRequestAcceptedSpy.clear() signRequestRejectedSpy.clear() } function test_OpenAndCloseDappList() { waitForRendering(controlUnderTest) mouseClick(controlUnderTest) waitForRendering(controlUnderTest) let popup = findChild(controlUnderTest, "dappsListPopup") verify(!!popup) verify(popup.opened) popup.close() waitForRendering(controlUnderTest) verify(!popup.opened) } function openPairModal() { waitForRendering(controlUnderTest) mouseClick(controlUnderTest) let popup = findChild(controlUnderTest, "dappsListPopup") verify(!!popup) verify(popup.opened) let connectButton = findChild(popup, "connectDappButton") verify(!!connectButton) mouseClick(connectButton) const btnWalletConnect = findChild(controlUnderTest, "btnWalletConnect") verify(!!btnWalletConnect) mouseClick(btnWalletConnect) verify(pairWCReadySpy.count === 1, "expected pairWCReady signal to be emitted") let pairWCModal = findChild(controlUnderTest, "pairWCModal") verify(!!pairWCModal) return pairWCModal } function test_OpenPairModal() { const pairWCModal = openPairModal() } function test_uriPairingSuccess() { const pairWCModal = openPairModal() //type: test url keyClick("a") keyClick("b") keyClick("c") keyClick("d") compare(pairingValidationRequestedSpy.count, 4, "expected pairingValidationRequested signal to be emitted") controlUnderTest.pairingValidated(Pairing.errors.uriOk) compare(pairingRequestedSpy.count, 1, "expected pairingRequested signal to be emitted") } function test_uriPairingFail() { const pairWCModal = openPairModal() //type: test url keyClick("a") keyClick("b") keyClick("c") compare(pairingValidationRequestedSpy.count, 3, "expected pairingValidationRequested signal to be emitted") controlUnderTest.pairingValidated(Pairing.errors.invalidUri) compare(pairingRequestedSpy.count, 0, "expected pairingRequested signal to not be emitted") } function test_OpenDappRequestModal() { waitForRendering(controlUnderTest) const request = buildSessionRequestResolved(dappsWorkflowTest, "0x1", "1", "b536a") controlUnderTest.accountsModel.append({ address: request.accountAddress, name: "helloworld", emoji: "😋", color: "#2A4AF5", keycardAccount: false }) controlUnderTest.networksModel.append({ chainId: request.chainId, chainName: "Test Chain", iconUrl: "network/Network=Ethereum", layer: 1 }) controlUnderTest.sessionRequestsModel.enqueue(request) waitForRendering(controlUnderTest) let popup = findChild(controlUnderTest, "dappsRequestModal") verify(!!popup) verify(popup.opened) verify(popup.visible) compare(popup.dappName, request.dappName) compare(popup.accountAddress, request.accountAddress) popup.close() waitForRendering(controlUnderTest) verify(!popup.opened) verify(!popup.visible) } function showRequestModal(topic, requestId) { waitForRendering(controlUnderTest) const request = buildSessionRequestResolved(dappsWorkflowTest, "0x1", "1", topic, requestId) controlUnderTest.accountsModel.append({ address: request.accountAddress, name: "helloworld", emoji: "😋", color: "#2A4AF5", keycardAccount: false }) controlUnderTest.networksModel.append({ chainId: request.chainId, chainName: "Test Chain", iconUrl: "network/Network=Ethereum", layer: 1 }) controlUnderTest.sessionRequestsModel.enqueue(request) waitForRendering(controlUnderTest) const popup = findChild(controlUnderTest, "dappsRequestModal") verify(popup.opened) const acceptButton = findChild(popup, "signButton") const rejectButton = findChild(popup, "rejectButton") // Workaround for the buttons not being aligned yet // Removing this could cause the wrong button to be clicked tryVerify(() => acceptButton.x > rejectButton.x + rejectButton.width) return popup } function test_RejectDappRequestModal() { const topic = "abcd" const requestId = "12345" let popup = showRequestModal(topic, requestId) let rejectButton = findChild(popup, "rejectButton") verify(!!rejectButton) mouseClick(rejectButton) compare(signRequestRejectedSpy.count, 1, "expected signRequestRejected signal to be emitted") compare(signRequestAcceptedSpy.count, 0, "expected signRequestAccepted signal to not be emitted") compare(signRequestRejectedSpy.signalArguments[0][0], topic, "expected id to be set") compare(signRequestRejectedSpy.signalArguments[0][1], requestId, "expected requestId to be set") verify(!popup.opened) verify(!popup.visible) } function test_AcceptDappRequestModal() { const topic = "abcd" const requestId = "12345" let popup = showRequestModal(topic, requestId) let signButton = findChild(popup, "signButton") mouseClick(signButton) compare(signRequestAcceptedSpy.count, 1, "expected signRequestAccepted signal to be emitted") compare(signRequestRejectedSpy.count, 0, "expected signRequestRejected signal to not be emitted") compare(signRequestAcceptedSpy.signalArguments[0][0], topic, "expected id to be set") compare(signRequestAcceptedSpy.signalArguments[0][1], requestId, "expected requestId to be set") waitForRendering(controlUnderTest) verify(!popup.opened) verify(!popup.visible) } function test_SignRequestExpired() { const topic = "abcd" const requestId = "12345" let popup = showRequestModal(topic, requestId) const request = controlUnderTest.sessionRequestsModel.findRequest(topic, requestId) verify(!!request) const countDownPill = findChild(popup, "countdownPill") verify(!!countDownPill) tryVerify(() => countDownPill.remainingSeconds > 0) // Hackish -> countdownPill internals ask for a refresh before going to expired state const remainingSeconds = countDownPill.remainingSeconds tryVerify(() => countDownPill.visible) tryVerify(() => countDownPill.remainingSeconds !== remainingSeconds) request.setExpired() tryVerify(() => countDownPill.isExpired) verify(countDownPill.visible) const signButton = findChild(popup, "signButton") const rejectButton = findChild(popup, "rejectButton") const closeButton = findChild(popup, "closeButton") tryVerify(() => !signButton.visible) verify(!rejectButton.visible) verify(closeButton.visible) } function test_SignRequestDoesWithoutExpiry() { const topic = "abcd" const requestId = "12345" let popup = showRequestModal(topic, requestId) const request = controlUnderTest.sessionRequestsModel.findRequest(topic, requestId) verify(!!request) request.expirationTimestamp = undefined const countDownPill = findChild(popup, "countdownPill") verify(!!countDownPill) tryVerify(() => !countDownPill.visible) request.setExpired() tryVerify(() => countDownPill.visible) const signButton = findChild(popup, "signButton") const rejectButton = findChild(popup, "rejectButton") const closeButton = findChild(popup, "closeButton") verify(signButton.visible) verify(rejectButton.visible) verify(!closeButton.visible) } function test_SignRequestModalAfterModelRemove() { const topic = "abcd" const requestId = "12345" let popup = showRequestModal(topic, requestId) const request = controlUnderTest.sessionRequestsModel.findRequest(topic, requestId) verify(!!request) controlUnderTest.sessionRequestsModel.removeRequest(topic, requestId) verify(!controlUnderTest.sessionRequestsModel.findRequest(topic, requestId)) waitForRendering(controlUnderTest) popup = findChild(controlUnderTest, "dappsRequestModal") verify(!popup) } } }