Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: re-enable secure send e2e test #4507

Merged
merged 12 commits into from
Nov 21, 2023
6 changes: 5 additions & 1 deletion e2e/scripts/check-e2e-wallet-balance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { E2E_TEST_WALLET } from './consts'
import { E2E_TEST_WALLET, E2E_TEST_WALLET_SECURE_SEND } from './consts'
import { checkBalance, getBalance } from './utils'
;(async () => {
console.log(E2E_TEST_WALLET)
console.table(await getBalance(E2E_TEST_WALLET))
await checkBalance(E2E_TEST_WALLET)

console.log(E2E_TEST_WALLET_SECURE_SEND)
console.table(await getBalance(E2E_TEST_WALLET_SECURE_SEND))
await checkBalance(E2E_TEST_WALLET_SECURE_SEND)
})()
2 changes: 2 additions & 0 deletions e2e/scripts/consts.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export const E2E_TEST_WALLET = '0x6131a6d616a4be3737b38988847270a64bc10caa'
export const E2E_TEST_WALLET_SECURE_SEND = '0x86b8f44386cb2d457db79c3dab8cf42f9d8a3fc0'
export const E2E_TEST_FAUCET = '0xe5F5363e31351C38ac82DBAdeaD91Fd5a7B08846'
export const REFILL_TOKENS = ['CELO', 'cUSD', 'cEUR']
131 changes: 78 additions & 53 deletions e2e/scripts/fund-e2e-accounts.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { newKitFromWeb3, StableToken } from '@celo/contractkit'
import dotenv from 'dotenv'
import Web3 from 'web3'
import { E2E_TEST_FAUCET, E2E_TEST_WALLET } from './consts'
import {
E2E_TEST_FAUCET,
E2E_TEST_WALLET,
E2E_TEST_WALLET_SECURE_SEND,
REFILL_TOKENS,
} from './consts'
import { checkBalance, getBalance } from './utils'

dotenv.config({ path: `${__dirname}/../.env` })
Expand All @@ -11,10 +16,16 @@ const kit = newKitFromWeb3(web3)
const valoraTestFaucetSecret = process.env['TEST_FAUCET_SECRET']!

;(async () => {
// Get E2E Test Wallet Balance & Valora Faucet Balance
const receivingBalance = await getBalance(E2E_TEST_WALLET)
const sendingBalance = (await getBalance(E2E_TEST_FAUCET)) ?? {}
console.table(await getBalance(E2E_TEST_FAUCET))
const walletsToBeFunded = [E2E_TEST_WALLET, E2E_TEST_WALLET_SECURE_SEND]
const walletBalances = await Promise.all(walletsToBeFunded.map(getBalance))
for (let i = 0; i < walletsToBeFunded.length; i++) {
console.log(`Initial balance for ${walletsToBeFunded[i]}:`)
console.table(walletBalances[i])
}

const faucetTokenBalances = (await getBalance(E2E_TEST_FAUCET)) ?? {}
console.log('Initial faucet balance:')
console.table(faucetTokenBalances)

// Connect Valora E2E Test Faucet - Private Key Stored in GitHub Secrets
kit.connection.addAccount(
Expand All @@ -30,24 +41,27 @@ const valoraTestFaucetSecret = process.env['TEST_FAUCET_SECRET']!
const ceurExchange = await kit.contracts.getExchange(StableToken.cEUR)

// Balance Faucet
let targetTokenRegex = /[cusd|celo|ceur]/gi
let balances = await getBalance(E2E_TEST_FAUCET)
let sum = 0
const numOfTokens = 3 // Only cusd, celo and ceur
for (let balance in balances) {
console.log(`${balance}: ${balances[balance]}`)
// Sum only the target balances
if (targetTokenRegex.test(balance)) sum += balances[balance]
}
let totalTokenHoldings = 0 // the absolute number of faucet tokens the faucet is holding
Object.entries(faucetTokenBalances).forEach(([tokenSymbol, tokenBalance]) => {
if (REFILL_TOKENS.includes(tokenSymbol)) {
totalTokenHoldings += tokenBalance
}
})
const targetFaucetTokenBalance = totalTokenHoldings / REFILL_TOKENS.length

for (let balance in balances) {
const target = sum / numOfTokens
if (balances[balance] >= sum / numOfTokens) {
const toSell = balances[balance] - target
console.log(toSell)
const amountToExchange = kit.web3.utils.toWei(`${toSell}`, 'ether')
console.log(`${balance} balance higher than ${sum / numOfTokens}`)
switch (balance) {
// Ensure that the faucet has enough balance for each refill tokens
for (const [tokenSymbol, tokenBalance] of Object.entries(faucetTokenBalances)) {
if (!REFILL_TOKENS.includes(tokenSymbol)) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not strictly necessary, but without this we'd proceed for tokens like cREAL and TT only to hit the default case in the switch statement (no transaction created)

continue
}

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}.`
)
switch (tokenSymbol) {
case 'CELO':
try {
const celoApproveTx = await celoToken
Expand Down Expand Up @@ -98,10 +112,12 @@ const valoraTestFaucetSecret = process.env['TEST_FAUCET_SECRET']!
}
}
} else {
const toBuy = target - balances[balance]
const amountToExchange = kit.web3.utils.toWei(`${toBuy}`, 'ether')
console.log(`${balance} balance lower than ${sum / numOfTokens}`)
switch (balance) {
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}.`
)
switch (tokenSymbol) {
case 'CELO':
try {
const celoApproveTx = await celoToken
Expand Down Expand Up @@ -155,42 +171,51 @@ const valoraTestFaucetSecret = process.env['TEST_FAUCET_SECRET']!
}

// Set Amount To Send
let amountToSend = web3.utils.toWei('100', 'ether')
const amountToSend = '100'
const amountToSendWei = web3.utils.toWei(amountToSend, 'ether')

// Loop through E2E Test Wallet Balance Object
for (const coin in receivingBalance) {
let tx: any
// Add funds if balance is less than 100 add 100
if (receivingBalance[coin] < 200 && sendingBalance[coin] > 100) {
switch (coin) {
case 'CELO':
tx = await celoToken
.transfer(E2E_TEST_WALLET, amountToSend)
.send({ from: E2E_TEST_FAUCET })
break
case 'cUSD':
tx = await cusdToken
.transfer(E2E_TEST_WALLET, amountToSend)
.send({ from: E2E_TEST_FAUCET })
break
case 'cEUR':
tx = await ceurToken
.transfer(E2E_TEST_WALLET, amountToSend)
.send({ from: E2E_TEST_FAUCET })
break
}
// Wait for the transactions to be processed
let receipt = await tx.waitReceipt()
for (let i = 0; i < walletsToBeFunded.length; i++) {
const walletAddress = walletsToBeFunded[i]
const walletBalance = walletBalances[i]
for (const tokenSymbol of REFILL_TOKENS) {
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()

// Print Receipt
console.log(' Transaction receipt: %o', receipt)
console.log(
`Received tx hash ${receipt.transactionHash} for ${tokenSymbol} transfer to ${walletAddress}`
)
}
}
}
console.log('Finished funding wallets.')

// Log Balances
console.log('E2E Test Account:', E2E_TEST_WALLET)
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.table(await getBalance(E2E_TEST_FAUCET))

await checkBalance(E2E_TEST_WALLET)
await checkBalance(E2E_TEST_WALLET_SECURE_SEND)
})()
14 changes: 10 additions & 4 deletions e2e/scripts/utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { REFILL_TOKENS } from './consts'

export const Web3 = require('web3')
export const ContractKit = require('@celo/contractkit')
export const dotenv = require('dotenv')
export const web3 = new Web3('https://alfajores-forno.celo-testnet.org')
export const kit = ContractKit.newKitFromWeb3(web3)

export async function checkBalance(address: string, minBalance = 10) {
let balanceObject = await getBalance(address)
for (const balance in balanceObject) {
if (balanceObject[balance as keyof typeof balanceObject] < minBalance) {
export async function checkBalance(
address: string,
minBalance = 10,
tokenSymbols: string[] = REFILL_TOKENS
) {
const balance = (await getBalance(address)) ?? {}
for (const [tokenSymbol, tokenBalance] of Object.entries(balance)) {
if (tokenSymbols.includes(tokenSymbol) && tokenBalance < minBalance) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use tokenSymbols to prevent throwing an error for tokens that we don't care about

throw new Error(
`${balance} balance of ${address} is below ${minBalance}. Please refill from the faucet https://celo.org/developers/faucet or run ./fund-e2e-accounts.ts if a Valora Dev.`
)
Expand Down
8 changes: 1 addition & 7 deletions e2e/src/Send.spec.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import SecureSend from './usecases/SecureSend'
import Send from './usecases/Send'
import { quickOnboarding } from './utils/utils'

describe('Given', () => {
beforeAll(async () => {
await quickOnboarding()
})

describe('Send', Send)
// TODO: unskip this test if we enable CPV in CI
describe.skip('SecureSend cUSD', SecureSend)
describe('SecureSend with CPV', SecureSend)
})
136 changes: 71 additions & 65 deletions e2e/src/usecases/SecureSend.js
Original file line number Diff line number Diff line change
@@ -1,86 +1,92 @@
import { reloadReactNative } from '../utils/retries'
import {
SAMPLE_BACKUP_KEY_VERIFIED,
VERIFIED_PHONE_NUMBER,
SAMPLE_WALLET_ADDRESS_VERIFIED_2,
} from '../utils/consts'
import { launchApp } from '../utils/retries'
import {
addComment,
enterPinUiIfNecessary,
inputNumberKeypad,
scrollIntoView,
sleep,
quickOnboarding,
} from '../utils/utils'
const faker = require('@faker-js/faker')

const PHONE_NUMBER = '+12057368924'
const LAST_ACCOUNT_CHARACTERS = 'FD08'
const AMOUNT_TO_SEND = '0.1'
const AMOUNT_TO_SEND = '0.01'

export default SecureSend = () => {
beforeEach(async () => {
await reloadReactNative()
})

it('Send cUSD to phone number with multiple mappings', async () => {
let randomContent = faker.lorem.words()
await waitFor(element(by.id('HomeAction-Send')))
.toBeVisible()
.withTimeout(30000)
await element(by.id('HomeAction-Send')).tap()

// Look for an address and tap on it.
await element(by.id('SearchInput')).tap()
await element(by.id('SearchInput')).replaceText(PHONE_NUMBER)
await element(by.id('RecipientItem')).tap()
describe.each([{ web3Library: 'contract-kit' }, { web3Library: 'viem' }])(
'Secure send flow with phone number lookup (with $web3Library)',
({ web3Library }) => {
beforeAll(async () => {
// uninstall the app to remove secure send mapping
await device.uninstallApp()
await device.installApp()
await launchApp({
newInstance: true,
permissions: { notifications: 'YES', contacts: 'YES' },
launchArgs: {
statsigGateOverrides: `use_new_send_flow=false,use_viem_for_send=${
web3Library === 'viem'
}`,
},
})
await quickOnboarding(SAMPLE_BACKUP_KEY_VERIFIED)
})

// Select the currency
await waitFor(element(by.id('cUSDBalance')))
.toBeVisible()
.withTimeout(30 * 1000)
await element(by.id('cUSDBalance')).tap()
it('Send cUSD to phone number with multiple mappings', async () => {
let randomContent = faker.lorem.words()
await waitFor(element(by.id('HomeAction-Send')))
.toBeVisible()
.withTimeout(30000)
await element(by.id('HomeAction-Send')).tap()
await waitFor(element(by.id('SendSearchInput'))).toBeVisible()

// Enter the amount and review
await inputNumberKeypad(AMOUNT_TO_SEND)
await element(by.id('Review')).tap()
await element(by.id('SearchInput')).tap()
await element(by.id('SearchInput')).replaceText(VERIFIED_PHONE_NUMBER)
await element(by.id('RecipientItem')).tap()

// hack: we shouldn't need this but the test fails without
await sleep(3000)
// Select the currency
await waitFor(element(by.id('cUSDTouchable'))).toBeVisible()
await element(by.id('cUSDTouchable')).tap()

// Click Edit if confirm account isn't served
try {
await element(by.id('accountEditButton')).tap()
} catch {}
// Enter the amount and review
await inputNumberKeypad(AMOUNT_TO_SEND)
await element(by.id('Review')).tap()

// Use the last digits of the account to confirm the sender.
await waitFor(element(by.id('confirmAccountButton')))
.toBeVisible()
.withTimeout(30000)
await element(by.id('confirmAccountButton')).tap()
for (let index = 0; index < 4; index++) {
const character = LAST_ACCOUNT_CHARACTERS[index]
await element(by.id(`SingleDigitInput/digit${index}`)).replaceText(character)
}

// Scroll to see submit button
await scrollIntoView('Submit', 'KeyboardAwareScrollView', 50)
await element(by.id('ConfirmAccountButton')).tap()
// Use the last digits of the account to confirm the sender.
await waitFor(element(by.id('confirmAccountButton'))).toBeVisible()
await element(by.id('confirmAccountButton')).tap()
// TODO: test case for AddressValidationType.PARTIAL but relies on mapping phone number to another address with unique last 4 digits
// for (let index = 0; index < 4; index++) {
// const character = LAST_ACCOUNT_CHARACTERS[index]
// await element(by.id(`SingleDigitInput/digit${index}`)).replaceText(character)
// }
await element(by.id('ValidateRecipientAccount/TextInput')).replaceText(
SAMPLE_WALLET_ADDRESS_VERIFIED_2
)
Comment on lines +66 to +68
Copy link
Member

@jeanregisser jeanregisser Nov 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why doesn't it need just the last 4 digits?
Oh, it's doing this lookup from the verified account itself.
Since we can't lookup numbers without being verified.

We'd need 2 other verified accounts, to another number to test that case.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if i understand correctly, the secure send screen either asks for the full address or the last 4 digits but this is depending on how "different" the possible addresses are. unfortunately we've mapped to 2 wallets and they both have a "0" in the last 4 digits of the address, so we've got the full text input.

idk if it is too important to test both the full text input and the 4 digits, i have 90% confidence that both flows are working if i know one of them is working. we can try to remap to another address, but this is Tom's burner number and he's out all week so would need to wait till he's back

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes the current test is fine for now.

For posterity, the check for uniqueness of the last 4 digits is done as a whole, but also includes the user's own address:

// Adding user's address so they don't mistakenly verify with last 4 digits of their own address
if (last4DigitsAreUnique([userAddress, ...possibleAddresses])) {
return AddressValidationType.PARTIAL
}

It's not the fact that there are chars in common between them that causes PARTIAL vs FULL.


// Write a comment.
await addComment(randomContent)
// Scroll to see submit button
await scrollIntoView('Submit', 'KeyboardAwareScrollView', 50)
await element(by.id('ConfirmAccountButton')).tap()

// Wait for the confirm button to be clickable. If it takes too long this test
// will be flaky :(
await sleep(3000)
// Write a comment.
await addComment(randomContent)

// Confirm and input PIN if necessary.
await element(by.id('ConfirmButton')).tap()
await enterPinUiIfNecessary()
// Confirm and input PIN if necessary.
await element(by.id('ConfirmButton')).tap()
await enterPinUiIfNecessary()

// Return to home screen.
await waitFor(element(by.id('HomeAction-Send')))
.toBeVisible()
.withTimeout(30 * 1000)
// Return to home screen.
await waitFor(element(by.id('HomeAction-Send')))
.toBeVisible()
.withTimeout(30 * 1000)

// TODO: See why these are taking so long in e2e tests to appear
// Look for the latest transaction and assert
// await waitFor(element(by.text(`${randomContent}`)))
// .toBeVisible()
// .withTimeout(60000)
})
await waitFor(element(by.text(`${randomContent}`)))
.toBeVisible()
.withTimeout(60000)
})
}
)
}
Loading