Skip to content

Commit

Permalink
Merge branch 'master' into refactor-2-advanced-logic-others-extensions
Browse files Browse the repository at this point in the history
  • Loading branch information
vrolland committed Jun 8, 2021
2 parents bec23a3 + 9e3ab6d commit 044165e
Show file tree
Hide file tree
Showing 26 changed files with 918 additions and 504 deletions.
Expand Up @@ -176,7 +176,12 @@ export default class AnyToErc20ProxyPaymentNetwork extends Erc20FeeProxyPaymentN
);
}

if (!supportedCurrencies[network][request.currency.type].includes(request.currency.value)) {
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.`,
);
Expand Down
Expand Up @@ -358,6 +358,25 @@ describe('extensions/payment-network/erc20/any-to-erc20-fee-proxy-contract', ()
).toEqual(DataConversionERC20FeeCreate.extensionFullState);
});

it('can applyActionToExtensions of creation when address is checksumed', () => {
const request = Utils.deepCopy(DataConversionERC20FeeCreate.requestStateNoExtensions);
request.currency = {
type: RequestLogicTypes.CURRENCY.ERC20,
value: '0x4E15361FD6b4BB609Fa63C81A2be19d873717870', // FTM
network: 'mainnet',
};

expect(
anyToErc20Proxy.applyActionToExtension(
request.extensions,
DataConversionERC20FeeCreate.actionCreationFull,
request,
TestData.otherIdRaw.identity,
TestData.arbitraryTimestamp,
),
).toEqual(DataConversionERC20FeeCreate.extensionFullState);
});

it('cannot applyActionToExtensions of creation with a previous state', () => {
// 'must throw'
expect(() => {
Expand Down Expand Up @@ -653,7 +672,7 @@ describe('extensions/payment-network/erc20/any-to-erc20-fee-proxy-contract', ()
),
).toEqual(DataConversionERC20FeeAddData.extensionStateWithFeeAfterCreation);
});

it('cannot applyActionToExtensions of addFee without a previous state', () => {
expect(() => {
anyToErc20Proxy.applyActionToExtension(
Expand All @@ -665,7 +684,7 @@ describe('extensions/payment-network/erc20/any-to-erc20-fee-proxy-contract', ()
);
}).toThrowError(`The extension should be created before receiving any other action`);
});

it('cannot applyActionToExtensions of addFee without a payee', () => {
const previousState = Utils.deepCopy(DataConversionERC20FeeCreate.requestStateCreatedEmpty);
previousState.payee = undefined;
Expand All @@ -679,7 +698,7 @@ describe('extensions/payment-network/erc20/any-to-erc20-fee-proxy-contract', ()
);
}).toThrowError(`The request must have a payee`);
});

it('cannot applyActionToExtensions of addFee signed by someone else than the payee', () => {
const previousState = Utils.deepCopy(DataConversionERC20FeeCreate.requestStateCreatedEmpty);
expect(() => {
Expand All @@ -692,7 +711,7 @@ describe('extensions/payment-network/erc20/any-to-erc20-fee-proxy-contract', ()
);
}).toThrowError(`The signer must be the payee`);
});

it('cannot applyActionToExtensions of addFee with fee data already given', () => {
expect(() => {
anyToErc20Proxy.applyActionToExtension(
Expand All @@ -704,7 +723,7 @@ describe('extensions/payment-network/erc20/any-to-erc20-fee-proxy-contract', ()
);
}).toThrowError(`Fee address already given`);
});

it('cannot applyActionToExtensions of addFee with fee address not valid', () => {
const testnetPaymentAddress = Utils.deepCopy(DataConversionERC20FeeAddData.actionAddFee);
testnetPaymentAddress.parameters.feeAddress = DataConversionERC20FeeAddData.invalidAddress;
Expand All @@ -718,10 +737,10 @@ describe('extensions/payment-network/erc20/any-to-erc20-fee-proxy-contract', ()
);
}).toThrowError('feeAddress is not a valid address');
});

it('cannot applyActionToExtensions of addFee with fee amount not valid', () => {
const testnetPaymentAddress = Utils.deepCopy(DataConversionERC20FeeAddData.actionAddFee);
testnetPaymentAddress.parameters.feeAmount = "invalid amount";
testnetPaymentAddress.parameters.feeAmount = 'invalid amount';
expect(() => {
anyToErc20Proxy.applyActionToExtension(
DataConversionERC20FeeCreate.requestStateCreatedEmpty.extensions,
Expand Down
2 changes: 1 addition & 1 deletion packages/currency/package.json
Expand Up @@ -49,7 +49,7 @@
"devDependencies": {
"@types/jest": "26.0.13",
"@types/node-dijkstra": "2.5.1",
"ethers": "5.0.32",
"ethers": "5.1.4",
"jest": "26.4.2",
"prettier": "2.1.1",
"shx": "0.3.2",
Expand Down
15 changes: 14 additions & 1 deletion packages/currency/src/erc20/networks/celo.ts
Expand Up @@ -2,10 +2,23 @@ import { TokenMap } from './types';

// List of the supported celo network tokens
export const supportedCeloERC20: TokenMap = {
// cUSD token (https://explorer.celo.org/address/0x765de816845861e75a25fca122bb6898b8b1282a/transactions)
// https://explorer.celo.org/address/0x765de816845861e75a25fca122bb6898b8b1282a/read_contract
'0x765DE816845861e75A25fCA122bb6898B8B1282a': {
// FIXME: should be cUSD, need to work on the retrocompatibility
symbol: 'CUSD',
decimals: 18,
name: 'Celo Dollar',
},
// https://explorer.celo.org/address/0x471EcE3750Da237f93B8E339c536989b8978a438/read_contract
'0x471EcE3750Da237f93B8E339c536989b8978a438': {
symbol: 'cGLD',
decimals: 18,
name: 'Celo Gold',
},
// https://explorer.celo.org/address/0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73/read_contract
'0xD8763CBa276a3738E6DE85b4b3bF5FDed6D6cA73': {
symbol: 'cEUR',
decimals: 18,
name: 'Celo Euro',
},
};
2 changes: 1 addition & 1 deletion packages/data-format/package.json
Expand Up @@ -40,7 +40,7 @@
},
"dependencies": {
"ajv": "6.12.4",
"tslib": "2.0.1"
"ethers": "5.1.4"
},
"devDependencies": {
"@types/node": "14.14.16",
Expand Down
2 changes: 2 additions & 0 deletions packages/data-format/src/format/rnf_invoice/index.ts
@@ -0,0 +1,2 @@
export type { ActorInfo, Address, Invoice, InvoiceItem, PaymentTerms, Tax } from './types';
export * from './utils';
65 changes: 65 additions & 0 deletions packages/data-format/src/format/rnf_invoice/types.ts
@@ -0,0 +1,65 @@
export interface Invoice {
meta: {
format: 'rnf_invoice';
version: string;
};
buyerInfo?: ActorInfo;
creationDate: string;
invoiceItems: InvoiceItem[];
invoiceNumber: string;
miscellaneous?: unknown;
note?: string;
paymentTerms?: PaymentTerms;
purchaseOrderId?: string;
sellerInfo?: ActorInfo;
terms?: string;
}

export interface InvoiceItem {
currency: string;
deliveryDate?: string;
deliveryPeriod?: string;
discount?: string;
name: string;
quantity: number;
reference?: string;
/**
* @deprecated since 0.0.3. Use tax instead
*/
taxPercent?: number;
tax: Tax;
unitPrice: string;
}

export interface Tax {
type: 'percentage' | 'fixed';
amount: string;
}

export interface ActorInfo {
address?: Address;
businessName?: string;
email?: string;
firstName?: string;
lastName?: string;
miscellaneous?: unknown;
phone?: string;
taxRegistration?: string;
}

export interface PaymentTerms {
dueDate?: string;
lateFeesFix?: string;
lateFeesPercent?: number;
miscellaneous?: unknown;
}

export interface Address {
'country-name'?: string;
'extended-address'?: string;
locality?: string;
'post-office-box'?: string;
'postal-code'?: string;
region?: string;
'street-address'?: string;
}
41 changes: 41 additions & 0 deletions packages/data-format/src/format/rnf_invoice/utils.ts
@@ -0,0 +1,41 @@
import { BigNumber } from 'ethers';
import { Invoice, InvoiceItem } from './types';

export const getInvoiceTotal = (invoice: Invoice): BigNumber => {
return invoice.invoiceItems.reduce(
(acc, item) => acc.add(getInvoiceLineTotal(item)),
BigNumber.from(0),
);
};

export const getInvoiceLineTotal = (item: InvoiceItem): BigNumber => {
// Every amount in currency is a big number with the good number of decimals for this currency
// Tax percent is not an amount in currency. To allow a big number multiplication, we convert
// it temporarily with preciselyOne (allows 6 decimals, ie 0.123456%)
const preciselyOne = 1000000;

// Support for rnf_version < 0.0.3
const tax = item.taxPercent
? { type: 'percentage', amount: String(item.taxPercent) }
: item.tax || { type: 'percentage', amount: '0' };

const taxPercent = tax.amount && tax.type === 'percentage' ? Number(tax.amount) + 100 : 100;
const taxFixed =
tax.amount && tax.type === 'fixed' ? BigNumber.from(tax.amount) : BigNumber.from(0);
const discount = item.discount ? BigNumber.from(item.discount) : BigNumber.from(0);

return (
BigNumber.from(item.unitPrice)
// account for floating quantities
.mul(Number(item.quantity * preciselyOne).toFixed(0))
.div(preciselyOne)
.sub(discount)
// Artificially offset the decimal to let the multiplication work
.mul(Number(taxPercent * preciselyOne).toFixed(0))
// Remove the decimal offset
.div(preciselyOne)
// Remove the percentage multiplier
.div(100)
.add(taxFixed)
);
};
6 changes: 6 additions & 0 deletions packages/data-format/src/index.ts
Expand Up @@ -49,3 +49,9 @@ export default {
return !!data.meta && data.meta.format === 'rnf_invoice';
},
};

// FIXME it would be better to export a subpath, ie @requestnetwork/data-format/invoice,
// using `exports` as introduced in Node 12.7.0 (https://nodejs.org/api/packages.html#packages_subpath_exports)
// but typescript doesn't currently support this (https://github.com/microsoft/TypeScript/issues/33079)
// NB: compatibilty with browser would need to be tested.
export * from './format/rnf_invoice';

0 comments on commit 044165e

Please sign in to comment.