-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
671bc63
commit a1bdaa1
Showing
5 changed files
with
501 additions
and
29 deletions.
There are no files selected for viewing
172 changes: 172 additions & 0 deletions
172
packages/ui-components/src/__tests__/getDeploymentTransactionArgs.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
import { describe, it, expect, vi, beforeEach } from 'vitest'; | ||
import { | ||
getDeploymentTransactionArgs, | ||
AddOrderErrors | ||
} from '../lib/components/deployment/getDeploymentTransactionArgs'; | ||
import { getAccount } from '@wagmi/core'; | ||
import type { Config } from '@wagmi/core'; | ||
import type { DotrainOrderGui, OrderIO } from '@rainlanguage/orderbook/js_api'; | ||
|
||
// Mock wagmi/core | ||
vi.mock('@wagmi/core', () => ({ | ||
getAccount: vi.fn() | ||
})); | ||
|
||
describe('getDeploymentTransactionArgs', () => { | ||
let mockGui: DotrainOrderGui; | ||
let mockWagmiConfig: Config; | ||
let mockTokenOutputs: OrderIO[]; | ||
|
||
beforeEach(() => { | ||
vi.clearAllMocks(); | ||
|
||
// Mock GUI with successful responses | ||
mockGui = { | ||
generateApprovalCalldatas: vi.fn().mockResolvedValue([{ token: '0x123', amount: '1000' }]), | ||
generateDepositAndAddOrderCalldatas: vi.fn().mockResolvedValue({ | ||
deposit: '0xdeposit', | ||
addOrder: '0xaddOrder' | ||
}), | ||
getCurrentDeployment: vi.fn().mockReturnValue({ | ||
deployment: { | ||
order: { | ||
network: { 'chain-id': 1 }, | ||
orderbook: { address: '0xorderbook' } | ||
} | ||
} | ||
}), | ||
getTokenInfo: vi.fn().mockResolvedValue({ | ||
address: '0x123', | ||
symbol: 'TEST' | ||
}) | ||
} as unknown as DotrainOrderGui; | ||
|
||
mockWagmiConfig = {} as Config; | ||
(getAccount as any).mockReturnValue({ address: '0xuser' }); | ||
|
||
mockTokenOutputs = [{ token: { key: 'token1' } }] as OrderIO[]; | ||
}); | ||
|
||
describe('successful cases', () => { | ||
it('should successfully return deployment transaction args', async () => { | ||
const result = await getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs); | ||
|
||
expect(result).toEqual({ | ||
approvals: [{ token: '0x123', amount: '1000', symbol: 'TEST' }], | ||
deploymentCalldata: { | ||
deposit: '0xdeposit', | ||
addOrder: '0xaddOrder' | ||
}, | ||
orderbookAddress: '0xorderbook', | ||
chainId: 1 | ||
}); | ||
|
||
expect(mockGui.generateApprovalCalldatas).toHaveBeenCalledWith('0xuser'); | ||
expect(mockGui.generateDepositAndAddOrderCalldatas).toHaveBeenCalled(); | ||
}); | ||
}); | ||
|
||
describe('input validation errors', () => { | ||
it('should throw MISSING_GUI when GUI is null', async () => { | ||
await expect( | ||
getDeploymentTransactionArgs(null, mockWagmiConfig, mockTokenOutputs) | ||
).rejects.toThrow(AddOrderErrors.MISSING_GUI); | ||
}); | ||
|
||
it('should throw MISSING_CONFIG when wagmiConfig is undefined', async () => { | ||
await expect( | ||
getDeploymentTransactionArgs(mockGui, undefined, mockTokenOutputs) | ||
).rejects.toThrow(AddOrderErrors.MISSING_CONFIG); | ||
}); | ||
|
||
it('should throw NO_WALLET when wallet address is not found', async () => { | ||
(getAccount as any).mockReturnValue({ address: null }); | ||
await expect( | ||
getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs) | ||
).rejects.toThrow(AddOrderErrors.NO_WALLET); | ||
}); | ||
}); | ||
|
||
describe('deployment errors', () => { | ||
it('should throw INVALID_CHAIN_ID when chain ID is missing', async () => { | ||
mockGui.getCurrentDeployment = vi.fn().mockReturnValue({ | ||
deployment: { | ||
order: { | ||
network: {}, | ||
orderbook: { address: '0xorderbook' } | ||
} | ||
} | ||
}); | ||
|
||
await expect( | ||
getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs) | ||
).rejects.toThrow(AddOrderErrors.INVALID_CHAIN_ID); | ||
}); | ||
|
||
it('should throw MISSING_ORDERBOOK when orderbook address is missing', async () => { | ||
mockGui.getCurrentDeployment = vi.fn().mockReturnValue({ | ||
deployment: { | ||
order: { | ||
network: { 'chain-id': 1 }, | ||
orderbook: {} | ||
} | ||
} | ||
}); | ||
|
||
await expect( | ||
getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs) | ||
).rejects.toThrow(AddOrderErrors.MISSING_ORDERBOOK); | ||
}); | ||
}); | ||
|
||
describe('approval and calldata errors', () => { | ||
it('should throw APPROVAL_FAILED when generateApprovalCalldatas fails', async () => { | ||
mockGui.generateApprovalCalldatas = vi.fn().mockRejectedValue(new Error('Approval error')); | ||
|
||
await expect( | ||
getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs) | ||
).rejects.toThrow(`${AddOrderErrors.APPROVAL_FAILED}: Approval error`); | ||
}); | ||
|
||
it('should throw DEPLOYMENT_FAILED when generateDepositAndAddOrderCalldatas fails', async () => { | ||
mockGui.generateDepositAndAddOrderCalldatas = vi | ||
.fn() | ||
.mockRejectedValue(new Error('Deployment error')); | ||
|
||
await expect( | ||
getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs) | ||
).rejects.toThrow(`${AddOrderErrors.DEPLOYMENT_FAILED}: Deployment error`); | ||
}); | ||
}); | ||
|
||
describe('token info errors', () => { | ||
it('should throw TOKEN_INFO_FAILED when token key is missing', async () => { | ||
const invalidTokenOutputs = [{ token: {} }] as OrderIO[]; | ||
|
||
await expect( | ||
getDeploymentTransactionArgs(mockGui, mockWagmiConfig, invalidTokenOutputs) | ||
).rejects.toThrow(`${AddOrderErrors.TOKEN_INFO_FAILED}: Token key is missing`); | ||
}); | ||
|
||
it('should throw TOKEN_INFO_FAILED when getTokenInfo fails', async () => { | ||
mockGui.getTokenInfo = vi.fn().mockRejectedValue(new Error('Token info error')); | ||
|
||
await expect( | ||
getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs) | ||
).rejects.toThrow(`${AddOrderErrors.TOKEN_INFO_FAILED}: Token info error`); | ||
}); | ||
|
||
it('should throw TOKEN_INFO_FAILED when token info is not found for approval', async () => { | ||
mockGui.getTokenInfo = vi.fn().mockResolvedValue({ | ||
address: '0x456', // Different address than the approval token | ||
symbol: 'TEST' | ||
}); | ||
|
||
await expect( | ||
getDeploymentTransactionArgs(mockGui, mockWagmiConfig, mockTokenOutputs) | ||
).rejects.toThrow( | ||
`${AddOrderErrors.TOKEN_INFO_FAILED}: Token info not found for address: 0x123` | ||
); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
107 changes: 107 additions & 0 deletions
107
packages/ui-components/src/lib/components/deployment/DisclaimerModal.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
<script lang="ts"> | ||
import { Alert, Modal, Button } from 'flowbite-svelte'; | ||
import { ExclamationCircleSolid } from 'flowbite-svelte-icons'; | ||
import { getDeploymentTransactionArgs } from './getDeploymentTransactionArgs'; | ||
import type { Config } from 'wagmi'; | ||
import type { Writable } from 'svelte/store'; | ||
import type { | ||
ApprovalCalldataResult, | ||
DepositAndAddOrderCalldataResult, | ||
DotrainOrderGui, | ||
OrderIO | ||
} from '@rainlanguage/orderbook/js_api'; | ||
import type { Hex } from 'viem'; | ||
import type { HandleAddOrderResult } from './getDeploymentTransactionArgs'; | ||
export let open: boolean; | ||
export let gui: DotrainOrderGui; | ||
export let allTokenOutputs: OrderIO[]; | ||
export let wagmiConfig: Writable<Config | undefined>; | ||
export let handleDeployModal: (args: { | ||
approvals: ApprovalCalldataResult; | ||
deploymentCalldata: DepositAndAddOrderCalldataResult; | ||
orderbookAddress: Hex; | ||
chainId: number; | ||
}) => void; | ||
let result: HandleAddOrderResult | null = null; | ||
let error: string | null = null; | ||
let errorDetails: string | null = null; | ||
let deployButtonText: 'Loading...' | 'Deploy' | 'Error' = 'Loading...'; | ||
const handleOpenModal = async () => { | ||
try { | ||
result = await getDeploymentTransactionArgs(gui, $wagmiConfig, allTokenOutputs); | ||
deployButtonText = 'Deploy'; | ||
} catch (e) { | ||
deployButtonText = 'Error'; | ||
error = 'Error getting deployment transaction data:'; | ||
errorDetails = e instanceof Error ? e.message : 'Unknown error'; | ||
} | ||
}; | ||
$: if (open === true) { | ||
handleOpenModal(); | ||
} | ||
async function handleAcceptDisclaimer() { | ||
if (!result) { | ||
error = 'No result found'; | ||
return; | ||
} else { | ||
open = false; | ||
handleDeployModal(result); | ||
} | ||
} | ||
</script> | ||
|
||
<Modal bind:open> | ||
<div class="flex flex-col items-start gap-y-4"> | ||
<div class="space-y-4"> | ||
<Alert color="red" class="text-base"> | ||
<div class="flex items-center justify-center"> | ||
<ExclamationCircleSolid class="h-6 w-6 text-red-500" /> | ||
<span class="ml-2"> | ||
Before you deploy your strategy, make sure you understand the following... | ||
</span> | ||
</div> | ||
</Alert> | ||
<ul class="list-outside list-disc space-y-2 text-gray-700"> | ||
<li class="ml-4"> | ||
This front end is provided as a tool to interact with the Raindex smart contracts. | ||
</li> | ||
<li class="ml-4"> | ||
You are deploying your own strategy and depositing funds to an immutable smart contract | ||
using your own wallet and private keys. | ||
</li> | ||
<li class="ml-4"> | ||
Nobody is custodying your funds, there is no recourse for recovery of funds if lost. | ||
</li> | ||
<li class="ml-4">There is no endorsement or guarantee provided with these strategies.</li> | ||
<li class="ml-4"> | ||
Do not proceed if you do not understand the strategy you are deploying. | ||
</li> | ||
<li class="ml-4">Do not invest unless you are prepared to lose all funds.</li> | ||
</ul> | ||
</div> | ||
<div class="flex gap-2"> | ||
<Button | ||
size="lg" | ||
class="w-32" | ||
color="green" | ||
disabled={!result} | ||
on:click={handleAcceptDisclaimer} | ||
> | ||
{deployButtonText} | ||
</Button> | ||
<Button size="lg" class="w-32" color="red" on:click={() => (open = false)}>Cancel</Button> | ||
</div> | ||
<div class="flex flex-col"> | ||
{#if error} | ||
<span class="ml-2 text-red-500">{error}</span> | ||
{/if} | ||
{#if errorDetails} | ||
<span class="ml-2 text-red-500">{errorDetails}</span> | ||
{/if} | ||
</div> | ||
</div> | ||
</Modal> |
Oops, something went wrong.