From 921033020880725a2bd382506fb183dd6d506d1d Mon Sep 17 00:00:00 2001 From: Theo Date: Sun, 27 Aug 2023 12:30:25 -0700 Subject: [PATCH] Change gas & fees calculations: - Use @tornado/tornado-oracles lib to calculate withdrawal fee via relayer - When calculating fee, save backwards compatibility for Ethereum Mainnet V4 relayers & old UI builds - Bump UI version to 1.2.0 - Display most up-to-date information about relayer withdrawal fees on WithdrawalTotal page --- components/withdraw/Withdraw.vue | 7 +- components/withdraw/WithdrawTotal.vue | 13 +-- package.json | 4 +- store/application.js | 120 +++++++------------------- store/fees.js | 31 ++++++- store/relayer.js | 2 +- yarn.lock | 8 +- 7 files changed, 73 insertions(+), 112 deletions(-) diff --git a/components/withdraw/Withdraw.vue b/components/withdraw/Withdraw.vue index 12fc6fa..da90dde 100644 --- a/components/withdraw/Withdraw.vue +++ b/components/withdraw/Withdraw.vue @@ -192,7 +192,7 @@ export default { ...mapState('relayer', ['isLoadingRelayers']), ...mapGetters('txHashKeeper', ['txExplorerUrl']), ...mapGetters('application', ['isNotEnoughTokens', 'selectedStatisticCurrency']), - ...mapGetters('metamask', ['networkConfig', 'netId', 'isLoggedIn', 'nativeCurrency']), + ...mapGetters('metamask', ['networkConfig', 'netId', 'isLoggedIn']), notEnoughDeposits() { if (this.depositsPast < 5) { return true @@ -337,10 +337,7 @@ export default { }) } this.$store.dispatch('application/setAndUpdateStatistic', { currency, amount: Number(amount) }) - this.$store.dispatch('fees/calculateWithdrawalNetworkFee', {}) - if (currency !== this.nativeCurrency) { - this.$store.dispatch('application/setDefaultEthToReceive', { currency }) - } + this.$store.dispatch('fees/calculateWithdrawalFeeViaRelayer', {}) this.$store.dispatch('loading/updateProgress', { progress: -1 }) this.depositsPast = Number(depositsPast) <= 0 ? 0 : depositsPast this.depositTxHash = txHash diff --git a/components/withdraw/WithdrawTotal.vue b/components/withdraw/WithdrawTotal.vue index 7c19361..609a4e9 100644 --- a/components/withdraw/WithdrawTotal.vue +++ b/components/withdraw/WithdrawTotal.vue @@ -71,7 +71,7 @@ export default { }, computed: { ...mapState('application', ['selectedStatistic']), - ...mapState('fees', ['withdrawalNetworkFee']), + ...mapState('fees', ['withdrawalNetworkFee', 'withdrawalFeeViaRelayer']), ...mapGetters('metamask', ['networkConfig', 'nativeCurrency']), ...mapGetters('metamask', { networkCurrency: 'currency' @@ -119,17 +119,12 @@ export default { return fromWei(this.ethToReceive) }, total() { - const { amount, currency } = this.selectedStatistic + const { amount } = this.selectedStatistic let total = toBN(this.fromDecimals(amount.toString())) if (this.withdrawType === 'relayer') { - const relayerFee = this.totalRelayerFee - - if (currency === this.nativeCurrency) { - total = total.sub(relayerFee) - } else { - total = total.sub(relayerFee).sub(this.ethToReceiveInToken) - } + const relayerFee = this.withdrawalFeeViaRelayer + total = total.sub(relayerFee) } return this.toDecimals(total, null, 6) diff --git a/package.json b/package.json index 4aae192..611e7e6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tornadocash-classic-ui", - "version": "1.1.0", + "version": "1.2.0", "description": "Private ethereum transactions", "author": "Tornado Cash Team", "scripts": { @@ -24,7 +24,7 @@ "@metamask/onboarding": "^1.0.0", "@nuxtjs/moment": "^1.6.0", "@tornado/snarkjs": "0.1.20-p2", - "@tornado/tornado-oracles": "1.2", + "@tornado/tornado-oracles": "1.3.2", "@tornado/websnark": "0.0.4-p1", "@walletconnect/web3-provider": "1.7.8", "ajv": "^6.10.2", diff --git a/store/application.js b/store/application.js index 58e54dd..8aef8b8 100644 --- a/store/application.js +++ b/store/application.js @@ -9,7 +9,6 @@ import MulticallABI from '@/abis/Multicall.json' import InstanceABI from '@/abis/Instance.abi.json' import TornadoProxyABI from '@/abis/TornadoProxy.abi.json' -import { ACTION, ACTION_GAS } from '@/constants/variables' import { graph, treesInterface, EventsFactory } from '@/services' import { @@ -18,7 +17,6 @@ import { toFixedHex, saveAsFile, isEmptyArray, - decimalPlaces, parseHexNote, checkCommitments, buffPedersenHash @@ -63,8 +61,7 @@ const state = () => { withdrawType: 'relayer', ethToReceive: '20000000000000000', defaultEthToReceive: '20000000000000000', - withdrawNote: '', - relayerWithdrawGasLimit: null + withdrawNote: '' } } @@ -105,9 +102,6 @@ const mutations = { }, SET_WITHDRAW_NOTE(state, withdrawNote) { state.withdrawNote = withdrawNote - }, - SET_RELAYER_WITHDRAW_GAS_LIMIT(state, { relayerWithdrawGasLimit }) { - this._vm.$set(state, 'relayerWithdrawGasLimit', relayerWithdrawGasLimit) } } @@ -146,42 +140,24 @@ const getters = { currentContract: (state, getters) => (params) => { return getters.tornadoProxyContract(params) }, - defaultWithdrawGas: (state, getters) => { - let action = ACTION.WITHDRAW_WITH_EXTRA + relayerWithdrawalTxData: (state, getters, rootState, rootGetters) => ({ + proof, + withdrawCallArgs, + currency, + amount + }) => { + const netId = rootGetters['metamask/netId'] + const tornadoProxy = getters.tornadoProxyContract({ netId }) + const tornadoInstance = getters.instanceContract({ currency, amount, netId }) - if (getters.hasEnabledLightProxy) { - action = ACTION.WITHDRAW - } + const calldata = tornadoProxy.methods + .withdraw(tornadoInstance._address, proof, ...withdrawCallArgs) + .encodeABI() - if (getters.isOptimismConnected) { - action = ACTION.OP_WITHDRAW - } - - if (getters.isArbitrumConnected) { - action = ACTION.ARB_WITHDRAW - } - - return ACTION_GAS[action] - }, - relayerFee: (state, getters, rootState, rootGetters) => { - const { currency, amount } = rootState.application.selectedStatistic - const { decimals } = rootGetters['metamask/networkConfig'].tokens[currency] - const nativeCurrency = rootGetters['metamask/nativeCurrency'] - const total = toBN(rootGetters['token/fromDecimals'](amount.toString())) - const fee = rootState.relayer.selectedRelayer.tornadoServiceFee - const decimalsPoint = decimalPlaces(fee) - const roundDecimal = 10 ** decimalsPoint - const aroundFee = toBN(parseInt(fee * roundDecimal, 10)) - const tornadoServiceFee = total.mul(aroundFee).div(toBN(roundDecimal * 100)) - const ethFee = rootState.fees.withdrawalNetworkFee - switch (currency) { - case nativeCurrency: { - return ethFee.add(tornadoServiceFee) - } - default: { - const tokenFee = ethFee.mul(toBN(10 ** decimals)).div(toBN(rootState.price.prices[currency])) - return tokenFee.add(tornadoServiceFee) - } + return { + to: tornadoProxy._address, + data: calldata, + value: withdrawCallArgs[5] || 0 } }, ethToReceiveInToken: (state, getters, rootState, rootGetters) => { @@ -196,7 +172,7 @@ const getters = { let total = toBN(rootGetters['token/fromDecimals'](amount.toString())) if (state.withdrawType === 'relayer') { - const relayerFee = getters.relayerFee + const relayerFee = rootState.fees.withdrawalFeeViaRelayer const nativeCurrency = rootGetters['metamask/nativeCurrency'] if (currency === nativeCurrency) { @@ -214,9 +190,8 @@ const getters = { const { decimals } = rootGetters['metamask/networkConfig'].tokens[currency] const total = toBN(rootGetters['token/fromDecimals'](amount.toString())) const price = rootState.price.prices[currency] - const relayerFee = getters.relayerFee return total - .sub(relayerFee) + .sub(rootState.fees.withdrawalFeeViaRelayer) .mul(toBN(price)) .div(toBN(10 ** decimals)) }, @@ -628,34 +603,6 @@ const actions = { return false } }, - async estimateRelayerWithdrawGasLimit( - { getters, rootState, rootGetters, commit }, - { currency, amount, netId, proof, withdrawCallArgs } - ) { - const tornadoProxy = getters.tornadoProxyContract({ netId }) - const tornadoInstance = getters.instanceContract({ currency, amount, netId }) - const relayer = rootState.relayer.selectedRelayer.address - - const gasPrice = toBN(rootGetters['fees/gasPrice']) - - let gasLimit - try { - const fetchedGasLimit = await tornadoProxy.methods - .withdraw(tornadoInstance._address, proof, ...withdrawCallArgs) - .estimateGas({ - from: relayer, - to: tornadoProxy._address, - value: withdrawCallArgs[5] || 0, - gasPrice, - gasLimit: getters.defaultWithdrawGas - }) - 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) { try { const isSpent = await dispatch('loadEvent', { @@ -724,7 +671,7 @@ const actions = { const nativeCurrency = rootGetters['metamask/nativeCurrency'] const withdrawType = state.withdrawType - let relayer = BigInt(recipient) + const relayer = BigInt(rootState.relayer.selectedRelayer.address) let fee = BigInt(0) let refund = BigInt(0) @@ -770,24 +717,21 @@ const actions = { // Don't need to calculate or estimate relayer fee, so, return proof immediately if (withdrawType !== 'relayer') return calculateSnarkProof() - relayer = BigInt(rootState.relayer.selectedRelayer.address) - const { proof: dummyProof, args: dummyArgs } = await calculateSnarkProof() - const { netId, amount, currency } = note - await dispatch('estimateRelayerWithdrawGasLimit', { - netId, - amount, - currency, + fee = BigInt(rootState.fees.withdrawalFeeViaRelayer) + const naiveProof = await calculateSnarkProof() + if (Number(note.netId) === 1) return naiveProof // Don't need to smart-estimate fee if we use V4 withdrawal + + const { proof: dummyProof, args: dummyArgs } = naiveProof + const withdrawalTx = getters.relayerWithdrawalTxData({ proof: dummyProof, - withdrawCallArgs: dummyArgs + withdrawCallArgs: dummyArgs, + amount: note.amount, + currency: note.currency }) - let totalRelayerFee = getters.relayerFee + if (note.currency !== nativeCurrency) refund = BigInt(state.ethToReceive.toString()) - if (note.currency !== nativeCurrency) { - refund = BigInt(state.ethToReceive.toString()) - totalRelayerFee = totalRelayerFee.add(getters.ethToReceiveInToken) - } - - fee = BigInt(totalRelayerFee.toString()) + await dispatch('fees/calculateWithdrawalFeeViaRelayer', { tx: withdrawalTx }, { root: true }) + fee = BigInt(rootState.fees.withdrawalFeeViaRelayer) // Recalculate proof with actual fee and refund return calculateSnarkProof() diff --git a/store/fees.js b/store/fees.js index f842af5..4b4feb6 100644 --- a/store/fees.js +++ b/store/fees.js @@ -5,7 +5,8 @@ import { TornadoFeeOracleV4, TornadoFeeOracleV5 } from '@tornado/tornado-oracles export const state = () => { return { gasPriceParams: { gasPrice: toWei(toBN(50), 'gwei') }, - withdrawalNetworkFee: toBN(0) + withdrawalNetworkFee: toBN(0), + withdrawalFeeViaRelayer: toBN(0) } } @@ -38,6 +39,9 @@ export const mutations = { }, SAVE_WITHDRAWAL_NETWORK_FEE(state, gasFee) { state.withdrawalNetworkFee = gasFee + }, + SAVE_WITHDRAWAL_FEE_VIA_RELAYER(state, fee) { + state.withdrawalFeeViaRelayer = fee } } @@ -47,8 +51,6 @@ export const actions = { try { const gasPriceParams = await getters.oracle.getGasPriceParams() - console.log(gasPriceParams) - commit('SAVE_GAS_PARAMS', gasPriceParams) } catch (e) { console.error('fetchGasPrice', e) @@ -64,5 +66,28 @@ export const actions = { const withdrawalGas = await getters.oracle.getGas(tx, 'user_withdrawal') commit('SAVE_WITHDRAWAL_NETWORK_FEE', toBN(withdrawalGas)) + }, + async calculateWithdrawalFeeViaRelayer({ dispatch, getters, commit, rootGetters, rootState }, { tx }) { + const feePercent = rootState.relayer.selectedRelayer.tornadoServiceFee + const { currency, amount } = rootState.application.selectedStatistic + const nativeCurrency = rootGetters['metamask/nativeCurrency'] + const { decimals } = rootGetters['metamask/networkConfig'].tokens[currency] + + await dispatch('calculateWithdrawalNetworkFee', { tx }) + if (currency !== nativeCurrency) + await dispatch('application/setDefaultEthToReceive', { currency }, { root: true }) + + const withdrawalFee = await getters.oracle.calculateWithdrawalFeeViaRelayer( + 'user_withdrawal', + tx, + feePercent, + currency.toLowerCase(), + amount, + decimals, + rootState.application.ethToReceive || 0, + rootState.price.prices[currency.toLowerCase()] + ) + + commit('SAVE_WITHDRAWAL_FEE_VIA_RELAYER', toBN(withdrawalFee)) } } diff --git a/store/relayer.js b/store/relayer.js index 1722433..b0733ec 100644 --- a/store/relayer.js +++ b/store/relayer.js @@ -249,7 +249,7 @@ export const actions = { const isUpdatedMajor = major === requiredMajor if (prerelease) return false - return isUpdatedMajor && Number(patch) >= 4 // Patch checking - also backwards compatibility + return isUpdatedMajor && (Number(patch) >= 4 || netId !== 1) // Patch checking - also backwards compatibility for Mainnet } if (!isRelayerUpdated()) { diff --git a/yarn.lock b/yarn.lock index e970a26..ef5896e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2477,10 +2477,10 @@ resolved "https://git.tornado.ws/api/packages/tornado-packages/npm/%40tornado%2Ftornado-config/-/2.0.0/tornado-config-2.0.0.tgz#52bbc179ecb2385f71b4d56e060b68e7dd6fb8b4" integrity sha512-7EkpWNfEm34VEOrbLnPpvd/aUJYnA1L+6/qx2fZ/AfmuJFkjSZ18Z4jvVGNY7ktKIhTu3/Tbze+9l3eNueCNIA== -"@tornado/tornado-oracles@1.2": - version "1.2.0" - resolved "https://git.tornado.ws/api/packages/tornado-packages/npm/%40tornado%2Ftornado-oracles/-/1.2.0/tornado-oracles-1.2.0.tgz#07121625343ba683f2d82f3e0cdf5ec70378d833" - integrity sha512-AmqudzDi5LS7Enm2uEAzDxjqqByUeUvs4Bg6FfkEufE6MPs3twOwdWxMDKx8xxlFMVlVeVCv2hMs1nGHnabWdw== +"@tornado/tornado-oracles@1.3.2": + version "1.3.2" + resolved "https://git.tornado.ws/api/packages/tornado-packages/npm/%40tornado%2Ftornado-oracles/-/1.3.2/tornado-oracles-1.3.2.tgz#83d269619b6e3a9a1abe964a561c3dfd533cca4c" + integrity sha512-z3NI35GKKznxVigF0OghqQ8S3sT/S4oD3AtXqyf+P2bfkriAsdrXnsSMW7Vxej529e5k3cA0dPtOs4eFqFE/7Q== dependencies: "@tornado/gas-price-oracle" "^0.5.3" "@tornado/tornado-config" "^2.0.0"