From 1ded1f51d1762d50e52144d36b00000056e931f2 Mon Sep 17 00:00:00 2001 From: Tornado Contrib Date: Sun, 12 May 2024 20:58:20 +0000 Subject: [PATCH] Update codes for @tornado/tornado-oracles and optimize withdrawal process --- constants/variables.js | 2 + networkConfig.js | 29 +++-- package.json | 3 +- services/lookupAddress.js | 6 +- services/registry/index.js | 14 ++- store/application.js | 149 +++++++++++++++++-------- store/fees.js | 206 +++++++++++++++++++++++++++++------ store/governance/gov.js | 8 +- store/governance/proposal.js | 13 +-- store/price.js | 23 +++- yarn.lock | 6 +- 11 files changed, 348 insertions(+), 111 deletions(-) diff --git a/constants/variables.js b/constants/variables.js index 3a31ad6..2b421fb 100644 --- a/constants/variables.js +++ b/constants/variables.js @@ -43,6 +43,8 @@ export const ACTION_GAS = Object.freeze({ [ACTION.ARB_WITHDRAW]: 1900000 }) +export const WITHDRAW_GAS_LIMIT = 600_000 + export const GAS_PRICES = ['low', 'standard', 'fast'] export const addressType = { type: 'string', pattern: '^0x[a-fA-F0-9]{40}$' } diff --git a/networkConfig.js b/networkConfig.js index 68070ba..ce89460 100644 --- a/networkConfig.js +++ b/networkConfig.js @@ -42,6 +42,7 @@ export default { routerContract: '0xd90e2f925DA726b50C4Ed8D0Fb90Ad053324F31b', registryContract: '0x58E8dCC13BE9780fC42E8723D8EaD4CF46943dF2', echoContractAccount: '0x9B27DD5Bb15d42DC224FCD0B7caEbBe16161Df42', + offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8', aggregatorContract: '0xE8F47A78A6D52D317D0D2FFFac56739fE14D1b49', tokens: { eth: { @@ -62,9 +63,10 @@ export default { '100000': '0x23773E65ed146A459791799d01336DB287f25334' }, tokenAddress: '0x6B175474E89094C44Da98b954EedeAC495271d0F', + tokenGasLimit: 70_000, symbol: 'DAI', decimals: 18, - gasLimit: '55000' + gasLimit: 700_000 }, cdai: { instanceAddress: { @@ -74,9 +76,10 @@ export default { '5000000': '0xD21be7248e0197Ee08E0c20D4a96DEBdaC3D20Af' }, tokenAddress: '0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643', + tokenGasLimit: 200_000, symbol: 'cDAI', decimals: 8, - gasLimit: '425000' + gasLimit: 700_000 }, /** * Instances frozen due to sanctions @@ -86,9 +89,10 @@ export default { '1000': '0x4736dCf1b7A3d580672CcE6E7c65cd5cc9cFBa9D' }, tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + tokenGasLimit: 70_000, symbol: 'USDC', decimals: 6, - gasLimit: '80000' + gasLimit: 700_000, }, usdt: { instanceAddress: { @@ -96,9 +100,10 @@ export default { '1000': '0x0836222F2B2B24A3F36f98668Ed8F0B38D1a872f' }, tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + tokenGasLimit: 70_000, symbol: 'USDT', decimals: 6, - gasLimit: '100000' + gasLimit: 700_000, }, **/ wbtc: { @@ -108,9 +113,10 @@ export default { '10': '0xbB93e510BbCD0B7beb5A853875f9eC60275CF498' }, tokenAddress: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', + tokenGasLimit: 70_000, symbol: 'WBTC', decimals: 8, - gasLimit: '85000' + gasLimit: 700_000 } }, ensSubdomainKey: 'mainnet-tornado', @@ -147,6 +153,7 @@ export default { deployedBlock: 8158799, multicall: '0x41263cba59eb80dc200f3e2544eda4ed6a90e76c', echoContractAccount: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', + offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8', rpcUrls: { tornadoRPC: { name: 'Tornado RPC', @@ -202,6 +209,9 @@ export default { deployedBlock: 16257962, multicall: '0x11ce4B23bD875D7F5C6a31084f55fDe1e9A87507', echoContractAccount: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', + offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8', + gasPriceOracleContract: '0xF81A8D8D3581985D3969fe53bFA67074aDFa8F3C', + gasStationApi: 'https://polygon-oracle.tornadocash-rpc.com', rpcUrls: { chainnodes: { name: 'Chainnodes RPC', @@ -253,7 +263,8 @@ export default { deployedBlock: 2243689, multicall: '0x35A6Cdb2C9AD4a45112df4a04147EB07dFA01aB7', echoContractAccount: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', - ovmGasPriceOracleContract: '0x420000000000000000000000000000000000000F', + offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8', + optimismL1FeeOracleAddress: '0x420000000000000000000000000000000000000F', rpcUrls: { tornadoRPC: { name: 'Tornado RPC', @@ -309,6 +320,7 @@ export default { deployedBlock: 3430648, multicall: '0x842eC2c7D803033Edf55E478F461FC547Bc54EB2', echoContractAccount: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', + offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8', rpcUrls: { tornadoRPC: { name: 'Tornado RPC', @@ -368,6 +380,7 @@ export default { deployedBlock: 17754561, multicall: '0xb5b692a88bdfc81ca69dcb1d924f59f0413a602a', echoContractAccount: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', + offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8', rpcUrls: { tornadoRPC: { name: 'Tornado RPC', @@ -423,6 +436,7 @@ export default { deployedBlock: 4429818, multicall: '0xe86e3989c74293Acc962156cd3F525c07b6a1B6e', echoContractAccount: '0xa75BF2815618872f155b7C4B0C81bF990f5245E4', + offchainOracleContract: '0x0AdDd25a91563696D8567Df78D5A01C9a991F9B8', rpcUrls: { publicRpc: { name: 'Avalanche RPC', @@ -511,9 +525,10 @@ export default { '100000': '0x73B4BD04bF83206B6e979BE2507098F92EDf4F90' }, tokenAddress: '0xFF34B3d4Aee8ddCd6F9AFFFB6Fe49bD371b8a357', + tokenGasLimit: 70_000, symbol: 'DAI', decimals: 18, - gasLimit: '55000' + gasLimit: 700_000 } }, ensSubdomainKey: 'sepolia-tornado', diff --git a/package.json b/package.json index 8ad81c5..0d433b8 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@nuxtjs/moment": "^1.6.0", "@tornado/fixed-merkle-tree": "0.7", "@tornado/snarkjs": "0.1.20", - "@tornado/tornado-oracles": "git+https://git.tornado.ws/tornadocontrib/tornado-oracles.git#937d65f829e044c1b649ca9305fa4d50733a6d57", + "@tornado/tornado-oracles": "git+https://git.tornado.ws/tornadocontrib/tornado-oracles.git#7c646dc11f5785457f64f3aba297183fb70992da", "@tornado/websnark": "0.0.4", "@walletconnect/web3-provider": "1.7.8", "ajv": "^6.10.2", @@ -44,6 +44,7 @@ "dotenv": "^8.2.0", "eth-ens-namehash": "^2.0.8", "eth-sig-util": "^2.5.3", + "ethers": "^6.12.1", "file-saver": "^2.0.5", "form-data": "^3.0.0", "graphql": "^15.5.1", diff --git a/services/lookupAddress.js b/services/lookupAddress.js index 66ac81c..d4e0323 100644 --- a/services/lookupAddress.js +++ b/services/lookupAddress.js @@ -1,12 +1,13 @@ // from https://github.com/ChainSafe/web3.js/issues/2683#issuecomment-547348416 import namehash from 'eth-ens-namehash' -import { BigNumber, utils } from 'ethers' import ABI from 'web3-eth-ens/lib/resources/ABI/Resolver' import uniq from 'lodash/uniq' import chunk from 'lodash/chunk' import { CHUNK_COUNT_PER_BATCH_REQUEST } from '@/constants' +const { isAddress } = require('web3-utils') + export const createBatchRequestCallback = (resolve, reject) => (error, data) => { if (error) { reject(error) @@ -65,8 +66,7 @@ const createFetchEnsNames = (web3, batch, results) => async (data) => { batch.add(requestData) }) - const isZeroAddress = - ensName.trim().length && utils.isAddress(ensName) && BigNumber.from(ensName).isZero() + const isZeroAddress = ensName.trim().length && isAddress(ensName) && BigInt(ensName) === BigInt(0) if (isZeroAddress) return results diff --git a/services/registry/index.js b/services/registry/index.js index 99a9759..cbf6280 100644 --- a/services/registry/index.js +++ b/services/registry/index.js @@ -270,12 +270,22 @@ class RelayerRegister { } getValidRelayers = async (relayers, ensSubdomainKey) => { - const relayerNameHashes = relayers.map((r) => namehash.hash(r.ensName)) + const relayersSet = new Set() + + const uniqueRelayers = relayers.filter(({ ensName }) => { + if (!relayersSet.has(ensName)) { + relayersSet.add(ensName) + return true + } + return false + }) + + const relayerNameHashes = uniqueRelayers.map((r) => namehash.hash(r.ensName)) const relayersData = await this.aggregator.methods.relayersData(relayerNameHashes, subdomains).call() const validRelayers = relayersData.reduce( - (acc, curr, index) => this.filterRelayer(acc, curr, ensSubdomainKey, relayers[index]), + (acc, curr, index) => this.filterRelayer(acc, curr, ensSubdomainKey, uniqueRelayers[index]), [] ) diff --git a/store/application.js b/store/application.js index a4afa98..777029f 100644 --- a/store/application.js +++ b/store/application.js @@ -1,6 +1,7 @@ /* eslint-disable camelcase */ /* eslint-disable no-console, import/order */ import Web3 from 'web3' +import { ZeroAddress } from 'ethers' import networkConfig from '@/networkConfig' import { cachedEventsLength, eventsType, httpConfig } from '@/constants' @@ -106,26 +107,29 @@ const mutations = { } const getters = { + getWeb3: (state, getters, rootState, rootGetters) => { + const netId = rootGetters['metamask/netId'] + const { url } = rootState.settings[`netId${netId}`].rpc + const httpProvider = new Web3.providers.HttpProvider(url, httpConfig) + return new Web3(httpProvider) + }, eventsInterface: (state, getters, rootState, rootGetters) => { const netId = rootGetters['metamask/netId'] const { url } = rootState.settings[`netId${netId}`].rpc return new EventsFactory(url) }, - instanceContract: (state, getters, rootState) => ({ currency, amount, netId }) => { + instanceContract: (state, getters) => ({ currency, amount, netId }) => { const config = networkConfig[`netId${netId}`] - const { url } = rootState.settings[`netId${netId}`].rpc const address = config.tokens[currency].instanceAddress[amount] - const httpProvider = new Web3.providers.HttpProvider(url, httpConfig) - const web3 = new Web3(httpProvider) + const web3 = getters.getWeb3 return new web3.eth.Contract(InstanceABI, address) }, - multicallContract: (state, getters, rootState) => ({ netId }) => { + multicallContract: (state, getters) => ({ netId }) => { const config = networkConfig[`netId${netId}`] - const { url } = rootState.settings[`netId${netId}`].rpc - const web3 = new Web3(url) + const web3 = getters.getWeb3 return new web3.eth.Contract(MulticallABI, config.multicall) }, - tornadoProxyContract: (state, getters, rootState) => ({ netId }) => { + tornadoProxyContract: (state, getters) => ({ netId }) => { const { 'tornado-proxy.contract.tornadocash.eth': tornadoProxy, 'tornado-router.contract.tornadocash.eth': tornadoRouter, @@ -133,8 +137,7 @@ const getters = { } = networkConfig[`netId${netId}`] const proxyContract = tornadoRouter || tornadoProxy || tornadoProxyLight - const { url } = rootState.settings[`netId${netId}`].rpc - const web3 = new Web3(url) + const web3 = getters.getWeb3 return new web3.eth.Contract(TornadoProxyABI, proxyContract) }, currentContract: (state, getters) => (params) => { @@ -150,11 +153,16 @@ const getters = { const tornadoProxy = getters.tornadoProxyContract({ netId }) const tornadoInstance = getters.instanceContract({ currency, amount, netId }) + const { withdrawType } = state + const relayer = rootState.relayer.selectedRelayer.address + const { ethAccount } = rootState.metamask + const calldata = tornadoProxy.methods .withdraw(tornadoInstance._address, proof, ...withdrawCallArgs) .encodeABI() return { + from: withdrawType === 'relayer' ? relayer : ethAccount, to: tornadoProxy._address, data: calldata, value: withdrawCallArgs[5] || 0 @@ -510,6 +518,7 @@ const actions = { }, async sendDeposit({ state, rootState, getters, rootGetters, dispatch, commit }, { isEncrypted }) { try { + const web3 = getters.getWeb3 const { commitment, note, prefix } = state // eslint-disable-next-line prefer-const let [, currency, amount, netId] = prefix.split('-') @@ -548,7 +557,7 @@ const actions = { value: numberToHex(value), data } - const gasLimit = await rootGetters['fees/oracle'].getGasLimit(incompletedTx, 'other', 10) + const gasLimit = Math.floor((await web3.eth.estimateGas(incompletedTx)) * 1.2) const callParams = { method: 'eth_sendTransaction', @@ -665,36 +674,69 @@ const actions = { { rootGetters, rootState, state, getters, dispatch }, { root, note, tree, recipient, leafIndex } ) { + const { circuit, provingKey } = await getTornadoKeys() + + if (!groth16) { + groth16 = await buildGroth16() + } + + const web3 = getters.getWeb3 + + const { nullifierHash, secret, nullifier, amount, currency } = note + + const { denomination, isNativeCurrency } = rootGetters['fees/selectedInstance'] + const withdrawType = state.withdrawType + const { pathElements, pathIndices } = tree.path(leafIndex) console.log('pathElements, pathIndices', pathElements, pathIndices) - const nativeCurrency = rootGetters['metamask/nativeCurrency'] - const withdrawType = state.withdrawType + const defaultGasLimit = rootGetters['fees/gasLimit']() + let gasLimit = defaultGasLimit - let relayer = BigInt(0) - let fee = BigInt(0) - let refund = BigInt(0) + async function getProof() { + let relayer = ZeroAddress + let fee = BigInt(0) + let refund = BigInt(0) + + if (withdrawType === 'relayer') { + // Recalculate proof with actual fee and refund + await dispatch( + 'fees/calculateWithdrawalFeeViaRelayer', + { + tx: { + gasLimit + } + }, + { root: true } + ) + + relayer = rootState.relayer.selectedRelayer.address + fee = BigInt(rootState.fees.withdrawalFeeViaRelayer) + + if (fee > denomination) { + throw new Error( + 'Relayer fee exceeds the withdraw amount, disable relayer withdrawal or use other amount' + ) + } + } + + if (!isNativeCurrency) { + refund = rootGetters['fees/ethRefund'] + } - async function calculateSnarkProof() { const input = { // public fee, root, refund, - relayer, + relayer: BigInt(relayer), recipient: BigInt(recipient), - nullifierHash: note.nullifierHash, + nullifierHash, // private pathIndices, pathElements, - secret: note.secret, - nullifier: note.nullifier - } - - const { circuit, provingKey } = await getTornadoKeys() - - if (!groth16) { - groth16 = await buildGroth16() + secret, + nullifier } console.log('Start generating SNARK proof', input) @@ -711,31 +753,41 @@ const actions = { toFixedHex(input.refund) ] console.timeEnd('SNARK proof time') - return { args, proof } + + return { + relayer, + fee, + proof, + args + } } - // Don't need to calculate or estimate relayer fee, so, return proof immediately - if (withdrawType !== 'relayer') return calculateSnarkProof() + // eslint-disable-next-line prefer-const + let { proof, args } = await getProof() - relayer = BigInt(rootState.relayer.selectedRelayer.address) - 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, - amount: note.amount, - currency: note.currency + proof, + withdrawCallArgs: args, + amount, + currency }) - if (note.currency !== nativeCurrency) refund = BigInt(state.ethToReceive.toString()) - await dispatch('fees/calculateWithdrawalFeeViaRelayer', { tx: withdrawalTx }, { root: true }) - fee = BigInt(rootState.fees.withdrawalFeeViaRelayer) + gasLimit = BigInt(await web3.eth.estimateGas(withdrawalTx)) - // Recalculate proof with actual fee and refund - return calculateSnarkProof() + // If required gasLimit is above default, calculate the snark proof again + if (gasLimit > defaultGasLimit) { + ;({ proof, args } = await getProof()) + + const withdrawalTx = getters.relayerWithdrawalTxData({ + proof, + withdrawCallArgs: args, + amount, + currency + }) + gasLimit = BigInt(await web3.eth.estimateGas(withdrawalTx)) + } + + return { proof, args } }, async prepareWithdraw({ dispatch, commit }, { note, recipient }) { commit('REMOVE_PROOF', { note }) @@ -765,6 +817,7 @@ const actions = { }, async withdraw({ state, rootState, rootGetters, dispatch, getters }, { note }) { try { + const web3 = getters.getWeb3 const [, currency, amount, netId] = note.split('-') const config = networkConfig[`netId${netId}`] const { proof, args } = state.notes[note] @@ -782,7 +835,7 @@ const actions = { to: contractInstance._address, from: ethAccount } - const gasLimit = await rootGetters['fees/oracle'].getGasLimit(incompletedTx, 'other', 20) + const gasLimit = Math.floor((await web3.eth.estimateGas(incompletedTx)) * 1.1) const callParams = { method: 'eth_sendTransaction', @@ -923,8 +976,8 @@ const actions = { console.error(`Method loadWithdrawalData has error: ${e}`) } }, - async setDefaultEthToReceive({ commit, rootGetters }, { currency }) { - const ethToReceive = await rootGetters['fees/oracle'].calculateRefundInETH(currency.toLowerCase()) + setDefaultEthToReceive({ commit, rootGetters }, { gasPrice, refundGasLimit }) { + const ethToReceive = rootGetters['fees/oracle'].defaultEthRefund(gasPrice, refundGasLimit).toString() commit('SAVE_ETH_TO_RECEIVE', { ethToReceive }) commit('SAVE_DEFAULT_ETH_TO_RECEIVE', { ethToReceive }) }, diff --git a/store/fees.js b/store/fees.js index 4b4feb6..fc0521a 100644 --- a/store/fees.js +++ b/store/fees.js @@ -1,35 +1,122 @@ /* eslint-disable no-console */ -import { toWei, fromWei, toBN } from 'web3-utils' -import { TornadoFeeOracleV4, TornadoFeeOracleV5 } from '@tornado/tornado-oracles' +import { toWei, toBN } from 'web3-utils' +import { formatUnits, parseUnits } from 'ethers' +import { ChainId, TornadoFeeOracle, getProvider } from '@tornado/tornado-oracles' +import { WITHDRAW_GAS_LIMIT } from '@/constants/variables' export const state = () => { return { gasPriceParams: { gasPrice: toWei(toBN(50), 'gwei') }, + l1Fee: toBN(0), withdrawalNetworkFee: toBN(0), withdrawalFeeViaRelayer: toBN(0) } } export const getters = { + provider: (state, getters, rootState, rootGetters) => { + const netId = Number(rootGetters['metamask/netId']) + const { url: rpcUrl } = rootState.settings[`netId${netId}`].rpc + const config = rootGetters['metamask/networkConfig'] + + return getProvider(netId, rpcUrl, config) + }, oracle: (state, getters, rootState, rootGetters) => { const netId = Number(rootGetters['metamask/netId']) const { url: rpcUrl } = rootState.settings[`netId${netId}`].rpc - const { gasPrices } = rootGetters['metamask/networkConfig'] + const config = rootGetters['metamask/networkConfig'] - // Return old oracle for backwards compatibility, if chain is ETH Mainnet - return netId === 1 - ? new TornadoFeeOracleV4(netId, rpcUrl, gasPrices) - : new TornadoFeeOracleV5(netId, rpcUrl, gasPrices) + return new TornadoFeeOracle(netId, rpcUrl, config) }, getGasPriceParams: (state) => { return state.gasPriceParams }, - gasPrice: (state, getters) => { - const { gasPrice, maxFeePerGas } = getters.getGasPriceParams - return maxFeePerGas || gasPrice + getMetamaskGasPriceParams: (state) => { + const feeData = state.gasPriceParams + + const gasParams = feeData.maxFeePerGas + ? { + maxFeePerGas: '0x' + feeData.maxFeePerGas.toString(16), + maxPriorityFeePerGas: '0x' + feeData.maxPriorityFeePerGas.toString(16) + } + : { + gasPrice: '0x' + feeData.gasPrice.toString(16) + } + + return gasParams + }, + l1Fee: (state) => { + return state.l1Fee + }, + selectedInstance: (state, getters, rootState, rootGetters) => { + const nativeCurrency = rootGetters['metamask/nativeCurrency'] + const { tokens } = rootGetters['metamask/networkConfig'] + const { currency, amount } = rootState.application.selectedStatistic + + const { + instanceAddress: instanceAddresses, + decimals, + gasLimit: instanceGasLimit, + tokenGasLimit + } = tokens[currency] + + const { [amount]: instanceAddress } = instanceAddresses + + const isNativeCurrency = currency.toLowerCase() === nativeCurrency + + const firstAmount = Object.keys(instanceAddresses).sort((a, b) => Number(a) - Number(b))[0] + const isFirstAmount = Number(amount) === Number(firstAmount) + + const denomination = parseUnits(String(amount), decimals) + + const tokenPriceInWei = rootState.price.prices[currency.toLowerCase()] + + return { + instanceAddress, + currency: currency.toLowerCase(), + amount: String(amount), + decimals, + denomination, + isNativeCurrency, + isFirstAmount, + instanceGasLimit, + tokenGasLimit, + tokenPriceInWei + } + }, + gasPrice: (state, getters, rootState, rootGetters) => { + const netId = Number(rootGetters['metamask/netId']) + const gasPriceParams = getters.getGasPriceParams + + let gasPrice = BigInt( + gasPriceParams.maxFeePerGas + ? gasPriceParams.maxFeePerGas.toString() + : gasPriceParams.gasPrice.toString() + ) + + // to-do: manually bump gas price for BSC, remove this when we are able to check gasPrice from relayer status + if (netId === ChainId.BSC && gasPrice < parseUnits('3.3', 'gwei')) { + gasPrice = parseUnits('3.3', 'gwei') + } + + return gasPrice }, gasPriceInGwei: (state, getters) => { - return fromWei(getters.gasPrice, 'gwei') + return formatUnits(getters.gasPrice, 'gwei') + }, + gasLimit: (state, getters) => ({ gas, gasLimit } = {}) => { + const { instanceGasLimit } = getters.selectedInstance + return BigInt(gas || gasLimit || instanceGasLimit || WITHDRAW_GAS_LIMIT) + }, + refundGasLimit: (state, getters) => { + const { isFirstAmount, tokenGasLimit } = getters.selectedInstance + return isFirstAmount && tokenGasLimit ? BigInt(tokenGasLimit) : undefined + }, + ethRefund: (state, getters, rootState) => { + return rootState.application.ethToReceive || 0 + }, + relayerFeePercent: (state, getters, rootState) => { + return rootState.relayer.selectedRelayer.tornadoServiceFee } } @@ -42,6 +129,9 @@ export const mutations = { }, SAVE_WITHDRAWAL_FEE_VIA_RELAYER(state, fee) { state.withdrawalFeeViaRelayer = fee + }, + SAVE_L1_FEE(state, fee) { + state.l1Fee = fee } } @@ -50,8 +140,20 @@ export const actions = { const { pollInterval } = rootGetters['metamask/networkConfig'] try { - const gasPriceParams = await getters.oracle.getGasPriceParams() - commit('SAVE_GAS_PARAMS', gasPriceParams) + const feeData = await getters.provider.getFeeData() + + const gasParams = feeData.maxFeePerGas + ? { + maxFeePerGas: feeData.maxFeePerGas.toString(), + maxPriorityFeePerGas: feeData.maxPriorityFeePerGas + ? feeData.maxPriorityFeePerGas.toString() + : null + } + : { + gasPrice: feeData.gasPrice ? feeData.gasPrice.toString() : parseUnits('50', 'gwei').toString() + } + + commit('SAVE_GAS_PARAMS', gasParams) } catch (e) { console.error('fetchGasPrice', e) } finally { @@ -62,32 +164,66 @@ export const actions = { const { gasPrices } = rootGetters['metamask/networkConfig'] commit('SAVE_GAS_PARAMS', { gasPrice: toWei(gasPrices?.fast?.toFixed(9) || 0, 'gwei') }) }, - async calculateWithdrawalNetworkFee({ getters, commit }, { tx }) { - const withdrawalGas = await getters.oracle.getGas(tx, 'user_withdrawal') + calculateWithdrawalNetworkFee({ getters, commit }, { tx }) { + const gasPrice = getters.gasPrice + const gasLimit = BigInt(tx?.gas || tx?.gasLimit || WITHDRAW_GAS_LIMIT) - commit('SAVE_WITHDRAWAL_NETWORK_FEE', toBN(withdrawalGas)) + const gasCost = gasPrice * gasLimit + + commit('SAVE_WITHDRAWAL_NETWORK_FEE', toBN(gasCost.toString())) + + return gasCost }, - 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] + async calculateL1Fee({ commit, getters }, { tx }) { + const oracle = getters.oracle - await dispatch('calculateWithdrawalNetworkFee', { tx }) - if (currency !== nativeCurrency) - await dispatch('application/setDefaultEthToReceive', { currency }, { root: true }) + const l1Fee = await oracle.fetchL1OptimismFee(tx) - 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_L1_FEE', toBN(l1Fee.toString())) + }, + async calculateWithdrawalFeeViaRelayer({ dispatch, getters, commit }, { tx }) { + const { decimals, denomination, isNativeCurrency, tokenPriceInWei } = getters.selectedInstance - commit('SAVE_WITHDRAWAL_FEE_VIA_RELAYER', toBN(withdrawalFee)) + const oracle = getters.oracle + const gasPrice = getters.gasPrice + const gasLimit = getters.gasLimit(tx) + const refundGasLimit = getters.refundGasLimit + const relayerFeePercent = getters.relayerFeePercent + + await dispatch('calculateL1Fee', { tx }) + dispatch('calculateWithdrawalNetworkFee', { tx }) + + const l1Fee = getters.l1Fee + + if (!isNativeCurrency) { + dispatch('application/setDefaultEthToReceive', { gasPrice, refundGasLimit }, { root: true }) + + const ethRefund = getters.ethRefund + + const relayerFee = oracle.calculateRelayerFee({ + gasPrice, + gasLimit, + l1Fee, + denomination, + ethRefund, + tokenPriceInWei, + tokenDecimals: decimals, + relayerFeePercent, + isEth: isNativeCurrency + }) + + commit('SAVE_WITHDRAWAL_FEE_VIA_RELAYER', toBN(relayerFee.toString())) + return + } + + const relayerFee = oracle.calculateRelayerFee({ + gasPrice, + gasLimit, + l1Fee, + denomination, + relayerFeePercent + }) + + commit('SAVE_WITHDRAWAL_FEE_VIA_RELAYER', toBN(relayerFee.toString())) } } diff --git a/store/governance/gov.js b/store/governance/gov.js index 3a47156..0205b9e 100644 --- a/store/governance/gov.js +++ b/store/governance/gov.js @@ -2,7 +2,7 @@ /* eslint-disable import/order */ import Web3 from 'web3' -import { utils } from 'ethers' +import { AbiCoder, concat } from 'ethers' import { ToastProgrammatic as Toast } from 'buefy' import networkConfig from '@/networkConfig' @@ -14,6 +14,8 @@ import { httpConfig } from '@/constants' const { numberToHex, toWei, fromWei, toBN, hexToNumber, hexToNumberString } = require('web3-utils') +const defaultAbiCoder = AbiCoder.defaultAbiCoder() + const state = () => { return { approvalAmount: 'unlimited', @@ -391,8 +393,8 @@ const actions = { if (contact || message) { const value = JSON.stringify([contact, message]) - const tail = utils.defaultAbiCoder.encode(['string'], [value]) - dataWithTail = utils.hexConcat([data, tail]) + const tail = defaultAbiCoder.encode(['string'], [value]) + dataWithTail = concat([data, tail]) } const gas = await web3.eth.estimateGas({ diff --git a/store/governance/proposal.js b/store/governance/proposal.js index be2df91..a0e32fb 100644 --- a/store/governance/proposal.js +++ b/store/governance/proposal.js @@ -1,7 +1,7 @@ /* eslint-disable no-console */ /* eslint-disable import/order */ -import { utils } from 'ethers' +import { AbiCoder, dataLength, dataSlice } from 'ethers' import uniqBy from 'lodash/uniqBy' import chunk from 'lodash/chunk' @@ -10,6 +10,8 @@ import { CHUNK_COUNT_PER_BATCH_REQUEST } from '@/constants' const { toWei, fromWei, toBN } = require('web3-utils') +const defaultAbiCoder = AbiCoder.defaultAbiCoder() + const CACHE_TX = {} const CACHE_BLOCK = {} @@ -18,15 +20,12 @@ const parseComment = (calldata, govInstance) => { if (!calldata || !govInstance) return empty const methodLength = 4 // length of castDelegatedVote method - const result = utils.defaultAbiCoder.decode( - ['address[]', 'uint256', 'bool'], - utils.hexDataSlice(calldata, methodLength) - ) + const result = defaultAbiCoder.decode(['address[]', 'uint256', 'bool'], dataSlice(calldata, methodLength)) const data = govInstance.methods.castDelegatedVote(...result).encodeABI() - const dataLength = utils.hexDataLength(data) + const length = dataLength(data) try { - const str = utils.defaultAbiCoder.decode(['string'], utils.hexDataSlice(calldata, dataLength)) + const str = defaultAbiCoder.decode(['string'], dataSlice(calldata, length)) const [contact, message] = JSON.parse(str) return { contact, message } } catch { diff --git a/store/price.js b/store/price.js index c2681fa..da2af31 100644 --- a/store/price.js +++ b/store/price.js @@ -18,8 +18,25 @@ export const getters = { priceOracle: (state, getters, rootState, rootGetters) => { const netId = Number(rootGetters['metamask/netId']) const { url: rpcUrl } = rootState.settings[`netId${netId}`].rpc + const config = rootGetters['metamask/networkConfig'] - return new TokenPriceOracle(rpcUrl) + return new TokenPriceOracle(netId, rpcUrl, config) + }, + tokens: (state, getters, rootStater, rootGetters) => { + const config = rootGetters['metamask/networkConfig'] + const { 'torn.contract.tornadocash.eth': tornContract, tokens } = config + return [ + { + tokenAddress: tornContract, + symbol: 'TORN', + decimals: 18 + }, + ...Object.values(tokens) + .map(({ tokenAddress, symbol, decimals }) => + tokenAddress ? { tokenAddress, symbol, decimals } : undefined + ) + .filter((t) => t) + ] }, tokenRate: (state, getters, rootState) => { return state.prices[rootState.application.selectedStatistic.currency] @@ -49,8 +66,10 @@ export const actions = { return } + const tokens = getters.tokens + try { - const prices = await getters.priceOracle.fetchPrices() + const prices = await getters.priceOracle.fetchPrices(tokens) console.log('prices', prices) commit('SAVE_TOKEN_PRICES', prices) diff --git a/yarn.lock b/yarn.lock index ad6b38e..4216b8a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2951,9 +2951,9 @@ keccak "^2.0.0" yargs "^12.0.5" -"@tornado/tornado-oracles@git+https://git.tornado.ws/tornadocontrib/tornado-oracles.git#937d65f829e044c1b649ca9305fa4d50733a6d57": +"@tornado/tornado-oracles@git+https://git.tornado.ws/tornadocontrib/tornado-oracles.git#7c646dc11f5785457f64f3aba297183fb70992da": version "3.3.0" - resolved "git+https://git.tornado.ws/tornadocontrib/tornado-oracles.git#937d65f829e044c1b649ca9305fa4d50733a6d57" + resolved "git+https://git.tornado.ws/tornadocontrib/tornado-oracles.git#7c646dc11f5785457f64f3aba297183fb70992da" dependencies: ethers "^6.4.0" @@ -7928,7 +7928,7 @@ ethers@^5.5.1: "@ethersproject/web" "5.6.0" "@ethersproject/wordlists" "5.6.0" -ethers@^6.4.0: +ethers@^6.12.1, ethers@^6.4.0: version "6.12.1" resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.12.1.tgz#517ff6d66d4fd5433e38e903051da3e57c87ff37" integrity sha512-j6wcVoZf06nqEcBbDWkKg8Fp895SS96dSnTCjiXT+8vt2o02raTn4Lo9ERUuIVU5bAjoPYeA+7ytQFexFmLuVw==