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.accountsService, result.tokenService)
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.profileService = profile_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) =
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.} =
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
## Abstract class (concept) which must be implemented by object/s used in this
## module.

View File

@ -40,6 +40,9 @@ method setHistoryFetchState*(self: AccessInterface, addresses: seq[string], isFe
method setIsNonArchivalNode*(self: AccessInterface, isNonArchivalNode: bool) {.base.} =
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
# Delegate for the view must be declared here due to use of QtObject and multi
# 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) =
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) =
self.view.setIsNonArchivalNode(isNonArchivalNode)

View File

@ -103,3 +103,6 @@ QtObject:
read = getIsNonArchivalNode
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 ""
result = "0x" & ownerAddr.substr(26)
proc buildTransaction*(source: Address, value: Uint256, gas = "", gasPrice = "", isEIP1559Enabled = false,
maxPriorityFeePerGas = "", maxFeePerGas = "", data = ""): TransactionDataDto =
proc buildTransaction*(
source: Address,
value: Uint256,
gas = "",
gasPrice = "",
isEIP1559Enabled = false,
maxPriorityFeePerGas = "",
maxFeePerGas = "",
data = ""
): TransactionDataDto =
result = TransactionDataDto(
source: source,
value: value.some,

View File

@ -71,4 +71,7 @@ proc tokenName*(contract: ContractDto): string =
getTokenString(contract, "name")
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 status/permissions as status_go_permissions
import status/core as status_go_core
import status/eth as status_eth
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/base58 import nil
import stew/byteutils
@ -134,17 +137,13 @@ proc process(self: Service, data: Web3SendAsyncReadOnly): string =
var success: bool
var errorMessage = ""
var response = ""
var response: RpcResponse[JsonNode]
var validInput: bool = true
# TODO: use the transaction service to send the trx
#[
let eip1559Enabled = self.wallet.isEIP1559Enabled()
let eip1559Enabled: bool = self.settingsService.isEIP1559Enabled()
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:
validInput = false
success = false
@ -152,39 +151,43 @@ proc process(self: Service, data: Web3SendAsyncReadOnly): string =
if validInput:
# TODO make this async
response = wallet.sendTransaction(fromAddress, to, value, selectedGasLimit, selectedGasPrice, eip1559Enabled, selectedTipLimit, selectedOverallLimit, password, success, txData)
errorMessage = if not success:
if response == "":
"web3-response-error"
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:
response
else:
""
return $ %* {
"type": ResponseTypes.Web3SendAsyncCallback,
"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": ""
}
}
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

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/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/tasks/[qt, threadpool]
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 ../eth/utils as eth_utils
import ../../common/conversion
export transaction_dto
@ -37,16 +47,29 @@ QtObject:
events: EventEmitter
threadpool: ThreadPool
walletAccountService: wallet_account_service.ServiceInterface
ethService: eth_service.ServiceInterface
networkService: network_service.ServiceInterface
settingsService: settings_service.ServiceInterface
proc delete*(self: Service) =
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)
result.QObject.setup
result.events = events
result.threadpool = threadpool
result.walletAccountService = walletAccountService
result.ethService = ethService
result.networkService = networkService
result.settingsService = settingsService
proc init*(self: Service) =
discard
@ -118,4 +141,40 @@ QtObject:
limit: limit,
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) {
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
property var estimateGas: Backpressure.debounce(gasSelector, 600, function() {
// Not Refactored Yet
// if (!(selectFromAccount.selectedAccount && selectFromAccount.selectedAccount.address &&
// selectRecipient.selectedRecipient && selectRecipient.selectedRecipient.address &&
// txtAmount.selectedAsset && txtAmount.selectedAsset.address &&
// txtAmount.selectedAmount)) return
if (!(selectFromAccount.selectedAccount && selectFromAccount.selectedAccount.address &&
selectRecipient.selectedRecipient && selectRecipient.selectedRecipient.address &&
txtAmount.selectedAsset && txtAmount.selectedAsset.address &&
txtAmount.selectedAmount)) return
// let gasEstimate = JSON.parse(walletModel.gasView.estimateGas(
// selectFromAccount.selectedAccount.address,
// selectRecipient.selectedRecipient.address,
// txtAmount.selectedAsset.address,
// txtAmount.selectedAmount,
// ""))
let gasEstimate = JSON.parse(walletModel.gasView.estimateGas(
selectFromAccount.selectedAccount.address,
selectRecipient.selectedRecipient.address,
txtAmount.selectedAsset.address,
txtAmount.selectedAmount,
""))
// if (!gasEstimate.success) {
// //% "Error estimating gas: %1"
// console.warn(qsTrId("error-estimating-gas---1").arg(gasEstimate.error.message))
// return
// }
if (!gasEstimate.success) {
//% "Error estimating gas: %1"
console.warn(qsTrId("error-estimating-gas---1").arg(gasEstimate.error.message))
return
}
// selectedGasLimit = gasEstimate.result
// defaultGasLimit = selectedGasLimit
selectedGasLimit = gasEstimate.result
defaultGasLimit = selectedGasLimit
})
}
GasValidator {
@ -256,7 +256,6 @@ ModalPopup {
StatusButton {
id: btnNext
anchors.right: parent.right
//% "Next"
text: qsTrId("next")
enabled: stack.currentGroup.isValid && !stack.currentGroup.isPending

View File

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