Estimate gas limit for withdrawal transaction via relayer correctly

This commit is contained in:
Theo 2023-07-03 03:13:14 -07:00
parent 6130c46d90
commit 311f6ebc85

@ -63,7 +63,8 @@ const state = () => {
withdrawType: 'relayer', withdrawType: 'relayer',
ethToReceive: '20000000000000000', ethToReceive: '20000000000000000',
defaultEthToReceive: '20000000000000000', defaultEthToReceive: '20000000000000000',
withdrawNote: '' withdrawNote: '',
relayerWithdrawGasLimit: null
} }
} }
@ -104,6 +105,9 @@ const mutations = {
}, },
SET_WITHDRAW_NOTE(state, withdrawNote) { SET_WITHDRAW_NOTE(state, withdrawNote) {
state.withdrawNote = withdrawNote state.withdrawNote = withdrawNote
},
SET_RELAYER_WITHDRAW_GAS_LIMIT(state, { relayerWithdrawGasLimit }) {
this._vm.$set(state, 'relayerWithdrawGasLimit', relayerWithdrawGasLimit)
} }
} }
@ -142,7 +146,7 @@ const getters = {
currentContract: (state, getters) => (params) => { currentContract: (state, getters) => (params) => {
return getters.tornadoProxyContract(params) return getters.tornadoProxyContract(params)
}, },
withdrawGas: (state, getters) => { defaultWithdrawGas: (state, getters) => {
let action = ACTION.WITHDRAW_WITH_EXTRA let action = ACTION.WITHDRAW_WITH_EXTRA
if (getters.hasEnabledLightProxy) { if (getters.hasEnabledLightProxy) {
@ -162,7 +166,7 @@ const getters = {
networkFee: (state, getters, rootState, rootGetters) => { networkFee: (state, getters, rootState, rootGetters) => {
const gasPrice = rootGetters['gasPrices/gasPrice'] const gasPrice = rootGetters['gasPrices/gasPrice']
const networkFee = toBN(gasPrice).mul(toBN(getters.withdrawGas)) const networkFee = toBN(gasPrice).mul(toBN(state.relayerWithdrawGasLimit || getters.defaultWithdrawGas))
if (getters.isOptimismConnected) { if (getters.isOptimismConnected) {
const l1Fee = rootGetters['gasPrices/l1Fee'] const l1Fee = rootGetters['gasPrices/l1Fee']
@ -636,6 +640,30 @@ const actions = {
return false return false
} }
}, },
async estimateRelayerWithdrawGasLimit(
{ getters, rootState, commit },
{ currency, amount, netId, proof, withdrawCallArgs }
) {
const tornadoProxy = getters.tornadoProxyContract({ netId })
const tornadoInstance = getters.instanceContract({ currency, amount, netId })
const relayer = rootState.relayer.selectedRelayer.address
let gasLimit
try {
const fetchedGasLimit = await tornadoProxy.methods
.withdraw(tornadoInstance._address, proof, ...withdrawCallArgs)
.estimateGas({
from: relayer,
to: tornadoProxy._address,
value: withdrawCallArgs[5] || 0
})
gasLimit = Math.floor(fetchedGasLimit * 1.3)
} catch (e) {
gasLimit = getters.defaultWithdrawGas
console.error(`Cannot fetch gas limit for relayer withdrawal, using default: ${gasLimit}`)
}
commit('SET_RELAYER_WITHDRAW_GAS_LIMIT', { relayerWithdrawGasLimit: gasLimit })
},
async checkSpentEventFromNullifier({ getters, dispatch }, parsedNote) { async checkSpentEventFromNullifier({ getters, dispatch }, parsedNote) {
try { try {
const isSpent = await dispatch('loadEvent', { const isSpent = await dispatch('loadEvent', {
@ -695,7 +723,7 @@ const actions = {
return { tree, root } return { tree, root }
}, },
async createSnarkProof( async createSnarkProof(
{ rootGetters, rootState, state, getters }, { rootGetters, rootState, state, getters, dispatch },
{ root, note, tree, recipient, leafIndex } { root, note, tree, recipient, leafIndex }
) { ) {
const { pathElements, pathIndices } = tree.path(leafIndex) const { pathElements, pathIndices } = tree.path(leafIndex)
@ -708,55 +736,71 @@ const actions = {
let fee = BigInt(0) let fee = BigInt(0)
let refund = BigInt(0) let refund = BigInt(0)
if (withdrawType === 'relayer') { async function calculateSnarkProof() {
let totalRelayerFee = getters.relayerFee const input = {
relayer = BigInt(rootState.relayer.selectedRelayer.address) // public
fee,
if (note.currency !== nativeCurrency) { root,
refund = BigInt(state.ethToReceive.toString()) refund,
totalRelayerFee = totalRelayerFee.add(getters.ethToReceiveInToken) relayer,
recipient: BigInt(recipient),
nullifierHash: note.nullifierHash,
// private
pathIndices,
pathElements,
secret: note.secret,
nullifier: note.nullifier
} }
fee = BigInt(totalRelayerFee.toString()) const { circuit, provingKey } = await getTornadoKeys()
if (!groth16) {
groth16 = await buildGroth16()
}
console.log('Start generating SNARK proof', input)
console.time('SNARK proof time')
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, provingKey)
const { proof } = websnarkUtils.toSolidityInput(proofData)
const args = [
toFixedHex(input.root),
toFixedHex(input.nullifierHash),
toFixedHex(input.recipient, 20),
toFixedHex(input.relayer, 20),
toFixedHex(input.fee),
toFixedHex(input.refund)
]
console.timeEnd('SNARK proof time')
return { args, proof }
} }
const input = { // Don't need to calculate or estimate relayer fee, so, return proof immediately
// public if (withdrawType !== 'relayer') return calculateSnarkProof()
fee,
root, relayer = BigInt(rootState.relayer.selectedRelayer.address)
refund, const { proof: dummyProof, args: dummyArgs } = await calculateSnarkProof()
relayer, const { netId, amount, currency } = note
recipient: BigInt(recipient), await dispatch('estimateRelayerWithdrawGasLimit', {
nullifierHash: note.nullifierHash, netId,
// private amount,
pathIndices, currency,
pathElements, proof: dummyProof,
secret: note.secret, withdrawCallArgs: dummyArgs
nullifier: note.nullifier })
let totalRelayerFee = getters.relayerFee
if (note.currency !== nativeCurrency) {
refund = BigInt(state.ethToReceive.toString())
totalRelayerFee = totalRelayerFee.add(getters.ethToReceiveInToken)
} }
const { circuit, provingKey } = await getTornadoKeys() fee = BigInt(totalRelayerFee.toString())
if (!groth16) { // Recalculate proof with actual fee and refund
groth16 = await buildGroth16() return calculateSnarkProof()
}
console.log('Start generating SNARK proof', input)
console.time('SNARK proof time')
const proofData = await websnarkUtils.genWitnessAndProve(groth16, input, circuit, provingKey)
const { proof } = websnarkUtils.toSolidityInput(proofData)
const args = [
toFixedHex(input.root),
toFixedHex(input.nullifierHash),
toFixedHex(input.recipient, 20),
toFixedHex(input.relayer, 20),
toFixedHex(input.fee),
toFixedHex(input.refund)
]
return { args, proof }
}, },
async prepareWithdraw({ dispatch, getters, commit }, { note, recipient }) { async prepareWithdraw({ dispatch, commit }, { note, recipient }) {
commit('REMOVE_PROOF', { note }) commit('REMOVE_PROOF', { note })
try { try {
const parsedNote = parseNote(note) const parsedNote = parseNote(note)
@ -776,7 +820,6 @@ const actions = {
note: parsedNote, note: parsedNote,
leafIndex: tree.indexOf(parsedNote.commitmentHex) leafIndex: tree.indexOf(parsedNote.commitmentHex)
}) })
console.timeEnd('SNARK proof time')
commit('SAVE_PROOF', { proof, args, note }) commit('SAVE_PROOF', { proof, args, note })
} catch (e) { } catch (e) {
console.error('prepareWithdraw', e) console.error('prepareWithdraw', e)