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

Escrow #48

Merged
merged 9 commits into from
Oct 14, 2024
144 changes: 102 additions & 42 deletions contracts/Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.18;

import '@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol';
import '@openzeppelin/contracts/utils/cryptography/ECDSA.sol';

import {GatewaySettingManager} from './GatewaySettingManager.sol';
import {IGateway, IERC20} from './interfaces/IGateway.sol';
Expand All @@ -11,15 +12,18 @@ import {IGateway, IERC20} from './interfaces/IGateway.sol';
* @notice This contract serves as a gateway for creating orders and managing settlements.
*/
contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable {
using ECDSA for bytes32;
struct fee {
uint256 protocolFee;
uint256 liquidityProviderAmount;
}

mapping(bytes32 => Order) private order;
mapping(bytes32 => OrderOut) private orderSettledOut;
OnahProsperity marked this conversation as resolved.
Show resolved Hide resolved
mapping(bytes32 => OrderIn) private orderSettledIn;
OnahProsperity marked this conversation as resolved.
Show resolved Hide resolved
mapping(address => uint256) private _nonce;
uint256[50] private __gap;
mapping(address => mapping(address => uint256)) private balance;
mapping(bytes32 => bool) private processedOrders;
uint256[50] private __gap;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
Expand Down Expand Up @@ -57,7 +61,7 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable {
* @param _amount The amount to be checked.
*/
modifier isValidAmount(uint256 _amount) {
require(_amount != 0, 'AmountIsZero');
require(_amount > 0, 'AmountIsZero');
_;
}

Expand Down Expand Up @@ -95,7 +99,7 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable {
_handler(_token, _amount, _refundAddress, _senderFeeRecipient, _senderFee);

// validate messageHash
require(bytes(messageHash).length != 0, 'InvalidMessageHash');
require(bytes(messageHash).length > 0, 'InvalidMessageHash');

// transfer token from msg.sender to contract
IERC20(_token).transferFrom(msg.sender, address(this), _amount + _senderFee);
Expand All @@ -108,7 +112,7 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable {

// update transaction
uint256 _protocolFee = (_amount * protocolFeePercent) / MAX_BPS;
order[orderId] = Order({
orderSettledOut[orderId] = OrderOut({
sender: msg.sender,
token: _token,
senderFeeRecipient: _senderFeeRecipient,
Expand All @@ -123,9 +127,9 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable {

// emit order created event
emit OrderCreated(
order[orderId].sender,
orderSettledOut[orderId].sender,
_token,
order[orderId].amount,
orderSettledOut[orderId].amount,
_protocolFee,
orderId,
_rate,
Expand All @@ -150,92 +154,92 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable {
) internal isTokenApproved(_token) isValidAmount(_amount) view {
require(_refundAddress != address(0), 'ThrowZeroAddress');

if (_senderFee != 0) {
if (_senderFee > 0) {
require(_senderFeeRecipient != address(0), 'InvalidSenderFeeRecipient');
}
}

/* ##################################################################
AGGREGATOR FUNCTIONS
################################################################## */
/** @dev See {settle-IGateway}. */
function settle(
/** @dev See {settleOrderOut-IGateway}. */
function settleOrderOut(
bytes32 _splitOrderId,
bytes32 _orderId,
address _liquidityProvider,
address _provider,
uint64 _settlePercent
) external onlyAggregator returns (bool) {
// ensure the transaction has not been fulfilled
require(!order[_orderId].isFulfilled, 'OrderFulfilled');
require(!order[_orderId].isRefunded, 'OrderRefunded');
require(!orderSettledOut[_orderId].isFulfilled, 'OrderFulfilled');
require(!orderSettledOut[_orderId].isRefunded, 'OrderRefunded');

// load the token into memory
address token = order[_orderId].token;
address token = orderSettledOut[_orderId].token;

// subtract sum of amount based on the input _settlePercent
order[_orderId].currentBPS -= _settlePercent;
orderSettledOut[_orderId].currentBPS -= _settlePercent;

if (order[_orderId].currentBPS == 0) {
if (orderSettledOut[_orderId].currentBPS == 0) {
// update the transaction to be fulfilled
order[_orderId].isFulfilled = true;
orderSettledOut[_orderId].isFulfilled = true;

if (order[_orderId].senderFee != 0) {
if (orderSettledOut[_orderId].senderFee > 0) {
// transfer sender fee
IERC20(order[_orderId].token).transfer(
order[_orderId].senderFeeRecipient,
order[_orderId].senderFee
IERC20(orderSettledOut[_orderId].token).transfer(
orderSettledOut[_orderId].senderFeeRecipient,
orderSettledOut[_orderId].senderFee
);

// emit event
emit SenderFeeTransferred(
order[_orderId].senderFeeRecipient,
order[_orderId].senderFee
orderSettledOut[_orderId].senderFeeRecipient,
orderSettledOut[_orderId].senderFee
);
}

}

// transfer to liquidity provider
uint256 liquidityProviderAmount = (order[_orderId].amount * _settlePercent) / MAX_BPS;
order[_orderId].amount -= liquidityProviderAmount;
uint256 liquidityProviderAmount = (orderSettledOut[_orderId].amount * _settlePercent) / MAX_BPS;
orderSettledOut[_orderId].amount -= liquidityProviderAmount;

uint256 protocolFee = (liquidityProviderAmount * protocolFeePercent) / MAX_BPS;
liquidityProviderAmount -= protocolFee;

// transfer protocol fee
IERC20(token).transfer(treasuryAddress, protocolFee);

IERC20(token).transfer(_liquidityProvider, liquidityProviderAmount);
IERC20(token).transfer(_provider, liquidityProviderAmount);

// emit settled event
emit OrderSettled(_splitOrderId, _orderId, _liquidityProvider, _settlePercent);
emit OrderSettledOut(_splitOrderId, _orderId, _provider, _settlePercent);

return true;
}

/** @dev See {refund-IGateway}. */
function refund(uint256 _fee, bytes32 _orderId) external onlyAggregator returns (bool) {
/** @dev See {refundOrder-IGateway}. */
function refundOrder(uint256 _fee, bytes32 _orderId) external onlyAggregator returns (bool) {
// ensure the transaction has not been fulfilled
require(!order[_orderId].isFulfilled, 'OrderFulfilled');
require(!order[_orderId].isRefunded, 'OrderRefunded');
require(order[_orderId].protocolFee >= _fee, 'FeeExceedsProtocolFee');
require(!orderSettledOut[_orderId].isFulfilled, 'OrderFulfilled');
require(!orderSettledOut[_orderId].isRefunded, 'OrderRefunded');
require(orderSettledOut[_orderId].protocolFee >= _fee, 'FeeExceedsProtocolFee');

if (_fee > 0) {
// transfer refund fee to the treasury
IERC20(order[_orderId].token).transfer(treasuryAddress, _fee);
IERC20(orderSettledOut[_orderId].token).transfer(treasuryAddress, _fee);
}

// reset state values
order[_orderId].isRefunded = true;
order[_orderId].currentBPS = 0;
orderSettledOut[_orderId].isRefunded = true;
orderSettledOut[_orderId].currentBPS = 0;

// deduct fee from order amount
uint256 refundAmount = order[_orderId].amount - _fee;
uint256 refundAmount = orderSettledOut[_orderId].amount - _fee;

// transfer refund amount and sender fee to the refund address
IERC20(order[_orderId].token).transfer(
order[_orderId].refundAddress,
refundAmount + order[_orderId].senderFee
IERC20(orderSettledOut[_orderId].token).transfer(
orderSettledOut[_orderId].refundAddress,
refundAmount + orderSettledOut[_orderId].senderFee
);

// emit refunded event
Expand All @@ -253,12 +257,63 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable {
return true;
}

/** @dev See {settleOrder-IGateway}. */
OnahProsperity marked this conversation as resolved.
Show resolved Hide resolved
function settleOrderIn(
bytes32 _orderId,
bytes memory _signature,
address _provider,
address _sender,
address _token,
uint256 _amount
) external onlyAggregator isValidAmount(_amount) {
require(!processedOrders[_orderId], "Order already processed");
require(_provider != address(0), "Invalid provider address");
require(_sender != address(0), "Invalid sender address");

// Verify signature
bytes32 messageHash = keccak256(abi.encodePacked(_orderId, _provider, _sender, _token, _amount));
bytes32 ethSignedMessageHash = messageHash.toEthSignedMessageHash();
address recoveredAddress = ethSignedMessageHash.recover(_signature);
require(recoveredAddress == _provider, "Invalid signature");
uint256 _protocolFee = (_amount * protocolFeePercent) / MAX_BPS;
// Note: There is no need for checks for token supported as the balance will be 0 if the token is not supported
require(balance[_token][_provider] >= _amount + _protocolFee, "Insufficient balance");

// Mark order as processed
processedOrders[_orderId] = true;

// Update balances
balance[_token][_provider] -= (_amount + _protocolFee);

orderSettledIn[_orderId] = OrderIn({
amount: _amount,
provider: _provider,
sender: _sender,
token: _token,
orderId: _orderId
});


// transfer to sender
IERC20(_token).transfer(_sender, _amount);
if (_protocolFee > 0) {
IERC20(_token).transfer(treasuryAddress, _protocolFee);
}

emit OrderSettledIn(_provider, _sender, _amount, _token, _orderId);
}

/* ##################################################################
VIEW CALLS
################################################################## */
/** @dev See {getOrderInfo-IGateway}. */
function getOrderInfo(bytes32 _orderId) external view returns (Order memory) {
return order[_orderId];
/** @dev See {getOrderInfoOut-IGateway}. */
function getOrderInfoOut(bytes32 _orderId) external view returns (OrderOut memory) {
return orderSettledOut[_orderId];
}

/** @dev See {getOrderInfoIn-IGateway}. */
function getOrderInfoIn(bytes32 _orderId) external view returns (OrderIn memory) {
return orderSettledIn[_orderId];
}

/** @dev See {isTokenSupported-IGateway}. */
Expand All @@ -276,4 +331,9 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable {
function getBalance(address _token, address _provider) external view returns (uint256) {
return balance[_token][_provider];
}

/** See {isOrderProcessed-IGateway} */
function isOrderProcessed(bytes32 _orderId) external view returns (bool) {
return processedOrders[_orderId];
}
}
Loading
Loading