Skip to content

Commit

Permalink
Initial propose script
Browse files Browse the repository at this point in the history
  • Loading branch information
alexkeating committed Feb 7, 2024
1 parent 3ed27c3 commit e5dd3d8
Show file tree
Hide file tree
Showing 9 changed files with 380 additions and 0 deletions.
4 changes: 4 additions & 0 deletions script/DeployInput.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ contract DeployInput {
address constant STAKE_TOKEN_ADDRESS = 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984;
// TODO not determined yet
uint256 constant PAYOUT_AMOUNT = 10e18;

address constant WBTC_WETH_3000_POOL = 0xCBCdF9626bC03E24f779434178A73a0B4bad62eD;
address constant DAI_WETH_3000_POOL = 0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8;
address constant DAI_USDC_100_POOL = 0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168;
}
79 changes: 79 additions & 0 deletions script/Propose.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {Script} from "forge-std/Script.sol";

import {DeployInput} from "script/DeployInput.sol";
import {GovernorBravoDelegate} from "script/interfaces/GovernorBravoInterfaces.sol";

// 1. Get Uniswap governor
// 2. Pick suitable proposer
// 3. Switch v3 factory owner
// 4. Then enable fee amount and set protocol fee for a specific pool
contract Propose is Script, DeployInput {
// TODO: Make a more specific interface
GovernorBravoDelegate constant GOVERNOR =
GovernorBravoDelegate(0x408ED6354d4973f66138C91495F2f2FCbd8724C3); // Mainnet governor
address PROPOSER = 0xe7925D190aea9279400cD9a005E33CEB9389Cc2b; // TODO: jessewldn

function propose(address _v3FactoryOwner) internal returns (uint256 _proposalId) {
address[] memory _targets = new address[](4);
uint256[] memory _values = new uint256[](4);
string[] memory _signatures = new string[](4);
bytes[] memory _calldatas = new bytes[](4);

_targets[0] = UNISWAP_V3_FACTORY_ADDRESS;
_values[0] = 0;
_signatures[0] = "setOwner(address)";
_calldatas[0] = abi.encode(address(_v3FactoryOwner));

// Set fees for the couple pools
// dai-usdc
// dai-weth
// wbtc-weth
_targets[1] = _v3FactoryOwner;
_values[1] = 0;
_signatures[1] = "setFeeProtocol(address,uint8,uint8)";
// TODO: placeholder fees
// wbtc-weth 0.3%
_calldatas[1] = abi.encode(WBTC_WETH_3000_POOL, uint8(10), uint8(10));

_targets[2] = _v3FactoryOwner;
_values[2] = 0;
_signatures[2] = "setFeeProtocol(address,uint8,uint8)";
// TODO: placeholder fees
// dai-weth 0.3%
_calldatas[2] = abi.encode(DAI_WETH_3000_POOL, 10, 10);

_targets[3] = _v3FactoryOwner;
_values[3] = 0;
_signatures[3] = "setFeeProtocol(address,uint8,uint8)";
// TODO: placeholder fees
// dai-usdc 0.01%
_calldatas[3] = abi.encode(DAI_USDC_100_POOL, 10, 10);

return GOVERNOR.propose(
_targets,
_values,
_signatures,
_calldatas,
"Change Uniswap V3 factory owner and set pool protocol fees"
);
}

/// @dev After the new Governor is deployed on mainnet, this can move from a parameter to a const
function run(address v3FactoryOwner) public returns (uint256 _proposalId) {
// The expectation is the key loaded here corresponds to the address of the `proposer` above.
// When running as a script, broadcast will fail if the key is not correct.
uint256 _proposerKey = vm.envOr(
"PROPOSER_PRIVATE_KEY",
uint256(0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d)
);
vm.rememberKey(_proposerKey);

vm.startBroadcast(PROPOSER);
_proposalId = propose(v3FactoryOwner);
vm.stopBroadcast();
return _proposalId;
}
}
115 changes: 115 additions & 0 deletions script/interfaces/GovernorBravoInterfaces.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

// Constracts were taken from
// https://etherscan.io/address/0x53a328f4086d7c0f1fa19e594c9b842125263026#code#F2#L182
// Taken from the address

interface GovernorBravoDelegate {
type ProposalState is uint8;

struct Receipt {
bool hasVoted;
uint8 support;
uint96 votes;
}

event NewAdmin(address oldAdmin, address newAdmin);
event NewImplementation(address oldImplementation, address newImplementation);
event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);
event ProposalCanceled(uint256 id);
event ProposalCreated(
uint256 id,
address proposer,
address[] targets,
uint256[] values,
string[] signatures,
bytes[] calldatas,
uint256 startBlock,
uint256 endBlock,
string description
);
event ProposalExecuted(uint256 id);
event ProposalQueued(uint256 id, uint256 eta);
event ProposalThresholdSet(uint256 oldProposalThreshold, uint256 newProposalThreshold);
event VoteCast(
address indexed voter, uint256 proposalId, uint8 support, uint256 votes, string reason
);
event VotingDelaySet(uint256 oldVotingDelay, uint256 newVotingDelay);
event VotingPeriodSet(uint256 oldVotingPeriod, uint256 newVotingPeriod);

function BALLOT_TYPEHASH() external view returns (bytes32);
function DOMAIN_TYPEHASH() external view returns (bytes32);
function MAX_PROPOSAL_THRESHOLD() external view returns (uint256);
function MAX_VOTING_DELAY() external view returns (uint256);
function MAX_VOTING_PERIOD() external view returns (uint256);
function MIN_PROPOSAL_THRESHOLD() external view returns (uint256);
function MIN_VOTING_DELAY() external view returns (uint256);
function MIN_VOTING_PERIOD() external view returns (uint256);
function _acceptAdmin() external;
function _initiate(uint256 proposalCount) external;
function _setPendingAdmin(address newPendingAdmin) external;
function _setProposalThreshold(uint256 newProposalThreshold) external;
function _setVotingDelay(uint256 newVotingDelay) external;
function _setVotingPeriod(uint256 newVotingPeriod) external;
function admin() external view returns (address);
function cancel(uint256 proposalId) external;
function castVote(uint256 proposalId, uint8 support) external;
function castVoteBySig(uint256 proposalId, uint8 support, uint8 v, bytes32 r, bytes32 s) external;
function castVoteWithReason(uint256 proposalId, uint8 support, string memory reason) external;
function execute(uint256 proposalId) external payable;
function getActions(uint256 proposalId)
external
view
returns (
address[] memory targets,
uint256[] memory values,
string[] memory signatures,
bytes[] memory calldatas
);
function getReceipt(uint256 proposalId, address voter) external view returns (Receipt memory);
function implementation() external view returns (address);
function initialProposalId() external view returns (uint256);
function initialize(
address timelock_,
address uni_,
uint256 votingPeriod_,
uint256 votingDelay_,
uint256 proposalThreshold_
) external;
function latestProposalIds(address) external view returns (uint256);
function name() external view returns (string memory);
function pendingAdmin() external view returns (address);
function proposalCount() external view returns (uint256);
function proposalMaxOperations() external view returns (uint256);
function proposalThreshold() external view returns (uint256);
function proposals(uint256)
external
view
returns (
uint256 id,
address proposer,
uint256 eta,
uint256 startBlock,
uint256 endBlock,
uint256 forVotes,
uint256 againstVotes,
uint256 abstainVotes,
bool canceled,
bool executed
);
function propose(
address[] memory targets,
uint256[] memory values,
string[] memory signatures,
bytes[] memory calldatas,
string memory description
) external returns (uint256);
function queue(uint256 proposalId) external;
function quorumVotes() external view returns (uint256);
function state(uint256 proposalId) external view returns (ProposalState);
function timelock() external view returns (address);
function uni() external view returns (address);
function votingDelay() external view returns (uint256);
function votingPeriod() external view returns (uint256);
}
5 changes: 5 additions & 0 deletions src/interfaces/IUniswapV3FactoryOwnerActions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ pragma solidity ^0.8.23;
/// @dev Stripped down and renamed from:
/// https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/interfaces/IUniswapV3Factory.sol
interface IUniswapV3FactoryOwnerActions {
/// @notice Returns the current owner of the factory
/// @dev Can be changed by the current owner via setOwner
/// @return The address of the factory owner
function owner() external view returns (address);

/// @notice Updates the owner of the factory
/// @dev Must be called by the current owner
/// @param _owner The new owner of the factory
Expand Down
30 changes: 30 additions & 0 deletions test/UniStaker.integration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import {DeployInput} from "script/DeployInput.sol";

import {V3FactoryOwner} from "src/V3FactoryOwner.sol";
import {UniStaker} from "src/UniStaker.sol";
import {ProposalTest} from "test/helpers/ProposalTest.sol";
import {IUniswapV3PoolOwnerActions} from "src/interfaces/IUniswapV3PoolOwnerActions.sol";
import {IUniswapV3FactoryOwnerActions} from "src/interfaces/IUniswapV3FactoryOwnerActions.sol";
import {IUniswapPool} from "test/helpers/interfaces/IUniswapPool.sol";

contract DeployScriptTest is Test, DeployInput {
function setUp() public {
Expand All @@ -30,3 +34,29 @@ contract DeployScriptTest is Test, DeployInput {
assertTrue(uniStaker.isRewardsNotifier(address(v3FactoryOwner)));
}
}

contract Propose is ProposalTest {
// After setup, skip to passed proposal
// with a passed proposal verify it looks right
function testFuzz_CorrectlyPassAndExecutreProposal() public {
_passAndQueueUniswapProposal();
_executeProposal();
// assert the owner of the v3 factory is correct
IUniswapV3FactoryOwnerActions factory =
IUniswapV3FactoryOwnerActions(UNISWAP_V3_FACTORY_ADDRESS);
IUniswapPool wbtcWethPool = IUniswapPool(WBTC_WETH_3000_POOL);
(,,,,, uint8 wbtcWethFeeProtocol,) = wbtcWethPool.slot0();

IUniswapPool daiWethPool = IUniswapPool(DAI_WETH_3000_POOL);
(,,,,, uint8 daiWethFeeProtocol,) = wbtcWethPool.slot0();

IUniswapPool daiUsdcPool = IUniswapPool(DAI_USDC_100_POOL);
(,,,,, uint8 daiUsdcFeeProtocol,) = wbtcWethPool.slot0();

assertEq(factory.owner(), address(v3FactoryOwner));

assertEq(wbtcWethFeeProtocol, 10 + (10 << 4));
assertEq(daiWethFeeProtocol, 10 + (10 << 4));
assertEq(daiUsdcFeeProtocol, 10 + (10 << 4));
}
}
6 changes: 6 additions & 0 deletions test/helpers/Constants.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

contract Constants {
address constant UNISWAP_GOVERNOR_ADDRESS = 0x408ED6354d4973f66138C91495F2f2FCbd8724C3;
}
120 changes: 120 additions & 0 deletions test/helpers/ProposalTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol";
import {Test} from "forge-std/Test.sol";

import {Deploy} from "script/Deploy.s.sol";
import {DeployInput} from "script/DeployInput.sol";
import {Propose} from "script/Propose.s.sol";
import {Constants} from "test/helpers/Constants.sol";
import {GovernorBravoDelegate} from "script/interfaces/GovernorBravoInterfaces.sol";
import {V3FactoryOwner} from "src/V3FactoryOwner.sol";
import {UniStaker} from "src/UniStaker.sol";

// 1. help vote on proposal and move it forward
abstract contract ProposalTest is Test, DeployInput, Constants {
//----------------- State and Setup ----------- //
uint256 uniswapProposalId;
address[] delegates;
UniStaker uniStaker;
V3FactoryOwner v3FactoryOwner;
GovernorBravoDelegate governor = GovernorBravoDelegate(UNISWAP_GOVERNOR_ADDRESS);

function setUp() public virtual {
//initialProposalCount = governor.proposalCount();

uint256 _forkBlock = 17_927_962;
vm.createSelectFork(vm.rpcUrl("mainnet"), _forkBlock);

address[] memory _delegates = new address[](6);
// Taken from https://www.tally.xyz/gov/pooltogether/delegates?sort=voting_power_desc.
// If you update these delegates (including updating order in the array),
// make sure to update any tests that reference specific delegates. The last delegate is the
// proposer and lower in the voting power than the above link.
_delegates[0] = 0x76f54Eeb0D33a2A2c5CCb72FE12542A56f35d67C;
_delegates[1] = 0x8E4ED221fa034245F14205f781E0b13C5bd6a42E;
_delegates[2] = 0xe7925D190aea9279400cD9a005E33CEB9389Cc2b;
_delegates[3] = 0x1d8F369F05343F5A642a78BD65fF0da136016452;
_delegates[4] = 0xe02457a1459b6C49469Bf658d4Fe345C636326bF;
_delegates[5] = 0x8962285fAac45a7CBc75380c484523Bb7c32d429;
for (uint256 i; i < _delegates.length; i++) {
address _delegate = _delegates[i];
delegates.push(_delegate);
}

Propose _proposeScript = new Propose();
// We override the deployer to use an alternate delegate, because in this context,
// lonser.eth already has a live proposal
//_proposeScript.overrideProposerForTests(0xFFb032E27b70DfAD518753BAAa77040F64df9840);
//
// Pass in deployed factory owner
Deploy _deployScript = new Deploy();
_deployScript.setUp();
(v3FactoryOwner, uniStaker) = _deployScript.run();
uniswapProposalId = _proposeScript.run(address(v3FactoryOwner));
}
//--------------- HELPERS ---------------//

function _uniswapProposalStartBlock() internal view returns (uint256) {
(,,, uint256 _startBlock,,,,,,) = governor.proposals(uniswapProposalId);
return _startBlock;
}

function _uniswapProposalEndBlock() internal view returns (uint256) {
(,,,, uint256 _endBlock,,,,,) = governor.proposals(uniswapProposalId);
return _endBlock;
}

function _uniswapProposalEta() internal view returns (uint256) {
(,, uint256 _eta,,,,,,,) = governor.proposals(uniswapProposalId);
return _eta;
}

function _jumpToActiveUniswapProposal() internal {
vm.roll(_uniswapProposalStartBlock() + 1);
}

function _jumpToUniswapVoteComplete() internal {
vm.roll(_uniswapProposalEndBlock() + 1);
}

function _jumpPastProposalEta() internal {
vm.roll(block.number + 1); // move up one block so we're not in the same block as when queued
vm.warp(_uniswapProposalEta() + 1); // jump past the eta timestamp
}

function _delegatesVoteOnUniswapProposal(bool _support) internal {
for (uint256 _index = 0; _index < delegates.length; _index++) {
vm.prank(delegates[_index]);
governor.castVote(uniswapProposalId, 1);
}
}

function _passUniswapProposal() internal {
_jumpToActiveUniswapProposal();
_delegatesVoteOnUniswapProposal(true);
_jumpToUniswapVoteComplete();
}

function _defeatUniswapProposal() internal {
_jumpToActiveUniswapProposal();
_delegatesVoteOnUniswapProposal(false);
_jumpToUniswapVoteComplete();
}

function _passAndQueueUniswapProposal() internal {
_passUniswapProposal();
governor.queue(uniswapProposalId);
}

function _executeProposal() internal {
_jumpPastProposalEta();
governor.execute(uniswapProposalId);
}

function assertEq(IGovernor.ProposalState _actual, IGovernor.ProposalState _expected) internal {
assertEq(uint8(_actual), uint8(_expected));
}
}
Loading

0 comments on commit e5dd3d8

Please sign in to comment.