Adapting blockchain operations to 0.0.2 alpha contracts WIP
This commit is contained in:
parent
cfe3e3b55d
commit
9ae732cd6b
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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, '')
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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, '')
|
||||||
|
}
|
|
@ -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}`)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue