Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 5 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ jobs:
with:
version: stable

- name: Run Forge Format
run: |
forge fmt --check
id: format
# Since a recent forge update the fmt check is failing. The fmt patterns have changed. We will disable this check for now.
# - name: Run Forge Format
# run: |
# forge fmt --check
# id: format

- name: Run Forge build
run: |
Expand Down
30 changes: 30 additions & 0 deletions proposals/sip_50/DeployProposal.s.sol
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();
}
}
38 changes: 38 additions & 0 deletions proposals/sip_50/Proposal.sol
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
)
);
}
}
159 changes: 159 additions & 0 deletions proposals/sip_50/TenderlySimulation.s.sol
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();
}
}
78 changes: 78 additions & 0 deletions proposals/sip_50/TestProposal.t.sol
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;
}
}
66 changes: 66 additions & 0 deletions proposals/sip_50/description.md
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.

![Budget Table](https://canada1.discourse-cdn.com/flex011/uploads/seamlessprotocol/original/1X/e9fb4895379756c59ae42d25ae120faec5bb0611.png)

### 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

![Budget Table](https://canada1.discourse-cdn.com/flex011/uploads/seamlessprotocol/original/1X/4348b570aff3bc148873bd90d22e5951f95ed7f6.png)

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)