From 4f65286ead92f46c9c9d19e29b1b579b514ef76a Mon Sep 17 00:00:00 2001 From: Sale Djenic Date: Wed, 9 Feb 2022 11:22:24 +0100 Subject: [PATCH] refactor(@desktop/browser): move provider logic to `status-go` Fixes #4693 --- src/app/boot/app_controller.nim | 3 +- src/app/global/utils.nim | 14 +- .../module_controller_delegate_interface.nim | 3 - .../module_view_delegate_interface.nim | 5 +- .../browser_section/provider/controller.nim | 6 +- .../main/browser_section/provider/module.nim | 4 +- src/app_service/common/network_constants.nim | 2 +- src/app_service/service/provider/service.nim | 264 +----------------- .../service/provider/service_interface.nim | 7 +- src/backend/provider.nim | 3 +- ui/app/AppLayouts/Browser/BrowserLayout.qml | 2 +- .../Browser/popups/BrowserConnectionModal.qml | 6 +- .../AppLayouts/Browser/stores/RootStore.qml | 8 + .../Browser/views/WebProviderObj.qml | 23 +- 14 files changed, 67 insertions(+), 283 deletions(-) diff --git a/src/app/boot/app_controller.nim b/src/app/boot/app_controller.nim index abc32e39d0..dca63ceea5 100644 --- a/src/app/boot/app_controller.nim +++ b/src/app/boot/app_controller.nim @@ -178,8 +178,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController = result.ensService = ens_service.newService(statusFoundation.events, statusFoundation.threadpool, result.settingsService, result.walletAccountService, result.transactionService, result.ethService, result.networkService, result.tokenService) - result.providerService = provider_service.newService(result.dappPermissionsService, - result.settingsService, result.ensService) + result.providerService = provider_service.newService(result.ensService) # Modules result.startupModule = startup_module.newModule[AppController]( diff --git a/src/app/global/utils.nim b/src/app/global/utils.nim index 1617441348..a3fc4d3842 100644 --- a/src/app/global/utils.nim +++ b/src/app/global/utils.nim @@ -3,7 +3,7 @@ import stew/byteutils import ./utils/qrcodegen # Services as instances shouldn't be used in this class, just some general/global procs -import ../../app_service/service/eth/utils as procs_from_utils +import ../../app_service/common/conversion import ../../app_service/service/accounts/service as procs_from_accounts @@ -30,14 +30,17 @@ QtObject: result = url_fromUserInput(input) proc eth2Wei*(self: Utils, eth: string, decimals: int): string {.slot.} = - let uintValue = procs_from_utils.eth2Wei(parseFloat(eth), decimals) + let uintValue = conversion.eth2Wei(parseFloat(eth), decimals) return uintValue.toString() + proc eth2Hex*(self: Utils, eth: float): string {.slot.} = + return "0x" & conversion.eth2Wei(eth, 18).toHex() + proc wei2Eth*(self: Utils, wei: string, decimals: int): string {.slot.} = var weiValue = wei if(weiValue.startsWith("0x")): weiValue = fromHex(Stuint[256], weiValue).toString() - return procs_from_utils.wei2Eth(weiValue, decimals) + return conversion.wei2Eth(weiValue, decimals) proc hex2Ascii*(self: Utils, value: string): string {.slot.} = result = string.fromBytes(hexToSeqByte(value)) @@ -52,7 +55,10 @@ QtObject: return str proc hex2Eth*(self: Utils, value: string): string {.slot.} = - return stripTrailingZeroes(procs_from_utils.wei2Eth(stint.fromHex(StUint[256], value))) + return stripTrailingZeroes(conversion.wei2Eth(stint.fromHex(StUint[256], value))) + + proc gwei2Hex*(self: Utils, gwei: float): string {.slot.} = + return "0x" & conversion.gwei2Wei(gwei).toHex() proc hex2Dec*(self: Utils, value: string): string {.slot.} = # somehow this value crashes the app diff --git a/src/app/modules/main/browser_section/dapps/private_interfaces/module_controller_delegate_interface.nim b/src/app/modules/main/browser_section/dapps/private_interfaces/module_controller_delegate_interface.nim index 39001552f6..918e33c85d 100644 --- a/src/app/modules/main/browser_section/dapps/private_interfaces/module_controller_delegate_interface.nim +++ b/src/app/modules/main/browser_section/dapps/private_interfaces/module_controller_delegate_interface.nim @@ -1,9 +1,6 @@ method hasPermission*(self: AccessInterface, hostname: string, permission: string): bool = raise newException(ValueError, "No implementation available") -method addPermission*(self: AccessInterface, hostname: string, permission: string) = - raise newException(ValueError, "No implementation available") - method clearPermissions*(self: AccessInterface, dapp: string) = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/browser_section/dapps/private_interfaces/module_view_delegate_interface.nim b/src/app/modules/main/browser_section/dapps/private_interfaces/module_view_delegate_interface.nim index 2c8910abd9..75eb1fc394 100644 --- a/src/app/modules/main/browser_section/dapps/private_interfaces/module_view_delegate_interface.nim +++ b/src/app/modules/main/browser_section/dapps/private_interfaces/module_view_delegate_interface.nim @@ -1,2 +1,5 @@ method viewDidLoad*(self: AccessInterface) {.base.} = - raise newException(ValueError, "No implementation available") + raise newException(ValueError, "No implementation available") + +method addPermission*(self: AccessInterface, hostname: string, permission: string) = + raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/browser_section/provider/controller.nim b/src/app/modules/main/browser_section/provider/controller.nim index 5e7c79e118..5b664d7ebd 100644 --- a/src/app/modules/main/browser_section/provider/controller.nim +++ b/src/app/modules/main/browser_section/provider/controller.nim @@ -2,9 +2,9 @@ import strutils import controller_interface import io_interface -import ../../../../../app_service/service/settings/service as settings_service +import ../../../../../app_service/service/settings/service_interface as settings_service import ../../../../../app_service/service/dapp_permissions/service as dapp_permissions_service -import ../../../../../app_service/service/provider/service as provider_service +import ../../../../../app_service/service/provider/service_interface as provider_service export controller_interface type @@ -45,7 +45,7 @@ method disconnect*(self: Controller) = discard self.dappPermissionsService.revoke("web3".toPermission()) method postMessage*(self: Controller, requestType: string, message: string): string = - return self.providerService.postMessage(parseEnum[RequestTypes](requestType), message) + return self.providerService.postMessage(requestType, message) method hasPermission*(self: Controller, hostname: string, permission: string): bool = return self.dappPermissionsService.hasPermission(hostname, permission.toPermission()) diff --git a/src/app/modules/main/browser_section/provider/module.nim b/src/app/modules/main/browser_section/provider/module.nim index e6463ac071..9de1046353 100644 --- a/src/app/modules/main/browser_section/provider/module.nim +++ b/src/app/modules/main/browser_section/provider/module.nim @@ -3,9 +3,9 @@ import io_interface import view import controller import ../io_interface as delegate_interface -import ../../../../../app_service/service/settings/service as settings_service +import ../../../../../app_service/service/settings/service_interface as settings_service import ../../../../../app_service/service/dapp_permissions/service as dapp_permissions_service -import ../../../../../app_service/service/provider/service as provider_service +import ../../../../../app_service/service/provider/service_interface as provider_service import ../../../../global/global_singleton export io_interface diff --git a/src/app_service/common/network_constants.nim b/src/app_service/common/network_constants.nim index 4efd275d1e..10f486d3d0 100644 --- a/src/app_service/common/network_constants.nim +++ b/src/app_service/common/network_constants.nim @@ -151,7 +151,7 @@ var NODE_CONFIG* = %* { "VerifyTransactionURL": "https://mainnet.infura.io/v3/" & INFURA_TOKEN_RESOLVED }, "Web3ProviderConfig": { - "Enabled": true + "Enabled": true }, "EnsConfig": { "Enabled": true diff --git a/src/app_service/service/provider/service.nim b/src/app_service/service/provider/service.nim index 326c93200a..97afbeb702 100644 --- a/src/app_service/service/provider/service.nim +++ b/src/app_service/service/provider/service.nim @@ -1,275 +1,39 @@ -import Tables, json, sequtils, chronicles -import sets -import options -import strutils -include ../../common/json_utils -import ../dapp_permissions/service as dapp_permissions_service -import ../ens/service as ens_service -import ../settings/service_interface as settings_service -import ../ens/utils as ens_utils +import json, chronicles + import service_interface -import ../../../backend/permissions as status_go_permissions -import ../../../backend/core as status_go_core -import ../../../backend/eth as status_eth -import ../../common/utils as status_utils -import ../eth/utils as eth_utils -import ../eth/dto/transaction as transaction_data_dto -import stew/byteutils +import ../../../backend/provider as status_go_provider +import ../ens/service as ens_service + export service_interface logScope: topics = "provider-service" -const AUTH_METHODS = toHashSet(["eth_accounts", "eth_coinbase", "eth_sendTransaction", "eth_sign", "keycard_signTypedData", "eth_signTypedData", "eth_signTypedData_v3", "personal_sign", "personal_ecRecover"]) -const SIGN_METHODS = toHashSet(["eth_sign", "personal_sign", "eth_signTypedData", "eth_signTypedData_v3"]) -const ACC_METHODS = toHashSet(["eth_accounts", "eth_coinbase"]) - -proc requestType(message: string): RequestTypes = - let data = message.parseJson - result = RequestTypes.Unknown - try: - result = parseEnum[RequestTypes](data["type"].getStr()) - except: - warn "Unknown request type received", value=data["permission"].getStr() - - -proc toWeb3SendAsyncReadOnly(message: string): Web3SendAsyncReadOnly = - let data = message.parseJson - result = Web3SendAsyncReadOnly( - messageId: data["messageId"], - request: $data["payload"], - hostname: data{"hostname"}.getStr(), - payload: Payload( - id: data["payload"]{"id"}, - rpcMethod: data["payload"]["method"].getStr() - ) - ) - -proc toAPIRequest(message: string): APIRequest = - let data = message.parseJson - - result = APIRequest( - messageId: data["messageId"], - isAllowed: data{"isAllowed"}.getBool(), - permission: data["permission"].getStr().toPermission(), - hostname: data{"hostname"}.getStr() - ) +const HTTPS_SCHEME* = "https" type Service* = ref object of service_interface.ServiceInterface - dappPermissionsService: dapp_permissions_service.ServiceInterface - settingsService: settings_service.ServiceInterface ensService: ens_service.Service method delete*(self: Service) = discard -proc newService*( - dappPermissionsService: dapp_permissions_service.ServiceInterface, - settingsService: settings_service.ServiceInterface, - ensService: ens_service.Service - ): Service = +proc newService*(ensService: ens_service.Service): Service = result = Service() - result.dappPermissionsService = dappPermissionsService - result.settingsService = settingsService result.ensService = ensService method init*(self: Service) = discard -proc process(self: Service, data: Web3SendAsyncReadOnly): string = - if AUTH_METHODS.contains(data.payload.rpcMethod) and not self.dappPermissionsService.hasPermission(data.hostname, Permission.Web3): - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "error": { - "code": 4100 - } - } - - if data.payload.rpcMethod == "eth_sendTransaction": - try: - let request = data.request.parseJson - let fromAddress = request["params"][0]["from"].getStr() - let to = request["params"][0]{"to"}.getStr() - let value = if (request["params"][0]["value"] != nil): - request["params"][0]["value"].getStr() - else: - "0" - let password = request["password"].getStr() - let selectedGasLimit = request["selectedGasLimit"].getStr() - let selectedGasPrice = request["selectedGasPrice"].getStr() - let selectedTipLimit = request{"selectedTipLimit"}.getStr() - let selectedOverallLimit = request{"selectedOverallLimit"}.getStr() - let txData = if (request["params"][0].hasKey("data") and request["params"][0]["data"].kind != JNull): - request["params"][0]["data"].getStr() - else: - "" - - var success: bool - var errorMessage = "" - var response: RpcResponse[JsonNode] - var validInput: bool = true - - let eip1559Enabled: bool = self.settingsService.isEIP1559Enabled() - - try: - eth_utils.validateTransactionInput(fromAddress, to, "", value, selectedGasLimit, selectedGasPrice, txData, eip1559Enabled, selectedTipLimit, selectedOverallLimit, "dummy") - except Exception as e: - validInput = false - success = false - errorMessage = e.msg - - if validInput: - # TODO make this async - var tx = ens_utils.buildTransaction( - parseAddress(fromAddress), - eth2Wei(parseFloat(value), 18), - selectedGasLimit, - selectedGasPrice, - eip1559Enabled, - selectedTipLimit, - selectedOverallLimit, - txData - ) - tx.to = parseAddress(to).some - - try: - # TODO: use the transaction service to send the trx - let json: JsonNode = %tx - response = status_eth.sendTransaction($json, password) - if response.error != nil: - success = false - errorMessage = response.error.message - else: - success = true - errorMessage = "" - except Exception as e: - error "Error sending transaction", msg = e.msg - errorMessage = e.msg - success = false - - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "error": errorMessage, - "result": { - "jsonrpc": "2.0", - "id": data.payload.id, - "result": if (success): response.result.getStr else: "" - } - } - - except Exception as e: - error "Error sending the transaction", msg = e.msg - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "error": { - "code": 4100, - "message": e.msg - } - } - - if SIGN_METHODS.contains(data.payload.rpcMethod): - try: - let request = data.request.parseJson - var params = request["params"] - let password = status_utils.hashPassword(request["password"].getStr()) - let dappAddress = self.settingsService.getDappsAddress() - var rpcResult = "{}" - - case data.payload.rpcMethod: - of "eth_signTypedData", "eth_signTypedData_v3": - rpcResult = signTypedData(params[1].getStr(), dappAddress, password) - else: - rpcResult = signMessage($ %* { - "data": params[0].getStr(), - "password": password, - "account": dappAddress - }) - - let jsonRpcResult = rpcResult.parseJson - let success: bool = not jsonRpcResult.hasKey("error") - let errorMessage = if success: "" else: jsonRpcResult["error"]{"message"}.getStr() - let response = if success: jsonRpcResult["result"].getStr() else: "" - - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "error": errorMessage, - "result": { - "jsonrpc": "2.0", - "id": if data.payload.id == nil: newJNull() else: data.payload.id, - "result": if (success): response else: "" - } - } - - except Exception as e: - error "Error signing message", msg = e.msg - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "error": { - "code": 4100, - "message": e.msg - } - } - - - - if ACC_METHODS.contains(data.payload.rpcMethod): - let dappAddress = self.settingsService.getDappsAddress() - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "result": { - "jsonrpc": "2.0", - "id": data.payload.id, - "result": if data.payload.rpcMethod == "eth_coinbase": newJString(dappAddress) else: %*[dappAddress] - } - } - - let rpcResult = callRPC(data.request) - - return $ %* { - "type": ResponseTypes.Web3SendAsyncCallback, - "messageId": data.messageId, - "error": (if rpcResult == "": newJString("web3-response-error") else: newJNull()), - "result": rpcResult.parseJson - } - -proc process(self: Service, data: APIRequest): string = - var value:JsonNode = case data.permission - of Permission.Web3: %* [self.settingsService.getDappsAddress()] - of Permission.ContactCode: %* self.settingsService.getPublicKey() - of Permission.Unknown: newJNull() - - let isAllowed = data.isAllowed and data.permission != Permission.Unknown - - info "API request received", host=data.hostname, value=data.permission, isAllowed - - if isAllowed: - discard self.dappPermissionsService.addPermission(data.hostname, data.permission) - - return $ %* { - "type": ResponseTypes.APIResponse, - "isAllowed": isAllowed, - "permission": data.permission, - "messageId": data.messageId, - "data": value - } - -method postMessage*(self: Service, requestType: RequestTypes, message: string): string = - case requestType: - of RequestTypes.Web3SendAsyncReadOnly: self.process(message.toWeb3SendAsyncReadOnly()) - of RequestTypes.HistoryStateChanged: """{"type":"TODO-IMPLEMENT-THIS"}""" ############# TODO: - of RequestTypes.APIRequest: self.process(message.toAPIRequest()) - else: """{"type":"TODO-IMPLEMENT-THIS"}""" ##################### TODO: - method ensResourceURL*(self: Service, username: string, url: string): (string, string, string, string, bool) = let (scheme, host, path) = self.ensService.resourceUrl(username) if host == "": return (url, url, HTTPS_SCHEME, "", false) + return (url, host, scheme, path, true) - return (url, host, scheme, path, true) \ No newline at end of file +method postMessage*(self: Service, requestType: string, message: string): string = + try: + return $status_go_provider.providerRequest(requestType, message).result + except Exception as e: + let errDescription = e.msg + error "error: ", errDescription \ No newline at end of file diff --git a/src/app_service/service/provider/service_interface.nim b/src/app_service/service/provider/service_interface.nim index 5e39c87026..4fabb60406 100644 --- a/src/app_service/service/provider/service_interface.nim +++ b/src/app_service/service/provider/service_interface.nim @@ -1,7 +1,4 @@ -import dto -export dto - -type +type ServiceInterface* {.pure inheritable.} = ref object of RootObj ## Abstract class for this service access. @@ -11,7 +8,7 @@ method delete*(self: ServiceInterface) {.base.} = method init*(self: ServiceInterface) {.base.} = raise newException(ValueError, "No implementation available") -method postMessage*(self: ServiceInterface, requestType: RequestTypes, message: string): string {.base.} = +method postMessage*(self: ServiceInterface, requestType: string, message: string): string {.base.} = raise newException(ValueError, "No implementation available") method ensResourceURL*(self: ServiceInterface, ens: string, url: string): (string, string, string, string, bool) = diff --git a/src/backend/provider.nim b/src/backend/provider.nim index d4e6ad0740..4261e014de 100644 --- a/src/backend/provider.nim +++ b/src/backend/provider.nim @@ -1,5 +1,4 @@ -import json, json_serialization, strformat, chronicles -import status_go +import json, json_serialization, chronicles import ./utils import ./core diff --git a/ui/app/AppLayouts/Browser/BrowserLayout.qml b/ui/app/AppLayouts/Browser/BrowserLayout.qml index bdba5e3f36..2974be85b1 100644 --- a/ui/app/AppLayouts/Browser/BrowserLayout.qml +++ b/ui/app/AppLayouts/Browser/BrowserLayout.qml @@ -495,7 +495,7 @@ Rectangle { } onDisconnect: { Web3ProviderStore.web3ProviderInst.disconnect() - provider.postMessage(`{"type":"web3-disconnect-account"}`) + provider.postMessage("web3-disconnect-account", "{}"); close() } } diff --git a/ui/app/AppLayouts/Browser/popups/BrowserConnectionModal.qml b/ui/app/AppLayouts/Browser/popups/BrowserConnectionModal.qml index 7f8645765e..9049106c8d 100644 --- a/ui/app/AppLayouts/Browser/popups/BrowserConnectionModal.qml +++ b/ui/app/AppLayouts/Browser/popups/BrowserConnectionModal.qml @@ -31,9 +31,11 @@ StatusModal { function postMessage(isAllowed){ interactedWith = true - request.isAllowed = isAllowed; RootStore.currentTabConnected = isAllowed - web3Response(Web3ProviderStore.web3ProviderInst.postMessage(Constants.api_request, JSON.stringify(request))) + if(isAllowed){ + Web3ProviderStore.addPermission(request.hostname, request.permission) + } + browserConnectionModal.web3Response(Web3ProviderStore.web3ProviderInst.postMessage(Constants.api_request, JSON.stringify(request))) } onClosed: { diff --git a/ui/app/AppLayouts/Browser/stores/RootStore.qml b/ui/app/AppLayouts/Browser/stores/RootStore.qml index 66b7583f7d..3118ff6850 100644 --- a/ui/app/AppLayouts/Browser/stores/RootStore.qml +++ b/ui/app/AppLayouts/Browser/stores/RootStore.qml @@ -31,6 +31,14 @@ QtObject { return globalUtils.wei2Eth(wei,decimals) } + function getEth2Hex(eth) { + return globalUtils.eth2Hex(eth) + } + + function getGwei2Hex(gwei){ + return globalUtils.gwei2Hex(gwei) + } + function generateIdenticon(pk) { return globalUtils.generateIdenticon(pk) } diff --git a/ui/app/AppLayouts/Browser/views/WebProviderObj.qml b/ui/app/AppLayouts/Browser/views/WebProviderObj.qml index fafdad0fd0..4a89139e56 100644 --- a/ui/app/AppLayouts/Browser/views/WebProviderObj.qml +++ b/ui/app/AppLayouts/Browser/views/WebProviderObj.qml @@ -41,7 +41,10 @@ QtObject { request.hostname = ensAddr; } - if (requestType === Constants.api_request) { + if (requestType === Constants.web3DisconnectAccount) { + RootStore.currentTabConnected = true + web3Response(JSON.stringify({type: Constants.web3DisconnectAccount})); + } else if (requestType === Constants.api_request) { if (!Web3ProviderStore.web3ProviderInst.hasPermission(request.hostname, request.permission)) { RootStore.currentTabConnected = false var dialog = createAccessDialogComponent() @@ -49,7 +52,6 @@ QtObject { dialog.open(); } else { RootStore.currentTabConnected = true - request.isAllowed = true web3Response(Web3ProviderStore.web3ProviderInst.postMessage(requestType, JSON.stringify(request))); } } else if (requestType === Constants.web3SendAsyncReadOnly && @@ -59,12 +61,19 @@ QtObject { const sendDialog = createSendTransactionModalComponent(request) sendDialog.sendTransaction = function (selectedGasLimit, selectedGasPrice, selectedTipLimit, selectedOverallLimit, enteredPassword) { - request.payload.selectedGasLimit = selectedGasLimit - request.payload.selectedGasPrice = selectedGasPrice - request.payload.selectedTipLimit = selectedTipLimit - request.payload.selectedOverallLimit = selectedOverallLimit + let trx = request.payload.params[0] + // TODO: use bignumber instead of floats + trx.value = RootStore.getEth2Hex(parseFloat(value)) + trx.gas = "0x" + parseInt(selectedGasLimit, 10).toString(16) + if (walletModel.transactionsView.isEIP1559Enabled) { + trx.maxPriorityFeePerGas = RootStore.getGwei2Hex(parseFloat(selectedTipLimit)) + trx.maxFeePerGas = RootStore.getGwei2Hex(parseFloat(selectedOverallLimit)) + } else { + trx.gasPrice = RootStore.getGwei2Hex(parseFloat(selectedGasPrice)) + } + request.payload.password = enteredPassword - request.payload.params[0].value = value + request.payload.params[0] = trx const response = Web3ProviderStore.web3ProviderInst.postMessage(requestType, JSON.stringify(request)) provider.web3Response(response)