From 382d9c1a356fdfdfdc1281c10b8b526206b5069d Mon Sep 17 00:00:00 2001 From: Mubaris NK Date: Mon, 15 Mar 2021 22:12:21 +0530 Subject: [PATCH 1/7] Add docs on adding new connector --- README.md | 2 +- docs/how-to-add-new-connector.md | 43 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 docs/how-to-add-new-connector.md diff --git a/README.md b/README.md index 9c37c876..4a338bf5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Connectors are standard proxy logics contract that let DeFi Smart Account (DSA) DSAs are powerful because they can easily be extended with connectors. Every new connector that is added is immediately usable by any developer building on top of DSAs. Connectors can either be base connectors to protocols, auth connectors, higher level connectors with more specific use cases like optimized lending, or connectors to native liquidity pools. -You can create a PR to request a support for specific protocol or external contracts. Following is the list of all the supported connectors. Following is the list of all the primary connectors used to cast spells: +You can create a PR to request a support for specific protocol or external contracts. The process to add a new connector is explained [here](docs/how-to-add-new-connector.md). Following is the list of all the supported connectors. Following is the list of all the primary connectors used to cast spells: [Read this post to learn about getId and setId used in the connectors](https://discuss.instadapp.io/t/how-to-use-getid-setid/104) diff --git a/docs/how-to-add-new-connector.md b/docs/how-to-add-new-connector.md new file mode 100644 index 00000000..29207022 --- /dev/null +++ b/docs/how-to-add-new-connector.md @@ -0,0 +1,43 @@ +## How to add a new connector + +You can create a new PR to add a new connector. To get the PR merged, certain requirements needs to be met which will be explained here. + +### New connector should follow the current directory structure + +Common files for all connectors are in `contracts/common` directory. + +* `math.sol` has methods for mathematical operations (`DSMath`) +* `interfaces.sol` contains the common interfaces + * `TokenInterface` for ERC-20 interface including WETH +* `stores.sol` contains the global constants as well as methods `getId` & `setId` (`Stores`) +* `basic.sol` inherits `DSMath` & `Stores` contracts. This contains few details explained below + * Wrapping & unwrapping ETH (`convertEthToWeth` & `convertWethToEth`) + * Getting token & ETH balance of DSA + +Connectors are under `contracts/connectors` directory, and should be formatted as follows: + +* Connector events should be in a separate contract: `events.sol` +* Interfaces should be defined in a seperate file: `interface.sol` +* If the connector has helper methods & constants (including interface instances), this should be defined in a separate file: `helpers.sol` + * `Helpers` contract should inherit `Basic` contract from common directory + * If the connector doesn't have any helper methods, the main contract should inherit `Basic` contract +* The main logic of the contract should be under `main.sol`, and the contract should inherit `Helpers` (if exists, otherwise `Basic`) & `Events` + +Few things to consider while writing the connector: + +* Connector should have a public string declared `name`, which will be the name of the connector. This will be versioned. Ex: `Compound-v1` +* User interacting methods (`external` methods) will not be emitting events, rather the methods will be returning 2 variables: + * `_eventName` of `string` type: This will be the event signture defined in the `Events` contract. Ex: `LogDeposit(address,address,uint256,uint256,uint256)` + * `_eventParam` of `bytes` type: This will be the abi encoded event parameters +* The contracts should not have `selfdestruct()` +* The contracts should not have `delegatecall()` +* Use `uint(-1)` for maximum amount everywhere +* Use `ethAddr` (declared in `Stores`) to denote Ethereum (non-ERC20) +* Use `address(this)` instead of `msg.sender` for fetching balance on-chain, etc +* Only `approve()` limited amount while giving ERC20 allowance, which strictly needs to be 0 by the end of the spell. +* Use `getUint()` (declared in `Stores`) for getting value that saved from previous spell +* Use `setUint()` (declared in `Stores`) for setting value to save for the future spell + +### Support + +If you can't find something you're looking for or have any questions, ask them at our developers community on [Discord](https://discord.gg/83vvrnY) or simply send an [Email](mailto:info@instadapp.io). From aee0690cfdd475dbe71fdd058f1e725c4924d69b Mon Sep 17 00:00:00 2001 From: Mubaris NK Date: Tue, 16 Mar 2021 21:55:39 +0530 Subject: [PATCH 2/7] Add compound mapping and update --- contracts/connectors/compound/events.sol | 41 ++++++++- contracts/connectors/compound/helpers.sol | 7 +- contracts/connectors/compound/interface.sol | 13 +++ contracts/connectors/compound/main.sol | 93 +++++++++++---------- contracts/mapping/compound.sol | 79 +++++++++++++++++ 5 files changed, 184 insertions(+), 49 deletions(-) create mode 100644 contracts/mapping/compound.sol diff --git a/contracts/connectors/compound/events.sol b/contracts/connectors/compound/events.sol index 9a0602f3..7d5ac529 100644 --- a/contracts/connectors/compound/events.sol +++ b/contracts/connectors/compound/events.sol @@ -1,13 +1,45 @@ pragma solidity ^0.7.0; contract Events { - event LogDeposit(address indexed token, address cToken, uint256 tokenAmt, uint256 getId, uint256 setId); - event LogWithdraw(address indexed token, address cToken, uint256 tokenAmt, uint256 getId, uint256 setId); - event LogBorrow(address indexed token, address cToken, uint256 tokenAmt, uint256 getId, uint256 setId); - event LogPayback(address indexed token, address cToken, uint256 tokenAmt, uint256 getId, uint256 setId); + event LogDeposit( + address indexed token, + string tokenId, + address cToken, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogWithdraw( + address indexed token, + string tokenId, + address cToken, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogBorrow( + address indexed token, + string tokenId, + address cToken, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); + + event LogPayback( + address indexed token, + string tokenId, + address cToken, + uint256 tokenAmt, + uint256 getId, + uint256 setId + ); event LogDepositCToken( address indexed token, + string tokenId, address cToken, uint256 tokenAmt, uint256 cTokenAmt, @@ -17,6 +49,7 @@ contract Events { event LogWithdrawCToken( address indexed token, + string tokenId, address cToken, uint256 tokenAmt, uint256 cTokenAmt, diff --git a/contracts/connectors/compound/helpers.sol b/contracts/connectors/compound/helpers.sol index 747921b3..236feac8 100644 --- a/contracts/connectors/compound/helpers.sol +++ b/contracts/connectors/compound/helpers.sol @@ -2,7 +2,7 @@ pragma solidity ^0.7.0; import { DSMath } from "../../common/math.sol"; import { Basic } from "../../common/basic.sol"; -import { ComptrollerInterface } from "./interface.sol"; +import { ComptrollerInterface, CompoundMappingInterface } from "./interface.sol"; abstract contract Helpers is DSMath, Basic { /** @@ -10,6 +10,11 @@ abstract contract Helpers is DSMath, Basic { */ ComptrollerInterface internal constant troller = ComptrollerInterface(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B); + /** + * @dev Compound Mapping + */ + CompoundMappingInterface internal constant compMapping = CompoundMappingInterface(0xe81F70Cc7C0D46e12d70efc60607F16bbD617E88); // Update the address + /** * @dev enter compound market */ diff --git a/contracts/connectors/compound/interface.sol b/contracts/connectors/compound/interface.sol index 9605dc53..cb89a11c 100644 --- a/contracts/connectors/compound/interface.sol +++ b/contracts/connectors/compound/interface.sol @@ -29,3 +29,16 @@ interface ComptrollerInterface { function getAccountLiquidity(address account) external view returns (uint, uint, uint); function claimComp(address) external; } + +interface CompoundMappingInterface { + function cTokenMapping(string calldata tokenId) external view returns (address); +} + +struct LiquidateData { + address borrower; + address tokenToPay; + string tokenPayId; + address tokenInReturn; + string tokenReturnId; + uint amt; +} \ No newline at end of file diff --git a/contracts/connectors/compound/main.sol b/contracts/connectors/compound/main.sol index e3db45be..d4bfb967 100644 --- a/contracts/connectors/compound/main.sol +++ b/contracts/connectors/compound/main.sol @@ -1,10 +1,11 @@ pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; import { TokenInterface } from "../../common/interfaces.sol"; import { Stores } from "../../common/stores.sol"; import { Helpers } from "./helpers.sol"; import { Events } from "./events.sol"; -import { CETHInterface, CTokenInterface } from "./interface.sol"; +import { CETHInterface, CTokenInterface, LiquidateData } from "./interface.sol"; abstract contract CompoundResolver is Events, Helpers { /** @@ -16,12 +17,13 @@ abstract contract CompoundResolver is Events, Helpers { */ function deposit( address token, + string calldata tokenId, uint amt, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint _amt = getUint(getId, amt); - address cToken = instaMapping.cTokenMapping(token); + address cToken = compMapping.cTokenMapping(tokenId); enterMarket(cToken); if (token == ethAddr) { _amt = _amt == uint(-1) ? address(this).balance : _amt; @@ -34,8 +36,8 @@ abstract contract CompoundResolver is Events, Helpers { } setUint(setId, _amt); - _eventName = "LogDeposit(address,address,uint256,uint256,uint256)"; - _eventParam = abi.encode(token, cToken, _amt, getId, setId); + _eventName = "LogDeposit(address,string,address,uint256,uint256,uint256)"; + _eventParam = abi.encode(token, tokenId, cToken, _amt, getId, setId); } /** @@ -47,12 +49,13 @@ abstract contract CompoundResolver is Events, Helpers { */ function withdraw( address token, + string calldata tokenId, uint amt, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint _amt = getUint(getId, amt); - address cToken = instaMapping.cTokenMapping(token); + address cToken = compMapping.cTokenMapping(tokenId); CTokenInterface cTokenContract = CTokenInterface(cToken); if (_amt == uint(-1)) { TokenInterface tokenContract = TokenInterface(token); @@ -65,8 +68,8 @@ abstract contract CompoundResolver is Events, Helpers { } setUint(setId, _amt); - _eventName = "LogWithdraw(address,address,uint256,uint256,uint256)"; - _eventParam = abi.encode(token, cToken, _amt, getId, setId); + _eventName = "LogWithdraw(address,string,address,uint256,uint256,uint256)"; + _eventParam = abi.encode(token, tokenId, cToken, _amt, getId, setId); } /** @@ -78,18 +81,19 @@ abstract contract CompoundResolver is Events, Helpers { */ function borrow( address token, + string calldata tokenId, uint amt, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint _amt = getUint(getId, amt); - address cToken = instaMapping.cTokenMapping(token); + address cToken = compMapping.cTokenMapping(tokenId); enterMarket(cToken); require(CTokenInterface(cToken).borrow(_amt) == 0, "borrow-failed"); setUint(setId, _amt); - _eventName = "LogBorrow(address,address,uint256,uint256,uint256)"; - _eventParam = abi.encode(token, cToken, _amt, getId, setId); + _eventName = "LogBorrow(address,string,address,uint256,uint256,uint256)"; + _eventParam = abi.encode(token, tokenId, cToken, _amt, getId, setId); } /** @@ -101,12 +105,13 @@ abstract contract CompoundResolver is Events, Helpers { */ function payback( address token, + string calldata tokenId, uint amt, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint _amt = getUint(getId, amt); - address cToken = instaMapping.cTokenMapping(token); + address cToken = compMapping.cTokenMapping(tokenId); CTokenInterface cTokenContract = CTokenInterface(cToken); _amt = _amt == uint(-1) ? cTokenContract.borrowBalanceCurrent(address(this)) : _amt; @@ -121,8 +126,8 @@ abstract contract CompoundResolver is Events, Helpers { } setUint(setId, _amt); - _eventName = "LogPayback(address,address,uint256,uint256,uint256)"; - _eventParam = abi.encode(token, cToken, _amt, getId, setId); + _eventName = "LogPayback(address,string,address,uint256,uint256,uint256)"; + _eventParam = abi.encode(token, tokenId, cToken, _amt, getId, setId); } /** @@ -134,12 +139,13 @@ abstract contract CompoundResolver is Events, Helpers { */ function depositCToken( address token, + string calldata tokenId, uint amt, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint _amt = getUint(getId, amt); - address cToken = instaMapping.cTokenMapping(token); + address cToken = compMapping.cTokenMapping(tokenId); enterMarket(cToken); CTokenInterface ctokenContract = CTokenInterface(cToken); @@ -159,8 +165,8 @@ abstract contract CompoundResolver is Events, Helpers { uint _cAmt = finalBal - initialBal; setUint(setId, _cAmt); - _eventName = "LogDepositCToken(address,address,uint256,uint256,uint256,uint256)"; - _eventParam = abi.encode(token, cToken, _amt, _cAmt, getId, setId); + _eventName = "LogDepositCToken(address,string,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(token, tokenId, cToken, _amt, _cAmt, getId, setId); } /** @@ -172,63 +178,62 @@ abstract contract CompoundResolver is Events, Helpers { */ function withdrawCToken( address token, + string calldata tokenId, uint cTokenAmt, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint _cAmt = getUint(getId, cTokenAmt); - address cToken = instaMapping.cTokenMapping(token); + address cToken = compMapping.cTokenMapping(tokenId); CTokenInterface cTokenContract = CTokenInterface(cToken); TokenInterface tokenContract = TokenInterface(token); _cAmt = _cAmt == uint(-1) ? cTokenContract.balanceOf(address(this)) : _cAmt; - uint initialBal = token != ethAddr ? tokenContract.balanceOf(address(this)) : address(this).balance; - require(cTokenContract.redeem(_cAmt) == 0, "redeem-failed"); - uint finalBal = token != ethAddr ? tokenContract.balanceOf(address(this)) : address(this).balance; + uint withdrawAmt; + { + uint initialBal = token != ethAddr ? tokenContract.balanceOf(address(this)) : address(this).balance; + require(cTokenContract.redeem(_cAmt) == 0, "redeem-failed"); + uint finalBal = token != ethAddr ? tokenContract.balanceOf(address(this)) : address(this).balance; + + withdrawAmt = sub(finalBal, initialBal); + } - uint withdrawAmt = sub(finalBal, initialBal); setUint(setId, withdrawAmt); - _eventName = "LogWithdrawCToken(address,address,uint256,uint256,uint256,uint256)"; - _eventParam = abi.encode(token, cToken, withdrawAmt, _cAmt, getId, setId); + _eventName = "LogWithdrawCToken(address,string,address,uint256,uint256,uint256,uint256)"; + _eventParam = abi.encode(token, tokenId, cToken, withdrawAmt, _cAmt, getId, setId); } /** * @dev Liquidate a position. - * @param borrower Borrower's Address. - * @param tokenToPay token address to pay for liquidation.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) - * @param tokenInReturn token address to return for liquidation.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) - * @param amt token amount to pay for liquidation. + * @param data Liquidation data * @param getId Get token amount at this ID from `InstaMemory` Contract. * @param setId Set token amount at this ID in `InstaMemory` Contract. */ function liquidate( - address borrower, - address tokenToPay, - address tokenInReturn, - uint amt, + LiquidateData calldata data, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { - uint _amt = getUint(getId, amt); - address cTokenPay = instaMapping.cTokenMapping(tokenToPay); - address cTokenColl = instaMapping.cTokenMapping(tokenInReturn); + uint _amt = getUint(getId, data.amt); + address cTokenPay = compMapping.cTokenMapping(data.tokenPayId); + address cTokenColl = compMapping.cTokenMapping(data.tokenReturnId); CTokenInterface cTokenContract = CTokenInterface(cTokenPay); { - (,, uint shortfal) = troller.getAccountLiquidity(borrower); + (,, uint shortfal) = troller.getAccountLiquidity(data.borrower); require(shortfal != 0, "account-cannot-be-liquidated"); } - _amt = _amt == uint(-1) ? cTokenContract.borrowBalanceCurrent(borrower) : _amt; - if (tokenToPay == ethAddr) { + _amt = _amt == uint(-1) ? cTokenContract.borrowBalanceCurrent(data.borrower) : _amt; + if (data.tokenToPay == ethAddr) { require(address(this).balance >= _amt, "not-enought-eth"); - CETHInterface(cTokenPay).liquidateBorrow{value: _amt}(borrower, cTokenColl); + CETHInterface(cTokenPay).liquidateBorrow{value: _amt}(data.borrower, cTokenColl); } else { - TokenInterface tokenContract = TokenInterface(tokenToPay); + TokenInterface tokenContract = TokenInterface(data.tokenToPay); require(tokenContract.balanceOf(address(this)) >= _amt, "not-enough-token"); tokenContract.approve(cTokenPay, _amt); - require(cTokenContract.liquidateBorrow(borrower, _amt, cTokenColl) == 0, "liquidate-failed"); + require(cTokenContract.liquidateBorrow(data.borrower, _amt, cTokenColl) == 0, "liquidate-failed"); } setUint(setId, _amt); @@ -236,8 +241,8 @@ abstract contract CompoundResolver is Events, Helpers { _eventName = "LogLiquidate(address,address,address,uint256,uint256,uint256,uint256)"; _eventParam = abi.encode( address(this), - tokenToPay, - tokenInReturn, + data.tokenToPay, + data.tokenInReturn, _amt, getId, setId @@ -245,6 +250,6 @@ abstract contract CompoundResolver is Events, Helpers { } } -contract ConnectCompound is CompoundResolver { - string public name = "Compound-v1.3"; +contract ConnectV2Compound is CompoundResolver { + string public name = "Compound-v1"; } diff --git a/contracts/mapping/compound.sol b/contracts/mapping/compound.sol new file mode 100644 index 00000000..86284b86 --- /dev/null +++ b/contracts/mapping/compound.sol @@ -0,0 +1,79 @@ +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +interface IndexInterface { + function master() external view returns (address); +} + +interface ConnectorsInterface { + function chief(address) external view returns (bool); +} + +interface CTokenInterface { + function isCToken() external view returns (bool); +} + +abstract contract Helpers { + + event LogCTokensAdded(string[] names, address[] ctokens); + event LogCTokensUpdated(string[] names, address[] ctokens); + + ConnectorsInterface public immutable connectors; + + // InstaIndex Address. + IndexInterface public constant instaIndex = IndexInterface(0x2971AdFa57b20E5a416aE5a708A8655A9c74f723); + + mapping (string => address) public cTokenMapping; + + modifier isChief { + require(msg.sender == instaIndex.master() || connectors.chief(msg.sender), "not-an-chief"); + _; + } + + constructor(address _connectors) { + connectors = ConnectorsInterface(_connectors); + } + + function _addCtokenMapping(string[] memory _names, address[] memory _ctokens) internal { + require(_names.length == _ctokens.length, "addCtokenMapping: not same length"); + for (uint i = 0; i < _ctokens.length; i++) { + require(cTokenMapping[_names[i]] == address(0), "addCtokenMapping: mapping added already"); + require(_ctokens[i] != address(0), "addCtokenMapping: _ctokens address not vaild"); + require(CTokenInterface(_ctokens[i]).isCToken(), "addCtokenMapping: not a cToken"); + + cTokenMapping[_names[i]] = _ctokens[i]; + // emit LogCTokenAdded(_names[i], _ctokens[i]); + } + emit LogCTokensAdded(_names, _ctokens); + } + + function updateCtokenMapping(string[] calldata _names, address[] calldata _ctokens) external { + require(msg.sender == instaIndex.master(), "not-master"); + require(_names.length == _ctokens.length, "updateCtokenMapping: not same length"); + for (uint i = 0; i < _ctokens.length; i++) { + require(cTokenMapping[_names[i]] != address(0), "updateCtokenMapping: mapping does not exist"); + require(_ctokens[i] != address(0), "updateCtokenMapping: _ctokens address not vaild"); + require(CTokenInterface(_ctokens[i]).isCToken(), "updateCtokenMapping: not a cToken"); + + cTokenMapping[_names[i]] = _ctokens[i]; + } + emit LogCTokensUpdated(_names, _ctokens); + } + + function addCtokenMapping(string[] memory _names, address[] memory _ctokens) external isChief { + _addCtokenMapping(_names, _ctokens); + } + +} + +contract InstaMapping is Helpers { + string constant public name = "Compound-Mapping-v1"; + + constructor( + address _connectors, + string[] memory _ctokenNames, + address[] memory _ctokens + ) Helpers(_connectors) { + _addCtokenMapping(_ctokenNames, _ctokens); + } +} \ No newline at end of file From 6460f6c34930ab36b15e58d8d8fe182d9e744404 Mon Sep 17 00:00:00 2001 From: Mubaris NK Date: Tue, 16 Mar 2021 22:10:04 +0530 Subject: [PATCH 3/7] Add deployment script with current mapping --- contracts/mapping/compound.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/mapping/compound.sol b/contracts/mapping/compound.sol index 86284b86..63308877 100644 --- a/contracts/mapping/compound.sol +++ b/contracts/mapping/compound.sol @@ -66,7 +66,7 @@ abstract contract Helpers { } -contract InstaMapping is Helpers { +contract InstaCompoundMapping is Helpers { string constant public name = "Compound-Mapping-v1"; constructor( From ba57f1498486d80bbd03fc4645a0908325c65c84 Mon Sep 17 00:00:00 2001 From: Mubaris NK Date: Tue, 16 Mar 2021 22:10:14 +0530 Subject: [PATCH 4/7] Add deployment script with current mapping --- scripts/deployCompoundMapping.js | 54 ++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 scripts/deployCompoundMapping.js diff --git a/scripts/deployCompoundMapping.js b/scripts/deployCompoundMapping.js new file mode 100644 index 00000000..99f7cebf --- /dev/null +++ b/scripts/deployCompoundMapping.js @@ -0,0 +1,54 @@ +const hre = require("hardhat"); +const { ethers } = hre; + +async function main() { + + // TODO - Replace with actual contract address after deployment + const CONNECTORS_V2 = "0x2971AdFa57b20E5a416aE5a708A8655A9c74f723"; + + const ctokenMapping = { + "ETH-A": "0x4ddc2d193948926d02f9b1fe9e1daa0718270ed5", + "BAT-A": "0x6c8c6b02e7b2be14d4fa6022dfd6d75921d90e4e", + "COMP-A": "0x70e36f6bf80a52b3b46b3af8e106cc0ed743e8e4", + "DAI-A": "0x5d3a536e4d6dbd6114cc1ead35777bab948e3643", + "REP-A": "0x158079ee67fce2f58472a96584a73c7ab9ac95c1", + "UNI-A": "0x35a18000230da775cac24873d00ff85bccded550", + "USDC-A": "0x39aa39c021dfbae8fac545936693ac917d5e7563", + "USDT-A": "0xf650c3d88d12db855b8bf7d11be6c55a4e07dcc9", + "WBTC-A": "0xc11b1268c1a384e55c48c2391d8d480264a3a7f4", + "ZRX-A": "0xb3319f5d18bc0d84dd1b4825dcde5d5f7266d407" + } + + const Mapping = await ethers.getContractFactory("InstaCompoundMapping"); + const mapping = await Mapping.deploy( + CONNECTORS_V2, + Object.keys(ctokenMapping), + Object.values(ctokenMapping) + ); + await mapping.deployed(); + + console.log(`InstaCompoundMapping Deployed: ${mapping.address}`); + + try { + await hre.run("verify:verify", { + address: mapping.address, + constructorArguments: [ + CONNECTORS_V2, + Object.keys(ctokenMapping), + Object.values(ctokenMapping) + ] + } + ) +} catch (error) { + console.log(`Failed to verify: InstaCompoundMapping@${mapping.address}`) + console.log(error) + console.log() +} +} + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error); + process.exit(1); + }); \ No newline at end of file From 73d3bef65917024d8f5f9a2b33c25abdb3af999d Mon Sep 17 00:00:00 2001 From: Mubaris NK Date: Tue, 16 Mar 2021 22:31:15 +0530 Subject: [PATCH 5/7] Add token mapping as well to compound --- contracts/mapping/compound.sol | 53 ++++++++++++++++++++++++++------ scripts/deployCompoundMapping.js | 15 +++++++++ 2 files changed, 58 insertions(+), 10 deletions(-) diff --git a/contracts/mapping/compound.sol b/contracts/mapping/compound.sol index 63308877..43fd3b7a 100644 --- a/contracts/mapping/compound.sol +++ b/contracts/mapping/compound.sol @@ -15,8 +15,8 @@ interface CTokenInterface { abstract contract Helpers { - event LogCTokensAdded(string[] names, address[] ctokens); - event LogCTokensUpdated(string[] names, address[] ctokens); + event LogCTokensAdded(string[] names, address[] tokens, address[] ctokens); + event LogCTokensUpdated(string[] names, address[] tokens, address[] ctokens); ConnectorsInterface public immutable connectors; @@ -24,6 +24,7 @@ abstract contract Helpers { IndexInterface public constant instaIndex = IndexInterface(0x2971AdFa57b20E5a416aE5a708A8655A9c74f723); mapping (string => address) public cTokenMapping; + mapping (string => address) public tokenMapping; modifier isChief { require(msg.sender == instaIndex.master() || connectors.chief(msg.sender), "not-an-chief"); @@ -34,34 +35,65 @@ abstract contract Helpers { connectors = ConnectorsInterface(_connectors); } - function _addCtokenMapping(string[] memory _names, address[] memory _ctokens) internal { + function _addCtokenMapping( + string[] memory _names, + address[] memory _tokens, + address[] memory _ctokens + ) internal { + require(_names.length == _tokens.length, "addCtokenMapping: not same length"); require(_names.length == _ctokens.length, "addCtokenMapping: not same length"); + for (uint i = 0; i < _ctokens.length; i++) { + require(tokenMapping[_names[i]] == address(0), "addCtokenMapping: mapping added already"); require(cTokenMapping[_names[i]] == address(0), "addCtokenMapping: mapping added already"); + + require(_tokens[i] != address(0), "addCtokenMapping: _tokens address not vaild"); require(_ctokens[i] != address(0), "addCtokenMapping: _ctokens address not vaild"); + require(CTokenInterface(_ctokens[i]).isCToken(), "addCtokenMapping: not a cToken"); + tokenMapping[_names[i]] = _tokens[i]; cTokenMapping[_names[i]] = _ctokens[i]; - // emit LogCTokenAdded(_names[i], _ctokens[i]); } - emit LogCTokensAdded(_names, _ctokens); + emit LogCTokensAdded(_names, _tokens, _ctokens); } - function updateCtokenMapping(string[] calldata _names, address[] calldata _ctokens) external { + function updateCtokenMapping( + string[] calldata _names, + address[] memory _tokens, + address[] calldata _ctokens + ) external { require(msg.sender == instaIndex.master(), "not-master"); + + require(_names.length == _tokens.length, "updateCtokenMapping: not same length"); require(_names.length == _ctokens.length, "updateCtokenMapping: not same length"); + for (uint i = 0; i < _ctokens.length; i++) { + require(tokenMapping[_names[i]] != address(0), "updateCtokenMapping: mapping does not exist"); require(cTokenMapping[_names[i]] != address(0), "updateCtokenMapping: mapping does not exist"); + + require(_tokens[i] != address(0), "updateCtokenMapping: _tokens address not vaild"); require(_ctokens[i] != address(0), "updateCtokenMapping: _ctokens address not vaild"); + require(CTokenInterface(_ctokens[i]).isCToken(), "updateCtokenMapping: not a cToken"); + tokenMapping[_names[i]] = _tokens[i]; cTokenMapping[_names[i]] = _ctokens[i]; } - emit LogCTokensUpdated(_names, _ctokens); + emit LogCTokensUpdated(_names, _tokens, _ctokens); } - function addCtokenMapping(string[] memory _names, address[] memory _ctokens) external isChief { - _addCtokenMapping(_names, _ctokens); + function addCtokenMapping( + string[] memory _names, + address[] memory _tokens, + address[] memory _ctokens + ) external isChief { + _addCtokenMapping(_names, _tokens, _ctokens); + } + + function getMapping(string memory _tokenId) external view returns (address _token, address _ctoken) { + _token = tokenMapping[_tokenId]; + _ctoken = cTokenMapping[_tokenId]; } } @@ -72,8 +104,9 @@ contract InstaCompoundMapping is Helpers { constructor( address _connectors, string[] memory _ctokenNames, + address[] memory _tokens, address[] memory _ctokens ) Helpers(_connectors) { - _addCtokenMapping(_ctokenNames, _ctokens); + _addCtokenMapping(_ctokenNames, _tokens, _ctokens); } } \ No newline at end of file diff --git a/scripts/deployCompoundMapping.js b/scripts/deployCompoundMapping.js index 99f7cebf..0e463e48 100644 --- a/scripts/deployCompoundMapping.js +++ b/scripts/deployCompoundMapping.js @@ -19,10 +19,24 @@ async function main() { "ZRX-A": "0xb3319f5d18bc0d84dd1b4825dcde5d5f7266d407" } + const tokenMapping = { + "ETH-A": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", + "BAT-A": "0x0D8775F648430679A709E98d2b0Cb6250d2887EF", + "COMP-A": "0xc00e94cb662c3520282e6f5717214004a7f26888", + "DAI-A": "0x6b175474e89094c44da98b954eedeac495271d0f", + "REP-A": "0x1985365e9f78359a9B6AD760e32412f4a445E862", + "UNI-A": "0x221657776846890989a759ba2973e427dff5c9bb", + "USDC-A": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + "USDT-A": "0xdac17f958d2ee523a2206206994597c13d831ec7", + "WBTC-A": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", + "ZRX-A": "0xe41d2489571d322189246dafa5ebde1f4699f498" + } + const Mapping = await ethers.getContractFactory("InstaCompoundMapping"); const mapping = await Mapping.deploy( CONNECTORS_V2, Object.keys(ctokenMapping), + Object.values(tokenMapping), Object.values(ctokenMapping) ); await mapping.deployed(); @@ -35,6 +49,7 @@ async function main() { constructorArguments: [ CONNECTORS_V2, Object.keys(ctokenMapping), + Object.values(tokenMapping), Object.values(ctokenMapping) ] } From fb800e27cb86521a773a175c5ed1fcc828082bbb Mon Sep 17 00:00:00 2001 From: Mubaris NK Date: Wed, 17 Mar 2021 12:42:17 +0530 Subject: [PATCH 6/7] Update compound for new mapping --- contracts/connectors/compound/interface.sol | 8 +-- contracts/connectors/compound/main.sol | 71 +++++++++++---------- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/contracts/connectors/compound/interface.sol b/contracts/connectors/compound/interface.sol index cb89a11c..bc0e1036 100644 --- a/contracts/connectors/compound/interface.sol +++ b/contracts/connectors/compound/interface.sol @@ -32,13 +32,13 @@ interface ComptrollerInterface { interface CompoundMappingInterface { function cTokenMapping(string calldata tokenId) external view returns (address); + function getMapping(string calldata tokenId) external view returns (address, address); } struct LiquidateData { - address borrower; address tokenToPay; - string tokenPayId; address tokenInReturn; - string tokenReturnId; - uint amt; + address cTokenPay; + address cTokenColl; + CTokenInterface cTokenContract; } \ No newline at end of file diff --git a/contracts/connectors/compound/main.sol b/contracts/connectors/compound/main.sol index d4bfb967..93e63d5a 100644 --- a/contracts/connectors/compound/main.sol +++ b/contracts/connectors/compound/main.sol @@ -10,20 +10,19 @@ import { CETHInterface, CTokenInterface, LiquidateData } from "./interface.sol"; abstract contract CompoundResolver is Events, Helpers { /** * @dev Deposit ETH/ERC20_Token. - * @param token token address to deposit.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param tokenId token id of the token to deposit.(For eg: ETH-A) * @param amt token amount to deposit. * @param getId Get token amount at this ID from `InstaMemory` Contract. * @param setId Set token amount at this ID in `InstaMemory` Contract. */ function deposit( - address token, string calldata tokenId, uint amt, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint _amt = getUint(getId, amt); - address cToken = compMapping.cTokenMapping(tokenId); + (address token, address cToken) = compMapping.getMapping(tokenId); enterMarket(cToken); if (token == ethAddr) { _amt = _amt == uint(-1) ? address(this).balance : _amt; @@ -42,20 +41,19 @@ abstract contract CompoundResolver is Events, Helpers { /** * @dev Withdraw ETH/ERC20_Token. - * @param token token address to withdraw.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param tokenId token id of the token to withdraw.(For eg: ETH-A) * @param amt token amount to withdraw. * @param getId Get token amount at this ID from `InstaMemory` Contract. * @param setId Set token amount at this ID in `InstaMemory` Contract. */ function withdraw( - address token, string calldata tokenId, uint amt, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint _amt = getUint(getId, amt); - address cToken = compMapping.cTokenMapping(tokenId); + (address token, address cToken) = compMapping.getMapping(tokenId); CTokenInterface cTokenContract = CTokenInterface(cToken); if (_amt == uint(-1)) { TokenInterface tokenContract = TokenInterface(token); @@ -74,20 +72,19 @@ abstract contract CompoundResolver is Events, Helpers { /** * @dev Borrow ETH/ERC20_Token. - * @param token token address to borrow.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param tokenId token id of the token to borrow.(For eg: DAI-A) * @param amt token amount to borrow. * @param getId Get token amount at this ID from `InstaMemory` Contract. * @param setId Set token amount at this ID in `InstaMemory` Contract. */ function borrow( - address token, string calldata tokenId, uint amt, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint _amt = getUint(getId, amt); - address cToken = compMapping.cTokenMapping(tokenId); + (address token, address cToken) = compMapping.getMapping(tokenId); enterMarket(cToken); require(CTokenInterface(cToken).borrow(_amt) == 0, "borrow-failed"); setUint(setId, _amt); @@ -98,20 +95,19 @@ abstract contract CompoundResolver is Events, Helpers { /** * @dev Payback borrowed ETH/ERC20_Token. - * @param token token address to payback.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param tokenId token id of the token to payback.(For eg: COMP-A) * @param amt token amount to payback. * @param getId Get token amount at this ID from `InstaMemory` Contract. * @param setId Set token amount at this ID in `InstaMemory` Contract. */ function payback( - address token, string calldata tokenId, uint amt, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint _amt = getUint(getId, amt); - address cToken = compMapping.cTokenMapping(tokenId); + (address token, address cToken) = compMapping.getMapping(tokenId); CTokenInterface cTokenContract = CTokenInterface(cToken); _amt = _amt == uint(-1) ? cTokenContract.borrowBalanceCurrent(address(this)) : _amt; @@ -132,20 +128,19 @@ abstract contract CompoundResolver is Events, Helpers { /** * @dev Deposit ETH/ERC20_Token. - * @param token token address to depositCToken.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param tokenId token id of the token to depositCToken.(For eg: DAI-A) * @param amt token amount to depositCToken. * @param getId Get token amount at this ID from `InstaMemory` Contract. * @param setId Set ctoken amount at this ID in `InstaMemory` Contract. */ function depositCToken( - address token, string calldata tokenId, uint amt, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint _amt = getUint(getId, amt); - address cToken = compMapping.cTokenMapping(tokenId); + (address token, address cToken) = compMapping.getMapping(tokenId); enterMarket(cToken); CTokenInterface ctokenContract = CTokenInterface(cToken); @@ -161,9 +156,13 @@ abstract contract CompoundResolver is Events, Helpers { require(ctokenContract.mint(_amt) == 0, "deposit-ctoken-failed."); } - uint finalBal = ctokenContract.balanceOf(address(this)); - uint _cAmt = finalBal - initialBal; - setUint(setId, _cAmt); + uint _cAmt; + + { + uint finalBal = ctokenContract.balanceOf(address(this)); + finalBal - initialBal; + setUint(setId, _cAmt); + } _eventName = "LogDepositCToken(address,string,address,uint256,uint256,uint256,uint256)"; _eventParam = abi.encode(token, tokenId, cToken, _amt, _cAmt, getId, setId); @@ -171,20 +170,19 @@ abstract contract CompoundResolver is Events, Helpers { /** * @dev Withdraw CETH/CERC20_Token using cToken Amt. - * @param token token address to withdraw CToken.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param tokenId token id of the token to withdraw CToken.(For eg: ETH-A) * @param cTokenAmt ctoken amount to withdrawCToken. * @param getId Get ctoken amount at this ID from `InstaMemory` Contract. * @param setId Set token amount at this ID in `InstaMemory` Contract. */ function withdrawCToken( - address token, string calldata tokenId, uint cTokenAmt, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { uint _cAmt = getUint(getId, cTokenAmt); - address cToken = compMapping.cTokenMapping(tokenId); + (address token, address cToken) = compMapping.getMapping(tokenId); CTokenInterface cTokenContract = CTokenInterface(cToken); TokenInterface tokenContract = TokenInterface(token); _cAmt = _cAmt == uint(-1) ? cTokenContract.balanceOf(address(this)) : _cAmt; @@ -206,34 +204,43 @@ abstract contract CompoundResolver is Events, Helpers { /** * @dev Liquidate a position. - * @param data Liquidation data + * @param borrower Borrower's Address. + * @param tokenIdToPay token id of the token to pay for liquidation.(For eg: ETH-A) + * @param tokenIdInReturn token id of the token to return for liquidation.(For eg: USDC-A) + * @param amt token amount to pay for liquidation. * @param getId Get token amount at this ID from `InstaMemory` Contract. * @param setId Set token amount at this ID in `InstaMemory` Contract. */ function liquidate( - LiquidateData calldata data, + address borrower, + string calldata tokenIdToPay, + string calldata tokenIdInReturn, + uint amt, uint getId, uint setId ) external payable returns (string memory _eventName, bytes memory _eventParam) { - uint _amt = getUint(getId, data.amt); - address cTokenPay = compMapping.cTokenMapping(data.tokenPayId); - address cTokenColl = compMapping.cTokenMapping(data.tokenReturnId); - CTokenInterface cTokenContract = CTokenInterface(cTokenPay); + uint _amt = getUint(getId, amt); + + LiquidateData memory data; + + (data.tokenToPay, data.cTokenPay) = compMapping.getMapping(tokenIdToPay); + (data.tokenInReturn, data.cTokenColl) = compMapping.getMapping(tokenIdInReturn); + data.cTokenContract = CTokenInterface(data.cTokenPay); { - (,, uint shortfal) = troller.getAccountLiquidity(data.borrower); + (,, uint shortfal) = troller.getAccountLiquidity(borrower); require(shortfal != 0, "account-cannot-be-liquidated"); + _amt = _amt == uint(-1) ? data.cTokenContract.borrowBalanceCurrent(borrower) : _amt; } - _amt = _amt == uint(-1) ? cTokenContract.borrowBalanceCurrent(data.borrower) : _amt; if (data.tokenToPay == ethAddr) { require(address(this).balance >= _amt, "not-enought-eth"); - CETHInterface(cTokenPay).liquidateBorrow{value: _amt}(data.borrower, cTokenColl); + CETHInterface(data.cTokenPay).liquidateBorrow{value: _amt}(borrower, data.cTokenColl); } else { TokenInterface tokenContract = TokenInterface(data.tokenToPay); require(tokenContract.balanceOf(address(this)) >= _amt, "not-enough-token"); - tokenContract.approve(cTokenPay, _amt); - require(cTokenContract.liquidateBorrow(data.borrower, _amt, cTokenColl) == 0, "liquidate-failed"); + tokenContract.approve(data.cTokenPay, _amt); + require(data.cTokenContract.liquidateBorrow(borrower, _amt, data.cTokenColl) == 0, "liquidate-failed"); } setUint(setId, _amt); From 4d1a20f144b916b401160fb88b2813be300303bc Mon Sep 17 00:00:00 2001 From: Mubaris NK Date: Thu, 18 Mar 2021 10:36:35 +0530 Subject: [PATCH 7/7] Update mapping --- contracts/mapping/compound.sol | 55 ++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/contracts/mapping/compound.sol b/contracts/mapping/compound.sol index 43fd3b7a..8b7338c8 100644 --- a/contracts/mapping/compound.sol +++ b/contracts/mapping/compound.sol @@ -11,10 +11,16 @@ interface ConnectorsInterface { interface CTokenInterface { function isCToken() external view returns (bool); + function underlying() external view returns (address); } abstract contract Helpers { + struct TokenMap { + address ctoken; + address token; + } + event LogCTokensAdded(string[] names, address[] tokens, address[] ctokens); event LogCTokensUpdated(string[] names, address[] tokens, address[] ctokens); @@ -23,8 +29,9 @@ abstract contract Helpers { // InstaIndex Address. IndexInterface public constant instaIndex = IndexInterface(0x2971AdFa57b20E5a416aE5a708A8655A9c74f723); - mapping (string => address) public cTokenMapping; - mapping (string => address) public tokenMapping; + address public constant ethAddr = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + + mapping (string => TokenMap) public cTokenMapping; modifier isChief { require(msg.sender == instaIndex.master() || connectors.chief(msg.sender), "not-an-chief"); @@ -44,16 +51,25 @@ abstract contract Helpers { require(_names.length == _ctokens.length, "addCtokenMapping: not same length"); for (uint i = 0; i < _ctokens.length; i++) { - require(tokenMapping[_names[i]] == address(0), "addCtokenMapping: mapping added already"); - require(cTokenMapping[_names[i]] == address(0), "addCtokenMapping: mapping added already"); + TokenMap memory _data = cTokenMapping[_names[i]]; + + require(_data.ctoken == address(0), "addCtokenMapping: mapping added already"); + require(_data.token == address(0), "addCtokenMapping: mapping added already"); require(_tokens[i] != address(0), "addCtokenMapping: _tokens address not vaild"); require(_ctokens[i] != address(0), "addCtokenMapping: _ctokens address not vaild"); - require(CTokenInterface(_ctokens[i]).isCToken(), "addCtokenMapping: not a cToken"); + CTokenInterface _ctokenContract = CTokenInterface(_ctokens[i]); - tokenMapping[_names[i]] = _tokens[i]; - cTokenMapping[_names[i]] = _ctokens[i]; + require(_ctokenContract.isCToken(), "addCtokenMapping: not a cToken"); + if (_tokens[i] != ethAddr) { + require(_ctokenContract.underlying() == _tokens[i], "addCtokenMapping: mapping mismatch"); + } + + cTokenMapping[_names[i]] = TokenMap( + _ctokens[i], + _tokens[i] + ); } emit LogCTokensAdded(_names, _tokens, _ctokens); } @@ -69,16 +85,25 @@ abstract contract Helpers { require(_names.length == _ctokens.length, "updateCtokenMapping: not same length"); for (uint i = 0; i < _ctokens.length; i++) { - require(tokenMapping[_names[i]] != address(0), "updateCtokenMapping: mapping does not exist"); - require(cTokenMapping[_names[i]] != address(0), "updateCtokenMapping: mapping does not exist"); + TokenMap memory _data = cTokenMapping[_names[i]]; + + require(_data.ctoken != address(0), "updateCtokenMapping: mapping does not exist"); + require(_data.token != address(0), "updateCtokenMapping: mapping does not exist"); require(_tokens[i] != address(0), "updateCtokenMapping: _tokens address not vaild"); require(_ctokens[i] != address(0), "updateCtokenMapping: _ctokens address not vaild"); - require(CTokenInterface(_ctokens[i]).isCToken(), "updateCtokenMapping: not a cToken"); + CTokenInterface _ctokenContract = CTokenInterface(_ctokens[i]); - tokenMapping[_names[i]] = _tokens[i]; - cTokenMapping[_names[i]] = _ctokens[i]; + require(_ctokenContract.isCToken(), "updateCtokenMapping: not a cToken"); + if (_tokens[i] != ethAddr) { + require(_ctokenContract.underlying() == _tokens[i], "addCtokenMapping: mapping mismatch"); + } + + cTokenMapping[_names[i]] = TokenMap( + _ctokens[i], + _tokens[i] + ); } emit LogCTokensUpdated(_names, _tokens, _ctokens); } @@ -91,9 +116,9 @@ abstract contract Helpers { _addCtokenMapping(_names, _tokens, _ctokens); } - function getMapping(string memory _tokenId) external view returns (address _token, address _ctoken) { - _token = tokenMapping[_tokenId]; - _ctoken = cTokenMapping[_tokenId]; + function getMapping(string memory _tokenId) external view returns (address, address) { + TokenMap memory _data = cTokenMapping[_tokenId]; + return (_data.token, _data.ctoken); } }