sdk-monorepo/src/lib/chain.ts

202 lines
6.3 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 { 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<number> {
if (!this.chainId) this.chainId = (await this.provider.getNetwork()).chainId
return this.chainId
}
async getChainSymbol(): Promise<string> {
if (!this.symbol) this.symbol = await Onchain.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 Onchain.getMulticall3Address(String(this.chainId)),
this.provider
).populateTransaction.aggregate3Value(callStruct as Array<Multicall3.Call3ValueStruct>)
return await Multicall3Contract__factory.connect(
await Onchain.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 Onchain.getMulticall3Address(String(this.chainId)),
this.signer
).aggregate3Value(callStruct as Array<Multicall3.Call3ValueStruct>)
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<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 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<TornadoInstance> {
const key = `TornadoInstance${network}${token}${denomination}`
if (!contractMap.has(key)) {
contractMap.set(
key,
_getContract<TornadoInstance>(
'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>('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
}
}