diff --git a/proposals/sip_47/DeployProposal.s.sol b/proposals/sip_47/DeployProposal.s.sol new file mode 100644 index 0000000..37d5614 --- /dev/null +++ b/proposals/sip_47/DeployProposal.s.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import { Script, console } from "forge-std/Script.sol"; +import { Proposal } from "./Proposal.sol"; +import { IGovernor } from "@openzeppelin/contracts/governance/IGovernor.sol"; +import { SeamlessAddressBook } from "../../helpers/SeamlessAddressBook.sol"; + +contract DeployProposal is Script { + function setUp() public { } + + function run(string memory descriptionPath) public { + Proposal proposal = new Proposal(); + + // Change this to GOVERNOR_LONG if you want to make proposal on the long governor + IGovernor governance = IGovernor(SeamlessAddressBook.GOVERNOR_SHORT); + + string memory description = vm.readFile(descriptionPath); + + address proposerAddress = vm.envAddress("PROPOSER_ADDRESS"); + vm.startBroadcast(proposerAddress); + governance.propose( + proposal.getTargets(), + proposal.getValues(), + proposal.getCalldatas(), + description + ); + vm.stopBroadcast(); + } +} diff --git a/proposals/sip_47/Proposal.sol b/proposals/sip_47/Proposal.sol new file mode 100644 index 0000000..309e08b --- /dev/null +++ b/proposals/sip_47/Proposal.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.21; + +import { + SeamlessGovProposal, + SeamlessAddressBook +} from "../../helpers/SeamlessGovProposal.sol"; +import { ISeamEmissionManager } from + "@seamless-governance/interfaces/ISeamEmissionManager.sol"; +import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; + +contract Proposal is SeamlessGovProposal { + uint256 public constant budgetAmount = 2_250_000 * 1e18; + + constructor() { + _makeProposal(); + } + + /// @dev This contract is not deployed onchain, do not make transactions to other contracts + /// or deploy a contract. Only the view/pure functions of deployed contracts can be called. + function _makeProposal() internal virtual override { + _addAction( + SeamlessAddressBook.SEAM, + abi.encodeWithSelector( + IERC20.transfer.selector, + SeamlessAddressBook.GUARDIAN_MULTISIG, + budgetAmount + ) + ); + } +} diff --git a/proposals/sip_47/TenderlySimulation.s.sol b/proposals/sip_47/TenderlySimulation.s.sol new file mode 100644 index 0000000..39919d1 --- /dev/null +++ b/proposals/sip_47/TenderlySimulation.s.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.21; + +import { Script, console } from "forge-std/Script.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { IVotes } from "@openzeppelin/contracts/governance/utils/IVotes.sol"; +import { Proposal } from "./Proposal.sol"; +import { IGovernor } from "@openzeppelin/contracts/governance/IGovernor.sol"; +import { SeamlessAddressBook } from "../../helpers/SeamlessAddressBook.sol"; +import { IVotes } from "@openzeppelin/contracts/governance/utils/IVotes.sol"; + +contract TenderlySimulation is Script { + // Change this to GOVERNOR_LONG if the proposal is made on the long governor + IGovernor governance = IGovernor(SeamlessAddressBook.GOVERNOR_SHORT); + IVotes seam = IVotes(SeamlessAddressBook.SEAM); + + Proposal proposal = new Proposal(); + + address proposerAddress = 0x67b6dB42115d94Cc3FE27E92a3d12bB224041ac0; + uint256 proposerPk = + 0x82fe25cccae9752b856c8857de74671320277f92e737b2116a5d9739dec59a26; + + function _getProposalData(string memory descriptionPath) + internal + view + returns (uint256 proposalId, bytes32 descriptionHash) + { + string memory description = vm.readFile(descriptionPath); + descriptionHash = keccak256(bytes(description)); + + proposalId = governance.hashProposal( + proposal.getTargets(), + proposal.getValues(), + proposal.getCalldatas(), + descriptionHash + ); + } + + function setupProposer() public { + console.log("Setting up proposer"); + + _fundETH(); + _fundSEAM(); + + moveOneBlockForwardOneSecond(); + } + + function delegateToProposer() public { + vm.startBroadcast(proposerPk); + seam.delegate(proposerAddress); + vm.stopBroadcast(); + } + + function moveOneBlockForwardOneSecond() public { + vm.rpc("evm_increaseTime", "[\"0x1\"]"); + } + + function _fundSEAM() public { + string memory params = string.concat( + "[\"", + Strings.toHexString(address(seam)), + "\",[\"", + Strings.toHexString(proposerAddress), + "\"],\"0x13DA329B6336471800000\"]" // 1.5M SEAM which is quorum + ); + vm.rpc("tenderly_setErc20Balance", params); + } + + function _fundETH() public { + string memory params = string.concat( + "[[\"", + Strings.toHexString(proposerAddress), + "\"],\"0xDE0B6B3A7640000\"]" // 1 ETH + ); + vm.rpc("tenderly_setBalance", params); + } + + function createProposal(string memory descriptionPath) public { + string memory description = vm.readFile(descriptionPath); + + vm.startBroadcast(proposerPk); + governance.propose( + proposal.getTargets(), + proposal.getValues(), + proposal.getCalldatas(), + description + ); + vm.stopBroadcast(); + } + + function increaseTimeVotingDelay() public { + console.log("Increasing voting delay"); + string memory params = string.concat( + "[", + Strings.toString(block.timestamp + governance.votingDelay() + 1), + "]" + ); + vm.rpc("evm_setNextBlockTimestamp", params); + vm.rpc("evm_mine", "[]"); + } + + function castVote(string memory descriptionPath) public { + console.log("Casting vote"); + (uint256 proposalId,) = _getProposalData(descriptionPath); + + vm.startBroadcast(proposerPk); + governance.castVote(proposalId, 1); + vm.stopBroadcast(); + } + + function increaseTimeVotingPeriod() public { + console.log("Increasing voting period"); + string memory params = string.concat( + "[", + Strings.toString(block.timestamp + governance.votingPeriod() + 1), + "]" + ); + vm.rpc("evm_setNextBlockTimestamp", params); + vm.rpc("evm_mine", "[]"); + } + + function queueProposal(string memory descriptionPath) public { + console.log("Queueing proposal"); + (, bytes32 descriptionHash) = _getProposalData(descriptionPath); + + vm.startBroadcast(proposerPk); + governance.queue( + proposal.getTargets(), + proposal.getValues(), + proposal.getCalldatas(), + descriptionHash + ); + vm.stopBroadcast(); + } + + function setTimeToProposalEta(string memory descriptionPath) public { + console.log("Setting time to proposal eta"); + (uint256 proposalId,) = _getProposalData(descriptionPath); + string memory params = string.concat( + "[", Strings.toString(governance.proposalEta(proposalId) + 1), "]" + ); + vm.rpc("evm_setNextBlockTimestamp", params); + vm.rpc("evm_mine", "[]"); + } + + function executeProposal(string memory descriptionPath) public { + console.log("Executing proposal"); + (, bytes32 descriptionHash) = _getProposalData(descriptionPath); + + vm.startBroadcast(proposerPk); + governance.execute( + proposal.getTargets(), + proposal.getValues(), + proposal.getCalldatas(), + descriptionHash + ); + vm.stopBroadcast(); + } +} diff --git a/proposals/sip_47/TestProposal.t.sol b/proposals/sip_47/TestProposal.t.sol new file mode 100644 index 0000000..cb7d6db --- /dev/null +++ b/proposals/sip_47/TestProposal.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import { GovTestHelper } from "../../helpers/GovTestHelper.sol"; +import { Proposal } from "./Proposal.sol"; +import { SeamlessAddressBook } from "../../helpers/SeamlessGovProposal.sol"; +import { ISeamEmissionManager } from + "@seamless-governance/interfaces/ISeamEmissionManager.sol"; +import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; + +contract TestProposal is GovTestHelper { + Proposal public proposal; + + function setUp() public { + vm.rollFork(31739600); + proposal = new Proposal(); + } + + function test_seamClaimedAndTransferedToGuardian_afterPassingProposal() + public + { + IERC20 seam = IERC20(SeamlessAddressBook.SEAM); + + uint256 guardianBalanceBefore = + seam.balanceOf(SeamlessAddressBook.GUARDIAN_MULTISIG); + uint256 timelockBalanceBefore = + seam.balanceOf(SeamlessAddressBook.TIMELOCK_SHORT); + + _passProposalShortGov(proposal); + + uint256 guardianBalanceAfter = + seam.balanceOf(SeamlessAddressBook.GUARDIAN_MULTISIG); + uint256 timelockBalanceAfter = + seam.balanceOf(SeamlessAddressBook.TIMELOCK_SHORT); + + assertEq( + guardianBalanceAfter, guardianBalanceBefore + (2_250_000 * 1e18) + ); + assertEq( + timelockBalanceAfter, timelockBalanceBefore - (2_250_000 * 1e18) + ); + } +} diff --git a/proposals/sip_47/description.md b/proposals/sip_47/description.md new file mode 100644 index 0000000..5ef2ab3 --- /dev/null +++ b/proposals/sip_47/description.md @@ -0,0 +1,44 @@ +# [SIP-47] Protocol Rewards Refresh Q3 2025 + +## Summary +This proposal seeks to extend platform reward emissions beyond their current end date of June 30, 2025 through Q3 2025 (July 1 - September 30). The proposal maintains our quarterly cadence while preserving monthly flexibility for adjustments. The total budget of 2.25 million SEAM will be sourced from the short governor timelock and master emissions vesting contracts. + +The proposed quarterly reward allocations are: +1. Seamless Vaults on Morpho (750,000 SEAM) + - Incentivize growth of Seamless Vaults on Morpho + - Maintain competitive yields for lenders and borrowers + - Ensure enough liquidity for Leverage Tokens strategies + +2. Leverage Tokens (750,000 SEAM) + - Incentivize Leverage Tokens strategies + +3. Safety Module (375,000 SEAM) + - Encourage protocol security through staking incentives + - Strengthen alignment between token holders and protocol safety + +4. Strategic Growth Reserve (375,000 SEAM) + - Flexible budget for emerging opportunities + + +## Context and motivation +This proposal aligns with the broader token emissions strategy outlined in the [\[GP-7\] Expanding Protocol Reward Categories - Increasing Token Emission Budget for Seamless!](https://snapshot.box/#/s:seamlessprotocol.eth/proposal/0x113a9f75eb623fcccdc2b4400ecb0e125b95c0085bf0c8b44c5f136b27c29c2d) and [\[GP-10\] In preparation for the upcoming Leverage Tokens launch](https://snapshot.box/#/s:seamlessprotocol.eth/proposal/0xb83326a5d7a6a2e5fc282208d3b4e87cfa12b29cf5b948362b9959803733d986). As such, this current proposal will move straight to onchain governance. + +The key drivers for continuing emissions are: +1. Market Timing: The current macro environment and crypto market sentiment create an opportune moment to accelerate growth and capture market share through strategic token emissions. +2. Product Evolution: Our expanding product suite - including new markets, recent Leverage Tokens launch, and innovative offerings - requires competitive incentives to drive initial adoption and sustainable usage. +3. Strategic Token Utilization: With approximately 44,5% of token supply [reserved for rewards](https://docs.seamlessprotocol.com/governance/seam-tokenomics), front-loading emissions during this growth phase allows us to: + - Maximize market presence and TVL capture + - Establish Seamless as the premier lending platform on Base + - Сapture a significant market share of Leverage Strategies on Base + - Build toward sustainable fee generation and token value accrual + - Create a framework for gradual emissions reduction over time + +The reward schedules will be implemented based on recommendations from ecosystem partners and contributors. While this represents a maximum quarterly budget, actual emissions may be lower based on market conditions and performance metrics. + +## Resources & References + +- [\[GP-7\] Expanding Protocol Reward Categories - Increasing Token Emission Budget for Seamless!](https://snapshot.box/#/s:seamlessprotocol.eth/proposal/0x113a9f75eb623fcccdc2b4400ecb0e125b95c0085bf0c8b44c5f136b27c29c2d) +- [\[GP-10\] In preparation for the upcoming Leverage Tokens launch](https://snapshot.box/#/s:seamlessprotocol.eth/proposal/0xb83326a5d7a6a2e5fc282208d3b4e87cfa12b29cf5b948362b9959803733d986) +- [Proposal Implementation](https://github.com/seamless-protocol/gov-proposals/tree/main/proposals/sip_47) +- Built using [Seamless Governance Proposals tools](https://github.com/seamless-protocol/gov-proposals) +