diff --git a/backend/.env.example b/backend/.env.example index 9846381..a1437dd 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -45,3 +45,9 @@ SMTP_HOST=smtp.sendgrid.net SMTP_PORT=587 SMTP_USER=apikey SMTP_PASS=your-smt-pass-here + +# Soroban contribution router contract (enables on-chain slippage enforcement + atomic fee split) +# Leave unset to fall back to classic path payment +ROUTER_CONTRACT_ID= +XLM_SAC_ADDRESS= +USDC_SAC_ADDRESS= diff --git a/backend/eslint.config.js b/backend/eslint.config.js new file mode 100644 index 0000000..a42a867 --- /dev/null +++ b/backend/eslint.config.js @@ -0,0 +1,19 @@ +// eslint.config.js — CommonJS flat config (no "type":"module" in package.json) +module.exports = [ + { + languageOptions: { + ecmaVersion: 2022, + sourceType: 'commonjs', + globals: { + require: 'readonly', module: 'readonly', exports: 'readonly', __dirname: 'readonly', + process: 'readonly', Buffer: 'readonly', console: 'readonly', + setTimeout: 'readonly', setInterval: 'readonly', setImmediate: 'readonly', + fetch: 'readonly', URL: 'readonly', URLSearchParams: 'readonly', AbortSignal: 'readonly', + }, + }, + rules: { + 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + }, + }, + { ignores: ['node_modules/', 'dist/'] }, +]; diff --git a/backend/package.json b/backend/package.json index 67098da..5a81de1 100644 --- a/backend/package.json +++ b/backend/package.json @@ -6,7 +6,7 @@ "scripts": { "dev": "nodemon src/index.js", "start": "node src/index.js", - "test": "JWT_SECRET=testsecret node --test src/**/*.test.js", + "test": "JWT_SECRET=testsecret node --test --test-force-exit src/**/*.test.js", "rotate-wallet-secrets": "node src/scripts/rotateWalletSecrets.js" }, "dependencies": { diff --git a/backend/src/config/database.js b/backend/src/config/database.js index fefb3c3..93ac716 100644 --- a/backend/src/config/database.js +++ b/backend/src/config/database.js @@ -1,7 +1,10 @@ const { Pool } = require('pg'); const logger = require('./logger'); -const pool = new Pool({ connectionString: process.env.DATABASE_URL }); +const pool = new Pool({ + connectionString: process.env.DATABASE_URL, + connectionTimeoutMillis: 5000, +}); pool.on('error', (err) => { logger.error('Unexpected database pool error', { error: err.message }); diff --git a/backend/src/config/stellar.js b/backend/src/config/stellar.js index cb3eff0..61dbf54 100644 --- a/backend/src/config/stellar.js +++ b/backend/src/config/stellar.js @@ -9,7 +9,7 @@ const server = new Horizon.Server( const networkPassphrase = isTestnet ? Networks.TESTNET : Networks.PUBLIC; // USDC asset — issuer differs between testnet and mainnet -const USDC = new Asset('USDC', process.env.USDC_ISSUER); +const USDC = process.env.USDC_ISSUER ? new Asset('USDC', process.env.USDC_ISSUER) : null; function parseAdditionalAssets() { if (!process.env.STELLAR_EXTRA_ASSETS) return {}; diff --git a/backend/src/models.test.js b/backend/src/models.test.js index 96914d6..8fcf0e8 100644 --- a/backend/src/models.test.js +++ b/backend/src/models.test.js @@ -29,6 +29,7 @@ describe('Database Models & Constraints', async () => { await client.query('ROLLBACK'); client.release(); } + await pool.end(); }); it('should allow creating a valid user', async () => { diff --git a/backend/src/routes/campaigns.js b/backend/src/routes/campaigns.js index 6e5913f..29b74de 100644 --- a/backend/src/routes/campaigns.js +++ b/backend/src/routes/campaigns.js @@ -191,13 +191,6 @@ router.get('/', getCampaignsValidation, validateRequest, async (req, res) => { // Get single Campaign router.get('/:id', async (req, res) => { - const { rows } = await db.query( - `SELECT c.*, u.name AS creator_name, u.kyc_status AS creator_kyc_status - FROM campaigns c - JOIN users u ON u.id = c.creator_id - WHERE c.id = $1`, - [req.params.id] - ); const query = ` SELECT *, (SELECT COUNT(DISTINCT sender_public_key)::int FROM contributions WHERE campaign_id = $1) AS contributor_count @@ -274,6 +267,7 @@ router.get('/:id/embed', async (req, res) => { progress_percentage: Math.round(pct * 10) / 10, contribution_url: `${process.env.FRONTEND_URL || 'http://localhost:5173'}/campaigns/${campaign.id}`, }); +}); // Get backers for a campaign router.get('/:id/backers', async (req, res) => { const campaignId = req.params.id; diff --git a/backend/src/routes/campaigns.test.js b/backend/src/routes/campaigns.test.js index d11968f..954db96 100644 --- a/backend/src/routes/campaigns.test.js +++ b/backend/src/routes/campaigns.test.js @@ -31,6 +31,12 @@ function buildApp({ queryImpl, buildWithdrawalTransactionImpl, insertWithdrawalP next(); }, }, + '../middleware/validation': { + createCampaignValidation: [], + getCampaignsValidation: [], + createCampaignUpdateValidation: [], + validateRequest: (_req, _res, next) => next(), + }, }); const app = express(); @@ -73,7 +79,7 @@ test('POST /api/campaigns blocks unverified creators when KYC gate is enabled', const app = buildApp({ authUser: { userId: 'creator-1', role: 'creator' }, queryImpl: async (text) => { - if (text.includes('SELECT wallet_public_key, kyc_status FROM users')) { + if (text.includes('SELECT email, wallet_public_key, kyc_status FROM users')) { return { rows: [{ wallet_public_key: 'GCREATOR', kyc_status: 'pending' }] }; } return { rows: [] }; @@ -102,7 +108,7 @@ test('POST /api/campaigns allows creation when KYC gate is disabled', async (t) const app = buildApp({ authUser: { userId: 'creator-1', role: 'creator' }, queryImpl: async (text) => { - if (text.includes('SELECT wallet_public_key, kyc_status FROM users')) { + if (text.includes('SELECT email, wallet_public_key, kyc_status FROM users')) { return { rows: [{ wallet_public_key: 'GCREATOR', kyc_status: 'unverified' }] }; } if (text.includes('INSERT INTO campaigns')) { diff --git a/backend/src/routes/contributions.js b/backend/src/routes/contributions.js index 170ac3e..8c79059 100644 --- a/backend/src/routes/contributions.js +++ b/backend/src/routes/contributions.js @@ -314,42 +314,12 @@ router.post('/submit-signed', requireAuth, async (req, res) => { return res.status(400).json({ error: 'signed_xdr and prepare_token are required' }); } - let txHash; - let conversionQuote = null; - let unsignedXdr; - let signedXdr; - let flowMetadata; - let platformFeeAmount = 0; - - if (send_asset === campaign.asset_type) { - const prepared = await prepareSignedContributionPayment({ - senderSecret, - destinationPublicKey: campaign.wallet_public_key, - asset: send_asset, - amount, - memo: `cp-${campaign_id}`, - }); - unsignedXdr = prepared.unsignedXdr; - signedXdr = prepared.signedXdr; - platformFeeAmount = prepared.feeAmount || 0; - flowMetadata = { - flow: 'payment', - send_asset, - amount: String(amount), - contributor_public_key: contributorPublicKey, - platform_fee_amount: platformFeeAmount, - }; - } else { - const paths = await getPathPaymentQuote({ - sendAsset: send_asset, - destAsset: campaign.asset_type, - destAmount: amount, - }); - if (!paths.length) { - return res.status(422).json({ - error: `No conversion path found for ${send_asset} -> ${campaign.asset_type}`, - }); - } + let prepared; + try { + prepared = verifyPreparedContributionToken(prepare_token); + } catch (err) { + return res.status(401).json({ error: 'Invalid or expired prepare token' }); + } if (prepared.user_id !== req.user.userId) { return res.status(403).json({ error: 'Prepared contribution token does not belong to this user' }); @@ -361,27 +331,6 @@ router.post('/submit-signed', requireAuth, async (req, res) => { unsignedXdr: prepared.unsigned_xdr, senderPublicKey: prepared.sender_public_key, }); - unsignedXdr = prepared.unsignedXdr; - signedXdr = prepared.signedXdr; - platformFeeAmount = prepared.feeAmount || 0; - - conversionQuote = { - send_asset, - campaign_asset: campaign.asset_type, - campaign_amount: String(amount), - quoted_source_amount: bestPath.source_amount, - max_send_amount: sendMax, - path: bestPath.path, - }; - flowMetadata = { - flow: 'path_payment_strict_receive', - send_asset, - dest_asset: campaign.asset_type, - dest_amount: String(amount), - max_send_amount: sendMax, - contributor_public_key: contributorPublicKey, - platform_fee_amount: platformFeeAmount, - }; } catch (err) { return res.status(422).json({ error: err.message }); } @@ -417,8 +366,8 @@ router.post('/submit-signed', requireAuth, async (req, res) => { tx_hash: txHash, stellar_transaction_id: stellarTransactionId, message: 'Transaction submitted', - platform_fee_amount: platformFeeAmount, - conversion_quote: conversionQuote, + platform_fee_amount: prepared.flow_metadata?.platform_fee_amount || 0, + conversion_quote: prepared.conversion_quote || null, }); }); @@ -466,6 +415,7 @@ router.post('/', requireAuth, contributionValidation, validateRequest, async (re tx_hash: result.txHash, stellar_transaction_id: result.stellarTransactionId, message: 'Transaction submitted', + platform_fee_amount: result.flowMetadata?.platform_fee_amount || 0, conversion_quote: result.conversionQuote, }); } catch (err) { diff --git a/backend/src/routes/contributions.test.js b/backend/src/routes/contributions.test.js index 00c3de5..8eb805d 100644 --- a/backend/src/routes/contributions.test.js +++ b/backend/src/routes/contributions.test.js @@ -122,6 +122,17 @@ function buildApp({ queryImpl, stellarImpl, stellarTxImpl }) { amount, sendAsset, }) => { + // Call ensureCustodialAccountFundedAndTrusted so tests can inject failures + try { + await stellarStub.ensureCustodialAccountFundedAndTrusted({ + publicKey: walletPublicKey, + secret: 'SDECRYPTED', + }); + } catch (err) { + err.statusCode = err.statusCode || 503; + throw err; + } + const intent = await contributionServiceStub.buildContributionIntent({ campaign, amount, @@ -148,20 +159,33 @@ function buildApp({ queryImpl, stellarImpl, stellarTxImpl }) { memo: 'cp-c-1', }); - const txHash = await stellarStub.submitPreparedTransaction(prepared.signedXdr); + let txHash; + try { + txHash = await stellarStub.submitPreparedTransaction(prepared.signedXdr); + } catch (err) { + err.statusCode = err.statusCode || 502; + throw err; + } + + const flowMetadata = { + ...intent.flowMetadata, + platform_fee_amount: prepared.feeAmount || 0, + }; + const stellarTransactionId = await stellarTxStub.insertContributionSubmitted(null, { txHash, campaignId, userId, unsignedXdr: prepared.unsignedXdr, signedXdr: prepared.signedXdr, - metadata: intent.flowMetadata, + metadata: flowMetadata, }); return { txHash, stellarTransactionId, conversionQuote: intent.conversionQuote, + flowMetadata, }; }, }; @@ -184,6 +208,10 @@ function buildApp({ queryImpl, stellarImpl, stellarTxImpl }) { next(); }, }, + '../middleware/validation': { + contributionValidation: [], + validateRequest: (_req, _res, next) => next(), + }, }); const app = express(); @@ -244,7 +272,7 @@ test('POST /api/contributions uses direct payment for same asset', async () => { queryImpl: async (text) => { if (text.includes('FROM campaigns')) { return { - rows: [{ id: 'c-1', status: 'active', asset_type: 'XLM', wallet_public_key: 'GDEST' }], + rows: [{ id: '00000000-0000-0000-0000-000000000001', status: 'active', asset_type: 'XLM', wallet_public_key: 'GDEST' }], }; } if (text.includes('FROM users')) { @@ -273,7 +301,7 @@ test('POST /api/contributions uses direct payment for same asset', async () => { const response = await request(app) .post('/api/contributions') .set('Authorization', 'Bearer token') - .send({ campaign_id: 'c-1', amount: '5.0000000', send_asset: 'XLM' }); + .send({ campaign_id: '00000000-0000-0000-0000-000000000001', amount: '5.0000000', send_asset: 'XLM' }); assert.equal(response.status, 202); assert.equal(response.body.tx_hash, 'tx-direct'); @@ -289,7 +317,7 @@ test('POST /api/contributions uses direct payment for same USDC asset', async () queryImpl: async (text) => { if (text.includes('FROM campaigns')) { return { - rows: [{ id: 'c-2', status: 'active', asset_type: 'USDC', wallet_public_key: 'GDEST2' }], + rows: [{ id: '00000000-0000-0000-0000-000000000002', status: 'active', asset_type: 'USDC', wallet_public_key: 'GDEST2' }], }; } if (text.includes('FROM users')) { @@ -316,7 +344,7 @@ test('POST /api/contributions uses direct payment for same USDC asset', async () const response = await request(app) .post('/api/contributions') .set('Authorization', 'Bearer token') - .send({ campaign_id: 'c-2', amount: '7.0000000', send_asset: 'USDC' }); + .send({ campaign_id: '00000000-0000-0000-0000-000000000002', amount: '7.0000000', send_asset: 'USDC' }); assert.equal(response.status, 202); assert.equal(response.body.tx_hash, 'tx-direct-usdc'); @@ -329,7 +357,7 @@ test('POST /api/contributions uses path payment for conversion', async () => { queryImpl: async (text) => { if (text.includes('FROM campaigns')) { return { - rows: [{ id: 'c-1', status: 'active', asset_type: 'USDC', wallet_public_key: 'GDEST' }], + rows: [{ id: '00000000-0000-0000-0000-000000000001', status: 'active', asset_type: 'USDC', wallet_public_key: 'GDEST' }], }; } if (text.includes('FROM users')) { @@ -363,7 +391,7 @@ test('POST /api/contributions uses path payment for conversion', async () => { const response = await request(app) .post('/api/contributions') .set('Authorization', 'Bearer token') - .send({ campaign_id: 'c-1', amount: '4.5000000', send_asset: 'XLM' }); + .send({ campaign_id: '00000000-0000-0000-0000-000000000001', amount: '4.5000000', send_asset: 'XLM' }); assert.equal(response.status, 202); assert.equal(response.body.tx_hash, 'tx-path'); @@ -379,7 +407,7 @@ test('POST /api/contributions supports reverse conversion USDC -> XLM', async () queryImpl: async (text) => { if (text.includes('FROM campaigns')) { return { - rows: [{ id: 'c-3', status: 'active', asset_type: 'XLM', wallet_public_key: 'GDEST3' }], + rows: [{ id: '00000000-0000-0000-0000-000000000003', status: 'active', asset_type: 'XLM', wallet_public_key: 'GDEST3' }], }; } if (text.includes('FROM users')) { @@ -414,7 +442,7 @@ test('POST /api/contributions supports reverse conversion USDC -> XLM', async () const response = await request(app) .post('/api/contributions') .set('Authorization', 'Bearer token') - .send({ campaign_id: 'c-3', amount: '10.0000000', send_asset: 'USDC' }); + .send({ campaign_id: '00000000-0000-0000-0000-000000000003', amount: '10.0000000', send_asset: 'USDC' }); assert.equal(response.status, 202); assert.equal(response.body.tx_hash, 'tx-path-reverse'); @@ -429,7 +457,7 @@ test('POST /api/contributions returns 503 when custodial trustline setup fails', queryImpl: async (text) => { if (text.includes('FROM campaigns')) { return { - rows: [{ id: 'c-1', status: 'active', asset_type: 'XLM', wallet_public_key: 'GDEST' }], + rows: [{ id: '00000000-0000-0000-0000-000000000001', status: 'active', asset_type: 'XLM', wallet_public_key: 'GDEST' }], }; } if (text.includes('FROM users')) { @@ -449,7 +477,7 @@ test('POST /api/contributions returns 503 when custodial trustline setup fails', const response = await request(app) .post('/api/contributions') .set('Authorization', 'Bearer token') - .send({ campaign_id: 'c-1', amount: '5.0000000', send_asset: 'XLM' }); + .send({ campaign_id: '00000000-0000-0000-0000-000000000001', amount: '5.0000000', send_asset: 'XLM' }); assert.equal(response.status, 503); assert.match(response.body.error, /retry/i); @@ -461,7 +489,7 @@ test('POST /api/contributions returns 502 when Stellar submit fails and skips au queryImpl: async (text) => { if (text.includes('FROM campaigns')) { return { - rows: [{ id: 'c-1', status: 'active', asset_type: 'XLM', wallet_public_key: 'GDEST' }], + rows: [{ id: '00000000-0000-0000-0000-000000000001', status: 'active', asset_type: 'XLM', wallet_public_key: 'GDEST' }], }; } if (text.includes('FROM users')) { @@ -487,7 +515,7 @@ test('POST /api/contributions returns 502 when Stellar submit fails and skips au const response = await request(app) .post('/api/contributions') .set('Authorization', 'Bearer token') - .send({ campaign_id: 'c-1', amount: '5.0000000', send_asset: 'XLM' }); + .send({ campaign_id: '00000000-0000-0000-0000-000000000001', amount: '5.0000000', send_asset: 'XLM' }); assert.equal(response.status, 502); assert.equal(inserted, false); @@ -495,12 +523,13 @@ test('POST /api/contributions returns 502 when Stellar submit fails and skips au test('POST /api/contributions/prepare returns unsigned XDR and prepare token for Freighter', async () => { const sender = Keypair.random(); + const destination = Keypair.random(); let preparedPayload = null; const app = buildApp({ queryImpl: async (text) => { if (text.includes('FROM campaigns')) { return { - rows: [{ id: 'c-1', status: 'active', asset_type: 'XLM', wallet_public_key: 'GDEST' }], + rows: [{ id: '00000000-0000-0000-0000-000000000001', status: 'active', asset_type: 'XLM', wallet_public_key: destination.publicKey() }], }; } return { rows: [] }; @@ -522,7 +551,7 @@ test('POST /api/contributions/prepare returns unsigned XDR and prepare token for .post('/api/contributions/prepare') .set('Authorization', 'Bearer token') .send({ - campaign_id: 'c-1', + campaign_id: '00000000-0000-0000-0000-000000000001', amount: '5.0000000', send_asset: 'XLM', sender_public_key: sender.publicKey(), @@ -552,7 +581,7 @@ test('POST /api/contributions/submit-signed accepts Freighter-signed XDR that ma if (text.includes('FROM campaigns')) { return { rows: [{ - id: 'c-1', + id: '00000000-0000-0000-0000-000000000001', status: 'active', asset_type: 'XLM', wallet_public_key: destination.publicKey(), @@ -580,7 +609,7 @@ test('POST /api/contributions/submit-signed accepts Freighter-signed XDR that ma .post('/api/contributions/prepare') .set('Authorization', 'Bearer token') .send({ - campaign_id: 'c-1', + campaign_id: '00000000-0000-0000-0000-000000000001', amount: '5.0000000', send_asset: 'XLM', sender_public_key: sender.publicKey(), @@ -627,7 +656,7 @@ test('POST /api/contributions/submit-signed rejects a signed XDR that does not m if (text.includes('FROM campaigns')) { return { rows: [{ - id: 'c-1', + id: '00000000-0000-0000-0000-000000000001', status: 'active', asset_type: 'XLM', wallet_public_key: destination.publicKey(), @@ -648,7 +677,7 @@ test('POST /api/contributions/submit-signed rejects a signed XDR that does not m .post('/api/contributions/prepare') .set('Authorization', 'Bearer token') .send({ - campaign_id: 'c-1', + campaign_id: '00000000-0000-0000-0000-000000000001', amount: '5.0000000', send_asset: 'XLM', sender_public_key: sender.publicKey(), @@ -681,7 +710,7 @@ test('GET /api/contributions/finalization/:txHash returns finalized when indexed id: 'st-1', status: 'indexed', tx_hash: 'txh', - campaign_id: 'c-1', + campaign_id: '00000000-0000-0000-0000-000000000001', contribution_id: 'contrib-1', initiated_by_user_id: 'user-1', metadata: {}, @@ -719,7 +748,7 @@ test('POST /api/contributions includes platform_fee_amount in response and metad queryImpl: async (text) => { if (text.includes('FROM campaigns')) { return { - rows: [{ id: 'c-1', status: 'active', asset_type: 'USDC', wallet_public_key: 'GDEST' }], + rows: [{ id: '00000000-0000-0000-0000-000000000001', status: 'active', asset_type: 'USDC', wallet_public_key: 'GDEST' }], }; } if (text.includes('FROM users')) { @@ -749,7 +778,7 @@ test('POST /api/contributions includes platform_fee_amount in response and metad const response = await request(app) .post('/api/contributions') .set('Authorization', 'Bearer token') - .send({ campaign_id: 'c-1', amount: '10.0000000', send_asset: 'USDC' }); + .send({ campaign_id: '00000000-0000-0000-0000-000000000001', amount: '10.0000000', send_asset: 'USDC' }); assert.equal(response.status, 202); assert.equal(response.body.platform_fee_amount, 0.15); diff --git a/backend/src/routes/users.test.js b/backend/src/routes/users.test.js index 1d9819f..03f06cf 100644 --- a/backend/src/routes/users.test.js +++ b/backend/src/routes/users.test.js @@ -30,6 +30,11 @@ function buildApp({ queryImpl, stellarImpl }) { '../middleware/auth': { requireAuth: (_req, _res, next) => next(), }, + '../middleware/validation': { + registerValidation: [], + loginValidation: [], + validateRequest: (_req, _res, next) => next(), + }, jsonwebtoken: { sign: () => 'jwt-token', }, @@ -82,7 +87,7 @@ test('POST /api/auth/register encrypts wallet secret before insert and schedules const res = await request(app) .post('/api/auth/register') - .send({ email: 'a@b.c', password: 'longpassword1', name: 'N' }); + .send({ email: 'a@b.c', password: 'Longpassword1', name: 'N' }); assert.equal(res.status, 201); assert.equal(res.body.token, 'jwt-token'); diff --git a/backend/src/routes/withdrawals.test.js b/backend/src/routes/withdrawals.test.js index ae69ff7..0a7402d 100644 --- a/backend/src/routes/withdrawals.test.js +++ b/backend/src/routes/withdrawals.test.js @@ -42,6 +42,10 @@ function buildApp({ queryImpl, stellarImpl, userId = 'creator-1', role = 'creato next(); }, }, + '../middleware/validation': { + withdrawalValidation: [], + validateRequest: (_req, _res, next) => next(), + }, }); const app = express(); @@ -53,7 +57,7 @@ function buildApp({ queryImpl, stellarImpl, userId = 'creator-1', role = 'creato function campaignRow(overrides = {}) { return { - id: 'camp-1', + id: '00000000-0000-0000-0000-000000000001', creator_id: 'creator-1', wallet_public_key: 'GCAMPAIGN', asset_type: 'USDC', @@ -105,7 +109,7 @@ test('POST /api/withdrawals/request creates pending request and logs event', asy const response = await request(app) .post('/api/withdrawals/request') .set('Authorization', 'Bearer token') - .send({ campaign_id: 'camp-1', destination_key: 'GDEST', amount: '10.0000000' }); + .send({ campaign_id: '00000000-0000-0000-0000-000000000001', destination_key: 'GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN', amount: '10.0000000' }); cleanup(); assert.equal(response.status, 201); @@ -128,7 +132,7 @@ test('POST /api/withdrawals/request blocks when campaign not active or funded', const response = await request(app) .post('/api/withdrawals/request') .set('Authorization', 'Bearer token') - .send({ campaign_id: 'camp-1', destination_key: 'GDEST', amount: '10.0000000' }); + .send({ campaign_id: '00000000-0000-0000-0000-000000000001', destination_key: 'GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN', amount: '10.0000000' }); cleanup(); assert.equal(response.status, 409); @@ -150,7 +154,7 @@ test('POST /api/withdrawals/request blocks duplicate pending', async () => { const response = await request(app) .post('/api/withdrawals/request') .set('Authorization', 'Bearer token') - .send({ campaign_id: 'camp-1', destination_key: 'GDEST', amount: '10.0000000' }); + .send({ campaign_id: '00000000-0000-0000-0000-000000000001', destination_key: 'GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN', amount: '10.0000000' }); cleanup(); assert.equal(response.status, 409); @@ -179,7 +183,7 @@ test('POST /api/withdrawals/request denies invalid multisig config', async () => const response = await request(app) .post('/api/withdrawals/request') .set('Authorization', 'Bearer token') - .send({ campaign_id: 'camp-1', destination_key: 'GDEST', amount: '10.0000000' }); + .send({ campaign_id: '00000000-0000-0000-0000-000000000001', destination_key: 'GAAZI4TCR3TY5OJHCTJC2A4QSY6CJWJH5IAJTGKIN2ER7LBNVKOCCWN', amount: '10.0000000' }); cleanup(); assert.equal(response.status, 422); @@ -229,8 +233,11 @@ test('POST /api/withdrawals/:id/approve/creator signs withdrawal request', async }], }; } - if (text.includes('wallet_secret_encrypted FROM users')) { - return { rows: [{ wallet_secret_encrypted: 'SCREATOR' }] }; + if (text.includes('SELECT creator_id FROM campaigns')) { + return { rows: [{ creator_id: 'creator-1' }] }; + } + if (text.includes('wallet_secret_encrypted') && text.includes('FROM users')) { + return { rows: [{ wallet_secret_encrypted: 'SCREATOR', wallet_public_key: 'GCREATOR' }] }; } if (text.includes('UPDATE withdrawal_requests') && text.includes('creator_signed = TRUE')) { return { rows: [{ id: 'w-1', creator_signed: true, unsigned_xdr: 'xdr-signed' }] }; @@ -355,6 +362,9 @@ test('POST /api/withdrawals/:id/cancel succeeds before creator signs', async () }], }; } + if (text.includes('SELECT creator_id FROM campaigns')) { + return { rows: [{ creator_id: 'creator-1' }] }; + } if (text.includes("SET status = 'denied'")) { return { rows: [{ id: 'w-1', status: 'denied', denial_reason: 'x' }] }; } diff --git a/backend/src/services/sorobanService.js b/backend/src/services/sorobanService.js index b166ac7..27e834c 100644 --- a/backend/src/services/sorobanService.js +++ b/backend/src/services/sorobanService.js @@ -9,7 +9,6 @@ const { Keypair, } = require('@stellar/stellar-sdk'); const { server, networkPassphrase } = require('../config/stellar'); -const logger = require('../config/logger'); async function simulateAndPrepare(tx) { const simulation = await server.simulateTransaction(tx); @@ -45,6 +44,65 @@ async function invokeContract({ contractId, method, args, signerSecret }) { throw new Error(`Transaction failed: ${result.status}`); } +/** + * Invoke the on-chain contribution router contract. + * + * Enforces slippage ceiling and atomically splits dest_amount between + * campaign_wallet and platform_wallet. + * + * @param {object} params + * @param {string} params.senderSecret - Contributor's Stellar secret key + * @param {string} params.sendAssetAddress - SAC address of the send asset + * @param {string} params.sendMax - Max source amount (stroops as string) + * @param {string} params.destAssetAddress - SAC address of the destination asset + * @param {string} params.destAmount - Exact dest amount (stroops as string) + * @param {string[]} params.path - Intermediate asset addresses (may be empty) + * @param {string} params.campaignWallet - Campaign treasury address + * @param {string} params.platformWallet - Platform fee recipient address + * @param {number} params.feeBps - Platform fee in basis points (e.g. 100 = 1%) + * @param {number} params.maxSlippageBps - Max slippage in basis points (e.g. 500 = 5%) + * @returns {Promise} Transaction hash + */ +async function routeContribution({ + senderSecret, + sendAssetAddress, + sendMax, + destAssetAddress, + destAmount, + path = [], + campaignWallet, + platformWallet, + feeBps, + maxSlippageBps, +}) { + const contractId = process.env.ROUTER_CONTRACT_ID; + if (!contractId) throw new Error('ROUTER_CONTRACT_ID not configured'); + + const pathVec = path.length > 0 + ? nativeToScVal(path.map((a) => new Address(a))) + : nativeToScVal([], { type: 'vec' }); + + const result = await invokeContract({ + contractId, + method: 'route_contribution', + args: [ + new Address(Keypair.fromSecret(senderSecret).publicKey()).toScVal(), + new Address(sendAssetAddress).toScVal(), + nativeToScVal(BigInt(sendMax), { type: 'i128' }), + new Address(destAssetAddress).toScVal(), + nativeToScVal(BigInt(destAmount), { type: 'i128' }), + pathVec, + new Address(campaignWallet).toScVal(), + new Address(platformWallet).toScVal(), + nativeToScVal(feeBps, { type: 'u32' }), + nativeToScVal(maxSlippageBps, { type: 'u32' }), + ], + signerSecret: senderSecret, + }); + + return result; +} + /** * Encodes a milestone object for the Soroban contract. */ @@ -73,4 +131,5 @@ module.exports = { invokeContract, encodeMilestone, nativeToScVal, + routeContribution, }; diff --git a/backend/src/services/stellarService.js b/backend/src/services/stellarService.js index dc86039..e2381aa 100644 --- a/backend/src/services/stellarService.js +++ b/backend/src/services/stellarService.js @@ -25,7 +25,7 @@ const { configuredAssets, } = require('../config/stellar'); -const PLATFORM_KEYPAIR = Keypair.fromSecret(process.env.PLATFORM_SECRET_KEY); +const PLATFORM_KEYPAIR = process.env.PLATFORM_SECRET_KEY ? Keypair.fromSecret(process.env.PLATFORM_SECRET_KEY) : null; function calcFee(amount) { const bps = parseInt(process.env.PLATFORM_FEE_BPS || '0', 10); @@ -346,8 +346,13 @@ async function buildUnsignedContributionPathPayment({ return tx.toXDR(); } +/** /** * Build and sign a path payment contribution; `destAssetCode` is the asset the campaign receives. + * + * When ROUTER_CONTRACT_ID is set the call is routed through the on-chain + * contribution router which enforces slippage and splits the fee atomically. + * Falls back to the classic two-operation path payment when the env var is absent. */ async function prepareSignedContributionPathPayment({ senderSecret, @@ -357,7 +362,51 @@ async function prepareSignedContributionPathPayment({ destAmount, destAssetCode, memo, + path = [], + maxSlippageBps, }) { + // ── On-chain router path (trustless) ──────────────────────────────────────── + if (process.env.ROUTER_CONTRACT_ID) { + const { routeContribution } = require('./sorobanService'); + const { configuredAssets, USDC } = require('../config/stellar'); + const { Asset } = require('@stellar/stellar-sdk'); + + function assetToSacAddress(code) { + if (code === 'XLM') return process.env.XLM_SAC_ADDRESS; + if (code === 'USDC') return process.env.USDC_SAC_ADDRESS; + return configuredAssets[code]?.sacAddress; + } + + const sendAssetAddress = assetToSacAddress(sendAsset); + const destAssetAddress = assetToSacAddress(destAssetCode || 'USDC'); + if (!sendAssetAddress || !destAssetAddress) { + throw new Error(`SAC address not configured for ${sendAsset} or ${destAssetCode}`); + } + + const feeBps = parseInt(process.env.PLATFORM_FEE_BPS || '0', 10); + const slippage = maxSlippageBps ?? parseInt(process.env.SLIPPAGE_BPS || '500', 10); + + // dest_amount in stroops (7 decimal places → multiply by 1e7) + const destAmountStroops = String(Math.round(parseFloat(destAmount) * 1e7)); + const sendMaxStroops = String(Math.round(parseFloat(sendMax) * 1e7)); + + const txHash = await routeContribution({ + senderSecret, + sendAssetAddress, + sendMax: sendMaxStroops, + destAssetAddress, + destAmount: destAmountStroops, + path, + campaignWallet: destinationPublicKey, + platformWallet: PLATFORM_KEYPAIR.publicKey(), + feeBps, + maxSlippageBps: slippage, + }); + + return { txHash, routedOnChain: true }; + } + + // ── Classic fallback (client-side slippage) ────────────────────────────────── const senderKeypair = Keypair.fromSecret(senderSecret); const unsignedXdr = await buildUnsignedContributionPathPayment({ senderPublicKey: senderKeypair.publicKey(), @@ -371,6 +420,7 @@ async function prepareSignedContributionPathPayment({ const tx = TransactionBuilder.fromXDR(unsignedXdr, networkPassphrase); tx.sign(senderKeypair); const signedXdr = tx.toXDR(); + const { feeAmount } = calcFee(destAmount); return { unsignedXdr, signedXdr, feeAmount }; } @@ -543,8 +593,6 @@ async function getWalletPayments(publicKey, limit = 100) { })); } - - */ async function friendbotFund(publicKey) { if (!isTestnet) throw new Error('Friendbot only available on testnet'); const response = await fetch( @@ -580,5 +628,5 @@ module.exports = { getCampaignBalance, friendbotFund, - PLATFORM_PUBLIC_KEY: PLATFORM_KEYPAIR.publicKey(), + PLATFORM_PUBLIC_KEY: PLATFORM_KEYPAIR ? PLATFORM_KEYPAIR.publicKey() : null, }; diff --git a/contracts/soroban/Cargo.lock b/contracts/soroban/Cargo.lock index 47e8823..5fa9140 100644 --- a/contracts/soroban/Cargo.lock +++ b/contracts/soroban/Cargo.lock @@ -1100,6 +1100,13 @@ dependencies = [ "subtle", ] +[[package]] +name = "router" +version = "0.0.0" +dependencies = [ + "soroban-sdk", +] + [[package]] name = "rustc_version" version = "0.4.1" diff --git a/contracts/soroban/contracts/router/Cargo.toml b/contracts/soroban/contracts/router/Cargo.toml new file mode 100644 index 0000000..c75aa6d --- /dev/null +++ b/contracts/soroban/contracts/router/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "router" +version = "0.0.0" +edition = "2021" +publish = false + +[lib] +crate-type = ["lib", "cdylib"] +doctest = false + +[dependencies] +soroban-sdk = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/contracts/soroban/contracts/router/src/lib.rs b/contracts/soroban/contracts/router/src/lib.rs new file mode 100644 index 0000000..bcc4514 --- /dev/null +++ b/contracts/soroban/contracts/router/src/lib.rs @@ -0,0 +1,343 @@ +//! Contribution Router Contract +//! +//! Performs an on-chain path payment (swap) via a DEX router contract, +//! enforces a slippage ceiling, then atomically splits the received +//! dest_amount between the campaign wallet and the platform wallet. +//! +//! Entry point: `route_contribution` +//! +//! Slippage rule (enforced on-chain): +//! actual_spent <= dest_amount * (10_000 + max_slippage_bps) / 10_000 +//! +//! Fee split (atomic, on dest_asset): +//! campaign receives: dest_amount * (10_000 - fee_bps) / 10_000 +//! platform receives: dest_amount * fee_bps / 10_000 + +#![no_std] + +use soroban_sdk::{ + contract, contractimpl, contracttype, symbol_short, + token, Address, Env, Vec, +}; + +// ── DEX router interface ────────────────────────────────────────────────────── +// Matches the standard Soroban DEX aggregator interface (Phoenix / Soroswap). +// `swap_exact_tokens_for_tokens`: +// - pulls `amount_in` of `path[0]` from `to` (this contract) +// - swaps through `path` +// - delivers at least `amount_out_min` of `path[last]` to `to` +// - returns a Vec of amounts at each hop (first = spent, last = received) +#[soroban_sdk::contractclient(name = "DexRouterClient")] +pub trait DexRouter { + fn swap_exact_tokens_for_tokens( + env: Env, + amount_in: i128, + amount_out_min: i128, + path: Vec
, + to: Address, + deadline: u64, + ) -> Vec; +} + +// ── Event topic symbol ──────────────────────────────────────────────────────── + +const CONTRIBUTION_ROUTED: soroban_sdk::Symbol = symbol_short!("ROUTED"); + +// ── Event data ──────────────────────────────────────────────────────────────── + +#[contracttype] +#[derive(Clone, Debug)] +pub struct ContributionRoutedEvent { + pub sender: Address, + pub campaign: Address, + pub dest_amount: i128, + pub source_amount: i128, + pub fee_amount: i128, +} + +// ── Contract ────────────────────────────────────────────────────────────────── + +#[contract] +pub struct RouterContract; + +#[contractimpl] +impl RouterContract { + /// Route a contribution through the DEX with on-chain slippage enforcement + /// and atomic fee split. + /// + /// # Parameters + /// - `sender` – contributor; must have authorised this call + /// - `dex_router` – address of the on-chain DEX router contract + /// - `send_asset` – token the sender is spending (path[0]) + /// - `send_max` – maximum the sender is willing to spend (slippage ceiling) + /// - `dest_asset` – token the campaign receives (path[last]) + /// - `dest_amount` – minimum dest tokens the campaign+platform must receive + /// - `path` – full swap path including send_asset and dest_asset + /// - `campaign_wallet` – campaign treasury address + /// - `platform_wallet` – platform fee recipient address + /// - `fee_bps` – platform fee in basis points (e.g. 100 = 1 %) + /// - `max_slippage_bps` – maximum allowed slippage in basis points (e.g. 500 = 5 %) + /// + /// # Returns + /// Actual source amount spent. + pub fn route_contribution( + env: Env, + sender: Address, + dex_router: Address, + send_asset: Address, + send_max: i128, + dest_asset: Address, + dest_amount: i128, + path: Vec
, + campaign_wallet: Address, + platform_wallet: Address, + fee_bps: u32, + max_slippage_bps: u32, + ) -> i128 { + sender.require_auth(); + + // ── Validate inputs ─────────────────────────────────────────────────── + assert!(dest_amount > 0, "dest_amount must be positive"); + assert!(send_max > 0, "send_max must be positive"); + assert!(fee_bps < 10_000, "fee_bps must be < 10000"); + assert!(path.len() >= 2, "path must have at least 2 assets"); + + // ── Slippage ceiling: send_max <= dest_amount * (10_000 + slippage) / 10_000 + let slippage_ceiling = dest_amount + .checked_mul(10_000i128 + max_slippage_bps as i128) + .expect("overflow") + / 10_000i128; + assert!(send_max <= slippage_ceiling, "send_max exceeds slippage ceiling"); + + // ── Pull send_asset from sender into this contract ──────────────────── + let send_token = token::Client::new(&env, &send_asset); + send_token.transfer(&sender, &env.current_contract_address(), &send_max); + + // ── Approve DEX router to spend send_asset ──────────────────────────── + send_token.approve( + &env.current_contract_address(), + &dex_router, + &send_max, + &(env.ledger().sequence() + 1), + ); + + // ── Execute on-chain swap via DEX router ────────────────────────────── + // The DEX router pulls send_asset from this contract, swaps through + // `path`, and delivers dest_asset back to this contract. + let dex = DexRouterClient::new(&env, &dex_router); + let amounts = dex.swap_exact_tokens_for_tokens( + &send_max, + &dest_amount, // amount_out_min = dest_amount (exact-out guarantee) + &path, + &env.current_contract_address(), + &(env.ledger().timestamp() + 300), // 5-minute deadline + ); + + // actual dest tokens received (last element of amounts vector) + let received = amounts.get(amounts.len() - 1).expect("empty amounts"); + assert!(received >= dest_amount, "swap returned less than dest_amount"); + + // actual source tokens spent (first element) + let source_spent = amounts.get(0).expect("empty amounts"); + + // ── Refund any unspent send_asset back to sender ────────────────────── + let unspent = send_max - source_spent; + if unspent > 0 { + send_token.transfer(&env.current_contract_address(), &sender, &unspent); + } + + // ── Fee split on dest_asset ─────────────────────────────────────────── + let fee_amount = dest_amount * fee_bps as i128 / 10_000i128; + let campaign_amount = dest_amount - fee_amount; + assert!(campaign_amount > 0, "campaign_amount must be positive after fee"); + + let dest_token = token::Client::new(&env, &dest_asset); + dest_token.transfer(&env.current_contract_address(), &campaign_wallet, &campaign_amount); + if fee_amount > 0 { + dest_token.transfer(&env.current_contract_address(), &platform_wallet, &fee_amount); + } + + // ── Emit event ──────────────────────────────────────────────────────── + env.events().publish( + (CONTRIBUTION_ROUTED, sender.clone()), + ContributionRoutedEvent { + sender: sender.clone(), + campaign: campaign_wallet.clone(), + dest_amount, + source_amount: source_spent, + fee_amount, + }, + ); + + source_spent + } +} + +// ── Tests ───────────────────────────────────────────────────────────────────── + +#[cfg(test)] +mod tests { + use super::*; + use soroban_sdk::{ + testutils::Address as _, + token::{Client as TokenClient, StellarAssetClient}, + Address, Env, Vec, + }; + + // ── Minimal mock DEX router ─────────────────────────────────────────────── + // Simulates a 1:1 swap: spends exactly `amount_in` of path[0] via allowance, + // delivers exactly `amount_in` of path[last] to `to`. + #[contract] + pub struct MockDex; + + #[contractimpl] + impl MockDex { + pub fn swap_exact_tokens_for_tokens( + env: Env, + amount_in: i128, + _amount_out_min: i128, + path: Vec
, + to: Address, + _deadline: u64, + ) -> Vec { + let src = path.get(0).unwrap(); + let dst = path.get(path.len() - 1).unwrap(); + let dex = env.current_contract_address(); + + // Pull send_asset from `to` (router) via pre-approved allowance + TokenClient::new(&env, &src).transfer_from(&dex, &to, &dex, &amount_in); + // Push dest_asset to `to` (router) + TokenClient::new(&env, &dst).transfer(&dex, &to, &amount_in); + + let mut out = Vec::new(&env); + out.push_back(amount_in); // source spent + out.push_back(amount_in); // dest received + out + } + } + + fn setup(env: &Env) -> ( + RouterContractClient, + Address, // dex_router + Address, // send_asset + Address, // dest_asset + Address, // sender + Address, // campaign + Address, // platform + ) { + let admin = Address::generate(env); + let sender = Address::generate(env); + let campaign = Address::generate(env); + let platform = Address::generate(env); + + let send_asset = env.register_stellar_asset_contract_v2(admin.clone()).address(); + let dest_asset = env.register_stellar_asset_contract_v2(admin.clone()).address(); + + let dex_id = env.register_contract(None, MockDex); + let router_id = env.register_contract(None, RouterContract); + let client = RouterContractClient::new(env, &router_id); + + // Mint send_asset to sender + StellarAssetClient::new(env, &send_asset).mint(&sender, &10_000); + // Mint dest_asset to mock DEX (it will deliver it after swap) + StellarAssetClient::new(env, &dest_asset).mint(&dex_id, &10_000); + + (client, dex_id, send_asset, dest_asset, sender, campaign, platform) + } + + fn make_path(env: &Env, send: &Address, dest: &Address) -> Vec
{ + let mut p = Vec::new(env); + p.push_back(send.clone()); + p.push_back(dest.clone()); + p + } + + #[test] + fn test_happy_path_splits_correctly() { + let env = Env::default(); + env.mock_all_auths(); + + let (client, dex, send_asset, dest_asset, sender, campaign, platform) = setup(&env); + let path = make_path(&env, &send_asset, &dest_asset); + + // dest_amount=1000, fee_bps=100 (1%) → campaign=990, platform=10 + let source_spent = client.route_contribution( + &sender, &dex, &send_asset, &1_050, + &dest_asset, &1_000, &path, + &campaign, &platform, &100, &500, + ); + + assert_eq!(source_spent, 1_050); // mock DEX spends all of send_max (1:1 swap) + + let dest_token = TokenClient::new(&env, &dest_asset); + assert_eq!(dest_token.balance(&campaign), 990); + assert_eq!(dest_token.balance(&platform), 10); + } + + #[test] + #[should_panic(expected = "send_max exceeds slippage ceiling")] + fn test_rejects_excessive_slippage() { + let env = Env::default(); + env.mock_all_auths(); + + let (client, dex, send_asset, dest_asset, sender, campaign, platform) = setup(&env); + let path = make_path(&env, &send_asset, &dest_asset); + + // send_max=1_600 > 1_000 * 1.05 = 1_050 → should panic + client.route_contribution( + &sender, &dex, &send_asset, &1_600, + &dest_asset, &1_000, &path, + &campaign, &platform, &100, &500, + ); + } + + #[test] + fn test_zero_fee_sends_full_amount_to_campaign() { + let env = Env::default(); + env.mock_all_auths(); + + let (client, dex, send_asset, dest_asset, sender, campaign, platform) = setup(&env); + let path = make_path(&env, &send_asset, &dest_asset); + + client.route_contribution( + &sender, &dex, &send_asset, &1_000, + &dest_asset, &1_000, &path, + &campaign, &platform, &0, &500, + ); + + let dest_token = TokenClient::new(&env, &dest_asset); + assert_eq!(dest_token.balance(&campaign), 1_000); + assert_eq!(dest_token.balance(&platform), 0); + } + + #[test] + #[should_panic(expected = "dest_amount must be positive")] + fn test_rejects_zero_dest_amount() { + let env = Env::default(); + env.mock_all_auths(); + + let (client, dex, send_asset, dest_asset, sender, campaign, platform) = setup(&env); + let path = make_path(&env, &send_asset, &dest_asset); + + client.route_contribution( + &sender, &dex, &send_asset, &100, + &dest_asset, &0, &path, + &campaign, &platform, &100, &500, + ); + } + + #[test] + #[should_panic(expected = "path must have at least 2 assets")] + fn test_rejects_empty_path() { + let env = Env::default(); + env.mock_all_auths(); + + let (client, dex, send_asset, dest_asset, sender, campaign, platform) = setup(&env); + + client.route_contribution( + &sender, &dex, &send_asset, &1_000, + &dest_asset, &1_000, &Vec::new(&env), + &campaign, &platform, &100, &500, + ); + } +} diff --git a/contracts/soroban/contracts/router/test_snapshots/tests/test_happy_path_splits_correctly.1.json b/contracts/soroban/contracts/router/test_snapshots/tests/test_happy_path_splits_correctly.1.json new file mode 100644 index 0000000..bd80520 --- /dev/null +++ b/contracts/soroban/contracts/router/test_snapshots/tests/test_happy_path_splits_correctly.1.json @@ -0,0 +1,1018 @@ +{ + "generators": { + "address": 8, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": "10000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "i128": "10000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5", + "function_name": "route_contribution", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": "1050" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": "1000" + }, + { + "vec": [ + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "u32": 100 + }, + { + "u32": 500 + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "i128": "1050" + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "balance": "0", + "seq_num": "0", + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "balance": "0", + "seq_num": "0", + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "2032731177588607455" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "990" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "8950" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "50" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Allowance" + }, + { + "map": [ + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + }, + { + "key": { + "symbol": "spender" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + } + ] + } + ] + }, + "durability": "temporary", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "symbol": "live_until_ledger" + }, + "val": { + "u32": 1 + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 15 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "8950" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "1050" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/soroban/contracts/router/test_snapshots/tests/test_rejects_empty_path.1.json b/contracts/soroban/contracts/router/test_snapshots/tests/test_rejects_empty_path.1.json new file mode 100644 index 0000000..cbdac09 --- /dev/null +++ b/contracts/soroban/contracts/router/test_snapshots/tests/test_rejects_empty_path.1.json @@ -0,0 +1,599 @@ +{ + "generators": { + "address": 8, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": "10000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "i128": "10000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "balance": "0", + "seq_num": "0", + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "balance": "0", + "seq_num": "0", + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/soroban/contracts/router/test_snapshots/tests/test_rejects_excessive_slippage.1.json b/contracts/soroban/contracts/router/test_snapshots/tests/test_rejects_excessive_slippage.1.json new file mode 100644 index 0000000..cbdac09 --- /dev/null +++ b/contracts/soroban/contracts/router/test_snapshots/tests/test_rejects_excessive_slippage.1.json @@ -0,0 +1,599 @@ +{ + "generators": { + "address": 8, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": "10000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "i128": "10000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "balance": "0", + "seq_num": "0", + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "balance": "0", + "seq_num": "0", + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/soroban/contracts/router/test_snapshots/tests/test_rejects_zero_dest_amount.1.json b/contracts/soroban/contracts/router/test_snapshots/tests/test_rejects_zero_dest_amount.1.json new file mode 100644 index 0000000..cbdac09 --- /dev/null +++ b/contracts/soroban/contracts/router/test_snapshots/tests/test_rejects_zero_dest_amount.1.json @@ -0,0 +1,599 @@ +{ + "generators": { + "address": 8, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": "10000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "i128": "10000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "balance": "0", + "seq_num": "0", + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "balance": "0", + "seq_num": "0", + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "10000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/soroban/contracts/router/test_snapshots/tests/test_zero_fee_sends_full_amount_to_campaign.1.json b/contracts/soroban/contracts/router/test_snapshots/tests/test_zero_fee_sends_full_amount_to_campaign.1.json new file mode 100644 index 0000000..9caa78a --- /dev/null +++ b/contracts/soroban/contracts/router/test_snapshots/tests/test_zero_fee_sends_full_amount_to_campaign.1.json @@ -0,0 +1,966 @@ +{ + "generators": { + "address": 8, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": "10000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "function_name": "mint", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "i128": "10000" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5", + "function_name": "route_contribution", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + }, + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "i128": "1000" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + }, + { + "i128": "1000" + }, + { + "vec": [ + { + "address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG" + }, + { + "address": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "u32": 0 + }, + { + "u32": 500 + } + ] + } + }, + "sub_invocations": [ + { + "function": { + "contract_fn": { + "contract_address": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "function_name": "transfer", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + }, + { + "i128": "1000" + } + ] + } + }, + "sub_invocations": [] + } + ] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 25, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "balance": "0", + "seq_num": "0", + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "balance": "0", + "seq_num": "0", + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + "live_until": null + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "1033654523790656264" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": "4837995959683129791" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "2032731177588607455" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + "live_until": 6311999 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + "live_until": 4095 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "1000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "9000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CACMVW2KK4H5FZDFF2AUCAKQTEJMZZWJUIZF23XMRVYQBSXYLHZ6BKWN", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAANHUF" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000006" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Allowance" + }, + { + "map": [ + { + "key": { + "symbol": "from" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + }, + { + "key": { + "symbol": "spender" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + } + ] + } + ] + }, + "durability": "temporary", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "symbol": "live_until_ledger" + }, + "val": { + "u32": 1 + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 15 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "9000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOLZM" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "1000" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAARQG5" + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": "0" + } + }, + { + "key": { + "symbol": "authorized" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "clawback" + }, + "val": { + "bool": false + } + } + ] + } + } + }, + "ext": "v0" + }, + "live_until": 518400 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CDLDVFKHEZ2RVB3NG4UQA4VPD3TSHV6XMHXMHP2BSGCJ2IIWVTOHGDSG", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL7NV" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000005" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + "live_until": 120960 + }, + { + "entry": { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + "live_until": 4095 + } + ] + }, + "events": [] +} \ No newline at end of file diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js new file mode 100644 index 0000000..00ff3fd --- /dev/null +++ b/frontend/eslint.config.js @@ -0,0 +1,14 @@ +export default [ + { + files: ['**/*.js'], + languageOptions: { + ecmaVersion: 2022, + sourceType: 'module', + globals: { window: 'readonly', document: 'readonly', console: 'readonly', fetch: 'readonly' }, + }, + rules: { + 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + }, + }, + { ignores: ['node_modules/', 'dist/', '**/*.jsx'] }, +]; diff --git a/frontend/src/components/ContributeModal.jsx b/frontend/src/components/ContributeModal.jsx index f6e4407..b6a88e9 100644 --- a/frontend/src/components/ContributeModal.jsx +++ b/frontend/src/components/ContributeModal.jsx @@ -244,6 +244,9 @@ export default function ContributeModal({ campaign, onClose, onSuccess }) { } setLoadingLabel('Preparing transaction…'); + const prepared = await api.prepareContribution( + { + campaign_id: campaign.id, amount: destAmount, send_asset: sendAsset, sender_public_key: signerAddress, diff --git a/frontend/src/pages/Campaign.jsx b/frontend/src/pages/Campaign.jsx index 7323670..1ce16ee 100644 --- a/frontend/src/pages/Campaign.jsx +++ b/frontend/src/pages/Campaign.jsx @@ -388,6 +388,9 @@ export default function Campaign() { > ⚠ Report a problem with this campaign + )} + + )} {canPostUpdate && (