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

Feature/mint #2

Open
wants to merge 20 commits into
base: main
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
10 changes: 10 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,13 @@
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/vectorized/solady
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
[submodule "lib/token"]
path = lib/token
url = [email protected]:Kwenta/token.git
branch = remove-inflation-escrow
5 changes: 5 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ optimizer_runs = 1_000_000
[fmt]
line_length = 80
number_underscore = "thousands"
multiline_func_header = "all"
sort_imports = true
contract_new_lines = true
override_spacing = false
wrap_comments = true

[rpc_endpoints]
mainnet = "${MAINNET_RPC_URL}"
Expand Down
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts-upgradeable
1 change: 1 addition & 0 deletions lib/solady
Submodule solady added at 678c91
1 change: 1 addition & 0 deletions lib/token
Submodule token added at 48a2b1
3 changes: 2 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
@token/=lib/token/contracts/
92 changes: 26 additions & 66 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -1,81 +1,41 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.25;

// TODO: adapt deploy script to deploy the KSXVault contract
/*
import {BaseGoerliParameters} from
"script/utils/parameters/BaseGoerliParameters.sol";
import {BaseParameters} from "script/utils/parameters/BaseParameters.sol";
// proxy
import {ERC1967Proxy as Proxy} from
"lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol";

// contracts
import {KSXVault} from "src/KSXVault.sol";

// parameters
import {OptimismGoerliParameters} from
"script/utils/parameters/OptimismGoerliParameters.sol";
import {OptimismParameters} from
"script/utils/parameters/OptimismParameters.sol";

// forge utils
import {Script} from "lib/forge-std/src/Script.sol";
import {Counter} from "src/Counter.sol";

/// @title Kwenta deployment script
/// @title Kwenta KSX deployment script
/// @author Flocqst ([email protected])
contract Setup is Script {
function deploySystem() public returns (address) {
Counter counter = new Counter();
return address(counter);
}
}

/// @dev steps to deploy and verify on Base:
/// (1) load the variables in the .env file via `source .env`
/// (2) run `forge script script/Deploy.s.sol:DeployBase --rpc-url $BASE_RPC_URL --etherscan-api-key $BASESCAN_API_KEY --broadcast --verify -vvvv`
contract DeployBase is Setup, BaseParameters {
function run() public {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(privateKey);

Setup.deploySystem();

vm.stopBroadcast();
}
}

/// @dev steps to deploy and verify on Base Goerli:
/// (1) load the variables in the .env file via `source .env`
/// (2) run `forge script script/Deploy.s.sol:DeployBaseGoerli --rpc-url $BASE_GOERLI_RPC_URL --etherscan-api-key $BASESCAN_API_KEY --broadcast --verify -vvvv`
contract DeployBaseGoerli is Setup, BaseGoerliParameters {
function run() public {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(privateKey);

Setup.deploySystem();

vm.stopBroadcast();
function deploySystem(
address token,
address stakingRewards,
uint8 decimalOffset
)
public
returns (KSXVault ksxVault)
{
ksxVault = new KSXVault(token, stakingRewards, decimalOffset);

// deploy ERC1967 proxy and set implementation to ksxVault
Proxy proxy = new Proxy(address(ksxVault), "");

// "wrap" proxy in IKSXVault interface
ksxVault = KSXVault(address(proxy));
}
}

/// @dev steps to deploy and verify on Optimism:
/// (1) load the variables in the .env file via `source .env`
/// (2) run `forge script script/Deploy.s.sol:DeployOptimism --rpc-url $OPTIMISM_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY --broadcast --verify -vvvv`
contract DeployOptimism is Setup, OptimismParameters {
function run() public {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(privateKey);

Setup.deploySystem();

vm.stopBroadcast();
}
}

/// @dev steps to deploy and verify on Optimism Goerli:
/// (1) load the variables in the .env file via `source .env`
/// (2) run `forge script script/Deploy.s.sol:DeployOptimismGoerli --rpc-url $OPTIMISM_GOERLI_RPC_URL --etherscan-api-key $OPTIMISM_ETHERSCAN_API_KEY --broadcast --verify -vvvv`

contract DeployOptimismGoerli is Setup, OptimismGoerliParameters {
function run() public {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(privateKey);

Setup.deploySystem();

vm.stopBroadcast();
}
}
*/
4 changes: 0 additions & 4 deletions script/utils/parameters/BaseGoerliParameters.sol

This file was deleted.

4 changes: 0 additions & 4 deletions script/utils/parameters/BaseParameters.sol

This file was deleted.

12 changes: 11 additions & 1 deletion script/utils/parameters/OptimismGoerliParameters.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.25;

contract OptimismGoerliParameters {}
contract OptimismGoerliParameters {

/// @dev this is an EOA used on testnet only
address public constant PDAO = 0x1b4fCFE451A15218aEeC811B508B4aa3f2A35904;

// https://developers.circle.com/stablecoins/docs/usdc-on-test-networks#usdc-on-op-goerli
address public constant USDC = 0xe05606174bac4A6364B31bd0eCA4bf4dD368f8C6;

address public constant KWENTA = 0x920Cf626a271321C151D027030D5d08aF699456b;

}
12 changes: 11 additions & 1 deletion script/utils/parameters/OptimismParameters.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.25;

contract OptimismParameters {}
contract OptimismParameters {

address public constant PDAO = 0xe826d43961a87fBE71C91d9B73F7ef9b16721C07;

// https://optimistic.etherscan.io/token/0x0b2c639c533813f4aa9d7837caf62653d097ff85
address public constant USDC = 0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85;

// https://optimistic.etherscan.io/token/0x920cf626a271321c151d027030d5d08af699456b
address public constant KWENTA = 0x920Cf626a271321C151D027030D5d08aF699456b;

}
173 changes: 169 additions & 4 deletions src/KSXVault.sol
Original file line number Diff line number Diff line change
@@ -1,16 +1,181 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.25;

import {ERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {ERC4626} from
"@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol";
import {ERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from
"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IStakingRewardsV2} from "@token/interfaces/IStakingRewardsV2.sol";

/// @title Kwenta Example Contract
/// @title KSXVault Contract
/// @notice KSX ERC4626 Vault
/// @author Flocqst ([email protected])
contract KSXVault is ERC4626 {
constructor(address _token)

/*//////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////*/

/// @notice Decimal offset used for calculating the conversion rate between
/// KWENTA and KSX.
/// @dev Set to 3 to ensure the initial fixed ratio of 1,000 KSX per KWENTA
/// further protect against inflation attacks
/// (https://docs.openzeppelin.com/contracts/4.x/erc4626#inflation-attack)
uint8 public immutable offset;

/// @notice Kwenta's StakingRewards contract
IStakingRewardsV2 internal immutable STAKING_REWARDS;

/// @notice KWENTA TOKEN
/// @dev The underlying asset of this vault
ERC20 private immutable KWENTA;

/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/

/// @notice Constructs the KSXVault contract
/// @param _token Kwenta token address
/// @param _stakingRewards Kwenta v2 staking rewards contract
/// @param _offset offset in the decimal representation between the
/// underlying asset's decimals and the vault decimals
constructor(
address _token,
address _stakingRewards,
uint8 _offset
)
ERC4626(IERC20(_token))
ERC20("KSX Vault", "KSX")
{}
{
offset = _offset;
STAKING_REWARDS = IStakingRewardsV2(_stakingRewards);
KWENTA = ERC20(_token);
}

/// @notice Returns the decimal offset for the vault
/// @dev This function is used internally by the ERC4626 implementation
/// @return The decimal offset value
function _decimalsOffset() internal view virtual override returns (uint8) {
return offset;
}

/// @notice Returns the total amount of underlying assets held by the vault
/// @dev Overrides the ERC4626 totalAssets function to include staked
/// assets.
/// Since all assets are automatically staked after deposit/mint operations,
/// we only need to return the staked balance.
/// @return The total amount of assets in the vault (all staked)
function totalAssets() public view virtual override returns (uint256) {
return STAKING_REWARDS.balanceOf(address(this));
}

/*//////////////////////////////////////////////////////////////
DEPOSIT/MINT FUNCTIONS
//////////////////////////////////////////////////////////////*/

/// @notice Deposit assets into the vault
/// @dev Overrides the ERC4626 deposit function to include reward collection
/// and staking
/// @param assets The amount of assets to deposit
/// @param receiver The address to receive the minted shares
/// @return shares The amount of shares minted
function deposit(
uint256 assets,
address receiver
)
public
virtual
override
returns (uint256)
{
uint256 shares = super.deposit(assets, receiver);
_collectAndStakeRewards();
return shares;
}

/// @dev Disabled to enforce deposit/redeem pattern. Use deposit() instead.
function mint(uint256, address) public virtual override returns (uint256) {
revert("Disabled");
}

/*//////////////////////////////////////////////////////////////
WITHDRAW/REDEEM FUNCTIONS
//////////////////////////////////////////////////////////////*/

/// @dev Disabled to enforce deposit/redeem pattern. Use redeem() instead.
function withdraw(
uint256,
address,
address
)
public
virtual
override
returns (uint256)
{
revert("Disabled");
}

/// @notice Internal withdrawal mechanism for both withdraw and redeem
/// operations
/// @dev Overrides ERC4626 _withdraw to handle unstaking before token
/// transfers.
/// Order of operations is important:
/// 1. Burn shares (maintaining ratio)
/// 2. Unstake tokens
/// 3. Transfer tokens to receiver
/// @param caller Address that initiated the withdrawal
/// @param receiver Address that will receive the tokens
/// @param owner Address that owns the shares
/// @param assets Amount of underlying tokens to withdraw
/// @param shares Amount of shares to burn
function _withdraw(
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
)
internal
virtual
override
{
// First check and burn shares
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}
_burn(owner, shares);

// Then unstake corresponding assets
_unstakeKWENTA(assets);

// Finally transfer assets
SafeERC20.safeTransfer(KWENTA, receiver, assets);

emit Withdraw(caller, receiver, owner, assets, shares);
}

/*//////////////////////////////////////////////////////////////
STAKING MANAGEMENT
//////////////////////////////////////////////////////////////*/

/// @notice Collect rewards and stake all available KWENTA
/// @dev This function is called after every deposit and mint operation
function _collectAndStakeRewards() internal {
STAKING_REWARDS.getReward();

uint256 totalToStake = KWENTA.balanceOf(address(this));
if (totalToStake > 0) {
STAKING_REWARDS.stake(totalToStake);
}
}

/// @notice Unstake KWENTA tokens
/// @dev This function is called before withdrawals and redemptions
/// @param kwentaAmount The amount of KWENTA to unstake
function _unstakeKWENTA(uint256 kwentaAmount) internal {
STAKING_REWARDS.unstake(kwentaAmount);
}

}
Loading
Loading