254 lines
8.9 KiB
Solidity
254 lines
8.9 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.20;
|
|
|
|
import "@openzeppelin/contracts/access/AccessControl.sol";
|
|
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
|
import "./Vault.sol";
|
|
import "./tokens/DepositToken.sol";
|
|
import "./tokens/DebtToken.sol";
|
|
import "./interfaces/ILedger.sol";
|
|
|
|
/**
|
|
* @title VaultFactory
|
|
* @notice Factory for creating vault instances with associated tokens
|
|
* @dev Creates Vault, DepositToken, and DebtToken instances
|
|
*/
|
|
contract VaultFactory is AccessControl {
|
|
bytes32 public constant VAULT_DEPLOYER_ROLE = keccak256("VAULT_DEPLOYER_ROLE");
|
|
|
|
address public immutable vaultImplementation;
|
|
address public immutable depositTokenImplementation;
|
|
address public immutable debtTokenImplementation;
|
|
|
|
ILedger public ledger;
|
|
address public entityRegistry;
|
|
address public collateralAdapter;
|
|
address public eMoneyJoin;
|
|
|
|
mapping(address => address[]) public vaultsByEntity; // entity => vaults[]
|
|
mapping(address => address) public vaultToEntity; // vault => entity
|
|
|
|
event VaultCreated(
|
|
address indexed vault,
|
|
address indexed entity,
|
|
address indexed owner,
|
|
address depositToken,
|
|
address debtToken
|
|
);
|
|
|
|
constructor(
|
|
address admin,
|
|
address vaultImplementation_,
|
|
address depositTokenImplementation_,
|
|
address debtTokenImplementation_,
|
|
address ledger_,
|
|
address entityRegistry_,
|
|
address collateralAdapter_,
|
|
address eMoneyJoin_
|
|
) {
|
|
_grantRole(DEFAULT_ADMIN_ROLE, admin);
|
|
_grantRole(VAULT_DEPLOYER_ROLE, admin);
|
|
|
|
vaultImplementation = vaultImplementation_;
|
|
depositTokenImplementation = depositTokenImplementation_;
|
|
debtTokenImplementation = debtTokenImplementation_;
|
|
ledger = ILedger(ledger_);
|
|
entityRegistry = entityRegistry_;
|
|
collateralAdapter = collateralAdapter_;
|
|
eMoneyJoin = eMoneyJoin_;
|
|
}
|
|
|
|
/**
|
|
* @notice Create a new vault for a regulated entity
|
|
* @param owner Vault owner address
|
|
* @param entity Regulated entity address
|
|
* @param asset Collateral asset address (for deposit token)
|
|
* @param currency eMoney currency address (for debt token)
|
|
* @return vault Address of created vault
|
|
* @return depositToken Address of deposit token
|
|
* @return debtToken Address of debt token
|
|
*/
|
|
function createVault(
|
|
address owner,
|
|
address entity,
|
|
address asset,
|
|
address currency
|
|
) external onlyRole(VAULT_DEPLOYER_ROLE) returns (
|
|
address vault,
|
|
address depositToken,
|
|
address debtToken
|
|
) {
|
|
require(owner != address(0), "VaultFactory: zero owner");
|
|
require(entity != address(0), "VaultFactory: zero entity");
|
|
|
|
// Deploy vault directly (not using proxy for simplicity)
|
|
// In production, could use proxy pattern for upgradeability
|
|
Vault vaultContract = new Vault(
|
|
owner,
|
|
entity,
|
|
address(ledger),
|
|
entityRegistry,
|
|
collateralAdapter,
|
|
eMoneyJoin
|
|
);
|
|
vault = address(vaultContract);
|
|
|
|
// Deploy deposit token (factory as admin so it can grant MINTER/BURNER to vault)
|
|
bytes memory depositTokenInitData = abi.encodeWithSelector(
|
|
DepositToken.initialize.selector,
|
|
string(abi.encodePacked("Deposit ", _getAssetSymbol(asset))),
|
|
string(abi.encodePacked("d", _getAssetSymbol(asset))),
|
|
vault,
|
|
asset,
|
|
address(this)
|
|
);
|
|
|
|
ERC1967Proxy depositTokenProxy = new ERC1967Proxy(depositTokenImplementation, depositTokenInitData);
|
|
depositToken = address(depositTokenProxy);
|
|
|
|
// Grant minter/burner roles to vault
|
|
DepositToken(depositToken).grantRole(keccak256("MINTER_ROLE"), vault);
|
|
DepositToken(depositToken).grantRole(keccak256("BURNER_ROLE"), vault);
|
|
|
|
// Deploy debt token (factory as admin so it can grant MINTER/BURNER to vault)
|
|
bytes memory debtTokenInitData = abi.encodeWithSelector(
|
|
DebtToken.initialize.selector,
|
|
string(abi.encodePacked("Debt ", _getCurrencySymbol(currency))),
|
|
string(abi.encodePacked("debt", _getCurrencySymbol(currency))),
|
|
vault,
|
|
currency,
|
|
address(this)
|
|
);
|
|
|
|
ERC1967Proxy debtTokenProxy = new ERC1967Proxy(debtTokenImplementation, debtTokenInitData);
|
|
debtToken = address(debtTokenProxy);
|
|
|
|
// Grant minter/burner roles to vault
|
|
DebtToken(debtToken).grantRole(keccak256("MINTER_ROLE"), vault);
|
|
DebtToken(debtToken).grantRole(keccak256("BURNER_ROLE"), vault);
|
|
|
|
// Configure vault with tokens (cast via payable since Vault has receive())
|
|
Vault(payable(vault)).setDepositToken(asset, depositToken);
|
|
Vault(payable(vault)).setDebtToken(currency, debtToken);
|
|
|
|
// Grant vault role in ledger
|
|
ledger.grantVaultRole(vault);
|
|
|
|
// Track vault
|
|
vaultsByEntity[entity].push(vault);
|
|
vaultToEntity[vault] = entity;
|
|
|
|
emit VaultCreated(vault, entity, owner, depositToken, debtToken);
|
|
}
|
|
|
|
/**
|
|
* @notice Create a vault with explicit decimals and debt transferability (DEX-ready)
|
|
* @param depositDecimals Deposit token decimals (e.g. 6 for stablecoins; 0 = 18)
|
|
* @param debtDecimals Debt token decimals (0 = 18)
|
|
* @param debtTransferable If true, debt token is freely transferable (DEX-ready)
|
|
*/
|
|
function createVaultWithDecimals(
|
|
address owner,
|
|
address entity,
|
|
address asset,
|
|
address currency,
|
|
uint8 depositDecimals,
|
|
uint8 debtDecimals,
|
|
bool debtTransferable
|
|
) external onlyRole(VAULT_DEPLOYER_ROLE) returns (
|
|
address vault,
|
|
address depositToken,
|
|
address debtToken
|
|
) {
|
|
require(owner != address(0), "VaultFactory: zero owner");
|
|
require(entity != address(0), "VaultFactory: zero entity");
|
|
|
|
Vault vaultContract = new Vault(
|
|
owner,
|
|
entity,
|
|
address(ledger),
|
|
entityRegistry,
|
|
collateralAdapter,
|
|
eMoneyJoin
|
|
);
|
|
vault = address(vaultContract);
|
|
|
|
uint8 dDec = depositDecimals > 0 ? depositDecimals : 18;
|
|
bytes memory depositTokenInitData = abi.encodeWithSelector(
|
|
DepositToken.initializeWithDecimals.selector,
|
|
string(abi.encodePacked("Deposit ", _getAssetSymbol(asset))),
|
|
string(abi.encodePacked("d", _getAssetSymbol(asset))),
|
|
vault,
|
|
asset,
|
|
address(this),
|
|
dDec
|
|
);
|
|
|
|
ERC1967Proxy depositTokenProxy = new ERC1967Proxy(depositTokenImplementation, depositTokenInitData);
|
|
depositToken = address(depositTokenProxy);
|
|
|
|
DepositToken(depositToken).grantRole(keccak256("MINTER_ROLE"), vault);
|
|
DepositToken(depositToken).grantRole(keccak256("BURNER_ROLE"), vault);
|
|
|
|
uint8 debtDec = debtDecimals > 0 ? debtDecimals : 18;
|
|
bytes memory debtTokenInitData = abi.encodeWithSelector(
|
|
DebtToken.initializeFull.selector,
|
|
string(abi.encodePacked("Debt ", _getCurrencySymbol(currency))),
|
|
string(abi.encodePacked("debt", _getCurrencySymbol(currency))),
|
|
vault,
|
|
currency,
|
|
address(this),
|
|
debtDec,
|
|
debtTransferable
|
|
);
|
|
|
|
ERC1967Proxy debtTokenProxy = new ERC1967Proxy(debtTokenImplementation, debtTokenInitData);
|
|
debtToken = address(debtTokenProxy);
|
|
|
|
DebtToken(debtToken).grantRole(keccak256("MINTER_ROLE"), vault);
|
|
DebtToken(debtToken).grantRole(keccak256("BURNER_ROLE"), vault);
|
|
|
|
Vault(payable(vault)).setDepositToken(asset, depositToken);
|
|
Vault(payable(vault)).setDebtToken(currency, debtToken);
|
|
|
|
ledger.grantVaultRole(vault);
|
|
|
|
vaultsByEntity[entity].push(vault);
|
|
vaultToEntity[vault] = entity;
|
|
|
|
emit VaultCreated(vault, entity, owner, depositToken, debtToken);
|
|
}
|
|
|
|
/**
|
|
* @notice Get asset symbol (helper)
|
|
* @param asset Asset address
|
|
* @return symbol Asset symbol
|
|
*/
|
|
function _getAssetSymbol(address asset) internal pure returns (string memory symbol) {
|
|
if (asset == address(0)) {
|
|
return "ETH";
|
|
}
|
|
// In production, would fetch from ERC20
|
|
return "ASSET";
|
|
}
|
|
|
|
/**
|
|
* @notice Get currency symbol (helper)
|
|
* @param currency Currency address
|
|
* @return symbol Currency symbol
|
|
*/
|
|
function _getCurrencySymbol(address currency) internal pure returns (string memory symbol) {
|
|
// In production, would fetch from eMoney token
|
|
return "CURRENCY";
|
|
}
|
|
|
|
/**
|
|
* @notice Get vaults for an entity
|
|
* @param entity Entity address
|
|
* @return vaults Array of vault addresses
|
|
*/
|
|
function getVaultsByEntity(address entity) external view returns (address[] memory vaults) {
|
|
return vaultsByEntity[entity];
|
|
}
|
|
}
|