Skip to content

Commit 6b93832

Browse files
authored
fix: amm bot (#13553)
Fix #13544
1 parent ea12d56 commit 6b93832

File tree

4 files changed

+73
-14
lines changed

4 files changed

+73
-14
lines changed

yarn-project/bot/src/amm_bot.ts

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { AztecAddress, Fr, SentTx, type Wallet } from '@aztec/aztec.js';
1+
import { AztecAddress, Fr, SentTx, TxReceipt, type Wallet } from '@aztec/aztec.js';
2+
import { jsonStringify } from '@aztec/foundation/json-rpc';
23
import type { AMMContract } from '@aztec/noir-contracts.js/AMM';
34
import type { TokenContract } from '@aztec/noir-contracts.js/Token';
45
import type { AztecNode, AztecNodeAdmin, PXE } from '@aztec/stdlib/interfaces/client';
@@ -7,7 +8,8 @@ import { BaseBot } from './base_bot.js';
78
import type { BotConfig } from './config.js';
89
import { BotFactory } from './factory.js';
910

10-
const TRANSFER_AMOUNT = 1_000;
11+
const TRANSFER_BASE_AMOUNT = 1_000;
12+
const TRANSFER_VARIANCE = 200;
1113

1214
type Balances = { token0: bigint; token1: bigint };
1315

@@ -35,24 +37,34 @@ export class AmmBot extends BaseBot {
3537
const { feePaymentMethod } = this.config;
3638
const { wallet, amm, token0, token1 } = this;
3739

38-
this.log.verbose(`Preparing tx with ${feePaymentMethod} fee to swap tokens`, logCtx);
40+
const balances = this.getBalances();
41+
this.log.info(`Preparing tx with ${feePaymentMethod} fee to swap tokens. Balances: ${jsonStringify(balances)}`, {
42+
...logCtx,
43+
balances,
44+
});
3945

40-
const ammBalances = await this.getAmmBalances();
41-
const amountIn = TRANSFER_AMOUNT;
46+
// 1000 ± 200
47+
const amountIn = Math.floor(TRANSFER_BASE_AMOUNT + (Math.random() - 0.5) * TRANSFER_VARIANCE);
4248
const nonce = Fr.random();
4349

50+
const [tokenIn, tokenOut] = Math.random() < 0.5 ? [token0, token1] : [token1, token0];
51+
4452
const swapAuthwit = await wallet.createAuthWit({
4553
caller: amm.address,
46-
action: token0.methods.transfer_to_public(wallet.getAddress(), amm.address, amountIn, nonce),
54+
action: tokenIn.methods.transfer_to_public(wallet.getAddress(), amm.address, amountIn, nonce),
4755
});
4856

4957
const amountOutMin = await amm.methods
50-
.get_amount_out_for_exact_in(ammBalances.token0, ammBalances.token1, amountIn)
58+
.get_amount_out_for_exact_in(
59+
await tokenIn.methods.balance_of_public(amm.address).simulate(),
60+
await tokenOut.methods.balance_of_public(amm.address).simulate(),
61+
amountIn,
62+
)
5163
.simulate();
5264

5365
const swapExactTokensInteraction = amm.methods.swap_exact_tokens_for_tokens(
54-
token0.address,
55-
token1.address,
66+
tokenIn.address,
67+
tokenOut.address,
5668
amountIn,
5769
amountOutMin,
5870
nonce,
@@ -63,9 +75,16 @@ export class AmmBot extends BaseBot {
6375
this.log.verbose(`Proving transaction`, logCtx);
6476
const tx = await swapExactTokensInteraction.prove(opts);
6577

78+
this.log.info(`Tx. Balances: ${jsonStringify(balances)}`, { ...logCtx, balances });
79+
6680
return tx.send();
6781
}
6882

83+
protected override async onTxMined(receipt: TxReceipt, logCtx: object): Promise<void> {
84+
const balances = await this.getBalances();
85+
this.log.info(`Balances after swap in tx ${receipt.txHash}: ${jsonStringify(balances)}`, { ...logCtx, balances });
86+
}
87+
6988
public getAmmBalances(): Promise<Balances> {
7089
return this.getPublicBalanceFor(this.amm.address);
7190
}

yarn-project/bot/src/base_bot.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,19 @@ export abstract class BaseBot {
5252
`Tx #${this.attempts} ${receipt.txHash} successfully mined in block ${receipt.blockNumber} (stats: ${this.successes}/${this.attempts} success)`,
5353
logCtx,
5454
);
55+
56+
await this.onTxMined(receipt, logCtx);
57+
5558
return receipt;
5659
}
5760

5861
protected abstract createAndSendTx(logCtx: object): Promise<SentTx>;
5962

63+
protected onTxMined(_receipt: TxReceipt, _logCtx: object): Promise<void> {
64+
// no-op
65+
return Promise.resolve();
66+
}
67+
6068
protected getSendMethodOpts(...authWitnesses: AuthWitness[]): SendMethodOptions {
6169
const sender = this.wallet.getAddress();
6270
const { l2GasLimit, daGasLimit, skipPublicSimulation } = this.config;

yarn-project/bot/src/factory.ts

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
} from '@aztec/aztec.js';
1818
import { createEthereumChain, createL1Clients } from '@aztec/ethereum';
1919
import { Fr } from '@aztec/foundation/fields';
20+
import { Timer } from '@aztec/foundation/timer';
2021
import { AMMContract } from '@aztec/noir-contracts.js/AMM';
2122
import { EasyPrivateTokenContract } from '@aztec/noir-contracts.js/EasyPrivateToken';
2223
import { TokenContract } from '@aztec/noir-contracts.js/Token';
@@ -85,7 +86,7 @@ export class BotFactory {
8586
const liquidityToken = await this.setupTokenContract(wallet, this.config.tokenSalt, 'BotLPToken', 'BOTLP');
8687
const amm = await this.setupAmmContract(wallet, this.config.tokenSalt, token0, token1, liquidityToken);
8788

88-
await this.fundAmm(wallet, amm, token0, token1);
89+
await this.fundAmm(wallet, amm, token0, token1, liquidityToken);
8990
this.log.info(`AMM initialized and funded`);
9091

9192
return { wallet, amm, token0, token1, pxe: this.pxe };
@@ -110,7 +111,9 @@ export class BotFactory {
110111
const isInit = (await this.pxe.getContractMetadata(account.getAddress())).isContractInitialized;
111112
if (isInit) {
112113
this.log.info(`Account at ${account.getAddress().toString()} already initialized`);
114+
const timer = new Timer();
113115
const wallet = await account.register();
116+
this.log.info(`Account at ${account.getAddress()} registered. duration=${timer.ms()}`);
114117
return wallet;
115118
} else {
116119
const address = account.getAddress();
@@ -232,7 +235,15 @@ export class BotFactory {
232235
amm: AMMContract,
233236
token0: TokenContract,
234237
token1: TokenContract,
238+
lpToken: TokenContract,
235239
): Promise<void> {
240+
const getPrivateBalances = () =>
241+
Promise.all([
242+
token0.methods.balance_of_private(wallet.getAddress()),
243+
token1.methods.balance_of_private(wallet.getAddress()),
244+
lpToken.methods.balance_of_private(wallet.getAddress()),
245+
]);
246+
236247
const nonce = Fr.random();
237248

238249
// keep some tokens for swapping
@@ -241,6 +252,12 @@ export class BotFactory {
241252
const amount1Max = MINT_BALANCE / 2;
242253
const amount1Min = MINT_BALANCE / 4;
243254

255+
const [t0Bal, t1Bal, lpBal] = await getPrivateBalances();
256+
257+
this.log.info(
258+
`Minting ${MINT_BALANCE} tokens of each BotToken0 and BotToken1. Current private balances of ${wallet.getAddress()}: token0=${t0Bal}, token1=${t1Bal}, lp=${lpBal}`,
259+
);
260+
244261
const token0Authwit = await wallet.createAuthWit({
245262
caller: amm.address,
246263
action: token0.methods.transfer_to_public(wallet.getAddress(), amm.address, amount0Max, nonce),
@@ -250,7 +267,6 @@ export class BotFactory {
250267
action: token1.methods.transfer_to_public(wallet.getAddress(), amm.address, amount1Max, nonce),
251268
});
252269

253-
this.log.info(`Minting tokens`);
254270
const mintTx = new BatchCall(wallet, [
255271
token0.methods.mint_to_private(wallet.getAddress(), wallet.getAddress(), MINT_BALANCE),
256272
token1.methods.mint_to_private(wallet.getAddress(), wallet.getAddress(), MINT_BALANCE),
@@ -259,13 +275,18 @@ export class BotFactory {
259275
this.log.info(`Sent mint tx: ${await mintTx.getTxHash()}`);
260276
await mintTx.wait({ timeout: this.config.txMinedWaitSeconds });
261277

262-
this.log.info(`Funding AMM`);
263278
const addLiquidityTx = amm.methods.add_liquidity(amount0Max, amount1Max, amount0Min, amount1Min, nonce).send({
264279
authWitnesses: [token0Authwit, token1Authwit],
265280
});
266281

267282
this.log.info(`Sent tx to add liquidity to the AMM: ${await addLiquidityTx.getTxHash()}`);
268283
await addLiquidityTx.wait({ timeout: this.config.txMinedWaitSeconds });
284+
this.log.info(`Liquidity added`);
285+
286+
const [newT0Bal, newT1Bal, newLPBal] = await getPrivateBalances();
287+
this.log.info(
288+
`Updated private balances of ${wallet.getAddress()} after minting and funding AMM: token0=${newT0Bal}, token1=${newT1Bal}, lp=${newLPBal}`,
289+
);
269290
}
270291

271292
private async registerOrDeployContract<T extends ContractBase>(

yarn-project/end-to-end/src/e2e_bot.test.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,19 @@ describe('e2e_bot', () => {
8989
const balancesBefore = await bot.getBalances();
9090
await expect(bot.run()).resolves.toBeDefined();
9191
const balancesAfter = await bot.getBalances();
92-
expect(balancesAfter.senderPrivate.token0).toBeLessThan(balancesBefore.senderPrivate.token0);
93-
expect(balancesAfter.senderPrivate.token1).toBeGreaterThan(balancesBefore.senderPrivate.token1);
92+
93+
// the bot swaps randomly
94+
// either we send token0 or token1
95+
expect(
96+
balancesAfter.senderPrivate.token0 < balancesBefore.senderPrivate.token0 ||
97+
balancesAfter.senderPrivate.token1 < balancesBefore.senderPrivate.token1,
98+
).toBeTrue();
99+
100+
// and get either token0 or token1
101+
expect(
102+
balancesAfter.senderPrivate.token0 > balancesBefore.senderPrivate.token0 ||
103+
balancesAfter.senderPrivate.token1 > balancesBefore.senderPrivate.token1,
104+
).toBeTrue();
94105
});
95106
});
96107
});

0 commit comments

Comments
 (0)