Settlement Mechanics
Last updated
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);
}
}
Bridge sends assets to Receiver contract
Receiver notifies Gateway of balance
Gateway pulls assets from Receiver
Gateway settles request with MetaVault
MetaVault updates states and completes request
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()
);
}
Assets Reception
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);
}
Asset Divestment Settlement
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;
}
Timeout Scenarios
Detection mechanisms
Recovery procedures
State rollback process
Receiver Contract Security
Access control model
Balance validation
Minimum balance requirements
Recovery Procedures
Force settlement conditions
Asset recovery process
State reconciliation steps
Settled amounts must match or exceed minimum expected
Request queue order must be maintained
Assets must be properly accounted for across chains
Recovery mechanisms must handle all failure cases
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();