UNPKG

o1js

Version:

TypeScript framework for zk-SNARKs and zkApps

330 lines (296 loc) 10.7 kB
import { expect } from 'expect'; import { AccountUpdate, Lightnet, Mina, PrivateKey, UInt64, fetchAccount } from 'o1js'; import os from 'os'; import { tic, toc } from '../../utils/tic-toc.node.js'; import { Dex, DexTokenHolder, addresses, keys, tokenIds } from './dex-with-actions.js'; import { TrivialCoin as TokenContract } from './erc20.js'; const useCustomLocalNetwork = process.env.USE_CUSTOM_LOCAL_NETWORK === 'true'; // setting this to a higher number allows you to skip a few transactions, to pick up after an error const successfulTransactions = 0; tic('Run DEX with actions, happy path, against real network.'); console.log(); const network = Mina.Network({ mina: useCustomLocalNetwork ? 'http://localhost:8080/graphql' : 'https://berkeley.minascan.io/graphql', archive: useCustomLocalNetwork ? 'http://localhost:8282' : 'https://api.minascan.io/archive/berkeley/v1/graphql', lightnetAccountManager: 'http://localhost:8181', }); Mina.setActiveInstance(network); let tx, pendingTx: Mina.PendingTransaction, balances, oldBalances; // compile contracts & wait for fee payer to be funded const senderKey = useCustomLocalNetwork ? (await Lightnet.acquireKeyPair()).privateKey : PrivateKey.random(); const sender = senderKey.toPublicKey(); if (!useCustomLocalNetwork) { console.log(`Funding the fee payer account.`); await ensureFundedAccount(senderKey.toBase58()); } await TokenContract.analyzeMethods(); await DexTokenHolder.analyzeMethods(); await Dex.analyzeMethods(); tic('compile (token)'); await TokenContract.compile(); toc(); tic('compile (dex token holder)'); await DexTokenHolder.compile(); toc(); tic('compile (dex main contract)'); await Dex.compile(); toc(); let tokenX = new TokenContract(addresses.tokenX); let tokenY = new TokenContract(addresses.tokenY); let dex = new Dex(addresses.dex); let dexTokenHolderX = new DexTokenHolder(addresses.dex, tokenIds.X); let dexTokenHolderY = new DexTokenHolder(addresses.dex, tokenIds.Y); let senderSpec = { sender, fee: 0.1e9 }; let userSpec = { sender: addresses.user, fee: 0.1e9 }; if (successfulTransactions <= 0) { tic('deploy & init token contracts'); tx = await Mina.transaction(senderSpec, async () => { await tokenX.deploy(); await tokenY.deploy(); // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves const accountFee = Mina.getNetworkConstants().accountCreationFee; let feePayerUpdate = AccountUpdate.fundNewAccount(sender, 2); feePayerUpdate.send({ to: tokenX.self, amount: accountFee }); feePayerUpdate.send({ to: tokenY.self, amount: accountFee }); }); await tx.prove(); pendingTx = await tx.sign([senderKey, keys.tokenX, keys.tokenY]).send(); toc(); console.log('account updates length', tx.transaction.accountUpdates.length); logPendingTransaction(pendingTx); tic('waiting'); await pendingTx.wait(); await sleep(10); toc(); } if (successfulTransactions <= 1) { tic('deploy dex contracts'); tx = await Mina.transaction(senderSpec, async () => { // pay fees for creating 3 dex accounts AccountUpdate.createSigned(sender).balance.subInPlace( Mina.getNetworkConstants().accountCreationFee.mul(3) ); await dex.deploy(); await dexTokenHolderX.deploy(); await tokenX.approveAccountUpdate(dexTokenHolderX.self); await dexTokenHolderY.deploy(); await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); pendingTx = await tx.sign([senderKey, keys.dex]).send(); toc(); console.log('account updates length', tx.transaction.accountUpdates.length); logPendingTransaction(pendingTx); tic('waiting'); await pendingTx.wait(); await sleep(10); toc(); } let USER_DX = 1_000n; if (successfulTransactions <= 2) { tic('transfer tokens to user'); tx = await Mina.transaction(senderSpec, async () => { // pay fees for creating 3 user accounts let feePayer = AccountUpdate.fundNewAccount(sender, 3); feePayer.send({ to: addresses.user, amount: 8e9 }); // give users MINA to pay fees await tokenX.transfer(addresses.tokenX, addresses.user, UInt64.from(USER_DX)); await tokenY.transfer(addresses.tokenY, addresses.user, UInt64.from(USER_DX)); }); await tx.prove(); pendingTx = await tx.sign([senderKey, keys.tokenX, keys.tokenY]).send(); toc(); console.log('account updates length', tx.transaction.accountUpdates.length); logPendingTransaction(pendingTx); tic('waiting'); await pendingTx.wait(); await sleep(10); toc(); } if (successfulTransactions <= 3) { // this is done in advance to avoid account update limit in `supply` tic("create user's lq token account"); tx = await Mina.transaction(userSpec, async () => { AccountUpdate.fundNewAccount(addresses.user); await dex.createAccount(); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); toc(); console.log('account updates length', tx.transaction.accountUpdates.length); logPendingTransaction(pendingTx); tic('waiting'); await pendingTx.wait(); await sleep(10); toc(); [oldBalances, balances] = [balances, await getTokenBalances()]; expect(balances.user.X).toEqual(USER_DX); console.log(balances); } if (successfulTransactions <= 4) { tic('supply liquidity'); tx = await Mina.transaction(userSpec, async () => { await dex.supplyLiquidityBase(UInt64.from(USER_DX), UInt64.from(USER_DX)); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); toc(); console.log('account updates length', tx.transaction.accountUpdates.length); logPendingTransaction(pendingTx); tic('waiting'); await pendingTx.wait(); await sleep(10); toc(); [oldBalances, balances] = [balances, await getTokenBalances()]; expect(balances.user.X).toEqual(0n); console.log(balances); } let USER_DL = 100n; if (successfulTransactions <= 5) { tic('redeem liquidity, step 1'); tx = await Mina.transaction(userSpec, async () => { await dex.redeemInitialize(UInt64.from(USER_DL)); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); toc(); console.log('account updates length', tx.transaction.accountUpdates.length); logPendingTransaction(pendingTx); tic('waiting'); await pendingTx.wait(); await sleep(10); toc(); console.log(await getTokenBalances()); } if (successfulTransactions <= 6) { tic('redeem liquidity, step 2a (get back token X)'); tx = await Mina.transaction(userSpec, async () => { await dexTokenHolderX.redeemLiquidityFinalize(); await tokenX.approveAccountUpdate(dexTokenHolderX.self); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); toc(); console.log('account updates length', tx.transaction.accountUpdates.length); logPendingTransaction(pendingTx); tic('waiting'); await pendingTx.wait(); await sleep(10); toc(); console.log(await getTokenBalances()); } if (successfulTransactions <= 7) { tic('redeem liquidity, step 2b (get back token Y)'); tx = await Mina.transaction(userSpec, async () => { await dexTokenHolderY.redeemLiquidityFinalize(); await tokenY.approveAccountUpdate(dexTokenHolderY.self); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); toc(); console.log('account updates length', tx.transaction.accountUpdates.length); logPendingTransaction(pendingTx); tic('waiting'); await pendingTx.wait(); await sleep(10); toc(); [oldBalances, balances] = [balances, await getTokenBalances()]; expect(balances.user.X).toEqual(USER_DL / 2n); console.log(balances); } if (successfulTransactions <= 8) { oldBalances = await getTokenBalances(); tic('swap 10 X for Y'); USER_DX = 10n; tx = await Mina.transaction(userSpec, async () => { await dex.swapX(UInt64.from(USER_DX)); }); await tx.prove(); pendingTx = await tx.sign([keys.user]).send(); toc(); console.log('account updates length', tx.transaction.accountUpdates.length); logPendingTransaction(pendingTx); tic('waiting'); await pendingTx.wait(); await sleep(10); toc(); balances = await getTokenBalances(); expect(balances.user.X).toEqual(oldBalances.user.X - USER_DX); console.log(balances); } toc(); console.log('dex happy path with actions was successful! 🎉'); console.log(); // Tear down const keyPairReleaseMessage = await Lightnet.releaseKeyPair({ publicKey: sender.toBase58(), }); if (keyPairReleaseMessage) console.info(keyPairReleaseMessage); async function ensureFundedAccount(privateKeyBase58: string) { let senderKey = PrivateKey.fromBase58(privateKeyBase58); let sender = senderKey.toPublicKey(); let result = await fetchAccount({ publicKey: sender }); let balance = result.account?.balance.toBigInt(); if (balance === undefined || balance <= 15_000_000_000n) { await Mina.faucet(sender); await sleep(1); } return { senderKey, sender }; } function logPendingTransaction(pendingTx: Mina.PendingTransaction) { if (pendingTx.status === 'rejected') throw Error('transaction failed'); console.log( 'tx sent: ' + (useCustomLocalNetwork ? `file://${os.homedir()}/.cache/zkapp-cli/lightnet/explorer/<version>/index.html?target=transaction&hash=${ pendingTx.hash }` : `https://minascan.io/berkeley/tx/${pendingTx.hash}?type=zk-tx`) ); } async function getTokenBalances() { // fetch accounts await Promise.all( [ { publicKey: addresses.user }, { publicKey: addresses.user, tokenId: tokenIds.X }, { publicKey: addresses.user, tokenId: tokenIds.Y }, { publicKey: addresses.user, tokenId: tokenIds.lqXY }, { publicKey: addresses.dex }, { publicKey: addresses.dex, tokenId: tokenIds.X }, { publicKey: addresses.dex, tokenId: tokenIds.Y }, ].map((a) => fetchAccount(a)) ); let balances = { user: { MINA: 0n, X: 0n, Y: 0n, lqXY: 0n }, dex: { X: 0n, Y: 0n, lqXYSupply: 0n }, }; let user = 'user' as const; try { balances.user.MINA = Mina.getBalance(addresses[user]).toBigInt() / 1_000_000_000n; } catch {} for (let token of ['X', 'Y', 'lqXY'] as const) { try { balances[user][token] = Mina.getBalance(addresses[user], tokenIds[token]).toBigInt(); } catch {} } try { balances.dex.X = Mina.getBalance(addresses.dex, tokenIds.X).toBigInt(); } catch {} try { balances.dex.Y = Mina.getBalance(addresses.dex, tokenIds.Y).toBigInt(); } catch {} try { let dex = new Dex(addresses.dex); balances.dex.lqXYSupply = dex.totalSupply.get().toBigInt(); } catch {} return balances; } async function sleep(sec: number) { return new Promise((r) => setTimeout(r, sec * 1000)); }