Skip to content

Repository files navigation

Ekubo Governance

Tests

Contracts for token-based governance on Starknet.

Components

Contracts in this repository are designed so that they may be used together or independently.

Distribution

Airdrop

Airdrop is a highly-optimized distribution contract for distributing a fungible ERC20-like token to many (O(1e6)) accounts. To use it, you must compute a binary merkle tree using the pedersen hash function of an id-sorted list of Claim structs. The root of this tree and the token address are passed as constructor arguments and cannot change.

  • Compute a merkle root by producing a list of Claim structs, hashing them, sorting by sequentially-assigned ID, and arranging them into a merkle binary tree
    • Claim IDs must be sorted, start from 0 and be contiguous to make optimal use of the contract's claim_128 entrypoint
  • Deploy the airdrop with the root from this merkle tree and the token address
  • Transfer the total amount of tokens to the Airdrop contract
  • Unclaimed tokens can be refunded to the specified-at-construction refund_to address after the refundable_timestamp, iff refundable_timestamp is not zero

Governance

Staker

Staker enables users to delegate the balance of a token towards an account, and tracks the historical delegation at each block. In addition, it allows the computation of the time-weighted average delegated tokens of any account over any historical period.

  • Users call Token#approve(staker, stake_amount), then Staker#stake_amount(delegate, stake_amount) to stake and delegate their tokens to other addresses
  • Users call Staker#withdraw_amount(delegate, recipient, amount) to remove part or all of their delegation
  • The average delegation weight is computable over any historical period
  • The contract has no owner, and cannot be updated nor configured

Governor

Governor enables stakers to vote on whether to make a batch of calls.

  • A user's voting weight for a period is determined by their average total delegation over the period voting_weight_smoothing_duration
  • A delegate may create a proposal to make a batch of calls if their voting weight exceeds the proposal creation threshold
  • After the voting_start_delay, users may choose to vote yea or nay on the created proposal for the duration of the voting_period
    • A voter's voting weight is computed based on their average delegation over the period voting_weight_smoothing_duration from before the start time of the proposal
  • If a proposal receives at least quorum in yea voting weight, has a strict majority (yea > nay), and the voting period is over, it may be executed exactly once; tied votes fail
    • Must happen after the voting has ended and the execution delay has passed
    • If any of the calls fails, the transaction will revert, and anyone may attempt to execute the proposal again, up until the end of the execution window
  • Proposals can be canceled at any time by anyone iff the voting weight of the proposer falls below the proposal creation threshold, up until it is executed
  • The proposer may also cancel the proposal at any time before the end of the voting period
  • Proposers may only have one active proposal at any time
  • The contract can be upgraded via calls to self

StarknetOwnerProxy.sol: L1 Proxy

StarknetOwnerProxy enables the L2 governor contract to perform actions on L1 via proposals that call self#send_message_to_l1.

  • The EthAddress should be the address of a StarknetOwnerProxy referencing the Governor L2 address
  • The payload can be computed by calling the getPayload view function on the deployed StarknetOwnerProxy
  • Once the message is accepted on L1, anyone can call StarknetOwnerProxy#execute with the proposal's specified target, value and data.

ArbitrumOwnerProxy.sol: Arbitrum L2 Proxy

ArbitrumOwnerProxy enables an Ethereum L1 address, such as a deployed StarknetOwnerProxy, to perform actions on an Arbitrum child chain.

  • Deploy it on the Arbitrum child chain with the unaliased L1 owner address
  • owner() returns the unaliased L1 owner address
  • Owner-gated calls are authorized from either the raw owner address on L2 or the owner's Arbitrum alias from an L1-to-L2 retryable ticket, exposed as l2OwnerAlias()
  • The owner can call execute(target, value, data) from either path to make arbitrary calls from the proxy

OPStackOwnerProxy.sol: OP Stack / Base L2 Proxy

OPStackOwnerProxy enables an Ethereum L1 address, such as a deployed StarknetOwnerProxy, to perform actions on an OP Stack or Base child chain.

  • Deploy it on the child chain with the L1 owner address
  • owner() returns the L1 owner address
  • Owner-gated calls are authorized from either the raw owner address on L2 or an L1-to-L2 message where msg.sender is the L2CrossDomainMessenger predeploy at 0x4200000000000000000000000000000000000007 and xDomainMessageSender() is the L1 owner
  • The owner can call execute(target, value, data) from either path to make arbitrary calls from the proxy

Testing

Make sure you have Scarb with asdf installed. You can look at the .tool-versions file to know which version of Scarb is currently used.

To run unit tests:

scarb test

Disclaimer

These contracts are unaudited. Use at your own risk. Additional review is greatly appreciated.

About

Contracts for governance of Starknet protocols

Resources

Stars

68 stars

Watchers

2 watching

Forks

Releases

Used by

Contributors

Languages