on master help sign in
fix: parseInt returns NaN, but we expected it to throw
0336a7cMatt Condon (shrugs)
src/components/InlineEthereumTransaction.js+4−1
@@ -23,6 +23,7 @@ import WarningBox from './WarningBox';
23import CopyButton from './CopyButton';
23import CopyButton from './CopyButton';
24import ProgressButton from './ProgressButton';
24import ProgressButton from './ProgressButton';
25import CopiableAddress from './CopiableAddress';
25import CopiableAddress from './CopiableAddress';
26+import convertToInt from 'lib/convertToInt';
26
27
27export default function InlineEthereumTransaction({
28export default function InlineEthereumTransaction({
28 // from useEthereumTransaction.bind
29 // from useEthereumTransaction.bind
@@ -191,7 +192,9 @@ export default function InlineEthereumTransaction({
191 min="1"
192 min="1"
192 max="100"
193 max="100"
193 value={gasPrice}
194 value={gasPrice}
195+ onChange={e =>
194− onChange={e => setGasPrice(parseInt(e.target.value, 10))}
196+ setGasPrice(convertToInt(e.target.value, 10))
197+ }
195 />
198 />
196 <Grid.Item
199 <Grid.Item
197 full
200 full
src/form/formatters.js+6−1
@@ -1,4 +1,5 @@
1import { compose, strSplice } from 'lib/lib';
1import { compose, strSplice } from 'lib/lib';
2+import convertToInt from 'lib/convertToInt';
2
3
3const HEX_PREFIX = '0x';
4const HEX_PREFIX = '0x';
4const TICKET_MAX_BYTE_LEN = 32; // tickets can be as large as 32 bytes
5const TICKET_MAX_BYTE_LEN = 32; // tickets can be as large as 32 bytes
@@ -48,8 +49,12 @@ export const ensureHexPrefix = s => {
48};
49};
49
50
50export const convertToNumber = s => {
51export const convertToNumber = s => {
52+ if (!s) {
53+ return s || '';
54+ }
55+
51 try {
56 try {
52− return parseInt(s, 10);
57+ return convertToInt(s, 10);
53 } catch {
58 } catch {
54 return 0;
59 return 0;
55 }
60 }
src/lib/convertToInt.jsnew file+17
@@ -0,0 +1,17 @@
1+import { isNaN } from 'lodash';
2+
3+/**
4+ * a version of parseInt that throws for NaN
5+ * fun fact, parseInt also handles 0x-prefixed hex strings, assuming the radix
6+ * to be 16
7+ *
8+ * @throws
9+ * @returns {number}
10+ */
11+export default (num, radix) => {
12+ const res = parseInt(num, radix);
13+ if (isNaN(res)) {
14+ throw new Error('NaN');
15+ }
16+ return res;
17+};
src/lib/patp2dec.js+3−1
@@ -1,9 +1,11 @@
1import * as ob from 'urbit-ob';
1import * as ob from 'urbit-ob';
2
2
3+import convertToInt from './convertToInt';
4+
3/**
5/**
4 * Convert a patp string into a number, or throw.
6 * Convert a patp string into a number, or throw.
5 *
7 *
6 * @throws
8 * @throws
7 * @return number
9 * @return number
8 */
10 */
9−export default patp => parseInt(ob.patp2dec(patp), 10);
11+export default patp => convertToInt(ob.patp2dec(patp), 10);
src/lib/trezor.js+3−1
@@ -1,9 +1,11 @@
1import TrezorConnect from 'trezor-connect';
1import TrezorConnect from 'trezor-connect';
2+import convertToInt from './convertToInt';
2
3
3const TREZOR_PATH = "m/44'/60'/0'/0/x";
4const TREZOR_PATH = "m/44'/60'/0'/0/x";
4
5
6+// handles 0x-prefixed hex string as well as number type
5const formatChainId = val =>
7const formatChainId = val =>
6− typeof val === 'number' ? val : parseInt(val.slice(2), 16); // assume 0x-prefixed hex string
8+ typeof val === 'number' ? val : convertToInt(val.slice(2), 16);
7
9
8const trezorSignTransaction = async (txn, hdpath) => {
10const trezorSignTransaction = async (txn, hdpath) => {
9 const trezorFormattedTxn = {
11 const trezorFormattedTxn = {
src/lib/useKeyfileGenerator.js+2−1
@@ -16,6 +16,7 @@ import {
16 compileNetworkingKey,
16 compileNetworkingKey,
17} from './keys';
17} from './keys';
18import useCurrentPermissions from './useCurrentPermissions';
18import useCurrentPermissions from './useCurrentPermissions';
19+import convertToInt from './convertToInt';
19
20
20export default function useKeyfileGenerator(manualNetworkSeed) {
21export default function useKeyfileGenerator(manualNetworkSeed) {
21 const { urbitWallet, wallet, authMnemonic } = useWallet();
22 const { urbitWallet, wallet, authMnemonic } = useWallet();
@@ -30,7 +31,7 @@ export default function useKeyfileGenerator(manualNetworkSeed) {
30 const _point = need.point(pointCursor);
31 const _point = need.point(pointCursor);
31 const _details = need.details(getDetails(_point));
32 const _details = need.details(getDetails(_point));
32
33
33− const networkRevision = parseInt(_details.keyRevisionNumber, 10);
34+ const networkRevision = convertToInt(_details.keyRevisionNumber, 10);
34 const { isOwner, isManagementProxy } = useCurrentPermissions();
35 const { isOwner, isManagementProxy } = useCurrentPermissions();
35
36
36 const hasNetworkingKeys = networkRevision > 0;
37 const hasNetworkingKeys = networkRevision > 0;
src/views/Activate/ActivateCode.js+2−1
@@ -37,6 +37,7 @@ import FormError from 'form/FormError';
37
37
38import { useActivateFlow } from './ActivateFlow';
38import { useActivateFlow } from './ActivateFlow';
39import { WARNING } from 'form/helpers';
39import { WARNING } from 'form/helpers';
40+import convertToInt from 'lib/convertToInt';
40
41
41export default function ActivateCode() {
42export default function ActivateCode() {
42 const history = useHistory();
43 const history = useHistory();
@@ -121,7 +122,7 @@ export default function ActivateCode() {
121 };
122 };
122 }
123 }
123
124
124− const point = parseInt(incoming[0], 10);
125+ const point = convertToInt(incoming[0], 10);
125
126
126 setDerivedPoint(Just(point));
127 setDerivedPoint(Just(point));
127 setInviteWallet(inviteWallet);
128 setInviteWallet(inviteWallet);
src/views/Admin/AdminEditPermissions.js+2−1
@@ -19,6 +19,7 @@ import ViewHeader from 'components/ViewHeader';
19import { ForwardButton } from 'components/Buttons';
19import { ForwardButton } from 'components/Buttons';
20import { matchBlinkyDate } from 'components/Blinky';
20import { matchBlinkyDate } from 'components/Blinky';
21import CopyButton from 'components/CopyButton';
21import CopyButton from 'components/CopyButton';
22+import convertToInt from 'lib/convertToInt';
22
23
23export default function AdminEditPermissions() {
24export default function AdminEditPermissions() {
24 const { push, names } = useLocalRouter();
25 const { push, names } = useLocalRouter();
@@ -34,7 +35,7 @@ export default function AdminEditPermissions() {
34 const isSenate = pointSize === azimuth.PointSize.Galaxy;
35 const isSenate = pointSize === azimuth.PointSize.Galaxy;
35
36
36 const details = need.details(getDetails(point));
37 const details = need.details(getDetails(point));
37− const networkRevision = parseInt(details.keyRevisionNumber, 10);
38+ const networkRevision = convertToInt(details.keyRevisionNumber, 10);
38 const { canManage, isOwner } = useCurrentPermissions();
39 const { canManage, isOwner } = useCurrentPermissions();
39
40
40 const goSetProxy = useCallback(
41 const goSetProxy = useCallback(
src/views/Admin/AdminNetworkingKeys.js+4−1
@@ -40,6 +40,7 @@ import {
40import BridgeForm from 'form/BridgeForm';
40import BridgeForm from 'form/BridgeForm';
41import Condition from 'form/Condition';
41import Condition from 'form/Condition';
42import FormError from 'form/FormError';
42import FormError from 'form/FormError';
43+import convertToInt from 'lib/convertToInt';
43
44
44const chainKeyProp = name => d =>
45const chainKeyProp = name => d =>
45 d[name] === CURVE_ZERO_ADDR ? Nothing() : Just(d[name]);
46 d[name] === CURVE_ZERO_ADDR ? Nothing() : Just(d[name]);
@@ -64,7 +65,7 @@ function useSetKeys() {
64 const _contracts = need.contracts(contracts);
65 const _contracts = need.contracts(contracts);
65 const _details = need.details(getDetails(_point));
66 const _details = need.details(getDetails(_point));
66
67
67− const networkRevision = parseInt(_details.keyRevisionNumber, 10);
68+ const networkRevision = convertToInt(_details.keyRevisionNumber, 10);
68 const randomSeed = useRef();
69 const randomSeed = useRef();
69
70
70 // NOTE: nd = 'nondeterministic' (can also be a 'manual' seed)
71 // NOTE: nd = 'nondeterministic' (can also be a 'manual' seed)
@@ -151,6 +152,8 @@ export default function AdminNetworkingKeys() {
151 const hasKeys = details.matchWith({
152 const hasKeys = details.matchWith({
152 Nothing: () => false,
153 Nothing: () => false,
153 Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
154 Just: ({ value: details }) => parseInt(details.keyRevisionNumber, 10) > 0,
155+ // we actually don't mind the default NaN behavior of parseInt here,
156+ // since NaN > 0 === false and that's a reasonable result
154 });
157 });
155
158
156 const {
159 const {
src/views/Admin/Reticket/ReticketExecute.js+2−1
@@ -22,6 +22,7 @@ import LoadingBar from 'components/LoadingBar';
22import Highlighted from 'components/Highlighted';
22import Highlighted from 'components/Highlighted';
23import { WALLET_TYPES } from 'lib/wallet';
23import { WALLET_TYPES } from 'lib/wallet';
24import CopiableAddress from 'components/CopiableAddress';
24import CopiableAddress from 'components/CopiableAddress';
25+import convertToInt from 'lib/convertToInt';
25
26
26const labelForProgress = progress => {
27const labelForProgress = progress => {
27 if (progress <= 0) {
28 if (progress <= 0) {
@@ -58,7 +59,7 @@ export default function ReticketExecute({ newWallet, setNewWallet }) {
58 (async () => {
59 (async () => {
59 const point = need.point(pointCursor);
60 const point = need.point(pointCursor);
60 const details = need.details(getDetails(point));
61 const details = need.details(getDetails(point));
61− const networkRevision = parseInt(details.keyRevisionNumber, 10);
62+ const networkRevision = convertToInt(details.keyRevisionNumber, 10);
62 try {
63 try {
63 await reticketPointBetweenWallets({
64 await reticketPointBetweenWallets({
64 fromWallet: need.wallet(wallet),
65 fromWallet: need.wallet(wallet),
src/views/IssueChild.js+2−1
@@ -29,6 +29,7 @@ import {
29import BridgeForm from 'form/BridgeForm';
29import BridgeForm from 'form/BridgeForm';
30import FormError from 'form/FormError';
30import FormError from 'form/FormError';
31import CopiableAddress from 'components/CopiableAddress';
31import CopiableAddress from 'components/CopiableAddress';
32+import convertToInt from 'lib/convertToInt';
32
33
33function useIssueChild() {
34function useIssueChild() {
34 const { contracts } = useNetwork();
35 const { contracts } = useNetwork();
@@ -60,7 +61,7 @@ export default function IssueChild() {
60 const { pointCursor } = usePointCursor();
61 const { pointCursor } = usePointCursor();
61
62
62 const _contracts = need.contracts(contracts);
63 const _contracts = need.contracts(contracts);
63− const _point = parseInt(need.point(pointCursor), 10);
64+ const _point = convertToInt(need.point(pointCursor), 10);
64
65
65 const availablePointsPromise = useConstant(() =>
66 const availablePointsPromise = useConstant(() =>
66 azimuth.azimuth
67 azimuth.azimuth