EUR0 SwapperEngine
High-Level Overview
The SwapperEngine contract is a smart contract designed to facilitate the swapping of EURC tokens for EUR0 tokens using an order matching mechanism. The contract allows users to create orders specifying the amount of EURC they wish to swap, and other users can fill these orders by providing EUR0 tokens in return. The contract aims to provide a direct token swapping solution without the need for intermediary liquidity pools.
The main objective of the SwapperEngine contract is to enable efficient and low-slippage token swaps between users. The contract relies on oracle-based pricing to determine swap prices, which helps minimize slippage. However, liquidity within the contract depends on the availability of active orders, and users may need to wait for new orders to be created if no matching orders are available.
It is important to note that the contract's mechanism can be utilized to facilitate a vampire attack, RWA → EUR0 → EURC → $$$ → RWA → to churn EURC into EUR0 by transparently staking treasury bonds to mint EUR0 swapping that EUR0 for EURC and cycling back into RWA ready to mint more EUR0 limited only by EURC order book depth.
Flow EURC Depositors:
Users deposit EURC tokens to create orders
Orders wait for matching with EUR0 providers
When matched, users receive EUR0 tokens
Flow RWA Provider:
RWA providers mint EUR0 through collateralization
They can swap EUR0 for EURC through the SwapperEngine
This creates a cycle: RWA → EUR0 → EURC → Cash → RWA
Contract Summary
The contract provides the following main functions:
depositEURC and depositEURCOnBehalf: Allows users to create a new order by depositing EURC and also to set a specific receiver with depositEURCOnBehalf.
withdrawEURC: Allows users to cancel an order and withdraw their deposited EURC.
provideEur0ReceiveEURC: Allows users to fill orders by providing EUR0 and receiving EURC in return.
The contract also includes utility functions such as getOrder
, getEur0WadEquivalent
, and getEurcWadPrice
to retrieve order details and perform price calculations. The swapperEngine has no option to define a maxEURCPrice for buyers and seller's don't have the option to define a minimumEURCPrice, instead the prices are provided by an EURC oracle, which also has measures against a potential EURC depeg. EUR0's price is considered to be €1 == 1EUR0 due to the numerous mechanisms in place to prevent a depeg, like reserves, CBR mechanism, arbitrage etc.
Inherited Contracts
Initializable (OZ): Used to provide a safe and controlled way to initialize the contract's state variables. It ensures that the contract's initializer function can only be called once, preventing accidental or malicious reinitialization.
ReentrancyGuardUpgradeable (OZ): Used to protect against reentrancy attacks. It provides a modifier that can be applied to functions to prevent them from being called recursively or from other functions that are also protected by the same guard.
PausableUpgradeable (OZ): Allows contract functionality to be paused by authorized accounts (PAUSING_CONTRACTS_ROLE to pause the contract and DEFAULT_ADMIN_ROLE to un-pause).
Functionality Breakdown
The SwapperEngine contract's primary purpose is to facilitate the swapping of EURC tokens for EUR0 tokens using an order matching mechanism. The contract's functionality can be broken down into the following key components:
Order Creation:
Users can create new orders by calling the depositEURC
function and specifying the amount of EURC they wish to swap and the receiver of the converted amount. The contract transfers the specified amount of EURC tokens from the user to itself and creates a new order with the deposited amount, the user's address as the requester and a receiver. The order is assigned a unique order ID and stored in the contract's orders mapping.
Order Cancellation:
Users who have created an order can cancel it by calling the withdrawEURC
function and specifying the order ID. The contract verifies that the caller is the requester of the order and that the order is active. If the conditions are met, the contract deactivates the order, sets its token amount to zero, and transfers the deposited EURC tokens back to the requester.
Order Matching:
Users can fill existing orders by specifying the recipient address, the amount of EURC to take (or the amount of EUR0 to give), an array of order IDs to match against, and whether partial matching is allowed. The contract verifies that the caller has sufficient EUR0 balance and allowance to cover the required amount based on the current EURC Price Calculation obtained from the oracle. The contract iterates through the provided order IDs and attempts to match the requested EURC amount against active orders. If partial matching is allowed and there is not enough EURC in the orders to fulfil the entire request, the contract will partially fill orders until the requested amount is met or all orders are exhausted. For each matched order, the contract transfers the corresponding EUR0 tokens from the caller to the order requester and transfers the EURC tokens from itself to the specified recipient. If partial matching is not allowed and the requested EURC amount cannot be fully matched, the contract reverts the transaction.
Price Calculation:
The contract relies on an external oracle contract to obtain the current price of EURC tokens in WAD format (18 decimals). The _getEurcWadPrice
function is used to retrieve the current EURC price from the oracle. The _getEur0WadEquivalent
function is used to calculate the equivalent amount of EUR0 tokens for a given amount of EURC tokens based on the current price.
Security Analysis
Method: _provideEur0ReceiveEURC
_provideEur0ReceiveEURC
This method allows users to provide EUR0 tokens and receive EURC tokens by matching against existing orders. It matches the requested EURC amount to the provided EUR0 tokens against the specified orders, transfers the corresponding EURC tokens to the recipient, and updates the order states accordingly.
function _provideEur0ReceiveEURC( ... ) internal returns (uint256 unmatchedEurcAmount, uint256 totalEur0Provided) {
if (amountEurcToTakeInNativeDecimals == 0) { revert AmountIsZero() }
if (orderIdsToTake.length == 0) { revert NoOrdersIdsProvided() }
SwapperEngineStorageV0 storage $ = _swapperEngineStorageV0();
uint256 eurcWadPrice = _getEurcWadPrice();
uint256 totalEurcTaken = 0;
The function is protected against reentrancy attacks by using the nonReentrant
modifier, ensuring that the function cannot be called recursively or from other functions that are also protected by the same guard.
Validates that the amount of EURC to take is greater than zero.
Validates that at least one order ID is provided for matching.
Retrieves the contract's storage using the correct storage pattern.
Retrieves the current price of EURC in WAD format (18 decimals) from an oracle, ensuring that the price used for calculations is up-to-date and accurate.
Initializes the total amount of EURC taken to zero.
for (uint256 i; i < orderIdsToTake.length && totalEurcTaken < amountEurcToTakeInNativeDecimals;) {
uint256 orderId = orderIdsToTake[i];
EurcOrder storage order = $.orders[orderId];
if (order.active) {
uint256 remainingAmountToTake = amountEurcToTakeInNativeDecimals - totalEurcTaken;
uint256 amountOfEurcFromOrder = order.tokenAmount > remainingAmountToTake ? remainingAmountToTake : order.tokenAmount;
order.tokenAmount -= amountOfEurcFromOrder;
totalEurcTaken += amountOfEurcFromOrder;
if (order.tokenAmount == 0) { order.active = false };
uint256 eur0Amount = _getEur0WadEquivalent(amountOfEurcFromOrder, eurcWadPrice);
totalEur0Provided += eur0Amount;
$.eur0.safeTransferFrom(msg.sender, order.requester, eur0Amount);
$.eurcToken.safeTransfer(recipient, amountOfEurcFromOrder);
emit OrderMatched(order.requester, msg.sender, orderId, amountOfEurcFromOrder);
}
unchecked { ++i }
}
if (!partialMatchingAllowed && totalEurcTaken != amountEurcToTakeInNativeDecimals || totalEurcTaken == 0) { revert AmountTooLow() }
return ((amountEurcToTakeInNativeDecimals - totalEurcTaken), totalEur0Provided);
Retrieves the order details for the current order ID.
Checks if the order is active before processing.
If the order is active, calculates the amount of EURC to take from the current order based on the remaining amount to take and the order's available balance.
Updates the order's token amount and the total EURC taken.
Marks the order as inactive if its token amount reaches zero.
Calculates the equivalent EUR0 amount for the EURC taken from the order using the
_getEur0WadEquivalent
function and the current EURC price.Updates the total EUR0 provided with the calculated amount.
Transfers the EUR0 tokens from the sender to the order requester.
Transfers the EURC tokens from the contract to the recipient.
Emits an OrderMatched event with the relevant details.
Increments the loop counter using an unchecked block for gas optimization.
Reverts the transaction if partial matching is not allowed and the total EURC taken does not match the requested amount or if no EURC was taken.
Returns the remaining amount of EURC that was not taken and the total EUR0 provided.
Method: _getEur0WadEquivalent
_getEur0WadEquivalent
This method calculates the EUR0 equivalent amount in WAD format (18 decimals) for a given EURC token amount. It converts the EURC token amount from its native decimal representation (6 decimals) to WAD format and then calculates the equivalent EUR0 amount based on the provided EURC price in WAD format.
function _getEur0WadEquivalent(uint256 eurcTokenAmountInNativeDecimals, uint256 eurcWadPrice) private view returns (uint256 eur0WadEquivalent) {
SwapperEngineStorageV0 storage $ = _swapperEngineStorageV0();
uint8 decimals = IERC20Metadata(address($.eurcToken)).decimals();
uint256 eurcWad = eurcTokenAmountInNativeDecimals.tokenAmountToWad(decimals);
eur0WadEquivalent = eurcWad.wadAmountByPrice(eurcWadPrice);
}
Retrieves the contract's storage using the correct storage pattern.
Retrieves the decimal places of the EURC token using the
decimals()
function from the IERC20Metadata interface.Converts the
eurcTokenAmountInNativeDecimals
to WAD format (18 decimals) using thetokenAmountToWad
function, which takes into account the token's native decimals.Calculates the equivalent amount of EUR0 tokens in WAD format by multiplying the
eurcWad
amount with theeurcWadPrice
using thewadAmountByPrice
function.
Method: depositEURC
depositEURC
This method allows users to deposit EURC tokens and create a new order. It transfers the specified amount of EURC tokens from the caller to the contract and creates a new order with the deposited amount and the caller as the requester.
function depositEURC(uint256 amountToDeposit) external nonReentrant whenNotPaused {
SwapperEngineStorageV0 storage $ = _swapperEngineStorageV0();
_depositEURC($, amountToDeposit, msg.sender, msg.sender);
}
The function is protected against reentrancy attacks by using the nonReentrant
modifier, ensuring that the function cannot be called recursively or from other functions that are also protected by the same guard.
Retrieves the contract's storage using the correct storage pattern.
Validates that the amount of EURC to deposit is greater than or equal to the minimum required amount specified in the contract's storage. This prevents any attempts to deposit amounts below the minimum threshold.
Sets the value of
orderId
to the current value of$.nextOrderId
then increments by 1. Since it is initialized as 1, the firstorderId
will be one and so on.Creates a new
EurcOrder
struct in storage using the order ID as key. The struct is set up correctly to contain: the requester's address (msg.sender
), the deposited token amount (amountToDeposit
), and sets the active flag to true.Transfers the specified amount of EURC tokens from the caller (
msg.sender
) to the contract (address(this)
) using thesafeTransferFrom
function to ensure that the transfer is successful and the contract receives the deposited tokens. If the transfer fails, the function will revert.Emits a Deposit event, providing the order ID and the deposited amount for the subgraph.
The depositEURCOnBehalf
method do the same but with a specific recipient that can be specified by the caller.
Method: withdrawEURC
withdrawEURC
This method allows the requester of an order to withdraw their deposited EURC tokens and cancel the order. It deactivates the specified order, sets its token amount to zero, and transfers the deposited EURC tokens back to the requester.
function withdrawEURC(uint256 orderToCancel) external nonReentrant whenNotPaused {
SwapperEngineStorageV0 storage $ = _swapperEngineStorageV0();
EurcOrder storage order = $.orders[orderToCancel];
if (!order.active) { revert OrderNotActive() }
if (order.requester != msg.sender) { revert NotRequester() }
uint256 amountToWithdraw = order.tokenAmount;
order.active = false;
order.tokenAmount = 0;
$.eurcToken.safeTransfer(msg.sender, amountToWithdraw);
emit Withdraw(msg.sender, orderToCancel, amountToWithdraw);
}
The function is protected against reentrancy attacks by using the nonReentrant
modifier, ensuring that the function cannot be called recursively or from other functions that are also protected by the same guard.
Retrieves the contract's storage using the correct storage pattern.
Retrieves the
EurcOrder
struct as storage so it will be modified.Checks if the order is active using the active flag. If the order is not active or does not exist, the function will revert with an appropriate error message. This prevents any attempts to withdraw from invalid or canceled orders.
Verifies that the caller (
msg.sender
) is the requester of the order. This ensures that only the original requester can cancel their own order and withdraw the deposited tokens.Retrieves the token amount associated with the order and assigns it to the
amountToWithdraw
variable.Sets the active flag of the order to false in storage.
Sets the
tokenAmount
of the order to zero in storage.Transfers the
amountToWithdraw
of EURC tokens from the contract back to the requester (msg.sender
) using thesafeTransfer
function. This ensures that the transfer is successful and the requester receives their tokens. If the transfer fails, the function will revert.Emits a Withdraw event, providing the
orderToCancel
ID and theamountToWithdraw
for the subgraph.
Method: swapEur0
swapEur0
This method allows users to provide EUR0 tokens and receive EURC tokens by matching against existing orders. It matches the specified amount of EUR0 tokens against the specified orders, transfers the corresponding EURC tokens to the recipient, and updates the order states accordingly.
function swapEur0(address recipient, uint256 amountEur0ToProvideInWad, uint256[] memory orderIdsToTake, bool partialMatchingAllowed) external nonReentrant whenNotPaused returns (uint256) {
uint256 eurcWadPrice = _getEurcWadPrice();
(, uint256 totalEur0Provided) = _provideEur0ReceiveEURC(
recipient, _getEurcAmountFromEur0WadEquivalent(amountEur0ToProvideInWad, eurcWadPrice), orderIdsToTake, partialMatchingAllowed, eurcWadPrice
);
return amountEur0ToProvideInWad - totalEur0Provided;
}
The function is protected against reentrancy attacks by using the nonReentrant
modifier, ensuring that the function cannot be called recursively or from other functions that are also protected by the same guard.
Retrieves the current EURC price in WAD format using the
_getEurcWadPrice()
function.Calculates the equivalent amount of EURC to take in native decimals based on the provided
amountEur0ToProvideInWad
and the currenteurcWadPrice
using the_getEurcAmountFromEur0WadEquivalent
function. Then calls the_provideEur0ReceiveEURC
function to perform the actual swap, passing the recipient,amountEurcToTakeInNativeDecimals
,orderIdsToTake
,partialMatchingAllowed
, andeurcWadPrice
parameters. The function returns the total amount of EUR0 provided.Returns the sum of unmatched EUR0 in wad format including dust, representing the total amount of EUR0 that was not matched or was left as dust.
Last updated
Was this helpful?