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

Proposal script #50

Merged
merged 37 commits into from
Feb 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
2765449
Initial propose script
alexkeating Feb 2, 2024
2175b08
Some Cleanup
alexkeating Feb 7, 2024
cfe2e67
Some more cleanup
alexkeating Feb 7, 2024
1e535e9
Some more cleanup
alexkeating Feb 7, 2024
603e7db
More cleanup
alexkeating Feb 7, 2024
38e635d
Format code
alexkeating Feb 7, 2024
21e7111
Remove unused imports
alexkeating Feb 7, 2024
ff0f803
Change propose script
alexkeating Feb 8, 2024
b0df199
Set protocol fee script
alexkeating Feb 8, 2024
82da127
Passing tests
alexkeating Feb 8, 2024
66fd437
Cleanup
alexkeating Feb 8, 2024
a57a3d5
Delete old script
alexkeating Feb 8, 2024
0e89ab6
More refinements
alexkeating Feb 8, 2024
497d504
Fix linting
alexkeating Feb 8, 2024
cde40c3
Changes based on feedback
alexkeating Feb 9, 2024
3dbf4cf
Update the script
alexkeating Feb 9, 2024
7d58ac2
Reorder deploy
alexkeating Feb 12, 2024
1ee7c37
Remove newline
alexkeating Feb 12, 2024
1bd25b4
Set private key for tests
alexkeating Feb 12, 2024
6da3c1e
Change proposer address
alexkeating Feb 13, 2024
19758b4
Only call once
alexkeating Feb 13, 2024
b150c9c
Add enum for vote type
alexkeating Feb 13, 2024
f49096d
Specify block for forking
alexkeating Feb 13, 2024
3289313
Make changes based on feedback
alexkeating Feb 14, 2024
86c3d0a
Format
alexkeating Feb 14, 2024
e64b40d
Some clean up
alexkeating Feb 14, 2024
2e7794b
Make changes based on feedback
alexkeating Feb 14, 2024
6be92eb
Fix linting
alexkeating Feb 14, 2024
d048d5b
Add some natspec
alexkeating Feb 14, 2024
9d7e284
Rename test helper
alexkeating Feb 14, 2024
b96a74a
Remove push
alexkeating Feb 14, 2024
9f7a789
Rename base protocol fees script
alexkeating Feb 14, 2024
0963d45
Rename concrete fees script
alexkeating Feb 14, 2024
94be298
Add light natspec and move constants
alexkeating Feb 14, 2024
bf75440
Fix formatting
alexkeating Feb 14, 2024
39f7f0f
Update natspec change broadcast
alexkeating Feb 14, 2024
fca11c9
Fix nits
alexkeating Feb 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
MAINNET_RPC_URL=
FOUNDRY_PROFILE=default
DEPLOYER_PRIVATE_KEY=
PROPOSER_ADDRESS=
1 change: 1 addition & 0 deletions script/DeployInput.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
pragma solidity 0.8.23;

contract DeployInput {
address constant UNISWAP_GOVERNOR = 0x408ED6354d4973f66138C91495F2f2FCbd8724C3;
address constant UNISWAP_GOVERNOR_TIMELOCK = 0x1a9C8182C09F50C8318d769245beA52c32BE35BC;
address constant UNISWAP_V3_FACTORY_ADDRESS = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
// TODO not finalized: currently WETH
Expand Down
52 changes: 52 additions & 0 deletions script/ProposeFactorySetOwner.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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";

contract ProposeFactorySetOwner is Script, DeployInput {
GovernorBravoDelegate constant GOVERNOR = GovernorBravoDelegate(UNISWAP_GOVERNOR);
// The default proposer is uf.eek.eth.
address _proposer =
vm.envOr("PROPOSER_ADDRESS", address(0x0459f41c5f09BF678D9C07331894dE31d8C22255));

/// @param _newV3FactoryOwner The new factory owner, i.e. the recently deployed `V3FactoryOwner`.
function proposeFactoryOwnerChangeOnGovernor(address _newV3FactoryOwner)
internal
returns (uint256 _proposalId)
{
address[] memory _targets = new address[](1);
uint256[] memory _values = new uint256[](1);
string[] memory _signatures = new string[](1);
bytes[] memory _calldatas = new bytes[](1);

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

return GOVERNOR.propose(
_targets, _values, _signatures, _calldatas, "Change Uniswap V3 factory owner"
);
}

/// @param _newV3FactoryOwner The new factory owner, i.e. the recently deployed `V3FactoryOwner`.
/// @dev After the UniStaker and V3FactoryOwner contracts are deployed, a delegate should run this
/// script to create a proposal to change the Uniswap v3 factory owner.
wildmolasses marked this conversation as resolved.
Show resolved Hide resolved
function run(address _newV3FactoryOwner) 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)
wildmolasses marked this conversation as resolved.
Show resolved Hide resolved
);
vm.rememberKey(_proposerKey);
alexkeating marked this conversation as resolved.
Show resolved Hide resolved

vm.startBroadcast(_proposer);
_proposalId = proposeFactoryOwnerChangeOnGovernor(_newV3FactoryOwner);
vm.stopBroadcast();
return _proposalId;
}
}
95 changes: 95 additions & 0 deletions script/ProposeProtocolFeesBase.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// 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";

/// @dev A new proposal script that updates a pool's fee settings should inherit this abstract
/// script and implement `getPoolFeeSettings`.
abstract contract ProposeProtocolFeesBase is Script, DeployInput {
GovernorBravoDelegate constant GOVERNOR = GovernorBravoDelegate(UNISWAP_GOVERNOR);
// The default proposer is uf.eek.eth.
address _proposer =
vm.envOr("PROPOSER_ADDRESS", address(0x0459f41c5f09BF678D9C07331894dE31d8C22255));

/// @dev The targets for the proposal which should be the `V3FactoryOwner`.
address[] public targets;
/// @dev The values to pass into the proposal which should all be 0.
uint256[] public values;
/// @dev The function signatures that will be called when a proposal is executed. All of the
/// signatures should be `setFeeProtocol(address,uint8,uint8)`.
string[] public signatures;
/// @dev The calldata for all of function calls in the proposal. These should match the
/// `PoolFeeSettings` defined in `getPoolFeeSettings`.
bytes[] public calldatas;

/// @dev A struct to represent all of the information needed to update a pool's fees. Such as the
/// target pool and the new fees for each token in the pool.
struct PoolFeeSettings {
address pool;
uint8 feeProtocol0;
uint8 feeProtocol1;
}

/// @dev A utility function that updates the targets, values, signatures, and calldatas for a
/// proposal that will only update protocol fees for a list of pools.
function pushFeeSettingToProposalCalldata(
address _v3FactoryOwner,
address _pool,
uint8 _feeProtocol0,
uint8 _feeProtocol1
) internal {
targets.push(_v3FactoryOwner);
values.push(0);
signatures.push("setFeeProtocol(address,uint8,uint8)");
calldatas.push(abi.encode(_pool, _feeProtocol0, _feeProtocol1));
}

/// @return A list of pool settings used to update protocol fees for each pool.
/// @dev A new `ProposeProtocolFees` script should extend this base script and only implement this
/// function to return a list of pools to be updated with their new settings. This function will
/// return the appropriate pool settings in the `run` method and add them to the proposal that
/// will be proposed.
function getPoolFeeSettings() internal pure virtual returns (PoolFeeSettings[] memory);

/// @dev Create a proposal on the Uniswap Governor with targets, values, signatures, and calldatas
/// defined in this contracts. These values should have been built using
/// `pushFeeSettingToProposalCalldata`.
function propose() internal returns (uint256 _proposalId) {
return GOVERNOR.propose(
targets,
values,
signatures,
calldatas,
"Change Uniswap V3 factory owner and set pool protocol fees"
);
}

/// @param _newV3FactoryOwner The new factory owner which should have be the recently deployed.
/// @dev This script sets protocol fees for whatever pools and fees are configured. This script
/// should only be run after `UniStaker` and the `V3FactoryOwner` are deployed, and after the
/// `V3FactoryOwner` becomes the owner of ther Uniswap v3 factory.
function run(address _newV3FactoryOwner) 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.envUint("PROPOSER_PRIVATE_KEY");
vm.rememberKey(_proposerKey);

PoolFeeSettings[] memory poolFeeSettings = getPoolFeeSettings();
for (uint256 i = 0; i < poolFeeSettings.length; i++) {
pushFeeSettingToProposalCalldata(
_newV3FactoryOwner,
poolFeeSettings[i].pool,
poolFeeSettings[i].feeProtocol0,
poolFeeSettings[i].feeProtocol1
);
}

vm.startBroadcast(_proposer);
_proposalId = propose();
vm.stopBroadcast();
return _proposalId;
}
}
26 changes: 26 additions & 0 deletions script/ProposeProtocolFeesBatch1.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// 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";
import {ProposeProtocolFeesBase} from "script/ProposeProtocolFeesBase.s.sol";

/// @dev This script will turn on protocol fees for the following pools: WBTC-WETH-3000,
/// DAI-WETH-300, and DAI-USDC-100.
contract ProposeProtocolFeesBatch1 is ProposeProtocolFeesBase {
// TODO Double check these are the right pools
address constant WBTC_WETH_3000_POOL = 0xCBCdF9626bC03E24f779434178A73a0B4bad62eD;
address constant DAI_WETH_3000_POOL = 0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8;
address constant DAI_USDC_100_POOL = 0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168;

/// @return A list of pool settings used to update protocol fees for each pool.
function getPoolFeeSettings() internal pure override returns (PoolFeeSettings[] memory) {
PoolFeeSettings[] memory poolFeeSettings = new PoolFeeSettings[](3);
poolFeeSettings[0] = PoolFeeSettings(WBTC_WETH_3000_POOL, 10, 10);
poolFeeSettings[1] = PoolFeeSettings(DAI_WETH_3000_POOL, 10, 10);
poolFeeSettings[2] = PoolFeeSettings(DAI_USDC_100_POOL, 10, 10);
return poolFeeSettings;
}
}
114 changes: 114 additions & 0 deletions script/interfaces/GovernorBravoInterfaces.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.23;

// This interface was created using cast interface. The contract can be found at
// https://etherscan.io/address/0x53a328f4086d7c0f1fa19e594c9b842125263026#code#F2#L182

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
43 changes: 41 additions & 2 deletions test/UniStaker.integration.t.sol
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity 0.8.23;

import {Vm, Test, console2} from "forge-std/Test.sol";
import {Test, console2} from "forge-std/Test.sol";
import {Deploy} from "script/Deploy.s.sol";
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 {IUniswapV3FactoryOwnerActions} from "src/interfaces/IUniswapV3FactoryOwnerActions.sol";
import {IUniswapPool} from "test/helpers/interfaces/IUniswapPool.sol";

contract DeployScriptTest is Test, DeployInput {
function setUp() public {
vm.createSelectFork(vm.rpcUrl("mainnet"));
vm.createSelectFork(vm.rpcUrl("mainnet"), 19_114_228);
}

function testFork_DeployStakingContracts() public {
Expand All @@ -30,3 +33,39 @@ contract DeployScriptTest is Test, DeployInput {
assertTrue(uniStaker.isRewardNotifier(address(v3FactoryOwner)));
}
}

contract Propose is ProposalTest {
apbendi marked this conversation as resolved.
Show resolved Hide resolved
function testFork_CorrectlyPassAndExecuteProposal() public {
IUniswapV3FactoryOwnerActions factory =
IUniswapV3FactoryOwnerActions(UNISWAP_V3_FACTORY_ADDRESS);

IUniswapPool wbtcWethPool = IUniswapPool(WBTC_WETH_3000_POOL);
(,,,,, uint8 oldWbtcWethFeeProtocol,) = wbtcWethPool.slot0();

IUniswapPool daiWethPool = IUniswapPool(DAI_WETH_3000_POOL);
(,,,,, uint8 oldDaiWethFeeProtocol,) = daiWethPool.slot0();

IUniswapPool daiUsdcPool = IUniswapPool(DAI_USDC_100_POOL);
(,,,,, uint8 oldDaiUsdcFeeProtocol,) = daiUsdcPool.slot0();

_passQueueAndExecuteProposals();

(,,,,, uint8 newWbtcWethFeeProtocol,) = wbtcWethPool.slot0();
(,,,,, uint8 newDaiWethFeeProtocol,) = daiWethPool.slot0();
(,,,,, uint8 newDaiUsdcFeeProtocol,) = daiUsdcPool.slot0();

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

assertEq(oldWbtcWethFeeProtocol, 0);
assertEq(oldDaiWethFeeProtocol, 0);
assertEq(oldDaiUsdcFeeProtocol, 0);

// The below assert is based off of the slot0.feeProtocol calculation which can be found at
// https://github.com/Uniswap/v3-core/blob/d8b1c635c275d2a9450bd6a78f3fa2484fef73eb/contracts/UniswapV3Pool.sol#L843.
//
// The calculation is feeProtocol0 + (feeProtocol1 << 4)
assertEq(newWbtcWethFeeProtocol, 10 + (10 << 4));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i appreciate now why we're hardcoding here, but can you leave a comment on why we're doing the addition?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I will add a comment

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

contract Constants {
address constant UNISWAP_GOVERNOR_ADDRESS = 0x408ED6354d4973f66138C91495F2f2FCbd8724C3;
address constant WBTC_WETH_3000_POOL = 0xCBCdF9626bC03E24f779434178A73a0B4bad62eD;
address constant DAI_WETH_3000_POOL = 0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8;
address constant DAI_USDC_100_POOL = 0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168;
}
Loading
Loading