Merge pull request #20 from 0xAyanami/develop

Bug fixes & Minor updates
This commit is contained in:
Roman Storm 2022-01-23 09:53:24 -08:00 committed by GitHub
commit 80a810e727
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 261 additions and 95 deletions

View File

@ -1,13 +1,40 @@
# Warning!
# Tornado cli
Command line tool to interact with [Tornado Cash](https://tornadocash.eth.link).
### Warning!
Current cli version doesn't support [Anonymity Mining](https://tornado-cash.medium.com/tornado-cash-governance-proposal-a55c5c7d0703)
### Goerli, Mainnet, Binance Smart Chain
### How to install tornado cli
Download and install [node.js](https://nodejs.org/en/download/).
If you have git installed on your system, clone the master branch.
```bash
$ git clone https://github.com/tornadocash/tornado-cli
```
Or, download the archive file from github
https://github.com/tornadocash/tornado-cli/archive/refs/heads/master.zip
After downloading or cloning the repository, you must install necessary libraries using the following command.
```bash
$ cd tornado-cli
$ npm install
```
If you want to use Tor connection to conceal ip address, install [Tor Browser](https://www.torproject.org/download/) and add `--tor 9150` for `cli.js` if you connect tor with browser.
Note that you should reset your tor connection by restarting the browser every time when you deposit & withdraw otherwise you will have the same exit node used for connection.
### Goerli, Mainnet, Binance Smart Chain, Gnosis Chain, Polygon Network, Arbitrum, Avalanche
1. Add `PRIVATE_KEY` to `.env` file
2. `./cli.js --help`
2. `node cli.js --help`
Example:
```bash
$ ./cli.js deposit ETH 0.1 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --tor 9050
$ node cli.js deposit ETH 0.1 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --tor 9150
Your note: tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652
Tornado ETH balance is 8.9
@ -18,7 +45,7 @@ Sender account ETH balance is 1004873.361652048361352542
```
```bash
$ ./cli.js withdraw tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --relayer https://goerli-frelay.duckdns.org --tor 9050
$ node cli.js withdraw tornado-eth-0.1-5-0xf73dd6833ccbcc046c44228c8e2aa312bf49e08389dadc7c65e6a73239867b7ef49c705c4db227e2fadd8489a494b6880bdcb6016047e019d1abec1c7652 0x8589427373D6D84E98730D7795D8f6f8731FDA16 --rpc https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161 --relayer https://goerli-frelay.duckdns.org --tor 9150
Relay address: 0x6A31736e7490AbE5D5676be059DFf064AB4aC754
Getting current state from tornado contract
@ -520,6 +547,41 @@ Infura API key fetched from https://rpc.info (Same one with Metamask)
"cachedUrl":"https://goerli-v2.defidevotee.xyz"
}
}
},
"netId10":{
"rpcUrls":{
"Optimism":{
"name":"Optimism Public RPC",
"url":"https://mainnet.optimism.io"
}
},
"relayers":{
"optimism.t-relay.eth":{
"url":"optimism.t-relay.eth",
"name":"optimism.t-relay.eth",
"cachedUrl":"https://optimism.t-relay.online/"
},
"optimism.therelayer.eth":{
"url":"optimism.therelayer.eth",
"name":"optimism.therelayer.eth",
"cachedUrl":"https://optimism.therelayer.xyz/"
},
"optimism.relayer-service.eth":{
"url":"optimism.relayer-service.eth",
"name":"optimism.relayer-service.eth",
"cachedUrl":"https://optimism-relayer.hertz.zone/"
},
"optimism.torn.eth":{
"url":"optimism.torn.eth",
"name":"optimism.torn.eth",
"cachedUrl":"https://optimism.torn.cash/"
},
"optimism.relaymy.eth":{
"url":"optimism.relaymy.eth",
"name":"optimism.relaymy.eth",
"cachedUrl":"https://optimism.relaymy.xyz/"
}
}
}
}
```

264
cli.js
View File

@ -71,7 +71,11 @@ async function generateTransaction(to, encodedData, value = 0) {
const bumped = Math.floor(fetchedGas * 1.3)
gasLimit = web3.utils.toHex(bumped)
}
await estimateGas();
if (encodedData) {
await estimateGas();
} else {
gasLimit = web3.utils.toHex(21000);
}
async function txoptions() {
// Generate EIP-1559 transaction
@ -85,6 +89,16 @@ async function generateTransaction(to, encodedData, value = 0) {
gas : gasLimit,
data : encodedData
}
} else if (netId == 137 || netId == 43114) {
tx = {
to : to,
value : value,
nonce : nonce,
maxFeePerGas : gasPrice,
maxPriorityFeePerGas : gasPrice,
gas : gasLimit,
data : encodedData
}
} else {
tx = {
to : to,
@ -144,7 +158,7 @@ async function deposit({ currency, amount }) {
const noteString = `tornado-${currency}-${amount}-${netId}-${note}`
console.log(`Your note: ${noteString}`)
await backupNote({ currency, amount, netId, note, noteString })
if (currency === 'eth' || currency === 'bnb' || currency === 'xdai' || currency === 'matic' || currency === 'avax') {
if (currency === netSymbol.toLowerCase()) {
await printETHBalance({ address: tornadoContract._address, name: 'Tornado contract', symbol: currency.toUpperCase() })
await printETHBalance({ address: senderAccount, name: 'Sender account', symbol: currency.toUpperCase() })
const value = isLocalRPC ? ETH_AMOUNT : fromDecimals({ amount, decimals: 18 })
@ -269,7 +283,7 @@ async function generateProof({ deposit, currency, amount, recipient, relayerAddr
*/
async function withdraw({ deposit, currency, amount, recipient, relayerURL, torPort, refund = '0' }) {
let options = {};
if (currency === 'eth' && refund !== '0') {
if (currency === netSymbol.toLowerCase() && refund !== '0') {
throw new Error('The ETH purchase is supposted to be 0 for ETH withdrawals')
}
refund = toWei(refund)
@ -328,7 +342,7 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, torP
// check if the address of recepient matches with the account of provided private key from environment to prevent accidental use of deposit address for withdrawal transaction.
const { address } = await web3.eth.accounts.privateKeyToAccount('0x' + PRIVATE_KEY)
assert(recipient.toLowerCase() == address.toLowerCase(), 'Withdrawal amount recepient',recipient,'mismatches with the account of provided private key from environment file',address)
assert(recipient.toLowerCase() == address.toLowerCase(), 'Withdrawal amount recepient mismatches with the account of provided private key from environment file')
const { proof, args } = await generateProof({ deposit, currency, amount, recipient, refund })
@ -338,6 +352,53 @@ async function withdraw({ deposit, currency, amount, recipient, relayerURL, torP
console.log('Done withdrawal from Tornado Cash')
}
/**
* Do an ETH / ERC20 send
* @param address Recepient address
* @param amount Amount to send
* @param tokenAddress ERC20 token address
*/
async function send({ address, amount, tokenAddress }) {
// using private key
assert(senderAccount != null, 'Error! PRIVATE_KEY not found. Please provide PRIVATE_KEY in .env file if you send')
if (tokenAddress) {
const erc20ContractJson = require('./build/contracts/ERC20Mock.json')
erc20 = new web3.eth.Contract(erc20ContractJson.abi, tokenAddress)
const balance = await erc20.methods.balanceOf(senderAccount).call()
const decimals = await erc20.methods.decimals().call()
const toSend = amount * Math.pow(10, decimals)
if (balance < toSend) {
console.error("You have",toDecimals(balance, decimals, (balance.length + decimals)).toString().replace(/\B(?<!\.\d*)(?=(\d{3})+(?!\d))/g, ","),(await erc20.methods.symbol().call()),", you can't send more than you have")
process.exit(1);
}
await generateTransaction(tokenAddress, await erc20.methods.transfer(address, toBN(toSend)).encodeABI())
console.log('Sent',amount,(await erc20.methods.symbol().call()),'to',address);
} else {
const balance = await web3.eth.getBalance(senderAccount)
if (balance == 0) {
console.error("You have 0 balance, can't send")
process.exit(1);
}
if (!amount) {
console.log('Amount not defined, sending all available amounts')
const gasPrice = await fetchGasPrice()
const gasLimit = 21000;
if (netId == 1 || netId == 5) {
const priorityFee = await gasPrices(3)
amount = (balance - (gasLimit * (parseInt(gasPrice) + parseInt(priorityFee))))
} else {
amount = (balance - (gasLimit * parseInt(gasPrice)))
}
}
if (balance < amount) {
console.error("You have",web3.utils.fromWei(toHex(balance)),netSymbol,", you can't send more than you have.")
process.exit(1);
}
await generateTransaction(address, null, amount)
console.log('Sent',web3.utils.fromWei(toHex(amount)),netSymbol,'to',address);
}
}
function getStatus(id, relayerURL, options) {
return new Promise((resolve) => {
async function getRelayerStatus() {
@ -476,6 +537,8 @@ function getExplorerLink() {
return 'goerli.etherscan.io'
case 42:
return 'kovan.etherscan.io'
case 10:
return 'optimistic.etherscan.io'
default:
return 'etherscan.io'
}
@ -500,6 +563,8 @@ function getCurrentNetworkName() {
return 'Goerli'
case 42:
return 'Kovan'
case 137:
return 'Optimism'
default:
return 'localRPC'
}
@ -562,23 +627,7 @@ function calculateFee({ currency, gasPrice, amount, refund, ethPrices, relayerSe
const expense = toBN(gasPrice).mul(toBN(5e5))
let desiredFee
switch (currency) {
case 'eth': {
desiredFee = expense.add(feePercent)
break
}
case 'bnb': {
desiredFee = expense.add(feePercent)
break
}
case 'xdai': {
desiredFee = expense.add(feePercent)
break
}
case 'matic': {
desiredFee = expense.add(feePercent)
break
}
case 'avax': {
case netSymbol.toLowerCase(): {
desiredFee = expense.add(feePercent)
break
}
@ -618,6 +667,21 @@ function waitForTxReceipt({ txHash, attempts = 60, delay = 1000 }) {
})
}
function initJson(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, 'utf8', (error, data) => {
if (error) {
reject(error);
}
try {
resolve(JSON.parse(data));
} catch (error) {
resolve([]);
}
});
});
};
function loadCachedEvents({ type, currency, amount }) {
try {
const module = require(`./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`)
@ -640,10 +704,6 @@ function loadCachedEvents({ type, currency, amount }) {
}
async function fetchEvents({ type, currency, amount}) {
let leafIndex = -1
let events = [];
let fetchedEvents, chunks, targetBlock;
if (type === "withdraw") {
type = "withdrawal"
}
@ -651,75 +711,86 @@ async function fetchEvents({ type, currency, amount}) {
const cachedEvents = loadCachedEvents({ type, currency, amount })
const startBlock = cachedEvents.lastBlock + 1
console.log("Loaded cached",amount,currency.toUpperCase(),type,"events for",startBlock,"block")
console.log("Fetching",amount,currency.toUpperCase(),type,"events for",netName,"network")
async function fetchLatestEvents() {
targetBlock = await web3.eth.getBlockNumber();
chunks = 1000;
fetchedEvents = [];
for (let i=startBlock; i < targetBlock; i+=chunks) {
await tornadoContract.getPastEvents(capitalizeFirstLetter(type), {
fromBlock: i,
toBlock: i+chunks-1,
}).then(r => { fetchedEvents = fetchedEvents.concat(r); console.log("Fetched",amount,currency.toUpperCase(),type,"events from block:", i) }, err => { console.error(i + " failed fetching",type,"events from node", err) }).catch(console.log);
}
}
await fetchLatestEvents()
async function mapDepositEvents() {
fetchedEvents = fetchedEvents.map(({ blockNumber, transactionHash, returnValues }) => {
const { commitment, leafIndex, timestamp } = returnValues
return {
blockNumber,
transactionHash,
commitment,
leafIndex: Number(leafIndex),
timestamp
}
})
}
async function mapWithdrawEvents() {
fetchedEvents = fetchedEvents.map(({ blockNumber, transactionHash, returnValues }) => {
const { nullifierHash, to, fee } = returnValues
return {
blockNumber,
transactionHash,
nullifierHash,
to,
fee
}
})
}
async function mapLatestEvents() {
console.log("Mapping",amount,currency.toUpperCase(),type,"events, please wait")
if (type === "deposit"){
await mapDepositEvents();
} else {
await mapWithdrawEvents();
}
}
await mapLatestEvents();
console.log("Gathering cached events + collected events from node")
async function concatEvents() {
events = cachedEvents.events.concat(fetchedEvents)
}
await concatEvents();
console.log('Total events:', events.length)
async function updateCache() {
async function syncEvents() {
try {
await fs.writeFileSync(`./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`, JSON.stringify(events, null, 2), 'utf8')
console.log("Cache updated for Tornado",type,amount,currency,"instance successfully")
} catch (e) {
throw new Error('Writing cache file failed:',e)
let targetBlock = await web3.eth.getBlockNumber();
let chunks = 1000;
for (let i=startBlock; i < targetBlock; i+=chunks) {
let fetchedEvents = [];
async function fetchLatestEvents(i) {
await tornadoContract.getPastEvents(capitalizeFirstLetter(type), {
fromBlock: i,
toBlock: i+chunks-1,
}).then(r => { fetchedEvents = fetchedEvents.concat(r); console.log("Fetched",amount,currency.toUpperCase(),type,"events to block:", i) }, err => { console.error(i + " failed fetching",type,"events from node", err); process.exit(1); }).catch(console.log);
}
async function mapDepositEvents() {
fetchedEvents = fetchedEvents.map(({ blockNumber, transactionHash, returnValues }) => {
const { commitment, leafIndex, timestamp } = returnValues
return {
blockNumber,
transactionHash,
commitment,
leafIndex: Number(leafIndex),
timestamp
}
})
}
async function mapWithdrawEvents() {
fetchedEvents = fetchedEvents.map(({ blockNumber, transactionHash, returnValues }) => {
const { nullifierHash, to, fee } = returnValues
return {
blockNumber,
transactionHash,
nullifierHash,
to,
fee
}
})
}
async function mapLatestEvents() {
if (type === "deposit"){
await mapDepositEvents();
} else {
await mapWithdrawEvents();
}
}
async function updateCache() {
try {
const fileName = `./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`
const localEvents = await initJson(fileName);
const events = localEvents.concat(fetchedEvents);
await fs.writeFileSync(fileName, JSON.stringify(events, null, 2), 'utf8')
} catch (error) {
throw new Error('Writing cache file failed:',error)
}
}
await fetchLatestEvents(i);
await mapLatestEvents();
await updateCache();
}
} catch (error) {
throw new Error("Error while updating cache")
process.exit(1)
}
}
await updateCache();
await syncEvents();
async function loadUpdatedEvents() {
const fileName = `./cache/${netName.toLowerCase()}/${type}s_${currency}_${amount}.json`
const updatedEvents = await initJson(fileName);
const updatedBlock = updatedEvents[updatedEvents.length - 1].blockNumber
console.log("Cache updated for Tornado",type,amount,currency,"instance to block",updatedBlock,"successfully")
console.log('Total events:', updatedEvents.length)
return updatedEvents;
}
const events = await loadUpdatedEvents();
return events
}
@ -846,7 +917,12 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100', torPort,
MERKLE_TREE_HEIGHT = process.env.MERKLE_TREE_HEIGHT || 20
ETH_AMOUNT = process.env.ETH_AMOUNT
TOKEN_AMOUNT = process.env.TOKEN_AMOUNT
PRIVATE_KEY = process.env.PRIVATE_KEY
const privKey = process.env.PRIVATE_KEY
if (privKey.includes("0x")) {
PRIVATE_KEY = process.env.PRIVATE_KEY.substring(2)
} else {
PRIVATE_KEY = process.env.PRIVATE_KEY
}
if (PRIVATE_KEY) {
const account = web3.eth.accounts.privateKeyToAccount('0x' + PRIVATE_KEY)
web3.eth.accounts.wallet.add('0x' + PRIVATE_KEY)
@ -893,6 +969,7 @@ async function init({ rpc, noteNetId, currency = 'dai', amount = '100', torPort,
process.exit(1)
}
}
netSymbol = getCurrentNetworkSymbol()
tornado = new web3.eth.Contract(contractJson, tornadoAddress)
tornadoContract = new web3.eth.Contract(instanceJson, tornadoInstance)
contractAddress = tornadoAddress
@ -958,6 +1035,13 @@ async function main() {
await printERC20Balance({ address, name: 'Account', tokenAddress })
}
})
program
.command('send <address> [amount] [token_address]')
.description('Send ETH or ERC to address')
.action(async (address, amount, tokenAddress) => {
await init({ rpc: program.rpc, torPort: program.tor, balanceCheck: true })
await send({ address, amount, tokenAddress })
})
program
.command('compliance <note>')
.description(

View File

@ -330,5 +330,25 @@ module.exports = {
},
proxy: '0x0D5550d52428E7e3175bfc9550207e4ad3859b17',
},
netId42161: {
'eth': {
'instanceAddress': {
'0.1': '0x84443CFd09A48AF6eF360C6976C5392aC5023a1F',
'1': '0xd47438C816c9E7f2E2888E060936a499Af9582b3',
'10': '0x330bdFADE01eE9bF63C209Ee33102DD334618e0a',
'100': '0x1E34A77868E19A6647b1f2F47B51ed72dEDE95DD'
},
'deployedBlockNumber': {
'0.1': 2243707,
'1': 2243709,
'10': 2243735,
'100': 2243749
},
'miningEnabled': false,
'symbol': 'ETH',
'decimals': 18
},
proxy: '0x0D5550d52428E7e3175bfc9550207e4ad3859b17',
},
}
}