Skip to content

Commit

Permalink
Token-manager: use bn.js (#432)
Browse files Browse the repository at this point in the history
Some related changes:

- Add tests (Jest).
- Support decimals.
- New algorithm to calculate stake percentages (remove 0% values).
- Sort holders table in descending order.
  • Loading branch information
2color authored and bpierre committed Oct 8, 2018
1 parent ec95a37 commit 0795433
Show file tree
Hide file tree
Showing 14 changed files with 265 additions and 129 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ env:
- TASK=test:finance
- TASK=test:survey
- TASK=test:token-manager
- TASK=test:token-manager:app
- TASK=test:vault
- TASK=test:voting
- TASK=coverage:finance
Expand Down
7 changes: 4 additions & 3 deletions apps/token-manager/app/.babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
],
"stage-2"
],
"env": {
"test": { "presets": [["env"], "stage-2"] }
},
"plugins": [
["styled-components", {
"displayName": true
}],
["styled-components", { "displayName": true }],
"transform-runtime"
]
}
8 changes: 5 additions & 3 deletions apps/token-manager/app/.eslintrc
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
{
"env": {
"browser": true,
"es6": true
"es6": true,
"jest/globals": true
},
"extends": [
"standard",
"standard-react",
"prettier",
"prettier/flowtype",
"prettier/react"
"prettier/react",
"plugin:jest/recommended"
],
"parser": "babel-eslint",
"parserOptions": {
Expand All @@ -18,7 +20,7 @@
},
"sourceType": "module"
},
"plugins": ["prettier", "react"],
"plugins": ["prettier", "react", "jest"],
"rules": {
"react/prop-types": 0,
"prettier/prettier": [
Expand Down
6 changes: 5 additions & 1 deletion apps/token-manager/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dependencies": {
"@aragon/client": "^1.0.0-beta.8",
"@aragon/ui": "^0.11.0",
"bn.js": "^4.11.6",
"prop-types": "^15.6.0",
"react": "^16.2.0",
"react-dom": "^16.2.0",
Expand All @@ -16,6 +17,7 @@
"@aragon/react-scripts": "^1.1.0-aragon.6",
"babel-core": "^6.26.0",
"babel-eslint": "^8.0.3",
"babel-jest": "^23.6.0",
"babel-plugin-styled-components": "^1.5.1",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.6.1",
Expand All @@ -26,11 +28,13 @@
"eslint-config-standard": "^10.2.1",
"eslint-config-standard-react": "^5.0.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-jest": "^21.24.1",
"eslint-plugin-node": "^5.2.1",
"eslint-plugin-prettier": "^2.3.1",
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-react": "^7.5.1",
"eslint-plugin-standard": "^3.0.1",
"jest": "^23.6.0",
"parcel-bundler": "^1.9.1",
"prettier": "^1.8.2"
},
Expand All @@ -40,7 +44,7 @@
"start": "npm run sync-assets && npm run build:script -- --no-minify && PORT=3003 react-scripts start",
"build": "npm run sync-assets && npm run build:script && react-scripts build",
"build:script": "parcel build src/script.js -d public/",
"test": "react-scripts test --env=jsdom",
"test": "jest",
"eject": "react-scripts eject"
},
"proxy": "http://localhost:3003",
Expand Down
33 changes: 27 additions & 6 deletions apps/token-manager/app/src/App.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import BN from 'bn.js'
import {
AragonApp,
AppBar,
Expand Down Expand Up @@ -36,15 +37,15 @@ class App extends React.Component {
// Is this the first time we've loaded the token settings?
if (!tokenSettingsLoaded && hasLoadedTokenSettings(nextProps)) {
this.setState({
tokenDecimalsBase: Math.pow(10, nextProps.tokenDecimals),
tokenSettingsLoaded: true,
})
}
}
handleAssignTokens = ({ amount, recipient }) => {
const { app } = this.props
const { tokenDecimalsBase } = this.state
app.mint(recipient, amount * tokenDecimalsBase)
const { app, tokenDecimalsBase } = this.props
const amountConverted = Math.floor(parseFloat(amount) * tokenDecimalsBase)
const toMint = new BN(`${amountConverted}`, 10)
app.mint(recipient, toMint.toString())
this.handleSidepanelClose()
}
handleAppBarLaunchAssignTokens = () => this.handleLaunchAssignTokens()
Expand All @@ -61,9 +62,14 @@ class App extends React.Component {
})
}
render() {
const { tokenSymbol, tokenSupply, holders, userAccount } = this.props
const {
tokenSymbol,
tokenSupply,
tokenDecimalsBase,
holders,
userAccount,
} = this.props
const {
assignTokensConfig,
sidepanelOpened,
tokenSettingsLoaded,
Expand Down Expand Up @@ -113,6 +119,7 @@ class App extends React.Component {
<AssignVotePanelContent
onAssignTokens={this.handleAssignTokens}
opened={sidepanelOpened}
tokenDecimalsBase={tokenDecimalsBase}
{...assignTokensConfig}
/>
</SidePanel>
Expand All @@ -130,6 +137,20 @@ const Title = styled.span`
`

export default observe(
observable => observable.map(state => ({ ...state })),
// Convert tokenSupply and holders balances to BNs,
// and calculate tokenDecimalsBase.
observable =>
observable.map(state => {
const { tokenSupply, holders, tokenDecimals } = state
const tokenDecimalsBase = new BN(10).pow(new BN(tokenDecimals))
return {
...state,
tokenSupply: new BN(tokenSupply),
tokenDecimalsBase,
holders: holders
.map(holder => ({ ...holder, balance: new BN(holder.balance) }))
.sort((a, b) => b.balance.cmp(a.balance)),
}
}),
{}
)(App)
15 changes: 10 additions & 5 deletions apps/token-manager/app/src/components/HolderRow.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
IconAdd,
Badge,
} from '@aragon/ui'
import { round } from '../math-utils'
import { formatBalance } from '../utils'

class HolderRow extends React.Component {
static defaultProps = {
Expand All @@ -29,21 +29,26 @@ class HolderRow extends React.Component {
tokenDecimalsBase,
isCurrentUser,
} = this.props
// Rounding their balance to 5 decimals should be enough... right?
const adjustedBalance = round(balance / tokenDecimalsBase, 5)
return (
<TableRow>
<TableCell>
<Owner>
<span>{name}</span>
{isCurrentUser && (
<Badge.Identity style={{ fontVariant: 'small-caps' }} title='This is your Ethereum address'>
<Badge.Identity
style={{ fontVariant: 'small-caps' }}
title="This is your Ethereum address"
>
you
</Badge.Identity>
)}
</Owner>
</TableCell>
{!groupMode && <TableCell align="right">{adjustedBalance}</TableCell>}
{!groupMode && (
<TableCell align="right">
{formatBalance(balance, tokenDecimalsBase)}
</TableCell>
)}
<TableCell align="right">
<ContextMenu>
<ContextMenuItem onClick={this.handleAssignTokens}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react'
import styled from 'styled-components'
import { Button, Field, IconCross, Text, TextInput } from '@aragon/ui'
import BN from 'bn.js'
import { addressPattern, isAddress } from '../../web3-utils'

const initialState = {
Expand Down Expand Up @@ -59,7 +60,7 @@ class AssignVotePanelContent extends React.Component {
const recipientAddress = recipient.value.trim()
if (isAddress(recipientAddress)) {
this.props.onAssignTokens({
amount: Number(amount),
amount: amount,
recipient: recipientAddress,
})
} else {
Expand All @@ -73,6 +74,10 @@ class AssignVotePanelContent extends React.Component {
}
render() {
const { amount, recipient } = this.state
const { tokenDecimalsBase } = this.props
const tokenBase = tokenDecimalsBase
? new BN(1).div(tokenDecimalsBase).toNumber()
: 0
return (
<div>
<form onSubmit={this.handleSubmit}>
Expand All @@ -93,8 +98,8 @@ class AssignVotePanelContent extends React.Component {
<TextInput.Number
value={amount}
onChange={this.handleAmountChange}
min={0}
step="any"
min={tokenBase}
step={tokenBase}
required
wide
/>
Expand Down
56 changes: 12 additions & 44 deletions apps/token-manager/app/src/components/SideBar.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import React from 'react'
import styled from 'styled-components'
import { Text, theme } from '@aragon/ui'
import { sciNot } from '../math-utils'

// Number of digits before "Total Supply" gets wrapped into two lines
const TOTAL_SUPPLY_CUTOFF_LENGTH = 18
import { formatBalance, stakesPercentages } from '../utils'

const DISTRIBUTION_ITEMS_MAX = 7
const DISTRIBUTION_COLORS = [
Expand All @@ -17,35 +14,16 @@ const DISTRIBUTION_COLORS = [
'#80AEDC',
]

const calculateStakes = (accounts, total) => {
const maxDisplayed = DISTRIBUTION_ITEMS_MAX - 1
const byStake = (a, b) => b.stake - a.stake

const stakes = accounts.map(({ address, balance }) => ({
name: address,
stake: Math.floor(balance / total * 100),
const displayedStakes = (accounts, total) => {
const positiveAccounts = accounts.filter(({ balance }) => balance > 0)
return stakesPercentages(positiveAccounts.map(({ balance }) => balance), {
total,
maxIncluded: DISTRIBUTION_ITEMS_MAX,
}).map((stake, index) => ({
name: stake.index === -1 ? 'Rest' : positiveAccounts[index].address,
stake: stake.percentage,
color: DISTRIBUTION_COLORS[index % DISTRIBUTION_COLORS.length],
}))

stakes.push({
name: 'Organization Reserves',
stake: Math.floor(
(total - accounts.reduce((total, { balance }) => total + balance, 0)) /
total *
100
),
})

const displayedStakes = stakes
.filter(({ stake }) => stake > 0)
.sort(byStake)
.slice(0, maxDisplayed)

const rest =
100 - displayedStakes.reduce((total, { stake }) => total + stake, 0)

return displayedStakes.length < accounts.length
? [...displayedStakes, { name: 'Rest', stake: rest }].sort(byStake)
: displayedStakes
}

class SideBar extends React.Component {
Expand All @@ -54,17 +32,7 @@ class SideBar extends React.Component {
}
render() {
const { holders, tokenDecimalsBase, tokenSupply } = this.props
const stakes = calculateStakes(holders, tokenSupply).map((stake, i) => ({
...stake,
color: DISTRIBUTION_COLORS[i] || '#000000',
}))

const adjustedTokenSupply = sciNot(
tokenSupply / tokenDecimalsBase,
TOTAL_SUPPLY_CUTOFF_LENGTH,
{ rounding: 5 }
)

const stakes = displayedStakes(holders, tokenSupply)
return (
<Main>
<Part>
Expand All @@ -77,7 +45,7 @@ class SideBar extends React.Component {
<InfoRow>
<span>Total Supply</span>
<span>:</span>
<strong>{adjustedTokenSupply}</strong>
<strong>{formatBalance(tokenSupply, tokenDecimalsBase)}</strong>
</InfoRow>
</ul>
</Part>
Expand Down
58 changes: 0 additions & 58 deletions apps/token-manager/app/src/math-utils.js

This file was deleted.

Loading

0 comments on commit 0795433

Please sign in to comment.