Skip to content

Commit

Permalink
feat: Payment netwrok any-to-eth smartcontracts
Browse files Browse the repository at this point in the history
  • Loading branch information
vrolland committed Sep 29, 2021
1 parent 0301e1d commit b32ecdf
Show file tree
Hide file tree
Showing 7 changed files with 554 additions and 0 deletions.
11 changes: 11 additions & 0 deletions packages/smart-contracts/scripts/3_deploy_chainlink_contract.ts
Expand Up @@ -2,6 +2,7 @@ import '@nomiclabs/hardhat-ethers';
import { CurrencyManager } from '@requestnetwork/currency';
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import deployERC20ConversionProxy from './erc20-conversion-proxy';
import deployEthConversionProxy from './eth-conversion-proxy';
import deploySwapConversion from './erc20-swap-to-conversion';
import { deployOne } from './deploy-one';

Expand Down Expand Up @@ -65,6 +66,15 @@ export default async function deploy(args: any, hre: HardhatRuntimeEnvironment)
// FIXME: should try to retrieve information from artifacts instead
await erc20SwapConversion.approveRouterToSpend('0x9FBDa871d559710256a2502A2517b794B482Db40');

// EthConversion
const ethConversionProxyAddress = await deployEthConversionProxy(
{
...args,
chainlinkConversionPathAddress: conversionPathInstance.address,
ethFeeProxyAddress: '0x3d49d1eF2adE060a33c6E6Aa213513A7EE9a6241',
},
hre,
);
// ----------------------------------
console.log('Contracts deployed');
console.log(`
Expand All @@ -73,5 +83,6 @@ export default async function deploy(args: any, hre: HardhatRuntimeEnvironment)
ChainlinkConversionPath: ${conversionPathInstance.address}
Erc20ConversionProxy: ${erc20ConversionAddress}
Erc20SwapConversionProxy: ${erc20SwapConversionAddress}
EthConversionProxy: ${ethConversionProxyAddress}
`);
}
27 changes: 27 additions & 0 deletions packages/smart-contracts/scripts/eth-conversion-proxy.ts
@@ -0,0 +1,27 @@
import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { deployOne } from './deploy-one';

export default async function deploy(
args: { chainlinkConversionPathAddress?: string; ethFeeProxyAddress?: string },
hre: HardhatRuntimeEnvironment,
) {
const contractName = 'EthConversionProxy';

if (!args.chainlinkConversionPathAddress) {
// FIXME: should try to retrieve information from artifacts instead
console.error(
`Missing ChainlinkConversionPath on ${hre.network.name}, cannot deploy ${contractName}.`,
);
return;
}
if (!args.ethFeeProxyAddress) {
// FIXME: should try to retrieve information from artifacts instead
console.error(`Missing EthereumFeeProxy on ${hre.network.name}, cannot deploy ${contractName}.`);
return;
}

return deployOne(args, hre, contractName, [
args.ethFeeProxyAddress,
args.chainlinkConversionPathAddress,
]);
}
122 changes: 122 additions & 0 deletions packages/smart-contracts/src/contracts/EthConversionProxy.sol
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./ChainlinkConversionPath.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

/**
* @title EthConversionProxy
* @notice This contract convert from chainlink then swaps ETH
* before paying a request thanks to a conversion payment proxy
* the dependance with ReentrancyGuard is required to perform
* "transferExactEthWithReferenceAndFee" of the eth-fee-proxy contract
*/
contract EthConversionProxy is ReentrancyGuard {
address public paymentProxy;
ChainlinkConversionPath public chainlinkConversionPath;

constructor(address _paymentProxyAddress, address _chainlinkConversionPathAddress) {
paymentProxy = _paymentProxyAddress;
chainlinkConversionPath = ChainlinkConversionPath(_chainlinkConversionPathAddress);
}

// Event to declare a conversion with a reference
event TransferWithConversionAndReference(
uint256 amount,
address currency,
bytes indexed paymentReference,
uint256 feeAmount,
uint256 maxRateTimespan
);

// Event to declare a transfer with a reference
event TransferWithReferenceAndFee(
address to,
uint256 amount,
bytes indexed paymentReference,
uint256 feeAmount,
address feeAddress
);

/**
* @notice Performs an ETH transfer with a reference computing the payment amount based on the request amount
* @param _to Transfer recipient of the payement
* @param _requestAmount Request amount
* @param _path Conversion path
* @param _paymentReference Reference of the payment related
* @param _feeAmount The amount of the payment fee
* @param _feeAddress The fee recipient
* @param _maxRateTimespan Max time span with the oldestrate, ignored if zero
*/
function transferWithReferenceAndFee(
address _to,
uint256 _requestAmount,
address[] calldata _path,
bytes calldata _paymentReference,
uint256 _feeAmount,
address _feeAddress,
uint256 _maxRateTimespan
)
external
payable
{
// Request currency hash for ether: 0xF5AF88e117747e87fC5929F2ff87221B1447652E
require(
_path[_path.length - 1] == address(0xF5AF88e117747e87fC5929F2ff87221B1447652E),
"payment currency must be ethers"
);

(uint256 amountToPay, uint256 amountToPayInFees) = getConversions(
_path,
_requestAmount,
_feeAmount,
_maxRateTimespan);

// Pay the request and fees
(bool status, ) = paymentProxy.delegatecall(
abi.encodeWithSignature(
"transferExactEthWithReferenceAndFee(address,uint256,bytes,uint256,address)",
_to,
amountToPay,
_paymentReference,
amountToPayInFees,
_feeAddress
)
);

require(status, "paymentProxy transferExactEthWithReferenceAndFee failed");

// Event to declare a transfer with a reference
emit TransferWithConversionAndReference(
_requestAmount,
// request currency
_path[0],
_paymentReference,
_feeAmount,
_maxRateTimespan
);
}

function getConversions(
address[] memory _path,
uint256 _requestAmount,
uint256 _feeAmount,
uint256 _maxRateTimespan
)
internal
view
returns (uint256 amountToPay, uint256 amountToPayInFees)
{
(uint256 rate, uint256 oldestTimestampRate, uint256 decimals) = chainlinkConversionPath.getRate(_path);

// Check rate timespan
require(
_maxRateTimespan == 0 || block.timestamp - oldestTimestampRate <= _maxRateTimespan,
"aggregator rate is outdated"
);

// Get the amount to pay in the crypto currency chosen
amountToPay = (_requestAmount * rate) / decimals;
amountToPayInFees = (_feeAmount * rate) /decimals;
}
}
@@ -0,0 +1,163 @@
{
"abi": [
{
"inputs": [
{
"internalType": "address",
"name": "_paymentProxyAddress",
"type": "address"
},
{
"internalType": "address",
"name": "_chainlinkConversionPathAddress",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "currency",
"type": "address"
},
{
"indexed": true,
"internalType": "bytes",
"name": "paymentReference",
"type": "bytes"
},
{
"indexed": false,
"internalType": "uint256",
"name": "feeAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "maxRateTimespan",
"type": "uint256"
}
],
"name": "TransferWithConversionAndReference",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"indexed": true,
"internalType": "bytes",
"name": "paymentReference",
"type": "bytes"
},
{
"indexed": false,
"internalType": "uint256",
"name": "feeAmount",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "feeAddress",
"type": "address"
}
],
"name": "TransferWithReferenceAndFee",
"type": "event"
},
{
"inputs": [],
"name": "chainlinkConversionPath",
"outputs": [
{
"internalType": "contract ChainlinkConversionPath",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "paymentProxy",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_to",
"type": "address"
},
{
"internalType": "uint256",
"name": "_requestAmount",
"type": "uint256"
},
{
"internalType": "address[]",
"name": "_path",
"type": "address[]"
},
{
"internalType": "bytes",
"name": "_paymentReference",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "_feeAmount",
"type": "uint256"
},
{
"internalType": "address",
"name": "_feeAddress",
"type": "address"
},
{
"internalType": "uint256",
"name": "_maxRateTimespan",
"type": "uint256"
}
],
"name": "transferWithReferenceAndFee",
"outputs": [],
"stateMutability": "payable",
"type": "function"
}
]
}
@@ -0,0 +1,19 @@
import { ContractArtifact } from '../../ContractArtifact';

import { abi as ABI_0_1_0 } from './0.1.0.json';
import type { EthConversionProxy } from '../../../types/EthConversionProxy';

export const ethConversionArtifact = new ContractArtifact<EthConversionProxy>(
{
'0.1.0': {
abi: ABI_0_1_0,
deployment: {
private: {
address: '0x8273e4B8ED6c78e252a9fCa5563Adfcc75C91b2A',
creationBlockNumber: 0,
},
},
},
},
'0.1.0',
);
1 change: 1 addition & 0 deletions packages/smart-contracts/src/lib/artifacts/index.ts
Expand Up @@ -9,6 +9,7 @@ export * from './ERC20SwapToPay';
export * from './Erc20SwapConversion';
export * from './EthereumProxy';
export * from './EthereumFeeProxy';
export * from './EthConversionProxy';

/**
* Request Storage
Expand Down

0 comments on commit b32ecdf

Please sign in to comment.