Unverified Commit c7496277 authored by Hanwen Cheng's avatar Hanwen Cheng Committed by GitHub
Browse files

fix: kusma extrinsic interpretion and signing error (#570)

* update react-native-camera and related polkadot api packages

* add registry store and use type override

* reduce android bundle size

* update yarn.lock

* update metadata

* prettier happy

* update polkadot api

* remove log

* still use iphone SE as test e2e device
parent 1afd9367
Pipeline #85230 failed with stages
in 3 minutes and 59 seconds
......@@ -3,5 +3,7 @@
"parser": "typescript",
"singleQuote": true,
"useTabs": true,
"tabWidth": 2
"tabWidth": 2,
"trailingComma": "none",
"arrowParens": "avoid"
}
......@@ -96,7 +96,7 @@ def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
def enableProguardInReleaseBuilds = true
/**
* The preferred build flavor of JavaScriptCore.
......
......@@ -182,13 +182,13 @@ PODS:
- React-cxxreact (= 0.61.5)
- React-jsi (= 0.61.5)
- React-jsinspector (0.61.5)
- react-native-camera (3.4.0):
- react-native-camera (3.21.0):
- React
- react-native-camera/RCT (= 3.4.0)
- react-native-camera/RN (= 3.4.0)
- react-native-camera/RCT (3.4.0):
- react-native-camera/RCT (= 3.21.0)
- react-native-camera/RN (= 3.21.0)
- react-native-camera/RCT (3.21.0):
- React
- react-native-camera/RN (3.4.0):
- react-native-camera/RN (3.21.0):
- React
- react-native-netinfo (4.2.1):
- React
......@@ -377,7 +377,7 @@ SPEC CHECKSUMS:
React-jsi: cb2cd74d7ccf4cffb071a46833613edc79cdf8f7
React-jsiexecutor: d5525f9ed5f782fdbacb64b9b01a43a9323d2386
React-jsinspector: fa0ecc501688c3c4c34f28834a76302233e29dc0
react-native-camera: f4f2144140ae1e808cad97cc7caf013767c11f1b
react-native-camera: 4e12e877b8fef60b0914a49c942a0f32479e5b50
react-native-netinfo: aeb1752abb78a9c15cdcea067a6c95d596dcfc45
react-native-randombytes: 3638d24759d67c68f6ccba60c52a7a8a8faa6a23
react-native-safe-area-context: 52342d2d80ea8faadd0ffa76d83b6051f20c5329
......
......@@ -22,7 +22,7 @@
"lint:fix": "npx eslint . --ext .js,.jsx,.ts,.tsx --fix --ignore-path .gitignore",
"start": "NODE_OPTIONS=--max_old_space_size=8192 npx react-native start",
"unit": "jest --config ./test/unit/jest.config.js",
"unit:debug": "node --inspect node_modules/.bin/jest --watch --runInBand",
"unit:debug": "node --inspect node_modules/.bin/jest --config ./test/unit/jest.config.js --watch --runInBand",
"test-rust": "cd ./rust/signer && cargo test && cd ../..",
"build-e2e:android": "detox build -c android.emu.debug -l info",
"test-e2e:android": "detox test -c android.emu.debug -l info --noStackTrace",
......@@ -37,11 +37,11 @@
}
},
"dependencies": {
"@polkadot/api": "1.6.0-beta.1",
"@polkadot/reactnative-identicon": "0.51.1",
"@polkadot/types": "1.6.0-beta.1",
"@polkadot/util": "2.6.1",
"@polkadot/util-crypto": "2.6.1",
"@polkadot/api": "1.9.0-beta.8",
"@polkadot/reactnative-identicon": "0.52.0-beta.20",
"@polkadot/types": "1.9.0-beta.8",
"@polkadot/util": "2.7.0-beta.8",
"@polkadot/util-crypto": "2.7.0-beta.8",
"@react-native-community/masked-view": "^0.1.6",
"@react-native-community/netinfo": "^4.1.5",
"@react-navigation/native": "^5.0.10",
......@@ -51,7 +51,7 @@
"node-libs-react-native": "^1.0.3",
"react": "^16.9.0",
"react-native": "0.61.5",
"react-native-camera": "^3.4.0",
"react-native-camera": "^3.21.0",
"react-native-elements": "^1.2.6",
"react-native-gesture-handler": "^1.6.0",
"react-native-keyboard-aware-scroll-view": "^0.9.1",
......@@ -98,7 +98,7 @@
"jest": "^25.1.0",
"jetifier": "^1.6.4",
"metro-react-native-babel-preset": "^0.56.0",
"prettier": "1.19.1",
"prettier": "2.0.2",
"react-native-safe-area-context": "^0.6.4",
"react-native-typescript-transformer": "^1.2.13",
"react-test-renderer": "16.9.0",
......
const pathLib = require('path');
module.exports = function({ types: t }) {
module.exports = function ({ types: t }) {
return {
name: 'rewrite node global __dirname',
visitor: {
Identifier: function(path, state) {
Identifier: function (path, state) {
if (path.node.name === '__dirname') {
const fallbackPath = `${state.cwd}/node_modules/@polkadot`;
const fileName = state.file.opts.filename;
......
......@@ -127,10 +127,7 @@ export default class AccountSeed extends Component<
<TouchableItem
key={i}
onPress={(): void => {
let phrase = left
.concat(suggestion, right)
.join(' ')
.trimEnd();
let phrase = left.concat(suggestion, right).join(' ').trimEnd();
const is24words = phrase.split(' ').length === 24;
if (!is24words) phrase += ' ';
this.props.onChangeText(phrase);
......
......@@ -14,11 +14,7 @@
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import {
TypeRegistry,
Metadata,
GenericExtrinsicPayload
} from '@polkadot/types';
import { GenericExtrinsicPayload } from '@polkadot/types';
import Call from '@polkadot/types/generic/Call';
import { formatBalance } from '@polkadot/util';
import { decodeAddress, encodeAddress } from '@polkadot/util-crypto';
......@@ -27,16 +23,225 @@ import { StyleSheet, Text, View, ViewStyle } from 'react-native';
import { AnyU8a, IExtrinsicEra, IMethod } from '@polkadot/types/types';
import { ExtrinsicEra } from '@polkadot/types/interfaces';
import RegistriesStore from 'stores/RegistriesStore';
import colors from 'styles/colors';
import { SUBSTRATE_NETWORK_LIST } from 'constants/networkSpecs';
import { getMetadata } from 'utils/identitiesUtils';
import { withRegistriesStore } from 'utils/HOC';
import { shortString } from 'utils/strings';
import fontStyles from 'styles/fontStyles';
import { alertDecodeError } from 'utils/alertUtils';
const registry = new TypeRegistry();
const recodeAddress = (encodedAddress: string, prefix: number): string =>
encodeAddress(decodeAddress(encodedAddress), prefix);
type ExtrinsicPartProps = {
fallback?: string;
label: string;
networkKey: string;
registriesStore: RegistriesStore;
value: AnyU8a | IMethod | IExtrinsicEra;
};
interface Props {
const ExtrinsicPart = withRegistriesStore<ExtrinsicPartProps>(
({
fallback,
label,
networkKey,
registriesStore,
value
}: ExtrinsicPartProps): React.ReactElement => {
const [period, setPeriod] = useState<string>();
const [phase, setPhase] = useState<string>();
const [formattedCallArgs, setFormattedCallArgs] = useState<any>();
const [tip, setTip] = useState<string>();
const [useFallback, setUseFallBack] = useState(false);
const prefix = SUBSTRATE_NETWORK_LIST[networkKey].prefix;
useEffect(() => {
if (label === 'Method' && !fallback) {
try {
const registry = registriesStore.get(networkKey);
const call = registry.createType('Call', value);
const methodArgs = {};
function formatArgs(
callInstance: Call,
callMethodArgs: any,
depth: number
): void {
const { args, meta, methodName, sectionName } = callInstance;
const paramArgKvArray = [];
if (!meta.args.length) {
const sectionMethod = `${sectionName}.${methodName}`;
callMethodArgs[sectionMethod] = null;
return;
}
for (let i = 0; i < meta.args.length; i++) {
let argument;
if (
args[i].toRawType() === 'Balance' ||
args[i].toRawType() === 'Compact<Balance>'
) {
argument = formatBalance(args[i].toString());
} else if (
args[i].toRawType() === 'Address' ||
args[i].toRawType() === 'AccountId'
) {
// encode Address and AccountId to the appropriate prefix
argument = recodeAddress(args[i].toString(), prefix);
} else if (args[i] instanceof Call) {
argument = formatArgs(args[i] as Call, callMethodArgs, depth++); // go deeper into the nested calls
} else if (
args[i].toRawType() === 'Vec<AccountId>' ||
args[i].toRawType() === 'Vec<Address>'
) {
argument = (args[i] as any).map((v: any) =>
recodeAddress(v.toString(), prefix)
);
} else {
argument = args[i].toString();
}
const param = meta.args[i].name.toString();
const sectionMethod = `${sectionName}.${methodName}`;
paramArgKvArray.push([param, argument]);
callMethodArgs[sectionMethod] = paramArgKvArray;
}
}
formatArgs(call, methodArgs, 0);
setFormattedCallArgs(methodArgs);
} catch (e) {
alertDecodeError();
setUseFallBack(true);
}
}
if (label === 'Era' && !fallback) {
if ((value as ExtrinsicEra).isMortalEra) {
setPeriod((value as ExtrinsicEra).asMortalEra.period.toString());
setPhase((value as ExtrinsicEra).asMortalEra.phase.toString());
}
}
if (label === 'Tip' && !fallback) {
setTip(formatBalance(value as any));
}
}, [fallback, label, prefix, value, networkKey, registriesStore]);
const renderEraDetails = (): React.ReactElement => {
if (period && phase) {
return (
<View style={styles.era}>
<Text style={{ ...styles.subLabel, flex: 1 }}>phase: {phase} </Text>
<Text style={{ ...styles.subLabel, flex: 1 }}>
period: {period}
</Text>
</View>
);
} else {
return (
<View
style={{
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap'
}}
>
<Text style={{ ...styles.subLabel, flex: 1 }}>Immortal Era</Text>
<Text style={{ ...styles.secondaryText, flex: 3 }}>
{value.toString()}
</Text>
</View>
);
}
};
type ArgsList = Array<[string, any]>;
type MethodCall = [string, ArgsList];
type FormattedArgs = Array<MethodCall>;
const renderMethodDetails = (): React.ReactNode => {
if (formattedCallArgs) {
const formattedArgs: FormattedArgs = Object.entries(formattedCallArgs);
// HACK: if there's a sudo method just put it to the front. Better way would be to order by depth but currently this is only relevant for a single extrinsic, so seems like overkill.
for (let i = 1; i < formattedArgs.length; i++) {
if (formattedArgs[i][0].includes('sudo')) {
const tmp = formattedArgs[i];
formattedArgs.splice(i, 1);
formattedArgs.unshift(tmp);
break;
}
}
return formattedArgs.map((entry, index) => {
const sectionMethod = entry[0];
const paramArgs: Array<[any, any]> = entry[1];
return (
<View key={index} style={styles.callDetails}>
<Text style={styles.subLabel}>
Call <Text style={styles.titleText}>{sectionMethod}</Text> with
the following arguments:
</Text>
{paramArgs ? (
paramArgs.map(([param, arg]) => (
<View key={param} style={styles.callDetails}>
<Text style={styles.titleText}>
{' { '}
{param}:{' '}
{arg && arg.length > 50
? shortString(arg)
: arg instanceof Array
? arg.join(', ')
: arg}{' '}
{'}'}
</Text>
</View>
))
) : (
<Text style={styles.secondaryText}>
This method takes 0 arguments.
</Text>
)}
</View>
);
});
}
};
const renderTipDetails = (): React.ReactElement => {
return (
<View style={{ display: 'flex', flexDirection: 'column' }}>
<Text style={styles.secondaryText}>{tip}</Text>
</View>
);
};
return (
<View style={[{ alignItems: 'baseline', justifyContent: 'flex-start' }]}>
<View style={{ marginBottom: 12, width: '100%' }}>
<Text style={styles.label}>{label}</Text>
{label === 'Method' && !useFallback ? (
renderMethodDetails()
) : label === 'Era' ? (
renderEraDetails()
) : label === 'Tip' ? (
renderTipDetails()
) : (
<Text style={styles.secondaryText}>
{useFallback ? value.toString() : value}
</Text>
)}
</View>
</View>
);
}
);
interface PayloadDetailsCardProps {
description?: string;
payload?: GenericExtrinsicPayload;
signature?: string;
......@@ -45,31 +250,28 @@ interface Props {
}
export default class PayloadDetailsCard extends React.PureComponent<
Props,
PayloadDetailsCardProps,
{
fallback: boolean;
}
> {
constructor(props: Props) {
constructor(props: PayloadDetailsCardProps) {
super(props);
const { networkKey } = this.props;
const networkMetadataRaw = getMetadata(networkKey);
const metadata = new Metadata(registry, networkMetadataRaw);
registry.setMetadata(metadata);
formatBalance.setDefaults({
decimals: SUBSTRATE_NETWORK_LIST[networkKey].decimals,
unit: SUBSTRATE_NETWORK_LIST[networkKey].unit
});
const isKnownNetworkKey = SUBSTRATE_NETWORK_LIST.hasOwnProperty(networkKey);
this.state = {
fallback: !metadata
fallback: !isKnownNetworkKey
};
}
render(): React.ReactElement {
const { fallback } = this.state;
const { description, payload, networkKey, signature, style } = this.props;
const prefix = SUBSTRATE_NETWORK_LIST[networkKey].prefix;
return (
<View style={[styles.body, style]}>
......@@ -78,32 +280,32 @@ export default class PayloadDetailsCard extends React.PureComponent<
<View style={styles.extrinsicContainer}>
<ExtrinsicPart
label="Method"
prefix={prefix}
networkKey={networkKey}
value={fallback ? payload.method.toString() : payload.method}
/>
<ExtrinsicPart
label="Block Hash"
prefix={prefix}
networkKey={networkKey}
value={payload.blockHash.toString()}
/>
<ExtrinsicPart
label="Era"
prefix={prefix}
networkKey={networkKey}
value={fallback ? payload.era.toString() : payload.era}
/>
<ExtrinsicPart
label="Nonce"
prefix={prefix}
networkKey={networkKey}
value={payload.nonce.toString()}
/>
<ExtrinsicPart
label="Tip"
prefix={prefix}
networkKey={networkKey}
value={payload.tip.toString()}
/>
<ExtrinsicPart
label="Genesis Hash"
prefix={prefix}
networkKey={networkKey}
value={payload.genesisHash.toString()}
/>
</View>
......@@ -119,206 +321,6 @@ export default class PayloadDetailsCard extends React.PureComponent<
}
}
const recodeAddress = (encodedAddress: string, prefix: number): string =>
encodeAddress(decodeAddress(encodedAddress), prefix);
function ExtrinsicPart({
label,
fallback,
prefix,
value
}: {
label: string;
prefix: number;
value: AnyU8a | IMethod | IExtrinsicEra;
fallback?: string;
}): React.ReactElement {
const [period, setPeriod] = useState<string>();
const [phase, setPhase] = useState<string>();
const [formattedCallArgs, setFormattedCallArgs] = useState<any>();
const [tip, setTip] = useState<string>();
const [useFallback, setUseFallBack] = useState(false);
useEffect(() => {
if (label === 'Method' && !fallback) {
try {
const call = new Call(registry, value);
const methodArgs = {};
function formatArgs(
callInstance: Call,
callMethodArgs: any,
depth: number
): void {
const { args, meta, methodName, sectionName } = callInstance;
const paramArgKvArray = [];
if (!meta.args.length) {
const sectionMethod = `${sectionName}.${methodName}`;
callMethodArgs[sectionMethod] = null;
return;
}
for (let i = 0; i < meta.args.length; i++) {
let argument;
if (
args[i].toRawType() === 'Balance' ||
args[i].toRawType() === 'Compact<Balance>'
) {
argument = formatBalance(args[i].toString());
} else if (
args[i].toRawType() === 'Address' ||
args[i].toRawType() === 'AccountId'
) {
// encode Address and AccountId to the appropriate prefix
argument = recodeAddress(args[i].toString(), prefix);
} else if (args[i] instanceof Call) {
argument = formatArgs(args[i] as Call, callMethodArgs, depth++); // go deeper into the nested calls
} else if (
args[i].toRawType() === 'Vec<AccountId>' ||
args[i].toRawType() === 'Vec<Address>'
) {
argument = (args[i] as any).map((v: any) =>
recodeAddress(v.toString(), prefix)
);
} else {
argument = args[i].toString();
}
const param = meta.args[i].name.toString();
const sectionMethod = `${sectionName}.${methodName}`;
paramArgKvArray.push([param, argument]);
callMethodArgs[sectionMethod] = paramArgKvArray;
}
}
formatArgs(call, methodArgs, 0);
setFormattedCallArgs(methodArgs);
} catch (e) {
alertDecodeError();
setUseFallBack(true);
}
}
if (label === 'Era' && !fallback) {
if ((value as ExtrinsicEra).isMortalEra) {
setPeriod((value as ExtrinsicEra).asMortalEra.period.toString());
setPhase((value as ExtrinsicEra).asMortalEra.phase.toString());
}
}
if (label === 'Tip' && !fallback) {
setTip(formatBalance(value as any));
}
}, [fallback, label, prefix, value]);
const renderEraDetails = (): React.ReactElement => {
if (period && phase) {
return (
<View style={styles.era}>
<Text style={{ ...styles.subLabel, flex: 1 }}>phase: {phase} </Text>
<Text style={{ ...styles.subLabel, flex: 1 }}>period: {period}</Text>
</View>
);
} else {
return (
<View
style={{
display: 'flex',
flexDirection: 'row',
flexWrap: 'wrap'
}}
>
<Text style={{ ...styles.subLabel, flex: 1 }}>Immortal Era</Text>
<Text style={{ ...styles.secondaryText, flex: 3 }}>
{value.toString()}
</Text>
</View>
);
}
};
type ArgsList = Array<[string, any]>;
type MethodCall = [string, ArgsList];
type FormattedArgs = Array<MethodCall>;
const renderMethodDetails = (): React.ReactNode => {
if (formattedCallArgs) {
const formattedArgs: FormattedArgs = Object.entries(formattedCallArgs);
// HACK: if there's a sudo method just put it to the front. Better way would be to order by depth but currently this is only relevant for a single extrinsic, so seems like overkill.
for (let i = 1; i < formattedArgs.length; i++) {
if (formattedArgs[i][0].includes('sudo')) {
const tmp = formattedArgs[i];
formattedArgs.splice(i, 1);
formattedArgs.unshift(tmp);
break;
}
}
return formattedArgs.map((entry, index) => {
const sectionMethod = entry[0];
const paramArgs: Array<[any, any]> = entry[1];
return (
<View key={index} style={styles.callDetails}>
<Text style={styles.subLabel}>
Call <Text style={styles.titleText}>{sectionMethod}</Text> with
the following arguments:
</Text>
{paramArgs ? (
paramArgs.map(([param, arg]) => (
<View key={param} style={styles.callDetails}>