Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
.repo-history-backups/
temp_auto_push.bat
.claude/

.gitignore
branch_structure.json
7 changes: 0 additions & 7 deletions .vscode/extensions.json

This file was deleted.

50 changes: 0 additions & 50 deletions .vscode/launch.json

This file was deleted.

23 changes: 0 additions & 23 deletions .vscode/settings.json

This file was deleted.

2 changes: 0 additions & 2 deletions .vscode/spellright.dict

This file was deleted.

33 changes: 0 additions & 33 deletions .vscode/tasks.json

This file was deleted.

18 changes: 7 additions & 11 deletions esure-backend/scenarios/issued-asset-payment.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
{
"schemaVersion": 1,
"id": "issued-asset-payment",
"version": 2,
"version": 1,
"name": "Issued asset payment",
"description": "Two issued assets share the code TESTUSD but have distinct issuer references. The recipient holds a trustline only to the first issuer, so a payment of the second issuer's TESTUSD must be rejected: a matching asset code is not enough because the issuer identity is part of the asset. The payment must fail with the precise missing-trustline error for the actual second issuer, and the recipient's existing trustline to the first issuer must remain present with its balance unchanged.",
"description": "Issue TESTUSD and send it to a recipient through a trustline.",
"network": "testnet",
"accounts": [
{ "id": "issuer-one", "generate": true, "fund": true },
{ "id": "issuer-two", "generate": true, "fund": true },
{ "id": "issuer", "generate": true, "fund": true },
{ "id": "recipient", "generate": true, "fund": true }
],
"assets": [
{ "id": "xlm", "type": "native" },
{ "id": "test-usd-one", "type": "issued", "code": "TESTUSD", "issuer": "issuer-one" },
{ "id": "test-usd-two", "type": "issued", "code": "TESTUSD", "issuer": "issuer-two" }
{ "id": "test-usd", "type": "issued", "code": "TESTUSD", "issuer": "issuer" }
],
"steps": [
{ "id": "create-trustline", "type": "changeTrust", "account": "recipient", "asset": "test-usd-one" },
{ "id": "send-testusd", "type": "payment", "from": "issuer-two", "to": "recipient", "asset": "test-usd-two", "amount": "100", "expectedError": { "code": "MISSING_TRUSTLINE", "account": "recipient", "asset": "test-usd-two", "issuer": "issuer-two" } }
{ "id": "create-trustline", "type": "changeTrust", "account": "recipient", "asset": "test-usd" },
{ "id": "send-testusd", "type": "payment", "from": "issuer", "to": "recipient", "asset": "test-usd", "amount": "100" }
],
"assertions": [
{ "type": "trustlineExists", "account": "recipient", "asset": "test-usd-one" },
{ "type": "balanceEquals", "account": "recipient", "asset": "test-usd-one", "amount": "0" },
{ "type": "trustlineMissing", "account": "recipient", "asset": "test-usd-two" }
{ "type": "balanceEquals", "account": "recipient", "asset": "test-usd", "amount": "100" }
]
}
41 changes: 8 additions & 33 deletions esure-backend/scenarios/missing-trustline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,10 @@ schemaVersion: 1
id: missing-trustline
version: 1
name: Missing trustline
description: >-
Verify that an asset payment fails when the recipient has no trustline.
Two issued assets share the same code TESTUSD but are issued by distinct
issuer accounts. The recipient trusts only the first issuer, so a payment
using the second issuer's asset must fail with a missing-trustline
operation code. This exercises issuer-aware failure matching: a matching
asset code alone must not satisfy the trustline requirement, because the
recipient's trustline is bound to a different issuer identity.
description: Verify that an asset payment fails when the recipient has no trustline.
network: testnet
accounts:
- id: issuer-a
generate: true
fund: true
- id: issuer-b
- id: issuer
generate: true
fund: true
- id: recipient
Expand All @@ -24,35 +14,20 @@ accounts:
assets:
- id: xlm
type: native
- id: test-usd-a
type: issued
code: TESTUSD
issuer: issuer-a
- id: test-usd-b
- id: test-usd
type: issued
code: TESTUSD
issuer: issuer-b
issuer: issuer
steps:
- id: trust-testusd-a
type: changeTrust
account: recipient
asset: test-usd-a
limit: "1000"
- id: send-testusd-b
- id: send-testusd
type: payment
from: issuer-b
from: issuer
to: recipient
asset: test-usd-b
asset: test-usd
amount: "100"
assertions:
- type: stepFailedWith
step: send-testusd-b
step: send-testusd
transactionCode: tx_failed
operationCodes:
- op_no_trust
- type: trustlineExists
account: recipient
asset: test-usd-a
- type: trustlineBalanceUnchanged
account: recipient
asset: test-usd-a
32 changes: 10 additions & 22 deletions esure-backend/src/scenario-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ function validateSafeValues(value: unknown): void {
const visit = (item: unknown, path: string): void => {
if (typeof item === "string") {
if (/S[A-Z2-7]{55}/.test(item)) issues.push(`${path} must not contain a Stellar secret seed`);
if (/\b(?:https|ftp|file):\/\//i.test(item)) issues.push(`${path} must not contain a URL`);
if (/\b(?:https?|ftp|file):\/\//i.test(item)) issues.push(`${path} must not contain a URL`);
return;
}
if (Array.isArray(item)) { item.forEach((child, index) => visit(child, `${path}/${index}`)); return; }
Expand Down Expand Up @@ -184,19 +184,11 @@ function validateReferences(scenario: ScenarioDefinition): void {
if (new Set(step.path).size !== step.path.length) issues.push(`step ${step.id} path contains duplicate assets`);
for (const assetId of step.path) {
if (!assets.has(assetId)) issues.push(`step ${step.id} references unknown path asset ${assetId}`);
if (assetId === step.sendAsset || assetId === step.destAsset) issues.push(`step ${step.id} path must contain only intermediate assets`);
}
}
}
for (const assertion of scenario.assertions) {
if (assertion.type === "transactionMemoEquals" || assertion.type === "stepSucceeded" || assertion.type === "transactionConfirmed" || assertion.type === "stepFailedWith") {
if (!steps.has(assertion.step)) issues.push(`assertion ${assertion.type} references unknown step ${assertion.step}`);
} else if (assertion.type === "accountExists") {
if (!accounts.has(assertion.account)) issues.push(`assertion accountExists references unknown account ${assertion.account}`);
} else {
if (!accounts.has(assertion.account)) issues.push(`assertion ${assertion.type} references unknown account ${assertion.account}`);
if (!assets.has(assertion.asset)) issues.push(`assertion ${assertion.type} references unknown asset ${assertion.asset}`);
}
}
for (const assertion of scenario.assertions) validateAssertionReferences(assertion, accounts, assets, steps, scenario, issues);
if (issues.length) throw new ScenarioValidationError(issues);
}

Expand All @@ -219,18 +211,14 @@ function validateAssertionReferences(assertion: ScenarioAssertion, accounts: Set
}

function uniqueIds(items: readonly { id: string }[], label: string, issues: string[]): Set<string> {
const seen = new Set<string>();
const ids = new Set<string>();
for (const item of items) {
if (seen.has(item.id)) issues.push(`${label} contains duplicate id ${item.id}`);
seen.add(item.id);
if (ids.has(item.id)) issues.push(`${label} contains duplicate id ${item.id}`);
ids.add(item.id);
}
return seen;
return ids;
}

function formatAjvErrors(errors: ErrorObject[] | null): string[] {
if (!errors) return [];
return errors.map((error) => {
const path = error.instancePath || "/";
return `${path} ${error.message ?? "is invalid"}`;
});
}
function formatAjvErrors(errors: ErrorObject[] | null | undefined): string[] {
return (errors ?? []).slice(0, 50).map((error) => `${error.instancePath || "/"} ${error.message ?? "is invalid"}`);
}
Loading
Loading