Settlement Mechanics
Last updated
/// @notice Core state tracking for settlements
struct RequestData {
address controller; // Request controller
uint256[] superformIds; // Target vaults
uint256[] requestedAssetsPerVault; // Assets per vault
uint256 requestedAssets; // Total requested
address receiverAddress; // Settlement receiver
}
// Settlement state tracking
mapping(bytes32 => RequestData) public requests;
EnumerableSetLib.Bytes32Set internal _requestsQueue;
address public receiverImplementation;
// Settlement events
event ReceiverDeployed(bytes32 indexed key, address indexed receiver);
event RequestSettled(bytes32 indexed key, address indexed controller, uint256 settledAmount);
event LiquidateXChain(address indexed controller, uint256[] indexed superformIds, uint256 indexed requestedAssets, bytes32 key);interface ERC20Receiver {
function initialize(bytes32 key) external;
function key() external returns (bytes32);
function balance() external view returns (uint256);
function minExpectedBalance() external view returns (uint256);
function setMinExpectedBalance(uint256 amount) external;
function pull(uint256 amount) external;
}function getReceiver(bytes32 key) public returns (address receiverAddress) {
if (key == bytes32(0)) revert InvalidKey();
address current = receivers[key];
if (current != address(0)) {
return current;
} else {
receiverAddress = LibClone.clone(receiverImplementation);
ERC20Receiver(receiverAddress).initialize(key);
receivers[key] = receiverAddress;
emit ReceiverDeployed(key, receiverAddress);
}
}function previewSettlement(bytes32 key) external view returns (
address controller,
uint256[] memory superformIds,
uint256 requestedAssets,
uint256 settledAssets
) {
RequestData memory data = requests[key];
address receiverContract = getReceiver(key);
return (
data.controller,
data.superformIds,
data.requestedAssets,
ERC20Receiver(receiverContract).balance()
);
}function settleLiquidation(bytes32 key, bool force) external onlyRoles(RELAYER_ROLE) {
if (!_requestsQueue.contains(key)) revert RequestNotFound();
RequestData memory data = requests[key];
if (data.controller == address(0)) revert InvalidController();
ERC20Receiver receiverContract = ERC20Receiver(getReceiver(key));
uint256 settledAssets = receiverContract.balance();
_requestsQueue.remove(key);
if (!force) {
if (receiverContract.balance() < receiverContract.minExpectedBalance()) {
revert MinimumBalanceNotMet();
}
}
receiverContract.pull(settledAssets);
asset.safeTransfer(address(vault), settledAssets);
vault.fulfillSettledRequest(data.controller, data.requestedAssets, settledAssets);
emit RequestSettled(key, data.controller, settledAssets);
}function settleDivest(bytes32 key, bool force) external onlyRoles(RELAYER_ROLE) {
if (!_requestsQueue.contains(key)) revert RequestNotFound();
RequestData memory data = requests[key];
_requestsQueue.remove(key);
ERC20Receiver receiverContract = ERC20Receiver(getReceiver(key));
if (data.controller != address(vault)) revert();
if (!force) {
if (receiverContract.balance() < receiverContract.minExpectedBalance()) {
revert MinimumBalanceNotMet();
}
}
uint256 settledAssets = receiverContract.balance();
uint256 requestedAssets = data.requestedAssets;
receiverContract.pull(settledAssets);
totalPendingXChainDivests -= settledAssets;
asset.safeTransfer(address(vault), settledAssets);
vault.settleXChainDivest(requestedAssets);
}function notifyRefund(uint256 superformId, uint256 value) external {
bytes32 key = ERC20Receiver(msg.sender).key();
if (requests[key].receiverAddress != msg.sender) revert();
RequestData memory req = requests[key];
uint256 currentExpectedBalance = ERC20Receiver(msg.sender).minExpectedBalance();
uint256 vaultIndex;
for (uint256 i = 0; i < req.superformIds.length; ++i) {
if (req.superformIds[i] == superformId) {
vaultIndex = i;
break;
}
}
uint256 vaultRequestedAssets = req.requestedAssetsPerVault[vaultIndex];
if (req.controller == address(vault)) {
totalPendingXChainDivests -= vaultRequestedAssets;
}
requests[key].requestedAssets -= vaultRequestedAssets;
ERC20Receiver(msg.sender).setMinExpectedBalance(
_sub0(currentExpectedBalance, vaultRequestedAssets)
);
superPositions.safeTransferFrom(msg.sender, address(this), superformId, value, "");
superPositions.safeTransferFrom(
address(this),
address(vault),
superformId,
value,
abi.encode(vaultRequestedAssets)
);
}function onERC1155Received(
address operator,
address from,
uint256 superformId,
uint256 value,
bytes memory data
) public returns (bytes4) {
operator;
value;
if (from != address(gateway)) revert Unauthorized();
if (data.length > 0) {
uint256 refundedAssets = abi.decode(data, (uint256));
if (refundedAssets != 0) {
_totalDebt += refundedAssets.toUint128();
vaults[superformId].totalDebt += refundedAssets.toUint128();
}
}
return this.onERC1155Received.selector;
}function onERC1155BatchReceived(
address operator,
address from,
uint256[] memory superformIds,
uint256[] memory values,
bytes memory data
) public returns (bytes4) {
// Process each NFT individually
for (uint256 i = 0; i < superformIds.length; ++i) {
onERC1155Received(address(0), from, superformIds[i], values[i], "");
}
return this.onERC1155BatchReceived.selector;
}modifier onlyRoles(uint256 role) {
if (!hasRole(role, msg.sender)) revert Unauthorized();
_;
}
modifier refundGas() {
uint256 balanceBefore;
assembly {
balanceBefore := sub(selfbalance(), callvalue())
}
_;
assembly {
let balanceAfter := selfbalance()
switch lt(balanceAfter, balanceBefore)
case true {
mstore(0x00, 0x1c26714c) // `InsufficientGas()`
revert(0x1c, 0x04)
}
}
}error RequestNotFound();
error InvalidController();
error MinimumBalanceNotMet();
error Unauthorized();
error InsufficientGas();
error InvalidKey();