diff --git a/modules/account-lib/src/index.ts b/modules/account-lib/src/index.ts index 3ead0b6831..a420a3ce08 100644 --- a/modules/account-lib/src/index.ts +++ b/modules/account-lib/src/index.ts @@ -166,10 +166,10 @@ const coinBuilderMap = { tinjective: Injective.TransactionBuilderFactory, zeta: Zeta.TransactionBuilderFactory, tzeta: Zeta.TransactionBuilderFactory, - core: Core.TransactionBuilderFactory, - tcore: Core.TransactionBuilderFactory, islm: Islm.TransactionBuilderFactory, tislm: Islm.TransactionBuilderFactory, + core: Core.TransactionBuilderFactory, + tcore: Core.TransactionBuilderFactory, }; /** diff --git a/modules/sdk-coin-islm/test/resources/islm.ts b/modules/sdk-coin-islm/test/resources/islm.ts new file mode 100644 index 0000000000..0b97a77c5e --- /dev/null +++ b/modules/sdk-coin-islm/test/resources/islm.ts @@ -0,0 +1,233 @@ +// Get the test data by running the scripts for the particular coin from coin-sandbox repo. + +export const TEST_ACCOUNT = { + pubAddress: 'haqq1z34mxtp9xd0tv89cfyvc9r4cckfqt3gs2a0xh5', + compressedPublicKey: '029ae62ba5168f4a7e4a48ce9ec85b6a0ba1c7cbb676a95ff993f8b0df74a461df', + compressedPublicKeyTwo: '031dabb2069dfa614b9457ce9fc84c2a7b3b6f5e3cbb57c633380c5f4fcfd1486a', + uncompressedPublicKey: + '049ae62ba5168f4a7e4a48ce9ec85b6a0ba1c7cbb676a95ff993f8b0df74a461df371b136a9be5dbc0e233ad70a733a37beeb41398df69376326c9207368255a6a', + privateKey: '2bc15c7ea4881524469f94e053429d92b89f60d1efb5048258291052ae0f70ff', + extendedPrv: + 'xprv9s21ZrQH143K2xS8TWuFksLBHRRc994e166knSrbtBYVLpXDNkEF1DX45MvAdW97nV9FsrzmPtPCcg8dMxzFBeS2SZ1pWHC2k8xLMuTv673', + extendedPub: + 'xpub661MyMwAqRbcFSWbZYSG81GuqTG6YbnVNK2MaqGDSX5UDcrMvHYVZ1qXvdnt6EZWFXMwerL8ySqJvj91855WUxiRcvDyVTwTYDhJ3JYnC8f', +}; + +export const TEST_SEND_TX = { + hash: '2679D297CD41346707EF8116D1691078199FEB8134D1EECAE5894B618D45AC84', + signature: 'Edqy59rfNqIrgmBcHh4j6vsLIl9MeOldeVy/a1aa0yw3163yQBtqvV9Esn3rpwAWNzN6sBFjB00Of3VYdqoxLQ==', + pubKey: 'AyO0EwzBxj7YzTOmNb9TcIGyEvrdfAlsM2LkyFb6FWzj', + privateKey: '3xn43lnhB0RxwYkJ2vW4UeBPPOcb947WYsYuqsOzXa0=', + signedTxBase64: + 'Co4BCosBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmsKK2hhcXExc2V5bXdyOHpzNmM5MnJjZnl6NmpsYTRhNTBkcm13Z3E5dHp2bHoSK2hhcXExOXAycm52emFoeHFoOXlrcmozcnFtamRndjBtejZlZm5qbWR6YTcaDwoFYUlTTE0SBjEwMDAwMBJ8ClkKTwooL2V0aGVybWludC5jcnlwdG8udjEuZXRoc2VjcDI1NmsxLlB1YktleRIjCiEDI7QTDMHGPtjNM6Y1v1NwgbIS+t18CWwzYuTIVvoVbOMSBAoCCAEYGBIfChkKBWFJU0xNEhA0MDAwMDAwMDAwMDAwMDAwEMCaDBpAEdqy59rfNqIrgmBcHh4j6vsLIl9MeOldeVy/a1aa0yw3163yQBtqvV9Esn3rpwAWNzN6sBFjB00Of3VYdqoxLQ==', + sender: 'haqq1seymwr8zs6c92rcfyz6jla4a50drmwgq9tzvlz', + recipient: 'haqq19p2rnvzahxqh9ykrj3rqmjdgv0mz6efnjmdza7', + chainId: 'haqq_54211-3', + accountNumber: 167676, + sequence: 24, + sendAmount: '100000', + feeAmount: '4000000000000000', + sendMessage: { + typeUrl: '/cosmos.bank.v1beta1.MsgSend', + value: { + fromAddress: 'haqq1seymwr8zs6c92rcfyz6jla4a50drmwgq9tzvlz', + toAddress: 'haqq19p2rnvzahxqh9ykrj3rqmjdgv0mz6efnjmdza7', + amount: [ + { + denom: 'aISLM', + amount: '100000', + }, + ], + }, + }, + gasBudget: { + amount: [ + { + denom: 'aISLM', + amount: '4000000000000000', + }, + ], + gasLimit: 200000, + }, +}; + +export const TEST_DELEGATE_TX = { + hash: 'F7E8933B59F514A0EEF66434C2438B639D208435071CC6F09BD07E229A19A262', + signature: 'Bd9eww9Nh2WlH6vI8pPkD667hDXh/ErfjNkESsP5EokkUp/qbTaz0VWRXG6fJSbsY0iWhtonlnZqKCijAYqPgQ==', + pubKey: 'AyO0EwzBxj7YzTOmNb9TcIGyEvrdfAlsM2LkyFb6FWzj', + privateKey: '3xn43lnhB0RxwYkJ2vW4UeBPPOcb947WYsYuqsOzXa0=', + signedTxBase64: + 'CpwBCpkBCiMvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dEZWxlZ2F0ZRJyCitoYXFxMXNleW13cjh6czZjOTJyY2Z5ejZqbGE0YTUwZHJtd2dxOXR6dmx6EjJoYXFxdmFsb3BlcjE4eGEyZTJ6NG5kbWdlZjlhN2M3bGowMGV0dXR3MmRhZnl5aGRreBoPCgVhSVNMTRIGMTAwMDAwEnwKWQpPCigvZXRoZXJtaW50LmNyeXB0by52MS5ldGhzZWNwMjU2azEuUHViS2V5EiMKIQMjtBMMwcY+2M0zpjW/U3CBshL63XwJbDNi5MhW+hVs4xIECgIIARgREh8KGQoFYUlTTE0SEDQwMDAwMDAwMDAwMDAwMDAQwJoMGkAF317DD02HZaUfq8jyk+QPrruENeH8St+M2QRKw/kSiSRSn+ptNrPRVZFcbp8lJuxjSJaG2ieWdmooKKMBio+B', + delegator: 'haqq1seymwr8zs6c92rcfyz6jla4a50drmwgq9tzvlz', + validator: 'haqqvaloper18xa2e2z4ndmgef9a7c7lj00etutw2dafyyhdkx', + chainId: 'haqq_54211-3', + accountNumber: 167676, + sequence: 17, + sendAmount: '100000', + feeAmount: '4000000000000000', + sendMessage: { + typeUrl: '/cosmos.staking.v1beta1.MsgDelegate', + value: { + delegatorAddress: 'haqq1seymwr8zs6c92rcfyz6jla4a50drmwgq9tzvlz', + validatorAddress: 'haqqvaloper18xa2e2z4ndmgef9a7c7lj00etutw2dafyyhdkx', + amount: { + denom: 'aISLM', + amount: '100000', + }, + }, + }, + gasBudget: { + amount: [ + { + denom: 'aISLM', + amount: '4000000000000000', + }, + ], + gasLimit: 200000, + }, +}; + +export const TEST_UNDELEGATE_TX = { + hash: 'CCBD92831278103E852E8FB0C038717EA2B122A4C59BFEDC76E2BEE5F4F128BE', + signature: 'fB8iHcGdjlb67Si9D68LK/jKsHPVkm7+Xcv+Hv+akG0RHSVrQvy2XowSFjfTwqt16Je7RWlLvfyfL/wq2Y9M6w==', + pubKey: 'AyO0EwzBxj7YzTOmNb9TcIGyEvrdfAlsM2LkyFb6FWzj', + privateKey: '3xn43lnhB0RxwYkJ2vW4UeBPPOcb947WYsYuqsOzXa0=', + signedTxBase64: + 'Cp4BCpsBCiUvY29zbW9zLnN0YWtpbmcudjFiZXRhMS5Nc2dVbmRlbGVnYXRlEnIKK2hhcXExc2V5bXdyOHpzNmM5MnJjZnl6NmpsYTRhNTBkcm13Z3E5dHp2bHoSMmhhcXF2YWxvcGVyMTh4YTJlMno0bmRtZ2VmOWE3YzdsajAwZXR1dHcyZGFmeXloZGt4Gg8KBWFJU0xNEgYxMDAwMDASfApZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohAyO0EwzBxj7YzTOmNb9TcIGyEvrdfAlsM2LkyFb6FWzjEgQKAggBGBMSHwoZCgVhSVNMTRIQNDAwMDAwMDAwMDAwMDAwMBDAmgwaQHwfIh3BnY5W+u0ovQ+vCyv4yrBz1ZJu/l3L/h7/mpBtER0la0L8tl6MEhY308KrdeiXu0VpS738ny/8KtmPTOs=', + delegator: 'haqq1seymwr8zs6c92rcfyz6jla4a50drmwgq9tzvlz', + validator: 'haqqvaloper18xa2e2z4ndmgef9a7c7lj00etutw2dafyyhdkx', + chainId: 'haqq_54211-3', + accountNumber: 167676, + sequence: 19, + sendAmount: '100000', + feeAmount: '4000000000000000', + sendMessage: { + typeUrl: '/cosmos.staking.v1beta1.MsgUndelegate', + value: { + delegatorAddress: 'haqq1seymwr8zs6c92rcfyz6jla4a50drmwgq9tzvlz', + validatorAddress: 'haqqvaloper18xa2e2z4ndmgef9a7c7lj00etutw2dafyyhdkx', + amount: { + denom: 'aISLM', + amount: '100000', + }, + }, + }, + gasBudget: { + amount: [ + { + denom: 'aISLM', + amount: '4000000000000000', + }, + ], + gasLimit: 200000, + }, +}; + +export const TEST_WITHDRAW_REWARDS_TX = { + hash: '11CD80A6B569D6EC5D42C0CE082CBD125472A6BF61FC55E500950BD1A9A3B957', + signature: 'KXFbRvLeI+CvphTMZYz0U6wJuZ6VAZApXg/M4N8MWWpLsK8ERzppbgSSWGU30hDuI2GtD+CHUAiYy1bS3mDzSQ==', + pubKey: 'AyO0EwzBxj7YzTOmNb9TcIGyEvrdfAlsM2LkyFb6FWzj', + privateKey: '3xn43lnhB0RxwYkJ2vW4UeBPPOcb947WYsYuqsOzXa0=', + signedTxBase64: + 'Cp8BCpwBCjcvY29zbW9zLmRpc3RyaWJ1dGlvbi52MWJldGExLk1zZ1dpdGhkcmF3RGVsZWdhdG9yUmV3YXJkEmEKK2hhcXExc2V5bXdyOHpzNmM5MnJjZnl6NmpsYTRhNTBkcm13Z3E5dHp2bHoSMmhhcXF2YWxvcGVyMTh4YTJlMno0bmRtZ2VmOWE3YzdsajAwZXR1dHcyZGFmeXloZGt4EnwKWQpPCigvZXRoZXJtaW50LmNyeXB0by52MS5ldGhzZWNwMjU2azEuUHViS2V5EiMKIQMjtBMMwcY+2M0zpjW/U3CBshL63XwJbDNi5MhW+hVs4xIECgIIARgSEh8KGQoFYUlTTE0SEDQwMDAwMDAwMDAwMDAwMDAQwJoMGkApcVtG8t4j4K+mFMxljPRTrAm5npUBkCleD8zg3wxZakuwrwRHOmluBJJYZTfSEO4jYa0P4IdQCJjLVtLeYPNJ', + delegator: 'haqq1seymwr8zs6c92rcfyz6jla4a50drmwgq9tzvlz', + validator: 'haqqvaloper18xa2e2z4ndmgef9a7c7lj00etutw2dafyyhdkx', + chainId: 'haqq_54211-3', + accountNumber: 167676, + sequence: 18, + sendAmount: '100000', + feeAmount: '4000000000000000', + sendMessage: { + typeUrl: '/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward', + value: { + delegatorAddress: 'haqq1seymwr8zs6c92rcfyz6jla4a50drmwgq9tzvlz', + validatorAddress: 'haqqvaloper18xa2e2z4ndmgef9a7c7lj00etutw2dafyyhdkx', + amount: { + denom: 'aISLM', + amount: '100000', + }, + }, + }, + gasBudget: { + amount: [ + { + denom: 'aISLM', + amount: '4000000000000000', + }, + ], + gasLimit: 200000, + }, +}; + +export const TEST_TX_WITH_MEMO = { + hash: 'D930F12BC6764AA1971AFB20F466AC6F598C664BBCFE792D2D421C8C77B239A8', + signature: 'ojuPe7cO37Zs69g5lHSg+NO18WE0/6qNoKnyeaomeYxhp+U7Ym7J5Z/fWAWTrP2+AWRmCqNuPOhnSxfFZabkSw==', + pubKey: 'AyO0EwzBxj7YzTOmNb9TcIGyEvrdfAlsM2LkyFb6FWzj', + privateKey: '3xn43lnhB0RxwYkJ2vW4UeBPPOcb947WYsYuqsOzXa0=', + signedTxBase64: + 'CpIBCosBChwvY29zbW9zLmJhbmsudjFiZXRhMS5Nc2dTZW5kEmsKK2hhcXExc2V5bXdyOHpzNmM5MnJjZnl6NmpsYTRhNTBkcm13Z3E5dHp2bHoSK2hhcXExOXAycm52emFoeHFoOXlrcmozcnFtamRndjBtejZlZm5qbWR6YTcaDwoFYUlTTE0SBjEwMDAwMBICMjMSfApZCk8KKC9ldGhlcm1pbnQuY3J5cHRvLnYxLmV0aHNlY3AyNTZrMS5QdWJLZXkSIwohAyO0EwzBxj7YzTOmNb9TcIGyEvrdfAlsM2LkyFb6FWzjEgQKAggBGBQSHwoZCgVhSVNMTRIQNDAwMDAwMDAwMDAwMDAwMBDAmgwaQKI7j3u3Dt+2bOvYOZR0oPjTtfFhNP+qjaCp8nmqJnmMYaflO2JuyeWf31gFk6z9vgFkZgqjbjzoZ0sXxWWm5Es=', + sender: 'haqq1seymwr8zs6c92rcfyz6jla4a50drmwgq9tzvlz', + recipient: 'haqq19p2rnvzahxqh9ykrj3rqmjdgv0mz6efnjmdza7', + chainId: 'haqq_54211-3', + accountNumber: 167676, + sequence: 20, + sendAmount: '100000', + feeAmount: '4000000000000000', + sendMessage: { + typeUrl: '/cosmos.bank.v1beta1.MsgSend', + value: { + fromAddress: 'haqq1seymwr8zs6c92rcfyz6jla4a50drmwgq9tzvlz', + toAddress: 'haqq19p2rnvzahxqh9ykrj3rqmjdgv0mz6efnjmdza7', + amount: [ + { + denom: 'aISLM', + amount: '100000', + }, + ], + }, + }, + memo: '23', + gasBudget: { + amount: [ + { + denom: 'aISLM', + amount: '4000000000000000', + }, + ], + gasLimit: 200000, + }, +}; + +export const address = { + address1: 'haqq1g3g6nfmqf3f9lmdhf5g84pu7ustw8jt7tvrfzc', + address2: 'haqq1j3hhwl6exe9yjnggy33raj5s3xeskgsljvzagt', + address3: 'haqq2k3hhwl6exe9yjnggy33raj5s3xeskgsljvzagt', + address4: 'haqq1tmm005xgms7qrfm7jwpst3mj9dp8tzdld34jgv', + validatorAddress1: 'haqqvaloper16lp0xpq87cre5z4jkfddq78r5l4vcd7el2jlmj', + validatorAddress2: 'haqqvaloper16gg3wzq8h98zyn9kjp7aw3jwy6pnxslrdhl7zp', + validatorAddress3: 'haqqvaloper35eafuvcrh3c07z4g3pqgq68n3lmsyu5jd9swsy', + validatorAddress4: 'haqqvaloder1p8k6xk94u24vv9dmxu3vkgg43fs3v72grkpjhm', + noMemoIdAddress: 'haqq1g3g6nfmqf3f9lmdhf5g84pu7ustw8jt7tvrfzc', + validMemoIdAddress: 'haqq1g3g6nfmqf3f9lmdhf5g84pu7ustw8jt7tvrfzc?memoId=2', + invalidMemoIdAddress: 'haqq1g3g6nfmqf3f9lmdhf5g84pu7ustw8jt7tvrfzc?memoId=xyz', + multipleMemoIdAddress: 'haqq1g3g6nfmqf3f9lmdhf5g84pu7ustw8jt7tvrfzc?memoId=3&memoId=12', +}; + +export const blockHash = { + hash1: '78009F3F043D3BFDE78D3DB46C98AC6C4D30BE586E8DA2A28FB1FE537DF79265', + hash2: '1CCB5E358CE84FB9FBD77311D25E0621356CF283159EA703455BA72A5CB61F97', +}; + +export const txIds = { + hash1: 'CCDCFAC079BA3833AD3F8EEF3B411C9D8AE2747EF33CA516488A40E522DDD34D', + hash2: 'B51713C6FFD9EBEAFF158498BB6C406AFCC5E1D0423F38073714DC1F54E576F6', + hash3: '74BF02FE620C37EFC242B945B01A21D8E3BDAEDC23617BB0964AFC5BC598042E', +}; + +export const coinAmounts = { + amount1: { amount: '100000', denom: 'aISLM' }, + amount2: { amount: '1000000', denom: 'aISLM' }, + amount3: { amount: '10000000', denom: 'aISLM' }, + amount4: { amount: '-1', denom: 'aISLM' }, + amount5: { amount: '1000000000', denom: 'uISLM' }, +}; diff --git a/modules/sdk-coin-islm/test/unit/islm.ts b/modules/sdk-coin-islm/test/unit/islm.ts new file mode 100644 index 0000000000..8da0dbfdce --- /dev/null +++ b/modules/sdk-coin-islm/test/unit/islm.ts @@ -0,0 +1,339 @@ +import { BitGoAPI } from '@bitgo/sdk-api'; +import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; +import BigNumber from 'bignumber.js'; +import sinon from 'sinon'; +import { Tislm, Islm } from '../../src'; +import utils from '../../src/lib/utils'; +import { + TEST_DELEGATE_TX, + TEST_SEND_TX, + TEST_TX_WITH_MEMO, + TEST_UNDELEGATE_TX, + TEST_WITHDRAW_REWARDS_TX, + address, +} from '../resources/islm'; +import should = require('should'); + +describe('Islm', function () { + let bitgo: TestBitGoAPI; + let basecoin; + before(function () { + bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' }); + bitgo.safeRegister('islm', Islm.createInstance); + bitgo.safeRegister('tislm', Tislm.createInstance); + bitgo.initializeTestVars(); + basecoin = bitgo.coin('tislm'); + }); + + it('should return the right info', function () { + const islm = bitgo.coin('islm'); + const tislm = bitgo.coin('tislm'); + + islm.getChain().should.equal('islm'); + islm.getFamily().should.equal('islm'); + islm.getFullName().should.equal('Islamic Coin'); + islm.getBaseFactor().should.equal(1e18); + + tislm.getChain().should.equal('tislm'); + tislm.getFamily().should.equal('islm'); + tislm.getFullName().should.equal('Testnet Islamic Coin'); + tislm.getBaseFactor().should.equal(1e18); + }); + + describe('Address Validation', () => { + it('should get address details without memoId', function () { + const addressDetails = basecoin.getAddressDetails(address.noMemoIdAddress); + addressDetails.address.should.equal(address.noMemoIdAddress); + should.not.exist(addressDetails.memoId); + }); + + it('should get address details with memoId', function () { + const addressDetails = basecoin.getAddressDetails(address.validMemoIdAddress); + addressDetails.address.should.equal(address.validMemoIdAddress.split('?')[0]); + addressDetails.memoId.should.equal('2'); + }); + + it('should throw on invalid memo id address', () => { + (() => { + basecoin.getAddressDetails(address.invalidMemoIdAddress); + }).should.throw(); + }); + + it('should throw on multiple memo id address', () => { + (() => { + basecoin.getAddressDetails(address.multipleMemoIdAddress); + }).should.throw(); + }); + + it('should validate wallet receive address', async function () { + const receiveAddress = { + address: 'haqq1g3g6nfmqf3f9lmdhf5g84pu7ustw8jt7tvrfzc?memoId=7', + coinSpecific: { + rootAddress: 'haqq1g3g6nfmqf3f9lmdhf5g84pu7ustw8jt7tvrfzc', + memoID: '7', + }, + }; + const isValid = await basecoin.isWalletAddress(receiveAddress); + isValid.should.equal(true); + }); + + it('should validate account addresses correctly', () => { + should.equal(utils.isValidAddress(address.address1), true); + should.equal(utils.isValidAddress(address.address2), true); + should.equal(utils.isValidAddress(address.address3), false); + should.equal(utils.isValidAddress(address.address4), true); + should.equal(utils.isValidAddress('dfjk35y'), false); + should.equal(utils.isValidAddress(undefined as unknown as string), false); + should.equal(utils.isValidAddress(''), false); + }); + + it('should validate validator addresses correctly', () => { + should.equal(utils.isValidValidatorAddress(address.validatorAddress1), true); + should.equal(utils.isValidValidatorAddress(address.validatorAddress2), true); + should.equal(utils.isValidValidatorAddress(address.validatorAddress3), false); + should.equal(utils.isValidValidatorAddress(address.validatorAddress4), false); + should.equal(utils.isValidValidatorAddress('dfjk35y'), false); + should.equal(utils.isValidValidatorAddress(undefined as unknown as string), false); + should.equal(utils.isValidValidatorAddress(''), false); + }); + }); + + describe('Verify transaction: ', () => { + it('should succeed to verify transaction', async function () { + const txPrebuild = { + txHex: TEST_SEND_TX.signedTxBase64, + txInfo: {}, + }; + const txParams = { + recipients: [ + { + address: TEST_SEND_TX.recipient, + amount: TEST_SEND_TX.sendAmount, + }, + ], + }; + const verification = {}; + const isTransactionVerified = await basecoin.verifyTransaction({ txParams, txPrebuild, verification }); + isTransactionVerified.should.equal(true); + }); + + it('should succeed to verify delegate transaction', async function () { + const txPrebuild = { + txHex: TEST_DELEGATE_TX.signedTxBase64, + txInfo: {}, + }; + const txParams = { + recipients: [ + { + address: TEST_DELEGATE_TX.validator, + amount: TEST_DELEGATE_TX.sendAmount, + }, + ], + }; + const verification = {}; + const isTransactionVerified = await basecoin.verifyTransaction({ txParams, txPrebuild, verification }); + isTransactionVerified.should.equal(true); + }); + + it('should succeed to verify undelegate transaction', async function () { + const txPrebuild = { + txHex: TEST_UNDELEGATE_TX.signedTxBase64, + txInfo: {}, + }; + const txParams = { + recipients: [ + { + address: TEST_UNDELEGATE_TX.validator, + amount: TEST_UNDELEGATE_TX.sendAmount, + }, + ], + }; + const verification = {}; + const isTransactionVerified = await basecoin.verifyTransaction({ txParams, txPrebuild, verification }); + isTransactionVerified.should.equal(true); + }); + + it('should succeed to verify withdraw rewards transaction', async function () { + const txPrebuild = { + txHex: TEST_WITHDRAW_REWARDS_TX.signedTxBase64, + txInfo: {}, + }; + const txParams = { + recipients: [ + { + address: TEST_WITHDRAW_REWARDS_TX.validator, + amount: 'UNAVAILABLE', + }, + ], + }; + const verification = {}; + const isTransactionVerified = await basecoin.verifyTransaction({ txParams, txPrebuild, verification }); + isTransactionVerified.should.equal(true); + }); + + it('should fail to verify transaction with invalid param', async function () { + const txPrebuild = {}; + const txParams = { recipients: undefined }; + await basecoin + .verifyTransaction({ + txParams, + txPrebuild, + }) + .should.rejectedWith('missing required tx prebuild property txHex'); + }); + }); + + describe('Explain Transaction: ', () => { + it('should explain a transfer transaction', async function () { + const explainedTransaction = await basecoin.explainTransaction({ + txHex: TEST_SEND_TX.signedTxBase64, + }); + explainedTransaction.should.deepEqual({ + displayOrder: ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'type'], + id: TEST_SEND_TX.hash, + outputs: [ + { + address: TEST_SEND_TX.recipient, + amount: TEST_SEND_TX.sendAmount, + }, + ], + outputAmount: TEST_SEND_TX.sendAmount, + changeOutputs: [], + changeAmount: '0', + fee: { fee: TEST_SEND_TX.gasBudget.amount[0].amount }, + type: 0, + }); + }); + + it('should explain a delegate transaction', async function () { + const explainedTransaction = await basecoin.explainTransaction({ + txHex: TEST_DELEGATE_TX.signedTxBase64, + }); + explainedTransaction.should.deepEqual({ + displayOrder: ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'type'], + id: TEST_DELEGATE_TX.hash, + outputs: [ + { + address: TEST_DELEGATE_TX.validator, + amount: TEST_DELEGATE_TX.sendAmount, + }, + ], + outputAmount: TEST_DELEGATE_TX.sendAmount, + changeOutputs: [], + changeAmount: '0', + fee: { fee: TEST_DELEGATE_TX.gasBudget.amount[0].amount }, + type: 13, + }); + }); + + it('should explain a undelegate transaction', async function () { + const explainedTransaction = await basecoin.explainTransaction({ + txHex: TEST_UNDELEGATE_TX.signedTxBase64, + }); + explainedTransaction.should.deepEqual({ + displayOrder: ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'type'], + id: TEST_UNDELEGATE_TX.hash, + outputs: [ + { + address: TEST_UNDELEGATE_TX.validator, + amount: TEST_UNDELEGATE_TX.sendAmount, + }, + ], + outputAmount: TEST_UNDELEGATE_TX.sendAmount, + changeOutputs: [], + changeAmount: '0', + fee: { fee: TEST_UNDELEGATE_TX.gasBudget.amount[0].amount }, + type: 17, + }); + }); + + it('should explain a withdraw transaction', async function () { + const explainedTransaction = await basecoin.explainTransaction({ + txHex: TEST_WITHDRAW_REWARDS_TX.signedTxBase64, + }); + explainedTransaction.should.deepEqual({ + displayOrder: ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'type'], + id: TEST_WITHDRAW_REWARDS_TX.hash, + outputs: [ + { + address: TEST_WITHDRAW_REWARDS_TX.validator, + amount: 'UNAVAILABLE', + }, + ], + outputAmount: undefined, + changeOutputs: [], + changeAmount: '0', + fee: { fee: TEST_WITHDRAW_REWARDS_TX.gasBudget.amount[0].amount }, + type: 15, + }); + }); + + it('should explain a transfer transaction with memo', async function () { + const explainedTransaction = await basecoin.explainTransaction({ + txHex: TEST_TX_WITH_MEMO.signedTxBase64, + }); + explainedTransaction.should.deepEqual({ + displayOrder: ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'type'], + id: TEST_TX_WITH_MEMO.hash, + outputs: [ + { + address: TEST_TX_WITH_MEMO.recipient, + amount: TEST_TX_WITH_MEMO.sendAmount, + memo: TEST_TX_WITH_MEMO.memo, + }, + ], + outputAmount: TEST_TX_WITH_MEMO.sendAmount, + changeOutputs: [], + changeAmount: '0', + fee: { fee: TEST_TX_WITH_MEMO.gasBudget.amount[0].amount }, + type: 0, + }); + }); + + it('should fail to explain transaction with missing params', async function () { + try { + await basecoin.explainTransaction({}); + } catch (error) { + should.equal(error.message, 'missing required txHex parameter'); + } + }); + + it('should fail to explain transaction with invalid params', async function () { + try { + await basecoin.explainTransaction({ txHex: 'randomString' }); + } catch (error) { + should.equal(error.message.startsWith('Invalid transaction:'), true); + } + }); + }); + + describe('Parse Transactions: ', () => { + it('should parse a transfer transaction', async function () { + const transferInputsResponse = { + address: TEST_SEND_TX.recipient, + amount: new BigNumber(TEST_SEND_TX.sendAmount).plus(TEST_SEND_TX.gasBudget.amount[0].amount).toFixed(), + }; + + const transferOutputsResponse = { + address: TEST_SEND_TX.recipient, + amount: TEST_SEND_TX.sendAmount, + }; + + const parsedTransaction = await basecoin.parseTransaction({ txHex: TEST_SEND_TX.signedTxBase64 }); + + parsedTransaction.should.deepEqual({ + inputs: [transferInputsResponse], + outputs: [transferOutputsResponse], + }); + }); + + it('should fail to parse a transfer transaction when explainTransaction response is undefined', async function () { + const stub = sinon.stub(Islm.prototype, 'explainTransaction'); + stub.resolves(undefined); + await basecoin + .parseTransaction({ txHex: TEST_SEND_TX.signedTxBase64 }) + .should.be.rejectedWith('Invalid transaction'); + stub.restore(); + }); + }); +}); diff --git a/modules/sdk-coin-islm/test/unit/keyPair.ts b/modules/sdk-coin-islm/test/unit/keyPair.ts new file mode 100644 index 0000000000..9b659cdf21 --- /dev/null +++ b/modules/sdk-coin-islm/test/unit/keyPair.ts @@ -0,0 +1,117 @@ +import assert from 'assert'; +import should from 'should'; +import { KeyPair } from '../../src'; +import { TEST_ACCOUNT } from '../resources/islm'; + +describe('Islm Key Pair', () => { + describe('should create a valid KeyPair', () => { + it('from an empty value', () => { + const keyPairObj = new KeyPair(); + const keys = keyPairObj.getKeys(); + should.exists(keys.prv); + should.exists(keys.pub); + should.equal(keys.prv?.length, 64); + should.equal(keys.pub.length, 66); + + const extendedKeys = keyPairObj.getExtendedKeys(); + should.exists(extendedKeys.xprv); + should.exists(extendedKeys.xpub); + }); + + it('from a private key', () => { + const privateKey = TEST_ACCOUNT.privateKey; + const keyPairObj = new KeyPair({ prv: privateKey }); + const keys = keyPairObj.getKeys(); + should.exists(keys.prv); + should.exists(keys.pub); + should.equal(keys.prv, TEST_ACCOUNT.privateKey); + should.equal(keys.pub, TEST_ACCOUNT.compressedPublicKey); + should.equal(keyPairObj.getAddress(), TEST_ACCOUNT.pubAddress); + + assert.throws(() => keyPairObj.getExtendedKeys()); + }); + + it('from a compressed public key', () => { + const publicKey = TEST_ACCOUNT.compressedPublicKey; + const keyPairObj = new KeyPair({ pub: publicKey }); + const keys = keyPairObj.getKeys(); + should.not.exist(keys.prv); + should.exists(keys.pub); + should.equal(keys.pub, TEST_ACCOUNT.compressedPublicKey); + + assert.throws(() => keyPairObj.getExtendedKeys()); + }); + + it('from an uncompressed public key', () => { + // Input is uncompressed, but we output the compressed key to keep + // parity with islm network expectations. + const publicKey = TEST_ACCOUNT.uncompressedPublicKey; + const keyPairObj = new KeyPair({ pub: publicKey }); + const keys = keyPairObj.getKeys(); + should.not.exist(keys.prv); + should.exists(keys.pub); + should.notEqual(keys.pub, publicKey); + should.equal(keys.pub, TEST_ACCOUNT.compressedPublicKey); + + assert.throws(() => keyPairObj.getExtendedKeys()); + }); + }); + + describe('should fail to create a KeyPair', () => { + it('from an invalid privateKey', () => { + assert.throws( + () => new KeyPair({ prv: '' }), + (e: any) => e.message === 'Unsupported private key' + ); + }); + + it('from an invalid publicKey', () => { + assert.throws( + () => new KeyPair({ pub: '' }), + (e: any) => e.message.startsWith('Unsupported public key') + ); + }); + + it('from an undefined seed', () => { + const undefinedBuffer = undefined as unknown as Buffer; + assert.throws( + () => new KeyPair({ seed: undefinedBuffer }), + (e: any) => e.message.startsWith('Invalid key pair options') + ); + }); + + it('from an undefined private key', () => { + const undefinedStr: string = undefined as unknown as string; + assert.throws( + () => new KeyPair({ prv: undefinedStr }), + (e: any) => e.message.startsWith('Invalid key pair options') + ); + }); + + it('from an undefined public key', () => { + const undefinedStr: string = undefined as unknown as string; + assert.throws( + () => new KeyPair({ pub: undefinedStr }), + (e: any) => e.message.startsWith('Invalid key pair options') + ); + }); + }); + + describe('should get unique address', () => { + it('from a private key', () => { + const keyPair = new KeyPair({ prv: TEST_ACCOUNT.privateKey }); + should.equal(keyPair.getAddress(), TEST_ACCOUNT.pubAddress); + }); + + it('from a compressed public key', () => { + const keyPair = new KeyPair({ pub: TEST_ACCOUNT.compressedPublicKey }); + should.equal(keyPair.getAddress(), TEST_ACCOUNT.pubAddress); + }); + + it('should be different for different public keys', () => { + const keyPairOne = new KeyPair({ pub: TEST_ACCOUNT.compressedPublicKey }); + const keyPairTwo = new KeyPair({ pub: TEST_ACCOUNT.compressedPublicKeyTwo }); + should.notEqual(keyPairOne.getAddress(), keyPairTwo.getAddress()); + }); + }); +}); diff --git a/modules/sdk-coin-islm/test/unit/transaction.ts b/modules/sdk-coin-islm/test/unit/transaction.ts new file mode 100644 index 0000000000..a63cdbc952 --- /dev/null +++ b/modules/sdk-coin-islm/test/unit/transaction.ts @@ -0,0 +1,232 @@ +import { toHex, TransactionType } from '@bitgo/sdk-core'; +import { coins } from '@bitgo/statics'; +import { fromBase64 } from '@cosmjs/encoding'; +import should from 'should'; + +import { + CosmosTransaction, + DelegateOrUndelegeteMessage, + SendMessage, + WithdrawDelegatorRewardsMessage, +} from '@bitgo/abstract-cosmos'; +import utils from '../../src/lib/utils'; +import * as testData from '../resources/islm'; + +describe('Islm Transaction', () => { + let tx: CosmosTransaction; + const config = coins.get('tislm'); + + beforeEach(() => { + tx = new CosmosTransaction(config, utils); + }); + + describe('Empty transaction', () => { + it('should throw empty transaction', function () { + should.throws(() => tx.toBroadcastFormat(), 'Empty transaction'); + }); + }); + + describe('From raw transaction', () => { + it('should build a transfer from raw signed base64', function () { + tx.enrichTransactionDetailsFromRawTransaction(testData.TEST_SEND_TX.signedTxBase64); + const json = tx.toJson(); + should.equal(json.sequence, testData.TEST_SEND_TX.sequence); + should.deepEqual(json.gasBudget, testData.TEST_SEND_TX.gasBudget); + should.equal(json.publicKey, toHex(fromBase64(testData.TEST_SEND_TX.pubKey))); + should.equal( + (json.sendMessages[0].value as SendMessage).toAddress, + testData.TEST_SEND_TX.sendMessage.value.toAddress + ); + should.deepEqual( + (json.sendMessages[0].value as SendMessage).amount, + testData.TEST_SEND_TX.sendMessage.value.amount + ); + should.equal(Buffer.from(json.signature as any).toString('base64'), testData.TEST_SEND_TX.signature); + should.equal(tx.type, TransactionType.Send); + tx.loadInputsAndOutputs(); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_SEND_TX.sender, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: 'tislm', + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_SEND_TX.sendMessage.value.toAddress, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: 'tislm', + }, + ]); + }); + + it('should build a transfer from raw signed hex', function () { + tx.enrichTransactionDetailsFromRawTransaction(toHex(fromBase64(testData.TEST_SEND_TX.signedTxBase64))); + const json = tx.toJson(); + should.equal(json.sequence, testData.TEST_SEND_TX.sequence); + should.deepEqual(json.gasBudget, testData.TEST_SEND_TX.gasBudget); + should.equal(json.publicKey, toHex(fromBase64(testData.TEST_SEND_TX.pubKey))); + should.equal( + (json.sendMessages[0].value as SendMessage).toAddress, + testData.TEST_SEND_TX.sendMessage.value.toAddress + ); + should.deepEqual( + (json.sendMessages[0].value as SendMessage).amount, + testData.TEST_SEND_TX.sendMessage.value.amount + ); + should.equal(Buffer.from(json.signature as any).toString('base64'), testData.TEST_SEND_TX.signature); + should.equal(tx.type, TransactionType.Send); + tx.loadInputsAndOutputs(); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_SEND_TX.sender, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: 'tislm', + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_SEND_TX.sendMessage.value.toAddress, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: 'tislm', + }, + ]); + }); + + it('should build a delegate txn from raw signed base64', function () { + tx.enrichTransactionDetailsFromRawTransaction(testData.TEST_DELEGATE_TX.signedTxBase64); + const json = tx.toJson(); + should.equal(json.sequence, testData.TEST_DELEGATE_TX.sequence); + should.deepEqual(json.gasBudget, testData.TEST_DELEGATE_TX.gasBudget); + should.equal(Buffer.from(json.publicKey as any, 'hex').toString('base64'), testData.TEST_DELEGATE_TX.pubKey); + should.equal( + (json.sendMessages[0].value as DelegateOrUndelegeteMessage).validatorAddress, + testData.TEST_DELEGATE_TX.sendMessage.value.validatorAddress + ); + should.deepEqual( + (json.sendMessages[0].value as DelegateOrUndelegeteMessage).amount, + testData.TEST_DELEGATE_TX.sendMessage.value.amount + ); + should.equal(Buffer.from(json.signature as any).toString('base64'), testData.TEST_DELEGATE_TX.signature); + should.equal(tx.type, TransactionType.StakingActivate); + tx.loadInputsAndOutputs(); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_DELEGATE_TX.delegator, + value: testData.TEST_DELEGATE_TX.sendMessage.value.amount.amount, + coin: 'tislm', + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_DELEGATE_TX.validator, + value: testData.TEST_DELEGATE_TX.sendMessage.value.amount.amount, + coin: 'tislm', + }, + ]); + }); + + it('should build a undelegate txn from raw signed base64', function () { + tx.enrichTransactionDetailsFromRawTransaction(testData.TEST_UNDELEGATE_TX.signedTxBase64); + const json = tx.toJson(); + should.equal(json.sequence, testData.TEST_UNDELEGATE_TX.sequence); + should.deepEqual(json.gasBudget, testData.TEST_UNDELEGATE_TX.gasBudget); + should.equal(Buffer.from(json.publicKey as any, 'hex').toString('base64'), testData.TEST_UNDELEGATE_TX.pubKey); + should.equal( + (json.sendMessages[0].value as DelegateOrUndelegeteMessage).validatorAddress, + testData.TEST_UNDELEGATE_TX.sendMessage.value.validatorAddress + ); + should.deepEqual( + (json.sendMessages[0].value as DelegateOrUndelegeteMessage).amount, + testData.TEST_UNDELEGATE_TX.sendMessage.value.amount + ); + should.equal(Buffer.from(json.signature as any).toString('base64'), testData.TEST_UNDELEGATE_TX.signature); + should.equal(tx.type, TransactionType.StakingDeactivate); + tx.loadInputsAndOutputs(); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_UNDELEGATE_TX.delegator, + value: testData.TEST_UNDELEGATE_TX.sendMessage.value.amount.amount, + coin: 'tislm', + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_UNDELEGATE_TX.validator, + value: testData.TEST_UNDELEGATE_TX.sendMessage.value.amount.amount, + coin: 'tislm', + }, + ]); + }); + + it('should build a withdraw rewards from raw signed base64', function () { + tx.enrichTransactionDetailsFromRawTransaction(testData.TEST_WITHDRAW_REWARDS_TX.signedTxBase64); + const json = tx.toJson(); + should.equal(json.sequence, testData.TEST_WITHDRAW_REWARDS_TX.sequence); + should.deepEqual(json.gasBudget, testData.TEST_WITHDRAW_REWARDS_TX.gasBudget); + should.equal( + Buffer.from(json.publicKey as any, 'hex').toString('base64'), + testData.TEST_WITHDRAW_REWARDS_TX.pubKey + ); + should.equal( + (json.sendMessages[0].value as WithdrawDelegatorRewardsMessage).validatorAddress, + testData.TEST_WITHDRAW_REWARDS_TX.sendMessage.value.validatorAddress + ); + should.equal(Buffer.from(json.signature as any).toString('base64'), testData.TEST_WITHDRAW_REWARDS_TX.signature); + should.equal(tx.type, TransactionType.StakingWithdraw); + + tx.loadInputsAndOutputs(); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_WITHDRAW_REWARDS_TX.delegator, + value: 'UNAVAILABLE', + coin: 'tislm', + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_WITHDRAW_REWARDS_TX.validator, + value: 'UNAVAILABLE', + coin: 'tislm', + }, + ]); + }); + + it('should fail to build a transfer from incorrect raw hex', function () { + should.throws( + () => tx.enrichTransactionDetailsFromRawTransaction('random' + testData.TEST_SEND_TX.signedTxBase64), + 'incorrect raw data' + ); + }); + + it('should fail to explain transaction with invalid raw hex', function () { + should.throws(() => tx.enrichTransactionDetailsFromRawTransaction('randomString'), 'Invalid transaction'); + }); + }); + + describe('Explain transaction', () => { + it('should explain a transfer pay transaction', function () { + tx.enrichTransactionDetailsFromRawTransaction(testData.TEST_SEND_TX.signedTxBase64); + const explainedTransaction = tx.explainTransaction(); + explainedTransaction.should.deepEqual({ + displayOrder: ['id', 'outputs', 'outputAmount', 'changeOutputs', 'changeAmount', 'fee', 'type'], + id: testData.TEST_SEND_TX.hash, + outputs: [ + { + address: testData.TEST_SEND_TX.recipient, + amount: testData.TEST_SEND_TX.sendAmount, + }, + ], + outputAmount: testData.TEST_SEND_TX.sendAmount, + changeOutputs: [], + changeAmount: '0', + fee: { fee: testData.TEST_SEND_TX.feeAmount }, + type: 0, + }); + }); + + it('should fail to explain transaction with invalid raw base64 string', function () { + should.throws(() => tx.enrichTransactionDetailsFromRawTransaction('randomString'), 'Invalid transaction'); + }); + }); +}); diff --git a/modules/sdk-coin-islm/test/unit/transactionBuilder/StakingActivateBuilder.ts b/modules/sdk-coin-islm/test/unit/transactionBuilder/StakingActivateBuilder.ts new file mode 100644 index 0000000000..30817080bd --- /dev/null +++ b/modules/sdk-coin-islm/test/unit/transactionBuilder/StakingActivateBuilder.ts @@ -0,0 +1,121 @@ +import { toHex, TransactionType } from '@bitgo/sdk-core'; +import { fromBase64 } from '@cosmjs/encoding'; +import should from 'should'; + +import { BitGoAPI } from '@bitgo/sdk-api'; +import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; +import { Islm, Tislm } from '../../../src'; +import * as testData from '../../resources/islm'; + +describe('Islm Delegate txn Builder', () => { + let bitgo: TestBitGoAPI; + let basecoin; + let factory; + let testTx; + before(function () { + bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' }); + bitgo.safeRegister('islm', Islm.createInstance); + bitgo.safeRegister('tislm', Tislm.createInstance); + bitgo.initializeTestVars(); + basecoin = bitgo.coin('tislm'); + factory = basecoin.getBuilder(); + testTx = testData.TEST_DELEGATE_TX; + }); + + it('should build a Delegate tx with signature', async function () { + const txBuilder = factory.getStakingActivateBuilder(); + txBuilder.sequence(testTx.sequence); + txBuilder.gasBudget(testTx.gasBudget); + txBuilder.messages([testTx.sendMessage.value]); + txBuilder.publicKey(toHex(fromBase64(testTx.pubKey))); + txBuilder.addSignature({ pub: toHex(fromBase64(testTx.pubKey)) }, Buffer.from(testTx.signature, 'base64')); + + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.StakingActivate); + should.deepEqual(json.gasBudget, testTx.gasBudget); + should.deepEqual(json.sendMessages, [testTx.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx.pubKey))); + should.deepEqual(json.sequence, testTx.sequence); + const rawTx = tx.toBroadcastFormat(); + should.equal(rawTx, testTx.signedTxBase64); + should.deepEqual(tx.inputs, [ + { + address: testTx.delegator, + value: testTx.sendMessage.value.amount.amount, + coin: basecoin.getChain(), + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testTx.validator, + value: testTx.sendMessage.value.amount.amount, + coin: basecoin.getChain(), + }, + ]); + }); + + it('should build a Delegate tx without signature', async function () { + const txBuilder = factory.getStakingActivateBuilder(); + txBuilder.sequence(testTx.sequence); + txBuilder.gasBudget(testTx.gasBudget); + txBuilder.messages([testTx.sendMessage.value]); + txBuilder.publicKey(toHex(fromBase64(testTx.pubKey))); + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.StakingActivate); + should.deepEqual(json.gasBudget, testTx.gasBudget); + should.deepEqual(json.sendMessages, [testTx.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx.pubKey))); + should.deepEqual(json.sequence, testTx.sequence); + tx.toBroadcastFormat(); + should.deepEqual(tx.inputs, [ + { + address: testTx.delegator, + value: testTx.sendMessage.value.amount.amount, + coin: basecoin.getChain(), + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testTx.validator, + value: testTx.sendMessage.value.amount.amount, + coin: basecoin.getChain(), + }, + ]); + }); + + it('should sign a Delegate tx', async function () { + const txBuilder = factory.getStakingActivateBuilder(); + txBuilder.sequence(testTx.sequence); + txBuilder.gasBudget(testTx.gasBudget); + txBuilder.messages([testTx.sendMessage.value]); + txBuilder.accountNumber(testTx.accountNumber); + txBuilder.chainId(testTx.chainId); + txBuilder.sign({ key: toHex(fromBase64(testTx.privateKey)) }); + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.StakingActivate); + should.deepEqual(json.gasBudget, testTx.gasBudget); + should.deepEqual(json.sendMessages, [testTx.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx.pubKey))); + should.deepEqual(json.sequence, testTx.sequence); + const rawTx = tx.toBroadcastFormat(); + should.equal(tx.signature[0], toHex(fromBase64(testTx.signature))); + should.equal(rawTx, testTx.signedTxBase64); + should.deepEqual(tx.inputs, [ + { + address: testTx.delegator, + value: testTx.sendMessage.value.amount.amount, + coin: basecoin.getChain(), + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testTx.validator, + value: testTx.sendMessage.value.amount.amount, + coin: basecoin.getChain(), + }, + ]); + }); +}); diff --git a/modules/sdk-coin-islm/test/unit/transactionBuilder/StakingDeactivateBuilder.ts b/modules/sdk-coin-islm/test/unit/transactionBuilder/StakingDeactivateBuilder.ts new file mode 100644 index 0000000000..897ef412d5 --- /dev/null +++ b/modules/sdk-coin-islm/test/unit/transactionBuilder/StakingDeactivateBuilder.ts @@ -0,0 +1,119 @@ +import { toHex, TransactionType } from '@bitgo/sdk-core'; +import { fromBase64 } from '@cosmjs/encoding'; +import should from 'should'; + +import { BitGoAPI } from '@bitgo/sdk-api'; +import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; +import { Islm, Tislm } from '../../../src'; +import * as testData from '../../resources/islm'; + +describe('Islm Undelegate txn Builder', () => { + let bitgo: TestBitGoAPI; + let basecoin; + let factory; + let testTx; + before(function () { + bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' }); + bitgo.safeRegister('islm', Islm.createInstance); + bitgo.safeRegister('tislm', Tislm.createInstance); + bitgo.initializeTestVars(); + basecoin = bitgo.coin('tislm'); + factory = basecoin.getBuilder(); + testTx = testData.TEST_UNDELEGATE_TX; + }); + + it('should build undelegate tx with signature', async function () { + const txBuilder = factory.getStakingDeactivateBuilder(); + txBuilder.sequence(testTx.sequence); + txBuilder.gasBudget(testTx.gasBudget); + txBuilder.messages([testTx.sendMessage.value]); + txBuilder.addSignature({ pub: toHex(fromBase64(testTx.pubKey)) }, Buffer.from(testTx.signature, 'base64')); + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.StakingDeactivate); + should.deepEqual(json.gasBudget, testTx.gasBudget); + should.deepEqual(json.sendMessages, [testTx.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx.pubKey))); + should.deepEqual(json.sequence, testTx.sequence); + const rawTx = tx.toBroadcastFormat(); + should.equal(rawTx, testTx.signedTxBase64); + should.deepEqual(tx.inputs, [ + { + address: testTx.delegator, + value: testTx.sendMessage.value.amount.amount, + coin: basecoin.getChain(), + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testTx.validator, + value: testTx.sendMessage.value.amount.amount, + coin: basecoin.getChain(), + }, + ]); + }); + + it('should build undelegate tx without signature', async function () { + const txBuilder = factory.getStakingDeactivateBuilder(); + txBuilder.sequence(testTx.sequence); + txBuilder.gasBudget(testTx.gasBudget); + txBuilder.messages([testTx.sendMessage.value]); + txBuilder.publicKey(toHex(fromBase64(testTx.pubKey))); + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.StakingDeactivate); + should.deepEqual(json.gasBudget, testTx.gasBudget); + should.deepEqual(json.sendMessages, [testTx.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx.pubKey))); + should.deepEqual(json.sequence, testTx.sequence); + tx.toBroadcastFormat(); + should.deepEqual(tx.inputs, [ + { + address: testTx.delegator, + value: testTx.sendMessage.value.amount.amount, + coin: basecoin.getChain(), + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testTx.validator, + value: testTx.sendMessage.value.amount.amount, + coin: basecoin.getChain(), + }, + ]); + }); + + it('should sign undelegate tx', async function () { + const txBuilder = factory.getStakingDeactivateBuilder(); + txBuilder.sequence(testTx.sequence); + txBuilder.gasBudget(testTx.gasBudget); + txBuilder.messages([testTx.sendMessage.value]); + txBuilder.accountNumber(testTx.accountNumber); + txBuilder.chainId(testTx.chainId); + txBuilder.sign({ key: toHex(fromBase64(testTx.privateKey)) }); + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.StakingDeactivate); + should.deepEqual(json.gasBudget, testTx.gasBudget); + should.deepEqual(json.sendMessages, [testTx.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx.pubKey))); + should.deepEqual(json.sequence, testTx.sequence); + const rawTx = tx.toBroadcastFormat(); + should.equal(tx.signature[0], toHex(fromBase64(testTx.signature))); + should.equal(rawTx, testTx.signedTxBase64); + should.deepEqual(tx.inputs, [ + { + address: testTx.delegator, + value: testTx.sendMessage.value.amount.amount, + coin: basecoin.getChain(), + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testTx.validator, + value: testTx.sendMessage.value.amount.amount, + coin: basecoin.getChain(), + }, + ]); + }); +}); diff --git a/modules/sdk-coin-islm/test/unit/transactionBuilder/StakingWithdrawRewardsBuilder.ts b/modules/sdk-coin-islm/test/unit/transactionBuilder/StakingWithdrawRewardsBuilder.ts new file mode 100644 index 0000000000..60b2a9808e --- /dev/null +++ b/modules/sdk-coin-islm/test/unit/transactionBuilder/StakingWithdrawRewardsBuilder.ts @@ -0,0 +1,121 @@ +import { toHex, TransactionType } from '@bitgo/sdk-core'; +import { fromBase64 } from '@cosmjs/encoding'; +import should from 'should'; + +import { BitGoAPI } from '@bitgo/sdk-api'; +import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; +import { Islm, Tislm } from '../../../src'; +import * as testData from '../../resources/islm'; + +describe('Islm WithdrawRewards txn Builder', () => { + let bitgo: TestBitGoAPI; + let basecoin; + let factory; + let testTx; + before(function () { + bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' }); + bitgo.safeRegister('islm', Islm.createInstance); + bitgo.safeRegister('tislm', Tislm.createInstance); + bitgo.initializeTestVars(); + basecoin = bitgo.coin('tislm'); + factory = basecoin.getBuilder(); + testTx = testData.TEST_WITHDRAW_REWARDS_TX; + }); + + it('should build a WithdrawRewards tx with signature', async function () { + const txBuilder = factory.getStakingWithdrawRewardsBuilder(); + txBuilder.sequence(testTx.sequence); + txBuilder.gasBudget(testTx.gasBudget); + txBuilder.messages([testTx.sendMessage.value]); + txBuilder.publicKey(toHex(fromBase64(testTx.pubKey))); + txBuilder.addSignature({ pub: toHex(fromBase64(testTx.pubKey)) }, Buffer.from(testTx.signature, 'base64')); + + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.StakingWithdraw); + should.deepEqual(json.gasBudget, testTx.gasBudget); + should.deepEqual(json.sendMessages, [testTx.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx.pubKey))); + should.deepEqual(json.sequence, testTx.sequence); + const rawTx = tx.toBroadcastFormat(); + should.equal(rawTx, testTx.signedTxBase64); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_WITHDRAW_REWARDS_TX.delegator, + value: 'UNAVAILABLE', + coin: basecoin.getChain(), + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_WITHDRAW_REWARDS_TX.validator, + value: 'UNAVAILABLE', + coin: basecoin.getChain(), + }, + ]); + }); + + it('should build a WithdrawRewards tx without signature', async function () { + const txBuilder = factory.getStakingWithdrawRewardsBuilder(); + txBuilder.sequence(testTx.sequence); + txBuilder.gasBudget(testTx.gasBudget); + txBuilder.messages([testTx.sendMessage.value]); + txBuilder.publicKey(toHex(fromBase64(testTx.pubKey))); + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.StakingWithdraw); + should.deepEqual(json.gasBudget, testTx.gasBudget); + should.deepEqual(json.sendMessages, [testTx.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx.pubKey))); + should.deepEqual(json.sequence, testTx.sequence); + tx.toBroadcastFormat(); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_WITHDRAW_REWARDS_TX.delegator, + value: 'UNAVAILABLE', + coin: basecoin.getChain(), + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_WITHDRAW_REWARDS_TX.validator, + value: 'UNAVAILABLE', + coin: basecoin.getChain(), + }, + ]); + }); + + it('should sign a WithdrawRewards tx', async function () { + const txBuilder = factory.getStakingWithdrawRewardsBuilder(); + txBuilder.sequence(testTx.sequence); + txBuilder.gasBudget(testTx.gasBudget); + txBuilder.messages([testTx.sendMessage.value]); + txBuilder.accountNumber(testTx.accountNumber); + txBuilder.chainId(testTx.chainId); + txBuilder.sign({ key: toHex(fromBase64(testTx.privateKey)) }); + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.StakingWithdraw); + should.deepEqual(json.gasBudget, testTx.gasBudget); + should.deepEqual(json.sendMessages, [testTx.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx.pubKey))); + should.deepEqual(json.sequence, testTx.sequence); + const rawTx = tx.toBroadcastFormat(); + should.equal(tx.signature[0], toHex(fromBase64(testTx.signature))); + should.equal(rawTx, testTx.signedTxBase64); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_WITHDRAW_REWARDS_TX.delegator, + value: 'UNAVAILABLE', + coin: basecoin.getChain(), + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_WITHDRAW_REWARDS_TX.validator, + value: 'UNAVAILABLE', + coin: basecoin.getChain(), + }, + ]); + }); +}); diff --git a/modules/sdk-coin-islm/test/unit/transactionBuilder/transactionBuilder.ts b/modules/sdk-coin-islm/test/unit/transactionBuilder/transactionBuilder.ts new file mode 100644 index 0000000000..c5f07c6480 --- /dev/null +++ b/modules/sdk-coin-islm/test/unit/transactionBuilder/transactionBuilder.ts @@ -0,0 +1,83 @@ +import { TransactionType } from '@bitgo/sdk-core'; +import should from 'should'; + +import { BitGoAPI } from '@bitgo/sdk-api'; +import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; +import { Islm, Tislm } from '../../../src'; +import * as testData from '../../resources/islm'; + +describe('Islm Transaction Builder', async () => { + let bitgo: TestBitGoAPI; + let basecoin; + let factory; + before(function () { + bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' }); + bitgo.safeRegister('islm', Islm.createInstance); + bitgo.safeRegister('tislm', Tislm.createInstance); + bitgo.initializeTestVars(); + basecoin = bitgo.coin('tislm'); + factory = basecoin.getBuilder(); + }); + + const testTxData = testData.TEST_SEND_TX; + let data; + + beforeEach(() => { + data = [ + { + type: TransactionType.Send, + testTx: testData.TEST_SEND_TX, + builder: factory.getTransferBuilder(), + }, + { + type: TransactionType.StakingActivate, + testTx: testData.TEST_DELEGATE_TX, + builder: factory.getStakingActivateBuilder(), + }, + { + type: TransactionType.StakingDeactivate, + testTx: testData.TEST_UNDELEGATE_TX, + builder: factory.getStakingDeactivateBuilder(), + }, + { + type: TransactionType.StakingWithdraw, + testTx: testData.TEST_WITHDRAW_REWARDS_TX, + builder: factory.getStakingWithdrawRewardsBuilder(), + }, + ]; + }); + + it('should build a signed tx from signed tx data', async function () { + const txBuilder = factory.from(testTxData.signedTxBase64); + const tx = await txBuilder.build(); + should.equal(tx.type, TransactionType.Send); + // Should recreate the same raw tx data when re-build and turned to broadcast format + const rawTx = tx.toBroadcastFormat(); + should.equal(rawTx, testTxData.signedTxBase64); + }); + + describe('gasBudget tests', async () => { + it('should succeed for valid gasBudget', function () { + for (const { builder } of data) { + should.doesNotThrow(() => builder.gasBudget(testTxData.gasBudget)); + } + }); + + it('should throw for invalid gasBudget', function () { + const invalidGasBudget = 0; + for (const { builder } of data) { + should(() => builder.gasBudget({ gasLimit: invalidGasBudget })).throw('Invalid gas limit ' + invalidGasBudget); + } + }); + }); + + it('validateAddress', function () { + const invalidAddress = { address: 'randomString' }; + for (const { builder } of data) { + should.doesNotThrow(() => builder.validateAddress({ address: testTxData.sender })); + should(() => builder.validateAddress(invalidAddress)).throwError( + 'transactionBuilder: address isValidAddress check failed: ' + invalidAddress.address + ); + } + }); +}); diff --git a/modules/sdk-coin-islm/test/unit/transactionBuilder/transferBuilder.ts b/modules/sdk-coin-islm/test/unit/transactionBuilder/transferBuilder.ts new file mode 100644 index 0000000000..5b4aa6187a --- /dev/null +++ b/modules/sdk-coin-islm/test/unit/transactionBuilder/transferBuilder.ts @@ -0,0 +1,160 @@ +import { BitGoAPI } from '@bitgo/sdk-api'; +import { TransactionType } from '@bitgo/sdk-core'; +import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test'; +import { fromBase64, toHex } from '@cosmjs/encoding'; +import should from 'should'; +import { Islm, Tislm } from '../../../src'; +import * as testData from '../../resources/islm'; + +describe('Islm Transfer Builder', () => { + let bitgo: TestBitGoAPI; + let basecoin; + let factory; + let testTx; + let testTxWithMemo; + before(function () { + bitgo = TestBitGo.decorate(BitGoAPI, { env: 'mock' }); + bitgo.safeRegister('islm', Islm.createInstance); + bitgo.safeRegister('tislm', Tislm.createInstance); + bitgo.initializeTestVars(); + basecoin = bitgo.coin('tislm'); + factory = basecoin.getBuilder(); + testTx = testData.TEST_SEND_TX; + testTxWithMemo = testData.TEST_TX_WITH_MEMO; + }); + + it('should build a Transfer tx with signature', async function () { + const txBuilder = factory.getTransferBuilder(); + txBuilder.sequence(testTx.sequence); + txBuilder.gasBudget(testTx.gasBudget); + txBuilder.messages([testTx.sendMessage.value]); + txBuilder.publicKey(toHex(fromBase64(testTx.pubKey))); + txBuilder.addSignature({ pub: toHex(fromBase64(testTx.pubKey)) }, Buffer.from(testTx.signature, 'base64')); + + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.Send); + should.deepEqual(json.gasBudget, testTx.gasBudget); + should.deepEqual(json.sendMessages, [testTx.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx.pubKey))); + should.deepEqual(json.sequence, testTx.sequence); + const rawTx = tx.toBroadcastFormat(); + should.equal(rawTx, testTx.signedTxBase64); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_SEND_TX.sender, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: basecoin.getChain(), + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_SEND_TX.sendMessage.value.toAddress, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: basecoin.getChain(), + }, + ]); + }); + + it('should build a Transfer tx with signature and memo', async function () { + const txBuilder = factory.getTransferBuilder(); + txBuilder.sequence(testTxWithMemo.sequence); + txBuilder.gasBudget(testTxWithMemo.gasBudget); + txBuilder.messages([testTxWithMemo.sendMessage.value]); + txBuilder.publicKey(toHex(fromBase64(testTxWithMemo.pubKey))); + txBuilder.memo(testTxWithMemo.memo); + txBuilder.addSignature( + { pub: toHex(fromBase64(testTxWithMemo.pubKey)) }, + Buffer.from(testTxWithMemo.signature, 'base64') + ); + + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.Send); + should.deepEqual(json.gasBudget, testTxWithMemo.gasBudget); + should.deepEqual(json.sendMessages, [testTxWithMemo.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTxWithMemo.pubKey))); + should.deepEqual(json.sequence, testTxWithMemo.sequence); + should.equal(json.memo, testTxWithMemo.memo); + const rawTx = tx.toBroadcastFormat(); + should.equal(rawTx, testTxWithMemo.signedTxBase64); + should.deepEqual(tx.inputs, [ + { + address: testTxWithMemo.sendMessage.value.fromAddress, + value: testTxWithMemo.sendMessage.value.amount[0].amount, + coin: basecoin.getChain(), + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testTxWithMemo.sendMessage.value.toAddress, + value: testTxWithMemo.sendMessage.value.amount[0].amount, + coin: basecoin.getChain(), + }, + ]); + }); + + it('should build a Transfer tx without signature', async function () { + const txBuilder = factory.getTransferBuilder(); + txBuilder.sequence(testTx.sequence); + txBuilder.gasBudget(testTx.gasBudget); + txBuilder.messages([testTx.sendMessage.value]); + txBuilder.publicKey(toHex(fromBase64(testTx.pubKey))); + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.Send); + should.deepEqual(json.gasBudget, testTx.gasBudget); + should.deepEqual(json.sendMessages, [testTx.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx.pubKey))); + should.deepEqual(json.sequence, testTx.sequence); + tx.toBroadcastFormat(); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_SEND_TX.sender, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: basecoin.getChain(), + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_SEND_TX.sendMessage.value.toAddress, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: basecoin.getChain(), + }, + ]); + }); + + it('should sign a Transfer tx', async function () { + const txBuilder = factory.getTransferBuilder(); + txBuilder.sequence(testTx.sequence); + txBuilder.gasBudget(testTx.gasBudget); + txBuilder.messages([testTx.sendMessage.value]); + txBuilder.accountNumber(testTx.accountNumber); + txBuilder.chainId(testTx.chainId); + txBuilder.sign({ key: toHex(fromBase64(testTx.privateKey)) }); + const tx = await txBuilder.build(); + const json = await (await txBuilder.build()).toJson(); + should.equal(tx.type, TransactionType.Send); + should.deepEqual(json.gasBudget, testTx.gasBudget); + should.deepEqual(json.sendMessages, [testTx.sendMessage]); + should.deepEqual(json.publicKey, toHex(fromBase64(testTx.pubKey))); + should.deepEqual(json.sequence, testTx.sequence); + const rawTx = tx.toBroadcastFormat(); + should.equal(tx.signature[0], toHex(fromBase64(testTx.signature))); + should.equal(rawTx, testTx.signedTxBase64); + should.deepEqual(tx.inputs, [ + { + address: testData.TEST_SEND_TX.sender, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: basecoin.getChain(), + }, + ]); + should.deepEqual(tx.outputs, [ + { + address: testData.TEST_SEND_TX.sendMessage.value.toAddress, + value: testData.TEST_SEND_TX.sendMessage.value.amount[0].amount, + coin: basecoin.getChain(), + }, + ]); + }); +}); diff --git a/modules/sdk-coin-islm/test/unit/utils.ts b/modules/sdk-coin-islm/test/unit/utils.ts new file mode 100644 index 0000000000..9cd2cffe2c --- /dev/null +++ b/modules/sdk-coin-islm/test/unit/utils.ts @@ -0,0 +1,47 @@ +import should from 'should'; + +import utils from '../../src/lib/utils'; +import * as testData from '../resources/islm'; +import { blockHash, txIds } from '../resources/islm'; + +describe('utils', () => { + it('should validate block hash correctly', () => { + should.equal(utils.isValidBlockId(blockHash.hash1), true); + should.equal(utils.isValidBlockId(blockHash.hash2), true); + // param is coming as undefined so it was causing an issue + should.equal(utils.isValidBlockId(undefined as unknown as string), false); + should.equal(utils.isValidBlockId(''), false); + }); + + it('should validate invalid block hash correctly', () => { + should.equal(utils.isValidBlockId(''), false); + should.equal(utils.isValidBlockId('0xade35465gfvdcsxsz24300'), false); + should.equal(utils.isValidBlockId(blockHash.hash2 + 'ff'), false); + should.equal(utils.isValidBlockId('latest'), false); + }); + + it('should validate transaction id correctly', () => { + should.equal(utils.isValidTransactionId(txIds.hash1), true); + should.equal(utils.isValidTransactionId(txIds.hash2), true); + should.equal(utils.isValidTransactionId(txIds.hash3), true); + }); + + it('should validate invalid transaction id correctly', () => { + should.equal(utils.isValidTransactionId(''), false); + should.equal(utils.isValidTransactionId(txIds.hash1.slice(3)), false); + should.equal(utils.isValidTransactionId(txIds.hash3 + '00'), false); + should.equal(utils.isValidTransactionId('dalij43ta0ga2dadda02'), false); + }); + + it('validateAmount', function () { + should.doesNotThrow(() => utils.validateAmountData([testData.coinAmounts.amount1])); + should.doesNotThrow(() => utils.validateAmountData([testData.coinAmounts.amount2])); + should.doesNotThrow(() => utils.validateAmountData([testData.coinAmounts.amount3])); + should(() => utils.validateAmountData([testData.coinAmounts.amount4])).throwError( + 'transactionBuilder: validateAmount: Invalid amount: ' + testData.coinAmounts.amount4.amount + ); + should(() => utils.validateAmountData([testData.coinAmounts.amount5])).throwError( + 'transactionBuilder: validateAmount: Invalid denom: ' + testData.coinAmounts.amount5.denom + ); + }); +});