diff --git a/.circleci/config.yml b/.circleci/config.yml index 97a9a7acd55..d208c4db5b0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -583,23 +583,6 @@ jobs: cd packages/celotool ./ci_test_sync.sh checkout master - end-to-end-geth-integration-sync-test: - <<: *e2e-defaults - steps: - - attach_workspace: - at: ~/app - - run: - name: Check if the test should run - command: | - FILES_TO_CHECK="${PWD}/packages/celotool,${PWD}/packages/protocol,${PWD}/.circleci/config.yml" - ./scripts/ci_check_if_test_should_run_v2.sh ${FILES_TO_CHECK} - - run: - name: Run test - command: | - set -e - cd packages/celotool - ./ci_test_sync_with_network.sh checkout master - end-to-end-geth-attestations-test: <<: *e2e-defaults resource_class: medium+ diff --git a/SETUP.md b/SETUP.md index bf65ec99d44..13dbf50865d 100644 --- a/SETUP.md +++ b/SETUP.md @@ -14,6 +14,7 @@ - [Installing OpenJDK 8](#installing-openjdk-8) - [Install Android Dev Tools](#install-android-dev-tools-1) - [Some common stuff](#some-common-stuff) + - [Install Go](#install-go) - [Optional: Install Rust](#optional-install-rust) - [Optional: Install an Android Emulator](#optional-install-an-android-emulator) - [Optional: Genymotion](#optional-genymotion) @@ -176,6 +177,23 @@ You can find the complete instructions about how to install the tools in Linux e ### Some common stuff +#### Install Go + +We need Go for [celo-blockchain](https://github.com/celo-org/celo-blockchain), the Go Celo implementation, and `gobind` to build Java language bindings to Go code for the Android Geth client). + +Note: We currently use Go 1.11. Brew installs Go 1.12 by default, which is not entirely compatible with our repositories. [Install Go 1.11 manually](https://golang.org/dl/), then run + +``` +go get golang.org/x/mobile/cmd/gobind +``` + +Execute the following (and make sure the lines are in your `~/.bash_profile`): + +``` +export GOPATH=$HOME/go +export PATH=$PATH:$GOPATH/bin +``` + #### Optional: Install Rust We use Rust to build the [bls-zexe](https://github.com/celo-org/bls-zexe) repo, which Geth depends on. If you only use the monorepo, you probably don't need this. diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 080af223194..dbe3613ba80 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -2,7 +2,7 @@ # More details: https://github.com/GoogleContainerTools/kaniko steps: - + - id: "docker:celotool" name: gcr.io/kaniko-project/executor:latest args: [ @@ -37,7 +37,7 @@ steps: args: [ "--dockerfile=dockerfiles/attestation-service/Dockerfile", "--cache=true", - "--destination=gcr.io/$PROJECT_ID/celo-monorepo:attestation-service-$COMMIT_SHA" + "--destination=us.gcr.io/$PROJECT_ID/celo-monorepo:attestation-service-$COMMIT_SHA" ] waitFor: ['-'] diff --git a/packages/celotool/ci_test_sync_with_network.sh b/packages/celotool/ci_test_sync_with_network.sh deleted file mode 100755 index 99645d25d71..00000000000 --- a/packages/celotool/ci_test_sync_with_network.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# This test starts a local node which tries to sync with remotely running nodes and -# verifies that the sync works. - -# For testing a particular commit hash of Geth repo (usually, on Circle CI) -# Usage: ci_test_sync_with_network.sh checkout -# For testing the local Geth dir (usually, for manual testing) -# Usage: ci_test_sync_with_network.sh local - -if [ "${1}" == "checkout" ]; then - export GETH_DIR="/tmp/geth" - # Test master by default. - COMMIT_HASH_TO_TEST=${2:-"master"} - echo "Checking out geth at commit hash ${COMMIT_HASH_TO_TEST}..." - # Shallow clone up to depth of 20. If the COMMIT_HASH_TO_TEST is not within the last 20 hashes then - # this test will fail. This will force someone to keep updating the COMMIT_HASH_TO_TEST we are - # testing. Clone up to 20 takes about 4 seconds on my machine and a full clone is - # about 60 seconds as of May 20, 2019. The difference will only grow over time. - git clone --depth 20 https://github.com/celo-org/celo-blockchain.git ${GETH_DIR} && cd ${GETH_DIR} && git checkout ${COMMIT_HASH_TO_TEST} && cd - -elif [ "${1}" == "local" ]; then - export GETH_DIR="${2}" - echo "Testing using local geth dir ${GETH_DIR}..." -fi - -# For now, the script assumes that it runs from a sub-dir of sub-dir of monorepo directory. -CELO_MONOREPO_DIR="${PWD}/../.." -# Assume that the logs are in /tmp/geth_stdout -GETH_LOG_FILE=/tmp/geth_stdout - -# usage: test_ultralight_sync -test_ultralight_sync () { - NETWORK_NAME=$1 - echo "Testing ultralight sync with '${NETWORK_NAME}' network" - # Run the sync in ultralight mode - geth_tests/network_sync_test.sh ${NETWORK_NAME} ultralight - # Verify what happened by reading the logs. - ${CELO_MONOREPO_DIR}/node_modules/.bin/mocha -r ts-node/register ${CELO_MONOREPO_DIR}/packages/celotool/src/e2e-tests/verify_ultralight_geth_logs.ts --network "${NETWORK_NAME}" --gethlogfile ${GETH_LOG_FILE} -} - -# Some code in celotool requires this file to contain the MNEMONOIC. -# The value of MNEMONOIC does not matter. -if [[ ! -e ${CELO_MONOREPO_DIR}/.env.mnemonic ]]; then - echo "MNEMONOIC=anything random" > ${CELO_MONOREPO_DIR}/.env.mnemonic -fi - -# Test syncing -export NETWORK_NAME="integration" -# Add an extra echo at the end to dump a new line, this makes the results a bit more readable. -geth_tests/network_sync_test.sh ${NETWORK_NAME} full && echo -# This is broken, I am not sure why, therefore, commented for now. -# geth_tests/network_sync_test.sh ${NETWORK_NAME} fast && echo -geth_tests/network_sync_test.sh ${NETWORK_NAME} light && echo -test_ultralight_sync ${NETWORK_NAME} && echo - -export NETWORK_NAME="alfajoresstaging" -geth_tests/network_sync_test.sh ${NETWORK_NAME} full && echo -# This is broken, I am not sure why, therefore, commented for now. -# geth_tests/network_sync_test.sh ${NETWORK_NAME} fast && echo -geth_tests/network_sync_test.sh ${NETWORK_NAME} light && echo -test_ultralight_sync ${NETWORK_NAME} && echo diff --git a/packages/celotool/geth_tests/network_sync_test.sh b/packages/celotool/geth_tests/network_sync_test.sh deleted file mode 100755 index 2997b623f55..00000000000 --- a/packages/celotool/geth_tests/network_sync_test.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Usage: geth_tests/integration_network_sync_test.sh [network name] [sync mode] -# Default to testing the integration network -NETWORK_NAME=${1:-"integration"} -# Default to testing the full sync mode -SYNCMODE=${2:-"full"} - -echo "This test will start a local node in '${SYNCMODE}' sync mode which will connect to network '${NETWORK_NAME}' and verify that syncing works" - -echo "Setting constants..." -# For now, the script assumes that it runs from a sub-dir of sub-dir of monorepo directory. -CELO_MONOREPO_DIR="${PWD}/../.." - -DATA_DIR="/tmp/tmp1" -GENESIS_FILE_PATH="/tmp/genesis_ibft.json" - -GETH_BINARY="${GETH_DIR}/build/bin/geth --datadir ${DATA_DIR}" -CELOTOOLJS="${CELO_MONOREPO_DIR}/packages/celotool/bin/celotooljs.sh" - -curl "https://www.googleapis.com/storage/v1/b/genesis_blocks/o/${NETWORK_NAME}?alt=media" --output ${GENESIS_FILE_PATH} - -${CELOTOOLJS} geth build --geth-dir ${GETH_DIR} - -rm -rf ${DATA_DIR} -${GETH_BINARY} init ${GENESIS_FILE_PATH} 1>/dev/null 2>/dev/null -curl "https://www.googleapis.com/storage/v1/b/static_nodes/o/${NETWORK_NAME}?alt=media" --output ${DATA_DIR}/static-nodes.json - -echo "Running geth in the background..." -LOG_FILE="/tmp/geth_stdout" -# Run geth in the background -${CELOTOOLJS} geth run \ - --geth-dir ${GETH_DIR} \ - --data-dir ${DATA_DIR} \ - --sync-mode ${SYNCMODE} 1>${LOG_FILE} 2>/tmp/geth_stderr & -# let it sync -sleep 20 -latestBlock=$(${GETH_BINARY} attach -exec eth.blockNumber) -echo "Latest block number is ${latestBlock}" - -pkill -9 geth - -if [ "$latestBlock" -eq "0" ]; then - echo "Sync is not working with network '${NETWORK_NAME}' in mode '${SYNCMODE}', see logs in ${LOG_FILE}" - if test ${CI}; then - echo "Running on CI, dumping logs from ${LOG_FILE}..." - cat ${LOG_FILE} - fi - exit 1 -fi diff --git a/packages/celotool/src/cmds/account/faucet.ts b/packages/celotool/src/cmds/account/faucet.ts index 168eb3b7892..bea3ad7f493 100644 --- a/packages/celotool/src/cmds/account/faucet.ts +++ b/packages/celotool/src/cmds/account/faucet.ts @@ -1,5 +1,6 @@ /* tslint:disable no-console */ import { newKit } from '@celo/contractkit' +import { switchToClusterFromEnv } from 'src/lib/cluster' import { convertToContractDecimals } from 'src/lib/contract-utils' import { portForwardAnd } from 'src/lib/port_forward' import { validateAccountAddress } from 'src/lib/utils' @@ -29,6 +30,8 @@ export const builder = (argv: yargs.Argv) => { } export const handler = async (argv: FaucetArgv) => { + await switchToClusterFromEnv() + const address = argv.account const cb = async () => { diff --git a/packages/celotool/src/e2e-tests/governance_tests.ts b/packages/celotool/src/e2e-tests/governance_tests.ts index 785c7809188..50ef0d040f8 100644 --- a/packages/celotool/src/e2e-tests/governance_tests.ts +++ b/packages/celotool/src/e2e-tests/governance_tests.ts @@ -132,6 +132,23 @@ describe('governance tests', () => { return blockNumber % epochSize === 0 } + const assertBalanceChanged = async ( + address: string, + blockNumber: number, + expected: BigNumber, + token: any + ) => { + const currentBalance = new BigNumber( + await token.methods.balanceOf(address).call({}, blockNumber) + ) + const previousBalance = new BigNumber( + await token.methods.balanceOf(address).call({}, blockNumber - 1) + ) + assert.isNotNaN(currentBalance) + assert.isNotNaN(previousBalance) + assertAlmostEqual(currentBalance.minus(previousBalance), expected) + } + describe('when the validator set is changing', () => { let epoch: number const blockNumbers: number[] = [] @@ -302,24 +319,8 @@ describe('governance tests', () => { ) const [group] = await validators.methods.getRegisteredValidatorGroups().call() - const assertBalanceChanged = async ( - validator: string, - blockNumber: number, - expected: BigNumber - ) => { - const currentBalance = new BigNumber( - await stableToken.methods.balanceOf(validator).call({}, blockNumber) - ) - const previousBalance = new BigNumber( - await stableToken.methods.balanceOf(validator).call({}, blockNumber - 1) - ) - assert.isNotNaN(currentBalance) - assert.isNotNaN(previousBalance) - assertAlmostEqual(currentBalance.minus(previousBalance), expected) - } - const assertBalanceUnchanged = async (validator: string, blockNumber: number) => { - await assertBalanceChanged(validator, blockNumber, new BigNumber(0)) + await assertBalanceChanged(validator, blockNumber, new BigNumber(0), stableToken) } const getExpectedTotalPayment = async (validator: string, blockNumber: number) => { @@ -359,17 +360,19 @@ describe('governance tests', () => { await assertBalanceChanged( validator, blockNumber, - expectedTotalPayment.minus(groupPayment) + expectedTotalPayment.minus(groupPayment), + stableToken ) expectedGroupPayment = expectedGroupPayment.plus(groupPayment) } - await assertBalanceChanged(group, blockNumber, expectedGroupPayment) + await assertBalanceChanged(group, blockNumber, expectedGroupPayment, stableToken) } }) it('should distribute epoch rewards at the end of each epoch', async () => { const lockedGold = await kit._web3Contracts.getLockedGold() const governance = await kit._web3Contracts.getGovernance() + const gasPriceMinimum = await kit._web3Contracts.getGasPriceMinimum() const [group] = await validators.methods.getRegisteredValidatorGroups().call() const assertVotesChanged = async (blockNumber: number, expected: BigNumber) => { @@ -382,6 +385,13 @@ describe('governance tests', () => { assertAlmostEqual(currentVotes.minus(previousVotes), expected) } + // Returns the gas fee base for a given block, which is distributed to the governance contract. + const blockBaseGasFee = async (blockNumber: number): Promise => { + const gas = (await web3.eth.getBlock(blockNumber)).gasUsed + const gpm = await gasPriceMinimum.methods.gasPriceMinimum().call({}, blockNumber) + return new BigNumber(gpm).times(new BigNumber(gas)) + } + const assertGoldTokenTotalSupplyChanged = async ( blockNumber: number, expected: BigNumber @@ -395,26 +405,12 @@ describe('governance tests', () => { assertAlmostEqual(currentSupply.minus(previousSupply), expected) } - const assertBalanceChanged = async ( - address: string, - blockNumber: number, - expected: BigNumber - ) => { - const currentBalance = new BigNumber( - await goldToken.methods.balanceOf(address).call({}, blockNumber) - ) - const previousBalance = new BigNumber( - await goldToken.methods.balanceOf(address).call({}, blockNumber - 1) - ) - assertAlmostEqual(currentBalance.minus(previousBalance), expected) - } - const assertLockedGoldBalanceChanged = async (blockNumber: number, expected: BigNumber) => { - await assertBalanceChanged(lockedGold.options.address, blockNumber, expected) + await assertBalanceChanged(lockedGold.options.address, blockNumber, expected, goldToken) } const assertGovernanceBalanceChanged = async (blockNumber: number, expected: BigNumber) => { - await assertBalanceChanged(governance.options.address, blockNumber, expected) + await assertBalanceChanged(governance.options.address, blockNumber, expected, goldToken) } const assertVotesUnchanged = async (blockNumber: number) => { @@ -429,10 +425,6 @@ describe('governance tests', () => { await assertLockedGoldBalanceChanged(blockNumber, new BigNumber(0)) } - const assertGovernanceBalanceUnchanged = async (blockNumber: number) => { - await assertGovernanceBalanceChanged(blockNumber, new BigNumber(0)) - } - const getStableTokenSupplyChange = async (blockNumber: number) => { const currentSupply = new BigNumber( await stableToken.methods.totalSupply().call({}, blockNumber) @@ -477,13 +469,16 @@ describe('governance tests', () => { .plus(stableTokenSupplyChange.div(exchangeRate)) await assertVotesChanged(blockNumber, expectedEpochReward) await assertLockedGoldBalanceChanged(blockNumber, expectedEpochReward) - await assertGovernanceBalanceChanged(blockNumber, expectedInfraReward) + await assertGovernanceBalanceChanged( + blockNumber, + expectedInfraReward.plus(await blockBaseGasFee(blockNumber)) + ) await assertGoldTokenTotalSupplyChanged(blockNumber, expectedGoldTotalSupplyChange) } else { await assertVotesUnchanged(blockNumber) await assertGoldTokenTotalSupplyUnchanged(blockNumber) await assertLockedGoldBalanceUnchanged(blockNumber) - await assertGovernanceBalanceUnchanged(blockNumber) + await assertGovernanceBalanceChanged(blockNumber, await blockBaseGasFee(blockNumber)) } } }) diff --git a/packages/celotool/src/e2e-tests/sync_tests.ts b/packages/celotool/src/e2e-tests/sync_tests.ts index 2d99ad4944b..965bd1a45d8 100644 --- a/packages/celotool/src/e2e-tests/sync_tests.ts +++ b/packages/celotool/src/e2e-tests/sync_tests.ts @@ -93,7 +93,7 @@ describe('sync tests', function(this: any) { const instance: GethInstanceConfig = gethConfig.instances[0] await killInstance(instance) await initAndStartGeth(hooks.gethBinaryPath, instance) - await sleep(60) // wait for round change / resync + await sleep(120) // wait for round change / resync const address = (await web3.eth.getAccounts())[0] const currentBlock = await web3.eth.getBlock('latest') for (let i = 0; i < gethConfig.instances.length; i++) { diff --git a/packages/celotool/src/e2e-tests/transfer_tests.ts b/packages/celotool/src/e2e-tests/transfer_tests.ts index eab473053ae..48567feaf30 100644 --- a/packages/celotool/src/e2e-tests/transfer_tests.ts +++ b/packages/celotool/src/e2e-tests/transfer_tests.ts @@ -18,35 +18,36 @@ import { sleep, } from './utils' -const nowSeconds = () => Math.floor(Date.now() / 1000) - /** * Helper Class to change StableToken Inflation in tests */ class InflationManager { private kit: ContractKit + private readonly minUpdateDelay = 10 + constructor(readonly validatorUri: string, readonly validatorAddress: string) { this.kit = newKit(validatorUri) this.kit.defaultAccount = validatorAddress } + now = async (): Promise => { + return (await this.kit.web3.eth.getBlock('pending')).timestamp + } + getNextUpdateRate = async (): Promise => { const stableToken = await this.kit.contracts.getStableToken() // Compute necessary `updateRate` so inflationFactor adjusment takes place on next operation const { factorLastUpdated } = await stableToken.getInflationParameters() - const timeSinceLastUpdated = nowSeconds() - factorLastUpdated.toNumber() - if (timeSinceLastUpdated < 10) { - // tslint:disable-next-line: no-console - console.log( - `Last inflation change too close, waiting ${10 - - timeSinceLastUpdated} seconds before doing it again` - ) - await sleep(10 - timeSinceLastUpdated) - return this.getNextUpdateRate() - } else { - return timeSinceLastUpdated + // Wait until until the minimum update delay has passed so we can set a rate that gives us some + // buffer time to make the transaction in the next availiable update window. + let timeSinceLastUpdated = (await this.now()) - factorLastUpdated.toNumber() + while (timeSinceLastUpdated < this.minUpdateDelay) { + await sleep(this.minUpdateDelay - timeSinceLastUpdated) + timeSinceLastUpdated = (await this.now()) - factorLastUpdated.toNumber() } + + return timeSinceLastUpdated } getParameters = async () => { @@ -54,16 +55,12 @@ class InflationManager { return stableToken.getInflationParameters() } - changeInflationFactorOnNextTransfer = async (desiredFactor: BigNumber) => { - const parameters = await this.getParameters() - if (desiredFactor.eq(parameters.factor)) { - return - } + setInflationRateForNextTransfer = async (rate: BigNumber) => { + // Possibly update the inflation factor and ensure it won't update again. + await this.setInflationParameters(new BigNumber(1), Number.MAX_SAFE_INTEGER) - // desiredFactor = factor * rate - const nextRate = desiredFactor.div(parameters.factor) const updateRate = await this.getNextUpdateRate() - await this.setInflationParameters(nextRate, updateRate) + await this.setInflationParameters(rate, updateRate) } setInflationParameters = async (rate: BigNumber, updatePeriod: number) => { @@ -72,19 +69,6 @@ class InflationManager { .setInflationParameters(toFixed(rate).toString(), updatePeriod) .sendAndWaitForReceipt({ from: this.validatorAddress }) } - - resetInflation = async () => { - await this.changeInflationFactorOnNextTransfer(new BigNumber('1')) - - const ONE = new BigNumber('1') - const ONE_WEEK = 7 * 24 * 60 * 60 - - // Reset factor, and change updatePeriod so no new inflation is added - await this.setInflationParameters(ONE, ONE_WEEK) - - const parametersPost = await this.getParameters() - assertEqualBN(parametersPost.factor, ONE) - } } const setIntrinsicGas = async (validatorUri: string, validatorAddress: string, gasCost: number) => { @@ -149,12 +133,6 @@ async function newBalanceWatcher(kit: ContractKit, accounts: string[]): Promise< } } -interface Fees { - total: BigNumber - proposer: BigNumber - recipient: BigNumber -} - function assertEqualBN(value: BigNumber, expected: BigNumber) { assert.equal(value.toString(), expected.toString()) } @@ -175,6 +153,7 @@ describe('Transfer tests', function(this: any) { const FromAddress = '0x5409ed021d9299bf6814279a6a1411a7e866a631' // Arbitrary addresses. + const governanceAddress = '0x00000000000000000000000000000000DeaDBeef' const ToAddress = '0xbBae99F0E1EE565404465638d40827b54D343638' const FeeRecipientAddress = '0x4f5f8a3f45d179553e7b95119ce296010f50f6f1' @@ -193,7 +172,7 @@ describe('Transfer tests', function(this: any) { await hooks.restart() kit = newKitFromWeb3(new Web3('http://localhost:8545')) - kit.gasInflactionFactor = 1 + kit.gasInflationFactor = 1 // TODO(mcortesi): magic sleep. without it unlockAccount sometimes fails await sleep(2) @@ -215,6 +194,14 @@ describe('Transfer tests', function(this: any) { } await initAndStartGeth(hooks.gethBinaryPath, fullInstance) + // Install an arbitrary address as the goverance address to act as the infrastructure fund. + // This is chosen instead of full migration for speed and to avoid the need for a governance + // proposal, as all contracts are owned by governance once the migration is complete. + const registry = await kit._web3Contracts.getRegistry() + const tx = registry.methods.setAddressFor(CeloContract.Governance, governanceAddress) + const gas = await tx.estimateGas({ from: validatorAddress }) + await tx.send({ from: validatorAddress, gas }) + // Give the account we will send transfers as sufficient gold and dollars. const startBalance = TransferAmount.times(500) const resDollars = await transferCeloDollars(validatorAddress, FromAddress, startBalance) @@ -237,13 +224,15 @@ describe('Transfer tests', function(this: any) { peers: [await getEnode(8547)], }) - // TODO(asa): Reduce this to speed tests up. - // Give the node time to sync the latest block. - await sleep(10) - // Reset contracts to send RPCs through transferring node. kit.web3.currentProvider = new kit.web3.providers.HttpProvider('http://localhost:8549') + // Give the node time to sync the latest block. + const upstream = await new Web3('http://localhost:8545').eth.getBlock('latest') + while ((await kit.web3.eth.getBlock('latest')).number < upstream.number) { + await sleep(0.5) + } + // Unlock Node account await kit.web3.eth.personal.unlockAccount(FromAddress, '', 1000000) } @@ -297,9 +286,21 @@ describe('Transfer tests', function(this: any) { } } + interface Fees { + total: BigNumber + tip: BigNumber + base: BigNumber + } + + interface GasUsage { + used?: number + expected: number + } + interface TestTxResults { - txOk: boolean - txFees: Fees + ok: boolean + fees: Fees + gas: GasUsage } const runTestTransaction = async ( @@ -310,44 +311,35 @@ describe('Transfer tests', function(this: any) { const minGasPrice = await getGasPriceMinimum(gasCurrency) assert.isAbove(parseInt(minGasPrice, 10), 0) - let txOk = false - let receipt: undefined | TransactionReceipt + let ok = false + let receipt: TransactionReceipt | undefined try { receipt = await txResult.waitReceipt() - txOk = true + ok = true } catch (err) { - txOk = false - } - - let usedGas = expectedGasUsed - if (receipt) { - if (receipt.gasUsed !== expectedGasUsed) { - // tslint:disable-next-line: no-console - console.log('OOPSS: Different Gas', receipt.gasUsed, expectedGasUsed) - } - // assert.equal(receipt.gasUsed, expectedGasUsed, 'Expected gas doesnt match') - usedGas = receipt.gasUsed + ok = false } + const gasVal = receipt ? receipt.gasUsed : expectedGasUsed + assert.isAbove(gasVal, 0) const txHash = await txResult.getHash() const tx = await kit.web3.eth.getTransaction(txHash) const gasPrice = tx.gasPrice assert.isAbove(parseInt(gasPrice, 10), 0) - const expectedTransactionFee = new BigNumber(usedGas).times(gasPrice) - const expectedProposerFeeFraction = 0.5 - const expectedTransactionFeeToProposer = new BigNumber(usedGas) - .times(minGasPrice) - .times(expectedProposerFeeFraction) - const expectedTransactionFeeToRecipient = expectedTransactionFee.minus( - expectedTransactionFeeToProposer - ) - const txFees = { - total: expectedTransactionFee, - proposer: expectedTransactionFeeToProposer, - recipient: expectedTransactionFeeToRecipient, + const txFee = new BigNumber(gasVal).times(gasPrice) + const txFeeBase = new BigNumber(gasVal).times(minGasPrice) + const txFeeTip = txFee.minus(txFeeBase) + + const fees = { + total: txFee, + base: txFeeBase, + tip: txFeeTip, } - - return { txOk, txFees } + const gas = { + used: receipt && receipt.gasUsed, + expected: expectedGasUsed, + } + return { ok, fees, gas } } function testTransferToken({ @@ -375,7 +367,13 @@ describe('Transfer tests', function(this: any) { ? await kit.registry.addressFor(CeloContract.StableToken) : undefined - const accounts = [FromAddress, ToAddress, validatorAddress, FeeRecipientAddress] + const accounts = [ + FromAddress, + ToAddress, + validatorAddress, + FeeRecipientAddress, + governanceAddress, + ] balances = await newBalanceWatcher(kit, accounts) const transferFn = @@ -385,20 +383,31 @@ describe('Transfer tests', function(this: any) { gasCurrency, }) + // Writing to an empty storage location (e.g. an uninitialized ERC20 account) costs 15k extra gas. + if ( + transferToken === CeloContract.StableToken && + balances.initial(ToAddress, transferToken).eq(0) + ) { + expectedGas += 15000 + } + txRes = await runTestTransaction(txResult, expectedGas, gasCurrency) await balances.update() }) if (expectSuccess) { - it(`should succeed`, () => assert.isTrue(txRes.txOk)) + it(`should succeed`, () => assert.isTrue(txRes.ok)) + + it(`should use the expected amount of gas`, () => + assert.equal(txRes.gas.used, txRes.gas.expected)) it(`should increment the receiver's ${transferToken} balance by the transfer amount`, () => assertEqualBN(balances.delta(ToAddress, transferToken), TransferAmount)) if (transferToken === feeToken) { it(`should decrement the sender's ${transferToken} balance by the transfer amount plus the gas fee`, () => { - const expectedBalanceChange = txRes.txFees.total.plus(TransferAmount) + const expectedBalanceChange = txRes.fees.total.plus(TransferAmount) assertEqualBN(balances.delta(FromAddress, transferToken).negated(), expectedBalanceChange) }) } else { @@ -406,13 +415,13 @@ describe('Transfer tests', function(this: any) { assertEqualBN(balances.delta(FromAddress, transferToken).negated(), TransferAmount)) it(`should decrement the sender's ${feeToken} balance by the gas fee`, () => - assertEqualBN(balances.delta(FromAddress, feeToken).negated(), txRes.txFees.total)) + assertEqualBN(balances.delta(FromAddress, feeToken).negated(), txRes.fees.total)) } } else { - it(`should fail`, () => assert.isFalse(txRes.txOk)) + it(`should fail`, () => assert.isFalse(txRes.ok)) it(`should decrement the sender's ${feeToken} balance by the gas fee`, () => - assertEqualBN(balances.delta(FromAddress, feeToken).negated(), txRes.txFees.total)) + assertEqualBN(balances.delta(FromAddress, feeToken).negated(), txRes.fees.total)) it(`should not change the receiver's ${transferToken} balance`, () => { assertEqualBN( @@ -431,13 +440,17 @@ describe('Transfer tests', function(this: any) { } } - it(`should increment the gas fee recipient's ${feeToken} balance by a portion of the gas fee`, () => - assertEqualBN(balances.delta(FeeRecipientAddress, feeToken), txRes.txFees.recipient)) + // TODO(nategraf): Replace gas fee recipient with gateway fee and adjust this check. + it.skip(`should increment the gas fee recipient's ${feeToken} balance by a portion of the gas fee`, () => + assertEqualBN(balances.delta(FeeRecipientAddress, feeToken), new BigNumber(0))) + + it(`should increment the infrastructure fund's ${feeToken} balance by the base portion of the gas fee`, () => + assertEqualBN(balances.delta(governanceAddress, feeToken), txRes.fees.base)) it(`should increment the proposers's ${feeToken} balance by the rest of the gas fee`, () => { assertEqualBN( balances.delta(validatorAddress, feeToken).mod(expectedProposerBlockReward), - txRes.txFees.proposer + txRes.fees.tip ) }) } @@ -450,8 +463,8 @@ describe('Transfer tests', function(this: any) { before(`start geth on sync: ${syncMode}`, () => startSyncNode(syncMode)) describe('Transfer CeloGold >', () => { - const GOLD_TRANSACTION_GAS_COST = 29180 - describe('gasCurrency = CeloGold >', () => { + const GOLD_TRANSACTION_GAS_COST = 30005 + describe('with gasCurrency = CeloGold >', () => { if (syncMode === 'light' || syncMode === 'ultralight') { describe('when running in light/ultralight sync mode', () => { describe('when not explicitly specifying a gas fee recipient', () => @@ -506,7 +519,7 @@ describe('Transfer tests', function(this: any) { describe('when there is no demurrage', () => { describe('when setting a gas amount greater than the amount of gas necessary', () => testTransferToken({ - expectedGas: 163180, + expectedGas: 164005, transferToken: CeloContract.GoldToken, feeToken: CeloContract.StableToken, txOptions: { @@ -550,7 +563,7 @@ describe('Transfer tests', function(this: any) { describe('Transfer CeloDollars', () => { describe('gasCurrency = CeloDollars >', () => { testTransferToken({ - expectedGas: 189456, + expectedGas: 175303, transferToken: CeloContract.StableToken, feeToken: CeloContract.StableToken, txOptions: { @@ -561,7 +574,7 @@ describe('Transfer tests', function(this: any) { describe('gasCurrency = CeloGold >', () => { testTransferToken({ - expectedGas: 40456, + expectedGas: 41303, transferToken: CeloContract.StableToken, feeToken: CeloContract.GoldToken, txOptions: { @@ -596,7 +609,7 @@ describe('Transfer tests', function(this: any) { describe('when there is no demurrage', () => { describe('when setting a gas amount greater than the amount of gas necessary', () => testTransferToken({ - expectedGas: 63180, + expectedGas: 64005, transferToken: CeloContract.GoldToken, feeToken: CeloContract.StableToken, txOptions: { @@ -640,7 +653,7 @@ describe('Transfer tests', function(this: any) { describe('Transfer CeloDollars', () => { describe('gasCurrency = CeloDollars >', () => { testTransferToken({ - expectedGas: 89456, + expectedGas: 75303, transferToken: CeloContract.StableToken, feeToken: CeloContract.StableToken, txOptions: { @@ -656,32 +669,34 @@ describe('Transfer tests', function(this: any) { describe('Transfer with Demurrage >', () => { let inflationManager: InflationManager + before(async () => { + await restartWithCleanNodes() + inflationManager = new InflationManager('http://localhost:8545', validatorAddress) + }) + for (const syncMode of syncModes) { describe(`${syncMode} Node >`, () => { - const restart = async () => { - await restartWithCleanNodes() - await startSyncNode(syncMode) - inflationManager = new InflationManager('http://localhost:8545', validatorAddress) - } + before(`start geth on sync: ${syncMode}`, () => startSyncNode(syncMode)) describe('when there is demurrage of 50% applied', () => { describe('when setting a gas amount greater than the amount of gas necessary', () => { let balances: BalanceWatcher let expectedFees: Fees + let txRes: TestTxResults before(async () => { - await restart() balances = await newBalanceWatcher(kit, [ FromAddress, ToAddress, validatorAddress, FeeRecipientAddress, + governanceAddress, ]) - await inflationManager.changeInflationFactorOnNextTransfer(new BigNumber(2)) + await inflationManager.setInflationRateForNextTransfer(new BigNumber(2)) const stableTokenAddress = await kit.registry.addressFor(CeloContract.StableToken) - const expectedGasUsed = 163180 - const txRes = await runTestTransaction( + const expectedGasUsed = 164005 + txRes = await runTestTransaction( await transferCeloGold(FromAddress, ToAddress, TransferAmount, { gasCurrency: stableTokenAddress, gasFeeRecipient: FeeRecipientAddress, @@ -689,12 +704,16 @@ describe('Transfer tests', function(this: any) { expectedGasUsed, stableTokenAddress ) - assert.isTrue(txRes.txOk) await balances.update() - expectedFees = txRes.txFees + expectedFees = txRes.fees }) + it('should succeed', () => assert.isTrue(txRes.ok)) + + it('should use the expected amount of gas', () => + assert.equal(txRes.gas.used, txRes.gas.expected)) + it("should decrement the sender's Celo Gold balance by the transfer amount", () => { assertEqualBN( balances.delta(FromAddress, CeloContract.GoldToken).negated(), @@ -710,51 +729,51 @@ describe('Transfer tests', function(this: any) { assertEqualBN( balances .initial(FromAddress, CeloContract.StableToken) - .div(2) + .idiv(2) .minus(balances.current(FromAddress, CeloContract.StableToken)), expectedFees.total ) }) - it("should increment the fee receipient's Celo Dollar balance by a portion of the gas fee", () => { + // TODO(nategraf): Replace gas fee recipient with gateway fee and adjust this check. + it.skip("should increment the fee receipient's Celo Dollar balance by a portion of the gas fee", () => { assertEqualBN( balances .current(FeeRecipientAddress, CeloContract.StableToken) - .minus(balances.initial(FeeRecipientAddress, CeloContract.StableToken).div(2)), - - // balances.delta(FeeRecipientAddress, CeloContract.StableToken), - expectedFees.recipient + .minus(balances.initial(FeeRecipientAddress, CeloContract.StableToken).idiv(2)), + new BigNumber(0) ) }) - // TODO mcortesi - // it("should increment the infrastructure fund's Celo Dollar balance by the rest of the gas fee", () => { - // assertEqualBN( - // newBalances[CeloContract.StableToken][governanceAddress] - // .minus(initialBalances[CeloContract.StableToken][governanceAddress]) - // , - // expectedFees.infrastructure - // ) - // }) + it("should halve the infrastructure fund's Celo Dollar balance then increment it by the base portion of the gas fee", () => { + assertEqualBN( + balances + .current(governanceAddress, CeloContract.StableToken) + .minus(balances.initial(governanceAddress, CeloContract.StableToken).idiv(2)), + expectedFees.base + ) + }) }) describe('when setting a gas amount less than the amount of gas necessary but more than the intrinsic gas amount', () => { let balances: BalanceWatcher let expectedFees: Fees + let txRes: TestTxResults + before(async () => { - await restart() balances = await newBalanceWatcher(kit, [ FromAddress, ToAddress, validatorAddress, FeeRecipientAddress, + governanceAddress, ]) - await inflationManager.changeInflationFactorOnNextTransfer(new BigNumber(2)) + await inflationManager.setInflationRateForNextTransfer(new BigNumber(2)) const intrinsicGas = 155000 const gas = intrinsicGas + 1000 - const txRes = await runTestTransaction( + txRes = await runTestTransaction( await transferCeloGold(FromAddress, ToAddress, TransferAmount, { gas, gasCurrency: await kit.registry.addressFor(CeloContract.StableToken), @@ -763,12 +782,13 @@ describe('Transfer tests', function(this: any) { gas, await kit.registry.addressFor(CeloContract.StableToken) ) - assert.isFalse(txRes.txOk) await balances.update() - expectedFees = txRes.txFees + expectedFees = txRes.fees }) + it('should fail', () => assert.isFalse(txRes.ok)) + it("should not change the sender's Celo Gold balance", () => { assertEqualBN(balances.delta(FromAddress, CeloContract.GoldToken), new BigNumber(0)) }) @@ -781,28 +801,37 @@ describe('Transfer tests', function(this: any) { assertEqualBN( balances .initial(FromAddress, CeloContract.StableToken) - .div(2) + .idiv(2) .minus(balances.current(FromAddress, CeloContract.StableToken)), expectedFees.total ) }) - it("should increment the fee recipient's Celo Dollar balance by a portion of the gas fee", () => { + // TODO(nategraf): Replace gas fee recipient with gateway fee and adjust this check. + it.skip("should increment the fee recipient's Celo Dollar balance by a portion of the gas fee", () => { assertEqualBN( balances.delta(FeeRecipientAddress, CeloContract.StableToken), - expectedFees.recipient + new BigNumber(0) ) }) - // TODO(mcortesi) - // it("should increment the proposers Celo Dollar balance by the rest of the gas fee", () => { - // assertEqualBN( - // newBalances[CeloContract.StableToken][governanceAddress] - // .minus(initialBalances[CeloContract.StableToken][governanceAddress]) - // , - // expectedFees.infrastructure - // ) - // }) + it(`should halve the infrastructure fund's Celo Dollar balance then increment it by the base portion of the gas fee`, () => { + assertEqualBN( + balances + .current(governanceAddress, CeloContract.StableToken) + .minus(balances.initial(governanceAddress, CeloContract.StableToken).idiv(2)), + expectedFees.base + ) + }) + + it('should halve the proposers Celo Dollar balance the increment it by the rest of the gas fee', () => { + assertEqualBN( + balances + .current(validatorAddress, CeloContract.StableToken) + .minus(balances.initial(validatorAddress, CeloContract.StableToken).idiv(2)), + expectedFees.tip + ) + }) }) }) }) diff --git a/packages/celotool/src/e2e-tests/utils.ts b/packages/celotool/src/e2e-tests/utils.ts index bfe5e018db0..98f669af9e8 100644 --- a/packages/celotool/src/e2e-tests/utils.ts +++ b/packages/celotool/src/e2e-tests/utils.ts @@ -243,13 +243,12 @@ async function isPortOpen(host: string, port: number) { } async function waitForPortOpen(host: string, port: number, seconds: number) { - while (seconds > 0) { + const deadline = Date.now() + seconds * 1000 + do { if (await isPortOpen(host, port)) { return true } - seconds -= 1 - await sleep(1) - } + } while (Date.now() < deadline) return false } diff --git a/packages/celotool/src/e2e-tests/verify_ultralight_geth_logs.ts b/packages/celotool/src/e2e-tests/verify_ultralight_geth_logs.ts deleted file mode 100644 index 0d79f7b608b..00000000000 --- a/packages/celotool/src/e2e-tests/verify_ultralight_geth_logs.ts +++ /dev/null @@ -1,109 +0,0 @@ -import GenesisBlockUtils from '@celo/walletkit/lib/src/genesis-block-utils' -import { equal, notEqual } from 'assert' -import * as fs from 'fs' - -// These tests read logs from a client which was running in Ultralight sync mode and verifies that -// only epoch headers are fetched till the height block and all headers are fetched afrerwards. -describe('Ultralight client', () => { - let epoch: number - - before(async () => { - const genesis = JSON.parse(await GenesisBlockUtils.getGenesisBlockAsync(argv.network)) - if (genesis.config.istanbul.epoch) { - epoch = Number(genesis.config.istanbul.epoch) - } else { - throw Error('epoch not found in genesis block') - } - }) - - beforeEach(function(this: any) { - this.timeout(0) - }) - - const argv = require('minimist')(process.argv.slice(2)) - const logfile = argv.gethlogfile - - let origin: number = -1 - let height: number = 0 - const insertedHeaderNumbers: number[] = [] - - console.debug('Reading logs from ' + logfile) - const fileContents = fs.readFileSync(logfile, 'utf8') - - // Fetch origin - const originInfo = fileContents.match('After the check origin is \\d+') - if (originInfo === null) { - throw Error('Origin is null') - } - const arr1 = originInfo[0].split(' ') - origin = parseInt(arr1[arr1.length - 1], 10) - console.debug('origin is ' + origin) - - // Fetch height - const heightInfo = fileContents.match('height is \\d+') - if (heightInfo === null) { - throw Error('Height is null') - } - const arr2 = heightInfo[0].split(' ') - height = parseInt(arr2[arr2.length - 1], 10) - console.debug('Height is ' + height) - - // Fetch all inserted headers - const insertedHeadersInfo = fileContents.match( - new RegExp('Inserted new header.*?number=\\d+', 'g') - ) - if (insertedHeadersInfo === null) { - throw Error('insertedHeadersInfo is null') - } - insertedHeadersInfo.forEach((insertedHeader) => { - const arr3 = insertedHeader.split('=') - const headerNumber = parseInt(arr3[arr3.length - 1], 10) - console.debug('Inserted header is ' + headerNumber) - insertedHeaderNumbers.push(headerNumber) - }) - - it('sync must start from 0', () => { - equal(origin, 0, 'Start header is not zero, it is ' + origin) - }) - - it('latest known header must be non-zero', () => { - notEqual(height, 0, 'Latest known header is zero') - }) - - it('height header must be fetched', () => { - let heightHeaderFetched: boolean = false - for (const headerNumber of insertedHeaderNumbers) { - if (headerNumber === height) { - heightHeaderFetched = true - break - } - } - equal(heightHeaderFetched, true, 'height header ' + height + ' not fetched') - }) - - it('must only download epoch blocks till height', () => { - for (const headerNumber of insertedHeaderNumbers) { - if (headerNumber < height) { - equal(headerNumber % epoch, 0, 'Non-epoch header below height fetched') - } - } - }) - - it('must fetch all headers after height', () => { - for ( - let i = insertedHeaderNumbers.length - 1; - i >= 0 && insertedHeaderNumbers[i] > height; - i++ - ) { - equal( - insertedHeaderNumbers[i] - insertedHeaderNumbers[i - 1], - 1, - 'Header(s) between ' + - insertedHeaderNumbers[i] + - ' and ' + - insertedHeaderNumbers[i - 1] + - ' are missing' - ) - } - }) -}) diff --git a/packages/contractkit/src/kit.test.ts b/packages/contractkit/src/kit.test.ts index 54adbf0354d..f0c0008e065 100644 --- a/packages/contractkit/src/kit.test.ts +++ b/packages/contractkit/src/kit.test.ts @@ -62,7 +62,7 @@ describe('kit.sendTransactionObject()', () => { test('should use inflation factor on gas', async () => { const txo = txoStub() txo.estimateGasMock.mockResolvedValue(1000) - kit.gasInflactionFactor = 2 + kit.gasInflationFactor = 2 await kit.sendTransactionObject(txo) expect(txo.send).toBeCalledWith( expect.objectContaining({ diff --git a/packages/contractkit/src/kit.ts b/packages/contractkit/src/kit.ts index a997ff88e06..d2d2c0e30df 100644 --- a/packages/contractkit/src/kit.ts +++ b/packages/contractkit/src/kit.ts @@ -144,7 +144,7 @@ export class ContractKit { return this.web3.eth.defaultAccount } - set gasInflactionFactor(factor: number) { + set gasInflationFactor(factor: number) { this.config.gasInflationFactor = factor } diff --git a/packages/contractkit/src/wrappers/GasPriceMinimum.ts b/packages/contractkit/src/wrappers/GasPriceMinimum.ts index 23c4cdfe998..dc97d22726e 100644 --- a/packages/contractkit/src/wrappers/GasPriceMinimum.ts +++ b/packages/contractkit/src/wrappers/GasPriceMinimum.ts @@ -6,7 +6,6 @@ export interface GasPriceMinimumConfig { gasPriceMinimum: BigNumber targetDensity: BigNumber adjustmentSpeed: BigNumber - proposerFraction: BigNumber } /** @@ -28,12 +27,6 @@ export class GasPriceMinimumWrapper extends BaseWrapper { * @returns multiplier that impacts how quickly gas price minimum is adjusted. */ adjustmentSpeed = proxyCall(this.contract.methods.adjustmentSpeed, undefined, toBigNumber) - /** - * Query infrastructure fraction parameter. - * @returns current fraction of the gas price minimum which is sent to - * the infrastructure fund - */ - proposerFraction = proxyCall(this.contract.methods.proposerFraction, undefined, toBigNumber) /** * Returns current configuration parameters. */ @@ -42,13 +35,11 @@ export class GasPriceMinimumWrapper extends BaseWrapper { this.gasPriceMinimum(), this.targetDensity(), this.adjustmentSpeed(), - this.proposerFraction(), ]) return { gasPriceMinimum: res[0], targetDensity: res[1], adjustmentSpeed: res[2], - proposerFraction: res[3], } } } diff --git a/packages/helm-charts/attestation-service/templates/attestation.secret.yaml b/packages/helm-charts/attestation-service/templates/attestation.secret.yaml index 453f413cc46..91f62260610 100644 --- a/packages/helm-charts/attestation-service/templates/attestation.secret.yaml +++ b/packages/helm-charts/attestation-service/templates/attestation.secret.yaml @@ -3,8 +3,8 @@ kind: Secret metadata: name: {{ .Release.Name }} labels: - app: ethstats - chart: ethstats + app: attestation-service + chart: attestation-service release: {{ .Release.Name }} heritage: {{ .Release.Service }} type: Opaque diff --git a/packages/mobile/ios/Podfile.lock b/packages/mobile/ios/Podfile.lock index 3b7ebbe71cb..9d1a4834a09 100644 --- a/packages/mobile/ios/Podfile.lock +++ b/packages/mobile/ios/Podfile.lock @@ -491,22 +491,19 @@ DEPENDENCIES: - Yoga (from `../../../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: - https://cdn.cocoapods.org/: + https://github.com/cocoapods/specs.git: - Analytics - boost-for-react-native - - Fabric - - Firebase - - FirebaseCore - - FirebaseInstanceID - - GoogleUtilities - https://github.com/cocoapods/specs.git: - Crashlytics + - Fabric - Firebase - FirebaseAnalytics - FirebaseAnalyticsInterop - FirebaseAuth - FirebaseAuthInterop + - FirebaseCore - FirebaseDatabase + - FirebaseInstanceID - FirebaseMessaging - FirebaseStorage - GoogleAppMeasurement diff --git a/packages/mobile/ios/celo/Info.plist b/packages/mobile/ios/celo/Info.plist index 8ce66d27d44..a34ccca2254 100644 --- a/packages/mobile/ios/celo/Info.plist +++ b/packages/mobile/ios/celo/Info.plist @@ -2,6 +2,8 @@ + UIUserInterfaceStyle + Light CFBundleDevelopmentRegion en CFBundleDisplayName diff --git a/packages/mobile/src/localCurrency/__snapshots__/SelectLocalCurrency.test.tsx.snap b/packages/mobile/src/localCurrency/__snapshots__/SelectLocalCurrency.test.tsx.snap index 7cbd2ec3664..081c23f145b 100644 --- a/packages/mobile/src/localCurrency/__snapshots__/SelectLocalCurrency.test.tsx.snap +++ b/packages/mobile/src/localCurrency/__snapshots__/SelectLocalCurrency.test.tsx.snap @@ -10,6 +10,11 @@ exports[`SelectLocalCurrency renders correctly 1`] = ` "MXN", "PHP", "LRD", + "SLL", + "KES", + "UGX", + "GHS", + "NGN", ] } disableVirtualization={false} @@ -518,6 +523,325 @@ exports[`SelectLocalCurrency renders correctly 1`] = ` + + + + + + + + + SLL + + + + + + + + + + + + + + KES + + + + + + + + + + + + + + UGX + + + + + + + + + + + + + + GHS + + + + + + `; diff --git a/packages/mobile/src/localCurrency/consts.ts b/packages/mobile/src/localCurrency/consts.ts index 619e8313e1b..70ddace0f3f 100644 --- a/packages/mobile/src/localCurrency/consts.ts +++ b/packages/mobile/src/localCurrency/consts.ts @@ -6,6 +6,11 @@ export enum LocalCurrencyCode { MXN = 'MXN', PHP = 'PHP', LRD = 'LRD', + SLL = 'SLL', + KES = 'KES', + UGX = 'UGX', + GHS = 'GHS', + NGN = 'NGN', } export enum LocalCurrencySymbol { @@ -15,6 +20,11 @@ export enum LocalCurrencySymbol { MXN = '$', PHP = '₱', LRD = 'L$', + SLL = 'Le', + KES = 'KSh', + UGX = 'USh', + GHS = 'GH₵', + NGN = '₦', } export const LOCAL_CURRENCY_CODES = Object.values(LocalCurrencyCode) diff --git a/packages/mobile/src/localCurrency/selectors.test.ts b/packages/mobile/src/localCurrency/selectors.test.ts new file mode 100644 index 00000000000..b696ad4e273 --- /dev/null +++ b/packages/mobile/src/localCurrency/selectors.test.ts @@ -0,0 +1,47 @@ +import { getLocalCurrencyCode } from 'src/localCurrency/selectors' + +describe(getLocalCurrencyCode, () => { + describe('when no preferred currency is set', () => { + it('returns the first supported local currency for the country of the phone number', () => { + const state: any = { + account: { e164PhoneNumber: '+231881551952' }, + localCurrency: { preferredCurrencyCode: undefined }, + } + expect(getLocalCurrencyCode(state)).toEqual('LRD') + }) + + it('returns null for US phone numbers', () => { + const state: any = { + account: { e164PhoneNumber: '+14155552671' }, + localCurrency: { preferredCurrencyCode: undefined }, + } + expect(getLocalCurrencyCode(state)).toBeNull() + }) + + it('returns CAD for CA phone numbers', () => { + const state: any = { + account: { e164PhoneNumber: '+18192216929' }, + localCurrency: { preferredCurrencyCode: undefined }, + } + expect(getLocalCurrencyCode(state)).toEqual('CAD') + }) + }) + + describe('when a preferred currency is set', () => { + it('returns the preferred currency', () => { + const state: any = { + account: { e164PhoneNumber: '+231881551952' }, + localCurrency: { preferredCurrencyCode: 'MXN' }, + } + expect(getLocalCurrencyCode(state)).toEqual('MXN') + }) + + it('returns null when USD is the preferred currency', () => { + const state: any = { + account: { e164PhoneNumber: '+14155552671' }, + localCurrency: { preferredCurrencyCode: 'USD' }, + } + expect(getLocalCurrencyCode(state)).toBeNull() + }) + }) +}) diff --git a/packages/mobile/src/localCurrency/selectors.ts b/packages/mobile/src/localCurrency/selectors.ts index df4a16d6bb3..a3ca2c23fad 100644 --- a/packages/mobile/src/localCurrency/selectors.ts +++ b/packages/mobile/src/localCurrency/selectors.ts @@ -1,4 +1,7 @@ -import * as RNLocalize from 'react-native-localize' +import { getRegionCode } from '@celo/utils/src/phoneNumbers' +import CountryData from 'country-data' +import { createSelector } from 'reselect' +import { e164NumberSelector } from 'src/account/reducer' import { LOCAL_CURRENCY_CODES, LocalCurrencyCode, @@ -8,25 +11,37 @@ import { RootState } from 'src/redux/reducers' const MIN_UPDATE_INTERVAL = 12 * 3600 * 1000 // 12 hours -// Returns the best currency possible (it respects the user preferred currencies list order). -function findBestAvailableCurrency(supportedCurrencyCodes: LocalCurrencyCode[]) { - const deviceCurrencies = RNLocalize.getCurrencies() - const supportedCurrenciesSet = new Set(supportedCurrencyCodes) +function getCountryCurrencies(e164PhoneNumber: string) { + const regionCode = getRegionCode(e164PhoneNumber) + const countries = CountryData.lookup.countries({ alpha2: regionCode }) + const country = countries.length > 0 ? countries[0] : undefined - for (const deviceCurrency of deviceCurrencies) { - if (supportedCurrenciesSet.has(deviceCurrency as LocalCurrencyCode)) { - return deviceCurrency as LocalCurrencyCode - } - } - - return null + return country ? country.currencies : [] } -// TODO(jean): listen to locale changes so this stays accurate when changed while the app is running -const DEVICE_BEST_CURRENCY_CODE = findBestAvailableCurrency(LOCAL_CURRENCY_CODES) +const getDefaultLocalCurrencyCode = createSelector( + e164NumberSelector, + (e164PhoneNumber): LocalCurrencyCode | null => { + // Note: we initially tried using the device locale for getting the currencies (`RNLocalize.getCurrencies()`) + // but the problem is some Android versions don't make it possible to select the appropriate language/country + // from the device settings. + // So here we use the country of the phone number + const countryCurrencies = getCountryCurrencies(e164PhoneNumber) + const supportedCurrenciesSet = new Set(LOCAL_CURRENCY_CODES) + + for (const countryCurrency of countryCurrencies) { + if (supportedCurrenciesSet.has(countryCurrency as LocalCurrencyCode)) { + return countryCurrency as LocalCurrencyCode + } + } + + return LocalCurrencyCode.USD + } +) export function getLocalCurrencyCode(state: RootState): LocalCurrencyCode | null { - const currencyCode = state.localCurrency.preferredCurrencyCode || DEVICE_BEST_CURRENCY_CODE + const currencyCode = + state.localCurrency.preferredCurrencyCode || getDefaultLocalCurrencyCode(state) if (!currencyCode || currencyCode === LocalCurrencyCode.USD) { // This disables local currency display return null @@ -36,7 +51,8 @@ export function getLocalCurrencyCode(state: RootState): LocalCurrencyCode | null } export function getLocalCurrencySymbol(state: RootState): LocalCurrencySymbol | null { - const currencyCode = state.localCurrency.preferredCurrencyCode || DEVICE_BEST_CURRENCY_CODE + const currencyCode = + state.localCurrency.preferredCurrencyCode || getDefaultLocalCurrencyCode(state) return currencyCode ? LocalCurrencySymbol[currencyCode] : null } diff --git a/packages/mobile/src/qrcode/QRScanner.tsx b/packages/mobile/src/qrcode/QRScanner.tsx index aff1036d2d3..0ca8521ccc7 100644 --- a/packages/mobile/src/qrcode/QRScanner.tsx +++ b/packages/mobile/src/qrcode/QRScanner.tsx @@ -23,25 +23,50 @@ interface DispatchProps { handleBarcodeDetected: typeof handleBarcodeDetected } +interface State { + isScanningEnabled: boolean +} + type Props = DispatchProps & WithNamespaces & NavigationFocusInjectedProps -class QRScanner extends React.Component { +class QRScanner extends React.Component { static navigationOptions = () => ({ ...headerWithBackButton, headerTitle: i18n.t('sendFlow7:scanCode'), }) + timeout: undefined | number = undefined + + state = { + isScanningEnabled: true, + } + camera: RNCamera | null = null + // This method would be called multiple times with the same + // QR code, so we need to catch only the first one onBardCodeDetected = (rawData: any) => { - Logger.debug('QRScanner', 'Bar code detected') - this.props.handleBarcodeDetected(rawData) + if (this.state.isScanningEnabled) { + this.setState({ isScanningEnabled: false }, () => { + Logger.debug('QRScanner', 'Bar code detected') + this.props.handleBarcodeDetected(rawData) + }) + this.timeout = setTimeout(() => { + this.setState({ isScanningEnabled: true }) + }, 1000) + } } onPressShowYourCode = () => { navigate(Screens.QRCode) } + componentWillUnmount() { + if (this.timeout) { + clearTimeout(this.timeout) + } + } + render() { const { t } = this.props return ( diff --git a/packages/mobile/src/transactions/TransferFeedItem.tsx b/packages/mobile/src/transactions/TransferFeedItem.tsx index 1a757e531ff..d1f8ee7c121 100644 --- a/packages/mobile/src/transactions/TransferFeedItem.tsx +++ b/packages/mobile/src/transactions/TransferFeedItem.tsx @@ -192,7 +192,6 @@ export function TransferFeedItem(props: Props) { {currencyStyle.direction} {showLocalCurrency && localCurrencySymbol} {transactionValue} - {showLocalCurrency && localCurrencyCode} {!!info && {info}} diff --git a/packages/mobile/src/transactions/__snapshots__/TransactionFeed.test.tsx.snap b/packages/mobile/src/transactions/__snapshots__/TransactionFeed.test.tsx.snap index 7983a599ca9..aa956d2d221 100644 --- a/packages/mobile/src/transactions/__snapshots__/TransactionFeed.test.tsx.snap +++ b/packages/mobile/src/transactions/__snapshots__/TransactionFeed.test.tsx.snap @@ -310,7 +310,6 @@ exports[`renders for gold to dollar exchange properly 1`] = ` - $ 133.00 - MXN => { config.gasPriceMinimum.initialMinimum, toFixed(config.gasPriceMinimum.targetDensity).toString(), toFixed(config.gasPriceMinimum.adjustmentSpeed).toString(), - toFixed(config.gasPriceMinimum.proposerFraction).toString(), ] } diff --git a/packages/protocol/migrationsConfig.js b/packages/protocol/migrationsConfig.js index 7ff2433457d..55cce422338 100644 --- a/packages/protocol/migrationsConfig.js +++ b/packages/protocol/migrationsConfig.js @@ -53,7 +53,6 @@ const DefaultConfig = { initialMinimum: 10000, targetDensity: 1 / 2, adjustmentSpeed: 1 / 2, - proposerFraction: 1 / 2, }, governance: { approvalStageDuration: 15 * 60, // 15 minutes diff --git a/packages/protocol/test/common/gaspriceminimum.ts b/packages/protocol/test/common/gaspriceminimum.ts index 2f13a386a93..6b15f21d38d 100644 --- a/packages/protocol/test/common/gaspriceminimum.ts +++ b/packages/protocol/test/common/gaspriceminimum.ts @@ -23,7 +23,6 @@ contract('GasPriceMinimum', (accounts: string[]) => { const initialGasPriceMinimum = new BigNumber(500) const targetDensity = toFixed(1 / 2) const adjustmentSpeed = toFixed(1 / 2) - const proposerFraction = toFixed(1 / 2) beforeEach(async () => { registry = await Registry.new() @@ -33,8 +32,7 @@ contract('GasPriceMinimum', (accounts: string[]) => { registry.address, initialGasPriceMinimum, targetDensity, - adjustmentSpeed, - proposerFraction + adjustmentSpeed ) }) @@ -59,19 +57,13 @@ contract('GasPriceMinimum', (accounts: string[]) => { assertEqualBN(actualAdjustmentSpeed, adjustmentSpeed) }) - it('should set the infrastructure fraction', async () => { - const actualProposerFraction = await gasPriceMinimum.proposerFraction() - assertEqualBN(actualProposerFraction, proposerFraction) - }) - it('should not be callable again', async () => { await assertRevert( gasPriceMinimum.initialize( registry.address, initialGasPriceMinimum, targetDensity, - adjustmentSpeed, - proposerFraction + adjustmentSpeed ) ) }) @@ -141,38 +133,6 @@ contract('GasPriceMinimum', (accounts: string[]) => { }) }) - describe('#setProposerFraction', () => { - const newProposerFraction = toFixed(1 / 3) - - it('should set the adjustment speed', async () => { - await gasPriceMinimum.setProposerFraction(newProposerFraction) - const actualProposerFraction = await gasPriceMinimum.proposerFraction() - assertEqualBN(actualProposerFraction, newProposerFraction) - }) - - it('should emit the ProposerFractionSet event', async () => { - const resp = await gasPriceMinimum.setProposerFraction(newProposerFraction) - assert.equal(resp.logs.length, 1) - const log = resp.logs[0] - assertLogMatches2(log, { - event: 'ProposerFractionSet', - args: { - proposerFraction: newProposerFraction, - }, - }) - }) - - it('should revert when the provided fraction is greater than one', async () => { - await assertRevert(gasPriceMinimum.setProposerFraction(toFixed(3 / 2))) - }) - - it('should revert when called by anyone other than the owner', async () => { - await assertRevert( - gasPriceMinimum.setProposerFraction(newProposerFraction, { from: nonOwner }) - ) - }) - }) - describe('#getUpdatedGasPriceMinimum', () => { describe('when the block is full', () => { it('should return 25% more than the initial minimum', async () => { diff --git a/packages/web/src/community/connect/CoverArea.tsx b/packages/web/src/community/connect/CoverArea.tsx index 4a96b763d05..e6bfb5235f1 100644 --- a/packages/web/src/community/connect/CoverArea.tsx +++ b/packages/web/src/community/connect/CoverArea.tsx @@ -60,7 +60,7 @@ class CoverArea extends React.PureComponent { > {t('cover.title')} - + {t('cover.joinMovement')} { buttonOne={{ title: t('installWallet'), href: CeloLinks.walletApp }} buttonTwo={{ title: t('seeCode'), href: CeloLinks.monorepo }} > -
  • {t('mobile.nonCustodial')}
  • -
  • {t('mobile.mobileUltra')}
  • -
  • {t('mobile.exchange')}
  • -
  • {t('mobile.qr')}
  • +
  • {t('mobile.nonCustodial')}
  • +
  • {t('mobile.mobileUltra')}
  • +
  • {t('mobile.exchange')}
  • +
  • {t('mobile.qr')}
  • { buttonOne={{ title: t('readMore'), href: CeloLinks.docsOverview }} buttonTwo={{ title: t('seeCode'), href: CeloLinks.monorepo }} > -
  • {t('protocol.algoReserve')}
  • -
  • {t('protocol.cryptoCollat')}
  • -
  • {t('protocol.native')}
  • +
  • {t('protocol.algoReserve')}
  • +
  • {t('protocol.cryptoCollat')}
  • +
  • {t('protocol.native')}
  • { buttonOne={{ title: t('readMore'), href: CeloLinks.docsOverview }} buttonTwo={{ title: t('seeCode'), href: CeloLinks.blockChainRepo }} > -
  • {t('proof.permissionless')}
  • -
  • {t('proof.rewardsWeighted')}
  • -
  • {t('proof.onChain')}
  • +
  • {t('proof.permissionless')}
  • +
  • {t('proof.rewardsWeighted')}
  • +
  • {t('proof.onChain')}
  • diff --git a/packages/web/src/dev/CoverAction.tsx b/packages/web/src/dev/CoverAction.tsx index 93b21f19e5e..0c5b3808df4 100644 --- a/packages/web/src/dev/CoverAction.tsx +++ b/packages/web/src/dev/CoverAction.tsx @@ -33,7 +33,7 @@ export default function CoverAction({ title, text, graphic, link, isMobile, styl {title} - {text} + {text} ) }) diff --git a/packages/web/src/dev/StackSection.tsx b/packages/web/src/dev/StackSection.tsx index 6871bd3d7f8..b033b193a08 100644 --- a/packages/web/src/dev/StackSection.tsx +++ b/packages/web/src/dev/StackSection.tsx @@ -38,7 +38,7 @@ export default withScreenSize(

    {title}

    - {text} + {text}
      {children}