Skip to content

Commit

Permalink
Merge pull request #2170 from Emurgo/ergo-connector-minted-assets-sig…
Browse files Browse the repository at this point in the history
…n-tx-fix

Ergo Connector: Fix crash when signing a TX that mints new assets
  • Loading branch information
vsubhuman committed Jun 16, 2021
2 parents 8a7e1a6 + 9dcc7bd commit 44a3f68
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 4 deletions.
3 changes: 3 additions & 0 deletions packages/yoroi-ergo-connector/example/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,13 @@ function initDapp() {
new wasm.Tokens());
console.log(`boxes selected: ${boxSelection.boxes().len()}`);
const outputCandidates = wasm.ErgoBoxCandidates.empty();
const token = new wasm.Token(wasm.TokenId.from_box_id(wasm.BoxId.from_str(utxos[1].boxId)), wasm.TokenAmount.from_i64(wasm.I64.from_str("12345678")));
const donationBoxBuilder = new wasm.ErgoBoxCandidateBuilder(
amountToSendBoxValue,
wasm.Contract.pay_to_address(wasm.Address.from_base58(donationAddr)),
creationHeight);
donationBoxBuilder.mint_token(token, "ECEDT", "Ergo Connector Example Dapp Token (for testing)", 5);
//donationBoxBuilder.add_token(token.id(), token.amount());
try {
outputCandidates.add(donationBoxBuilder.build());
} catch (e) {
Expand Down
36 changes: 34 additions & 2 deletions packages/yoroi-ergo-connector/example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,11 @@ import ExplorableHashContainer from '../../../containers/widgets/ExplorableHashC
import { SelectedExplorer } from '../../../domain/SelectedExplorer';
import { calculateAndFormatValue } from '../../../utils/unit-of-account';
import classnames from 'classnames';
import { mintedTokenInfo } from '../../../../chrome/extension/ergo-connector/utils';
import type { Tx } from '../../../../chrome/extension/ergo-connector/types';

type Props = {|
+tx: Tx,
+txData: ISignRequest<any>,
+onCopyAddressTooltip: (string, string) => void,
+onCancel: () => void,
Expand Down Expand Up @@ -122,10 +125,21 @@ class SignTxPage extends Component<Props> {
return undefined;
}

// Tokens can be minted inside the transaction so we have to look it up there first
_resolveTokenInfo: TokenEntry => $ReadOnly<TokenRow> = tokenEntry => {
const { tx } = this.props;
const mintedTokens = mintedTokenInfo(tx);
const mintedToken = mintedTokens.find(t => tokenEntry.identifier === t.Identifier);
if (mintedToken != null) {
return mintedToken;
}
return this.props.getTokenInfo(tokenEntry);
}

renderAmountDisplay: {|
entry: TokenEntry,
|} => Node = (request) => {
const tokenInfo = this.props.getTokenInfo(request.entry);
const tokenInfo = this._resolveTokenInfo(request.entry);
const shiftedAmount = request.entry.amount
.shiftedBy(-tokenInfo.Metadata.numberOfDecimals);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export default class SignTxContainer extends Component<
? null
: uiNotifications.getTooltipActiveNotification(this.notificationElementId)
}
tx={signingMessage.sign.tx}
txData={txData}
getTokenInfo={genLookupOrFail(this.generated.stores.tokenInfoStore.tokenInfo)}
defaultToken={selectedWallet.publicDeriver.getParent().getDefaultToken()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import type { ISignRequest } from '../../api/common/lib/transactions/ISignReques
import { ErgoExternalTxSignRequest } from '../../api/ergo/lib/transactions/ErgoExternalTxSignRequest';
import { RustModule } from '../../api/ada/lib/cardanoCrypto/rustLoader';
import { toRemoteUtxo } from '../../api/ergo/lib/transactions/utils';
import { mintedTokenInfo } from '../../../chrome/extension/ergo-connector/utils';

// Need to run only once - Connecting wallets
let initedConnecting = false;
Expand Down Expand Up @@ -283,6 +284,8 @@ export default class ConnectorStore extends Store<StoresMap, ActionsMap> {

if (!signingMessage.sign.tx) return;
const { tx } = signingMessage.sign;
// it's possible we minted assets in this tx, so looking them up will fail
const mintedTokenIds = mintedTokenInfo(tx).map(t => t.Identifier);
const tokenIdentifiers = Array.from(new Set([
...tx.inputs
.flatMap(output => output.assets)
Expand All @@ -292,7 +295,7 @@ export default class ConnectorStore extends Store<StoresMap, ActionsMap> {
.map(asset => asset.tokenId),
// force inclusion of primary token for chain
selectedWallet.getParent().getDefaultToken().defaultIdentifier
]));
])).filter(id => !mintedTokenIds.includes(id));
const stateFetcher = this.stores.substores.ergo.stateFetchStore.fetcher;
await addErgoAssets({
db: selectedWallet.getDb(),
Expand Down
66 changes: 66 additions & 0 deletions packages/yoroi-extension/chrome/extension/ergo-connector/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// @flow

import type { Tx } from './types';
import type { TokenRow } from '../../../app/api/ada/lib/storage/database/primitives/tables';
import { Logger } from '../../../app/utils/logging';

function parseEIP0004Data(input: any): ?string {
// https://github.com/ergoplatform/eips/blob/master/eip-0004.md
// format is: 0e + vlq(len(body as bytes)) + body (as bytes formatted in hex)
// where body is a utf8 string
// and the vlq encoding is also byte-padded e.g. 01 instead of 1
if (typeof input !== 'string' || !input.startsWith('0e') || input.length < 4) {
return null
}
let body = input.slice(2);
let len = 0;
let readNext = true;
do {
const lenChunk = parseInt(body.slice(0, 2), 16);
body = body.slice(2);
if (isNaN(lenChunk)) {
return null;
}
readNext = (lenChunk & 0x80) !== 0;
len = (128 * len) + (lenChunk & 0x7F);
} while (readNext);
if (2 * len > body.length) {
Logger.info(`vlq decode trailing data: ${body.slice(2 * len)}`);
}
if (2 * len < body.length) {
return null;
}
return Buffer.from(body.slice(0, 2 * len), 'hex').toString('utf8');
}

// you should not be able to mint more than 1
export function mintedTokenInfo(tx: Tx): $ReadOnly<TokenRow>[] {
const tokens = []
for (const output of tx.outputs) {
const name = parseEIP0004Data(output.additionalRegisters.R4);
const description = parseEIP0004Data(output.additionalRegisters.R5);
const decimals = parseEIP0004Data(output.additionalRegisters.R6);
if (name != null && description != null && decimals != null) {
tokens.push({
TokenId: 0,
NetworkId: 0,
IsDefault: false,
Digest: 0,
Identifier: tx.inputs[0].boxId,
Metadata: {
type: 'Ergo',
height: tx.inputs[0].creationHeight,
boxId: tx.inputs[0].boxId,
numberOfDecimals: parseInt(decimals, 10),
ticker: name,
longName: description,
description,
}
});
}
}
if (tokens.length > 1) {
Logger.info(`tx ${JSON.stringify(tx)} had multiple EIP-0004-looking outputs`);
}
return tokens;
}

0 comments on commit 44a3f68

Please sign in to comment.