-
Notifications
You must be signed in to change notification settings - Fork 8
refactor: consolidate fee splitting functions in Gateway contract #80
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
Open
onahprosper
wants to merge
16
commits into
main
Choose a base branch
from
processOnrampSettlement
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
05f5632
feat: implement settleIn function for onramp order processing and upd…
onahprosper 3d24b24
refactor: update settleIn and settleOut function documentation for cl…
onahprosper 798b088
refactor: rename OrderSettled event to SettleOut and update order exi…
f282199
refactor: update Gateway contract to rename settle function to settle…
662ea53
chore: update package-lock and yarn.lock files to remove extraneous d…
122d9f2
refactor: update comments in Gateway and IGateway contracts for clari…
onahprosper b631eb0
Refactor code structure for improved readability and maintainability
onahprosper 11b7047
fix: update Gateway contract to handle aggregator fees instead of pro…
onahprosper 72204c2
refactor: update Gateway contract to improve fee handling and add agg…
chibie 3aeaca3
Refactor fee handling in Gateway contract to calculate recipient amou…
onahprosper 501425a
Refactor code structure for improved readability and maintainability
onahprosper 4fe2f00
feat:
onahprosper cdf3ba0
chore: update hardhat configuration and dependencies
chibie af5d499
feat: add custom Hardhat tasks for account listing and contract flatt…
onahprosper eeaf0d0
chore: update Hardhat configuration and dependencies
onahprosper c1b88ef
refactor: enhance Gateway contract with SafeERC20 for secure token tr…
onahprosper File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -154,7 +154,7 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable { | |
| AGGREGATOR FUNCTIONS | ||
| ################################################################## */ | ||
| /** @dev See {settle-IGateway}. */ | ||
| function settle( | ||
| function settleOut( | ||
| bytes32 _splitOrderId, | ||
| bytes32 _orderId, | ||
| address _liquidityProvider, | ||
|
|
@@ -180,25 +180,23 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable { | |
|
|
||
| if (order[_orderId].senderFee != 0 && order[_orderId].protocolFee != 0) { | ||
| // fx transfer - sender keeps all fee | ||
| _handleFxTransferFeeSplitting(_orderId); | ||
| _handleFxTransferFeeSplitting(_orderId, token, order[_orderId].senderFeeRecipient, order[_orderId].senderFee); | ||
| } | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| if (order[_orderId].senderFee != 0 && order[_orderId].protocolFee == 0) { | ||
| // local transfer - split sender fee | ||
| _handleLocalTransferFeeSplitting(_orderId, _liquidityProvider, _settlePercent); | ||
| _handleLocalTransferFeeSplitting(_orderId, _liquidityProvider, order[_orderId].senderFeeRecipient, _settlePercent); | ||
| } | ||
|
|
||
| // transfer to liquidity provider | ||
| uint256 liquidityProviderAmount = (order[_orderId].amount * _settlePercent) / | ||
| currentOrderBPS; | ||
| uint256 liquidityProviderAmount = (order[_orderId].amount * _settlePercent) / currentOrderBPS; | ||
| order[_orderId].amount -= liquidityProviderAmount; | ||
|
|
||
| if (order[_orderId].protocolFee != 0) { | ||
| // FX transfer - use token-specific providerToAggregatorFx | ||
chibie marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| TokenFeeSettings memory settings = _tokenFeeSettings[order[_orderId].token]; | ||
| uint256 protocolFee = (liquidityProviderAmount * settings.providerToAggregatorFx) / | ||
| MAX_BPS; | ||
| uint256 protocolFee = (liquidityProviderAmount * settings.providerToAggregatorFx) / MAX_BPS; | ||
| liquidityProviderAmount -= protocolFee; | ||
|
|
||
| if (_rebatePercent != 0) { | ||
|
|
@@ -226,6 +224,88 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable { | |
| return true; | ||
| } | ||
|
|
||
| /** @dev See {processSettlement-IGateway}. */ | ||
| function settleIn( | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| bytes32 _orderId, | ||
| address _token, | ||
| uint256 _amount, | ||
| address _senderFeeRecipient, | ||
| uint96 _senderFee, | ||
| address _recipient, | ||
| uint96 _rate, | ||
| string calldata _messageHash | ||
| ) external whenNotPaused returns (bool) { | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| require(!order[_orderId].isFulfilled, 'OrderAlreadyFulfilled'); | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| require(_amount > MAX_BPS, 'AmountBelowMinimum'); | ||
|
||
| _handler(_token, _amount, _recipient, _senderFeeRecipient, _senderFee); | ||
|
|
||
| IERC20(_token).transferFrom(msg.sender, address(this), _amount); | ||
|
|
||
| uint256 processedAmount = _amount; | ||
| uint256 protocolFee; | ||
|
|
||
| // Determine if this is FX or local transfer based on rate | ||
chibie marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (_rate == 100) { | ||
| // Local transfer (rate = 1) - no protocol fee from amount | ||
chibie marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| require(_senderFee > 0, 'SenderFeeIsZero'); | ||
chibie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| protocolFee = 0; | ||
| // Split sender fee for local transfers (100% settlement) | ||
| processedAmount -= _senderFee; | ||
coderabbitai[bot] marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } else { | ||
chibie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| // FX transfer (rate != 1) - use token-specific providerToAggregatorFx | ||
chibie marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| TokenFeeSettings memory settings = _tokenFeeSettings[_token]; | ||
| require(settings.providerToAggregatorFx > 0, 'TokenFeeSettingsNotConfigured'); | ||
|
|
||
| protocolFee = (_amount * settings.providerToAggregatorFx) / MAX_BPS; | ||
|
|
||
| if (protocolFee > 0) { | ||
| processedAmount -= protocolFee; | ||
| IERC20(_token).transfer(treasuryAddress, protocolFee); | ||
| } | ||
|
|
||
| if (_senderFee != 0) { | ||
| // For FX transfers, handle sender fee similar to settleOut | ||
| processedAmount -= _senderFee; | ||
| } | ||
| } | ||
|
|
||
| IERC20(_token).transfer(_recipient, processedAmount); | ||
|
|
||
| // record the order state | ||
| order[_orderId].sender = _recipient; | ||
| order[_orderId].token = _token; | ||
| order[_orderId].senderFeeRecipient = _senderFeeRecipient; | ||
| order[_orderId].senderFee = _senderFee; | ||
| order[_orderId].protocolFee = protocolFee; | ||
| order[_orderId].isFulfilled = true; | ||
| order[_orderId].amount = processedAmount; | ||
| order[_orderId].currentBPS = 0; // Fully settled | ||
chibie marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // Handle fee splitting after order state is recorded | ||
|
||
| if (_senderFee != 0) { | ||
| if (protocolFee == 0) { | ||
| // Local transfer - split sender fee using the consolidated function | ||
| _handleLocalTransferFeeSplitting(_orderId, msg.sender, _senderFeeRecipient, MAX_BPS); | ||
| } else { | ||
| // FX transfer - sender keeps all fee using the consolidated function | ||
| _handleFxTransferFeeSplitting(_orderId, _token, _senderFeeRecipient, _senderFee); | ||
| } | ||
| } | ||
|
|
||
| // emit settlement event | ||
| emit SettleIn( | ||
| _orderId, | ||
| _amount, | ||
| _recipient, | ||
| _token, | ||
| _senderFeeRecipient, | ||
| _rate, | ||
| _messageHash | ||
| ); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| /** @dev See {refund-IGateway}. */ | ||
| function refund(uint256 _fee, bytes32 _orderId) external onlyAggregator returns (bool) { | ||
| // ensure the transaction has not been fulfilled | ||
|
|
@@ -275,73 +355,76 @@ contract Gateway is IGateway, GatewaySettingManager, PausableUpgradeable { | |
| * @dev Handles fee splitting for local transfers (rate = 1). | ||
| * @param _orderId The order ID to process. | ||
| * @param _liquidityProvider The address of the liquidity provider who fulfilled the order. | ||
| * @param _settlePercent The percentage of the order being settled (10000 for 100% in settleIn). | ||
| */ | ||
| function _handleLocalTransferFeeSplitting( | ||
| bytes32 _orderId, | ||
| address _liquidityProvider, | ||
| address _senderFeeRecipient, | ||
| uint64 _settlePercent | ||
| ) internal { | ||
| TokenFeeSettings memory settings = _tokenFeeSettings[order[_orderId].token]; | ||
| uint256 senderFee = order[_orderId].senderFee; | ||
| address token = order[_orderId].token; | ||
|
|
||
| // Calculate splits based on config | ||
| uint256 providerAmount = (senderFee * settings.senderToProvider) / MAX_BPS; | ||
| uint256 currentProviderAmount = (providerAmount * _settlePercent) / MAX_BPS; | ||
| uint256 aggregatorAmount = (currentProviderAmount * settings.providerToAggregator) / | ||
| MAX_BPS; | ||
| uint256 aggregatorAmount = (currentProviderAmount * settings.providerToAggregator) / MAX_BPS; | ||
| uint256 senderAmount = senderFee - providerAmount; | ||
|
|
||
| // Transfer sender portion | ||
| // Transfer sender portion (only when order is fully settled) | ||
chibie marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (senderAmount != 0 && order[_orderId].currentBPS == 0) { | ||
| IERC20(order[_orderId].token).transfer( | ||
| order[_orderId].senderFeeRecipient, | ||
| senderAmount | ||
| ); | ||
| IERC20(token).transfer(_senderFeeRecipient, senderAmount); | ||
| } | ||
|
|
||
| // Transfer aggregator portion to treasury | ||
| if (aggregatorAmount != 0) { | ||
| IERC20(order[_orderId].token).transfer(treasuryAddress, aggregatorAmount); | ||
| IERC20(token).transfer(treasuryAddress, aggregatorAmount); | ||
| } | ||
|
|
||
| // Transfer provider portion to the liquidity provider who fulfilled the order | ||
| currentProviderAmount = currentProviderAmount - aggregatorAmount; | ||
| if (currentProviderAmount != 0) { | ||
| IERC20(order[_orderId].token).transfer(_liquidityProvider, currentProviderAmount); | ||
| IERC20(token).transfer(_liquidityProvider, currentProviderAmount); | ||
| } | ||
|
|
||
| // Emit events | ||
| emit SenderFeeTransferred(order[_orderId].senderFeeRecipient, senderAmount); | ||
| emit SenderFeeTransferred(_orderId, _senderFeeRecipient, senderAmount); | ||
| emit LocalTransferFeeSplit(_orderId, senderAmount, currentProviderAmount, aggregatorAmount); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Handles fee splitting for FX transfers (rate != 1). | ||
| * @param _orderId The order ID to process. | ||
| * @dev Handles sender fee splitting for FX transfers. | ||
| * @param _orderId The order ID. | ||
| * @param _token The token address. | ||
| * @param _senderFeeRecipient The sender fee recipient address. | ||
| * @param _senderFee The total sender fee amount. | ||
| */ | ||
| function _handleFxTransferFeeSplitting(bytes32 _orderId) internal { | ||
| TokenFeeSettings memory settings = _tokenFeeSettings[order[_orderId].token]; | ||
| uint256 senderFee = order[_orderId].senderFee; | ||
| function _handleFxTransferFeeSplitting( | ||
| bytes32 _orderId, | ||
| address _token, | ||
| address _senderFeeRecipient, | ||
| uint256 _senderFee | ||
| ) internal { | ||
| TokenFeeSettings memory settings = _tokenFeeSettings[_token]; | ||
|
|
||
| // Calculate sender portion based on senderToAggregator setting | ||
| uint256 senderAmount = (senderFee * (MAX_BPS - settings.senderToAggregator)) / MAX_BPS; | ||
| uint256 aggregatorAmount = senderFee - senderAmount; | ||
| // Calculate sender portion based on senderToAggregator setting (similar to settleOut FX) | ||
| uint256 senderAmount = (_senderFee * (MAX_BPS - settings.senderToAggregator)) / MAX_BPS; | ||
| uint256 aggregatorAmount = _senderFee - senderAmount; | ||
|
|
||
| // Transfer sender portion | ||
| if (senderAmount > 0) { | ||
| IERC20(order[_orderId].token).transfer( | ||
| order[_orderId].senderFeeRecipient, | ||
| senderAmount | ||
| ); | ||
| IERC20(_token).transfer(_senderFeeRecipient, senderAmount); | ||
| } | ||
|
|
||
| // Transfer aggregator portion to treasury | ||
| if (aggregatorAmount > 0) { | ||
| IERC20(order[_orderId].token).transfer(treasuryAddress, aggregatorAmount); | ||
| IERC20(_token).transfer(treasuryAddress, aggregatorAmount); | ||
| } | ||
|
|
||
| // Emit events | ||
| emit SenderFeeTransferred(order[_orderId].senderFeeRecipient, senderAmount); | ||
| emit SenderFeeTransferred(_orderId, _senderFeeRecipient, senderAmount); | ||
| emit FxTransferFeeSplit(_orderId, senderAmount, aggregatorAmount); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.18; | ||
|
|
||
| import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; | ||
|
|
||
| interface IGateway { | ||
| function getAggregator() external view returns (address); | ||
| } | ||
| /** | ||
| * @title ProviderBatchCallAndSponsor | ||
| * | ||
| * When an EOA upgrades via EIP‑7702, it delegates to this implementation. | ||
| * Off‑chain, the account signs a message authorizing a batch of calls. The message is the hash of: | ||
| * keccak256(abi.encodePacked(nonce, calls)) | ||
| * The signature must be generated with the EOA’s private key so that, once upgraded, the recovered signer equals the account’s own address (i.e. address(this)). | ||
| * | ||
| * This contract provides just one way to execute a batch: | ||
| * 1. With a signature: Any sponsor can submit the batch if it carries a valid signature. | ||
| * | ||
| * Replay protection is achieved by using a nonce that is included in the signed message. | ||
| */ | ||
| contract ProviderBatchCallAndSponsor { | ||
| using ECDSA for bytes32; | ||
|
|
||
| address public constant gatewayAddress = 0x30F6A8457F8E42371E204a9c103f2Bd42341dD0F; | ||
| /// @notice A nonce used for replay protection. | ||
| uint256 public nonce; | ||
|
|
||
| /// @notice Represents a single call within a batch. | ||
| struct Call { | ||
| address to; | ||
| uint256 value; | ||
| bytes data; | ||
| } | ||
|
|
||
| modifier onlyAggregator() { | ||
| require(msg.sender == IGateway(gatewayAddress).getAggregator(), 'OnlyAggregator'); | ||
| _; | ||
| } | ||
|
|
||
| /// @notice Emitted for every individual call executed. | ||
| event CallExecuted(address indexed sender, address indexed to, uint256 value, bytes data); | ||
| /// @notice Emitted when a full batch is executed. | ||
| event BatchExecuted(uint256 indexed nonce, Call[] calls); | ||
|
|
||
| /** | ||
| * @notice Executes a batch of calls using an off–chain signature. | ||
| * @param calls An array of Call structs containing destination, ETH value, and calldata. | ||
| * @param signature The ECDSA signature over the current nonce and the call data. | ||
| * | ||
| * The signature must be produced off–chain by signing: | ||
| * The signing key should be the account’s key (which becomes the smart account’s own identity after upgrade). | ||
| */ | ||
| function execute(Call[] calldata calls, bytes calldata signature) external payable onlyAggregator { | ||
| // Compute the digest that the account was expected to sign. | ||
| bytes memory encodedCalls; | ||
| for (uint256 i = 0; i < calls.length; i++) { | ||
| encodedCalls = abi.encodePacked(encodedCalls, calls[i].to, calls[i].value, calls[i].data); | ||
| } | ||
| bytes32 digest = keccak256(abi.encodePacked(nonce, encodedCalls)); | ||
onahprosper marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| bytes32 ethSignedMessageHash = ECDSA.toEthSignedMessageHash(digest); | ||
|
|
||
| // Recover the signer from the provided signature. | ||
| address recovered = ECDSA.recover(ethSignedMessageHash, signature); | ||
| require(recovered == address(this), "Invalid signature"); | ||
|
|
||
| _executeBatch(calls); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Executes a batch of calls directly. | ||
| * @dev This contract doesnt authorized self execution. | ||
| * @param calls An array of Call structs containing destination, ETH value, and calldata. | ||
| */ | ||
| function execute(Call[] calldata calls) external payable { | ||
| revert("Not implemented"); // we don't expect this to be called directly | ||
| } | ||
|
|
||
| /** | ||
| * @dev Internal function that handles batch execution and nonce incrementation. | ||
| * @param calls An array of Call structs. | ||
| */ | ||
| function _executeBatch(Call[] calldata calls) internal { | ||
| uint256 currentNonce = nonce; | ||
| nonce++; // Increment nonce to protect against replay attacks | ||
|
|
||
| for (uint256 i = 0; i < calls.length; i++) { | ||
| _executeCall(calls[i]); | ||
| } | ||
|
|
||
| emit BatchExecuted(currentNonce, calls); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Internal function to execute a single call. | ||
| * @param callItem The Call struct containing destination, value, and calldata. | ||
| */ | ||
| function _executeCall(Call calldata callItem) internal { | ||
| (bool success,) = callItem.to.call{value: callItem.value}(callItem.data); | ||
| require(success, "Call reverted"); | ||
| emit CallExecuted(msg.sender, callItem.to, callItem.value, callItem.data); | ||
| } | ||
|
|
||
| // Allow the contract to receive ETH (e.g. from DEX swaps or other transfers). | ||
| fallback() external payable {} | ||
| receive() external payable {} | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.