// Types import { MarkOptional } from 'ts-essentials' import * as Types from 'types/sdk/chain' // 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' // External imports import { TransactionRequest } from '@ethersproject/abstract-provider' import { BaseContract, BigNumber, ContractTransaction, providers, Signer, VoidSigner } from 'ethers' import { randomBytes } from 'crypto' // Local modules import { Onchain } from 'lib/data' import { ErrorUtils, HexUtils } from 'lib/utils' // We use a vanilla provider here, but in reality we will probably // add a censorship-checking custom derivative of it type Provider = providers.Provider /** * 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 public symbol?: string 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 getChainSymbol(): Promise { if (!this.symbol) this.symbol = await Onchain.getNetworkSymbol(String(await this.getChainId())) return this.symbol } latestBlockNum(): Promise { return this.provider.getBlockNumber() } getAccountBalance(account: string): Promise { return this.provider.getBalance(account) } getGasPrice(): Promise { return this.provider.getGasPrice() } getTokenContract(tokenAddress: string): ERC20 { return Contracts.getToken(tokenAddress, this.signer ?? this.provider) } 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' + HexUtils.prepareAddress(account) } let divisor = normalized ? BigNumber.from(10).pow(await this.getTokenDecimals(token)) : 1 return BigNumber.from(await this._emptySigner.call(treq)).div(divisor) } async populateBatchCall( callStruct: Array> ): Promise { if (callStruct[0].value) return await Multicall3Contract__factory.connect( await Onchain.getMulticall3Address(String(this.chainId)), this.provider ).populateTransaction.aggregate3Value(callStruct as Array) return await Multicall3Contract__factory.connect( await Onchain.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 Onchain.getMulticall3Address(String(this.chainId)), this.signer ).aggregate3Value(callStruct as Array) else { return await Multicall3Contract__factory.connect( await Onchain.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 Onchain.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 Onchain.getInstanceAddress(network, token, denomination), signerOrProvider ) ) } return contractMap.get(key) as TornadoInstance } export function getToken(tokenAddress: string, signerOrProvider: Signer | Provider): ERC20 { if (!contractMap.has(tokenAddress)) contractMap.set(tokenAddress, _getContract('ERC20', tokenAddress, signerOrProvider)) return contractMap.get(tokenAddress) as ERC20 } export function getTornToken(signerOrProvider: Signer | Provider): ERC20 { const key = '$TORN' if (!contractMap.has(key)) { contractMap.set( key, _getContract('ERC20', '0x77777feddddffc19ff86db637967013e6c6a116c', signerOrProvider) ) } return contractMap.get(key) as ERC20 } }