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

[PDE-381] [test] [fix] increase coverage, remove holders #132

Merged
merged 3 commits into from
Dec 20, 2024
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
3 changes: 0 additions & 3 deletions smart-wallets/src/extensions/AssetVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,6 @@ contract AssetVault is WalletUtils, IAssetVault {
* @param amount Amount of AssetTokens included in this yield allowance
* @param expiration Timestamp at which the yield expires
*/



function acceptYieldAllowance(IAssetToken assetToken, uint256 amount, uint256 expiration) external {
AssetVaultStorage storage $ = _getAssetVaultStorage();
address beneficiary = msg.sender;
Expand Down
10 changes: 5 additions & 5 deletions smart-wallets/src/interfaces/IYieldDistributionToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ interface IYieldDistributionToken is IERC20 {

/// @notice Indicates a failure because a yield deposit is made in the same block as the last one
error DepositSameBlock();

/**
* @notice Indicates a failure because the transfer of CurrencyToken failed
* @param user Address of the user whose transfer failed
* @param currencyTokenAmount Amount of CurrencyToken to be transferred
*/
* @notice Indicates a failure because the transfer of CurrencyToken failed
* @param user Address of the user whose transfer failed
* @param currencyTokenAmount Amount of CurrencyToken to be transferred
*/
error TransferFailed(address user, uint256 currencyTokenAmount);

/**
Expand Down
2 changes: 1 addition & 1 deletion smart-wallets/src/mocks/MockAssetToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ contract MockAssetToken is IAssetToken, ERC20Upgradeable, OwnableUpgradeable {

function pendingYield(
address user
) external override view returns (uint256) {
) external view override returns (uint256) {
// Mock implementation for testing
}

Expand Down
2 changes: 1 addition & 1 deletion smart-wallets/src/mocks/MockInvalidAssetToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ contract MockInvalidAssetToken is IAssetToken {

function pendingYield(
address user
) external override view returns (uint256) {
) external view override returns (uint256) {
// Mock implementation for testing
}

Expand Down
24 changes: 4 additions & 20 deletions smart-wallets/src/token/AssetToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ contract AssetToken is WalletUtils, YieldDistributionToken, IAssetToken {
uint256 totalValue;
/// @dev Mapping of whitelisted users
mapping(address user => bool whitelisted) isWhitelisted;
/// @dev List of all users that have ever held AssetTokens
address[] holders;
/// @dev Mapping of all users that have ever held AssetTokens
mapping(address user => bool held) hasHeld;
}

// keccak256(abi.encode(uint256(keccak256("plume.storage.AssetToken")) - 1)) & ~bytes32(uint256(0xff))
Expand Down Expand Up @@ -120,6 +116,10 @@ contract AssetToken is WalletUtils, YieldDistributionToken, IAssetToken {
uint256 totalValue_,
bool isWhitelistEnabled_
) YieldDistributionToken(owner, name, symbol, currencyToken, decimals_, tokenURI_) {
if (address(currencyToken) == address(0)) {
revert InvalidAddress();
}

AssetTokenStorage storage $ = _getAssetTokenStorage();
$.totalValue = totalValue_;
isWhitelistEnabled = isWhitelistEnabled_;
Expand Down Expand Up @@ -293,22 +293,6 @@ contract AssetToken is WalletUtils, YieldDistributionToken, IAssetToken {
return _getAssetTokenStorage().isWhitelisted[user];
}

/// @notice List of all users that have ever held AssetTokens
function getHolders() external view returns (address[] memory) {
return _getAssetTokenStorage().holders;
}

/**
* @notice Check if the user has ever held AssetTokens
* @param user Address of the user to check
* @return held Boolean indicating if the user has ever held AssetTokens
*/
function hasBeenHolder(
address user
) external view returns (bool held) {
return _getAssetTokenStorage().hasHeld[user];
}

/// @notice Price of an AssetToken based on its total value and total supply
function getPricePerToken() external view returns (uint256) {
return _getAssetTokenStorage().totalValue / totalSupply();
Expand Down
163 changes: 123 additions & 40 deletions smart-wallets/test/AssetToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,6 @@ contract AssetTokenTest is Test {
address public user2;
address public walletProxyAddress;

// Events for testing
event Deposited(address indexed user, uint256 currencyTokenAmount);

// small hack to be excluded from coverage report
// function test_() public { }

function setUp() public {
owner = ADMIN_ADDRESS;
user1 = address(0x1);
Expand Down Expand Up @@ -116,7 +110,6 @@ contract AssetTokenTest is Test {
assertEq(assetToken.name(), "Asset Token", "Name mismatch");
assertEq(assetToken.symbol(), "AT", "Symbol mismatch");
assertEq(assetToken.decimals(), 18, "Decimals mismatch");
//assertEq(assetToken.tokenURI_(), "http://example.com/token", "TokenURI mismatch");
assertEq(assetToken.totalSupply(), 10_000 * 10 ** 18, "Total supply mismatch");
assertEq(assetToken.getTotalValue(), 10_000 * 10 ** 18, "Total value mismatch");
assertFalse(assetToken.isWhitelistEnabled(), "Whitelist should be enabled");
Expand Down Expand Up @@ -184,18 +177,30 @@ contract AssetTokenTest is Test {
vm.stopPrank();
}

//TODO: convert to SmartWalletCall
function test_GetBalanceAvailable() public {
// Create new MockSmartWallet instance
MockSmartWallet mockWallet = new MockSmartWallet();
uint256 totalBalance = 1000 * 10 ** 18;
uint256 lockedBalance = 300 * 10 ** 18;

vm.startPrank(address(testWalletImplementation));

uint256 balance = 1000 * 10 ** 18;
assetToken.addToWhitelist(user1);
assetToken.mint(user1, balance);
// Setup the mock wallet with tokens
assetToken.addToWhitelist(address(mockWallet));
assetToken.mint(address(mockWallet), totalBalance);

assertEq(assetToken.getBalanceAvailable(user1), balance);
// Lock some tokens
mockWallet.lockTokens(IAssetToken(address(assetToken)), lockedBalance);
vm.stopPrank();
// Note: To fully test getBalanceAvailable, you would need to mock a SmartWallet
// contract that implements the ISmartWallet interface and returns a locked balance.

// Test available balance
uint256 availableBalance = assetToken.getBalanceAvailable(address(mockWallet));

// Available balance should be total balance minus locked balance
assertEq(availableBalance, totalBalance - lockedBalance, "Available balance incorrect");
assertEq(
mockWallet.getBalanceLocked(IAssetToken(address(assetToken))), lockedBalance, "Locked balance incorrect"
);
}

function test_Transfer() public {
Expand Down Expand Up @@ -275,7 +280,6 @@ contract AssetTokenTest is Test {
function isWhitelistEnabled() public view returns (bool) {
return assetTokenWhitelisted.isWhitelistEnabled();
}
// Update the test function

function test_AddAndRemoveFromWhitelist() public {
console.log("AssetToken owner:", assetTokenWhitelisted.owner());
Expand Down Expand Up @@ -319,28 +323,43 @@ contract AssetTokenTest is Test {

function test_RequestYield() public {
mockSmartWallet = new SmartWallet();
uint256 initialBalance = 100 ether;
uint256 yieldAmount = 10 ether;

// Setup: Mint tokens and deposit yield
vm.startPrank(address(testWalletImplementation));

// Setup mock wallet
assetToken.addToWhitelist(address(mockSmartWallet));
assetToken.mint(address(mockSmartWallet), 1000 * 10 ** 18);
assetToken.mint(address(mockSmartWallet), initialBalance);

// Setup yield
ERC20Mock(address(currencyToken)).mint(address(testWalletImplementation), yieldAmount);
currencyToken.approve(address(assetToken), yieldAmount);

// Deposit yield
vm.warp(block.timestamp + 1); // Advance time to avoid same block deposit
assetToken.depositYield(yieldAmount);

// Advance time to accrue yield
vm.warp(block.timestamp + 60); // Advance by 1 minute
vm.stopPrank();

// Record balances before yield request
uint256 walletBalanceBefore = currencyToken.balanceOf(address(mockSmartWallet));

// Request yield
vm.expectEmit(true, true, true, false); // Don't check data as exact yield amount may vary
emit IYieldDistributionToken.YieldAccrued(address(mockSmartWallet), 0); // Amount will be checked separately
assetToken.requestYield(address(mockSmartWallet));
// You may need to implement a way to verify that the yield was requested
}

function test_GetHoldersAndHasBeenHolder() public {
vm.startPrank(address(testWalletImplementation));
assetToken.addToWhitelist(user1);
assetToken.addToWhitelist(user2);
assetToken.mint(user1, 100 * 10 ** 18);
assetToken.mint(user2, 100 * 10 ** 18);
vm.stopPrank();
// Verify balances after yield request
uint256 walletBalanceAfter = currencyToken.balanceOf(address(mockSmartWallet));
uint256 claimedYield = walletBalanceAfter - walletBalanceBefore;

address[] memory holders = assetToken.getHolders();
assertEq(holders.length, 3, "Should have 3 holders (owner, user1, user2)");
assertTrue(assetToken.hasBeenHolder(user1), "User1 should be a holder");
assertTrue(assetToken.hasBeenHolder(user2), "User2 should be a holder");
// Assert balance changes
assertGt(claimedYield, 0, "Should have claimed some yield");
assertLe(claimedYield, yieldAmount, "Claimed yield should not exceed deposited amount");
}

function test_GetPricePerToken() public {
Expand Down Expand Up @@ -412,17 +431,14 @@ contract AssetTokenTest is Test {

// Test: Successfully remove from whitelist
vm.expectEmit(true, false, false, false);
emit AddressRemovedFromWhitelist(user1);
emit AssetToken.AddressRemovedFromWhitelist(user1);
assetTokenWhitelisted.removeFromWhitelist(user1);

// Verify user is no longer whitelisted
assertFalse(assetTokenWhitelisted.isAddressWhitelisted(user1));
vm.stopPrank();
}

// Helper function to check if event was emitted
event AddressRemovedFromWhitelist(address indexed user);

function test_DepositYield() public {
uint256 depositAmount = 100 ether;

Expand Down Expand Up @@ -477,10 +493,9 @@ contract AssetTokenTest is Test {
vm.warp(block.timestamp + 1);
assetToken.depositYield(yieldAmount);

// Let yield accrue
vm.warp(block.timestamp + 1 days);
// Let yield accrue for just a few seconds
vm.warp(block.timestamp + 10); // Only 10 seconds instead of 1 hour

// Claim yield for both users
vm.stopPrank();

vm.prank(user1);
Expand All @@ -489,9 +504,16 @@ contract AssetTokenTest is Test {
vm.prank(user2);
(IERC20 token2, uint256 amount2) = assetToken.claimYield(user2);

// Debug logs
console.log("User 1 yield amount:", amount1);
console.log("User 2 yield amount:", amount2);
console.log("Total yield deposited:", yieldAmount);

// Test assumptions:
// 1. Both users should get roughly equal yield (within 1%)
assertApproxEqRel(amount1, amount2, 0.01e18);
// 2. Total claimed yield should not exceed deposited amount
assertLe(amount1 + amount2, yieldAmount);
}

function test_YieldCalculationsWithMultipleDeposits() public {
Expand All @@ -510,24 +532,85 @@ contract AssetTokenTest is Test {
vm.warp(block.timestamp + 1);
assetToken.depositYield(firstYield);

// Second yield deposit
vm.warp(block.timestamp + 1 days);
// Second yield deposit after a short time
vm.warp(block.timestamp + 10); // Only 10 seconds
ERC20Mock(address(currencyToken)).mint(address(testWalletImplementation), secondYield);
currencyToken.approve(address(assetToken), secondYield);
assetToken.depositYield(secondYield);

vm.stopPrank();

// Claim yield
// Claim yield after a short time
vm.startPrank(user1);
vm.warp(block.timestamp + 1 days);
vm.warp(block.timestamp + 10); // Only 10 seconds

(IERC20 token, uint256 claimedAmount) = assetToken.claimYield(user1);

// Debug logs
console.log("Claimed amount:", claimedAmount);
console.log("Total yield deposited:", firstYield + secondYield);

vm.stopPrank();

// Test assumptions:
// 1. Token should be the correct currency token
assertEq(address(token), address(currencyToken));
// 2. Claimed amount should not exceed total deposited yield
assertLe(claimedAmount, firstYield + secondYield);
}

function test_RevertConstructorInvalidAddress() public {
// First test: Invalid owner (should revert with OwnableInvalidOwner)
vm.expectRevert(abi.encodeWithSignature("OwnableInvalidOwner(address)", address(0)));
new AssetToken(
address(0), // Invalid owner address
"Asset Token",
"AT",
ERC20(address(currencyToken)),
18,
"http://example.com/token",
1000 * 10 ** 18,
10_000 * 10 ** 18,
true
);

// Second test: Invalid currency token (should revert with InvalidAddress)
vm.expectRevert(AssetToken.InvalidAddress.selector);
new AssetToken(
address(this), // valid owner
"Asset Token",
"AT",
ERC20(address(0)), // Invalid currency token address
18,
"http://example.com/token",
1000 * 10 ** 18,
10_000 * 10 ** 18,
true
);
}

function test_GetBalanceAvailableWithLockedBalance() public {
uint256 initialBalance = 100 ether;
uint256 lockedBalance = 30 ether;

// Create new MockSmartWallet instance
MockSmartWallet mockWallet = new MockSmartWallet();

vm.startPrank(address(testWalletImplementation));

// Mint some tokens to the mock wallet
assetToken.mint(address(mockWallet), initialBalance);

// Lock some tokens
mockWallet.lockTokens(IAssetToken(address(assetToken)), lockedBalance);

vm.stopPrank();

// Check available balance
uint256 availableBalance = assetToken.getBalanceAvailable(address(mockWallet));

// Available balance should be initial balance minus locked balance
assertEq(availableBalance, initialBalance - lockedBalance);
}

}
Loading