Added deposit

This commit is contained in:
Aleksandr Kraiz
2022-04-22 01:27:32 +04:00
parent c2e21d8e7e
commit 945ef60a5f
9 changed files with 289 additions and 137 deletions

View File

@@ -4,41 +4,9 @@ import clone from 'just-clone';
import { contracts, utils } from '.';
import { APPROVE_ERC20_GAS_LIMIT, NATIVE_CURRENCY_PRECISION } from './constants';
import {
AggregatedBalanceRequirement, Asset, BalanceIssue, BalanceRequirement, Source,
AggregatedBalanceRequirement, Approve, Asset, BalanceIssue, BalanceRequirement, Source,
} from './types';
const arrayEquals = (a: unknown[], b: unknown[]) => a.length === b.length
&& a.every((value, index) => value === b[index]);
// By asset + sources + spender
const aggregateBalanceRequirements = (requirements: BalanceRequirement[]) => requirements
.reduce<AggregatedBalanceRequirement[]>((prev, curr) => {
const aggregatedBalanceRequirement = prev.find(
(item) => item.asset.address === curr.asset.address
&& arrayEquals(item.sources, curr.sources)
&& item.spenderAddress === curr.spenderAddress,
);
if (aggregatedBalanceRequirement) {
aggregatedBalanceRequirement.items = {
...aggregatedBalanceRequirement.items,
[curr.reason]: curr.amount,
};
return prev;
}
return [
...prev,
{
asset: curr.asset,
sources: curr.sources,
spenderAddress: curr.spenderAddress,
items: {
[curr.reason]: curr.amount,
},
},
];
}, []);
import arrayEquals from './utils/arrayEquals';
export default class BalanceGuard {
private readonly balances: Partial<
@@ -56,18 +24,18 @@ export default class BalanceGuard {
private readonly provider: ethers.providers.Provider;
private readonly walletAddress: string;
private readonly signer: ethers.Signer;
constructor(
balances: Partial<Record<string, Record<'exchange' | 'wallet' | 'allowance', BigNumber>>>,
nativeCryptocurrency: Asset,
provider: ethers.providers.Provider,
walletAddress: string,
signer: ethers.Signer,
) {
this.balances = balances;
this.nativeCryptocurrency = nativeCryptocurrency;
this.provider = provider;
this.walletAddress = walletAddress;
this.signer = signer;
}
registerRequirement(expense: BalanceRequirement) {
@@ -84,8 +52,8 @@ export default class BalanceGuard {
private async checkResetRequired(
assetAddress: string,
spenderAddress: string,
walletAddress: string,
) {
const walletAddress = await this.signer.getAddress();
const tokenContract = contracts.ERC20__factory
.connect(assetAddress, this.provider);
const unsignedTx = await tokenContract.populateTransaction
@@ -103,9 +71,97 @@ export default class BalanceGuard {
return resetRequired;
}
async check() {
// By asset + sources + spender
static aggregateBalanceRequirements(requirements: BalanceRequirement[]) {
return requirements
.reduce<AggregatedBalanceRequirement[]>((prev, curr) => {
const aggregatedBalanceRequirement = prev.find(
(item) => item.asset.address === curr.asset.address
&& arrayEquals(item.sources, curr.sources)
&& item.spenderAddress === curr.spenderAddress,
);
if (aggregatedBalanceRequirement) {
aggregatedBalanceRequirement.items = {
...aggregatedBalanceRequirement.items,
[curr.reason]: curr.amount,
};
return prev;
}
return [
...prev,
{
asset: curr.asset,
sources: curr.sources,
spenderAddress: curr.spenderAddress,
items: {
[curr.reason]: curr.amount,
},
},
];
}, []);
}
private fixAllAutofixableBalanceIssues = async (
balanceIssues: BalanceIssue[],
) => {
const fixBalanceIssue = async (issue: BalanceIssue) => {
const tokenContract = contracts.ERC20__factory.connect(issue.asset.address, this.provider);
const approve = async ({ spenderAddress, targetAmount }: Approve) => {
const bnTargetAmount = new BigNumber(targetAmount);
const unsignedApproveTx = await tokenContract
.populateTransaction
.approve(
spenderAddress,
bnTargetAmount.isZero()
? '0' // Reset
: ethers.constants.MaxUint256, // Infinite approve
);
const walletAddress = await this.signer.getAddress();
const nonce = await this.provider.getTransactionCount(walletAddress, 'pending');
const gasPrice = await this.provider.getGasPrice();
const network = await this.provider.getNetwork();
unsignedApproveTx.chainId = network.chainId;
unsignedApproveTx.gasPrice = gasPrice;
unsignedApproveTx.nonce = nonce;
unsignedApproveTx.from = walletAddress;
const gasLimit = await this.provider.estimateGas(unsignedApproveTx);
unsignedApproveTx.gasLimit = gasLimit;
const signedTx = await this.signer.signTransaction(unsignedApproveTx);
const txResponse = await this.provider.sendTransaction(signedTx);
console.log(`${issue.asset.name} approve transaction sent ${txResponse.hash}. Waiting for confirmation...`);
await txResponse.wait();
console.log(`${issue.asset.name} approve transaction confirmed.`);
};
await issue.approves?.reduce(async (promise, item) => {
await promise;
return approve(item);
}, Promise.resolve());
};
const autofixableBalanceIssues = balanceIssues.filter((balanceIssue) => balanceIssue.approves);
await autofixableBalanceIssues.reduce(async (promise, item) => {
await promise;
return fixBalanceIssue(item);
}, Promise.resolve());
return balanceIssues.filter((item) => !autofixableBalanceIssues.includes(item));
};
async check(fixAutofixable?: boolean) {
console.log(`Balance requirements: ${this.requirements
.map((requirement) => `${requirement.amount} ${requirement.asset.name} `
+ `for '${requirement.reason}' `
+ `from [${requirement.sources.join(' + ')}]`)
.join(', ')}`);
const remainingBalances = clone(this.balances);
const aggregatedRequirements = aggregateBalanceRequirements(this.requirements);
const aggregatedRequirements = BalanceGuard.aggregateBalanceRequirements(this.requirements);
// Balance absorption order is important!
// 1. Exchange-contract only
@@ -193,7 +249,6 @@ export default class BalanceGuard {
const resetRequired = await this.checkResetRequired(
asset.address,
spenderAddress,
this.walletAddress,
);
const gasPriceWei = await this.provider.getGasPrice();
const approveTransactionCost = ethers.BigNumber
@@ -269,7 +324,6 @@ export default class BalanceGuard {
const resetRequired = await this.checkResetRequired(
asset.address,
spenderAddress,
this.walletAddress,
);
const gasPriceWei = await this.provider.getGasPrice();
const approveTransactionCost = ethers.BigNumber
@@ -332,6 +386,11 @@ export default class BalanceGuard {
}
});
return balanceIssues;
if (fixAutofixable) {
const unfixed = await this.fixAllAutofixableBalanceIssues(balanceIssues);
if (unfixed.length > 0) throw new Error(`Balance issues: ${unfixed.map((issue, i) => `${i + 1}. ${issue.message}`).join('\n')}`);
} else if (balanceIssues.length > 0) {
throw new Error(`Balance issues: ${balanceIssues.map((issue, i) => `${i + 1}. ${issue.message}`).join('\n')}`);
}
}
}