diff --git a/contracts/arbitrum/common/interfaces.sol b/contracts/arbitrum/common/interfaces.sol index 5fbd1b6d..0a0c2c58 100644 --- a/contracts/arbitrum/common/interfaces.sol +++ b/contracts/arbitrum/common/interfaces.sol @@ -26,6 +26,15 @@ interface AccountInterface { function enable(address) external; function disable(address) external; function isAuth(address) external view returns (bool); + function cast( + string[] calldata _targetNames, + bytes[] calldata _datas, + address _origin + ) external payable returns (bytes32[] memory responses); +} + +interface ListInterface { + function accountID(address) external returns (uint64); } interface InstaConnectors { diff --git a/contracts/arbitrum/common/stores.sol b/contracts/arbitrum/common/stores.sol index dce4a455..01d5ccd3 100644 --- a/contracts/arbitrum/common/stores.sol +++ b/contracts/arbitrum/common/stores.sol @@ -1,7 +1,7 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.7.0; -import { MemoryInterface, InstaMapping } from "./interfaces.sol"; +import { MemoryInterface, InstaMapping, ListInterface, InstaConnectors } from "./interfaces.sol"; abstract contract Stores { @@ -21,6 +21,16 @@ abstract contract Stores { */ MemoryInterface constant internal instaMemory = MemoryInterface(0xc109f7Ef06152c3a63dc7254fD861E612d3Ac571); + /** + * @dev Return InstaList address + */ + ListInterface internal constant instaList = ListInterface(0x3565F6057b7fFE36984779A507fC87b31EFb0f09); + + /** + * @dev Return connectors registry address + */ + InstaConnectors internal constant instaConnectors = InstaConnectors(0x67fCE99Dd6d8d659eea2a1ac1b8881c57eb6592B); + /** * @dev Get Uint value from InstaMemory Contract. */ diff --git a/contracts/arbitrum/connectors/dsa-spell/events.sol b/contracts/arbitrum/connectors/dsa-spell/events.sol new file mode 100644 index 00000000..097aeec9 --- /dev/null +++ b/contracts/arbitrum/connectors/dsa-spell/events.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma abicoder v2; + +contract Events { + event LogCastOnDSA( + address indexed targetDSA, + string[] connectors, + bytes[] datas + ); + event LogCastAny( + string[] connectors, + string connectorName, + string eventName, + bytes eventParam + ); +} diff --git a/contracts/arbitrum/connectors/dsa-spell/main.sol b/contracts/arbitrum/connectors/dsa-spell/main.sol new file mode 100644 index 00000000..87c5a655 --- /dev/null +++ b/contracts/arbitrum/connectors/dsa-spell/main.sol @@ -0,0 +1,91 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +/** + * @title DSA Spell. + * @dev Cast spells on DSA. + */ + +import { AccountInterface } from "../../common/interfaces.sol"; +import { Stores } from "../../common/stores.sol"; +import { Events } from "./events.sol"; + +abstract contract DSASpellsResolver is Events, Stores { + /** + *@dev Casts spells on a DSA, caller DSA should be an auth of the target DSA. Reverts if any spell failed. + *@notice Interact with a target DSA by casting spells on it. + *@param targetDSA target DSA to cast spells on. + *@param connectors Array of connector names (For example, ["1INCH-A", "BASIC-A"]). + *@param datas Array of connector calldatas (function selectors encoded with parameters). + */ + function castOnDSA( + address targetDSA, + string[] memory connectors, + bytes[] memory datas + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + require(instaList.accountID(targetDSA) != 0, "not-a-DSA"); + + AccountInterface(targetDSA).cast(connectors, datas, address(this)); + + _eventName = "LogCastOnDSA(address,string[],bytes[])"; + _eventParam = abi.encode(targetDSA, connectors, datas); + } + + /** + *@dev Casts spell on caller DSA. Stops casting further spells as soon as a spell gets casted successfully. + * Reverts if none of the spells is successful. + *@notice Casts the first successful spell on the DSA. + *@param connectors Array of connector names, in preference order, if any (For example, ["1INCH-A", "ZEROX-A"]). + *@param datas Array of connector calldatas (function selectors encoded with parameters). + */ + function castAny(string[] memory connectors, bytes[] memory datas) + external + payable + returns (string memory eventName, bytes memory eventParam) + { + uint256 _length = connectors.length; + require(_length > 0, "zero-length-not-allowed"); + require(datas.length == _length, "calldata-length-invalid"); + + (bool isOk, address[] memory _connectors) = instaConnectors + .isConnectors(connectors); + require(isOk, "connector-names-invalid"); + + string memory _connectorName; + string memory _eventName; + bytes memory _eventParam; + bytes memory returnData; + bool success; + + for (uint256 i = 0; i < _length; i++) { + (success, returnData) = _connectors[i].delegatecall(datas[i]); + + if (success) { + _connectorName = connectors[i]; + (_eventName, _eventParam) = abi.decode( + returnData, + (string, bytes) + ); + break; + } + } + require(success, "dsa-spells-failed"); + + eventName = "LogCastAny(string[],string,string,bytes)"; + eventParam = abi.encode( + connectors, + _connectorName, + _eventName, + _eventParam + ); + } +} + +contract ConnectV2DSASpellArbitrum is DSASpellsResolver { + string public name = "DSA-Spell-v1.0"; +} diff --git a/contracts/avalanche/common/interfaces.sol b/contracts/avalanche/common/interfaces.sol index 912e57b1..11186b6b 100644 --- a/contracts/avalanche/common/interfaces.sol +++ b/contracts/avalanche/common/interfaces.sol @@ -21,6 +21,15 @@ interface AccountInterface { function enable(address) external; function disable(address) external; function isAuth(address) external view returns (bool); + function cast( + string[] calldata _targetNames, + bytes[] calldata _datas, + address _origin + ) external payable returns (bytes32[] memory responses); +} + +interface ListInterface { + function accountID(address) external returns (uint64); } interface InstaConnectors { diff --git a/contracts/avalanche/common/stores.sol b/contracts/avalanche/common/stores.sol index 97f51614..2e84c065 100644 --- a/contracts/avalanche/common/stores.sol +++ b/contracts/avalanche/common/stores.sol @@ -1,7 +1,7 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.7.0; -import { MemoryInterface } from "./interfaces.sol"; +import { MemoryInterface, ListInterface, InstaConnectors } from "./interfaces.sol"; abstract contract Stores { @@ -21,6 +21,16 @@ abstract contract Stores { */ MemoryInterface constant internal instaMemory = MemoryInterface(0x3254Ce8f5b1c82431B8f21Df01918342215825C2); + /** + * @dev Return InstaList address + */ + ListInterface internal constant instaList = ListInterface(0x9926955e0Dd681Dc303370C52f4Ad0a4dd061687); + + /** + * @dev Return connectors registry address + */ + InstaConnectors internal constant instaConnectors = InstaConnectors(0x127d8cD0E2b2E0366D522DeA53A787bfE9002C14); + /** * @dev Get Uint value from InstaMemory Contract. */ diff --git a/contracts/avalanche/connectors/dsa-spell/events.sol b/contracts/avalanche/connectors/dsa-spell/events.sol new file mode 100644 index 00000000..097aeec9 --- /dev/null +++ b/contracts/avalanche/connectors/dsa-spell/events.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma abicoder v2; + +contract Events { + event LogCastOnDSA( + address indexed targetDSA, + string[] connectors, + bytes[] datas + ); + event LogCastAny( + string[] connectors, + string connectorName, + string eventName, + bytes eventParam + ); +} diff --git a/contracts/avalanche/connectors/dsa-spell/main.sol b/contracts/avalanche/connectors/dsa-spell/main.sol new file mode 100644 index 00000000..f2ca0f1c --- /dev/null +++ b/contracts/avalanche/connectors/dsa-spell/main.sol @@ -0,0 +1,91 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +/** + * @title DSA Spell. + * @dev Cast spells on DSA. + */ + +import { AccountInterface } from "../../common/interfaces.sol"; +import { Stores } from "../../common/stores.sol"; +import { Events } from "./events.sol"; + +abstract contract DSASpellsResolver is Events, Stores { + /** + *@dev Casts spells on a DSA, caller DSA should be an auth of the target DSA. Reverts if any spell failed. + *@notice Interact with a target DSA by casting spells on it. + *@param targetDSA target DSA to cast spells on. + *@param connectors Array of connector names (For example, ["1INCH-A", "BASIC-A"]). + *@param datas Array of connector calldatas (function selectors encoded with parameters). + */ + function castOnDSA( + address targetDSA, + string[] memory connectors, + bytes[] memory datas + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + require(instaList.accountID(targetDSA) != 0, "not-a-DSA"); + + AccountInterface(targetDSA).cast(connectors, datas, address(this)); + + _eventName = "LogCastOnDSA(address,string[],bytes[])"; + _eventParam = abi.encode(targetDSA, connectors, datas); + } + + /** + *@dev Casts spell on caller DSA. Stops casting further spells as soon as a spell gets casted successfully. + * Reverts if none of the spells is successful. + *@notice Casts the first successful spell on the DSA. + *@param connectors Array of connector names, in preference order, if any (For example, ["1INCH-A", "ZEROX-A"]). + *@param datas Array of connector calldatas (function selectors encoded with parameters). + */ + function castAny(string[] memory connectors, bytes[] memory datas) + external + payable + returns (string memory eventName, bytes memory eventParam) + { + uint256 _length = connectors.length; + require(_length > 0, "zero-length-not-allowed"); + require(datas.length == _length, "calldata-length-invalid"); + + (bool isOk, address[] memory _connectors) = instaConnectors + .isConnectors(connectors); + require(isOk, "connector-names-invalid"); + + string memory _connectorName; + string memory _eventName; + bytes memory _eventParam; + bytes memory returnData; + bool success; + + for (uint256 i = 0; i < _length; i++) { + (success, returnData) = _connectors[i].delegatecall(datas[i]); + + if (success) { + _connectorName = connectors[i]; + (_eventName, _eventParam) = abi.decode( + returnData, + (string, bytes) + ); + break; + } + } + require(success, "dsa-spells-failed"); + + eventName = "LogCastAny(string[],string,string,bytes)"; + eventParam = abi.encode( + connectors, + _connectorName, + _eventName, + _eventParam + ); + } +} + +contract ConnectV2DSASpellAvalanche is DSASpellsResolver { + string public name = "DSA-Spell-v1.0"; +} diff --git a/contracts/fantom/common/interfaces.sol b/contracts/fantom/common/interfaces.sol index cf1abfee..b228ebe8 100644 --- a/contracts/fantom/common/interfaces.sol +++ b/contracts/fantom/common/interfaces.sol @@ -34,6 +34,16 @@ interface AccountInterface { function disable(address) external; function isAuth(address) external view returns (bool); + + function cast( + string[] calldata _targetNames, + bytes[] calldata _datas, + address _origin + ) external payable returns (bytes32[] memory responses); +} + +interface ListInterface { + function accountID(address) external returns (uint64); } interface InstaConnectors { diff --git a/contracts/fantom/common/stores.sol b/contracts/fantom/common/stores.sol index 0e16eb06..39c2c8ae 100644 --- a/contracts/fantom/common/stores.sol +++ b/contracts/fantom/common/stores.sol @@ -1,7 +1,7 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.7.0; -import { MemoryInterface } from "./interfaces.sol"; +import { MemoryInterface, ListInterface, InstaConnectors } from "./interfaces.sol"; abstract contract Stores { /** @@ -22,6 +22,16 @@ abstract contract Stores { MemoryInterface internal constant instaMemory = MemoryInterface(0x56439117379A53bE3CC2C55217251e2481B7a1C8); + /** + * @dev Return InstaList address + */ + ListInterface internal constant instaList = ListInterface(0x10e166c3FAF887D8a61dE6c25039231eE694E926); + + /** + * @dev Return connectors registry address + */ + InstaConnectors internal constant instaConnectors = InstaConnectors(0x819910794a030403F69247E1e5C0bBfF1593B968); + /** * @dev Get Uint value from InstaMemory Contract. */ diff --git a/contracts/fantom/connectors/dsa-spell/events.sol b/contracts/fantom/connectors/dsa-spell/events.sol new file mode 100644 index 00000000..097aeec9 --- /dev/null +++ b/contracts/fantom/connectors/dsa-spell/events.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma abicoder v2; + +contract Events { + event LogCastOnDSA( + address indexed targetDSA, + string[] connectors, + bytes[] datas + ); + event LogCastAny( + string[] connectors, + string connectorName, + string eventName, + bytes eventParam + ); +} diff --git a/contracts/fantom/connectors/dsa-spell/main.sol b/contracts/fantom/connectors/dsa-spell/main.sol new file mode 100644 index 00000000..78330f24 --- /dev/null +++ b/contracts/fantom/connectors/dsa-spell/main.sol @@ -0,0 +1,91 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +/** + * @title DSA Spell. + * @dev Cast spells on DSA. + */ + +import { AccountInterface } from "../../common/interfaces.sol"; +import { Stores } from "../../common/stores.sol"; +import { Events } from "./events.sol"; + +abstract contract DSASpellsResolver is Events, Stores { + /** + *@dev Casts spells on a DSA, caller DSA should be an auth of the target DSA. Reverts if any spell failed. + *@notice Interact with a target DSA by casting spells on it. + *@param targetDSA target DSA to cast spells on. + *@param connectors Array of connector names (For example, ["1INCH-A", "BASIC-A"]). + *@param datas Array of connector calldatas (function selectors encoded with parameters). + */ + function castOnDSA( + address targetDSA, + string[] memory connectors, + bytes[] memory datas + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + require(instaList.accountID(targetDSA) != 0, "not-a-DSA"); + + AccountInterface(targetDSA).cast(connectors, datas, address(this)); + + _eventName = "LogCastOnDSA(address,string[],bytes[])"; + _eventParam = abi.encode(targetDSA, connectors, datas); + } + + /** + *@dev Casts spell on caller DSA. Stops casting further spells as soon as a spell gets casted successfully. + * Reverts if none of the spells is successful. + *@notice Casts the first successful spell on the DSA. + *@param connectors Array of connector names, in preference order, if any (For example, ["1INCH-A", "ZEROX-A"]). + *@param datas Array of connector calldatas (function selectors encoded with parameters). + */ + function castAny(string[] memory connectors, bytes[] memory datas) + external + payable + returns (string memory eventName, bytes memory eventParam) + { + uint256 _length = connectors.length; + require(_length > 0, "zero-length-not-allowed"); + require(datas.length == _length, "calldata-length-invalid"); + + (bool isOk, address[] memory _connectors) = instaConnectors + .isConnectors(connectors); + require(isOk, "connector-names-invalid"); + + string memory _connectorName; + string memory _eventName; + bytes memory _eventParam; + bytes memory returnData; + bool success; + + for (uint256 i = 0; i < _length; i++) { + (success, returnData) = _connectors[i].delegatecall(datas[i]); + + if (success) { + _connectorName = connectors[i]; + (_eventName, _eventParam) = abi.decode( + returnData, + (string, bytes) + ); + break; + } + } + require(success, "dsa-spells-failed"); + + eventName = "LogCastAny(string[],string,string,bytes)"; + eventParam = abi.encode( + connectors, + _connectorName, + _eventName, + _eventParam + ); + } +} + +contract ConnectV2DSASpellFantom is DSASpellsResolver { + string public name = "DSA-Spell-v1.0"; +} diff --git a/contracts/mainnet/common/interfaces.sol b/contracts/mainnet/common/interfaces.sol index 29f60e4b..9b872418 100644 --- a/contracts/mainnet/common/interfaces.sol +++ b/contracts/mainnet/common/interfaces.sol @@ -27,6 +27,15 @@ interface AccountInterface { function enable(address) external; function disable(address) external; function isAuth(address) external view returns (bool); + function cast( + string[] calldata _targetNames, + bytes[] calldata _datas, + address _origin + ) external payable returns (bytes32[] memory responses); +} + +interface ListInterface { + function accountID(address) external returns (uint64); } interface InstaConnectors { diff --git a/contracts/mainnet/common/stores.sol b/contracts/mainnet/common/stores.sol index 25cb226f..8be01eb7 100644 --- a/contracts/mainnet/common/stores.sol +++ b/contracts/mainnet/common/stores.sol @@ -1,7 +1,7 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.7.0; -import { MemoryInterface, InstaMapping } from "./interfaces.sol"; +import { MemoryInterface, InstaMapping, ListInterface, InstaConnectors } from "./interfaces.sol"; abstract contract Stores { @@ -26,6 +26,16 @@ abstract contract Stores { */ InstaMapping constant internal instaMapping = InstaMapping(0xe81F70Cc7C0D46e12d70efc60607F16bbD617E88); + /** + * @dev Return InstaList Address + */ + ListInterface internal constant instaList = ListInterface(0x4c8a1BEb8a87765788946D6B19C6C6355194AbEb); + + /** + * @dev Return connectors registry address + */ + InstaConnectors internal constant instaConnectors = InstaConnectors(0x97b0B3A8bDeFE8cB9563a3c610019Ad10DB8aD11); + /** * @dev Get Uint value from InstaMemory Contract. */ diff --git a/contracts/mainnet/connectors/dsa-spell/events.sol b/contracts/mainnet/connectors/dsa-spell/events.sol new file mode 100644 index 00000000..097aeec9 --- /dev/null +++ b/contracts/mainnet/connectors/dsa-spell/events.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma abicoder v2; + +contract Events { + event LogCastOnDSA( + address indexed targetDSA, + string[] connectors, + bytes[] datas + ); + event LogCastAny( + string[] connectors, + string connectorName, + string eventName, + bytes eventParam + ); +} diff --git a/contracts/mainnet/connectors/dsa-spell/main.sol b/contracts/mainnet/connectors/dsa-spell/main.sol new file mode 100644 index 00000000..a4f51143 --- /dev/null +++ b/contracts/mainnet/connectors/dsa-spell/main.sol @@ -0,0 +1,91 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +/** + * @title DSA Spell. + * @dev Cast spells on DSA. + */ + +import { AccountInterface } from "../../common/interfaces.sol"; +import { Stores } from "../../common/stores.sol"; +import { Events } from "./events.sol"; + +abstract contract DSASpellsResolver is Events, Stores { + /** + *@dev Casts spells on a DSA, caller DSA should be an auth of the target DSA. Reverts if any spell failed. + *@notice Interact with a target DSA by casting spells on it. + *@param targetDSA target DSA to cast spells on. + *@param connectors Array of connector names (For example, ["1INCH-A", "BASIC-A"]). + *@param datas Array of connector calldatas (function selectors encoded with parameters). + */ + function castOnDSA( + address targetDSA, + string[] memory connectors, + bytes[] memory datas + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + require(instaList.accountID(targetDSA) != 0, "not-a-DSA"); + + AccountInterface(targetDSA).cast(connectors, datas, address(this)); + + _eventName = "LogCastOnDSA(address,string[],bytes[])"; + _eventParam = abi.encode(targetDSA, connectors, datas); + } + + /** + *@dev Casts spell on caller DSA. Stops casting further spells as soon as a spell gets casted successfully. + * Reverts if none of the spells is successful. + *@notice Casts the first successful spell on the DSA. + *@param connectors Array of connector names, in preference order, if any (For example, ["1INCH-A", "ZEROX-A"]). + *@param datas Array of connector calldatas (function selectors encoded with parameters). + */ + function castAny(string[] memory connectors, bytes[] memory datas) + external + payable + returns (string memory eventName, bytes memory eventParam) + { + uint256 _length = connectors.length; + require(_length > 0, "zero-length-not-allowed"); + require(datas.length == _length, "calldata-length-invalid"); + + (bool isOk, address[] memory _connectors) = instaConnectors + .isConnectors(connectors); + require(isOk, "connector-names-invalid"); + + string memory _connectorName; + string memory _eventName; + bytes memory _eventParam; + bytes memory returnData; + bool success; + + for (uint256 i = 0; i < _length; i++) { + (success, returnData) = _connectors[i].delegatecall(datas[i]); + + if (success) { + _connectorName = connectors[i]; + (_eventName, _eventParam) = abi.decode( + returnData, + (string, bytes) + ); + break; + } + } + require(success, "dsa-spells-failed"); + + eventName = "LogCastAny(string[],string,string,bytes)"; + eventParam = abi.encode( + connectors, + _connectorName, + _eventName, + _eventParam + ); + } +} + +contract ConnectV2DSASpell is DSASpellsResolver { + string public name = "DSA-Spell-v1.0"; +} diff --git a/contracts/optimism/common/interfaces.sol b/contracts/optimism/common/interfaces.sol index 797bce15..05fab373 100644 --- a/contracts/optimism/common/interfaces.sol +++ b/contracts/optimism/common/interfaces.sol @@ -23,6 +23,15 @@ interface AccountInterface { function enable(address) external; function disable(address) external; function isAuth(address) external view returns (bool); + function cast( + string[] calldata _targetNames, + bytes[] calldata _datas, + address _origin + ) external payable returns (bytes32[] memory responses); +} + +interface ListInterface { + function accountID(address) external returns (uint64); } interface InstaConnectors { diff --git a/contracts/optimism/common/stores.sol b/contracts/optimism/common/stores.sol index 09d3cfca..d13351bb 100644 --- a/contracts/optimism/common/stores.sol +++ b/contracts/optimism/common/stores.sol @@ -1,7 +1,7 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.7.0; -import { MemoryInterface } from "./interfaces.sol"; +import { MemoryInterface, ListInterface, InstaConnectors } from "./interfaces.sol"; abstract contract Stores { @@ -20,6 +20,16 @@ abstract contract Stores { */ MemoryInterface constant internal instaMemory = MemoryInterface(0x3254Ce8f5b1c82431B8f21Df01918342215825C2); + /** + * @dev Return InstaList address + */ + ListInterface internal constant instaList = ListInterface(0x9926955e0Dd681Dc303370C52f4Ad0a4dd061687); + + /** + * @dev Returns connectors registry address + */ + InstaConnectors internal constant instaConnectors = InstaConnectors(0x127d8cD0E2b2E0366D522DeA53A787bfE9002C14); + /** * @dev Get Uint value from InstaMemory Contract. */ diff --git a/contracts/optimism/connectors/dsa-spell/events.sol b/contracts/optimism/connectors/dsa-spell/events.sol new file mode 100644 index 00000000..097aeec9 --- /dev/null +++ b/contracts/optimism/connectors/dsa-spell/events.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma abicoder v2; + +contract Events { + event LogCastOnDSA( + address indexed targetDSA, + string[] connectors, + bytes[] datas + ); + event LogCastAny( + string[] connectors, + string connectorName, + string eventName, + bytes eventParam + ); +} diff --git a/contracts/optimism/connectors/dsa-spell/main.sol b/contracts/optimism/connectors/dsa-spell/main.sol new file mode 100644 index 00000000..ccc6bc82 --- /dev/null +++ b/contracts/optimism/connectors/dsa-spell/main.sol @@ -0,0 +1,91 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +/** + * @title DSA Spell. + * @dev Cast spells on DSA. + */ + +import { AccountInterface } from "../../common/interfaces.sol"; +import { Stores } from "../../common/stores.sol"; +import { Events } from "./events.sol"; + +abstract contract DSASpellsResolver is Events, Stores { + /** + *@dev Casts spells on a DSA, caller DSA should be an auth of the target DSA. Reverts if any spell failed. + *@notice Interact with a target DSA by casting spells on it. + *@param targetDSA target DSA to cast spells on. + *@param connectors Array of connector names (For example, ["1INCH-A", "BASIC-A"]). + *@param datas Array of connector calldatas (function selectors encoded with parameters). + */ + function castOnDSA( + address targetDSA, + string[] memory connectors, + bytes[] memory datas + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + require(instaList.accountID(targetDSA) != 0, "not-a-DSA"); + + AccountInterface(targetDSA).cast(connectors, datas, address(this)); + + _eventName = "LogCastOnDSA(address,string[],bytes[])"; + _eventParam = abi.encode(targetDSA, connectors, datas); + } + + /** + *@dev Casts spell on caller DSA. Stops casting further spells as soon as a spell gets casted successfully. + * Reverts if none of the spells is successful. + *@notice Casts the first successful spell on the DSA. + *@param connectors Array of connector names, in preference order, if any (For example, ["1INCH-A", "ZEROX-A"]). + *@param datas Array of connector calldatas (function selectors encoded with parameters). + */ + function castAny(string[] memory connectors, bytes[] memory datas) + external + payable + returns (string memory eventName, bytes memory eventParam) + { + uint256 _length = connectors.length; + require(_length > 0, "zero-length-not-allowed"); + require(datas.length == _length, "calldata-length-invalid"); + + (bool isOk, address[] memory _connectors) = instaConnectors + .isConnectors(connectors); + require(isOk, "connector-names-invalid"); + + string memory _connectorName; + string memory _eventName; + bytes memory _eventParam; + bytes memory returnData; + bool success; + + for (uint256 i = 0; i < _length; i++) { + (success, returnData) = _connectors[i].delegatecall(datas[i]); + + if (success) { + _connectorName = connectors[i]; + (_eventName, _eventParam) = abi.decode( + returnData, + (string, bytes) + ); + break; + } + } + require(success, "dsa-spells-failed"); + + eventName = "LogCastAny(string[],string,string,bytes)"; + eventParam = abi.encode( + connectors, + _connectorName, + _eventName, + _eventParam + ); + } +} + +contract ConnectV2DSASpellOptimism is DSASpellsResolver { + string public name = "DSA-Spell-v1.0"; +} diff --git a/contracts/polygon/common/interfaces.sol b/contracts/polygon/common/interfaces.sol index 94f054e1..5041400a 100644 --- a/contracts/polygon/common/interfaces.sol +++ b/contracts/polygon/common/interfaces.sol @@ -26,6 +26,15 @@ interface AccountInterface { function enable(address) external; function disable(address) external; function isAuth(address) external view returns (bool); + function cast( + string[] calldata _targetNames, + bytes[] calldata _datas, + address _origin + ) external payable returns (bytes32[] memory responses); +} + +interface ListInterface { + function accountID(address) external returns (uint64); } interface InstaConnectors { diff --git a/contracts/polygon/common/stores.sol b/contracts/polygon/common/stores.sol index e5e21f55..9231dcde 100644 --- a/contracts/polygon/common/stores.sol +++ b/contracts/polygon/common/stores.sol @@ -1,7 +1,7 @@ //SPDX-License-Identifier: MIT pragma solidity ^0.7.0; -import { MemoryInterface, InstaMapping } from "./interfaces.sol"; +import { MemoryInterface, InstaMapping, ListInterface, InstaConnectors } from "./interfaces.sol"; abstract contract Stores { @@ -21,6 +21,16 @@ abstract contract Stores { */ MemoryInterface constant internal instaMemory = MemoryInterface(0x6C7256cf7C003dD85683339F75DdE9971f98f2FD); + /** + * @dev Return InstaList address + */ + ListInterface internal constant instaList = ListInterface(0x839c2D3aDe63DF5b0b8F3E57D5e145057Ab41556); + + /** + * @dev Return connectors registry address + */ + InstaConnectors internal constant instaConnectors = InstaConnectors(0x2A00684bFAb9717C21271E0751BCcb7d2D763c88); + /** * @dev Get Uint value from InstaMemory Contract. */ diff --git a/contracts/polygon/connectors/dsa-spell/events.sol b/contracts/polygon/connectors/dsa-spell/events.sol new file mode 100644 index 00000000..097aeec9 --- /dev/null +++ b/contracts/polygon/connectors/dsa-spell/events.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma abicoder v2; + +contract Events { + event LogCastOnDSA( + address indexed targetDSA, + string[] connectors, + bytes[] datas + ); + event LogCastAny( + string[] connectors, + string connectorName, + string eventName, + bytes eventParam + ); +} diff --git a/contracts/polygon/connectors/dsa-spell/main.sol b/contracts/polygon/connectors/dsa-spell/main.sol new file mode 100644 index 00000000..26d9f316 --- /dev/null +++ b/contracts/polygon/connectors/dsa-spell/main.sol @@ -0,0 +1,91 @@ +//SPDX-License-Identifier: MIT +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +/** + * @title DSA Spell. + * @dev Cast spells on DSA. + */ + +import { AccountInterface } from "../../common/interfaces.sol"; +import { Stores } from "../../common/stores.sol"; +import { Events } from "./events.sol"; + +abstract contract DSASpellsResolver is Events, Stores { + /** + *@dev Casts spells on a DSA, caller DSA should be an auth of the target DSA. Reverts if any spell failed. + *@notice Interact with a target DSA by casting spells on it. + *@param targetDSA target DSA to cast spells on. + *@param connectors Array of connector names (For example, ["1INCH-A", "BASIC-A"]). + *@param datas Array of connector calldatas (function selectors encoded with parameters). + */ + function castOnDSA( + address targetDSA, + string[] memory connectors, + bytes[] memory datas + ) + external + payable + returns (string memory _eventName, bytes memory _eventParam) + { + require(instaList.accountID(targetDSA) != 0, "not-a-DSA"); + + AccountInterface(targetDSA).cast(connectors, datas, address(this)); + + _eventName = "LogCastOnDSA(address,string[],bytes[])"; + _eventParam = abi.encode(targetDSA, connectors, datas); + } + + /** + *@dev Casts spell on caller DSA. Stops casting further spells as soon as a spell gets casted successfully. + * Reverts if none of the spells is successful. + *@notice Casts the first successful spell on the DSA. + *@param connectors Array of connector names, in preference order, if any (For example, ["1INCH-A", "ZEROX-A"]). + *@param datas Array of connector calldatas (function selectors encoded with parameters). + */ + function castAny(string[] memory connectors, bytes[] memory datas) + external + payable + returns (string memory eventName, bytes memory eventParam) + { + uint256 _length = connectors.length; + require(_length > 0, "zero-length-not-allowed"); + require(datas.length == _length, "calldata-length-invalid"); + + (bool isOk, address[] memory _connectors) = instaConnectors + .isConnectors(connectors); + require(isOk, "connector-names-invalid"); + + string memory _connectorName; + string memory _eventName; + bytes memory _eventParam; + bytes memory returnData; + bool success; + + for (uint256 i = 0; i < _length; i++) { + (success, returnData) = _connectors[i].delegatecall(datas[i]); + + if (success) { + _connectorName = connectors[i]; + (_eventName, _eventParam) = abi.decode( + returnData, + (string, bytes) + ); + break; + } + } + require(success, "dsa-spells-failed"); + + eventName = "LogCastAny(string[],string,string,bytes)"; + eventParam = abi.encode( + connectors, + _connectorName, + _eventName, + _eventParam + ); + } +} + +contract ConnectV2DSASpellPolygon is DSASpellsResolver { + string public name = "DSA-Spell-v1.0"; +} diff --git a/test/arbitrum/dsa-spell/dsa-spell.test.ts b/test/arbitrum/dsa-spell/dsa-spell.test.ts new file mode 100644 index 00000000..6ae968c5 --- /dev/null +++ b/test/arbitrum/dsa-spell/dsa-spell.test.ts @@ -0,0 +1,180 @@ +import hre from "hardhat"; +import axios from "axios"; +import { expect } from "chai"; +const { ethers } = hre; //check +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { addresses } from "../../../scripts/tests/arbitrum/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import { ConnectV2DSASpellArbitrum__factory } from "../../../typechain"; +import { Signer, Contract } from "ethers"; +import { BigNumber } from "bignumber.js"; + +describe("DSA Spell", function () { + const connectorName = "dsa-spell-test"; + + let dsaWallet0: any; + let dsaWallet1: any; + let dsaWallet2: any; + let walletB: any; + let wallet0: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: any; + + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url + } + } + ] + }); + [wallet0] = await ethers.getSigners(); + + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2DSASpellArbitrum__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + console.log("\tConnector address", connector.address); + }); + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + walletB = await ethers.getSigner(dsaWallet0.address); + dsaWallet1 = await buildDSAv2(dsaWallet0.address); + expect(!!dsaWallet1.address).to.be.true; + console.log(`\t${dsaWallet1.address}`); + }); + + it("Deposit eth into DSA wallet 0", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + + it("Deposit eth into DSA wallet 1", async function () { + await wallet0.sendTransaction({ + to: dsaWallet1.address, + value: ethers.utils.parseEther("10") + }); + + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + }); + + describe("Main", function () { + let ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; + let USDC = "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8"; + let usdc = new ethers.Contract(USDC, abis.basic.erc20); + let aETH = "0xe50fA9b3c56FfB159cB0FCA61F5c9D750e8128c8"; + let aEth = new ethers.Contract(aETH, abis.basic.aToken); + var abi = [ + "function withdraw(address,uint256,address,uint256,uint256)", + "function deposit(address,uint256,uint256,uint256)", + "function borrow(address,uint256,uint256,uint256,uint256)" + ]; + function getCallData(spell: string, params: any) { + var iface = new ethers.utils.Interface(abi); + let data = iface.encodeFunctionData(spell, params); + return ethers.utils.hexlify(data); + } + + it("should cast spells", async function () { + async function getArg(connectors: any, spells: any, params: any) { + let datas = []; + for (let i = 0; i < connectors.length; i++) { + datas.push(getCallData(spells[i], params[i])); + } + return [dsaWallet1.address, connectors, datas]; + } + + let connectors = ["BASIC-A", "AAVE-V3-A", "AAVE-V3-A"]; + let methods = ["withdraw", "deposit", "borrow"]; + let params = [ + [ETH, ethers.utils.parseEther("2"), dsaWallet0.address, 0, 0], + [ETH, ethers.constants.MaxUint256, 0, 0], + [USDC, ethers.utils.parseUnits("1", 6), 2, 0, 0] + ]; + let arg = await getArg(connectors, methods, params); + + const spells = [ + { + connector: connectorName, + method: "castOnDSA", + args: arg + } + ]; + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), await wallet0.getAddress()); + const receipt = await tx.wait(); + }); + + it("should check balances after cast on DSA", async function () { + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.lte(0); + expect(await usdc.connect(wallet0).balanceOf(dsaWallet1.address)).to.be.gte( + new BigNumber(1).multipliedBy(1e6).toString() + ); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte( + new BigNumber(12).multipliedBy(1e18).toString() + ); + }); + + it("should cast spell on the first successful", async function () { + async function getArg(connectors: any, spells: any, params: any) { + let datas = []; + for (let i = 0; i < connectors.length; i++) { + datas.push(getCallData(spells[i], params[i])); + } + return [connectors, datas]; + } + + let connectors = ["AAVE-V3-A"]; + let methods = ["deposit"]; + let params = [ + [ETH, ethers.utils.parseEther("10"), 0, 0] + ]; + let arg = await getArg(connectors, methods, params); + const spells = [ + { + connector: connectorName, + method: "castAny", + args: arg + } + ]; + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), await wallet0.getAddress()); + const receipt = await tx.wait(); + }); + + it("should check balances after spells on DSA", async function () { + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte( + new BigNumber(2).multipliedBy(1e18).toString() + ); + expect(await aEth.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.gte( + new BigNumber(10).multipliedBy(1e18).toString() + ); + }); + }); +}); diff --git a/test/avalanche/dsa-spell/dsa-spell.test.ts b/test/avalanche/dsa-spell/dsa-spell.test.ts new file mode 100644 index 00000000..e5430cd2 --- /dev/null +++ b/test/avalanche/dsa-spell/dsa-spell.test.ts @@ -0,0 +1,185 @@ +import hre from "hardhat"; +import axios from "axios"; +import { expect } from "chai"; +const { ethers } = hre; //check +import { BigNumber } from "bignumber.js"; +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { addresses } from "../../../scripts/tests/avalanche/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import { ConnectV2DSASpellAvalanche__factory } from "../../../typechain"; +import er20abi from "../../../scripts/constant/abi/basics/erc20.json"; +import type { Signer, Contract } from "ethers"; + +describe("DSA Spell", function () { + const connectorName = "dsa-spell-test"; + + let dsaWallet0: any; + let dsaWallet1: any; + let dsaWallet2: any; + let walletB: any; + let wallet0: any; + let walletBsigner: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: any; + + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url + } + } + ] + }); + [wallet0] = await ethers.getSigners(); + + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2DSASpellAvalanche__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + console.log("\tConnector address", connector.address); + }); + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + walletB = await ethers.getSigner(dsaWallet0.address); + dsaWallet1 = await buildDSAv2(dsaWallet0.address); + expect(!!dsaWallet1.address).to.be.true; + console.log(`\t${dsaWallet1.address}`); + dsaWallet2 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet2.address).to.be.true; + }); + + it("Deposit avax into DSA wallet 0", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + + it("Deposit avax into DSA wallet 1", async function () { + await wallet0.sendTransaction({ + to: dsaWallet1.address, + value: ethers.utils.parseEther("10") + }); + + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + }); + + describe("Main", function () { + let AVAX = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; + let USDC = "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E"; + let usdc = new ethers.Contract(USDC, abis.basic.erc20); + let aAVAX = "0x6d80113e533a2C0fe82EaBD35f1875DcEA89Ea97"; + let aAvax = new ethers.Contract(aAVAX, abis.basic.aToken); + var abi = [ + "function withdraw(address,uint256,address,uint256,uint256)", + "function deposit(address,uint256,uint256,uint256)", + "function borrow(address,uint256,uint256,uint256,uint256)" + ]; + function getCallData(spell: string, params: any) { + var iface = new ethers.utils.Interface(abi); + let data = iface.encodeFunctionData(spell, params); + return ethers.utils.hexlify(data); + } + + it("should cast spells", async function () { + async function getArg(connectors: any, spells: any, params: any) { + let datas = []; + for (let i = 0; i < connectors.length; i++) { + datas.push(getCallData(spells[i], params[i])); + } + return [dsaWallet1.address, connectors, datas]; + } + + let connectors = ["BASIC-A", "AAVE-V3-A", "AAVE-V3-A"]; + let methods = ["withdraw", "deposit", "borrow"]; + let params = [ + [AVAX, ethers.utils.parseEther("2"), dsaWallet0.address, 0, 0], + [AVAX, ethers.constants.MaxUint256, 0, 0], + [USDC, ethers.utils.parseUnits("1", 6), 2, 0, 0] + ]; + let arg = await getArg(connectors, methods, params); + + const spells = [ + { + connector: connectorName, + method: "castOnDSA", + args: arg + } + ]; + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), await wallet0.getAddress()); + const receipt = await tx.wait(); + }); + + it("should check balances after cast on DSA", async function () { + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.lte(0); + expect(await usdc.connect(wallet0).balanceOf(dsaWallet1.address)).to.be.gte( + new BigNumber(1).multipliedBy(1e6).toString() + ); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte( + new BigNumber(12).multipliedBy(1e18).toString() + ); + }); + + it("should cast spell on the first successful", async function () { + async function getArg(connectors: any, spells: any, params: any) { + let datas = []; + for (let i = 0; i < connectors.length; i++) { + datas.push(getCallData(spells[i], params[i])); + } + return [connectors, datas]; + } + + let connectors = ["AAVE-V3-A", "AAVE-V2-A"]; + let methods = ["deposit","deposit"]; + let params = [ + [AVAX, ethers.utils.parseEther("10"), 0, 0], + [AVAX, ethers.utils.parseEther("10"), 0, 0] + ]; + let arg = await getArg(connectors, methods, params); + const spells = [ + { + connector: connectorName, + method: "castAny", + args: arg + } + ]; + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), await wallet0.getAddress()); + const receipt = await tx.wait(); + }); + + it("should check balances after spells on DSA", async function () { + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte( + new BigNumber(2).multipliedBy(1e18).toString() + ); + expect(await aAvax.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.gte( + new BigNumber(10).multipliedBy(1e18).toString() + ); + }); + }); +}); diff --git a/test/fantom/dsa-spell/dsa-spell.test.ts b/test/fantom/dsa-spell/dsa-spell.test.ts new file mode 100644 index 00000000..5a1f0c88 --- /dev/null +++ b/test/fantom/dsa-spell/dsa-spell.test.ts @@ -0,0 +1,182 @@ +import hre from "hardhat"; +import axios from "axios"; +import { expect } from "chai"; +const { ethers } = hre; //check +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { addresses } from "../../../scripts/tests/fantom/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import { ConnectV2DSASpellFantom__factory } from "../../../typechain"; +import type { Signer, Contract } from "ethers"; +import BigNumber from "bignumber.js"; + +describe("DSA Spell", function () { + const connectorName = "dsa-spell-test"; + + let dsaWallet0: any; + let dsaWallet1: any; + let dsaWallet2: any; + let walletB: any; + let wallet0: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: any; + + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url + } + } + ] + }); + [wallet0] = await ethers.getSigners(); + + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2DSASpellFantom__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + console.log("\tConnector address", connector.address); + }); + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + walletB = await ethers.getSigner(dsaWallet0.address); + dsaWallet1 = await buildDSAv2(dsaWallet0.address); + expect(!!dsaWallet1.address).to.be.true; + console.log(`\t${dsaWallet1.address}`); + dsaWallet2 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet2.address).to.be.true; + }); + + it("Deposit ftm into DSA wallet 0", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + + it("Deposit ftm into DSA wallet 1", async function () { + await wallet0.sendTransaction({ + to: dsaWallet1.address, + value: ethers.utils.parseEther("10") + }); + + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + }); + + describe("Main", function () { + let FTM = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; + let aFTM = "0x6d80113e533a2C0fe82EaBD35f1875DcEA89Ea97"; + let USDC = "0x04068DA6C83AFCFA0e13ba15A6696662335D5B75"; + let usdc = new ethers.Contract(USDC, abis.basic.erc20); + let aFtm = new ethers.Contract(aFTM, abis.basic.aToken); + var abi = [ + "function withdraw(address,uint256,address,uint256,uint256)", + "function deposit(address,uint256,uint256,uint256)", + "function borrow(address,uint256,uint256,uint256,uint256)" + ]; + function getCallData(spell: string, params: any) { + var iface = new ethers.utils.Interface(abi); + let data = iface.encodeFunctionData(spell, params); + return ethers.utils.hexlify(data); + } + + it("should cast spells", async function () { + async function getArg(connectors: any, spells: any, params: any) { + let datas = []; + for (let i = 0; i < connectors.length; i++) { + datas.push(getCallData(spells[i], params[i])); + } + return [dsaWallet1.address, connectors, datas]; + } + + let connectors = ["BASIC-A", "AAVE-V3-A", "AAVE-V3-A"]; + let methods = ["withdraw", "deposit", "borrow"]; + let params = [ + [FTM, ethers.utils.parseEther("2"), dsaWallet0.address, 0, 0], + [FTM, ethers.constants.MaxUint256, 0, 0], + [USDC, ethers.utils.parseUnits("1", 5), 2, 0, 0] + ]; + let arg = await getArg(connectors, methods, params); + + const spells = [ + { + connector: connectorName, + method: "castOnDSA", + args: arg + } + ]; + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), await wallet0.getAddress()); + const receipt = await tx.wait(); + }); + + it("should check balances after cast on DSA", async function () { + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.lte(0); + expect(await usdc.connect(wallet0).balanceOf(dsaWallet1.address)).to.be.gte( + new BigNumber(1).multipliedBy(1e5).toString() + ); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte( + new BigNumber(12).multipliedBy(1e18).toString() + ); + }); + + it("should cast spell on the first successful", async function () { + async function getArg(connectors: any, spells: any, params: any) { + let datas = []; + for (let i = 0; i < connectors.length; i++) { + datas.push(getCallData(spells[i], params[i])); + } + return [connectors, datas]; + } + + let connectors = ["AAVE-V3-A"]; + let methods = ["deposit"]; + let params = [ + [FTM, ethers.utils.parseEther("10"), 0, 0] + ]; + let arg = await getArg(connectors, methods, params); + const spells = [ + { + connector: connectorName, + method: "castAny", + args: arg + } + ]; + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), await wallet0.getAddress()); + const receipt = await tx.wait(); + }); + + it("should check balances after spells on DSA", async function () { + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte( + new BigNumber(2).multipliedBy(1e18).toString() + ); + expect(await aFtm.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.gte( + new BigNumber(10).multipliedBy(1e18).toString() + ); + }); + }); +}); diff --git a/test/mainnet/dsa-spell/dsa-spell.test.ts b/test/mainnet/dsa-spell/dsa-spell.test.ts new file mode 100644 index 00000000..a717ec39 --- /dev/null +++ b/test/mainnet/dsa-spell/dsa-spell.test.ts @@ -0,0 +1,176 @@ +import hre from "hardhat"; +import axios from "axios"; +import { expect } from "chai"; +const { ethers } = hre; //check +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { addresses } from "../../../scripts/tests/mainnet/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import { ConnectV2DSASpell__factory } from "../../../typechain"; +import type { Signer, Contract } from "ethers"; +import BigNumber from "bignumber.js"; + +describe("DSA Spell", function () { + const connectorName = "dsa-spell-test"; + + let dsaWallet0: any; + let dsaWallet1: any; + let dsaWallet2: any; + let walletB: any; + let wallet0: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: any; + + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url + } + } + ] + }); + [wallet0] = await ethers.getSigners(); + + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2DSASpell__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + console.log("\tConnector address", connector.address); + }); + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + walletB = await ethers.getSigner(dsaWallet0.address); + dsaWallet1 = await buildDSAv2(dsaWallet0.address); + expect(!!dsaWallet1.address).to.be.true; + console.log(`\t${dsaWallet1.address}`); + }); + + it("Deposit eth into DSA wallet 0", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + + it("Deposit eth into DSA wallet 1", async function () { + await wallet0.sendTransaction({ + to: dsaWallet1.address, + value: ethers.utils.parseEther("10") + }); + + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + }); + + describe("Main", function () { + let ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; + let USDC = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; + let usdc = new ethers.Contract(USDC, abis.basic.erc20); + let aETH = "0x030bA81f1c18d280636F32af80b9AAd02Cf0854e"; + let aEth = new ethers.Contract(aETH, abis.basic.aToken); + var abi = [ + "function withdraw(address,uint256,address,uint256,uint256)", + "function deposit(address,uint256,uint256,uint256)", + "function borrow(address,uint256,uint256,uint256,uint256)" + ]; + function getCallData(spell: string, params: any) { + var iface = new ethers.utils.Interface(abi); + let data = iface.encodeFunctionData(spell, params); + return ethers.utils.hexlify(data); + } + + it("should cast spells", async function () { + async function getArg(connectors: any, spells: any, params: any) { + let datas = []; + for (let i = 0; i < connectors.length; i++) { + datas.push(getCallData(spells[i], params[i])); + } + return [dsaWallet1.address, connectors, datas]; + } + + let connectors = ["BASIC-A"]; + let methods = ["withdraw"]; + let params = [ + [ETH, ethers.utils.parseEther("2"), dsaWallet0.address, 0, 0], + ]; + let arg = await getArg(connectors, methods, params); + + const spells = [ + { + connector: connectorName, + method: "castOnDSA", + args: arg + } + ]; + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), await wallet0.getAddress()); + const receipt = await tx.wait(); + }); + + it("should check balances after cast on DSA", async function () { + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.lte(new BigNumber(8).multipliedBy(1e18).toString()); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte( + new BigNumber(12).multipliedBy(1e18).toString() + ); + }); + + it("should cast spell on the first successful", async function () { + async function getArg(connectors: any, spells: any, params: any) { + let datas = []; + for (let i = 0; i < connectors.length; i++) { + datas.push(getCallData(spells[i], params[i])); + } + return [connectors, datas]; + } + + let connectors = ["AAVE-V2-A", "AAVE-V1-A"]; + let methods = ["deposit", "deposit"]; + let params = [ + [ETH, ethers.utils.parseEther("10"), 0, 0], + [ETH, ethers.utils.parseEther("10"), 0, 0] + ]; + let arg = await getArg(connectors, methods, params); + const spells = [ + { + connector: connectorName, + method: "castAny", + args: arg + } + ]; + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), await wallet0.getAddress()); + const receipt = await tx.wait(); + }); + + it("should check balances after spells on DSA", async function () { + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte( + new BigNumber(2).multipliedBy(1e18).toString() + ); + expect(await aEth.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.gte( + new BigNumber(10).multipliedBy(1e18).toString() + ); + }); + }); +}); diff --git a/test/optimism/dsa-spell/dsa-spell.test.ts b/test/optimism/dsa-spell/dsa-spell.test.ts new file mode 100644 index 00000000..af9fa8b0 --- /dev/null +++ b/test/optimism/dsa-spell/dsa-spell.test.ts @@ -0,0 +1,180 @@ +import hre from "hardhat"; +import axios from "axios"; +import { expect } from "chai"; +const { ethers } = hre; //check +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { addresses } from "../../../scripts/tests/optimism/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import { ConnectV2DSASpellOptimism__factory } from "../../../typechain"; +import type { Signer, Contract } from "ethers"; +import BigNumber from "bignumber.js"; + +describe("DSA Spell", function () { + const connectorName = "dsa-spell-test"; + + let dsaWallet0: any; + let dsaWallet1: any; + let dsaWallet2: any; + let walletB: any; + let wallet0: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: any; + + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url + } + } + ] + }); + [wallet0] = await ethers.getSigners(); + + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2DSASpellOptimism__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + console.log("\tConnector address", connector.address); + }); + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + walletB = await ethers.getSigner(dsaWallet0.address); + dsaWallet1 = await buildDSAv2(dsaWallet0.address); + expect(!!dsaWallet1.address).to.be.true; + console.log(`\t${dsaWallet1.address}`); + }); + + it("Deposit eth into DSA wallet 0", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + + it("Deposit eth into DSA wallet 1", async function () { + await wallet0.sendTransaction({ + to: dsaWallet1.address, + value: ethers.utils.parseEther("10") + }); + + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + }); + + describe("Main", function () { + let ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; + let USDC = "0x7F5c764cBc14f9669B88837ca1490cCa17c31607"; + let usdc = new ethers.Contract(USDC, abis.basic.erc20); + let aETH = "0xe50fA9b3c56FfB159cB0FCA61F5c9D750e8128c8"; + let aEth = new ethers.Contract(aETH, abis.basic.aToken); + var abi = [ + "function withdraw(address,uint256,address,uint256,uint256)", + "function deposit(address,uint256,uint256,uint256)", + "function borrow(address,uint256,uint256,uint256,uint256)" + ]; + function getCallData(spell: string, params: any) { + var iface = new ethers.utils.Interface(abi); + let data = iface.encodeFunctionData(spell, params); + return ethers.utils.hexlify(data); + } + + it("should cast spells", async function () { + async function getArg(connectors: any, spells: any, params: any) { + let datas = []; + for (let i = 0; i < connectors.length; i++) { + datas.push(getCallData(spells[i], params[i])); + } + return [dsaWallet1.address, connectors, datas]; + } + + let connectors = ["BASIC-A", "AAVE-V3-A", "AAVE-V3-A"]; + let methods = ["withdraw", "deposit", "borrow"]; + let params = [ + [ETH, ethers.utils.parseEther("2"), dsaWallet0.address, 0, 0], + [ETH, ethers.constants.MaxUint256, 0, 0], + [USDC, ethers.utils.parseUnits("1", 6), 2, 0, 0] + ]; + let arg = await getArg(connectors, methods, params); + + const spells = [ + { + connector: connectorName, + method: "castOnDSA", + args: arg + } + ]; + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), await wallet0.getAddress()); + const receipt = await tx.wait(); + }); + + it("should check balances after cast on DSA", async function () { + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.lte(0); + expect(await usdc.connect(wallet0).balanceOf(dsaWallet1.address)).to.be.gte( + new BigNumber(1).multipliedBy(1e6).toString() + ); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte( + new BigNumber(12).multipliedBy(1e18).toString() + ); + }); + + it("should cast spell on the first successful", async function () { + async function getArg(connectors: any, spells: any, params: any) { + let datas = []; + for (let i = 0; i < connectors.length; i++) { + datas.push(getCallData(spells[i], params[i])); + } + return [connectors, datas]; + } + + let connectors = ["AAVE-V3-A"]; + let methods = ["deposit"]; + let params = [ + [ETH, ethers.utils.parseEther("10"), 0, 0] + ]; + let arg = await getArg(connectors, methods, params); + const spells = [ + { + connector: connectorName, + method: "castAny", + args: arg + } + ]; + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), await wallet0.getAddress()); + const receipt = await tx.wait(); + }); + + it("should check balances after spells on DSA", async function () { + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte( + new BigNumber(2).multipliedBy(1e18).toString() + ); + expect(await aEth.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.gte( + new BigNumber(10).multipliedBy(1e18).toString() + ); + }); + }); +}); diff --git a/test/polygon/dsa-spell/dsa-spell.test.ts b/test/polygon/dsa-spell/dsa-spell.test.ts new file mode 100644 index 00000000..11be803c --- /dev/null +++ b/test/polygon/dsa-spell/dsa-spell.test.ts @@ -0,0 +1,185 @@ +import hre from "hardhat"; +import axios from "axios"; +import { expect } from "chai"; +const { ethers } = hre; //check +import { BigNumber } from "bignumber.js"; +import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector"; +import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2"; +import { encodeSpells } from "../../../scripts/tests/encodeSpells"; +import { getMasterSigner } from "../../../scripts/tests/getMasterSigner"; +import { addresses } from "../../../scripts/tests/polygon/addresses"; +import { abis } from "../../../scripts/constant/abis"; +import { ConnectV2DSASpellPolygon__factory } from "../../../typechain"; +import er20abi from "../../../scripts/constant/abi/basics/erc20.json"; +import type { Signer, Contract } from "ethers"; + +describe("DSA Spell", function () { + const connectorName = "dsa-spell-test"; + + let dsaWallet0: any; + let dsaWallet1: any; + let dsaWallet2: any; + let walletB: any; + let wallet0: any; + let walletBsigner: any; + let masterSigner: Signer; + let instaConnectorsV2: Contract; + let connector: any; + + before(async () => { + await hre.network.provider.request({ + method: "hardhat_reset", + params: [ + { + forking: { + // @ts-ignore + jsonRpcUrl: hre.config.networks.hardhat.forking.url + } + } + ] + }); + [wallet0] = await ethers.getSigners(); + + masterSigner = await getMasterSigner(); + instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2); + connector = await deployAndEnableConnector({ + connectorName, + contractArtifact: ConnectV2DSASpellPolygon__factory, + signer: masterSigner, + connectors: instaConnectorsV2 + }); + console.log("\tConnector address", connector.address); + }); + + it("Should have contracts deployed.", async function () { + expect(!!instaConnectorsV2.address).to.be.true; + expect(!!connector.address).to.be.true; + expect(!!(await masterSigner.getAddress())).to.be.true; + }); + + describe("DSA wallet setup", function () { + it("Should build DSA v2", async function () { + dsaWallet0 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet0.address).to.be.true; + walletB = await ethers.getSigner(dsaWallet0.address); + dsaWallet1 = await buildDSAv2(dsaWallet0.address); + expect(!!dsaWallet1.address).to.be.true; + console.log(`\t${dsaWallet1.address}`); + dsaWallet2 = await buildDSAv2(wallet0.address); + expect(!!dsaWallet2.address).to.be.true; + }); + + it("Deposit matic into DSA wallet 0", async function () { + await wallet0.sendTransaction({ + to: dsaWallet0.address, + value: ethers.utils.parseEther("10") + }); + + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + + it("Deposit matic into DSA wallet 1", async function () { + await wallet0.sendTransaction({ + to: dsaWallet1.address, + value: ethers.utils.parseEther("10") + }); + + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.gte(ethers.utils.parseEther("10")); + }); + }); + + describe("Main", function () { + let MATIC = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; + let USDC = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"; + let aMATIC = "0x6d80113e533a2C0fe82EaBD35f1875DcEA89Ea97"; + let usdc = new ethers.Contract(USDC, abis.basic.erc20); + let aMatic = new ethers.Contract(aMATIC, abis.basic.aToken); + var abi = [ + "function withdraw(address,uint256,address,uint256,uint256)", + "function deposit(address,uint256,uint256,uint256)", + "function borrow(address,uint256,uint256,uint256,uint256)" + ]; + function getCallData(spell: string, params: any) { + var iface = new ethers.utils.Interface(abi); + let data = iface.encodeFunctionData(spell, params); + return ethers.utils.hexlify(data); + } + + it("should cast spells", async function () { + async function getArg(connectors: any, spells: any, params: any) { + let datas = []; + for (let i = 0; i < connectors.length; i++) { + datas.push(getCallData(spells[i], params[i])); + } + return [dsaWallet1.address, connectors, datas]; + } + + let connectors = ["BASIC-A", "AAVE-V3-A", "AAVE-V3-A"]; + let methods = ["withdraw", "deposit", "borrow"]; + let params = [ + [MATIC, ethers.utils.parseEther("2"), dsaWallet0.address, 0, 0], + [MATIC, ethers.constants.MaxUint256, 0, 0], + [USDC, ethers.utils.parseUnits("1", 6), 2, 0, 0] + ]; + let arg = await getArg(connectors, methods, params); + + const spells = [ + { + connector: connectorName, + method: "castOnDSA", + args: arg + } + ]; + const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), await wallet0.getAddress()); + const receipt = await tx.wait(); + }); + + it("should check balances after cast on DSA", async function () { + expect(await ethers.provider.getBalance(dsaWallet1.address)).to.be.lte(0); + expect(await usdc.connect(wallet0).balanceOf(dsaWallet1.address)).to.be.gte( + new BigNumber(1).multipliedBy(1e6).toString() + ); + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte( + new BigNumber(12).multipliedBy(1e18).toString() + ); + }); + + it("should cast spell on the first successful", async function () { + async function getArg(connectors: any, spells: any, params: any) { + let datas = []; + for (let i = 0; i < connectors.length; i++) { + datas.push(getCallData(spells[i], params[i])); + } + return [connectors, datas]; + } + + let connectors = ["AAVE-V3-A", "AAVE-V2-A"]; + let methods = ["deposit","deposit"]; + let params = [ + [MATIC, ethers.utils.parseEther("10"), 0, 0], + [MATIC, ethers.utils.parseEther("10"), 0, 0] + ]; + let arg = await getArg(connectors, methods, params); + const spells = [ + { + connector: connectorName, + method: "castAny", + args: arg + } + ]; + const tx = await dsaWallet0 + .connect(wallet0) + .cast(...encodeSpells(spells), await wallet0.getAddress()); + const receipt = await tx.wait(); + }); + + it("should check balances after spells on DSA", async function () { + expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.lte( + new BigNumber(2).multipliedBy(1e18).toString() + ); + expect(await aMatic.connect(wallet0).balanceOf(dsaWallet0.address)).to.be.gte( + new BigNumber(10).multipliedBy(1e18).toString() + ); + }); + }); +});