Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding safe transfer to collateralManager #64

Open
wants to merge 6 commits into
base: merge-train-r4-test
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
13 changes: 9 additions & 4 deletions packages/contracts/contracts/CollateralManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
// Libraries
import "@openzeppelin/contracts-upgradeable/utils/structs/EnumerableSetUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import {SafeERC20} from "./openzeppelin/SafeERC20.sol";

// Interfaces
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol";
Expand All @@ -19,6 +21,9 @@ import "./interfaces/ITellerV2.sol";
import "./interfaces/IProtocolPausingManager.sol";
import "./interfaces/IHasProtocolPausingManager.sol";
contract CollateralManager is OwnableUpgradeable, ICollateralManager {

using SafeERC20 for IERC20;

/* Storage */
using EnumerableSetUpgradeable for EnumerableSetUpgradeable.AddressSet;
ITellerV2 public tellerV2;
Expand Down Expand Up @@ -396,12 +401,12 @@ contract CollateralManager is OwnableUpgradeable, ICollateralManager {
);
// Pull collateral from borrower & deposit into escrow
if (collateralInfo._collateralType == CollateralType.ERC20) {
IERC20Upgradeable(collateralInfo._collateralAddress).transferFrom(
IERC20(collateralInfo._collateralAddress).safeTransferFrom(
borrower,
address(this),
collateralInfo._amount
);
IERC20Upgradeable(collateralInfo._collateralAddress).approve(
IERC20(collateralInfo._collateralAddress).forceApprove(
escrowAddress,
collateralInfo._amount
);
Expand Down Expand Up @@ -570,7 +575,7 @@ contract CollateralManager is OwnableUpgradeable, ICollateralManager {
if (collateralType == CollateralType.ERC20) {
return
_collateralInfo._amount <=
IERC20Upgradeable(_collateralInfo._collateralAddress).balanceOf(
IERC20(_collateralInfo._collateralAddress).balanceOf(
_borrowerAddress
);
} else if (collateralType == CollateralType.ERC721) {
Expand Down
148 changes: 109 additions & 39 deletions packages/contracts/contracts/TellerV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import "./interfaces/ILoanRepaymentListener.sol";

// Libraries
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {SafeERC20} from "./openzeppelin/SafeERC20.sol";

import "./libraries/NumbersLib.sol";
import "./libraries/ExcessivelySafeCall.sol";

import { V2Calculations, PaymentCycleType } from "./libraries/V2Calculations.sol";

Expand Down Expand Up @@ -859,53 +860,71 @@ contract TellerV2 is
}


/*
If for some reason the lender cannot receive funds, should put those funds into the escrow
so the loan can always be repaid and the borrower can get collateral out


*/
function _sendOrEscrowFunds(uint256 _bidId, Payment memory _payment)
internal
internal virtual
{
Bid storage bid = bids[_bidId];
address lender = getLoanLender(_bidId);

uint256 _paymentAmount = _payment.principal + _payment.interest;

try

bid.loanDetails.lendingToken.transferFrom{ gas: 100000 }(
_msgSenderForMarket(bid.marketplaceId),
lender,
_paymentAmount
)
{} catch {
address sender = _msgSenderForMarket(bid.marketplaceId);

uint256 balanceBefore = bid.loanDetails.lendingToken.balanceOf(
address(this)
);

//if unable, pay to escrow
bid.loanDetails.lendingToken.safeTransferFrom(
sender,
address(this),
_paymentAmount
);

uint256 balanceAfter = bid.loanDetails.lendingToken.balanceOf(
address(this)
);

//used for fee-on-send tokens
uint256 paymentAmountReceived = balanceAfter - balanceBefore;
//USER STORY: Should function properly with USDT and USDC and WETH for sure

//USER STORY : if the lender cannot receive funds for some reason (denylisted)
//then we will try to send the funds to the EscrowContract bc we want the borrower to be able to get back their collateral !
// i.e. lender not being able to recieve funds should STILL allow repayment to succeed !


bool transferSuccess = safeTransferFromERC20Custom(
address(bid.loanDetails.lendingToken),
_msgSenderForMarket(bid.marketplaceId) , //from
lender, //to
_paymentAmount // amount
);

if (!transferSuccess) {
//could not send funds due to an issue with lender (denylisted?) so we are going to try and send the funds to the
// escrow wallet FOR the lender to be able to retrieve at a later time when they are no longer denylisted by the token

address sender = _msgSenderForMarket(bid.marketplaceId);

uint256 balanceBefore = bid.loanDetails.lendingToken.balanceOf(
address(this)
);

//if unable, pay to escrow
bid.loanDetails.lendingToken.safeTransferFrom(
sender,
address(this),
_paymentAmount
);

uint256 balanceAfter = bid.loanDetails.lendingToken.balanceOf(
address(this)
);

//used for fee-on-send tokens
uint256 paymentAmountReceived = balanceAfter - balanceBefore;

bid.loanDetails.lendingToken.forceApprove(
address(escrowVault),
paymentAmountReceived
);

IEscrowVault(escrowVault).deposit(
lender,
address(bid.loanDetails.lendingToken),
paymentAmountReceived
);

bid.loanDetails.lendingToken.approve(
address(escrowVault),
paymentAmountReceived
);

IEscrowVault(escrowVault).deposit(
lender,
address(bid.loanDetails.lendingToken),
paymentAmountReceived
);
}
}

address loanRepaymentListener = repaymentListenerForBid[_bidId];

Expand All @@ -924,7 +943,58 @@ contract TellerV2 is
}
}

/*
A try/catch pattern for safeTransferERC20 that helps support standard ERC20 tokens and non-standard ones like USDT

@notice If the token address is an EOA, callSuccess will always be true so token address should always be a contract.
*/
function safeTransferFromERC20Custom(

address _token,
address _from,
address _to,
uint256 _amount

) internal virtual returns (bool success) {

//https://github.com/nomad-xyz/ExcessivelySafeCall
//this works similarly to a try catch -- an inner revert doesnt revert us but will make callSuccess be false.
( bool callSuccess, bytes memory callReturnData ) = ExcessivelySafeCall.excessivelySafeCall(
address(_token),
100000,
0,
1000, //max return data size
abi.encodePacked(
abi.encodeWithSelector(
IERC20
.transferFrom
.selector,
_from, //from
_to, //to
_amount // amount

),
msg.sender
)
);


//If the token returns data, make sure it returns true. This helps us with USDT which may revert but never returns a bool.
bool dataIsSuccess = true;
if (callReturnData.length >= 32) {
assembly {
// Load the first 32 bytes of the return data (assuming it's a bool)
let result := mload(add(callReturnData, 0x20))
// Check if the result equals `true` (1)
dataIsSuccess := eq(result, 1)
}
}

// ensures that both callSuccess (the low-level call didn't fail) and dataIsSuccess (the function returned true if it returned something).
return callSuccess && dataIsSuccess;


}


/**
Expand Down
138 changes: 138 additions & 0 deletions packages/contracts/contracts/libraries/ExcessivelySafeCall.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.7.6;

library ExcessivelySafeCall {
uint256 constant LOW_28_MASK =
0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff;

/// @notice Use when you _really_ really _really_ don't trust the called
/// contract. This prevents the called contract from causing reversion of
/// the caller in as many ways as we can.
/// @dev The main difference between this and a solidity low-level call is
/// that we limit the number of bytes that the callee can cause to be
/// copied to caller memory. This prevents stupid things like malicious
/// contracts returning 10,000,000 bytes causing a local OOG when copying
/// to memory.
/// @param _target The address to call
/// @param _gas The amount of gas to forward to the remote contract
/// @param _value The value in wei to send to the remote contract
/// @param _maxCopy The maximum number of bytes of returndata to copy
/// to memory.
/// @param _calldata The data to send to the remote contract
/// @return success and returndata, as `.call()`. Returndata is capped to
/// `_maxCopy` bytes.
function excessivelySafeCall(
address _target,
uint256 _gas,
uint256 _value,
uint16 _maxCopy,
bytes memory _calldata
) internal returns (bool, bytes memory) {
// set up for assembly call
uint256 _toCopy;
bool _success;
bytes memory _returnData = new bytes(_maxCopy);
// dispatch message to recipient
// by assembly calling "handle" function
// we call via assembly to avoid memcopying a very large returndata
// returned by a malicious contract
assembly {
_success := call(
_gas, // gas
_target, // recipient
_value, // ether value
add(_calldata, 0x20), // inloc
mload(_calldata), // inlen
0, // outloc
0 // outlen
)
// limit our copy to 256 bytes
_toCopy := returndatasize()
if gt(_toCopy, _maxCopy) {
_toCopy := _maxCopy
}
// Store the length of the copied bytes
mstore(_returnData, _toCopy)
// copy the bytes from returndata[0:_toCopy]
returndatacopy(add(_returnData, 0x20), 0, _toCopy)
}
return (_success, _returnData);
}

/// @notice Use when you _really_ really _really_ don't trust the called
/// contract. This prevents the called contract from causing reversion of
/// the caller in as many ways as we can.
/// @dev The main difference between this and a solidity low-level call is
/// that we limit the number of bytes that the callee can cause to be
/// copied to caller memory. This prevents stupid things like malicious
/// contracts returning 10,000,000 bytes causing a local OOG when copying
/// to memory.
/// @param _target The address to call
/// @param _gas The amount of gas to forward to the remote contract
/// @param _maxCopy The maximum number of bytes of returndata to copy
/// to memory.
/// @param _calldata The data to send to the remote contract
/// @return success and returndata, as `.call()`. Returndata is capped to
/// `_maxCopy` bytes.
function excessivelySafeStaticCall(
address _target,
uint256 _gas,
uint16 _maxCopy,
bytes memory _calldata
) internal view returns (bool, bytes memory) {
// set up for assembly call
uint256 _toCopy;
bool _success;
bytes memory _returnData = new bytes(_maxCopy);
// dispatch message to recipient
// by assembly calling "handle" function
// we call via assembly to avoid memcopying a very large returndata
// returned by a malicious contract
assembly {
_success := staticcall(
_gas, // gas
_target, // recipient
add(_calldata, 0x20), // inloc
mload(_calldata), // inlen
0, // outloc
0 // outlen
)
// limit our copy to 256 bytes
_toCopy := returndatasize()
if gt(_toCopy, _maxCopy) {
_toCopy := _maxCopy
}
// Store the length of the copied bytes
mstore(_returnData, _toCopy)
// copy the bytes from returndata[0:_toCopy]
returndatacopy(add(_returnData, 0x20), 0, _toCopy)
}
return (_success, _returnData);
}

/**
* @notice Swaps function selectors in encoded contract calls
* @dev Allows reuse of encoded calldata for functions with identical
* argument types but different names. It simply swaps out the first 4 bytes
* for the new selector. This function modifies memory in place, and should
* only be used with caution.
* @param _newSelector The new 4-byte selector
* @param _buf The encoded contract args
*/
function swapSelector(bytes4 _newSelector, bytes memory _buf)
internal
pure
{
require(_buf.length >= 4);
uint256 _mask = LOW_28_MASK;
assembly {
// load the first word of
let _word := mload(add(_buf, 0x20))
// mask out the top 4 bytes
// /x
_word := and(_word, _mask)
_word := or(_newSelector, _word)
mstore(add(_buf, 0x20), _word)
}
}
}
Loading