Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
c5f711f
fix(ante): reject oversized gas limits (#6) (#21)
juno-ai-dev Aug 13, 2026
eb5241e
fix(ci): repair and harden juno-std release dispatch (#22)
juno-ai-dev Aug 13, 2026
b0cf9e1
test(pfm): fix third-hop escrow channel assertion (#23)
juno-ai-dev Aug 13, 2026
622b775
chore(deps): patch v31 dependency stack (#8) (#24)
juno-ai-dev Aug 13, 2026
9119f13
ci: expand v31 validation coverage (#12) (#25)
juno-ai-dev Aug 13, 2026
89ea8a1
test(node): make state sync a real release gate (#13) (#26)
juno-ai-dev Aug 13, 2026
fab6df9
fix(feepay): use configured fee denom throughout (#7) (#27)
juno-ai-dev Aug 13, 2026
68d1cbd
feat(upgrade): add v31 handler and rehearsal (#28)
juno-ai-dev Aug 13, 2026
b6a76cb
test(dao-dao): exercise cw4 lifecycle
juno-ai-dev Aug 14, 2026
6462870
test(voting-snapshot): benchmark validator-wide updates
juno-ai-dev Aug 14, 2026
a12f4c0
fix(feepay): reject uint64 accounting overflow
juno-ai-dev Aug 14, 2026
427819b
test(wasm): add voting-snapshot query probe
juno-ai-dev Aug 14, 2026
9ebc8da
test(dao-dao): exercise voting-snapshot Wasm queries
juno-ai-dev Aug 14, 2026
e389ca4
test(wasm): pin probe build toolchain
juno-ai-dev Aug 14, 2026
007dc57
fix(feepay): key wallet limits by contract sender
juno-ai-dev Aug 14, 2026
85e7e1f
fix(feepay): canonicalize wallet usage sender
juno-ai-dev Aug 14, 2026
a6838a0
test(feepay): authenticate canonical sender regression
juno-ai-dev Aug 14, 2026
c718f71
fix(app): preserve retired governance codecs
juno-ai-dev Aug 14, 2026
21643cc
test(state): rehearse v31 export and snapshot restore
juno-ai-dev Aug 14, 2026
b79b2e4
fix(rehearsal): enforce review evidence invariants
juno-ai-dev Aug 14, 2026
1e6f079
fix(rehearsal): close state evidence blockers
juno-ai-dev Aug 14, 2026
afa28e8
docs(rehearsal): fix runtime command locations
juno-ai-dev Aug 14, 2026
de47258
ci(release): publish immutable reproducible artifacts
juno-ai-dev Aug 14, 2026
1570764
fix(release): enforce immutable dependency-bearing builds
juno-ai-dev Aug 14, 2026
9e6652d
fix(release): harden verifiable publication
juno-ai-dev Aug 14, 2026
ffa454c
fix(release): bind exact pushed tag object
juno-ai-dev Aug 14, 2026
e4234bd
fix(release): encode SLSA digests as hexadecimal
juno-ai-dev Aug 14, 2026
5fe4b9f
fix(release): isolate publication policy and provenance
juno-ai-dev Aug 14, 2026
a6377d7
fix(v31): migrate legacy contract caps
juno-ai-dev Aug 14, 2026
2eb80e0
Merge pull request #29 from juno-ai-dev/integration/v31-rc-readiness
juno-ai-dev Aug 18, 2026
c59c436
fix(ci): satisfy release workflow actionlint
juno-ai-dev Aug 18, 2026
91b7769
fix(ci): document release environment inputs
juno-ai-dev Aug 18, 2026
adbb475
fix(ci): apply ShellCheck directives correctly
juno-ai-dev Aug 18, 2026
7f99a37
fix(ci): bind release commit locally
juno-ai-dev Aug 18, 2026
7a08f34
fix(proto): refresh generated artifacts
juno-ai-dev Aug 18, 2026
9e3658e
fix(proto): isolate gogo generation output
juno-ai-dev Aug 18, 2026
b1864c0
fix(proto): preserve workspace ownership
juno-ai-dev Aug 18, 2026
f71608d
fix: satisfy release candidate lint
juno-ai-dev Aug 18, 2026
36ce78c
fix(proto): normalize generated OpenAPI
juno-ai-dev Aug 18, 2026
9b91c99
fix(e2e): restore wasm runtime compatibility
juno-ai-dev Aug 18, 2026
7081ece
fix(e2e): fund upgrade relayer fees
juno-ai-dev Aug 18, 2026
23b6d37
fix(e2e): enable state sync snapshots at startup
juno-ai-dev Aug 18, 2026
fd890df
fix(e2e): serialize multi-path relayer setup
juno-ai-dev Aug 18, 2026
9fb00df
fix: repair v31 validation and e2e regressions
juno-ai-dev Aug 18, 2026
b210e5a
fix(e2e): avoid live snapshot database probe
juno-ai-dev Aug 18, 2026
123d63d
build(deps): update security-sensitive dependencies
juno-ai-dev Aug 18, 2026
fd2bdf6
fix(ante): include fee escrow in gas simulation
juno-ai-dev Aug 20, 2026
55389a1
Merge pull request #33 from juno-ai-dev/fix/wallet-gas-simulation
juno-ai-dev Aug 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
29 changes: 29 additions & 0 deletions .github/scripts/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .github/scripts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "juno-release-dispatch-tests",
"private": true,
"scripts": {
"test": "node --test release-dispatch.test.js"
},
"devDependencies": {
"yaml": "2.9.0"
}
}
93 changes: 93 additions & 0 deletions .github/scripts/release-dispatch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
'use strict';

const TARGET_REPOSITORY = 'CosmosContracts/juno-std';
const EVENT_TYPE = 'juno-release';

function validateReleaseTag(tag) {
// Juno release tags use SemVer with a mandatory leading "v". Numeric
// prerelease identifiers may not contain leading zeroes (SemVer 2.0.0).
const semver = /^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-(?:(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
if (typeof tag !== 'string' || !semver.test(tag)) {
throw new Error(`Invalid Juno release tag: '${tag || ''}'`);
}

return tag;
}

function requiredEnv(env, name) {
const value = env[name];
if (!value) {
throw new Error(`Missing required environment variable: ${name}`);
}
return value;
}

function buildDispatchRequest(payload, env) {
const release = payload && payload.release;
if (!release) {
throw new Error('A resolved GitHub release payload is required');
}
const releaseTag = validateReleaseTag(release.tag_name);
const isDraft = Boolean(release.draft);
const isPrerelease = Boolean(release.prerelease);
const [owner, repo] = TARGET_REPOSITORY.split('/');

const repos = {
juno: {
name: 'juno',
repo: requiredEnv(env, 'JUNO_REPO'),
rev: releaseTag,
dir: requiredEnv(env, 'JUNO_DIR'),
exclude_mods: [],
},
cosmos_sdk: {
name: 'cosmos',
repo: requiredEnv(env, 'COSMOS_SDK_REPO'),
rev: requiredEnv(env, 'COSMOS_SDK_REV'),
dir: requiredEnv(env, 'COSMOS_SDK_DIR'),
exclude_mods: ['cosmos/benchmark', 'cosmos/counter', 'cosmos/epochs', 'cosmos/protocolpool'],
},
wasmd: {
name: 'wasm',
repo: requiredEnv(env, 'WASMD_REPO'),
rev: requiredEnv(env, 'WASMD_REV'),
dir: requiredEnv(env, 'WASMD_DIR'),
exclude_mods: [],
},
cometbft: {
name: 'cometbft',
repo: requiredEnv(env, 'COMETBFT_REPO'),
rev: requiredEnv(env, 'COMETBFT_REV'),
dir: requiredEnv(env, 'COMETBFT_DIR'),
exclude_mods: [],
},
ibc_go: {
name: 'ibc-go',
repo: requiredEnv(env, 'IBC_GO_REPO'),
rev: requiredEnv(env, 'IBC_GO_REV'),
dir: requiredEnv(env, 'IBC_GO_DIR'),
exclude_mods: [],
},
ics23: {
name: 'ics23',
repo: requiredEnv(env, 'ICS23_REPO'),
rev: requiredEnv(env, 'ICS23_REV'),
dir: requiredEnv(env, 'ICS23_DIR'),
exclude_mods: [],
},
};

return {
owner,
repo,
event_type: EVENT_TYPE,
client_payload: {
is_draft: isDraft,
is_prerelease: isPrerelease,
release_tag: releaseTag,
repos,
},
};
}

module.exports = { buildDispatchRequest, validateReleaseTag };
174 changes: 174 additions & 0 deletions .github/scripts/release-dispatch.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
'use strict';

const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const YAML = require('yaml');

const { buildDispatchRequest, validateReleaseTag } = require('./release-dispatch');

const root = path.resolve(__dirname, '../..');
const workflowPath = path.join(root, '.github/workflows/release-dispatch.yml');
const ciPath = path.join(root, '.github/workflows/release-dispatch-ci.yml');
const workflow = YAML.parse(fs.readFileSync(workflowPath, 'utf8'));

const dependencyEnv = {
JUNO_REPO: 'https://github.com/CosmosContracts/juno.git',
JUNO_DIR: 'proto',
COSMOS_SDK_REPO: 'https://github.com/cosmos/cosmos-sdk.git',
COSMOS_SDK_REV: 'v0.53.7',
COSMOS_SDK_DIR: 'proto',
WASMD_REPO: 'https://github.com/CosmWasm/wasmd.git',
WASMD_REV: 'v0.61.11',
WASMD_DIR: 'proto',
COMETBFT_REPO: 'https://github.com/cometbft/cometbft.git',
COMETBFT_REV: 'v0.38.23',
COMETBFT_DIR: 'proto',
IBC_GO_REPO: 'https://github.com/cosmos/ibc-go.git',
IBC_GO_REV: 'v10.6.0',
IBC_GO_DIR: 'proto',
ICS23_REPO: 'https://github.com/cosmos/ics23.git',
ICS23_REV: 'go/v0.11.0',
ICS23_DIR: 'proto',
};

function request(payload) {
return buildDispatchRequest(payload, dependencyEnv);
}

test('builds a payload from a published release event', () => {
const result = request({
action: 'published',
release: { tag_name: 'v31.0.0', draft: false, prerelease: false },
});

assert.equal(result.owner, 'CosmosContracts');
assert.equal(result.repo, 'juno-std');
assert.equal(result.event_type, 'juno-release');
assert.deepEqual(result.client_payload, {
is_draft: false,
is_prerelease: false,
release_tag: 'v31.0.0',
repos: {
juno: {
name: 'juno', repo: dependencyEnv.JUNO_REPO, rev: 'v31.0.0', dir: 'proto', exclude_mods: [],
},
cosmos_sdk: {
name: 'cosmos', repo: dependencyEnv.COSMOS_SDK_REPO, rev: 'v0.53.7', dir: 'proto',
exclude_mods: ['cosmos/benchmark', 'cosmos/counter', 'cosmos/epochs', 'cosmos/protocolpool'],
},
wasmd: {
name: 'wasm', repo: dependencyEnv.WASMD_REPO, rev: 'v0.61.11', dir: 'proto', exclude_mods: [],
},
cometbft: {
name: 'cometbft', repo: dependencyEnv.COMETBFT_REPO, rev: 'v0.38.23', dir: 'proto', exclude_mods: [],
},
ibc_go: {
name: 'ibc-go', repo: dependencyEnv.IBC_GO_REPO, rev: 'v10.6.0', dir: 'proto', exclude_mods: [],
},
ics23: {
name: 'ics23', repo: dependencyEnv.ICS23_REPO, rev: 'go/v0.11.0', dir: 'proto', exclude_mods: [],
},
},
});
});

test('preserves prerelease flags from a published release payload', () => {
const result = request({
action: 'published',
release: { tag_name: 'v31.0.0-rc.1', draft: false, prerelease: true },
});

assert.equal(result.client_payload.release_tag, 'v31.0.0-rc.1');
assert.equal(result.client_payload.is_draft, false);
assert.equal(result.client_payload.is_prerelease, true);
});

test('manual dispatch uses the release resolved by the workflow API lookup', () => {
const result = request({
inputs: { release_tag: 'v32.1.0-rc.2' },
release: { tag_name: 'v32.1.0-rc.2', draft: false, prerelease: true },
});

assert.equal(result.client_payload.release_tag, 'v32.1.0-rc.2');
assert.equal(result.client_payload.is_draft, false);
assert.equal(result.client_payload.is_prerelease, true);
});

test('accepts Juno semantic-version tags including release candidates', () => {
for (const tag of ['v31.0.0', 'v31.0.0-rc.1', 'v32.4.5-beta.2+build.7']) {
assert.equal(validateReleaseTag(tag), tag);
}
});

test('rejects branches, refs, and arbitrary strings as release tags', () => {
for (const tag of [
'', 'main', 'release/v31', 'refs/tags/v31.0.0', 'v31', 'v31.0', 'v31.01.0',
'v31.0.0-', 'v31.0.0-rc..1', 'v31.0.0 rc1', '../v31.0.0',
]) {
assert.throws(() => validateReleaseTag(tag), /Juno release tag/i, JSON.stringify(tag));
}
});

test('workflow listens only for release published and derives release state from payload', () => {
assert.deepEqual(workflow.on.release.types, ['published']);
assert.ok(workflow.on.workflow_dispatch.inputs.release_tag.required);
assert.deepEqual(Object.keys(workflow.on.workflow_dispatch.inputs), ['release_tag']);
});

test('manual workflow validates tag existence before dispatch', () => {
const script = workflow.jobs.dispatch.steps.find((step) => step.name === 'Dispatch release event').with.script;
const validation = script.indexOf('validateReleaseTag(payload.inputs.release_tag)');
const lookup = script.indexOf('getReleaseByTag');
const dispatch = script.indexOf('createDispatchEvent');
assert.ok(validation >= 0 && lookup > validation, 'tag format must be validated before API lookup');
assert.ok(lookup >= 0, 'manual path must resolve an existing GitHub release');
assert.ok(dispatch > lookup, 'release lookup must occur before repository dispatch');
assert.match(script, /context\.repo/);
});

test('workflow checks out and executes the exact trusted workflow revision', () => {
assert.deepEqual(workflow.permissions, { contents: 'read' });
const checkout = workflow.jobs.dispatch.steps.find((step) => String(step.uses || '').startsWith('actions/checkout@'));
assert.match(checkout.uses, /^actions\/checkout@[0-9a-f]{40}$/);
assert.equal(checkout.with.ref, '${{ github.workflow_sha }}');
assert.equal(checkout.with['persist-credentials'], false);
assert.match(workflow.jobs.dispatch.steps.at(-1).uses, /^actions\/github-script@[0-9a-f]{40}$/);
});

test('workflow dependency revisions track the selected v31 Go dependency graph', () => {
const goMod = fs.readFileSync(path.join(root, 'go.mod'), 'utf8');
const moduleVersions = new Map(
[...goMod.matchAll(/^\s*(\S+)\s+(v\S+)(?:\s+\/\/.*)?$/gm)].map((match) => [match[1], match[2]]),
);
const expected = {
COSMOS_SDK_REV: moduleVersions.get('github.com/cosmos/cosmos-sdk'),
WASMD_REV: moduleVersions.get('github.com/CosmWasm/wasmd'),
COMETBFT_REV: moduleVersions.get('github.com/cometbft/cometbft'),
IBC_GO_REV: moduleVersions.get('github.com/cosmos/ibc-go/v10'),
ICS23_REV: `go/${moduleVersions.get('github.com/cosmos/ics23/go')}`,
};

for (const [name, revision] of Object.entries(expected)) {
assert.ok(revision && !revision.includes('undefined'), `${name} dependency must exist in go.mod`);
assert.equal(workflow.env[name], revision);
}
});

test('dedicated CI runs offline Node tests and actionlint for dispatch files', () => {
const ci = YAML.parse(fs.readFileSync(ciPath, 'utf8'));
assert.deepEqual(ci.permissions, { contents: 'read' });
const allSteps = Object.values(ci.jobs).flatMap((job) => job.steps);
assert.ok(allSteps.some((step) => step.run && step.run.includes('npm ci')));
assert.ok(allSteps.some((step) => step.run && step.run.includes('npm test')));
assert.ok(allSteps.some((step) => step.run && step.run.includes('actionlint')));
for (const step of allSteps.filter((candidate) => candidate.uses)) {
assert.match(step.uses, /^[^@]+@[0-9a-f]{40}$/, `${step.uses} must use an immutable SHA`);
}
});

test('offline payload builder cannot perform network lookup or dispatch', () => {
const builder = fs.readFileSync(path.join(__dirname, 'release-dispatch.js'), 'utf8');
assert.doesNotMatch(builder, /createDispatchEvent|getReleaseByTag|https?:/);
});
Loading
Loading