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

Add LiquidStone Investor & Asset Receiver roles and set URI. #188

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
2 changes: 1 addition & 1 deletion .github/workflows/ci-dev-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
version: stable

- name: Install dependencies
run: yarn install
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci-dev-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
version: stable

- name: Install Project dependencies
run: yarn install
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci-dev-contracts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
version: stable

- name: Run tests
run: forge test -vvv
Expand All @@ -52,7 +52,7 @@ jobs:
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly
version: stable

# re-run the tests here on purpose to ensure quality.
- name: Run tests
Expand Down
6 changes: 4 additions & 2 deletions packages/contracts/script/DeployLiquidMultiTokenVault.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ contract DeployLiquidMultiTokenVault is TomlConfig {
owner: _tomlConfig.readAddress(".evm.address.owner"),
operator: _tomlConfig.readAddress(".evm.address.operator"),
upgrader: _tomlConfig.readAddress(".evm.address.upgrader"),
assetManager: _tomlConfig.readAddress(".evm.address.asset_manager")
assetManager: _tomlConfig.readAddress(".evm.address.asset_manager"),
assetReceiver: _tomlConfig.readAddress(".evm.address.custodian")
});
}

Expand Down Expand Up @@ -129,7 +130,8 @@ contract DeployLiquidMultiTokenVault is TomlConfig {
redeemOptimizer: redeemOptimizer,
vaultStartTimestamp: startTimestamp,
redeemNoticePeriod: 1,
contextParams: contextParams
contextParams: contextParams,
shouldCheckInvestorRole: true
});

return vaultParams;
Expand Down
86 changes: 70 additions & 16 deletions packages/contracts/src/yield/LiquidContinuousMultiTokenVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ contract LiquidContinuousMultiTokenVault is
address operator;
address upgrader;
address assetManager;
address assetReceiver;
}

struct VaultParams {
Expand All @@ -59,27 +60,32 @@ contract LiquidContinuousMultiTokenVault is
uint256 vaultStartTimestamp;
uint256 redeemNoticePeriod;
TripleRateContext.ContextParams contextParams;
bool shouldCheckInvestorRole;
}

IYieldStrategy public _yieldStrategy;
IRedeemOptimizer public _redeemOptimizer;
uint256 public _vaultStartTimestamp;

// [Jan-2025] added - must be after previous fields due to upgrading
bool public _shouldCheckInvestorRole = true;

uint256 private constant ZERO_REQUEST_ID = 0;

bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
bytes32 public constant ASSET_MANAGER_ROLE = keccak256("ASSET_MANAGER_ROLE");
bytes32 public constant ASSET_RECEIVER_ROLE = keccak256("ASSET_RECEIVER_ROLE");
bytes32 public constant INVESTOR_ROLE = keccak256("INVESTOR_ROLE"); // deposits and redeems

error LiquidContinuousMultiTokenVault__InvalidFrequency(uint256 frequency);
error LiquidContinuousMultiTokenVault__InvalidAuthAddress(string authName, address authAddress);
error LiquidContinuousMultiTokenVault__ControllerNotSender(address sender, address controller);
error LiquidContinuousMultiTokenVault__UnAuthorized(address sender, address authorizedOwner);
error LiquidContinuousMultiTokenVault__AmountMismatch(uint256 amount1, uint256 amount2);
error LiquidContinuousMultiTokenVault__UnlockPeriodMismatch(uint256 unlockPeriod1, uint256 unlockPeriod2);
error LiquidContinuousMultiTokenVault__InvalidComponentTokenAmount(
uint256 componentTokenAmount, uint256 unlockRequestedAmount
);
error LiquidContinuousMultiTokenVault__RedeemSharesMismatch(uint256 redeemShares, uint256 requestRedeemShares);
error LiquidContinuousMultiTokenVault__InvestorOnly(address sender, address account);

constructor() {
_disableInitializers();
Expand All @@ -96,10 +102,12 @@ contract LiquidContinuousMultiTokenVault is
_initRole("operator", OPERATOR_ROLE, vaultParams.vaultAuth.operator);
_initRole("upgrader", UPGRADER_ROLE, vaultParams.vaultAuth.upgrader);
_initRole("assetManager", ASSET_MANAGER_ROLE, vaultParams.vaultAuth.assetManager);
_initRole("assetReceiver", ASSET_RECEIVER_ROLE, vaultParams.vaultAuth.assetReceiver);

_yieldStrategy = vaultParams.yieldStrategy;
_redeemOptimizer = vaultParams.redeemOptimizer;
_vaultStartTimestamp = vaultParams.vaultStartTimestamp;
_shouldCheckInvestorRole = vaultParams.shouldCheckInvestorRole;

if (vaultParams.contextParams.frequency != 360 && vaultParams.contextParams.frequency != 365) {
revert LiquidContinuousMultiTokenVault__InvalidFrequency(vaultParams.contextParams.frequency);
Expand All @@ -119,6 +127,17 @@ contract LiquidContinuousMultiTokenVault is

// ===================== MultiTokenVault =====================

/// @inheritdoc MultiTokenVault
function deposit(uint256 assets, address receiver)
public
virtual
override
onlyInvestor(receiver)
returns (uint256 shares)
{
return super.deposit(assets, receiver);
}

/// @inheritdoc MultiTokenVault
function convertToSharesForDepositPeriod(uint256 assets, uint256 /* depositPeriod */ )
public
Expand All @@ -131,14 +150,26 @@ contract LiquidContinuousMultiTokenVault is
return assets; // 1 asset = 1 share
}

/// @inheritdoc MultiTokenVault
function redeemForDepositPeriod(uint256 shares, address receiver, address owner, uint256 depositPeriod)
public
virtual
override
onlyInvestor(owner)
onlyAuthorized(owner)
returns (uint256 assets)
{
return super.redeemForDepositPeriod(shares, receiver, owner, depositPeriod);
}

/// @inheritdoc MultiTokenVault
function redeemForDepositPeriod(
uint256 shares,
address receiver,
address owner,
uint256 depositPeriod,
uint256 redeemPeriod
) public virtual override returns (uint256 assets) {
) public virtual override onlyInvestor(owner) onlyAuthorized(owner) returns (uint256 assets) {
_unlock(owner, depositPeriod, redeemPeriod, shares);

return _redeemForDepositPeriodAfterUnlock(shares, receiver, owner, depositPeriod, redeemPeriod);
Expand Down Expand Up @@ -182,6 +213,7 @@ contract LiquidContinuousMultiTokenVault is
*/
function requestDeposit(uint256 assets, address controller, address owner)
public
onlyInvestor(owner)
onlyAuthorized(owner)
onlyController(controller)
returns (uint256 requestId_)
Expand All @@ -200,6 +232,7 @@ contract LiquidContinuousMultiTokenVault is
*/
function deposit(uint256 assets, address receiver, address controller)
public
onlyInvestor(receiver)
onlyController(controller)
returns (uint256 shares_)
{
Expand All @@ -216,6 +249,7 @@ contract LiquidContinuousMultiTokenVault is
*/
function requestRedeem(uint256 shares, address controller, address owner)
public
onlyInvestor(owner)
onlyAuthorized(owner)
onlyController(controller)
returns (uint256 requestId_)
Expand All @@ -237,27 +271,27 @@ contract LiquidContinuousMultiTokenVault is
*/
function redeem(uint256 shares, address receiver, address controller)
public
onlyInvestor(controller)
onlyController(controller)
returns (uint256 assets)
{
uint256 requestId = currentPeriod(); // requestId = redeemPeriod, and redeem can only be called where redeemPeriod = currentPeriod()

uint256 unlockRequestedAmount = unlockRequestAmount(controller, requestId);
if (shares != unlockRequestedAmount) {
revert LiquidContinuousMultiTokenVault__InvalidComponentTokenAmount(shares, unlockRequestedAmount);
revert LiquidContinuousMultiTokenVault__RedeemSharesMismatch(shares, unlockRequestedAmount);
}

(uint256[] memory depositPeriods, uint256[] memory sharesAtPeriods) = unlock(controller, requestId); // unlockPeriod = redeemPeriod

uint256 totalAssetsRedeemed = 0;

assets = 0;
for (uint256 i = 0; i < depositPeriods.length; ++i) {
totalAssetsRedeemed += _redeemForDepositPeriodAfterUnlock(
assets += _redeemForDepositPeriodAfterUnlock(
sharesAtPeriods[i], receiver, controller, depositPeriods[i], requestId
);
}
emit Withdraw(_msgSender(), receiver, controller, totalAssetsRedeemed, shares);
return totalAssetsRedeemed;
emit Withdraw(_msgSender(), receiver, controller, assets, shares);
return assets;
}

// Getter View Functions
Expand Down Expand Up @@ -359,8 +393,8 @@ contract LiquidContinuousMultiTokenVault is
* @param requestId Discriminator between non-fungible requests
* @return shares Amount of pending redeem shares for the given requestId and controller
*/
function pendingRedeemRequest(uint256 requestId, address /* controller */ ) public view returns (uint256 shares) {
return unlockRequestAmount(_msgSender(), requestId);
function pendingRedeemRequest(uint256 requestId, address controller) public view returns (uint256 shares) {
return unlockRequestAmount(controller, requestId);
}

/**
Expand Down Expand Up @@ -456,11 +490,13 @@ contract LiquidContinuousMultiTokenVault is
* @dev Withdraws the assets from out of vault for investment, i.e. in RWA.
* Only the Asset Manager can call this function.
*
* @param to The trusted address that will receive the assets, e.g. custodian
* @param assetReceiver The trusted address that will receive the assets, e.g. custodian
* @param amount The amount of the ERC-20 underlying assets to be withdrawn from the vault.
*/
function withdrawAsset(address to, uint256 amount) public onlyRole(ASSET_MANAGER_ROLE) {
_withdrawAssest(to, amount);
function withdrawAsset(address assetReceiver, uint256 amount) public onlyRole(ASSET_MANAGER_ROLE) {
_checkRole(ASSET_RECEIVER_ROLE, assetReceiver);

_withdrawAssest(assetReceiver, amount);
}

/**
Expand Down Expand Up @@ -506,6 +542,16 @@ contract LiquidContinuousMultiTokenVault is

// ===================== Utility =====================

// @dev enable/disable investor role checking
function setShouldCheckInvestorRole(bool shouldCheckInvestorRole_) public virtual onlyRole(OPERATOR_ROLE) {
_shouldCheckInvestorRole = shouldCheckInvestorRole_;
}

// @@inheritdoc ERC1155Upgradeable
function setURI(string memory newUri) public virtual onlyRole(OPERATOR_ROLE) {
_setURI(newUri);
}

/// minimum shares required to convert to assets and vice-versa.
function _minConversionThreshold() internal view returns (uint256 minConversionThreshold) {
return SCALE < 10 ? SCALE : 10;
Expand All @@ -517,6 +563,14 @@ contract LiquidContinuousMultiTokenVault is
_;
}

// @dev ensure that the account has the INVESTOR_ROLE
modifier onlyInvestor(address account) {
if (_shouldCheckInvestorRole && !hasRole(INVESTOR_ROLE, account)) {
revert LiquidContinuousMultiTokenVault__InvestorOnly(_msgSender(), account);
}
_;
}

// @dev ensure the controller is the caller
modifier onlyController(address controller) {
address caller = _msgSender();
Expand Down Expand Up @@ -545,6 +599,6 @@ contract LiquidContinuousMultiTokenVault is
}

function getVersion() public pure returns (uint256 version) {
return 2;
return 3;
}
}
1 change: 1 addition & 0 deletions packages/contracts/test/src/LiquidStoneNinetyDayTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ contract DeployLiquidStoneNinetyDay is DeployLiquidMultiTokenVault {
vaultParams.redeemNoticePeriod = 0;
vaultParams.contextParams.fullRateScaled = 10 * scale;
vaultParams.contextParams.initialReducedRate.interestRate = 0; // zero for less than tenor
vaultParams.shouldCheckInvestorRole = false;

return vaultParams;
}
Expand Down
Loading
Loading