Skip to content

Commit

Permalink
Merge pull request #2404 from Emurgo/release/4.7.300
Browse files Browse the repository at this point in the history
Release / 4.7.300
  • Loading branch information
vsubhuman committed Sep 22, 2021
2 parents e34d2c6 + ef65039 commit 054cbf9
Show file tree
Hide file tree
Showing 41 changed files with 998 additions and 127 deletions.
5 changes: 5 additions & 0 deletions packages/yoroi-extension/app/Routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import type { GeneratedData as WalletData } from './containers/wallet/Wallet';
import MyWalletsPage from './containers/wallet/MyWalletsPage';
import WalletSummaryPage from './containers/wallet/WalletSummaryPage';
import WalletSendPage from './containers/wallet/WalletSendPage';
import WalletAssetsPage from './containers/wallet/WalletAssetsPage';
import WalletReceivePage from './containers/wallet/WalletReceivePage';
import URILandingPage from './containers/uri/URILandingPage';
import Transfer from './containers/transfer/Transfer';
Expand Down Expand Up @@ -156,6 +157,10 @@ const WalletsSubpages = (stores, actions) => (
path={ROUTES.WALLETS.SEND}
component={(props) => <WalletSendPage {...props} stores={stores} actions={actions} />}
/>
<Route
path={ROUTES.WALLETS.ASSETS}
component={(props) => <WalletAssetsPage {...props} stores={stores} actions={actions} />}
/>
<Route
path={ROUTES.WALLETS.RECEIVE.ROOT}
component={(props) => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@
import typeof * as WasmV2 from 'cardano-wallet-browser';
import typeof * as WasmV3 from '@emurgo/js-chain-libs/js_chain_libs';
import typeof * as WasmV4 from '@emurgo/cardano-serialization-lib-browser/cardano_serialization_lib';
import type { BigNum, LinearFee, TransactionBuilder } from '@emurgo/cardano-serialization-lib-browser/cardano_serialization_lib';
import typeof * as SigmaRust from 'ergo-lib-wasm-browser';

// TODO: unmagic the constants
const MAX_VALUE_BYTES = 5000;
const MAX_TX_BYTES = 16384;

class Module {
_wasmv2: WasmV2;
_wasmv3: WasmV3;
Expand Down Expand Up @@ -32,6 +37,24 @@ class Module {
return this._wasmv4;
}
// Need to expose through a getter to get Flow to detect the type correctly
WalletV4TxBuilder(
linearFee: LinearFee,
minimumUtxoVal: BigNum,
poolDeposit: BigNum,
keyDeposit: BigNum,
maxValueBytes: number = MAX_VALUE_BYTES,
maxTxBytes: number = MAX_TX_BYTES,
): TransactionBuilder {
return this.WalletV4.TransactionBuilder.new(
linearFee,
minimumUtxoVal,
poolDeposit,
keyDeposit,
maxValueBytes,
maxTxBytes,
);
}
// Need to expose through a getter to get Flow to detect the type correctly
get SigmaRust(): SigmaRust {
return this._ergo;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,10 @@ import { ISignRequest } from '../../../common/lib/transactions/ISignRequest';
import type { CardanoAddressedUtxo } from '../types';
import { RustModule } from '../../lib/cardanoCrypto/rustLoader';
import { toHexOrBase58 } from '../../lib/storage/bridge/utils';
import {
MultiToken,
} from '../../../common/lib/MultiToken';
import { MultiToken, } from '../../../common/lib/MultiToken';
import { PRIMARY_ASSET_CONSTANTS } from '../../lib/storage/database/primitives/enums';
import { multiTokenFromCardanoValue, multiTokenFromRemote } from '../utils';
import type {
Address, Value, Addressing,
} from '../../lib/storage/models/PublicDeriver/interfaces';
import type { Address, Addressing, Value, } from '../../lib/storage/models/PublicDeriver/interfaces';

/**
* We take a copy of these parameters instead of re-evaluating them from the network
Expand Down Expand Up @@ -94,6 +90,13 @@ implements ISignRequest<RustModule.WalletV4.TransactionBuilder> {
).to_bytes()).toString('hex');
}

size(): {| full: number, outputs: number[] |} {
return {
full: this.unsignedTx.full_size(),
outputs: [...this.unsignedTx.output_sizes()],
};
}

inputs(): Array<{|
address: string,
value: MultiToken,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ test('Create Ledger transaction', async () => {
assets: [],
}];
const protocolParams = getProtocolParams();
const txBuilder = RustModule.WalletV4.TransactionBuilder.new(
const txBuilder = RustModule.WalletV4TxBuilder(
protocolParams.linearFee,
protocolParams.minimumUtxoVal,
protocolParams.poolDeposit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
NotEnoughMoneyToSendError,
AssetOverflowError,
NoOutputsError,
CannotSendBelowMinimumValueError,
} from '../../../common/errors';

import { RustModule } from '../../lib/cardanoCrypto/rustLoader';
Expand Down Expand Up @@ -232,7 +233,7 @@ export function sendAllUnsignedTxFromUtxo(
throw new NotEnoughMoneyToSendError();
}

const txBuilder = RustModule.WalletV4.TransactionBuilder.new(
const txBuilder = RustModule.WalletV4TxBuilder(
protocolParams.linearFee,
protocolParams.minimumUtxoVal,
protocolParams.poolDeposit,
Expand Down Expand Up @@ -269,10 +270,20 @@ export function sendAllUnsignedTxFromUtxo(
throw new Error(`${nameof(sendAllUnsignedTxFromUtxo)} receiver not a valid Shelley address`);
}

// semantically, sending all ADA to somebody
// is the same as if you're sending all the ADA as change to yourself
// (module the fact the address doesn't belong to you)
const couldSendAmount = txBuilder.add_change_if_needed(wasmReceiver);
let couldSendAmount = false;
try {
// semantically, sending all ADA to somebody
// is the same as if you're sending all the ADA as change to yourself
// (module the fact the address doesn't belong to you)
couldSendAmount = txBuilder.add_change_if_needed(wasmReceiver);
} catch (e) {
if (!String(e).includes('Not enough ADA')) {
// any other error except not-enough-ada terminates here
// eslint-disable-next-line no-console
console.error('Failed to construct send-all output!', e);
throw e;
}
}
if (!couldSendAmount) {
// if you couldn't send any amount,
// it's because you couldn't cover the fee of adding an output
Expand Down Expand Up @@ -434,6 +445,63 @@ export function newAdaUnsignedTxFromUtxo(
allowNoOutputs: boolean,
metadata: RustModule.WalletV4.AuxiliaryData | void,
): V4UnsignedTxUtxoResponse {

const outputAssets = outputs.reduce((set, o) => {
o.amount.values
.map(v => v.identifier)
.filter(id => id.length > 0)
.forEach(id => set.add(id));
return set;
}, new Set<string>());
const isAssetsRequired = outputAssets.size > 0;

const utxosMapped: Array<[RemoteUnspentOutput, boolean, boolean, number]> =
utxos.map((u: RemoteUnspentOutput) => {
if (u.assets.length === 0) {
return [u, true, false, 0];
}
const hasRequiredAsset = isAssetsRequired
&& u.assets.some(a => outputAssets.has(a.assetId));
const amount = RustModule.WalletV4.BigNum.from_str(u.amount);
const minRequired = RustModule.WalletV4.min_ada_required(
cardanoValueFromRemoteFormat(u),
protocolParams.minimumUtxoVal,
);
const spendable = parseInt(amount.clamped_sub(minRequired).to_str(), 10);
// Round down the spendable value to the nearest full ADA for safer deposit
// TODO: unmagic the constant
return [u, false, hasRequiredAsset, Math.floor(spendable / 1_000_000) * 1_000_000];
});

const utxosFiltered: Array<[RemoteUnspentOutput, boolean, boolean, number]> = utxosMapped
.filter(([, isPure, hasRequiredAsset, spendableValue]) =>
isPure || hasRequiredAsset || (spendableValue > 0));

// prioritize inputs
const sortedUtxos: Array<RemoteUnspentOutput> = utxosFiltered.sort((v1, v2) => {
const [, isPure1, hasRequiredAsset1, spendableValue1] = v1;
const [, isPure2, hasRequiredAsset2, spendableValue2] = v2;
// $FlowFixMe[unsafe-addition]
if (hasRequiredAsset1 ^ hasRequiredAsset2) {
// one but not both of the utxos has required assets
// utxos with required assets are always prioritized
// ahead of any other, pure or dirty
return hasRequiredAsset1 ? -1 : 1;
}
if (isPure1 || isPure2) {
// at least one of the utxos is clean
if (isPure1 && isPure2) {
// both utxos are clean - randomize them
return Math.random() - 0.5;
}
// The clean utxo is prioritized
return isPure1 ? -1 : 1;
}
// both utxos are dirty
// dirty utxos with highest spendable ADA are prioritised
return spendableValue2 - spendableValue1;
}).map(([u]) => u);

/*
This is an ad-hoc optimization for one specific senario:
If the input is enough to cover the output and the fee, but the remaining amount
Expand All @@ -446,7 +514,7 @@ export function newAdaUnsignedTxFromUtxo(
const result = _newAdaUnsignedTxFromUtxo(
outputs,
changeAdaAddr,
utxos,
sortedUtxos,
absSlotNumber,
protocolParams,
certificates,
Expand All @@ -460,7 +528,7 @@ export function newAdaUnsignedTxFromUtxo(
const resultWithOneExtraInput = _newAdaUnsignedTxFromUtxo(
outputs,
changeAdaAddr,
utxos,
sortedUtxos,
absSlotNumber,
protocolParams,
certificates,
Expand Down Expand Up @@ -527,7 +595,7 @@ function _newAdaUnsignedTxFromUtxo(
const emptyAsset = RustModule.WalletV4.MultiAsset.new();
shouldForceChange(undefined);

const txBuilder = RustModule.WalletV4.TransactionBuilder.new(
const txBuilder = RustModule.WalletV4TxBuilder(
protocolParams.linearFee,
protocolParams.minimumUtxoVal,
protocolParams.poolDeposit,
Expand Down Expand Up @@ -563,12 +631,19 @@ function _newAdaUnsignedTxFromUtxo(
if (wasmReceiver == null) {
throw new Error(`${nameof(newAdaUnsignedTxFromUtxo)} receiver not a valid Shelley address`);
}
txBuilder.add_output(
RustModule.WalletV4.TransactionOutput.new(
wasmReceiver,
cardanoValueFromMultiToken(output.amount),
)
);
try {
txBuilder.add_output(
RustModule.WalletV4.TransactionOutput.new(
wasmReceiver,
cardanoValueFromMultiToken(output.amount),
)
);
} catch (e) {
if (String(e).includes('less than the minimum UTXO value')) {
throw new CannotSendBelowMinimumValueError();
}
throw e;
}
}
}

Expand All @@ -591,14 +666,16 @@ function _newAdaUnsignedTxFromUtxo(
break;
}
const currentInputSum = txBuilder.get_explicit_input().checked_add(implicitSum);
const output = targetOutput
const neededInput = targetOutput
.checked_add(RustModule.WalletV4.Value.new(txBuilder.min_fee()));
const remainingNeeded = output.clamped_sub(currentInputSum);
const excessiveInputAssets = currentInputSum.multiasset()
?.sub(neededInput.multiasset() ?? emptyAsset);

const remainingNeeded = neededInput.clamped_sub(currentInputSum);
// update amount required to make sure we have ADA required for change UTXO entry
if (shouldForceChange(currentInputSum.multiasset()?.sub(output.multiasset() ?? emptyAsset))) {
if (shouldForceChange(excessiveInputAssets)) {
if (changeAdaAddr == null) throw new NoOutputsError();
const difference = currentInputSum.clamped_sub(output);
const difference = currentInputSum.clamped_sub(neededInput);
const minimumNeededForChange = minRequiredForChange(
txBuilder,
changeAdaAddr,
Expand All @@ -612,13 +689,12 @@ function _newAdaUnsignedTxFromUtxo(
}

// stop if we've added all the assets we needed
const isNonEmptyInputs = usedUtxos.length > 0;
{
const remainingAssets = remainingNeeded.multiasset();
if (
remainingNeeded.coin().compare(RustModule.WalletV4.BigNum.from_str('0')) === 0 &&
(remainingAssets == null || remainingAssets.len() === 0) &&
usedUtxos.length > 0
) {
const isRemainingNeededCoinZero = isBigNumZero(remainingNeeded.coin());
const isRemainingNeededAssetZero = (remainingAssets?.len() ?? 0) === 0;
if (isRemainingNeededCoinZero && isRemainingNeededAssetZero && isNonEmptyInputs) {
if (oneExtraInput) {
// We've added all the assets we need, but we add one extra.
// Set the flag so that the adding loop stops after this extra one is added.
Expand All @@ -635,7 +711,7 @@ function _newAdaUnsignedTxFromUtxo(
undefined : // avoid 'NO_NEED'
{
value: remainingNeeded,
hasInput: usedUtxos.length > 0,
hasInput: isNonEmptyInputs,
},
utxo,
true,
Expand Down Expand Up @@ -708,7 +784,17 @@ function _newAdaUnsignedTxFromUtxo(
if (wasmChange == null) {
throw new Error(`${nameof(newAdaUnsignedTxFromUtxo)} change not a valid Shelley address`);
}
const changeWasAdded = txBuilder.add_change_if_needed(wasmChange);
let changeWasAdded: boolean;
try {
changeWasAdded = txBuilder.add_change_if_needed(wasmChange);
} catch (e) {
if (String(e).includes('Not enough ADA')) {
throw new NotEnoughMoneyToSendError();
}
// eslint-disable-next-line no-console
console.error('Failed to construct tx change!', e);
throw e;
}
if (forceChange && !changeWasAdded) {
// note: this should never happened since it should have been handled by earlier code
throw new Error(`No change added even though it should be forced`);
Expand Down Expand Up @@ -880,7 +966,7 @@ export function genFilterSmallUtxo(request: {|
|}): (
RemoteUnspentOutput => boolean
) {
const txBuilder = RustModule.WalletV4.TransactionBuilder.new(
const txBuilder = RustModule.WalletV4TxBuilder(
request.protocolParams.linearFee,
// no need for the following parameters just to calculate the fee of adding a UTXO
RustModule.WalletV4.BigNum.from_str('0'),
Expand All @@ -904,3 +990,7 @@ export function genFilterSmallUtxo(request: {|
return feeForInput.lte(utxo.amount);
};
}

function isBigNumZero(b: RustModule.WalletV4.BigNum): boolean {
return b.compare(RustModule.WalletV4.BigNum.zero()) === 0;
}

0 comments on commit 054cbf9

Please sign in to comment.