Adapting blockchain operations to 0.0.2 alpha contracts WIP

This commit is contained in:
apanizo 2018-09-28 09:35:45 +02:00
parent cfe3e3b55d
commit 9ae732cd6b
5 changed files with 201 additions and 174 deletions

View File

@ -3,7 +3,8 @@ import { calculateGasOf, checkReceiptStatus, calculateGasPrice } from '~/logic/w
import { type Operation, submitOperation } from '~/logic/safe/safeTxHistory' import { type Operation, submitOperation } from '~/logic/safe/safeTxHistory'
import { getDailyLimitModuleFrom } from '~/logic/contracts/dailyLimitContracts' import { getDailyLimitModuleFrom } from '~/logic/contracts/dailyLimitContracts'
import { getSafeEthereumInstance } from '~/logic/safe/safeFrontendOperations' import { getSafeEthereumInstance } from '~/logic/safe/safeFrontendOperations'
import { generateMetamaskSignature, generateTxGasEstimateFrom, estimateDataGas } from '~/logic/safe/safeTxSigner' import { buildSignaturesFrom } from '~/logic/safe/safeTxSigner'
import { generateMetamaskSignature, generateTxGasEstimateFrom, estimateDataGas } from '~/logic/safe/safeTxSignerEIP712'
import { storeSignature, getSignaturesFrom } from '~/utils/localStorage/signatures' import { storeSignature, getSignaturesFrom } from '~/utils/localStorage/signatures'
import { signaturesViaMetamask } from '~/config' import { signaturesViaMetamask } from '~/config'
@ -30,10 +31,12 @@ export const approveTransaction = async (
} }
const gnosisSafe = await getSafeEthereumInstance(safeAddress) const gnosisSafe = await getSafeEthereumInstance(safeAddress)
const txData = gnosisSafe.contract.approveTransactionWithParameters.getData(to, valueInWei, data, operation, nonce) const contractTxHash = await gnosisSafe.getTransactionHash(to, valueInWei, data, operation, 0, 0, 0, 0, 0, nonce)
const gas = await calculateGasOf(txData, sender, safeAddress)
const txReceipt = await gnosisSafe const approveData = gnosisSafe.contract.approveHash.getData(contractTxHash)
.approveTransactionWithParameters(to, valueInWei, data, operation, nonce, { from: sender, gas, gasPrice }) const gas = await calculateGasOf(approveData, sender, safeAddress)
const txReceipt = await gnosisSafe.approveHash(contractTxHash, { from: sender, gas, gasPrice })
const txHash = txReceipt.tx const txHash = txReceipt.tx
await checkReceiptStatus(txHash) await checkReceiptStatus(txHash)
@ -89,17 +92,24 @@ export const executeTransaction = async (
} }
const gnosisSafe = await getSafeEthereumInstance(safeAddress) const gnosisSafe = await getSafeEthereumInstance(safeAddress)
const txConfirmationData = const ownersWhoHasSigned = [] // to obtain from tx-history-service
gnosisSafe.contract.execTransactionIfApproved.getData(to, valueInWei, data, operation, nonce) const signatures = buildSignaturesFrom(ownersWhoHasSigned, sender)
const txExecutionData =
gnosisSafe.contract.execTransaction.getData(to, valueInWei, data, operation, 0, 0, 0, 0, 0, signatures)
const gas = await calculateGasOf(txExecutionData, sender, safeAddress)
const numOwners = await gnosisSafe.getOwners() const numOwners = await gnosisSafe.getOwners()
const gas = await calculateGasOf(txConfirmationData, sender, safeAddress)
const gasIncludingRemovingStoreUpfront = gas + (numOwners.length * 15000) const gasIncludingRemovingStoreUpfront = gas + (numOwners.length * 15000)
const txReceipt = await gnosisSafe.execTransactionIfApproved( const txReceipt = await gnosisSafe.execTransaction(
to, to,
valueInWei, valueInWei,
data, data,
operation, operation,
nonce, 0,
0,
0,
0,
0,
signatures,
{ from: sender, gas: gasIncludingRemovingStoreUpfront, gasPrice }, { from: sender, gas: gasIncludingRemovingStoreUpfront, gasPrice },
) )
const txHash = txReceipt.tx const txHash = txReceipt.tx

View File

@ -18,7 +18,8 @@ const calculateBodyFrom = async (
type: TxServiceType, type: TxServiceType,
) => { ) => {
const gnosisSafe = await getSafeEthereumInstance(safeAddress) const gnosisSafe = await getSafeEthereumInstance(safeAddress)
const contractTransactionHash = await gnosisSafe.getTransactionHash(to, valueInWei, data, operation, nonce) const contractTransactionHash =
await gnosisSafe.getTransactionHash(to, valueInWei, data, operation, 0, 0, 0, 0, 0, nonce)
return JSON.stringify({ return JSON.stringify({
to: getWeb3().toChecksumAddress(to), to: getWeb3().toChecksumAddress(to),

View File

@ -1,166 +1,13 @@
// @flow // @flow
import { getWeb3 } from '~/logic/wallets/getWeb3' const generateSignatureFrom = (account: string) =>
import { promisify } from '~/utils/promisify' `000000000000000000000000${account.replace('0x', '')}000000000000000000000000000000000000000000000000000000000000000001`
import { BigNumber } from 'bignumber.js'
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
import { getSignaturesFrom } from '~/utils/localStorage/signatures'
const estimateDataGasCosts = (data) => { export const buildSignaturesFrom = (ownersWhoHasSigned: string[], sender: string) => {
const reducer = (accumulator, currentValue) => { let sigs = '0x'
if (currentValue === EMPTY_DATA) { ownersWhoHasSigned.forEach((owner: string) => {
return accumulator + 0 sigs += generateSignatureFrom(owner)
} })
sigs += generateSignatureFrom(sender)
if (currentValue === '00') {
return accumulator + 4 return sigs
}
return accumulator + 68
}
return data.match(/.{2}/g).reduce(reducer, 0)
}
export const estimateDataGas = (
safe: any,
to: string,
valueInWei: number,
data: string,
operation: number,
txGasEstimate: number,
gasToken: number,
nonce: number,
signatureCount: number,
refundReceiver: number,
) => {
// numbers < 256 are 192 -> 31 * 4 + 68
// numbers < 65k are 256 -> 30 * 4 + 2 * 68
// For signature array length and dataGasEstimate we already calculated
// the 0 bytes so we just add 64 for each non-zero byte
const gasPrice = 0 // no need to get refund when we submit txs to metamask
const signatureCost = signatureCount * (68 + 2176 + 2176) // array count (3 -> r, s, v) * signature count
const sigs = getSignaturesFrom(safe.address, nonce)
const payload = safe.contract.execTransaction
.getData(to, valueInWei, data, operation, txGasEstimate, 0, gasPrice, gasToken, refundReceiver, sigs)
let dataGasEstimate = estimateDataGasCosts(payload) + signatureCost
if (dataGasEstimate > 65536) {
dataGasEstimate += 64
} else {
dataGasEstimate += 128
}
return dataGasEstimate + 34000 // Add aditional gas costs (e.g. base tx costs, transfer costs)
}
// eslint-disable-next-line
export const generateTxGasEstimateFrom = async (
safe: any,
safeAddress: string,
data: string,
to: string,
valueInWei: number,
operation: number,
) => {
try {
const estimateData = safe.contract.requiredTxGas.getData(to, valueInWei, data, operation)
const estimateResponse = await promisify(cb => getWeb3().eth.call({
to: safeAddress,
from: safeAddress,
data: estimateData,
}, cb))
const txGasEstimate = new BigNumber(estimateResponse.substring(138), 16)
// Add 10k else we will fail in case of nested calls
return Promise.resolve(txGasEstimate.toNumber() + 10000)
} catch (error) {
// eslint-disable-next-line
console.log("Error calculating tx gas estimation " + error)
return Promise.resolve(0)
}
}
const generateTypedDataFrom = async (
safe: any,
safeAddress: string,
to: string,
valueInWei: number,
nonce: number,
data: string,
operation: number,
txGasEstimate: number,
) => {
const txGasToken = 0
// const threshold = await safe.getThreshold()
// estimateDataGas(safe, to, valueInWei, data, operation, txGasEstimate, txGasToken, nonce, threshold)
const dataGasEstimate = 0
const gasPrice = 0
const refundReceiver = 0
const typedData = {
types: {
EIP712Domain: [
{
type: 'address',
name: 'verifyingContract',
},
],
SafeTx: [
{ type: 'address', name: 'to' },
{ type: 'uint256', name: 'value' },
{ type: 'bytes', name: 'data' },
{ type: 'uint8', name: 'operation' },
{ type: 'uint256', name: 'safeTxGas' },
{ type: 'uint256', name: 'dataGas' },
{ type: 'uint256', name: 'gasPrice' },
{ type: 'address', name: 'gasToken' },
{ type: 'address', name: 'refundReceiver' },
{ type: 'uint256', name: 'nonce' },
],
},
domain: {
verifyingContract: safeAddress,
},
primaryType: 'SafeTx',
message: {
to,
value: Number(valueInWei),
data,
operation,
safeTxGas: txGasEstimate,
dataGas: dataGasEstimate,
gasPrice,
gasToken: txGasToken,
refundReceiver,
nonce: Number(nonce),
},
}
return typedData
}
export const generateMetamaskSignature = async (
safe: any,
safeAddress: string,
sender: string,
to: string,
valueInWei: number,
nonce: number,
data: string,
operation: number,
txGasEstimate: number,
) => {
const web3 = getWeb3()
const typedData =
await generateTypedDataFrom(safe, safeAddress, to, valueInWei, nonce, data, operation, txGasEstimate)
const jsonTypedData = JSON.stringify(typedData)
const signedTypedData = {
method: 'eth_signTypedData_v3',
params: [jsonTypedData, sender],
from: sender,
}
const txSignedResponse = await promisify(cb => web3.currentProvider.sendAsync(signedTypedData, cb))
return txSignedResponse.result.replace(EMPTY_DATA, '')
} }

View File

@ -0,0 +1,169 @@
// @flow
import { getWeb3 } from '~/logic/wallets/getWeb3'
import { promisify } from '~/utils/promisify'
import { BigNumber } from 'bignumber.js'
import { EMPTY_DATA } from '~/logic/wallets/ethTransactions'
import { getSignaturesFrom } from '~/utils/localStorage/signatures'
const estimateDataGasCosts = (data) => {
const reducer = (accumulator, currentValue) => {
if (currentValue === EMPTY_DATA) {
return accumulator + 0
}
if (currentValue === '00') {
return accumulator + 4
}
return accumulator + 68
}
return data.match(/.{2}/g).reduce(reducer, 0)
}
export const estimateDataGas = (
safe: any,
to: string,
valueInWei: number,
data: string,
operation: number,
txGasEstimate: number,
gasToken: number,
nonce: number,
signatureCount: number,
refundReceiver: number,
) => {
// numbers < 256 are 192 -> 31 * 4 + 68
// numbers < 65k are 256 -> 30 * 4 + 2 * 68
// For signature array length and dataGasEstimate we already calculated
// the 0 bytes so we just add 64 for each non-zero byte
const gasPrice = 0 // no need to get refund when we submit txs to metamask
const signatureCost = signatureCount * (68 + 2176 + 2176) // array count (3 -> r, s, v) * signature count
const sigs = getSignaturesFrom(safe.address, nonce)
const payload = safe.contract.execTransaction
.getData(to, valueInWei, data, operation, txGasEstimate, 0, gasPrice, gasToken, refundReceiver, sigs)
let dataGasEstimate = estimateDataGasCosts(payload) + signatureCost
if (dataGasEstimate > 65536) {
dataGasEstimate += 64
} else {
dataGasEstimate += 128
}
return dataGasEstimate + 34000 // Add aditional gas costs (e.g. base tx costs, transfer costs)
}
// eslint-disable-next-line
export const generateTxGasEstimateFrom = async (
safe: any,
safeAddress: string,
data: string,
to: string,
valueInWei: number,
operation: number,
) => {
try {
const estimateData = safe.contract.requiredTxGas.getData(to, valueInWei, data, operation)
const estimateResponse = await promisify(cb => getWeb3().eth.call({
to: safeAddress,
from: safeAddress,
data: estimateData,
}, cb))
const txGasEstimate = new BigNumber(estimateResponse.substring(138), 16)
// Add 10k else we will fail in case of nested calls
return Promise.resolve(txGasEstimate.toNumber() + 10000)
} catch (error) {
// eslint-disable-next-line
console.log("Error calculating tx gas estimation " + error)
return Promise.resolve(0)
}
}
const generateTypedDataFrom = async (
safe: any,
safeAddress: string,
to: string,
valueInWei: number,
nonce: number,
data: string,
operation: number,
txGasEstimate: number,
) => {
const txGasToken = 0
// const threshold = await safe.getThreshold()
// estimateDataGas(safe, to, valueInWei, data, operation, txGasEstimate, txGasToken, nonce, threshold)
const dataGasEstimate = 0
const gasPrice = 0
const refundReceiver = 0
const typedData = {
types: {
EIP712Domain: [
{
type: 'address',
name: 'verifyingContract',
},
],
SafeTx: [
{ type: 'address', name: 'to' },
{ type: 'uint256', name: 'value' },
{ type: 'bytes', name: 'data' },
{ type: 'uint8', name: 'operation' },
{ type: 'uint256', name: 'safeTxGas' },
{ type: 'uint256', name: 'dataGas' },
{ type: 'uint256', name: 'gasPrice' },
{ type: 'address', name: 'gasToken' },
{ type: 'address', name: 'refundReceiver' },
{ type: 'uint256', name: 'nonce' },
],
},
domain: {
verifyingContract: safeAddress,
},
primaryType: 'SafeTx',
message: {
to,
value: Number(valueInWei),
data,
operation,
safeTxGas: txGasEstimate,
dataGas: dataGasEstimate,
gasPrice,
gasToken: txGasToken,
refundReceiver,
nonce: Number(nonce),
},
}
return typedData
}
export const generateMetamaskSignature = async (
safe: any,
safeAddress: string,
sender: string,
to: string,
valueInWei: number,
nonce: number,
data: string,
operation: number,
txGasEstimate: number,
) => {
const web3 = getWeb3()
const typedData =
await generateTypedDataFrom(safe, safeAddress, to, valueInWei, nonce, data, operation, txGasEstimate)
const jsonTypedData = JSON.stringify(typedData)
const signedTypedData = {
method: 'eth_signTypedData_v3',
// To change once Metamask fixes their status
// https://github.com/MetaMask/metamask-extension/pull/5368
// https://github.com/MetaMask/metamask-extension/issues/5366
params: [jsonTypedData, sender],
from: sender,
}
const txSignedResponse = await promisify(cb => web3.currentProvider.sendAsync(signedTypedData, cb))
return txSignedResponse.result.replace(EMPTY_DATA, '')
}

View File

@ -15,7 +15,7 @@ export const printOutApprove = async (
console.log(subject) console.log(subject)
const gnosisSafe = await getGnosisSafeInstanceAt(address) const gnosisSafe = await getGnosisSafeInstanceAt(address)
const transactionHash = await gnosisSafe.getTransactionHash(address, 0, data, 0, nonce) const transactionHash = await gnosisSafe.getTransactionHash(address, 0, data, 0, 0, 0, 0, 0, 0, nonce)
// eslint-disable-next-line // eslint-disable-next-line
console.log(`EO transaction hash ${transactionHash}`) console.log(`EO transaction hash ${transactionHash}`)