Some checks failed
CI/CD Pipeline / Solidity Contracts (push) Failing after 1m3s
CI/CD Pipeline / Security Scanning (push) Successful in 2m18s
CI/CD Pipeline / Lint and Format (push) Failing after 34s
CI/CD Pipeline / Terraform Validation (push) Failing after 20s
CI/CD Pipeline / Kubernetes Validation (push) Successful in 22s
Deploy ChainID 138 / Deploy ChainID 138 (push) Failing after 40s
HYBX OMNL TypeScript & anchor / token-aggregation build + reconcile artifact (push) Failing after 49s
OMNL reconcile anchor / Run omnl:reconcile and upload artifacts (push) Failing after 21s
Validation / validate-genesis (push) Successful in 25s
Validation / validate-terraform (push) Failing after 21s
Validation / validate-kubernetes (push) Failing after 8s
Validation / validate-smart-contracts (push) Failing after 8s
Validation / validate-security (push) Failing after 1m11s
Validation / validate-documentation (push) Failing after 14s
Verify Deployment / Verify Deployment (push) Failing after 45s
Ship AddressActivityRegistry V1/V2, ISO20022IntakeGateway, Chain138ParticipantSurface, checkpoint hub contracts, checkpoint-core package, aggregator/indexer/sdk services, relay profile guards, M00 diamond bridge facet, and OMNL compliance contracts. Co-authored-by: Cursor <cursoragent@cursor.com>
329 lines
11 KiB
Solidity
329 lines
11 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;
|
|
address public gruVaultIndex;
|
|
|
|
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 Wire GRU vault index (optional). Grants FACTORY_ROLE on index to this factory.
|
|
*/
|
|
function setGruVaultIndex(address index) external onlyRole(DEFAULT_ADMIN_ROLE) {
|
|
gruVaultIndex = index;
|
|
}
|
|
|
|
function _recordGruVault(
|
|
address vault,
|
|
address entity,
|
|
address baseToken,
|
|
address depositToken,
|
|
address debtToken,
|
|
uint8 gruTier,
|
|
bytes32 ibanHash,
|
|
bytes32 policyProfileKey
|
|
) internal {
|
|
if (gruVaultIndex == address(0)) return;
|
|
(bool ok, bytes memory data) = gruVaultIndex.call(
|
|
abi.encodeWithSignature(
|
|
"recordVault(address,address,address,address,address,uint8,bytes32,bytes32)",
|
|
vault,
|
|
entity,
|
|
baseToken,
|
|
depositToken,
|
|
debtToken,
|
|
gruTier,
|
|
ibanHash,
|
|
policyProfileKey
|
|
)
|
|
);
|
|
ok;
|
|
data;
|
|
}
|
|
|
|
/**
|
|
* @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.registerVault(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
|
|
) {
|
|
return _deployVaultWithDecimals(
|
|
owner, entity, asset, currency, depositDecimals, debtDecimals, debtTransferable
|
|
);
|
|
}
|
|
|
|
function _deployVaultWithDecimals(
|
|
address owner,
|
|
address entity,
|
|
address asset,
|
|
address currency,
|
|
uint8 depositDecimals,
|
|
uint8 debtDecimals,
|
|
bool debtTransferable
|
|
) internal 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.registerVault(vault);
|
|
|
|
vaultsByEntity[entity].push(vault);
|
|
vaultToEntity[vault] = entity;
|
|
|
|
emit VaultCreated(vault, entity, owner, depositToken, debtToken);
|
|
}
|
|
|
|
/**
|
|
* @notice Create vault with explicit GRU tier, IBAN hash, and policy profile key.
|
|
*/
|
|
function createVaultWithDecimalsGRU(
|
|
address owner,
|
|
address entity,
|
|
address asset,
|
|
address currency,
|
|
uint8 depositDecimals,
|
|
uint8 debtDecimals,
|
|
bool debtTransferable,
|
|
uint8 gruTier,
|
|
bytes32 ibanHash,
|
|
bytes32 policyProfileKey
|
|
) external onlyRole(VAULT_DEPLOYER_ROLE) returns (address vault, address depositToken, address debtToken) {
|
|
(vault, depositToken, debtToken) = _deployVaultWithDecimals(
|
|
owner, entity, asset, currency, depositDecimals, debtDecimals, debtTransferable
|
|
);
|
|
_recordGruVault(vault, entity, asset, depositToken, debtToken, gruTier, ibanHash, policyProfileKey);
|
|
}
|
|
|
|
/**
|
|
* @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];
|
|
}
|
|
}
|