Skip to content

Commit

Permalink
Merge branch 'master' into feat/remove-token-function
Browse files Browse the repository at this point in the history
  • Loading branch information
0xethsign authored Jan 24, 2025
2 parents e429b99 + 8ad85f0 commit c7792c4
Show file tree
Hide file tree
Showing 9 changed files with 446 additions and 23 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# Lucidly DeFi Vault Protocol

We're change onchain vault experiences as you see it today. This is the working repo.
We're changing onchain vault experiences as you see it today. This is the working repo.
Binary file added audits/PAG-July.pdf
Binary file not shown.
Binary file added audits/PAG-May.pdf
Binary file not shown.
105 changes: 105 additions & 0 deletions script/DeployRingsVault.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {Script} from "forge-std/Script.sol";

import {Pool} from "../src/Pool.sol";
import {PoolToken} from "../src/PoolToken.sol";
import {Vault} from "../src/Vault.sol";
import {MockToken} from "../src/Mocks/MockToken.sol";
import {IRateProvider} from "../src/RateProvider/IRateProvider.sol";
import {RingsVaultRateProvider} from "../src/RateProvider/sonic-rings/RingsVaultRateProvider.sol";
import {PoolOwner} from "../src/PoolOwner.sol";

contract RingsVaultDeploymentScript is Script {
PoolToken poolToken;
Pool pool;
Vault vault;
PoolOwner ownerContract;
IRateProvider rateProvider;

address private ADMIN_ADDRESS = 0x1b514df3413DA9931eB31f2Ab72e32c0A507Cad5;

address private constant USDC_BRIDGED = 0x29219dd400f2Bf60E5a23d13Be72B486D4038894;
address private constant SCUSD = 0xd3DCe716f3eF535C5Ff8d041c1A41C3bd89b97aE;
address private constant SCUSD_USDC_REDSTONE_FEED = 0xb81131B6368b3F0a83af09dB4E39Ac23DA96C2Db;

MockToken token0 = MockToken(USDC_BRIDGED);
MockToken token1 = MockToken(SCUSD);

uint256 private PRECISION = 1e18;

function run() public {
uint256 adminPk = vm.envUint("PRIVATE_KEY_1");
vm.startBroadcast(adminPk);

rateProvider = new RingsVaultRateProvider();

address[] memory tokens = new address[](2);
uint256[] memory weights = new uint256[](2);
address[] memory rateProviders = new address[](2);

// tokens
tokens[0] = address(token0);
tokens[1] = address(token1);

// set weights
weights[0] = 40 * PRECISION / 100;
weights[1] = 60 * PRECISION / 100;

// set rateProviders
rateProviders[0] = address(rateProvider);
rateProviders[1] = address(rateProvider);

address admin = vm.addr(adminPk);
poolToken = new PoolToken("**", "**", 18, admin);
pool = new Pool(address(poolToken), 450 * PRECISION, tokens, rateProviders, weights, admin);
vault = new Vault(address(poolToken), "lucidly rings vault share", "lcdRingsUSD", 100, admin, admin);
ownerContract = new PoolOwner(address(pool));

poolToken.setPool(address(pool));
pool.setVaultAddress(address(vault));
pool.setSwapFeeRate(3 * PRECISION / 10_000); // 3bps
vault.setDepositFeeInBps(100);
vault.setProtocolFeeAddress(admin);

vm.stopBroadcast();
}
}

contract RingsVaultSeedingScript is Script {
PoolToken poolToken = PoolToken(0xa93C9411f8FeCF5E6aCd81ECd99a71C165d48c4D);
Pool pool = Pool(0xc8291D518fE771B5612Ecc0D6A99D5DC03db3DD8);
Vault vault = Vault(0xedEa2647CfE580c9B6f2148C270f9aaE6B08bcA5);
PoolOwner ownerContract = PoolOwner(0x2210A9357D51fF909EAa43570b3F1275E76cB6d6);
IRateProvider rateProvider = IRateProvider(0xa633C15E09cA2a8DBB6CD52aae915a3b379dEEb3);

address private ADMIN_ADDRESS = 0x1b514df3413DA9931eB31f2Ab72e32c0A507Cad5;

address private constant USDC_BRIDGED = 0x29219dd400f2Bf60E5a23d13Be72B486D4038894;
address private constant SCUSD = 0xd3DCe716f3eF535C5Ff8d041c1A41C3bd89b97aE;
address private constant SCUSD_USDC_REDSTONE_FEED = 0xb81131B6368b3F0a83af09dB4E39Ac23DA96C2Db;

MockToken token0 = MockToken(USDC_BRIDGED);
MockToken token1 = MockToken(SCUSD);

uint256 private PRECISION = 1e18;

function run() public {
uint256 adminPk = vm.envUint("PRIVATE_KEY_1");
vm.startBroadcast(adminPk);
address admin = vm.addr(adminPk);

uint256[] memory amounts = new uint256[](2);
amounts[0] = 4 * 10 ** (token0.decimals());
amounts[1] = 6 * 10 ** (token1.decimals());

require(token0.approve(address(pool), type(uint256).max), "pool cannot spend USDC.e");
require(token1.approve(address(pool), type(uint256).max), "pool cannot spend scUSD");
uint256 lp = pool.addLiquidity(amounts, 0, admin);
require(poolToken.approve(address(vault), type(uint256).max), "vault cannot spend poolToken");
vault.deposit(lp, admin);

vm.stopBroadcast();
}
}
75 changes: 72 additions & 3 deletions src/Aggregator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {LibSort} from "solady/utils/LibSort.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol";

import {Pool} from "../src/Pool.sol";
import {Pool} from "../src/Poolv2.sol";
import {PoolToken} from "../src/PoolToken.sol";
import {Vault} from "../src/Vault.sol";
import {MockToken} from "../src/Mocks/MockToken.sol";
Expand Down Expand Up @@ -36,7 +36,7 @@ contract Aggregator {
shares = Vault(vaultAddress).deposit(lpReceived, receiver);
}

function depositFor(
function depositFromRouter(
address[] calldata tokens,
uint256[] calldata tokenAmounts,
address receiver,
Expand All @@ -46,16 +46,85 @@ contract Aggregator {
require(tokens.length == tokenAmounts.length, "tokens and tokenAmounts should be of same length");

for (uint256 i = 0; i < tokenAmounts.length; i++) {
// SafeTransferLib.safeTransferFrom(tokens[i], msg.sender, address(this), tokenAmounts[i]);
ERC20(tokens[i]).approve(poolAddress, tokenAmounts[i]);
}

address vaultAddress = Pool(poolAddress).vaultAddress();
uint256 lpReceived = Pool(poolAddress).addLiquidity(tokenAmounts, minLpAmount, address(this));
PoolToken(Pool(poolAddress).tokenAddress()).approve(vaultAddress, lpReceived);
shares = Vault(vaultAddress).deposit(lpReceived, receiver);
}

function depositSingle(
uint256 tokenIndex,
uint256 tokenAmount,
address receiver,
uint256 minLpAmount,
address poolAddress
) external returns (uint256 shares) {
address token = Pool(poolAddress).tokens(tokenIndex);
SafeTransferLib.safeTransferFrom(token, msg.sender, address(this), tokenAmount);
ERC20(token).approve(poolAddress, tokenAmount);

uint256 numTokens = Pool(poolAddress).numTokens();
uint256[] memory addLiquidityAmounts = new uint256[](numTokens);
addLiquidityAmounts[tokenIndex] = tokenAmount;
address vaultAddress = Pool(poolAddress).vaultAddress();
uint256 lpReceived = Pool(poolAddress).addLiquidity(addLiquidityAmounts, minLpAmount, address(this));
PoolToken(Pool(poolAddress).tokenAddress()).approve(vaultAddress, lpReceived);
shares = Vault(vaultAddress).deposit(lpReceived, receiver);
}

function depositSingleFromRouter(
uint256 tokenIndex,
uint256 tokenAmount,
address receiver,
uint256 minLpAmount,
address poolAddress
) external returns (uint256 shares) {
address token = Pool(poolAddress).tokens(tokenIndex);
ERC20(token).approve(poolAddress, tokenAmount);

uint256 numTokens = Pool(poolAddress).numTokens();
uint256[] memory addLiquidityAmounts = new uint256[](numTokens);
addLiquidityAmounts[tokenIndex] = tokenAmount;
address vaultAddress = Pool(poolAddress).vaultAddress();
uint256 lpReceived = Pool(poolAddress).addLiquidity(addLiquidityAmounts, minLpAmount, address(this));
PoolToken(Pool(poolAddress).tokenAddress()).approve(vaultAddress, lpReceived);
shares = Vault(vaultAddress).deposit(lpReceived, receiver);
}

function depositFor(
address[] calldata tokens,
uint256[] calldata tokenAmounts,
address receiver,
uint256 minLpAmount,
address poolAddress
) external returns (uint256 shares) {
require(tokens.length == tokenAmounts.length, "tokens and tokenAmounts should be of same length");
address vaultAddress = Pool(poolAddress).vaultAddress();
uint256 lpReceived = Pool(poolAddress).addLiquidityFor(tokenAmounts, minLpAmount, msg.sender, address(this));
PoolToken(Pool(poolAddress).tokenAddress()).approve(vaultAddress, lpReceived);
shares = Vault(vaultAddress).deposit(lpReceived, receiver);
}

function depositForSingle(
uint256 tokenIndex,
uint256 tokenAmount,
address receiver,
uint256 minLpAmount,
address poolAddress
) external returns (uint256 shares) {
address vaultAddress = Pool(poolAddress).vaultAddress();
uint256 numTokens = Pool(poolAddress).numTokens();
uint256[] memory addLiquidityAmounts = new uint256[](numTokens);
addLiquidityAmounts[tokenIndex] = tokenAmount;
uint256 lpReceived =
Pool(poolAddress).addLiquidityFor(addLiquidityAmounts, minLpAmount, msg.sender, address(this));
PoolToken(Pool(poolAddress).tokenAddress()).approve(vaultAddress, lpReceived);
shares = Vault(vaultAddress).deposit(lpReceived, receiver);
}

function redeemBalanced(
address poolAddress,
uint256 sharesToBurn,
Expand Down
1 change: 1 addition & 0 deletions src/Poolv2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ contract PoolV2 is OwnableRoles, ReentrancyGuard {
/// @notice deposit tokens into the pool
/// @param amounts_ array of the amount for each token to take from caller
/// @param minLpAmount_ minimum amount of lp tokens to mint
/// @param owner_ address from which to add liquidity from
/// @param receiver_ account to receive the lp tokens
/// @return amount of LP tokens minted
function addLiquidityFor(uint256[] calldata amounts_, uint256 minLpAmount_, address owner_, address receiver_)
Expand Down
33 changes: 33 additions & 0 deletions src/RateProvider/sonic-rings/RingsVaultRateProvider.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT
pragma solidity >0.8.0;

import {IRateProvider} from "../IRateProvider.sol";
import {FixedPointMathLib} from "../../../lib/solady/src/utils/FixedPointMathLib.sol";
import {AggregatorV3Interface, RedstoneDataFeedLib} from "../libraries/RedstoneDataFeedLib.sol";

contract RingsVaultRateProvider is IRateProvider {
using RedstoneDataFeedLib for AggregatorV3Interface;
using FixedPointMathLib for uint256;

error RateProvider__InvalidParam();

uint256 private constant PRECISION = 1e18;

address private constant USDC_BRIDGED = 0x29219dd400f2Bf60E5a23d13Be72B486D4038894;
address private constant SCUSD = 0xd3DCe716f3eF535C5Ff8d041c1A41C3bd89b97aE;
address private constant SCUSD_USDC_REDSTONE_FEED = 0xb81131B6368b3F0a83af09dB4E39Ac23DA96C2Db;

/// @dev hardcode price of usdc.e to PRECISION
function rate(address token) external view returns (uint256) {
if (token == USDC_BRIDGED) {
return PRECISION;
} else if (token == SCUSD) {
AggregatorV3Interface feed = AggregatorV3Interface(SCUSD_USDC_REDSTONE_FEED);
uint256 priceInUsdc = feed.getPrice();
uint256 decimals = feed.getDecimals();
return priceInUsdc.mulWad(10 ** (36 - decimals));
} else {
revert RateProvider__InvalidParam();
}
}
}
96 changes: 90 additions & 6 deletions tests/Aggregator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,14 @@ contract AggregatorTest is Test {
vm.stopPrank();

uint256 unadjustedRate = IRateProvider(rateProvider).rate(token); // price of the asset scaled to 18 precision
uint256 amount =
(total * weights[i] * 1e18 * 1e10) / (unadjustedRate * (10 ** (36 - ERC20(token).decimals())));
amounts[i] = amount;

// uint256 amount =
// (total * weights[i] * 1e18 * 1e10) / (unadjustedRate * (10 ** (36 - ERC20(token).decimals())));
// amounts[i] = amount;

amounts[i] = FixedPointMathLib.divUp(
FixedPointMathLib.mulDiv(total, weights[i], unadjustedRate), (10 ** (18 - ERC20(token).decimals()))
);
}
return amounts;
}
Expand All @@ -146,7 +151,7 @@ contract AggregatorTest is Test {
uint256 numTokens = pool.numTokens();

uint256[] memory amounts1 = new uint256[](numTokens);
uint256 total1 = 100 * 1e8;
uint256 total1 = 100 * 1e18;

amounts1 = _calculateSeedAmounts(total1);

Expand All @@ -163,7 +168,7 @@ contract AggregatorTest is Test {
function test__deposit() public {
uint256 numTokens = pool.numTokens();
uint256[] memory amounts = new uint256[](numTokens);
uint256 total1 = 100 * 1e8;
uint256 total1 = 100 * 1e18;
amounts = _calculateSeedAmounts(total1);

// approve agg as spender
Expand All @@ -183,10 +188,46 @@ contract AggregatorTest is Test {
assert(shares == (vault.balanceOf(alice) - sharesOfAlice));
}

function test__depositSingle() public {
uint256 numTokens = pool.numTokens();
uint256[] memory amounts = new uint256[](numTokens);
uint256[] memory amountsEstimated = new uint256[](numTokens);
uint256 total1 = 100 * 1e18;
amounts = _calculateSeedAmounts(total1);
amountsEstimated[0] = amounts[0];
amounts[1] = 0;
amounts[2] = 0;
amountsEstimated[1] = 0;
amountsEstimated[2] = 0;

uint256 ss = vm.snapshotState();

vm.startPrank(alice);
uint256 lpTokens = pool.addLiquidity(amountsEstimated, 0, alice);
poolToken.approve(address(vault), lpTokens);
uint256 sharesEstimated = vault.deposit(lpTokens, alice);
vm.stopPrank();

vm.revertToState(ss);

vm.startPrank(alice);
require(ERC20(pool.tokens(0)).approve(address(agg), type(uint256).max), "could not approve");
vm.stopPrank();

uint256 sharesOfAlice = vault.balanceOf(alice);

vm.startPrank(alice);
uint256 shares = agg.depositSingle(0, amounts[0], alice, 0, address(pool));
vm.stopPrank();

assert(shares == (vault.balanceOf(alice) - sharesOfAlice));
assert(sharesEstimated == shares);
}

function test__depositFor() public {
uint256 numTokens = pool.numTokens();
uint256[] memory amounts = new uint256[](numTokens);
uint256 total1 = 100 * 1e8;
uint256 total1 = 100 * 1e18;
amounts = _calculateSeedAmounts(total1);

// approve agg as spender
Expand All @@ -197,6 +238,9 @@ contract AggregatorTest is Test {
vm.stopPrank();
}

vm.startPrank(alice);
require(ERC20(pool.tokens(0)).approve(address(agg), type(uint256).max), "could not approve");
vm.stopPrank();
uint256 sharesOfAlice = vault.balanceOf(alice);

vm.startPrank(alice);
Expand All @@ -206,6 +250,46 @@ contract AggregatorTest is Test {
assert(shares == (vault.balanceOf(alice) - sharesOfAlice));
}

function test__depositForSingle() public {
uint256 numTokens = pool.numTokens();
uint256[] memory amounts = new uint256[](numTokens);
uint256[] memory amountsEstimated = new uint256[](numTokens);
uint256 total1 = 100 * 1e18;
amounts = _calculateSeedAmounts(total1);
amountsEstimated[0] = amounts[0];
amounts[1] = 0;
amounts[2] = 0;
amountsEstimated[1] = 0;
amountsEstimated[2] = 0;

uint256 ss = vm.snapshotState();

vm.startPrank(alice);
uint256 lpTokens = pool.addLiquidityFor(amountsEstimated, 0, alice, alice);
poolToken.approve(address(vault), lpTokens);
uint256 sharesEstimated = vault.deposit(lpTokens, alice);
vm.stopPrank();

vm.revertToState(ss);

// approve agg as spender
for (uint256 i = 0; i < numTokens; i++) {
address token = tokens[i];
vm.startPrank(alice);
require(ERC20(token).approve(address(agg), type(uint256).max), "could not approve");
vm.stopPrank();
}

uint256 sharesOfAlice = vault.balanceOf(alice);

vm.startPrank(alice);
uint256 shares = agg.depositForSingle(0, amounts[0], alice, 0, address(pool));
vm.stopPrank();

assert(shares == (vault.balanceOf(alice) - sharesOfAlice));
assert(sharesEstimated == shares);
}

function test__redeemBalanced() public {
PoolEstimator estimator = new PoolEstimator(address(pool));
uint256 aliceShares = vault.balanceOf(alice);
Expand Down
Loading

0 comments on commit c7792c4

Please sign in to comment.