|
| 1 | +import { test } from 'node:test'; |
| 2 | +import assert from 'node:assert/strict'; |
| 3 | +import { readFile } from 'node:fs/promises'; |
| 4 | +import { dirname, resolve } from 'node:path'; |
| 5 | +import { fileURLToPath } from 'node:url'; |
| 6 | + |
| 7 | +const here = dirname(fileURLToPath(import.meta.url)); |
| 8 | +const sourcePath = resolve(here, '../../src/features/marketCreation/marketCreationWorkflow.js'); |
| 9 | +const source = await readFile(sourcePath, 'utf8'); |
| 10 | +const workflow = await import(`data:text/javascript;charset=utf-8,${encodeURIComponent(source)}`); |
| 11 | + |
| 12 | +const { |
| 13 | + buildMetadataDraft, |
| 14 | + buildOneStepMarketPlan, |
| 15 | + buildPermissionlessStackPlan, |
| 16 | + createMarketWizardDefaults, |
| 17 | + KNOWN_ORGANIZATIONS, |
| 18 | + MARKET_CREATION_STAGES, |
| 19 | + PERMISSIONLESS_STACK_STAGES, |
| 20 | + validateContractActionDependencies, |
| 21 | + validateOneStepMarketPlan, |
| 22 | +} = workflow; |
| 23 | + |
| 24 | +const NOW = 1_782_777_600; |
| 25 | + |
| 26 | +test('Kleros defaults use KIP, PNK, sDAI, and FLM liquidity', () => { |
| 27 | + const defaults = createMarketWizardDefaults({ organizationId: 'kleros', nowSeconds: NOW }); |
| 28 | + |
| 29 | + assert.equal(defaults.organizationName, 'Kleros DAO'); |
| 30 | + assert.equal(defaults.proposalCode, 'KIP-90'); |
| 31 | + assert.equal(defaults.companyToken.symbol, 'PNK'); |
| 32 | + assert.equal(defaults.currencyToken.symbol, 'sDAI'); |
| 33 | + assert.equal(defaults.initialLiquidityMode, 'flm'); |
| 34 | + assert.equal(defaults.snapshotLinkAfterLiquidity, true); |
| 35 | +}); |
| 36 | + |
| 37 | +test('Gnosis defaults use GIP, GNO, sDAI, and Gnosis Snapshot space', () => { |
| 38 | + const defaults = createMarketWizardDefaults({ organizationId: 'gnosis', nowSeconds: NOW }); |
| 39 | + |
| 40 | + assert.equal(defaults.organizationName, 'Gnosis DAO'); |
| 41 | + assert.equal(defaults.proposalCode, 'GIP-151'); |
| 42 | + assert.equal(defaults.companyToken.symbol, 'GNO'); |
| 43 | + assert.equal(defaults.currencyToken.symbol, 'sDAI'); |
| 44 | + assert.equal(KNOWN_ORGANIZATIONS.gnosis.snapshotSpace, 'gnosis.eth'); |
| 45 | +}); |
| 46 | + |
| 47 | +test('one-step market plan covers the operational stages in required order', () => { |
| 48 | + const plan = buildOneStepMarketPlan({ organizationId: 'kleros', nowSeconds: NOW }); |
| 49 | + const stageIds = plan.stages.map((stage) => stage.id); |
| 50 | + |
| 51 | + assert.deepEqual(stageIds, MARKET_CREATION_STAGES.map((stage) => stage.id)); |
| 52 | + assert.ok(stageIds.indexOf('liquidity') < stageIds.indexOf('snapshot')); |
| 53 | + assert.ok(stageIds.includes('metadata')); |
| 54 | + assert.ok(stageIds.includes('indexing')); |
| 55 | + assert.ok(stageIds.includes('arbitrage')); |
| 56 | + assert.ok(stageIds.includes('publish')); |
| 57 | +}); |
| 58 | + |
| 59 | +test('metadata draft includes registry fields needed by market pages and proposal routing', () => { |
| 60 | + const draft = buildMetadataDraft({ |
| 61 | + organizationId: 'kleros', |
| 62 | + nowSeconds: NOW, |
| 63 | + snapshotId: '0xba2749a4f1283da9d1ca925d9f17bf712fa06a23e6a07d759c54340277820932', |
| 64 | + }); |
| 65 | + |
| 66 | + assert.equal(draft.chain, 100); |
| 67 | + assert.equal(draft.snapshot_id, '0xba2749a4f1283da9d1ca925d9f17bf712fa06a23e6a07d759c54340277820932'); |
| 68 | + assert.equal(draft.resolution_status, 'unresolved'); |
| 69 | + assert.equal(draft.visibility, 'public'); |
| 70 | + assert.equal(draft.companyTokens.base.tokenSymbol, 'PNK'); |
| 71 | + assert.equal(draft.currencyTokens.base.tokenSymbol, 'sDAI'); |
| 72 | + assert.equal(draft.flm.mode, 'flm'); |
| 73 | + assert.equal(draft.snapshot.visibilityMetadata.includeOnSnapshotWebsite, true); |
| 74 | + assert.equal( |
| 75 | + draft.registry.proposalMetadataMethod, |
| 76 | + 'FutarchyOrganizationMetadata.createAndAddProposalMetadata' |
| 77 | + ); |
| 78 | + assert.equal( |
| 79 | + draft.registry.defaultLiquidityManagerMethod, |
| 80 | + 'FutarchyOrganizationMetadata.setDefaultLiquidityManager' |
| 81 | + ); |
| 82 | +}); |
| 83 | + |
| 84 | +test('permissionless stack plan includes org listing, owner proposals, and default FLM', () => { |
| 85 | + const plan = buildPermissionlessStackPlan(); |
| 86 | + const stageIds = plan.stages.map((stage) => stage.id); |
| 87 | + |
| 88 | + assert.deepEqual(stageIds, PERMISSIONLESS_STACK_STAGES.map((stage) => stage.id)); |
| 89 | + assert.ok(stageIds.includes('create-organization')); |
| 90 | + assert.ok(stageIds.includes('list-organization')); |
| 91 | + assert.ok(stageIds.includes('default-flm')); |
| 92 | + assert.ok(stageIds.includes('owner-proposal')); |
| 93 | + assert.ok(stageIds.indexOf('default-flm') < stageIds.indexOf('create-organization')); |
| 94 | + assert.equal(plan.values.chainId, 10200); |
| 95 | +}); |
| 96 | + |
| 97 | +test('permissionless contract actions create FLM before default organization wiring', () => { |
| 98 | + const plan = buildPermissionlessStackPlan(); |
| 99 | + const actions = plan.contractActions; |
| 100 | + const actionIds = actions.map((action) => action.id); |
| 101 | + const createFlm = actions.find((action) => action.id === 'create-default-flm-bundle'); |
| 102 | + const createOrganization = actions.find((action) => action.id === 'create-and-list-organization'); |
| 103 | + |
| 104 | + assert.equal(validateContractActionDependencies(actions).ok, true); |
| 105 | + assert.equal(createFlm.contract, 'FutarchyLiquidityManagerFactory'); |
| 106 | + assert.equal(createFlm.method, 'createLiquidityManager'); |
| 107 | + assert.equal(createOrganization.contract, 'FutarchyAggregatorsMetadata'); |
| 108 | + assert.equal(createOrganization.method, 'createAndAddOrganizationMetadataWithDefaultLiquidityManager'); |
| 109 | + assert.ok(createOrganization.dependsOn.includes('create-default-flm-bundle')); |
| 110 | + assert.ok(actionIds.indexOf('create-default-flm-bundle') < actionIds.indexOf('create-and-list-organization')); |
| 111 | +}); |
| 112 | + |
| 113 | +test('one-step market contract actions order market, FLM, official proposal, liquidity, and Snapshot', () => { |
| 114 | + const plan = buildOneStepMarketPlan({ organizationId: 'kleros', nowSeconds: NOW }); |
| 115 | + const actions = plan.contractActions; |
| 116 | + const actionIds = actions.map((action) => action.id); |
| 117 | + const createMarket = actions.find((action) => action.id === 'create-futarchy-proposal'); |
| 118 | + const createFlm = actions.find((action) => action.id === 'create-flm-bundle'); |
| 119 | + const setOfficialProposal = actions.find((action) => action.id === 'set-official-proposal'); |
| 120 | + const linkSnapshot = actions.find((action) => action.id === 'link-snapshot-proposal'); |
| 121 | + |
| 122 | + assert.equal(validateContractActionDependencies(actions).ok, true); |
| 123 | + assert.equal(createMarket.contract, 'IFutarchyFactory'); |
| 124 | + assert.equal(createMarket.method, 'createProposal'); |
| 125 | + assert.equal(createFlm.contract, 'FutarchyLiquidityManagerFactory'); |
| 126 | + assert.equal(createFlm.method, 'createLiquidityManager'); |
| 127 | + assert.equal(setOfficialProposal.contract, 'FutarchyOfficialProposalSource'); |
| 128 | + assert.equal(setOfficialProposal.method, 'setOfficialProposal'); |
| 129 | + assert.ok(setOfficialProposal.dependsOn.includes('create-futarchy-proposal')); |
| 130 | + assert.ok(linkSnapshot.dependsOn.includes('bootstrap-flm-liquidity')); |
| 131 | + assert.ok(actionIds.indexOf('create-flm-bundle') < actionIds.indexOf('set-official-proposal')); |
| 132 | + assert.ok(actionIds.indexOf('set-official-proposal') < actionIds.indexOf('bootstrap-flm-liquidity')); |
| 133 | + assert.ok(actionIds.indexOf('bootstrap-flm-liquidity') < actionIds.indexOf('link-snapshot-proposal')); |
| 134 | +}); |
| 135 | + |
| 136 | +test('validation rejects flows that would link Snapshot before liquidity', () => { |
| 137 | + const validPlan = buildOneStepMarketPlan({ organizationId: 'gnosis', nowSeconds: NOW }); |
| 138 | + assert.equal(validateOneStepMarketPlan(validPlan, { nowSeconds: NOW }).ok, true); |
| 139 | + |
| 140 | + const invalidPlan = buildOneStepMarketPlan({ |
| 141 | + organizationId: 'gnosis', |
| 142 | + nowSeconds: NOW, |
| 143 | + snapshotLinkAfterLiquidity: false, |
| 144 | + }); |
| 145 | + assert.deepEqual(validateOneStepMarketPlan(invalidPlan).errors, ['snapshotLinkAfterLiquidity']); |
| 146 | +}); |
0 commit comments