diff --git a/.gitignore b/.gitignore index 85198aaa..9d91b783 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,7 @@ cache/ out/ # Ignores development broadcast logs -!/broadcast -/broadcast/*/31337/ -/broadcast/**/dry-run/ +broadcast/ # Docs docs/ diff --git a/foundry.toml b/foundry.toml index 2d836dcd..5de5c284 100644 --- a/foundry.toml +++ b/foundry.toml @@ -29,4 +29,8 @@ number_underscore = "preserve" [profile.default.rpc_endpoints] mainnet = "${MAINNET_RPC_URL}" -tenderly = "${TENDERLY_FORK_URL}" \ No newline at end of file +sepolia = "${SEPOLIA_RPC_URL}" +tenderly = "${TENDERLY_FORK_URL}" + +# forge script --chain mainnet script/DeployLocal.s.sol:DeployLocalScript --rpc-url https://eth-mainnet.alchemyapi.io/v2/B4w2ueJLjihQPuf868vthxg7FvfND5i5 +# forge script --chain sepolia script/DeploySepolia.s.sol:DeploySepoliaScript --rpc-url https://eth-sepolia.g.alchemy.com/v2/6U2Z1L4BEW2VkZeHS5NQAWrvciif1DDI diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index df9ee8b0..00000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; - -contract CounterScript is Script { - function setUp() public {} - - function run() public { - vm.broadcast(); - } -} diff --git a/script/DeploySepolia.s.sol b/script/DeploySepolia.s.sol new file mode 100644 index 00000000..9b9443df --- /dev/null +++ b/script/DeploySepolia.s.sol @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; +import {MockERC20} from "forge-std/mocks/MockERC20.sol"; + +import {PoolManager, Deployers, Hooks} from "v4-core/test/utils/Deployers.sol"; +import {ICurveStableswapFactoryNG} from "../src/interfaces/ICurveStableswapFactoryNG.sol"; +import {ICurveStableswapNG} from "../src/interfaces/ICurveStableswapNG.sol"; +import {ILiquidityGauge} from "./../src/interfaces/ILiquidityGauge.sol"; + +import {IGovernance} from "../src/interfaces/IGovernance.sol"; + +import {Governance} from "../src/Governance.sol"; +import {UniV4Donations} from "../src/UniV4Donations.sol"; +import {CurveV2GaugeRewards} from "../src/CurveV2GaugeRewards.sol"; +import {Hooks} from "../src/utils/BaseHook.sol"; + +import {MockStakingV1} from "../test/mocks/MockStakingV1.sol"; +import {HookMiner} from "./utils/HookMiner.sol"; + +contract DeploySepoliaScript is Script, Deployers { + // Environment Constants + MockERC20 private lqty; + MockERC20 private bold; + address private stakingV1; + MockERC20 private usdc; + + PoolManager private constant poolManager = PoolManager(0xE8E23e97Fa135823143d6b9Cba9c699040D51F70); + ICurveStableswapFactoryNG private constant curveFactory = + ICurveStableswapFactoryNG(address(0xfb37b8D939FFa77114005e61CFc2e543d6F49A81)); + + // Governance Constants + uint128 private constant REGISTRATION_FEE = 100e18; + uint128 private constant REGISTRATION_THRESHOLD_FACTOR = 0.001e18; + uint128 private constant UNREGISTRATION_THRESHOLD_FACTOR = 3e18; + uint16 private constant REGISTRATION_WARM_UP_PERIOD = 4; + uint16 private constant UNREGISTRATION_AFTER_EPOCHS = 4; + uint128 private constant VOTING_THRESHOLD_FACTOR = 0.03e18; + uint88 private constant MIN_CLAIM = 500e18; + uint88 private constant MIN_ACCRUAL = 1000e18; + uint32 private constant EPOCH_DURATION = 604800; + uint32 private constant EPOCH_VOTING_CUTOFF = 518400; + + // UniV4Donations Constants + uint256 private immutable VESTING_EPOCH_START = block.timestamp; + uint256 private constant VESTING_EPOCH_DURATION = 7 days; + uint24 private constant FEE = 400; + int24 constant MAX_TICK_SPACING = 32767; + + // CurveV2GaugeRewards Constants + uint256 private constant DURATION = 7 days; + + // Contracts + Governance private governance; + address[] private initialInitiatives; + UniV4Donations private uniV4Donations; + CurveV2GaugeRewards private curveV2GaugeRewards; + ICurveStableswapNG private curvePool; + ILiquidityGauge private gauge; + + // Deployer + address private deployer; + uint256 private privateKey; + uint256 private nonce; + + function setUp() public { + privateKey = vm.envUint("PRIVATE_KEY"); + deployer = vm.createWallet(privateKey).addr; + nonce = vm.getNonce(deployer); + } + + function deployEnvironment() private { + lqty = deployMockERC20("Liquity", "LQTY", 18); + bold = deployMockERC20("Bold", "BOLD", 18); + usdc = deployMockERC20("USD Coin", "USDC", 6); + stakingV1 = address(new MockStakingV1(address(lqty))); + } + + function deployGovernance() private { + governance = new Governance( + address(lqty), + address(bold), + stakingV1, + address(bold), + IGovernance.Configuration({ + registrationFee: REGISTRATION_FEE, + registrationThresholdFactor: REGISTRATION_THRESHOLD_FACTOR, + unregistrationThresholdFactor: UNREGISTRATION_THRESHOLD_FACTOR, + registrationWarmUpPeriod: REGISTRATION_WARM_UP_PERIOD, + unregistrationAfterEpochs: UNREGISTRATION_AFTER_EPOCHS, + votingThresholdFactor: VOTING_THRESHOLD_FACTOR, + minClaim: MIN_CLAIM, + minAccrual: MIN_ACCRUAL, + epochStart: uint32(block.timestamp), + epochDuration: EPOCH_DURATION, + epochVotingCutoff: EPOCH_VOTING_CUTOFF + }), + initialInitiatives + ); + assert(governance == uniV4Donations.governance()); + } + + function deployUniV4Donations(uint256 _nonce) private { + address gov = address(vm.computeCreateAddress(deployer, _nonce)); + uint160 flags = uint160(Hooks.AFTER_INITIALIZE_FLAG | Hooks.AFTER_ADD_LIQUIDITY_FLAG); + + (, bytes32 salt) = HookMiner.find( + 0x4e59b44847b379578588920cA78FbF26c0B4956C, + // address(this), + flags, + type(UniV4Donations).creationCode, + abi.encode( + gov, + address(bold), + address(lqty), + block.timestamp, + EPOCH_DURATION, + address(poolManager), + address(usdc), + FEE, + MAX_TICK_SPACING + ) + ); + + uniV4Donations = new UniV4Donations{salt: salt}( + gov, + address(bold), + address(lqty), + block.timestamp, + EPOCH_DURATION, + address(poolManager), + address(usdc), + FEE, + MAX_TICK_SPACING + ); + + initialInitiatives.push(address(uniV4Donations)); + } + + function deployCurveV2GaugeRewards(uint256 _nonce) private { + address[] memory _coins = new address[](2); + _coins[0] = address(bold); + _coins[1] = address(usdc); + uint8[] memory _asset_types = new uint8[](2); + _asset_types[0] = 0; + _asset_types[1] = 0; + bytes4[] memory _method_ids = new bytes4[](2); + _method_ids[0] = 0x0; + _method_ids[1] = 0x0; + address[] memory _oracles = new address[](2); + _oracles[0] = address(0x0); + _oracles[1] = address(0x0); + + curvePool = ICurveStableswapNG( + curveFactory.deploy_plain_pool( + "BOLD-USDC", "BOLDUSDC", _coins, 200, 1000000, 50000000000, 866, 0, _asset_types, _method_ids, _oracles + ) + ); + + gauge = ILiquidityGauge(curveFactory.deploy_gauge(address(curvePool))); + + curveV2GaugeRewards = new CurveV2GaugeRewards( + address(vm.computeCreateAddress(address(this), _nonce)), + address(bold), + address(lqty), + address(gauge), + DURATION + ); + + initialInitiatives.push(address(curveV2GaugeRewards)); + } + + function run() public { + vm.startBroadcast(privateKey); + deployEnvironment(); + deployUniV4Donations(nonce + 8); + deployGovernance(); + vm.stopBroadcast(); + } +} diff --git a/script/utils/HookMiner.sol b/script/utils/HookMiner.sol new file mode 100644 index 00000000..d6b30c40 --- /dev/null +++ b/script/utils/HookMiner.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +pragma solidity ^0.8.21; + +/// @title HookMiner - a library for mining hook addresses +/// @dev This library is intended for `forge test` environments. There may be gotchas when using salts in `forge script` or `forge create` +library HookMiner { + // mask to slice out the bottom 14 bit of the address + uint160 constant FLAG_MASK = 0x3FFF; + + // Maximum number of iterations to find a salt, avoid infinite loops + uint256 constant MAX_LOOP = 100_000; + + /// @notice Find a salt that produces a hook address with the desired `flags` + /// @param deployer The address that will deploy the hook. In `forge test`, this will be the test contract `address(this)` or the pranking address + /// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy) + /// @param flags The desired flags for the hook address + /// @param creationCode The creation code of a hook contract. Example: `type(Counter).creationCode` + /// @param constructorArgs The encoded constructor arguments of a hook contract. Example: `abi.encode(address(manager))` + /// @return hookAddress salt and corresponding address that was found. The salt can be used in `new Hook{salt: salt}()` + function find(address deployer, uint160 flags, bytes memory creationCode, bytes memory constructorArgs) + internal + view + returns (address, bytes32) + { + address hookAddress; + bytes memory creationCodeWithArgs = abi.encodePacked(creationCode, constructorArgs); + + uint256 salt; + for (salt; salt < MAX_LOOP; salt++) { + hookAddress = computeAddress(deployer, salt, creationCodeWithArgs); + if (uint160(hookAddress) & FLAG_MASK == flags && hookAddress.code.length == 0) { + return (hookAddress, bytes32(salt)); + } + } + revert("HookMiner: could not find salt"); + } + + /// @notice Precompute a contract address deployed via CREATE2 + /// @param deployer The address that will deploy the hook. In `forge test`, this will be the test contract `address(this)` or the pranking address + /// In `forge script`, this should be `0x4e59b44847b379578588920cA78FbF26c0B4956C` (CREATE2 Deployer Proxy) + /// @param salt The salt used to deploy the hook + /// @param creationCode The creation code of a hook contract + function computeAddress(address deployer, uint256 salt, bytes memory creationCode) + internal + pure + returns (address hookAddress) + { + return address( + uint160(uint256(keccak256(abi.encodePacked(bytes1(0xFF), deployer, salt, keccak256(creationCode))))) + ); + } +} diff --git a/test/Governance.t.sol b/test/Governance.t.sol index 713d946d..eb2a5554 100644 --- a/test/Governance.t.sol +++ b/test/Governance.t.sol @@ -923,7 +923,6 @@ contract GovernanceTest is Test { address userProxy = governance.deployUserProxy(); vm.store(address(lqty), keccak256(abi.encode(user, 0)), bytes32(abi.encode(uint256(_deltaLQTYVotes)))); - console.log(lqty.balanceOf(user)); lqty.approve(address(userProxy), _deltaLQTYVotes); governance.depositLQTY(_deltaLQTYVotes); @@ -948,7 +947,6 @@ contract GovernanceTest is Test { address userProxy = governance.deployUserProxy(); vm.store(address(lqty), keccak256(abi.encode(user, 0)), bytes32(abi.encode(uint256(_deltaLQTYVetos)))); - console.log(lqty.balanceOf(user)); lqty.approve(address(userProxy), _deltaLQTYVetos); governance.depositLQTY(_deltaLQTYVetos); diff --git a/test/UniV4Donations.t.sol b/test/UniV4Donations.t.sol index 71f63651..51fe21a5 100644 --- a/test/UniV4Donations.t.sol +++ b/test/UniV4Donations.t.sol @@ -5,7 +5,7 @@ import {Test} from "forge-std/Test.sol"; import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; -import {IPoolManager, PoolManager, Deployers, TickMath, Hooks} from "v4-core/test/utils/Deployers.sol"; +import {IPoolManager, PoolManager, Deployers, TickMath, Hooks, IHooks} from "v4-core/test/utils/Deployers.sol"; import {PoolModifyLiquidityTest} from "v4-core/src/test/PoolModifyLiquidityTest.sol"; import {IGovernance} from "../src/interfaces/IGovernance.sol";