import QtQuick 2.15 import QtTest 1.15 import AppLayouts.Wallet.services.dapps 1.0 import AppLayouts.Wallet.services.dapps.plugins 1.0 import AppLayouts.Wallet.services.dapps.types 1.0 import shared.stores 1.0 import utils 1.0 Item { id: root Component { id: signRequestPluginComponent SignRequestPlugin { id: plugin property SignalSpy acceptedSpy: SignalSpy { target: plugin; signalName: "accepted" } property SignalSpy rejectedSpy: SignalSpy { target: plugin; signalName: "rejected" } property SignalSpy signCompletedSpy: SignalSpy { target: plugin; signalName: "signCompleted" } sdk: WalletConnectSDKBase { id: sdk enabled: true projectId: "" property bool sdkReady: true property var getActiveSessionsCallbacks: [] getActiveSessions: function(callback) { getActiveSessionsCallbacks.push({callback}) } 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}) } } store: DAppsStore { id: dappsStore signal userAuthenticated(string topic, string id, string password, string pin, string payload) 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) function hexToDec(hex) { return parseInt(hex, 16) } function getEstimatedTime() { return Constants.TransactionEstimatedTime.LessThanThreeMins } function convertFeesInfoToHex(feesInfo) { return null } property var mockedSuggestedFees: ({ gasPrice: 2.0, baseFee: 5.0, maxPriorityFeePerGas: 2.0, maxFeePerGasL: 1.0, maxFeePerGasM: 1.1, maxFeePerGasH: 1.2, l1GasFee: 0.0, eip1559Enabled: true }) function getSuggestedFees() { return mockedSuggestedFees } property var authenticateUserCalls: [] function authenticateUser(topic, id, address) { authenticateUserCalls.push({topic, id, address}) } property var signMessageCalls: [] function signMessage(topic, id, address, message, password, pin) { signMessageCalls.push({topic, id, address, password, pin}) } property var signMessageUnsafeCalls: [] function signMessageUnsafe(topic, id, address, data, password, pin) { signMessageUnsafeCalls.push({topic, id, address, data, password, pin}) } property var safeSignTypedDataCalls: [] function safeSignTypedData(topic, id, address, message, chainId, legacy, password, pin) { safeSignTypedDataCalls.push({topic, id, address, message, chainId, legacy, password, pin}) } property var signTransactionCalls: [] function signTransaction(topic, id, address, chainId, txObj, password, pin) { signTransactionCalls.push({topic, id, address, chainId, txObj, password, pin}) } property var sendTransactionCalls: [] function sendTransaction(topic, id, address, chainID, txObj, password, pin) { sendTransactionCalls.push({topic, id, address, chainID, txObj, password, pin}) } } dappsModel: ListModel { id: dappsModel } groupedAccountAssetsModel: ListModel { id: groupedAccountAssetsModel } networksModel: ListModel { id: networksModel ListElement { chainId: 1 layer: 1 } } accountsModel: ListModel { id: accountsModel ListElement { address: "0x123" } } requests: SessionRequestsModel {} getFiatValue: (balance, cryptoSymbol) => { return parseFloat(balance) } } } TestCase { id: signRequestPluginTest property SignRequestPlugin componentUnderTest: null function populateDAppData(topic) { const dapp = { topic, name: "Example", url: "https://example.com", iconUrl: "https://example.com/icon.png", connectorId: 0, accountAddresses: [{address: "0x123"}], rawSessions: [{session: {topic}}] } componentUnderTest.dappsModel.append(dapp) } function executeRequest(signEvent) { populateDAppData(signEvent.topic) componentUnderTest.sdk.sessionRequestEvent(signEvent) // Execute the request const request = componentUnderTest.requests.get(0) request.requestItem.execute("passowrd", "pin") componentUnderTest.store.signingResult(signEvent.topic, signEvent.id, "result") compare(componentUnderTest.sdk.acceptSessionRequestCalls.length, 1, "Accept session request should be called") compare(componentUnderTest.sdk.acceptSessionRequestCalls[0].signature, "result", "Accept session request should be called with the correct signature") compare(componentUnderTest.sdk.acceptSessionRequestCalls[0].topic.toString(), signEvent.topic.toString(), "Accept session request should be called with the correct topic") compare(componentUnderTest.sdk.acceptSessionRequestCalls[0].id.toString(), signEvent.id.toString(), "Accept session request should be called with the correct id") compare(componentUnderTest.acceptedSpy.count, 1, "Accepted signal should be emitted") } function init() { componentUnderTest = createTemporaryObject(signRequestPluginComponent, root) } function test_signMessage() { const signEvent = {"id":1730896110928724,"params":{"chainId":"eip155:1","request":{"method":"personal_sign","params":["0x4578616d706c652060706572736f6e616c5f7369676e60206d657373616765","0x123","Example password"]}},"topic":"43a74a4c6c71e3ab67ef80283dc43f392445642c8dce3dabe63f89ab83cfcfc3","verifyContext":{"verified":{"origin":"https://metamask.github.io/test-dapp/","validation":"UNKNOWN","verifyUrl":"https://verify.walletconnect.org"}}} // Case 1: DApp not found ignoreWarning(`Error finding dapp for topic ${signEvent.topic} id ${signEvent.id}`) componentUnderTest.sdk.sessionRequestEvent(signEvent) compare(componentUnderTest.sdk.rejectSessionRequestCalls.length, 1, "Reject session request should not be called") // Case 2: DApp found executeRequest(signEvent) compare(componentUnderTest.store.signMessageCalls.length, 1, "Sign message should be called") componentUnderTest.sdk.sessionRequestUserAnswerResult(signEvent.topic, signEvent.id, true, null) const requestItem = componentUnderTest.requests.get(0).requestItem compare(requestItem.requestId, signEvent.id.toString(), "Request id should be set") compare(requestItem.topic, signEvent.topic.toString(), "Topic should be set") compare(requestItem.method, signEvent.params.request.method, "Method should be set") compare(requestItem.accountAddress, signEvent.params.request.params[1], "Account address should be set") compare(requestItem.chainId, signEvent.params.chainId.split(':').pop().trim(), "Chain id should be set") compare(requestItem.sourceId, componentUnderTest.dappsModel.get(0).connectorId, "Source id should be set") compare(componentUnderTest.signCompletedSpy.count, 1, "Sign completed signal should be emitted") } function test_signMessageUnsafe() { const signEvent = {"id":1730896224189361,"params":{"chainId":"eip155:1","request":{"method":"eth_sign","params":["0x123","0x123"]}},"topic":"43a74a4c6c71e3ab67ef80283dc43f392445642c8dce3dabe63f89ab83cfcfc3","verifyContext":{"verified":{"origin":"https://metamask.github.io/test-dapp/","validation":"UNKNOWN","verifyUrl":"https://verify.walletconnect.org"}}} executeRequest(signEvent) compare(componentUnderTest.store.signMessageUnsafeCalls.length, 1, "Sign message unsafe should be called") componentUnderTest.sdk.sessionRequestUserAnswerResult(signEvent.topic, signEvent.id, true, null) compare(componentUnderTest.signCompletedSpy.count, 1, "Sign completed signal should be emitted") } function test_safeSignTypedData() { const signEvent = {"id":1730896495619543,"params":{"chainId":"eip155:1","request":{"method":"eth_signTypedData_v4","params":["0x123","{\"domain\":{\"chainId\":\"1\",\"name\":\"Ether Mail\",\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\",\"version\":\"1\"},\"message\":{\"contents\":\"Hello, Bob!\",\"from\":{\"name\":\"Cow\",\"wallets\":[\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\",\"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF\"]},\"to\":[{\"name\":\"Bob\",\"wallets\":[\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\",\"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57\",\"0xB0B0b0b0b0b0B000000000000000000000000000\"]}],\"attachment\":\"0x\"},\"primaryType\":\"Mail\",\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Group\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"members\",\"type\":\"Person[]\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person[]\"},{\"name\":\"contents\",\"type\":\"string\"},{\"name\":\"attachment\",\"type\":\"bytes\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallets\",\"type\":\"address[]\"}]}}"]}},"topic":"43a74a4c6c71e3ab67ef80283dc43f392445642c8dce3dabe63f89ab83cfcfc3","verifyContext":{"verified":{"origin":"https://metamask.github.io/test-dapp/","validation":"UNKNOWN","verifyUrl":"https://verify.walletconnect.org"}}} executeRequest(signEvent) compare(componentUnderTest.store.safeSignTypedDataCalls.length, 1, "Safe sign typed data should be called") componentUnderTest.sdk.sessionRequestUserAnswerResult(signEvent.topic, signEvent.id, true, null) compare(componentUnderTest.signCompletedSpy.count, 1, "Sign completed signal should be emitted") } function test_sendTransaction() { const signEvent = {"id":1730899979094571,"params":{"chainId":"eip155:1","request":{"method":"eth_sendTransaction","params":[{"from":"0x123","gasLimit":"0x5028","maxFeePerGas":"0x2540be400","maxPriorityFeePerGas":"0x3b9aca00","to":"0x0c54FcCd2e384b4BB6f2E405Bf5Cbc15a017AaFb","value":"0x0"}]}},"topic":"147fa9ddffcf9d782b5e002eb36b041e43a1db2bad79a598da6926105ce6680f","verifyContext":{"verified":{"origin":"https://metamask.github.io/test-dapp/","validation":"UNKNOWN","verifyUrl":"https://verify.walletconnect.org"}}} executeRequest(signEvent) compare(componentUnderTest.store.sendTransactionCalls.length, 1, "Send transaction should be called") componentUnderTest.sdk.sessionRequestUserAnswerResult(signEvent.topic, signEvent.id, true, null) compare(componentUnderTest.signCompletedSpy.count, 1, "Sign completed signal should be emitted") } function reject_sign() { const signEvent = {"id":1730896495619543,"params":{"chainId":"eip155:1","request":{"method":"eth_signTypedData_v4","params":["0x123","{\"domain\":{\"chainId\":\"1\",\"name\":\"Ether Mail\",\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\",\"version\":\"1\"},\"message\":{\"contents\":\"Hello, Bob!\",\"from\":{\"name\":\"Cow\",\"wallets\":[\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\",\"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF\"]},\"to\":[{\"name\":\"Bob\",\"wallets\":[\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\",\"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57\",\"0xB0B0b0b0b0b0B000000000000000000000000000\"]}],\"attachment\":\"0x\"},\"primaryType\":\"Mail\",\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Group\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"members\",\"type\":\"Person[]\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person[]\"},{\"name\":\"contents\",\"type\":\"string\"},{\"name\":\"attachment\",\"type\":\"bytes\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallets\",\"type\":\"address[]\"}]}}"]}},"topic":"43a74a4c6c71e3ab67ef80283dc43f392445642c8dce3dabe63f89ab83cfcfc3","verifyContext":{"verified":{"origin":"https://metamask.github.io/test-dapp/","validation":"UNKNOWN","verifyUrl":"https://verify.walletconnect.org"}}} populateDAppData(signEvent.topic) componentUnderTest.sdk.sessionRequestEvent(signEvent) // Execute the request const request = componentUnderTest.requests.get(0) request.requestItem.rejected(false) compare(componentUnderTest.sdk.rejectSessionRequestCalls.length, 1, "Reject session request should be called") compare(componentUnderTest.rejectedSpy.count, 1, "Rejected signal should be emitted") compare(componentUnderTest.signCompletedSpy.count, 0, "Sign completed signal should not be emitted") } function test_authFailed() { const signEvent = {"id":1730896495619543,"params":{"chainId":"eip155:1","request":{"method":"eth_signTypedData_v4","params":["0x123","{\"domain\":{\"chainId\":\"1\",\"name\":\"Ether Mail\",\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\",\"version\":\"1\"},\"message\":{\"contents\":\"Hello, Bob!\",\"from\":{\"name\":\"Cow\",\"wallets\":[\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\",\"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF\"]},\"to\":[{\"name\":\"Bob\",\"wallets\":[\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\",\"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57\",\"0xB0B0b0b0b0b0B000000000000000000000000000\"]}],\"attachment\":\"0x\"},\"primaryType\":\"Mail\",\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Group\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"members\",\"type\":\"Person[]\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person[]\"},{\"name\":\"contents\",\"type\":\"string\"},{\"name\":\"attachment\",\"type\":\"bytes\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallets\",\"type\":\"address[]\"}]}}"]}},"topic":"43a74a4c6c71e3ab67ef80283dc43f392445642c8dce3dabe63f89ab83cfcfc3","verifyContext":{"verified":{"origin":"https://metamask.github.io/test-dapp/","validation":"UNKNOWN","verifyUrl":"https://verify.walletconnect.org"}}} populateDAppData(signEvent.topic) componentUnderTest.sdk.sessionRequestEvent(signEvent) // Execute the request const request = componentUnderTest.requests.get(0) request.requestItem.authFailed() compare(componentUnderTest.sdk.rejectSessionRequestCalls.length, 1, "Reject session request should be called") compare(componentUnderTest.rejectedSpy.count, 1, "Rejected signal should be emitted") compare(componentUnderTest.signCompletedSpy.count, 0, "Sign completed signal should not be emitted") } function test_signMessageFails() { const signEvent = {"id":1730896495619543,"params":{"chainId":"eip155:1","request":{"method":"eth_signTypedData_v4","params":["0x123","{\"domain\":{\"chainId\":\"1\",\"name\":\"Ether Mail\",\"verifyingContract\":\"0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC\",\"version\":\"1\"},\"message\":{\"contents\":\"Hello, Bob!\",\"from\":{\"name\":\"Cow\",\"wallets\":[\"0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826\",\"0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF\"]},\"to\":[{\"name\":\"Bob\",\"wallets\":[\"0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB\",\"0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57\",\"0xB0B0b0b0b0b0B000000000000000000000000000\"]}],\"attachment\":\"0x\"},\"primaryType\":\"Mail\",\"types\":{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"Group\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"members\",\"type\":\"Person[]\"}],\"Mail\":[{\"name\":\"from\",\"type\":\"Person\"},{\"name\":\"to\",\"type\":\"Person[]\"},{\"name\":\"contents\",\"type\":\"string\"},{\"name\":\"attachment\",\"type\":\"bytes\"}],\"Person\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"wallets\",\"type\":\"address[]\"}]}}"]}},"topic":"43a74a4c6c71e3ab67ef80283dc43f392445642c8dce3dabe63f89ab83cfcfc3","verifyContext":{"verified":{"origin":"https://metamask.github.io/test-dapp/","validation":"UNKNOWN","verifyUrl":"https://verify.walletconnect.org"}}} populateDAppData(signEvent.topic) componentUnderTest.sdk.sessionRequestEvent(signEvent) // Execute the request const request = componentUnderTest.requests.get(0) request.requestItem.execute("passowrd", "pin") componentUnderTest.store.signingResult(signEvent.topic, signEvent.id, "") compare(componentUnderTest.sdk.rejectSessionRequestCalls.length, 1, "Reject session request should be called") compare(componentUnderTest.rejectedSpy.count, 1, "Rejected signal should be emitted") compare(componentUnderTest.signCompletedSpy.count, 0, "Sign completed signal should not be emitted") } } }