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

feat: Develop SingleRequestProxy Smart Contracts #1453

Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
e2567f4
feat: develop for native currencies
aimensahnoun Sep 12, 2024
c624474
feat: develop for ERC20 currencies
aimensahnoun Sep 12, 2024
917d472
feat: create `EthereumSingleRequestProxy` factory contract
aimensahnoun Sep 12, 2024
d5e4728
feat: create `ERC20SingleRequestProxy` factory contract
aimensahnoun Sep 12, 2024
0543092
refactor: merge the single request proxy factories into one factory
aimensahnoun Sep 13, 2024
2bd3495
fix: infinite loop between `EthSingleRequestProxy` and `EthFeeProxy`
aimensahnoun Sep 13, 2024
19b5e40
refactor: refactor `nonReentrant` to match openzeppelin implementation
aimensahnoun Sep 16, 2024
c1d3a77
fix: update `ERC20SingleRequestProxy` to use `balance`
aimensahnoun Sep 16, 2024
1376582
fix: update `SingleRequestProxyFactory` to call Ownable constructor
aimensahnoun Sep 16, 2024
c2f55ee
chore: update contracts to use `0.8.9` solidity version
aimensahnoun Sep 17, 2024
bc922e8
test: add test for `EthereumSingleRequestProxy`
aimensahnoun Sep 18, 2024
8aef234
fix: `ERC20SingleRequestProxy` to factor in fee amount
aimensahnoun Sep 18, 2024
0769aa7
test: `ERC20SingleRequestProxy` functionality
aimensahnoun Sep 18, 2024
3bd9e38
test: `SingleRequestProxyFactory` functionality
aimensahnoun Sep 18, 2024
ed429ae
chore: update `EthereumSingleRequestProxy` tests
aimensahnoun Sep 18, 2024
d9a4484
Merge branch 'master' into 1394-develop-smart-contracts-nativesingler…
aimensahnoun Sep 18, 2024
a341ce1
feat: write deployment script for `SingleRequestProxyFactory`
aimensahnoun Sep 23, 2024
10974b5
Merge branch '1394-develop-smart-contracts-nativesinglerequestproxy-e…
aimensahnoun Sep 23, 2024
2cc82a0
refactor: rewrite deployment to use `CREATE2` schema
aimensahnoun Sep 23, 2024
bee1f58
fix: add SingleRequestProxyFactory `create2ContractDeploymentList`
aimensahnoun Sep 23, 2024
aab6711
chore: include SingleRequestProxyFactory in transfer ownership cases …
aimensahnoun Sep 23, 2024
0c68eba
refactor: rename updateEthereumFeeProxy to setEthereumFeeProxy for cl…
aimensahnoun Sep 23, 2024
796f2b9
Merge branch 'master' into 1394-develop-smart-contracts-nativesingler…
aimensahnoun Sep 23, 2024
0482f6d
fix: type and add new events
aimensahnoun Sep 26, 2024
b8183f3
Merge branch '1394-develop-smart-contracts-nativesinglerequestproxy-e…
aimensahnoun Sep 26, 2024
85fdc13
Merge branch 'master' of github.com:RequestNetwork/requestNetwork int…
aimensahnoun Oct 16, 2024
9033066
docs: add more documentation to nonReentrant modifier
aimensahnoun Oct 16, 2024
cd91ec2
feat: add `rescueFunds` to `EthereumSingleRequestProxy`
aimensahnoun Oct 16, 2024
f944199
docs: add more documentation to `SingleRequestProxyFactory`
aimensahnoun Oct 16, 2024
c77c1eb
fix: use `safeApprove` instead of `approve`
aimensahnoun Oct 16, 2024
41e43b5
feat: add rescue funds method
aimensahnoun Oct 17, 2024
01c28d2
test: add more ownership tests
aimensahnoun Oct 17, 2024
cfba1b9
test: add partial payment and non-standard ERC20 tests
aimensahnoun Oct 17, 2024
7241e0c
test: add rescue-funds tests
aimensahnoun Oct 17, 2024
4abe9e9
test: refactor tests
aimensahnoun Oct 17, 2024
90b413f
test: add await back
aimensahnoun Oct 17, 2024
c493758
feat: add triggerERC20Payment
aimensahnoun Oct 17, 2024
06e015a
chore: add require for zero address
aimensahnoun Oct 17, 2024
c778b17
feat: add rescue methods for ERC20 and native tokens
aimensahnoun Oct 17, 2024
b45607a
test: add tests for both rescue methods
aimensahnoun Oct 17, 2024
c58dfc0
fix: rename rescueFunds to rescueERC20Funds
aimensahnoun Oct 17, 2024
9ee661a
fix: typo in "receive"
aimensahnoun Oct 18, 2024
0141d22
Merge branch 'master' of github.com:RequestNetwork/requestNetwork int…
aimensahnoun Oct 18, 2024
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 @@ -64,7 +64,8 @@ export const computeCreate2DeploymentAddressesFromList = async (
case 'BatchConversionPayments':
case 'ERC20SwapToPay':
case 'ERC20SwapToConversion':
case 'ERC20TransferableReceivable': {
case 'ERC20TransferableReceivable':
case 'SingleRequestProxyFactory': {
try {
const constructorArgs = getConstructorArgs(contract, chain);
address = await computeCreate2DeploymentAddress({ contract, constructorArgs }, hre);
Expand Down
11 changes: 11 additions & 0 deletions packages/smart-contracts/scripts-create2/constructor-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ export const getConstructorArgs = (
const erc20FeeProxyAddress = erc20FeeProxy.getAddress(network);
return ['Request Network Transferable Receivable', 'tREC', erc20FeeProxyAddress];
}
case 'SingleRequestProxyFactory': {
if (!network) {
throw new Error('SingleRequestProxyFactory requires network parameter');
}
const erc20FeeProxy = artifacts.erc20FeeProxyArtifact;
const erc20FeeProxyAddress = erc20FeeProxy.getAddress(network);
const ethereumFeeProxy = artifacts.ethereumFeeProxyArtifact;
const ethereumFeeProxyAddress = ethereumFeeProxy.getAddress(network);

return [ethereumFeeProxyAddress, erc20FeeProxyAddress];
}
MantisClone marked this conversation as resolved.
Show resolved Hide resolved
default:
return [];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export const transferOwnership = async (
case 'Erc20ConversionProxy':
case 'BatchConversionPayments':
case 'ERC20SwapToPay':
case 'ERC20SwapToConversion': {
case 'ERC20SwapToConversion':
case 'SingleRequestProxyFactory': {
await updateOwner({ contract, hre, signWithEoa });
break;
}
Expand Down
1 change: 1 addition & 0 deletions packages/smart-contracts/scripts-create2/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const create2ContractDeploymentList = [
'BatchConversionPayments',
'ERC20EscrowToPay',
'ERC20TransferableReceivable',
'SingleRequestProxyFactory',
MantisClone marked this conversation as resolved.
Show resolved Hide resolved
];

/**
Expand Down
57 changes: 57 additions & 0 deletions packages/smart-contracts/src/contracts/ERC20SingleRequestProxy.sol
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.9;

import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import './interfaces/ERC20FeeProxy.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
}

token.approve(address(erc20FeeProxy), balance);
MantisClone marked this conversation as resolved.
Show resolved Hide resolved
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved

erc20FeeProxy.transferFromWithReferenceAndFee(
tokenAddress,
payee,
paymentAmount,
paymentReference,
feeAmount,
feeAddress
);
}
MantisClone marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// 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;

// Reentrancy guard
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved

MantisClone 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;
}

modifier nonReentrant() {
if (msg.sender != address(ethereumFeeProxy)) {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, 'ReentrancyGuard: reentrant call');
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
_;
if (msg.sender != address(ethereumFeeProxy)) {
// By storing the original value once again, a refund is triggered
_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
}
103 changes: 103 additions & 0 deletions packages/smart-contracts/src/contracts/SingleRequestProxyFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// 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 {
address public ethereumFeeProxy;
address public erc20FeeProxy;
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved

event EtheruemSingleRequestProxyCreated(
address indexed proxyAddress,
address indexed payee,
bytes indexed paymentReference
);
aimensahnoun 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

constructor(address _ethereumFeeProxy, address _erc20FeeProxy) Ownable() {
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved
ethereumFeeProxy = _ethereumFeeProxy;
erc20FeeProxy = _erc20FeeProxy;
}
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved

/**
* @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 EtheruemSingleRequestProxyCreated(address(proxy), _payee, _paymentReference);
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved
return address(proxy);
}
MantisClone marked this conversation as resolved.
Show resolved Hide resolved

/**
* @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 {
erc20FeeProxy = _newERC20FeeProxy;
}
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved

/**
* @notice Updates the EthereumFeeProxy address
* @param _newEthereumFeeProxy The new EthereumFeeProxy address
*/
function setEthereumFeeProxy(address _newEthereumFeeProxy) external onlyOwner {
ethereumFeeProxy = _newEthereumFeeProxy;
}
aimensahnoun marked this conversation as resolved.
Show resolved Hide resolved
}
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
}
15 changes: 15 additions & 0 deletions packages/smart-contracts/src/contracts/test/TestToken.sol
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
}
Loading