Proposal: OpenZeppelin Security Partnership - 2022 Q2 Adjustment

Hi everyone,

Unfortunately, our second compensation proposal has also run into a timing issue with the Sablier stream. The proposal was not queued in the Timelock with enough time to execute by the startDate. Given that this is our third attempt, I want to take the time to explain the issue we’re facing in more detail and show how we’re planning to address it for a final time. Hopefully, this will also be useful for others that are using Sablier in future Compound proposals.

Sablier’s createSteam() Timing Issue Explained

As explained in a previous post, Sablier is a protocol for creating real-time payment streams within a given time frame. Sablier has been proposed as a more robust replacement for the setContributorCompSpeed function that is currently used for streaming payments to Compound contributors and vendors due to Sablier’s ability to set specific durations and amounts, as opposed to a continuous stream that must be directly turned off by governance.

To migrate a stream to Sablier through a Compound proposal, the process appears simple. You can see an example of a successful migration with Gauntlet’s proposal 82 with each step summarized below:

  1. End the setContributorCompSpeed() stream by setting the value to 0 (if applicable)
  2. Give the Sablier contract approval to access the necessary amount of COMP
  3. Call createStream() on the Sablier contract and pass it the appropriate parameters

The issue that has come up multiple times now is with the third step when calling createStream() which accepts 5 parameters as seen below. In addition to the payment details of the recipient, deposit amount and tokenAddress, the stream must know when the startTime and stopTime are for the stream.

function createStream(address recipient, uint256 deposit, address tokenAddress, uint256 startTime, uint256 stopTime) public returns (uint256)

{ …
   require(startTime >= block.timestamp, "start time before block.timestamp");
...

Most importantly, the startTime must be set AFTER the function is called and will otherwise revert. While this seems like an obvious requirement, it becomes more complicated to fulfill when going through a 7-day governance process before execution can occur. Even if the original startTime is set to be longer than 7 days, any delay in the proposal’s timeline runs the risk of missing this deadline.

In OpenZeppelin’s case, our first proposal was submitted a day later than originally planned when we created the startTime and the parameter was not updated accordingly. This issue was raised by @Arr00 during the Review stage and so we canceled this proposal before voting began. With the second proposal, we double-checked that we had an extra day to execute the proposal before the startTime deadline. However, we failed to realize that any delay in queuing the proposal after it passed would also cause the same issue. The proposal passed its voting stage over the weekend while the team was not actively watching it. As a result, it was not queued in the Timelock with enough time to execute before the startTime.

While this is ultimately a user error on our part, we are not alone as other Compound proposals, namely Proposal 79, ran into the same timing problem as well. It’s important for other Compound community members to be aware of how Sablier works to ensure it does not result in other failed proposals. We would also encourage the Sablier team to consider design changes in future versions of their protocol that are more accommodating for time-variable governance proposals.

Our Plan for a Third Proposal

Ultimately, OpenZeppelin bears responsibility for failing to account for these timing concerns in both proposals submitted. Both myself and our operations team went through an internal process to check that the accounts and amount details were correct, but we failed to fully account for meeting the Sablier stream’s startTime requirement in both cases. Given that this has occurred twice, we owe the community a detailed plan for a successful third proposal.

First of all, we intend to refund the gas costs of all 21 voters on Proposal 95 to ensure that our mistakes here don’t discourage community participation. It’s important to note that this is not meant as an encouragement or expectation that these same voters must vote YES on future OpenZeppelin proposals. While we feel fortunate not to have had a single NO vote on this proposal, we would be refunding those accounts as well if they existed.

In our third proposal’s createStream parameters, we will be providing ample buffer time for the startTime to fall comfortably after the proposal can be executed. This will include testing the startTime parameter prior to deployment of the proposal to ensure that a repeat of the first proposal is not possible. We’ll also be adjusting the compensation amount to account for the time that the old stream will continue running in Q2.

To address the issue of timely queuing and execution, we’ll be using a tool we frankly should have been using from the start, Defender Autotasks. Autotasks are a robust feature that allows web3 developers to run code snippets in response to a schedule, webhook or on-chain transaction. In this case, we’ll be configuring an Autotask that will regularly check the status of our proposal. Once it reaches the appropriate stages, our Autotask will immediately call the Queue function and then later the Execute function for the proposal. By eliminating the need for human intervention, we’ll ensure no time is lost between stages.

Given that this sort of Autotask would be useful for automating the lifecycle of other proposals, we plan to eventually publish this Autotask for the Compound community to use themselves. With a free-tier account on Defender, a Compound user will be able to setup Autotasks to automate their own proposals and other on-chain operations to easily prevent issues with Sablier stream proposals and any other time-sensitive tasks.

Summary

In short, we are addressing the issues faced by the first two proposals by doing the following:

  1. Explaining the issue we faced using Salbier so others can avoid it
  2. Refunding gas costs to voters to encourage future engagement
  3. Ensuring the startTime parameter is sufficient with tighter internal checks and tests
  4. Automating the manual steps to prevent any loss of time

Despite the annoyance this situation may have caused, we’ve learned about how to be better and we hope the community will find value in the solutions that will be shared as a result.

We expect our third proposal will be submitted by this Friday so stay tuned.

5 Likes