From fe63cf598e48bbac868c9edab52f8070190a95ba Mon Sep 17 00:00:00 2001 From: Jean Regisser Date: Sat, 10 Feb 2024 06:08:53 +0100 Subject: [PATCH] ci: fix e2e funding script (#4877) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Description E2E funding script broke because the Exchange contract (Mento V1) was retired. We now have to use Mento V2. See https://github.com/celo-org/developer-tooling/discussions/5#discussioncomment-8174020 and this Slack [thread](https://valora-app.slack.com/archives/C02E2FE98P2/p1707313527742089). ### Test plan - Faucet script works ``` ❯ NODE_OPTIONS='--unhandled-rejections=strict' yarn ts-node ./e2e/scripts/fund-e2e-accounts.ts yarn run v1.22.21 $ /Users/jean/src/github.com/valora-inc/wallet/node_modules/.bin/ts-node ./e2e/scripts/fund-e2e-accounts.ts Initial balance for 0x6131a6d616a4be3737b38988847270a64bc10caa: ┌─────────┬────────────────────┐ │ (index) │ Values │ ├─────────┼────────────────────┤ │ CELO │ 296.4630610934859 │ │ cUSD │ 210.81235045479227 │ │ cEUR │ 254.58206490185884 │ │ cREAL │ 0.5079986401468344 │ └─────────┴────────────────────┘ Initial balance for 0x86b8f44386cb2d457db79c3dab8cf42f9d8a3fc0: ┌─────────┬────────────────────┐ │ (index) │ Values │ ├─────────┼────────────────────┤ │ CELO │ 200.1086345828401 │ │ cUSD │ 284.97333333333336 │ │ cEUR │ 299.99761512691964 │ │ cREAL │ 4.97972124268512 │ └─────────┴────────────────────┘ Initial faucet balance: ┌─────────┬────────────────────┐ │ (index) │ Values │ ├─────────┼────────────────────┤ │ CELO │ 0.3998044596294922 │ │ cUSD │ 314.92579075821374 │ │ cEUR │ 208.37725279341225 │ │ cREAL │ 547.2075494343318 │ └─────────┴────────────────────┘ CELO balance is 0.3998044596294922, which is lower than target 174.56761600375182. Buying 174.16781154412234 CELO with ~126.230605652876074139 cUSD with max slippage of 1%. Received allowance tx hash 0x779e593031093e3c8fce959660f5eb66d35461e1a601113cf34de762140bce0b with status 1 Received swap tx hash 0xcc5e02c3ae987f49cd78cbb14dcd9327aa3cb65c5602a9181a57e5b08d3e0115 with status 1 cUSD balance is 314.92579075821374, which is higher than target 174.56761600375182. Selling 140.3581747544619 cUSD for ~193.65329082439476394 CELO with max slippage of 1%. Received allowance tx hash 0x199799819bf1c921a9f8822dc4cce26b49b7d5ff1297d78c036e21715724a13e with status 1 Received swap tx hash 0xde418d00719b76760731170153a437901f4d3e1187f2b37ef17f5c4405042ebd with status 1 cEUR balance is 208.37725279341225, which is higher than target 174.56761600375182. Selling 33.80963678966043 cEUR for ~50.263771272202399881 CELO with max slippage of 1%. Received allowance tx hash 0x4b7e3e7c64dc0dec5c7c6934b37d9e4cee6d3db6a242c5d446081663109f7f5d with status 1 Received swap tx hash 0x3471806819ad3e914e08a4f7fd2694df606616121ac3f1e8657fdf3cd23dfa78 with status 1 Finished funding wallets. E2E Test Account: 0x6131a6d616a4be3737b38988847270a64bc10caa ┌─────────┬────────────────────┐ │ (index) │ Values │ ├─────────┼────────────────────┤ │ CELO │ 296.4630610934859 │ │ cUSD │ 210.81235045479227 │ │ cEUR │ 254.58206490185884 │ │ cREAL │ 0.5079986401468344 │ └─────────┴────────────────────┘ E2E Test Account Secure Send: 0x86b8f44386cb2d457db79c3dab8cf42f9d8a3fc0 ┌─────────┬────────────────────┐ │ (index) │ Values │ ├─────────┼────────────────────┤ │ CELO │ 200.1086345828401 │ │ cUSD │ 284.97333333333336 │ │ cEUR │ 299.99761512691964 │ │ cREAL │ 4.97972124268512 │ └─────────┴────────────────────┘ Valora Test Faucet: 0xe5F5363e31351C38ac82DBAdeaD91Fd5a7B08846 ┌─────────┬────────────────────┐ │ (index) │ Values │ ├─────────┼────────────────────┤ │ CELO │ 418.478571902849 │ │ cUSD │ 48.33701035087574 │ │ cEUR │ 174.56761600375185 │ │ cREAL │ 547.2075494343318 │ └─────────┴────────────────────┘ ✨ Done in 55.69s. ``` ### Related issues - N/A ### Backwards compatibility Yes --- e2e/scripts/fund-e2e-accounts.ts | 314 +++++++++++++++++-------------- package.json | 2 + yarn.lock | 14 +- 3 files changed, 183 insertions(+), 147 deletions(-) diff --git a/e2e/scripts/fund-e2e-accounts.ts b/e2e/scripts/fund-e2e-accounts.ts index 40827d60606..66bb259e06b 100644 --- a/e2e/scripts/fund-e2e-accounts.ts +++ b/e2e/scripts/fund-e2e-accounts.ts @@ -1,6 +1,7 @@ -import { newKitFromWeb3, StableToken } from '@celo/contractkit' +import { Mento } from '@mento-protocol/mento-sdk' import dotenv from 'dotenv' -import Web3 from 'web3' +// Would be nice to use viem, but mento is using ethers +import { Contract, providers, utils, Wallet } from 'ethers' import { E2E_TEST_FAUCET, E2E_TEST_WALLET, @@ -9,12 +10,39 @@ import { } from './consts' import { checkBalance, getBalance } from './utils' +const provider = new providers.JsonRpcProvider('https://alfajores-forno.celo-testnet.org') + dotenv.config({ path: `${__dirname}/../.env` }) -const web3 = new Web3('https://alfajores-forno.celo-testnet.org') -const kit = newKitFromWeb3(web3) const valoraTestFaucetSecret = process.env['TEST_FAUCET_SECRET']! +interface Token { + symbol: string + address: string // Mento expects address to be in checksum format, or else it won't find the trading pair + decimals: number +} + +const CELO: Token = { + symbol: 'CELO', + address: utils.getAddress('0xf194afdf50b03e69bd7d057c1aa9e10c9954e4c9'), + decimals: 18, +} +const CUSD: Token = { + symbol: 'cUSD', + address: utils.getAddress('0x874069fa1eb16d44d622f2e0ca25eea172369bc1'), + decimals: 18, +} +const CEUR: Token = { + symbol: 'cEUR', + address: utils.getAddress('0x10c892a6ec43a53e45d0b916b4b7d383b1b78c0f'), + decimals: 18, +} +const TOKENS_BY_SYMBOL: Record = { + CELO, + cUSD: CUSD, + cEUR: CEUR, +} + ;(async () => { const walletsToBeFunded = [E2E_TEST_WALLET, E2E_TEST_WALLET_SECURE_SEND] const walletBalances = await Promise.all(walletsToBeFunded.map(getBalance)) @@ -28,17 +56,8 @@ const valoraTestFaucetSecret = process.env['TEST_FAUCET_SECRET']! console.table(faucetTokenBalances) // Connect Valora E2E Test Faucet - Private Key Stored in GitHub Secrets - kit.connection.addAccount( - web3.eth.accounts.privateKeyToAccount(valoraTestFaucetSecret.toString()).privateKey - ) - - // Get Token Contract Wrappers - const celoToken = await kit.contracts.getGoldToken() - const cusdToken = await kit.contracts.getStableToken() - const ceurToken = await kit.contracts.getStableToken(StableToken.cEUR) - const celoExchange = await kit.contracts.getExchange() - const cusdExchange = await kit.contracts.getExchange(StableToken.cUSD) - const ceurExchange = await kit.contracts.getExchange(StableToken.cEUR) + const signer = new Wallet(valoraTestFaucetSecret, provider) + const mento = await Mento.create(signer) // Balance Faucet let totalTokenHoldings = 0 // the absolute number of faucet tokens the faucet is holding @@ -49,6 +68,98 @@ const valoraTestFaucetSecret = process.env['TEST_FAUCET_SECRET']! }) const targetFaucetTokenBalance = totalTokenHoldings / REFILL_TOKENS.length + async function swapSell( + sellToken: Token, + buyToken: Token, + sellAmount: number, // in decimal + maxSlippagePercent: number + ) { + try { + const sellAmountInSmallestUnit = utils.parseUnits(sellAmount.toString(), sellToken.decimals) + const quoteAmountOut = await mento.getAmountOut( + sellToken.address, + buyToken.address, + sellAmountInSmallestUnit + ) + console.log( + `Selling ${sellAmount} ${sellToken.symbol} for ~${utils.formatUnits( + quoteAmountOut, + buyToken.decimals + )} ${buyToken.symbol} with max slippage of ${maxSlippagePercent}%.` + ) + const allowanceTxObj = await mento.increaseTradingAllowance( + sellToken.address, + sellAmountInSmallestUnit + ) + const allowanceTx = await signer.sendTransaction(allowanceTxObj) + const allowanceReceipt = await allowanceTx.wait() + console.log( + `Received allowance tx hash ${allowanceReceipt.transactionHash} with status ${allowanceReceipt.status}` + ) + // allow maxSlippagePercent from quote + const amountOutMin = quoteAmountOut.mul(100 - maxSlippagePercent).div(100) + const swapTxObj = await mento.swapIn( + sellToken.address, + buyToken.address, + sellAmountInSmallestUnit, + amountOutMin + ) + const swapTx = await signer.sendTransaction(swapTxObj) + const swapTxReceipt = await swapTx.wait() + console.log( + `Received swap tx hash ${swapTxReceipt.transactionHash} with status ${swapTxReceipt.status}` + ) + if (swapTxReceipt.status !== 1) { + throw new Error(`Swap reverted. Tx hash: ${swapTxReceipt.transactionHash}`) + } + } catch (err) { + console.log(`Failed to sell ${sellToken.symbol} for ${buyToken.symbol}`, err) + } + } + + async function swapBuy( + sellToken: Token, + buyToken: Token, + buyAmount: number, // in decimal + maxSlippagePercent: number + ) { + try { + const buyAmountInSmallestUnit = utils.parseUnits(buyAmount.toString(), buyToken.decimals) + const quoteAmountIn = await mento.getAmountIn( + sellToken.address, + buyToken.address, + buyAmountInSmallestUnit + ) + console.log( + `Buying ${buyAmount} ${buyToken.symbol} with ~${utils.formatUnits(quoteAmountIn, sellToken.decimals)} ${sellToken.symbol} with max slippage of ${maxSlippagePercent}%.` + ) + // allow maxSlippagePercent from quote + const amountInMax = quoteAmountIn.mul(100 + maxSlippagePercent).div(100) + const allowanceTxObj = await mento.increaseTradingAllowance(sellToken.address, amountInMax) + const allowanceTx = await signer.sendTransaction(allowanceTxObj) + const allowanceReceipt = await allowanceTx.wait() + console.log( + `Received allowance tx hash ${allowanceReceipt.transactionHash} with status ${allowanceReceipt.status}` + ) + const swapTxObj = await mento.swapOut( + sellToken.address, + buyToken.address, + buyAmountInSmallestUnit, + amountInMax + ) + const swapTx = await signer.sendTransaction(swapTxObj) + const swapTxReceipt = await swapTx.wait() + console.log( + `Received swap tx hash ${swapTxReceipt.transactionHash} with status ${swapTxReceipt.status}` + ) + if (swapTxReceipt.status !== 1) { + throw new Error(`Swap reverted. Tx hash: ${swapTxReceipt.transactionHash}`) + } + } catch (err) { + console.log(`Failed to buy ${buyToken.symbol} with ${sellToken.symbol}`, err) + } + } + // Ensure that the faucet has enough balance for each refill tokens for (const [tokenSymbol, tokenBalance] of Object.entries(faucetTokenBalances)) { if (!REFILL_TOKENS.includes(tokenSymbol)) { @@ -56,153 +167,64 @@ const valoraTestFaucetSecret = process.env['TEST_FAUCET_SECRET']! } if (tokenBalance >= targetFaucetTokenBalance) { - const sellAmount = tokenBalance - targetFaucetTokenBalance - const amountToExchange = kit.web3.utils.toWei(`${sellAmount}`, 'ether') console.log( - `${tokenSymbol} balance is ${tokenBalance}, which is higher than target ${targetFaucetTokenBalance}. Selling ${sellAmount}.` + `${tokenSymbol} balance is ${tokenBalance}, which is higher than target ${targetFaucetTokenBalance}.` + ) + const sellAmount = tokenBalance - targetFaucetTokenBalance + await swapSell( + TOKENS_BY_SYMBOL[tokenSymbol], + tokenSymbol === 'CELO' ? CUSD : CELO, + sellAmount, + 1 ) - switch (tokenSymbol) { - case 'CELO': - try { - const celoApproveTx = await celoToken - .approve(celoExchange.address, amountToExchange) - .send({ from: E2E_TEST_FAUCET }) - await celoApproveTx.waitReceipt() - const celoSellAmount = await celoExchange.quoteGoldSell(amountToExchange) - const celoSellTx = await celoExchange - .sellGold(amountToExchange, celoSellAmount) - .send({ from: E2E_TEST_FAUCET }) - await celoSellTx.waitReceipt() - } catch (err) { - console.log('Failed to sell CELO', err) - } finally { - break - } - case 'cUSD': - try { - const cusdApproveTx = await cusdToken - .approve(cusdExchange.address, amountToExchange) - .send({ from: E2E_TEST_FAUCET }) - await cusdApproveTx.waitReceipt() - const cusdSellAmount = await cusdExchange.quoteStableSell(amountToExchange) - const cusdSellTx = await cusdExchange - .sellStable(amountToExchange, cusdSellAmount) - .send({ from: E2E_TEST_FAUCET }) - await cusdSellTx.waitReceipt() - } catch (err) { - console.log('Failed to sell cUSD', err) - } finally { - break - } - case 'cEUR': - try { - const ceurApproveTx = await ceurToken - .approve(ceurExchange.address, amountToExchange) - .send({ from: E2E_TEST_FAUCET }) - await ceurApproveTx.waitReceipt() - const ceurSellAmount = await ceurExchange.quoteStableSell(amountToExchange) - const ceurSellTx = await ceurExchange - .sellStable(amountToExchange, ceurSellAmount) - .send({ from: E2E_TEST_FAUCET }) - await ceurSellTx.waitReceipt() - } catch (err) { - console.log('Failed to sell cEUR', err) - } finally { - break - } - } } else { - const buyAmount = targetFaucetTokenBalance - tokenBalance - const amountToExchange = kit.web3.utils.toWei(`${buyAmount}`, 'ether') console.log( - `${tokenSymbol} balance is ${tokenBalance}, which is lower than target ${targetFaucetTokenBalance}. Buying ${buyAmount}.` + `${tokenSymbol} balance is ${tokenBalance}, which is lower than target ${targetFaucetTokenBalance}.` ) - switch (tokenSymbol) { - case 'CELO': - try { - const celoApproveTx = await celoToken - .approve(cusdExchange.address, amountToExchange) - .send({ from: E2E_TEST_FAUCET }) - await celoApproveTx.waitReceipt() - const celoBuyAmount = await celoExchange.quoteGoldBuy(amountToExchange) - const celoBuyTx = await celoExchange - .buyGold(amountToExchange, celoBuyAmount) - .send({ from: E2E_TEST_FAUCET }) - await celoBuyTx.waitReceipt() - } catch (err) { - console.log('Failed to buy CELO', err) - } finally { - break - } - case 'cUSD': - try { - const cusdApproveTx = await celoToken - .approve(cusdExchange.address, amountToExchange) - .send({ from: E2E_TEST_FAUCET }) - await cusdApproveTx.waitReceipt() - const cusdBuyAmount = await cusdExchange.quoteStableBuy(amountToExchange) - const cusdBuyTx = await cusdExchange - .buyStable(amountToExchange, cusdBuyAmount) - .send({ from: E2E_TEST_FAUCET }) - await cusdBuyTx.waitReceipt() - } catch (err) { - console.log('Failed to buy cUSD', err) - } finally { - break - } - case 'cEUR': - try { - const ceurApproveTx = await celoToken - .approve(ceurExchange.address, amountToExchange) - .send({ from: E2E_TEST_FAUCET }) - await ceurApproveTx.waitReceipt() - const ceurBuyAmount = await ceurExchange.quoteStableBuy(amountToExchange) - const ceurBuyTx = await ceurExchange - .buyStable(amountToExchange, ceurBuyAmount) - .send({ from: E2E_TEST_FAUCET }) - await ceurBuyTx.waitReceipt() - } catch (err) { - console.log('Failed to buy cEUR', err) - } finally { - break - } - } + const buyAmount = targetFaucetTokenBalance - tokenBalance + await swapBuy( + tokenSymbol === 'CELO' ? CUSD : CELO, + TOKENS_BY_SYMBOL[tokenSymbol], + buyAmount, + 1 + ) + } + } + + async function transferToken( + token: Token, + amount: string, // in decimal + to: string + ): Promise { + const abi = ['function transfer(address to, uint256 value) returns (bool)'] + const contract = new Contract(token.address, abi, signer) + + const amountInSmallestUnit = utils.parseUnits(amount, token.decimals) + const txObj = await contract.populateTransaction.transfer(to, amountInSmallestUnit) + const tx = await signer.sendTransaction(txObj) + const receipt = await tx.wait() + console.log( + `Received transfer tx hash ${receipt.transactionHash} with status ${receipt.status}` + ) + + if (receipt.status !== 1) { + throw new Error(`Transfer reverted. Tx hash: ${receipt.transactionHash}`) } + + return receipt } // Set Amount To Send const amountToSend = '100' - const amountToSendWei = web3.utils.toWei(amountToSend, 'ether') for (let i = 0; i < walletsToBeFunded.length; i++) { const walletAddress = walletsToBeFunded[i] const walletBalance = walletBalances[i] for (const tokenSymbol of REFILL_TOKENS) { + // @ts-ignore if (walletBalance && walletBalance[tokenSymbol] < 200) { console.log(`Sending ${amountToSend} ${tokenSymbol} to ${walletAddress}`) - let tx: any - switch (tokenSymbol) { - case 'CELO': - tx = await celoToken - .transfer(walletAddress, amountToSendWei) - .send({ from: E2E_TEST_FAUCET }) - break - case 'cUSD': - tx = await cusdToken - .transfer(walletAddress, amountToSendWei) - .send({ from: E2E_TEST_FAUCET }) - break - case 'cEUR': - tx = await ceurToken - .transfer(walletAddress, amountToSendWei) - .send({ from: E2E_TEST_FAUCET }) - break - } - const receipt = await tx.waitReceipt() - - console.log( - `Received tx hash ${receipt.transactionHash} for ${tokenSymbol} transfer to ${walletAddress}` - ) + await transferToken(TOKENS_BY_SYMBOL[tokenSymbol], amountToSend, walletAddress) } } } @@ -213,7 +235,7 @@ const valoraTestFaucetSecret = process.env['TEST_FAUCET_SECRET']! console.table(await getBalance(E2E_TEST_WALLET)) console.log('E2E Test Account Secure Send:', E2E_TEST_WALLET_SECURE_SEND) console.table(await getBalance(E2E_TEST_WALLET_SECURE_SEND)) - console.log('Valora Test Facuet:', E2E_TEST_FAUCET) + console.log('Valora Test Faucet:', E2E_TEST_FAUCET) console.table(await getBalance(E2E_TEST_FAUCET)) await checkBalance(E2E_TEST_WALLET) diff --git a/package.json b/package.json index 8a37cc17f8d..0e44fe502f9 100644 --- a/package.json +++ b/package.json @@ -216,6 +216,7 @@ "@babel/runtime": "^7.20.0", "@faker-js/faker": "^5.5.3", "@jambit/eslint-plugin-typed-redux-saga": "^0.4.0", + "@mento-protocol/mento-sdk": "^0.2.3", "@testing-library/jest-native": "^5.4.3", "@testing-library/react-native": "^12.4.3", "@types/crypto-js": "^4.1.1", @@ -257,6 +258,7 @@ "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-native": "^4.0.0", + "ethers": "^5.7.2", "husky": "^3.0.0", "jest": "^29.6.2", "jest-circus": "^29.6.2", diff --git a/yarn.lock b/yarn.lock index da7c6a249d4..0ac2105dc78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2331,6 +2331,18 @@ "@json-rpc-tools/types" "^1.7.6" "@pedrouid/environment" "^1.0.1" +"@mento-protocol/mento-core-ts@^0.2.0": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@mento-protocol/mento-core-ts/-/mento-core-ts-0.2.1.tgz#d8f1f1aaf8e11645541badf9e308410996f82a24" + integrity sha512-p99LMVVZ7VEHgirdjgUjDH1HA9810c8z+i0EzcgTPBK8TjkGY47aV2DDWknjETcjIYfXek7V/GYhwdjeKoqEbw== + +"@mento-protocol/mento-sdk@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@mento-protocol/mento-sdk/-/mento-sdk-0.2.3.tgz#9afb791b3f39cce8bb50f6e56d58939977923a70" + integrity sha512-Rf/RBAW5+gekHWoAeNPuN1G3JbiDNEfN5QvInDvpy5JQKhqXC0ZuH7N5ja/D7ziEnY912rBGyylE1QP/H1toCA== + dependencies: + "@mento-protocol/mento-core-ts" "^0.2.0" + "@noble/curves@1.1.0", "@noble/curves@~1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.1.0.tgz#f13fc667c89184bc04cccb9b11e8e7bae27d8c3d" @@ -8778,7 +8790,7 @@ ethereumjs-util@^7.1.5: ethereum-cryptography "^0.1.3" rlp "^2.2.4" -ethers@^5.0.13, ethers@^5.5.4: +ethers@^5.0.13, ethers@^5.5.4, ethers@^5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==