From a72b5bb750b524971646978d2b0a891ce75e498b Mon Sep 17 00:00:00 2001 From: Romain TREFAULT Date: Wed, 27 Jul 2022 17:49:06 +0200 Subject: [PATCH] feat: squash commits goerli storage --- .../ethereum-storage/src/ethereum-utils.ts | 1 + .../test/ethereum-utils.test.ts | 6 ++ .../src/payment/erc777-stream.ts | 99 ++++++++++++++----- .../test/payment/erc777-stream.test.ts | 58 +++++++++-- .../contract-setup/adminTasks.ts | 6 +- .../contract-setup/setupBatchPayments.ts | 6 +- .../src/contracts/ERC20EscrowToPay.sol | 10 +- .../lib/artifacts/RequestHashStorage/index.ts | 4 + .../artifacts/RequestHashSubmitter/index.ts | 4 + .../test/contracts/ERC20EscrowToPay.test.ts | 30 ++++-- packages/types/src/storage-types.ts | 1 + 11 files changed, 179 insertions(+), 46 deletions(-) diff --git a/packages/ethereum-storage/src/ethereum-utils.ts b/packages/ethereum-storage/src/ethereum-utils.ts index 9b879685d..75351078f 100644 --- a/packages/ethereum-storage/src/ethereum-utils.ts +++ b/packages/ethereum-storage/src/ethereum-utils.ts @@ -8,6 +8,7 @@ const networks = { [StorageTypes.EthereumNetwork.MAINNET]: 'mainnet', [StorageTypes.EthereumNetwork.KOVAN]: 'kovan', [StorageTypes.EthereumNetwork.RINKEBY]: 'rinkeby', + [StorageTypes.EthereumNetwork.GOERLI]: 'goerli', [StorageTypes.EthereumNetwork.SOKOL]: 'sokol', [StorageTypes.EthereumNetwork.XDAI]: 'xdai', }; diff --git a/packages/ethereum-storage/test/ethereum-utils.test.ts b/packages/ethereum-storage/test/ethereum-utils.test.ts index 17fc6d353..a423b9dda 100644 --- a/packages/ethereum-storage/test/ethereum-utils.test.ts +++ b/packages/ethereum-storage/test/ethereum-utils.test.ts @@ -21,6 +21,9 @@ describe('Ethereum Utils', () => { expect(EthereumUtils.getEthereumNetworkNameFromId(StorageTypes.EthereumNetwork.RINKEBY)).toBe( 'rinkeby', ); + expect(EthereumUtils.getEthereumNetworkNameFromId(StorageTypes.EthereumNetwork.GOERLI)).toBe( + 'goerli', + ); expect(EthereumUtils.getEthereumNetworkNameFromId(StorageTypes.EthereumNetwork.SOKOL)).toBe( 'sokol', ); @@ -48,6 +51,9 @@ describe('Ethereum Utils', () => { expect(EthereumUtils.getEthereumIdFromNetworkName('rinkeby')).toBe( StorageTypes.EthereumNetwork.RINKEBY, ); + expect(EthereumUtils.getEthereumIdFromNetworkName('goerli')).toBe( + StorageTypes.EthereumNetwork.GOERLI, + ); expect(EthereumUtils.getEthereumIdFromNetworkName('sokol')).toBe( StorageTypes.EthereumNetwork.SOKOL, ); diff --git a/packages/payment-processor/src/payment/erc777-stream.ts b/packages/payment-processor/src/payment/erc777-stream.ts index 01a627db9..ef2553118 100644 --- a/packages/payment-processor/src/payment/erc777-stream.ts +++ b/packages/payment-processor/src/payment/erc777-stream.ts @@ -10,12 +10,16 @@ import { } from './utils'; import { Framework } from '@superfluid-finance/sdk-core'; -export const resolverAddress = '0x913bbCFea2f347a24cfCA441d483E7CBAc8De3Db'; +export const RESOLVER_ADDRESS = '0x913bbCFea2f347a24cfCA441d483E7CBAc8De3Db'; +// Superfluid payments of requests use the generic field `userData` to index payments. +// Since it's a multi-purpose field, payments will use a fix-prefix heading the payment reference, +// in order to speed up the indexing and payment detection. +export const USERDATA_PREFIX = '0xbeefac'; /** * Processes a transaction to pay an ERC777 stream Request. * @param request - * @param signerOrProvider the Web3 provider, or signer. Defaults to window.ethereum. + * @param signer the Web3 signer. Defaults to window.ethereum. * @param overrides optionally, override default transaction values, like gas. */ export async function payErc777StreamRequest( @@ -28,35 +32,84 @@ export async function payErc777StreamRequest( throw new Error('Not a supported ERC777 payment network request'); } validateRequest(request, PaymentTypes.PAYMENT_NETWORK_ID.ERC777_STREAM); - const networkName = - request.currencyInfo.network === 'private' ? 'custom' : request.currencyInfo.network; - const sf = await Framework.create({ - networkName, - provider: signer.provider ?? getProvider(), - dataMode: request.currencyInfo.network === 'private' ? 'WEB3_ONLY' : undefined, - resolverAddress: request.currencyInfo.network === 'private' ? resolverAddress : undefined, - protocolReleaseVersion: request.currencyInfo.network === 'private' ? 'test' : undefined, - }); - const superSigner = sf.createSigner({ - signer: signer, - provider: signer.provider, - }); - const superToken = await sf.loadSuperToken(request.currencyInfo.value); + const sf = await getSuperFluidFramework(request, signer); // FIXME: according to specs PR https://github.com/RequestNetwork/requestNetwork/pull/688 // in file packages/advanced-logic/specs/payment-network-erc777-stream-0.1.0.md + // Below are the SF actions to add in the BatchCall: // - use expectedStartDate to compute offset between start of invoicing and start of streaming // - start fee streaming + const streamPayOp = await getStartStreamOp(sf, request, overrides); + const batchCall = sf.batchCall([streamPayOp]); + return batchCall.exec(signer); +} + +/** + * Processes a transaction to complete an ERC777 stream paying a Request. + * @param request + * @param signer the Web3 signer. Defaults to window.ethereum. + * @param overrides optionally, override default transaction values, like gas. + */ +export async function completeErc777StreamRequest( + request: ClientTypes.IRequestData, + signer: Signer, + overrides?: Overrides, +): Promise { + const id = getPaymentNetworkExtension(request)?.id; + if (id !== ExtensionTypes.ID.PAYMENT_NETWORK_ERC777_STREAM) { + throw new Error('Not a supported ERC777 payment network request'); + } + validateRequest(request, PaymentTypes.PAYMENT_NETWORK_ID.ERC777_STREAM); + const sf = await getSuperFluidFramework(request, signer); + // FIXME: according to specs PR https://github.com/RequestNetwork/requestNetwork/pull/688 + // in file packages/advanced-logic/specs/payment-network-erc777-stream-0.1.0.md + // Below are the SF actions to add in the BatchCall : + // - use expectedEndDate to compute offset between stop of invoicing and stop of streaming + // - stop fee streaming + const streamPayOp = await getStopStreamOp(sf, signer, request, overrides); + const batchCall = sf.batchCall([streamPayOp]); + return batchCall.exec(signer); +} + +async function getSuperFluidFramework(request: ClientTypes.IRequestData, signer: Signer) { + const isNetworkPrivate = request.currencyInfo.network === 'private'; + const networkName = isNetworkPrivate ? 'custom' : request.currencyInfo.network; + return await Framework.create({ + networkName, + provider: signer.provider ?? getProvider(), + dataMode: isNetworkPrivate ? 'WEB3_ONLY' : undefined, + resolverAddress: isNetworkPrivate ? RESOLVER_ADDRESS : undefined, + protocolReleaseVersion: isNetworkPrivate ? 'test' : undefined, + }); +} +async function getStartStreamOp( + sf: Framework, + request: ClientTypes.IRequestData, + overrides?: Overrides, +) { + const superToken = await sf.loadSuperToken(request.currencyInfo.value); const { paymentReference, paymentAddress, expectedFlowRate } = getRequestPaymentValues(request); - // Superfluid payments of requests use the generic field `userData` to index payments. - // Since it's a multi-purpose field, payments will use a fix-prefix heading the payment reference, - // in order to speed up the indexing and payment detection. - const streamPayOp = sf.cfaV1.createFlow({ + return sf.cfaV1.createFlow({ flowRate: expectedFlowRate ?? '0', receiver: paymentAddress, superToken: superToken.address, - userData: `0xbeefac${paymentReference}`, + userData: `${USERDATA_PREFIX}${paymentReference}`, + overrides: overrides, + }); +} + +async function getStopStreamOp( + sf: Framework, + signer: Signer, + request: ClientTypes.IRequestData, + overrides?: Overrides, +) { + const superToken = await sf.loadSuperToken(request.currencyInfo.value); + const { paymentReference, paymentAddress } = getRequestPaymentValues(request); + return sf.cfaV1.deleteFlow({ + superToken: superToken.address, + sender: await signer.getAddress(), + receiver: paymentAddress, + userData: `${USERDATA_PREFIX}${paymentReference}`, overrides: overrides, }); - const batchCall = sf.batchCall([streamPayOp]); - return batchCall.exec(superSigner); } diff --git a/packages/payment-processor/test/payment/erc777-stream.test.ts b/packages/payment-processor/test/payment/erc777-stream.test.ts index 605e3f03d..644e71225 100644 --- a/packages/payment-processor/test/payment/erc777-stream.test.ts +++ b/packages/payment-processor/test/payment/erc777-stream.test.ts @@ -10,7 +10,11 @@ import { } from '@requestnetwork/types'; import Utils from '@requestnetwork/utils'; -import { payErc777StreamRequest, resolverAddress } from '../../src/payment/erc777-stream'; +import { + completeErc777StreamRequest, + payErc777StreamRequest, + RESOLVER_ADDRESS, +} from '../../src/payment/erc777-stream'; import { getRequestPaymentValues } from '../../src/payment/utils'; const daiABI = require('../abis/fDAIABI'); @@ -120,8 +124,8 @@ describe('erc777-stream', () => { }); }); - describe('payErc777StreamRequest', () => { - it('should pay an ERC777 request with fees', async () => { + describe('Streams management', () => { + it('payErc777StreamRequest should pay an ERC777 request', async () => { let tx; let confirmedTx; // initialize the superfluid framework...put custom and web3 only bc we are using ganache locally @@ -129,7 +133,7 @@ describe('erc777-stream', () => { networkName: 'custom', provider, dataMode: 'WEB3_ONLY', - resolverAddress: resolverAddress, + resolverAddress: RESOLVER_ADDRESS, protocolReleaseVersion: 'test', }); @@ -183,18 +187,56 @@ describe('erc777-stream', () => { expect(confirmedTx.status).toBe(1); expect(tx.hash).not.toBeUndefined(); - const wFlowRate = await sf.cfaV1.getNetFlow({ + const walletFlowRate = await sf.cfaV1.getNetFlow({ + superToken: daix.address, + account: wallet.address, + providerOrSigner: provider, + }); + expect(walletFlowRate).toBe(`-${expectedFlowRate}`); + const paymentFlowRate = await sf.cfaV1.getNetFlow({ + superToken: daix.address, + account: paymentAddress, + providerOrSigner: provider, + }); + expect(paymentFlowRate).toBe(expectedFlowRate); + }); + + it('completeErc777StreamRequest should complete an ERC777 request', async () => { + let tx; + let confirmedTx; + // initialize the superfluid framework...put custom and web3 only bc we are using ganache locally + const sf = await Framework.create({ + networkName: 'custom', + provider, + dataMode: 'WEB3_ONLY', + resolverAddress: RESOLVER_ADDRESS, + protocolReleaseVersion: 'test', + }); + + // use the framework to get the SuperToken + const daix = await sf.loadSuperToken('fDAIx'); + + // wait 2 seconds of streaming to avoid failing + await new Promise((r) => setTimeout(r, 2000)); + + // Stopping fDAIX stream request + tx = await completeErc777StreamRequest(validRequest, wallet); + confirmedTx = await tx.wait(1); + expect(confirmedTx.status).toBe(1); + expect(tx.hash).not.toBeUndefined(); + + const walletFlowRate = await sf.cfaV1.getNetFlow({ superToken: daix.address, account: wallet.address, providerOrSigner: provider, }); - expect(wFlowRate).toBe(`-${expectedFlowRate}`); - const pFlowRate = await sf.cfaV1.getNetFlow({ + expect(walletFlowRate).toBe('0'); + const paymentFlowRate = await sf.cfaV1.getNetFlow({ superToken: daix.address, account: paymentAddress, providerOrSigner: provider, }); - expect(pFlowRate).toBe(expectedFlowRate); + expect(paymentFlowRate).toBe('0'); }); }); }); diff --git a/packages/smart-contracts/scripts-create2/contract-setup/adminTasks.ts b/packages/smart-contracts/scripts-create2/contract-setup/adminTasks.ts index b33fe6fcb..a2c8d6ffc 100644 --- a/packages/smart-contracts/scripts-create2/contract-setup/adminTasks.ts +++ b/packages/smart-contracts/scripts-create2/contract-setup/adminTasks.ts @@ -5,8 +5,8 @@ import { BigNumber } from 'ethers'; // Fees: 0.5% export const REQUEST_SWAP_FEES = 5; -// Batch Fees: 1% -export const BATCH_FEE = 10; +// Batch Fees: .3% +export const BATCH_FEE = 3; export const updateChainlinkConversionPath = async ( contract: any, @@ -57,6 +57,8 @@ export const updateBatchPaymentFees = async ( ): Promise => { const currentFees = await contract.batchFee(); if (currentFees !== BATCH_FEE) { + // Log is useful to have a direct view on was is being updated + console.log(`currentFees: ${currentFees.toString()}, new fees: ${BATCH_FEE}`); await contract.setBatchFee(BATCH_FEE, { nonce: nonce, gasPrice: gasPrice }); } }; diff --git a/packages/smart-contracts/scripts-create2/contract-setup/setupBatchPayments.ts b/packages/smart-contracts/scripts-create2/contract-setup/setupBatchPayments.ts index db3b703bc..78fa718e0 100644 --- a/packages/smart-contracts/scripts-create2/contract-setup/setupBatchPayments.ts +++ b/packages/smart-contracts/scripts-create2/contract-setup/setupBatchPayments.ts @@ -37,9 +37,9 @@ export const setupBatchPayments = async ( // start from the adminNonce, increase gasPrice if needed await Promise.all([ - updateBatchPaymentFees(batchPaymentConnected, adminNonce, gasPrice), - updatePaymentErc20FeeProxy(batchPaymentConnected, network, adminNonce + 1, gasPrice), - updatePaymentEthFeeProxy(batchPaymentConnected, network, adminNonce + 2, gasPrice), + updateBatchPaymentFees(batchPaymentConnected, adminNonce, gasPrice.mul(2)), + updatePaymentErc20FeeProxy(batchPaymentConnected, network, adminNonce + 1, gasPrice.mul(2)), + updatePaymentEthFeeProxy(batchPaymentConnected, network, adminNonce + 2, gasPrice.mul(2)), ]); }), ); diff --git a/packages/smart-contracts/src/contracts/ERC20EscrowToPay.sol b/packages/smart-contracts/src/contracts/ERC20EscrowToPay.sol index a961db772..cda270218 100644 --- a/packages/smart-contracts/src/contracts/ERC20EscrowToPay.sol +++ b/packages/smart-contracts/src/contracts/ERC20EscrowToPay.sol @@ -151,13 +151,19 @@ contract ERC20EscrowToPay is Ownable { revert('not payable receive'); } + /** + * @notice Sets duration of emergency period, with minimum value of 30 days. + */ function setEmergencyClaimPeriod(uint256 _emergencyClaimPeriod) external onlyOwner { - require(_emergencyClaimPeriod >= 30, 'emergency period too short'); + require(_emergencyClaimPeriod >= 30 days, 'emergency period too short'); emergencyClaimPeriod = _emergencyClaimPeriod; } + /** + * @notice Sets duration of freeze period, with minimum value of 30 days. + */ function setFrozenPeriod(uint256 _frozenPeriod) external onlyOwner { - require(_frozenPeriod >= 30, 'frozen period too short'); + require(_frozenPeriod >= 30 days, 'frozen period too short'); frozenPeriod = _frozenPeriod; } diff --git a/packages/smart-contracts/src/lib/artifacts/RequestHashStorage/index.ts b/packages/smart-contracts/src/lib/artifacts/RequestHashStorage/index.ts index e3e0981d6..d9b4ae963 100644 --- a/packages/smart-contracts/src/lib/artifacts/RequestHashStorage/index.ts +++ b/packages/smart-contracts/src/lib/artifacts/RequestHashStorage/index.ts @@ -21,6 +21,10 @@ export const requestHashStorageArtifact = new ContractArtifact { }); }); describe('Admin should be able to change emergency and freeze periods', () => { - it('Should be able to change emergency period', async () => { + it('Minimum emergency period is 30 days', async () => { const twoDaysInSeconds = 3600 * 24 * 2; - await erc20EscrowToPay.connect(admin).setEmergencyClaimPeriod(twoDaysInSeconds); + expect( + erc20EscrowToPay.connect(admin).setEmergencyClaimPeriod(twoDaysInSeconds), + ).to.be.revertedWith('emergency period too short'); + }); + + it('Minimum frozen period is 30 days', async () => { + const twentyNineDaysInSeconds = 3600 * 24 * 29; + expect( + erc20EscrowToPay.connect(admin).setFrozenPeriod(twentyNineDaysInSeconds), + ).to.be.revertedWith('frozen period too short'); + }); + + it('Should be able to adjust emergency period', async () => { + const thirtyOneDaysInSeconds = 3600 * 24 * 31; + await erc20EscrowToPay.connect(admin).setEmergencyClaimPeriod(thirtyOneDaysInSeconds); const contractEmergencyPeriod = await erc20EscrowToPay.connect(payee).emergencyClaimPeriod(); - expect(contractEmergencyPeriod).to.be.equal(twoDaysInSeconds); + expect(contractEmergencyPeriod).to.be.equal(thirtyOneDaysInSeconds); }); - it('Should be able to adjust freeze period, only admin', async () => { - const twoDaysInSeconds = 3600 * 24 * 2; - await erc20EscrowToPay.connect(admin).setFrozenPeriod(twoDaysInSeconds); + it('Should be able to adjust freeze period', async () => { + const thirtyOneDaysInSeconds = 3600 * 24 * 31; + await erc20EscrowToPay.connect(admin).setFrozenPeriod(thirtyOneDaysInSeconds); const contractFreezePeriod = await erc20EscrowToPay.connect(payee).frozenPeriod(); - expect(contractFreezePeriod).to.be.equal(twoDaysInSeconds); + expect(contractFreezePeriod).to.be.equal(thirtyOneDaysInSeconds); }); - it('Contract creator should not be able to change periods', async () => { + it('Contract creator who is not the owner, should not be able to change periods', async () => { expect(erc20EscrowToPay.connect(owner).setEmergencyClaimPeriod(100)).to.be.revertedWith( 'Ownable: caller is not the owner', ); diff --git a/packages/types/src/storage-types.ts b/packages/types/src/storage-types.ts index c6e51c33b..4ab5c6702 100644 --- a/packages/types/src/storage-types.ts +++ b/packages/types/src/storage-types.ts @@ -136,6 +136,7 @@ export enum EthereumNetwork { PRIVATE = 0, MAINNET = 1, RINKEBY = 4, + GOERLI = 5, KOVAN = 42, SOKOL = 77, XDAI = 100,