Files
smom-dbis-138/contracts/vault/VaultFactory.sol
2026-03-02 12:14:09 -08:00

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];
}
}