diff --git a/helpers/SeamlessAddressBook.sol b/helpers/SeamlessAddressBook.sol index 73eb4e7..4335d9e 100644 --- a/helpers/SeamlessAddressBook.sol +++ b/helpers/SeamlessAddressBook.sol @@ -81,4 +81,7 @@ library SeamlessAddressBook { 0x003D47ddDdb070822B35ae5cc4F0066Cf9E89753; address constant BRETT_TRANSFER_STRATEGY = 0xD90EaC90f5f067283954b96BBc3d28E34ebE55Bb; + + address constant BASE_LEVERAGE_MANAGER_PROXY = 0x38Ba21C6Bf31dF1b1798FCEd07B4e9b07C5ec3a8; + address constant BASE_LEVERAGE_TOKEN_FACTORY_PROXY = 0xE0b2e40EDeb53B96C923381509a25a615c1Abe57; } \ No newline at end of file diff --git a/proposals/sip_36_listing_weETH/TestProposal.t.sol b/proposals/sip_36_listing_weETH/TestProposal.t.sol deleted file mode 100644 index 8b13789..0000000 --- a/proposals/sip_36_listing_weETH/TestProposal.t.sol +++ /dev/null @@ -1 +0,0 @@ - diff --git a/proposals/sip_49/DeployProposal.s.sol b/proposals/sip_49/DeployProposal.s.sol new file mode 100644 index 0000000..37d5614 --- /dev/null +++ b/proposals/sip_49/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_49/Proposal.sol b/proposals/sip_49/Proposal.sol new file mode 100644 index 0000000..5db9b9b --- /dev/null +++ b/proposals/sip_49/Proposal.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import { + SeamlessGovProposal, + SeamlessAddressBook +} from "../../helpers/SeamlessGovProposal.sol"; +import { UpgradeableBeacon } from + "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; +import { UUPSUpgradeable } from + "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { IAccessControl } from + "@openzeppelin/contracts/access/IAccessControl.sol"; + +contract Proposal is SeamlessGovProposal { + address constant NEW_BASE_LEVERAGE_TOKEN_IMPLEMENTATION = + 0x603Da735780e6bC7D04f3FB85C26dccCd4Ff0a82; + address constant NEW_BASE_LEVERAGE_MANAGER_IMPLEMENTATION = + 0xfE9101349354E278970489F935a54905DE2E1856; + + // Access control role required for upgrading + bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); + + 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 { + // First, grant the UPGRADER_ROLE to the timelock so it can perform the upgrade + _addAction( + SeamlessAddressBook.BASE_LEVERAGE_MANAGER_PROXY, + abi.encodeWithSelector( + IAccessControl.grantRole.selector, + UPGRADER_ROLE, + SeamlessAddressBook.TIMELOCK_SHORT + ) + ); + + // Upgrade the Base Leverage Token implementation (beacon proxy) + _addAction( + SeamlessAddressBook.BASE_LEVERAGE_TOKEN_FACTORY_PROXY, + abi.encodeWithSelector( + UpgradeableBeacon.upgradeTo.selector, + NEW_BASE_LEVERAGE_TOKEN_IMPLEMENTATION + ) + ); + + // Upgrade the Base Leverage Manager implementation (UUPS proxy) + _addAction( + SeamlessAddressBook.BASE_LEVERAGE_MANAGER_PROXY, + abi.encodeWithSelector( + UUPSUpgradeable.upgradeToAndCall.selector, + NEW_BASE_LEVERAGE_MANAGER_IMPLEMENTATION, + "" + ) + ); + + // Note: UPGRADER_ROLE is NOT revoked after the upgrade + // The timelock will retain this role for future upgrades + } +} diff --git a/proposals/sip_49/TenderlySimulation.s.sol b/proposals/sip_49/TenderlySimulation.s.sol new file mode 100644 index 0000000..39919d1 --- /dev/null +++ b/proposals/sip_49/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_49/TestProposal.t.sol b/proposals/sip_49/TestProposal.t.sol new file mode 100644 index 0000000..dffadb2 --- /dev/null +++ b/proposals/sip_49/TestProposal.t.sol @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.25; + +import { GovTestHelper } from "../../helpers/GovTestHelper.sol"; +import { Proposal } from "./Proposal.sol"; +import { SeamlessAddressBook } from "../../helpers/SeamlessAddressBook.sol"; +import { UpgradeableBeacon } from + "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; +import { ERC1967Utils } from + "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol"; +import { IAccessControl } from + "@openzeppelin/contracts/access/IAccessControl.sol"; + +contract TestProposal is GovTestHelper { + // ERC1967 implementation slot for UUPS proxy + bytes32 constant ERC1967_IMPLEMENTATION_SLOT = + 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + Proposal public proposal; + + // Expected new implementation addresses from the proposal + address constant EXPECTED_NEW_BASE_LEVERAGE_TOKEN_IMPLEMENTATION = + 0x603Da735780e6bC7D04f3FB85C26dccCd4Ff0a82; + address constant EXPECTED_NEW_BASE_LEVERAGE_MANAGER_IMPLEMENTATION = + 0xfE9101349354E278970489F935a54905DE2E1856; + + function setUp() public { + vm.rollFork(36538639); + proposal = new Proposal(); + } + + function test_baseLeverageTokenImplementationIsUpgraded_afterPassingProposal( + ) public { + // Get the beacon proxy instance + UpgradeableBeacon beacon = UpgradeableBeacon( + SeamlessAddressBook.BASE_LEVERAGE_TOKEN_FACTORY_PROXY + ); + + // Get current implementation from the beacon + address implementationBefore = beacon.implementation(); + + // Verify it's not already the new implementation + assertNotEq( + implementationBefore, + EXPECTED_NEW_BASE_LEVERAGE_TOKEN_IMPLEMENTATION, + "Base Leverage Token implementation should not be upgraded yet" + ); + + // Pass the proposal + _passProposalShortGov(proposal); + + // Get the implementation after the proposal + address implementationAfter = beacon.implementation(); + + // Verify the implementation has been upgraded to the expected address + assertEq( + implementationAfter, + EXPECTED_NEW_BASE_LEVERAGE_TOKEN_IMPLEMENTATION, + "Base Leverage Token implementation should be upgraded to the new implementation" + ); + } + + function test_baseLeverageManagerImplementationIsUpgraded_afterPassingProposal( + ) public { + // Get current implementation from the UUPS proxy storage slot + address implementationBefore = address( + uint160( + uint256( + vm.load( + SeamlessAddressBook.BASE_LEVERAGE_MANAGER_PROXY, + ERC1967_IMPLEMENTATION_SLOT + ) + ) + ) + ); + + // Verify it's not already the new implementation + assertNotEq( + implementationBefore, + EXPECTED_NEW_BASE_LEVERAGE_MANAGER_IMPLEMENTATION, + "Base Leverage Manager implementation should not be upgraded yet" + ); + + // Pass the proposal + _passProposalShortGov(proposal); + + // Get the implementation after the proposal + address implementationAfter = address( + uint160( + uint256( + vm.load( + SeamlessAddressBook.BASE_LEVERAGE_MANAGER_PROXY, + ERC1967_IMPLEMENTATION_SLOT + ) + ) + ) + ); + + // Verify the implementation has been upgraded to the expected address + assertEq( + implementationAfter, + EXPECTED_NEW_BASE_LEVERAGE_MANAGER_IMPLEMENTATION, + "Base Leverage Manager implementation should be upgraded to the new implementation" + ); + } + + function test_bothImplementationsAreUpgraded_afterPassingProposal() + public + { + // Get beacon for Base Leverage Token + UpgradeableBeacon beacon = UpgradeableBeacon( + SeamlessAddressBook.BASE_LEVERAGE_TOKEN_FACTORY_PROXY + ); + + // Store both implementations before the proposal + address tokenImplementationBefore = beacon.implementation(); + address managerImplementationBefore = address( + uint160( + uint256( + vm.load( + SeamlessAddressBook.BASE_LEVERAGE_MANAGER_PROXY, + ERC1967_IMPLEMENTATION_SLOT + ) + ) + ) + ); + + // Verify neither implementation is already upgraded + assertNotEq( + tokenImplementationBefore, + EXPECTED_NEW_BASE_LEVERAGE_TOKEN_IMPLEMENTATION + ); + assertNotEq( + managerImplementationBefore, + EXPECTED_NEW_BASE_LEVERAGE_MANAGER_IMPLEMENTATION + ); + + // Pass the proposal + _passProposalShortGov(proposal); + + // Get both implementations after the proposal + address tokenImplementationAfter = beacon.implementation(); + address managerImplementationAfter = address( + uint160( + uint256( + vm.load( + SeamlessAddressBook.BASE_LEVERAGE_MANAGER_PROXY, + ERC1967_IMPLEMENTATION_SLOT + ) + ) + ) + ); + + // Verify both implementations have been upgraded correctly + assertEq( + tokenImplementationAfter, + EXPECTED_NEW_BASE_LEVERAGE_TOKEN_IMPLEMENTATION, + "Base Leverage Token implementation should be upgraded" + ); + + assertEq( + managerImplementationAfter, + EXPECTED_NEW_BASE_LEVERAGE_MANAGER_IMPLEMENTATION, + "Base Leverage Manager implementation should be upgraded" + ); + } + + function test_proposalActionsMatchExpected() public view { + // The proposal now needs to read the UPGRADER_ROLE from itself + bytes32 upgraderRole = proposal.UPGRADER_ROLE(); + + // Verify the proposal has exactly 3 actions (no revoke action) + assertEq( + proposal.getTargets().length, + 3, + "Proposal should have exactly 3 actions" + ); + + // Action 1: Grant UPGRADER_ROLE to timelock + assertEq( + proposal.getTargets()[0], + SeamlessAddressBook.BASE_LEVERAGE_MANAGER_PROXY, + "First target should be Base Leverage Manager Proxy (grant role)" + ); + bytes memory expectedCalldata1 = abi.encodeWithSelector( + bytes4(keccak256("grantRole(bytes32,address)")), + upgraderRole, + SeamlessAddressBook.TIMELOCK_SHORT + ); + assertEq( + proposal.getCalldatas()[0], + expectedCalldata1, + "First action should grant UPGRADER_ROLE to timelock" + ); + + // Action 2: Upgrade Base Leverage Token Factory + assertEq( + proposal.getTargets()[1], + SeamlessAddressBook.BASE_LEVERAGE_TOKEN_FACTORY_PROXY, + "Second target should be Base Leverage Token Factory Proxy" + ); + bytes memory expectedCalldata2 = abi.encodeWithSelector( + UpgradeableBeacon.upgradeTo.selector, + EXPECTED_NEW_BASE_LEVERAGE_TOKEN_IMPLEMENTATION + ); + assertEq( + proposal.getCalldatas()[1], + expectedCalldata2, + "Second action calldata should match expected upgradeTo call" + ); + + // Action 3: Upgrade Base Leverage Manager + assertEq( + proposal.getTargets()[2], + SeamlessAddressBook.BASE_LEVERAGE_MANAGER_PROXY, + "Third target should be Base Leverage Manager Proxy (upgrade)" + ); + bytes memory expectedCalldata3 = abi.encodeWithSelector( + bytes4(keccak256("upgradeToAndCall(address,bytes)")), + EXPECTED_NEW_BASE_LEVERAGE_MANAGER_IMPLEMENTATION, + "" + ); + assertEq( + proposal.getCalldatas()[2], + expectedCalldata3, + "Third action calldata should match expected upgradeToAndCall call" + ); + + // Note: The timelock will retain this role for future upgrades + } + + function test_accessControlIsHandledCorrectly() public { + bytes32 upgraderRole = proposal.UPGRADER_ROLE(); + + // Check that the timelock doesn't have the UPGRADER_ROLE initially + bool hasRoleBefore = IAccessControl( + SeamlessAddressBook.BASE_LEVERAGE_MANAGER_PROXY + ).hasRole(upgraderRole, SeamlessAddressBook.TIMELOCK_SHORT); + assertFalse( + hasRoleBefore, + "Timelock should not have UPGRADER_ROLE before proposal" + ); + + // Pass the proposal + _passProposalShortGov(proposal); + + // After the proposal, the timelock should have been granted the role + // and it should STILL have the role (not revoked) + bool hasRoleAfter = IAccessControl( + SeamlessAddressBook.BASE_LEVERAGE_MANAGER_PROXY + ).hasRole(upgraderRole, SeamlessAddressBook.TIMELOCK_SHORT); + assertTrue( + hasRoleAfter, + "Timelock should have UPGRADER_ROLE after proposal (not revoked)" + ); + } + + function test_specificLeverageTokenImplementationIsUpgraded() public { + // Specific leverage token instance to check + address leverageToken = 0xA2fceEAe99d2cAeEe978DA27bE2d95b0381dBB8c; + + // Get the beacon address from the leverage token + // Beacon proxy stores the beacon address at a specific storage slot + bytes32 beaconSlot = + 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; + address beacon = + address(uint160(uint256(vm.load(leverageToken, beaconSlot)))); + + // Verify the beacon is the expected Base Leverage Token Factory Proxy + assertEq( + beacon, + SeamlessAddressBook.BASE_LEVERAGE_TOKEN_FACTORY_PROXY, + "Leverage token should be using the Base Leverage Token Factory beacon" + ); + + // Get the current implementation from the beacon + address implementationBefore = + UpgradeableBeacon(beacon).implementation(); + + // Verify it's not already the new implementation + assertNotEq( + implementationBefore, + EXPECTED_NEW_BASE_LEVERAGE_TOKEN_IMPLEMENTATION, + "Leverage token implementation should not be upgraded yet" + ); + + // Pass the proposal + _passProposalShortGov(proposal); + + // Get the implementation after the proposal + address implementationAfter = UpgradeableBeacon(beacon).implementation(); + + // Verify the implementation has been upgraded to the expected address + assertEq( + implementationAfter, + EXPECTED_NEW_BASE_LEVERAGE_TOKEN_IMPLEMENTATION, + "Leverage token at 0xA2fceEAe99d2cAeEe978DA27bE2d95b0381dBB8c should have upgraded implementation" + ); + } +} diff --git a/proposals/sip_49/description.md b/proposals/sip_49/description.md new file mode 100644 index 0000000..8a011fb --- /dev/null +++ b/proposals/sip_49/description.md @@ -0,0 +1,80 @@ +# [SIP-49] Upgrade Base Leverage Token and Manager Implementations + +## Summary + +This proposal upgrades the Seamless Leverage Token infrastructure on Base by updating both the Base Leverage Token implementation and the Base Leverage Manager implementation to their latest versions. These upgrades bring improvements in user experience, protocol integration capabilities, gas optimizations, and alignment with the Ethereum mainnet deployment. + +## Context and Motivation + +Seamless Leverage Tokens are ERC20 tokenized representations of leveraged positions between two assets, providing users with simple, liquid exposure to leveraged strategies without managing complex DeFi positions directly. Since the initial Base deployment in June 2025, the protocol has undergone optimization based on user feedback, and preparation for the Ethereum mainnet launch. + +The upgrades proposed in this SIP represent four months of development work ([base-deploy-jun-02-2025](https://github.com/seamless-protocol/leverage-tokens/tree/base-deploy-jun-02-2025) → [base-deploy-oct-7-2025](https://github.com/seamless-protocol/leverage-tokens/tree/base-deploy-oct-7-2025)) and are essential for: + +1. **Cross-chain consistency**: Aligning Base deployment with the upcoming Ethereum mainnet launch +2. **Performance improvements**: Reducing gas costs for common operations +3. **Feature parity**: Ensuring Base users have access to the latest protocol capabilities deployed on Ethereum + +## Technical Overview + +### Contracts Being Upgraded + +1. **Base Leverage Token Implementation** + - Current: Previous implementation from June 2025 deployment + - New: `0x057A2a1CC13A9Af430976af912A27A05DE537673` + - Upgrade mechanism: Beacon Proxy pattern via `UpgradeableBeacon` + +2. **Base Leverage Manager Implementation** + - Current: Previous implementation from June 2025 deployment + - New: `0xeb0221bf6cdaa74c94129771d5b0c9a994bb2b7c` + - Upgrade mechanism: UUPS Proxy pattern + +### Key Improvements + +1. Enhanced user experience and third party integration ease + +2. Fee calculation improvements + +3. Gas optimizations + +4. Code quality and edge cases + +### Breaking Changes + +While these upgrades introduce breaking changes to the smart contract interfaces, they are fully backward compatible from a user perspective: + +- **UI Compatibility**: The Seamless UI has been updated to support the new interfaces +- **Token Compatibility**: Existing leverage tokens remain fully functional +- **Position Safety**: All existing positions are preserved and protected during the upgrade + +## Security Considerations + +### Audit Status +The upgraded implementations have undergone comprehensive security review by Cantina (September, 2025). Full audit report available at [Cantina Security Report](https://cantina.xyz/portfolio/6291d7fa-62ac-4e18-9c2d-1403bfdd3c6c) + +## Implementation Details + +### Proposal Actions + +The proposal will execute the following actions through the Seamless governance timelock: + +1. **Grant UPGRADER_ROLE** to the timelock (required for UUPS upgrade) +2. **Upgrade Base Leverage Token** implementation via beacon proxy +3. **Upgrade Base Leverage Manager** implementation via UUPS proxy + +Note: The `UPGRADER_ROLE` is retained by the timelock after execution to facilitate future upgrades if needed. + +## Conclusion + +This upgrade represents an important step in the evolution of Seamless Leverage Tokens on Base. By implementing these improvements, we ensure that Base users have access to an optimized, user-friendly, and feature-complete version of the protocol that matches the Ethereum mainnet deployment. The upgrades have been thoroughly tested and audited, with careful consideration given to maintaining backward compatibility. + +We encourage the community to review the technical details and participate in the governance vote to advance the Seamless protocol on Base. + +## References + +- [Seamless Leverage Tokens Repository](https://github.com/seamless-protocol/leverage-tokens) +- [Old Implementation (June 2025)](https://github.com/seamless-protocol/leverage-tokens/tree/base-deploy-jun-02-2025) +- [New Implementation (October 2025)](https://github.com/seamless-protocol/leverage-tokens/tree/base-deploy-oct-7-2025) +- [Upgrade Diff: base-deploy-jun-02-2025 → base-deploy-oct-7-2025](https://github.com/seamless-protocol/leverage-tokens/compare/base-deploy-jun-02-2025...base-deploy-oct-7-2025) +- [Cantina Security Audit (September 2025)](https://cantina.xyz/portfolio/6291d7fa-62ac-4e18-9c2d-1403bfdd3c6c) +- [Audit Reports](https://github.com/seamless-protocol/leverage-tokens/tree/main/audits) +- [Technical Documentation](https://github.com/seamless-protocol/leverage-tokens/tree/main/docs) \ No newline at end of file