Skip to content

Commit

Permalink
Add sendToCustodian function to VestingWalletPaused (#198)
Browse files Browse the repository at this point in the history
* Add sendToCustodian function to VestingWalletPaused

* Updated fmt

* Use SafeERC20

* Added comments to VestingWallets

* Add custodianAddress check
  • Loading branch information
Phanco authored Aug 29, 2024
1 parent 2302e95 commit 87f50bc
Show file tree
Hide file tree
Showing 12 changed files with 155 additions and 46 deletions.
10 changes: 7 additions & 3 deletions src/L1/paused/L1VestingWalletPaused.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import { L2VestingWalletPaused } from "src/L2/paused/L2VestingWalletPaused.sol";

/// @title L1VestingWalletPaused - Paused version of L1VestingWallet contract
/// @notice This contract is used to pause the L1VestingWallet contract. In case of any emergency, the owner can upgrade
/// and
/// pause the contract to prevent any further vesting operations.
/// and pause the contract to prevent any further vesting operations.
/// L1VestingWalletPaused shares the same functionality of L1VestingWalletPaused.
contract L1VestingWalletPaused is L2VestingWalletPaused { }
contract L1VestingWalletPaused is L2VestingWalletPaused {
function custodianAddress() public pure virtual override returns (address) {
// Address of Security Council on L1
return 0xD2D7535e099F26EbfbA26d96bD1a661d3531d0e9;
}
}
8 changes: 2 additions & 6 deletions src/L2/L2Governor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,7 @@ contract L2Governor is

// The below functions are overrides required by Solidity.

function state(
uint256 proposalId
)
function state(uint256 proposalId)
public
view
virtual
Expand All @@ -101,9 +99,7 @@ contract L2Governor is
return super.state(proposalId);
}

function proposalNeedsQueuing(
uint256 proposalId
)
function proposalNeedsQueuing(uint256 proposalId)
public
view
virtual
Expand Down
12 changes: 3 additions & 9 deletions src/L2/L2LockingPosition.sol
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,7 @@ contract L2LockingPosition is Initializable, Ownable2StepUpgradeable, UUPSUpgrad
/// initialized to 0 or address(0).
/// @param position Locking position to be checked.
/// @return Whether the given locking position is null.
function isLockingPositionNull(
IL2LockingPosition.LockingPosition memory position
)
function isLockingPositionNull(IL2LockingPosition.LockingPosition memory position)
internal
view
virtual
Expand Down Expand Up @@ -266,9 +264,7 @@ contract L2LockingPosition is Initializable, Ownable2StepUpgradeable, UUPSUpgrad
/// @notice Returns the locking position for the given position ID.
/// @param positionId ID of the locking position.
/// @return Locking position for the given position ID.
function getLockingPosition(
uint256 positionId
)
function getLockingPosition(uint256 positionId)
public
view
virtual
Expand All @@ -280,9 +276,7 @@ contract L2LockingPosition is Initializable, Ownable2StepUpgradeable, UUPSUpgrad
/// @notice Returns all locking positions for the given owner.
/// @param lockOwner Owner address.
/// @return All locking positions for the given owner.
function getAllLockingPositionsByOwner(
address lockOwner
)
function getAllLockingPositionsByOwner(address lockOwner)
public
view
virtual
Expand Down
8 changes: 2 additions & 6 deletions src/L2/L2Staking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,7 @@ contract L2Staking is Initializable, Ownable2StepUpgradeable, UUPSUpgradeable, I
/// initialized to 0 or address(0).
/// @param position Locking position to be checked.
/// @return Whether the given locking position is null.
function isLockingPositionNull(
IL2LockingPosition.LockingPosition memory position
)
function isLockingPositionNull(IL2LockingPosition.LockingPosition memory position)
internal
view
virtual
Expand Down Expand Up @@ -187,9 +185,7 @@ contract L2Staking is Initializable, Ownable2StepUpgradeable, UUPSUpgradeable, I
/// @notice Returns the remaining locking duration for the given locking position.
/// @param lock The locking position for which the remaining locking duration is returned.
/// @return The remaining locking duration for the given locking position.
function remainingLockingDuration(
IL2LockingPosition.LockingPosition memory lock
)
function remainingLockingDuration(IL2LockingPosition.LockingPosition memory lock)
internal
view
virtual
Expand Down
8 changes: 2 additions & 6 deletions src/L2/L2VestingWallet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,7 @@ contract L2VestingWallet is

/// @notice Since `Ownable2StepUpgradeable` is enforced on top of `OwnableUpgradeable`. Overriding is required.
/// @param _newOwner New proposed owner.
function transferOwnership(
address _newOwner
)
function transferOwnership(address _newOwner)
public
virtual
override(Ownable2StepUpgradeable, OwnableUpgradeable)
Expand All @@ -78,9 +76,7 @@ contract L2VestingWallet is

// Overriding _transferOwnership and solely uses `Ownable2StepUpgradeable`.
/// @param _newOwner New proposed owner.
function _transferOwnership(
address _newOwner
)
function _transferOwnership(address _newOwner)
internal
virtual
override(Ownable2StepUpgradeable, OwnableUpgradeable)
Expand Down
24 changes: 20 additions & 4 deletions src/L2/paused/L2VestingWalletPaused.sol
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.23;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { L2VestingWallet } from "src/L2/L2VestingWallet.sol";

/// @title L2VestingWalletPaused - Paused version of L2VestingWallet contract
/// @notice This contract is used to pause the L2VestingWallet contract. In case of any emergency, the owner can upgrade
/// and
/// pause the contract to prevent any further vesting operations.
/// @notice This contract is used to pause the L2VestingWallet contract. In case of any emergency, the contract admin
/// can upgrade
/// and pause the contract to prevent any further vesting operations.
/// Contract admin can also withdraw all tokens to the Security Council wallet.
contract L2VestingWalletPaused is L2VestingWallet {
using SafeERC20 for IERC20;

error VestingWalletIsPaused();

/// @notice Setting global params.
function initializePaused() public reinitializer(2) {
version = "1.0.0-paused";
version = "1.0.1-paused";
}

/// @notice Hard-coded address to recover tokens.
function custodianAddress() public pure virtual returns (address) {
// Address of Security Council on L2
return 0x394Ae9d48eeca1C69a989B5A8C787081595c55A7;
}

/// @notice Withdraw all balances of token to recoveryAddress.
function sendToCustodian(IERC20 _token) public virtual onlyRole(CONTRACT_ADMIN_ROLE) {
_token.safeTransfer(custodianAddress(), _token.balanceOf(address(this)));
}

/// @notice Override the release function to prevent release of token from being processed.
Expand Down
4 changes: 1 addition & 3 deletions src/interfaces/L2/IL2Governor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,7 @@ interface IL2Governor {
function proposalProposer(uint256 proposalId) external view returns (address);
function proposalSnapshot(uint256 proposalId) external view returns (uint256);
function proposalThreshold() external view returns (uint256);
function proposalVotes(
uint256 proposalId
)
function proposalVotes(uint256 proposalId)
external
view
returns (uint256 againstVotes, uint256 forVotes, uint256 abstainVotes);
Expand Down
4 changes: 1 addition & 3 deletions src/interfaces/L2/IL2LockingPosition.sol
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ interface IL2LockingPosition {
function initialize(address _stakingContract) external;
function initializeVotingPower(address _votingPowerContract) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
function lockingPositions(
uint256
)
function lockingPositions(uint256)
external
view
returns (address creator, uint256 amount, uint256 expDate, uint256 pausedLockingDuration);
Expand Down
76 changes: 76 additions & 0 deletions test/L1/paused/L1VestingWalletPaused.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.23;

import { Test, console, stdJson } from "forge-std/Test.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { L1VestingWallet } from "src/L1/L1VestingWallet.sol";
import { L1VestingWalletPaused } from "src/L1/paused/L1VestingWalletPaused.sol";
import { MockERC20 } from "test/mock/MockERC20.sol";

contract L1VestingWalletPausedTest is Test {
using stdJson for string;

L1VestingWallet public l1VestingWallet;
L1VestingWallet public l1VestingWalletImplementation;

L1VestingWalletPaused public l1VestingWalletPaused;
L1VestingWalletPaused public l1VestingWalletPausedImplementation;

MockERC20 public mockToken;

address public beneficiary = vm.addr(uint256(bytes32("beneficiary")));
address public contractAdmin = vm.addr(uint256(bytes32("contractAdmin")));
uint64 public startTimestamp = uint64(vm.getBlockTimestamp());
uint64 public durationSeconds = 1000;
string public name = "Vesting Wallet";

uint256 public vestAmount = 1_000_000;

function setUp() public {
// deploy L1VestingWallet implementation contract
l1VestingWalletImplementation = new L1VestingWallet();

// deploy L1VestingWallet contract via proxy and initialize it at the same time
l1VestingWallet = L1VestingWallet(
payable(
address(
new ERC1967Proxy(
address(l1VestingWalletImplementation),
abi.encodeWithSelector(
l1VestingWalletImplementation.initialize.selector,
beneficiary,
startTimestamp,
durationSeconds,
name,
contractAdmin
)
)
)
)
);
assert(address(l1VestingWallet) != address(0x0));

// transfer token to vesting contract
mockToken = new MockERC20(vestAmount);
mockToken.transfer(address(l1VestingWallet), vestAmount);

// deploy L1VestingWalletPaused implementation contract
l1VestingWalletPausedImplementation = new L1VestingWalletPaused();

// upgrade L1VestingWallet contract to L1VestingWalletPaused contract
vm.startPrank(contractAdmin);
l1VestingWallet.upgradeToAndCall(
address(l1VestingWalletPausedImplementation),
abi.encodeWithSelector(l1VestingWalletPausedImplementation.initializePaused.selector)
);
vm.stopPrank();

// Wrapping with L1VestingWalletPaused
l1VestingWalletPaused = L1VestingWalletPaused(payable(address(l1VestingWallet)));
}

function test_CustodianAddress() public view {
// Address of Security Council on L1
assertEq(l1VestingWalletPaused.custodianAddress(), 0xD2D7535e099F26EbfbA26d96bD1a661d3531d0e9);
}
}
4 changes: 1 addition & 3 deletions test/L2/L2LockingPosition.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,7 @@ contract L2LockingPositionV2 is L2LockingPosition {
}

contract L2LockingPositionHarness is L2LockingPosition {
function exposedIsLockingPositionNull(
IL2LockingPosition.LockingPosition memory position
)
function exposedIsLockingPositionNull(IL2LockingPosition.LockingPosition memory position)
public
view
returns (bool)
Expand Down
4 changes: 1 addition & 3 deletions test/L2/L2Staking.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ contract L2StakingHarness is L2Staking {
return canLockingPositionBeModified(lockId, lock);
}

function exposedRemainingLockingDuration(
IL2LockingPosition.LockingPosition memory lock
)
function exposedRemainingLockingDuration(IL2LockingPosition.LockingPosition memory lock)
public
view
returns (uint256)
Expand Down
39 changes: 39 additions & 0 deletions test/L2/paused/L2VestingWalletPaused.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity 0.8.23;

import { Test, console, stdJson } from "forge-std/Test.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IAccessControl } from "@openzeppelin/contracts/access/IAccessControl.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { L2VestingWallet } from "src/L2/L2VestingWallet.sol";
import { L2VestingWalletPaused } from "src/L2/paused/L2VestingWalletPaused.sol";
Expand All @@ -22,6 +24,8 @@ contract L2VestingWalletPausedTest is Test {

L2VestingWallet public l2VestingWallet;
L2VestingWallet public l2VestingWalletImplementation;

L2VestingWalletPaused public l2VestingWalletPaused;
L2VestingWalletPaused public l2VestingWalletPausedImplementation;

MockERC20 public mockToken;
Expand Down Expand Up @@ -72,6 +76,41 @@ contract L2VestingWalletPausedTest is Test {
abi.encodeWithSelector(l2VestingWalletPausedImplementation.initializePaused.selector)
);
vm.stopPrank();

// Wrapping with L2VestingWalletPaused
l2VestingWalletPaused = L2VestingWalletPaused(payable(address(l2VestingWallet)));
}

function test_CustodianAddress() public view {
// Address of Security Council on L2
assertEq(l2VestingWalletPaused.custodianAddress(), 0x394Ae9d48eeca1C69a989B5A8C787081595c55A7);
}

function test_SendToCustodian_RevertWhenNotContractAdmin() public {
address nobody = vm.addr(1);

vm.startPrank(nobody);
vm.expectRevert(
abi.encodeWithSelector(
IAccessControl.AccessControlUnauthorizedAccount.selector, nobody, l2VestingWallet.CONTRACT_ADMIN_ROLE()
)
);
l2VestingWalletPaused.sendToCustodian(IERC20(address(mockToken)));
vm.stopPrank();
}

function test_SendToCustodian() public {
uint256 contractBalanceBefore = mockToken.balanceOf(address(l2VestingWalletPaused));
uint256 recoveryAddressBalanceBefore = mockToken.balanceOf(l2VestingWalletPaused.custodianAddress());

vm.startPrank(contractAdmin);
l2VestingWalletPaused.sendToCustodian(IERC20(address(mockToken)));
vm.stopPrank();

assertEq(
mockToken.balanceOf(l2VestingWalletPaused.custodianAddress()),
contractBalanceBefore + recoveryAddressBalanceBefore
);
}

function test_ReleaseWithAddress_Paused() public {
Expand Down

0 comments on commit 87f50bc

Please sign in to comment.