Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,25 @@ contract LoopringIOExchangeOwner is SelectorBasedAccessManager, ChiDiscount, ERC
address[] receivers;
}

struct FlashDepositInfo
{
address to;
address token;
uint96 amount;
}

struct FlashVaultInfo
{
address to;
bytes data;
}

struct FlashConfig
{
FlashDepositInfo[] deposits;
FlashVaultInfo[] vaults;
}

constructor(
address _exchange,
address _chiToken
Expand Down Expand Up @@ -101,7 +120,8 @@ contract LoopringIOExchangeOwner is SelectorBasedAccessManager, ChiDiscount, ERC
bool isDataCompressed,
bytes calldata data,
CallbackConfig calldata config,
ChiConfig calldata chiConfig
ChiConfig calldata chiConfig,
FlashConfig calldata flashConfig
)
external
discountCHI(chiToken, chiConfig)
Expand Down Expand Up @@ -135,7 +155,31 @@ contract LoopringIOExchangeOwner is SelectorBasedAccessManager, ChiDiscount, ERC
// Process the callback logic.
_beforeBlockSubmission(blocks, config);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For AMM, maybe it's OK to move code inside _beforeBlockSubmission after target.fastCallAndVerify(gasleft(), 0, decompressed), but for other possible use cases, maybe it's still required to be run beforehand. Should we add a flag runBeforeBlockSubmission to TxCallback? Or an alternative is to pass in two CallbackConfig objects, one named txReceiverPreCallbacks, another named ``txReceiverPostCallbacks`.

I'm not sure if the current postBlocksCallbacks also has the potential of being invoked in some future scenarios before submitBlocks. It may open doors for some other possibilities.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be useful, but I don't know if we should add support for things that we currently don't plan to use (and if we use it maybe it needs additional changes again).

But a boolean before/after may indeed be useful with perhaps very little work, so perhaps still worth it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The normal callbacks can now be run before or after the blocks submission using a flag.

I kept the TransactionReceivers (AMM, Bridge) to after the block submissions only to keep things simple (a bit more difficult to do then for the normal callbacks).


// Do flash deposits
for (uint i = 0; i < flashConfig.deposits.length; i++) {
IExchangeV3(target).flashDeposit(
flashConfig.deposits[i].to,
flashConfig.deposits[i].token,
flashConfig.deposits[i].amount
);
}

target.fastCallAndVerify(gasleft(), 0, decompressed);

// Do vault logic
for (uint i = 0; i < flashConfig.vaults.length; i++) {
require(flashConfig.vaults[i].to != target, "EXCHANGE_CANNOT_BE_VAULT");
(bool success, ) = flashConfig.vaults[i].to.call(flashConfig.vaults[i].data);
require(success, "VAULT_CALL_FAILED");
}

// Make sure flash deposits were repaid
for (uint i = 0; i < flashConfig.deposits.length; i++) {
require(
IExchangeV3(target).getAmountFlashDeposited(flashConfig.deposits[i].token) == 0,
"FLASH_DEPOSIT_NOT REPAID"
);
}
}

function _beforeBlockSubmission(
Expand Down
3 changes: 3 additions & 0 deletions packages/loopring_v3/contracts/core/iface/ExchangeData.sol
Original file line number Diff line number Diff line change
Expand Up @@ -224,5 +224,8 @@ library ExchangeData

// Last time the protocol fee was withdrawn for a specific token
mapping (address => uint) protocolFeeLastWithdrawnTime;

// Flash deposits
mapping (address => uint96) amountFlashDeposited;
}
}
27 changes: 27 additions & 0 deletions packages/loopring_v3/contracts/core/iface/IExchangeV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,33 @@ abstract contract IExchangeV3 is Claimable
view
returns (uint96);


function flashDeposit(
address to,
address tokenAddress,
uint96 amount
)
external
virtual;

function repayFlashDeposit(
address from,
address tokenAddress,
uint96 amount,
bytes calldata extraData
)
external
virtual
payable;

function getAmountFlashDeposited(
address tokenAddress
)
external
virtual
view
returns (uint96);

// -- Withdrawals --
/// @dev Submits an onchain request to force withdraw Ether or ERC20 tokens.
/// This request always withdraws the full balance.
Expand Down
40 changes: 39 additions & 1 deletion packages/loopring_v3/contracts/core/impl/ExchangeV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ contract ExchangeV3 is IExchangeV3, ReentrancyGuard
nonReentrant
onlyFromUserOrAgent(from)
{
state.deposit(from, to, tokenAddress, amount, extraData);
state.deposit(from, to, tokenAddress, amount, extraData, false);
}

function getPendingDepositAmount(
Expand All @@ -382,6 +382,44 @@ contract ExchangeV3 is IExchangeV3, ReentrancyGuard
return state.pendingDeposits[owner][tokenID].amount;
}

function flashDeposit(
address to,
address tokenAddress,
uint96 amount
)
external
override
nonReentrant
onlyOwner
{
state.deposit(to, to, tokenAddress, amount, new bytes(0), true);
}

function repayFlashDeposit(
address from,
address tokenAddress,
uint96 amount,
bytes calldata extraData
)
external
payable
override
nonReentrant
{
state.repayFlashDeposit(from, tokenAddress, amount, extraData);
}

function getAmountFlashDeposited(
address tokenAddress
)
external
override
view
returns (uint96)
{
return state.amountFlashDeposited[tokenAddress];
}

// -- Withdrawals --

function forceWithdraw(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ library ExchangeDeposits
address to,
address tokenAddress,
uint96 amount, // can be zero
bytes memory extraData
bytes memory extraData,
bool flash
)
internal // inline call
{
Expand All @@ -46,26 +47,53 @@ library ExchangeDeposits

uint16 tokenID = S.getTokenID(tokenAddress);

// Transfer the tokens to this contract
uint96 amountDeposited = S.depositContract.deposit{value: msg.value}(
from,
tokenAddress,
amount,
extraData
);
uint96 amountDeposited = amount;
if (flash) {
require(msg.value == 0, "INVALID_FLASH_DEPOSIT");
S.amountFlashDeposited[tokenAddress] = S.amountFlashDeposited[tokenAddress].add(amount);
} else {
// Transfer the tokens to this contract
amountDeposited = S.depositContract.deposit{value: msg.value}(
from,
tokenAddress,
amount,
extraData
);

emit DepositRequested(
from,
to,
tokenAddress,
tokenID,
amountDeposited
);
}

// Add the amount to the deposit request and reset the time the operator has to process it
ExchangeData.Deposit memory _deposit = S.pendingDeposits[to][tokenID];
_deposit.timestamp = uint64(block.timestamp);
_deposit.amount = _deposit.amount.add(amountDeposited);
S.pendingDeposits[to][tokenID] = _deposit;
}

emit DepositRequested(
function repayFlashDeposit(
ExchangeData.State storage S,
address from,
address tokenAddress,
uint96 amount,
bytes memory extraData
)
public
{
// Transfer the tokens to this contract
uint96 amountDeposited = S.depositContract.deposit{value: msg.value}(
from,
to,
tokenAddress,
tokenID,
amountDeposited
amount,
extraData
);

// Paid back
S.amountFlashDeposited[tokenAddress] = S.amountFlashDeposited[tokenAddress].sub(amountDeposited);
}
}
54 changes: 54 additions & 0 deletions packages/loopring_v3/contracts/test/TestVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2017 Loopring Technology Limited.
pragma solidity ^0.7.0;

import "../core/iface/IExchangeV3.sol";
import "../lib/Claimable.sol";
import "../lib/Drainable.sol";
import "../lib/ERC20.sol";


/// @author Brecht Devos - <[email protected]>
contract TestVault is Claimable, Drainable {

function swapAndRepay(
address exchange,
address swapContract,
bytes calldata swapData,
address swapToken,
uint swapAmount,
address repayToken,
uint96 repayAmount
)
public
{
// Swap
if (swapToken != address(0)) {
ERC20(swapToken).approve(swapContract, swapAmount);
}
(bool success, ) = swapContract.call(swapData);
require(success, "SWAP_FAILED");

// Repay
if (repayToken != address(0)) {
IDepositContract depositContract = IExchangeV3(exchange).getDepositContract();
ERC20(repayToken).approve(address(depositContract), repayAmount);
}
uint repayValue = (repayToken == address(0)) ? repayAmount : 0;
IExchangeV3(exchange).repayFlashDeposit{value: repayValue}(
address(this),
repayToken,
repayAmount,
new bytes(0)
);
}

function canDrain(address drainer, address /* token */)
public
override
view
returns (bool)
{
return drainer == owner;
}
}