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

[Wallet] Migrate app view functions to contractkit #1381

Merged
merged 31 commits into from
Oct 25, 2019
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
12316b9
WIP
annakaz Oct 16, 2019
0b69fe3
Fix confiq for contractkit import
annakaz Oct 16, 2019
e339991
Add comment to replicate self reference bug
annakaz Oct 17, 2019
6ba7a93
Remove unused code for debuggin
annakaz Oct 17, 2019
36d784c
Make this runnable
annakaz Oct 17, 2019
db50930
Get view exchange working
annakaz Oct 17, 2019
6ec179e
Move exchange logic to saga
annakaz Oct 17, 2019
e27424f
Move exchange logic to saga not actions
annakaz Oct 17, 2019
6f1d0f4
Merge branch 'master' into annakaz/app-contractkit
annakaz Oct 17, 2019
6e4a628
Merge branch 'master' into annakaz/app-contractkit
annakaz Oct 17, 2019
48763fa
Use proper yield call
annakaz Oct 17, 2019
86696e5
Merge branch 'master' into annakaz/app-contractkit
annakaz Oct 17, 2019
2f9e91f
WIP adding exchange/saga tests
annakaz Oct 22, 2019
2301cb9
Move balance fetching to ck. Tested on alfajores
annakaz Oct 22, 2019
8a157d9
Add contract decimals caching
annakaz Oct 22, 2019
15be5e1
add comments
annakaz Oct 22, 2019
d18cacd
Add decimal caching
annakaz Oct 22, 2019
f982a9f
Merge branch 'master' into annakaz/app-contractkit
annakaz Oct 22, 2019
f5a0917
Revert .env file
annakaz Oct 22, 2019
fd38798
Merge branch 'annakaz/app-contractkit' of github.com:celo-org/celo-mo…
annakaz Oct 22, 2019
3daa8c5
Merge branch 'master' into annakaz/app-contractkit
annakaz Oct 22, 2019
ca81485
WIP fixing tests
annakaz Oct 22, 2019
028dd5d
Merge branch 'annakaz/app-contractkit' of github.com:celo-org/celo-mo…
annakaz Oct 23, 2019
8aec57b
Merge branch 'master' into annakaz/app-contractkit
annakaz Oct 23, 2019
6fec476
WIP fixing tests
annakaz Oct 23, 2019
750a6af
WIP with saga tests
annakaz Oct 23, 2019
821b5c9
Fix invite saga tests
annakaz Oct 24, 2019
d59e21a
Update tests to work with contractkit
annakaz Oct 24, 2019
80416ae
Merge branch 'master' into annakaz/app-contractkit
annakaz Oct 24, 2019
cbc4e86
Wait for web3 sync instead of connectedAccount
annakaz Oct 25, 2019
76228df
Merge branch 'master' into annakaz/app-contractkit
annakaz Oct 25, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
},
"dependencies": {
"@celo/client": "691d471",
"@celo/contractkit": "^0.1.6",
"@celo/react-components": "1.0.0",
"@celo/react-native-sms-retriever": "git+https://github.com/celo-org/react-native-sms-retriever#d3a2fdb",
"@celo/utils": "^0.1.1",
Expand Down
3 changes: 2 additions & 1 deletion packages/mobile/rn-cli.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const root = path.resolve(cwd, '../..')
const escapedRoot = escapeStringRegexp(root)
const rnRegex = new RegExp(`${escapedRoot}\/node_modules\/(react-native)\/.*`)
const celoRegex = new RegExp(
`${escapedRoot}\/packages\/(?!mobile|utils|walletkit|react-components).*`
`${escapedRoot}\/packages\/(?!mobile|utils|walletkit|contractkit|react-components).*`
)
const nestedRnRegex = new RegExp(`.*\/node_modules\/.*\/node_modules\/(react-native)\/.*`)
const componentsRnRegex = new RegExp(`.*react-components\/node_modules\/(react-native)\/.*`)
Expand All @@ -24,6 +24,7 @@ module.exports = {
extraNodeModules: {
...nodeLibs,
'crypto-js': path.resolve(cwd, 'node_modules/crypto-js'),
fs: require.resolve('react-native-fs'),
'isomorphic-fetch': require.resolve('cross-fetch'),
net: require.resolve('react-native-tcp'),
'react-native': path.resolve(cwd, 'node_modules/react-native'),
Expand Down
251 changes: 0 additions & 251 deletions packages/mobile/src/exchange/actions.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,6 @@
import {
ContractUtils,
getExchangeContract,
getGoldTokenContract,
getStableTokenContract,
} from '@celo/walletkit'
import { Exchange as ExchangeType } from '@celo/walletkit/types/Exchange'
import { GoldToken as GoldTokenType } from '@celo/walletkit/types/GoldToken'
import { StableToken as StableTokenType } from '@celo/walletkit/types/StableToken'
import BigNumber from 'bignumber.js'
import { call, put, select } from 'redux-saga/effects'
import { showError } from 'src/alert/actions'
import { ErrorMessages } from 'src/app/ErrorMessages'
import { ExchangeRatePair } from 'src/exchange/reducer'
import { CURRENCY_ENUM } from 'src/geth/consts'
import { RootState } from 'src/redux/reducers'
import {
addStandbyTransaction,
generateStandbyTransactionId,
removeStandbyTransaction,
} from 'src/transactions/actions'
import { TransactionStatus, TransactionTypes } from 'src/transactions/reducer'
import { sendAndMonitorTransaction } from 'src/transactions/saga'
import { sendTransaction } from 'src/transactions/send'
import { getRateForMakerToken, getTakerAmount } from 'src/utils/currencyExchange'
import { roundDown } from 'src/utils/formatting'
import Logger from 'src/utils/Logger'
import { web3 } from 'src/web3/contracts'
import { getConnectedAccount, getConnectedUnlockedAccount } from 'src/web3/saga'
import * as util from 'util'

const TAG = 'exchange/actions'
const LARGE_DOLLARS_SELL_AMOUNT_IN_WEI = new BigNumber(1000 * 1000000000000000000) // To estimate exchange rate from exchange contract
const LARGE_GOLD_SELL_AMOUNT_IN_WEI = new BigNumber(100 * 1000000000000000000)
const EXCHANGE_DIFFERENCE_TOLERATED = 0.01 // Maximum difference between actual and displayed takerAmount

export enum Actions {
FETCH_EXCHANGE_RATE = 'EXCHANGE/FETCH_EXCHANGE_RATE',
Expand Down Expand Up @@ -70,223 +38,4 @@ export const exchangeTokens = (
makerToken,
makerAmount,
})

export type ActionTypes = SetExchangeRateAction | ExchangeTokensAction

export function* doFetchExchangeRate(makerAmount?: BigNumber, makerToken?: CURRENCY_ENUM) {
Logger.debug(TAG, 'Calling @doFetchExchangeRate')

let dollarMakerAmount: BigNumber
let goldMakerAmount: BigNumber
if (makerAmount && makerToken === CURRENCY_ENUM.GOLD) {
dollarMakerAmount = LARGE_DOLLARS_SELL_AMOUNT_IN_WEI
goldMakerAmount = makerAmount
} else if (makerAmount && makerToken === CURRENCY_ENUM.DOLLAR) {
dollarMakerAmount = makerAmount
goldMakerAmount = LARGE_GOLD_SELL_AMOUNT_IN_WEI
} else {
dollarMakerAmount = LARGE_DOLLARS_SELL_AMOUNT_IN_WEI
goldMakerAmount = LARGE_GOLD_SELL_AMOUNT_IN_WEI
if (makerAmount || makerToken) {
Logger.debug(
TAG,
'Using default makerAmount estimates. Need both makerAmount and makerToken to override. '
)
}
}

try {
yield call(getConnectedAccount)

const dollarMakerExchangeRate: BigNumber = yield call(
ContractUtils.getExchangeRate,
web3,
CURRENCY_ENUM.DOLLAR,
new BigNumber(dollarMakerAmount)
)
const goldMakerExchangeRate: BigNumber = yield call(
ContractUtils.getExchangeRate,
web3,
CURRENCY_ENUM.GOLD,
new BigNumber(goldMakerAmount)
)

if (!dollarMakerExchangeRate || !goldMakerExchangeRate) {
Logger.error(TAG, 'Invalid exchange rate')
throw new Error('Invalid exchange rate')
}

Logger.debug(
TAG,
`Retrieved exchange rate:
${dollarMakerExchangeRate.toString()} gold per dollar, estimated at ${dollarMakerAmount}
${goldMakerExchangeRate.toString()} dollar per gold, estimated at ${goldMakerAmount}`
)

yield put(
setExchangeRate({
goldMaker: goldMakerExchangeRate.toString(),
dollarMaker: dollarMakerExchangeRate.toString(),
})
)
} catch (error) {
Logger.error(TAG, 'Error fetching exchange rate', error)
yield put(showError(ErrorMessages.EXCHANGE_RATE_FAILED))
}
}

export function* exchangeGoldAndStableTokens(action: ExchangeTokensAction) {
Logger.debug(`${TAG}@exchangeGoldAndStableTokens`, 'Exchanging gold and stable CURRENCY_ENUM')
const { makerToken, makerAmount } = action
Logger.debug(TAG, `Exchanging ${makerAmount.toString()} of CURRENCY_ENUM ${makerToken}`)
let txId: string | null = null
try {
const account: string = yield call(getConnectedUnlockedAccount)
const exchangeRatePair: ExchangeRatePair = yield select(
(state: RootState) => state.exchange.exchangeRatePair
)
const exchangeRate = getRateForMakerToken(exchangeRatePair, makerToken)
if (!exchangeRate) {
Logger.error(TAG, 'Invalid exchange rate from exchange contract')
return
}
if (exchangeRate.isZero()) {
Logger.error(TAG, 'Cannot do exchange with rate of 0. Stopping.')
throw new Error('Invalid exchange rate')
}

txId = yield createStandbyTx(makerToken, makerAmount, exchangeRate, account)

const goldTokenContract: GoldTokenType = yield call(getGoldTokenContract, web3)
const stableTokenContract: StableTokenType = yield call(getStableTokenContract, web3)
const exchangeContract: ExchangeType = yield call(getExchangeContract, web3)

const makerTokenContract =
makerToken === CURRENCY_ENUM.DOLLAR ? stableTokenContract : goldTokenContract

const convertedMakerAmount: BigNumber = yield call(
convertToContractDecimals,
makerAmount,
makerTokenContract
)
const sellGold = makerToken === CURRENCY_ENUM.GOLD

const updatedExchangeRate: BigNumber = yield call(
// Updating with actual makerAmount, rather than conservative estimate displayed
ContractUtils.getExchangeRate,
web3,
makerToken,
convertedMakerAmount
)

const exceedsExpectedSize =
makerToken === CURRENCY_ENUM.GOLD
? convertedMakerAmount.isGreaterThan(LARGE_GOLD_SELL_AMOUNT_IN_WEI)
: convertedMakerAmount.isGreaterThan(LARGE_DOLLARS_SELL_AMOUNT_IN_WEI)

if (exceedsExpectedSize) {
Logger.error(
TAG,
`Displayed exchange rate was estimated with a smaller makerAmount than actual ${convertedMakerAmount}`
)
// Note that exchange will still go through if makerAmount difference is within EXCHANGE_DIFFERENCE_TOLERATED
}

// Ensure the user gets makerAmount at least as good as displayed (rounded to EXCHANGE_DIFFERENCE_TOLERATED)
const minimumTakerAmount = getTakerAmount(makerAmount, exchangeRate).minus(
EXCHANGE_DIFFERENCE_TOLERATED
)
const updatedTakerAmount = getTakerAmount(makerAmount, updatedExchangeRate)
if (minimumTakerAmount.isGreaterThan(updatedTakerAmount)) {
Logger.error(
TAG,
`Not receiving enough ${makerToken} due to change in exchange rate. Exchange failed.`
)
yield put(showError(ErrorMessages.EXCHANGE_RATE_CHANGE))
return
}

const takerTokenContract =
makerToken === CURRENCY_ENUM.DOLLAR ? goldTokenContract : stableTokenContract
const convertedTakerAmount: BigNumber = roundDown(
yield call(convertToContractDecimals, minimumTakerAmount, takerTokenContract),
0
)
Logger.debug(
TAG,
`Will receive at least ${convertedTakerAmount}
wei for ${convertedMakerAmount} wei of ${makerToken}`
)

let approveTx
if (makerToken === CURRENCY_ENUM.GOLD) {
approveTx = goldTokenContract.methods.approve(
exchangeContract._address,
convertedMakerAmount.toString()
)
} else if (makerToken === CURRENCY_ENUM.DOLLAR) {
approveTx = stableTokenContract.methods.approve(
exchangeContract._address,
convertedMakerAmount.toString()
)
} else {
Logger.error(TAG, `Unexpected maker token ${makerToken}`)
return
}
yield call(sendTransaction, approveTx, account, TAG, 'approval')
Logger.debug(TAG, `Transaction approved: ${util.inspect(approveTx.arguments)}`)

const tx = exchangeContract.methods.exchange(
convertedMakerAmount.toString(),
convertedTakerAmount.toString(),
sellGold
)

if (!txId) {
Logger.error(TAG, 'No txId. Did not exchange.')
return
}
yield call(sendAndMonitorTransaction, txId, tx, account)
} catch (error) {
Logger.error(TAG, 'Error doing exchange', error)
if (txId) {
yield put(removeStandbyTransaction(txId))
}

if (error.message === ErrorMessages.INCORRECT_PIN) {
yield put(showError(ErrorMessages.INCORRECT_PIN))
} else {
yield put(showError(ErrorMessages.EXCHANGE_FAILED))
}
}
}

function* createStandbyTx(
makerToken: CURRENCY_ENUM,
makerAmount: BigNumber,
exchangeRate: BigNumber,
account: string
) {
const takerAmount = getTakerAmount(makerAmount, exchangeRate, 2)
const txId = generateStandbyTransactionId(account)
yield put(
addStandbyTransaction({
id: txId,
type: TransactionTypes.EXCHANGE,
status: TransactionStatus.Pending,
inSymbol: makerToken,
inValue: makerAmount.toString(),
outSymbol: makerToken === CURRENCY_ENUM.DOLLAR ? CURRENCY_ENUM.GOLD : CURRENCY_ENUM.DOLLAR,
outValue: takerAmount.toString(),
timestamp: Math.floor(Date.now() / 1000),
})
)
return txId
}

async function convertToContractDecimals(value: BigNumber | string | number, contract: any) {
// TODO(Rossy): Move this function to SDK and cache this decimals amount
const decimals = await contract.methods.decimals().call()
const one = new BigNumber(10).pow(new BigNumber(decimals).toNumber())
return one.times(value)
}
Loading