d83dcd8112
Signed-off-by: T-Hax <>
206 lines
6.4 KiB
TypeScript
206 lines
6.4 KiB
TypeScript
// 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 { OnchainData } 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<number> {
|
|
if (!this.chainId) this.chainId = (await this.provider.getNetwork()).chainId
|
|
return this.chainId
|
|
}
|
|
|
|
async getChainSymbol(): Promise<string> {
|
|
if (!this.symbol) this.symbol = await OnchainData.getNetworkSymbol(String(await this.getChainId()))
|
|
return this.symbol
|
|
}
|
|
|
|
latestBlockNum(): Promise<number> {
|
|
return this.provider.getBlockNumber()
|
|
}
|
|
|
|
getAccountBalance(account: string): Promise<BigNumber> {
|
|
return this.provider.getBalance(account)
|
|
}
|
|
|
|
getGasPrice(): Promise<BigNumber> {
|
|
return this.provider.getGasPrice()
|
|
}
|
|
|
|
getTokenContract(tokenAddress: string): ERC20 {
|
|
return Contracts.getToken(tokenAddress, this.signer ?? this.provider)
|
|
}
|
|
|
|
async getTokenDecimals(token: string): Promise<BigNumber> {
|
|
let treq = {
|
|
to: token,
|
|
data: '0x313ce567'
|
|
}
|
|
return BigNumber.from(await this._emptySigner.call(treq))
|
|
}
|
|
|
|
async getTokenBalance(account: string, token: string, normalized: boolean = false): Promise<BigNumber> {
|
|
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<MarkOptional<Multicall3.Call3ValueStruct, 'value'>>
|
|
): Promise<TransactionRequest> {
|
|
if (callStruct[0].value)
|
|
return await Multicall3Contract__factory.connect(
|
|
await OnchainData.getMulticall3Address(String(this.chainId)),
|
|
this.provider
|
|
).populateTransaction.aggregate3Value(callStruct as Array<Multicall3.Call3ValueStruct>)
|
|
|
|
return await Multicall3Contract__factory.connect(
|
|
await OnchainData.getMulticall3Address(String(this.chainId)),
|
|
this.provider
|
|
).populateTransaction.aggregate3(callStruct)
|
|
}
|
|
|
|
async batchCall(
|
|
callStruct: Array<MarkOptional<Multicall3.Call3ValueStruct, 'value'>>
|
|
): Promise<ContractTransaction> {
|
|
if (this.signer)
|
|
if (callStruct[0].value)
|
|
return await Multicall3Contract__factory.connect(
|
|
await OnchainData.getMulticall3Address(String(this.chainId)),
|
|
this.signer
|
|
).aggregate3Value(callStruct as Array<Multicall3.Call3ValueStruct>)
|
|
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<C extends Types.Contracts.TornadoContracts>(
|
|
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<Path, BaseContract> = new Map<Path, BaseContract>()
|
|
|
|
export async function getProxy(
|
|
network: string,
|
|
signerOrProvider: Signer | Provider
|
|
): Promise<TornadoProxy> {
|
|
const key = `TornadoProxy${network}`
|
|
if (!contractMap.has(key)) {
|
|
contractMap.set(
|
|
key,
|
|
_getContract<TornadoProxy>(
|
|
'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<TornadoInstance> {
|
|
const key = `TornadoInstance${network}${token}${denomination}`
|
|
if (!contractMap.has(key)) {
|
|
contractMap.set(
|
|
key,
|
|
_getContract<TornadoInstance>(
|
|
'TornadoInstance',
|
|
await OnchainData.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>('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>('ERC20', '0x77777feddddffc19ff86db637967013e6c6a116c', signerOrProvider)
|
|
)
|
|
}
|
|
return contractMap.get(key) as ERC20
|
|
}
|
|
}
|