Skip to content

Commit

Permalink
Merge pull request #3744 from Emurgo/release/5.4.400
Browse files Browse the repository at this point in the history
Release / 5.4.400
  • Loading branch information
vsubhuman authored Nov 20, 2024
2 parents d5e95bb + 998b589 commit 7e53687
Show file tree
Hide file tree
Showing 37 changed files with 1,152 additions and 412 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/flow-and-lint.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Flow and lint
name: Flow and Lint and TSC

on:
workflow_dispatch:
Expand Down Expand Up @@ -43,4 +43,7 @@ jobs:
npm run flow
- name: lint
run: |
npm run eslint
npm run eslint
- name: tsc
run: |
npm run tsc
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,18 @@ const AccordionDetails = styled(MuiAccordionDetails)(({ theme }: any) => ({
type Props = {
title: string;
content: React.ReactNode;
expanded?: boolean,
};

export const Collapsible = ({ title, content }: Props) => {
const [expanded, setExpanded] = React.useState<any>('none');
export const Collapsible = ({ title, content, expanded: startExpanded }: Props) => {
const [expanded, setExpanded] = React.useState<boolean>(startExpanded ?? false);

const handleChange = (panel: string | false) => (_: React.SyntheticEvent, newExpanded: boolean) => {
setExpanded(newExpanded ? panel : false);
const handleChange = (_: React.SyntheticEvent, newExpanded: boolean) => {
setExpanded(newExpanded);
};

return (
<Accordion disableGutters elevation={0} square expanded={expanded === 'panel1'} onChange={handleChange('panel1')}>
<Accordion disableGutters elevation={0} square expanded={expanded} onChange={handleChange}>
<AccordionSummary
aria-controls="panel1d-content"
id="panel1d-header"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,22 @@ import { FormattedMessage } from 'react-intl';
import globalMessages from '../../../i18n/global-messages';
import { useNavigateTo } from '../../features/governace/common/useNavigateTo';
import { FailedIlustration } from './FailedIlustration';
import LocalizableError from '../../../i18n/LocalizableError';

export const TransactionFailed = () => {
export const TransactionFailed = (props: { error: Error | null }) => {
const navigate = useNavigateTo();
const { error } = props;

return (
<Stack alignItems="center" mt="110px">
<FailedIlustration />
<Typography variant="h5" fontWeight="500" mt={4} mb={1}>
<FormattedMessage {...globalMessages.transactionFailed} />
</Typography>
<Typography variant="body1" mb={2} color="ds.text_gray_low">
<FormattedMessage {...globalMessages.transactionFailedInfo} />
<FormattedMessage {...(
error instanceof LocalizableError ? error : globalMessages.transactionFailedInfo
)}/>
</Typography>
{/* @ts-ignore */}
<Button variant="primary" onClick={() => navigate.selectStatus()}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const messages = Object.freeze(
},
drepId: {
id: 'governance.drepId',
defaultMessage: '!!!Drep ID (Fingerprint):',
defaultMessage: '!!!Drep ID (CIP 129):',
},
delegateToDRep: {
id: 'governance.delegateToDRep',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { GovernanceApi } from '@emurgo/yoroi-lib/dist/governance/emurgo-api';
import { bech32 } from 'bech32';
import * as React from 'react';

import { RustModule } from '../../../../api/ada/lib/cardanoCrypto/rustLoader';
import { dRepNormalize } from '../../../../api/ada/lib/cardanoCrypto/utils';
import { unwrapStakingKey } from '../../../../api/ada/lib/storage/bridge/utils';
import { getPrivateStakingKey } from '../../../../api/thunk';
import { DREP_ALWAYS_ABSTAIN, DREP_ALWAYS_NO_CONFIDENCE } from '../common/constants';
import { getFormattedPairingValue } from '../common/helpers';
import { useGovernanceManagerMaker } from '../common/useGovernanceManagerMaker';
import { GovernanceActionType, GovernanceReducer, defaultGovernanceActions, defaultGovernanceState } from './state';
import {hexToBytes} from "../../../tsCoreUtils";

type drepDelegation = { status: string | null; drep: string | null };

Expand Down Expand Up @@ -123,14 +122,14 @@ export const GovernanceContextProvider = ({
});

const governanceStatusState: any = await govApi.getAccountState(stakingKeyHex || '', stakingKeyHex || '');
const { drep, drepKind } = governanceStatusState?.drepDelegation ?? {};

if (governanceStatusState && governanceStatusState.drepDelegation?.drep === 'abstain') {
if (drep === 'abstain') {
setGovernanceStatus({ status: DREP_ALWAYS_ABSTAIN, drep: null });
} else if (governanceStatusState && governanceStatusState.drepDelegation?.drep === 'no_confidence') {
} else if (drep === 'no_confidence') {
setGovernanceStatus({ status: DREP_ALWAYS_NO_CONFIDENCE, drep: null });
} else if (governanceStatusState !== null && governanceStatusState.drepDelegation?.drep.length > 0) {
const words = bech32.toWords(hexToBytes(governanceStatusState.drepDelegation?.drep));
const encoded = bech32.encode('drep', words, 64);
} else if (drep?.length > 0) {
const encoded = dRepNormalize(drep, drepKind);
setGovernanceStatus({ status: 'delegate', drep: encoded || null });
} else {
setGovernanceStatus({ status: 'none', drep: null });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import { genFormatTokenAmount, genLookupOrFail } from '../../../../../stores/sta
import { Collapsible } from '../../../../components/Collapsible/Collapsible';
import { PasswordInput } from '../../../../components/Input/PasswordInput';
import { DREP_ALWAYS_ABSTAIN, DREP_ALWAYS_NO_CONFIDENCE } from '../../common/constants';
import { dRepNormalize, dRepToPreCip129 } from '../../../../../api/ada/lib/cardanoCrypto/utils';
import { useNavigateTo } from '../../common/useNavigateTo';
import { useStrings } from '../../common/useStrings';
import { useGovernance } from '../../module/GovernanceContextProvider';
import { mapStatus } from '../SelectGovernanceStatus/GovernanceStatusSelection';
import { maybe } from '../../../../../coreUtils'

const Container = styled(Box)(() => ({
paddingTop: '23px',
Expand Down Expand Up @@ -74,9 +76,7 @@ export const DelagationForm = () => {
const strings = useStrings();
const confirmDelegation = async () => {
const response = await checkUserPassword(password);
if (isHardwareWallet) {
signGovernanceTx();
} else if (response?.name === 'WrongPassphraseError') {
if (response?.name === 'WrongPassphraseError') {
setIsIncorectPassword(true);
} else {
signGovernanceTx();
Expand Down Expand Up @@ -111,6 +111,10 @@ export const DelagationForm = () => {
setIsIncorectPassword(false);
}, [password]);

const specifiedDrep = governanceVote.drepID;
const normalizedDrep = maybe(specifiedDrep, dRepNormalize);
const preCip129Drep = maybe(specifiedDrep, dRepToPreCip129);

return (
<Container>
<Stack>
Expand Down Expand Up @@ -138,13 +142,32 @@ export const DelagationForm = () => {
</Typography>
<Box mb="40px">
<Collapsible
expanded={isHardwareWallet}
title={strings.operations}
content={
<TransactionDetails>
{governanceVote.kind === 'delegate' && (
<OperationInfo label={`Delegate voting to ${governanceVote.drepID}`} fee={txFee} />
<OperationInfo label={(
<>
<Typography variant="body1" color="ds.text_gray_medium">
Delegate voting to DRep (CIP 129): {normalizedDrep}
</Typography>
{specifiedDrep !== normalizedDrep ? (
<Typography variant="body1" color="ds.text_gray_medium">
Specified as: {specifiedDrep}
</Typography>
) : null}
{isHardwareWallet && (preCip129Drep !== specifiedDrep) ? (
<Typography variant="body1" color="ds.text_gray_medium">
On a Hardware device this DRep ID might be displayed in old format: {preCip129Drep}
</Typography>
) : null}
</>
)} fee={txFee} />
)}
{governanceVote.kind === DREP_ALWAYS_ABSTAIN && (
<OperationInfo label={strings.selectAbstein} fee={txFee} />
)}
{governanceVote.kind === DREP_ALWAYS_ABSTAIN && <OperationInfo label={strings.selectAbstein} fee={txFee} />}
{governanceVote.kind === DREP_ALWAYS_NO_CONFIDENCE && (
<OperationInfo label={strings.selectNoConfidence} fee={txFee} />
)}
Expand Down Expand Up @@ -186,16 +209,18 @@ export const DelagationForm = () => {
};

type OperationInfoProps = {
label: string;
label: string | JSX.Element;
fee: string;
};

const OperationInfo = ({ label, fee }: OperationInfoProps) => {
return (
<>
<Typography variant="body1" color="ds.text_gray_medium">
{label}
</Typography>
{typeof label === 'string' ? (
<Typography variant="body1" color="ds.text_gray_medium">
{label}
</Typography>
) : label}
<Stack direction="row" justifyContent="space-between">
<Typography variant="body1" fontWeight="500" color="ds.text_gray_normal">
Transaction fee
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type Props = {
const GovernanceTransactionFailedPage = (props: Props): any => {
return (
<GovernanceLayout {...props}>
<TransactionFailed />
<TransactionFailed error={props.stores.substores.ada.delegationTransaction.error} />
</GovernanceLayout>
);
};
Expand Down
52 changes: 50 additions & 2 deletions packages/yoroi-extension/app/api/ada/lib/cardanoCrypto/utils.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// @flow

import { RustModule } from './rustLoader';
import { bytesToHex, hexToBytes, maybe } from '../../../../coreUtils';
import { base32ToHex } from '../storage/bridge/utils';
import { bytesToHex, fail, forceNonNull, hexToBytes, maybe } from '../../../../coreUtils';
import { base32ToHex, hexToBase32 } from '../storage/bridge/utils';

export function v4PublicToV2(
v4Key: RustModule.WalletV4.Bip32PublicKey
Expand Down Expand Up @@ -58,8 +58,14 @@ export function dRepToMaybeCredentialHex(s: string): ?string {
try {
if (s.startsWith('drep1')) {
if (s.length === 58) {
// CIP129 drep1 encoding is extended value with internal prefix
return maybe(base32ToHex(s), dRepToMaybeCredentialHex);
}
// Pre CIP129 drep1 encoding means same as drep_vkh1 now
return Module.WalletV4.Credential
.from_keyhash(Module.WalletV4.Ed25519KeyHash.from_bech32(s)).to_hex();
}
if (s.startsWith('drep_vkh1')) {
return Module.WalletV4.Credential
.from_keyhash(Module.WalletV4.Ed25519KeyHash.from_bech32(s)).to_hex();
}
Expand All @@ -80,6 +86,48 @@ export function dRepToMaybeCredentialHex(s: string): ?string {
})
}

function parseDrep(drep: string): ?{| hash: string, isScript: boolean |} {
const credentialHex = dRepToMaybeCredentialHex(drep);
if (!credentialHex) {
return null;
}
return RustModule.WasmScope(Module => {
const cred = Module.WalletV4.Credential.from_hex(credentialHex);
const isScript = cred.kind() === Module.WalletV4.CredKind.Script;
const hash = isScript ?
forceNonNull(cred.to_scripthash()).to_hex()
: forceNonNull(cred.to_keyhash()).to_hex();
return { hash, isScript };
})
}

export function dRepNormalize(drep: string, kind?: string): string {
function encodeDrepHash(hash: string, isScript: boolean): string {
// cip129 prefix
const prefix = isScript ? '23' : '22';
return hexToBase32(prefix + hash, 'drep');
}
if (kind != null) {
// drep is hash hex
return encodeDrepHash(drep, kind === 'scripthash');
}
if (drep.startsWith('drep1') && drep.length === 58) {
// drep already cip129
return drep;
}
return maybe(parseDrep(drep), r => encodeDrepHash(r.hash, r.isScript))
?? fail('Failed to normalize drep: ' + drep + ' | kind: ' + String(kind));
}

export function dRepToPreCip129(drep: string): string {
if ((drep.startsWith('drep1') && drep.length < 58) || drep.startsWith('drep_script1')) {
// drep already pre cip129 compatible
return drep;
}
return maybe(parseDrep(drep), r => hexToBase32(r.hash, r.isScript ? 'drep_script' : 'drep'))
?? fail('Failed to normalize drep: ' + drep);
}

export function pubKeyHashToRewardAddress(hex: string, network: number): string {
return RustModule.WasmScope(Module =>
Module.WalletV4.RewardAddress.new(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -551,34 +551,32 @@ export class RemoteFetcher implements IFetcher {

getUtxoData: GetUtxoDataRequest => Promise<GetUtxoDataResponse> = async (body) => {
const { BackendService } = body.network.Backend;
if (body.utxos.length !== 1) {
throw new Error('the RemoteFetcher.getUtxoData expects 1 UTXO');
}
const { txHash, txIndex } = body.utxos[0];
if (BackendService == null) throw new Error(`${nameof(this.getUtxoData)} missing backend url`);
return axios(
`${BackendService}/api/txs/io/${txHash}/o/${txIndex}`,
{
method: 'get',
timeout: 2 * CONFIG.app.walletRefreshInterval,
headers: {
'yoroi-version': this.getLastLaunchVersion(),
'yoroi-locale': this.getCurrentLocale()
}
}
).then(response => [ response.data ])
.catch((error) => {
if (error.response.status === 404 && error.response.data === 'Transaction not found') {
return [ null ];
return Promise.all(body.utxos.map(({ txHash, txIndex }) => {
return axios(
`${BackendService}/api/txs/io/${txHash}/o/${txIndex}`,
{
method: 'get',
timeout: 2 * CONFIG.app.walletRefreshInterval,
headers: {
'yoroi-version': this.getLastLaunchVersion(),
'yoroi-locale': this.getCurrentLocale()
}
}
Logger.error(`${nameof(RemoteFetcher)}::${nameof(this.getUtxoData)} error: ` + stringifyError(error));
throw new GetUtxoDataError();
});
).then(response => response.data)
.catch((error) => {
if (error.response.status === 404 && error.response.data === 'No outputs found') {
return null;
}
Logger.error(`${nameof(RemoteFetcher)}::${nameof(this.getUtxoData)} error: ` + stringifyError(error));
throw new GetUtxoDataError();
});
}));
}

getLatestBlockBySlot: GetLatestBlockBySlotFunc = async (body) => {
const { BackendService } = body.network.Backend;
if (BackendService == null) throw new Error(`${nameof(this.getUtxoData)} missing backend url`);
if (BackendService == null) throw new Error(`${nameof(this.getLatestBlockBySlot)} missing backend url`);
return axios(
`${BackendService}/api/v2.1/lastBlockBySlot`,
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,10 @@ export const base32ToHex = (base32: string): ?string => {
return maybe(base32Words?.words, convertBase32ToHex);
};

export const hexToBase32 = (hex: string, prefix: string): string => {
return bech32Module.encode(prefix, bech32Module.toWords(hexToBytes(hex)));
};

/* eslint-disable */
const bigIntToBase58 = n => {
const base58Alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
Expand Down
Loading

0 comments on commit 7e53687

Please sign in to comment.