Skip to content

Commit

Permalink
fix: update currency validation (#1018)
Browse files Browse the repository at this point in the history
  • Loading branch information
leoslr committed Dec 20, 2022
1 parent 47bf2d8 commit 0e7778f
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 26 deletions.
30 changes: 20 additions & 10 deletions packages/currency/src/currencyManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,22 +227,19 @@ export class CurrencyManager<TMeta = unknown> implements ICurrencyManager<TMeta>
* Validates an address for a given currency.
* Throws if the currency is an ISO4217 currency.
*/
static validateAddress(address: string, currency: CurrencyInput): boolean {
static validateAddress(address: string, currency: CurrencyInput | StorageCurrency): boolean {
if (currency.type === RequestLogicTypes.CURRENCY.ISO4217) {
throw new Error(`Could not validate an address for an ISO4217 currency`);
}
switch (currency.type) {
case RequestLogicTypes.CURRENCY.ISO4217:
throw new Error(`Could not validate an address for an ISO4217 currency`);
case RequestLogicTypes.CURRENCY.ETH:
case RequestLogicTypes.CURRENCY.ERC20:
case RequestLogicTypes.CURRENCY.ERC777:
switch (currency.symbol) {
case 'NEAR':
case 'NEAR-testnet':
switch (currency.network) {
case 'aurora':
case 'aurora-testnet':
return isValidNearAddress(address, currency.network);
default:
// we don't pass a third argument to the validate method here
// because there is no difference between testnet and prod
// for the ethereum validator, see:
// https://github.com/christsim/multicoin-address-validator/blob/f8f3626f441c0d53fdc3b89678629dc1d33c0546/src/ethereum_validator.js
return addressValidator.validate(address, 'ETH');
}
case RequestLogicTypes.CURRENCY.BTC:
Expand All @@ -256,6 +253,19 @@ export class CurrencyManager<TMeta = unknown> implements ICurrencyManager<TMeta>
}
}

/**
* Validate the correctness of a Storage Currency
*/
static validateCurrency(currency: StorageCurrency): boolean {
if (
currency.type === RequestLogicTypes.CURRENCY.ISO4217 ||
currency.type === RequestLogicTypes.CURRENCY.ETH ||
currency.type === RequestLogicTypes.CURRENCY.BTC
)
return true;
return this.validateAddress(currency.value, currency);
}

/**
* Returns the list of currencies supported by Request out of the box
* Contains:
Expand Down
89 changes: 89 additions & 0 deletions packages/currency/test/currencyManager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,95 @@ describe('CurrencyManager', () => {
});
});

describe('Validate currencies', () => {
describe('Valid cases', () => {
it.each([
{
currency: {
type: RequestLogicTypes.CURRENCY.ISO4217,
value: 'FIAT',
},
label: 'ISO4217 Currency',
},
{
currency: {
type: RequestLogicTypes.CURRENCY.ETH,
value: 'ETH',
network: 'matic',
},
label: 'native currency',
},
{
currency: {
type: RequestLogicTypes.CURRENCY.BTC,
value: 'BTC',
network: 'mainnet',
},
label: 'Bitcoin currency',
},
{
currency: {
type: RequestLogicTypes.CURRENCY.ERC20,
value: '0x52908400098527886E0F7030069857D2E4169EE7',
network: 'optimism',
},
label: 'ERC20 Currency - evm',
},
{
currency: {
type: RequestLogicTypes.CURRENCY.ERC20,
value: 'usdc.near',
network: 'aurora',
},
label: 'ERC20 currency - near',
},
{
currency: {
type: RequestLogicTypes.CURRENCY.ERC777,
value: '0x52908400098527886E0F7030069857D2E4169EE7',
network: 'avalanche',
},
label: 'ERC777 currency',
},
])('Should validate $label', ({ currency }) => {
const result = CurrencyManager.validateCurrency(currency);
expect(result).toBe(true);
});
});

describe('Invalid cases', () => {
it.each([
{
currency: {
type: RequestLogicTypes.CURRENCY.ERC20,
value: 'invalid',
network: 'optimism',
},
label: 'ERC20 Currency - evm',
},
{
currency: {
type: RequestLogicTypes.CURRENCY.ERC20,
value: 'invalid',
network: 'aurora',
},
label: 'ERC20 currency - near',
},
{
currency: {
type: RequestLogicTypes.CURRENCY.ERC777,
value: 'invalid',
network: 'avalanche',
},
label: 'ERC777 currency',
},
])('Should not validate an invalid $label', ({ currency }) => {
const result = CurrencyManager.validateCurrency(currency);
expect(result).toBe(false);
});
});
});

describe('Conversion paths', () => {
let eur: CurrencyDefinition, usd: CurrencyDefinition, dai: CurrencyDefinition;
beforeEach(() => {
Expand Down
19 changes: 3 additions & 16 deletions packages/request-client.js/src/api/request-network.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { utils as ethersUtils } from 'ethers';
import { AdvancedLogic } from '@requestnetwork/advanced-logic';
import { PaymentNetworkFactory, PaymentNetworkOptions } from '@requestnetwork/payment-detection';
import { RequestLogic } from '@requestnetwork/request-logic';
Expand Down Expand Up @@ -383,11 +382,9 @@ export default class RequestNetwork {
const contentData = parameters.contentData;
const topics = parameters.topics?.slice() || [];

// If ERC20, validate that the value is a checksum address
if (requestParameters.currency.type === RequestLogicTypes.CURRENCY.ERC20) {
if (!this.validERC20Address(requestParameters.currency.value)) {
throw new Error('The ERC20 currency address needs to be a valid Ethereum checksum address');
}
// Check that currency is valid
if (!CurrencyManager.validateCurrency(currency)) {
throw new Error('The currency is not valid');
}

// avoid mutation of the parameters
Expand Down Expand Up @@ -435,14 +432,4 @@ export default class RequestNetwork {

return { requestParameters: copiedRequestParameters, topics, paymentNetwork };
}

/**
* Returns true if the address is a valid checksum address
*
* @param address The address to validate
* @returns If the address is valid or not
*/
private validERC20Address(address: string): boolean {
return ethersUtils.getAddress(address) === address;
}
}
38 changes: 38 additions & 0 deletions packages/request-client.js/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,44 @@ describe('request-client.js', () => {
});
});

it('cannot create ERC20 address based requests with invalid currency', async () => {
const testErc20TokenAddress = 'invalidErc20Address';

const requestNetwork = new RequestNetwork({
signatureProvider: TestData.fakeSignatureProvider,
useMockStorage: true,
});
// generate address randomly to avoid collisions
const paymentAddress =
'0x' + (await Utils.crypto.CryptoWrapper.random32Bytes()).slice(12).toString('hex');
const refundAddress =
'0x' + (await Utils.crypto.CryptoWrapper.random32Bytes()).slice(12).toString('hex');

const paymentNetwork: PaymentTypes.PaymentNetworkCreateParameters = {
id: ExtensionTypes.PAYMENT_NETWORK_ID.ERC20_ADDRESS_BASED,
parameters: {
paymentAddress,
refundAddress,
},
};

const requestInfo = Object.assign({}, TestData.parametersWithoutExtensionsData, {
currency: {
network: 'private',
type: RequestLogicTypes.CURRENCY.ERC20,
value: testErc20TokenAddress,
},
});

await expect(
requestNetwork.createRequest({
paymentNetwork,
requestInfo,
signer: TestData.payee.identity,
}),
).rejects.toThrowError('The currency is not valid');
});

describe('ERC20 proxy contract requests', () => {
it('can create ERC20 requests with given salt', async () => {
const requestNetwork = new RequestNetwork({
Expand Down
5 changes: 5 additions & 0 deletions packages/types/src/request-logic-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,10 +270,15 @@ export enum ACTION_NAME {

/** Supported currencies */
export enum CURRENCY {
/** ETH Currency type refers to any kind of blockchain native currency - excepted bitcoin */
ETH = 'ETH',
/** BTC Currency type refers to the Bitcoin native currency */
BTC = 'BTC',
/** ISO4217 Currency type refers to fiat currencies (e.g. EUR, USD, ...) */
ISO4217 = 'ISO4217',
/** ERC20 Currency type refers to any kind of fungible token (USDC, DAI, ...) */
ERC20 = 'ERC20',
/** ERC777 Currency type refers to any kind of streamable fungible token (USDCx, DAIx, ...) */
ERC777 = 'ERC777',
}

Expand Down

0 comments on commit 0e7778f

Please sign in to comment.