Skip to content

Proposal Signature Omission#

Informational

When a proposal is created, the calldatas, targets, and values are stored on chain and emitted in an event, but the signatures of the functions to be called are neither stored nor emitted.

proposals[proposalId] = Proposal({
    startTime: uint40(startTime), // time will be less than 2**40 until year 36812
    endTime: uint40(endTime),
    canceled: false,
    executed: false,
    quorum: currentQuorum(),
    proposer: msg.sender,
    targets: targets,
    values: values,
    calldatas: calldatas
});

latestProposalIds[msg.sender] = proposalId;

emit ProposalCreated(
    proposalId,
    msg.sender,
    targets,
    values,
    calldatas,
    startTime,
    endTime,
    description
);

Function signatures are useful to know what is going on under the hood in a proposal. By omitting them from the proposal events and on chain object, although gas is saved, there may be some unintended consequences downstream.

Namely:

  1. It breaks compatibility with governance UIs such as Tally which display function selectors, see their docs on event signatures here
  2. Even if governance UI compatibility is is not a concern, omitting selectors makes it easier for proposers to be dishonest about what is happening in their proposal. E.G. proposing with an innocuous proposal description when the underlying function calls are malicious.

Additional Information#

Signatures, while helpful for transparency are not foolproof. The end selector used to make a function call is only 4 bytes, which, being a relatively small space can have collisions between two different signatures that hash to the same end selector.

Thus attackers who precompute colliding signatures could still mislead proposer reviewers by including a colliding signature that is different from the real function that is actually implemented in the contract.

Additionally, proposers can still choose to omit the function selector in favor of encoding their own calldata (or the target contract's fallback function).

All of this is to say, there is not a substitute to inspecting the transaction target itself in the context of the proposed transaction.

Recommendation#

  • Keep signatures in the Proposal Event for transparency, it may not be necessary to store them on chain.
  • Make the propose function which does not accept signatures internal, as to force proposers to go through the function which includes signatures and encourage providing function signatures.
  • More generally, follow guidelines around function / event signatures by existing governance UIs if you wish to maintain compatibility with them, e.g. https://docs.tally.xyz/user-guides/tally-contract-compatibility/compound-bravo-style#event-signatures