fix(browser-tx): fix estimate gas and send TX on the browser

Fixes #4557
This commit is contained in:
Jonathan Rainville 2022-02-01 10:31:57 -05:00
parent 6f5fcd8623
commit 82fb325dac
13 changed files with 190 additions and 93 deletions

View File

@ -142,7 +142,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.walletAccountService = wallet_account_service.newService(statusFoundation.events, result.settingsService, result.walletAccountService = wallet_account_service.newService(statusFoundation.events, result.settingsService,
result.accountsService, result.tokenService) result.accountsService, result.tokenService)
result.transactionService = transaction_service.newService(statusFoundation.events, statusFoundation.threadpool, result.transactionService = transaction_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.walletAccountService) result.walletAccountService, result.ethService, result.networkService, result.settingsService)
result.bookmarkService = bookmark_service.newService() result.bookmarkService = bookmark_service.newService()
result.profileService = profile_service.newService() result.profileService = profile_service.newService()
result.stickersService = stickers_service.newService( result.stickersService = stickers_service.newService(

View File

@ -75,3 +75,6 @@ method getAccountByAddress*(self: Controller, address: string): WalletAccountDto
method loadTransactions*(self: Controller, address: string, toBlock: Uint256, limit: int = 20, loadMore: bool = false) = method loadTransactions*(self: Controller, address: string, toBlock: Uint256, limit: int = 20, loadMore: bool = false) =
self.transactionService.loadTransactions(address, toBlock, limit, loadMore) self.transactionService.loadTransactions(address, toBlock, limit, loadMore)
method estimateGas*(self: Controller, from_addr: string, to: string, assetAddress: string, value: string, data: string): string =
result = self.transactionService.estimateGas(from_addr, to, assetAddress, value, data)

View File

@ -29,6 +29,9 @@ method loadTransactions*(self: AccessInterface, address: string, toBlock: Uint25
method setTrxHistoryResult*(self: AccessInterface, historyJSON: string) {.base.} = method setTrxHistoryResult*(self: AccessInterface, historyJSON: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method estimateGas*(self: AccessInterface, from_addr: string, to: string, assetAddress: string, value: string, data: string): string {.base.} =
raise newException(ValueError, "No implementation available")
type type
## Abstract class (concept) which must be implemented by object/s used in this ## Abstract class (concept) which must be implemented by object/s used in this
## module. ## module.

View File

@ -40,6 +40,9 @@ method setHistoryFetchState*(self: AccessInterface, addresses: seq[string], isFe
method setIsNonArchivalNode*(self: AccessInterface, isNonArchivalNode: bool) {.base.} = method setIsNonArchivalNode*(self: AccessInterface, isNonArchivalNode: bool) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method estimateGas*(self: AccessInterface, from_addr: string, to: string, assetAddress: string, value: string, data: string): string {.base.} =
raise newException(ValueError, "No implementation available")
# View Delegate Interface # View Delegate Interface
# Delegate for the view must be declared here due to use of QtObject and multi # Delegate for the view must be declared here due to use of QtObject and multi
# inheritance, which is not well supported in Nim. # inheritance, which is not well supported in Nim.

View File

@ -80,5 +80,8 @@ method setTrxHistoryResult*(self: Module, transactions: seq[TransactionDto], add
method setHistoryFetchState*(self: Module, addresses: seq[string], isFetching: bool) = method setHistoryFetchState*(self: Module, addresses: seq[string], isFetching: bool) =
self.view.setHistoryFetchStateForAccounts(addresses, isFetching) self.view.setHistoryFetchStateForAccounts(addresses, isFetching)
method estimateGas*(self: Module, from_addr: string, to: string, assetAddress: string, value: string, data: string): string =
result = self.controller.estimateGas(from_addr, to, assetAddress, value, data)
method setIsNonArchivalNode*(self: Module, isNonArchivalNode: bool) = method setIsNonArchivalNode*(self: Module, isNonArchivalNode: bool) =
self.view.setIsNonArchivalNode(isNonArchivalNode) self.view.setIsNonArchivalNode(isNonArchivalNode)

View File

@ -103,3 +103,6 @@ QtObject:
read = getIsNonArchivalNode read = getIsNonArchivalNode
notify = isNonArchivalNodeChanged notify = isNonArchivalNodeChanged
proc estimateGas*(self: View, from_addr: string, to: string, assetAddress: string, value: string, data: string): string {.slot.} =
result = self.delegate.estimateGas(from_addr, to, assetAddress, value, data)

View File

@ -186,8 +186,16 @@ proc owner*(username: string): string =
return "" return ""
result = "0x" & ownerAddr.substr(26) result = "0x" & ownerAddr.substr(26)
proc buildTransaction*(source: Address, value: Uint256, gas = "", gasPrice = "", isEIP1559Enabled = false, proc buildTransaction*(
maxPriorityFeePerGas = "", maxFeePerGas = "", data = ""): TransactionDataDto = source: Address,
value: Uint256,
gas = "",
gasPrice = "",
isEIP1559Enabled = false,
maxPriorityFeePerGas = "",
maxFeePerGas = "",
data = ""
): TransactionDataDto =
result = TransactionDataDto( result = TransactionDataDto(
source: source, source: source,
value: value.some, value: value.some,

View File

@ -71,4 +71,7 @@ proc tokenName*(contract: ContractDto): string =
getTokenString(contract, "name") getTokenString(contract, "name")
proc tokenSymbol*(contract: ContractDto): string = proc tokenSymbol*(contract: ContractDto): string =
getTokenString(contract, "symbol") getTokenString(contract, "symbol")
proc getMethod*(contract: ContractDto, methodName: string): MethodDto =
return contract.methods["methodName"]

View File

@ -9,7 +9,10 @@ import ../ens/utils as ens_utils
import service_interface import service_interface
import status/permissions as status_go_permissions import status/permissions as status_go_permissions
import status/core as status_go_core import status/core as status_go_core
import status/eth as status_eth
import ../../common/utils as status_utils import ../../common/utils as status_utils
import ../eth/utils as eth_utils
import ../eth/dto/transaction as transaction_data_dto
from stew/base32 import nil from stew/base32 import nil
from stew/base58 import nil from stew/base58 import nil
import stew/byteutils import stew/byteutils
@ -134,17 +137,13 @@ proc process(self: Service, data: Web3SendAsyncReadOnly): string =
var success: bool var success: bool
var errorMessage = "" var errorMessage = ""
var response = "" var response: RpcResponse[JsonNode]
var validInput: bool = true var validInput: bool = true
let eip1559Enabled: bool = self.settingsService.isEIP1559Enabled()
# TODO: use the transaction service to send the trx
#[
let eip1559Enabled = self.wallet.isEIP1559Enabled()
try: try:
validateTransactionInput(fromAddress, to, "", value, selectedGasLimit, selectedGasPrice, txData, eip1559Enabled, selectedTipLimit, selectedOverallLimit, "dummy") eth_utils.validateTransactionInput(fromAddress, to, "", value, selectedGasLimit, selectedGasPrice, txData, eip1559Enabled, selectedTipLimit, selectedOverallLimit, "dummy")
except Exception as e: except Exception as e:
validInput = false validInput = false
success = false success = false
@ -152,39 +151,43 @@ proc process(self: Service, data: Web3SendAsyncReadOnly): string =
if validInput: if validInput:
# TODO make this async # TODO make this async
response = wallet.sendTransaction(fromAddress, to, value, selectedGasLimit, selectedGasPrice, eip1559Enabled, selectedTipLimit, selectedOverallLimit, password, success, txData) var tx = ens_utils.buildTransaction(
errorMessage = if not success: parseAddress(fromAddress),
if response == "": eth2Wei(parseFloat(value), 18),
"web3-response-error" 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: else:
response success = true
else: errorMessage = ""
"" except Exception as e:
error "Error sending transaction", msg = e.msg
return $ %* { errorMessage = e.msg
"type": ResponseTypes.Web3SendAsyncCallback, success = false
"messageId": data.messageId,
"error": errorMessage,
"result": {
"jsonrpc": "2.0",
"id": data.payload.id,
"result": if (success): response else: ""
}
}
]#
# TODO: delete this:
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"messageId": data.messageId,
"error": "",
"result": {
"jsonrpc": "2.0",
"id": data.payload.id,
"result": ""
}
}
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: except Exception as e:
error "Error sending the transaction", msg = e.msg error "Error sending the transaction", msg = e.msg

View File

@ -1,12 +1,22 @@
import NimQml, chronicles, sequtils, sugar, stint, strutils, json import NimQml, chronicles, sequtils, sugar, stint, strutils, json, strformat
import status/transactions as transactions import status/transactions as transactions
import status/wallet as status_wallet import status/wallet as status_wallet
import status/eth
import ../ens/utils as ens_utils
from ../../common/account_constants import ZERO_ADDRESS
import ../../../app/core/[main] import ../../../app/core/[main]
import ../../../app/core/tasks/[qt, threadpool] import ../../../app/core/tasks/[qt, threadpool]
import ../wallet_account/service as wallet_account_service import ../wallet_account/service as wallet_account_service
import ../eth/service as eth_service
import ../network/service as network_service
import ../settings/service as settings_service
import ../eth/dto/transaction as transaction_data_dto
import ../eth/dto/[contract, method_dto]
import ./dto as transaction_dto import ./dto as transaction_dto
import ../eth/utils as eth_utils import ../eth/utils as eth_utils
import ../../common/conversion
export transaction_dto export transaction_dto
@ -37,16 +47,29 @@ QtObject:
events: EventEmitter events: EventEmitter
threadpool: ThreadPool threadpool: ThreadPool
walletAccountService: wallet_account_service.ServiceInterface walletAccountService: wallet_account_service.ServiceInterface
ethService: eth_service.ServiceInterface
networkService: network_service.ServiceInterface
settingsService: settings_service.ServiceInterface
proc delete*(self: Service) = proc delete*(self: Service) =
self.QObject.delete self.QObject.delete
proc newService*(events: EventEmitter, threadpool: ThreadPool, walletAccountService: wallet_account_service.ServiceInterface): Service = proc newService*(
events: EventEmitter,
threadpool: ThreadPool,
walletAccountService: wallet_account_service.ServiceInterface,
ethService: eth_service.ServiceInterface,
networkService: network_service.ServiceInterface,
settingsService: settings_service.ServiceInterface
): Service =
new(result, delete) new(result, delete)
result.QObject.setup result.QObject.setup
result.events = events result.events = events
result.threadpool = threadpool result.threadpool = threadpool
result.walletAccountService = walletAccountService result.walletAccountService = walletAccountService
result.ethService = ethService
result.networkService = networkService
result.settingsService = settingsService
proc init*(self: Service) = proc init*(self: Service) =
discard discard
@ -118,4 +141,40 @@ QtObject:
limit: limit, limit: limit,
loadMore: loadMore loadMore: loadMore
) )
self.threadpool.start(arg) self.threadpool.start(arg)
proc estimateGas*(self: Service, from_addr: string, to: string, assetAddress: string, value: string, data: string = ""): string {.slot.} =
var response: RpcResponse[JsonNode]
try:
if assetAddress != ZERO_ADDRESS and not assetAddress.isEmptyOrWhitespace:
var tx = buildTokenTransaction(
parseAddress(from_addr),
parseAddress(assetAddress)
)
let networkType = self.settingsService.getCurrentNetwork().toNetworkType()
let network = self.networkService.getNetwork(networkType)
let contract = self.ethService.findErc20Contract(network.chainId, assetAddress)
if contract == nil:
raise newException(ValueError, fmt"Could not find ERC-20 contract with address '{assetAddress}' for the current network")
let transfer = Transfer(to: parseAddress(to), value: conversion.eth2Wei(parseFloat(value), contract.decimals))
let methodThing = contract.getMethod("transfer")
var success: bool
let gas = methodThing.estimateGas(tx, transfer, success)
result = $(%* { "result": gas, "success": success })
else:
var tx = ens_utils.buildTransaction(
parseAddress(from_addr),
eth2Wei(parseFloat(value), 18),
data = data
)
tx.to = parseAddress(to).some
response = eth.estimateGas(%*[%tx])
let res = fromHex[int](response.result.getStr)
result = $(%* { "result": %res, "success": true })
except Exception as e:
error "Error estimating gas", msg = e.msg
result = $(%* { "result": "-1", "success": false, "error": { "message": e.msg } })

View File

@ -54,4 +54,17 @@ QtObject {
function generateIdenticon(pk) { function generateIdenticon(pk) {
return globalUtils.generateIdenticon(pk); return globalUtils.generateIdenticon(pk);
} }
property string currentCurrency: walletSection.currentCurrency
function estimateGas(from_addr, to, assetAddress, value, data) {
return walletSectionTransactions.estimateGas(from_addr, to, assetAddress, value, data)
}
// TODO change this to use a better store once it is moved out of the ENS module
property string gasPrice: profileSectionStore.ensUsernamesStore.gasPrice
function getFiatValue(balance, cryptoSymbo, fiatSymbol) {
return profileSectionStore.ensUsernamesStore.getFiatValue(balance, cryptoSymbo, fiatSymbol)
}
function getGasEthValue(gweiValue, gasLimit) {
return profileSectionStore.ensUsernamesStore.getGasEthValue(gweiValue, gasLimit)
}
} }

View File

@ -155,26 +155,26 @@ ModalPopup {
width: stack.width width: stack.width
property var estimateGas: Backpressure.debounce(gasSelector, 600, function() { property var estimateGas: Backpressure.debounce(gasSelector, 600, function() {
// Not Refactored Yet // Not Refactored Yet
// if (!(selectFromAccount.selectedAccount && selectFromAccount.selectedAccount.address && if (!(selectFromAccount.selectedAccount && selectFromAccount.selectedAccount.address &&
// selectRecipient.selectedRecipient && selectRecipient.selectedRecipient.address && selectRecipient.selectedRecipient && selectRecipient.selectedRecipient.address &&
// txtAmount.selectedAsset && txtAmount.selectedAsset.address && txtAmount.selectedAsset && txtAmount.selectedAsset.address &&
// txtAmount.selectedAmount)) return txtAmount.selectedAmount)) return
// let gasEstimate = JSON.parse(walletModel.gasView.estimateGas( let gasEstimate = JSON.parse(walletModel.gasView.estimateGas(
// selectFromAccount.selectedAccount.address, selectFromAccount.selectedAccount.address,
// selectRecipient.selectedRecipient.address, selectRecipient.selectedRecipient.address,
// txtAmount.selectedAsset.address, txtAmount.selectedAsset.address,
// txtAmount.selectedAmount, txtAmount.selectedAmount,
// "")) ""))
// if (!gasEstimate.success) { if (!gasEstimate.success) {
// //% "Error estimating gas: %1" //% "Error estimating gas: %1"
// console.warn(qsTrId("error-estimating-gas---1").arg(gasEstimate.error.message)) console.warn(qsTrId("error-estimating-gas---1").arg(gasEstimate.error.message))
// return return
// } }
// selectedGasLimit = gasEstimate.result selectedGasLimit = gasEstimate.result
// defaultGasLimit = selectedGasLimit defaultGasLimit = selectedGasLimit
}) })
} }
GasValidator { GasValidator {
@ -256,7 +256,6 @@ ModalPopup {
StatusButton { StatusButton {
id: btnNext id: btnNext
anchors.right: parent.right
//% "Next" //% "Next"
text: qsTrId("next") text: qsTrId("next")
enabled: stack.currentGroup.isValid && !stack.currentGroup.isPending enabled: stack.currentGroup.isValid && !stack.currentGroup.isPending

View File

@ -105,7 +105,7 @@ StatusModal {
// //% "Authorize %1 %2" // //% "Authorize %1 %2"
// return qsTrId("authorize--1--2").arg(approveData.amount).arg(approveData.symbol) // return qsTrId("authorize--1--2").arg(approveData.amount).arg(approveData.symbol)
// } // }
return qsTrId("command-button-send"); return qsTr("Send");
} }
//% "Continue" //% "Continue"
footerText: qsTrId("continue") footerText: qsTrId("continue")
@ -119,7 +119,7 @@ StatusModal {
id: selectFromAccount id: selectFromAccount
// Not Refactored Yet // Not Refactored Yet
// accounts: root.store.walletModelInst.accountsView.accounts // accounts: root.store.walletModelInst.accountsView.accounts
// currency: root.store.walletModelInst.balanceView.defaultCurrency currency: root.store.currentCurrency
width: stack.width width: stack.width
selectedAccount: root.selectedAccount selectedAccount: root.selectedAccount
//% "Choose account" //% "Choose account"
@ -150,37 +150,35 @@ StatusModal {
GasSelector { GasSelector {
id: gasSelector id: gasSelector
anchors.topMargin: Style.current.padding anchors.topMargin: Style.current.padding
// Not Refactored Yet gasPrice: parseFloat(root.store.gasPrice)
// gasPrice: parseFloat(root.store.walletModelInst.gasView.gasPrice) getGasEthValue: root.store.getGasEthValue
// getGasEthValue: root.store.walletModelInst.gasView.getGasEthValue getFiatValue: root.store.getFiatValue
// getFiatValue: root.store.walletModelInst.balanceView.getFiatValue defaultCurrency: root.store.currentCurrency
// defaultCurrency: root.store.walletModelInst.balanceView.defaultCurrency
width: stack.width width: stack.width
property var estimateGas: Backpressure.debounce(gasSelector, 600, function() { property var estimateGas: Backpressure.debounce(gasSelector, 600, function() {
// Not Refactored Yet if (!(selectFromAccount.selectedAccount && selectFromAccount.selectedAccount.address &&
// if (!(selectFromAccount.selectedAccount && selectFromAccount.selectedAccount.address && selectRecipient.selectedRecipient && selectRecipient.selectedRecipient.address &&
// selectRecipient.selectedRecipient && selectRecipient.selectedRecipient.address && root.selectedAsset && root.selectedAsset.address &&
// root.selectedAsset && root.selectedAsset.address && root.selectedAmount)) {
// root.selectedAmount)) { selectedGasLimit = 250000
// selectedGasLimit = 250000 defaultGasLimit = selectedGasLimit
// defaultGasLimit = selectedGasLimit return
// return }
// }
// let gasEstimate = JSON.parse(root.store.walletModelInst.gasView.estimateGas( let gasEstimate = JSON.parse(root.store.estimateGas(
// selectFromAccount.selectedAccount.address, selectFromAccount.selectedAccount.address,
// selectRecipient.selectedRecipient.address, selectRecipient.selectedRecipient.address,
// root.selectedAsset.address, root.selectedAsset.address,
// root.selectedAmount, root.selectedAmount,
// trxData)) trxData))
// if (!gasEstimate.success) { if (!gasEstimate.success) {
// let message = qsTrId("error-estimating-gas---1").arg(gasEstimate.error.message) let message = qsTr("Error estimating gas: %1").arg(gasEstimate.error.message)
// root.openGasEstimateErrorPopup(message); root.openGasEstimateErrorPopup(message);
// } }
// selectedGasLimit = gasEstimate.result selectedGasLimit = gasEstimate.result
// defaultGasLimit = selectedGasLimit defaultGasLimit = selectedGasLimit
}) })
} }
GasValidator { GasValidator {
@ -217,8 +215,7 @@ StatusModal {
toAccount: selectRecipient.selectedRecipient toAccount: selectRecipient.selectedRecipient
asset: root.selectedAsset asset: root.selectedAsset
amount: { "value": root.selectedAmount, "fiatValue": root.selectedFiatAmount } amount: { "value": root.selectedAmount, "fiatValue": root.selectedFiatAmount }
// Not Refactored Yet currency: root.store.currentCurrency
// currency: root.store.walletModelInst.balanceView.defaultCurrency
isFromEditable: false isFromEditable: false
trxData: root.trxData trxData: root.trxData
isGasEditable: true isGasEditable: true