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

Finance: fix token fallbacks and error handling #813

Merged
merged 4 commits into from
Apr 18, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
111 changes: 59 additions & 52 deletions apps/finance/app/src/components/NewTransfer/Deposit.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ const TOKEN_ALLOWANCE_WEBSITE = 'https://tokenallowance.io/'

const tokenAbi = [].concat(tokenBalanceOfAbi, tokenDecimalsAbi, tokenSymbolAbi)

const renderBalanceForSelectedToken = selectedToken => {
const { decimals, loading, symbol, userBalance } = selectedToken.data
if (loading || !userBalance) {
return ''
}

return userBalance === -1
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be '-1'?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

😅 this is why I need a second pair of eyes 😄

? `Your balance could not be found for ${symbol}`
: `You have ${
userBalance === '0' ? 'no' : fromDecimals(userBalance, decimals)
} ${symbol} available`
}

const initialState = {
amount: {
error: NO_ERROR,
Expand Down Expand Up @@ -129,58 +142,61 @@ class Deposit extends React.Component {
const { selectedToken } = this.state
return selectedToken.value && !selectedToken.data.loading
}
loadTokenData(address) {
async loadTokenData(address) {
const { api, network, connectedAccount } = this.props

// ETH
if (addressesEqual(address, ETHER_TOKEN_FAKE_ADDRESS)) {
return new Promise((resolve, reject) =>
api.web3Eth('getBalance', connectedAccount).subscribe(
ethBalance =>
resolve({
decimals: 18,
loading: false,
symbol: 'ETH',
userBalance: ethBalance,
}),
reject
)
)
}

// Tokens
const token = api.external(address, tokenAbi)

return new Promise(async (resolve, reject) => {
const userBalance = await token.balanceOf(connectedAccount).toPromise()

const decimalsFallback =
tokenDataFallback(address, 'decimals', network.type) || '0'
const symbolFallback =
tokenDataFallback(address, 'symbol', network.type) || ''
const userBalance = await api
.web3Eth('getBalance', connectedAccount)
.toPromise()
.catch(() => '-1')

const tokenData = {
userBalance,
decimals: parseInt(decimalsFallback, 10),
return {
decimals: 18,
loading: false,
symbol: symbolFallback,
symbol: 'ETH',
userBalance,
}
}

const [tokenSymbol, tokenDecimals] = await Promise.all([
getTokenSymbol(api, address),
token.decimals().toPromise(),
])
// Tokens
const token = api.external(address, tokenAbi)
const userBalance = await token
.balanceOf(connectedAccount)
.toPromise()
.catch(() => '-1')

const decimalsFallback =
tokenDataFallback(address, 'decimals', network.type) || '0'
const symbolFallback =
tokenDataFallback(address, 'symbol', network.type) || ''

const tokenData = {
userBalance,
decimals: parseInt(decimalsFallback, 10),
loading: false,
symbol: symbolFallback,
}

// If symbol or decimals are resolved, overwrite the fallbacks
if (tokenSymbol) {
tokenData.symbol = tokenSymbol
}
if (tokenDecimals) {
tokenData.decimals = parseInt(tokenDecimals, 10)
}
const [tokenSymbol, tokenDecimals] = await Promise.all([
getTokenSymbol(api, address).catch(() => ''),
token
.decimals()
.toPromise()
.then(decimals => parseInt(decimals, 10))
.catch(() => ''),
])

// If symbol or decimals are resolved, overwrite the fallbacks
if (tokenSymbol) {
tokenData.symbol = tokenSymbol
}
if (tokenDecimals) {
tokenData.decimals = tokenDecimals
}

resolve(tokenData)
})
return tokenData
}
validateInputs({ amount, selectedToken } = {}) {
amount = amount || this.state.amount
Expand Down Expand Up @@ -255,16 +271,7 @@ class Deposit extends React.Component {

const selectedTokenIsAddress = isAddress(selectedToken.value)
const showTokenBadge = selectedTokenIsAddress && selectedToken.coerced
const tokenBalanceMessage = selectedToken.data.userBalance
? `You have ${
selectedToken.data.userBalance === '0'
? 'no'
: fromDecimals(
selectedToken.data.userBalance,
selectedToken.data.decimals
)
} ${selectedToken.data.symbol} available`
: ''
const tokenBalanceMessage = renderBalanceForSelectedToken(selectedToken)

const ethSelected =
selectedTokenIsAddress &&
Expand Down
34 changes: 18 additions & 16 deletions apps/finance/app/src/lib/token-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,31 @@ export const tokenDataFallback = (tokenAddress, fieldName, networkType) => {
export async function getTokenSymbol(app, address) {
// Symbol is optional; note that aragon.js doesn't return an error (only an falsey value) when
// getting this value fails
let token = app.external(address, tokenSymbolAbi)
let tokenSymbol = await token.symbol().toPromise()
if (tokenSymbol) {
return tokenSymbol
let tokenSymbol
try {
const token = app.external(address, tokenSymbolAbi)
tokenSymbol = await token.symbol().toPromise()
} catch (err) {
// Some tokens (e.g. DS-Token) use bytes32 as the return type for symbol().
const token = app.external(address, tokenSymbolBytesAbi)
tokenSymbol = toUtf8(await token.symbol().toPromise())
}
// Some tokens (e.g. DS-Token) use bytes32 as the return type for symbol().
token = app.external(address, tokenSymbolBytesAbi)
tokenSymbol = await token.symbol().toPromise()

return tokenSymbol ? toUtf8(tokenSymbol) : null
return tokenSymbol || null
}

export async function getTokenName(app, address) {
// Name is optional; note that aragon.js doesn't return an error (only an falsey value) when
// getting this value fails
let token = app.external(address, tokenNameAbi)
let tokenName = await token.name().toPromise()
if (tokenName) {
return tokenName
let tokenName
try {
const token = app.external(address, tokenNameAbi)
tokenName = await token.name().toPromise()
} catch (err) {
// Some tokens (e.g. DS-Token) use bytes32 as the return type for name().
const token = app.external(address, tokenNameBytesAbi)
tokenName = toUtf8(await token.name().toPromise())
}
// Some tokens (e.g. DS-Token) use bytes32 as the return type for name().
token = app.external(address, tokenNameBytesAbi)
tokenName = await token.name().toPromise()

return tokenName ? toUtf8(tokenName) : null
return tokenName || null
}
104 changes: 52 additions & 52 deletions apps/finance/app/src/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const INITIALIZATION_TRIGGER = Symbol('INITIALIZATION_TRIGGER')
const TEST_TOKEN_ADDRESSES = []
const tokenContracts = new Map() // Addr -> External contract
const tokenDecimals = new Map() // External contract -> decimals
const tokenName = new Map() // External contract -> name
const tokenNames = new Map() // External contract -> name
const tokenSymbols = new Map() // External contract -> symbol

const ETH_CONTRACT = Symbol('ETH_CONTRACT')
Expand Down Expand Up @@ -89,7 +89,7 @@ async function initialize(vaultAddress, ethAddress) {
// Set up ETH placeholders
tokenContracts.set(ethAddress, ETH_CONTRACT)
tokenDecimals.set(ETH_CONTRACT, '18')
tokenName.set(ETH_CONTRACT, 'Ether')
tokenNames.set(ETH_CONTRACT, 'Ether')
tokenSymbols.set(ETH_CONTRACT, 'ETH')

return createStore({
Expand Down Expand Up @@ -317,62 +317,62 @@ function loadTokenBalance(tokenAddress, { vault }) {
return vault.contract.balance(tokenAddress).toPromise()
}

function loadTokenDecimals(tokenContract, tokenAddress, { network }) {
return new Promise((resolve, reject) => {
if (tokenDecimals.has(tokenContract)) {
resolve(tokenDecimals.get(tokenContract))
} else {
const fallback =
tokenDataFallback(tokenAddress, 'decimals', network.type) || '0'

tokenContract.decimals().subscribe(
(decimals = fallback) => {
tokenDecimals.set(tokenContract, decimals)
resolve(decimals)
},
() => {
// Decimals is optional
resolve(fallback)
}
)
}
})
async function loadTokenDecimals(tokenContract, tokenAddress, { network }) {
if (tokenDecimals.has(tokenContract)) {
return tokenDecimals.get(tokenContract)
}

const fallback =
tokenDataFallback(tokenAddress, 'decimals', network.type) || '0'

let decimals
try {
decimals = (await tokenContract.decimals().toPromise()) || fallback
tokenDecimals.set(tokenContract, decimals)
} catch (err) {
// decimals is optional
decimals = fallback
}
return decimals
}

function loadTokenName(tokenContract, tokenAddress, { network }) {
return new Promise((resolve, reject) => {
if (tokenName.has(tokenContract)) {
resolve(tokenName.get(tokenContract))
} else {
const fallback =
tokenDataFallback(tokenAddress, 'name', network.type) || ''
const name = getTokenName(app, tokenAddress)
resolve(name || fallback)
}
})
async function loadTokenName(tokenContract, tokenAddress, { network }) {
if (tokenNames.has(tokenContract)) {
return tokenNames.get(tokenContract)
}
const fallback = tokenDataFallback(tokenAddress, 'name', network.type) || ''

let name
try {
name = (await getTokenName(app, tokenAddress)) || fallback
tokenNames.set(tokenContract, name)
} catch (err) {
// name is optional
name = fallback
}
return name
}

function loadTokenSymbol(tokenContract, tokenAddress, { network }) {
return new Promise((resolve, reject) => {
if (tokenSymbols.has(tokenContract)) {
resolve(tokenSymbols.get(tokenContract))
} else {
const fallback =
tokenDataFallback(tokenAddress, 'symbol', network.type) || ''
const tokenSymbol = getTokenSymbol(app, tokenAddress)
resolve(tokenSymbol || fallback)
}
})
async function loadTokenSymbol(tokenContract, tokenAddress, { network }) {
if (tokenSymbols.has(tokenContract)) {
return tokenSymbols.get(tokenContract)
}
const fallback = tokenDataFallback(tokenAddress, 'symbol', network.type) || ''

let symbol
try {
symbol = (await getTokenSymbol(app, tokenAddress)) || fallback
tokenSymbols.set(tokenContract, symbol)
} catch (err) {
// symbol is optional
symbol = fallback
}
return symbol
}

function loadTransactionDetails(id) {
return new Promise((resolve, reject) =>
app
.call('getTransaction', id)
.subscribe(
transaction => resolve(marshallTransactionDetails(transaction)),
reject
)
async function loadTransactionDetails(id) {
return marshallTransactionDetails(
await app.call('getTransaction', id).toPromise()
)
}

Expand Down