diff --git a/src/Portal.sol b/src/DestinationPortal.sol similarity index 65% rename from src/Portal.sol rename to src/DestinationPortal.sol index f658115..7d0f5d3 100644 --- a/src/Portal.sol +++ b/src/DestinationPortal.sol @@ -4,16 +4,15 @@ pragma solidity 0.8.24; import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -contract MyOFT is OFT { +contract DestinationPortal is OFT { uint8 private constant DECIMALS = 6; constructor( - string memory _name, - string memory _symbol, - address _lzEndpoint + address _lzEndpoint, + address _globalOwner ) - OFT(_name, _symbol, _lzEndpoint, _delegate) - Ownable(msg.sender) + OFT("Portal", "PORTAL", _lzEndpoint, _globalOwner) + Ownable(_globalOwner) { } function decimals() public pure override returns (uint8) { diff --git a/src/MultiAssetVault.sol b/src/MultiAssetVault.sol index c490162..8d77e5d 100644 --- a/src/MultiAssetVault.sol +++ b/src/MultiAssetVault.sol @@ -9,22 +9,23 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { IAssetRegistry } from "./interfaces/IAssetRegistry.sol"; import { IMultiAssetVault } from "./interfaces/IMultiAssetVault.sol"; -import { IPortal } from "./interfaces/IPortal.sol"; import { IPythOracle } from "./interfaces/IPythOracle.sol"; +import { ISourcePortal } from "./interfaces/ISourcePortal.sol"; contract MultiAssetVault is IMultiAssetVault { using SafeERC20 for IERC20; IAssetRegistry private immutable i_assetRegistry; IPythOracle private immutable i_pythOracle; - IPortal private immutable i_portal; + ISourcePortal private immutable i_sourcePortal; mapping(address user => mapping(address asset => Position position)) private s_positions; - constructor(address _assetRegistry, address _pythOracle) { + constructor(address _assetRegistry, address _pythOracle, address _sourcePortal) { if (_assetRegistry == address(0) || _pythOracle == address(0)) revert MultiAssetVault__AddressZero(); i_assetRegistry = IAssetRegistry(_assetRegistry); i_pythOracle = IPythOracle(_pythOracle); + i_sourcePortal = ISourcePortal(_sourcePortal); } function depositCollateral(address _asset, uint256 _amount, address _for) external { @@ -55,9 +56,33 @@ contract MultiAssetVault is IMultiAssetVault { emit AmountWithdrawn(msg.sender, _asset, _amount, _to); } - function mint() external { } + function mint(address _asset, uint256 _amount, address _to) external { + _revertIfAssetNotWhitelisted(_asset); + if (_amount == 0) revert MultiAssetVault__AmountZero(); + if (_to == address(0)) revert MultiAssetVault__AddressZero(); + + Position memory position = s_positions[msg.sender][_asset]; + position.amountMinted += _amount; + s_positions[msg.sender][_asset] = position; + + if (!_isPositionHealthy(_asset, position)) revert MultiAssetVault__MinimumCollateralisationRatioBreached(); + i_sourcePortal.mint(_to, _amount); + + emit PortalMinted(msg.sender, _asset, _amount, _to); + } - function burn() external { } + function burn(address _asset, uint256 _amount) external { + _revertIfAssetNotWhitelisted(_asset); + if (_amount == 0) revert MultiAssetVault__AmountZero(); + + Position memory position = s_positions[msg.sender][_asset]; + position.amountMinted -= _amount; + s_positions[msg.sender][_asset] = position; + + i_sourcePortal.burn(msg.sender, _amount); + + emit PortalBurned(msg.sender, _asset, _amount); + } function _revertIfAssetNotWhitelisted(address _asset) internal view { if (!i_assetRegistry.isAssetWhitelisted(_asset)) revert MultiAssetVault__AssetNotWhitelisted(_asset); @@ -75,7 +100,7 @@ contract MultiAssetVault is IMultiAssetVault { function _getCollateralisationRatio(address _asset, Position memory _position) internal view returns (uint256) { if (_position.amountMinted == 0) return type(uint256).max; return _getDepositedAmountValueInUsd(_asset, _position.amountDeposited) - * 10 ** IERC20Metadata(address(i_portal)).decimals() / _position.amountMinted; + * 10 ** IERC20Metadata(address(i_sourcePortal)).decimals() / _position.amountMinted; } function _getDepositedAmountValueInUsd(address _asset, uint256 _depositedAmount) internal view returns (uint256) { diff --git a/src/SourcePortal.sol b/src/SourcePortal.sol new file mode 100644 index 0000000..7832e74 --- /dev/null +++ b/src/SourcePortal.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import { OFT } from "@layerzerolabs/oft-evm/contracts/OFT.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +import { ISourcePortal } from "./interfaces/ISourcePortal.sol"; + +contract SourcePortal is OFT, ISourcePortal { + uint8 private constant DECIMALS = 6; + + address private immutable i_multiAssetVault; + + modifier onlyMultiAssetVault() { + if (msg.sender != i_multiAssetVault) revert SourcePortal__NotMultiAssetVault(msg.sender, i_multiAssetVault); + _; + } + + constructor( + address _lzEndpoint, + address _globalOwner, + address _multiAssetVault + ) + OFT("Portal", "PORTAL", _lzEndpoint, _globalOwner) + Ownable(_globalOwner) + { + i_multiAssetVault = _multiAssetVault; + } + + function mint(address _to, uint256 _amount) external onlyMultiAssetVault { + _mint(_to, _amount); + } + + function burn(address _to, uint256 _amount) external onlyMultiAssetVault { + _burn(_to, _amount); + } + + function decimals() public pure override returns (uint8) { + return DECIMALS; + } +} diff --git a/src/interfaces/IMultiAssetVault.sol b/src/interfaces/IMultiAssetVault.sol index 796ca39..712c7dd 100644 --- a/src/interfaces/IMultiAssetVault.sol +++ b/src/interfaces/IMultiAssetVault.sol @@ -9,6 +9,8 @@ interface IMultiAssetVault { event AmountDeposited(address by, address indexed asset, uint256 indexed amount, address indexed onBehalfOf); event AmountWithdrawn(address indexed by, address indexed asset, uint256 indexed amount, address to); + event PortalMinted(address indexed by, address indexed asset, uint256 indexed amount, address to); + event PortalBurned(address indexed by, address indexed asset, uint256 indexed amount); error MultiAssetVault__AddressZero(); error MultiAssetVault__AssetNotWhitelisted(address asset); diff --git a/src/interfaces/IPortal.sol b/src/interfaces/IPortal.sol deleted file mode 100644 index 0ebf7ee..0000000 --- a/src/interfaces/IPortal.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -interface IPortal { } diff --git a/src/interfaces/ISourcePortal.sol b/src/interfaces/ISourcePortal.sol new file mode 100644 index 0000000..5d4c2f4 --- /dev/null +++ b/src/interfaces/ISourcePortal.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +interface ISourcePortal { + error SourcePortal__NotMultiAssetVault(address caller, address multiAssetVault); + + function mint(address _to, uint256 _amount) external; + + function burn(address _to, uint256 _amount) external; +}