Compare commits

..

3 Commits

4 changed files with 133 additions and 68 deletions

@ -23,6 +23,9 @@
"@apollo/client": "^3.3.20", "@apollo/client": "^3.3.20",
"@metamask/onboarding": "^1.0.0", "@metamask/onboarding": "^1.0.0",
"@nuxtjs/moment": "^1.6.0", "@nuxtjs/moment": "^1.6.0",
"@tornado/gas-price-oracle": "^0.5.2-p1",
"@tornado/snarkjs": "0.1.20-p2",
"@tornado/websnark": "0.0.4-p1",
"@walletconnect/web3-provider": "1.7.8", "@walletconnect/web3-provider": "1.7.8",
"ajv": "^6.10.2", "ajv": "^6.10.2",
"arraybuffer-loader": "^1.0.8", "arraybuffer-loader": "^1.0.8",
@ -30,6 +33,7 @@
"bignumber.js": "^9.0.0", "bignumber.js": "^9.0.0",
"bloomfilter.js": "^1.0.2", "bloomfilter.js": "^1.0.2",
"circomlibjs": "0.1.2", "circomlibjs": "0.1.2",
"cross-env": "7.0.3",
"crypto": "^1.0.1", "crypto": "^1.0.1",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"eth-ens-namehash": "^2.0.8", "eth-ens-namehash": "^2.0.8",
@ -37,7 +41,6 @@
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"fixed-merkle-tree": "^0.7.3", "fixed-merkle-tree": "^0.7.3",
"form-data": "^3.0.0", "form-data": "^3.0.0",
"gas-price-oracle": "^0.5.0",
"graphql": "^15.5.1", "graphql": "^15.5.1",
"idb": "^6.0.0", "idb": "^6.0.0",
"jspdf": "^1.5.3", "jspdf": "^1.5.3",
@ -53,10 +56,7 @@
"vue-clipboard2": "^0.3.1", "vue-clipboard2": "^0.3.1",
"vue-i18n": "^8.15.4", "vue-i18n": "^8.15.4",
"vuex-persistedstate": "^2.7.0", "vuex-persistedstate": "^2.7.0",
"web3": "1.5.2", "web3": "1.5.2"
"cross-env": "7.0.3",
"@tornado/snarkjs": "0.1.20-p2",
"@tornado/websnark": "0.0.4-p1"
}, },
"devDependencies": { "devDependencies": {
"@nuxtjs/eslint-config": "^1.1.2", "@nuxtjs/eslint-config": "^1.1.2",

@ -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)

@ -1,7 +1,7 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import Web3 from 'web3' import Web3 from 'web3'
import { toHex, fromWei } from 'web3-utils' import { toHex, toWei, fromWei, toBN } from 'web3-utils'
import { GasPriceOracle } from 'gas-price-oracle' import { GasPriceOracle } from '@tornado/gas-price-oracle'
import { serialize } from '@ethersproject/transactions' import { serialize } from '@ethersproject/transactions'
import networkConfig from '@/networkConfig' import networkConfig from '@/networkConfig'
@ -10,7 +10,7 @@ import { DUMMY_NONCE, DUMMY_WITHDRAW_DATA } from '@/constants/variables'
export const state = () => { export const state = () => {
return { return {
gasParams: { gasPrice: 50 }, gasParams: { gasPrice: toWei(toBN(50), 'gwei') },
l1Fee: '0' l1Fee: '0'
} }
} }
@ -23,6 +23,9 @@ export const getters = {
return new GasPriceOracle({ return new GasPriceOracle({
chainId: netId, chainId: netId,
defaultRpc: rootGetters['settings/currentRpc'].url, defaultRpc: rootGetters['settings/currentRpc'].url,
minPriority: netId === 1 ? 2 : 0.05,
percentile: 5,
blocksCount: 20,
defaultFallbackGasPrices: gasPrices defaultFallbackGasPrices: gasPrices
}) })
}, },
@ -63,14 +66,25 @@ export const mutations = {
} }
export const actions = { export const actions = {
async fetchGasPrice({ getters, dispatch, commit, rootGetters }) { async fetchGasPrice({ getters, dispatch, commit, rootGetters, rootState }) {
const netId = rootGetters['metamask/netId']
const { pollInterval } = rootGetters['metamask/networkConfig'] const { pollInterval } = rootGetters['metamask/networkConfig']
const netId = Number(rootGetters['metamask/netId'])
const isLegacy = netId === 137 const { url: rpcUrl } = rootState.settings[`netId${netId}`].rpc
try { try {
const txGasParams = await getters.oracle.getTxGasParams({ isLegacy }) // Bump more for Polygon (MATIC) and for Goerli, because minPriority for this sidechains don't affect correctly
const bumpPercent = netId === 137 || netId === 5 ? 30 : 10
let txGasParams = {}
try {
// Use maxFeePerGas if eip1599 gas support by chain, use fast if legacy gas fetched
txGasParams = await getters.oracle.getTxGasParams({ legacySpeed: 'fast', bumpPercent }) // in wei
} catch (e) {
const web3 = new Web3(rpcUrl)
const wei = toBN(await web3.eth.getGasPrice())
const bumped = wei.add(wei.mul(toBN(bumpPercent)).div(toBN(100)))
txGasParams = { gasPrice: toHex(bumped) }
}
commit('SAVE_GAS_PARAMS', txGasParams) commit('SAVE_GAS_PARAMS', txGasParams)
await dispatch('fetchL1Fee') await dispatch('fetchL1Fee')
} catch (e) { } catch (e) {
@ -81,7 +95,7 @@ export const actions = {
}, },
setDefault({ commit, rootGetters }) { setDefault({ commit, rootGetters }) {
const { gasPrices } = rootGetters['metamask/networkConfig'] const { gasPrices } = rootGetters['metamask/networkConfig']
commit('SAVE_GAS_PARAMS', { gasPrice: gasPrices?.fast }) commit('SAVE_GAS_PARAMS', { gasPrice: toWei(gasPrices?.fast?.toFixed(9) || 0, 'gwei') })
}, },
async fetchL1Fee({ commit, getters, rootGetters }) { async fetchL1Fee({ commit, getters, rootGetters }) {
const netId = rootGetters['metamask/netId'] const netId = rootGetters['metamask/netId']

@ -2109,6 +2109,15 @@
dependencies: dependencies:
defer-to-connect "^1.0.1" defer-to-connect "^1.0.1"
"@tornado/gas-price-oracle@^0.5.2-p1":
version "0.5.2-p1"
resolved "https://git.tornado.ws/api/packages/tornado-packages/npm/%40tornado%2Fgas-price-oracle/-/0.5.2-p1/gas-price-oracle-0.5.2-p1.tgz#310fed9b481383ffeaa4c1bd105c934a71a9513c"
integrity sha512-FbkhDQMD/aEBogdG6IdSXmlraG9LdEhaO+8aeNXZA0Tgp5P4PJ8pgZXxTK2rpVXBMpXrFiFg4IDkpRGrx67d4g==
dependencies:
axios "^0.21.2"
bignumber.js "^9.0.0"
node-cache "^5.1.2"
"@tornado/snarkjs@0.1.20-p2": "@tornado/snarkjs@0.1.20-p2":
version "0.1.20-p2" version "0.1.20-p2"
resolved "https://git.tornado.ws/api/packages/tornado-packages/npm/%40tornado%2Fsnarkjs/-/0.1.20-p2/snarkjs-0.1.20-p2.tgz#e25a1d4ca8305887202d02cb38077795108f1ec3" resolved "https://git.tornado.ws/api/packages/tornado-packages/npm/%40tornado%2Fsnarkjs/-/0.1.20-p2/snarkjs-0.1.20-p2.tgz#e25a1d4ca8305887202d02cb38077795108f1ec3"
@ -7800,14 +7809,6 @@ functional-red-black-tree@^1.0.1:
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
gas-price-oracle@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/gas-price-oracle/-/gas-price-oracle-0.5.0.tgz#b29f83c97bb4b091a08da7c10e2d1e5888bbade4"
integrity sha512-um0cmd9qxGkDHirV1HcrjQ4vedVxK7u+uMeJIjo2yUMYe6T46ihbMnRncF5tfP9deU5hPHJ8FvVRZY1Y/CKkLQ==
dependencies:
axios "^0.21.2"
bignumber.js "^9.0.0"
gauge@~2.7.3: gauge@~2.7.3:
version "2.7.4" version "2.7.4"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
@ -11039,6 +11040,13 @@ node-cache@^4.1.1:
clone "2.x" clone "2.x"
lodash "^4.17.15" lodash "^4.17.15"
node-cache@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d"
integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==
dependencies:
clone "2.x"
node-fetch@2.6.1, node-fetch@^2.6.1: node-fetch@2.6.1, node-fetch@^2.6.1:
version "2.6.1" version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"