-
Notifications
You must be signed in to change notification settings - Fork 0
feat: implement sip 50 #69
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
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| pragma solidity ^0.8.25; | ||
|
|
||
| import { | ||
| SeamlessGovProposal, | ||
| SeamlessAddressBook | ||
| } from "../../helpers/SeamlessGovProposal.sol"; | ||
| import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; | ||
| import { ISeamEmissionManager } from | ||
| "@seamless-governance/interfaces/ISeamEmissionManager.sol"; | ||
|
|
||
| contract Proposal is SeamlessGovProposal { | ||
| uint256 public constant budgetAmount = 3_327_030_27 * 1e16; | ||
| 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_EMISSION_MANAGER_2, | ||
| abi.encodeWithSelector( | ||
| ISeamEmissionManager.claim.selector, | ||
| SeamlessAddressBook.TIMELOCK_SHORT | ||
| ) | ||
| ); | ||
|
|
||
| _addAction( | ||
| SeamlessAddressBook.SEAM, | ||
| abi.encodeWithSelector( | ||
| IERC20.transfer.selector, | ||
| SeamlessAddressBook.GUARDIAN_MULTISIG, | ||
| budgetAmount | ||
| ) | ||
| ); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| // 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(40721300); | ||
| 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); | ||
| uint256 emissionManagerBalanceBefore2 = | ||
| seam.balanceOf(SeamlessAddressBook.SEAM_EMISSION_MANAGER_2); | ||
|
|
||
| uint256 expectedEmissionClaimAmount2 = | ||
| _expectedEmissionManagerClaimAmount( | ||
| ISeamEmissionManager(SeamlessAddressBook.SEAM_EMISSION_MANAGER_2) | ||
| ); | ||
|
|
||
| _passProposalShortGov(proposal); | ||
|
|
||
| uint256 guardianBalanceAfter = | ||
| seam.balanceOf(SeamlessAddressBook.GUARDIAN_MULTISIG); | ||
| uint256 timelockBalanceAfter = | ||
| seam.balanceOf(SeamlessAddressBook.TIMELOCK_SHORT); | ||
| uint256 emissionManagerBalanceAfter2 = | ||
| seam.balanceOf(SeamlessAddressBook.SEAM_EMISSION_MANAGER_2); | ||
|
|
||
| assertEq( | ||
| guardianBalanceAfter, | ||
| guardianBalanceBefore + (3_327_030_27 * 1e16) | ||
| ); | ||
| assertEq( | ||
| timelockBalanceAfter, | ||
| timelockBalanceBefore + expectedEmissionClaimAmount2 | ||
| - (3_327_030_27 * 1e16) | ||
| ); | ||
| assertEq( | ||
| emissionManagerBalanceBefore2 - emissionManagerBalanceAfter2, | ||
| expectedEmissionClaimAmount2 | ||
| ); | ||
| } | ||
|
|
||
| function _expectedEmissionManagerClaimAmount( | ||
| ISeamEmissionManager emissionManager | ||
| ) internal returns (uint256) { | ||
| uint256 snapshotId = vm.snapshot(); | ||
|
|
||
| _passProposalShortGov(proposal); | ||
|
|
||
| uint64 currentTimestamp = uint64(block.timestamp); | ||
|
|
||
| require( | ||
| vm.revertToAndDelete(snapshotId), "Failed to revert to snapshot" | ||
| ); | ||
|
|
||
| uint256 emissionPerSecond = emissionManager.getEmissionPerSecond(); | ||
| uint64 lastClaimedTimestamp = emissionManager.getLastClaimedTimestamp(); | ||
|
|
||
| return (currentTimestamp - lastClaimedTimestamp) * emissionPerSecond; | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| # [SIP-50] 2026 SEAM Rewards Budget Proposal | ||
|
|
||
| ## Summary | ||
|
|
||
| This proposal seeks the DAO’s approval of Seamless Protocol’s 2026 annual budget plan, requesting authorization for a 5,341,998.60 SEAM budget, recognizing the 2,014,968.33 SEAM surplus carried over from 2025, and therefore allocating an additional 3,327,030.27 SEAM in new funding, while moving from quarterly budget approvals to a streamlined annual funding model designed to improve operational agility and reduce governance overhead. | ||
|
|
||
| ## Context and motivation | ||
|
|
||
| ### 2025 Results | ||
|
|
||
| In 2025, the team operated within the framework of a pre-approved annual budget (approved end of 2024) but received funding quarterly, with each tranche requiring a DAO vote. The DAO allocated 5,075,000 SEAM in 2025. The team spent 3,060,031.67 SEAM. The resulting surplus equal to 2,014,968.33 SEAM remains unspent and available for carry-over to 2026. | ||
|
|
||
|  | ||
|
|
||
| ### Major Milestones | ||
|
|
||
| - On April 14, 2025, Seamless Protocol fully sunset the legacy ILMs and the Platform. This deprecation significantly reduced maintenance overhead, freed resources, and completed the strategic transition away from the older architecture. | ||
|
|
||
| - With the sunset of ILMs/Platform, Seamless officially entered the platform-less era, centered on Morpho Vaults. This transition aligned the protocol with a more efficient, scalable, and modular architecture, and marked a major strategic shift in product direction. | ||
|
|
||
| - In 2025, Seamless successfully launched Leverage Tokens (LTs), first on Base and later expanded to Ethereum Mainnet. This product vertical became one of the core growth drivers for TVL, user adoption, and future fee generation. | ||
|
|
||
| Between the shutdown of legacy systems, improved operational focus, and efficient resource usage, the team achieved substantial savings. Net surplus retained for 2025: 2,014,968.33 SEAM. | ||
|
|
||
| While the quarterly tranche model provided oversight, it also introduced constraints, including funding delays of 1+ week per vote, limited ability to respond quickly to market opportunities, difficulty adjusting incentives, liquidity programs, or development schedules in real time. To improve agility and protocol competitiveness, the team recommends transitioning to annual planning. | ||
|
|
||
| ### 2026 Budget Framework | ||
|
|
||
| Total proposed 2026 budget: 5,341,998.60 SEAM | ||
|
|
||
| 2025 surplus carried over: 2,014,968.33 SEAM | ||
|
|
||
| Additional SEAM requested from DAO: 3,327,030.27 SEAM | ||
|
|
||
|  | ||
|
|
||
| The proposed budget supports two primary initiatives: | ||
|
|
||
| Scaling & expanding Leverage Tokens (LTs). The plan is to launch 4 new LTs per quarter. | ||
|
|
||
| Potential launch of Morpho Vaults on Ethereum Mainnet | ||
|
|
||
| Both initiatives directly support Seamless Protocol’s growth, revenue, and competitive positioning. | ||
|
|
||
| For 2026, we propose shifting to a single annual budget approval, enabling faster reaction to market changes, reduced governance overhead, more predictable planning and execution, greater strategic flexibility for the team and DAO. | ||
|
|
||
| Given the rapidly evolving nature of crypto and DeFi markets, the DAO acknowledges that the Seamless team may reallocate budgeted funds toward alternative strategic initiatives if these are deemed more beneficial, yield higher expected returns, or present materially stronger opportunities for the protocol and DAO. | ||
|
|
||
| This clause ensures that the protocol remains adaptive, competitive, and able to seize high-impact opportunities. | ||
|
|
||
| ### Resolution | ||
|
|
||
| The DAO is asked to vote FOR or AGAINST the following actions: | ||
|
|
||
| Approve the 2026 annual budget of 5,341,998.60 SEAM. | ||
|
|
||
| Approve carrying over the 2025 surplus of 2,014,968.33 SEAM into fiscal year 2026. | ||
|
|
||
| Authorize the issuance of 3,327,030.27 existing SEAM tokens from the DAO treasury for 2026 operations. | ||
|
|
||
| Grant operational flexibility allowing the team to redirect funds to alternative strategic opportunities if justified by market conditions. | ||
|
|
||
| ## Resources & References | ||
|
|
||
| - [Governance discussion](https://seamlessprotocol.discourse.group/t/gp-seamless-protocol-2026-budget-proposal/982) | ||
| - [Proposal Implementation](https://github.com/seamless-protocol/gov-proposals/tree/main/proposals/sip_50) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.