refactor(archive): move historical contracts and adapters to archive directory

- Archived multiple non-EVM adapters (Algorand, Hedera, Tron, TON, Cosmos, Solana) and compliance contracts (IndyVerifier) to `archive/solidity/contracts/`.
- Updated documentation to reflect the historical status of archived components.
- Adjusted `foundry.toml` and `README.md` for clarity on historical dependencies and configurations.
- Enhanced Makefile and package.json scripts for improved contract testing and building processes.
- Removed obsolete contracts (AlltraCustomBridge, CommodityCCIPBridge, ISO4217WCCIPBridge, VaultBridgeAdapter) from the main directory.
- Updated implementation reports to indicate archived status for various components.
This commit is contained in:
defiQUG
2026-04-12 18:21:05 -07:00
parent 8ec6af94d5
commit 2b52cc6e32
146 changed files with 2010 additions and 423 deletions

View File

@@ -1,128 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./interfaces/IAlltraTransport.sol";
/**
* @title AlltraCustomBridge
* @notice Custom transport for 138 <-> ALL Mainnet (651940). Locks tokens and emits event; no CCIP.
* @dev Deploy at same address on 138 and 651940 via CREATE2. On 138: lock + emit LockForAlltra.
* Off-chain relayer or contract on 651940 completes mint/unlock. On 651940: implement
* unlockOrMint (called by relayer or same contract) to complete the flow.
*/
contract AlltraCustomBridge is IAlltraTransport, AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant RELAYER_ROLE = keccak256("RELAYER_ROLE");
uint256 public constant ALL_MAINNET_CHAIN_ID = 651940;
mapping(bytes32 => LockRecord) public locks;
mapping(bytes32 => bool) public releasedOnAlltra; // on 651940: prevent double release
mapping(address => uint256) public nonces;
bool private _hasRelayer;
struct LockRecord {
address sender;
address token;
uint256 amount;
address recipient;
uint256 createdAt;
bool released;
}
event LockForAlltra(
bytes32 indexed requestId,
address indexed sender,
address indexed token,
uint256 amount,
address recipient,
uint256 sourceChainId
);
event UnlockOnAlltra(
bytes32 indexed requestId,
address indexed recipient,
address indexed token,
uint256 amount
);
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(RELAYER_ROLE, admin);
_hasRelayer = true;
}
/**
* @notice Lock tokens and emit event for relay to ALL Mainnet. Does not use CCIP.
*/
function lockAndRelay(
address token,
uint256 amount,
address recipient
) external payable override nonReentrant returns (bytes32 requestId) {
require(recipient != address(0), "zero recipient");
require(amount > 0, "zero amount");
requestId = keccak256(abi.encodePacked(
msg.sender,
token,
amount,
recipient,
nonces[msg.sender]++,
block.chainid,
block.timestamp
));
if (token == address(0)) {
require(msg.value >= amount, "insufficient value");
} else {
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
}
locks[requestId] = LockRecord({
sender: msg.sender,
token: token,
amount: amount,
recipient: recipient,
createdAt: block.timestamp,
released: false
});
emit LockForAlltra(requestId, msg.sender, token, amount, recipient, block.chainid);
return requestId;
}
function isConfigured() external view override returns (bool) {
return _hasRelayer;
}
/**
* @notice On ALL Mainnet (651940): release tokens to recipient after relay proof.
* @dev Only RELAYER_ROLE; in production, verify merkle proof or signature from source chain.
* Uses releasedOnAlltra[requestId] to prevent double release on this chain.
*/
function releaseOnAlltra(
bytes32 requestId,
address token,
uint256 amount,
address recipient
) external onlyRole(RELAYER_ROLE) nonReentrant {
require(!releasedOnAlltra[requestId], "already released");
releasedOnAlltra[requestId] = true;
if (token == address(0)) {
(bool sent,) = payable(recipient).call{value: amount}("");
require(sent, "transfer failed");
} else {
IERC20(token).safeTransfer(recipient, amount);
}
emit UnlockOnAlltra(requestId, recipient, token, amount);
}
receive() external payable {}
}

View File

@@ -1,212 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./UniversalCCIPBridge.sol";
/**
* @title CommodityCCIPBridge
* @notice Specialized bridge for commodity-backed tokens (gold, oil, etc.)
* @dev Includes certificate validation and physical delivery coordination
*/
contract CommodityCCIPBridge is UniversalCCIPBridge {
// Certificate registry (authenticity verification)
mapping(address => mapping(bytes32 => bool)) public validCertificates;
mapping(address => mapping(bytes32 => CertificateInfo)) public certificates;
struct CertificateInfo {
bytes32 certificateHash;
address custodian;
uint256 quantity;
string commodityType;
uint256 issuedAt;
uint256 expiresAt;
bool isValid;
}
// Custodian registry
mapping(address => address) public tokenCustodians;
mapping(address => bool) public approvedCustodians;
// Physical delivery tracking
struct DeliveryRequest {
bytes32 messageId;
address token;
uint256 amount;
address requester;
string deliveryAddress;
uint256 requestedAt;
DeliveryStatus status;
}
enum DeliveryStatus {
Requested,
Confirmed,
InTransit,
Delivered,
Cancelled
}
mapping(bytes32 => DeliveryRequest) public deliveryRequests;
bytes32[] public deliveryIds;
event CertificateRegistered(
address indexed token,
bytes32 indexed certificateHash,
address custodian
);
event PhysicalDeliveryRequested(
bytes32 indexed deliveryId,
bytes32 indexed messageId,
address token,
uint256 amount
);
event DeliveryStatusUpdated(
bytes32 indexed deliveryId,
DeliveryStatus status
);
/**
* @notice Bridge commodity token with certificate validation
*/
function bridgeCommodity(
address token,
uint256 amount,
uint64 destinationChain,
address recipient,
bytes32 certificateHash,
bytes calldata custodianSignature
) external nonReentrant returns (bytes32 messageId) {
// Verify asset is Commodity type
UniversalAssetRegistry.UniversalAsset memory asset = assetRegistry.getAsset(token);
require(asset.assetType == UniversalAssetRegistry.AssetType.Commodity, "Not commodity");
require(asset.isActive, "Asset not active");
// Verify certificate
require(validCertificates[token][certificateHash], "Invalid certificate");
CertificateInfo memory cert = certificates[token][certificateHash];
require(cert.isValid, "Certificate not valid");
require(block.timestamp < cert.expiresAt, "Certificate expired");
require(cert.quantity >= amount, "Certificate insufficient");
// Verify custodian
require(approvedCustodians[cert.custodian], "Custodian not approved");
// Verify custodian signature
_verifyCustodianSignature(token, amount, certificateHash, custodianSignature, cert.custodian);
// Execute bridge
BridgeOperation memory op = BridgeOperation({
token: token,
amount: amount,
destinationChain: destinationChain,
recipient: recipient,
assetType: bytes32(uint256(UniversalAssetRegistry.AssetType.Commodity)),
usePMM: true, // Commodities can use PMM
useVault: false,
complianceProof: abi.encode(certificateHash),
vaultInstructions: ""
});
messageId = this.bridge(op);
return messageId;
}
/**
* @notice Initiate physical delivery of commodity
*/
function initiatePhysicalDelivery(
bytes32 messageId,
string calldata deliveryAddress
) external returns (bytes32 deliveryId) {
require(messageId != bytes32(0), "Invalid message ID");
deliveryId = keccak256(abi.encode(messageId, msg.sender, block.timestamp));
// This would integrate with off-chain logistics systems
deliveryRequests[deliveryId] = DeliveryRequest({
messageId: messageId,
token: address(0), // Would be fetched from bridge record
amount: 0, // Would be fetched from bridge record
requester: msg.sender,
deliveryAddress: deliveryAddress,
requestedAt: block.timestamp,
status: DeliveryStatus.Requested
});
deliveryIds.push(deliveryId);
emit PhysicalDeliveryRequested(deliveryId, messageId, address(0), 0);
return deliveryId;
}
/**
* @notice Update physical delivery status
*/
function updateDeliveryStatus(
bytes32 deliveryId,
DeliveryStatus status
) external onlyRole(BRIDGE_OPERATOR_ROLE) {
require(deliveryRequests[deliveryId].requestedAt > 0, "Delivery not found");
deliveryRequests[deliveryId].status = status;
emit DeliveryStatusUpdated(deliveryId, status);
}
/**
* @notice Verify custodian signature
*/
function _verifyCustodianSignature(
address /* token */,
uint256 /* amount */,
bytes32 certificateHash,
bytes memory signature,
address custodian
) internal view {
// In production, this would use EIP-712 signature verification
// For now, simplified check
require(signature.length > 0, "Missing signature");
require(custodian != address(0), "Invalid custodian");
}
// Admin functions
function registerCertificate(
address token,
bytes32 certificateHash,
address custodian,
uint256 quantity,
string calldata commodityType,
uint256 expiresAt
) external onlyRole(BRIDGE_OPERATOR_ROLE) {
validCertificates[token][certificateHash] = true;
certificates[token][certificateHash] = CertificateInfo({
certificateHash: certificateHash,
custodian: custodian,
quantity: quantity,
commodityType: commodityType,
issuedAt: block.timestamp,
expiresAt: expiresAt,
isValid: true
});
emit CertificateRegistered(token, certificateHash, custodian);
}
function approveCustodian(address custodian, bool approved) external onlyRole(DEFAULT_ADMIN_ROLE) {
approvedCustodians[custodian] = approved;
}
function revokeCertificate(address token, bytes32 certificateHash)
external onlyRole(BRIDGE_OPERATOR_ROLE) {
validCertificates[token][certificateHash] = false;
certificates[token][certificateHash].isValid = false;
}
}

View File

@@ -1,105 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "./UniversalCCIPBridge.sol";
import "../iso4217w/interfaces/IISO4217WToken.sol";
/**
* @title ISO4217WCCIPBridge
* @notice Specialized bridge for ISO-4217W eMoney/CBDC tokens
* @dev Enforces KYC compliance and reserve backing verification
*/
contract ISO4217WCCIPBridge is UniversalCCIPBridge {
mapping(address => bool) public kycVerified;
mapping(address => uint256) public kycExpiration;
mapping(string => bool) public allowedJurisdictions;
mapping(address => string) public userJurisdictions;
mapping(address => uint256) public verifiedReserves;
event KYCVerified(address indexed user, uint256 expirationTime);
event KYCRevoked(address indexed user);
event JurisdictionEnabled(string indexed jurisdiction);
event ReserveVerified(address indexed token, uint256 amount);
function bridgeISO4217W(
address token,
uint256 amount,
uint64 destinationChain,
address recipient,
bytes calldata complianceProof
) external nonReentrant returns (bytes32 messageId) {
UniversalAssetRegistry.UniversalAsset memory asset = assetRegistry.getAsset(token);
require(asset.assetType == UniversalAssetRegistry.AssetType.ISO4217W, "Not ISO-4217W");
require(asset.isActive, "Asset not active");
require(_checkKYC(msg.sender), "KYC required");
require(_checkKYC(recipient), "Recipient KYC required");
string memory senderJurisdiction = userJurisdictions[msg.sender];
string memory recipientJurisdiction = userJurisdictions[recipient];
require(
allowedJurisdictions[senderJurisdiction] &&
allowedJurisdictions[recipientJurisdiction],
"Jurisdiction not allowed"
);
require(_verifyReserveBacking(token, amount), "Insufficient reserves");
BridgeOperation memory op = BridgeOperation({
token: token,
amount: amount,
destinationChain: destinationChain,
recipient: recipient,
assetType: bytes32(uint256(UniversalAssetRegistry.AssetType.ISO4217W)),
usePMM: false,
useVault: false,
complianceProof: complianceProof,
vaultInstructions: ""
});
messageId = this.bridge(op);
return messageId;
}
function _verifyReserveBacking(address token, uint256 amount) internal view returns (bool) {
try IISO4217WToken(token).verifiedReserve() returns (uint256 reserve) {
return reserve >= amount;
} catch {
return verifiedReserves[token] >= amount;
}
}
function _checkKYC(address user) internal view returns (bool) {
return kycVerified[user] && block.timestamp < kycExpiration[user];
}
function setKYCStatus(address user, bool status, uint256 expirationTime)
external onlyRole(BRIDGE_OPERATOR_ROLE) {
kycVerified[user] = status;
kycExpiration[user] = expirationTime;
if (status) {
emit KYCVerified(user, expirationTime);
} else {
emit KYCRevoked(user);
}
}
function enableJurisdiction(string calldata jurisdiction) external onlyRole(DEFAULT_ADMIN_ROLE) {
allowedJurisdictions[jurisdiction] = true;
emit JurisdictionEnabled(jurisdiction);
}
function setUserJurisdiction(address user, string calldata jurisdiction)
external onlyRole(BRIDGE_OPERATOR_ROLE) {
userJurisdictions[user] = jurisdiction;
}
function updateVerifiedReserve(address token, uint256 reserve)
external onlyRole(BRIDGE_OPERATOR_ROLE) {
verifiedReserves[token] = reserve;
emit ReserveVerified(token, reserve);
}
}

View File

@@ -1,47 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "./UniversalCCIPBridge.sol";
contract VaultBridgeAdapter is AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant ADAPTER_ADMIN_ROLE = keccak256("ADAPTER_ADMIN_ROLE");
address public vaultFactory;
UniversalCCIPBridge public bridge;
mapping(address => address) public userVaults;
event VaultCreated(address indexed user, address indexed vault);
constructor(address _vaultFactory, address _bridge, address admin) {
require(_vaultFactory != address(0), "Zero factory");
require(_bridge != address(0), "Zero bridge");
vaultFactory = _vaultFactory;
bridge = UniversalCCIPBridge(payable(_bridge));
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(ADAPTER_ADMIN_ROLE, admin);
}
function getOrCreateVault(address user) public returns (address vault) {
vault = userVaults[user];
if (vault == address(0)) {
(bool success, bytes memory data) = vaultFactory.call(
abi.encodeWithSignature("createVault(address)", user)
);
if (success) {
vault = abi.decode(data, (address));
userVaults[user] = vault;
emit VaultCreated(user, vault);
}
}
return vault;
}
}

View File

@@ -1,140 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../../interfaces/IChainAdapter.sol";
contract AlgorandAdapter is IChainAdapter, AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant BRIDGE_OPERATOR_ROLE = keccak256("BRIDGE_OPERATOR_ROLE");
bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
bool public isActive;
mapping(bytes32 => BridgeRequest) public bridgeRequests;
mapping(bytes32 => string) public txHashes;
mapping(address => uint256) public nonces;
event AlgorandBridgeInitiated(
bytes32 indexed requestId,
address indexed sender,
address indexed token,
uint256 amount,
string destination
);
event AlgorandBridgeConfirmed(
bytes32 indexed requestId,
string indexed txHash
);
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(BRIDGE_OPERATOR_ROLE, admin);
_grantRole(ORACLE_ROLE, admin);
isActive = true;
}
function getChainType() external pure override returns (string memory) {
return "Algorand";
}
function getChainIdentifier() external pure override returns (uint256 chainId, string memory identifier) {
return (0, "Algorand-Mainnet");
}
function validateDestination(bytes calldata destination) external pure override returns (bool) {
return destination.length > 0;
}
function bridge(
address token,
uint256 amount,
bytes calldata destination,
bytes calldata recipient
) external payable override nonReentrant returns (bytes32 requestId) {
require(isActive, "Adapter inactive");
require(amount > 0, "Zero amount");
string memory dest = string(destination);
requestId = keccak256(abi.encodePacked(
msg.sender,
token,
amount,
dest,
nonces[msg.sender]++,
block.timestamp
));
if (token == address(0)) {
require(msg.value >= amount, "Insufficient ETH");
} else {
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
}
bridgeRequests[requestId] = BridgeRequest({
sender: msg.sender,
token: token,
amount: amount,
destinationData: destination,
requestId: requestId,
status: BridgeStatus.Locked,
createdAt: block.timestamp,
completedAt: 0
});
emit AlgorandBridgeInitiated(requestId, msg.sender, token, amount, dest);
return requestId;
}
function confirmTransaction(
bytes32 requestId,
string calldata txHash
) external onlyRole(ORACLE_ROLE) {
BridgeRequest storage request = bridgeRequests[requestId];
require(request.status == BridgeStatus.Locked, "Invalid status");
request.status = BridgeStatus.Confirmed;
request.completedAt = block.timestamp;
txHashes[requestId] = txHash;
emit AlgorandBridgeConfirmed(requestId, txHash);
}
function getBridgeStatus(bytes32 requestId)
external view override returns (BridgeRequest memory) {
return bridgeRequests[requestId];
}
function cancelBridge(bytes32 requestId) external override returns (bool) {
BridgeRequest storage request = bridgeRequests[requestId];
require(request.status == BridgeStatus.Pending || request.status == BridgeStatus.Locked, "Cannot cancel");
require(msg.sender == request.sender, "Not request sender");
if (request.token == address(0)) {
payable(request.sender).transfer(request.amount);
} else {
IERC20(request.token).safeTransfer(request.sender, request.amount);
}
request.status = BridgeStatus.Cancelled;
return true;
}
function estimateFee(
address token,
uint256 amount,
bytes calldata destination
) external view override returns (uint256 fee) {
return 1000000000000000;
}
function setIsActive(bool _isActive) external onlyRole(DEFAULT_ADMIN_ROLE) {
isActive = _isActive;
}
}

View File

@@ -1,140 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../../interfaces/IChainAdapter.sol";
contract CosmosAdapter is IChainAdapter, AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant BRIDGE_OPERATOR_ROLE = keccak256("BRIDGE_OPERATOR_ROLE");
bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
bool public isActive;
mapping(bytes32 => BridgeRequest) public bridgeRequests;
mapping(bytes32 => string) public txHashes;
mapping(address => uint256) public nonces;
event CosmosBridgeInitiated(
bytes32 indexed requestId,
address indexed sender,
address indexed token,
uint256 amount,
string destination
);
event CosmosBridgeConfirmed(
bytes32 indexed requestId,
string indexed txHash
);
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(BRIDGE_OPERATOR_ROLE, admin);
_grantRole(ORACLE_ROLE, admin);
isActive = true;
}
function getChainType() external pure override returns (string memory) {
return "Cosmos";
}
function getChainIdentifier() external pure override returns (uint256 chainId, string memory identifier) {
return (0, "Cosmos-Mainnet");
}
function validateDestination(bytes calldata destination) external pure override returns (bool) {
return destination.length > 0;
}
function bridge(
address token,
uint256 amount,
bytes calldata destination,
bytes calldata recipient
) external payable override nonReentrant returns (bytes32 requestId) {
require(isActive, "Adapter inactive");
require(amount > 0, "Zero amount");
string memory dest = string(destination);
requestId = keccak256(abi.encodePacked(
msg.sender,
token,
amount,
dest,
nonces[msg.sender]++,
block.timestamp
));
if (token == address(0)) {
require(msg.value >= amount, "Insufficient ETH");
} else {
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
}
bridgeRequests[requestId] = BridgeRequest({
sender: msg.sender,
token: token,
amount: amount,
destinationData: destination,
requestId: requestId,
status: BridgeStatus.Locked,
createdAt: block.timestamp,
completedAt: 0
});
emit CosmosBridgeInitiated(requestId, msg.sender, token, amount, dest);
return requestId;
}
function confirmTransaction(
bytes32 requestId,
string calldata txHash
) external onlyRole(ORACLE_ROLE) {
BridgeRequest storage request = bridgeRequests[requestId];
require(request.status == BridgeStatus.Locked, "Invalid status");
request.status = BridgeStatus.Confirmed;
request.completedAt = block.timestamp;
txHashes[requestId] = txHash;
emit CosmosBridgeConfirmed(requestId, txHash);
}
function getBridgeStatus(bytes32 requestId)
external view override returns (BridgeRequest memory) {
return bridgeRequests[requestId];
}
function cancelBridge(bytes32 requestId) external override returns (bool) {
BridgeRequest storage request = bridgeRequests[requestId];
require(request.status == BridgeStatus.Pending || request.status == BridgeStatus.Locked, "Cannot cancel");
require(msg.sender == request.sender, "Not request sender");
if (request.token == address(0)) {
payable(request.sender).transfer(request.amount);
} else {
IERC20(request.token).safeTransfer(request.sender, request.amount);
}
request.status = BridgeStatus.Cancelled;
return true;
}
function estimateFee(
address token,
uint256 amount,
bytes calldata destination
) external view override returns (uint256 fee) {
return 1000000000000000;
}
function setIsActive(bool _isActive) external onlyRole(DEFAULT_ADMIN_ROLE) {
isActive = _isActive;
}
}

View File

@@ -1,140 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../../interfaces/IChainAdapter.sol";
contract HederaAdapter is IChainAdapter, AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant BRIDGE_OPERATOR_ROLE = keccak256("BRIDGE_OPERATOR_ROLE");
bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
bool public isActive;
mapping(bytes32 => BridgeRequest) public bridgeRequests;
mapping(bytes32 => string) public txHashes;
mapping(address => uint256) public nonces;
event HederaBridgeInitiated(
bytes32 indexed requestId,
address indexed sender,
address indexed token,
uint256 amount,
string destination
);
event HederaBridgeConfirmed(
bytes32 indexed requestId,
string indexed txHash
);
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(BRIDGE_OPERATOR_ROLE, admin);
_grantRole(ORACLE_ROLE, admin);
isActive = true;
}
function getChainType() external pure override returns (string memory) {
return "Hedera";
}
function getChainIdentifier() external pure override returns (uint256 chainId, string memory identifier) {
return (0, "Hedera-Mainnet");
}
function validateDestination(bytes calldata destination) external pure override returns (bool) {
return destination.length > 0;
}
function bridge(
address token,
uint256 amount,
bytes calldata destination,
bytes calldata recipient
) external payable override nonReentrant returns (bytes32 requestId) {
require(isActive, "Adapter inactive");
require(amount > 0, "Zero amount");
string memory dest = string(destination);
requestId = keccak256(abi.encodePacked(
msg.sender,
token,
amount,
dest,
nonces[msg.sender]++,
block.timestamp
));
if (token == address(0)) {
require(msg.value >= amount, "Insufficient ETH");
} else {
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
}
bridgeRequests[requestId] = BridgeRequest({
sender: msg.sender,
token: token,
amount: amount,
destinationData: destination,
requestId: requestId,
status: BridgeStatus.Locked,
createdAt: block.timestamp,
completedAt: 0
});
emit HederaBridgeInitiated(requestId, msg.sender, token, amount, dest);
return requestId;
}
function confirmTransaction(
bytes32 requestId,
string calldata txHash
) external onlyRole(ORACLE_ROLE) {
BridgeRequest storage request = bridgeRequests[requestId];
require(request.status == BridgeStatus.Locked, "Invalid status");
request.status = BridgeStatus.Confirmed;
request.completedAt = block.timestamp;
txHashes[requestId] = txHash;
emit HederaBridgeConfirmed(requestId, txHash);
}
function getBridgeStatus(bytes32 requestId)
external view override returns (BridgeRequest memory) {
return bridgeRequests[requestId];
}
function cancelBridge(bytes32 requestId) external override returns (bool) {
BridgeRequest storage request = bridgeRequests[requestId];
require(request.status == BridgeStatus.Pending || request.status == BridgeStatus.Locked, "Cannot cancel");
require(msg.sender == request.sender, "Not request sender");
if (request.token == address(0)) {
payable(request.sender).transfer(request.amount);
} else {
IERC20(request.token).safeTransfer(request.sender, request.amount);
}
request.status = BridgeStatus.Cancelled;
return true;
}
function estimateFee(
address token,
uint256 amount,
bytes calldata destination
) external view override returns (uint256 fee) {
return 1000000000000000;
}
function setIsActive(bool _isActive) external onlyRole(DEFAULT_ADMIN_ROLE) {
isActive = _isActive;
}
}

View File

@@ -1,140 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../../interfaces/IChainAdapter.sol";
contract SolanaAdapter is IChainAdapter, AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant BRIDGE_OPERATOR_ROLE = keccak256("BRIDGE_OPERATOR_ROLE");
bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
bool public isActive;
mapping(bytes32 => BridgeRequest) public bridgeRequests;
mapping(bytes32 => string) public txHashes;
mapping(address => uint256) public nonces;
event SolanaBridgeInitiated(
bytes32 indexed requestId,
address indexed sender,
address indexed token,
uint256 amount,
string destination
);
event SolanaBridgeConfirmed(
bytes32 indexed requestId,
string indexed txHash
);
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(BRIDGE_OPERATOR_ROLE, admin);
_grantRole(ORACLE_ROLE, admin);
isActive = true;
}
function getChainType() external pure override returns (string memory) {
return "Solana";
}
function getChainIdentifier() external pure override returns (uint256 chainId, string memory identifier) {
return (0, "Solana-Mainnet");
}
function validateDestination(bytes calldata destination) external pure override returns (bool) {
return destination.length > 0;
}
function bridge(
address token,
uint256 amount,
bytes calldata destination,
bytes calldata recipient
) external payable override nonReentrant returns (bytes32 requestId) {
require(isActive, "Adapter inactive");
require(amount > 0, "Zero amount");
string memory dest = string(destination);
requestId = keccak256(abi.encodePacked(
msg.sender,
token,
amount,
dest,
nonces[msg.sender]++,
block.timestamp
));
if (token == address(0)) {
require(msg.value >= amount, "Insufficient ETH");
} else {
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
}
bridgeRequests[requestId] = BridgeRequest({
sender: msg.sender,
token: token,
amount: amount,
destinationData: destination,
requestId: requestId,
status: BridgeStatus.Locked,
createdAt: block.timestamp,
completedAt: 0
});
emit SolanaBridgeInitiated(requestId, msg.sender, token, amount, dest);
return requestId;
}
function confirmTransaction(
bytes32 requestId,
string calldata txHash
) external onlyRole(ORACLE_ROLE) {
BridgeRequest storage request = bridgeRequests[requestId];
require(request.status == BridgeStatus.Locked, "Invalid status");
request.status = BridgeStatus.Confirmed;
request.completedAt = block.timestamp;
txHashes[requestId] = txHash;
emit SolanaBridgeConfirmed(requestId, txHash);
}
function getBridgeStatus(bytes32 requestId)
external view override returns (BridgeRequest memory) {
return bridgeRequests[requestId];
}
function cancelBridge(bytes32 requestId) external override returns (bool) {
BridgeRequest storage request = bridgeRequests[requestId];
require(request.status == BridgeStatus.Pending || request.status == BridgeStatus.Locked, "Cannot cancel");
require(msg.sender == request.sender, "Not request sender");
if (request.token == address(0)) {
payable(request.sender).transfer(request.amount);
} else {
IERC20(request.token).safeTransfer(request.sender, request.amount);
}
request.status = BridgeStatus.Cancelled;
return true;
}
function estimateFee(
address token,
uint256 amount,
bytes calldata destination
) external view override returns (uint256 fee) {
return 1000000000000000;
}
function setIsActive(bool _isActive) external onlyRole(DEFAULT_ADMIN_ROLE) {
isActive = _isActive;
}
}

View File

@@ -1,140 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../../interfaces/IChainAdapter.sol";
contract TONAdapter is IChainAdapter, AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant BRIDGE_OPERATOR_ROLE = keccak256("BRIDGE_OPERATOR_ROLE");
bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
bool public isActive;
mapping(bytes32 => BridgeRequest) public bridgeRequests;
mapping(bytes32 => string) public txHashes;
mapping(address => uint256) public nonces;
event TONBridgeInitiated(
bytes32 indexed requestId,
address indexed sender,
address indexed token,
uint256 amount,
string destination
);
event TONBridgeConfirmed(
bytes32 indexed requestId,
string indexed txHash
);
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(BRIDGE_OPERATOR_ROLE, admin);
_grantRole(ORACLE_ROLE, admin);
isActive = true;
}
function getChainType() external pure override returns (string memory) {
return "TON";
}
function getChainIdentifier() external pure override returns (uint256 chainId, string memory identifier) {
return (0, "TON-Mainnet");
}
function validateDestination(bytes calldata destination) external pure override returns (bool) {
return destination.length > 0;
}
function bridge(
address token,
uint256 amount,
bytes calldata destination,
bytes calldata recipient
) external payable override nonReentrant returns (bytes32 requestId) {
require(isActive, "Adapter inactive");
require(amount > 0, "Zero amount");
string memory dest = string(destination);
requestId = keccak256(abi.encodePacked(
msg.sender,
token,
amount,
dest,
nonces[msg.sender]++,
block.timestamp
));
if (token == address(0)) {
require(msg.value >= amount, "Insufficient ETH");
} else {
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
}
bridgeRequests[requestId] = BridgeRequest({
sender: msg.sender,
token: token,
amount: amount,
destinationData: destination,
requestId: requestId,
status: BridgeStatus.Locked,
createdAt: block.timestamp,
completedAt: 0
});
emit TONBridgeInitiated(requestId, msg.sender, token, amount, dest);
return requestId;
}
function confirmTransaction(
bytes32 requestId,
string calldata txHash
) external onlyRole(ORACLE_ROLE) {
BridgeRequest storage request = bridgeRequests[requestId];
require(request.status == BridgeStatus.Locked, "Invalid status");
request.status = BridgeStatus.Confirmed;
request.completedAt = block.timestamp;
txHashes[requestId] = txHash;
emit TONBridgeConfirmed(requestId, txHash);
}
function getBridgeStatus(bytes32 requestId)
external view override returns (BridgeRequest memory) {
return bridgeRequests[requestId];
}
function cancelBridge(bytes32 requestId) external override returns (bool) {
BridgeRequest storage request = bridgeRequests[requestId];
require(request.status == BridgeStatus.Pending || request.status == BridgeStatus.Locked, "Cannot cancel");
require(msg.sender == request.sender, "Not request sender");
if (request.token == address(0)) {
payable(request.sender).transfer(request.amount);
} else {
IERC20(request.token).safeTransfer(request.sender, request.amount);
}
request.status = BridgeStatus.Cancelled;
return true;
}
function estimateFee(
address token,
uint256 amount,
bytes calldata destination
) external view override returns (uint256 fee) {
return 1000000000000000;
}
function setIsActive(bool _isActive) external onlyRole(DEFAULT_ADMIN_ROLE) {
isActive = _isActive;
}
}

View File

@@ -1,140 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "../../interfaces/IChainAdapter.sol";
contract TronAdapter is IChainAdapter, AccessControl, ReentrancyGuard {
using SafeERC20 for IERC20;
bytes32 public constant BRIDGE_OPERATOR_ROLE = keccak256("BRIDGE_OPERATOR_ROLE");
bytes32 public constant ORACLE_ROLE = keccak256("ORACLE_ROLE");
bool public isActive;
mapping(bytes32 => BridgeRequest) public bridgeRequests;
mapping(bytes32 => string) public txHashes;
mapping(address => uint256) public nonces;
event TronBridgeInitiated(
bytes32 indexed requestId,
address indexed sender,
address indexed token,
uint256 amount,
string destination
);
event TronBridgeConfirmed(
bytes32 indexed requestId,
string indexed txHash
);
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(BRIDGE_OPERATOR_ROLE, admin);
_grantRole(ORACLE_ROLE, admin);
isActive = true;
}
function getChainType() external pure override returns (string memory) {
return "Tron";
}
function getChainIdentifier() external pure override returns (uint256 chainId, string memory identifier) {
return (0, "Tron-Mainnet");
}
function validateDestination(bytes calldata destination) external pure override returns (bool) {
return destination.length > 0;
}
function bridge(
address token,
uint256 amount,
bytes calldata destination,
bytes calldata recipient
) external payable override nonReentrant returns (bytes32 requestId) {
require(isActive, "Adapter inactive");
require(amount > 0, "Zero amount");
string memory dest = string(destination);
requestId = keccak256(abi.encodePacked(
msg.sender,
token,
amount,
dest,
nonces[msg.sender]++,
block.timestamp
));
if (token == address(0)) {
require(msg.value >= amount, "Insufficient ETH");
} else {
IERC20(token).safeTransferFrom(msg.sender, address(this), amount);
}
bridgeRequests[requestId] = BridgeRequest({
sender: msg.sender,
token: token,
amount: amount,
destinationData: destination,
requestId: requestId,
status: BridgeStatus.Locked,
createdAt: block.timestamp,
completedAt: 0
});
emit TronBridgeInitiated(requestId, msg.sender, token, amount, dest);
return requestId;
}
function confirmTransaction(
bytes32 requestId,
string calldata txHash
) external onlyRole(ORACLE_ROLE) {
BridgeRequest storage request = bridgeRequests[requestId];
require(request.status == BridgeStatus.Locked, "Invalid status");
request.status = BridgeStatus.Confirmed;
request.completedAt = block.timestamp;
txHashes[requestId] = txHash;
emit TronBridgeConfirmed(requestId, txHash);
}
function getBridgeStatus(bytes32 requestId)
external view override returns (BridgeRequest memory) {
return bridgeRequests[requestId];
}
function cancelBridge(bytes32 requestId) external override returns (bool) {
BridgeRequest storage request = bridgeRequests[requestId];
require(request.status == BridgeStatus.Pending || request.status == BridgeStatus.Locked, "Cannot cancel");
require(msg.sender == request.sender, "Not request sender");
if (request.token == address(0)) {
payable(request.sender).transfer(request.amount);
} else {
IERC20(request.token).safeTransfer(request.sender, request.amount);
}
request.status = BridgeStatus.Cancelled;
return true;
}
function estimateFee(
address token,
uint256 amount,
bytes calldata destination
) external view override returns (uint256 fee) {
return 1000000000000000;
}
function setIsActive(bool _isActive) external onlyRole(DEFAULT_ADMIN_ROLE) {
isActive = _isActive;
}
}

View File

@@ -1,74 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IAtomicSettlementAdapter} from "../interfaces/IAtomicSettlementAdapter.sol";
import {UniversalCCIPBridge} from "../../UniversalCCIPBridge.sol";
contract UniversalCcipAtomicSettlementAdapter is AccessControl, IAtomicSettlementAdapter {
using SafeERC20 for IERC20;
bytes32 public constant ROUTER_ROLE = keccak256("ROUTER_ROLE");
UniversalCCIPBridge public immutable universalBridge;
struct SettlementParams {
uint64 destinationChain;
address destinationRecipient;
bytes32 assetType;
bool usePMM;
bool useVault;
bytes complianceProof;
bytes vaultInstructions;
}
event AtomicSettlementBridged(
bytes32 indexed obligationId,
bytes32 indexed messageId,
address indexed token,
uint256 amount,
uint64 destinationChain,
address recipient
);
constructor(address universalBridge_, address admin) {
universalBridge = UniversalCCIPBridge(payable(universalBridge_));
_grantRole(DEFAULT_ADMIN_ROLE, admin);
}
function executeSettlement(
bytes32 obligationId,
address token,
uint256 amount,
address recipient,
bytes calldata data
) external payable onlyRole(ROUTER_ROLE) returns (bytes32 settlementId) {
SettlementParams memory params = abi.decode(data, (SettlementParams));
address finalRecipient = params.destinationRecipient == address(0) ? recipient : params.destinationRecipient;
IERC20(token).forceApprove(address(universalBridge), amount);
settlementId = universalBridge.bridge{value: msg.value}(
UniversalCCIPBridge.BridgeOperation({
token: token,
amount: amount,
destinationChain: params.destinationChain,
recipient: finalRecipient,
assetType: params.assetType,
usePMM: params.usePMM,
useVault: params.useVault,
complianceProof: params.complianceProof,
vaultInstructions: params.vaultInstructions
})
);
IERC20(token).forceApprove(address(universalBridge), 0);
emit AtomicSettlementBridged(
obligationId,
settlementId,
token,
amount,
params.destinationChain,
finalRecipient
);
}
}

View File

@@ -1,13 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IAtomicUnwindAdapter {
function executeUnwind(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 minAmountOut,
address recipient,
bytes calldata data
) external returns (uint256 amountOut);
}

View File

@@ -1,227 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "../../vendor/openzeppelin/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
/**
* @title BridgeModuleRegistry
* @notice Registry for bridge modules (hooks, validators, fee calculators)
* @dev Enables extending bridge functionality without modifying core contracts
*/
contract BridgeModuleRegistry is
Initializable,
AccessControlUpgradeable,
UUPSUpgradeable
{
bytes32 public constant MODULE_ADMIN_ROLE = keccak256("MODULE_ADMIN_ROLE");
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
enum ModuleType {
PreBridgeHook, // Execute before bridge (e.g., compliance check)
PostBridgeHook, // Execute after bridge (e.g., notification)
FeeCalculator, // Custom fee calculation
RateLimiter, // Rate limiting logic
Validator // Custom validation
}
struct Module {
address implementation;
bool active;
uint256 priority;
uint256 registeredAt;
}
// Storage
mapping(ModuleType => address[]) public modules;
mapping(ModuleType => mapping(address => Module)) public moduleInfo;
mapping(ModuleType => uint256) public moduleCount;
event ModuleRegistered(
ModuleType indexed moduleType,
address indexed module,
uint256 priority
);
event ModuleUnregistered(
ModuleType indexed moduleType,
address indexed module
);
event ModuleExecuted(
ModuleType indexed moduleType,
address indexed module,
bool success
);
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(address admin) external initializer {
__AccessControl_init();
__UUPSUpgradeable_init();
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(MODULE_ADMIN_ROLE, admin);
_grantRole(UPGRADER_ROLE, admin);
}
function _authorizeUpgrade(address newImplementation)
internal override onlyRole(UPGRADER_ROLE) {}
/**
* @notice Register module
*/
function registerModule(
ModuleType moduleType,
address module,
uint256 priority
) external onlyRole(MODULE_ADMIN_ROLE) {
require(module != address(0), "Zero address");
require(module.code.length > 0, "Not a contract");
require(moduleInfo[moduleType][module].implementation == address(0), "Already registered");
modules[moduleType].push(module);
moduleInfo[moduleType][module] = Module({
implementation: module,
active: true,
priority: priority,
registeredAt: block.timestamp
});
moduleCount[moduleType]++;
emit ModuleRegistered(moduleType, module, priority);
}
/**
* @notice Unregister module
*/
function unregisterModule(
ModuleType moduleType,
address module
) external onlyRole(MODULE_ADMIN_ROLE) {
require(moduleInfo[moduleType][module].implementation != address(0), "Not registered");
moduleInfo[moduleType][module].active = false;
moduleCount[moduleType]--;
emit ModuleUnregistered(moduleType, module);
}
/**
* @notice Execute all modules of a type
*/
function executeModules(
ModuleType moduleType,
bytes calldata data
) external returns (bytes[] memory results) {
address[] memory activeModules = modules[moduleType];
uint256 activeCount = 0;
// Count active modules
for (uint256 i = 0; i < activeModules.length; i++) {
if (moduleInfo[moduleType][activeModules[i]].active) {
activeCount++;
}
}
results = new bytes[](activeCount);
uint256 resultIndex = 0;
// Execute each active module
for (uint256 i = 0; i < activeModules.length; i++) {
address module = activeModules[i];
if (!moduleInfo[moduleType][module].active) continue;
(bool success, bytes memory result) = module.call(data);
emit ModuleExecuted(moduleType, module, success);
if (success) {
results[resultIndex] = result;
resultIndex++;
}
}
return results;
}
/**
* @notice Execute single module
*/
function executeModule(
ModuleType moduleType,
address module,
bytes calldata data
) external returns (bytes memory result) {
require(moduleInfo[moduleType][module].active, "Module not active");
(bool success, bytes memory returnData) = module.call(data);
emit ModuleExecuted(moduleType, module, success);
require(success, "Module execution failed");
return returnData;
}
/**
* @notice Set module priority
*/
function setModulePriority(
ModuleType moduleType,
address module,
uint256 priority
) external onlyRole(MODULE_ADMIN_ROLE) {
require(moduleInfo[moduleType][module].implementation != address(0), "Not registered");
moduleInfo[moduleType][module].priority = priority;
}
// View functions
function getModules(ModuleType moduleType) external view returns (address[] memory) {
return modules[moduleType];
}
function getActiveModules(ModuleType moduleType) external view returns (address[] memory) {
address[] memory allModules = modules[moduleType];
uint256 activeCount = 0;
for (uint256 i = 0; i < allModules.length; i++) {
if (moduleInfo[moduleType][allModules[i]].active) {
activeCount++;
}
}
address[] memory activeModules = new address[](activeCount);
uint256 index = 0;
for (uint256 i = 0; i < allModules.length; i++) {
if (moduleInfo[moduleType][allModules[i]].active) {
activeModules[index] = allModules[i];
index++;
}
}
return activeModules;
}
function getModuleInfo(ModuleType moduleType, address module)
external view returns (Module memory) {
return moduleInfo[moduleType][module];
}
function getModuleCount(ModuleType moduleType) external view returns (uint256) {
return moduleCount[moduleType];
}
function isModuleActive(ModuleType moduleType, address module) external view returns (bool) {
return moduleInfo[moduleType][module].active;
}
}

View File

@@ -1,139 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../ccip/IRouterClient.sol";
/**
* @title CCIPTxReporter
* @notice Sends Chain-138 transaction reports to Ethereum Mainnet via Chainlink CCIP
* @dev Encodes batch data for CCIPLogger.ccipReceive
*/
contract CCIPTxReporter {
IRouterClient public immutable router;
uint64 public destChainSelector;
address public destReceiver;
address public owner;
event SingleTxReported(bytes32 indexed txHash, address from, address to, uint256 value);
event BatchReported(bytes32 indexed batchId, uint256 count);
modifier onlyOwner() {
require(msg.sender == owner, "CCIPTxReporter: only owner");
_;
}
constructor(
address _router,
uint64 _destChainSelector,
address _destReceiver
) {
require(_router != address(0), "CCIPTxReporter: zero router");
require(_destReceiver != address(0), "CCIPTxReporter: zero receiver");
router = IRouterClient(payable(_router));
destChainSelector = _destChainSelector;
destReceiver = _destReceiver;
owner = msg.sender;
}
/**
* @notice Report a single transaction to the CCIPLogger on destination chain
*/
function reportTx(
bytes32 txHash,
address from,
address to,
uint256 value,
bytes calldata extraData
) external payable returns (bytes32 messageId) {
bytes32[] memory txHashes = new bytes32[](1);
address[] memory froms = new address[](1);
address[] memory tos = new address[](1);
uint256[] memory values = new uint256[](1);
txHashes[0] = txHash;
froms[0] = from;
tos[0] = to;
values[0] = value;
messageId = _reportBatch(txHash, txHashes, froms, tos, values, extraData);
emit SingleTxReported(txHash, from, to, value);
}
/**
* @notice Report a batch of transactions to the CCIPLogger on destination chain
*/
function reportBatch(
bytes32 batchId,
bytes32[] calldata txHashes,
address[] calldata froms,
address[] calldata tos,
uint256[] calldata values,
bytes calldata extraData
) external payable returns (bytes32 messageId) {
messageId = _reportBatch(batchId, txHashes, froms, tos, values, extraData);
emit BatchReported(batchId, txHashes.length);
}
function _reportBatch(
bytes32 batchId,
bytes32[] memory txHashes,
address[] memory froms,
address[] memory tos,
uint256[] memory values,
bytes memory extraData
) internal returns (bytes32 messageId) {
bytes memory data = abi.encode(
batchId,
txHashes,
froms,
tos,
values,
extraData
);
IRouterClient.EVM2AnyMessage memory message = IRouterClient.EVM2AnyMessage({
receiver: abi.encode(destReceiver),
data: data,
tokenAmounts: new IRouterClient.TokenAmount[](0),
feeToken: address(0),
extraArgs: ""
});
(messageId, ) = router.ccipSend{value: msg.value}(destChainSelector, message);
}
/**
* @notice Estimate fee for sending a batch
*/
function estimateFee(
bytes32[] calldata txHashes,
address[] calldata froms,
address[] calldata tos,
uint256[] calldata values
) external view returns (uint256 fee) {
// Use block.difficulty for London EVM compatibility (prevrandao is Paris+)
bytes32 batchId = keccak256(abi.encodePacked(block.timestamp, block.difficulty, txHashes.length));
bytes memory data = abi.encode(
batchId,
txHashes,
froms,
tos,
values,
bytes("")
);
IRouterClient.EVM2AnyMessage memory message = IRouterClient.EVM2AnyMessage({
receiver: abi.encode(destReceiver),
data: data,
tokenAmounts: new IRouterClient.TokenAmount[](0),
feeToken: address(0),
extraArgs: ""
});
return router.getFee(destChainSelector, message);
}
function setDestReceiver(address _destReceiver) external onlyOwner {
require(_destReceiver != address(0), "CCIPTxReporter: zero address");
destReceiver = _destReceiver;
}
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "CCIPTxReporter: zero address");
owner = newOwner;
}
}

View File

@@ -1,218 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "./IRouterClient.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title Optimized CCIP Router
* @notice Optimized version with message batching and fee caching
* @dev Performance optimizations for CCIP message handling
*/
contract CCIPRouterOptimized is IRouterClient {
using SafeERC20 for IERC20;
address public admin;
uint256 public baseFee = 1 ether;
uint256 public dataFeePerByte = 1000;
uint256 public tokenFeePerToken = 1 ether;
mapping(uint64 => address[]) public supportedTokens;
// Fee caching
mapping(bytes32 => uint256) public cachedFees;
uint256 public cacheExpiry = 1 hours;
mapping(bytes32 => uint256) public cacheTimestamp;
// Message batching
struct BatchedMessage {
bytes32[] messageIds;
uint64 destinationChainSelector;
uint256 totalFee;
uint256 timestamp;
}
mapping(uint256 => BatchedMessage) public batches;
uint256 public batchId;
uint256 public batchWindow = 5 minutes;
uint256 public maxBatchSize = 100;
event RouterAdminChanged(address indexed oldAdmin, address indexed newAdmin);
event BaseFeeUpdated(uint256 oldFee, uint256 newFee);
event MessageBatched(uint256 indexed batchId, uint256 messageCount);
event FeeCached(bytes32 indexed cacheKey, uint256 fee);
modifier onlyAdmin() {
require(msg.sender == admin, "CCIPRouterOptimized: only admin");
_;
}
constructor() {
admin = msg.sender;
}
/**
* @inheritdoc IRouterClient
*/
function ccipSend(
uint64 destinationChainSelector,
EVM2AnyMessage memory message
) external payable returns (bytes32 messageId, uint256 fees) {
fees = getFee(destinationChainSelector, message);
// Handle fee payment
if (fees > 0) {
if (message.feeToken == address(0)) {
// Native token (ETH) fees
require(msg.value >= fees, "CCIPRouterOptimized: insufficient native token fee");
} else {
// ERC20 token fees
IERC20(message.feeToken).safeTransferFrom(msg.sender, address(this), fees);
}
}
messageId = keccak256(abi.encodePacked(block.timestamp, block.number, message.data));
emit MessageSent(
messageId,
destinationChainSelector,
msg.sender,
message.receiver,
message.data,
message.tokenAmounts,
message.feeToken,
message.extraArgs
);
return (messageId, fees);
}
/**
* @inheritdoc IRouterClient
*/
function getFee(
uint64 destinationChainSelector,
EVM2AnyMessage memory message
) public view override returns (uint256 fee) {
// Check cache
bytes32 cacheKey = keccak256(abi.encode(destinationChainSelector, message.receiver, message.data.length));
if (cacheTimestamp[cacheKey] != 0 && block.timestamp < cacheTimestamp[cacheKey] + cacheExpiry) {
return cachedFees[cacheKey];
}
// Calculate fee
fee = baseFee;
fee += uint256(message.data.length) * dataFeePerByte;
for (uint256 i = 0; i < message.tokenAmounts.length; i++) {
fee += message.tokenAmounts[i].amount * tokenFeePerToken;
}
return fee;
}
/**
* @notice Batch multiple messages
*/
function batchSend(
uint64 destinationChainSelector,
EVM2AnyMessage[] memory messages
) external payable returns (uint256 batchId_, bytes32[] memory messageIds) {
require(messages.length <= maxBatchSize, "CCIPRouterOptimized: batch too large");
batchId_ = batchId++;
messageIds = new bytes32[](messages.length);
uint256 totalFee = 0;
for (uint256 i = 0; i < messages.length; i++) {
uint256 fee = getFee(destinationChainSelector, messages[i]);
totalFee += fee;
bytes32 messageId = keccak256(abi.encodePacked(block.timestamp, block.number, i, messages[i].data));
messageIds[i] = messageId;
}
require(msg.value >= totalFee, "CCIPRouterOptimized: insufficient fee");
batches[batchId_] = BatchedMessage({
messageIds: messageIds,
destinationChainSelector: destinationChainSelector,
totalFee: totalFee,
timestamp: block.timestamp
});
emit MessageBatched(batchId_, messages.length);
return (batchId_, messageIds);
}
/**
* @notice Cache fee calculation
*/
function cacheFee(
uint64 destinationChainSelector,
bytes memory receiver,
uint256 dataLength
) external returns (uint256 fee) {
bytes32 cacheKey = keccak256(abi.encode(destinationChainSelector, receiver, dataLength));
// Calculate fee
fee = baseFee + (dataLength * dataFeePerByte);
// Cache it
cachedFees[cacheKey] = fee;
cacheTimestamp[cacheKey] = block.timestamp;
emit FeeCached(cacheKey, fee);
return fee;
}
/**
* @inheritdoc IRouterClient
*/
function getSupportedTokens(
uint64 destinationChainSelector
) external view override returns (address[] memory) {
return supportedTokens[destinationChainSelector];
}
/**
* @notice Update base fee
*/
function updateBaseFee(uint256 newFee) external onlyAdmin {
require(newFee > 0, "CCIPRouterOptimized: fee must be greater than 0");
emit BaseFeeUpdated(baseFee, newFee);
baseFee = newFee;
}
/**
* @notice Update cache expiry
*/
function setCacheExpiry(uint256 newExpiry) external onlyAdmin {
cacheExpiry = newExpiry;
}
/**
* @notice Update batch window
*/
function setBatchWindow(uint256 newWindow) external onlyAdmin {
batchWindow = newWindow;
}
/**
* @notice Update max batch size
*/
function setMaxBatchSize(uint256 newSize) external onlyAdmin {
require(newSize > 0, "CCIPRouterOptimized: size must be greater than 0");
maxBatchSize = newSize;
}
/**
* @notice Change admin
*/
function changeAdmin(address newAdmin) external onlyAdmin {
require(newAdmin != address(0), "CCIPRouterOptimized: zero address");
emit RouterAdminChanged(admin, newAdmin);
admin = newAdmin;
}
}

View File

@@ -1,176 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
/**
* @title IndyVerifier
* @notice Verifies Hyperledger Indy credentials before allowing bridge operations
* @dev Uses zero-knowledge proofs for privacy-preserving credential verification
*/
contract IndyVerifier is AccessControl {
bytes32 public constant VERIFIER_ROLE = keccak256("VERIFIER_ROLE");
bytes32 public constant ISSUER_ROLE = keccak256("ISSUER_ROLE");
enum CredentialType {
KYC,
AccreditedInvestor,
Institution,
Jurisdiction,
Compliance
}
struct CredentialSchema {
string schemaId; // Indy schema ID
string credDefId; // Credential definition ID
bool required;
CredentialType credType;
uint256 minScore; // Minimum verification score (0-100)
}
struct VerificationResult {
bool verified;
uint256 score;
uint256 verifiedAt;
string proofId;
}
mapping(address => mapping(CredentialType => VerificationResult)) public userCredentials;
mapping(CredentialType => CredentialSchema) public schemas;
mapping(string => bool) public verifiedProofs; // proofId => verified
event CredentialVerified(
address indexed user,
CredentialType credType,
uint256 score,
string proofId
);
event CredentialRevoked(
address indexed user,
CredentialType credType
);
event SchemaRegistered(
CredentialType credType,
string schemaId,
string credDefId
);
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(VERIFIER_ROLE, admin);
_grantRole(ISSUER_ROLE, admin);
}
/**
* @notice Register credential schema
*/
function registerSchema(
CredentialType credType,
string calldata schemaId,
string calldata credDefId,
bool required,
uint256 minScore
) external onlyRole(ISSUER_ROLE) {
schemas[credType] = CredentialSchema({
schemaId: schemaId,
credDefId: credDefId,
required: required,
credType: credType,
minScore: minScore
});
emit SchemaRegistered(credType, schemaId, credDefId);
}
/**
* @notice Verify credential proof (called by oracle/agent)
* @dev Off-chain: Verify ZK proof with Indy
* On-chain: Store verification result
*/
function verifyCredentialProof(
address user,
CredentialType credType,
bytes calldata zkProof,
uint256 score,
string calldata proofId
) external onlyRole(VERIFIER_ROLE) returns (bool) {
require(bytes(schemas[credType].schemaId).length > 0, "Schema not registered");
require(!verifiedProofs[proofId], "Proof already used");
require(score >= schemas[credType].minScore, "Score too low");
// Mark proof as used (prevent replay)
verifiedProofs[proofId] = true;
// Store verification result
userCredentials[user][credType] = VerificationResult({
verified: true,
score: score,
verifiedAt: block.timestamp,
proofId: proofId
});
emit CredentialVerified(user, credType, score, proofId);
return true;
}
/**
* @notice Check if user has required credential
*/
function hasCredential(
address user,
CredentialType credType
) external view returns (bool) {
VerificationResult memory result = userCredentials[user][credType];
if (!result.verified) return false;
CredentialSchema memory schema = schemas[credType];
return result.score >= schema.minScore;
}
/**
* @notice Get credential verification details
*/
function getCredentialDetails(
address user,
CredentialType credType
) external view returns (VerificationResult memory) {
return userCredentials[user][credType];
}
/**
* @notice Revoke credential (for compliance)
*/
function revokeCredential(
address user,
CredentialType credType
) external onlyRole(VERIFIER_ROLE) {
delete userCredentials[user][credType];
emit CredentialRevoked(user, credType);
}
/**
* @notice Batch verify credentials (for efficiency)
*/
function batchVerifyCredentials(
address[] calldata users,
CredentialType[] calldata credTypes,
bytes[] calldata zkProofs,
uint256[] calldata scores,
string[] calldata proofIds
) external onlyRole(VERIFIER_ROLE) {
require(
users.length == credTypes.length &&
credTypes.length == zkProofs.length &&
zkProofs.length == scores.length &&
scores.length == proofIds.length,
"Array length mismatch"
);
for (uint256 i = 0; i < users.length; i++) {
this.verifyCredentialProof(users[i], credTypes[i], zkProofs[i], scores[i], proofIds[i]);
}
}
}

View File

@@ -1,71 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "../vendor/openzeppelin/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
/**
* @title ConfigurationRegistry
* @notice Centralized configuration without hardcoding
* @dev Eliminates hardcoded addresses, enables runtime configuration
*/
contract ConfigurationRegistry is
Initializable,
AccessControlUpgradeable,
UUPSUpgradeable
{
bytes32 public constant CONFIG_ADMIN_ROLE = keccak256("CONFIG_ADMIN_ROLE");
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
mapping(address => mapping(bytes32 => bytes)) private configs;
mapping(address => bytes32[]) private configKeys;
event ConfigSet(address indexed contractAddr, bytes32 indexed key, bytes value);
event ConfigDeleted(address indexed contractAddr, bytes32 indexed key);
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(address admin) external initializer {
__AccessControl_init();
__UUPSUpgradeable_init();
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(CONFIG_ADMIN_ROLE, admin);
_grantRole(UPGRADER_ROLE, admin);
}
function _authorizeUpgrade(address newImplementation)
internal override onlyRole(UPGRADER_ROLE) {}
function set(address contractAddr, bytes32 key, bytes calldata value) external onlyRole(CONFIG_ADMIN_ROLE) {
require(contractAddr != address(0), "Zero address");
require(key != bytes32(0), "Zero key");
if (configs[contractAddr][key].length == 0) {
configKeys[contractAddr].push(key);
}
configs[contractAddr][key] = value;
emit ConfigSet(contractAddr, key, value);
}
function get(address contractAddr, bytes32 key) external view returns (bytes memory) {
return configs[contractAddr][key];
}
function getAddress(address contractAddr, bytes32 key) external view returns (address) {
bytes memory data = configs[contractAddr][key];
require(data.length == 32, "Invalid data");
return abi.decode(data, (address));
}
function getUint256(address contractAddr, bytes32 key) external view returns (uint256) {
bytes memory data = configs[contractAddr][key];
require(data.length == 32, "Invalid data");
return abi.decode(data, (uint256));
}
}

View File

@@ -3,7 +3,7 @@
- **Contracts:** All DBIS Rail contracts (RootRegistry, ParticipantRegistry, SignerRegistry, SettlementRouter, GRU_MintController, StablecoinReferenceRegistry, Conversion Router) are in this folder and implement Technical Spec v1 and v1.5 add-ons.
- **Tests:** `test/dbis/DBIS_Rail.t.sol` covers submitMintAuth success, replay revert, and signer-revoked-at-block. Uses `MockMintableToken` for a minimal GRU token in tests.
- **Build:** Default Foundry config (`via_ir = true`, `optimizer_runs = 200`) builds successfully. Yul stack-too-deep was resolved by:
- Moving EIP-712 hashing and signature recovery into `DBIS_EIP712Helper` (and optional `DBIS_EIP712Lib`).
- Moving EIP-712 hashing and signature recovery into `DBIS_EIP712Helper` (and optional historical `DBIS_EIP712Lib`, now archived at `archive/solidity/contracts/dbis/DBIS_EIP712Lib.sol`).
- Extracting the mint loop in `DBIS_GRU_MintController.mintFromAuthorization` into `_mintToRecipients` to reduce stack depth.
- Using `StablecoinReferenceRegistry._setEntry` for struct assignment and `SignerRegistry.hasDuplicateSigners` / `areSignersActiveAtBlock` to keep router loops out of the main path.
- **Deploy:** Run `DeployDBISRail.s.sol` on Chain 138; deploy `DBIS_EIP712Helper` first and pass its address to both `DBIS_SettlementRouter` and `DBIS_ConversionRouter` constructors; then set GRU token on MintController, grant MINTER_ROLE on c* tokens to MintController, register stablecoins, and add venues/quote issuers as needed.

View File

@@ -1,77 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title DBIS_EIP712Lib
* @notice External library for EIP-712 hashing and ecrecover (delegatecall = own stack).
*/
library DBIS_EIP712Lib {
function hashAddressArray(address[] calldata arr) external pure returns (bytes32) {
bytes32[] memory hashes = new bytes32[](arr.length);
for (uint256 i = 0; i < arr.length; i++) {
hashes[i] = keccak256(abi.encode(arr[i]));
}
return keccak256(abi.encodePacked(hashes));
}
function hashUint256Array(uint256[] calldata arr) external pure returns (bytes32) {
bytes32[] memory hashes = new bytes32[](arr.length);
for (uint256 i = 0; i < arr.length; i++) {
hashes[i] = keccak256(abi.encode(arr[i]));
}
return keccak256(abi.encodePacked(hashes));
}
function getMintAuthStructHash(
bytes32 typeHash,
bytes32 messageId,
bytes32 isoType,
bytes32 isoHash,
bytes32 accountingRef,
uint8 fundsStatus,
bytes32 corridor,
uint8 assetClass,
bytes32 recipientsHash,
bytes32 amountsHash,
uint64 notBefore,
uint64 expiresAt,
uint256 chainId,
address verifyingContract
) external pure returns (bytes32) {
return keccak256(abi.encode(
typeHash,
messageId,
isoType,
isoHash,
accountingRef,
fundsStatus,
corridor,
assetClass,
recipientsHash,
amountsHash,
notBefore,
expiresAt,
chainId,
verifyingContract
));
}
function getDigest(bytes32 domainSeparator, bytes32 structHash) external pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
}
function recover(bytes32 digest, bytes calldata signature) external pure returns (address) {
require(signature.length == 65, "DBIS: sig length");
bytes32 r;
bytes32 s;
uint8 v;
assembly {
r := calldataload(signature.offset)
s := calldataload(add(signature.offset, 32))
v := byte(0, calldataload(add(signature.offset, 64)))
}
if (v < 27) v += 27;
require(v == 27 || v == 28, "DBIS: invalid v");
return ecrecover(digest, v, r, s);
}
}

View File

@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
@@ -85,7 +86,7 @@ interface IAaveAtomicBridgeCoordinator {
* @notice Aave V3 flash-loan receiver for the quote-push workflow:
* flash borrow quote (`flashLoan` single-asset) -> buy PMM base -> unwind base externally -> repay lender, retaining any surplus.
*/
contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver, IAaveFlashLoanReceiver {
contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver, IAaveFlashLoanReceiver, Ownable {
using SafeERC20 for IERC20;
address public immutable pool;
@@ -120,6 +121,7 @@ contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver, IAaveFlashL
error BadParams();
error InsufficientToRepay();
error InvalidAtomicBridge();
error NothingToSweep();
event QuotePushExecuted(
address indexed quoteToken,
@@ -137,8 +139,11 @@ contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver, IAaveFlashL
uint256 bridgeAmount,
uint256 minDestinationAmount
);
event TokenSwept(address indexed token, address indexed to, uint256 amount);
event SurplusSwept(address indexed token, address indexed to, uint256 amount, uint256 reserveRetained);
constructor(address pool_) {
constructor(address pool_, address initialOwner) Ownable(initialOwner) {
if (pool_ == address(0) || initialOwner == address(0)) revert BadParams();
pool = pool_;
}
@@ -155,6 +160,31 @@ contract AaveQuotePushFlashReceiver is IAaveFlashLoanSimpleReceiver, IAaveFlashL
);
}
function quoteSurplusBalance(address quoteToken, uint256 reserveRetained) public view returns (uint256 surplus) {
uint256 quoteBal = IERC20(quoteToken).balanceOf(address(this));
if (quoteBal > reserveRetained) {
surplus = quoteBal - reserveRetained;
}
}
function sweepQuoteSurplus(address quoteToken, address to, uint256 reserveRetained)
external
onlyOwner
returns (uint256 amount)
{
if (quoteToken == address(0) || to == address(0)) revert BadParams();
amount = quoteSurplusBalance(quoteToken, reserveRetained);
if (amount == 0) revert NothingToSweep();
IERC20(quoteToken).safeTransfer(to, amount);
emit SurplusSwept(quoteToken, to, amount, reserveRetained);
}
function sweepToken(address token, address to, uint256 amount) external onlyOwner {
if (token == address(0) || to == address(0) || amount == 0) revert BadParams();
IERC20(token).safeTransfer(to, amount);
emit TokenSwept(token, to, amount);
}
function executeOperation(
address[] calldata assets,
uint256[] calldata amounts,

View File

@@ -0,0 +1,165 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
interface IQuotePushSweepableReceiver {
function owner() external view returns (address);
function quoteSurplusBalance(address quoteToken, uint256 reserveRetained) external view returns (uint256 surplus);
function sweepQuoteSurplus(address quoteToken, address to, uint256 reserveRetained)
external
returns (uint256 amount);
}
/**
* @title QuotePushTreasuryManager
* @notice Minimal treasury manager for Aave quote-push retained surplus.
* Intended flow:
* 1. Receiver retains quote surplus after flash repayment.
* 2. This manager, as receiver owner, harvests that surplus into itself.
* 3. Owner/operator distributes quote to gas and recycle recipients.
*
* Gas replenishment is still an operator policy in quote units; converting
* quote into native gas token remains off-chain / external.
*/
contract QuotePushTreasuryManager is Ownable {
using SafeERC20 for IERC20;
IQuotePushSweepableReceiver public immutable receiver;
IERC20 public immutable quoteToken;
address public operator;
address public gasRecipient;
address public recycleRecipient;
uint256 public receiverReserveRetained;
uint256 public managerReserveRetained;
error BadConfig();
error Unauthorized();
error NothingToHarvest();
error InsufficientAvailable(uint256 available, uint256 requested);
event OperatorUpdated(address indexed previousOperator, address indexed newOperator);
event RecipientsUpdated(address indexed gasRecipient, address indexed recycleRecipient);
event ReservesUpdated(uint256 receiverReserveRetained, uint256 managerReserveRetained);
event ReceiverSurplusHarvested(address indexed token, uint256 amount, uint256 receiverReserveRetained);
event QuoteDistributed(address indexed token, address indexed to, uint256 amount, bytes32 purpose);
event TokenRescued(address indexed token, address indexed to, uint256 amount);
modifier onlyOwnerOrOperator() {
if (msg.sender != owner() && msg.sender != operator) revert Unauthorized();
_;
}
constructor(
address initialOwner,
address receiver_,
address quoteToken_,
address operator_,
address gasRecipient_,
address recycleRecipient_,
uint256 receiverReserveRetained_,
uint256 managerReserveRetained_
) Ownable(initialOwner) {
if (
initialOwner == address(0) || receiver_ == address(0) || quoteToken_ == address(0)
|| gasRecipient_ == address(0) || recycleRecipient_ == address(0)
) revert BadConfig();
receiver = IQuotePushSweepableReceiver(receiver_);
quoteToken = IERC20(quoteToken_);
operator = operator_;
gasRecipient = gasRecipient_;
recycleRecipient = recycleRecipient_;
receiverReserveRetained = receiverReserveRetained_;
managerReserveRetained = managerReserveRetained_;
}
function receiverOwner() public view returns (address) {
return receiver.owner();
}
function isReceiverOwnedByManager() public view returns (bool) {
return receiver.owner() == address(this);
}
function quoteBalance() public view returns (uint256) {
return quoteToken.balanceOf(address(this));
}
function availableQuote() public view returns (uint256 available) {
uint256 balance = quoteBalance();
if (balance > managerReserveRetained) {
available = balance - managerReserveRetained;
}
}
function receiverSweepableQuote() public view returns (uint256) {
return receiver.quoteSurplusBalance(address(quoteToken), receiverReserveRetained);
}
function setOperator(address newOperator) external onlyOwner {
emit OperatorUpdated(operator, newOperator);
operator = newOperator;
}
function setRecipients(address gasRecipient_, address recycleRecipient_) external onlyOwner {
if (gasRecipient_ == address(0) || recycleRecipient_ == address(0)) revert BadConfig();
gasRecipient = gasRecipient_;
recycleRecipient = recycleRecipient_;
emit RecipientsUpdated(gasRecipient_, recycleRecipient_);
}
function setReserves(uint256 receiverReserveRetained_, uint256 managerReserveRetained_) external onlyOwner {
receiverReserveRetained = receiverReserveRetained_;
managerReserveRetained = managerReserveRetained_;
emit ReservesUpdated(receiverReserveRetained_, managerReserveRetained_);
}
function harvestReceiverSurplus() public onlyOwnerOrOperator returns (uint256 amount) {
amount = receiver.sweepQuoteSurplus(address(quoteToken), address(this), receiverReserveRetained);
if (amount == 0) revert NothingToHarvest();
emit ReceiverSurplusHarvested(address(quoteToken), amount, receiverReserveRetained);
}
function distributeQuote(address to, uint256 amount, bytes32 purpose) external onlyOwnerOrOperator {
_distributeQuote(to, amount, purpose);
}
function distributeToConfiguredRecipients(uint256 gasAmount, uint256 recycleAmount) external onlyOwnerOrOperator {
uint256 requested = gasAmount + recycleAmount;
_requireAvailable(requested);
if (gasAmount > 0) {
quoteToken.safeTransfer(gasRecipient, gasAmount);
emit QuoteDistributed(address(quoteToken), gasRecipient, gasAmount, bytes32("gas"));
}
if (recycleAmount > 0) {
quoteToken.safeTransfer(recycleRecipient, recycleAmount);
emit QuoteDistributed(address(quoteToken), recycleRecipient, recycleAmount, bytes32("recycle"));
}
}
function rescueToken(address token, address to, uint256 amount) external onlyOwner {
if (token == address(0) || to == address(0) || amount == 0) revert BadConfig();
if (token == address(quoteToken)) {
_requireAvailable(amount);
}
IERC20(token).safeTransfer(to, amount);
emit TokenRescued(token, to, amount);
}
function _distributeQuote(address to, uint256 amount, bytes32 purpose) internal {
if (to == address(0) || amount == 0) revert BadConfig();
_requireAvailable(amount);
quoteToken.safeTransfer(to, amount);
emit QuoteDistributed(address(quoteToken), to, amount, purpose);
}
function _requireAvailable(uint256 requested) internal view {
uint256 available = availableQuote();
if (requested > available) revert InsufficientAvailable(available, requested);
}
}

View File

@@ -1,40 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
/**
* @title IAggregator
* @notice Interface for oracle aggregator (Chainlink-compatible)
*/
interface IAggregator {
function latestAnswer() external view returns (int256);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function getRoundData(uint80 _roundId)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function updateAnswer(uint256 answer) external;
function decimals() external view returns (uint8);
function description() external view returns (string memory);
function version() external view returns (uint256);
function latestRound() external view returns (uint256);
}

View File

@@ -1,234 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "../vendor/openzeppelin/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "../registry/UniversalAssetRegistry.sol";
/**
* @title PoolManager
* @notice Manages pool creation, configuration, and lifecycle
* @dev Auto-creates pools when new assets are registered
*/
contract PoolManager is
Initializable,
AccessControlUpgradeable,
UUPSUpgradeable
{
bytes32 public constant POOL_ADMIN_ROLE = keccak256("POOL_ADMIN_ROLE");
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
struct PoolInfo {
address pool;
address provider; // DODO, Uniswap, etc.
address tokenA;
address tokenB;
uint256 liquidityUSD;
uint256 volume24h;
uint256 createdAt;
uint256 lastUpdateTime;
bool isActive;
}
// Storage
UniversalAssetRegistry public assetRegistry;
mapping(address => PoolInfo[]) public tokenPools; // token => pools
mapping(address => PoolInfo) public poolRegistry; // pool address => info
address[] public allPools;
// Provider addresses
address public dodoProvider;
address public uniswapV3Provider;
address public curveProvider;
event PoolCreated(
address indexed pool,
address indexed token,
address provider,
UniversalAssetRegistry.AssetType assetType
);
event PoolHealthChecked(
address indexed pool,
bool isHealthy,
string reason
);
event PoolLiquidityUpdated(
address indexed pool,
uint256 newLiquidityUSD
);
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(
address _assetRegistry,
address admin
) external initializer {
__AccessControl_init();
__UUPSUpgradeable_init();
require(_assetRegistry != address(0), "Zero registry");
assetRegistry = UniversalAssetRegistry(_assetRegistry);
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(POOL_ADMIN_ROLE, admin);
_grantRole(UPGRADER_ROLE, admin);
}
function _authorizeUpgrade(address newImplementation)
internal override onlyRole(UPGRADER_ROLE) {}
/**
* @notice Auto-create pool when new asset registered
*/
function onAssetRegistered(
address token,
UniversalAssetRegistry.AssetType assetType
) external onlyRole(POOL_ADMIN_ROLE) returns (address pool) {
// Determine optimal pool type based on asset type
if (assetType == UniversalAssetRegistry.AssetType.Stablecoin ||
assetType == UniversalAssetRegistry.AssetType.ISO4217W ||
assetType == UniversalAssetRegistry.AssetType.GRU) {
// Create DODO PMM pool (optimal for stable / GRU c* pairs)
pool = _createDODOPool(token);
} else if (assetType == UniversalAssetRegistry.AssetType.ERC20Standard ||
assetType == UniversalAssetRegistry.AssetType.GovernanceToken) {
// Create Uniswap V3 pool (optimal for volatile pairs)
pool = _createUniswapV3Pool(token);
} else {
// For other types (Security, Commodity), manual pool creation
return address(0);
}
emit PoolCreated(pool, token, dodoProvider, assetType);
return pool;
}
/**
* @notice Create DODO PMM pool
*/
function _createDODOPool(address token) internal returns (address) {
// In production, this would call DODOPMMIntegration to create actual pool
// For now, placeholder
return address(0);
}
/**
* @notice Create Uniswap V3 pool
*/
function _createUniswapV3Pool(address token) internal returns (address) {
// In production, this would deploy Uniswap V3 pool
// For now, placeholder
return address(0);
}
/**
* @notice Register existing pool
*/
function registerPool(
address pool,
address provider,
address tokenA,
address tokenB,
uint256 liquidityUSD
) external onlyRole(POOL_ADMIN_ROLE) {
require(pool != address(0), "Zero address");
require(!poolRegistry[pool].isActive, "Already registered");
PoolInfo memory info = PoolInfo({
pool: pool,
provider: provider,
tokenA: tokenA,
tokenB: tokenB,
liquidityUSD: liquidityUSD,
volume24h: 0,
createdAt: block.timestamp,
lastUpdateTime: block.timestamp,
isActive: true
});
poolRegistry[pool] = info;
tokenPools[tokenA].push(info);
if (tokenB != tokenA) {
tokenPools[tokenB].push(info);
}
allPools.push(pool);
}
/**
* @notice Check pool health
*/
function checkPoolHealth(address pool) external view returns (
bool isHealthy,
string memory reason
) {
PoolInfo memory info = poolRegistry[pool];
if (!info.isActive) {
return (false, "Pool inactive");
}
if (info.liquidityUSD < 1000e18) {
return (false, "Insufficient liquidity");
}
if (block.timestamp - info.lastUpdateTime > 7 days) {
return (false, "Stale data");
}
return (true, "Healthy");
}
/**
* @notice Update pool liquidity
*/
function updatePoolLiquidity(
address pool,
uint256 liquidityUSD
) external onlyRole(POOL_ADMIN_ROLE) {
require(poolRegistry[pool].isActive, "Pool not registered");
poolRegistry[pool].liquidityUSD = liquidityUSD;
poolRegistry[pool].lastUpdateTime = block.timestamp;
emit PoolLiquidityUpdated(pool, liquidityUSD);
}
/**
* @notice Set provider addresses
*/
function setProviders(
address _dodoProvider,
address _uniswapV3Provider,
address _curveProvider
) external onlyRole(DEFAULT_ADMIN_ROLE) {
dodoProvider = _dodoProvider;
uniswapV3Provider = _uniswapV3Provider;
curveProvider = _curveProvider;
}
// View functions
function getPoolsForToken(address token) external view returns (PoolInfo[] memory) {
return tokenPools[token];
}
function getPoolInfo(address pool) external view returns (PoolInfo memory) {
return poolRegistry[pool];
}
function getAllPools() external view returns (address[] memory) {
return allPools;
}
function getPoolCount() external view returns (uint256) {
return allPools.length;
}
}

View File

@@ -1,112 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/utils/Base64.sol";
/**
* @title GRUFormulasNFT
* @notice ERC-721 NFT depicting the three GRU-related monetary formulas as on-chain SVG graphics
* @dev Token IDs: 0 = Money Supply (GRU M00/M0/M1), 1 = Money Velocity (M×V=P×Y), 2 = Money Multiplier (m=1.0)
*/
contract GRUFormulasNFT is ERC721, ERC721URIStorage, AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
uint256 public constant TOKEN_ID_MONEY_SUPPLY = 0;
uint256 public constant TOKEN_ID_MONEY_VELOCITY = 1;
uint256 public constant TOKEN_ID_MONEY_MULTIPLIER = 2;
uint256 public constant MAX_TOKEN_ID = 2;
constructor(address admin) ERC721("GRU Formulas", "GRUF") {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(MINTER_ROLE, admin);
}
/**
* @notice Mint one of the three formula NFTs (tokenId 0, 1, or 2)
* @param to Recipient
* @param tokenId 0 = Money Supply, 1 = Money Velocity, 2 = Money Multiplier
*/
function mint(address to, uint256 tokenId) external onlyRole(MINTER_ROLE) {
require(tokenId <= MAX_TOKEN_ID, "GRUFormulasNFT: invalid tokenId");
_safeMint(to, tokenId);
}
function _baseURI() internal pure override returns (string memory) {
return "";
}
/**
* @notice Returns metadata URI with on-chain SVG image for the formula
* @param tokenId 0 = Money Supply, 1 = Money Velocity, 2 = Money Multiplier
*/
function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) {
require(_ownerOf(tokenId) != address(0), "ERC721: invalid token ID");
(string memory name, string memory description, string memory svg) = _formulaData(tokenId);
string memory imageData = string.concat("data:image/svg+xml;base64,", Base64.encode(bytes(svg)));
string memory json = string.concat(
'{"name":"', name,
'","description":"', description,
'","image":"', imageData,
'"}'
);
return string.concat("data:application/json;base64,", Base64.encode(bytes(json)));
}
function _formulaData(uint256 tokenId) internal pure returns (string memory name, string memory description, string memory svg) {
if (tokenId == TOKEN_ID_MONEY_SUPPLY) {
name = "GRU Money Supply (M)";
description = "GRU monetary layers: 1 M00 = 5 M0 = 25 M1 (base, collateral, credit). Non-ISO synthetic unit of account.";
svg = _svgMoneySupply();
} else if (tokenId == TOKEN_ID_MONEY_VELOCITY) {
name = "Money Velocity (V)";
description = "Equation of exchange: M x V = P x Y (money supply, velocity, price level, output).";
svg = _svgMoneyVelocity();
} else if (tokenId == TOKEN_ID_MONEY_MULTIPLIER) {
name = "Money Multiplier (m)";
description = "m = Reserve / Supply; GRU and ISO4217W enforce m = 1.0 (no fractional reserve).";
svg = _svgMoneyMultiplier();
} else {
revert("GRUFormulasNFT: invalid tokenId");
}
}
function _svgMoneySupply() internal pure returns (string memory) {
return string.concat(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 120' width='320' height='120'>",
"<rect width='320' height='120' fill='#1a1a2e' rx='8'/>",
"<text x='160' y='42' text-anchor='middle' fill='#eaeaea' font-family='sans-serif' font-size='14'>Money Supply (M) - GRU layers</text>",
"<text x='160' y='68' text-anchor='middle' fill='#a0e0a0' font-family='monospace' font-size='16'>1 M00 = 5 M0 = 25 M1</text>",
"<text x='160' y='92' text-anchor='middle' fill='#888' font-family='sans-serif' font-size='11'>M00 base | M0 collateral | M1 credit</text>",
"</svg>"
);
}
function _svgMoneyVelocity() internal pure returns (string memory) {
return string.concat(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 120' width='320' height='120'>",
"<rect width='320' height='120' fill='#1a1a2e' rx='8'/>",
"<text x='160' y='42' text-anchor='middle' fill='#eaeaea' font-family='sans-serif' font-size='14'>Money Velocity (V)</text>",
"<text x='160' y='72' text-anchor='middle' fill='#a0c0ff' font-family='serif' font-size='20'>M * V = P * Y</text>",
"<text x='160' y='98' text-anchor='middle' fill='#888' font-family='sans-serif' font-size='10'>Equation of exchange</text>",
"</svg>"
);
}
function _svgMoneyMultiplier() internal pure returns (string memory) {
return string.concat(
"<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 120' width='320' height='120'>",
"<rect width='320' height='120' fill='#1a1a2e' rx='8'/>",
"<text x='160' y='42' text-anchor='middle' fill='#eaeaea' font-family='sans-serif' font-size='14'>Money Multiplier (m)</text>",
"<text x='160' y='72' text-anchor='middle' fill='#ffc0a0' font-family='serif' font-size='18'>m = Reserve / Supply = 1.0</text>",
"<text x='160' y='98' text-anchor='middle' fill='#888' font-family='sans-serif' font-size='10'>No fractional reserve (GRU / ISO4217W)</text>",
"</svg>"
);
}
function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage, AccessControl) returns (bool) {
return super.supportsInterface(interfaceId) || AccessControl.supportsInterface(interfaceId);
}
}

View File

@@ -1,192 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "../vendor/openzeppelin/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
/**
* @title PluginRegistry
* @notice Central registry for all pluggable components
* @dev Enables adding new asset types, liquidity providers, compliance modules without redeployment
*/
contract PluginRegistry is
Initializable,
AccessControlUpgradeable,
UUPSUpgradeable
{
bytes32 public constant PLUGIN_ADMIN_ROLE = keccak256("PLUGIN_ADMIN_ROLE");
bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");
enum PluginType {
AssetTypeHandler,
LiquidityProvider,
ComplianceModule,
OracleProvider,
VaultStrategy
}
struct Plugin {
address implementation;
string version;
bool active;
uint256 registeredAt;
string description;
}
// Storage
mapping(PluginType => mapping(bytes32 => Plugin)) public plugins;
mapping(PluginType => bytes32[]) public pluginsByType;
// Plugin metadata
mapping(address => bool) public isRegisteredPlugin;
mapping(PluginType => uint256) public pluginCount;
event PluginRegistered(
PluginType indexed pluginType,
bytes32 indexed identifier,
address implementation,
string version
);
event PluginActivated(PluginType indexed pluginType, bytes32 indexed identifier);
event PluginDeactivated(PluginType indexed pluginType, bytes32 indexed identifier);
event PluginUpgraded(
PluginType indexed pluginType,
bytes32 indexed identifier,
address oldImplementation,
address newImplementation
);
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(address admin) external initializer {
__AccessControl_init();
__UUPSUpgradeable_init();
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(PLUGIN_ADMIN_ROLE, admin);
_grantRole(UPGRADER_ROLE, admin);
}
function _authorizeUpgrade(address newImplementation)
internal override onlyRole(UPGRADER_ROLE) {}
/**
* @notice Register new plugin
*/
function registerPlugin(
PluginType pluginType,
bytes32 identifier,
address implementation,
string calldata version,
string calldata description
) external onlyRole(PLUGIN_ADMIN_ROLE) {
require(implementation != address(0), "Zero address");
require(implementation.code.length > 0, "Not a contract");
require(plugins[pluginType][identifier].implementation == address(0), "Already registered");
plugins[pluginType][identifier] = Plugin({
implementation: implementation,
version: version,
active: true,
registeredAt: block.timestamp,
description: description
});
pluginsByType[pluginType].push(identifier);
isRegisteredPlugin[implementation] = true;
pluginCount[pluginType]++;
emit PluginRegistered(pluginType, identifier, implementation, version);
}
/**
* @notice Upgrade plugin to new implementation
*/
function upgradePlugin(
PluginType pluginType,
bytes32 identifier,
address newImplementation,
string calldata newVersion
) external onlyRole(PLUGIN_ADMIN_ROLE) {
require(newImplementation != address(0), "Zero address");
require(newImplementation.code.length > 0, "Not a contract");
Plugin storage plugin = plugins[pluginType][identifier];
require(plugin.implementation != address(0), "Plugin not found");
address oldImplementation = plugin.implementation;
plugin.implementation = newImplementation;
plugin.version = newVersion;
isRegisteredPlugin[newImplementation] = true;
emit PluginUpgraded(pluginType, identifier, oldImplementation, newImplementation);
}
/**
* @notice Activate plugin
*/
function activatePlugin(
PluginType pluginType,
bytes32 identifier
) external onlyRole(PLUGIN_ADMIN_ROLE) {
Plugin storage plugin = plugins[pluginType][identifier];
require(plugin.implementation != address(0), "Plugin not found");
plugin.active = true;
emit PluginActivated(pluginType, identifier);
}
/**
* @notice Deactivate plugin
*/
function deactivatePlugin(
PluginType pluginType,
bytes32 identifier
) external onlyRole(PLUGIN_ADMIN_ROLE) {
Plugin storage plugin = plugins[pluginType][identifier];
require(plugin.implementation != address(0), "Plugin not found");
plugin.active = false;
emit PluginDeactivated(pluginType, identifier);
}
// View functions
function getPlugin(
PluginType pluginType,
bytes32 identifier
) external view returns (address implementation) {
Plugin memory plugin = plugins[pluginType][identifier];
require(plugin.active, "Plugin not active");
return plugin.implementation;
}
function getPluginInfo(
PluginType pluginType,
bytes32 identifier
) external view returns (Plugin memory) {
return plugins[pluginType][identifier];
}
function getAllPlugins(PluginType pluginType) external view returns (bytes32[] memory) {
return pluginsByType[pluginType];
}
function getPluginCount(PluginType pluginType) external view returns (uint256) {
return pluginCount[pluginType];
}
function isPluginActive(
PluginType pluginType,
bytes32 identifier
) external view returns (bool) {
return plugins[pluginType][identifier].active;
}
}

View File

@@ -1,15 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title TruthNetworkAdapter
* @notice Minimal adapter on Chain 138 for ChainRegistry: stores Ethereum Truth Bridge address.
*/
contract TruthNetworkAdapter {
address public immutable ethereumTruthBridge;
constructor(address _ethereumTruthBridge) {
require(_ethereumTruthBridge != address(0), "Zero address");
ethereumTruthBridge = _ethereumTruthBridge;
}
}

View File

@@ -1,49 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../interfaces/IAssetTypeHandler.sol";
/**
* @title CommodityHandler
* @notice Handler for commodity-backed tokens (gold, oil, etc.) with certificate validation
*/
contract CommodityHandler is IAssetTypeHandler {
// Certificate registry for commodity authenticity
mapping(address => mapping(bytes32 => bool)) public validCertificates;
// Custodian registry
mapping(address => address) public custodians;
function validateAsset(address token) external view override returns (bool) {
if (token.code.length == 0) return false;
// Verify custodian is registered
return custodians[token] != address(0);
}
function getRequiredCompliance() external pure override returns (UniversalAssetRegistry.ComplianceLevel) {
return UniversalAssetRegistry.ComplianceLevel.KYC;
}
function getDefaultLimits() external pure override returns (uint256 min, uint256 max) {
return (1e15, 1000000e18); // 0.001 to 1M units
}
function preTransferHook(address, address, uint256 amount) external pure override {
require(amount > 0, "Invalid amount");
// Certificate validation would happen here in production
}
function postTransferHook(address, address, uint256) external pure override {
// Post-transfer custodian notification if needed
}
// Admin functions
function registerCustodian(address token, address custodian) external {
custodians[token] = custodian;
}
function registerCertificate(address token, bytes32 certificateHash) external {
validCertificates[token][certificateHash] = true;
}
}

View File

@@ -1,37 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../interfaces/IAssetTypeHandler.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title ERC20Handler
* @notice Handler for standard ERC-20 tokens
*/
contract ERC20Handler is IAssetTypeHandler {
function validateAsset(address token) external view override returns (bool) {
if (token.code.length == 0) return false;
try IERC20(token).totalSupply() returns (uint256) {
return true;
} catch {
return false;
}
}
function getRequiredCompliance() external pure override returns (UniversalAssetRegistry.ComplianceLevel) {
return UniversalAssetRegistry.ComplianceLevel.Public;
}
function getDefaultLimits() external pure override returns (uint256 min, uint256 max) {
return (1e15, 1000000e18); // 0.001 to 1M tokens
}
function preTransferHook(address, address, uint256) external pure override {
// No pre-transfer checks for standard ERC-20
}
function postTransferHook(address, address, uint256) external pure override {
// No post-transfer hooks for standard ERC-20
}
}

View File

@@ -1,37 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../interfaces/IAssetTypeHandler.sol";
import "../../vault/libraries/GRUConstants.sol";
/**
* @title GRUHandler
* @notice Handler for Global Reserve Unit (GRU) tokens with layer validation
*/
contract GRUHandler is IAssetTypeHandler {
using GRUConstants for *;
function validateAsset(address token) external view override returns (bool) {
if (token.code.length == 0) return false;
// Additional GRU-specific validation could be added here
// For now, basic contract existence check
return true;
}
function getRequiredCompliance() external pure override returns (UniversalAssetRegistry.ComplianceLevel) {
return UniversalAssetRegistry.ComplianceLevel.Institutional;
}
function getDefaultLimits() external pure override returns (uint256 min, uint256 max) {
return (1e18, 10000000e18); // 1 to 10M GRU
}
function preTransferHook(address, address, uint256) external pure override {
// GRU layer validation happens in the token contract itself
}
function postTransferHook(address, address, uint256) external pure override {
// Post-transfer hooks for GRU if needed
}
}

View File

@@ -1,42 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../interfaces/IAssetTypeHandler.sol";
import "../../iso4217w/interfaces/IISO4217WToken.sol";
/**
* @title ISO4217WHandler
* @notice Handler for ISO-4217W eMoney/CBDC tokens with compliance checks
*/
contract ISO4217WHandler is IAssetTypeHandler {
function validateAsset(address token) external view override returns (bool) {
if (token.code.length == 0) return false;
try IISO4217WToken(token).currencyCode() returns (string memory code) {
// Verify it follows ISO-4217W format (e.g., USDW, EURW)
bytes memory codeBytes = bytes(code);
return codeBytes.length >= 4 && codeBytes[codeBytes.length - 1] == 'W';
} catch {
return false;
}
}
function getRequiredCompliance() external pure override returns (UniversalAssetRegistry.ComplianceLevel) {
return UniversalAssetRegistry.ComplianceLevel.KYC;
}
function getDefaultLimits() external pure override returns (uint256 min, uint256 max) {
return (100e18, 1000000e18); // 100 to 1M units
}
function preTransferHook(address from, address to, uint256 amount) external view override {
// Compliance checks would go here
// In practice, this would integrate with ComplianceGuard
require(from != address(0) && to != address(0), "Invalid addresses");
require(amount > 0, "Invalid amount");
}
function postTransferHook(address, address, uint256) external pure override {
// Post-transfer compliance tracking if needed
}
}

View File

@@ -1,49 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../interfaces/IAssetTypeHandler.sol";
/**
* @title SecurityHandler
* @notice Handler for tokenized securities with accredited investor requirements
*/
contract SecurityHandler is IAssetTypeHandler {
// In production, this would integrate with accredited investor registry
mapping(address => bool) public accreditedInvestors;
function validateAsset(address token) external view override returns (bool) {
if (token.code.length == 0) return false;
// Additional security token validation
// Could check for compliance with standards like ERC-3643 (T-REX)
return true;
}
function getRequiredCompliance() external pure override returns (UniversalAssetRegistry.ComplianceLevel) {
return UniversalAssetRegistry.ComplianceLevel.Accredited;
}
function getDefaultLimits() external pure override returns (uint256 min, uint256 max) {
return (1e18, 100000e18); // 1 to 100K securities
}
function preTransferHook(address from, address to, uint256 amount) external view override {
// Verify accredited investor status
if (from != address(0)) {
require(accreditedInvestors[from], "Sender not accredited");
}
if (to != address(0)) {
require(accreditedInvestors[to], "Recipient not accredited");
}
require(amount > 0, "Invalid amount");
}
function postTransferHook(address, address, uint256) external pure override {
// Post-transfer compliance tracking (e.g., T+2 settlement)
}
// Admin function to set accredited status (in production, would be permissioned)
function setAccreditedStatus(address investor, bool status) external {
accreditedInvestors[investor] = status;
}
}

View File

@@ -1,16 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../UniversalAssetRegistry.sol";
/**
* @title IAssetTypeHandler
* @notice Interface for asset-specific validation and hooks
*/
interface IAssetTypeHandler {
function validateAsset(address token) external view returns (bool);
function getRequiredCompliance() external view returns (UniversalAssetRegistry.ComplianceLevel);
function getDefaultLimits() external view returns (uint256 min, uint256 max);
function preTransferHook(address from, address to, uint256 amount) external;
function postTransferHook(address from, address to, uint256 amount) external;
}

View File

@@ -1,156 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";
/**
* @title ProxyFactory
* @notice Factory for deploying upgradeable proxies (UUPS and Beacon patterns)
* @dev Tracks all deployed proxies for the universal bridge system
*/
contract ProxyFactory is AccessControl {
bytes32 public constant DEPLOYER_ROLE = keccak256("DEPLOYER_ROLE");
struct ProxyInfo {
address proxy;
address implementation;
ProxyType proxyType;
uint256 deployedAt;
address deployer;
bool isActive;
}
enum ProxyType {
UUPS,
Beacon,
Transparent
}
// Storage
mapping(address => ProxyInfo) public proxies;
address[] public allProxies;
mapping(address => address[]) public proxiesByImplementation;
mapping(address => UpgradeableBeacon) public beacons;
event ProxyDeployed(
address indexed proxy,
address indexed implementation,
ProxyType proxyType,
address deployer
);
event BeaconCreated(
address indexed beacon,
address indexed implementation
);
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(DEPLOYER_ROLE, admin);
}
/**
* @notice Deploy UUPS proxy
*/
function deployUUPSProxy(
address implementation,
bytes calldata initData
) external onlyRole(DEPLOYER_ROLE) returns (address proxy) {
require(implementation != address(0), "Zero implementation");
require(implementation.code.length > 0, "Not a contract");
// Deploy proxy
proxy = address(new ERC1967Proxy(implementation, initData));
// Track proxy
_trackProxy(proxy, implementation, ProxyType.UUPS);
emit ProxyDeployed(proxy, implementation, ProxyType.UUPS, msg.sender);
return proxy;
}
/**
* @notice Deploy beacon proxy
*/
function deployBeaconProxy(
address beacon,
bytes calldata initData
) external onlyRole(DEPLOYER_ROLE) returns (address proxy) {
require(beacon != address(0), "Zero beacon");
// Get implementation from beacon
address implementation = UpgradeableBeacon(beacon).implementation();
// Deploy beacon proxy
proxy = address(new BeaconProxy(beacon, initData));
// Track proxy
_trackProxy(proxy, implementation, ProxyType.Beacon);
emit ProxyDeployed(proxy, implementation, ProxyType.Beacon, msg.sender);
return proxy;
}
/**
* @notice Create beacon for implementation
*/
function createBeacon(address implementation) external onlyRole(DEPLOYER_ROLE) returns (address beacon) {
require(implementation != address(0), "Zero implementation");
require(implementation.code.length > 0, "Not a contract");
UpgradeableBeacon newBeacon = new UpgradeableBeacon(implementation, address(this));
beacons[implementation] = newBeacon;
emit BeaconCreated(address(newBeacon), implementation);
return address(newBeacon);
}
/**
* @notice Track deployed proxy
*/
function _trackProxy(
address proxy,
address implementation,
ProxyType proxyType
) internal {
proxies[proxy] = ProxyInfo({
proxy: proxy,
implementation: implementation,
proxyType: proxyType,
deployedAt: block.timestamp,
deployer: msg.sender,
isActive: true
});
allProxies.push(proxy);
proxiesByImplementation[implementation].push(proxy);
}
// View functions
function getProxyInfo(address proxy) external view returns (ProxyInfo memory) {
return proxies[proxy];
}
function getAllProxies() external view returns (address[] memory) {
return allProxies;
}
function getProxiesByImplementation(address implementation) external view returns (address[] memory) {
return proxiesByImplementation[implementation];
}
function getProxyCount() external view returns (uint256) {
return allProxies.length;
}
function isProxy(address addr) external view returns (bool) {
return proxies[addr].isActive;
}
}

View File

@@ -1,130 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/AccessControl.sol";
/**
* @title BridgeVaultExtension
* @notice Extension to vault for tracking bridge operations
* @dev Can be attached to existing vault contracts
*/
contract BridgeVaultExtension is AccessControl {
bytes32 public constant BRIDGE_OPERATOR_ROLE = keccak256("BRIDGE_OPERATOR_ROLE");
enum BridgeStatus {
Initiated,
Confirmed,
Completed,
Failed
}
struct BridgeRecord {
bytes32 messageId;
address token;
uint256 amount;
uint64 destinationChain;
address recipient;
uint256 timestamp;
BridgeStatus status;
}
// Storage
mapping(bytes32 => BridgeRecord) public bridgeRecords;
bytes32[] public bridgeHistory;
mapping(address => bytes32[]) public userBridgeHistory;
event BridgeRecorded(
bytes32 indexed messageId,
address indexed token,
uint256 amount,
uint64 destinationChain
);
event BridgeStatusUpdated(
bytes32 indexed messageId,
BridgeStatus status
);
constructor(address admin) {
_grantRole(DEFAULT_ADMIN_ROLE, admin);
_grantRole(BRIDGE_OPERATOR_ROLE, admin);
}
/**
* @notice Record bridge operation
*/
function recordBridgeOperation(
bytes32 messageId,
address token,
uint256 amount,
uint64 destinationChain,
address recipient
) external onlyRole(BRIDGE_OPERATOR_ROLE) {
require(messageId != bytes32(0), "Invalid message ID");
require(bridgeRecords[messageId].timestamp == 0, "Already recorded");
bridgeRecords[messageId] = BridgeRecord({
messageId: messageId,
token: token,
amount: amount,
destinationChain: destinationChain,
recipient: recipient,
timestamp: block.timestamp,
status: BridgeStatus.Initiated
});
bridgeHistory.push(messageId);
userBridgeHistory[recipient].push(messageId);
emit BridgeRecorded(messageId, token, amount, destinationChain);
}
/**
* @notice Update bridge status
*/
function updateBridgeStatus(
bytes32 messageId,
BridgeStatus status
) external onlyRole(BRIDGE_OPERATOR_ROLE) {
require(bridgeRecords[messageId].timestamp > 0, "Not found");
bridgeRecords[messageId].status = status;
emit BridgeStatusUpdated(messageId, status);
}
// View functions
function getBridgeRecord(bytes32 messageId) external view returns (BridgeRecord memory) {
return bridgeRecords[messageId];
}
function getBridgeHistory(address user) external view returns (BridgeRecord[] memory) {
bytes32[] memory userHistory = userBridgeHistory[user];
BridgeRecord[] memory records = new BridgeRecord[](userHistory.length);
for (uint256 i = 0; i < userHistory.length; i++) {
records[i] = bridgeRecords[userHistory[i]];
}
return records;
}
function getAllBridgeHistory() external view returns (BridgeRecord[] memory) {
BridgeRecord[] memory records = new BridgeRecord[](bridgeHistory.length);
for (uint256 i = 0; i < bridgeHistory.length; i++) {
records[i] = bridgeRecords[bridgeHistory[i]];
}
return records;
}
function getBridgeCount() external view returns (uint256) {
return bridgeHistory.length;
}
function getUserBridgeCount(address user) external view returns (uint256) {
return userBridgeHistory[user].length;
}
}

View File

@@ -1,42 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "../../dex/DODOPMMIntegration.sol";
/**
* @title PMMPriceProvider
* @notice Provides asset price in quote token using DODO PMM pool (oracle-backed when configured)
* @dev Used for vault collateral valuation or off-chain reporting when asset is a PMM pair token.
* Ledger uses XAUOracle for primary valuation; this adapter can be used by a future
* Ledger extension or by keepers/UIs to get PMM-based price (e.g. cUSDT/cUSDC in USD terms).
*/
contract PMMPriceProvider {
DODOPMMIntegration public immutable pmmIntegration;
constructor(address pmmIntegration_) {
require(pmmIntegration_ != address(0), "PMMPriceProvider: zero address");
pmmIntegration = DODOPMMIntegration(pmmIntegration_);
}
/**
* @notice Get price of asset in terms of quoteToken (18 decimals: quoteToken per 1 unit of asset)
* @param asset Asset to price (e.g. cUSDT)
* @param quoteToken Quote token (e.g. USDT or cUSDC)
* @return price Price in 18 decimals (1e18 = 1:1 for stablecoins); 0 if no pool
*/
function getPrice(address asset, address quoteToken) external view returns (uint256 price) {
if (asset == quoteToken) return 1e18;
address pool = pmmIntegration.pools(asset, quoteToken);
if (pool == address(0)) {
pool = pmmIntegration.pools(quoteToken, asset);
if (pool == address(0)) return 0;
// asset is quote, quoteToken is base: price = 1e36 / basePerQuote
uint256 basePerQuote = pmmIntegration.getPoolPriceOrOracle(pool);
if (basePerQuote == 0) return 0;
return (1e36) / basePerQuote;
}
// asset is base, quoteToken is quote
return pmmIntegration.getPoolPriceOrOracle(pool);
}
}

View File

@@ -1,29 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @notice Custom errors for vault system
*/
error ZeroAddress();
error ZeroAmount();
error AssetNotRegistered();
error AssetNotApproved();
error CurrencyNotApproved();
error InsufficientCollateral();
error InsufficientDebt();
error InsufficientDepositTokens();
error BorrowNotAllowed(bytes32 reasonCode);
error VaultNotLiquidatable();
error EntityNotRegistered();
error EntitySuspended();
error NotAuthorized();
error NotEligible();
error BelowMinCollateralization();
error DebtCeilingExceeded();
error InvalidLiquidationRatio();
error InvalidCreditMultiplier();
error InvalidWeight();
error FeedNotFound();
error StalePrice();
error InvalidPrice();
error Paused();

View File

@@ -1,15 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title IVaultStrategy
* @notice Interface for vault strategies (deferred implementation)
* @dev Defined now for forward compatibility
*/
interface IVaultStrategy {
function onDeposit(address token, uint256 amount) external;
function onWithdraw(address token, uint256 amount) external;
function onBridgePending(address token, uint256 amount, uint256 estimatedWait) external;
function execute(bytes calldata strategyData) external;
function getStrategyType() external pure returns (string memory);
}

View File

@@ -1,150 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title CurrencyValidation
* @notice Library for validating currency codes and types according to ISO 4217 compliance
* @dev Ensures all currency references are either ISO 4217 compliant or explicitly identified as non-ISO
*/
library CurrencyValidation {
/**
* @notice Currency type enumeration
*/
enum CurrencyType {
ISO4217_FIAT, // Standard ISO 4217 fiat currency (USD, EUR, etc.)
ISO4217_CRYPTO, // ISO 4217 cryptocurrency code (if applicable)
NON_ISO_SYNTHETIC, // Non-ISO synthetic unit of account (e.g., GRU)
NON_ISO_INTERNAL, // Non-ISO internal accounting unit
COMMODITY // Commodity code (XAU, XAG, etc.)
}
/**
* @notice Validate ISO 4217 currency code format
* @dev ISO 4217 codes are exactly 3 uppercase letters
* @param code Currency code to validate
* @return isValid True if code matches ISO 4217 format
*/
function isValidISO4217Format(string memory code) internal pure returns (bool isValid) {
bytes memory codeBytes = bytes(code);
if (codeBytes.length != 3) {
return false;
}
for (uint256 i = 0; i < 3; i++) {
uint8 char = uint8(codeBytes[i]);
if (char < 65 || char > 90) { // Not A-Z
return false;
}
}
return true;
}
/**
* @notice Check if currency code is a recognized ISO 4217 code
* @dev This is a simplified check - in production, maintain a full registry
* @param code Currency code
* @return isISO4217 True if code is recognized ISO 4217
*/
function isISO4217Currency(string memory code) internal pure returns (bool isISO4217) {
if (!isValidISO4217Format(code)) {
return false;
}
// Common ISO 4217 codes (extend as needed)
bytes32 codeHash = keccak256(bytes(code));
// Major currencies
if (codeHash == keccak256("USD") || // US Dollar
codeHash == keccak256("EUR") || // Euro
codeHash == keccak256("GBP") || // British Pound
codeHash == keccak256("JPY") || // Japanese Yen
codeHash == keccak256("CNY") || // Chinese Yuan
codeHash == keccak256("CHF") || // Swiss Franc
codeHash == keccak256("CAD") || // Canadian Dollar
codeHash == keccak256("AUD") || // Australian Dollar
codeHash == keccak256("NZD") || // New Zealand Dollar
codeHash == keccak256("SGD")) { // Singapore Dollar
return true;
}
// In production, maintain a full registry or use oracle/external validation
return false;
}
/**
* @notice Check if code is GRU (Global Reserve Unit)
* @dev GRU is a non-ISO 4217 synthetic unit of account
* @param code Currency code
* @return gru True if code is GRU
*/
function isGRU(string memory code) internal pure returns (bool gru) {
bytes32 codeHash = keccak256(bytes(code));
return codeHash == keccak256("GRU") ||
codeHash == keccak256("M00") ||
codeHash == keccak256("M0") ||
codeHash == keccak256("M1");
}
/**
* @notice Check if code is XAU (Gold)
* @dev XAU is the ISO 4217 commodity code for gold
* @param code Currency code
* @return xau True if code is XAU
*/
function isXAU(string memory code) internal pure returns (bool xau) {
return keccak256(bytes(code)) == keccak256("XAU");
}
/**
* @notice Get currency type
* @param code Currency code
* @return currencyType Type of currency
*/
function getCurrencyType(string memory code) internal pure returns (CurrencyType currencyType) {
if (isXAU(code)) {
return CurrencyType.COMMODITY;
}
if (isGRU(code)) {
return CurrencyType.NON_ISO_SYNTHETIC;
}
if (isISO4217Currency(code)) {
return CurrencyType.ISO4217_FIAT;
}
// If format is valid but not recognized, treat as potentially valid ISO 4217
if (isValidISO4217Format(code)) {
return CurrencyType.ISO4217_FIAT; // Assume valid but not in our list
}
return CurrencyType.NON_ISO_INTERNAL;
}
/**
* @notice Validate that currency is legal tender (ISO 4217 fiat)
* @param code Currency code
* @return legalTender True if currency is ISO 4217 legal tender
*/
function isLegalTender(string memory code) internal pure returns (bool legalTender) {
CurrencyType currencyType = getCurrencyType(code);
return currencyType == CurrencyType.ISO4217_FIAT;
}
/**
* @notice Require currency to be legal tender or revert
* @param code Currency code
*/
function requireLegalTender(string memory code) internal pure {
require(isLegalTender(code), "CurrencyValidation: not legal tender - must be ISO 4217");
}
/**
* @notice Require currency to NOT be legal tender (for synthetic units)
* @param code Currency code
*/
function requireNonLegalTender(string memory code) internal pure {
require(!isLegalTender(code), "CurrencyValidation: must be non-legal tender");
}
}

View File

@@ -1,87 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title MonetaryFormulas
* @notice Implementation of mandatory monetary formulas
* @dev All formulas MUST be applied exactly as specified without modification
*
* Formulas:
* - Money Supply: M = C + D and M = MB × m
* - Money Velocity: V = PQ / M
* - Money Multiplier: m = 1 / r and m = (1 + c) / (r + c)
*/
library MonetaryFormulas {
/**
* @notice Calculate money supply: M = C + D
* @dev M = Money Supply, C = Currency, D = Deposits
* @param currency Currency component (C)
* @param deposits Deposits component (D)
* @return moneySupply Money supply (M)
*/
function calculateMoneySupply(uint256 currency, uint256 deposits) internal pure returns (uint256 moneySupply) {
return currency + deposits;
}
/**
* @notice Calculate money supply: M = MB × m
* @dev M = Money Supply, MB = Monetary Base, m = Money Multiplier
* @param monetaryBase Monetary base (MB)
* @param moneyMultiplier Money multiplier (m)
* @return moneySupply Money supply (M)
*/
function calculateMoneySupplyFromMultiplier(uint256 monetaryBase, uint256 moneyMultiplier) internal pure returns (uint256 moneySupply) {
return (monetaryBase * moneyMultiplier) / 1e18; // Assuming multiplier in 18 decimals
}
/**
* @notice Calculate money velocity: V = PQ / M
* @dev V = Velocity, P = Price Level, Q = Quantity of Goods, M = Money Supply
* @param priceLevel Price level (P)
* @param quantityOfGoods Quantity of goods (Q)
* @param moneySupply Money supply (M)
* @return velocity Money velocity (V)
*/
function calculateMoneyVelocity(uint256 priceLevel, uint256 quantityOfGoods, uint256 moneySupply) internal pure returns (uint256 velocity) {
if (moneySupply == 0) {
return 0;
}
return (priceLevel * quantityOfGoods) / moneySupply;
}
/**
* @notice Calculate simple money multiplier: m = 1 / r
* @dev m = Money Multiplier, r = Reserve Ratio
* @param reserveRatio Reserve ratio (r) in basis points (e.g., 1000 = 10%)
* @return moneyMultiplier Money multiplier (m) in 18 decimals
*/
function calculateSimpleMoneyMultiplier(uint256 reserveRatio) internal pure returns (uint256 moneyMultiplier) {
require(reserveRatio > 0, "MonetaryFormulas: reserve ratio must be positive");
require(reserveRatio <= 10000, "MonetaryFormulas: reserve ratio cannot exceed 100%");
// m = 1 / r, where r is in basis points
// Convert to 18 decimals: m = (1e18 * 10000) / reserveRatio
return (1e18 * 10000) / reserveRatio;
}
/**
* @notice Calculate money multiplier with currency ratio: m = (1 + c) / (r + c)
* @dev m = Money Multiplier, r = Reserve Ratio, c = Currency Ratio
* @param reserveRatio Reserve ratio (r) in basis points
* @param currencyRatio Currency ratio (c) in basis points
* @return moneyMultiplier Money multiplier (m) in 18 decimals
*/
function calculateMoneyMultiplierWithCurrency(uint256 reserveRatio, uint256 currencyRatio) internal pure returns (uint256 moneyMultiplier) {
require(reserveRatio > 0, "MonetaryFormulas: reserve ratio must be positive");
require(reserveRatio <= 10000, "MonetaryFormulas: reserve ratio cannot exceed 100%");
require(currencyRatio <= 10000, "MonetaryFormulas: currency ratio cannot exceed 100%");
// m = (1 + c) / (r + c), where r and c are in basis points
// Convert to 18 decimals: m = (1e18 * (10000 + currencyRatio)) / (reserveRatio + currencyRatio)
uint256 numerator = 1e18 * (10000 + currencyRatio);
uint256 denominator = reserveRatio + currencyRatio;
require(denominator > 0, "MonetaryFormulas: invalid denominator");
return numerator / denominator;
}
}

View File

@@ -1,63 +0,0 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* @title XAUTriangulation
* @notice Library for enforcing XAU triangulation for all currency conversions
* @dev MANDATORY: All currency conversions MUST go through XAU (gold)
*
* Triangulation formula:
* CurrencyA → XAU → CurrencyB
*
* Steps:
* 1. Convert CurrencyA to XAU: xauAmount = currencyAAmount / xauRateA
* 2. Convert XAU to CurrencyB: currencyBAmount = xauAmount * xauRateB
*/
library XAUTriangulation {
/**
* @notice Triangulate from Currency A to Currency B via XAU
* @dev All conversions MUST go through XAU - this is mandatory
* @param currencyAAmount Amount in Currency A (18 decimals)
* @param xauRateA Rate: 1 oz XAU = xauRateA units of Currency A (18 decimals)
* @param xauRateB Rate: 1 oz XAU = xauRateB units of Currency B (18 decimals)
* @return currencyBAmount Amount in Currency B (18 decimals)
*/
function triangulate(
uint256 currencyAAmount,
uint256 xauRateA,
uint256 xauRateB
) internal pure returns (uint256 currencyBAmount) {
require(xauRateA > 0, "XAUTriangulation: invalid XAU rate A");
require(xauRateB > 0, "XAUTriangulation: invalid XAU rate B");
// Step 1: Convert Currency A to XAU
// xauAmount = currencyAAmount / xauRateA
uint256 xauAmount = (currencyAAmount * 1e18) / xauRateA;
// Step 2: Convert XAU to Currency B
// currencyBAmount = xauAmount * xauRateB / 1e18
currencyBAmount = (xauAmount * xauRateB) / 1e18;
}
/**
* @notice Convert amount to XAU
* @param amount Amount to convert (18 decimals)
* @param xauRate Rate: 1 oz XAU = xauRate units (18 decimals)
* @return xauAmount Amount in XAU (18 decimals)
*/
function toXAU(uint256 amount, uint256 xauRate) internal pure returns (uint256 xauAmount) {
require(xauRate > 0, "XAUTriangulation: invalid XAU rate");
xauAmount = (amount * 1e18) / xauRate;
}
/**
* @notice Convert XAU amount to currency
* @param xauAmount Amount in XAU (18 decimals)
* @param xauRate Rate: 1 oz XAU = xauRate units (18 decimals)
* @return currencyAmount Amount in currency (18 decimals)
*/
function fromXAU(uint256 xauAmount, uint256 xauRate) internal pure returns (uint256 currencyAmount) {
require(xauRate > 0, "XAUTriangulation: invalid XAU rate");
currencyAmount = (xauAmount * xauRate) / 1e18;
}
}