// Tooling for interaction with the blockchain. import * as Types from 'types/sdk/chain' // External imports import { TransactionRequest } from '@ethersproject/abstract-provider' import { BaseContract, BigNumber, ContractTransaction, providers, Signer, VoidSigner } from 'ethers' import { randomBytes } from 'crypto' // Our local types import { ERC20Tornado__factory, ETHTornado__factory, TornadoInstance, TornadoInstance__factory, TornadoProxy__factory, TornadoProxy, ERC20__factory, ERC20, Multicall3Contract__factory } from 'types/deth' import { Multicall3 } from 'types/deth/Multicall3Contract' // Local modules import { OnchainData } from 'lib/data' import { ErrorUtils } from './utils' import { MarkOptional } from 'ts-essentials' // We use a vanilla provider here, but in reality we will probably // add a censorship-checking custom derivative of it type Provider = providers.Provider export function prepareAddress(address: string): string { return (address.slice(0, 2) == '0x' ? address.slice(2) : address).toLowerCase().padStart(64, '0') } /** * The Chain class stores Tornado-agnostic chain data and also * handles such interactions. */ export class Chain { public signer?: Signer public provider: Provider private _emptySigner: VoidSigner public chainId?: number constructor(provider: Provider, signer?: Signer) { this.provider = provider this.signer = signer this._emptySigner = new VoidSigner('0x' + randomBytes(20).toString('hex'), provider) } async getChainId(): Promise { if (!this.chainId) this.chainId = (await this.provider.getNetwork()).chainId return this.chainId } async latestBlockNum(): Promise { return this.provider.getBlockNumber() } async getAccountBalance(account: string): Promise { return this.provider.getBalance(account) } async getTokenDecimals(token: string): Promise { let treq = { to: token, data: '0x313ce567' } return BigNumber.from(await this._emptySigner.call(treq)) } async getTokenBalance(account: string, token: string, normalized: boolean = false): Promise { let treq = { to: token, data: '0x70a08231000000000000000000000000' + prepareAddress(account) } let divisor = normalized ? BigNumber.from(10).pow(await this.getTokenDecimals(token)) : 1 return BigNumber.from(await this._emptySigner.call(treq)).div(divisor) } getTokenContract(tokenAddress: string): ERC20 { return ERC20__factory.connect(tokenAddress, this.provider) } async populateBatchCall( callStruct: Array> ): Promise { if (callStruct[0].value) return await Multicall3Contract__factory.connect( await OnchainData.getMulticall3Address(String(this.chainId)), this.provider ).populateTransaction.aggregate3Value(callStruct as Array) return await Multicall3Contract__factory.connect( await OnchainData.getMulticall3Address(String(this.chainId)), this.provider ).populateTransaction.aggregate3(callStruct) } async batchCall( callStruct: Array> ): Promise { if (this.signer) if (callStruct[0].value) return await Multicall3Contract__factory.connect( await OnchainData.getMulticall3Address(String(this.chainId)), this.signer ).aggregate3Value(callStruct as Array) else { return await Multicall3Contract__factory.connect( await OnchainData.getMulticall3Address(String(this.chainId)), this.provider ).aggregate3(callStruct) } else throw ErrorUtils.getError('Chain.batchCall: no signer provided.') } } /** * This is Tornado-specific. */ export namespace Contracts { function _getContract( name: string, address: string, signerOrProvider: Signer | Provider ): C { if (name == 'TornadoInstance') { return TornadoInstance__factory.connect(address, signerOrProvider) as C } else if (name == 'TornadoProxy') { return TornadoProxy__factory.connect(address, signerOrProvider) as C } else if (name == 'ETHTornado') { return ETHTornado__factory.connect(address, signerOrProvider) as C } else if (name == 'ERC20Tornado') { return ERC20Tornado__factory.connect(address, signerOrProvider) as C } else { return ERC20__factory.connect(address, signerOrProvider) as C } } type Path = string const contractMap: Map = new Map() export async function getProxy( network: string, signerOrProvider: Signer | Provider ): Promise { const key = `TornadoProxy${network}` if (!contractMap.has(key)) { contractMap.set( key, _getContract( 'TornadoProxy', await OnchainData.getProxyAddress(network), signerOrProvider ) ) } return contractMap.get(`TornadoProxy${network}`) as TornadoProxy } export async function getInstance( network: string, token: string, denomination: string, signerOrProvider: Signer | Provider ): Promise { const key = `TornadoInstance${network}${token}${denomination}` if (!contractMap.has(key)) { contractMap.set( key, _getContract( 'TornadoInstance', await OnchainData.getInstanceAddress(network, token, denomination), signerOrProvider ) ) } return contractMap.get(key) as TornadoInstance } export async function getTornToken(signerOrProvider: Signer | Provider): Promise { const key = '$TORN' if (!contractMap.has(key)) { contractMap.set( key, _getContract('ERC20', '0x77777feddddffc19ff86db637967013e6c6a116c', signerOrProvider) ) } return contractMap.get(key) as ERC20 } }