diff --git a/nest/src/interfaces/IBoringVault.sol b/nest/src/interfaces/IBoringVault.sol index 7358533..58be5ec 100644 --- a/nest/src/interfaces/IBoringVault.sol +++ b/nest/src/interfaces/IBoringVault.sol @@ -2,9 +2,8 @@ pragma solidity ^0.8.25; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -interface IBoringVault is IERC20, IERC20Metadata { +interface IBoringVault is IERC20 { /** * @notice Deposits assets into the vault in exchange for shares @@ -26,4 +25,6 @@ interface IBoringVault is IERC20, IERC20Metadata { */ function exit(address to, address asset, uint256 assetAmount, address from, uint256 shareAmount) external; -} + // ERC20 interface functions + function decimals() external view returns (uint8); +} \ No newline at end of file diff --git a/nest/src/mocks/MockVault.sol b/nest/src/mocks/MockVault.sol index 367aafa..3c7d511 100644 --- a/nest/src/mocks/MockVault.sol +++ b/nest/src/mocks/MockVault.sol @@ -1,117 +1,134 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; -import { ERC1155Holder } from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +import { ERC20 } from "@solmate/tokens/ERC20.sol"; +import { SafeTransferLib } from "@solmate/utils/SafeTransferLib.sol"; import { Auth, Authority } from "@solmate/auth/Auth.sol"; - +import { ERC721Holder } from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol"; +import { ERC1155Holder } from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; import { IBoringVault } from "../interfaces/IBoringVault.sol"; +import { BeforeTransferHook } from "@boringvault/src/interfaces/BeforeTransferHook.sol"; -contract MockVault is ERC20, Auth, ERC721Holder, ERC1155Holder, IBoringVault { - - using SafeERC20 for IERC20; +contract MockVault is IBoringVault, Auth, ERC721Holder, ERC1155Holder { + using SafeTransferLib for ERC20; - // token => account => balance - mapping(address => mapping(address => uint256)) private _balances; + // State variables + string public name; + string public symbol; + uint8 public decimals; + uint256 public totalSupply; + mapping(address => uint256) public balanceOf; + mapping(address => mapping(address => uint256)) public allowance; + BeforeTransferHook public hook; - mapping(address => mapping(address => mapping(address => uint256))) private _allowances; + // Events + event DebugCall(string functionName, bytes data); + event Enter(address indexed from, address indexed asset, uint256 amount, address indexed to, uint256 shares); + event Exit(address indexed to, address indexed asset, uint256 amount, address indexed from, uint256 shares); - IERC20 public asset; - IERC20 public immutable usdc; - IERC20 public immutable usdt; - address public beforeTransferHook; - constructor( + constructor( address _owner, string memory _name, string memory _symbol, - address _usdc - ) ERC20(_name, _symbol) Auth(_owner, Authority(address(0))) { - usdc = IERC20(_usdc); + uint8 _decimals + ) Auth(_owner, Authority(address(0))) { + name = _name; + symbol = _symbol; + decimals = _decimals; } - function enter(address from, address asset_, uint256 assetAmount, address to, uint256 shareAmount) external { - if (assetAmount > 0) { - IERC20(asset_).safeTransferFrom(from, address(this), assetAmount); - } - _balances[asset_][to] += shareAmount; - _allowances[asset_][to][msg.sender] = type(uint256).max; + function approve(address spender, uint256 amount) public returns (bool) { + allowance[msg.sender][spender] = amount; + emit Approval(msg.sender, spender, amount); + return true; } - function exit(address to, address asset_, uint256 assetAmount, address from, uint256 shareAmount) external { - // Change from checking 'from' balance to checking the actual owner's balance - address owner = from == msg.sender ? to : from; - require(_balances[asset_][owner] >= shareAmount, "MockVault: insufficient balance"); + function transfer(address to, uint256 amount) public returns (bool) { + _callBeforeTransfer(msg.sender); + return _transfer(msg.sender, to, amount); + } - uint256 allowed = _allowances[asset_][owner][msg.sender]; + function transferFrom(address from, address to, uint256 amount) public returns (bool) { + _callBeforeTransfer(from); + uint256 allowed = allowance[from][msg.sender]; if (allowed != type(uint256).max) { - require(allowed >= shareAmount, "MockVault: insufficient allowance"); - _allowances[asset_][owner][msg.sender] = allowed - shareAmount; - } - - _balances[asset_][owner] -= shareAmount; - - // Changed: Transfer to 'to' instead of msg.sender, and always transfer if we have shares - if (shareAmount > 0) { - IERC20(asset_).safeTransfer(to, shareAmount); + require(allowed >= amount, "MockVault: insufficient allowance"); + allowance[from][msg.sender] = allowed - amount; } + return _transfer(from, to, amount); } - function transferFrom(address asset_, address from, address to, uint256 amount) external returns (bool) { - require(_balances[asset_][from] >= amount, "MockVault: insufficient balance"); + function _transfer(address from, address to, uint256 amount) internal returns (bool) { + require(from != address(0), "MockVault: transfer from zero address"); + require(to != address(0), "MockVault: transfer to zero address"); + require(balanceOf[from] >= amount, "MockVault: insufficient balance"); - uint256 allowed = _allowances[asset_][from][msg.sender]; - if (allowed != type(uint256).max) { - require(allowed >= amount, "MockVault: insufficient allowance"); - _allowances[asset_][from][msg.sender] = allowed - amount; - } + balanceOf[from] -= amount; + balanceOf[to] += amount; - _balances[asset_][from] -= amount; - _balances[asset_][to] += amount; + emit Transfer(from, to, amount); return true; } - function approve(address asset_, address spender, uint256 amount) external returns (bool) { - _allowances[asset_][msg.sender][spender] = amount; - return true; + function _mint(address to, uint256 amount) internal { + require(to != address(0), "MockVault: mint to zero address"); + totalSupply += amount; + balanceOf[to] += amount; + emit Transfer(address(0), to, amount); } - function balanceOf( - address account - ) public view virtual override(ERC20, IERC20) returns (uint256) { - // Return total balance across all assets - return _balances[address(usdc)][account] + _balances[address(usdt)][account]; + function _burn(address from, uint256 amount) internal { + require(from != address(0), "MockVault: burn from zero address"); + require(balanceOf[from] >= amount, "MockVault: insufficient balance"); + totalSupply -= amount; + balanceOf[from] -= amount; + emit Transfer(from, address(0), amount); } - function totalSupply() public view virtual override(ERC20, IERC20) returns (uint256) { - // Return total supply across all assets - return _balances[address(usdc)][address(this)] + _balances[address(usdt)][address(this)]; - } + function enter( + address from, + address asset, + uint256 assetAmount, + address to, + uint256 shareAmount + ) external override requiresAuth { + emit DebugCall("enter", abi.encode(from, asset, assetAmount, to, shareAmount)); + + if (assetAmount > 0) { + ERC20(asset).safeTransferFrom(from, address(this), assetAmount); + } - function decimals() public pure virtual override(ERC20, IERC20Metadata) returns (uint8) { - return 6; - } + _mint(to, shareAmount); - function tokenBalance(address token, address account) external view returns (uint256) { - return _balances[token][account]; + emit Enter(from, asset, assetAmount, to, shareAmount); } - function setBalance(address token, uint256 amount) external { - _balances[token][address(this)] = amount; + function exit( + address to, + address asset, + uint256 assetAmount, + address from, + uint256 shareAmount + ) external override requiresAuth { + emit DebugCall("exit", abi.encode(to, asset, assetAmount, from, shareAmount)); + + _burn(from, shareAmount); + + if (assetAmount > 0) { + ERC20(asset).safeTransfer(to, assetAmount); + } + + emit Exit(to, address(asset), assetAmount, from, shareAmount); } - function allowance(address asset_, address owner, address spender) external view returns (uint256) { - return _allowances[asset_][owner][spender]; + function setBeforeTransferHook(address _hook) external requiresAuth { + hook = BeforeTransferHook(_hook); } - function setBeforeTransferHook( - address hook - ) external { - beforeTransferHook = hook; + function _callBeforeTransfer(address from) internal view { + if (address(hook) != address(0)) hook.beforeTransfer(from); } -} + receive() external payable {} +} \ No newline at end of file diff --git a/nest/src/vault/NestTeller.sol b/nest/src/vault/NestTeller.sol index de858f8..77574f4 100644 --- a/nest/src/vault/NestTeller.sol +++ b/nest/src/vault/NestTeller.sol @@ -13,6 +13,7 @@ import { MultiChainLayerZeroTellerWithMultiAssetSupport } from import { ITeller } from "../interfaces/ITeller.sol"; import { NestBoringVaultModule } from "./NestBoringVaultModule.sol"; +import { console } from "forge-std/console.sol"; /** * @title NestTeller @@ -41,7 +42,28 @@ contract NestTeller is NestBoringVaultModule, MultiChainLayerZeroTellerWithMulti MultiChainLayerZeroTellerWithMultiAssetSupport(_owner, _vault, _accountant, _endpoint) NestBoringVaultModule(_owner, _vault, _accountant, IERC20(_asset)) { + console.log("Debug: Starting NestTeller constructor"); + + + // Input validation + require(_owner != address(0), "NestTeller: owner cannot be zero address"); + require(_vault != address(0), "NestTeller: vault cannot be zero address"); + require(_accountant != address(0), "NestTeller: accountant cannot be zero address"); + require(_endpoint != address(0), "NestTeller: endpoint cannot be zero address"); + require(_asset != address(0), "NestTeller: asset cannot be zero address"); + require(_minimumMintPercentage > 0 && _minimumMintPercentage <= 10000, "NestTeller: invalid minimum mint percentage"); + + console.log("Debug: Owner", _owner); + console.log("Debug: Vault", _vault); + console.log("Debug: Accountant", _accountant); + console.log("Debug: Endpoint", _endpoint); + console.log("Debug: Asset", _asset); + console.log("Debug: MinimumMintPercentage", _minimumMintPercentage); + minimumMintPercentage = _minimumMintPercentage; + console.log("Debug: Set minimumMintPercentage to:", _minimumMintPercentage); + + console.log("Debug: NestTeller constructor completed"); } /** diff --git a/nest/test/NestBoringVaultModuleTest.t.sol b/nest/test/NestBoringVaultModuleTest.t.sol index b4d5750..a779700 100644 --- a/nest/test/NestBoringVaultModuleTest.t.sol +++ b/nest/test/NestBoringVaultModuleTest.t.sol @@ -21,20 +21,26 @@ abstract contract NestBoringVaultModuleTest is Test { owner = address(this); user = makeAddr("user"); - // Deploy mocks + // Deploy mocks in specific order asset = new MockUSDC(); + +vault = new MockVault(owner, "Mock Vault", "mVault", 6); // Use 6 for USDC decimals +/* vault = new MockVault( - owner, // _owner - "Mock Vault", // _name - "MVLT", // _symbol - address(asset) // _usdc + owner, // _owner + "Mock Vault", // _name + "MVLT", // _symbol + address(asset) // _usdc ); - +*/ accountant = new MockAccountantWithRateProviders( address(vault), // _vault address(asset), // _base 1e18 // startingExchangeRate (1:1 ratio) ); + + // Deal some tokens to the vault for initial liquidity + deal(address(asset), address(vault), 1000000e6); } // Common tests that apply to all NestBoringVaultModule implementations diff --git a/nest/test/NestTeller.t.sol b/nest/test/NestTeller.t.sol index e0662a8..3957ff2 100644 --- a/nest/test/NestTeller.t.sol +++ b/nest/test/NestTeller.t.sol @@ -4,6 +4,11 @@ pragma solidity ^0.8.25; import { NestTeller } from "../src/vault/NestTeller.sol"; import { NestBoringVaultModuleTest } from "./NestBoringVaultModuleTest.t.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { MockUSDC } from "../src/mocks/MockUSDC.sol"; +import { MockVault } from "../src/mocks/MockVault.sol"; +import { MockAccountantWithRateProviders } from "../src/mocks/MockAccountantWithRateProviders.sol"; + +import { console } from "forge-std/console.sol"; contract NestTellerTest is NestBoringVaultModuleTest { @@ -11,24 +16,124 @@ contract NestTellerTest is NestBoringVaultModuleTest { address public endpoint; uint256 public constant MINIMUM_MINT_PERCENTAGE = 9900; // 99% - function setUp() public override { - super.setUp(); - endpoint = makeAddr("endpoint"); + function setUp() public override { + super.setUp(); + endpoint = makeAddr("endpoint"); + + // Debug logs + console.log("Asset decimals:", asset.decimals()); + console.log("Vault decimals:", vault.decimals()); + console.log("Asset address:", address(asset)); + console.log("Vault address:", address(vault)); + console.log("Accountant address:", address(accountant)); + + // Add validation checks before deployment + require(address(vault) != address(0), "Vault address is zero"); + require(address(accountant) != address(0), "Accountant address is zero"); + require(address(asset) != address(0), "Asset address is zero"); + require(endpoint != address(0), "Endpoint address is zero"); + + // Add more detailed logging + + + console.log("Debug: About to deploy NestTeller"); + console.log("Debug: Owner:", owner); + console.log("Debug: Vault:", address(vault)); + console.log("Debug: Accountant:", address(accountant)); + console.log("Debug: Endpoint:", endpoint); + console.log("Debug: Asset:", address(asset)); + console.log("Debug: MinimumMintPercentage:", MINIMUM_MINT_PERCENTAGE); + + + // Try to deploy teller with try-catch and more detailed error handling + try new NestTeller( + owner, + address(vault), + address(accountant), + endpoint, + address(asset), + MINIMUM_MINT_PERCENTAGE + ) returns (NestTeller _teller) { + teller = _teller; + console.log("Teller deployed successfully at:", address(teller)); + + // Verify initialization + require(teller.owner() == owner, "Owner not set correctly"); + require(address(teller.vaultContract()) == address(vault), "Vault not set correctly"); + require(address(teller.accountantContract()) == address(accountant), "Accountant not set correctly"); + require(teller.asset() == address(asset), "Asset not set correctly"); + + } catch Error(string memory reason) { + console.log("Deployment failed with reason:", reason); + revert(reason); + } catch Panic(uint errorCode) { + string memory panicReason = getPanicReason(errorCode); + console.log("Deployment failed with panic:", panicReason); + revert(string(abi.encodePacked("Panic: ", panicReason))); + } catch (bytes memory returnData) { + console.log("Deployment failed with raw data:"); + console.logBytes(returnData); + + // Try to decode the revert reason if possible + if (returnData.length > 4) { + bytes4 selector = bytes4(returnData); + console.log("Selector:", bytes4ToString(selector)); + } + + revert("Unknown error"); + } - // Deal some tokens to the vault for initial liquidity - deal(address(asset), address(vault), 1_000_000e6); + // Approve teller to spend vault's tokens + vm.prank(address(vault)); + IERC20(address(asset)).approve(address(teller), type(uint256).max); +} - // Setup initial rates in accountant - accountant.setRateInQuote(1e18); // 1:1 ratio +// Helper function to get panic reason +function getPanicReason(uint errorCode) internal pure returns (string memory) { + if (errorCode == 0x01) return "Assertion failed"; + if (errorCode == 0x11) return "Arithmetic overflow/underflow"; + if (errorCode == 0x12) return "Division by zero"; + if (errorCode == 0x21) return "Invalid enum value"; + if (errorCode == 0x22) return "Storage write to inaccessible slot"; + if (errorCode == 0x31) return "Pop from empty array"; + if (errorCode == 0x32) return "Array access out of bounds"; + if (errorCode == 0x41) return "Zero initialization of uninitialized variable"; + if (errorCode == 0x51) return "Invalid memory access"; + return string(abi.encodePacked("Unknown panic code: ", uint2str(errorCode))); +} - teller = new NestTeller( - owner, address(vault), address(accountant), endpoint, address(asset), MINIMUM_MINT_PERCENTAGE - ); +// Helper function to convert uint to string +function uint2str(uint _i) internal pure returns (string memory) { + if (_i == 0) return "0"; + uint j = _i; + uint len; + while (j != 0) { + len++; + j /= 10; + } + bytes memory bstr = new bytes(len); + uint k = len; + while (_i != 0) { + k = k-1; + uint8 temp = (48 + uint8(_i - _i / 10 * 10)); + bytes1 b1 = bytes1(temp); + bstr[k] = b1; + _i /= 10; + } + return string(bstr); +} - // Approve teller to spend vault's tokens - vm.prank(address(vault)); - IERC20(address(asset)).approve(address(teller), type(uint256).max); +// Helper function to convert bytes4 to string +function bytes4ToString(bytes4 _bytes) internal pure returns (string memory) { + bytes memory bytesArray = new bytes(8); + uint256 value = uint256(uint32(_bytes)); + for (uint256 i; i < 4; i++) { + uint8 temp = uint8(value / (2**(8*(3-i)))); + bytesArray[i*2] = bytes1(uint8((temp / 16) + (temp / 16 < 10 ? 48 : 87))); + bytesArray[i*2+1] = bytes1(uint8((temp % 16) + (temp % 16 < 10 ? 48 : 87))); } + return string(bytesArray); +} function testInitialization() public override { assertEq(teller.owner(), owner); diff --git a/nest/test/pUSD.t.sol b/nest/test/pUSD.t.sol index 9bb1e1d..c029e9c 100644 --- a/nest/test/pUSD.t.sol +++ b/nest/test/pUSD.t.sol @@ -63,7 +63,8 @@ contract pUSDTest is Test { //asset = new MockUSDC(); usdc = new MockUSDC(); - vault = new MockVault(owner, "Mock Vault", "mVault", address(usdc)); + //vault = new MockVault(owner, "Mock Vault", "mVault", address(usdc)); + vault = new MockVault(owner, "Mock Vault", "mVault", 6); mockTeller = new MockTeller(); mockAtomicQueue = new MockAtomicQueue(); mockLens = new MockLens();