-
Notifications
You must be signed in to change notification settings - Fork 91
feat: Develop SingleRequestProxy Smart Contracts
#1453
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
Merged
aimensahnoun
merged 43 commits into
master
from
1394-develop-smart-contracts-nativesinglerequestproxy-erc20singlerequestproxy-singlerequestproxyfactory
Oct 18, 2024
Merged
Changes from 36 commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
e2567f4
feat: develop for native currencies
aimensahnoun c624474
feat: develop for ERC20 currencies
aimensahnoun 917d472
feat: create `EthereumSingleRequestProxy` factory contract
aimensahnoun d5e4728
feat: create `ERC20SingleRequestProxy` factory contract
aimensahnoun 0543092
refactor: merge the single request proxy factories into one factory
aimensahnoun 2bd3495
fix: infinite loop between `EthSingleRequestProxy` and `EthFeeProxy`
aimensahnoun 19b5e40
refactor: refactor `nonReentrant` to match openzeppelin implementation
aimensahnoun c1d3a77
fix: update `ERC20SingleRequestProxy` to use `balance`
aimensahnoun 1376582
fix: update `SingleRequestProxyFactory` to call Ownable constructor
aimensahnoun c2f55ee
chore: update contracts to use `0.8.9` solidity version
aimensahnoun bc922e8
test: add test for `EthereumSingleRequestProxy`
aimensahnoun 8aef234
fix: `ERC20SingleRequestProxy` to factor in fee amount
aimensahnoun 0769aa7
test: `ERC20SingleRequestProxy` functionality
aimensahnoun 3bd9e38
test: `SingleRequestProxyFactory` functionality
aimensahnoun ed429ae
chore: update `EthereumSingleRequestProxy` tests
aimensahnoun d9a4484
Merge branch 'master' into 1394-develop-smart-contracts-nativesingler…
aimensahnoun a341ce1
feat: write deployment script for `SingleRequestProxyFactory`
aimensahnoun 10974b5
Merge branch '1394-develop-smart-contracts-nativesinglerequestproxy-e…
aimensahnoun 2cc82a0
refactor: rewrite deployment to use `CREATE2` schema
aimensahnoun bee1f58
fix: add SingleRequestProxyFactory `create2ContractDeploymentList`
aimensahnoun aab6711
chore: include SingleRequestProxyFactory in transfer ownership cases …
aimensahnoun 0c68eba
refactor: rename updateEthereumFeeProxy to setEthereumFeeProxy for cl…
aimensahnoun 796f2b9
Merge branch 'master' into 1394-develop-smart-contracts-nativesingler…
aimensahnoun 0482f6d
fix: type and add new events
aimensahnoun b8183f3
Merge branch '1394-develop-smart-contracts-nativesinglerequestproxy-e…
aimensahnoun 85fdc13
Merge branch 'master' of github.com:RequestNetwork/requestNetwork int…
aimensahnoun 9033066
docs: add more documentation to nonReentrant modifier
aimensahnoun cd91ec2
feat: add `rescueFunds` to `EthereumSingleRequestProxy`
aimensahnoun f944199
docs: add more documentation to `SingleRequestProxyFactory`
aimensahnoun c77c1eb
fix: use `safeApprove` instead of `approve`
aimensahnoun 41e43b5
feat: add rescue funds method
aimensahnoun 01c28d2
test: add more ownership tests
aimensahnoun cfba1b9
test: add partial payment and non-standard ERC20 tests
aimensahnoun 7241e0c
test: add rescue-funds tests
aimensahnoun 4abe9e9
test: refactor tests
aimensahnoun 90b413f
test: add await back
aimensahnoun c493758
feat: add triggerERC20Payment
aimensahnoun 06e015a
chore: add require for zero address
aimensahnoun c778b17
feat: add rescue methods for ERC20 and native tokens
aimensahnoun b45607a
test: add tests for both rescue methods
aimensahnoun c58dfc0
fix: rename rescueFunds to rescueERC20Funds
aimensahnoun 9ee661a
fix: typo in "receive"
aimensahnoun 0141d22
Merge branch 'master' of github.com:RequestNetwork/requestNetwork int…
aimensahnoun 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
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
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
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
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
70 changes: 70 additions & 0 deletions
70
packages/smart-contracts/src/contracts/ERC20SingleRequestProxy.sol
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
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,70 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.9; | ||
|
|
||
| import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; | ||
| import './interfaces/ERC20FeeProxy.sol'; | ||
| import './lib/SafeERC20.sol'; | ||
|
|
||
| /** | ||
| * @title ERC20SingleRequestProxy | ||
| * @notice This contract is used to send a single request to a payee with a fee sent to a third address for ERC20 | ||
| */ | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| contract ERC20SingleRequestProxy { | ||
| address public payee; | ||
| address public tokenAddress; | ||
| address public feeAddress; | ||
| uint256 public feeAmount; | ||
leoslr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| bytes public paymentReference; | ||
| IERC20FeeProxy public erc20FeeProxy; | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| constructor( | ||
| address _payee, | ||
| address _tokenAddress, | ||
| address _feeAddress, | ||
| uint256 _feeAmount, | ||
| bytes memory _paymentReference, | ||
| address _erc20FeeProxy | ||
| ) { | ||
| payee = _payee; | ||
| tokenAddress = _tokenAddress; | ||
| feeAddress = _feeAddress; | ||
| feeAmount = _feeAmount; | ||
| paymentReference = _paymentReference; | ||
| erc20FeeProxy = IERC20FeeProxy(_erc20FeeProxy); | ||
| } | ||
MantisClone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| receive() external payable { | ||
| require(msg.value == 0, 'This function is only for triggering the transfer'); | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| IERC20 token = IERC20(tokenAddress); | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| uint256 balance = token.balanceOf(address(this)); | ||
| uint256 paymentAmount = balance; | ||
| if (feeAmount > 0 && feeAddress != address(0)) { | ||
| require(balance > feeAmount, 'Insufficient balance to cover fee'); | ||
| paymentAmount = balance - feeAmount; | ||
MantisClone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| require(SafeERC20.safeApprove(token, address(erc20FeeProxy), balance), 'Approval failed'); | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| erc20FeeProxy.transferFromWithReferenceAndFee( | ||
| tokenAddress, | ||
| payee, | ||
| paymentAmount, | ||
| paymentReference, | ||
| feeAmount, | ||
| feeAddress | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Rescues any trapped funds by sending them to the payee | ||
| * @dev Can be called by anyone, but funds are always sent to the payee | ||
| */ | ||
| function rescueFunds() external { | ||
| IERC20 token = IERC20(tokenAddress); | ||
| uint256 balance = token.balanceOf(address(this)); | ||
| require(balance > 0, 'No funds to rescue'); | ||
| bool success = SafeERC20.safeTransfer(token, payee, balance); | ||
| require(success, 'ERC20 rescue failed'); | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
98 changes: 98 additions & 0 deletions
98
packages/smart-contracts/src/contracts/EthereumSingleRequestProxy.sol
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,98 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.9; | ||
|
|
||
| import './interfaces/EthereumFeeProxy.sol'; | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * @title EthereumSingleRequestProxy | ||
| * @notice This contract is used to send a single request to a payee with a fee sent to a third address | ||
| */ | ||
| contract EthereumSingleRequestProxy { | ||
| address public payee; | ||
| bytes public paymentReference; | ||
| address public feeAddress; | ||
| uint256 public feeAmount; | ||
leoslr marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| IEthereumFeeProxy public ethereumFeeProxy; | ||
MantisClone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| address private originalSender; | ||
|
|
||
| /** | ||
| * @dev Custom reentrancy guard. | ||
| * Similar to OpenZeppelin's ReentrancyGuard, but allows reentrancy from ethereumFeeProxy. | ||
| * This enables controlled callbacks from ethereumFeeProxy while protecting against other reentrancy attacks. | ||
| */ | ||
| uint256 private constant _NOT_ENTERED = 1; | ||
| uint256 private constant _ENTERED = 2; | ||
| uint256 private _status; | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| constructor( | ||
| address _payee, | ||
| bytes memory _paymentReference, | ||
| address _ethereumFeeProxy, | ||
| address _feeAddress, | ||
| uint256 _feeAmount | ||
| ) { | ||
| payee = _payee; | ||
| paymentReference = _paymentReference; | ||
| feeAddress = _feeAddress; | ||
| feeAmount = _feeAmount; | ||
| ethereumFeeProxy = IEthereumFeeProxy(_ethereumFeeProxy); | ||
| _status = _NOT_ENTERED; | ||
| } | ||
|
|
||
| /** | ||
| * @dev Modified nonReentrant guard. | ||
| * Prevents reentrancy except for calls from ethereumFeeProxy. | ||
| */ | ||
| modifier nonReentrant() { | ||
| if (msg.sender != address(ethereumFeeProxy)) { | ||
| require(_status != _ENTERED, 'ReentrancyGuard: reentrant call'); | ||
| _status = _ENTERED; | ||
| } | ||
| _; | ||
| if (msg.sender != address(ethereumFeeProxy)) { | ||
| _status = _NOT_ENTERED; | ||
| } | ||
| } | ||
|
|
||
| receive() external payable nonReentrant { | ||
| if (msg.sender == address(ethereumFeeProxy)) { | ||
| // Funds are being sent back from EthereumFeeProxy | ||
| require(originalSender != address(0), 'No original sender stored'); | ||
|
|
||
| // Forward the funds to the original sender | ||
| (bool forwardSuccess, ) = payable(originalSender).call{value: msg.value}(''); | ||
| require(forwardSuccess, 'Forwarding to original sender failed'); | ||
|
|
||
| // Clear the stored original sender | ||
| originalSender = address(0); | ||
| } else { | ||
| require(originalSender == address(0), 'Another request is in progress'); | ||
|
|
||
| originalSender = msg.sender; | ||
|
|
||
| bytes memory data = abi.encodeWithSignature( | ||
| 'transferWithReferenceAndFee(address,bytes,uint256,address)', | ||
| payable(payee), | ||
| paymentReference, | ||
| feeAmount, | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| payable(feeAddress) | ||
| ); | ||
|
|
||
| (bool callSuccess, ) = address(ethereumFeeProxy).call{value: msg.value}(data); | ||
| require(callSuccess, 'Call to EthereumFeeProxy failed'); | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
MantisClone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * @notice Rescues any trapped funds by sending them to the payee | ||
| * @dev Can be called by anyone, but funds are always sent to the payee | ||
| */ | ||
| function rescueFunds() external nonReentrant { | ||
| uint256 balance = address(this).balance; | ||
| require(balance > 0, 'No funds to rescue'); | ||
|
|
||
| (bool success, ) = payable(payee).call{value: balance}(''); | ||
| require(success, 'Rescue failed'); | ||
| } | ||
| } | ||
117 changes: 117 additions & 0 deletions
117
packages/smart-contracts/src/contracts/SingleRequestProxyFactory.sol
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,117 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity 0.8.9; | ||
|
|
||
| import '@openzeppelin/contracts/access/Ownable.sol'; | ||
| import './ERC20SingleRequestProxy.sol'; | ||
| import './EthereumSingleRequestProxy.sol'; | ||
|
|
||
| /** | ||
| * @title SingleRequestProxyFactory | ||
| * @notice This contract is used to create SingleRequestProxy instances | ||
| */ | ||
| contract SingleRequestProxyFactory is Ownable { | ||
| /// @notice The address of the EthereumFeeProxy contract | ||
| /// @dev This proxy is used for handling Ethereum-based fee transactions | ||
| address public ethereumFeeProxy; | ||
|
|
||
| /// @notice The address of the ERC20FeeProxy contract | ||
| /// @dev This proxy is used for handling ERC20-based fee transactions | ||
| address public erc20FeeProxy; | ||
|
|
||
| event EthereumSingleRequestProxyCreated( | ||
| address indexed proxyAddress, | ||
| address indexed payee, | ||
| bytes indexed paymentReference | ||
| ); | ||
MantisClone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| event ERC20SingleRequestProxyCreated( | ||
| address indexed proxyAddress, | ||
| address indexed payee, | ||
| address tokenAddress, | ||
| bytes indexed paymentReference | ||
| ); | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| event ERC20FeeProxyUpdated(address indexed newERC20FeeProxy); | ||
| event EthereumFeeProxyUpdated(address indexed newEthereumFeeProxy); | ||
|
|
||
| constructor(address _ethereumFeeProxy, address _erc20FeeProxy) { | ||
| require(_ethereumFeeProxy != address(0), 'EthereumFeeProxy address cannot be zero'); | ||
| require(_erc20FeeProxy != address(0), 'ERC20FeeProxy address cannot be zero'); | ||
| ethereumFeeProxy = _ethereumFeeProxy; | ||
| erc20FeeProxy = _erc20FeeProxy; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Creates a new EthereumSingleRequestProxy instance | ||
| * @param _payee The address of the payee | ||
| * @param _paymentReference The payment reference | ||
| * @param _feeAddress The address of the fee recipient | ||
| * @param _feeAmount The fee amount | ||
| * @return The address of the newly created proxy | ||
| */ | ||
| function createEthereumSingleRequestProxy( | ||
| address _payee, | ||
| bytes memory _paymentReference, | ||
| address _feeAddress, | ||
| uint256 _feeAmount | ||
| ) external returns (address) { | ||
| EthereumSingleRequestProxy proxy = new EthereumSingleRequestProxy( | ||
| _payee, | ||
| _paymentReference, | ||
| ethereumFeeProxy, | ||
| _feeAddress, | ||
| _feeAmount | ||
| ); | ||
| emit EthereumSingleRequestProxyCreated(address(proxy), _payee, _paymentReference); | ||
| return address(proxy); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Creates a new ERC20SingleRequestProxy instance | ||
| * @param _payee The address of the payee | ||
| * @param _tokenAddress The address of the token | ||
| * @param _paymentReference The payment reference | ||
| * @param _feeAddress The address of the fee recipient | ||
| * @param _feeAmount The fee amount | ||
| * @return The address of the newly created proxy | ||
| */ | ||
| function createERC20SingleRequestProxy( | ||
| address _payee, | ||
| address _tokenAddress, | ||
| bytes memory _paymentReference, | ||
| address _feeAddress, | ||
| uint256 _feeAmount | ||
| ) external returns (address) { | ||
| ERC20SingleRequestProxy proxy = new ERC20SingleRequestProxy( | ||
| _payee, | ||
| _tokenAddress, | ||
| _feeAddress, | ||
| _feeAmount, | ||
| _paymentReference, | ||
| erc20FeeProxy | ||
| ); | ||
|
|
||
| emit ERC20SingleRequestProxyCreated(address(proxy), _payee, _tokenAddress, _paymentReference); | ||
| return address(proxy); | ||
| } | ||
MantisClone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * @notice Updates the ERC20FeeProxy address | ||
| * @param _newERC20FeeProxy The new ERC20FeeProxy address | ||
| */ | ||
| function setERC20FeeProxy(address _newERC20FeeProxy) external onlyOwner { | ||
| require(_newERC20FeeProxy != address(0), 'ERC20FeeProxy address cannot be zero'); | ||
| erc20FeeProxy = _newERC20FeeProxy; | ||
| emit ERC20FeeProxyUpdated(_newERC20FeeProxy); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Updates the EthereumFeeProxy address | ||
| * @param _newEthereumFeeProxy The new EthereumFeeProxy address | ||
| */ | ||
| function setEthereumFeeProxy(address _newEthereumFeeProxy) external onlyOwner { | ||
| require(_newEthereumFeeProxy != address(0), 'EthereumFeeProxy address cannot be zero'); | ||
| ethereumFeeProxy = _newEthereumFeeProxy; | ||
| emit EthereumFeeProxyUpdated(_newEthereumFeeProxy); | ||
| } | ||
| } | ||
20 changes: 20 additions & 0 deletions
20
packages/smart-contracts/src/contracts/test/EthereumFeeProxyMock.sol
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,20 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| contract MockEthereumFeeProxy { | ||
| function transferWithReferenceAndFee( | ||
| address payable _to, | ||
| bytes calldata _paymentReference, | ||
| uint256 _feeAmount, | ||
| address payable _feeAddress | ||
| ) external payable { | ||
| // Do nothing, just accept the funds | ||
| } | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| function sendFundsBack(address payable _to, uint256 _amount) external { | ||
| (bool success, ) = _to.call{value: _amount}(''); | ||
| require(success, 'Failed to send funds back'); | ||
| } | ||
MantisClone marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| receive() external payable {} | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
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,8 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity ^0.8.0; | ||
|
|
||
| contract ForceSend { | ||
| function forceSend(address payable recipient) public payable { | ||
| selfdestruct(recipient); | ||
| } | ||
| } |
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,15 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| // Compatible with OpenZeppelin Contracts ^5.0.0 | ||
| pragma solidity ^0.8.9; | ||
|
|
||
| import '@openzeppelin/contracts/token/ERC20/ERC20.sol'; | ||
| import '@openzeppelin/contracts/access/Ownable.sol'; | ||
| import '@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol'; | ||
|
|
||
| contract TestToken is ERC20, Ownable, ERC20Permit { | ||
| constructor(address initialOwner) ERC20('TestToken', 'TTK') Ownable() ERC20Permit('TestToken') {} | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| function mint(address to, uint256 amount) public onlyOwner { | ||
| _mint(to, amount); | ||
| } | ||
aimensahnoun marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
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.