diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8a733c87..75acd686e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,10 +55,10 @@ jobs: with: repository: 'oceanprotocol/barge' path: 'barge' - - name: Run Ganache with Barge working-directory: ${{ github.workspace }}/barge run: | + export CONTRACTS_VERSION=v2.3.0 bash -x start_ocean.sh --no-aquarius --no-elasticsearch --no-provider --no-dashboard 2>&1 > start_ocean.log & - run: npm ci - name: Wait for contracts deployment diff --git a/package-lock.json b/package-lock.json index 416004f2f..96bb68ea6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "Apache-2.0", "dependencies": { "@oasisprotocol/sapphire-paratime": "^1.3.2", - "@oceanprotocol/contracts": "^2.2.0", + "@oceanprotocol/contracts": "^2.3.0", "@oceanprotocol/ddo-js": "^0.0.8", "@rdfjs/dataset": "^2.0.2", "@rdfjs/formats-common": "^3.1.0", @@ -4276,9 +4276,9 @@ } }, "node_modules/@oceanprotocol/contracts": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-2.2.1.tgz", - "integrity": "sha512-ub+CuN61seLtUvdTm/iFCyF6+wG5iCovhLaDQywKJw3RuM4gzSnxeOkBf0n0sf1ZJOGuhVcPZXHOfybtUPqVjA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-2.3.0.tgz", + "integrity": "sha512-vRNj8Giibe22LRWTKk47ZrQs1HkrhLO2vOpSmwjLktqx6UB5JXpH4Tf1kQNpEIKMpoHF69/oHlCMOxjmc4r8ow==", "license": "Apache-2.0" }, "node_modules/@oceanprotocol/ddo-js": { @@ -25629,9 +25629,9 @@ } }, "@oceanprotocol/contracts": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-2.2.1.tgz", - "integrity": "sha512-ub+CuN61seLtUvdTm/iFCyF6+wG5iCovhLaDQywKJw3RuM4gzSnxeOkBf0n0sf1ZJOGuhVcPZXHOfybtUPqVjA==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-2.3.0.tgz", + "integrity": "sha512-vRNj8Giibe22LRWTKk47ZrQs1HkrhLO2vOpSmwjLktqx6UB5JXpH4Tf1kQNpEIKMpoHF69/oHlCMOxjmc4r8ow==" }, "@oceanprotocol/ddo-js": { "version": "0.0.8", diff --git a/package.json b/package.json index 5f5a052df..3469a710c 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ }, "dependencies": { "@oasisprotocol/sapphire-paratime": "^1.3.2", - "@oceanprotocol/contracts": "^2.2.0", + "@oceanprotocol/contracts": "^2.3.0", "@oceanprotocol/ddo-js": "^0.0.8", "@rdfjs/dataset": "^2.0.2", "@rdfjs/formats-common": "^3.1.0", diff --git a/src/contracts/Escrow.ts b/src/contracts/Escrow.ts new file mode 100644 index 000000000..732278057 --- /dev/null +++ b/src/contracts/Escrow.ts @@ -0,0 +1,167 @@ +import { Signer } from 'ethers' +import Escrow from '@oceanprotocol/contracts/artifacts/contracts/escrow/Escrow.sol/Escrow.json' +import { amountToUnits, sendTx } from '../utils/ContractUtils' +import { AbiItem, ReceiptOrEstimate } from '../@types' +import { Config } from '../config' +import { SmartContractWithAddress } from './SmartContractWithAddress' + +export class EscrowContract extends SmartContractWithAddress { + public abiEnterprise: AbiItem[] + + getDefaultAbi() { + return Escrow.abi as AbiItem[] + } + + /** + * Instantiate AccessList class + * @param {string} address The contract address. + * @param {Signer} signer The signer object. + * @param {string | number} [network] Network id or name + * @param {Config} [config] The configuration object. + * @param {AbiItem[]} [abi] ABI array of the smart contract + * @param {AbiItem[]} abiEnterprise Enterprise ABI array of the smart contract + */ + constructor( + address: string, + signer: Signer, + network?: string | number, + config?: Config, + abi?: AbiItem[] + ) { + super(address, signer, network, config, abi) + this.abi = abi || this.getDefaultAbi() + } + + /** + * Get Funds + * @return {Promise} Funds + */ + public async getFunds(token: string): Promise { + return await this.contract.getFunds(token) + } + + /** + * Get User Funds + * @return {Promise} User funds + */ + public async getUserFunds(payer: string, token: string): Promise { + return await this.contract.getUserFunds(payer, token) + } + + /** + * Get Locks + * @return {Promise<[]>} Locks + */ + public async getLocks(token: string, payer: string, payee: string): Promise { + return await this.contract.getLocks(token, payer, payee) + } + + /** + * Get Authorizations + * @return {Promise<[]>} Authorizations + */ + public async getAuthorizations( + token: string, + payer: string, + payee: string + ): Promise { + return await this.contract.getAuthorizations(token, payer, payee) + } + + /** + * Deposit funds + * @param {String} token Token address + * @param {String} amount tokenURI + * @param {Boolean} estimateGas if True, return gas estimate + * @return {Promise} returns the transaction receipt or the estimateGas value + */ + public async deposit( + token: string, + amount: string, + estimateGas?: G + ): Promise> { + const amountParsed = amountToUnits(null, null, amount, 18) + const estGas = await this.contract.estimateGas.deposit(token, amountParsed) + if (estimateGas) return >estGas + const trxReceipt = await sendTx( + estGas, + this.getSignerAccordingSdk(), + this.config?.gasFeeMultiplier, + this.contract.deposit, + token, + amountParsed + ) + return >trxReceipt + } + + /** + * Withdraw funds + * @param {String[]} tokens Array of token addresses + * @param {String[]} amounts Array of token amounts + * @param {Boolean} estimateGas if True, return gas estimate + * @return {Promise} returns the transaction receipt or the estimateGas value + */ + public async withdraw( + tokens: string[], + amounts: string[], + estimateGas?: G + ): Promise> { + const amountsParsed = amounts.map((amount) => amountToUnits(null, null, amount, 18)) + + const estGas = await this.contract.estimateGas.withdraw(tokens, amountsParsed) + if (estimateGas) return >estGas + + const trxReceipt = await sendTx( + estGas, + this.getSignerAccordingSdk(), + this.config?.gasFeeMultiplier, + this.contract.withdraw, + tokens, + amountsParsed + ) + return >trxReceipt + } + + /** + * Authorize locks + * @param {String} token Token address + * @param {String} payee, + * @param {String} maxLockedAmount, + * @param {String} maxLockSeconds, + * @param {String} maxLockCounts, + * @param {Boolean} estimateGas if True, return gas estimate + * @return {Promise} returns the transaction receipt or the estimateGas value + */ + public async authorize( + token: string, + payee: string, + maxLockedAmount: string, + maxLockSeconds: string, + maxLockCounts: string, + estimateGas?: G + ): Promise> { + const maxLockedAmountParsed = amountToUnits(null, null, maxLockedAmount, 18) + const maxLockSecondsParsed = amountToUnits(null, null, maxLockSeconds, 18) + const maxLockCountsParsed = amountToUnits(null, null, maxLockCounts, 18) + const estGas = await this.contract.estimateGas.authorize( + token, + payee, + maxLockedAmountParsed, + maxLockSecondsParsed, + maxLockCountsParsed + ) + if (estimateGas) return >estGas + const trxReceipt = await sendTx( + estGas, + this.getSignerAccordingSdk(), + this.config?.gasFeeMultiplier, + this.contract.authorize, + token, + payee, + maxLockedAmountParsed, + maxLockSecondsParsed, + maxLockCountsParsed + ) + return >trxReceipt + } +} diff --git a/test/unit/Escrow.test.ts b/test/unit/Escrow.test.ts new file mode 100644 index 000000000..39f53c90b --- /dev/null +++ b/test/unit/Escrow.test.ts @@ -0,0 +1,99 @@ +import { assert } from 'chai' +import { provider, getAddresses } from '../config' +import { BigNumber, Signer } from 'ethers' + +import { Datatoken, amountToUnits, unitsToAmount } from '../../src/' +import { EscrowContract } from '../../src/contracts/Escrow' + +describe('Escrow payments flow', () => { + let user1: Signer + let user2: Signer + let Escrow: EscrowContract + let datatoken: Datatoken + let addresses + let OCEAN + + before(async () => { + user1 = (await provider.getSigner(3)) as Signer + user2 = (await provider.getSigner(4)) as Signer + + addresses = await getAddresses() + OCEAN = addresses.Ocean + }) + + it('should initialize Escrow class', async () => { + Escrow = new EscrowContract(addresses.Escrow, user2, await user2.getChainId()) + assert(Escrow !== null) + }) + + it('User2 makes a deposit in Escrow', async () => { + datatoken = new Datatoken(user2, await user2.getChainId()) + const initialBalance = await datatoken.balance(OCEAN, await user2.getAddress()) + const initialDepositedEscrow = await Escrow.getUserFunds( + await user2.getAddress(), + OCEAN + ) + const initialDepositedEscrowAmount = await unitsToAmount( + null, + null, + initialDepositedEscrow[0].toString(), + 18 + ) + + await datatoken.approve(OCEAN, await user2.getAddress(), '1000') + + await datatoken.transfer(OCEAN, await user2.getAddress(), '1000') + + assert( + (await datatoken.balance(OCEAN, await user2.getAddress())) !== + `${initialBalance + 1000}` + ) + + await datatoken.approve(OCEAN, addresses.Escrow, '1000') + await Escrow.deposit(OCEAN, '100') + + const funds = await Escrow.getUserFunds(await user2.getAddress(), OCEAN) + const available = BigNumber.from(funds[0]) + const expectedAmount = await amountToUnits( + null, + null, + String(Number(initialDepositedEscrowAmount) + 100), + 18 + ) + assert(available.toString() === expectedAmount) + }) + + it('Withdraws funds', async () => { + const availableUserFunds = await Escrow.getUserFunds(await user2.getAddress(), OCEAN) + const availableUserFundsAmount = await unitsToAmount( + null, + null, + availableUserFunds[0].toString(), + 18 + ) + + const tx = await Escrow.withdraw([OCEAN], ['50']) + + assert(tx, 'failed to withdraw half of available tokens') + const funds = await Escrow.getUserFunds(await user2.getAddress(), OCEAN) + const available = BigNumber.from(funds[0]) + const expectedAmount = await amountToUnits( + null, + null, + String(Number(availableUserFundsAmount) - 50), + 18 + ) + assert(available.toString() === expectedAmount) + }) + + it('Authorize user1', async () => { + const tx = await Escrow.authorize(OCEAN, await user1.getAddress(), '20', '100', '3') + assert(tx, 'failed to authorize user1') + const auths = await Escrow.getAuthorizations( + OCEAN, + await user2.getAddress(), + await user1.getAddress() + ) + assert(auths[0][0] === (await user1.getAddress()), 'payee address not present') + }) +})