From 8df2093496fb40dc6504e816d985186a9b3ba33d Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 1 Oct 2021 10:44:57 +0300 Subject: [PATCH 1/9] feat: adapters for ClusterToken and tests for adapters --- .../adapters/dehive/ClusterTokenAdapter.sol | 49 +++ .../adapters/dehive/DeHiveProtocolAdapter.sol | 37 ++ .../dehive/ClusterTokenInteractiveAdapter.sol | 104 ++++++ test/adapters/ClusterTokenAdapter.js | 43 +++ test/adapters/DeHiveProtocolAdapter.js | 29 ++ .../ClusterTokenInteractiveAdapter.js | 316 ++++++++++++++++++ 6 files changed, 578 insertions(+) create mode 100644 contracts/adapters/dehive/ClusterTokenAdapter.sol create mode 100644 contracts/adapters/dehive/DeHiveProtocolAdapter.sol create mode 100644 contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapter.sol create mode 100644 test/adapters/ClusterTokenAdapter.js create mode 100644 test/adapters/DeHiveProtocolAdapter.js create mode 100644 test/interactiveAdapters/ClusterTokenInteractiveAdapter.js diff --git a/contracts/adapters/dehive/ClusterTokenAdapter.sol b/contracts/adapters/dehive/ClusterTokenAdapter.sol new file mode 100644 index 00000000..d4c10c1d --- /dev/null +++ b/contracts/adapters/dehive/ClusterTokenAdapter.sol @@ -0,0 +1,49 @@ +// Copyright (C) 2020 Zerion Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: LGPL-3.0-only + +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import { TokenAdapter } from "../TokenAdapter.sol"; +import { Component } from "../../shared/Structs.sol"; +import { IClusterToken } from "../../interfaces/IClusterToken.sol"; + +/** + * @title Token adapter for ClusterTokens. + * @dev Implementation of TokenAdapter abstract contract. + * @author Igor Sobolev + */ +contract ClusterTokenAdapter is TokenAdapter { + /** + * @return Array of Component structs with underlying tokens rates for the given token. + * @dev Implementation of TokenAdapter abstract contract function. + */ + function getComponents(address token) external override view returns (Component[] memory) { + address[] memory underlyings = IClusterToken(token).getUnderlyings(); + uint256[] memory underlyingsShares = IClusterToken(token).getUnderlyingInCluster(); + + Component[] memory components = new Component[](underlyings.length); + for(uint256 i = 0; i < underlyings.length; i++) { + components[i] = Component({ + token: underlyings[i], + rate: int256(underlyingsShares[i] * 1e18 / 1e6) + }); + } + + return components; + } +} diff --git a/contracts/adapters/dehive/DeHiveProtocolAdapter.sol b/contracts/adapters/dehive/DeHiveProtocolAdapter.sol new file mode 100644 index 00000000..55a29271 --- /dev/null +++ b/contracts/adapters/dehive/DeHiveProtocolAdapter.sol @@ -0,0 +1,37 @@ +// Copyright (C) 2020 Zerion Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: LGPL-3.0-only + +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import { ERC20 } from "../../interfaces/ERC20.sol"; +import { ProtocolAdapter } from "../ProtocolAdapter.sol"; + +/** + * @title Cluster adapter for DeHive protocol. + * @dev Implementation of ProtocolAdapter abstract contract. + * @author Igor Sobolev + */ +contract DeHiveProtocolAdapter is ProtocolAdapter { + /** + * @dev MUST return amount and type of the given token + * locked on the protocol by the given account. + */ + function getBalance(address token, address account) public override view returns (int256) { + return int256(ERC20(token).balanceOf(account)); + } +} \ No newline at end of file diff --git a/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapter.sol b/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapter.sol new file mode 100644 index 00000000..d82cdf25 --- /dev/null +++ b/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapter.sol @@ -0,0 +1,104 @@ +// Copyright (C) 2020 Zerion Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: LGPL-3.0-only + +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import { TokenAmount } from "../../shared/Structs.sol"; +import { InteractiveAdapter } from "../InteractiveAdapter.sol"; +import { ERC20 } from "../../interfaces/ERC20.sol"; +import { SafeERC20 } from "../../shared/SafeERC20.sol"; + +import { DeHiveProtocolAdapter } from "../../adapters/dehive/DeHiveProtocolAdapter.sol"; + +import { IExternalAdapter } from "../../interfaces/IExternalAdapter.sol"; + +/** + * @title Interactive adapter for DeHive protocol. + * @dev Implementation of InteractiveAdapter abstract contract. + */ + +contract ClusterTokenInteractiveAdapter is InteractiveAdapter, DeHiveProtocolAdapter { + using SafeERC20 for ERC20; + + address internal constant EXTERNAL_ADAPTER = address(0x0c1520153044bE0bd29D93E356c1BABfa4996c6A); + /** + * @notice Deposits tokens to the DeHive ClusterToken. + * @param tokenAmounts Array with one element - TokenAmount struct with + * ETH address, ETH amount to be deposited, and amount type. + * @param data ABI-encoded additional parameters: + * - clusterToken - ClusterToken address. + * @return tokensToBeWithdrawn Array with two elements - ETH and ClusterToken address. + * @dev Implementation of InteractiveAdapter function. + */ + + function deposit(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + require(tokenAmounts.length == 1, "DeHive: should be one token[1]"); + require(tokenAmounts[0].token == ETH, "DeHive: should be ETH[2]"); + + address clusterToken = abi.decode(data, (address)); + uint256 amount = getAbsoluteAmountDeposit(tokenAmounts[0]); + + tokensToBeWithdrawn = new address[](2); + tokensToBeWithdrawn[0] = clusterToken; + tokensToBeWithdrawn[1] = ETH; + + //require(false, "error 2"); + // solhint-disable-next-line no-empty-blocks + try IExternalAdapter(EXTERNAL_ADAPTER).deposit{value: amount}(clusterToken) {} catch Error(string memory reason) { + revert(reason); + } catch { + revert("DeHive: deposit fail"); + } + } + + /** + * @notice Withdraws tokens from the DeHive protocol. + * @param tokenAmounts Array with one element - TokenAmount struct with + * ClusterToken address, ClusterToken amount to be redeemed, and amount type. + * @return tokensToBeWithdrawn Array with one element - ETH address. + * @dev Implementation of InteractiveAdapter function. + */ + + function withdraw(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + require(tokenAmounts.length == 1, "DeHive: should be 1 tokenAmount[3]"); + + address clusterToken = tokenAmounts[0].token; + uint256 amount = getAbsoluteAmountWithdraw(tokenAmounts[0]); + + tokensToBeWithdrawn = new address[](1); + tokensToBeWithdrawn[0] = ETH; + + ERC20(clusterToken).safeApprove(EXTERNAL_ADAPTER, amount, "DeHive"); + // solhint-disable-next-line no-empty-blocks + try IExternalAdapter(EXTERNAL_ADAPTER).withdraw(clusterToken, amount) {} catch Error(string memory reason) { + revert(reason); + } catch { + revert("DeHive: withdraw fail"); + } + } +} \ No newline at end of file diff --git a/test/adapters/ClusterTokenAdapter.js b/test/adapters/ClusterTokenAdapter.js new file mode 100644 index 00000000..918feae6 --- /dev/null +++ b/test/adapters/ClusterTokenAdapter.js @@ -0,0 +1,43 @@ +const BN = web3.utils.BN; + +const TokenAdapter = artifacts.require('ClusterTokenAdapter'); +const IClusterToken = artifacts.require('IClusterToken'); + +contract('ClusterTokenAdapter', () => { + const polyClusterAddress = '0x4964B3B599B82C3FdDC56e3A9Ffd77d48c6AF0f0'; + let accounts; + let tokenAdapter; + let clusterToken; + const DPOL = [ + 'Polycluster', + 'DPOL', + '18' + ]; + + before(async() => { + accounts = await web3.eth.getAccounts(); + tokenAdapter = await TokenAdapter.new(); + clusterToken = await IClusterToken.at(polyClusterAddress); + }); + + it('Should return correct components', async() => { + const underlyingsFromCluster = await clusterToken.getUnderlyings(); + const proportions = await clusterToken.getUnderlyingInCluster(); + + const components = await tokenAdapter.getComponents(polyClusterAddress); + for(let i = 0; i < underlyingsFromCluster.length; i++) { + assert.equal(underlyingsFromCluster[i], components[i].token); + assert.equal((proportions[i].mul( + new BN('1000000000000000000') + ).div( + new BN('1000000') + ) + ).toString(), components[i].rate); + } + }); + + it('Should return correct metadata', async() => { + const metadata = await tokenAdapter.getMetadata(polyClusterAddress); + assert.deepEqual(DPOL, metadata); + }) +}); \ No newline at end of file diff --git a/test/adapters/DeHiveProtocolAdapter.js b/test/adapters/DeHiveProtocolAdapter.js new file mode 100644 index 00000000..5d281bb5 --- /dev/null +++ b/test/adapters/DeHiveProtocolAdapter.js @@ -0,0 +1,29 @@ +const BN = web3.utils.BN; + +const ProtocolAdapter = artifacts.require('DeHiveProtocolAdapter'); +const IClusterToken = artifacts.require('IClusterToken'); + +contract('DeHiveProtocolAdapter', () => { + const polyClusterAddress = '0x4964B3B599B82C3FdDC56e3A9Ffd77d48c6AF0f0'; + let accounts; + let protocolAdapter; + let clusterToken; + + before(async() => { + accounts = await web3.eth.getAccounts(); + protocolAdapter = await ProtocolAdapter.new({from: accounts[0]}); + clusterToken = await IClusterToken.at(polyClusterAddress); + }); + + it('Should return correct balance', async() => { + let bal = await protocolAdapter.getBalance(polyClusterAddress, accounts[0]); + assert.equal(bal, 0); + + let cl = new BN('1000000000000000000'); + let eth = await web3.utils.toWei('3000', 'ether'); + await clusterToken.assemble(cl, true, {from: accounts[0], value: eth}); + + assert.equal(cl.toString(), + (await protocolAdapter.getBalance(polyClusterAddress, accounts[0])).toString()); + }); +}); \ No newline at end of file diff --git a/test/interactiveAdapters/ClusterTokenInteractiveAdapter.js b/test/interactiveAdapters/ClusterTokenInteractiveAdapter.js new file mode 100644 index 00000000..01434227 --- /dev/null +++ b/test/interactiveAdapters/ClusterTokenInteractiveAdapter.js @@ -0,0 +1,316 @@ +import expectRevert from '../helpers/expectRevert'; +import convertToShare from '../helpers/convertToShare'; +import convertToBytes32 from '../helpers/convertToBytes32'; + +const BN = web3.utils.BN; + +const CLUSTER_TOKEN_ADAPTER = convertToBytes32('Cluster Token'); + +const ACTION_DEPOSIT = 1; +const ACTION_WITHDRAW = 2; +const AMOUNT_RELATIVE = 1; +const AMOUNT_ABSOLUTE = 2; +const EMPTY_BYTES = '0x'; + +const ZERO = '0x0000000000000000000000000000000000000000'; + +const ProtocolAdapterRegistry = artifacts.require('./ProtocolAdapterRegistry'); +const InteractiveAdapter = artifacts.require('./ClusterTokenInteractiveAdapter'); +const Core = artifacts.require('./Core'); +const Router = artifacts.require('./Router'); +const ERC20 = artifacts.require('./ERC20'); +const ClusterExternalAdapter = artifacts.require('./IExternalAdapter'); + + +contract('ClusterTokenInteractiveAdapter', () => { + const ethAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; + const polyClusterAddress = '0x4964B3B599B82C3FdDC56e3A9Ffd77d48c6AF0f0'; + const daiAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; + const clusterExternalAdapterAddress = '0x0c1520153044bE0bd29D93E356c1BABfa4996c6A'; + let encodedData; + + let accounts; + let core; + let router; + let protocolAdapterRegistry; + let protocolAdapterAddress; + let clusterTokenInstance; + let clusterExternalAdapter; + + beforeEach(async () => { + accounts = await web3.eth.getAccounts(); + encodedData = web3.eth.abi.encodeParameter('address', polyClusterAddress); + await InteractiveAdapter.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterAddress = result.address; + }); + await ProtocolAdapterRegistry.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterRegistry = result.contract; + }); + await protocolAdapterRegistry.methods.addProtocolAdapters( + [ + CLUSTER_TOKEN_ADAPTER, + ], + [ + protocolAdapterAddress, + ], + [[]], + ) + .send({ + from: accounts[0], + gas: '1000000', + }); + await Core.new( + protocolAdapterRegistry.options.address, + { from: accounts[0] }, + ) + .then((result) => { + core = result.contract; + }); + await Router.new( + core.options.address, + { from: accounts[0] }, + ) + .then((result) => { + router = result.contract; + }); + await ERC20.at(polyClusterAddress) + .then((result) => { + clusterTokenInstance = result.contract; + }); + clusterExternalAdapter = await ClusterExternalAdapter.at(clusterExternalAdapterAddress); + const role = await clusterExternalAdapter.PROTOCOL_ROLE(); + await clusterExternalAdapter.grantRole(role, core.options.address); + }); + + describe('ETH <-> PolyCluster', async() => { + it('Should not be correct ETH -> PolyCluster if 2 tokens', async() => { + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`polycluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await expectRevert(router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_DEPOSIT, + [ + [ethAddress, convertToShare(1), AMOUNT_RELATIVE], + [ethAddress, convertToShare(1), AMOUNT_RELATIVE], + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + })); + }); + + it('Should not be correct DAI -> PolyCluster', async() => { + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`polycluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await expectRevert(router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_DEPOSIT, + [ + [daiAddress, convertToShare(1), AMOUNT_RELATIVE] + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + })); + }); + + it('Should be correct ETH -> PolyCluster deposit', async() => { + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`polycluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_DEPOSIT, + [ + [ethAddress, convertToShare(1), AMOUNT_RELATIVE] + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + }) + .then((receipt) => { + console.log(`called router for ${receipt.cumulativeGasUsed} gas`); + }); + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`polycluster amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + await clusterTokenInstance.methods['balanceOf(address)'](core.options.address) + .call() + .then((result) => { + assert.equal(result, 0); + }); + await web3.eth.getBalance(core.options.address) + .then((result) => { + assert.equal(result, 0); + }); + }); + + it('Should not be correct ETH <- Polycluster withdraw if 2 tokens', async() => { + let polyClusterAmount; + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + polyClusterAmount = result; + console.log(`polycluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await clusterTokenInstance.methods.approve(router.options.address, (polyClusterAmount * 2).toString()) + .send({ + gas: 10000000, + from: accounts[0], + }); + await expectRevert(router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_WITHDRAW, + [ + [polyClusterAddress, polyClusterAmount, AMOUNT_ABSOLUTE], + [polyClusterAddress, polyClusterAmount, AMOUNT_ABSOLUTE] + ], + EMPTY_BYTES, + ], + ], + [ + [ + [polyClusterAddress, polyClusterAmount, AMOUNT_ABSOLUTE], + [0, EMPTY_BYTES] + ] + ], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + })); + }); + + it('Should be correct ETH <- PolyCluster withdraw', async() => { + let polyClusterAmount; + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + polyClusterAmount = result; + console.log(`polycluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await clusterTokenInstance.methods.approve(router.options.address, (polyClusterAmount * 2).toString()) + .send({ + gas: 10000000, + from: accounts[0], + }); + await router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_WITHDRAW, + [ + [polyClusterAddress, polyClusterAmount, AMOUNT_ABSOLUTE] + ], + EMPTY_BYTES, + ], + ], + [ + [ + [polyClusterAddress, polyClusterAmount, AMOUNT_ABSOLUTE], + [0, EMPTY_BYTES] + ] + ], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + }) + .then((receipt) => { + console.log(`called router for ${receipt.cumulativeGasUsed} gas`); + }); + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`polycluster amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + await clusterTokenInstance.methods['balanceOf(address)'](core.options.address) + .call() + .then((result) => { + assert.equal(result, 0); + }); + await web3.eth.getBalance(core.options.address) + .then((result) => { + assert.equal(result, 0); + }); + }); + }); +}); \ No newline at end of file From 07d6482e5b73e405e1f06801268b9cca98f5250d Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Oct 2021 11:34:51 +0300 Subject: [PATCH 2/9] feat: correct external adapter address --- .../dehive/ClusterTokenInteractiveAdapter.sol | 3 +- contracts/interfaces/IClusterToken.sol | 34 +++++++++++++++++++ contracts/interfaces/IExternalAdapter.sol | 10 ++++++ .../ClusterTokenInteractiveAdapter.js | 4 --- 4 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 contracts/interfaces/IClusterToken.sol create mode 100644 contracts/interfaces/IExternalAdapter.sol diff --git a/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapter.sol b/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapter.sol index d82cdf25..2b33e754 100644 --- a/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapter.sol +++ b/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapter.sol @@ -35,7 +35,7 @@ import { IExternalAdapter } from "../../interfaces/IExternalAdapter.sol"; contract ClusterTokenInteractiveAdapter is InteractiveAdapter, DeHiveProtocolAdapter { using SafeERC20 for ERC20; - address internal constant EXTERNAL_ADAPTER = address(0x0c1520153044bE0bd29D93E356c1BABfa4996c6A); + address internal constant EXTERNAL_ADAPTER = address(0xaf63F200148fe942280C10220aCD0780006BBA91); /** * @notice Deposits tokens to the DeHive ClusterToken. * @param tokenAmounts Array with one element - TokenAmount struct with @@ -62,7 +62,6 @@ contract ClusterTokenInteractiveAdapter is InteractiveAdapter, DeHiveProtocolAda tokensToBeWithdrawn[0] = clusterToken; tokensToBeWithdrawn[1] = ETH; - //require(false, "error 2"); // solhint-disable-next-line no-empty-blocks try IExternalAdapter(EXTERNAL_ADAPTER).deposit{value: amount}(clusterToken) {} catch Error(string memory reason) { revert(reason); diff --git a/contracts/interfaces/IClusterToken.sol b/contracts/interfaces/IClusterToken.sol new file mode 100644 index 00000000..129e6f2d --- /dev/null +++ b/contracts/interfaces/IClusterToken.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; + +interface IClusterToken { + function assemble(uint256 clusterAmount, bool coverDhvWithEth) external payable returns (uint256); + + function disassemble(uint256 indexAmount, bool coverDhvWithEth) external; + + function withdrawToAccumulation(uint256 _clusterAmount) external; + + function refundFromAccumulation(uint256 _clusterAmount) external; + + function returnDebtFromAccumulation(uint256[] calldata _amounts, uint256 _clusterAmount) external; + + function optimizeProportion(uint256[] memory updatedShares) external returns (uint256[] memory debt); + + function getUnderlyingInCluster() external view returns (uint256[] calldata); + + function getUnderlyings() external view returns (address[] calldata); + + function getUnderlyingBalance(address _underlying) external view returns (uint256); + + function getUnderlyingsAmountsFromClusterAmount(uint256 _clusterAmount) external view returns (uint256[] calldata); + + function clusterTokenLock() external view returns (uint256); + + function clusterLock(address _token) external view returns (uint256); + + function controllerChange(address) external; + + function assembleByAdapter(uint256 _clusterAmount) external; + + function disassembleByAdapter(uint256 _clusterAmount) external; +} diff --git a/contracts/interfaces/IExternalAdapter.sol b/contracts/interfaces/IExternalAdapter.sol new file mode 100644 index 00000000..d75a62a9 --- /dev/null +++ b/contracts/interfaces/IExternalAdapter.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; + +interface IExternalAdapter { + function getTotalClusterBalance(address _asset, address _user) external returns (uint256); + + function deposit(address _asset) external payable returns (uint256); + + function withdraw(address _asset, uint256 _amount) external returns (uint256); +} diff --git a/test/interactiveAdapters/ClusterTokenInteractiveAdapter.js b/test/interactiveAdapters/ClusterTokenInteractiveAdapter.js index 01434227..a6e9d585 100644 --- a/test/interactiveAdapters/ClusterTokenInteractiveAdapter.js +++ b/test/interactiveAdapters/ClusterTokenInteractiveAdapter.js @@ -26,7 +26,6 @@ contract('ClusterTokenInteractiveAdapter', () => { const ethAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; const polyClusterAddress = '0x4964B3B599B82C3FdDC56e3A9Ffd77d48c6AF0f0'; const daiAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; - const clusterExternalAdapterAddress = '0x0c1520153044bE0bd29D93E356c1BABfa4996c6A'; let encodedData; let accounts; @@ -79,9 +78,6 @@ contract('ClusterTokenInteractiveAdapter', () => { .then((result) => { clusterTokenInstance = result.contract; }); - clusterExternalAdapter = await ClusterExternalAdapter.at(clusterExternalAdapterAddress); - const role = await clusterExternalAdapter.PROTOCOL_ROLE(); - await clusterExternalAdapter.grantRole(role, core.options.address); }); describe('ETH <-> PolyCluster', async() => { From 1992dfd15131c5cae44f4ed1bfac49f5072a9824 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 11 Oct 2021 14:39:52 +0300 Subject: [PATCH 3/9] feat: adapter for ETH cluster --- .../ClusterTokenInteractiveAdapterETH.sol | 103 ++++++ ...=> ClusterTokenInteractiveAdapterPoly.sol} | 2 +- .../ClusterTokenInteractiveAdapterEth.js | 311 ++++++++++++++++++ ... => ClusterTokenInteractiveAdapterPoly.js} | 2 +- 4 files changed, 416 insertions(+), 2 deletions(-) create mode 100644 contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterETH.sol rename contracts/interactiveAdapters/dehive/{ClusterTokenInteractiveAdapter.sol => ClusterTokenInteractiveAdapterPoly.sol} (97%) create mode 100644 test/interactiveAdapters/ClusterTokenInteractiveAdapterEth.js rename test/interactiveAdapters/{ClusterTokenInteractiveAdapter.js => ClusterTokenInteractiveAdapterPoly.js} (99%) diff --git a/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterETH.sol b/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterETH.sol new file mode 100644 index 00000000..43bd20dc --- /dev/null +++ b/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterETH.sol @@ -0,0 +1,103 @@ +// Copyright (C) 2020 Zerion Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: LGPL-3.0-only + +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import { TokenAmount } from "../../shared/Structs.sol"; +import { InteractiveAdapter } from "../InteractiveAdapter.sol"; +import { ERC20 } from "../../interfaces/ERC20.sol"; +import { SafeERC20 } from "../../shared/SafeERC20.sol"; + +import { DeHiveProtocolAdapter } from "../../adapters/dehive/DeHiveProtocolAdapter.sol"; + +import { IExternalAdapter } from "../../interfaces/IExternalAdapter.sol"; + +/** + * @title Interactive adapter for DeHive protocol. + * @dev Implementation of InteractiveAdapter abstract contract. + */ + +contract ClusterTokenInteractiveAdapterETH is InteractiveAdapter, DeHiveProtocolAdapter { + using SafeERC20 for ERC20; + + address internal constant EXTERNAL_ADAPTER = address(0xE45713a2D7d87bd7A55d84da55aEb9EC21067870); + /** + * @notice Deposits tokens to the DeHive ClusterToken. + * @param tokenAmounts Array with one element - TokenAmount struct with + * ETH address, ETH amount to be deposited, and amount type. + * @param data ABI-encoded additional parameters: + * - clusterToken - ClusterToken address. + * @return tokensToBeWithdrawn Array with two elements - ETH and ClusterToken address. + * @dev Implementation of InteractiveAdapter function. + */ + + function deposit(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + require(tokenAmounts.length == 1, "DeHive: should be one token[1]"); + require(tokenAmounts[0].token == ETH, "DeHive: should be ETH[2]"); + + address clusterToken = abi.decode(data, (address)); + uint256 amount = getAbsoluteAmountDeposit(tokenAmounts[0]); + + tokensToBeWithdrawn = new address[](2); + tokensToBeWithdrawn[0] = clusterToken; + tokensToBeWithdrawn[1] = ETH; + + // solhint-disable-next-line no-empty-blocks + try IExternalAdapter(EXTERNAL_ADAPTER).deposit{value: amount}(clusterToken) {} catch Error(string memory reason) { + revert(reason); + } catch { + revert("DeHive: deposit fail"); + } + } + + /** + * @notice Withdraws tokens from the DeHive protocol. + * @param tokenAmounts Array with one element - TokenAmount struct with + * ClusterToken address, ClusterToken amount to be redeemed, and amount type. + * @return tokensToBeWithdrawn Array with one element - ETH address. + * @dev Implementation of InteractiveAdapter function. + */ + + function withdraw(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + require(tokenAmounts.length == 1, "DeHive: should be 1 tokenAmount[3]"); + + address clusterToken = tokenAmounts[0].token; + uint256 amount = getAbsoluteAmountWithdraw(tokenAmounts[0]); + + tokensToBeWithdrawn = new address[](1); + tokensToBeWithdrawn[0] = ETH; + + ERC20(clusterToken).safeApprove(EXTERNAL_ADAPTER, amount, "DeHive"); + // solhint-disable-next-line no-empty-blocks + try IExternalAdapter(EXTERNAL_ADAPTER).withdraw(clusterToken, amount) {} catch Error(string memory reason) { + revert(reason); + } catch { + revert("DeHive: withdraw fail"); + } + } +} \ No newline at end of file diff --git a/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapter.sol b/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterPoly.sol similarity index 97% rename from contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapter.sol rename to contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterPoly.sol index 2b33e754..be3da0eb 100644 --- a/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapter.sol +++ b/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterPoly.sol @@ -32,7 +32,7 @@ import { IExternalAdapter } from "../../interfaces/IExternalAdapter.sol"; * @dev Implementation of InteractiveAdapter abstract contract. */ -contract ClusterTokenInteractiveAdapter is InteractiveAdapter, DeHiveProtocolAdapter { +contract ClusterTokenInteractiveAdapterPoly is InteractiveAdapter, DeHiveProtocolAdapter { using SafeERC20 for ERC20; address internal constant EXTERNAL_ADAPTER = address(0xaf63F200148fe942280C10220aCD0780006BBA91); diff --git a/test/interactiveAdapters/ClusterTokenInteractiveAdapterEth.js b/test/interactiveAdapters/ClusterTokenInteractiveAdapterEth.js new file mode 100644 index 00000000..1f922bc3 --- /dev/null +++ b/test/interactiveAdapters/ClusterTokenInteractiveAdapterEth.js @@ -0,0 +1,311 @@ +import expectRevert from '../helpers/expectRevert'; +import convertToShare from '../helpers/convertToShare'; +import convertToBytes32 from '../helpers/convertToBytes32'; + +const BN = web3.utils.BN; + +const CLUSTER_TOKEN_ADAPTER = convertToBytes32('Cluster Token'); + +const ACTION_DEPOSIT = 1; +const ACTION_WITHDRAW = 2; +const AMOUNT_RELATIVE = 1; +const AMOUNT_ABSOLUTE = 2; +const EMPTY_BYTES = '0x'; + +const ZERO = '0x0000000000000000000000000000000000000000'; + +const ProtocolAdapterRegistry = artifacts.require('./ProtocolAdapterRegistry'); +const InteractiveAdapter = artifacts.require('./ClusterTokenInteractiveAdapterETH'); +const Core = artifacts.require('./Core'); +const Router = artifacts.require('./Router'); +const ERC20 = artifacts.require('./ERC20'); +const ClusterExternalAdapter = artifacts.require('./IExternalAdapter'); + + +contract('ClusterTokenInteractiveAdapter', () => { + const ethAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; + const decrClusterAddress = '0x6Bc3F65Fc50E49060e21eD6996be96ee4B404752'; + const daiAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; + let encodedData; + + let accounts; + let core; + let router; + let protocolAdapterRegistry; + let protocolAdapterAddress; + let clusterTokenInstance; + + beforeEach(async () => { + accounts = await web3.eth.getAccounts(); + encodedData = web3.eth.abi.encodeParameter('address', decrClusterAddress); + await InteractiveAdapter.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterAddress = result.address; + }); + await ProtocolAdapterRegistry.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterRegistry = result.contract; + }); + await protocolAdapterRegistry.methods.addProtocolAdapters( + [ + CLUSTER_TOKEN_ADAPTER, + ], + [ + protocolAdapterAddress, + ], + [[]], + ) + .send({ + from: accounts[0], + gas: '1000000', + }); + await Core.new( + protocolAdapterRegistry.options.address, + { from: accounts[0] }, + ) + .then((result) => { + core = result.contract; + }); + await Router.new( + core.options.address, + { from: accounts[0] }, + ) + .then((result) => { + router = result.contract; + }); + await ERC20.at(decrClusterAddress) + .then((result) => { + clusterTokenInstance = result.contract; + }); + }); + + describe('ETH <-> decrCluster', async() => { + it('Should not be correct ETH -> decrCluster if 2 tokens', async() => { + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`decrCluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await expectRevert(router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_DEPOSIT, + [ + [ethAddress, convertToShare(1), AMOUNT_RELATIVE], + [ethAddress, convertToShare(1), AMOUNT_RELATIVE], + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + })); + }); + + it('Should not be correct DAI -> decrCluster', async() => { + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`decrCluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await expectRevert(router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_DEPOSIT, + [ + [daiAddress, convertToShare(1), AMOUNT_RELATIVE] + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + })); + }); + + it('Should be correct ETH -> decrCluster deposit', async() => { + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`decrCluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_DEPOSIT, + [ + [ethAddress, convertToShare(1), AMOUNT_RELATIVE] + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + }) + .then((receipt) => { + console.log(`called router for ${receipt.cumulativeGasUsed} gas`); + }); + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`decrCluster amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + await clusterTokenInstance.methods['balanceOf(address)'](core.options.address) + .call() + .then((result) => { + assert.equal(result, 0); + }); + await web3.eth.getBalance(core.options.address) + .then((result) => { + assert.equal(result, 0); + }); + }); + + it('Should not be correct ETH <- decrCluster withdraw if 2 tokens', async() => { + let decrClusterAmount; + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + decrClusterAmount = result; + console.log(`decrCluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await clusterTokenInstance.methods.approve(router.options.address, (decrClusterAmount * 2).toString()) + .send({ + gas: 10000000, + from: accounts[0], + }); + await expectRevert(router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_WITHDRAW, + [ + [decrClusterAddress, decrClusterAmount, AMOUNT_ABSOLUTE], + [decrClusterAddress, decrClusterAmount, AMOUNT_ABSOLUTE] + ], + EMPTY_BYTES, + ], + ], + [ + [ + [decrClusterAddress, decrClusterAmount, AMOUNT_ABSOLUTE], + [0, EMPTY_BYTES] + ] + ], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + })); + }); + + it('Should be correct ETH <- decrCluster withdraw', async() => { + let decrClusterAmount; + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + decrClusterAmount = result; + console.log(`decrCluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await clusterTokenInstance.methods.approve(router.options.address, (decrClusterAmount * 2).toString()) + .send({ + gas: 10000000, + from: accounts[0], + }); + await router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_WITHDRAW, + [ + [decrClusterAddress, decrClusterAmount, AMOUNT_ABSOLUTE] + ], + EMPTY_BYTES, + ], + ], + [ + [ + [decrClusterAddress, decrClusterAmount, AMOUNT_ABSOLUTE], + [0, EMPTY_BYTES] + ] + ], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + }) + .then((receipt) => { + console.log(`called router for ${receipt.cumulativeGasUsed} gas`); + }); + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`decrCluster amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + await clusterTokenInstance.methods['balanceOf(address)'](core.options.address) + .call() + .then((result) => { + assert.equal(result, 0); + }); + await web3.eth.getBalance(core.options.address) + .then((result) => { + assert.equal(result, 0); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/interactiveAdapters/ClusterTokenInteractiveAdapter.js b/test/interactiveAdapters/ClusterTokenInteractiveAdapterPoly.js similarity index 99% rename from test/interactiveAdapters/ClusterTokenInteractiveAdapter.js rename to test/interactiveAdapters/ClusterTokenInteractiveAdapterPoly.js index a6e9d585..9d7267ea 100644 --- a/test/interactiveAdapters/ClusterTokenInteractiveAdapter.js +++ b/test/interactiveAdapters/ClusterTokenInteractiveAdapterPoly.js @@ -15,7 +15,7 @@ const EMPTY_BYTES = '0x'; const ZERO = '0x0000000000000000000000000000000000000000'; const ProtocolAdapterRegistry = artifacts.require('./ProtocolAdapterRegistry'); -const InteractiveAdapter = artifacts.require('./ClusterTokenInteractiveAdapter'); +const InteractiveAdapter = artifacts.require('./ClusterTokenInteractiveAdapterPoly'); const Core = artifacts.require('./Core'); const Router = artifacts.require('./Router'); const ERC20 = artifacts.require('./ERC20'); From 31dde43f06362736b2a78db28308fb1d23920788 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 12 Oct 2021 13:45:04 +0300 Subject: [PATCH 4/9] feat: Adapter for Staking DHV in Polygon --- .../adapters/dehive/StakingDHVAdapter.sol | 38 +++ .../StakingDHVInteractiveAdapterPoly.sol | 87 +++++++ contracts/interfaces/IStakingPools.sol | 53 +++++ .../StakingDHVInteractiveAdapter.js | 220 ++++++++++++++++++ 4 files changed, 398 insertions(+) create mode 100644 contracts/adapters/dehive/StakingDHVAdapter.sol create mode 100644 contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterPoly.sol create mode 100644 contracts/interfaces/IStakingPools.sol create mode 100644 test/interactiveAdapters/StakingDHVInteractiveAdapter.js diff --git a/contracts/adapters/dehive/StakingDHVAdapter.sol b/contracts/adapters/dehive/StakingDHVAdapter.sol new file mode 100644 index 00000000..801cabd8 --- /dev/null +++ b/contracts/adapters/dehive/StakingDHVAdapter.sol @@ -0,0 +1,38 @@ +// Copyright (C) 2020 Zerion Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: LGPL-3.0-only + +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import { ProtocolAdapter } from "../ProtocolAdapter.sol"; +import { IStakingPools } from "../../interfaces/IStakingPools.sol"; + +/** + * @title DHV Staking adapter for DeHive protocol. + * @dev Implementation of ProtocolAdapter abstract contract. + * @author Igor Sobolev + */ +contract StakingDHVAdapter is ProtocolAdapter { + /** + * @dev MUST return amount and type of the given token + * locked on the protocol by the given account. + */ + function getBalance(address token, address account) public override view returns (int256) { + uint256 amount = IStakingPools(token).userInfo(0, account); + return int256(amount); + } +} \ No newline at end of file diff --git a/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterPoly.sol b/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterPoly.sol new file mode 100644 index 00000000..6e8c22be --- /dev/null +++ b/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterPoly.sol @@ -0,0 +1,87 @@ +// Copyright (C) 2020 Zerion Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: LGPL-3.0-only + +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import { TokenAmount } from "../../shared/Structs.sol"; +import { InteractiveAdapter } from "../InteractiveAdapter.sol"; +import { ERC20 } from "../../interfaces/ERC20.sol"; +import { SafeERC20 } from "../../shared/SafeERC20.sol"; + +import { StakingDHVAdapter } from "../../adapters/dehive/StakingDHVAdapter.sol"; + +import { IStakingPools } from "../../interfaces/IStakingPools.sol"; + +/** + * @title Interactive adapter for DeHive protocol. + * @dev Implementation of InteractiveAdapter abstract contract. + */ + +contract StakingDHVInteractiveAdapterPoly is InteractiveAdapter, StakingDHVAdapter { + using SafeERC20 for ERC20; + + address internal constant STAKING_DHV = address(0x88cFC1bc9aEb80f6C8f5d310d6C3761c2a646Df7); + address internal constant DHV_TOKEN = address(0x5fCB9de282Af6122ce3518CDe28B7089c9F97b26); + /** + * @notice Deposits tokens to the DeHive ClusterToken. + * @param tokenAmounts Array with one element - TokenAmount struct with + * DHV address, DHV amount to be deposited, and amount type. + * @param data ABI-encoded additional parameters: + * - userAddress - Address of user address. + * @return tokensToBeWithdrawn Empty array. + * @dev Implementation of InteractiveAdapter function. + */ + + function deposit(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + require(tokenAmounts.length == 1, "StakingDHV: should be one token[1]"); + require(tokenAmounts[0].token == DHV_TOKEN, "StakingDHV: should be DHV[2]"); + + address userAddress = abi.decode(data, (address)); + uint256 amount = getAbsoluteAmountDeposit(tokenAmounts[0]); + + ERC20(tokenAmounts[0].token).safeApprove(address(0x88cFC1bc9aEb80f6C8f5d310d6C3761c2a646Df7), amount, "DHV"); + //require(ERC20(tokenAmounts[0].token).balanceOf(address(this)) > 0, "123"); + // solhint-disable-next-line no-empty-blocks + try IStakingPools(address(0x88cFC1bc9aEb80f6C8f5d310d6C3761c2a646Df7)).depositFor(0, amount, userAddress) {} catch Error(string memory reason) { + revert(reason); + } catch { + revert("StakingDHV: deposit fail"); + } + } + + /** + * @notice Withdraws tokens from the DeHive protocol. + * @param tokenAmounts Empty array. + * @return tokensToBeWithdrawn Empty array. + * @dev Implementation of InteractiveAdapter function. + */ + + function withdraw(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + revert("StakingDHV: Can't withdraw"); + } +} \ No newline at end of file diff --git a/contracts/interfaces/IStakingPools.sol b/contracts/interfaces/IStakingPools.sol new file mode 100644 index 00000000..b6689d71 --- /dev/null +++ b/contracts/interfaces/IStakingPools.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +interface IStakingPools { + struct UserInfo { + uint256 amount; // How many ASSET tokensens the user has provided. + uint256[] rewardsDebts; // Order like in AssetInfo rewardsTokens + } + struct PoolInfo { + address assetToken; // Address of LP token contract. + uint256 lastRewardBlock; // Last block number that DHVs distribution occurs. + uint256[] accumulatedPerShare; // Accumulated token per share, times token decimals. See below. + address[] rewardsTokens; // Must be constant. + uint256[] rewardsPerBlock; // Tokens to distribute per block. + uint256[] accuracy; // Tokens accuracy. + uint256 poolSupply; // Total amount of deposits by users. + bool paused; + } + + function poolInfo(uint256 _pid) external view returns (PoolInfo memory); + + function userInfo(uint256 _pid, address _user) external view returns (uint256); + + function addPool( + uint256 _pid, + address _assetAddress, + address[] calldata _rewardsTokens, + uint256[] calldata _rewardsPerBlock + ) external; + + function updatePoolSettings( + uint256 _pid, + uint256[] calldata _rewardsPerBlock, + bool _withUpdate + ) external; + + function setOnPause(address _assetAddress, bool _paused) external; + + function pendingRewards(uint256 _pid, address _user) external view returns (uint256[] memory amounts); + + function updatePool(uint256 _pid) external; + + function deposit(uint256 _pid, uint256 _amount) external; + + function depositFor(uint256 _pid, uint256 _amount, address _user) external; + + function withdraw(uint256 _pid, uint256 _amount) external; + + function claimRewards(uint256 _pid) external; + + function poolExist(uint256 _pid) external view returns (bool); +} diff --git a/test/interactiveAdapters/StakingDHVInteractiveAdapter.js b/test/interactiveAdapters/StakingDHVInteractiveAdapter.js new file mode 100644 index 00000000..609c5f62 --- /dev/null +++ b/test/interactiveAdapters/StakingDHVInteractiveAdapter.js @@ -0,0 +1,220 @@ +import expectRevert from '../helpers/expectRevert'; +import convertToShare from '../helpers/convertToShare'; +import convertToBytes32 from '../helpers/convertToBytes32'; + +const BN = web3.utils.BN; + +const DHV_STAKING_ADAPTER = convertToBytes32('DHV Staking'); + +const ACTION_DEPOSIT = 1; +const ACTION_WITHDRAW = 2; +const AMOUNT_RELATIVE = 1; +const AMOUNT_ABSOLUTE = 2; +const EMPTY_BYTES = '0x'; + +const ZERO = '0x0000000000000000000000000000000000000000'; + +const ProtocolAdapterRegistry = artifacts.require('./ProtocolAdapterRegistry'); +const InteractiveAdapter = artifacts.require('./StakingDHVInteractiveAdapterPoly'); +const Core = artifacts.require('./Core'); +const Router = artifacts.require('./Router'); +const ERC20 = artifacts.require('./ERC20'); +const UniswapRouter = artifacts.require('./UniswapV2Router02'); +const StakingPools = artifacts.require('./IStakingPools'); +const StakingDHVAdapter = artifacts.require('./StakingDHVAdapter'); + + +contract('StakingDHVInteractiveAdapterPoly', () => { + const ethAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; + const dhvAddress = '0x5fCB9de282Af6122ce3518CDe28B7089c9F97b26'; + const daiAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; + const stakingDHVAddress = '0x88cFC1bc9aEb80f6C8f5d310d6C3761c2a646Df7'; + let encodedData; + + let accounts; + let core; + let router; + let protocolAdapterRegistry; + let protocolAdapterAddress; + let stakingPools; + let dhvToken; + let uniSwapRouter; + let stakingAdapter; + + let protocol; + + before(async () => { + accounts = await web3.eth.getAccounts(); + encodedData = web3.eth.abi.encodeParameter('address', accounts[0]); + + stakingAdapter = await StakingDHVAdapter.new({from: accounts[0]}); + + uniSwapRouter = await UniswapRouter.at("0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff"); + let ethValue = await web3.utils.toWei('1', 'ether'); + await uniSwapRouter.swapExactETHForTokens(0, ["0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270", "0x831753DD7087CaC61aB5644b308642cc1c33Dc13", dhvAddress], accounts[0], '10000000000', {from: accounts[0], value: ethValue}); + + await InteractiveAdapter.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterAddress = result.address; + protocol = result.contract; + }); + await ProtocolAdapterRegistry.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterRegistry = result.contract; + }); + await protocolAdapterRegistry.methods.addProtocolAdapters( + [ + DHV_STAKING_ADAPTER, + ], + [ + protocolAdapterAddress, + ], + [[]], + ) + .send({ + from: accounts[0], + gas: '1000000', + }); + await Core.new( + protocolAdapterRegistry.options.address, + { from: accounts[0] }, + ) + .then((result) => { + core = result.contract; + }); + await Router.new( + core.options.address, + { from: accounts[0] }, + ) + .then((result) => { + router = result.contract; + }); + await ERC20.at(dhvAddress) + .then((result) => { + dhvToken = result.contract; + }); + }); + + describe('DHV <-> StakingDHV', async() => { + it('Should not be correct DHV -> DHV Staking if 2 tokens', async() => { + let dhvAmount; + await dhvToken.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + dhvAmount = result; + console.log(`dhv amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + // let result = await protocol.methods['getBalance(address,address)'](stakingDHVAddress, accounts[0]).call(); + // console.log(`staking dhv amount before is ${web3.utils.fromWei(result, 'ether')}`); + + await dhvToken.methods.approve(router.options.address, dhvAmount) + .send({ + gas: 10000000, + from: accounts[0], + }); + await expectRevert(router.methods.execute( + [ + [ + DHV_STAKING_ADAPTER, + ACTION_DEPOSIT, + [ + [dhvAddress, convertToShare(1), AMOUNT_RELATIVE], + [ethAddress, convertToShare(1), AMOUNT_RELATIVE], + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0] + })); + }); + + it('Should not be correct DAI -> DHV Staking', async() => { + let dhvAmount; + await dhvToken.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + dhvAmount = result; + console.log(`dhv amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await dhvToken.methods.approve(router.options.address, dhvAmount) + .send({ + gas: 10000000, + from: accounts[0], + }); + await expectRevert(router.methods.execute( + [ + [ + DHV_STAKING_ADAPTER, + ACTION_DEPOSIT, + [ + [daiAddress, convertToShare(1), AMOUNT_RELATIVE] + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0] + })); + }); + + it('Should be correct DHV -> DHV Staking deposit', async() => { + let dhvAmount; + await dhvToken.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + dhvAmount = result; + console.log(`dhv amount before is ${web3.utils.fromWei(dhvAmount, 'ether')}`); + }); + await dhvToken.methods.approve(router.options.address, dhvAmount) + .send({ + gas: 10000000, + from: accounts[0], + }); + await router.methods.execute( + [ + [ + DHV_STAKING_ADAPTER, + ACTION_DEPOSIT, + [ + [dhvAddress, dhvAmount, AMOUNT_ABSOLUTE] + ], + encodedData, + ], + ], + [ + [ + [dhvAddress, dhvAmount, AMOUNT_ABSOLUTE], + [0, EMPTY_BYTES] + ] + ], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0] + }) + .then((receipt) => { + console.log(`called router for ${receipt.cumulativeGasUsed} gas`); + }); + await dhvToken.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`dhv amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + let result = await protocol.methods['getBalance(address,address)'](stakingDHVAddress, accounts[0]).call(); + console.log(`staking dhv amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + }); +}); \ No newline at end of file From f31a3f8b32a4d732c0773fa7b38a24e58cf9dbd4 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 13 Oct 2021 16:33:20 +0300 Subject: [PATCH 5/9] feat: staking dhv and staking pools adapters for Ethereum --- .../StakingDHVInteractiveAdapterEth.sol | 86 +++++++++++++++++++ .../StakingDHVInteractiveAdapterPoly.sol | 9 +- .../StakingPoolsInteractiveAdapterEth.sol | 85 ++++++++++++++++++ ...js => StakingDHVInteractiveAdapterPoly.js} | 0 4 files changed, 175 insertions(+), 5 deletions(-) create mode 100644 contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterEth.sol create mode 100644 contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterEth.sol rename test/interactiveAdapters/{StakingDHVInteractiveAdapter.js => StakingDHVInteractiveAdapterPoly.js} (100%) diff --git a/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterEth.sol b/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterEth.sol new file mode 100644 index 00000000..e7f5a591 --- /dev/null +++ b/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterEth.sol @@ -0,0 +1,86 @@ +// Copyright (C) 2020 Zerion Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: LGPL-3.0-only + +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import { TokenAmount } from "../../shared/Structs.sol"; +import { InteractiveAdapter } from "../InteractiveAdapter.sol"; +import { ERC20 } from "../../interfaces/ERC20.sol"; +import { SafeERC20 } from "../../shared/SafeERC20.sol"; + +import { StakingDHVAdapter } from "../../adapters/dehive/StakingDHVAdapter.sol"; + +import { IStakingPools } from "../../interfaces/IStakingPools.sol"; + +/** + * @title Interactive adapter for DeHive protocol. + * @dev Implementation of InteractiveAdapter abstract contract. + */ + +contract StakingDHVInteractiveAdapterPoly is InteractiveAdapter, StakingDHVAdapter { + using SafeERC20 for ERC20; + + address internal constant STAKING_DHV = address(0x04595f9010F79422a9b411ef963e4dd1F7107704); + address internal constant DHV_TOKEN = address(0x62Dc4817588d53a056cBbD18231d91ffCcd34b2A); + /** + * @notice Deposits tokens to the DeHive StakingDHV. + * @param tokenAmounts Array with one element - TokenAmount struct with + * DHV address, DHV amount to be deposited, and amount type. + * @param data ABI-encoded additional parameters: + * - userAddress - Address of user address. + * @return tokensToBeWithdrawn Empty array. + * @dev Implementation of InteractiveAdapter function. + */ + + function deposit(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + require(tokenAmounts.length == 1, "StakingDHV: should be one token[1]"); + require(tokenAmounts[0].token == DHV_TOKEN, "StakingDHV: should be DHV[2]"); + + address userAddress = abi.decode(data, (address)); + uint256 amount = getAbsoluteAmountDeposit(tokenAmounts[0]); + + ERC20(tokenAmounts[0].token).safeApprove(STAKING_DHV, amount, "DHV"); + // solhint-disable-next-line no-empty-blocks + try IStakingPools(STAKING_DHV).depositFor(0, amount, userAddress) {} catch Error(string memory reason) { + revert(reason); + } catch { + revert("StakingDHV: deposit fail"); + } + } + + /** + * @notice Withdraws tokens from the DeHive StakingDHV. + * @param tokenAmounts Empty array. + * @return tokensToBeWithdrawn Empty array. + * @dev Implementation of InteractiveAdapter function. + */ + + function withdraw(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + revert("StakingDHV: Can't withdraw"); + } +} \ No newline at end of file diff --git a/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterPoly.sol b/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterPoly.sol index 6e8c22be..ec3b57f7 100644 --- a/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterPoly.sol +++ b/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterPoly.sol @@ -38,7 +38,7 @@ contract StakingDHVInteractiveAdapterPoly is InteractiveAdapter, StakingDHVAdapt address internal constant STAKING_DHV = address(0x88cFC1bc9aEb80f6C8f5d310d6C3761c2a646Df7); address internal constant DHV_TOKEN = address(0x5fCB9de282Af6122ce3518CDe28B7089c9F97b26); /** - * @notice Deposits tokens to the DeHive ClusterToken. + * @notice Deposits tokens to the DeHive StakingDHV. * @param tokenAmounts Array with one element - TokenAmount struct with * DHV address, DHV amount to be deposited, and amount type. * @param data ABI-encoded additional parameters: @@ -59,10 +59,9 @@ contract StakingDHVInteractiveAdapterPoly is InteractiveAdapter, StakingDHVAdapt address userAddress = abi.decode(data, (address)); uint256 amount = getAbsoluteAmountDeposit(tokenAmounts[0]); - ERC20(tokenAmounts[0].token).safeApprove(address(0x88cFC1bc9aEb80f6C8f5d310d6C3761c2a646Df7), amount, "DHV"); - //require(ERC20(tokenAmounts[0].token).balanceOf(address(this)) > 0, "123"); + ERC20(tokenAmounts[0].token).safeApprove(STAKING_DHV, amount, "DHV"); // solhint-disable-next-line no-empty-blocks - try IStakingPools(address(0x88cFC1bc9aEb80f6C8f5d310d6C3761c2a646Df7)).depositFor(0, amount, userAddress) {} catch Error(string memory reason) { + try IStakingPools(STAKING_DHV).depositFor(0, amount, userAddress) {} catch Error(string memory reason) { revert(reason); } catch { revert("StakingDHV: deposit fail"); @@ -70,7 +69,7 @@ contract StakingDHVInteractiveAdapterPoly is InteractiveAdapter, StakingDHVAdapt } /** - * @notice Withdraws tokens from the DeHive protocol. + * @notice Withdraws tokens from the DeHive StakingDHV. * @param tokenAmounts Empty array. * @return tokensToBeWithdrawn Empty array. * @dev Implementation of InteractiveAdapter function. diff --git a/contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterEth.sol b/contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterEth.sol new file mode 100644 index 00000000..56a0c719 --- /dev/null +++ b/contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterEth.sol @@ -0,0 +1,85 @@ +// Copyright (C) 2020 Zerion Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: LGPL-3.0-only + +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import { TokenAmount } from "../../shared/Structs.sol"; +import { InteractiveAdapter } from "../InteractiveAdapter.sol"; +import { ERC20 } from "../../interfaces/ERC20.sol"; +import { SafeERC20 } from "../../shared/SafeERC20.sol"; + +import { StakingDHVAdapter } from "../../adapters/dehive/StakingDHVAdapter.sol"; + +import { IStakingPools } from "../../interfaces/IStakingPools.sol"; + +/** + * @title Interactive adapter for DeHive protocol. + * @dev Implementation of InteractiveAdapter abstract contract. + */ + +contract StakingPoolsInteractiveAdapterEth is InteractiveAdapter, StakingDHVAdapter { + using SafeERC20 for ERC20; + + address internal constant STAKING_POOLS = address(0x4964B3B599B82C3FdDC56e3A9Ffd77d48c6AF0f0); + /** + * @notice Deposits tokens to the DeHive StakingPools. + * @param tokenAmounts Array with one element - TokenAmount struct with + * staked token address,staked token to be deposited, and amount type. + * @param data ABI-encoded additional parameters: + - poolId - ID of pool for staked token in StakingPools. + * - userAddress - Address of user address. + * @return tokensToBeWithdrawn Empty array. + * @dev Implementation of InteractiveAdapter function. + */ + + function deposit(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + require(tokenAmounts.length == 1, "StakingPools: should be one token[1]"); + + (uint256 poolId, address userAddress) = abi.decode(data, (uint256, address)); + uint256 amount = getAbsoluteAmountDeposit(tokenAmounts[0]); + + ERC20(tokenAmounts[0].token).safeApprove(STAKING_POOLS, amount, "DHV"); + // solhint-disable-next-line no-empty-blocks + try IStakingPools(STAKING_POOLS).depositFor(poolId, amount, userAddress) {} catch Error(string memory reason) { + revert(reason); + } catch { + revert("StakingPools: deposit fail"); + } + } + + /** + * @notice Withdraws tokens from the DeHive StakingPools. + * @param tokenAmounts Empty array. + * @return tokensToBeWithdrawn Empty array. + * @dev Implementation of InteractiveAdapter function. + */ + + function withdraw(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + revert("StakingPools: Can't withdraw"); + } +} \ No newline at end of file diff --git a/test/interactiveAdapters/StakingDHVInteractiveAdapter.js b/test/interactiveAdapters/StakingDHVInteractiveAdapterPoly.js similarity index 100% rename from test/interactiveAdapters/StakingDHVInteractiveAdapter.js rename to test/interactiveAdapters/StakingDHVInteractiveAdapterPoly.js From 3449c0904bb31c79cb6d008a855ec3fa51a17f16 Mon Sep 17 00:00:00 2001 From: Midvel Date: Sun, 17 Oct 2021 21:11:41 +0300 Subject: [PATCH 6/9] comment correction --- contracts/adapters/dehive/ClusterTokenAdapter.sol | 1 - contracts/adapters/dehive/DeHiveProtocolAdapter.sol | 1 - contracts/adapters/dehive/StakingDHVAdapter.sol | 1 - 3 files changed, 3 deletions(-) diff --git a/contracts/adapters/dehive/ClusterTokenAdapter.sol b/contracts/adapters/dehive/ClusterTokenAdapter.sol index d4c10c1d..72da6d5e 100644 --- a/contracts/adapters/dehive/ClusterTokenAdapter.sol +++ b/contracts/adapters/dehive/ClusterTokenAdapter.sol @@ -25,7 +25,6 @@ import { IClusterToken } from "../../interfaces/IClusterToken.sol"; /** * @title Token adapter for ClusterTokens. * @dev Implementation of TokenAdapter abstract contract. - * @author Igor Sobolev */ contract ClusterTokenAdapter is TokenAdapter { /** diff --git a/contracts/adapters/dehive/DeHiveProtocolAdapter.sol b/contracts/adapters/dehive/DeHiveProtocolAdapter.sol index 55a29271..2a85d326 100644 --- a/contracts/adapters/dehive/DeHiveProtocolAdapter.sol +++ b/contracts/adapters/dehive/DeHiveProtocolAdapter.sol @@ -24,7 +24,6 @@ import { ProtocolAdapter } from "../ProtocolAdapter.sol"; /** * @title Cluster adapter for DeHive protocol. * @dev Implementation of ProtocolAdapter abstract contract. - * @author Igor Sobolev */ contract DeHiveProtocolAdapter is ProtocolAdapter { /** diff --git a/contracts/adapters/dehive/StakingDHVAdapter.sol b/contracts/adapters/dehive/StakingDHVAdapter.sol index 801cabd8..87080351 100644 --- a/contracts/adapters/dehive/StakingDHVAdapter.sol +++ b/contracts/adapters/dehive/StakingDHVAdapter.sol @@ -24,7 +24,6 @@ import { IStakingPools } from "../../interfaces/IStakingPools.sol"; /** * @title DHV Staking adapter for DeHive protocol. * @dev Implementation of ProtocolAdapter abstract contract. - * @author Igor Sobolev */ contract StakingDHVAdapter is ProtocolAdapter { /** From 338218cec0218a81b7883c72f70f4143e0731a05 Mon Sep 17 00:00:00 2001 From: Midvel Date: Sun, 17 Oct 2021 21:49:20 +0300 Subject: [PATCH 7/9] correct comments and staked balance --- .../adapters/dehive/ClusterTokenAdapter.sol | 1 + contracts/adapters/dehive/StakingDHVAdapter.sol | 16 +++++++++++++++- .../dehive/StakingDHVInteractiveAdapterPoly.sol | 1 + .../dehive/StakingPoolsInteractiveAdapterEth.sol | 1 + 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/contracts/adapters/dehive/ClusterTokenAdapter.sol b/contracts/adapters/dehive/ClusterTokenAdapter.sol index 72da6d5e..d8fc2bac 100644 --- a/contracts/adapters/dehive/ClusterTokenAdapter.sol +++ b/contracts/adapters/dehive/ClusterTokenAdapter.sol @@ -30,6 +30,7 @@ contract ClusterTokenAdapter is TokenAdapter { /** * @return Array of Component structs with underlying tokens rates for the given token. * @dev Implementation of TokenAdapter abstract contract function. + * @param token Cluster address */ function getComponents(address token) external override view returns (Component[] memory) { address[] memory underlyings = IClusterToken(token).getUnderlyings(); diff --git a/contracts/adapters/dehive/StakingDHVAdapter.sol b/contracts/adapters/dehive/StakingDHVAdapter.sol index 87080351..60019b39 100644 --- a/contracts/adapters/dehive/StakingDHVAdapter.sol +++ b/contracts/adapters/dehive/StakingDHVAdapter.sol @@ -29,9 +29,23 @@ contract StakingDHVAdapter is ProtocolAdapter { /** * @dev MUST return amount and type of the given token * locked on the protocol by the given account. + * @param token DHV address + * @param account User's account */ function getBalance(address token, address account) public override view returns (int256) { - uint256 amount = IStakingPools(token).userInfo(0, account); + address STAKING_DHV_ETH = address(0x04595f9010F79422a9b411ef963e4dd1F7107704); + address DHV_TOKEN_ETH = address(0x62Dc4817588d53a056cBbD18231d91ffCcd34b2A); + + address STAKING_DHV_POLY = address(0x88cFC1bc9aEb80f6C8f5d310d6C3761c2a646Df7); + address DHV_TOKEN_POLY = address(0x5fCB9de282Af6122ce3518CDe28B7089c9F97b26); + + uint256 amount = 0; + if (token == DHV_TOKEN_ETH) { + amount = IStakingPools(STAKING_DHV_ETH).userInfo(0, account); + } + else if (token == DHV_TOKEN_POLY) { + amount = IStakingPools(STAKING_DHV_POLY).userInfo(0, account); + } return int256(amount); } } \ No newline at end of file diff --git a/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterPoly.sol b/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterPoly.sol index ec3b57f7..be91ea03 100644 --- a/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterPoly.sol +++ b/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterPoly.sol @@ -70,6 +70,7 @@ contract StakingDHVInteractiveAdapterPoly is InteractiveAdapter, StakingDHVAdapt /** * @notice Withdraws tokens from the DeHive StakingDHV. + * Should be performed through the platform * @param tokenAmounts Empty array. * @return tokensToBeWithdrawn Empty array. * @dev Implementation of InteractiveAdapter function. diff --git a/contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterEth.sol b/contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterEth.sol index 56a0c719..7e3117c1 100644 --- a/contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterEth.sol +++ b/contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterEth.sol @@ -69,6 +69,7 @@ contract StakingPoolsInteractiveAdapterEth is InteractiveAdapter, StakingDHVAdap /** * @notice Withdraws tokens from the DeHive StakingPools. + * Should be performed through the platform * @param tokenAmounts Empty array. * @return tokensToBeWithdrawn Empty array. * @dev Implementation of InteractiveAdapter function. From 26a39da20f76a271a9c33ac2cf7561dbd589be43 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 30 Nov 2021 14:55:43 +0200 Subject: [PATCH 8/9] feat: bsc integration adapters --- .../ClusterTokenInteractiveAdapterBSC.sol | 103 ++++++ .../StakingDHVInteractiveAdapterBSC.sol | 86 +++++ .../StakingDHVInteractiveAdapterEth.sol | 3 +- .../StakingPoolsInteractiveAdapterBSC.sol | 85 +++++ .../StakingPoolsInteractiveAdapterEth.sol | 2 +- contracts/interfaces/UniswapV2Router02.sol | 17 + .../ClusterTokenInteractiveAdapterBSC.js | 312 ++++++++++++++++++ .../StakingDHVInteractiveAdapterBSC.js | 221 +++++++++++++ .../StakingDHVInteractiveAdapterEth.js | 221 +++++++++++++ .../StakingPoolsInteractiveAdapterBSC.js | 175 ++++++++++ .../StakingPoolsInteractiveAdapterEth.js | 198 +++++++++++ 11 files changed, 1421 insertions(+), 2 deletions(-) create mode 100644 contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterBSC.sol create mode 100644 contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterBSC.sol create mode 100644 contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterBSC.sol create mode 100644 test/interactiveAdapters/ClusterTokenInteractiveAdapterBSC.js create mode 100644 test/interactiveAdapters/StakingDHVInteractiveAdapterBSC.js create mode 100644 test/interactiveAdapters/StakingDHVInteractiveAdapterEth.js create mode 100644 test/interactiveAdapters/StakingPoolsInteractiveAdapterBSC.js create mode 100644 test/interactiveAdapters/StakingPoolsInteractiveAdapterEth.js diff --git a/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterBSC.sol b/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterBSC.sol new file mode 100644 index 00000000..b10f1e8c --- /dev/null +++ b/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterBSC.sol @@ -0,0 +1,103 @@ +// Copyright (C) 2020 Zerion Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: LGPL-3.0-only + +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import { TokenAmount } from "../../shared/Structs.sol"; +import { InteractiveAdapter } from "../InteractiveAdapter.sol"; +import { ERC20 } from "../../interfaces/ERC20.sol"; +import { SafeERC20 } from "../../shared/SafeERC20.sol"; + +import { DeHiveProtocolAdapter } from "../../adapters/dehive/DeHiveProtocolAdapter.sol"; + +import { IExternalAdapter } from "../../interfaces/IExternalAdapter.sol"; + +/** + * @title Interactive adapter for DeHive protocol. + * @dev Implementation of InteractiveAdapter abstract contract. + */ + +contract ClusterTokenInteractiveAdapterBSC is InteractiveAdapter, DeHiveProtocolAdapter { + using SafeERC20 for ERC20; + + address internal constant EXTERNAL_ADAPTER = address(0); + /** + * @notice Deposits tokens to the DeHive ClusterToken. + * @param tokenAmounts Array with one element - TokenAmount struct with + * ETH address, ETH amount to be deposited, and amount type. + * @param data ABI-encoded additional parameters: + * - clusterToken - ClusterToken address. + * @return tokensToBeWithdrawn Array with two elements - ETH and ClusterToken address. + * @dev Implementation of InteractiveAdapter function. + */ + + function deposit(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + require(tokenAmounts.length == 1, "DeHive: should be one token[1]"); + require(tokenAmounts[0].token == ETH, "DeHive: should be ETH[2]"); + + address clusterToken = abi.decode(data, (address)); + uint256 amount = getAbsoluteAmountDeposit(tokenAmounts[0]); + + tokensToBeWithdrawn = new address[](2); + tokensToBeWithdrawn[0] = clusterToken; + tokensToBeWithdrawn[1] = ETH; + + // solhint-disable-next-line no-empty-blocks + try IExternalAdapter(EXTERNAL_ADAPTER).deposit{value: amount}(clusterToken) {} catch Error(string memory reason) { + revert(reason); + } catch { + revert("DeHive: deposit fail"); + } + } + + /** + * @notice Withdraws tokens from the DeHive protocol. + * @param tokenAmounts Array with one element - TokenAmount struct with + * ClusterToken address, ClusterToken amount to be redeemed, and amount type. + * @return tokensToBeWithdrawn Array with one element - ETH address. + * @dev Implementation of InteractiveAdapter function. + */ + + function withdraw(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + require(tokenAmounts.length == 1, "DeHive: should be 1 tokenAmount[3]"); + + address clusterToken = tokenAmounts[0].token; + uint256 amount = getAbsoluteAmountWithdraw(tokenAmounts[0]); + + tokensToBeWithdrawn = new address[](1); + tokensToBeWithdrawn[0] = ETH; + + ERC20(clusterToken).safeApprove(EXTERNAL_ADAPTER, amount, "DeHive"); + // solhint-disable-next-line no-empty-blocks + try IExternalAdapter(EXTERNAL_ADAPTER).withdraw(clusterToken, amount) {} catch Error(string memory reason) { + revert(reason); + } catch { + revert("DeHive: withdraw fail"); + } + } +} \ No newline at end of file diff --git a/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterBSC.sol b/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterBSC.sol new file mode 100644 index 00000000..ad28946d --- /dev/null +++ b/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterBSC.sol @@ -0,0 +1,86 @@ +// Copyright (C) 2020 Zerion Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: LGPL-3.0-only + +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import { TokenAmount } from "../../shared/Structs.sol"; +import { InteractiveAdapter } from "../InteractiveAdapter.sol"; +import { ERC20 } from "../../interfaces/ERC20.sol"; +import { SafeERC20 } from "../../shared/SafeERC20.sol"; + +import { StakingDHVAdapter } from "../../adapters/dehive/StakingDHVAdapter.sol"; + +import { IStakingPools } from "../../interfaces/IStakingPools.sol"; + +/** + * @title Interactive adapter for DeHive protocol. + * @dev Implementation of InteractiveAdapter abstract contract. + */ + +contract StakingDHVInteractiveAdapterBSC is InteractiveAdapter, StakingDHVAdapter { + using SafeERC20 for ERC20; + + address internal constant STAKING_DHV = address(0x35f28aA0B2F34eFF17d2830135312ab2a777De36); + address internal constant DHV_TOKEN = address(0x58759Dd469ae5631C42cf8a473992335575b58D7); + /** + * @notice Deposits tokens to the DeHive StakingDHV. + * @param tokenAmounts Array with one element - TokenAmount struct with + * DHV address, DHV amount to be deposited, and amount type. + * @param data ABI-encoded additional parameters: + * - userAddress - Address of user address. + * @return tokensToBeWithdrawn Empty array. + * @dev Implementation of InteractiveAdapter function. + */ + + function deposit(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + require(tokenAmounts.length == 1, "StakingDHV: should be one token[1]"); + require(tokenAmounts[0].token == DHV_TOKEN, "StakingDHV: should be DHV[2]"); + + address userAddress = abi.decode(data, (address)); + uint256 amount = getAbsoluteAmountDeposit(tokenAmounts[0]); + + ERC20(tokenAmounts[0].token).safeApprove(STAKING_DHV, amount, "DHV"); + // solhint-disable-next-line no-empty-blocks + try IStakingPools(STAKING_DHV).depositFor(0, amount, userAddress) {} catch Error(string memory reason) { + revert(reason); + } catch { + revert("StakingDHV: deposit fail"); + } + } + + /** + * @notice Withdraws tokens from the DeHive StakingDHV. + * @param tokenAmounts Empty array. + * @return tokensToBeWithdrawn Empty array. + * @dev Implementation of InteractiveAdapter function. + */ + + function withdraw(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + revert("StakingDHV: Can't withdraw"); + } +} \ No newline at end of file diff --git a/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterEth.sol b/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterEth.sol index e7f5a591..3e843ea9 100644 --- a/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterEth.sol +++ b/contracts/interactiveAdapters/dehive/StakingDHVInteractiveAdapterEth.sol @@ -32,7 +32,7 @@ import { IStakingPools } from "../../interfaces/IStakingPools.sol"; * @dev Implementation of InteractiveAdapter abstract contract. */ -contract StakingDHVInteractiveAdapterPoly is InteractiveAdapter, StakingDHVAdapter { +contract StakingDHVInteractiveAdapterEth is InteractiveAdapter, StakingDHVAdapter { using SafeERC20 for ERC20; address internal constant STAKING_DHV = address(0x04595f9010F79422a9b411ef963e4dd1F7107704); @@ -59,6 +59,7 @@ contract StakingDHVInteractiveAdapterPoly is InteractiveAdapter, StakingDHVAdapt address userAddress = abi.decode(data, (address)); uint256 amount = getAbsoluteAmountDeposit(tokenAmounts[0]); + ERC20(tokenAmounts[0].token).safeApprove(STAKING_DHV, amount, "DHV"); // solhint-disable-next-line no-empty-blocks try IStakingPools(STAKING_DHV).depositFor(0, amount, userAddress) {} catch Error(string memory reason) { diff --git a/contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterBSC.sol b/contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterBSC.sol new file mode 100644 index 00000000..5ceec659 --- /dev/null +++ b/contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterBSC.sol @@ -0,0 +1,85 @@ +// Copyright (C) 2020 Zerion Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// SPDX-License-Identifier: LGPL-3.0-only + +pragma solidity 0.7.6; +pragma experimental ABIEncoderV2; + +import { TokenAmount } from "../../shared/Structs.sol"; +import { InteractiveAdapter } from "../InteractiveAdapter.sol"; +import { ERC20 } from "../../interfaces/ERC20.sol"; +import { SafeERC20 } from "../../shared/SafeERC20.sol"; + +import { StakingDHVAdapter } from "../../adapters/dehive/StakingDHVAdapter.sol"; + +import { IStakingPools } from "../../interfaces/IStakingPools.sol"; + +/** + * @title Interactive adapter for DeHive protocol. + * @dev Implementation of InteractiveAdapter abstract contract. + */ + +contract StakingPoolsInteractiveAdapterBSC is InteractiveAdapter, StakingDHVAdapter { + using SafeERC20 for ERC20; + + address internal constant STAKING_POOLS = address(0xF2e8CD1c40C766FEe73f56607fDffa526Ba8fa6c); + /** + * @notice Deposits tokens to the DeHive StakingPools. + * @param tokenAmounts Array with one element - TokenAmount struct with + * staked token address,staked token to be deposited, and amount type. + * @param data ABI-encoded additional parameters: + - poolId - ID of pool for staked token in StakingPools. + * - userAddress - Address of user address. + * @return tokensToBeWithdrawn Empty array. + * @dev Implementation of InteractiveAdapter function. + */ + + function deposit(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + require(tokenAmounts.length == 1, "StakingPools: should be one token[1]"); + + (uint256 poolId, address userAddress) = abi.decode(data, (uint256, address)); + uint256 amount = getAbsoluteAmountDeposit(tokenAmounts[0]); + + ERC20(tokenAmounts[0].token).safeApprove(STAKING_POOLS, amount, "DHV"); + // solhint-disable-next-line no-empty-blocks + try IStakingPools(STAKING_POOLS).depositFor(poolId, amount, userAddress) {} catch Error(string memory reason) { + revert(reason); + } catch { + revert("StakingPools: deposit fail"); + } + } + + /** + * @notice Withdraws tokens from the DeHive StakingPools. + * @param tokenAmounts Empty array. + * @return tokensToBeWithdrawn Empty array. + * @dev Implementation of InteractiveAdapter function. + */ + + function withdraw(TokenAmount[] calldata tokenAmounts, bytes calldata data) + external + payable + override + returns (address[] memory tokensToBeWithdrawn) + { + revert("StakingPools: Can't withdraw"); + } +} \ No newline at end of file diff --git a/contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterEth.sol b/contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterEth.sol index 56a0c719..475bcbce 100644 --- a/contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterEth.sol +++ b/contracts/interactiveAdapters/dehive/StakingPoolsInteractiveAdapterEth.sol @@ -55,7 +55,7 @@ contract StakingPoolsInteractiveAdapterEth is InteractiveAdapter, StakingDHVAdap { require(tokenAmounts.length == 1, "StakingPools: should be one token[1]"); - (uint256 poolId, address userAddress) = abi.decode(data, (uint256, address)); + (uint256 poolId, address userAddress) = abi.decode(data, (uint256, address)); uint256 amount = getAbsoluteAmountDeposit(tokenAmounts[0]); ERC20(tokenAmounts[0].token).safeApprove(STAKING_POOLS, amount, "DHV"); diff --git a/contracts/interfaces/UniswapV2Router02.sol b/contracts/interfaces/UniswapV2Router02.sol index 4772993e..eb36c699 100644 --- a/contracts/interfaces/UniswapV2Router02.sol +++ b/contracts/interfaces/UniswapV2Router02.sol @@ -40,4 +40,21 @@ interface UniswapV2Router02 { address to, uint256 deadline ) external returns (uint256[] memory amounts); + + function addLiquidity( + address tokenA, + address tokenB, + uint256 amountADesired, + uint256 amountBDesired, + uint256 amountAMin, + uint256 amountBMin, + address to, + uint256 deadline + ) + external + returns ( + uint256 amountA, + uint256 amountB, + uint256 liquidity + ); } diff --git a/test/interactiveAdapters/ClusterTokenInteractiveAdapterBSC.js b/test/interactiveAdapters/ClusterTokenInteractiveAdapterBSC.js new file mode 100644 index 00000000..1257bff9 --- /dev/null +++ b/test/interactiveAdapters/ClusterTokenInteractiveAdapterBSC.js @@ -0,0 +1,312 @@ +import expectRevert from '../helpers/expectRevert'; +import convertToShare from '../helpers/convertToShare'; +import convertToBytes32 from '../helpers/convertToBytes32'; + +const BN = web3.utils.BN; + +const CLUSTER_TOKEN_ADAPTER = convertToBytes32('Cluster Token BSC'); + +const ACTION_DEPOSIT = 1; +const ACTION_WITHDRAW = 2; +const AMOUNT_RELATIVE = 1; +const AMOUNT_ABSOLUTE = 2; +const EMPTY_BYTES = '0x'; + +const ZERO = '0x0000000000000000000000000000000000000000'; + +const ProtocolAdapterRegistry = artifacts.require('./ProtocolAdapterRegistry'); +const InteractiveAdapter = artifacts.require('./ClusterTokenInteractiveAdapterBSC'); +const Core = artifacts.require('./Core'); +const Router = artifacts.require('./Router'); +const ERC20 = artifacts.require('./ERC20'); +const ClusterExternalAdapter = artifacts.require('./IExternalAdapter'); + + +contract('ClusterTokenInteractiveAdapter', () => { + const ethAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; + const bscClusterAddress = '0x0a684421ef48b431803BFd75F38675EAb1e38Ed5'; + const busdAddress = '0xe9e7cea3dedca5984780bafc599bd69add087d56'; + let encodedData; + + let accounts; + let core; + let router; + let protocolAdapterRegistry; + let protocolAdapterAddress; + let clusterTokenInstance; + let clusterExternalAdapter; + + beforeEach(async () => { + accounts = await web3.eth.getAccounts(); + encodedData = web3.eth.abi.encodeParameter('address', bscClusterAddress); + await InteractiveAdapter.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterAddress = result.address; + }); + await ProtocolAdapterRegistry.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterRegistry = result.contract; + }); + await protocolAdapterRegistry.methods.addProtocolAdapters( + [ + CLUSTER_TOKEN_ADAPTER, + ], + [ + protocolAdapterAddress, + ], + [[]], + ) + .send({ + from: accounts[0], + gas: '1000000', + }); + await Core.new( + protocolAdapterRegistry.options.address, + { from: accounts[0] }, + ) + .then((result) => { + core = result.contract; + }); + await Router.new( + core.options.address, + { from: accounts[0] }, + ) + .then((result) => { + router = result.contract; + }); + await ERC20.at(bscClusterAddress) + .then((result) => { + clusterTokenInstance = result.contract; + }); + }); + + describe('ETH <-> BscCluster', async() => { + it('Should not be correct ETH -> BscCluster if 2 tokens', async() => { + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`BscCluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await expectRevert(router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_DEPOSIT, + [ + [ethAddress, convertToShare(1), AMOUNT_RELATIVE], + [ethAddress, convertToShare(1), AMOUNT_RELATIVE], + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + })); + }); + + it('Should not be correct BUSD -> BscCluster', async() => { + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`BscCluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await expectRevert(router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_DEPOSIT, + [ + [busdAddress, convertToShare(1), AMOUNT_RELATIVE] + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + })); + }); + + it('Should be correct ETH -> BscCluster deposit', async() => { + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`BscCluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_DEPOSIT, + [ + [ethAddress, convertToShare(1), AMOUNT_RELATIVE] + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + }) + .then((receipt) => { + console.log(`called router for ${receipt.cumulativeGasUsed} gas`); + }); + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`BscCluster amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + await clusterTokenInstance.methods['balanceOf(address)'](core.options.address) + .call() + .then((result) => { + assert.equal(result, 0); + }); + await web3.eth.getBalance(core.options.address) + .then((result) => { + assert.equal(result, 0); + }); + }); + + it('Should not be correct ETH <- BscCluster withdraw if 2 tokens', async() => { + let BscClusterAmount; + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + BscClusterAmount = result; + console.log(`BscCluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await clusterTokenInstance.methods.approve(router.options.address, (BscClusterAmount * 2).toString()) + .send({ + gas: 10000000, + from: accounts[0], + }); + await expectRevert(router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_WITHDRAW, + [ + [bscClusterAddress, BscClusterAmount, AMOUNT_ABSOLUTE], + [bscClusterAddress, BscClusterAmount, AMOUNT_ABSOLUTE] + ], + EMPTY_BYTES, + ], + ], + [ + [ + [bscClusterAddress, BscClusterAmount, AMOUNT_ABSOLUTE], + [0, EMPTY_BYTES] + ] + ], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + })); + }); + + it('Should be correct ETH <- BscCluster withdraw', async() => { + let BscClusterAmount; + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + BscClusterAmount = result; + console.log(`BscCluster amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await clusterTokenInstance.methods.approve(router.options.address, (BscClusterAmount * 2).toString()) + .send({ + gas: 10000000, + from: accounts[0], + }); + await router.methods.execute( + [ + [ + CLUSTER_TOKEN_ADAPTER, + ACTION_WITHDRAW, + [ + [bscClusterAddress, BscClusterAmount, AMOUNT_ABSOLUTE] + ], + EMPTY_BYTES, + ], + ], + [ + [ + [bscClusterAddress, BscClusterAmount, AMOUNT_ABSOLUTE], + [0, EMPTY_BYTES] + ] + ], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0], + value: web3.utils.toWei('1', 'ether'), + }) + .then((receipt) => { + console.log(`called router for ${receipt.cumulativeGasUsed} gas`); + }); + await clusterTokenInstance.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`BscCluster amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + await web3.eth.getBalance(accounts[0]) + .then((result) => { + console.log(` eth amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + await clusterTokenInstance.methods['balanceOf(address)'](core.options.address) + .call() + .then((result) => { + assert.equal(result, 0); + }); + await web3.eth.getBalance(core.options.address) + .then((result) => { + assert.equal(result, 0); + }); + }); + }); +}); \ No newline at end of file diff --git a/test/interactiveAdapters/StakingDHVInteractiveAdapterBSC.js b/test/interactiveAdapters/StakingDHVInteractiveAdapterBSC.js new file mode 100644 index 00000000..f9d8bfce --- /dev/null +++ b/test/interactiveAdapters/StakingDHVInteractiveAdapterBSC.js @@ -0,0 +1,221 @@ +import expectRevert from '../helpers/expectRevert'; +import convertToShare from '../helpers/convertToShare'; +import convertToBytes32 from '../helpers/convertToBytes32'; + +const BN = web3.utils.BN; + +const DHV_STAKING_ADAPTER = convertToBytes32('DHV Staking'); + +const ACTION_DEPOSIT = 1; +const ACTION_WITHDRAW = 2; +const AMOUNT_RELATIVE = 1; +const AMOUNT_ABSOLUTE = 2; +const EMPTY_BYTES = '0x'; + +const ZERO = '0x0000000000000000000000000000000000000000'; + +const ProtocolAdapterRegistry = artifacts.require('./ProtocolAdapterRegistry'); +const InteractiveAdapter = artifacts.require('./StakingDHVInteractiveAdapterBSC'); +const Core = artifacts.require('./Core'); +const Router = artifacts.require('./Router'); +const ERC20 = artifacts.require('./ERC20'); +const UniswapRouter = artifacts.require('./UniswapV2Router02'); +const StakingPools = artifacts.require('./IStakingPools'); +const StakingDHVAdapter = artifacts.require('./StakingDHVAdapter'); + + +contract('StakingDHVInteractiveAdapterEth', () => { + const ethAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; + const dhvAddress = '0x58759dd469ae5631c42cf8a473992335575b58d7'; + const busdAddress = '0xe9e7cea3dedca5984780bafc599bd69add087d56'; + const stakingDHVAddress = '0x35f28aA0B2F34eFF17d2830135312ab2a777De36'; + const wbnb = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'; + let encodedData; + + let accounts; + let core; + let router; + let protocolAdapterRegistry; + let protocolAdapterAddress; + let stakingPools; + let dhvToken; + let uniSwapRouter; + let stakingAdapter; + + let protocol; + + before(async () => { + accounts = await web3.eth.getAccounts(); + encodedData = web3.eth.abi.encodeParameter('address', accounts[0]); + + stakingAdapter = await StakingDHVAdapter.new({from: accounts[0]}); + + uniSwapRouter = await UniswapRouter.at("0x10ED43C718714eb63d5aA57B78B54704E256024E"); + let ethValue = await web3.utils.toWei('1', 'ether'); + await uniSwapRouter.swapExactETHForTokens(0, [wbnb, busdAddress, dhvAddress], accounts[0], '10000000000', {from: accounts[0], value: ethValue}); + + await InteractiveAdapter.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterAddress = result.address; + protocol = result.contract; + }); + await ProtocolAdapterRegistry.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterRegistry = result.contract; + }); + await protocolAdapterRegistry.methods.addProtocolAdapters( + [ + DHV_STAKING_ADAPTER, + ], + [ + protocolAdapterAddress, + ], + [[]], + ) + .send({ + from: accounts[0], + gas: '1000000', + }); + await Core.new( + protocolAdapterRegistry.options.address, + { from: accounts[0] }, + ) + .then((result) => { + core = result.contract; + }); + await Router.new( + core.options.address, + { from: accounts[0] }, + ) + .then((result) => { + router = result.contract; + }); + await ERC20.at(dhvAddress) + .then((result) => { + dhvToken = result.contract; + }); + }); + + describe('DHV <-> StakingDHV', async() => { + it('Should not be correct DHV -> DHV Staking if 2 tokens', async() => { + let dhvAmount; + await dhvToken.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + dhvAmount = result; + console.log(`dhv amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + // let result = await protocol.methods['getBalance(address,address)'](stakingDHVAddress, accounts[0]).call(); + // console.log(`staking dhv amount before is ${web3.utils.fromWei(result, 'ether')}`); + + await dhvToken.methods.approve(router.options.address, dhvAmount) + .send({ + gas: 10000000, + from: accounts[0], + }); + await expectRevert(router.methods.execute( + [ + [ + DHV_STAKING_ADAPTER, + ACTION_DEPOSIT, + [ + [dhvAddress, convertToShare(1), AMOUNT_RELATIVE], + [ethAddress, convertToShare(1), AMOUNT_RELATIVE], + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0] + })); + }); + + it('Should not be correct BUSD -> DHV Staking', async() => { + let dhvAmount; + await dhvToken.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + dhvAmount = result; + console.log(`dhv amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await dhvToken.methods.approve(router.options.address, dhvAmount) + .send({ + gas: 10000000, + from: accounts[0], + }); + await expectRevert(router.methods.execute( + [ + [ + DHV_STAKING_ADAPTER, + ACTION_DEPOSIT, + [ + [busdAddress, convertToShare(1), AMOUNT_RELATIVE] + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0] + })); + }); + + it('Should be correct DHV -> DHV Staking deposit', async() => { + let dhvAmount; + await dhvToken.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + dhvAmount = result; + console.log(`dhv amount before is ${web3.utils.fromWei(dhvAmount, 'ether')}`); + }); + await dhvToken.methods.approve(router.options.address, dhvAmount) + .send({ + gas: 10000000, + from: accounts[0], + }); + await router.methods.execute( + [ + [ + DHV_STAKING_ADAPTER, + ACTION_DEPOSIT, + [ + [dhvAddress, dhvAmount, AMOUNT_ABSOLUTE] + ], + encodedData, + ], + ], + [ + [ + [dhvAddress, dhvAmount, AMOUNT_ABSOLUTE], + [0, EMPTY_BYTES] + ] + ], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0] + }) + .then((receipt) => { + console.log(`called router for ${receipt.cumulativeGasUsed} gas`); + }); + await dhvToken.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`dhv amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + let result = await protocol.methods['getBalance(address,address)'](stakingDHVAddress, accounts[0]).call(); + console.log(`staking dhv amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + }); +}); \ No newline at end of file diff --git a/test/interactiveAdapters/StakingDHVInteractiveAdapterEth.js b/test/interactiveAdapters/StakingDHVInteractiveAdapterEth.js new file mode 100644 index 00000000..0c82684a --- /dev/null +++ b/test/interactiveAdapters/StakingDHVInteractiveAdapterEth.js @@ -0,0 +1,221 @@ +import expectRevert from '../helpers/expectRevert'; +import convertToShare from '../helpers/convertToShare'; +import convertToBytes32 from '../helpers/convertToBytes32'; + +const BN = web3.utils.BN; + +const DHV_STAKING_ADAPTER = convertToBytes32('DHV Staking'); + +const ACTION_DEPOSIT = 1; +const ACTION_WITHDRAW = 2; +const AMOUNT_RELATIVE = 1; +const AMOUNT_ABSOLUTE = 2; +const EMPTY_BYTES = '0x'; + +const ZERO = '0x0000000000000000000000000000000000000000'; + +const ProtocolAdapterRegistry = artifacts.require('./ProtocolAdapterRegistry'); +const InteractiveAdapter = artifacts.require('./StakingDHVInteractiveAdapterEth'); +const Core = artifacts.require('./Core'); +const Router = artifacts.require('./Router'); +const ERC20 = artifacts.require('./ERC20'); +const UniswapRouter = artifacts.require('./UniswapV2Router02'); +const StakingPools = artifacts.require('./IStakingPools'); +const StakingDHVAdapter = artifacts.require('./StakingDHVAdapter'); + + +contract('StakingDHVInteractiveAdapterEth', () => { + const ethAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; + const dhvAddress = '0x62Dc4817588d53a056cBbD18231d91ffCcd34b2A'; + const daiAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; + const stakingDHVAddress = '0x04595f9010F79422a9b411ef963e4dd1F7107704'; + const weth = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; + let encodedData; + + let accounts; + let core; + let router; + let protocolAdapterRegistry; + let protocolAdapterAddress; + let stakingPools; + let dhvToken; + let uniSwapRouter; + let stakingAdapter; + + let protocol; + + before(async () => { + accounts = await web3.eth.getAccounts(); + encodedData = web3.eth.abi.encodeParameter('address', accounts[0]); + + stakingAdapter = await StakingDHVAdapter.new({from: accounts[0]}); + + uniSwapRouter = await UniswapRouter.at("0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"); + let ethValue = await web3.utils.toWei('1', 'ether'); + await uniSwapRouter.swapExactETHForTokens(0, [weth, dhvAddress], accounts[0], '10000000000', {from: accounts[0], value: ethValue}); + + await InteractiveAdapter.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterAddress = result.address; + protocol = result.contract; + }); + await ProtocolAdapterRegistry.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterRegistry = result.contract; + }); + await protocolAdapterRegistry.methods.addProtocolAdapters( + [ + DHV_STAKING_ADAPTER, + ], + [ + protocolAdapterAddress, + ], + [[]], + ) + .send({ + from: accounts[0], + gas: '1000000', + }); + await Core.new( + protocolAdapterRegistry.options.address, + { from: accounts[0] }, + ) + .then((result) => { + core = result.contract; + }); + await Router.new( + core.options.address, + { from: accounts[0] }, + ) + .then((result) => { + router = result.contract; + }); + await ERC20.at(dhvAddress) + .then((result) => { + dhvToken = result.contract; + }); + }); + + describe('DHV <-> StakingDHV', async() => { + it('Should not be correct DHV -> DHV Staking if 2 tokens', async() => { + let dhvAmount; + await dhvToken.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + dhvAmount = result; + console.log(`dhv amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + // let result = await protocol.methods['getBalance(address,address)'](stakingDHVAddress, accounts[0]).call(); + // console.log(`staking dhv amount before is ${web3.utils.fromWei(result, 'ether')}`); + + await dhvToken.methods.approve(router.options.address, dhvAmount) + .send({ + gas: 10000000, + from: accounts[0], + }); + await expectRevert(router.methods.execute( + [ + [ + DHV_STAKING_ADAPTER, + ACTION_DEPOSIT, + [ + [dhvAddress, convertToShare(1), AMOUNT_RELATIVE], + [ethAddress, convertToShare(1), AMOUNT_RELATIVE], + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0] + })); + }); + + it('Should not be correct DAI -> DHV Staking', async() => { + let dhvAmount; + await dhvToken.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + dhvAmount = result; + console.log(`dhv amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + await dhvToken.methods.approve(router.options.address, dhvAmount) + .send({ + gas: 10000000, + from: accounts[0], + }); + await expectRevert(router.methods.execute( + [ + [ + DHV_STAKING_ADAPTER, + ACTION_DEPOSIT, + [ + [daiAddress, convertToShare(1), AMOUNT_RELATIVE] + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0] + })); + }); + + it('Should be correct DHV -> DHV Staking deposit', async() => { + let dhvAmount; + await dhvToken.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + dhvAmount = result; + console.log(`dhv amount before is ${web3.utils.fromWei(dhvAmount, 'ether')}`); + }); + await dhvToken.methods.approve(router.options.address, dhvAmount) + .send({ + gas: 10000000, + from: accounts[0], + }); + await router.methods.execute( + [ + [ + DHV_STAKING_ADAPTER, + ACTION_DEPOSIT, + [ + [dhvAddress, dhvAmount, AMOUNT_ABSOLUTE] + ], + encodedData, + ], + ], + [ + [ + [dhvAddress, dhvAmount, AMOUNT_ABSOLUTE], + [0, EMPTY_BYTES] + ] + ], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0] + }) + .then((receipt) => { + console.log(`called router for ${receipt.cumulativeGasUsed} gas`); + }); + await dhvToken.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`dhv amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + let result = await protocol.methods['getBalance(address,address)'](stakingDHVAddress, accounts[0]).call(); + console.log(`staking dhv amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + }); +}); \ No newline at end of file diff --git a/test/interactiveAdapters/StakingPoolsInteractiveAdapterBSC.js b/test/interactiveAdapters/StakingPoolsInteractiveAdapterBSC.js new file mode 100644 index 00000000..bb0ee767 --- /dev/null +++ b/test/interactiveAdapters/StakingPoolsInteractiveAdapterBSC.js @@ -0,0 +1,175 @@ +import expectRevert from '../helpers/expectRevert'; +import convertToShare from '../helpers/convertToShare'; +import convertToBytes32 from '../helpers/convertToBytes32'; + +const BN = web3.utils.BN; + +const STAKING_POOLS_ADAPTER = convertToBytes32('Staking pools'); + +const ACTION_DEPOSIT = 1; +const ACTION_WITHDRAW = 2; +const AMOUNT_RELATIVE = 1; +const AMOUNT_ABSOLUTE = 2; +const EMPTY_BYTES = '0x'; + +const ZERO = '0x0000000000000000000000000000000000000000'; + +const ProtocolAdapterRegistry = artifacts.require('./ProtocolAdapterRegistry'); +const InteractiveAdapter = artifacts.require('./StakingPoolsInteractiveAdapterBSC'); +const Core = artifacts.require('./Core'); +const Router = artifacts.require('./Router'); +const ERC20 = artifacts.require('./ERC20'); +const UniswapRouter = artifacts.require('./UniswapV2Router02'); +const WETH = artifacts.require('./WETH9'); + + + +contract('StakingPoolsInteractiveAdapterBSC', () => { + const ethAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; + const busdAddress = '0xe9e7cea3dedca5984780bafc599bd69add087d56'; + const busdDhvAddress = '0x72ba008b631d9fd5a8e8013023cb3c05e19a7ca9'; + const wbnb = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'; + const dhv = '0x58759dd469ae5631c42cf8a473992335575b58d7'; + const uniswapRouterAddress = '0x10ED43C718714eb63d5aA57B78B54704E256024E'; + const stakingPoolsAddress = '0xF2e8CD1c40C766FEe73f56607fDffa526Ba8fa6c'; + const WETH_DHV_PID = 0; + let encodedData; + + let accounts; + let core; + let router; + let protocolAdapterRegistry; + let protocolAdapterAddress; + let dhvToken; + let uniSwapRouter; + let busdERC20; + let busdDhvLp; + + let protocol; + + before(async () => { + accounts = await web3.eth.getAccounts(); + encodedData = web3.eth.abi.encodeParameters(['uint', 'address'], [WETH_DHV_PID, accounts[0]); + + uniSwapRouter = await UniswapRouter.at(uniswapRouterAddress); + busdDhvLp = await ERC20.at(busdDhvAddress); + dhvToken = await ERC20.at(dhv); + busdERC20 = await ERC20.at(busdAddress); + let ethValue = await web3.utils.toWei('1', 'ether'); + await uniSwapRouter.swapExactETHForTokens(0, [wbnb, busdAddress, dhv], accounts[0], '10000000000', {from: accounts[0], value: ethValue}); + await uniSwapRouter.swapExactETHForTokens(0, [wbnb, busdAddress], accounts[0], '10000000000', {from: accounts[0], value: ethValue}); + let dhv_bal = await dhvToken.balanceOf(accounts[0]); + let busd_bal = await busdERC20.balanceOf(accounts[0]); + await dhvToken.approve(uniswapRouterAddress, dhv_bal, {from: accounts[0]}); + await busdERC20.approve(uniswapRouterAddress, busd_bal, {from: accounts[0]}); + await uniSwapRouter.addLiquidity(busdAddress, dhv, busd_bal, dhv_bal, 0, 0, accounts[0], '10000000000', {from: accounts[0]}); + + await InteractiveAdapter.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterAddress = result.address; + protocol = result.contract; + }); + await ProtocolAdapterRegistry.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterRegistry = result.contract; + }); + await protocolAdapterRegistry.methods.addProtocolAdapters( + [ + STAKING_POOLS_ADAPTER, + ], + [ + protocolAdapterAddress, + ], + [[]], + ) + .send({ + from: accounts[0], + gas: '1000000', + }); + await Core.new( + protocolAdapterRegistry.options.address, + { from: accounts[0] }, + ) + .then((result) => { + core = result.contract; + }); + await Router.new( + core.options.address, + { from: accounts[0] }, + ) + .then((result) => { + router = result.contract; + }); + await ERC20.at(dhv) + .then((result) => { + dhvToken = result.contract; + }); + }); + + describe('DHV-BUSD LP <-> StakingPools', async() => { + it('Should not be correct DHV-BUSD LP -> StakingPools if 2 tokens', async() => { + let dhvAmount = await busdDhvLp.balanceOf(accounts[0]); + console.log(`DHV-BUSD lp amount before is ${web3.utils.fromWei(dhvAmount, 'ether')}`); + + await busdDhvLp.approve(router.options.address, dhvAmount, {from: accounts[0]}); + await expectRevert(router.methods.execute( + [ + [ + STAKING_POOLS_ADAPTER, + ACTION_DEPOSIT, + [ + [busdDhvAddress, convertToShare(1), AMOUNT_ABSOLUTE], + [wbnb, convertToShare(1), AMOUNT_ABSOLUTE], + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0] + })); + }); + + it('Should be correct DHV-BUSD LP -> StakingPools deposit', async() => { + let dhvAmount = await busdDhvLp.balanceOf(accounts[0]); + console.log(`DHV-BUSD lp amount before is ${web3.utils.fromWei(dhvAmount, 'ether')}`); + + await busdDhvLp.approve(router.options.address, dhvAmount, {from: accounts[0]}); + await router.methods.execute( + [ + [ + STAKING_POOLS_ADAPTER, + ACTION_DEPOSIT, + [ + [busdDhvAddress, dhvAmount, AMOUNT_ABSOLUTE] + ], + encodedData, + ], + ], + [ + [ + [busdDhvAddress, dhvAmount, AMOUNT_ABSOLUTE], + [0, EMPTY_BYTES] + ] + ], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0] + }) + .then((receipt) => { + console.log(`called router for ${receipt.cumulativeGasUsed} gas`); + }); + dhvAmount = await busdDhvLp.balanceOf(accounts[0]); + console.log(`DHV-BUSD lp amount after is ${web3.utils.fromWei(dhvAmount, 'ether')}`); + let result = await protocol.methods['getBalance(address,address)'](stakingPoolsAddress, accounts[0]).call(); + console.log(`staking pools busd-dhv lp amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + }); +}); \ No newline at end of file diff --git a/test/interactiveAdapters/StakingPoolsInteractiveAdapterEth.js b/test/interactiveAdapters/StakingPoolsInteractiveAdapterEth.js new file mode 100644 index 00000000..bc9359b5 --- /dev/null +++ b/test/interactiveAdapters/StakingPoolsInteractiveAdapterEth.js @@ -0,0 +1,198 @@ +import expectRevert from '../helpers/expectRevert'; +import convertToShare from '../helpers/convertToShare'; +import convertToBytes32 from '../helpers/convertToBytes32'; + +const BN = web3.utils.BN; + +const STAKING_POOLS_ADAPTER = convertToBytes32('Staking pools'); + +const ACTION_DEPOSIT = 1; +const ACTION_WITHDRAW = 2; +const AMOUNT_RELATIVE = 1; +const AMOUNT_ABSOLUTE = 2; +const EMPTY_BYTES = '0x'; + +const ZERO = '0x0000000000000000000000000000000000000000'; + +const ProtocolAdapterRegistry = artifacts.require('./ProtocolAdapterRegistry'); +const InteractiveAdapter = artifacts.require('./StakingPoolsInteractiveAdapterEth'); +const Core = artifacts.require('./Core'); +const Router = artifacts.require('./Router'); +const ERC20 = artifacts.require('./ERC20'); +const UniswapRouter = artifacts.require('./UniswapV2Router02'); +const WETH = artifacts.require('./WETH9'); + + + +contract('StakingPoolsInteractiveAdapterEth', () => { + const ethAddress = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'; + const daiAddress = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; + const wethDhvLpAddress = '0x60c5BF43140d6341bebFE13293567FafBe01D65b'; + const weth = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; + const dhv = '0x62Dc4817588d53a056cBbD18231d91ffCcd34b2A'; + const uniswapRouterAddress = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'; + const stakingPoolsAddress = '0x4964B3B599B82C3FdDC56e3A9Ffd77d48c6AF0f0'; + const WETH_DHV_PID = 0; + let encodedData; + + let accounts; + let core; + let router; + let protocolAdapterRegistry; + let protocolAdapterAddress; + let dhvToken; + let uniSwapRouter; + let wethProtocol, wethERC20; + let wethDhvLp; + + let protocol; + + before(async () => { + accounts = await web3.eth.getAccounts(); + encodedData = web3.eth.abi.encodeParameters(['uint', 'address', 'address[]'], [WETH_DHV_PID, accounts[0], [dhv]]); + + uniSwapRouter = await UniswapRouter.at(uniswapRouterAddress); + wethProtocol = await WETH.at(weth); + wethERC20 = await ERC20.at(weth); + wethDhvLp = await ERC20.at(wethDhvLpAddress); + dhvToken = ERC20.at(dhv); + let ethValue = await web3.utils.toWei('1', 'ether'); + await uniSwapRouter.swapExactETHForTokens(0, [weth, dhv], accounts[0], '10000000000', {from: accounts[0], value: ethValue}); + await wethProtocol.deposit({from: accounts[0], value: ethValue}); + let dhv_bal = await dhvToken.balanceOf(accounts[0]); + let weth_bal = await wethERC20.balanceOf(accounts[0]); + await dhvToken.approve(uniswapRouterAddress, dhv_bal, {from: accounts[0]}); + await wethERC20.approve(uniswapRouterAddress, weth_bal, {from: accounts[0]}); + await uniSwapRouter.addLiquidity(weth, dhv, await weth_bal, dhv_bal, 0, 0, accounts[0], '10000000000', {from: accounts[0]}); + + await InteractiveAdapter.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterAddress = result.address; + protocol = result.contract; + }); + await ProtocolAdapterRegistry.new({ from: accounts[0] }) + .then((result) => { + protocolAdapterRegistry = result.contract; + }); + await protocolAdapterRegistry.methods.addProtocolAdapters( + [ + STAKING_POOLS_ADAPTER, + ], + [ + protocolAdapterAddress, + ], + [[]], + ) + .send({ + from: accounts[0], + gas: '1000000', + }); + await Core.new( + protocolAdapterRegistry.options.address, + { from: accounts[0] }, + ) + .then((result) => { + core = result.contract; + }); + await Router.new( + core.options.address, + { from: accounts[0] }, + ) + .then((result) => { + router = result.contract; + }); + await ERC20.at(dhvAddress) + .then((result) => { + dhvToken = result.contract; + }); + }); + + describe('DHV-WETH LP <-> StakingPools', async() => { + it('Should not be correct DHV-WETH LP -> StakingPools if 2 tokens', async() => { + let dhvAmount; + await wethDhvLp.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + dhvAmount = result; + console.log(`dhv-weth lp amount before is ${web3.utils.fromWei(result, 'ether')}`); + }); + // let result = await protocol.methods['getBalance(address,address)'](stakingDHVAddress, accounts[0]).call(); + // console.log(`staking dhv amount before is ${web3.utils.fromWei(result, 'ether')}`); + + await wethDhvLp.methods.approve(router.options.address, dhvAmount) + .send({ + gas: 10000000, + from: accounts[0], + }); + await expectRevert(router.methods.execute( + [ + [ + STAKING_POOLS_ADAPTER, + ACTION_DEPOSIT, + [ + [wethDhvLpAddress, convertToShare(1), AMOUNT_RELATIVE], + [weth, convertToShare(1), AMOUNT_RELATIVE], + ], + encodedData, + ], + ], + [], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0] + })); + }); + + it('Should be correct DHV-WETH LP -> StakingPools deposit', async() => { + let dhvAmount; + await wethDhvLp.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + dhvAmount = result; + console.log(`dhv-weth lp amount before is ${web3.utils.fromWei(dhvAmount, 'ether')}`); + }); + await wethDhvLp.methods.approve(router.options.address, dhvAmount) + .send({ + gas: 10000000, + from: accounts[0], + }); + await router.methods.execute( + [ + [ + STAKING_POOLS_ADAPTER, + ACTION_DEPOSIT, + [ + [wethDhvLp, dhvAmount, AMOUNT_ABSOLUTE] + ], + encodedData, + ], + ], + [ + [ + [wethDhvLp, dhvAmount, AMOUNT_ABSOLUTE], + [0, EMPTY_BYTES] + ] + ], + [0, ZERO], + [], + ) + .send({ + gas: 10000000, + from: accounts[0] + }) + .then((receipt) => { + console.log(`called router for ${receipt.cumulativeGasUsed} gas`); + }); + await wethDhvLp.methods['balanceOf(address)'](accounts[0]) + .call() + .then((result) => { + console.log(`dhv-weth lp amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + let result = await protocol.methods['getBalance(address,address)'](stakingPoolsAddress, accounts[0]).call(); + console.log(`staking pools weth-dhv lp amount after is ${web3.utils.fromWei(result, 'ether')}`); + }); + }); +}); \ No newline at end of file From a164f79998fe8fe44af217f32ea7e369be8bf390 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 7 Dec 2021 15:55:49 +0200 Subject: [PATCH 9/9] feat: add address of bsc external adapter --- .../dehive/ClusterTokenInteractiveAdapterBSC.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterBSC.sol b/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterBSC.sol index b10f1e8c..13da055f 100644 --- a/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterBSC.sol +++ b/contracts/interactiveAdapters/dehive/ClusterTokenInteractiveAdapterBSC.sol @@ -35,7 +35,7 @@ import { IExternalAdapter } from "../../interfaces/IExternalAdapter.sol"; contract ClusterTokenInteractiveAdapterBSC is InteractiveAdapter, DeHiveProtocolAdapter { using SafeERC20 for ERC20; - address internal constant EXTERNAL_ADAPTER = address(0); + address internal constant EXTERNAL_ADAPTER = address(0x92450c9Dc4c709F4169f9196E908772744D89C8c); /** * @notice Deposits tokens to the DeHive ClusterToken. * @param tokenAmounts Array with one element - TokenAmount struct with