Description
HookEvent (types.rs) declares seven variants — GrantCreated, MilestoneApproved, MilestonePaid, DisputeRaised, DisputeResolved, ContributorRegistered, BountyAwarded — and hooks::register_hook happily accepts a registration for any of them, charging the caller gas and storing the registration. But hooks::trigger is only ever invoked from two places in lib.rs: once for HookEvent::GrantCreated (around line 4235) and once for HookEvent::MilestoneApproved (around line 633). A contributor who registers an external contract hook for MilestonePaid, DisputeRaised, DisputeResolved, ContributorRegistered, or BountyAwarded will never receive a callback — the registration silently succeeds but is permanently inert.
On top of that, both real call sites pass an empty payload:
// lib.rs:634
hooks::trigger(&env, HookEvent::MilestoneApproved, Bytes::new(&env));
...
// lib.rs:4236
hooks::trigger(env, HookEvent::GrantCreated, Bytes::new(env));
cross_contract::call_hook_receiver forwards event_type and payload to the target contract's on_hook function, but since payload is always an empty Bytes, external hook receivers have no way to know which grant_id (or milestone_idx) the event pertains to — they only learn that some event of a given type fired somewhere in the contract, which makes the payload parameter of the whole hook interface effectively useless for any receiver that needs to react to a specific grant.
Technical Requirements
Files to update
contracts/contracts/stellar-grants/src/lib.rs — add hooks::trigger call sites for MilestonePaid, DisputeRaised, DisputeResolved, ContributorRegistered, and BountyAwarded at their respective real state-transition points, and encode the relevant grant_id/milestone_idx into the payload Bytes at all six call sites (existing two plus the new ones).
contracts/contracts/stellar-grants/src/hooks.rs (trigger, lines 98-134 — no change needed, just needs real payload data from callers)
What to verify first
Confirm the current call sites via:
// lib.rs:633-634
if hooks::has_hooks(&env, HookEvent::MilestoneApproved) {
hooks::trigger(&env, HookEvent::MilestoneApproved, Bytes::new(&env));
}
// lib.rs:4235-4236
if hooks::has_hooks(env, HookEvent::GrantCreated) {
hooks::trigger(env, HookEvent::GrantCreated, Bytes::new(env));
}
These are the only two hooks::trigger call sites in the entire crate.
Acceptance Criteria
hooks::trigger is called at the correct real state-transition points for MilestonePaid, DisputeRaised, DisputeResolved, ContributorRegistered, and BountyAwarded (or the unreachable variants are removed from HookEvent if the maintainer decides some don't have a corresponding real entrypoint — confirm with a maintainer comment before removing).
- All
hooks::trigger call sites pass a non-empty payload encoding at minimum the relevant grant_id (and milestone_idx where applicable) instead of Bytes::new(&env).
- A test registers a hook for a previously-unreachable event (e.g.
DisputeRaised), performs the real action that should trigger it, and asserts HookTriggered was published / the mock receiver was called with a non-empty payload.
cargo test passes.
Estimated Effort
Beginner: 6 hours
Intermediate: 3 hours
Expert: 1.5 hours
How to work this issue
- Read
contracts/ContributionGuide.md for the contribution workflow.
- Comment on the issue to claim it before starting.
- Branch:
fix/issue-114-hooks-missing-triggers-empty-payload.
- Run
cargo fmt, cargo clippy -- -D warnings, cargo test before opening your PR.
- Use a Conventional Commit message, e.g.
fix: wire up remaining HookEvent triggers and populate hook payloads.
Before you start
If you find this project interesting, please consider starring the repository on GitHub. It helps the project gain visibility and supports the Drips Wave program that rewards contributors for merged fixes like this one.
Description
HookEvent(types.rs) declares seven variants —GrantCreated,MilestoneApproved,MilestonePaid,DisputeRaised,DisputeResolved,ContributorRegistered,BountyAwarded— andhooks::register_hookhappily accepts a registration for any of them, charging the caller gas and storing the registration. Buthooks::triggeris only ever invoked from two places inlib.rs: once forHookEvent::GrantCreated(around line 4235) and once forHookEvent::MilestoneApproved(around line 633). A contributor who registers an external contract hook forMilestonePaid,DisputeRaised,DisputeResolved,ContributorRegistered, orBountyAwardedwill never receive a callback — the registration silently succeeds but is permanently inert.On top of that, both real call sites pass an empty payload:
cross_contract::call_hook_receiverforwardsevent_typeandpayloadto the target contract'son_hookfunction, but sincepayloadis always an emptyBytes, external hook receivers have no way to know whichgrant_id(ormilestone_idx) the event pertains to — they only learn that some event of a given type fired somewhere in the contract, which makes the payload parameter of the whole hook interface effectively useless for any receiver that needs to react to a specific grant.Technical Requirements
Files to update
contracts/contracts/stellar-grants/src/lib.rs— addhooks::triggercall sites forMilestonePaid,DisputeRaised,DisputeResolved,ContributorRegistered, andBountyAwardedat their respective real state-transition points, and encode the relevantgrant_id/milestone_idxinto thepayloadBytesat all six call sites (existing two plus the new ones).contracts/contracts/stellar-grants/src/hooks.rs(trigger, lines 98-134 — no change needed, just needs real payload data from callers)What to verify first
Confirm the current call sites via:
These are the only two
hooks::triggercall sites in the entire crate.Acceptance Criteria
hooks::triggeris called at the correct real state-transition points forMilestonePaid,DisputeRaised,DisputeResolved,ContributorRegistered, andBountyAwarded(or the unreachable variants are removed fromHookEventif the maintainer decides some don't have a corresponding real entrypoint — confirm with a maintainer comment before removing).hooks::triggercall sites pass a non-empty payload encoding at minimum the relevantgrant_id(andmilestone_idxwhere applicable) instead ofBytes::new(&env).DisputeRaised), performs the real action that should trigger it, and assertsHookTriggeredwas published / the mock receiver was called with a non-empty payload.cargo testpasses.Estimated Effort
Beginner: 6 hours
Intermediate: 3 hours
Expert: 1.5 hours
How to work this issue
contracts/ContributionGuide.mdfor the contribution workflow.fix/issue-114-hooks-missing-triggers-empty-payload.cargo fmt,cargo clippy -- -D warnings,cargo testbefore opening your PR.fix: wire up remaining HookEvent triggers and populate hook payloads.Before you start
If you find this project interesting, please consider starring the repository on GitHub. It helps the project gain visibility and supports the Drips Wave program that rewards contributors for merged fixes like this one.