Merge pull request #190 from cosmos/fix/validate-microcoin

Validate macrocoin, allow empty funds and IBC
This commit is contained in:
Abel Fernández 2024-02-08 12:37:28 +01:00 committed by GitHub
commit 85cd22523e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 164 additions and 59 deletions

View File

@ -20,7 +20,7 @@ const TxMsgCreateVestingAccountDetails = ({ msgValue }: TxMsgCreateVestingAccoun
</li>
<li>
<label>Amount:</label>
<div>{printableCoins(msgValue.amount, chain)}</div>
<div>{printableCoins(msgValue.amount, chain) || "None"}</div>
</li>
<li>
<label>From:</label>

View File

@ -43,7 +43,7 @@ const TxMsgExecuteContractDetails = ({ msgValue }: TxMsgExecuteContractDetailsPr
</li>
<li>
<label>Funds:</label>
<div>{printableCoins(msgValue.funds, chain)}</div>
<div>{printableCoins(msgValue.funds, chain) || "None"}</div>
</li>
{parseError ? (
<li className="parse-error">

View File

@ -59,7 +59,7 @@ const TxMsgInstantiateContract2Details = ({ msgValue }: TxMsgInstantiateContract
</li>
<li>
<label>Funds:</label>
<div>{printableCoins(msgValue.funds, chain)}</div>
<div>{printableCoins(msgValue.funds, chain) || "None"}</div>
</li>
{parseError ? (
<li className="parse-error">

View File

@ -55,7 +55,7 @@ const TxMsgInstantiateContractDetails = ({ msgValue }: TxMsgInstantiateContractD
</li>
<li>
<label>Funds:</label>
<div>{printableCoins(msgValue.funds, chain)}</div>
<div>{printableCoins(msgValue.funds, chain) || "None"}</div>
</li>
{parseError ? (
<li className="parse-error">

View File

@ -17,7 +17,7 @@ const TxMsgSendDetails = ({ msgValue }: TxMsgSendDetailsProps) => {
</li>
<li>
<label>Amount:</label>
<div>{printableCoins(msgValue.amount, chain)}</div>
<div>{printableCoins(msgValue.amount, chain) || "None"}</div>
</li>
<li>
<label>To:</label>

View File

@ -68,7 +68,7 @@ const TransactionInfo = ({ tx }: TransactionInfoProps) => {
</li>
<li>
<label>Fee:</label>
<div>{printableCoins(tx.fee.amount, chain)}</div>
<div>{printableCoins(tx.fee.amount, chain) || "None"}</div>
</li>
</>
) : null}

View File

@ -2,7 +2,7 @@ import { EncodeObject } from "@cosmjs/proto-signing";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { macroCoinToMicroCoin } from "../../../../lib/coinHelpers";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import {
datetimeLocalFromTimestamp,
timestampFromDatetimeLocal,
@ -58,6 +58,13 @@ const MsgCreateVestingAccountForm = ({
return false;
}
try {
displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
const timeoutDate = new Date(Number(timestampFromDatetimeLocal(endTime, "ms")));
if (timeoutDate <= new Date()) {
setEndTimeError("End time must be a date in the future");
@ -69,16 +76,20 @@ const MsgCreateVestingAccountForm = ({
const microCoin = (() => {
try {
return macroCoinToMicroCoin({ denom: chain.displayDenom, amount }, chain.assets);
if (!amount || amount === "0") {
return null;
}
return displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch {
return { denom: chain.displayDenom, amount: "0" };
return null;
}
})();
const msgValue = MsgCodecs[MsgTypeUrls.CreateVestingAccount].fromPartial({
fromAddress,
toAddress,
amount: [microCoin],
amount: microCoin ? [microCoin] : [],
endTime: timestampFromDatetimeLocal(endTime, "s"),
delayed,
});

View File

@ -2,7 +2,7 @@ import { MsgDelegateEncodeObject } from "@cosmjs/stargate";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { macroCoinToMicroCoin } from "../../../../lib/coinHelpers";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
@ -46,12 +46,19 @@ const MsgDelegateForm = ({ delegatorAddress, setMsgGetter, deleteMsg }: MsgDeleg
return false;
}
try {
displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
return true;
};
const microCoin = (() => {
try {
return macroCoinToMicroCoin({ denom: chain.displayDenom, amount }, chain.assets);
return displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch {
return { denom: chain.displayDenom, amount: "0" };
}

View File

@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { ChainInfo } from "../../../../context/ChainsContext/types";
import { macroCoinToMicroCoin } from "../../../../lib/coinHelpers";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
@ -55,6 +55,8 @@ const MsgExecuteContractForm = ({
useEffect(() => {
// eslint-disable-next-line no-shadow
const { contractAddress, customDenom, amount } = trimmedInputs;
const denom =
selectedDenom.value === customDenomOption.value ? customDenom : selectedDenom.value.symbol;
const isMsgValid = (): boolean => {
setContractAddressError("");
@ -71,7 +73,12 @@ const MsgExecuteContractForm = ({
return false;
}
if (selectedDenom.value === customDenomOption.value && !customDenom) {
if (
selectedDenom.value === customDenomOption.value &&
!customDenom &&
amount &&
amount !== "0"
) {
setCustomDenomError("Custom denom must be set because of selection above");
return false;
}
@ -86,17 +93,27 @@ const MsgExecuteContractForm = ({
return false;
}
if (denom && amount) {
try {
displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
}
return true;
};
const denom =
selectedDenom.value === customDenomOption.value ? customDenom : selectedDenom.value.symbol;
const microCoin = (() => {
try {
return macroCoinToMicroCoin({ denom, amount }, chain.assets);
if (!denom || !amount || amount === "0") {
return null;
}
return displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch {
return { denom, amount: "0" };
return null;
}
})();
@ -113,7 +130,7 @@ const MsgExecuteContractForm = ({
sender: fromAddress,
contract: contractAddress,
msg: msgContentUtf8Array,
funds: [microCoin],
funds: microCoin ? [microCoin] : [],
});
const msg: MsgExecuteContractEncodeObject = { typeUrl: MsgTypeUrls.Execute, value: msgValue };

View File

@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { ChainInfo } from "../../../../context/ChainsContext/types";
import { macroCoinToMicroCoin } from "../../../../lib/coinHelpers";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
@ -101,7 +101,12 @@ const MsgInstantiateContract2Form = ({
return false;
}
if (selectedDenom.value === customDenomOption.value && !customDenom) {
if (
selectedDenom.value === customDenomOption.value &&
!customDenom &&
amount &&
amount !== "0"
) {
setCustomDenomError("Custom denom must be set because of selection above");
return false;
}
@ -116,6 +121,13 @@ const MsgInstantiateContract2Form = ({
return false;
}
try {
displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
return true;
};
@ -124,9 +136,13 @@ const MsgInstantiateContract2Form = ({
const microCoin = (() => {
try {
return macroCoinToMicroCoin({ denom, amount }, chain.assets);
if (!denom || !amount || amount === "0") {
return null;
}
return displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch {
return { denom, amount: "0" };
return null;
}
})();
@ -155,7 +171,7 @@ const MsgInstantiateContract2Form = ({
fixMsg: false,
salt: hexSalt,
msg: msgContentUtf8Array,
funds: [microCoin],
funds: microCoin ? [microCoin] : [],
});
const msg: MsgInstantiateContract2EncodeObject = {

View File

@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { ChainInfo } from "../../../../context/ChainsContext/types";
import { macroCoinToMicroCoin } from "../../../../lib/coinHelpers";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
@ -87,7 +87,12 @@ const MsgInstantiateContractForm = ({
return false;
}
if (selectedDenom.value === customDenomOption.value && !customDenom) {
if (
selectedDenom.value === customDenomOption.value &&
!customDenom &&
amount &&
amount !== "0"
) {
setCustomDenomError("Custom denom must be set because of selection above");
return false;
}
@ -102,6 +107,13 @@ const MsgInstantiateContractForm = ({
return false;
}
try {
displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
return true;
};
@ -110,9 +122,13 @@ const MsgInstantiateContractForm = ({
const microCoin = (() => {
try {
return macroCoinToMicroCoin({ denom, amount }, chain.assets);
if (!denom || !amount || amount === "0") {
return null;
}
return displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch {
return { denom, amount: "0" };
return null;
}
})();
@ -131,7 +147,7 @@ const MsgInstantiateContractForm = ({
label,
admin: adminAddress,
msg: msgContentUtf8Array,
funds: [microCoin],
funds: microCoin ? [microCoin] : [],
});
const msg: MsgInstantiateContractEncodeObject = {

View File

@ -2,7 +2,7 @@ import { EncodeObject } from "@cosmjs/proto-signing";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { macroCoinToMicroCoin } from "../../../../lib/coinHelpers";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
@ -61,12 +61,19 @@ const MsgRedelegateForm = ({
return false;
}
try {
displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
return true;
};
const microCoin = (() => {
try {
return macroCoinToMicroCoin({ denom: chain.displayDenom, amount }, chain.assets);
return displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch {
return { denom: chain.displayDenom, amount: "0" };
}

View File

@ -2,7 +2,7 @@ import { MsgSendEncodeObject } from "@cosmjs/stargate";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { macroCoinToMicroCoin } from "../../../../lib/coinHelpers";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { RegistryAsset } from "../../../../types/chainRegistry";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
@ -57,7 +57,12 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps)
return false;
}
if (selectedDenom.value === customDenomOption.value && !customDenom) {
if (
selectedDenom.value === customDenomOption.value &&
!customDenom &&
amount &&
amount !== "0"
) {
setCustomDenomError("Custom denom must be set because of selection above");
return false;
}
@ -72,6 +77,13 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps)
return false;
}
try {
displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
return true;
};
@ -80,16 +92,20 @@ const MsgSendForm = ({ fromAddress, setMsgGetter, deleteMsg }: MsgSendFormProps)
const microCoin = (() => {
try {
return macroCoinToMicroCoin({ denom, amount }, chain.assets);
if (!denom || !amount || amount === "0") {
return null;
}
return displayCoinToBaseCoin({ denom, amount }, chain.assets);
} catch {
return { denom, amount: "0" };
return null;
}
})();
const msgValue = MsgCodecs[MsgTypeUrls.Send].fromPartial({
fromAddress,
toAddress,
amount: [microCoin],
amount: microCoin ? [microCoin] : [],
});
const msg: MsgSendEncodeObject = { typeUrl: MsgTypeUrls.Send, value: msgValue };

View File

@ -2,7 +2,7 @@ import { MsgUndelegateEncodeObject } from "@cosmjs/stargate";
import { useEffect, useState } from "react";
import { MsgGetter } from "..";
import { useChains } from "../../../../context/ChainsContext";
import { macroCoinToMicroCoin } from "../../../../lib/coinHelpers";
import { displayCoinToBaseCoin } from "../../../../lib/coinHelpers";
import { checkAddress, exampleAddress, trimStringsObj } from "../../../../lib/displayHelpers";
import { MsgCodecs, MsgTypeUrls } from "../../../../types/txMsg";
import Input from "../../../inputs/Input";
@ -50,12 +50,19 @@ const MsgUndelegateForm = ({
return false;
}
try {
displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch (e: unknown) {
setAmountError(e instanceof Error ? e.message : "Could not set decimals");
return false;
}
return true;
};
const microCoin = (() => {
try {
return macroCoinToMicroCoin({ denom: chain.displayDenom, amount }, chain.assets);
return displayCoinToBaseCoin({ denom: chain.displayDenom, amount }, chain.assets);
} catch {
return { denom: chain.displayDenom, amount: "0" };
}

View File

@ -1,5 +1,5 @@
import { RegistryAsset } from "../types/chainRegistry";
import { macroCoinToMicroCoin } from "./coinHelpers";
import { displayCoinToBaseCoin } from "./coinHelpers";
const assets: readonly RegistryAsset[] = [
{
@ -75,70 +75,70 @@ const assets: readonly RegistryAsset[] = [
describe("macroCoinToMicroCoin", () => {
it("works with symbol", () => {
expect(macroCoinToMicroCoin({ denom: "JUNOX", amount: "12" }, assets)).toEqual({
expect(displayCoinToBaseCoin({ denom: "JUNOX", amount: "12" }, assets)).toEqual({
denom: "ujunox",
amount: "12000000",
});
expect(macroCoinToMicroCoin({ denom: "CHEQ", amount: "34" }, assets)).toEqual({
expect(displayCoinToBaseCoin({ denom: "CHEQ", amount: "34" }, assets)).toEqual({
denom: "ncheq",
amount: "34000000000",
});
expect(macroCoinToMicroCoin({ denom: "arUSD", amount: "56" }, assets)).toEqual({
expect(displayCoinToBaseCoin({ denom: "arUSD", amount: "56" }, assets)).toEqual({
denom: "erc20/0x2Cbea61fdfDFA520Ee99700F104D5b75ADf50B0c",
amount: "56000000000000000000",
});
expect(macroCoinToMicroCoin({ denom: "ETH", amount: "78" }, assets)).toEqual({
expect(displayCoinToBaseCoin({ denom: "ETH", amount: "78" }, assets)).toEqual({
denom: "wei",
amount: "78000000000000000000",
});
});
it("works with base unit", () => {
expect(macroCoinToMicroCoin({ denom: "ujunox", amount: "12" }, assets)).toEqual({
expect(displayCoinToBaseCoin({ denom: "ujunox", amount: "12" }, assets)).toEqual({
denom: "ujunox",
amount: "12",
});
expect(macroCoinToMicroCoin({ denom: "ncheq", amount: "34" }, assets)).toEqual({
expect(displayCoinToBaseCoin({ denom: "ncheq", amount: "34" }, assets)).toEqual({
denom: "ncheq",
amount: "34",
});
expect(
macroCoinToMicroCoin(
displayCoinToBaseCoin(
{ denom: "erc20/0x2Cbea61fdfDFA520Ee99700F104D5b75ADf50B0c", amount: "56" },
assets,
),
).toEqual({ denom: "erc20/0x2Cbea61fdfDFA520Ee99700F104D5b75ADf50B0c", amount: "56" });
expect(macroCoinToMicroCoin({ denom: "wei", amount: "78" }, assets)).toEqual({
expect(displayCoinToBaseCoin({ denom: "wei", amount: "78" }, assets)).toEqual({
denom: "wei",
amount: "78",
});
});
it("works with biggest unit", () => {
expect(macroCoinToMicroCoin({ denom: "junox", amount: "12" }, assets)).toEqual({
expect(displayCoinToBaseCoin({ denom: "junox", amount: "12" }, assets)).toEqual({
denom: "ujunox",
amount: "12000000",
});
expect(macroCoinToMicroCoin({ denom: "cheq", amount: "34" }, assets)).toEqual({
expect(displayCoinToBaseCoin({ denom: "cheq", amount: "34" }, assets)).toEqual({
denom: "ncheq",
amount: "34000000000",
});
expect(macroCoinToMicroCoin({ denom: "arusd", amount: "56" }, assets)).toEqual({
expect(displayCoinToBaseCoin({ denom: "arusd", amount: "56" }, assets)).toEqual({
denom: "erc20/0x2Cbea61fdfDFA520Ee99700F104D5b75ADf50B0c",
amount: "56000000000000000000",
});
expect(macroCoinToMicroCoin({ denom: "eth", amount: "78" }, assets)).toEqual({
expect(displayCoinToBaseCoin({ denom: "eth", amount: "78" }, assets)).toEqual({
denom: "wei",
amount: "78000000000000000000",
});
});
it("works with intermediate unit", () => {
expect(macroCoinToMicroCoin({ denom: "gwei", amount: "78" }, assets)).toEqual({
expect(displayCoinToBaseCoin({ denom: "gwei", amount: "78" }, assets)).toEqual({
denom: "wei",
amount: "78000000000",
});
expect(macroCoinToMicroCoin({ denom: "GWEI", amount: "78" }, assets)).toEqual({
expect(displayCoinToBaseCoin({ denom: "GWEI", amount: "78" }, assets)).toEqual({
denom: "wei",
amount: "78000000000",
});

View File

@ -3,8 +3,8 @@ import { Coin } from "@cosmjs/stargate";
import { assert } from "@cosmjs/utils";
import { RegistryAsset } from "../types/chainRegistry";
const macroCoinToMicroCoin = (macroCoin: Coin, assets: readonly RegistryAsset[]): Coin => {
const lowerCaseDenom = macroCoin.denom.toLowerCase();
const displayCoinToBaseCoin = (displayCoin: Coin, assets: readonly RegistryAsset[]): Coin => {
const lowerCaseDenom = displayCoin.denom.toLowerCase();
const asset = assets.find(
(currentAsset) =>
@ -17,7 +17,12 @@ const macroCoinToMicroCoin = (macroCoin: Coin, assets: readonly RegistryAsset[])
),
);
assert(asset, `An asset with the given symbol ${macroCoin.denom} was not found`);
// Leave IBC coins as is if not found on registry assets
if (!asset && displayCoin.denom.toLowerCase().startsWith("ibc/")) {
return displayCoin;
}
assert(asset, `An asset with the given symbol ${displayCoin.denom} was not found`);
const macroUnit = asset.denom_units.find(
(currentUnit) => lowerCaseDenom === currentUnit.denom.toLowerCase(),
@ -28,9 +33,12 @@ const macroCoinToMicroCoin = (macroCoin: Coin, assets: readonly RegistryAsset[])
assert(baseUnit, `A base unit with exponent = 0 was not found`);
const denom = baseUnit.denom;
const amount = Decimal.fromUserInput(macroCoin.amount.trim() || "0", macroUnit.exponent).atomics;
const amount = Decimal.fromUserInput(
displayCoin.amount.trim() || "0",
macroUnit.exponent,
).atomics;
return { denom, amount };
};
export { macroCoinToMicroCoin };
export { displayCoinToBaseCoin };