From 60a9284650ac0ba9470cc8f15bc1fbe2a9c6efa0 Mon Sep 17 00:00:00 2001 From: vrolland Date: Wed, 29 Sep 2021 17:24:41 +0200 Subject: [PATCH 01/19] add advanced logic and specs for any to eth --- .../payment-network-any-to-eth-proxy-0.1.0.md | 304 ++++++++++++++++++ packages/advanced-logic/src/advanced-logic.ts | 3 + .../payment-network/any-to-eth-proxy.ts | 157 +++++++++ 3 files changed, 464 insertions(+) create mode 100644 packages/advanced-logic/specs/payment-network-any-to-eth-proxy-0.1.0.md create mode 100644 packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts diff --git a/packages/advanced-logic/specs/payment-network-any-to-eth-proxy-0.1.0.md b/packages/advanced-logic/specs/payment-network-any-to-eth-proxy-0.1.0.md new file mode 100644 index 0000000000..0bbfb1d48a --- /dev/null +++ b/packages/advanced-logic/specs/payment-network-any-to-eth-proxy-0.1.0.md @@ -0,0 +1,304 @@ +# Payment Network - Any currency - conversion to ethers with Fee + +You may be interested in this document if: + +- you want to create your own implementation of the Request protocol +- you are curious enough to dive and see what is under the hood of the Request protocol + +Prerequisite: Having read the advanced logic specification (see [here](./advanced-logic-specs-0.1.0.md)). + +## Description + +This extension allows the payments and the refunds to be made in ethers on the Ethereum blockchain for a request made in others currencies. +The rate is computing at the payment thanks to onchain oracles. +This Payment Network is quite similar to the [ETH Fee Proxy Contract](./payment-network-eth-fee-proxy-contract-0.1.0.md) extension, with a rate conversion before the payment. + +The payment is made through a proxy contract. This proxy contract call the [ETH Fee Proxy Contract](./payment-network-eth-fee-proxy-contract-0.1.0.md) to do the ethers transfer on behalf of the user. The contract ensures a link between an ethers transfer and a request through a `paymentReference`. + +This `paymentReference` consists of the last 8 bytes of a salted hash of the requestId: `last8Bytes(hash(lowercase(requestId + salt + address)))`: + +The contract also ensures that the `feeAmount` amount of the ethers transfer will be forwarded to the `feeAddress`. + +- `requestId` is the id of the request +- `salt` is a random number with at least 8 bytes of randomness. It must be unique to each request +- `address` is the payment address for payments, the refund address for refunds +- `feeAmount` is the amount of the transfer that should be paid in fees +- `feeAddress` is the address where the fee will be sent to +- `conversion path` is a list of currency hashes to perform the conversion +- `network` is the network of the tokens accepted for payments and refunds +- `maxRateTimespan` is the time span maximum accepted between the payment and the rate timestamp +- `lowercase()` transforms all characters to lowercase +- `hash()` is a keccak256 hash function +- `last8Bytes()` take the last 8 bytes + +As a payment network, this extension allows to deduce a payment `balance` for the request. (see +[Interpretation](#Interpretation)) + +## Contract + +The contract contains one function called `transferWithReferenceAndFee` which takes 6 arguments: + +- `to` is the destination address for the tokens +- `requestAmount` is the amount to be paid in the request currency +- `path` is the conversion path from the request currency to the payment token (see `conversion path`) +- `paymentReference` is the reference data used to track the transfer (see `paymentReference`) +- `feeAmount` is the amount of fees to be paid in the request currency +- `feeAddress` is the destination address for the fee +- `maxRateTimespan` is the time span maximum accepted between the payment and the rate timestamp + +The `TransferWithConversionAndReference` event is emitted when the tokens are transfered. This event contains: + +- `amount` is the amount paid in the request currency +- `requestCurrency` is the request currency hash +- `paymentReference` is the reference data used to track the transfer (see `paymentReference`) +- `feeAmount` is the amount of fees to be paid in the request currency +- `maxRateTimespan` is the time span maximum given for the payment + +[See smart contract source](https://github.com/RequestNetwork/requestNetwork/blob/master/packages/smart-contracts/src/contracts/EthConversionProxy.sol) + +| Network | Contract Address | +| ------- | ------------------------------------------ | +| Mainnet | TODO | +| Rinkeby | TODO | +| Private | 0x8273e4B8ED6c78e252a9fCa5563Adfcc75C91b2A | + +## Properties + +| Property | Type | Description | Requirement | +| ------------------------- | ------ | --------------------------------------------------------------------- | ------------- | +| **id** | String | constant value: "pn-any-to-eth-proxy" | **Mandatory** | +| **type** | String | constant value: "paymentNetwork" | **Mandatory** | +| **version** | String | constant value: "0.1.0" | **Mandatory** | +| **events** | Array | List of the actions performed by the extension | **Mandatory** | +| **values** | Object | | | +| **values.salt** | String | Salt for the request | **Mandatory** | +| **values.paymentAddress** | String | Ethereum address for the payment | Optional | +| **values.refundAddress** | String | Ethereum address for the refund | Optional | +| **values.feeAddress** | String | Ethereum address for the fee payment | Optional | +| **values.feeAmount** | String | The fee amount in the request `currency` | Optional | +| **values.network** | String | Ethereum network for the payments | Optional | +| **values.maxTimespan** | Number | Time span maximum accepted between the payment and the rate timestamp | Optional | + +--- + +## Actions + +### Creation + +#### Parameters + +| | Type | Description | Requirement | +| ----------------------------- | ------ | --------------------------------------------------------------------- | ------------- | +| **id** | String | Constant value: "pn-any-to-eth-proxy" | **Mandatory** | +| **type** | String | Constant value: "paymentNetwork" | **Mandatory** | +| **version** | String | Constant value: "0.1.0" | **Mandatory** | +| **parameters** | Object | | | +| **parameters.salt** | String | Salt for the request | **Mandatory** | +| **parameters.paymentAddress** | String | Ethereum address for the payment | Optional | +| **parameters.refundAddress** | String | Ethereum address for the refund | Optional | +| **parameters.feeAddress** | String | Ethereum address for the fee payment | Optional | +| **parameters.feeAmount** | String | The fee amount in the request `currency` | Optional | +| **parameters.network** | String | Ethereum network for the payments | Optional | +| **parameters.maxTimespan** | Number | Time span maximum accepted between the payment and the rate timestamp | Optional | + +#### Conditions + +This action is valid if: + +- The `salt` is not empty and long enough (8 bytes of randomness minimum). + +#### Warnings + +This action must trigger the warnings: + +| Warning | Condition | +| --------------------------------------- | ----------------------------------------------------------- | +| "paymentAddress is given by the payer"  | If `signer` is the payer **and** `paymentAddress` is given  | +| "feeAddress is given by the payer"  | If `signer` is the payer **and** `feeAddress` is given  | +| "feeAmount is given by the payer"  | If `signer` is the payer **and** `feeAddress` is given  | +| "refundAddress is given by the payee"  | If `signer` is the payee **and** `refundAddress` is given | + +Note: These warnings are necessary to highlight to avoid attempts of fake payments and refunds. For example, a payer could create a request using as the payment address one of his own addresses. A system could interpret a transaction to this address as a payment while the payee did not receive the funds. + +#### Results + +An extension state is created with the following properties: + +|  Property |  Value | +| ------------------------- | -------------------------------------------------------------- | +| **id** | "pn-any-to-eth-proxy" | +| **type** | "paymentNetwork" | +| **version** | "0.1.0" | +| **values** | | +| **values.paymentAddress** | `paymentAddress` from parameters if given, undefined otherwise | +| **values.refundAddress** | `refundAddress` from parameters if given, undefined otherwise | +| **values.feeAddress** | `feeAddress` from parameters if given, undefined otherwise | +| **values.feeAmount** | `feeAmount` from parameters if given, undefined otherwise | +| **values.salt** | Salt for the request | +| **values.network** | `network` from parameters if given, undefined otherwise | +| **values.maxTimespan** | `maxTimespan` from parameters if given, undefined otherwise | +| **events** | Array with one 'create' event (see below) | + +the 'create' event: + +|  Property |  Value | +| ----------------------------- | -------------------------------------------------------------- | +| **name** | 'create' | +| **parameters** | | +| **parameters.paymentAddress** | `paymentAddress` from parameters if given, undefined otherwise | +| **parameters.refundAddress** | `refundAddress` from parameters if given, undefined otherwise | +| **parameters.feeAddress** | `feeAddress` from parameters if given, undefined otherwise | +| **parameters.feeAmount** | `feeAmount` from parameters if given, undefined otherwise | +| **values.network** | `network` from parameters if given, undefined otherwise | +| **values.maxTimespan** | `maxTimespan` from parameters if given, undefined otherwise | +| **parameters.salt** | Salt for the request | + +--- + +### Updates + +#### addPaymentAddress + +##### Parameters + +| | Type | Description | Requirement | +| ----------------------------- | ------ | ------------------------------------------------------------ | ------------- | +| **id** | String | Constant value: "pn-any-to-eth-proxy" | **Mandatory** | +| **action** | String | Constant value: "addPaymentAddress" | **Mandatory** | +| **parameters** | Object | | | +| **parameters.paymentAddress** | String | Ethereum address for the payment | **Mandatory** | + +##### Conditions + +This action is valid, if: + +- The extension state with the id "pn-any-to-eth-proxy" exists +- The signer is the `payee` +- The extension property `paymentAddress` is undefined + +##### Warnings + +None. + +##### Results + +An extension state is updated with the following properties: + +|  Property |  Value | +| ------------------------- | ---------------------------------------------------- | +| **values.paymentAddress** | `paymentAddress` from parameters | +| **events** | Add an 'paymentAddress' event (see below) at its end | + +the 'addPaymentAddress' event: + +|  Property |  Value | +| ----------------------------- | ----------------------------------- | +| **name** | Constant value: "addPaymentAddress" | +| **parameters** | | +| **parameters.paymentAddress** | `paymentAddress` from parameters | + +#### addRefundAddress + +##### Parameters + +| | Type | Description | Requirement | +| ---------------------------- | ------ | ------------------------------------------------------------ | ------------- | +| **id** | String | Constant value: "pn-any-to-eth-proxy" | **Mandatory** | +| **action** | String | Constant value: "addRefundAddress" | **Mandatory** | +| **parameters** | Object | | | +| **parameters.refundAddress** | String | Ethereum address for the refund | **Mandatory** | + +##### Conditions + +This action is valid if: + +- The extension state with the id "pn-any-to-eth-proxy" exists +- The signer is the `payer` +- The extension property `refundAddress` is undefined + +##### Warnings + +None. + +##### Results + +An extension state is updated with the following properties: + +|  Property |  Value | +| ------------------------ | ------------------------------------------------------ | +| **values.refundAddress** | `refundAddress` from parameters | +| **events** | Add an 'addRefundAddress' event (see below) at its end | + +The 'addRefundAddress' event: + +|  Property |  Value | +| ---------------------------- | ------------------------------- | +| **name** | 'addRefundAddress' | +| **parameters** | | +| **parameters.refundAddress** | `refundAddress` from parameters | + +#### addFee + +##### Parameters + +| | Type | Description | Requirement | +| ------------------------ | ------ | ------------------------------------------------------------ | ------------- | +| **id** | String | Constant value: "pn-any-to-eth-proxy" | **Mandatory** | +| **action** | String | Constant value: "addfeeAddress" | **Mandatory** | +| **parameters** | Object | | | +| **parameters.feeAddress** | String | Ethereum address for the fee payment | **Mandatory** | +| **parameters.feeAmount** | String | The fee amount | **Mandatory** | + +##### Conditions + +This action is valid, if: + +- The extension state with the id "pn-any-to-eth-proxy" exists +- The signer is the `payee` +- The extension property `feeAddress` is undefined +- The extension property `feeAmount` is undefined or represents an integer greater or equal than zero + +##### Warnings + +None. + +##### Results + +An extension state is updated with the following properties: + +|  Property |  Value | +| -------------------- | ---------------------------------------- | +| **values.feeAddress** | `feeAddress` from parameters | +| **values.feeAmount** | `feeAmount` from parameters | +| **events** | Add a 'fee' event (see below) at its end | + +the 'addFee' event: + +|  Property |  Value | +| ------------------------ | --------------------------- | +| **name** | Constant value: "addfeeAddress" | +| **parameters** | | +| **parameters.feeAddress** | `feeAddress` from parameters | +| **parameters.feeAmount** | `feeAmount` from parameters | + +--- + +## Interpretation + +The fee proxy contract address is determined by the `paymentNetwork.values.network` (see (table)[#Contract] with proxy contract addresses). + +Any `TransferWithConversionAndReference` events emitted from the proxy contract with the following arguments are considered as a payment: + +- `to` `===` `paymentAddress` +- `paymentReference` `===` `last8Bytes(hash(lowercase(requestId + salt + payment address)))` +- `maxRateTimespan` `===` `paymentNetwork.values.maxRateTimespan` + +Any `TransferWithConversionAndReference` events emitted from the proxy contract with the following arguments are considered as a refund: + +- `to` `===` `refundAddress` +- `paymentReference` `===` `last8Bytes(hash(lowercase(requestId + salt + refund address)))` +- `maxRateTimespan` `===` `paymentNetwork.values.maxRateTimespan` + +The sum of payment amounts minus the sum of refund amounts is considered the balance. + +The fees amount can be be infered from the `TransferWithConversionAndReference` events emitted from the proxy contract. diff --git a/packages/advanced-logic/src/advanced-logic.ts b/packages/advanced-logic/src/advanced-logic.ts index ea0d96e24d..4c0ff90f4c 100644 --- a/packages/advanced-logic/src/advanced-logic.ts +++ b/packages/advanced-logic/src/advanced-logic.ts @@ -16,6 +16,7 @@ import FeeProxyContractEth from './extensions/payment-network/ethereum/fee-proxy import EthereumInputData from './extensions/payment-network/ethereum/input-data'; import NearNative from './extensions/payment-network/near-native'; import AnyToErc20Proxy from './extensions/payment-network/any-to-erc20-proxy'; +import AnyToEthProxy from './extensions/payment-network/any-to-eth-proxy'; /** * Module to manage Advanced logic extensions @@ -35,6 +36,7 @@ export default class AdvancedLogic implements AdvancedLogicTypes.IAdvancedLogic feeProxyContractErc20: new FeeProxyContractErc20(), proxyContractErc20: new ProxyContractErc20(), feeProxyContractEth: new FeeProxyContractEth(), + anyToEthProxy: new AnyToEthProxy(), }; /** @@ -90,6 +92,7 @@ export default class AdvancedLogic implements AdvancedLogicTypes.IAdvancedLogic [ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ERC20_PROXY]: this.extensions.anyToErc20Proxy, [ExtensionTypes.ID.PAYMENT_NETWORK_ETH_FEE_PROXY_CONTRACT]: this.extensions .feeProxyContractEth, + [ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY]: this.extensions.anyToEthProxy, }[id]; if (!extension) { diff --git a/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts b/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts new file mode 100644 index 0000000000..68d668b717 --- /dev/null +++ b/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts @@ -0,0 +1,157 @@ +import { ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types'; +import EthereumFeeProxyPaymentNetwork from './ethereum/fee-proxy-contract'; + +const CURRENT_VERSION = '0.1.0'; +// Default network if the storage data does not give any +const DEFAULT_NETWORK = 'mainnet'; + +/** + * These currencies are supported by Chainlink for conversion. + * Only ETH is supported as accepted token by the payment proxy. + */ +const supportedCurrencies: Record> = { + private: { + [RequestLogicTypes.CURRENCY.ISO4217]: ['USD', 'EUR'], + [RequestLogicTypes.CURRENCY.ERC20]: ['0x38cf23c52bb4b13f051aec09580a2de845a7fa35'], + [RequestLogicTypes.CURRENCY.ETH]: ['ETH'], + [RequestLogicTypes.CURRENCY.BTC]: [], + }, + rinkeby: { + [RequestLogicTypes.CURRENCY.ISO4217]: ['EUR', 'GBP', 'USD'], + [RequestLogicTypes.CURRENCY.ERC20]: ['0xfab46e002bbf0b4509813474841e0716e6730136'], + [RequestLogicTypes.CURRENCY.ETH]: ['ETH'], + [RequestLogicTypes.CURRENCY.BTC]: [], + }, + mainnet: { + [RequestLogicTypes.CURRENCY.ISO4217]: ['AUD', 'CAD', 'CHF', 'EUR', 'GBP', 'SGD', 'USD'], + [RequestLogicTypes.CURRENCY.ERC20]: [ + '0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c', + '0x3845badade8e6dff049820680d1f14bd3903a5d0', + '0x4e15361fd6b4bb609fa63c81a2be19d873717870', + '0x6b175474e89094c44da98b954eedeac495271d0f', + '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9', + '0x8290333cef9e6d528dd5618fb97a76f268f3edd4', + '0x8ab7404063ec4dbcfd4598215992dc3f8ec853d7', + '0x967da4048cd07ab37855c090aaf366e4ce1b9f48', + '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + '0xa117000000f279d81a1d3cc75430faa017fa5a2e', + '0xc944e90c64b2c07662a292be6244bdf05cda44a7', + '0xdac17f958d2ee523a2206206994597c13d831ec7', + ], + [RequestLogicTypes.CURRENCY.ETH]: ['ETH'], + [RequestLogicTypes.CURRENCY.BTC]: [], + }, +}; + +export default class AnyToEthProxyPaymentNetwork extends EthereumFeeProxyPaymentNetwork { + public constructor( + extensionId: ExtensionTypes.ID = ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + currentVersion: string = CURRENT_VERSION, + ) { + super(extensionId, currentVersion, Object.keys(supportedCurrencies)); + } + + /** + * Creates the extensionsData to create the extension ETH fee proxy contract payment detection + * + * @param creationParameters extensions parameters to create + * + * @returns IExtensionCreationAction the extensionsData to be stored in the request + */ + public createCreationAction( + creationParameters: ExtensionTypes.PnAnyToEth.ICreationParameters, + ): ExtensionTypes.IAction { + const network = creationParameters.network; + if (!network) { + throw Error('network is required'); + } + if (!supportedCurrencies[network]) { + throw Error(`network ${network} not supported`); + } + return super.createCreationAction(creationParameters); + } + + /** + * Applies a creation extension action + * + * @param extensionAction action to apply + * @param timestamp action timestamp + * + * @returns state of the extension created + */ + protected applyCreation( + extensionAction: ExtensionTypes.IAction, + timestamp: number, + ): ExtensionTypes.IState { + if (!extensionAction.parameters.network || extensionAction.parameters.network.length === 0) { + throw Error('network is required'); + } + + const feePNCreationAction = super.applyCreation(extensionAction, timestamp); + + return { + ...feePNCreationAction, + events: [ + { + name: 'create', + parameters: { + feeAddress: extensionAction.parameters.feeAddress, + feeAmount: extensionAction.parameters.feeAmount, + paymentAddress: extensionAction.parameters.paymentAddress, + refundAddress: extensionAction.parameters.refundAddress, + salt: extensionAction.parameters.salt, + network: extensionAction.parameters.network || DEFAULT_NETWORK, + maxRateTimespan: extensionAction.parameters.maxRateTimespan, + }, + timestamp, + }, + ], + values: { + ...feePNCreationAction.values, + network: extensionAction.parameters.network || DEFAULT_NETWORK, + maxRateTimespan: extensionAction.parameters.maxRateTimespan, + }, + }; + } + + /** + * Validate the extension action regarding the currency and network + * It must throw in case of error + * + * @param request + */ + protected validate( + request: RequestLogicTypes.IRequest, + extensionAction: ExtensionTypes.IAction, + ): void { + const network = + extensionAction.parameters.network || request.extensions[this.extensionId]?.values.network; + + // Nothing can be validated if the network has not been given yet + if (!network) { + return; + } + + if (!supportedCurrencies[network]) { + throw new Error(`The network (${network}) is not supported for this payment network.`); + } + + if (!supportedCurrencies[network][request.currency.type]) { + throw new Error( + `The currency type (${request.currency.type}) of the request is not supported for this payment network.`, + ); + } + + const currency = + request.currency.type === RequestLogicTypes.CURRENCY.ERC20 + ? request.currency.value.toLowerCase() + : request.currency.value; + + if (!supportedCurrencies[network][request.currency.type].includes(currency)) { + throw new Error( + `The currency (${request.currency.value}) of the request is not supported for this payment network.`, + ); + } + } +} From 558978afaa67102ea657ffb63bd7bbacb27192fe Mon Sep 17 00:00:00 2001 From: vrolland Date: Wed, 29 Sep 2021 17:31:54 +0200 Subject: [PATCH 02/19] add types --- packages/types/src/extension-types.ts | 3 +++ .../types/src/extensions/pn-any-to-eth-types.ts | 13 +++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 packages/types/src/extensions/pn-any-to-eth-types.ts diff --git a/packages/types/src/extension-types.ts b/packages/types/src/extension-types.ts index 50d283db5a..d591638baa 100644 --- a/packages/types/src/extension-types.ts +++ b/packages/types/src/extension-types.ts @@ -4,6 +4,7 @@ import * as PnAnyDeclarative from './extensions/pn-any-declarative-types'; import * as PnFeeReferenceBased from './extensions/pn-any-fee-reference-based-types'; import * as PnReferenceBased from './extensions/pn-any-reference-based-types'; import * as PnAnyToErc20 from './extensions/pn-any-to-er20-types'; +import * as PnAnyToEth from './extensions/pn-any-to-er20-types'; import * as Identity from './identity-types'; import * as RequestLogic from './request-logic-types'; @@ -14,6 +15,7 @@ export { PnFeeReferenceBased, PnReferenceBased, PnAnyToErc20, + PnAnyToEth, }; /** Extension interface is extended by the extensions implementation */ @@ -73,6 +75,7 @@ export enum ID { PAYMENT_NETWORK_NATIVE_TOKEN = 'pn-native-token', PAYMENT_NETWORK_ANY_DECLARATIVE = 'pn-any-declarative', PAYMENT_NETWORK_ANY_TO_ERC20_PROXY = 'pn-any-to-erc20-proxy', + PAYMENT_NETWORK_ANY_TO_ETH_PROXY = 'pn-any-to-eth-proxy', } /** Type of extensions */ diff --git a/packages/types/src/extensions/pn-any-to-eth-types.ts b/packages/types/src/extensions/pn-any-to-eth-types.ts new file mode 100644 index 0000000000..f824b7f869 --- /dev/null +++ b/packages/types/src/extensions/pn-any-to-eth-types.ts @@ -0,0 +1,13 @@ +import * as Extension from '../extension-types'; +import * as PnAnyFees from './pn-any-fee-reference-based-types'; + +/** Any to ERC20e reference-based payment network extension interface */ +export interface IAnyToERC20 extends PnAnyFees.IFeeReferenceBased { + createCreationAction: (creationParameters: ICreationParameters) => Extension.IAction; +} + +/** Parameters for the creation action */ +export interface ICreationParameters extends Extension.PnFeeReferenceBased.ICreationParameters { + network?: string; + maxRateTimespan?: number; +} From d19acc5bdc8d2737be09d74ab2e74686fae6516d Mon Sep 17 00:00:00 2001 From: vrolland Date: Wed, 29 Sep 2021 17:38:02 +0200 Subject: [PATCH 03/19] gather chainlink supported currencies --- .../payment-network/any-to-erc20-proxy.ts | 50 +------------------ .../payment-network/any-to-eth-proxy.ts | 40 +-------------- .../chainlink-supported-currencies.ts | 49 ++++++++++++++++++ 3 files changed, 51 insertions(+), 88 deletions(-) create mode 100644 packages/advanced-logic/src/extensions/payment-network/chainlink-supported-currencies.ts diff --git a/packages/advanced-logic/src/extensions/payment-network/any-to-erc20-proxy.ts b/packages/advanced-logic/src/extensions/payment-network/any-to-erc20-proxy.ts index ce8f7dbcf7..0f79106711 100644 --- a/packages/advanced-logic/src/extensions/payment-network/any-to-erc20-proxy.ts +++ b/packages/advanced-logic/src/extensions/payment-network/any-to-erc20-proxy.ts @@ -1,59 +1,11 @@ import { ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types'; import Erc20FeeProxyPaymentNetwork from './erc20/fee-proxy-contract'; +import { supportedCurrencies } from './chainlink-supported-currencies'; const CURRENT_VERSION = '0.1.0'; // Default network if the storage data does not give any const DEFAULT_NETWORK = 'mainnet'; -/** - * These currencies are supported by Chainlink for conversion. - * Only ERC20 is supported as accepted token by the payment proxy. - */ -const supportedCurrencies: Record> = { - private: { - [RequestLogicTypes.CURRENCY.ISO4217]: ['USD', 'EUR'], - [RequestLogicTypes.CURRENCY.ERC20]: ['0x38cf23c52bb4b13f051aec09580a2de845a7fa35'], - [RequestLogicTypes.CURRENCY.ETH]: ['ETH'], - [RequestLogicTypes.CURRENCY.BTC]: [], - }, - rinkeby: { - [RequestLogicTypes.CURRENCY.ISO4217]: ['EUR', 'GBP', 'USD'], - [RequestLogicTypes.CURRENCY.ERC20]: ['0xfab46e002bbf0b4509813474841e0716e6730136'], - [RequestLogicTypes.CURRENCY.ETH]: ['ETH'], - [RequestLogicTypes.CURRENCY.BTC]: [], - }, - mainnet: { - [RequestLogicTypes.CURRENCY.ISO4217]: ['AUD', 'CAD', 'CHF', 'EUR', 'GBP', 'SGD', 'USD'], - [RequestLogicTypes.CURRENCY.ERC20]: [ - '0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c', - '0x3845badade8e6dff049820680d1f14bd3903a5d0', - '0x4e15361fd6b4bb609fa63c81a2be19d873717870', - '0x6b175474e89094c44da98b954eedeac495271d0f', - '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9', - '0x8290333cef9e6d528dd5618fb97a76f268f3edd4', - '0x8ab7404063ec4dbcfd4598215992dc3f8ec853d7', - '0x967da4048cd07ab37855c090aaf366e4ce1b9f48', - '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - '0xa117000000f279d81a1d3cc75430faa017fa5a2e', - '0xc944e90c64b2c07662a292be6244bdf05cda44a7', - '0xdac17f958d2ee523a2206206994597c13d831ec7', - ], - [RequestLogicTypes.CURRENCY.ETH]: ['ETH'], - [RequestLogicTypes.CURRENCY.BTC]: [], - }, - matic: { - ISO4217: ['AUD', 'CAD', 'CHF', 'EUR', 'GBP', 'SGD', 'USD'], - ERC20: [ - '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', - '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', - '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', - ], - ETH: ['ETH'], - BTC: [], - }, -}; - export default class AnyToErc20ProxyPaymentNetwork extends Erc20FeeProxyPaymentNetwork { public constructor( extensionId: ExtensionTypes.ID = ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ERC20_PROXY, diff --git a/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts b/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts index 68d668b717..3aa9a4ee17 100644 --- a/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts +++ b/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts @@ -1,49 +1,11 @@ import { ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types'; import EthereumFeeProxyPaymentNetwork from './ethereum/fee-proxy-contract'; +import { supportedCurrencies } from './chainlink-supported-currencies'; const CURRENT_VERSION = '0.1.0'; // Default network if the storage data does not give any const DEFAULT_NETWORK = 'mainnet'; -/** - * These currencies are supported by Chainlink for conversion. - * Only ETH is supported as accepted token by the payment proxy. - */ -const supportedCurrencies: Record> = { - private: { - [RequestLogicTypes.CURRENCY.ISO4217]: ['USD', 'EUR'], - [RequestLogicTypes.CURRENCY.ERC20]: ['0x38cf23c52bb4b13f051aec09580a2de845a7fa35'], - [RequestLogicTypes.CURRENCY.ETH]: ['ETH'], - [RequestLogicTypes.CURRENCY.BTC]: [], - }, - rinkeby: { - [RequestLogicTypes.CURRENCY.ISO4217]: ['EUR', 'GBP', 'USD'], - [RequestLogicTypes.CURRENCY.ERC20]: ['0xfab46e002bbf0b4509813474841e0716e6730136'], - [RequestLogicTypes.CURRENCY.ETH]: ['ETH'], - [RequestLogicTypes.CURRENCY.BTC]: [], - }, - mainnet: { - [RequestLogicTypes.CURRENCY.ISO4217]: ['AUD', 'CAD', 'CHF', 'EUR', 'GBP', 'SGD', 'USD'], - [RequestLogicTypes.CURRENCY.ERC20]: [ - '0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c', - '0x3845badade8e6dff049820680d1f14bd3903a5d0', - '0x4e15361fd6b4bb609fa63c81a2be19d873717870', - '0x6b175474e89094c44da98b954eedeac495271d0f', - '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9', - '0x8290333cef9e6d528dd5618fb97a76f268f3edd4', - '0x8ab7404063ec4dbcfd4598215992dc3f8ec853d7', - '0x967da4048cd07ab37855c090aaf366e4ce1b9f48', - '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', - '0xa117000000f279d81a1d3cc75430faa017fa5a2e', - '0xc944e90c64b2c07662a292be6244bdf05cda44a7', - '0xdac17f958d2ee523a2206206994597c13d831ec7', - ], - [RequestLogicTypes.CURRENCY.ETH]: ['ETH'], - [RequestLogicTypes.CURRENCY.BTC]: [], - }, -}; - export default class AnyToEthProxyPaymentNetwork extends EthereumFeeProxyPaymentNetwork { public constructor( extensionId: ExtensionTypes.ID = ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, diff --git a/packages/advanced-logic/src/extensions/payment-network/chainlink-supported-currencies.ts b/packages/advanced-logic/src/extensions/payment-network/chainlink-supported-currencies.ts new file mode 100644 index 0000000000..5decd0c19b --- /dev/null +++ b/packages/advanced-logic/src/extensions/payment-network/chainlink-supported-currencies.ts @@ -0,0 +1,49 @@ +import { RequestLogicTypes } from '@requestnetwork/types'; + +/** + * These currencies are supported by Chainlink for conversion. + */ +export const supportedCurrencies: Record> = { + private: { + [RequestLogicTypes.CURRENCY.ISO4217]: ['USD', 'EUR'], + [RequestLogicTypes.CURRENCY.ERC20]: ['0x38cf23c52bb4b13f051aec09580a2de845a7fa35'], + [RequestLogicTypes.CURRENCY.ETH]: ['ETH'], + [RequestLogicTypes.CURRENCY.BTC]: [], + }, + rinkeby: { + [RequestLogicTypes.CURRENCY.ISO4217]: ['EUR', 'GBP', 'USD'], + [RequestLogicTypes.CURRENCY.ERC20]: ['0xfab46e002bbf0b4509813474841e0716e6730136'], + [RequestLogicTypes.CURRENCY.ETH]: ['ETH'], + [RequestLogicTypes.CURRENCY.BTC]: [], + }, + mainnet: { + [RequestLogicTypes.CURRENCY.ISO4217]: ['AUD', 'CAD', 'CHF', 'EUR', 'GBP', 'SGD', 'USD'], + [RequestLogicTypes.CURRENCY.ERC20]: [ + '0x1f573d6fb3f13d689ff844b4ce37794d79a7ff1c', + '0x3845badade8e6dff049820680d1f14bd3903a5d0', + '0x4e15361fd6b4bb609fa63c81a2be19d873717870', + '0x6b175474e89094c44da98b954eedeac495271d0f', + '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9', + '0x8290333cef9e6d528dd5618fb97a76f268f3edd4', + '0x8ab7404063ec4dbcfd4598215992dc3f8ec853d7', + '0x967da4048cd07ab37855c090aaf366e4ce1b9f48', + '0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2', + '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', + '0xa117000000f279d81a1d3cc75430faa017fa5a2e', + '0xc944e90c64b2c07662a292be6244bdf05cda44a7', + '0xdac17f958d2ee523a2206206994597c13d831ec7', + ], + [RequestLogicTypes.CURRENCY.ETH]: ['ETH'], + [RequestLogicTypes.CURRENCY.BTC]: [], + }, + matic: { + ISO4217: ['AUD', 'CAD', 'CHF', 'EUR', 'GBP', 'SGD', 'USD'], + ERC20: [ + '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', + '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063', + '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', + ], + ETH: ['ETH'], + BTC: [], + }, +}; From 0301e1d7d79e9efab97835db56ffe7a8c7eaf602 Mon Sep 17 00:00:00 2001 From: vrolland Date: Wed, 29 Sep 2021 18:29:50 +0200 Subject: [PATCH 04/19] add tests --- .../payment-network/any-to-eth-proxy.test.ts | 711 ++++++++++++++++++ .../any-to-eth-proxy-add-data-generator.ts | 203 +++++ .../any-to-eth-proxy-create-data-generator.ts | 236 ++++++ 3 files changed, 1150 insertions(+) create mode 100644 packages/advanced-logic/test/extensions/payment-network/any-to-eth-proxy.test.ts create mode 100644 packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts create mode 100644 packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-create-data-generator.ts diff --git a/packages/advanced-logic/test/extensions/payment-network/any-to-eth-proxy.test.ts b/packages/advanced-logic/test/extensions/payment-network/any-to-eth-proxy.test.ts new file mode 100644 index 0000000000..aeac6b0fc7 --- /dev/null +++ b/packages/advanced-logic/test/extensions/payment-network/any-to-eth-proxy.test.ts @@ -0,0 +1,711 @@ +import { ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types'; +import Utils from '@requestnetwork/utils'; + +import AnyToEthProxy from '../../../src/extensions/payment-network/any-to-eth-proxy'; +import * as DataConversionETHFeeAddData from '../../utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator'; +import * as DataConversionETHFeeCreate from '../../utils/payment-network/ethereum/any-to-eth-proxy-create-data-generator'; +import * as TestData from '../../utils/test-data-generator'; + +const anyToEthProxy = new AnyToEthProxy(); + +/* eslint-disable @typescript-eslint/no-unused-expressions */ +describe('extensions/payment-network/ethereum/any-to-eth-fee-proxy-contract', () => { + describe('createCreationAction', () => { + it('can create a create action with all parameters', () => { + // 'extension data is wrong' + expect( + anyToEthProxy.createCreationAction({ + feeAddress: '0x0000000000000000000000000000000000000001', + feeAmount: '0', + paymentAddress: '0x0000000000000000000000000000000000000002', + refundAddress: '0x0000000000000000000000000000000000000003', + salt: 'ea3bc7caf64110ca', + network: 'rinkeby', + maxRateTimespan: 1000000, + }), + ).toEqual({ + action: 'create', + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + parameters: { + feeAddress: '0x0000000000000000000000000000000000000001', + feeAmount: '0', + paymentAddress: '0x0000000000000000000000000000000000000002', + refundAddress: '0x0000000000000000000000000000000000000003', + salt: 'ea3bc7caf64110ca', + network: 'rinkeby', + maxRateTimespan: 1000000, + }, + version: '0.1.0', + }); + }); + + it('can create a create action without fee parameters', () => { + // 'extension data is wrong' + expect( + anyToEthProxy.createCreationAction({ + paymentAddress: '0x0000000000000000000000000000000000000001', + refundAddress: '0x0000000000000000000000000000000000000002', + salt: 'ea3bc7caf64110ca', + network: 'rinkeby', + + }), + ).toEqual({ + action: 'create', + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + parameters: { + paymentAddress: '0x0000000000000000000000000000000000000001', + refundAddress: '0x0000000000000000000000000000000000000002', + salt: 'ea3bc7caf64110ca', + network: 'rinkeby', + + }, + version: '0.1.0', + }); + }); + + it('cannot createCreationAction with payment address not an ethereum address', () => { + // 'must throw' + expect(() => { + anyToEthProxy.createCreationAction({ + paymentAddress: 'not an ethereum address', + refundAddress: '0x0000000000000000000000000000000000000002', + + network: 'rinkeby', + salt: 'ea3bc7caf64110ca', + }); + }).toThrowError("paymentAddress 'not an ethereum address' is not a valid address"); + }); + + it('cannot createCreationAction with refund address not an ethereum address', () => { + // 'must throw' + expect(() => { + anyToEthProxy.createCreationAction({ + paymentAddress: '0x0000000000000000000000000000000000000001', + + network: 'rinkeby', + refundAddress: 'not an ethereum address', + salt: 'ea3bc7caf64110ca', + }); + }).toThrowError("refundAddress 'not an ethereum address' is not a valid address"); + }); + + it('cannot createCreationAction with fee address not an ethereum address', () => { + // 'must throw' + expect(() => { + anyToEthProxy.createCreationAction({ + feeAddress: 'not an ethereum address', + paymentAddress: '0x0000000000000000000000000000000000000001', + + network: 'rinkeby', + salt: 'ea3bc7caf64110ca', + }); + }).toThrowError('feeAddress is not a valid address'); + }); + + it('cannot createCreationAction with invalid fee amount', () => { + // 'must throw' + expect(() => { + anyToEthProxy.createCreationAction({ + feeAmount: '-20000', + paymentAddress: '0x0000000000000000000000000000000000000001', + + network: 'rinkeby', + salt: 'ea3bc7caf64110ca', + }); + }).toThrowError('feeAmount is not a valid amount'); + }); + + it('cannot createCreationAction with network not supported', () => { + // 'must throw' + expect(() => { + anyToEthProxy.createCreationAction({ + paymentAddress: '0x0000000000000000000000000000000000000001', + salt: 'ea3bc7caf64110ca', + network: 'kovan', + }); + }).toThrowError('network kovan not supported'); + }); + + it('cannot applyActionToExtensions of creation on a non supported currency', () => { + const requestCreatedNoExtension: RequestLogicTypes.IRequest = Utils.deepCopy( + TestData.requestCreatedNoExtension, + ); + requestCreatedNoExtension.currency = { + type: RequestLogicTypes.CURRENCY.ETH, + value: 'ETH', + }; + + const action: ExtensionTypes.IAction = Utils.deepCopy( + DataConversionETHFeeCreate.actionCreationFull, + ); + action.parameters.network = 'invalid network'; + + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + TestData.requestCreatedNoExtension.extensions, + action, + requestCreatedNoExtension, + TestData.otherIdRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError(`The network (invalid network) is not supported for this payment network.`); + }); + + it('cannot applyActionToExtensions of creation on a non supported currency', () => { + const requestCreatedNoExtension: RequestLogicTypes.IRequest = Utils.deepCopy( + TestData.requestCreatedNoExtension, + ); + requestCreatedNoExtension.currency = { + type: RequestLogicTypes.CURRENCY.ETH, + value: 'invalid value', + }; + + const action: ExtensionTypes.IAction = Utils.deepCopy( + DataConversionETHFeeCreate.actionCreationFull, + ); + + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + TestData.requestCreatedNoExtension.extensions, + action, + requestCreatedNoExtension, + TestData.otherIdRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError( + `The currency (invalid value) of the request is not supported for this payment network.`, + ); + }); + }); + + describe('createAddPaymentAddressAction', () => { + it('can createAddPaymentAddressAction', () => { + // 'extension data is wrong' + expect( + anyToEthProxy.createAddPaymentAddressAction({ + paymentAddress: '0x0000000000000000000000000000000000000001', + }), + ).toEqual({ + action: ExtensionTypes.PnReferenceBased.ACTION.ADD_PAYMENT_ADDRESS, + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + parameters: { + paymentAddress: '0x0000000000000000000000000000000000000001', + }, + }); + }); + + it('cannot createAddPaymentAddressAction with payment address not an ethereum address', () => { + // 'must throw' + expect(() => { + anyToEthProxy.createAddPaymentAddressAction({ + paymentAddress: 'not an ethereum address', + }); + }).toThrowError("paymentAddress 'not an ethereum address' is not a valid address"); + }); + }); + + describe('createAddRefundAddressAction', () => { + it('can createAddRefundAddressAction', () => { + // 'extension data is wrong' + expect( + anyToEthProxy.createAddRefundAddressAction({ + refundAddress: '0x0000000000000000000000000000000000000002', + }), + ).toEqual({ + action: ExtensionTypes.PnReferenceBased.ACTION.ADD_REFUND_ADDRESS, + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + parameters: { + refundAddress: '0x0000000000000000000000000000000000000002', + }, + }); + }); + + it('cannot createAddRefundAddressAction with payment address not an ethereum address', () => { + // 'must throw' + expect(() => { + anyToEthProxy.createAddRefundAddressAction({ + refundAddress: 'not an ethereum address', + }); + }).toThrowError("refundAddress 'not an ethereum address' is not a valid address"); + }); + }); + + describe('createAddFeeAction', () => { + it('can createAddFeeAction', () => { + // 'extension data is wrong' + expect( + anyToEthProxy.createAddFeeAction({ + feeAddress: '0x0000000000000000000000000000000000000002', + feeAmount: '2000', + }), + ).toEqual({ + action: ExtensionTypes.PnFeeReferenceBased.ACTION.ADD_FEE, + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + parameters: { + feeAddress: '0x0000000000000000000000000000000000000002', + feeAmount: '2000', + }, + }); + }); + + it('cannot createAddFeeAddressAction with payment address not an ethereum address', () => { + // 'must throw' + expect(() => { + anyToEthProxy.createAddFeeAction({ + feeAddress: 'not an ethereum address', + feeAmount: '2000', + }); + }).toThrowError('feeAddress is not a valid address'); + }); + + it('cannot createAddFeeAction with amount non positive integer', () => { + // 'must throw' + expect(() => { + anyToEthProxy.createAddFeeAction({ + feeAddress: '0x0000000000000000000000000000000000000002', + feeAmount: '-30000', + }); + }).toThrowError('feeAmount is not a valid amount'); + }); + }); + + describe('applyActionToExtension', () => { + describe('applyActionToExtension/unknown action', () => { + it('cannot applyActionToExtensions of unknown action', () => { + const unknownAction = Utils.deepCopy(DataConversionETHFeeAddData.actionAddPaymentAddress); + unknownAction.action = 'unknown action' as any; + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateCreatedEmpty.extensions, + unknownAction, + DataConversionETHFeeCreate.requestStateCreatedEmpty, + TestData.payeeRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError('Unknown action: unknown action'); + }); + + it('cannot applyActionToExtensions of unknown id', () => { + const unknownAction = Utils.deepCopy(DataConversionETHFeeAddData.actionAddPaymentAddress); + unknownAction.id = 'unknown id' as any; + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateCreatedEmpty.extensions, + unknownAction, + DataConversionETHFeeCreate.requestStateCreatedEmpty, + TestData.payeeRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError('The extension should be created before receiving any other action'); + }); + }); + + describe('applyActionToExtension/create', () => { + it('can applyActionToExtensions of creation', () => { + // 'new extension state wrong' + expect( + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateNoExtensions.extensions, + DataConversionETHFeeCreate.actionCreationFull, + DataConversionETHFeeCreate.requestStateNoExtensions, + TestData.otherIdRaw.identity, + TestData.arbitraryTimestamp, + ), + ).toEqual(DataConversionETHFeeCreate.extensionFullState); + }); + + it('can applyActionToExtensions of creation when address is checksumed', () => { + const request = Utils.deepCopy(DataConversionETHFeeCreate.requestStateNoExtensions); + + request.currency = { + type: RequestLogicTypes.CURRENCY.ERC20, + value: '0x4E15361FD6b4BB609Fa63C81A2be19d873717870', // FTM + network: 'mainnet', + }; + + expect( + anyToEthProxy.applyActionToExtension( + request.extensions, + DataConversionETHFeeCreate.actionCreationFull, + request, + TestData.otherIdRaw.identity, + TestData.arbitraryTimestamp, + ), + ).toEqual(DataConversionETHFeeCreate.extensionFullState); + }); + + it('cannot applyActionToExtensions of creation with a previous state', () => { + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestFullStateCreated.extensions, + DataConversionETHFeeCreate.actionCreationFull, + DataConversionETHFeeCreate.requestFullStateCreated, + TestData.otherIdRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError('This extension has already been created'); + }); + + it('cannot applyActionToExtensions of creation on a non supported currency', () => { + const requestCreatedNoExtension: RequestLogicTypes.IRequest = Utils.deepCopy( + TestData.requestCreatedNoExtension, + ); + requestCreatedNoExtension.currency = { + type: RequestLogicTypes.CURRENCY.BTC, + value: 'BTC', + }; + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + TestData.requestCreatedNoExtension.extensions, + DataConversionETHFeeCreate.actionCreationFull, + requestCreatedNoExtension, + TestData.otherIdRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError( + 'The currency (BTC) of the request is not supported for this payment network.', + ); + }); + + it('cannot applyActionToExtensions of creation with payment address not valid', () => { + const testnetPaymentAddress = Utils.deepCopy( + DataConversionETHFeeCreate.actionCreationFull, + ); + testnetPaymentAddress.parameters.paymentAddress = + DataConversionETHFeeAddData.invalidAddress; + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateNoExtensions.extensions, + testnetPaymentAddress, + DataConversionETHFeeCreate.requestStateNoExtensions, + TestData.otherIdRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError( + `paymentAddress '${DataConversionETHFeeAddData.invalidAddress}' is not a valid address`, + ); + }); + + it('cannot applyActionToExtensions of creation with refund address not valid', () => { + const testnetRefundAddress = Utils.deepCopy( + DataConversionETHFeeCreate.actionCreationFull, + ); + testnetRefundAddress.parameters.refundAddress = + DataConversionETHFeeAddData.invalidAddress; + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateNoExtensions.extensions, + testnetRefundAddress, + DataConversionETHFeeCreate.requestStateNoExtensions, + TestData.otherIdRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError( + `refundAddress '${DataConversionETHFeeAddData.invalidAddress}' is not a valid address`, + ); + }); + it('keeps the version used at creation', () => { + const newState = anyToEthProxy.applyActionToExtension( + {}, + { ...DataConversionETHFeeCreate.actionCreationFull, version: 'ABCD' }, + DataConversionETHFeeCreate.requestStateNoExtensions, + TestData.otherIdRaw.identity, + TestData.arbitraryTimestamp, + ); + expect(newState[anyToEthProxy.extensionId].version).toBe('ABCD'); + }); + + it('requires a version at creation', () => { + expect(() => { + anyToEthProxy.applyActionToExtension( + {}, + { ...DataConversionETHFeeCreate.actionCreationFull, version: '' }, + DataConversionETHFeeCreate.requestStateNoExtensions, + TestData.otherIdRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError('version is required at creation'); + }); + }); + + describe('applyActionToExtension/addPaymentAddress', () => { + it('can applyActionToExtensions of addPaymentAddress', () => { + // 'new extension state wrong' + expect( + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateCreatedEmpty.extensions, + DataConversionETHFeeAddData.actionAddPaymentAddress, + DataConversionETHFeeCreate.requestStateCreatedEmpty, + TestData.payeeRaw.identity, + TestData.arbitraryTimestamp, + ), + ).toEqual(DataConversionETHFeeAddData.extensionStateWithPaymentAfterCreation); + }); + + it('cannot applyActionToExtensions of addPaymentAddress without a previous state', () => { + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateNoExtensions.extensions, + DataConversionETHFeeAddData.actionAddPaymentAddress, + DataConversionETHFeeCreate.requestStateNoExtensions, + TestData.payeeRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError(`The extension should be created before receiving any other action`); + }); + + it('cannot applyActionToExtensions of addPaymentAddress without a payee', () => { + const previousState = Utils.deepCopy(DataConversionETHFeeCreate.requestStateCreatedEmpty); + previousState.payee = undefined; + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + previousState.extensions, + DataConversionETHFeeAddData.actionAddPaymentAddress, + previousState, + TestData.payeeRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError(`The request must have a payee`); + }); + + it('cannot applyActionToExtensions of addPaymentAddress signed by someone else than the payee', () => { + const previousState = Utils.deepCopy(DataConversionETHFeeCreate.requestStateCreatedEmpty); + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + previousState.extensions, + DataConversionETHFeeAddData.actionAddPaymentAddress, + previousState, + TestData.payerRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError(`The signer must be the payee`); + }); + + it('cannot applyActionToExtensions of addPaymentAddress with payment address already given', () => { + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestFullStateCreated.extensions, + DataConversionETHFeeAddData.actionAddPaymentAddress, + DataConversionETHFeeCreate.requestFullStateCreated, + TestData.payeeRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError(`Payment address already given`); + }); + + it('cannot applyActionToExtensions of addPaymentAddress with payment address not valid', () => { + const testnetPaymentAddress = Utils.deepCopy( + DataConversionETHFeeAddData.actionAddPaymentAddress, + ); + testnetPaymentAddress.parameters.paymentAddress = + DataConversionETHFeeAddData.invalidAddress; + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateCreatedEmpty.extensions, + testnetPaymentAddress, + DataConversionETHFeeCreate.requestStateCreatedEmpty, + TestData.payeeRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError( + `paymentAddress '${DataConversionETHFeeAddData.invalidAddress}' is not a valid address`, + ); + }); + }); + + describe('applyActionToExtension/addRefundAddress', () => { + it('can applyActionToExtensions of addRefundAddress', () => { + // 'new extension state wrong' + expect( + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateCreatedEmpty.extensions, + DataConversionETHFeeAddData.actionAddRefundAddress, + DataConversionETHFeeCreate.requestStateCreatedEmpty, + TestData.payerRaw.identity, + TestData.arbitraryTimestamp, + ), + ).toEqual(DataConversionETHFeeAddData.extensionStateWithRefundAfterCreation); + }); + + it('cannot applyActionToExtensions of addRefundAddress without a previous state', () => { + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateNoExtensions.extensions, + DataConversionETHFeeAddData.actionAddRefundAddress, + DataConversionETHFeeCreate.requestStateNoExtensions, + TestData.payerRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError(`The extension should be created before receiving any other action`); + }); + + it('cannot applyActionToExtensions of addRefundAddress without a payer', () => { + const previousState = Utils.deepCopy(DataConversionETHFeeCreate.requestStateCreatedEmpty); + previousState.payer = undefined; + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + previousState.extensions, + DataConversionETHFeeAddData.actionAddRefundAddress, + previousState, + TestData.payerRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError(`The request must have a payer`); + }); + + it('cannot applyActionToExtensions of addRefundAddress signed by someone else than the payer', () => { + const previousState = Utils.deepCopy(DataConversionETHFeeCreate.requestStateCreatedEmpty); + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + previousState.extensions, + DataConversionETHFeeAddData.actionAddRefundAddress, + previousState, + TestData.payeeRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError(`The signer must be the payer`); + }); + + it('cannot applyActionToExtensions of addRefundAddress with payment address already given', () => { + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestFullStateCreated.extensions, + DataConversionETHFeeAddData.actionAddRefundAddress, + DataConversionETHFeeCreate.requestFullStateCreated, + TestData.payerRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError(`Refund address already given`); + }); + + it('cannot applyActionToExtensions of addRefundAddress with refund address not valid', () => { + const testnetPaymentAddress = Utils.deepCopy( + DataConversionETHFeeAddData.actionAddRefundAddress, + ); + testnetPaymentAddress.parameters.refundAddress = + DataConversionETHFeeAddData.invalidAddress; + // 'must throw' + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateCreatedEmpty.extensions, + testnetPaymentAddress, + DataConversionETHFeeCreate.requestStateCreatedEmpty, + TestData.payeeRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError('refundAddress is not a valid address'); + }); + }); + + describe('applyActionToExtension/addFee', () => { + it('can applyActionToExtensions of addFee', () => { + // 'new extension state wrong' + expect( + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateCreatedEmpty.extensions, + DataConversionETHFeeAddData.actionAddFee, + DataConversionETHFeeCreate.requestStateCreatedEmpty, + TestData.payeeRaw.identity, + TestData.arbitraryTimestamp, + ), + ).toEqual(DataConversionETHFeeAddData.extensionStateWithFeeAfterCreation); + }); + + it('cannot applyActionToExtensions of addFee without a previous state', () => { + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateNoExtensions.extensions, + DataConversionETHFeeAddData.actionAddFee, + DataConversionETHFeeCreate.requestStateNoExtensions, + TestData.payeeRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError(`The extension should be created before receiving any other action`); + }); + + it('cannot applyActionToExtensions of addFee without a payee', () => { + const previousState = Utils.deepCopy(DataConversionETHFeeCreate.requestStateCreatedEmpty); + previousState.payee = undefined; + expect(() => { + anyToEthProxy.applyActionToExtension( + previousState.extensions, + DataConversionETHFeeAddData.actionAddFee, + previousState, + TestData.payeeRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError(`The request must have a payee`); + }); + + it('cannot applyActionToExtensions of addFee signed by someone else than the payee', () => { + const previousState = Utils.deepCopy(DataConversionETHFeeCreate.requestStateCreatedEmpty); + expect(() => { + anyToEthProxy.applyActionToExtension( + previousState.extensions, + DataConversionETHFeeAddData.actionAddFee, + previousState, + TestData.payerRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError(`The signer must be the payee`); + }); + + it('cannot applyActionToExtensions of addFee with fee data already given', () => { + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestFullStateCreated.extensions, + DataConversionETHFeeAddData.actionAddFee, + DataConversionETHFeeCreate.requestFullStateCreated, + TestData.payeeRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError(`Fee address already given`); + }); + + it('cannot applyActionToExtensions of addFee with fee address not valid', () => { + const testnetPaymentAddress = Utils.deepCopy(DataConversionETHFeeAddData.actionAddFee); + testnetPaymentAddress.parameters.feeAddress = DataConversionETHFeeAddData.invalidAddress; + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateCreatedEmpty.extensions, + testnetPaymentAddress, + DataConversionETHFeeCreate.requestStateCreatedEmpty, + TestData.payerRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError('feeAddress is not a valid address'); + }); + + it('cannot applyActionToExtensions of addFee with fee amount not valid', () => { + const testnetPaymentAddress = Utils.deepCopy(DataConversionETHFeeAddData.actionAddFee); + testnetPaymentAddress.parameters.feeAmount = 'invalid amount'; + expect(() => { + anyToEthProxy.applyActionToExtension( + DataConversionETHFeeCreate.requestStateCreatedEmpty.extensions, + testnetPaymentAddress, + DataConversionETHFeeCreate.requestStateCreatedEmpty, + TestData.payerRaw.identity, + TestData.arbitraryTimestamp, + ); + }).toThrowError('feeAmount is not a valid amount'); + }); + }); + }); +}); diff --git a/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts b/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts new file mode 100644 index 0000000000..09bc1d64e8 --- /dev/null +++ b/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts @@ -0,0 +1,203 @@ +import * as TestDataCreate from './fee-proxy-contract-create-data-generator'; + +import * as TestData from '../../test-data-generator'; + +import { ExtensionTypes, IdentityTypes, RequestLogicTypes } from '@requestnetwork/types'; + +export const arbitraryTimestamp = 1544426030; + +// --------------------------------------------------------------------- +// Mock addresses for testing generic address based payment networks +export const paymentAddress = '0x627306090abaB3A6e1400e9345bC60c78a8BEf57'; +export const refundAddress = '0xf17f52151EbEF6C7334FAD080c5704D77216b732'; +export const feeAddress = '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef'; +export const feeAmount = '2000000000000000000'; +export const invalidAddress = '0x not and address'; +// --------------------------------------------------------------------- +export const salt = 'ea3bc7caf64110ca'; +// actions +export const actionAddPaymentAddress = { + action: ExtensionTypes.PnFeeReferenceBased.ACTION.ADD_PAYMENT_ADDRESS, + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + parameters: { + paymentAddress, + }, +}; +export const actionAddRefundAddress = { + action: ExtensionTypes.PnFeeReferenceBased.ACTION.ADD_REFUND_ADDRESS, + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + parameters: { + refundAddress, + }, +}; +export const actionAddFee = { + action: ExtensionTypes.PnFeeReferenceBased.ACTION.ADD_FEE, + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + parameters: { + feeAddress, + feeAmount, + }, +}; + +// --------------------------------------------------------------------- +// extensions states +export const extensionStateWithPaymentAfterCreation = { + [ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY as string]: { + events: [ + { + name: ExtensionTypes.PnFeeReferenceBased.ACTION.CREATE, + parameters: {}, + timestamp: arbitraryTimestamp, + }, + { + name: ExtensionTypes.PnFeeReferenceBased.ACTION.ADD_PAYMENT_ADDRESS, + parameters: { + paymentAddress, + }, + timestamp: arbitraryTimestamp, + }, + ], + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: { + paymentAddress, + }, + version: '0.1.0', + }, +}; + +export const extensionStateWithRefundAfterCreation = { + [ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY as string]: { + events: [ + { + name: ExtensionTypes.PnFeeReferenceBased.ACTION.CREATE, + parameters: {}, + timestamp: arbitraryTimestamp, + }, + { + name: ExtensionTypes.PnFeeReferenceBased.ACTION.ADD_REFUND_ADDRESS, + parameters: { + refundAddress, + }, + timestamp: arbitraryTimestamp, + }, + ], + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: { + refundAddress, + }, + version: '0.1.0', + }, +}; + +export const extensionStateWithFeeAfterCreation = { + [ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY as string]: { + events: [ + { + name: ExtensionTypes.PnFeeReferenceBased.ACTION.CREATE, + parameters: {}, + timestamp: arbitraryTimestamp, + }, + { + name: ExtensionTypes.PnFeeReferenceBased.ACTION.ADD_FEE, + parameters: { + feeAddress, + feeAmount, + }, + timestamp: arbitraryTimestamp, + }, + ], + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: { + feeAddress, + feeAmount, + }, + version: '0.1.0', + }, +}; + +// --------------------------------------------------------------------- +// request states +export const requestStateCreatedEmptyThenAddPayment: RequestLogicTypes.IRequest = { + creator: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + currency: { + type: RequestLogicTypes.CURRENCY.ISO4217, + value: 'EUR', + }, + events: [ + { + actionSigner: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + name: RequestLogicTypes.ACTION_NAME.CREATE, + parameters: { + expectedAmount: '123400000000000000', + extensionsDataLength: 2, + isSignedRequest: false, + }, + timestamp: arbitraryTimestamp, + }, + ], + expectedAmount: TestData.arbitraryExpectedAmount, + extensions: extensionStateWithPaymentAfterCreation, + extensionsData: [TestDataCreate.actionCreationEmpty, actionAddPaymentAddress], + payee: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + payer: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payerRaw.address, + }, + requestId: TestData.requestIdMock, + state: RequestLogicTypes.STATE.CREATED, + timestamp: TestData.arbitraryTimestamp, + version: '0.1.0', +}; + +export const requestStateCreatedEmptyThenAddFee: RequestLogicTypes.IRequest = { + creator: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + currency: { + type: RequestLogicTypes.CURRENCY.ISO4217, + value: 'EUR', + }, + events: [ + { + actionSigner: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + name: RequestLogicTypes.ACTION_NAME.CREATE, + parameters: { + expectedAmount: '123400000000000000', + extensionsDataLength: 2, + isSignedRequest: false, + }, + timestamp: arbitraryTimestamp, + }, + ], + expectedAmount: TestData.arbitraryExpectedAmount, + extensions: extensionStateWithFeeAfterCreation, + extensionsData: [TestDataCreate.actionCreationEmpty, actionAddFee], + payee: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + payer: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payerRaw.address, + }, + requestId: TestData.requestIdMock, + state: RequestLogicTypes.STATE.CREATED, + timestamp: TestData.arbitraryTimestamp, + version: '0.1.0', +}; diff --git a/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-create-data-generator.ts b/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-create-data-generator.ts new file mode 100644 index 0000000000..0e8acf752b --- /dev/null +++ b/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-create-data-generator.ts @@ -0,0 +1,236 @@ +import * as TestData from '../../test-data-generator'; + +import { ExtensionTypes, IdentityTypes, RequestLogicTypes } from '@requestnetwork/types'; + +export const arbitraryTimestamp = 1544426030; + +// --------------------------------------------------------------------- +// Mock addresses for testing ETH payment networks +export const paymentAddress = '0x627306090abaB3A6e1400e9345bC60c78a8BEf57'; +export const refundAddress = '0xf17f52151EbEF6C7334FAD080c5704D77216b732'; +export const feeAddress = '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef'; +export const feeAmount = '2000000000000000000'; +export const invalidAddress = '0x not and address'; +export const network = 'mainnet'; +// --------------------------------------------------------------------- +export const salt = 'ea3bc7caf64110ca'; +// actions +export const actionCreationFull = { + action: 'create', + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + parameters: { + feeAddress, + feeAmount, + paymentAddress, + refundAddress, + salt, + network + }, + version: '0.1.0', +}; +export const actionCreationOnlyPayment = { + action: 'create', + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + parameters: { + paymentAddress, + network + }, + version: '0.1.0', +}; +export const actionCreationOnlyRefund = { + action: 'create', + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + parameters: { + refundAddress, + network + }, + version: '0.1.0', +}; +export const actionCreationOnlyFee = { + action: 'create', + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + parameters: { + feeAddress, + feeAmount, + network + }, + version: '0.1.0', +}; +export const actionCreationEmpty = { + action: 'create', + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + parameters: {}, + version: '0.1.0', +}; + +// --------------------------------------------------------------------- +// extensions states +export const extensionFullState = { + [ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY as string]: { + events: [ + { + name: 'create', + parameters: { + feeAddress, + feeAmount, + network, + paymentAddress, + refundAddress, + salt, + }, + timestamp: arbitraryTimestamp, + }, + ], + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: { + feeAddress, + feeAmount, + network, + paymentAddress, + refundAddress, + salt, + }, + version: '0.1.0', + }, +}; +export const extensionStateCreatedEmpty = { + [ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY as string]: { + events: [ + { + name: 'create', + parameters: {}, + timestamp: arbitraryTimestamp, + }, + ], + id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, + type: ExtensionTypes.TYPE.PAYMENT_NETWORK, + values: {}, + version: '0.1.0', + }, +}; + +// --------------------------------------------------------------------- +// request states +export const requestStateNoExtensions: RequestLogicTypes.IRequest = { + creator: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + currency: { + type: RequestLogicTypes.CURRENCY.ISO4217, + value: 'EUR', + }, + events: [ + { + actionSigner: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + name: RequestLogicTypes.ACTION_NAME.CREATE, + parameters: { + expectedAmount: '123400000000000000', + extensionsDataLength: 0, + isSignedRequest: false, + }, + timestamp: arbitraryTimestamp, + }, + ], + expectedAmount: TestData.arbitraryExpectedAmount, + extensions: {}, + extensionsData: [], + payee: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + payer: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payerRaw.address, + }, + requestId: TestData.requestIdMock, + state: RequestLogicTypes.STATE.CREATED, + timestamp: TestData.arbitraryTimestamp, + version: '0.1.0', +}; + +export const requestFullStateCreated: RequestLogicTypes.IRequest = { + creator: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + currency: { + type: RequestLogicTypes.CURRENCY.ISO4217, + value: 'EUR', + }, + events: [ + { + actionSigner: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + name: RequestLogicTypes.ACTION_NAME.CREATE, + parameters: { + expectedAmount: '123400000000000000', + extensionsDataLength: 1, + isSignedRequest: false, + }, + timestamp: arbitraryTimestamp, + }, + ], + expectedAmount: TestData.arbitraryExpectedAmount, + extensions: extensionFullState, + extensionsData: [actionCreationFull], + payee: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + payer: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payerRaw.address, + }, + requestId: TestData.requestIdMock, + state: RequestLogicTypes.STATE.CREATED, + timestamp: TestData.arbitraryTimestamp, + version: '0.1.0', +}; + +export const requestStateCreatedEmpty: RequestLogicTypes.IRequest = { + creator: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + currency: { + type: RequestLogicTypes.CURRENCY.ISO4217, + value: 'EUR', + }, + events: [ + { + actionSigner: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + name: RequestLogicTypes.ACTION_NAME.CREATE, + parameters: { + expectedAmount: '123400000000000000', + extensionsDataLength: 1, + isSignedRequest: false, + }, + timestamp: arbitraryTimestamp, + }, + ], + expectedAmount: TestData.arbitraryExpectedAmount, + extensions: extensionStateCreatedEmpty, + extensionsData: [actionCreationEmpty], + payee: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payeeRaw.address, + }, + payer: { + type: IdentityTypes.TYPE.ETHEREUM_ADDRESS, + value: TestData.payerRaw.address, + }, + requestId: TestData.requestIdMock, + state: RequestLogicTypes.STATE.CREATED, + timestamp: TestData.arbitraryTimestamp, + version: '0.1.0', +}; From b32ecdfe6a40b5786571c1b18a950bb987a13af3 Mon Sep 17 00:00:00 2001 From: vrolland Date: Wed, 29 Sep 2021 18:44:50 +0200 Subject: [PATCH 05/19] feat: Payment netwrok any-to-eth smartcontracts --- .../scripts/3_deploy_chainlink_contract.ts | 11 + .../scripts/eth-conversion-proxy.ts | 27 +++ .../src/contracts/EthConversionProxy.sol | 122 ++++++++++ .../artifacts/EthConversionProxy/0.1.0.json | 163 ++++++++++++++ .../lib/artifacts/EthConversionProxy/index.ts | 19 ++ .../src/lib/artifacts/index.ts | 1 + .../test/contracts/EthConversionProxy.test.ts | 211 ++++++++++++++++++ 7 files changed, 554 insertions(+) create mode 100644 packages/smart-contracts/scripts/eth-conversion-proxy.ts create mode 100644 packages/smart-contracts/src/contracts/EthConversionProxy.sol create mode 100644 packages/smart-contracts/src/lib/artifacts/EthConversionProxy/0.1.0.json create mode 100644 packages/smart-contracts/src/lib/artifacts/EthConversionProxy/index.ts create mode 100644 packages/smart-contracts/test/contracts/EthConversionProxy.test.ts diff --git a/packages/smart-contracts/scripts/3_deploy_chainlink_contract.ts b/packages/smart-contracts/scripts/3_deploy_chainlink_contract.ts index e159a826eb..4dd2b63022 100644 --- a/packages/smart-contracts/scripts/3_deploy_chainlink_contract.ts +++ b/packages/smart-contracts/scripts/3_deploy_chainlink_contract.ts @@ -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'; @@ -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(` @@ -73,5 +83,6 @@ export default async function deploy(args: any, hre: HardhatRuntimeEnvironment) ChainlinkConversionPath: ${conversionPathInstance.address} Erc20ConversionProxy: ${erc20ConversionAddress} Erc20SwapConversionProxy: ${erc20SwapConversionAddress} + EthConversionProxy: ${ethConversionProxyAddress} `); } diff --git a/packages/smart-contracts/scripts/eth-conversion-proxy.ts b/packages/smart-contracts/scripts/eth-conversion-proxy.ts new file mode 100644 index 0000000000..fe7fe742ee --- /dev/null +++ b/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, + ]); +} diff --git a/packages/smart-contracts/src/contracts/EthConversionProxy.sol b/packages/smart-contracts/src/contracts/EthConversionProxy.sol new file mode 100644 index 0000000000..21895c92e6 --- /dev/null +++ b/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; + } +} diff --git a/packages/smart-contracts/src/lib/artifacts/EthConversionProxy/0.1.0.json b/packages/smart-contracts/src/lib/artifacts/EthConversionProxy/0.1.0.json new file mode 100644 index 0000000000..5bd09d3552 --- /dev/null +++ b/packages/smart-contracts/src/lib/artifacts/EthConversionProxy/0.1.0.json @@ -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" + } + ] +} diff --git a/packages/smart-contracts/src/lib/artifacts/EthConversionProxy/index.ts b/packages/smart-contracts/src/lib/artifacts/EthConversionProxy/index.ts new file mode 100644 index 0000000000..813eff6775 --- /dev/null +++ b/packages/smart-contracts/src/lib/artifacts/EthConversionProxy/index.ts @@ -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( + { + '0.1.0': { + abi: ABI_0_1_0, + deployment: { + private: { + address: '0x8273e4B8ED6c78e252a9fCa5563Adfcc75C91b2A', + creationBlockNumber: 0, + }, + }, + }, + }, + '0.1.0', +); diff --git a/packages/smart-contracts/src/lib/artifacts/index.ts b/packages/smart-contracts/src/lib/artifacts/index.ts index ed59dbdfd6..b20aa2919a 100644 --- a/packages/smart-contracts/src/lib/artifacts/index.ts +++ b/packages/smart-contracts/src/lib/artifacts/index.ts @@ -9,6 +9,7 @@ export * from './ERC20SwapToPay'; export * from './Erc20SwapConversion'; export * from './EthereumProxy'; export * from './EthereumFeeProxy'; +export * from './EthConversionProxy'; /** * Request Storage diff --git a/packages/smart-contracts/test/contracts/EthConversionProxy.test.ts b/packages/smart-contracts/test/contracts/EthConversionProxy.test.ts new file mode 100644 index 0000000000..bf9e5246c8 --- /dev/null +++ b/packages/smart-contracts/test/contracts/EthConversionProxy.test.ts @@ -0,0 +1,211 @@ +import { ethers, network } from 'hardhat'; +import { + EthereumFeeProxy__factory, + EthConversionProxy__factory, + EthereumFeeProxy, + ChainlinkConversionPath, + EthConversionProxy, +} from '../../src/types'; +import { BigNumber, Signer } from 'ethers'; +import { expect, use } from 'chai'; +import { solidity } from 'ethereum-waffle'; +import { CurrencyManager } from '@requestnetwork/currency'; +import { chainlinkConversionPath } from '../../src/lib'; + +use(solidity); + +describe('contract: EthConversionProxy', () => { + let from: string; + let to: string; + let feeAddress: string; + let signer: Signer; + const smallAmountInFIAT = BigNumber.from('100000000'); + const smallerAmountInFIAT = BigNumber.from('10000000'); + const referenceExample = '0xaaaa'; + + const currencyManager = CurrencyManager.getDefault(); + + const ETH_address = currencyManager.fromSymbol('ETH')!.hash; + const USD_address = currencyManager.fromSymbol('USD')!.hash; + const EUR_address = currencyManager.fromSymbol('EUR')!.hash; + + let testEthConversionProxy: EthConversionProxy; + let ethFeeProxy: EthereumFeeProxy; + let chainlinkPath: ChainlinkConversionPath; + const provider = new ethers.providers.JsonRpcProvider(); + + before(async () => { + [from, to, feeAddress] = (await ethers.getSigners()).map((s) => s.address); + [signer] = await ethers.getSigners(); + + chainlinkPath = chainlinkConversionPath.connect(network.name, signer); + ethFeeProxy = await new EthereumFeeProxy__factory(signer).deploy(); + testEthConversionProxy = await new EthConversionProxy__factory(signer).deploy( + ethFeeProxy.address, + chainlinkPath.address, + ); + }); + + describe('transferWithReferenceAndFee', () => { + describe('transferWithReferenceAndFee with ETH', () => { + it('allows to transfer ETH for USD payment', async function () { + const path = [USD_address, ETH_address]; + + const fromOldBalance = await provider.getBalance(from); + const toOldBalance = await provider.getBalance(to); + const feeOldBalance = await provider.getBalance(feeAddress); + const conversionToPay = await chainlinkPath.getConversion(smallAmountInFIAT, path); + const conversionFees = await chainlinkPath.getConversion(smallerAmountInFIAT, path); + + const tx = testEthConversionProxy.transferWithReferenceAndFee( + to, + smallAmountInFIAT, + path, + referenceExample, + smallerAmountInFIAT, + feeAddress, + 0, + { + value: conversionFees.result.add(conversionToPay.result), + } + ) + + await expect(tx) + .to.emit(testEthConversionProxy, 'TransferWithConversionAndReference') + .withArgs( + smallAmountInFIAT, + ethers.utils.getAddress(path[0]), + ethers.utils.keccak256(referenceExample), + smallerAmountInFIAT, + '0', + ); + + const receipt = await (await tx).wait(); + + const fromNewBalance = await provider.getBalance(from); + const toNewBalance = await provider.getBalance(to); + const feeNewBalance = await provider.getBalance(feeAddress); + const contractBalance = await provider.getBalance(testEthConversionProxy.address); + + const toDiffBalance = BigNumber.from(toNewBalance) + .sub(toOldBalance) + .toString(); + const feeDiffBalance = BigNumber.from(feeNewBalance) + .sub(feeOldBalance) + .toString(); + + const gasPrice = (await provider.getFeeData()).gasPrice || 0; + // Check balance changes + expect(fromNewBalance.toString()).to.equals( + fromOldBalance.sub(conversionToPay.result).sub(conversionFees.result).sub(receipt.gasUsed.mul(gasPrice)).toString(), + ); + expect(toDiffBalance).to.equals(conversionToPay.result.toString()); + expect(feeDiffBalance).to.equals(conversionFees.result.toString()); + expect(contractBalance.toString()).to.equals("0") + }); + + it('allows to transfer ETH for EUR payment and extra msg.value', async function () { + const path = [EUR_address, USD_address, ETH_address]; + + const fromOldBalance = await provider.getBalance(from); + const toOldBalance = await provider.getBalance(to); + const feeOldBalance = await provider.getBalance(feeAddress); + const conversionToPay = await chainlinkPath.getConversion(smallAmountInFIAT, path); + const conversionFees = await chainlinkPath.getConversion(smallerAmountInFIAT, path); + + const tx = testEthConversionProxy.transferWithReferenceAndFee( + to, + smallAmountInFIAT, + path, + referenceExample, + smallerAmountInFIAT, + feeAddress, + 0, + { + value: conversionFees.result.add(conversionToPay.result)//.add("100000"), + } + ) + + await expect(tx) + .to.emit(testEthConversionProxy, 'TransferWithConversionAndReference') + .withArgs( + smallAmountInFIAT, + ethers.utils.getAddress(path[0]), + ethers.utils.keccak256(referenceExample), + smallerAmountInFIAT, + '0', + ); + + const receipt = await (await tx).wait(); + + const fromNewBalance = await provider.getBalance(from); + const toNewBalance = await provider.getBalance(to); + const feeNewBalance = await provider.getBalance(feeAddress); + const contractBalance = await provider.getBalance(testEthConversionProxy.address); + const contractFeeBalance = await provider.getBalance(ethFeeProxy.address); + + const toDiffBalance = BigNumber.from(toNewBalance) + .sub(toOldBalance) + .toString(); + const feeDiffBalance = BigNumber.from(feeNewBalance) + .sub(feeOldBalance) + .toString(); + + expect(contractBalance.toString()).to.equals("0"); + expect(contractFeeBalance.toString()).to.equals("0"); + + const gasPrice = (await provider.getFeeData()).gasPrice || 0; + // Check balance changes + expect(fromNewBalance.toString()).to.equals( + fromOldBalance.sub(conversionToPay.result).sub(conversionFees.result).sub(receipt.cumulativeGasUsed.mul(gasPrice)).toString(), + ); + expect(toDiffBalance).to.equals(conversionToPay.result.toString()); + expect(feeDiffBalance).to.equals(conversionFees.result.toString()); + }); + }); + + describe('transferWithReferenceAndFee with errors', () => { + it('cannot transfer if msg.value too low', async function () { + const path = [USD_address, ETH_address]; + + const conversionToPay = await chainlinkPath.getConversion(smallAmountInFIAT, path); + + await expect( + testEthConversionProxy.transferWithReferenceAndFee( + to, + smallAmountInFIAT, + path, + referenceExample, + smallerAmountInFIAT, + feeAddress, + 0, + { + value: conversionToPay.result, + } + ), + ).to.be.revertedWith('revert paymentProxy transferExactEthWithReferenceAndFee failed') + }); + + it('cannot transfer if rate is too old', async function () { + const path = [USD_address, ETH_address]; + + const conversionToPay = await chainlinkPath.getConversion(smallAmountInFIAT, path); + const conversionFees = await chainlinkPath.getConversion(smallerAmountInFIAT, path); + await expect( + testEthConversionProxy.transferWithReferenceAndFee( + to, + smallAmountInFIAT, + path, + referenceExample, + smallerAmountInFIAT, + feeAddress, + 1, // second + { + value: conversionFees.result.add(conversionToPay.result), + } + ), + ).to.be.revertedWith('revert aggregator rate is outdated') + }); + }); + }); +}); From 2cba7013ef5651cc6ded206341de0c149d6af268 Mon Sep 17 00:00:00 2001 From: vrolland Date: Wed, 29 Sep 2021 19:05:22 +0200 Subject: [PATCH 06/19] fix from YMA review --- .../payment-network-any-to-eth-proxy-0.1.0.md | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/packages/advanced-logic/specs/payment-network-any-to-eth-proxy-0.1.0.md b/packages/advanced-logic/specs/payment-network-any-to-eth-proxy-0.1.0.md index 0bbfb1d48a..575eb10cfc 100644 --- a/packages/advanced-logic/specs/payment-network-any-to-eth-proxy-0.1.0.md +++ b/packages/advanced-logic/specs/payment-network-any-to-eth-proxy-0.1.0.md @@ -1,23 +1,16 @@ -# Payment Network - Any currency - conversion to ethers with Fee - -You may be interested in this document if: - -- you want to create your own implementation of the Request protocol -- you are curious enough to dive and see what is under the hood of the Request protocol - -Prerequisite: Having read the advanced logic specification (see [here](./advanced-logic-specs-0.1.0.md)). +# Payment Network - Any currency conversion to ETH with Fee ## Description -This extension allows the payments and the refunds to be made in ethers on the Ethereum blockchain for a request made in others currencies. +This extension allows the payments and the refunds to be made in ETH on Ethereum or native tokens on EVM blockchains for a request made in others currencies. The rate is computing at the payment thanks to onchain oracles. This Payment Network is quite similar to the [ETH Fee Proxy Contract](./payment-network-eth-fee-proxy-contract-0.1.0.md) extension, with a rate conversion before the payment. -The payment is made through a proxy contract. This proxy contract call the [ETH Fee Proxy Contract](./payment-network-eth-fee-proxy-contract-0.1.0.md) to do the ethers transfer on behalf of the user. The contract ensures a link between an ethers transfer and a request through a `paymentReference`. +The payment is made through a proxy contract. This proxy contract call the [ETH Fee Proxy Contract](./payment-network-eth-fee-proxy-contract-0.1.0.md) to do the ETH transfer on behalf of the user. The contract ensures a link between an ETH transfer and a request through a `paymentReference`. This `paymentReference` consists of the last 8 bytes of a salted hash of the requestId: `last8Bytes(hash(lowercase(requestId + salt + address)))`: -The contract also ensures that the `feeAmount` amount of the ethers transfer will be forwarded to the `feeAddress`. +The contract also ensures that the `feeAmount` amount of the ETH transfer will be forwarded to the `feeAddress`. - `requestId` is the id of the request - `salt` is a random number with at least 8 bytes of randomness. It must be unique to each request From 5adabec09db39b16c15f2d404b6a276224296a21 Mon Sep 17 00:00:00 2001 From: vrolland Date: Wed, 29 Sep 2021 19:10:00 +0200 Subject: [PATCH 07/19] from YMA review 2 --- packages/types/src/extensions/pn-any-to-eth-types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/types/src/extensions/pn-any-to-eth-types.ts b/packages/types/src/extensions/pn-any-to-eth-types.ts index f824b7f869..65c9c1a32a 100644 --- a/packages/types/src/extensions/pn-any-to-eth-types.ts +++ b/packages/types/src/extensions/pn-any-to-eth-types.ts @@ -1,8 +1,8 @@ import * as Extension from '../extension-types'; import * as PnAnyFees from './pn-any-fee-reference-based-types'; -/** Any to ERC20e reference-based payment network extension interface */ -export interface IAnyToERC20 extends PnAnyFees.IFeeReferenceBased { +/** Any to ETH reference-based payment network extension interface */ +export interface IAnyToETH extends PnAnyFees.IFeeReferenceBased { createCreationAction: (creationParameters: ICreationParameters) => Extension.IAction; } From 4593adee1fd564c5532459327f7069d4235062a4 Mon Sep 17 00:00:00 2001 From: vrolland Date: Wed, 29 Sep 2021 19:23:48 +0200 Subject: [PATCH 08/19] add native token hash on the constructor --- .../scripts/3_deploy_chainlink_contract.ts | 1 + .../scripts/eth-conversion-proxy.ts | 8 +++++++- .../src/contracts/EthConversionProxy.sol | 11 ++++++----- .../artifacts/EthConversionProxy/0.1.0.json | 18 ++++++++++++++++++ .../test/contracts/EthConversionProxy.test.ts | 1 + 5 files changed, 33 insertions(+), 6 deletions(-) diff --git a/packages/smart-contracts/scripts/3_deploy_chainlink_contract.ts b/packages/smart-contracts/scripts/3_deploy_chainlink_contract.ts index 4dd2b63022..8b248b454f 100644 --- a/packages/smart-contracts/scripts/3_deploy_chainlink_contract.ts +++ b/packages/smart-contracts/scripts/3_deploy_chainlink_contract.ts @@ -72,6 +72,7 @@ export default async function deploy(args: any, hre: HardhatRuntimeEnvironment) ...args, chainlinkConversionPathAddress: conversionPathInstance.address, ethFeeProxyAddress: '0x3d49d1eF2adE060a33c6E6Aa213513A7EE9a6241', + nativeTokenHash: ETH_address, }, hre, ); diff --git a/packages/smart-contracts/scripts/eth-conversion-proxy.ts b/packages/smart-contracts/scripts/eth-conversion-proxy.ts index fe7fe742ee..65a9f56e70 100644 --- a/packages/smart-contracts/scripts/eth-conversion-proxy.ts +++ b/packages/smart-contracts/scripts/eth-conversion-proxy.ts @@ -2,7 +2,7 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { deployOne } from './deploy-one'; export default async function deploy( - args: { chainlinkConversionPathAddress?: string; ethFeeProxyAddress?: string }, + args: { chainlinkConversionPathAddress?: string; ethFeeProxyAddress?: string, nativeTokenHash?: string }, hre: HardhatRuntimeEnvironment, ) { const contractName = 'EthConversionProxy'; @@ -20,8 +20,14 @@ export default async function deploy( return; } + if (!args.nativeTokenHash) { + console.error(`Missing nativeTokenHash on ${hre.network.name}, cannot deploy ${contractName}.`); + return; + } + return deployOne(args, hre, contractName, [ args.ethFeeProxyAddress, args.chainlinkConversionPathAddress, + args.nativeTokenHash, ]); } diff --git a/packages/smart-contracts/src/contracts/EthConversionProxy.sol b/packages/smart-contracts/src/contracts/EthConversionProxy.sol index 21895c92e6..54d2445599 100644 --- a/packages/smart-contracts/src/contracts/EthConversionProxy.sol +++ b/packages/smart-contracts/src/contracts/EthConversionProxy.sol @@ -14,10 +14,12 @@ import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract EthConversionProxy is ReentrancyGuard { address public paymentProxy; ChainlinkConversionPath public chainlinkConversionPath; + address public nativeTokenHash; - constructor(address _paymentProxyAddress, address _chainlinkConversionPathAddress) { + constructor(address _paymentProxyAddress, address _chainlinkConversionPathAddress, address _nativeTokenHash) { paymentProxy = _paymentProxyAddress; chainlinkConversionPath = ChainlinkConversionPath(_chainlinkConversionPathAddress); + nativeTokenHash = _nativeTokenHash; } // Event to declare a conversion with a reference @@ -60,10 +62,9 @@ contract EthConversionProxy is ReentrancyGuard { external payable { - // Request currency hash for ether: 0xF5AF88e117747e87fC5929F2ff87221B1447652E require( - _path[_path.length - 1] == address(0xF5AF88e117747e87fC5929F2ff87221B1447652E), - "payment currency must be ethers" + _path[_path.length - 1] == nativeTokenHash, + "payment currency must be the native token" ); (uint256 amountToPay, uint256 amountToPayInFees) = getConversions( @@ -115,7 +116,7 @@ contract EthConversionProxy is ReentrancyGuard { "aggregator rate is outdated" ); - // Get the amount to pay in the crypto currency chosen + // Get the amount to pay in the native token amountToPay = (_requestAmount * rate) / decimals; amountToPayInFees = (_feeAmount * rate) /decimals; } diff --git a/packages/smart-contracts/src/lib/artifacts/EthConversionProxy/0.1.0.json b/packages/smart-contracts/src/lib/artifacts/EthConversionProxy/0.1.0.json index 5bd09d3552..b81f587a30 100644 --- a/packages/smart-contracts/src/lib/artifacts/EthConversionProxy/0.1.0.json +++ b/packages/smart-contracts/src/lib/artifacts/EthConversionProxy/0.1.0.json @@ -11,6 +11,11 @@ "internalType": "address", "name": "_chainlinkConversionPathAddress", "type": "address" + }, + { + "internalType": "address", + "name": "_nativeTokenHash", + "type": "address" } ], "stateMutability": "nonpayable", @@ -103,6 +108,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "nativeTokenHash", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "paymentProxy", diff --git a/packages/smart-contracts/test/contracts/EthConversionProxy.test.ts b/packages/smart-contracts/test/contracts/EthConversionProxy.test.ts index bf9e5246c8..773dfc601d 100644 --- a/packages/smart-contracts/test/contracts/EthConversionProxy.test.ts +++ b/packages/smart-contracts/test/contracts/EthConversionProxy.test.ts @@ -43,6 +43,7 @@ describe('contract: EthConversionProxy', () => { testEthConversionProxy = await new EthConversionProxy__factory(signer).deploy( ethFeeProxy.address, chainlinkPath.address, + ETH_address, ); }); From 577a48f4ddf45f7bbeea3dc30fb6c94c2c4f627c Mon Sep 17 00:00:00 2001 From: Vincent <4611986+vrolland@users.noreply.github.com> Date: Thu, 30 Sep 2021 09:29:57 +0200 Subject: [PATCH 09/19] Update packages/advanced-logic/specs/payment-network-any-to-eth-proxy-0.1.0.md Co-authored-by: Yo <56731761+yomarion@users.noreply.github.com> --- .../specs/payment-network-any-to-eth-proxy-0.1.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/advanced-logic/specs/payment-network-any-to-eth-proxy-0.1.0.md b/packages/advanced-logic/specs/payment-network-any-to-eth-proxy-0.1.0.md index 575eb10cfc..37cae39185 100644 --- a/packages/advanced-logic/specs/payment-network-any-to-eth-proxy-0.1.0.md +++ b/packages/advanced-logic/specs/payment-network-any-to-eth-proxy-0.1.0.md @@ -3,7 +3,7 @@ ## Description This extension allows the payments and the refunds to be made in ETH on Ethereum or native tokens on EVM blockchains for a request made in others currencies. -The rate is computing at the payment thanks to onchain oracles. +The rate is computed at the payment thanks to onchain oracles. This Payment Network is quite similar to the [ETH Fee Proxy Contract](./payment-network-eth-fee-proxy-contract-0.1.0.md) extension, with a rate conversion before the payment. The payment is made through a proxy contract. This proxy contract call the [ETH Fee Proxy Contract](./payment-network-eth-fee-proxy-contract-0.1.0.md) to do the ETH transfer on behalf of the user. The contract ensures a link between an ETH transfer and a request through a `paymentReference`. From dfd5fe28c612427804d9f113abaa5ee8d1201012 Mon Sep 17 00:00:00 2001 From: vrolland Date: Thu, 30 Sep 2021 09:33:55 +0200 Subject: [PATCH 10/19] rename chainlink to conversion --- .../src/extensions/payment-network/any-to-erc20-proxy.ts | 2 +- .../src/extensions/payment-network/any-to-eth-proxy.ts | 2 +- ...pported-currencies.ts => conversion-supported-currencies.ts} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/advanced-logic/src/extensions/payment-network/{chainlink-supported-currencies.ts => conversion-supported-currencies.ts} (100%) diff --git a/packages/advanced-logic/src/extensions/payment-network/any-to-erc20-proxy.ts b/packages/advanced-logic/src/extensions/payment-network/any-to-erc20-proxy.ts index 0f79106711..7380636d19 100644 --- a/packages/advanced-logic/src/extensions/payment-network/any-to-erc20-proxy.ts +++ b/packages/advanced-logic/src/extensions/payment-network/any-to-erc20-proxy.ts @@ -1,6 +1,6 @@ import { ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types'; import Erc20FeeProxyPaymentNetwork from './erc20/fee-proxy-contract'; -import { supportedCurrencies } from './chainlink-supported-currencies'; +import { supportedCurrencies } from './conversion-supported-currencies'; const CURRENT_VERSION = '0.1.0'; // Default network if the storage data does not give any diff --git a/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts b/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts index 3aa9a4ee17..eaa917f3a8 100644 --- a/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts +++ b/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts @@ -1,6 +1,6 @@ import { ExtensionTypes, RequestLogicTypes } from '@requestnetwork/types'; import EthereumFeeProxyPaymentNetwork from './ethereum/fee-proxy-contract'; -import { supportedCurrencies } from './chainlink-supported-currencies'; +import { supportedCurrencies } from './conversion-supported-currencies'; const CURRENT_VERSION = '0.1.0'; // Default network if the storage data does not give any diff --git a/packages/advanced-logic/src/extensions/payment-network/chainlink-supported-currencies.ts b/packages/advanced-logic/src/extensions/payment-network/conversion-supported-currencies.ts similarity index 100% rename from packages/advanced-logic/src/extensions/payment-network/chainlink-supported-currencies.ts rename to packages/advanced-logic/src/extensions/payment-network/conversion-supported-currencies.ts From b9b41a57dfca91f980eca2f88eae363507ac018f Mon Sep 17 00:00:00 2001 From: vrolland Date: Thu, 30 Sep 2021 09:41:07 +0200 Subject: [PATCH 11/19] remove defult network --- .../src/extensions/payment-network/any-to-erc20-proxy.ts | 6 ++---- .../src/extensions/payment-network/any-to-eth-proxy.ts | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/advanced-logic/src/extensions/payment-network/any-to-erc20-proxy.ts b/packages/advanced-logic/src/extensions/payment-network/any-to-erc20-proxy.ts index 7380636d19..0e35193957 100644 --- a/packages/advanced-logic/src/extensions/payment-network/any-to-erc20-proxy.ts +++ b/packages/advanced-logic/src/extensions/payment-network/any-to-erc20-proxy.ts @@ -3,8 +3,6 @@ import Erc20FeeProxyPaymentNetwork from './erc20/fee-proxy-contract'; import { supportedCurrencies } from './conversion-supported-currencies'; const CURRENT_VERSION = '0.1.0'; -// Default network if the storage data does not give any -const DEFAULT_NETWORK = 'mainnet'; export default class AnyToErc20ProxyPaymentNetwork extends Erc20FeeProxyPaymentNetwork { public constructor( @@ -94,7 +92,7 @@ export default class AnyToErc20ProxyPaymentNetwork extends Erc20FeeProxyPaymentN paymentAddress: extensionAction.parameters.paymentAddress, refundAddress: extensionAction.parameters.refundAddress, salt: extensionAction.parameters.salt, - network: extensionAction.parameters.network || DEFAULT_NETWORK, + network: extensionAction.parameters.network, acceptedTokens: extensionAction.parameters.acceptedTokens, maxRateTimespan: extensionAction.parameters.maxRateTimespan, }, @@ -103,7 +101,7 @@ export default class AnyToErc20ProxyPaymentNetwork extends Erc20FeeProxyPaymentN ], values: { ...feePNCreationAction.values, - network: extensionAction.parameters.network || DEFAULT_NETWORK, + network: extensionAction.parameters.network, acceptedTokens: extensionAction.parameters.acceptedTokens, maxRateTimespan: extensionAction.parameters.maxRateTimespan, }, diff --git a/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts b/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts index eaa917f3a8..607c4d9477 100644 --- a/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts +++ b/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts @@ -3,8 +3,6 @@ import EthereumFeeProxyPaymentNetwork from './ethereum/fee-proxy-contract'; import { supportedCurrencies } from './conversion-supported-currencies'; const CURRENT_VERSION = '0.1.0'; -// Default network if the storage data does not give any -const DEFAULT_NETWORK = 'mainnet'; export default class AnyToEthProxyPaymentNetwork extends EthereumFeeProxyPaymentNetwork { public constructor( @@ -63,7 +61,7 @@ export default class AnyToEthProxyPaymentNetwork extends EthereumFeeProxyPayment paymentAddress: extensionAction.parameters.paymentAddress, refundAddress: extensionAction.parameters.refundAddress, salt: extensionAction.parameters.salt, - network: extensionAction.parameters.network || DEFAULT_NETWORK, + network: extensionAction.parameters.network, maxRateTimespan: extensionAction.parameters.maxRateTimespan, }, timestamp, @@ -71,7 +69,7 @@ export default class AnyToEthProxyPaymentNetwork extends EthereumFeeProxyPayment ], values: { ...feePNCreationAction.values, - network: extensionAction.parameters.network || DEFAULT_NETWORK, + network: extensionAction.parameters.network, maxRateTimespan: extensionAction.parameters.maxRateTimespan, }, }; From ba44ed2fea55b875044f28a1539002718d28ec06 Mon Sep 17 00:00:00 2001 From: vrolland Date: Thu, 30 Sep 2021 09:58:51 +0200 Subject: [PATCH 12/19] network mandatory --- .../payment-network/any-to-eth-proxy.ts | 5 +++-- .../payment-network/any-to-eth-proxy.test.ts | 8 ++++---- .../any-to-eth-proxy-add-data-generator.ts | 16 +++++++++++++--- .../any-to-eth-proxy-create-data-generator.ts | 8 ++++++-- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts b/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts index 607c4d9477..360c1b4b36 100644 --- a/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts +++ b/packages/advanced-logic/src/extensions/payment-network/any-to-eth-proxy.ts @@ -88,9 +88,10 @@ export default class AnyToEthProxyPaymentNetwork extends EthereumFeeProxyPayment const network = extensionAction.parameters.network || request.extensions[this.extensionId]?.values.network; - // Nothing can be validated if the network has not been given yet if (!network) { - return; + throw new Error( + `The network must be provided from the creation action or from the extension state`, + ); } if (!supportedCurrencies[network]) { diff --git a/packages/advanced-logic/test/extensions/payment-network/any-to-eth-proxy.test.ts b/packages/advanced-logic/test/extensions/payment-network/any-to-eth-proxy.test.ts index aeac6b0fc7..88c218d9cf 100644 --- a/packages/advanced-logic/test/extensions/payment-network/any-to-eth-proxy.test.ts +++ b/packages/advanced-logic/test/extensions/payment-network/any-to-eth-proxy.test.ts @@ -9,7 +9,7 @@ import * as TestData from '../../utils/test-data-generator'; const anyToEthProxy = new AnyToEthProxy(); /* eslint-disable @typescript-eslint/no-unused-expressions */ -describe('extensions/payment-network/ethereum/any-to-eth-fee-proxy-contract', () => { +describe.only('extensions/payment-network/ethereum/any-to-eth-fee-proxy-contract', () => { describe('createCreationAction', () => { it('can create a create action with all parameters', () => { // 'extension data is wrong' @@ -460,7 +460,7 @@ describe('extensions/payment-network/ethereum/any-to-eth-fee-proxy-contract', () TestData.payeeRaw.identity, TestData.arbitraryTimestamp, ); - }).toThrowError(`The extension should be created before receiving any other action`); + }).toThrowError(`The network must be provided from the creation action or from the extension state`); }); it('cannot applyActionToExtensions of addPaymentAddress without a payee', () => { @@ -550,7 +550,7 @@ describe('extensions/payment-network/ethereum/any-to-eth-fee-proxy-contract', () TestData.payerRaw.identity, TestData.arbitraryTimestamp, ); - }).toThrowError(`The extension should be created before receiving any other action`); + }).toThrowError(`The network must be provided from the creation action or from the extension state`); }); it('cannot applyActionToExtensions of addRefundAddress without a payer', () => { @@ -637,7 +637,7 @@ describe('extensions/payment-network/ethereum/any-to-eth-fee-proxy-contract', () TestData.payeeRaw.identity, TestData.arbitraryTimestamp, ); - }).toThrowError(`The extension should be created before receiving any other action`); + }).toThrowError(`The network must be provided from the creation action or from the extension state`); }); it('cannot applyActionToExtensions of addFee without a payee', () => { diff --git a/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts b/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts index 09bc1d64e8..a175350cf8 100644 --- a/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts +++ b/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts @@ -13,6 +13,7 @@ export const refundAddress = '0xf17f52151EbEF6C7334FAD080c5704D77216b732'; export const feeAddress = '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef'; export const feeAmount = '2000000000000000000'; export const invalidAddress = '0x not and address'; +const network = 'mainnet'; // --------------------------------------------------------------------- export const salt = 'ea3bc7caf64110ca'; // actions @@ -46,7 +47,9 @@ export const extensionStateWithPaymentAfterCreation = { events: [ { name: ExtensionTypes.PnFeeReferenceBased.ACTION.CREATE, - parameters: {}, + parameters: { + network + }, timestamp: arbitraryTimestamp, }, { @@ -61,6 +64,7 @@ export const extensionStateWithPaymentAfterCreation = { type: ExtensionTypes.TYPE.PAYMENT_NETWORK, values: { paymentAddress, + network, }, version: '0.1.0', }, @@ -71,7 +75,9 @@ export const extensionStateWithRefundAfterCreation = { events: [ { name: ExtensionTypes.PnFeeReferenceBased.ACTION.CREATE, - parameters: {}, + parameters: { + network + }, timestamp: arbitraryTimestamp, }, { @@ -86,6 +92,7 @@ export const extensionStateWithRefundAfterCreation = { type: ExtensionTypes.TYPE.PAYMENT_NETWORK, values: { refundAddress, + network, }, version: '0.1.0', }, @@ -96,7 +103,9 @@ export const extensionStateWithFeeAfterCreation = { events: [ { name: ExtensionTypes.PnFeeReferenceBased.ACTION.CREATE, - parameters: {}, + parameters: { + network + }, timestamp: arbitraryTimestamp, }, { @@ -113,6 +122,7 @@ export const extensionStateWithFeeAfterCreation = { values: { feeAddress, feeAmount, + network, }, version: '0.1.0', }, diff --git a/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-create-data-generator.ts b/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-create-data-generator.ts index 0e8acf752b..99f1409b84 100644 --- a/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-create-data-generator.ts +++ b/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-create-data-generator.ts @@ -99,13 +99,17 @@ export const extensionStateCreatedEmpty = { events: [ { name: 'create', - parameters: {}, + parameters: { + network: 'mainnet', + }, timestamp: arbitraryTimestamp, }, ], id: ExtensionTypes.ID.PAYMENT_NETWORK_ANY_TO_ETH_PROXY, type: ExtensionTypes.TYPE.PAYMENT_NETWORK, - values: {}, + values: { + network: 'mainnet', + }, version: '0.1.0', }, }; From 8a3e8624bbd438c2b7ca316ceff146805bbd4e7f Mon Sep 17 00:00:00 2001 From: vrolland Date: Thu, 30 Sep 2021 10:07:49 +0200 Subject: [PATCH 13/19] fix import --- packages/types/src/extension-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/extension-types.ts b/packages/types/src/extension-types.ts index d591638baa..e43efc3ba6 100644 --- a/packages/types/src/extension-types.ts +++ b/packages/types/src/extension-types.ts @@ -4,7 +4,7 @@ import * as PnAnyDeclarative from './extensions/pn-any-declarative-types'; import * as PnFeeReferenceBased from './extensions/pn-any-fee-reference-based-types'; import * as PnReferenceBased from './extensions/pn-any-reference-based-types'; import * as PnAnyToErc20 from './extensions/pn-any-to-er20-types'; -import * as PnAnyToEth from './extensions/pn-any-to-er20-types'; +import * as PnAnyToEth from './extensions/pn-any-to-eth-types'; import * as Identity from './identity-types'; import * as RequestLogic from './request-logic-types'; From a52fbade543dc7bbaf18e5ce7d44a0cd252b778f Mon Sep 17 00:00:00 2001 From: vrolland Date: Thu, 30 Sep 2021 10:08:31 +0200 Subject: [PATCH 14/19] fix commit --- packages/types/src/extensions/pn-any-to-er20-types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/types/src/extensions/pn-any-to-er20-types.ts b/packages/types/src/extensions/pn-any-to-er20-types.ts index 6fa183b68c..d65d760aa7 100644 --- a/packages/types/src/extensions/pn-any-to-er20-types.ts +++ b/packages/types/src/extensions/pn-any-to-er20-types.ts @@ -1,7 +1,7 @@ import * as Extension from '../extension-types'; import * as PnAnyFees from './pn-any-fee-reference-based-types'; -/** Any to ERC20e reference-based payment network extension interface */ +/** Any to ERC20 reference-based payment network extension interface */ export interface IAnyToERC20 extends PnAnyFees.IFeeReferenceBased { createCreationAction: (creationParameters: ICreationParameters) => Extension.IAction; } From caeae86ab81391d351ff2c5309f9e0631078c353 Mon Sep 17 00:00:00 2001 From: vrolland Date: Thu, 30 Sep 2021 12:26:23 +0200 Subject: [PATCH 15/19] rename test var --- .../ethereum/any-to-eth-proxy-add-data-generator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts b/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts index a175350cf8..32ebf31321 100644 --- a/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts +++ b/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts @@ -12,7 +12,7 @@ export const paymentAddress = '0x627306090abaB3A6e1400e9345bC60c78a8BEf57'; export const refundAddress = '0xf17f52151EbEF6C7334FAD080c5704D77216b732'; export const feeAddress = '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef'; export const feeAmount = '2000000000000000000'; -export const invalidAddress = '0x not and address'; +export const invalidAddress = '0x not an address'; const network = 'mainnet'; // --------------------------------------------------------------------- export const salt = 'ea3bc7caf64110ca'; From af3961e9342a2baae651d50e26694f8d162fe12f Mon Sep 17 00:00:00 2001 From: vrolland Date: Thu, 30 Sep 2021 16:18:34 +0200 Subject: [PATCH 16/19] from BJU revieuw --- .../conversion-supported-currencies.ts | 8 ++++---- .../payment-network/any-to-erc20-proxy.test.ts | 10 +++++----- .../payment-network/any-to-eth-proxy.test.ts | 16 ++++++++-------- .../erc20/fee-proxy-contract.test.ts | 12 ++++++------ .../ethereum/fee-proxy-contract.test.ts | 12 ++++++------ .../any-to-eth-proxy-add-data-generator.ts | 11 ++++++----- 6 files changed, 35 insertions(+), 34 deletions(-) diff --git a/packages/advanced-logic/src/extensions/payment-network/conversion-supported-currencies.ts b/packages/advanced-logic/src/extensions/payment-network/conversion-supported-currencies.ts index 5decd0c19b..f2247371d4 100644 --- a/packages/advanced-logic/src/extensions/payment-network/conversion-supported-currencies.ts +++ b/packages/advanced-logic/src/extensions/payment-network/conversion-supported-currencies.ts @@ -37,13 +37,13 @@ export const supportedCurrencies: Record { describe('createCreationAction', () => { it('can create a create action with all parameters', () => { - // 'extension data is wrong' + expect( anyToErc20Proxy.createCreationAction({ feeAddress: '0x0000000000000000000000000000000000000001', @@ -42,7 +42,7 @@ describe('extensions/payment-network/erc20/any-to-erc20-fee-proxy-contract', () }); it('can create a create action without fee parameters', () => { - // 'extension data is wrong' + expect( anyToErc20Proxy.createCreationAction({ paymentAddress: '0x0000000000000000000000000000000000000001', @@ -222,7 +222,7 @@ describe('extensions/payment-network/erc20/any-to-erc20-fee-proxy-contract', () describe('createAddPaymentAddressAction', () => { it('can createAddPaymentAddressAction', () => { - // 'extension data is wrong' + expect( anyToErc20Proxy.createAddPaymentAddressAction({ paymentAddress: '0x0000000000000000000000000000000000000001', @@ -248,7 +248,7 @@ describe('extensions/payment-network/erc20/any-to-erc20-fee-proxy-contract', () describe('createAddRefundAddressAction', () => { it('can createAddRefundAddressAction', () => { - // 'extension data is wrong' + expect( anyToErc20Proxy.createAddRefundAddressAction({ refundAddress: '0x0000000000000000000000000000000000000002', @@ -274,7 +274,7 @@ describe('extensions/payment-network/erc20/any-to-erc20-fee-proxy-contract', () describe('createAddFeeAction', () => { it('can createAddFeeAction', () => { - // 'extension data is wrong' + expect( anyToErc20Proxy.createAddFeeAction({ feeAddress: '0x0000000000000000000000000000000000000002', diff --git a/packages/advanced-logic/test/extensions/payment-network/any-to-eth-proxy.test.ts b/packages/advanced-logic/test/extensions/payment-network/any-to-eth-proxy.test.ts index 88c218d9cf..9abfee87dc 100644 --- a/packages/advanced-logic/test/extensions/payment-network/any-to-eth-proxy.test.ts +++ b/packages/advanced-logic/test/extensions/payment-network/any-to-eth-proxy.test.ts @@ -12,7 +12,7 @@ const anyToEthProxy = new AnyToEthProxy(); describe.only('extensions/payment-network/ethereum/any-to-eth-fee-proxy-contract', () => { describe('createCreationAction', () => { it('can create a create action with all parameters', () => { - // 'extension data is wrong' + expect( anyToEthProxy.createCreationAction({ feeAddress: '0x0000000000000000000000000000000000000001', @@ -40,7 +40,7 @@ describe.only('extensions/payment-network/ethereum/any-to-eth-fee-proxy-contract }); it('can create a create action without fee parameters', () => { - // 'extension data is wrong' + expect( anyToEthProxy.createCreationAction({ paymentAddress: '0x0000000000000000000000000000000000000001', @@ -126,7 +126,7 @@ describe.only('extensions/payment-network/ethereum/any-to-eth-fee-proxy-contract }).toThrowError('network kovan not supported'); }); - it('cannot applyActionToExtensions of creation on a non supported currency', () => { + it('cannot applyActionToExtensions of creation with an invalid network', () => { const requestCreatedNoExtension: RequestLogicTypes.IRequest = Utils.deepCopy( TestData.requestCreatedNoExtension, ); @@ -182,7 +182,7 @@ describe.only('extensions/payment-network/ethereum/any-to-eth-fee-proxy-contract describe('createAddPaymentAddressAction', () => { it('can createAddPaymentAddressAction', () => { - // 'extension data is wrong' + expect( anyToEthProxy.createAddPaymentAddressAction({ paymentAddress: '0x0000000000000000000000000000000000000001', @@ -208,7 +208,7 @@ describe.only('extensions/payment-network/ethereum/any-to-eth-fee-proxy-contract describe('createAddRefundAddressAction', () => { it('can createAddRefundAddressAction', () => { - // 'extension data is wrong' + expect( anyToEthProxy.createAddRefundAddressAction({ refundAddress: '0x0000000000000000000000000000000000000002', @@ -222,7 +222,7 @@ describe.only('extensions/payment-network/ethereum/any-to-eth-fee-proxy-contract }); }); - it('cannot createAddRefundAddressAction with payment address not an ethereum address', () => { + it('cannot createAddRefundAddressAction with refund address not an ethereum address', () => { // 'must throw' expect(() => { anyToEthProxy.createAddRefundAddressAction({ @@ -234,7 +234,7 @@ describe.only('extensions/payment-network/ethereum/any-to-eth-fee-proxy-contract describe('createAddFeeAction', () => { it('can createAddFeeAction', () => { - // 'extension data is wrong' + expect( anyToEthProxy.createAddFeeAction({ feeAddress: '0x0000000000000000000000000000000000000002', @@ -250,7 +250,7 @@ describe.only('extensions/payment-network/ethereum/any-to-eth-fee-proxy-contract }); }); - it('cannot createAddFeeAddressAction with payment address not an ethereum address', () => { + it('cannot createAddFeeAddressAction with fee address not an ethereum address', () => { // 'must throw' expect(() => { anyToEthProxy.createAddFeeAction({ diff --git a/packages/advanced-logic/test/extensions/payment-network/erc20/fee-proxy-contract.test.ts b/packages/advanced-logic/test/extensions/payment-network/erc20/fee-proxy-contract.test.ts index 626fd76343..713833b53e 100644 --- a/packages/advanced-logic/test/extensions/payment-network/erc20/fee-proxy-contract.test.ts +++ b/packages/advanced-logic/test/extensions/payment-network/erc20/fee-proxy-contract.test.ts @@ -13,7 +13,7 @@ const erc20FeeProxyContract = new Erc20FeeProxyContract(); describe('extensions/payment-network/erc20/fee-proxy-contract', () => { describe('createCreationAction', () => { it('can create a create action with all parameters', () => { - // 'extension data is wrong' + expect( erc20FeeProxyContract.createCreationAction({ feeAddress: '0x0000000000000000000000000000000000000001', @@ -37,7 +37,7 @@ describe('extensions/payment-network/erc20/fee-proxy-contract', () => { }); it('can create a create action without fee parameters', () => { - // 'extension data is wrong' + expect( erc20FeeProxyContract.createCreationAction({ paymentAddress: '0x0000000000000000000000000000000000000001', @@ -57,7 +57,7 @@ describe('extensions/payment-network/erc20/fee-proxy-contract', () => { }); it('can create a create action with only salt', () => { - // 'extension data is wrong' + expect( erc20FeeProxyContract.createCreationAction({ salt: 'ea3bc7caf64110ca', @@ -119,7 +119,7 @@ describe('extensions/payment-network/erc20/fee-proxy-contract', () => { describe('createAddPaymentAddressAction', () => { it('can createAddPaymentAddressAction', () => { - // 'extension data is wrong' + expect( erc20FeeProxyContract.createAddPaymentAddressAction({ paymentAddress: '0x0000000000000000000000000000000000000001', @@ -145,7 +145,7 @@ describe('extensions/payment-network/erc20/fee-proxy-contract', () => { describe('createAddRefundAddressAction', () => { it('can createAddRefundAddressAction', () => { - // 'extension data is wrong' + expect( erc20FeeProxyContract.createAddRefundAddressAction({ refundAddress: '0x0000000000000000000000000000000000000002', @@ -171,7 +171,7 @@ describe('extensions/payment-network/erc20/fee-proxy-contract', () => { describe('createAddFeeAction', () => { it('can createAddFeeAction', () => { - // 'extension data is wrong' + expect( erc20FeeProxyContract.createAddFeeAction({ feeAddress: '0x0000000000000000000000000000000000000002', diff --git a/packages/advanced-logic/test/extensions/payment-network/ethereum/fee-proxy-contract.test.ts b/packages/advanced-logic/test/extensions/payment-network/ethereum/fee-proxy-contract.test.ts index 640c9addf6..6763d9b941 100644 --- a/packages/advanced-logic/test/extensions/payment-network/ethereum/fee-proxy-contract.test.ts +++ b/packages/advanced-logic/test/extensions/payment-network/ethereum/fee-proxy-contract.test.ts @@ -13,7 +13,7 @@ const ethFeeProxyContract = new EthereumFeeProxyContract(); describe('extensions/payment-network/ethereum/fee-proxy-contract', () => { describe('createCreationAction', () => { it('can create a create action with all parameters', () => { - // 'extension data is wrong' + expect( ethFeeProxyContract.createCreationAction({ feeAddress: '0x0000000000000000000000000000000000000001', @@ -37,7 +37,7 @@ describe('extensions/payment-network/ethereum/fee-proxy-contract', () => { }); it('can create a create action without fee parameters', () => { - // 'extension data is wrong' + expect( ethFeeProxyContract.createCreationAction({ paymentAddress: '0x0000000000000000000000000000000000000001', @@ -57,7 +57,7 @@ describe('extensions/payment-network/ethereum/fee-proxy-contract', () => { }); it('can create a create action with only salt', () => { - // 'extension data is wrong' + expect( ethFeeProxyContract.createCreationAction({ salt: 'ea3bc7caf64110ca', @@ -119,7 +119,7 @@ describe('extensions/payment-network/ethereum/fee-proxy-contract', () => { describe('createAddPaymentAddressAction', () => { it('can createAddPaymentAddressAction', () => { - // 'extension data is wrong' + expect( ethFeeProxyContract.createAddPaymentAddressAction({ paymentAddress: '0x0000000000000000000000000000000000000001', @@ -145,7 +145,7 @@ describe('extensions/payment-network/ethereum/fee-proxy-contract', () => { describe('createAddRefundAddressAction', () => { it('can createAddRefundAddressAction', () => { - // 'extension data is wrong' + expect( ethFeeProxyContract.createAddRefundAddressAction({ refundAddress: '0x0000000000000000000000000000000000000002', @@ -171,7 +171,7 @@ describe('extensions/payment-network/ethereum/fee-proxy-contract', () => { describe('createAddFeeAction', () => { it('can createAddFeeAction', () => { - // 'extension data is wrong' + expect( ethFeeProxyContract.createAddFeeAction({ feeAddress: '0x0000000000000000000000000000000000000002', diff --git a/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts b/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts index 32ebf31321..aed07010e9 100644 --- a/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts +++ b/packages/advanced-logic/test/utils/payment-network/ethereum/any-to-eth-proxy-add-data-generator.ts @@ -14,6 +14,7 @@ export const feeAddress = '0xC5fdf4076b8F3A5357c5E395ab970B5B54098Fef'; export const feeAmount = '2000000000000000000'; export const invalidAddress = '0x not an address'; const network = 'mainnet'; +const version = '0.1.0'; // --------------------------------------------------------------------- export const salt = 'ea3bc7caf64110ca'; // actions @@ -66,7 +67,7 @@ export const extensionStateWithPaymentAfterCreation = { paymentAddress, network, }, - version: '0.1.0', + version, }, }; @@ -94,7 +95,7 @@ export const extensionStateWithRefundAfterCreation = { refundAddress, network, }, - version: '0.1.0', + version, }, }; @@ -124,7 +125,7 @@ export const extensionStateWithFeeAfterCreation = { feeAmount, network, }, - version: '0.1.0', + version, }, }; @@ -168,7 +169,7 @@ export const requestStateCreatedEmptyThenAddPayment: RequestLogicTypes.IRequest requestId: TestData.requestIdMock, state: RequestLogicTypes.STATE.CREATED, timestamp: TestData.arbitraryTimestamp, - version: '0.1.0', + version, }; export const requestStateCreatedEmptyThenAddFee: RequestLogicTypes.IRequest = { @@ -209,5 +210,5 @@ export const requestStateCreatedEmptyThenAddFee: RequestLogicTypes.IRequest = { requestId: TestData.requestIdMock, state: RequestLogicTypes.STATE.CREATED, timestamp: TestData.arbitraryTimestamp, - version: '0.1.0', + version, }; From ebb52084b9df5a09674783cacd8fa98f879f53a4 Mon Sep 17 00:00:00 2001 From: Vincent <4611986+vrolland@users.noreply.github.com> Date: Mon, 4 Oct 2021 14:54:14 +0200 Subject: [PATCH 17/19] Update packages/smart-contracts/src/contracts/EthConversionProxy.sol Co-authored-by: Benjamin Levesque <14175665+benjlevesque@users.noreply.github.com> --- .../smart-contracts/src/contracts/EthConversionProxy.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/smart-contracts/src/contracts/EthConversionProxy.sol b/packages/smart-contracts/src/contracts/EthConversionProxy.sol index 54d2445599..4ce13919dd 100644 --- a/packages/smart-contracts/src/contracts/EthConversionProxy.sol +++ b/packages/smart-contracts/src/contracts/EthConversionProxy.sol @@ -6,10 +6,10 @@ 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 + * @notice This contract converts from chainlink then swaps ETH (or native token) + * before paying a request thanks to a conversion payment proxy. + * The inheritance from ReentrancyGuard is required to perform + * "transferExactEthWithReferenceAndFee" on the eth-fee-proxy contract */ contract EthConversionProxy is ReentrancyGuard { address public paymentProxy; From c5ee3a2ed278e3e25c3e72992ea5ea849235696a Mon Sep 17 00:00:00 2001 From: vrolland Date: Mon, 4 Oct 2021 14:59:21 +0200 Subject: [PATCH 18/19] from BLE review --- .../scripts/3_deploy_chainlink_contract.ts | 12 +-- .../contracts/ChainlinkConversionPath.test.ts | 88 +++++++++---------- .../contracts/Erc20ConversionProxy.test.ts | 56 ++++++------ .../test/contracts/EthConversionProxy.test.ts | 58 ++++++------ 4 files changed, 107 insertions(+), 107 deletions(-) diff --git a/packages/smart-contracts/scripts/3_deploy_chainlink_contract.ts b/packages/smart-contracts/scripts/3_deploy_chainlink_contract.ts index 8b248b454f..3cfae99652 100644 --- a/packages/smart-contracts/scripts/3_deploy_chainlink_contract.ts +++ b/packages/smart-contracts/scripts/3_deploy_chainlink_contract.ts @@ -16,9 +16,9 @@ export default async function deploy(args: any, hre: HardhatRuntimeEnvironment) const currencyManager = CurrencyManager.getDefault(); // all these addresses are for test purposes - const ETH_address = currencyManager.fromSymbol('ETH')!.hash; - const USD_address = currencyManager.fromSymbol('USD')!.hash; - const EUR_address = currencyManager.fromSymbol('EUR')!.hash; + const ETH_hash = currencyManager.fromSymbol('ETH')!.hash; + const USD_hash = currencyManager.fromSymbol('USD')!.hash; + const EUR_hash = currencyManager.fromSymbol('EUR')!.hash; // Cf. ERC20Alpha in TestERC20.sol const DAI_address = '0x38cF23C52Bb4B13F051Aec09580a2dE845a7FA35'; @@ -32,8 +32,8 @@ export default async function deploy(args: any, hre: HardhatRuntimeEnvironment) // all these aggregators are for test purposes await conversionPathInstance.updateAggregatorsList( - [DAI_address, EUR_address, ETH_address, USDT_address], - [USD_address, USD_address, USD_address, ETH_address], + [DAI_address, EUR_hash, ETH_hash, USDT_address], + [USD_hash, USD_hash, USD_hash, ETH_hash], [AggDAI_USD_address, AggEUR_USD_address, AggETH_USD_address, AggUSDT_ETH_address], ); console.log('AggregatorsList updated.'); @@ -72,7 +72,7 @@ export default async function deploy(args: any, hre: HardhatRuntimeEnvironment) ...args, chainlinkConversionPathAddress: conversionPathInstance.address, ethFeeProxyAddress: '0x3d49d1eF2adE060a33c6E6Aa213513A7EE9a6241', - nativeTokenHash: ETH_address, + nativeTokenHash: ETH_hash, }, hre, ); diff --git a/packages/smart-contracts/test/contracts/ChainlinkConversionPath.test.ts b/packages/smart-contracts/test/contracts/ChainlinkConversionPath.test.ts index c378272629..ef6b401924 100644 --- a/packages/smart-contracts/test/contracts/ChainlinkConversionPath.test.ts +++ b/packages/smart-contracts/test/contracts/ChainlinkConversionPath.test.ts @@ -14,9 +14,9 @@ const address5 = '0x5555555555555555555555555555555555555555'; const address6 = '0x6666666666666666666666666666666666666666'; const currencyManager = CurrencyManager.getDefault(); -const ETH_address = currencyManager.fromSymbol('ETH')!.hash; -const USD_address = currencyManager.fromSymbol('USD')!.hash; -const EUR_address = currencyManager.fromSymbol('EUR')!.hash; +const ETH_hash = currencyManager.fromSymbol('ETH')!.hash; +const USD_hash = currencyManager.fromSymbol('USD')!.hash; +const EUR_hash = currencyManager.fromSymbol('EUR')!.hash; let DAI_address: string; let USDT_address: string; let conversionPathInstance: ChainlinkConversionPath; @@ -60,29 +60,29 @@ describe('contract: ChainlinkConversionPath', () => { describe('getRate', async () => { describe('only fiat rates', async () => { it('can get rate from EUR to USD', async () => { - const conversion = await conversionPathInstance.getRate([EUR_address, USD_address]); + const conversion = await conversionPathInstance.getRate([EUR_hash, USD_hash]); expect(conversion.rate.toString(), '1200000000000000000'); }); it('can get rate from USD to EUR', async () => { - const conversion = await conversionPathInstance.getRate([USD_address, EUR_address]); + const conversion = await conversionPathInstance.getRate([USD_hash, EUR_hash]); expect(conversion.rate.toString(), '833333333333333333'); }); it('can get rate from USD to EUR to USD', async () => { const conversion = await conversionPathInstance.getRate([ - USD_address, - EUR_address, - USD_address, + USD_hash, + EUR_hash, + USD_hash, ]); expect(conversion.rate.toString(), '999999999999999999'); }); it('can get rate from ETH to USD to EUR', async () => { const conversion = await conversionPathInstance.getRate([ - ETH_address, - USD_address, - EUR_address, + ETH_hash, + USD_hash, + EUR_hash, ]); expect(conversion.rate.toString(), '41666666666'); }); @@ -91,33 +91,33 @@ describe('contract: ChainlinkConversionPath', () => { describe('Ethereum rates', async () => { it('can get rate from USD to ETH', async () => { - const conversion = await conversionPathInstance.getRate([USD_address, ETH_address]); + const conversion = await conversionPathInstance.getRate([USD_hash, ETH_hash]); expect(conversion.rate.toString(), '20000000000000000000000000'); }); it('can get rate from ETH to USD', async () => { - const conversion = await conversionPathInstance.getRate([ETH_address, USD_address]); + const conversion = await conversionPathInstance.getRate([ETH_hash, USD_hash]); expect(conversion.rate.toString(), '50000000000'); }); it('can get rate from EUR to USD to ETH', async () => { const conversion = await conversionPathInstance.getRate([ - EUR_address, - USD_address, - ETH_address, + EUR_hash, + USD_hash, + ETH_hash, ]); expect(conversion.rate.toString(), '24000000000000000000000000'); }); it('can get rate from USD to ERC20', async () => { - const conversion = await conversionPathInstance.getRate([USD_address, DAI_address]); + const conversion = await conversionPathInstance.getRate([USD_hash, DAI_address]); expect(conversion.rate.toString(), '9900990099009900990099009900'); }); it('can get rate from ETH to USD to ERC20', async () => { const conversion = await conversionPathInstance.getRate([ - ETH_address, - USD_address, + ETH_hash, + USD_hash, DAI_address, ]); expect(conversion.rate.toString(), '495049504950495049504'); @@ -127,8 +127,8 @@ describe('contract: ChainlinkConversionPath', () => { describe('USDT rates', async () => { it('can get rate from USD to ETH to USDT', async () => { const conversion = await conversionPathInstance.getRate([ - USD_address, - ETH_address, + USD_hash, + ETH_hash, USDT_address, ]); expect(conversion.rate.toString(), '10000000000000000'); @@ -137,8 +137,8 @@ describe('contract: ChainlinkConversionPath', () => { it('can get rate from USDT to ETH to USD', async () => { const conversion = await conversionPathInstance.getRate([ USDT_address, - ETH_address, - USD_address, + ETH_hash, + USD_hash, ]); expect(conversion.rate.toString(), '100000000000000000000'); }); @@ -148,25 +148,25 @@ describe('contract: ChainlinkConversionPath', () => { describe('only fiat conversion', async () => { it('can convert EUR to USD', async () => { const conversion = await conversionPathInstance.getConversion('10000000000', [ - EUR_address, - USD_address, + EUR_hash, + USD_hash, ]); expect(conversion.result.toString(), '12000000000'); }); it('can convert USD to EUR', async () => { const conversion = await conversionPathInstance.getConversion('10000000000', [ - USD_address, - EUR_address, + USD_hash, + EUR_hash, ]); expect(conversion.result.toString(), '8333333333'); }); it('can convert USD to EUR to USD', async () => { const conversion = await conversionPathInstance.getConversion('10000000000', [ - USD_address, - EUR_address, - USD_address, + USD_hash, + EUR_hash, + USD_hash, ]); expect(conversion.result.toString(), '9999999999'); }); @@ -175,34 +175,34 @@ describe('contract: ChainlinkConversionPath', () => { describe('Ethereum conversion', async () => { it('can convert USD to ETH', async () => { const conversion = await conversionPathInstance.getConversion('100000000000', [ - USD_address, - ETH_address, + USD_hash, + ETH_hash, ]); expect(conversion.result.toString(), '2000000000000000000'); }); it('can convert ETH to USD', async () => { const conversion = await conversionPathInstance.getConversion('2000000000000000000', [ - ETH_address, - USD_address, + ETH_hash, + USD_hash, ]); expect(conversion.result.toString(), '100000000000'); }); it('can convert EUR to USD to ETH', async () => { const conversion = await conversionPathInstance.getConversion('100000000000', [ - EUR_address, - USD_address, - ETH_address, + EUR_hash, + USD_hash, + ETH_hash, ]); expect(conversion.result.toString(), '2400000000000000000'); }); it('can convert ETH to USD to EUR', async () => { const conversion = await conversionPathInstance.getConversion('2000000000000000000', [ - ETH_address, - USD_address, - EUR_address, + ETH_hash, + USD_hash, + EUR_hash, ]); expect(conversion.result.toString(), '83333333332'); }); @@ -211,8 +211,8 @@ describe('contract: ChainlinkConversionPath', () => { describe('USDT conversion', async () => { it('can convert USD to ETH to USDT', async () => { const conversion = await conversionPathInstance.getConversion('10000000000', [ - USD_address, - ETH_address, + USD_hash, + ETH_hash, USDT_address, ]); expect(conversion.result.toString(), '100000000'); @@ -221,8 +221,8 @@ describe('contract: ChainlinkConversionPath', () => { it('can convert USDT to ETH to USD', async () => { const conversion = await conversionPathInstance.getConversion('100000000', [ USDT_address, - ETH_address, - USD_address, + ETH_hash, + USD_hash, ]); expect(conversion.result.toString(), '10000000000'); }); diff --git a/packages/smart-contracts/test/contracts/Erc20ConversionProxy.test.ts b/packages/smart-contracts/test/contracts/Erc20ConversionProxy.test.ts index d366bc4b0c..4634afb29b 100644 --- a/packages/smart-contracts/test/contracts/Erc20ConversionProxy.test.ts +++ b/packages/smart-contracts/test/contracts/Erc20ConversionProxy.test.ts @@ -22,17 +22,17 @@ describe('contract: Erc20ConversionProxy', () => { let to: string; let feeAddress: string; let signer: Signer; - const smallAmountInFIAT = '100000000'; // 1 with 8 decimal - const smallerAmountInFIAT = '10000000'; // 0.1 with 8 decimal + const amountInFiat = '100000000'; // 1 with 8 decimal + const feesAmountInFiat = '10000000'; // 0.1 with 8 decimal const thousandWith18Decimal = '1000000000000000000000'; const hundredWith18Decimal = '100000000000000000000'; const referenceExample = '0xaaaa'; const currencyManager = CurrencyManager.getDefault(); - const ETH_address = currencyManager.fromSymbol('ETH')!.hash; - const USD_address = currencyManager.fromSymbol('USD')!.hash; - const EUR_address = currencyManager.fromSymbol('EUR')!.hash; + const ETH_hash = currencyManager.fromSymbol('ETH')!.hash; + const USD_hash = currencyManager.fromSymbol('USD')!.hash; + const EUR_hash = currencyManager.fromSymbol('EUR')!.hash; let DAI_address: string; let testErc20ConversionProxy: Erc20ConversionProxy; @@ -58,7 +58,7 @@ describe('contract: Erc20ConversionProxy', () => { describe('transferFromWithReferenceAndFee', () => { describe('transferFromWithReferenceAndFee with DAI', () => { it('allows to transfer DAI tokens for USD payment', async function () { - const path = [USD_address, DAI_address]; + const path = [USD_hash, DAI_address]; await testERC20.approve(testErc20ConversionProxy.address, thousandWith18Decimal, { from, }); @@ -66,16 +66,16 @@ describe('contract: Erc20ConversionProxy', () => { const fromOldBalance = await testERC20.balanceOf(from); const toOldBalance = await testERC20.balanceOf(to); const feeOldBalance = await testERC20.balanceOf(feeAddress); - const conversionToPay = await chainlinkPath.getConversion(smallAmountInFIAT, path); - const conversionFees = await chainlinkPath.getConversion(smallerAmountInFIAT, path); + const conversionToPay = await chainlinkPath.getConversion(amountInFiat, path); + const conversionFees = await chainlinkPath.getConversion(feesAmountInFiat, path); await expect( testErc20ConversionProxy.transferFromWithReferenceAndFee( to, - smallAmountInFIAT, + amountInFiat, path, referenceExample, - smallerAmountInFIAT, + feesAmountInFiat, feeAddress, hundredWith18Decimal, 0, @@ -83,10 +83,10 @@ describe('contract: Erc20ConversionProxy', () => { ) .to.emit(testErc20ConversionProxy, 'TransferWithConversionAndReference') .withArgs( - smallAmountInFIAT, + amountInFiat, ethers.utils.getAddress(path[0]), ethers.utils.keccak256(referenceExample), - smallerAmountInFIAT, + feesAmountInFiat, '0', ); @@ -114,22 +114,22 @@ describe('contract: Erc20ConversionProxy', () => { }); it('allows to transfer DAI tokens for EUR payment', async function () { - const path = [EUR_address, USD_address, DAI_address]; + const path = [EUR_hash, USD_hash, DAI_address]; await testERC20.approve(testErc20ConversionProxy.address, thousandWith18Decimal, { from }); const fromOldBalance = await testERC20.balanceOf(from); const toOldBalance = await testERC20.balanceOf(to); const feeOldBalance = await testERC20.balanceOf(feeAddress); - const conversionToPay = await chainlinkPath.getConversion(smallAmountInFIAT, path); - const conversionFees = await chainlinkPath.getConversion(smallerAmountInFIAT, path); + const conversionToPay = await chainlinkPath.getConversion(amountInFiat, path); + const conversionFees = await chainlinkPath.getConversion(feesAmountInFiat, path); await expect( testErc20ConversionProxy.transferFromWithReferenceAndFee( to, - smallAmountInFIAT, + amountInFiat, path, referenceExample, - smallerAmountInFIAT, + feesAmountInFiat, feeAddress, hundredWith18Decimal, 0, @@ -138,10 +138,10 @@ describe('contract: Erc20ConversionProxy', () => { .to.emit(testERC20, 'Transfer') .to.emit(testErc20ConversionProxy, 'TransferWithConversionAndReference') .withArgs( - smallAmountInFIAT, + amountInFiat, ethers.utils.getAddress(path[0]), ethers.utils.keccak256(referenceExample), - smallerAmountInFIAT, + feesAmountInFiat, '0', ) .to.emit(testErc20ConversionProxy, 'TransferWithReferenceAndFee') @@ -173,7 +173,7 @@ describe('contract: Erc20ConversionProxy', () => { describe('transferFromWithReferenceAndFee with errors', () => { it('cannot transfer with invalid path', async function () { - const path = [EUR_address, ETH_address, DAI_address]; + const path = [EUR_hash, ETH_hash, DAI_address]; await testERC20.approve(testErc20ConversionProxy.address, thousandWith18Decimal, { from, }); @@ -181,10 +181,10 @@ describe('contract: Erc20ConversionProxy', () => { await expect( testErc20ConversionProxy.transferFromWithReferenceAndFee( to, - smallAmountInFIAT, + amountInFiat, path, referenceExample, - smallerAmountInFIAT, + feesAmountInFiat, feeAddress, hundredWith18Decimal, 0, @@ -194,7 +194,7 @@ describe('contract: Erc20ConversionProxy', () => { }); it('cannot transfer if max to spend too low', async function () { - const path = [USD_address, DAI_address]; + const path = [USD_hash, DAI_address]; await testERC20.approve(testErc20ConversionProxy.address, thousandWith18Decimal, { from, }); @@ -202,10 +202,10 @@ describe('contract: Erc20ConversionProxy', () => { await expect( testErc20ConversionProxy.transferFromWithReferenceAndFee( to, - smallAmountInFIAT, + amountInFiat, path, referenceExample, - smallerAmountInFIAT, + feesAmountInFiat, feeAddress, 100, 0, @@ -215,7 +215,7 @@ describe('contract: Erc20ConversionProxy', () => { }); it('cannot transfer if rate is too old', async function () { - const path = [USD_address, DAI_address]; + const path = [USD_hash, DAI_address]; await testERC20.approve(testErc20ConversionProxy.address, thousandWith18Decimal, { from, }); @@ -223,10 +223,10 @@ describe('contract: Erc20ConversionProxy', () => { await expect( testErc20ConversionProxy.transferFromWithReferenceAndFee( to, - smallAmountInFIAT, + amountInFiat, path, referenceExample, - smallerAmountInFIAT, + feesAmountInFiat, feeAddress, hundredWith18Decimal, 10, // ten secondes diff --git a/packages/smart-contracts/test/contracts/EthConversionProxy.test.ts b/packages/smart-contracts/test/contracts/EthConversionProxy.test.ts index 773dfc601d..5806ad5b09 100644 --- a/packages/smart-contracts/test/contracts/EthConversionProxy.test.ts +++ b/packages/smart-contracts/test/contracts/EthConversionProxy.test.ts @@ -19,15 +19,15 @@ describe('contract: EthConversionProxy', () => { let to: string; let feeAddress: string; let signer: Signer; - const smallAmountInFIAT = BigNumber.from('100000000'); - const smallerAmountInFIAT = BigNumber.from('10000000'); + const amountInFiat = BigNumber.from('100000000'); + const feesAmountInFiat = BigNumber.from('10000000'); const referenceExample = '0xaaaa'; const currencyManager = CurrencyManager.getDefault(); - const ETH_address = currencyManager.fromSymbol('ETH')!.hash; - const USD_address = currencyManager.fromSymbol('USD')!.hash; - const EUR_address = currencyManager.fromSymbol('EUR')!.hash; + const ETH_hash = currencyManager.fromSymbol('ETH')!.hash; + const USD_hash = currencyManager.fromSymbol('USD')!.hash; + const EUR_hash = currencyManager.fromSymbol('EUR')!.hash; let testEthConversionProxy: EthConversionProxy; let ethFeeProxy: EthereumFeeProxy; @@ -43,27 +43,27 @@ describe('contract: EthConversionProxy', () => { testEthConversionProxy = await new EthConversionProxy__factory(signer).deploy( ethFeeProxy.address, chainlinkPath.address, - ETH_address, + ETH_hash, ); }); describe('transferWithReferenceAndFee', () => { describe('transferWithReferenceAndFee with ETH', () => { it('allows to transfer ETH for USD payment', async function () { - const path = [USD_address, ETH_address]; + const path = [USD_hash, ETH_hash]; const fromOldBalance = await provider.getBalance(from); const toOldBalance = await provider.getBalance(to); const feeOldBalance = await provider.getBalance(feeAddress); - const conversionToPay = await chainlinkPath.getConversion(smallAmountInFIAT, path); - const conversionFees = await chainlinkPath.getConversion(smallerAmountInFIAT, path); + const conversionToPay = await chainlinkPath.getConversion(amountInFiat, path); + const conversionFees = await chainlinkPath.getConversion(feesAmountInFiat, path); const tx = testEthConversionProxy.transferWithReferenceAndFee( to, - smallAmountInFIAT, + amountInFiat, path, referenceExample, - smallerAmountInFIAT, + feesAmountInFiat, feeAddress, 0, { @@ -74,10 +74,10 @@ describe('contract: EthConversionProxy', () => { await expect(tx) .to.emit(testEthConversionProxy, 'TransferWithConversionAndReference') .withArgs( - smallAmountInFIAT, + amountInFiat, ethers.utils.getAddress(path[0]), ethers.utils.keccak256(referenceExample), - smallerAmountInFIAT, + feesAmountInFiat, '0', ); @@ -106,20 +106,20 @@ describe('contract: EthConversionProxy', () => { }); it('allows to transfer ETH for EUR payment and extra msg.value', async function () { - const path = [EUR_address, USD_address, ETH_address]; + const path = [EUR_hash, USD_hash, ETH_hash]; const fromOldBalance = await provider.getBalance(from); const toOldBalance = await provider.getBalance(to); const feeOldBalance = await provider.getBalance(feeAddress); - const conversionToPay = await chainlinkPath.getConversion(smallAmountInFIAT, path); - const conversionFees = await chainlinkPath.getConversion(smallerAmountInFIAT, path); + const conversionToPay = await chainlinkPath.getConversion(amountInFiat, path); + const conversionFees = await chainlinkPath.getConversion(feesAmountInFiat, path); const tx = testEthConversionProxy.transferWithReferenceAndFee( to, - smallAmountInFIAT, + amountInFiat, path, referenceExample, - smallerAmountInFIAT, + feesAmountInFiat, feeAddress, 0, { @@ -130,10 +130,10 @@ describe('contract: EthConversionProxy', () => { await expect(tx) .to.emit(testEthConversionProxy, 'TransferWithConversionAndReference') .withArgs( - smallAmountInFIAT, + amountInFiat, ethers.utils.getAddress(path[0]), ethers.utils.keccak256(referenceExample), - smallerAmountInFIAT, + feesAmountInFiat, '0', ); @@ -167,17 +167,17 @@ describe('contract: EthConversionProxy', () => { describe('transferWithReferenceAndFee with errors', () => { it('cannot transfer if msg.value too low', async function () { - const path = [USD_address, ETH_address]; + const path = [USD_hash, ETH_hash]; - const conversionToPay = await chainlinkPath.getConversion(smallAmountInFIAT, path); + const conversionToPay = await chainlinkPath.getConversion(amountInFiat, path); await expect( testEthConversionProxy.transferWithReferenceAndFee( to, - smallAmountInFIAT, + amountInFiat, path, referenceExample, - smallerAmountInFIAT, + feesAmountInFiat, feeAddress, 0, { @@ -188,17 +188,17 @@ describe('contract: EthConversionProxy', () => { }); it('cannot transfer if rate is too old', async function () { - const path = [USD_address, ETH_address]; + const path = [USD_hash, ETH_hash]; - const conversionToPay = await chainlinkPath.getConversion(smallAmountInFIAT, path); - const conversionFees = await chainlinkPath.getConversion(smallerAmountInFIAT, path); + const conversionToPay = await chainlinkPath.getConversion(amountInFiat, path); + const conversionFees = await chainlinkPath.getConversion(feesAmountInFiat, path); await expect( testEthConversionProxy.transferWithReferenceAndFee( to, - smallAmountInFIAT, + amountInFiat, path, referenceExample, - smallerAmountInFIAT, + feesAmountInFiat, feeAddress, 1, // second { From 75a6561c418c1b7b7e1a1cdb97c5d614eb8dec65 Mon Sep 17 00:00:00 2001 From: vrolland Date: Mon, 4 Oct 2021 15:09:02 +0200 Subject: [PATCH 19/19] add comment --- packages/smart-contracts/src/contracts/EthConversionProxy.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/smart-contracts/src/contracts/EthConversionProxy.sol b/packages/smart-contracts/src/contracts/EthConversionProxy.sol index 4ce13919dd..0ae2eee2c7 100644 --- a/packages/smart-contracts/src/contracts/EthConversionProxy.sol +++ b/packages/smart-contracts/src/contracts/EthConversionProxy.sol @@ -32,6 +32,7 @@ contract EthConversionProxy is ReentrancyGuard { ); // Event to declare a transfer with a reference + // This event is emitted by this contract from a delegate call of the payment-proxy event TransferWithReferenceAndFee( address to, uint256 amount,