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:
@@ -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 {}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
165
contracts/flash/QuotePushTreasuryManager.sol
Normal file
165
contracts/flash/QuotePushTreasuryManager.sol
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user