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
92 changes: 92 additions & 0 deletions .github/workflows/contracts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: Contracts

on:
push:
branches:
- main
paths:
- '**'
- '.github/workflows/contracts.yaml'
pull_request:
types:
- opened
- reopened
- synchronize
- ready_for_review
paths:
- '**'
- '.github/workflows/contracts.yaml'

defaults:
run:
working-directory: '.'

jobs:
foundry:
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
permissions: {}

steps:
- name: Checkout sources
uses: actions/checkout@v4
with:
submodules: recursive
persist-credentials: false

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@82dee4ba654bd2146511f85f0d013af94670c4de # v1.4.0
with:
version: nightly

- name: Setup LCOV
uses: hrishikesh-kadam/setup-lcov@6c1aa0cc9e1c02f9f58f01ac599f1064ccc83470 # v1

- name: Install Node.js 18
uses: actions/setup-node@v4
with:
node-version: '18'

- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"

- name: Cache yarn dependencies
uses: actions/cache@v4
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-

- name: Cache node_modules
id: npm_cache
uses: actions/cache@v4
with:
path: node_modules
key: node_modules-${{ hashFiles('yarn.lock') }}

- name: yarn install
# if: steps.npm_cache.outputs.cache-hit != 'true'
run: yarn install

- name: Compile with foundry
run: forge build --evm-version cancun

- name: Run foundry tests
run: forge test --evm-version cancun -vvv

- name: Run foundry coverage
run : forge coverage --evm-version cancun --report lcov

- name : Prune coverage
run : lcov --rc branch_coverage=1 --remove ./lcov.info -o ./lcov.info.pruned 'src/mocks/*' 'test/*' 'script/*' 'node_modules/*' 'lib/*' --ignore-errors unused,unused

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # v5.4.2
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
files: lcov.info.pruned
flags: contracts
43 changes: 0 additions & 43 deletions .github/workflows/test.yml

This file was deleted.

3 changes: 2 additions & 1 deletion script/DeployScroll.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ contract DeployScroll is Script {
(
address(usxProxy),
address(0), // Treasury address (will be set later)
admin,
governance
)
);
Expand Down Expand Up @@ -179,7 +180,7 @@ contract DeployScroll is Script {
console.log("2.2. Deploying Treasury Proxy...");
bytes memory treasuryInitData = abi.encodeCall(
TreasuryDiamond.initialize,
(usdcAddress, address(usxProxy), address(susxProxy), governance, governanceWarchest, assetManager, insuranceVault)
(usdcAddress, address(usxProxy), address(susxProxy), admin, governance, governanceWarchest, assetManager, insuranceVault)
);

treasuryProxy = address(new ERC1967Proxy(address(treasuryImpl), treasuryInitData));
Expand Down
71 changes: 48 additions & 23 deletions src/StakedUSX.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ contract StakedUSX is ERC4626Upgradeable, UUPSUpgradeable, ReentrancyGuardUpgrad
error ZeroAddress();
error ZeroAmount();
error NotGovernance();
error NotAdmin();
error NotTreasury();
error WithdrawalAlreadyClaimed();
error WithdrawalPeriodNotPassed();
Expand All @@ -38,6 +39,7 @@ contract StakedUSX is ERC4626Upgradeable, UUPSUpgradeable, ReentrancyGuardUpgrad

event TreasurySet(address indexed treasury);
event GovernanceTransferred(address indexed oldGovernance, address indexed newGovernance);
event AdminTransferred(address indexed oldAdmin, address indexed newAdmin);
event EpochAdvanced(uint256 oldEpochBlock, uint256 newEpochBlock);
event WithdrawalRequested(address indexed user, uint256 sharesAmount, uint256 withdrawalId);
event WithdrawalClaimed(address indexed user, uint256 withdrawalId, uint256 usxAmount);
Expand All @@ -62,6 +64,11 @@ contract StakedUSX is ERC4626Upgradeable, UUPSUpgradeable, ReentrancyGuardUpgrad
_;
}

modifier onlyAdmin() {
if (msg.sender != _getStorage().admin) revert NotAdmin();
_;
}

modifier onlyTreasury() {
if (msg.sender != address(_getStorage().treasury)) revert NotTreasury();
_;
Expand All @@ -88,6 +95,7 @@ contract StakedUSX is ERC4626Upgradeable, UUPSUpgradeable, ReentrancyGuardUpgrad
struct SUSXStorage {
IERC20 USX; // USX token reference (the underlying asset)
ITreasury treasury; // treasury contract
address admin; // address that controls admin of the contract
address governance; // address that controls governance of the contract
uint256 withdrawalPeriod; // withdrawal period in seconds, (default == 15 * 24 * 60 * 60 (15 days))
uint256 withdrawalFeeFraction; // fraction of withdrawals determining the withdrawal fee, (default 0.05% == 500) with precision 6 decimals
Expand Down Expand Up @@ -126,7 +134,7 @@ contract StakedUSX is ERC4626Upgradeable, UUPSUpgradeable, ReentrancyGuardUpgrad
/// @param _usx Address of the USX token
/// @param _treasury Address of the Treasury contract
/// @param _governance Address of the governance
function initialize(address _usx, address _treasury, address _governance) public initializer {
function initialize(address _usx, address _treasury, address _admin, address _governance) public initializer {
if (_usx == address(0) || _governance == address(0)) revert ZeroAddress();

// Initialize ERC4626, ERC20, and ReentrancyGuard
Expand All @@ -137,6 +145,7 @@ contract StakedUSX is ERC4626Upgradeable, UUPSUpgradeable, ReentrancyGuardUpgrad
SUSXStorage storage $ = _getStorage();
$.USX = IERC20(_usx);
$.treasury = ITreasury(_treasury);
$.admin = _admin;
$.governance = _governance;

// Set default values
Expand All @@ -147,7 +156,7 @@ contract StakedUSX is ERC4626Upgradeable, UUPSUpgradeable, ReentrancyGuardUpgrad

/// @notice Set the initial Treasury address - can only be called once when treasury is address(0)
/// @param _treasury Address of the Treasury contract
function initializeTreasury(address _treasury) external onlyGovernance {
function initializeTreasury(address _treasury) external onlyAdmin {
if (_treasury == address(0)) revert ZeroAddress();
SUSXStorage storage $ = _getStorage();
if ($.treasury != ITreasury(address(0))) revert TreasuryAlreadySet();
Expand Down Expand Up @@ -216,15 +225,6 @@ contract StakedUSX is ERC4626Upgradeable, UUPSUpgradeable, ReentrancyGuardUpgrad

/*=========================== Governance Functions =========================*/

/// @notice Sets withdrawal period in seconds
/// @param _withdrawalPeriod The new withdrawal period in seconds
function setWithdrawalPeriod(uint256 _withdrawalPeriod) public onlyGovernance {
SUSXStorage storage $ = _getStorage();
uint256 oldPeriod = $.withdrawalPeriod;
$.withdrawalPeriod = _withdrawalPeriod;
emit WithdrawalPeriodSet(oldPeriod, _withdrawalPeriod);
}

/// @notice Sets withdrawal fee with precision to 0.001 percent
/// @param _withdrawalFeeFraction The new withdrawal fee fraction
function setWithdrawalFeeFraction(uint256 _withdrawalFeeFraction) public onlyGovernance {
Expand All @@ -235,16 +235,6 @@ contract StakedUSX is ERC4626Upgradeable, UUPSUpgradeable, ReentrancyGuardUpgrad
emit WithdrawalFeeFractionSet(oldFraction, _withdrawalFeeFraction);
}

/// @notice Sets duration of epoch in seconds
/// @param _epochDurationSeconds The new epoch duration in seconds
function setEpochDuration(uint256 _epochDurationSeconds) public onlyGovernance {
if (_epochDurationSeconds < MIN_EPOCH_DURATION) revert InvalidEpochDuration();
SUSXStorage storage $ = _getStorage();
uint256 oldDuration = $.epochDuration;
$.epochDuration = _epochDurationSeconds;
emit EpochDurationSet(oldDuration, _epochDurationSeconds);
}

/// @notice Set new governance address
/// @param newGovernance Address of new governance
function setGovernance(address newGovernance) external onlyGovernance {
Expand All @@ -257,15 +247,46 @@ contract StakedUSX is ERC4626Upgradeable, UUPSUpgradeable, ReentrancyGuardUpgrad
emit GovernanceTransferred(oldGovernance, newGovernance);
}

/*=========================== Admin Functions =========================*/

/// @notice Set new admin address
/// @param newAdmin Address of new admin
function setAdmin(address newAdmin) external onlyAdmin {
if (newAdmin == address(0)) revert ZeroAddress();
SUSXStorage storage $ = _getStorage();
address oldAdmin = $.admin;
$.admin = newAdmin;
emit AdminTransferred(oldAdmin, newAdmin);
}

/// @notice Sets withdrawal period in seconds
/// @param _withdrawalPeriod The new withdrawal period in seconds
function setWithdrawalPeriod(uint256 _withdrawalPeriod) public onlyAdmin {
SUSXStorage storage $ = _getStorage();
uint256 oldPeriod = $.withdrawalPeriod;
$.withdrawalPeriod = _withdrawalPeriod;
emit WithdrawalPeriodSet(oldPeriod, _withdrawalPeriod);
}

/// @notice Sets duration of epoch in seconds
/// @param _epochDurationSeconds The new epoch duration in seconds
function setEpochDuration(uint256 _epochDurationSeconds) public onlyAdmin {
if (_epochDurationSeconds < MIN_EPOCH_DURATION) revert InvalidEpochDuration();
SUSXStorage storage $ = _getStorage();
uint256 oldDuration = $.epochDuration;
$.epochDuration = _epochDurationSeconds;
emit EpochDurationSet(oldDuration, _epochDurationSeconds);
}

/// @notice Unpause deposit, allowing users to deposit again
function unpauseDeposit() external onlyGovernance {
function unpauseDeposit() external onlyAdmin {
SUSXStorage storage $ = _getStorage();
$.depositPaused = false;
emit DepositPausedChanged(false);
}

/// @notice Pause deposit, preventing users from depositing USX
function pauseDeposit() external onlyGovernance {
function pauseDeposit() external onlyAdmin {
SUSXStorage storage $ = _getStorage();
$.depositPaused = true;
emit DepositPausedChanged(true);
Expand Down Expand Up @@ -415,6 +436,10 @@ contract StakedUSX is ERC4626Upgradeable, UUPSUpgradeable, ReentrancyGuardUpgrad
return _getStorage().governance;
}

function admin() public view returns (address) {
return _getStorage().admin;
}

function withdrawalPeriod() public view returns (uint256) {
return _getStorage().withdrawalPeriod;
}
Expand Down
14 changes: 13 additions & 1 deletion src/TreasuryDiamond.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,14 @@ contract TreasuryDiamond is Initializable, UUPSUpgradeable, ReentrancyGuardUpgra
address _USDC,
address _USX,
address _sUSX,
address _admin,
address _governance,
address _governanceWarchest,
address _assetManager,
address _insuranceVault
) public initializer {
if (
_USDC == address(0) || _USX == address(0) || _sUSX == address(0) || _governance == address(0)
_USDC == address(0) || _USX == address(0) || _sUSX == address(0) || _admin == address(0) || _governance == address(0)
|| _governanceWarchest == address(0)
) {
revert ZeroAddress();
Expand All @@ -71,6 +72,7 @@ contract TreasuryDiamond is Initializable, UUPSUpgradeable, ReentrancyGuardUpgra
$.USDC = IERC20(_USDC);
$.USX = IUSX(_USX);
$.sUSX = IStakedUSX(_sUSX);
$.admin = _admin;
$.governance = _governance;
$.assetManager = _assetManager;
$.governanceWarchest = _governanceWarchest;
Expand Down Expand Up @@ -199,6 +201,16 @@ contract TreasuryDiamond is Initializable, UUPSUpgradeable, ReentrancyGuardUpgra
emit InsuranceVaultTransferred(oldInsuranceVault, newInsuranceVault);
}

/// @notice Set new admin address
/// @param newAdmin Address of new admin
function setAdmin(address newAdmin) external onlyAdmin {
if (newAdmin == address(0)) revert ZeroAddress();
TreasuryStorage.TreasuryStorageStruct storage $ = _getStorage();
address oldAdmin = $.admin;
$.admin = newAdmin;

emit AdminTransferred(oldAdmin, newAdmin);
}
/*=========================== Fallback =========================*/

/// @dev Fallback function that delegates calls to facets
Expand Down
13 changes: 13 additions & 0 deletions src/TreasuryStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@ contract TreasuryStorage {
error InvalidInsuranceFundFraction();

// Access control errors
error NotAdmin();
error NotGovernance();
error NotAllocator();
error NotTreasury();
error NotReporter();

/*=========================== Events =========================*/

event AdminTransferred(address indexed oldAdmin, address indexed newAdmin);
event GovernanceTransferred(address indexed oldGovernance, address indexed newGovernance);
event GovernanceWarchestTransferred(address indexed oldGovernanceWarchest, address indexed newGovernanceWarchest);
event InsuranceVaultTransferred(address indexed oldInsuranceVault, address indexed newInsuranceVault);
Expand All @@ -57,6 +59,12 @@ contract TreasuryStorage {

/*=========================== Modifiers =========================*/

// Modifier to restrict access to admin functions
modifier onlyAdmin() {
if (msg.sender != _getStorage().admin) revert NotAdmin();
_;
}

// Modifier to restrict access to governance functions
modifier onlyGovernance() {
if (msg.sender != _getStorage().governance) revert NotGovernance();
Expand Down Expand Up @@ -88,6 +96,7 @@ contract TreasuryStorage {
IUSX USX; // USX token contract
IStakedUSX sUSX; // sUSX vault contract
IERC20 USDC; // USDC token contract
address admin; // Admin address
address governance; // Governance address
address reporter; // Reporter address
address allocator; // Allocator address
Expand Down Expand Up @@ -126,6 +135,10 @@ contract TreasuryStorage {
return _getStorage().USDC;
}

function admin() public view returns (address) {
return _getStorage().admin;
}

function governance() public view returns (address) {
return _getStorage().governance;
}
Expand Down
Loading
Loading