Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DRAFT: require noun age to fork #806

Draft
wants to merge 9 commits into
base: verbs-spend-or-burn-excess-eth-every-n-auctions
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,8 @@ contract NounsDAOStorageV3 {
INounsDAOForkEscrow forkEscrow;
/// @notice address of the DAO's fork deployer contract
IForkDAODeployer forkDAODeployer;
/// @notice the age of a noun, in noun ids, for a noun to be allowed to fork
uint16 nounAgeRequiredToFork;
/// @notice ERC20 tokens to include when sending funds to a deployed fork
address[] erc20TokensToIncludeInFork;
/// @notice The treasury contract of the last deployed fork
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -803,6 +803,10 @@ contract NounsDAOLogicV3 is NounsDAOStorageV3, NounsDAOEventsV3 {
ds._setForkThresholdBPS(newForkThresholdBPS);
}

function _setNounAgeRequiredToFork(uint16 newNounAgeRequiredToFork) external {
ds._setNounAgeRequiredToFork(newNounAgeRequiredToFork);
}

/**
* @notice Admin function for setting the proposal id at which vote snapshots start using the voting start block
* instead of the proposal creation block.
Expand Down Expand Up @@ -1008,6 +1012,10 @@ contract NounsDAOLogicV3 is NounsDAOStorageV3, NounsDAOEventsV3 {
return ds.forkDAODeployer;
}

function nounAgeRequiredToFork() public view returns (uint16) {
return ds.nounAgeRequiredToFork;
}

function forkEndTimestamp() public view returns (uint256) {
return ds.forkEndTimestamp;
}
Expand Down
11 changes: 11 additions & 0 deletions packages/nouns-contracts/contracts/governance/NounsDAOV3Admin.sol
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ library NounsDAOV3Admin {
/// @notice Emitted when the main timelock, timelockV1 and admin are set
event TimelocksAndAdminSet(address timelock, address timelockV1, address admin);

event NounAgeRequiredToForkSet(uint16 oldNounAgeRequiredToFork, uint16 newNounAgeRequiredToFork);

/// @notice The minimum setable proposal threshold
uint256 public constant MIN_PROPOSAL_THRESHOLD_BPS = 1; // 1 basis point or 0.01%

Expand Down Expand Up @@ -557,6 +559,15 @@ library NounsDAOV3Admin {
ds.forkThresholdBPS = newForkThresholdBPS;
}

function _setNounAgeRequiredToFork(NounsDAOStorageV3.StorageV3 storage ds, uint16 newNounAgeRequiredToFork)
external
onlyAdmin(ds)
{
emit NounAgeRequiredToForkSet(ds.nounAgeRequiredToFork, newNounAgeRequiredToFork);

ds.nounAgeRequiredToFork = newNounAgeRequiredToFork;
}

/**
* @notice Admin function for setting the timelocks and admin
* @param timelock the new timelock contract
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ pragma solidity ^0.8.19;
import { NounsDAOStorageV3, INounsDAOForkEscrow, INounsDAOExecutorV2 } from '../NounsDAOInterfaces.sol';
import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import { NounsTokenFork } from './newdao/token/NounsTokenFork.sol';
import { INounsAuctionHouseV2 } from '../../interfaces/INounsAuctionHouseV2.sol';

library NounsDAOV3Fork {
error ForkThresholdNotMet();
error ForkPeriodNotActive();
error ForkPeriodActive();
error AdminOnly();
error UseAlternativeWithdrawFunction();
error NounIdNotOldEnough();

/// @notice Emitted when someones adds nouns to the fork escrow
event EscrowedToFork(
Expand Down Expand Up @@ -80,7 +82,10 @@ library NounsDAOV3Fork {
if (isForkPeriodActive(ds)) revert ForkPeriodActive();
INounsDAOForkEscrow forkEscrow = ds.forkEscrow;

uint16 nounAgeRequiredToFork = ds.nounAgeRequiredToFork;
uint256 auctionedNounId = INounsAuctionHouseV2(ds.nouns.minter()).auction().nounId;
for (uint256 i = 0; i < tokenIds.length; i++) {
checkNounIdIsAllowedToFork(auctionedNounId, nounAgeRequiredToFork, tokenIds[i]);
ds.nouns.safeTransferFrom(msg.sender, address(forkEscrow), tokenIds[i]);
}

Expand Down Expand Up @@ -150,7 +155,10 @@ library NounsDAOV3Fork {
address timelock = address(ds.timelock);
sendProRataTreasury(ds, ds.forkDAOTreasury, tokenIds.length, adjustedTotalSupply(ds));

uint16 nounAgeRequiredToFork = ds.nounAgeRequiredToFork;
uint256 auctionedNounId = INounsAuctionHouseV2(ds.nouns.minter()).auction().nounId;
for (uint256 i = 0; i < tokenIds.length; i++) {
checkNounIdIsAllowedToFork(auctionedNounId, nounAgeRequiredToFork, tokenIds[i]);
ds.nouns.transferFrom(msg.sender, timelock, tokenIds[i]);
}

Expand Down Expand Up @@ -216,6 +224,14 @@ library NounsDAOV3Fork {
return ds.forkEscrow.numTokensInEscrow();
}

function checkNounIdIsAllowedToFork(
uint256 auctionedNounId,
uint16 nounAgeRequiredToFork,
uint256 tokenId
) internal pure {
if (tokenId >= auctionedNounId - nounAgeRequiredToFork) revert NounIdNotOldEnough();
}

/**
* @notice Returns the number of nouns in supply minus nouns owned by the DAO, i.e. held in the treasury or in an
* escrow after it has closed.
Expand Down
15 changes: 15 additions & 0 deletions packages/nouns-contracts/script/DAOLogicV3p1/DeployDAOV3p1.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

import 'forge-std/Script.sol';
import { NounsDAOLogicV3 } from '../../contracts/governance/NounsDAOLogicV3.sol';

contract DeployDAOV3p1 is Script {
function run() public returns (NounsDAOLogicV3 newLogic) {
uint256 deployerKey = vm.envUint('DEPLOYER_PRIVATE_KEY');

vm.startBroadcast(deployerKey);
newLogic = new NounsDAOLogicV3();
vm.stopBroadcast();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.15;

import 'forge-std/Script.sol';
import { NounsDAOLogicV1 } from '../../contracts/governance/NounsDAOLogicV1.sol';
import { NounsDAOForkEscrow } from '../../contracts/governance/fork/NounsDAOForkEscrow.sol';
import { ForkDAODeployer } from '../../contracts/governance/fork/ForkDAODeployer.sol';
import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol';

contract ProposeDAOV3p1UpgradeMainnet is Script {
NounsDAOLogicV1 public constant NOUNS_DAO_PROXY_MAINNET =
NounsDAOLogicV1(0x6f3E6272A167e8AcCb32072d08E0957F9c79223d);

function run() public returns (uint256 proposalId) {
uint256 proposerKey = vm.envUint('PROPOSER_KEY');
address newDAOImpl = vm.envAddress('DAO_V3p1_IMPL');
string memory description = vm.readFile(vm.envString('PROPOSAL_DESCRIPTION_FILE'));

vm.startBroadcast(proposerKey);

uint8 numTxs = 1;
address[] memory targets = new address[](numTxs);
uint256[] memory values = new uint256[](numTxs);
string[] memory signatures = new string[](numTxs);
bytes[] memory calldatas = new bytes[](numTxs);

uint256 i = 0;
targets[i] = address(NOUNS_DAO_PROXY_MAINNET);
values[i] = 0;
signatures[i] = '_setImplementation(address)';
calldatas[i] = abi.encode(newDAOImpl);

proposalId = NOUNS_DAO_PROXY_MAINNET.propose(targets, values, signatures, calldatas, description);
console.log('Proposed proposalId: %d', proposalId);

vm.stopBroadcast();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,20 @@ abstract contract NounsAuctionHouseBaseTest is DeployUtils {
INounsToken nouns;
address noundersDAO = makeAddr('noundersDAO');
address owner = makeAddr('owner');
NounsAuctionHouseProxy auctionHouseProxy;
address auctionHouseProxy;
NounsAuctionHouseProxyAdmin proxyAdmin;
uint256[] nounIds;

function setUp() public virtual {
(
NounsAuctionHouseProxy auctionHouseProxy_,
NounsAuctionHouseProxyAdmin proxyAdmin_
) = _deployAuctionHouseV1AndToken(owner, noundersDAO, address(0));
(address auctionHouseProxy_, NounsAuctionHouseProxyAdmin proxyAdmin_) = _deployAuctionHouseV1AndToken(
owner,
noundersDAO,
address(0)
);
auctionHouseProxy = auctionHouseProxy_;
proxyAdmin = proxyAdmin_;

auctionHouse = INounsAuctionHouse(address(auctionHouseProxy_));
auctionHouse = INounsAuctionHouse(auctionHouseProxy_);

vm.prank(owner);
auctionHouse.unpause();
Expand Down
20 changes: 10 additions & 10 deletions packages/nouns-contracts/test/foundry/NounsAuctionHouseV2.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ contract NounsAuctionHouseV2TestBase is Test, DeployUtils {
NounsAuctionHouseV2 auction;

function setUp() public {
(NounsAuctionHouseProxy auctionProxy, NounsAuctionHouseProxyAdmin proxyAdmin) = _deployAuctionHouseV1AndToken(
(address auctionProxy, NounsAuctionHouseProxyAdmin proxyAdmin) = _deployAuctionHouseV1AndToken(
owner,
noundersDAO,
minter
);

AuctionHouseUpgrader.upgradeAuctionHouse(owner, proxyAdmin, auctionProxy);

auction = NounsAuctionHouseV2(address(auctionProxy));
auction = NounsAuctionHouseV2(auctionProxy);

vm.prank(owner);
auction.unpause();
Expand Down Expand Up @@ -161,13 +161,13 @@ contract NounsAuctionHouseV2Test is NounsAuctionHouseV2TestBase {
}

function test_settleAuction_revertsWhenAuctionHasntBegunYet() public {
(NounsAuctionHouseProxy auctionProxy, NounsAuctionHouseProxyAdmin proxyAdmin) = _deployAuctionHouseV1AndToken(
(address auctionProxy, NounsAuctionHouseProxyAdmin proxyAdmin) = _deployAuctionHouseV1AndToken(
owner,
noundersDAO,
minter
);
AuctionHouseUpgrader.upgradeAuctionHouse(owner, proxyAdmin, auctionProxy);
auction = NounsAuctionHouseV2(address(auctionProxy));
auction = NounsAuctionHouseV2(auctionProxy);

vm.expectRevert("Auction hasn't begun");
auction.settleAuction();
Expand All @@ -185,12 +185,12 @@ contract NounsAuctionHouseV2Test is NounsAuctionHouseV2TestBase {
}

function test_V2Migration_works() public {
(NounsAuctionHouseProxy auctionProxy, NounsAuctionHouseProxyAdmin proxyAdmin) = _deployAuctionHouseV1AndToken(
(address auctionProxy, NounsAuctionHouseProxyAdmin proxyAdmin) = _deployAuctionHouseV1AndToken(
owner,
noundersDAO,
minter
);
NounsAuctionHouse auctionV1 = NounsAuctionHouse(address(auctionProxy));
NounsAuctionHouse auctionV1 = NounsAuctionHouse(auctionProxy);
vm.prank(owner);
auctionV1.unpause();
vm.roll(block.number + 1);
Expand All @@ -207,7 +207,7 @@ contract NounsAuctionHouseV2Test is NounsAuctionHouseV2TestBase {

AuctionHouseUpgrader.upgradeAuctionHouse(owner, proxyAdmin, auctionProxy);

NounsAuctionHouseV2 auctionV2 = NounsAuctionHouseV2(address(auctionProxy));
NounsAuctionHouseV2 auctionV2 = NounsAuctionHouseV2(auctionProxy);

INounsAuctionHouseV2.AuctionV2 memory auctionV2State = auctionV2.auction();

Expand All @@ -229,12 +229,12 @@ contract NounsAuctionHouseV2Test is NounsAuctionHouseV2TestBase {
}

function test_V2Migration_copiesPausedWhenTrue() public {
(NounsAuctionHouseProxy auctionProxy, NounsAuctionHouseProxyAdmin proxyAdmin) = _deployAuctionHouseV1AndToken(
(address auctionProxy, NounsAuctionHouseProxyAdmin proxyAdmin) = _deployAuctionHouseV1AndToken(
owner,
noundersDAO,
minter
);
NounsAuctionHouse auctionV1 = NounsAuctionHouse(address(auctionProxy));
NounsAuctionHouse auctionV1 = NounsAuctionHouse(auctionProxy);
vm.prank(owner);
auctionV1.unpause();
vm.roll(block.number + 1);
Expand All @@ -251,7 +251,7 @@ contract NounsAuctionHouseV2Test is NounsAuctionHouseV2TestBase {

AuctionHouseUpgrader.upgradeAuctionHouse(owner, proxyAdmin, auctionProxy);

NounsAuctionHouseV2 auctionV2 = NounsAuctionHouseV2(address(auctionProxy));
NounsAuctionHouseV2 auctionV2 = NounsAuctionHouseV2(auctionProxy);
assertEq(auctionV2.paused(), true);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,7 @@ abstract contract ExecutableProposalState is NounsDAOLogicV3BaseTest {
function setUp() public virtual override {
super.setUp();

vm.startPrank(minter);
nounsToken.mint();
nounsToken.transferFrom(minter, user, 1);
vm.stopPrank();
vm.roll(block.number + 1);
mintTo(user);

// prep an executable proposal
proposalId = propose(user, makeAddr('target'), 0, '', '', '');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import { NounsSeeder } from '../../../contracts/NounsSeeder.sol';
import { IProxyRegistry } from '../../../contracts/external/opensea/IProxyRegistry.sol';
import { NounsDAOExecutorV2 } from '../../../contracts/governance/NounsDAOExecutorV2.sol';
import { NounsDAOForkEscrow } from '../../../contracts/governance/fork/NounsDAOForkEscrow.sol';
import { INounsAuctionHouseV2 } from '../../../contracts/interfaces/INounsAuctionHouseV2.sol';

interface INounsAuctionHouseForTest is INounsAuctionHouseV2 {
function owner() external view returns (address);
}

abstract contract NounsDAOLogicV3BaseTest is Test, DeployUtilsV3, SigUtils {
event ProposalUpdated(
Expand Down Expand Up @@ -93,11 +98,26 @@ abstract contract NounsDAOLogicV3BaseTest is Test, DeployUtilsV3, SigUtils {
}

function mintTo(address to) internal returns (uint256 tokenID) {
vm.startPrank(minter);
tokenID = nounsToken.mint();
nounsToken.transferFrom(minter, to, tokenID);
vm.stopPrank();
INounsAuctionHouseForTest ah = INounsAuctionHouseForTest(minter);

uint256 ownerBalanceBefore = ah.owner().balance;

if (block.timestamp >= ah.auction().endTime) {
ah.settleCurrentAndCreateNewAuction();
}

tokenID = ah.auction().nounId;

ah.createBid{ value: 1 ether }(tokenID);
vm.warp(block.timestamp + ah.auction().endTime + 1);
ah.settleCurrentAndCreateNewAuction();

nounsToken.transferFrom(address(this), to, tokenID);
vm.roll(block.number + 1);

// Reverting treasury balance to prevent auction revenue from interfering with existing tests
// that previous used minting without auctions
vm.deal(ah.owner(), ownerBalanceBefore);
}

function propose(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ abstract contract DAOForkZeroState is NounsDAOLogicV3BaseTest {
erc20Mock.mint(address(timelock), 300e18);

// Mint total of 20 tokens. 18 to token holder, 2 to nounders
vm.startPrank(minter);
while (nounsToken.totalSupply() < 20) {
nounsToken.mint();
nounsToken.transferFrom(minter, tokenHolder, nounsToken.totalSupply() - 1);
mintTo(tokenHolder);
}
vm.stopPrank();

address nounders = nounsToken.noundersDAO();
vm.prank(nounders);
nounsToken.transferFrom(nounders, tokenHolder, 10);

assertEq(dao.nouns().balanceOf(tokenHolder), 18);

escrow = dao.forkEscrow();
Expand Down
Loading