From dd383ec36598f29ffb400cbe047bba302a04b72e Mon Sep 17 00:00:00 2001 From: Alex Coseru Date: Tue, 6 Sep 2022 21:43:51 +0300 Subject: [PATCH] add DataFarming contract support (#1596) * add DataFarming contract support --- src/contracts/df/DfRewards.ts | 106 ++++++++++++++++++++++++ src/contracts/df/DfStrategyV1.ts | 67 ++++++++++++++++ src/contracts/index.ts | 2 + test/unit/DFRewards.test.ts | 133 +++++++++++++++++++++++++++++++ 4 files changed, 308 insertions(+) create mode 100644 src/contracts/df/DfRewards.ts create mode 100644 src/contracts/df/DfStrategyV1.ts create mode 100644 test/unit/DFRewards.test.ts diff --git a/src/contracts/df/DfRewards.ts b/src/contracts/df/DfRewards.ts new file mode 100644 index 000000000..7e54a0ca3 --- /dev/null +++ b/src/contracts/df/DfRewards.ts @@ -0,0 +1,106 @@ +import { AbiItem } from 'web3-utils' +import dfRewardsABI from '@oceanprotocol/contracts/artifacts/contracts/df/DFRewards.sol/DFRewards.json' +import { calculateEstimatedGas, sendTx } from '../../utils' +import { SmartContractWithAddress } from '../SmartContractWithAddress' +import { ReceiptOrEstimate } from '../../@types' + +/** + * Provides an interface for DFRewards contract + */ +export class DfRewards extends SmartContractWithAddress { + getDefaultAbi(): AbiItem | AbiItem[] { + return dfRewardsABI.abi as AbiItem[] + } + + /** Get available DF Rewards for a token + * @param {String} userAddress user address + * @param {String} tokenAddress token address + * @return {Promise} + */ + public async getAvailableRewards( + userAddress: string, + tokenAddress: string + ): Promise { + const rewards = await this.contract.methods + .claimable(userAddress, tokenAddress) + .call() + const rewardsFormated = await this.unitsToAmount(tokenAddress, rewards) + + return rewardsFormated + } + + /** + * claim rewards for any address + * @param {String} fromUserAddress user that generates the tx + * @param {String} userAddress user address to claim + * @param {String} tokenAddress token address + * @return {Promise} + */ + public async claimRewards( + fromUserAddress: string, + userAddress: string, + tokenAddress: string, + estimateGas?: G + ): Promise> { + const estGas = await calculateEstimatedGas( + fromUserAddress, + this.contract.methods.claimFor, + userAddress, + tokenAddress + ) + if (estimateGas) return >estGas + + // Invoke function of the contract + const trxReceipt = await sendTx( + fromUserAddress, + estGas + 1, + this.web3, + this.config?.gasFeeMultiplier, + this.contract.methods.claimFor, + userAddress, + tokenAddress + ) + return >trxReceipt + } + + /** + * allocate rewards to address. An approve must exist before calling this function. + * @param {String} fromUserAddress user that generates the tx + * @param {String[]} userAddresses array of users that will receive rewards + * @param {String[]} amounts array of amounts + * @param {String} tokenAddress token address + * @return {Promise} + */ + public async allocateRewards( + fromUserAddress: string, + userAddresses: string[], + amounts: string[], + tokenAddress: string, + estimateGas?: G + ): Promise> { + for (let i = 0; i < amounts.length; i++) { + amounts[i] = await this.amountToUnits(tokenAddress, amounts[i]) + } + const estGas = await calculateEstimatedGas( + fromUserAddress, + this.contract.methods.allocate, + userAddresses, + amounts, + tokenAddress + ) + if (estimateGas) return >estGas + + // Invoke function of the contract + const trxReceipt = await sendTx( + fromUserAddress, + estGas + 1, + this.web3, + this.config?.gasFeeMultiplier, + this.contract.methods.allocate, + userAddresses, + amounts, + tokenAddress + ) + return >trxReceipt + } +} diff --git a/src/contracts/df/DfStrategyV1.ts b/src/contracts/df/DfStrategyV1.ts new file mode 100644 index 000000000..5e5731e1a --- /dev/null +++ b/src/contracts/df/DfStrategyV1.ts @@ -0,0 +1,67 @@ +import { AbiItem } from 'web3-utils' +import dfStrategyV1ABI from '@oceanprotocol/contracts/artifacts/contracts/df/DFStrategyV1.sol/DFStrategyV1.json' +import { calculateEstimatedGas, sendTx } from '../../utils' +import { SmartContractWithAddress } from '../SmartContractWithAddress' +import { ReceiptOrEstimate } from '../../@types' + +/** + * Provides an interface for dfStrategyV1 contract + */ +export class DfStrategyV1 extends SmartContractWithAddress { + getDefaultAbi(): AbiItem | AbiItem[] { + return dfStrategyV1ABI.abi as AbiItem[] + } + + /** Get available DF Rewards for multiple tokens + * @param {String} userAddress user address + * @param {String} tokenAddresses array of tokens + * @return {Promise} + */ + public async getMultipleAvailableRewards( + userAddress: string, + tokenAddresses: string[] + ): Promise { + const rewards = await this.contract.methods + .claimables(userAddress, tokenAddresses) + .call() + const rewardsFormated: string[] = [] + for (let i = 0; i < rewards.length; i++) { + rewardsFormated.push(await this.unitsToAmount(tokenAddresses[i], rewards[i])) + } + return rewardsFormated + } + + /** + * claim multiple token rewards for any address + * @param {String} fromUserAddress user that generates the tx + * @param {String} userAddress user address to claim + * @param {String} tokenAddresses array of tokens + * @return {Promise} + */ + public async claimMultipleRewards( + fromUserAddress: string, + userAddress: string, + tokenAddresses: string[], + estimateGas?: G + ): Promise> { + const estGas = await calculateEstimatedGas( + fromUserAddress, + this.contract.methods.claimMultiple, + userAddress, + tokenAddresses + ) + if (estimateGas) return >estGas + + // Invoke function of the contract + const trxReceipt = await sendTx( + fromUserAddress, + estGas + 1, + this.web3, + this.config?.gasFeeMultiplier, + this.contract.methods.claimMultiple, + userAddress, + tokenAddresses + ) + return >trxReceipt + } +} diff --git a/src/contracts/index.ts b/src/contracts/index.ts index 7c5bb899a..2db333639 100644 --- a/src/contracts/index.ts +++ b/src/contracts/index.ts @@ -9,3 +9,5 @@ export * from './NFTFactory' export * from './ve/VeOcean' export * from './ve/VeFeeDistributor' export * from './ve/VeAllocate' +export * from './df/DfRewards' +export * from './df/DfStrategyV1' diff --git a/test/unit/DFRewards.test.ts b/test/unit/DFRewards.test.ts new file mode 100644 index 000000000..b63e2bfb5 --- /dev/null +++ b/test/unit/DFRewards.test.ts @@ -0,0 +1,133 @@ +import { assert } from 'chai' +import { AbiItem } from 'web3-utils' +import { web3, getTestConfig, getAddresses } from '../config' +import { + Config, + approve, + DfRewards, + DfStrategyV1, + sendTx, + calculateEstimatedGas +} from '../../src' + +describe('veOcean tests', async () => { + let config: Config + let addresses: any + let nftFactory + let dfRewards: DfRewards + let dfStrategy: DfStrategyV1 + let ownerAccount: string + let Alice: string + let Bob: string + let nft1, nft2, nft3 + let tokenContract + let chainId + + before(async () => { + config = await getTestConfig(web3) + }) + + it('initialize accounts', async () => { + addresses = getAddresses() + const accounts = await web3.eth.getAccounts() + chainId = await web3.eth.getChainId() + ownerAccount = accounts[0] + Alice = accounts[1] + Bob = accounts[2] + const minAbi = [ + { + constant: false, + inputs: [ + { name: 'to', type: 'address' }, + { name: 'value', type: 'uint256' } + ], + name: 'mint', + outputs: [{ name: '', type: 'bool' }], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + }, + { + constant: true, + inputs: [{ name: 'owner', type: 'address' }], + name: 'balanceOf', + outputs: [{ name: '', type: 'uint256' }], + payable: false, + stateMutability: 'view', + type: 'function' + } + ] as AbiItem[] + tokenContract = new web3.eth.Contract(minAbi, addresses.Ocean) + const estGas = await calculateEstimatedGas( + ownerAccount, + tokenContract.methods.mint, + ownerAccount, + web3.utils.toWei('10000') + ) + // mint some Oceans + await sendTx( + ownerAccount, + estGas, + web3, + 1, + tokenContract.methods.mint, + ownerAccount, + web3.utils.toWei('1000') + ) + dfRewards = new DfRewards(addresses.DFRewards, web3) + dfStrategy = new DfStrategyV1(addresses.DFStrategyV1, web3) + }) + + it('Generous owner should allocate some DF Rewards', async () => { + const dfOceanBalance = web3.utils.fromWei( + await tokenContract.methods.balanceOf(addresses.DFRewards).call() + ) + // approve 500 tokens + await approve(web3, config, ownerAccount, addresses.Ocean, addresses.DFRewards, '300') + // fund DFRewards + await dfRewards.allocateRewards( + ownerAccount, + [Alice, Bob], + ['100', '200'], + addresses.Ocean + ) + const newDfOceanBalance = web3.utils.fromWei( + await tokenContract.methods.balanceOf(addresses.DFRewards).call() + ) + const expected = parseInt(dfOceanBalance) + 300 + assert(parseInt(newDfOceanBalance) === expected, 'DFRewards allocate failed') + }) + + it('Alice should check for rewards', async () => { + const rewards = await dfRewards.getAvailableRewards(Alice, addresses.Ocean) + assert(parseInt(rewards) >= 100, 'Alice reward missmatch, got only ' + rewards) + const multipleRewards = await dfStrategy.getMultipleAvailableRewards(Alice, [ + addresses.Ocean + ]) + assert(parseInt(multipleRewards[0]) >= 100, 'Alice reward missmatch') + }) + + it('Alice should claim the rewards using DFRewards claim', async () => { + const aliceOceanBalance = web3.utils.fromWei( + await tokenContract.methods.balanceOf(Alice).call() + ) + await dfRewards.claimRewards(Alice, Alice, addresses.Ocean) + const newAliceOceanBalance = web3.utils.fromWei( + await tokenContract.methods.balanceOf(Alice).call() + ) + const expected = parseInt(aliceOceanBalance) + 100 + assert(parseInt(newAliceOceanBalance) >= expected, 'Alice failed to claim') + }) + + it('Bob should claim the rewards using DFStrategy claim', async () => { + const bobOceanBalance = web3.utils.fromWei( + await tokenContract.methods.balanceOf(Bob).call() + ) + await dfStrategy.claimMultipleRewards(Bob, Bob, [addresses.Ocean]) + const newBobOceanBalance = web3.utils.fromWei( + await tokenContract.methods.balanceOf(Bob).call() + ) + const expected = parseInt(bobOceanBalance) + 200 + assert(parseInt(newBobOceanBalance) >= expected, 'Bob failed to claim') + }) +})