UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

278 lines 14.9 kB
import { expect } from 'chai'; import fs from 'fs'; import sinon from 'sinon'; import { parse as yamlParse } from 'yaml'; import { test1, test2, testCosmosChain, testScale1, testScale2, testSealevelChain, testVSXERC20, testXERC20, testXERC20Lockbox, } from '../consts/testChains.js'; import { MultiProtocolProvider } from '../providers/MultiProtocolProvider.js'; import { ProviderType } from '../providers/ProviderType.js'; import { Token } from '../token/Token.js'; import { TokenStandard } from '../token/TokenStandard.js'; import { WarpCore } from './WarpCore.js'; import { WarpTxCategory } from './types.js'; const MOCK_LOCAL_QUOTE = { gasUnits: 2000n, gasPrice: 100, fee: 200000n }; const MOCK_INTERCHAIN_QUOTE = { amount: 20000n }; const TRANSFER_AMOUNT = BigInt('1000000000000000000'); // 1 units @ 18 decimals const MEDIUM_TRANSFER_AMOUNT = BigInt('15000000000000000000'); // 15 units @ 18 deicmals const BIG_TRANSFER_AMOUNT = BigInt('100000000000000000000'); // 100 units @ 18 decimals const MOCK_BALANCE = BigInt('10000000000000000000'); // 10 units @ 18 decimals const MEDIUM_MOCK_BALANCE = BigInt('50000000000000000000'); // 50 units at @ 18 decimals const MOCK_ADDRESS = '0x0000000000000000000000000000000000000001'; describe('WarpCore', () => { const multiProvider = MultiProtocolProvider.createTestMultiProtocolProvider(); let warpCore; let evmHypNative; let evmHypNativeScale1; let evmHypNativeScale2; let evmHypSynthetic; let evmHypXERC20; let evmHypVSXERC20; let evmHypXERC20Lockbox; let evmHypCollateralFiat; let sealevelHypSynthetic; let cwHypCollateral; let cw20; let cosmosIbc; // Stub MultiProvider fee estimation to avoid real network calls sinon .stub(multiProvider, 'estimateTransactionFee') .returns(Promise.resolve(MOCK_LOCAL_QUOTE)); it('Constructs', () => { const fromArgs = new WarpCore(multiProvider, [ Token.FromChainMetadataNativeToken(test1), ]); const exampleConfig = yamlParse(fs.readFileSync('./src/warp/test-warp-core-config.yaml', 'utf-8')); const fromConfig = WarpCore.FromConfig(multiProvider, exampleConfig); expect(fromArgs).to.be.instanceOf(WarpCore); expect(fromConfig).to.be.instanceOf(WarpCore); expect(fromConfig.tokens.length).to.equal(exampleConfig.tokens.length); warpCore = fromConfig; [ evmHypNative, evmHypSynthetic, evmHypXERC20, evmHypVSXERC20, evmHypXERC20Lockbox, evmHypNativeScale1, evmHypNativeScale2, evmHypCollateralFiat, sealevelHypSynthetic, cwHypCollateral, cw20, cosmosIbc, ] = warpCore.tokens; }); it('Finds tokens', () => { expect(warpCore.findToken(test1.name, evmHypNative.addressOrDenom)).to.be.instanceOf(Token); expect(warpCore.findToken(testSealevelChain.name, sealevelHypSynthetic.addressOrDenom)).to.be.instanceOf(Token); expect(warpCore.findToken(testCosmosChain.name, cw20.addressOrDenom)).to.be.instanceOf(Token); expect(warpCore.findToken(test1.name, sealevelHypSynthetic.addressOrDenom)) .to.be.null; }); it('Gets transfer gas quote', async () => { const stubs = warpCore.tokens.map((t) => sinon.stub(t, 'getHypAdapter').returns({ quoteTransferRemoteGas: () => Promise.resolve(MOCK_INTERCHAIN_QUOTE), isApproveRequired: () => Promise.resolve(false), populateTransferRemoteTx: () => Promise.resolve({}), isRevokeApprovalRequired: () => Promise.resolve(false), })); const testQuote = async (token, destination, standard, interchainQuote = MOCK_INTERCHAIN_QUOTE) => { const result = await warpCore.estimateTransferRemoteFees({ originToken: token, destination, sender: MOCK_ADDRESS, }); expect(result.localQuote.token.standard, `token local standard check for ${token.chainName} to ${destination}`).equals(standard); expect(result.localQuote.amount, `token local amount check for ${token.chainName} to ${destination}`).to.equal(MOCK_LOCAL_QUOTE.fee); expect(result.interchainQuote.token.standard, `token interchain standard check for ${token.chainName} to ${destination}`).equals(standard); expect(result.interchainQuote.amount, `token interchain amount check for ${token.chainName} to ${destination}`).to.equal(interchainQuote.amount); }; await testQuote(evmHypNative, test1.name, TokenStandard.EvmNative); await testQuote(evmHypNative, testCosmosChain.name, TokenStandard.EvmNative); await testQuote(evmHypNative, testSealevelChain.name, TokenStandard.EvmNative); await testQuote(evmHypSynthetic, test2.name, TokenStandard.EvmNative); await testQuote(sealevelHypSynthetic, test2.name, TokenStandard.SealevelNative); await testQuote(cosmosIbc, test1.name, TokenStandard.CosmosNative); // Note, this route uses an igp quote const config await testQuote(cwHypCollateral, test2.name, TokenStandard.CosmosNative, { amount: 1n, addressOrDenom: 'atom', }); stubs.forEach((s) => s.restore()); }); it('Checks for destination collateral', async () => { const stubs = warpCore.tokens.map((t) => sinon.stub(t, 'getHypAdapter').returns({ getBalance: () => Promise.resolve(MOCK_BALANCE), getBridgedSupply: () => Promise.resolve(MOCK_BALANCE), isRevokeApprovalRequired: () => Promise.resolve(false), })); const testCollateral = async (token, destination, expectedBigResult) => { const smallResult = await warpCore.isDestinationCollateralSufficient({ originTokenAmount: token.amount(TRANSFER_AMOUNT), destination, }); expect(smallResult, `small collateral check for ${token.chainName} to ${destination}`).to.be.true; const bigResult = await warpCore.isDestinationCollateralSufficient({ originTokenAmount: token.amount(BIG_TRANSFER_AMOUNT), destination, }); expect(bigResult, `big collateral check for ${token.chainName} to ${destination}`).to.equal(expectedBigResult); }; await testCollateral(evmHypNative, test2.name, true); await testCollateral(evmHypNative, testCosmosChain.name, false); await testCollateral(evmHypNative, testSealevelChain.name, true); await testCollateral(cwHypCollateral, test1.name, false); await testCollateral(evmHypXERC20, testVSXERC20.name, true); await testCollateral(evmHypVSXERC20, testXERC20.name, true); await testCollateral(evmHypXERC20Lockbox, testXERC20.name, true); await testCollateral(evmHypNative, testXERC20Lockbox.name, false); stubs.forEach((s) => s.restore()); }); it('Checks for destination collateral with scaling factors', async () => { const stubs = warpCore.tokens.map((t) => sinon.stub(t, 'getHypAdapter').returns({ getBalance: () => Promise.resolve(10n), getBridgedSupply: () => Promise.resolve(10n), isRevokeApprovalRequired: () => Promise.resolve(false), })); const testCollateral = async (token, destination, amount, expectedResult) => { const result = await warpCore.isDestinationCollateralSufficient({ originTokenAmount: token.amount(amount), destination, }); expect(result, `collateral check for ${token.chainName} to ${destination}`).to.equal(expectedResult); }; await testCollateral(evmHypNativeScale1, testScale2.name, 10n, false); await testCollateral(evmHypNativeScale1, testScale2.name, 1n, true); await testCollateral(evmHypNativeScale2, testScale1.name, 10n, true); await testCollateral(evmHypNativeScale2, testScale1.name, 100n, true); await testCollateral(evmHypNativeScale2, testScale1.name, 101n, false); stubs.forEach((s) => s.restore()); }); it('Validates transfers', async () => { const balanceStubs = warpCore.tokens.map((t) => sinon.stub(t, 'getBalance').resolves({ amount: MOCK_BALANCE })); const minimumTransferAmount = 10n; const quoteStubs = warpCore.tokens.map((t) => sinon.stub(t, 'getHypAdapter').returns({ quoteTransferRemoteGas: () => Promise.resolve(MOCK_INTERCHAIN_QUOTE), isApproveRequired: () => Promise.resolve(false), populateTransferRemoteTx: () => Promise.resolve({}), getMinimumTransferAmount: () => Promise.resolve(minimumTransferAmount), getBalance: () => Promise.resolve(MOCK_BALANCE), getBridgedSupply: () => Promise.resolve(MOCK_BALANCE), getMintLimit: () => Promise.resolve(MEDIUM_MOCK_BALANCE), getMintMaxLimit: () => Promise.resolve(MEDIUM_MOCK_BALANCE), isRevokeApprovalRequired: () => Promise.resolve(false), })); const validResult = await warpCore.validateTransfer({ originTokenAmount: evmHypNative.amount(TRANSFER_AMOUNT), destination: test2.name, recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); expect(validResult).to.be.null; const invalidChain = await warpCore.validateTransfer({ originTokenAmount: evmHypNative.amount(TRANSFER_AMOUNT), destination: 'fakechain', recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); expect(Object.keys(invalidChain || {})[0]).to.equal('destination'); const invalidRecipient = await warpCore.validateTransfer({ originTokenAmount: evmHypNative.amount(TRANSFER_AMOUNT), destination: testCosmosChain.name, recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); expect(Object.keys(invalidRecipient || {})[0]).to.equal('recipient'); const invalidAmount = await warpCore.validateTransfer({ originTokenAmount: evmHypNative.amount(-10), destination: test2.name, recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); expect(Object.keys(invalidAmount || {})[0]).to.equal('amount'); const insufficientAmount = await warpCore.validateTransfer({ originTokenAmount: evmHypNative.amount(minimumTransferAmount - 1n), destination: test2.name, recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); expect(Object.keys(insufficientAmount || {})[0]).to.equal('amount'); const insufficientBalance = await warpCore.validateTransfer({ originTokenAmount: evmHypNative.amount(BIG_TRANSFER_AMOUNT), destination: test2.name, recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); expect(Object.keys(insufficientBalance || {})[0]).to.equal('amount'); const validXERC20TokenResult = await warpCore.validateTransfer({ originTokenAmount: evmHypNative.amount(TRANSFER_AMOUNT), destination: testXERC20.name, recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); expect(validXERC20TokenResult).to.be.null; const invalidRateLimit = await warpCore.validateTransfer({ originTokenAmount: evmHypNative.amount(BIG_TRANSFER_AMOUNT), destination: testXERC20.name, recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); expect(Object.values(invalidRateLimit || {})[0]).to.equal('Rate limit exceeded on destination'); const invalidXERC20LockboxTokenRateLimit = await warpCore.validateTransfer({ originTokenAmount: evmHypXERC20.amount(BIG_TRANSFER_AMOUNT), destination: testXERC20Lockbox.name, recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); expect(Object.values(invalidXERC20LockboxTokenRateLimit || {})[0]).to.equal('Rate limit exceeded on destination'); const invalidCollateralFiatTokenRateLimit = await warpCore.validateTransfer({ originTokenAmount: evmHypNative.amount(BIG_TRANSFER_AMOUNT), destination: evmHypCollateralFiat.chainName, recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); expect(Object.values(invalidCollateralFiatTokenRateLimit || {})[0]).to.equal('Rate limit exceeded on destination'); const invalidCollateralXERC20LockboxToken = await warpCore.validateTransfer({ originTokenAmount: evmHypXERC20.amount(MEDIUM_TRANSFER_AMOUNT), destination: testXERC20Lockbox.name, recipient: MOCK_ADDRESS, sender: MOCK_ADDRESS, }); expect(Object.values(invalidCollateralXERC20LockboxToken || {})[0]).to.equal('Insufficient collateral on destination'); balanceStubs.forEach((s) => s.restore()); quoteStubs.forEach((s) => s.restore()); }); it('Gets transfer remote txs', async () => { const coreStub = sinon .stub(warpCore, 'isApproveRequired') .returns(Promise.resolve(false)); const adapterStubs = warpCore.tokens.map((t) => sinon.stub(t, 'getHypAdapter').returns({ quoteTransferRemoteGas: () => Promise.resolve(MOCK_INTERCHAIN_QUOTE), populateTransferRemoteTx: () => Promise.resolve({}), isRevokeApprovalRequired: () => Promise.resolve(false), })); const testGetTxs = async (token, destination, providerType = ProviderType.EthersV5) => { const result = await warpCore.getTransferRemoteTxs({ originTokenAmount: token.amount(TRANSFER_AMOUNT), destination, sender: MOCK_ADDRESS, recipient: MOCK_ADDRESS, }); expect(result.length).to.equal(1); expect(result[0], `transfer tx for ${token.chainName} to ${destination}`).to.eql({ category: WarpTxCategory.Transfer, transaction: {}, type: providerType, }); }; await testGetTxs(evmHypNative, test1.name); await testGetTxs(evmHypNative, testCosmosChain.name); await testGetTxs(evmHypNative, testSealevelChain.name); await testGetTxs(evmHypSynthetic, test2.name); await testGetTxs(sealevelHypSynthetic, test2.name, ProviderType.SolanaWeb3); await testGetTxs(cwHypCollateral, test1.name, ProviderType.CosmJsWasm); await testGetTxs(cosmosIbc, test1.name, ProviderType.CosmJs); coreStub.restore(); adapterStubs.forEach((s) => s.restore()); }); }); //# sourceMappingURL=WarpCore.test.js.map