Skip to content

Commit

Permalink
Merge pull request #13 from robriks/wave-rebrand
Browse files Browse the repository at this point in the history
💄 Rebrand to Wave Protocol for v1.1 release tag
  • Loading branch information
robriks authored May 5, 2024
2 parents 614257c + fe0cd82 commit f8b6a4e
Show file tree
Hide file tree
Showing 11 changed files with 364 additions and 364 deletions.
38 changes: 19 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div align="center">

# PropLot Protocol
# Wave Protocol

</div>

PropLot Protocol is a decentralized system built on top of the Nouns Governance ecosystem to noncustodially and permissionlessly democratize access to the Nouns sphere and lower the barrier of entry so that anyone with a worthy Nouns governance idea may participate and make a difference.
Wave Protocol is a decentralized system built on top of the Nouns Governance ecosystem to noncustodially and permissionlessly democratize access to the Nouns sphere and lower the barrier of entry so that anyone with a worthy Nouns governance idea may participate and make a difference.

## Table of Contents
- [Why Extend Nouns Governance?](#why-extend-nouns-governance)
Expand All @@ -17,43 +17,43 @@ PropLot Protocol is a decentralized system built on top of the Nouns Governance

## Why extend Nouns governance?

The PropLot protocol introduces numerous benefits to all parties involved. It provides Nouns NFT holders with a way to earn yield on their Nouns tokens by noncustodially lending their voting power to the PropLot protocol via delegation. Delegating to PropLot thereby extends the right to make onchain proposals to addresses that don't hold Nouns tokens but would like to submit proposal ideas.
The Wave protocol introduces numerous benefits to all parties involved. It provides Nouns NFT holders with a way to earn yield on their Nouns tokens by noncustodially lending their voting power to the Wave protocol via delegation. Delegating to Wave thereby extends the right to make onchain proposals to addresses that don't hold Nouns tokens but would like to submit proposal ideas.

### On security

The protocol is designed with maximal attention to security; since voting power is simply delegated noncustodially to PropLot contracts, there is no requirement for any kind of approval, transfer, or other action which could compromise the ownership of the Nouns NFTs. Only the voting power of the Nouns token's `ERC721Checkpointable` ledger is ever required to participate in PropLot and earn yield.
The protocol is designed with maximal attention to security; since voting power is simply delegated noncustodially to Wave contracts, there is no requirement for any kind of approval, transfer, or other action which could compromise the ownership of the Nouns NFTs. Only the voting power of the Nouns token's `ERC721Checkpointable` ledger is ever required to participate in Wave and earn yield.

## Architecture Overview

PropLot consists of three major parts: the IdeaTokenHub ERC1155 auction mechanism, the PropLot Core contract, and Delegates which are used to push winning proposals to Nouns governance contracts.
Wave consists of three major parts: the IdeaTokenHub ERC1155 auction mechanism, the Wave Core contract, and Delegates which are used to push winning proposals to Nouns governance contracts.

- **IdeaTokenHub**
- **PropLot Core**
- **Wave Core**
- **Delegates**

### IdeaTokenHub

The IdeaTokenHub handles tokenization and crowdfunding of permissionlessly submitted ideas for new Nouns governance proposals. Each idea is represented as a unique ERC1155 tokenId, which enables permissionless on-chain minting. Competition for pushing an idea token through to Nouns governance is introduced through a crowdfunding auction called a "wave". Tokenized ideas with the most funding at the end of each auction are officially proposed into the Nouns governance system by leveraging proposal power lent/delegated to PropLot by Nouns tokenholders.
The IdeaTokenHub handles tokenization and crowdfunding of permissionlessly submitted ideas for new Nouns governance proposals. Each idea is represented as a unique ERC1155 tokenId, which enables permissionless on-chain minting. Competition for pushing an idea token through to Nouns governance is introduced through a crowdfunding auction called a "wave". Tokenized ideas with the most funding at the end of each auction are officially proposed into the Nouns governance system by leveraging proposal power lent/delegated to Wave by Nouns tokenholders.

### PropLot Core
### Wave Core

To perform official onchain proposals to Nouns governance, the PropLot Core contract manages a set of deterministically derived Delegate contracts. These Delegate contracts are designed for a single function: to non-custodially receive delegation from Noun token holders and push onchain proposals to the Nouns governance ecosystem. Nouns NFT holders who delegate to PropLot are compensated for granting the protocol the ability to create proposals on their behalf in the form of earning yield.
To perform official onchain proposals to Nouns governance, the Wave Core contract manages a set of deterministically derived Delegate contracts. These Delegate contracts are designed for a single function: to non-custodially receive delegation from Noun token holders and push onchain proposals to the Nouns governance ecosystem. Nouns NFT holders who delegate to Wave are compensated for granting the protocol the ability to create proposals on their behalf in the form of earning yield.

One caveat worth noting is that since Nouns voting power delegation is all-or-nothing on an address basis, Noun token holders can only delegate (and earn yield) on Nouns token balances up to the proposal threshold per wallet address. Furthermore, registered delegations are handled optimistically and resolved at proposal time due to the fact that delegations can be revoked directly on the Nouns token contract.

### Delegates

All PropLot Protocol Delegate contracts are managed by the PropLot Core. They are designed to receive Nouns token delegation non-custodially so they can be used as proxies to push onchain proposals to Nouns governance.
All Wave Protocol Delegate contracts are managed by the Wave Core. They are designed to receive Nouns token delegation non-custodially so they can be used as proxies to push onchain proposals to Nouns governance.

For utmost security, Delegates never custody Nouns tokens and can only push proposals.

## User Flow

### Nouns holders

Nouns tokenholders must delegate their voting power to PropLot via a call to the Nouns token contract using either the `delegate()` or `delegateBySig()` function, while providing a valid Delegate address. Functions for selecting a suitable delegate for a Nouns holder can be referenced in the "Usage" section below.
Nouns tokenholders must delegate their voting power to Wave via a call to the Nouns token contract using either the `delegate()` or `delegateBySig()` function, while providing a valid Delegate address. Functions for selecting a suitable delegate for a Nouns holder can be referenced in the "Usage" section below.

Once voting power has been delegated to PropLot, the tokenholder must register their delegation with PropLot and thus their intent to provide proposal power. Registration updates this contract's storage to optimistically expect the registered voting power. Since delegation is performed directly on the Nouns token contract, this may change and is validated at the conclusion of each auction.
Once voting power has been delegated to Wave, the tokenholder must register their delegation with Wave and thus their intent to provide proposal power. Registration updates this contract's storage to optimistically expect the registered voting power. Since delegation is performed directly on the Nouns token contract, this may change and is validated at the conclusion of each auction.

```solidity
/// @dev Updates this contract's storage to reflect delegations performed directly on the Nouns token contract
Expand All @@ -64,13 +64,13 @@ Using ECDSA signatures, Nouns tokenholders can simultaneously create a delegate

```solidity
/// @dev Simultaneously creates a delegate if it doesn't yet exist and grants voting power to the delegate
function delegateBySig(PropLotSignature calldata propLotSig) external;
function delegateBySig(WaveSignature calldata waveSig) external;
```

At the end of each wave, delegations deemed to have violated their optimistic registration are cleared and the remaining delegators whose voting power was legitimately provided to the protocol are marked eligible to claim their yield:

```solidity
/// @dev Provides a way to collect the yield earned by Nounders who have delegated to PropLot
/// @dev Provides a way to collect the yield earned by Nounders who have delegated to Wave
function claim() external returns (uint256 claimAmt);
```

Expand Down Expand Up @@ -106,15 +106,15 @@ $ forge test

## Usage

The PropLot protocol core contract provides numerous convenience functions to improve offchain devX by returning values relevant for developing offchain components.
The Wave protocol core contract provides numerous convenience functions to improve offchain devX by returning values relevant for developing offchain components.

### To view the current minimum votes required to submit an onchain proposal to Nouns governance

```solidity
function getCurrentMinRequiredVotes() external view returns (uint256 minRequiredVotes);
```

### To fetch a suitable PropLot delegate for a given user based on their Nouns token voting power. This is the address the tokenholder should delegate to, using the Nouns token contract `delegate()` function.
### To fetch a suitable Wave delegate for a given user based on their Nouns token voting power. This is the address the tokenholder should delegate to, using the Nouns token contract `delegate()` function.

```solidity
/// @dev Returns a suitable delegate address for an account based on its voting power
Expand Down Expand Up @@ -180,7 +180,7 @@ function getOrderedEligibleIdeaIds(uint256 optLimiter) external view returns (ui

```solidity
/// @dev Returns an array of the current wave's leading IdeaIds where the array length is determined
/// by the protocol's number of available proposer delegates, fetched from the PropLotCore contract
/// by the protocol's number of available proposer delegates, fetched from the WaveCore contract
function getWinningIdeaIds() external view returns (uint256 minRequiredVotes, uint256 numEligibleProposers, uint96[] memory winningIds);
```

Expand All @@ -201,12 +201,12 @@ function getOrderedProposedIdeaIds() external view returns (uint96[] memory orde

## Live Deployments

PropLot protocol is currently deployed in Beta on Base Sepolia testnet for backend & frontend development and finalized Ethereum mainnet deployments are coming soon.
Wave protocol is currently deployed in Beta on Base Sepolia testnet for backend & frontend development and finalized Ethereum mainnet deployments are coming soon.

| Name | Contract Details | Contract Address |
| --- | --- | --- |
| IdeaTokenHub | Harness, Proxy | 0xaB626b93B3f98d79ae1FBf6c76Bf678F83E7faf3 |
| PropLot | Harness, Proxy | 0xD49c56d08D3c40854c0543bA5B1747f2Ad1c7b89 |
| Wave | Harness, Proxy | 0xD49c56d08D3c40854c0543bA5B1747f2Ad1c7b89 |
| NounsToken | Harness | 0x1B8D11880fe221B51FC814fF4C41366a91A59DEB |

Note that the above testnet contracts deployed to Base Sepolia network are harnesses to expose convenience functions that would normally otherwise be protected to expedite development.
20 changes: 10 additions & 10 deletions script/CreateIdeas.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ import {NounsTokenLike} from "nouns-monorepo/governance/NounsDAOInterfaces.sol";
import {IERC721Checkpointable} from "src/interfaces/IERC721Checkpointable.sol";
import {IdeaTokenHub} from "src/IdeaTokenHub.sol";
import {Delegate} from "src/Delegate.sol";
import {IPropLot} from "src/interfaces/IPropLot.sol";
import {PropLot} from "src/PropLot.sol";
import {PropLotHarness} from "test/harness/PropLotHarness.sol";
import {IWave} from "src/interfaces/IWave.sol";
import {Wave} from "src/Wave.sol";
import {WaveHarness} from "test/harness/WaveHarness.sol";

contract CreateIdeas is Script {
/// @notice Harness contract is used on testnet ONLY
PropLotHarness propLot;
WaveHarness waveCore;
IdeaTokenHub ideaTokenHub;
IERC721Checkpointable nounsToken;

string uri;
NounsDAOV3Proposals.ProposalTxs txs;
string description;
IPropLot.Proposal[] proposals;
IWave.Proposal[] proposals;

function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
Expand All @@ -33,8 +33,8 @@ contract CreateIdeas is Script {
vm.startBroadcast(deployerPrivateKey);

uri = "someURI";
propLot = PropLotHarness(0x92bc9f0D42A3194Df2C5AB55c3bbDD82e6Fb2F92);
ideaTokenHub = IdeaTokenHub(address(propLot.ideaTokenHub()));
waveCore = WaveHarness(0x92bc9f0D42A3194Df2C5AB55c3bbDD82e6Fb2F92);
ideaTokenHub = IdeaTokenHub(address(waveCore.ideaTokenHub()));
nounsToken = IERC721Checkpointable(0x9B786579B3d4372d54DFA212cc8B1589Aaf6DcF3);

// setup mock proposal
Expand All @@ -54,10 +54,10 @@ contract CreateIdeas is Script {
// ideaTokenHub.createIdea{value: 0.0001 ether}(txs, description);
// ideaTokenHub.sponsorIdea{value: 0.0001 ether}(1);

// proplot delegate events
// (address targetProxy, uint256 votes) = propLot.getSuitableDelegateFor(deployer);
// Wave delegate events
// (address targetProxy, uint256 votes) = waveCore.getSuitableDelegateFor(deployer);
// console2.logUint(votes);
// assert(targetProxy == propLot.getDelegateAddress(1));
// assert(targetProxy == waveCore.getDelegateAddress(1));
// nounsToken.delegate(targetProxy);

vm.stopBroadcast();
Expand Down
24 changes: 12 additions & 12 deletions script/TestnetDeployment.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ import {IERC721Checkpointable} from "src/interfaces/IERC721Checkpointable.sol";
import {INounsDAOLogicV3} from "src/interfaces/INounsDAOLogicV3.sol";
import {IdeaTokenHub} from "src/IdeaTokenHub.sol";
import {Delegate} from "src/Delegate.sol";
import {IPropLot} from "src/interfaces/IPropLot.sol";
import {PropLot} from "src/PropLot.sol";
import {PropLotHarness} from "test/harness/PropLotHarness.sol";
import {IWave} from "src/interfaces/IWave.sol";
import {Wave} from "src/Wave.sol";
import {WaveHarness} from "test/harness/WaveHarness.sol";
import {NounsDAOExecutorV2Testnet} from "test/harness/NounsDAOExecutorV2Testnet.sol";


Expand All @@ -40,7 +40,7 @@ import {NounsDAOExecutorV2Testnet} from "test/harness/NounsDAOExecutorV2Testnet.
/// Verification:
/*
`forge verify-contract <ideaTokenHub> --verifier-url $BASESCAN_SEPOLIA_ENDPOINT --watch --etherscan-api-key $BASESCAN_API_KEY src/IdeaTokenHub.sol:IdeaTokenHub`
`forge verify-contract <propLot> --verifier-url $BASESCAN_SEPOLIA_ENDPOINT --watch --etherscan-api-key $BASESCAN_API_KEY test/harness/PropLotHarness.sol:PropLotHarness`
`forge verify-contract <waveCore> --verifier-url $BASESCAN_SEPOLIA_ENDPOINT --watch --etherscan-api-key $BASESCAN_API_KEY test/harness/WaveHarness.sol:WaveHarness`
`forge verify-contract <nounsToken> --verifier-url $BASESCAN_SEPOLIA_ENDPOINT --watch --etherscan-api-key $BASESCAN_API_KEY lib/nouns-monorepo/nouns-contracts/contracts/test/NounsTokenHarness.sol`
*/

Expand All @@ -53,8 +53,8 @@ contract Deploy is Script {
uint256 waveLength = 50; // TESTNET ONLY

/// @notice Harness contract is used on testnet ONLY
PropLotHarness propLotImpl;
PropLotHarness propLot;
WaveHarness waveCoreImpl;
WaveHarness waveCore;
IdeaTokenHub ideaTokenHubImpl;
IdeaTokenHub ideaTokenHub;

Expand Down Expand Up @@ -155,19 +155,19 @@ contract Deploy is Script {

//end nouns setup

// setup PropLot contracts
// setup Wave contracts
string memory uri = "someURI";
ideaTokenHubImpl = new IdeaTokenHub();
ideaTokenHub = IdeaTokenHub(address(new ERC1967Proxy(address(ideaTokenHubImpl), '')));
propLotImpl = new PropLotHarness();
bytes memory initData = abi.encodeWithSelector(IPropLot.initialize.selector, address(ideaTokenHub), address(nounsGovernorProxy), address(nounsTokenHarness), minSponsorshipAmount, waveLength, uri);
propLot = PropLotHarness(address(new ERC1967Proxy(address(propLotImpl), initData)));
waveCoreImpl = new WaveHarness();
bytes memory initData = abi.encodeWithSelector(IWave.initialize.selector, address(ideaTokenHub), address(nounsGovernorProxy), address(nounsTokenHarness), minSponsorshipAmount, waveLength, uri);
waveCore = WaveHarness(address(new ERC1967Proxy(address(waveCoreImpl), initData)));

require(address(ideaTokenHub).code.length > 0);
require(address(propLot).code.length > 0);
require(address(waveCore).code.length > 0);
require(address(nounsTokenHarness).code.length > 0);
console2.logAddress(address(ideaTokenHub));
console2.logAddress(address(propLot));
console2.logAddress(address(waveCore));
console2.logAddress(address(nounsTokenHarness));

// balances to roughly mirror mainnet
Expand Down
14 changes: 7 additions & 7 deletions src/Delegate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,27 @@ pragma solidity ^0.8.24;
import {NounsDAOV3Proposals} from "nouns-monorepo/governance/NounsDAOV3Proposals.sol";
import {INounsDAOLogicV3} from "src/interfaces/INounsDAOLogicV3.sol";

/// @title PropLot Protocol Delegate
/// @title Wave Protocol Delegate
/// @author 📯📯📯.eth
/// @notice All PropLot Protocol Delegate contracts are managed by the PropLot Core. They are designed to receive
/// @notice All Wave Protocol Delegate contracts are managed by the Wave Core. They are designed to receive
/// Nouns token delegation non-custodially so they can be used as proxies to push onchain proposals to Nouns governance.
/// @notice For utmost security, Delegates never custody Nouns tokens and can only push proposals

contract Delegate {
error NotPropLotCore(address caller);
error NotWaveCore(address caller);

address public immutable propLot;
address public immutable waveCore;

constructor(address propLot_) {
propLot = propLot_;
constructor(address waveCore_) {
waveCore = waveCore_;
}

function pushProposal(
INounsDAOLogicV3 governor,
NounsDAOV3Proposals.ProposalTxs calldata txs,
string calldata description
) external returns (uint256 nounsProposalId) {
if (msg.sender != propLot) revert NotPropLotCore(msg.sender);
if (msg.sender != waveCore) revert NotWaveCore(msg.sender);

nounsProposalId =
INounsDAOLogicV3(governor).propose(txs.targets, txs.values, txs.signatures, txs.calldatas, description);
Expand Down
Loading

0 comments on commit f8b6a4e

Please sign in to comment.