// VaultA's positions are completely separate from VaultB's
balances[vaultA][manager1][positionId] = 100e18
balances[vaultB][manager1][positionId] = 200e18
// Only vaultA can modify its positions
modifier onlyAuthorized(address account) {
require(msg.sender == account); // msg.sender must be the vault
_;
}
// ❌ WRONG - allows anyone to deposit
function deposit(address account, address subaccount, bytes32 positionId, uint256 amount) external {
balances[account][subaccount][positionId] += amount;
}
// ✅ CORRECT - only vault can deposit to its namespace
function deposit(address account, address subaccount, bytes32 positionId, uint256 amount) external {
require(msg.sender == account, "Unauthorized");
balances[account][subaccount][positionId] += amount;
}
contract MyWrapperTest is Test {
MyWrapper wrapper;
MockERC20 asset;
MockVault vault;
address manager;
function setUp() public {
asset = new MockERC20("Test", "TEST", 18);
wrapper = new MyWrapper(address(asset), factory);
vault = new MockVault();
manager = makeAddr("manager");
}
function test_deposit_success() public {
uint256 amount = 100e18;
bytes32 positionId = bytes32(0);
// Setup
asset.mint(address(vault), amount);
vm.startPrank(address(vault));
asset.approve(address(wrapper), amount);
// Deposit
wrapper.deposit(address(vault), manager, positionId, amount);
// Verify
assertEq(wrapper.getBalance(address(vault), manager, positionId), amount);
assertEq(asset.balanceOf(address(wrapper)), amount);
}
function test_deposit_unauthorized() public {
vm.expectRevert();
vm.prank(manager); // manager tries to deposit directly
wrapper.deposit(address(vault), manager, bytes32(0), 100e18);
}
function test_withdraw_success() public {
// Deposit first
test_deposit_success();
// Withdraw
vm.prank(address(vault));
wrapper.withdraw(address(vault), manager, bytes32(0), manager, 50e18);
// Verify
assertEq(wrapper.getBalance(address(vault), manager, bytes32(0)), 50e18);
assertEq(asset.balanceOf(manager), 50e18);
}
function test_getValue_withOracle() public {
// Setup oracle
vm.mockCall(
address(oracleRegistry),
abi.encodeWithSelector(IOracleRegistry.getValueUSD.selector),
abi.encode(1000e18) // $1000 per token
);
// Deposit
test_deposit_success();
// Check value
uint256 value = wrapper.getValue(address(vault), manager, bytes32(0));
assertEq(value, 100_000e18); // 100 tokens * $1000 = $100k
}
}
function test_integration_withRealVault() public {
// Deploy real vault
FluxVault vault = fluxVaultFactory.createVault(
baseAsset,
baseAssetWrapper,
strategy,
accessPolicy,
"Test Vault",
"TV"
);
// Manager deposits via vault
vm.startPrank(manager);
vault.unlock(abi.encode("deposit", address(wrapper), 100e18));
vm.stopPrank();
// Verify position tracked correctly
assertGt(wrapper.getValue(address(vault), manager, WORKING_CAPITAL_POS_ID), 0);
}
// Deploy via factory (for standard ERC20/ERC4626)
address wrapper = assetWrapperFactory.deployERC20Wrapper(tokenAddress);
// Deploy custom wrapper
MyCustomWrapper wrapper = new MyCustomWrapper(asset, factory);
// Governance whitelists the wrapper
fluxVaultFactory.whitelistAsset(address(wrapper));
// Strategy creator includes wrapper in allowlist
address[] memory allowedAssets = [
wethWrapper,
usdcWrapper,
myCustomWrapper // Your new wrapper
];
address strategy = strategyFactory.deployImmutableStrategy(
allowedAssets,
params,
factory
);
// Create vault using strategy with your wrapper
address vault = fluxVaultFactory.createVault(
baseAsset,
baseAssetWrapper,
strategy,
accessPolicy,
"My Vault",
"MV"
);
// Manager can now use your wrapper
// ✅ GOOD: Pack storage
struct Position {
uint128 balance; // Pack multiple values in one slot
uint64 lastUpdate;
uint64 reserved;
}
// ✅ GOOD: Cache storage reads
function _withdraw(...) internal {
uint256 balance = balances[account][subaccount][positionId]; // Cache
if (amount == type(uint256).max) amount = balance;
balances[account][subaccount][positionId] = balance - amount; // Write once
}
// ❌ BAD: Multiple storage reads
function _withdraw(...) internal {
if (amount == type(uint256).max) amount = balances[...];
balances[...] = balances[...] - amount; // Read twice, write once
}
// ✅ GOOD: Use immutable for constants
IFluxVaultFactory public immutable factory;
// ❌ BAD: Use storage for constants
IFluxVaultFactory public factory;
// Manager deposits to custom position
vault.locked_depositToWrapper(wrapper, keccak256("my-position"), 100e18);
// Strategy only values WORKING_CAPITAL and BOND positions → 0 value!
function _validatePositionId(bytes32 positionId) private pure {
if (positionId != WORKING_CAPITAL_POS_ID && positionId != BOND_POS_ID) {
revert InvalidPositionId();
}
}