Skip to content
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

Feat: locking l2 upgrade #542

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
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
127 changes: 121 additions & 6 deletions contracts/governance/locking/LockingBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ import "./libs/LibBrokenLine.sol";
abstract contract LockingBase is OwnableUpgradeable, IVotesUpgradeable {
using LibBrokenLine for LibBrokenLine.BrokenLine;
/**
* @dev Duration of a week in blocks on the CELO blockchain assuming 5 seconds per block
* @dev Duration of a week in blocks on the CELO blockchain before the L2 transition (5 seconds per block)
*/
uint32 public constant WEEK = 120_960;

/**
* @dev Duration of a week in blocks on the CELO blockchain after the L2 transition (1 seconds per block)
*/
uint32 public constant L2_WEEK = 604_800;

/**
* @dev Maximum allowable cliff period for token locks in weeks
*/
Expand Down Expand Up @@ -84,6 +90,31 @@ abstract contract LockingBase is OwnableUpgradeable, IVotesUpgradeable {
* @dev Total supply line of veMento
*/
LibBrokenLine.BrokenLine public totalSupplyLine;

// ***************
// New variables for L2 transition upgrade (3 slots)
// ***************
/**
* @dev L2 transtion block number
*/
uint256 public l2Block;
/**
* @dev L2 starting point week number
*/
int256 public l2StartingPointWeek;
/**
* @dev Shift amount used after L2 transition to move the start of the epoch to 00-00 UTC Wednesday (approx)
*/
uint32 public l2Shift;
/**
* @dev Address of the Mento Labs multisig
*/
address public mentoLabsMultisig;
/**
* @dev Flag to pause locking and governance
*/
bool public paused;

/**
* @dev Emitted when create Lock with parameters (account, delegate, amount, slopePeriod, cliff)
*/
Expand Down Expand Up @@ -125,6 +156,26 @@ abstract contract LockingBase is OwnableUpgradeable, IVotesUpgradeable {
* @dev set newMinSlopePeriod
*/
event SetMinSlopePeriod(uint256 indexed newMinSlopePeriod);
/**
* @dev set new Mento Labs multisig address
*/
event SetMentoLabsMultisig(address indexed mentoLabsMultisig);
/**
* @dev set new L2 transition block number
*/
event SetL2TransitionBlock(uint256 indexed l2Block);
/**
* @dev set new L2 shift amount
*/
event SetL2Shift(uint32 indexed l2Shift);
/**
* @dev set new L2 starting point week number
*/
event SetL2StartingPointWeek(int256 indexed l2StartingPointWeek);
/**
* @dev set new paused flag
*/
event SetPaused(bool indexed paused);

/**
* @dev Initializes the contract with token, starting point week, and minimum cliff and slope periods.
Expand All @@ -149,6 +200,11 @@ abstract contract LockingBase is OwnableUpgradeable, IVotesUpgradeable {
minSlopePeriod = _minSlopePeriod;
}

modifier onlyMentoLabs() {
require(msg.sender == mentoLabsMultisig, "caller is not MentoLabs multisig");
_;
}

/**
* @notice Adds a new locking line for an account, initializing the lock with specified parameters.
* @param account Address for which tokens are being locked.
Expand Down Expand Up @@ -256,20 +312,29 @@ abstract contract LockingBase is OwnableUpgradeable, IVotesUpgradeable {

/**
* @notice Calculates the week number for a given blocknumber
* @dev It takes L2 transition into account to calculate the week number consistently
* @param ts block number
* @return week number the block number belongs to
*/
function roundTimestamp(uint32 ts) public view returns (uint32) {
require(!paused, "locking is paused");

if (ts < getEpochShift()) {
return 0;
}
uint32 shifted = ts - (getEpochShift());
return shifted / WEEK - uint32(startingPointWeek);

if (l2Block == 0 || ts < l2Block) {
uint32 shifted = ts - getEpochShift();
return shifted / WEEK - uint32(startingPointWeek);
} else {
uint32 shifted = ts - l2Shift;
return uint32(uint256(int256(uint256(shifted / L2_WEEK)) - l2StartingPointWeek));
}
}

/**
* @notice method returns the amount of blocks to shift locking epoch to.
* we move it to 00-00 UTC Wednesday (approx) by shifting 89964 blocks (CELO)
* @notice method returns the amount of blocks to shift locking epoch on L1 CELO.
* we move it to 00-00 UTC Wednesday (approx) by shifting 89964 blocks
*/
function getEpochShift() internal view virtual returns (uint32) {
return 89964;
Expand Down Expand Up @@ -351,6 +416,56 @@ abstract contract LockingBase is OwnableUpgradeable, IVotesUpgradeable {
uint32 time = roundTimestamp(blockNumber);
updateTotalSupplyLine(time);
}
/**
* @notice Sets the Mento Labs multisig address
* @param mentoLabsMultisig_ address of the Mento Labs multisig
*
*/
function setMentoLabsMultisig(address mentoLabsMultisig_) external onlyOwner {
mentoLabsMultisig = mentoLabsMultisig_;
emit SetMentoLabsMultisig(mentoLabsMultisig_);
}
/**
* @notice Sets the L2 transition block number and pauses locking and governance
* @param l2Block_ block number of the L2 transition
*
*/
function setL2TransitionBlock(uint256 l2Block_) external onlyMentoLabs {
l2Block = l2Block_;
paused = true;

emit SetL2TransitionBlock(l2Block_);
}

/**
* @notice Sets the L2 shift amount
* @param l2Shift_ shift amount that will be used after L2 transition
*/
function setL2Shift(uint32 l2Shift_) external onlyMentoLabs {
l2Shift = l2Shift_;

emit SetL2Shift(l2Shift_);
}

/**
* @notice Sets the L2 starting point week number
* @param l2StartingPointWeek_ starting point week number that will be used after L2 transition
*/
function setL2StartingPointWeek(int256 l2StartingPointWeek_) external onlyMentoLabs {
l2StartingPointWeek = l2StartingPointWeek_;

emit SetL2StartingPointWeek(l2StartingPointWeek_);
}

/**
* @notice Sets the paused flag
* @param paused_ flag to pause locking and governance
*/
function setPaused(bool paused_) external onlyMentoLabs {
paused = paused_;

emit SetPaused(paused_);
}

uint256[50] private __gap;
uint256[47] private __gap;
}
3 changes: 3 additions & 0 deletions test/fork/ForkTests.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import { BancorExchangeProviderForkTest } from "./BancorExchangeProviderForkTest
import { GoodDollarTradingLimitsForkTest } from "./GoodDollar/TradingLimitsForkTest.sol";
import { GoodDollarSwapForkTest } from "./GoodDollar/SwapForkTest.sol";
import { GoodDollarExpansionForkTest } from "./GoodDollar/ExpansionForkTest.sol";
import { LockingUpgradeForkTest } from "./upgrades/LockingUpgradeForkTest.sol";

contract Alfajores_ChainForkTest is ChainForkTest(ALFAJORES_ID, 1, uints(15)) {}

Expand Down Expand Up @@ -116,3 +117,5 @@ contract Celo_GoodDollarTradingLimitsForkTest is GoodDollarTradingLimitsForkTest
contract Celo_GoodDollarSwapForkTest is GoodDollarSwapForkTest(CELO_ID) {}

contract Celo_GoodDollarExpansionForkTest is GoodDollarExpansionForkTest(CELO_ID) {}

contract Celo_LockingUpgradeForkTest is LockingUpgradeForkTest(CELO_ID) {}
190 changes: 190 additions & 0 deletions test/fork/upgrades/LockingUpgradeForkTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/* solhint-disable max-line-length */
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8;

import { BaseForkTest } from "../BaseForkTest.sol";
import { Locking } from "contracts/governance/locking/Locking.sol";
import { GovernanceFactory } from "contracts/governance/GovernanceFactory.sol";
import { ProxyAdmin } from "openzeppelin-contracts-next/contracts/proxy/transparent/ProxyAdmin.sol";
import { ITransparentUpgradeableProxy } from "openzeppelin-contracts-next/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";

// used to avoid stack too deep error
struct LockingSnapshot {
uint256 weekNo;
uint256 totalSupply;
uint256 pastTotalSupply;
uint256 balance1;
uint256 balance2;
uint256 votingPower1;
uint256 votingPower2;
uint256 pastVotingPower1;
uint256 pastVotingPower2;
uint256 lockedBalance1;
uint256 lockedBalance2;
uint256 withdrawable1;
uint256 withdrawable2;
}

contract LockingUpgradeForkTest is BaseForkTest {
// airdrop claimers from the mainnet
address public constant AIRDROP_CLAIMER_1 = 0x3152eE4a18ee3209524F9071A6BcAdA098f19838;
address public constant AIRDROP_CLAIMER_2 = 0x44EB9Bf2D6B161499f1b706c331aa2Ba1d5069c7;

uint256 public constant L1_WEEK = 7 days / 5;
uint256 public constant L2_WEEK = 7 days;

GovernanceFactory public governanceFactory = GovernanceFactory(0xee6CE2dbe788dFC38b8F583Da86cB9caf2C8cF5A);
ProxyAdmin public proxyAdmin;
Locking public locking;
address public timelockController;
address public newLockingImplementation;

address public mentoLabsMultisig = makeAddr("mentoLabsMultisig");

constructor(uint256 _chainId) BaseForkTest(_chainId) {}

function setUp() public virtual override {
super.setUp();
proxyAdmin = governanceFactory.proxyAdmin();
locking = governanceFactory.locking();
timelockController = address(governanceFactory.governanceTimelock());

newLockingImplementation = address(new Locking());
vm.prank(timelockController);
proxyAdmin.upgrade(ITransparentUpgradeableProxy(address(locking)), newLockingImplementation);

vm.prank(timelockController);
locking.setMentoLabsMultisig(mentoLabsMultisig);

// THU Nov-07-2024 00:00:23 +UTC
vm.roll(28653031);
vm.warp(1730937623);
}

function test_blockNoDependentCalculations_afterL2Transition_shouldWorkAsBefore() public {
LockingSnapshot memory beforeSnapshot;
LockingSnapshot memory afterSnapshot;

// move 3 weeks forward on L1
_moveDays({ day: 3 * 7, forward: true, isL2: false });

// Take snapshot 3 weeks after Nov 07
beforeSnapshot = _takeSnapshot(AIRDROP_CLAIMER_1, AIRDROP_CLAIMER_2);

// move 5 weeks forward on L1
_moveDays({ day: 5 * 7, forward: true, isL2: false });

// Take snapshot 8 weeks after Nov 07
afterSnapshot = _takeSnapshot(AIRDROP_CLAIMER_1, AIRDROP_CLAIMER_2);

// move 5 weeks backward on L1
_moveDays({ day: 5 * 7, forward: false, isL2: false });

uint256 blocksTillNextWeekL1 = _calculateBlocksTillNextWeek({ isL2: false });

_simulateL2Upgrade();

uint256 blocksTillNextWeekL2 = _calculateBlocksTillNextWeek({ isL2: true });

// if the shift number is correct, the number of blocks till the next week should be 5 times the previous number
assertEq(blocksTillNextWeekL2, 5 * blocksTillNextWeekL1);

assertEq(locking.getWeek(), beforeSnapshot.weekNo);
assertEq(locking.totalSupply(), beforeSnapshot.totalSupply);
// the past values should be calculated using the L1 week value
assertEq(locking.getPastTotalSupply(block.number - 3 * L1_WEEK), beforeSnapshot.pastTotalSupply);
assertEq(locking.balanceOf(AIRDROP_CLAIMER_1), beforeSnapshot.balance1);
assertEq(locking.balanceOf(AIRDROP_CLAIMER_2), beforeSnapshot.balance2);
assertEq(locking.getVotes(AIRDROP_CLAIMER_1), beforeSnapshot.votingPower1);
assertEq(locking.getVotes(AIRDROP_CLAIMER_2), beforeSnapshot.votingPower2);
assertEq(locking.getPastVotes(AIRDROP_CLAIMER_1, block.number - 3 * L1_WEEK), beforeSnapshot.pastVotingPower1);
assertEq(locking.getPastVotes(AIRDROP_CLAIMER_2, block.number - 3 * L1_WEEK), beforeSnapshot.pastVotingPower2);
assertEq(locking.locked(AIRDROP_CLAIMER_1), beforeSnapshot.lockedBalance1);
assertEq(locking.locked(AIRDROP_CLAIMER_2), beforeSnapshot.lockedBalance2);
assertEq(locking.getAvailableForWithdraw(AIRDROP_CLAIMER_1), beforeSnapshot.withdrawable1);
assertEq(locking.getAvailableForWithdraw(AIRDROP_CLAIMER_2), beforeSnapshot.withdrawable2);

// move 5 weeks forward on L2
_moveDays({ day: 5 * 7, forward: true, isL2: true });

blocksTillNextWeekL2 = _calculateBlocksTillNextWeek({ isL2: true });

assertEq(blocksTillNextWeekL2, 5 * blocksTillNextWeekL1);
assertEq(locking.getWeek(), afterSnapshot.weekNo);
assertEq(locking.totalSupply(), afterSnapshot.totalSupply);
assertEq(locking.getPastTotalSupply(block.number - 3 * L2_WEEK), afterSnapshot.pastTotalSupply);
assertEq(locking.balanceOf(AIRDROP_CLAIMER_1), afterSnapshot.balance1);
assertEq(locking.balanceOf(AIRDROP_CLAIMER_2), afterSnapshot.balance2);
assertEq(locking.getVotes(AIRDROP_CLAIMER_1), afterSnapshot.votingPower1);
assertEq(locking.getVotes(AIRDROP_CLAIMER_2), afterSnapshot.votingPower2);
assertEq(locking.getPastVotes(AIRDROP_CLAIMER_1, block.number - 3 * L2_WEEK), afterSnapshot.pastVotingPower1);
assertEq(locking.getPastVotes(AIRDROP_CLAIMER_2, block.number - 3 * L2_WEEK), afterSnapshot.pastVotingPower2);
assertEq(locking.locked(AIRDROP_CLAIMER_1), afterSnapshot.lockedBalance1);
assertEq(locking.locked(AIRDROP_CLAIMER_2), afterSnapshot.lockedBalance2);
assertEq(locking.getAvailableForWithdraw(AIRDROP_CLAIMER_1), afterSnapshot.withdrawable1);
assertEq(locking.getAvailableForWithdraw(AIRDROP_CLAIMER_2), afterSnapshot.withdrawable2);

// move 5 days forward on L2
_moveDays({ day: 5, forward: true, isL2: true });
// we should be at the same week (TUE around 00:00)
assertEq(locking.getWeek(), afterSnapshot.weekNo);
// move 1 day forward on L2 + 90 mins as buffer
_moveDays({ day: 1, forward: true, isL2: true });
vm.roll(block.number + 90 minutes);
// we should be at the next week (WED around 01:30)
assertEq(locking.getWeek(), afterSnapshot.weekNo + 1);
}

// takes a snapshot of the locking contract at current block
function _takeSnapshot(address claimer1, address claimer2) internal view returns (LockingSnapshot memory snapshot) {
snapshot.weekNo = locking.getWeek();
snapshot.totalSupply = locking.totalSupply();
snapshot.pastTotalSupply = locking.getPastTotalSupply(block.number - 3 * L1_WEEK);
snapshot.balance1 = locking.balanceOf(claimer1);
snapshot.balance2 = locking.balanceOf(claimer2);
snapshot.votingPower1 = locking.getVotes(claimer1);
snapshot.votingPower2 = locking.getVotes(claimer2);
snapshot.pastVotingPower1 = locking.getPastVotes(claimer1, block.number - 3 * L1_WEEK);
snapshot.pastVotingPower2 = locking.getPastVotes(claimer2, block.number - 3 * L1_WEEK);
snapshot.lockedBalance1 = locking.locked(claimer1);
snapshot.lockedBalance2 = locking.locked(claimer2);
snapshot.withdrawable1 = locking.getAvailableForWithdraw(claimer1);
snapshot.withdrawable2 = locking.getAvailableForWithdraw(claimer2);
}

// returns the number of blocks till the next week
// by calculating the first block of the next week and substracting the current block
function _calculateBlocksTillNextWeek(bool isL2) internal view returns (uint256) {
if (isL2) {
return L2_WEEK * uint256(int256(locking.getWeek()) + locking.l2StartingPointWeek() + 1) + 507776 - block.number;
} else {
return L1_WEEK * (locking.getWeek() + locking.startingPointWeek() + 1) + 89964 - block.number;
}
}

// simulates the L2 upgrade by setting the necessary parameters
function _simulateL2Upgrade() internal {
vm.prank(mentoLabsMultisig);
locking.setL2TransitionBlock(block.number);
vm.prank(mentoLabsMultisig);
locking.setL2StartingPointWeek(20);
vm.prank(mentoLabsMultisig);
locking.setL2Shift(507776);
vm.prank(mentoLabsMultisig);
locking.setPaused(false);
}

// move days forward or backward on L1 or L2
function _moveDays(uint256 day, bool forward, bool isL2) internal {
uint256 ts = vm.getBlockTimestamp();
uint256 height = vm.getBlockNumber();

uint256 newTs = forward ? ts + day * 1 days : ts - day * 1 days;

uint256 blockChange = isL2 ? (day * 1 days) : ((day * 1 days) / 5);
uint256 newHeight = forward ? height + blockChange : height - blockChange;

vm.warp(newTs);
vm.roll(newHeight);
}
}
4 changes: 4 additions & 0 deletions test/unit/governance/Locking/LockingTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@ contract LockingTest is GovernanceTest {
function _incrementBlock(uint32 _amount) internal {
locking.incrementBlock(_amount);
}

function _reduceBlock(uint32 _amount) internal {
locking.reduceBlock(_amount);
}
}
Loading
Loading