Boilertalk / Web3.swift

A pure swift Ethereum Web3 library
MIT License
643 stars 189 forks source link

insufficient funds for gas * price + value #103

Open AndreyNovikov2909 opened 3 years ago

AndreyNovikov2909 commented 3 years ago

Hello I always get this error when signing the transaction "insufficient funds for gas * price + value" I understand that the problem is not with gas, but in the private key with which I sign the transaction, what could be the problem? Methods that do not require gas are successful, I can say for sure that there is a sufficient amount on my wallet - 0x92Ddc4493d6839811F047D7729b2cFcB4DA3780F. if i don't sign the transaction i call the send method directly i get this error - "The method eth_sendTransaction does not exist/is not available"

// code // the mint method of the smatr contract

    firstly {
        self.contract["balanceOf"]!(self.myWallet).call()
    }.done { (data) in
        print("mint:", data) // ok
    }.catch { (error) in
        print("mint:", error) 
    }

    let transaction =  self.contract["mint"]!(self.contractAddress, BigInt(10000000000)).createTransaction(nonce: 4,
                                                                                     from: self.myWallet,
                                                                                     value: 0,
                                                                                     gas: 300000,
                                                                                     gasPrice: EthereumQuantity(quantity: 80.gwei))!

    let sign = try! transaction.sign(with: self.privateKey)

    firstly {
        self.web3.eth.sendRawTransaction(transaction: sign)
    }.done { (v) in
        print(v)
    }.catch { (error) in
        print(error) // always "insufficient funds for gas * price + value"
    }

// the transfer method of the smatr contract

  let transaction = contract["transfer"]!(toSendWallet, BigUInt(10000)).createTransaction(nonce: 0,
                                                                                            from: privateKey.address,
                                                                                            value: 0,
                                                                                            gas: 1000000,
                                                                                            gasPrice: EthereumQuantity(quantity: 80.gwei))!

    // I think there is a problem here when I sign a transaction
    let signedTx = try! transaction.sign(with: privateKey)

    firstly {
        web3.eth.sendRawTransaction(transaction: signedTx)
    }.done { txHash in
        print(txHash)
        fatalError()
    }.catch { error in
        print(error) // always "insufficient funds for gas * price + value"
    }

     firstly {
        self.contract["mint"]!(self.contractAddress, BigInt(10000000000)).send(from: self.myWallet,
                                                   value: 0,
                                                   gas: 300000,
                                                   gasPrice: EthereumQuantity(quantity: 80.gwei))
    }.done { (value) in
        print(value)
    }.catch { (error) in
        print(error) // always The method eth_sendTransaction does not exist/is not available
    }
mudilajaganios commented 3 years ago

@AndreyNovikov2909 Did you fix the issue? I am also facing the same problem. Signing transaction is not working well it seems. I checked the same values with javascript code the hash of signed transaction is different from the swift code.I believe the r s v values are not calculated properly in the swift code. Please let me know if you have any developments.

mudilajaganios commented 3 years ago

@AndreyNovikov2909 I got rid off the issue by extending the EthereumTransaction struct also I had to extend RLPItem struct too.

It worked for me, try for yourself. Please suggest me any better approach.

I have compared it with the Web3.js javascript implementation. Only 6 properties to be used for signing the transaction they are as below:

none
gasPrice
gasLimit
to
value - <To-be empty string>
data
extension EthereumTransaction {
    public func signX(with privateKey: EthereumPrivateKey, chainId: EthereumQuantity = 0) throws -> EthereumSignedTransaction {

        // These values are required for signing
        guard let nonce = nonce, let gasPrice = gasPrice, let gasLimit = gas, let value = value else {
            throw EthereumSignedTransaction.Error.transactionInvalid
        }

        let rlp = RLPItem(
            nonce: nonce,
            gasPrice: gasPrice,
            gasLimit: gasLimit,
            to: to,
            data: data
        )

        let rawRlp = try RLPEncoder().encode(rlp)

        guard let signedTransaction = try? privateKey.sign(message: rawRlp) else { throw EthereumSignedTransaction.Error.transactionInvalid }

        let v: BigUInt
        if chainId.quantity == 0 {
            v = BigUInt(signedTransaction.v) + BigUInt(27)
        } else {
            let sigV = BigUInt(signedTransaction.v)
            let big27 = BigUInt(27)
            let chainIdCalc = (chainId.quantity * BigUInt(2) + BigUInt(8))
            v = sigV + big27 + chainIdCalc
        }

        return EthereumSignedTransaction(
            nonce: nonce,
            gasPrice: gasPrice,
            gasLimit: gasLimit,
            to: to,
            value: value,
            data: data,
            v: EthereumQuantity(quantity: v),
            r: EthereumQuantity(quantity: BigUInt(signedTransaction.r)),
            s: EthereumQuantity(quantity: BigUInt(signedTransaction.s)),
            chainId: chainId
        )
    }
}

extension RLPItem {
    /**
     * Create an RLPItem representing a transaction. The RLPItem must be an array of 9 items in the proper order.
     *
     * - parameter nonce: The nonce of this transaction.
     * - parameter gasPrice: The gas price for this transaction in wei.
     * - parameter gasLimit: The gas limit for this transaction.
     * - parameter to: The address of the receiver.
     * - parameter data: Input data for this transaction.
     * - parameter v: EC signature parameter v, or a EIP155 chain id for an unsigned transaction.
     * - parameter r: EC signature parameter r.
     * - parameter s: EC recovery ID.
     */
    init(
        nonce: EthereumQuantity,
        gasPrice: EthereumQuantity,
        gasLimit: EthereumQuantity,
        to: EthereumAddress?,
        data: EthereumData
    ) {
        self = .array(
            .bigUInt(nonce.quantity),
            .bigUInt(gasPrice.quantity),
            .bigUInt(gasLimit.quantity),
            .bytes(to?.rawAddress ?? Bytes()),
            .string(""),
            .bytes(data.bytes)
        )
    }
}
podkovyrin commented 3 years ago

You need to estimate gas before sending tx to the network.

let call = EthereumCall(from: from, to: to, gasPrice: gasPrice, value: value, data: data)
eth.estimateGas(call: call, block: .latest) { response in
  // use gas limit from response
}
mudilajaganios commented 3 years ago

You need to estimate gas before sending tx to the network.

let call = EthereumCall(from: from, to: to, gasPrice: gasPrice, value: value, data: data)
eth.estimateGas(call: call, block: .latest) { response in
  // use gas limit from response
}

Thanks @podkovyrin I am through with this issue. As I mentioned in my previous reply. There is an issue with the Transaction signature. So, Implemented the changes.

podkovyrin commented 3 years ago

@mudilajaganios

        let rlp = RLPItem(
            nonce: nonce,
            gasPrice: gasPrice,
            gasLimit: gasLimit,
            to: to,
            data: data
        )

This looks kinda dangerous, you need to include chain id in the signature otherwise it might lead to replay attacks. See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md

mudilajaganios commented 3 years ago

@podkovyrin

Hey sorry, the code was not updated. I faced the issue with the chain id too. I changed the method to include it.

func signX(with privateKey: EthereumPrivateKey, chainId: Int = 0) throws -> EthereumSignedTransaction {

        // These values are required for signing
        guard let nonce = nonce, let gasPrice = gasPrice, let gasLimit = gas, let value = value else {
            throw EthereumSignedTransaction.Error.transactionInvalid
        }

        let rlp = RLPItem(
            nonce: nonce,
            gasPrice: gasPrice,
            gasLimit: gasLimit,
            to: to,
            data: data,
            chainId: UInt(chainId)
        )

        let rawRlp = try RLPEncoder().encode(rlp)

        guard let signedTransaction = try? privateKey.sign(message: rawRlp) else { throw EthereumSignedTransaction.Error.transactionInvalid }

        let v: BigUInt
        if chainId == 0 {
            v = BigUInt(signedTransaction.v) + BigUInt(27)
        } else {
            let sigV = BigUInt(signedTransaction.v)
            let big27 = BigUInt(27)
            let chainIdCalc = (BigUInt(chainId) * BigUInt(2) + BigUInt(8))
            v = sigV + big27 + chainIdCalc
        }

        return EthereumSignedTransaction(
            nonce: nonce,
            gasPrice: gasPrice,
            gasLimit: gasLimit,
            to: to,
            value: value,
            data: data,
            v: EthereumQuantity(quantity: v),
            r: EthereumQuantity(quantity: BigUInt(signedTransaction.r)),
            s: EthereumQuantity(quantity: BigUInt(signedTransaction.s)),
            chainId: EthereumQuantity(quantity: BigUInt(chainId))
        )
    }

RLPItem needs to be as below.

extension RLPItem {
    /**
     * Create an RLPItem representing a transaction. The RLPItem must be an array of 6 items in the proper order.
     *
     * - parameter nonce: The nonce of this transaction.
     * - parameter gasPrice: The gas price for this transaction in wei.
     * - parameter gasLimit: The gas limit for this transaction.
     * - parameter to: The address of the receiver.
     * - parameter data: Input data for this transaction.
     */
    init(
        nonce: EthereumQuantity,
        gasPrice: EthereumQuantity,
        gasLimit: EthereumQuantity,
        to: EthereumAddress?,
        data: EthereumData,
        chainId: UInt
    ) {
        self = .array(
            .bigUInt(nonce.quantity),
            .bigUInt(gasPrice.quantity),
            .bigUInt(gasLimit.quantity),
            .bytes(to?.rawAddress ?? Bytes()),
            .string(""),
            .bytes(data.bytes),
            .init(integerLiteral: chainId),
            .init(integerLiteral: 0),
            .init(integerLiteral: 0)
        )
    }
}