Calculate withdrawal gas & token prices via @tornado/tornado-oracles

This commit is contained in:
Theo 2023-09-02 10:39:27 -07:00
parent 40c55b3e7c
commit 67c0782794
8 changed files with 52 additions and 311 deletions

@ -1,6 +1,6 @@
NET_ID=1
HTTP_RPC_URL=https://api.securerpc.com/v1
# WS_RPC_URL=wss://mainnet.infura.io/ws/v3/
WS_RPC_URL=wss://mainnet.infura.io/ws/v3/
# ORACLE_RPC_URL should always point to the mainnet
ORACLE_RPC_URL=https://api.securerpc.com/v1
REDIS_URL=redis://127.0.0.1:6379
@ -13,7 +13,7 @@ APP_PORT=8000
# without 0x prefix
PRIVATE_KEY=
# 0.05 means 0.05%
REGULAR_TORNADO_WITHDRAW_FEE=0.35
RELAYER_FEE=0.4
MINING_SERVICE_FEE=0.05
REWARD_ACCOUNT=
CONFIRMATIONS=4

@ -1,181 +0,0 @@
[
{
"inputs": [
{ "internalType": "contract MultiWrapper", "name": "_multiWrapper", "type": "address" },
{ "internalType": "contract IOracle[]", "name": "existingOracles", "type": "address[]" },
{ "internalType": "enum OffchainOracle.OracleType[]", "name": "oracleTypes", "type": "uint8[]" },
{ "internalType": "contract IERC20[]", "name": "existingConnectors", "type": "address[]" },
{ "internalType": "contract IERC20", "name": "wBase", "type": "address" }
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{ "indexed": false, "internalType": "contract IERC20", "name": "connector", "type": "address" }
],
"name": "ConnectorAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": false, "internalType": "contract IERC20", "name": "connector", "type": "address" }
],
"name": "ConnectorRemoved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": false, "internalType": "contract MultiWrapper", "name": "multiWrapper", "type": "address" }
],
"name": "MultiWrapperUpdated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": false, "internalType": "contract IOracle", "name": "oracle", "type": "address" },
{
"indexed": false,
"internalType": "enum OffchainOracle.OracleType",
"name": "oracleType",
"type": "uint8"
}
],
"name": "OracleAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": false, "internalType": "contract IOracle", "name": "oracle", "type": "address" },
{
"indexed": false,
"internalType": "enum OffchainOracle.OracleType",
"name": "oracleType",
"type": "uint8"
}
],
"name": "OracleRemoved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{ "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" },
{ "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" }
],
"name": "OwnershipTransferred",
"type": "event"
},
{
"inputs": [{ "internalType": "contract IERC20", "name": "connector", "type": "address" }],
"name": "addConnector",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "contract IOracle", "name": "oracle", "type": "address" },
{ "internalType": "enum OffchainOracle.OracleType", "name": "oracleKind", "type": "uint8" }
],
"name": "addOracle",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "connectors",
"outputs": [{ "internalType": "contract IERC20[]", "name": "allConnectors", "type": "address[]" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "contract IERC20", "name": "srcToken", "type": "address" },
{ "internalType": "contract IERC20", "name": "dstToken", "type": "address" },
{ "internalType": "bool", "name": "useWrappers", "type": "bool" }
],
"name": "getRate",
"outputs": [{ "internalType": "uint256", "name": "weightedRate", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{ "internalType": "contract IERC20", "name": "srcToken", "type": "address" },
{ "internalType": "bool", "name": "useSrcWrappers", "type": "bool" }
],
"name": "getRateToEth",
"outputs": [{ "internalType": "uint256", "name": "weightedRate", "type": "uint256" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "multiWrapper",
"outputs": [{ "internalType": "contract MultiWrapper", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "oracles",
"outputs": [
{ "internalType": "contract IOracle[]", "name": "allOracles", "type": "address[]" },
{ "internalType": "enum OffchainOracle.OracleType[]", "name": "oracleTypes", "type": "uint8[]" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "contract IERC20", "name": "connector", "type": "address" }],
"name": "removeConnector",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{ "internalType": "contract IOracle", "name": "oracle", "type": "address" },
{ "internalType": "enum OffchainOracle.OracleType", "name": "oracleKind", "type": "uint8" }
],
"name": "removeOracle",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "renounceOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "contract MultiWrapper", "name": "_multiWrapper", "type": "address" }],
"name": "setMultiWrapper",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }],
"name": "transferOwnership",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

@ -1,7 +1,7 @@
{
"name": "relay",
"version": "5.1.0",
"description": "Relayer for Tornado.cash privacy solution. https://tornado.cash",
"version": "5.2.0",
"description": "Relayer for Tornado.cash privacy solution.",
"scripts": {
"server": "node src/server.js",
"worker": "node src/worker",
@ -16,22 +16,22 @@
"build": "docker build -t tornadocash/relayer:mainnet-v5 .",
"start": "docker-compose up -d redis && concurrently \"yarn server\" \"yarn priceWatcher\" \"yarn treeWatcher\" \"yarn worker\" \"yarn healthWatcher\""
},
"author": "tornado.cash",
"author": "Tornado Cash team",
"license": "MIT",
"dependencies": {
"circomlib": "git+https://git.tornado.ws/tornado-packages/circomlib.git#3b492f9801573eebcfe1b6c584afe8a3beecf2b4",
"@tornado/tornado-config": "^2.0.0",
"@tornado/tornado-oracles": "^2.1.0",
"ajv": "^6.12.5",
"async-mutex": "^0.2.4",
"bull": "^3.12.1",
"circomlib": "git+https://git.tornado.ws/tornado-packages/circomlib.git#3b492f9801573eebcfe1b6c584afe8a3beecf2b4",
"concurrently": "^8.2.0",
"dotenv": "^8.2.0",
"eth-ens-namehash": "^2.0.8",
"express": "^4.17.1",
"fixed-merkle-tree": "^0.4.0",
"@tornado/gas-price-oracle": "^0.5.3",
"ioredis": "^4.14.1",
"node-fetch": "^2.6.7",
"torn-token": "1.0.6",
"tornado-anonymity-mining": "^2.1.2",
"tx-manager": "^0.4.8",
"uuid": "^8.3.0",

@ -1,14 +1,13 @@
require('dotenv').config()
const { jobType } = require('./constants')
const tornConfig = require('torn-token')
const tornConfig = require('@tornado/tornado-config')
module.exports = {
netId: Number(process.env.NET_ID) || 1,
redisUrl: process.env.REDIS_URL || 'redis://127.0.0.1:6379',
httpRpcUrl: process.env.HTTP_RPC_URL,
wsRpcUrl: process.env.WS_RPC_URL,
oracleRpcUrl: process.env.ORACLE_RPC_URL || 'https://mainnet.infura.io/',
offchainOracleAddress: '0x07D91f5fb9Bf7798734C3f606dB065549F6893bb',
oracleRpcUrl: process.env.ORACLE_RPC_URL || 'https://api.securerpc.com/v1',
aggregatorAddress: process.env.AGGREGATOR,
minerMerkleTreeHeight: 20,
privateKey: process.env.PRIVATE_KEY,
@ -16,13 +15,10 @@ module.exports = {
torn: tornConfig,
port: process.env.APP_PORT || 8000,
tornadoServiceFee: Number(process.env.REGULAR_TORNADO_WITHDRAW_FEE),
miningServiceFee: Number(process.env.MINING_SERVICE_FEE),
rewardAccount: process.env.REWARD_ACCOUNT,
governanceAddress: '0x5efda50f22d34F262c29268506C5Fa42cB56A1Ce',
tornadoGoerliProxy: '0x454d870a72e29d5E5697f635128D18077BD04C60',
gasLimits: {
[jobType.TORNADO_WITHDRAW]: 390000,
WITHDRAW_WITH_EXTRA: 700000,
[jobType.MINING_REWARD]: 455000,
[jobType.MINING_WITHDRAW]: 400000,
},

@ -1,41 +1,17 @@
const { offchainOracleAddress } = require('./config')
const {
getArgsForOracle,
setSafeInterval,
toChecksumAddress,
toBN,
RelayerError,
logRelayerError,
} = require('./utils')
const { setSafeInterval, RelayerError, logRelayerError } = require('./utils')
const { redis } = require('./modules/redis')
const web3 = require('./modules/web3')('oracle')
const { TokenPriceOracle } = require('@tornado/tornado-oracles')
const { oracleRpcUrl } = require('./config')
const offchainOracleABI = require('../abis/OffchainOracle.abi.json')
const offchainOracle = new web3.eth.Contract(offchainOracleABI, offchainOracleAddress)
const { tokenAddresses, oneUintAmount, currencyLookup } = getArgsForOracle()
const priceOracle = new TokenPriceOracle(oracleRpcUrl)
async function main() {
try {
const ethPrices = {}
for (let i = 0; i < tokenAddresses.length; i++) {
try {
const isWrap =
toChecksumAddress(tokenAddresses[i]) ===
toChecksumAddress('0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643')
const price = await offchainOracle.methods.getRateToEth(tokenAddresses[i], isWrap).call()
const numerator = toBN(oneUintAmount[i])
const denominator = toBN(10).pow(toBN(18)) // eth decimals
const priceFormatted = toBN(price).mul(numerator).div(denominator)
ethPrices[currencyLookup[tokenAddresses[i]]] = priceFormatted.toString()
} catch (e) {
console.error('cant get price of ', tokenAddresses[i])
}
}
const ethPrices = await priceOracle.fetchPrices()
if (!Object.values(ethPrices).length) {
throw new RelayerError('Can`t update prices', 1)
}
await redis.hmset('prices', ethPrices)
console.log('Wrote following prices to redis', ethPrices)
} catch (e) {

@ -2,16 +2,8 @@ const { instances, netId } = require('./config')
const { poseidon } = require('circomlib')
const { toBN, toChecksumAddress, BN, fromWei, isAddress, toWei, toHex } = require('web3-utils')
const TOKENS = {
torn: {
tokenAddress: '0x77777FeDdddFfC19Ff86DB637967013e6C6A116C',
symbol: 'TORN',
decimals: 18,
},
}
const addressMap = new Map()
const instance = instances[`netId${netId}`]
const instance = instances[netId]
for (const [currency, { instanceAddress, symbol, decimals }] of Object.entries(instance)) {
Object.entries(instanceAddress).forEach(([amount, address]) =>
@ -61,24 +53,6 @@ function when(source, event) {
})
}
function getArgsForOracle() {
const tokens = {
...instances.netId1,
...TOKENS,
}
const tokenAddresses = []
const oneUintAmount = []
const currencyLookup = {}
Object.entries(tokens).map(([currency, data]) => {
if (currency !== 'eth') {
tokenAddresses.push(data.tokenAddress)
oneUintAmount.push(toBN('10').pow(toBN(data.decimals.toString())).toString())
currencyLookup[data.tokenAddress] = currency
}
})
return { tokenAddresses, oneUintAmount, currencyLookup }
}
function fromDecimals(value, decimals) {
value = value.toString()
let ether = value.toString()
@ -155,7 +129,6 @@ module.exports = {
poseidonHash2,
sleep,
when,
getArgsForOracle,
fromDecimals,
toBN,
toChecksumAddress,

@ -1,8 +1,7 @@
const fs = require('fs')
const MerkleTree = require('fixed-merkle-tree')
const { GasPriceOracle } = require('@tornado/gas-price-oracle')
const { Utils, Controller } = require('tornado-anonymity-mining')
const { TornadoFeeOracleV5 } = require('@tornado/tornado-oracles')
const swapABI = require('../abis/swap.abi.json')
const miningABI = require('../abis/mining.abi.json')
const tornadoABI = require('../abis/tornadoABI.json')
@ -47,7 +46,7 @@ let txManager
let controller
let swap
let minerContract
let gasPriceOracle
const feeOracle = new TornadoFeeOracleV5(netId, oracleRpcUrl)
async function fetchTree() {
const elements = await redis.get('tree:elements')
@ -94,12 +93,7 @@ async function start() {
BASE_FEE_RESERVE_PERCENTAGE: baseFeeReserve,
},
})
gasPriceOracle = new GasPriceOracle({
defaultRpc: oracleRpcUrl,
minPriority: 2,
percentile: 5,
blocksCount: 20,
})
swap = new web3.eth.Contract(swapABI, await resolver.resolve(torn.rewardSwap.address))
minerContract = new web3.eth.Contract(miningABI, await resolver.resolve(torn.miningV2.address))
redisSubscribe.subscribe('treeUpdate', fetchTree)
@ -125,37 +119,6 @@ function checkFee({ data }, gasInfo) {
return checkMiningFee(data)
}
async function getGasPrice() {
try {
const { maxFeePerGas, gasPrice } = await gasPriceOracle.getTxGasParams({
legacySpeed: 'fast',
bumpPercent: 10,
})
return toBN(maxFeePerGas || gasPrice)
} catch (e) {
const block = await web3.eth.getBlock('latest')
if (block && block.baseFeePerGas) {
return toBN(block.baseFeePerGas)
}
const gasPrice = await web3.eth.getGasPrice()
return toBN(gasPrice)
}
}
async function estimateWithdrawalGasLimit(tx) {
try {
const fetchedGasLimit = await web3.eth.estimateGas(tx)
const bumped = Math.floor(fetchedGasLimit * 1.2)
return bumped
} catch (e) {
console.log('Estimation error: ', e)
return gasLimits[jobType.TORNADO_WITHDRAW]
}
}
async function checkTornadoFee({ args, contract }, { gasLimit, gasPrice }) {
const { currency, amount, decimals } = getInstance(contract)
const [fee, refund] = [args[4], args[5]].map(toBN)
@ -197,12 +160,12 @@ async function checkTornadoFee({ args, contract }, { gasLimit, gasPrice }) {
}
async function checkMiningFee({ args }) {
const gasPrice = await getGasPrice()
const gasPrice = await feeOracle.getGasPrice()
const ethPrice = await redis.hget('prices', 'torn')
const isMiningReward = currentJob.data.type === jobType.MINING_REWARD
const providedFee = isMiningReward ? toBN(args.fee) : toBN(args.extData.fee)
const expense = gasPrice.mul(toBN(gasLimits[currentJob.data.type]))
const expense = toBN(gasPrice).mul(toBN(gasLimits[currentJob.data.type]))
const expenseInTorn = expense.mul(toBN(1e18)).div(toBN(ethPrice))
// todo make aggregator for ethPrices and rewardSwap data
const balance = await swap.methods.tornVirtualBalance().call()
@ -259,17 +222,15 @@ async function getTxObject({ data }) {
calldata = contract.methods.withdraw(data.proof, ...data.args).encodeABI()
}
const gasPrice = await getGasPrice()
const incompleteTx = {
value: data.args[5],
from: txManager.address, // Required, because without it relayerRegistry.burn will fail, because msg.sender is not relayer
to: contract._address,
data: calldata,
gasPrice: toHex(gasPrice),
}
const gasLimit = await estimateWithdrawalGasLimit(incompleteTx)
const { gasLimit, gasPrice } = await feeOracle.getGasParams(incompleteTx, 'relayer_withdrawal')
return Object.assign(incompleteTx, { gasLimit })
return Object.assign(incompleteTx, { gasLimit, gasPrice })
} else {
const method = data.type === jobType.MINING_REWARD ? 'reward' : 'withdraw'
const calldata = minerContract.methods[method](data.proof, data.args).encodeABI()

@ -409,7 +409,7 @@
fastfile "0.0.19"
ffjavascript "^0.2.30"
"@openzeppelin/contracts@^3.1.0", "@openzeppelin/contracts@^3.4.0":
"@openzeppelin/contracts@^3.4.0":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-3.4.2.tgz#d81f786fda2871d1eb8a8c5a73e455753ba53527"
integrity sha512-z0zMCjyhhp4y7XKAcDAi3Vgms4T2PstwBdahiO0+9NaGICQKjynK3wduSRplTgk4LXmoO1yfDGO5RbjKYxtuxA==
@ -456,6 +456,22 @@
bignumber.js "^9.0.0"
node-cache "^5.1.2"
"@tornado/tornado-config@^2.0.0":
version "2.0.0"
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@^2.1.0":
version "2.1.0"
resolved "https://git.tornado.ws/api/packages/tornado-packages/npm/%40tornado%2Ftornado-oracles/-/2.1.0/tornado-oracles-2.1.0.tgz#2aa0d8c9288992e6d194d4bb28acb37c2035c453"
integrity sha512-Y6FPAGnCvHLWzUnNYgGoOv+X7KY3CF02rRSawataYaLyl+v2ivh7RYZZZ3G/B5hXf+pD3IFeCdm4PDnTNyNe1g==
dependencies:
"@tornado/gas-price-oracle" "^0.5.3"
"@tornado/tornado-config" "^2.0.0"
"@types/node" "^20.5.1"
bignumber.js "^9.1.1"
ethers "5.7"
"@types/bn.js@^4.11.3":
version "4.11.6"
resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.6.tgz#c306c70d9358aaea33cd4eda092a742b9505967c"
@ -502,6 +518,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240"
integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==
"@types/node@^20.5.1":
version "20.5.8"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.8.tgz#fb171fd22d37ca6e2ea97fde88e6a13ee14bc327"
integrity sha512-eajsR9aeljqNhK028VG0Wuw+OaY5LLxYmxeoXynIoE6jannr9/Ucd1LL0hSSoafk5LTYG+FfqsyGt81Q6Zkybw==
"@types/pbkdf2@^3.0.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@types/pbkdf2/-/pbkdf2-3.1.0.tgz#039a0e9b67da0cdc4ee5dab865caa6b267bb66b1"
@ -788,6 +809,11 @@ bignumber.js@^9.0.0:
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.1.tgz#c4df7dc496bd849d4c9464344c1aa74228b4dac6"
integrity sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==
bignumber.js@^9.1.1:
version "9.1.2"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c"
integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==
binary-extensions@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
@ -2098,7 +2124,7 @@ ethereumjs-util@^7.0.3, ethereumjs-util@^7.1.0, ethereumjs-util@^7.1.1, ethereum
ethereum-cryptography "^0.1.3"
rlp "^2.2.4"
ethers@^5.4.6:
ethers@5.7, ethers@^5.4.6:
version "5.7.2"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e"
integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==
@ -4896,16 +4922,6 @@ toidentifier@1.0.1:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35"
integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==
torn-token@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/torn-token/-/torn-token-1.0.6.tgz#66cde5f85b611033918c807b4a8d9d4e5bb3fcfc"
integrity sha512-ilCS7fN+JM2O8l1Iw5cEWXyiQQg8GxEeYYvqALJcn5cO6qSpD+xJb3Dji4EHXa1Yu1OBd/19ktWNvUkWNvuAaQ==
dependencies:
"@openzeppelin/contracts" "^3.1.0"
eth-sig-util "^2.5.3"
ethereumjs-util "^7.0.3"
web3 "^1.2.11"
tornado-anonymity-mining@^2.1.2:
version "2.1.5"
resolved "https://registry.yarnpkg.com/tornado-anonymity-mining/-/tornado-anonymity-mining-2.1.5.tgz#7dbc7f099ce9667f2cc91fbb7ce8f78663afc4cc"