Gelato DSA setup tests complete

This commit is contained in:
gitpusha 2020-08-17 12:17:04 +02:00
parent 7c7112fec3
commit 095c036ea3
32 changed files with 14287 additions and 0 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
*.sol linguist-language=Solidity

16
.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
# Dependency directory
node_modules
# local env variables
.env
#buidler
artifacts/
cache/
# macOS
.DS_Store
*.icloud
# yarn
yarn-error.log

29
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,29 @@
{
// HTML
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// Javascript
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// JSON
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// Solidity
"solidity.formatter": "none",
"solidity.linter": "solhint",
"solidity.packageDefaultDependenciesContractsDirectory": "",
"solidity.packageDefaultDependenciesDirectory": "node_modules",
"solidity.solhintRules": {
"quotes": ["error", "double"]
},
// Typscript
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

154
buidler.config.js Normal file
View File

@ -0,0 +1,154 @@
// Libraries
const assert = require("assert");
const { utils } = require("ethers");
const GelatoCoreLib = require("@gelatonetwork/core");
// Process Env Variables
require("dotenv").config();
const INFURA_ID = process.env.INFURA_ID;
assert.ok(INFURA_ID, "no Infura ID in process.env");
const INSTA_MASTER = "0xfCD22438AD6eD564a1C26151Df73F6B33B817B56";
// ================================= CONFIG =========================================
module.exports = {
defaultNetwork: "ganache",
networks: {
ganache: {
// Standard config
url: "http://localhost:8545",
fork: `https://mainnet.infura.io/v3/${INFURA_ID}`,
unlocked_accounts: [INSTA_MASTER],
// Custom
GelatoCore: "0x1d681d76ce96E4d70a88A00EBbcfc1E47808d0b8",
InstaMaster: INSTA_MASTER,
InstaIndex: "0x2971AdFa57b20E5a416aE5a708A8655A9c74f723",
InstaList: "0x4c8a1BEb8a87765788946D6B19C6C6355194AbEb",
InstaConnectors: "0xD6A602C01a023B98Ecfb29Df02FBA380d3B21E0c",
InstaAccount: "0x939Daad09fC4A9B8f8A9352A485DAb2df4F4B3F8",
ConnectAuth: "0xd1aFf9f2aCf800C876c409100D6F39AEa93Fc3D9",
ConnectBasic: "0x6a31c5982C5Bc5533432913cf06a66b6D3333a95",
ConnectMaker: "0xac02030d8a8F49eD04b2f52C394D3F901A10F8A9",
ConnectCompound: "0x07F81230d73a78f63F0c2A3403AD281b067d28F8",
DAI: "0x6b175474e89094c44da98b954eedeac495271d0f",
DAI_UNISWAP: "0x2a1530C4C41db0B0b2bB646CB5Eb1A67b7158667",
},
},
solc: {
version: "0.6.12",
optimizer: { enabled: true },
},
};
// ================================= PLUGINS =========================================
usePlugin("@nomiclabs/buidler-ethers");
usePlugin("@nomiclabs/buidler-ganache");
usePlugin("@nomiclabs/buidler-waffle");
// ================================= TASKS =========================================
task("abi-encode-withselector")
.addPositionalParam(
"abi",
"Contract ABI in array form",
undefined,
types.json
)
.addPositionalParam("functionname")
.addOptionalVariadicPositionalParam(
"inputs",
"Array of function params",
undefined,
types.json
)
.addFlag("log")
.setAction(async (taskArgs) => {
try {
if (taskArgs.log) console.log(taskArgs);
if (!taskArgs.abi)
throw new Error("abi-encode-withselector: no abi passed");
const interFace = new utils.Interface(taskArgs.abi);
let functionFragment;
try {
functionFragment = interFace.getFunction(taskArgs.functionname);
} catch (error) {
throw new Error(
`\n ❌ abi-encode-withselector: functionname "${taskArgs.functionname}" not found`
);
}
let payloadWithSelector;
if (taskArgs.inputs) {
let iterableInputs;
try {
iterableInputs = [...taskArgs.inputs];
} catch (error) {
iterableInputs = [taskArgs.inputs];
}
payloadWithSelector = interFace.encodeFunctionData(
functionFragment,
iterableInputs
);
} else {
payloadWithSelector = interFace.encodeFunctionData(
functionFragment,
[]
);
}
if (taskArgs.log)
console.log(`\nEncodedPayloadWithSelector:\n${payloadWithSelector}\n`);
return payloadWithSelector;
} catch (err) {
console.error(err);
process.exit(1);
}
});
task(
"fetchGelatoGasPrice",
`Returns the current gelato gas price used for calling canExec and exec`
)
.addOptionalParam("gelatocoreaddress")
.addFlag("log", "Logs return values to stdout")
.setAction(async (taskArgs) => {
try {
const gelatoCore = await ethers.getContractAt(
GelatoCoreLib.GelatoCore.abi,
taskArgs.gelatocoreaddress
? taskArgs.gelatocoreaddress
: network.config.GelatoCore
);
const oracleAbi = ["function latestAnswer() view returns (int256)"];
const gelatoGasPriceOracleAddress = await gelatoCore.gelatoGasPriceOracle();
// Get gelatoGasPriceOracleAddress
const gelatoGasPriceOracle = await ethers.getContractAt(
oracleAbi,
gelatoGasPriceOracleAddress
);
// lastAnswer is used by GelatoGasPriceOracle as well as the Chainlink Oracle
const gelatoGasPrice = await gelatoGasPriceOracle.latestAnswer();
if (taskArgs.log) {
console.log(
`\ngelatoGasPrice: ${utils.formatUnits(
gelatoGasPrice.toString(),
"gwei"
)} gwei\n`
);
}
return gelatoGasPrice;
} catch (error) {
console.error(error, "\n");
process.exit(1);
}
});

View File

@ -0,0 +1,102 @@
// "SPDX-License-Identifier: UNLICENSED"
pragma solidity 0.6.12;
import {
GelatoConditionsStandard
} from "@gelatonetwork/core/contracts/conditions/GelatoConditionsStandard.sol";
import {SafeMath} from "@gelatonetwork/core/contracts/external/SafeMath.sol";
import {IERC20} from "@gelatonetwork/core/contracts/external/IERC20.sol";
import {
IGelatoCore
} from "@gelatonetwork/core/contracts/gelato_core/interfaces/IGelatoCore.sol";
import {GelatoBytes} from "./GelatoBytes.sol";
/// @notice A general contract for retrieving and comparing 2 uints from 2 contracts.
/// @dev This contract only works if the refContracts fns returndata has a uint in
/// the first 32-byte position.
contract ConditionCompareUintsFromTwoSources is GelatoConditionsStandard {
using GelatoBytes for bytes;
using SafeMath for uint256;
/// @notice Helper to encode the Condition data field off-chain
function getConditionData(
address _sourceA,
address _sourceB,
bytes calldata _sourceAData,
bytes calldata _sourceBData,
uint256 _minSpread
)
public
pure
virtual
returns (bytes memory)
{
return abi.encode(_sourceA, _sourceB, _sourceAData, _sourceBData, _minSpread);
}
/// @notice Gelato Standard Condition function.
/// @dev Every Gelato Condition must have this function selector as entry point.
/// @param _conditionData The encoded data from getConditionData()
function ok(uint256, bytes calldata _conditionData, uint256)
public
view
virtual
override
returns (string memory)
{
(address _sourceA,
address _sourceB,
bytes memory _sourceAData,
bytes memory _sourceBData,
uint256 _minSpread) = abi.decode(
_conditionData,
(address,address,bytes,bytes,uint256)
);
return compare(_sourceA, _sourceB, _sourceAData, _sourceBData, _minSpread);
}
/// @notice Compares 2 values from sourceA and sourceB to check if minSpread is there.
/// @dev If you want to trigger when ContractA uint is greater than or equal
/// to ContractB by _minSpread: (ContractA=_sourceA, ContractB=_sourceB)
/// For the reverse (lower than/equal to): (ContractA=_sourceB, ContractB=_sourceA)
/// @param _sourceA The first contract that returns a uint for comparison.
/// @param _sourceB The second contract that returns a uint256 for comparison.
/// @param _sourceAData Payload for retrieving the uint from _sourceA.
/// @param _sourceBData Payload for retrieving the uint from _sourceB.
/// @param _minSpread The minimum diff between sourceA and sourceB
/// for the Condition to be relevant.
/// @return OK if the Condition is fulfilled.
function compare(
address _sourceA,
address _sourceB,
bytes memory _sourceAData,
bytes memory _sourceBData,
uint256 _minSpread
)
public
view
virtual
returns (string memory)
{
// Retrieve uint256 from sourceA
(bool success, bytes memory returndata) = _sourceA.staticcall(_sourceAData);
if (!success) {
return returndata.generateErrorString(
"ConditionCompareTwoUints.compare._sourceA:"
);
}
uint256 a = abi.decode(returndata, (uint256));
// Retrieve uint256 from sourceB
(success, returndata) = _sourceB.staticcall(_sourceBData);
if (!success) {
return returndata.generateErrorString(
"ConditionCompareTwoUints.compare._sourceB:"
);
}
uint256 b = abi.decode(returndata, (uint256));
if (a >= b.add(_minSpread)) return OK;
return "ANotGreaterOrEqualToBbyMinspread";
}
}

View File

@ -0,0 +1,75 @@
// "SPDX-License-Identifier: UNLICENSED"
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import {
GelatoConditionsStandard
} from "@gelatonetwork/core/contracts/conditions/GelatoConditionsStandard.sol";
import {IERC20} from "@gelatonetwork/core/contracts/external/IERC20.sol";
contract ConditionHasBalanceAndAllowance is GelatoConditionsStandard {
/// @dev Use this function to encode the data off-chain for the condition data field
/// @param _token The token whose balance and allowance we check.
/// 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE for ETH.
/// @param _from The account whose balance to check
/// @param _to The account whose allowance to check. AddressZero for ETH.
/// @param _value The balance/allowance that needs to be present at least.
/// @return abi encoded params (without selector)
function getConditionData(
address _token,
address _from,
address _to,
uint256 _value
)
public
pure
virtual
returns(bytes memory)
{
return abi.encode(_from, _to, _token, _value);
}
/// @param _conditionData The encoded data from getConditionData()
function ok(uint256, bytes calldata _conditionData, uint256)
public
view
virtual
override
returns(string memory)
{
(address _token,
address _from,
address _to,
uint256 _value) = abi.decode(_conditionData, (address,address,address,uint256));
return check(_token, _from, _to, _value);
}
// Specific Implementation
function check(address _token, address _from, address _to, uint256 _value)
public
view
virtual
returns(string memory)
{
// ETH balance
if (_token == 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) {
if (_from.balance >= _value) return OK;
return "NotOkETHBalance";
} else {
// ERC20 balance and allowance
IERC20 erc20 = IERC20(_token);
try erc20.balanceOf(_from) returns (uint256 balance) {
if (balance < _value) return "NotOkERC20Balance";
} catch {
return "ERC20BalanceError";
}
try erc20.allowance(_from, _to) returns (uint256 allowance) {
if (allowance >= _value) return OK;
return "NotOkERC20Allowance";
} catch {
return "ERC20AllowanceError";
}
}
}
}

136
contracts/ConnectGelato.sol Normal file
View File

@ -0,0 +1,136 @@
// "SPDX-License-Identifier: UNLICENSED"
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import {
IGelatoCore,
Provider,
Task,
TaskReceipt
} from "@gelatonetwork/core/contracts/gelato_core/interfaces/IGelatoCore.sol";
import {
IGelatoProviders,
TaskSpec
} from "@gelatonetwork/core/contracts/gelato_core/interfaces/IGelatoProviders.sol";
import {
IGelatoProviderModule
} from "@gelatonetwork/core/contracts/provider_modules/IGelatoProviderModule.sol";
import {Address} from "@gelatonetwork/core/contracts/external/Address.sol";
import {SafeMath} from "@gelatonetwork/core/contracts/external/SafeMath.sol";
interface ConnectorInterface {
function connectorID() external view returns(uint _type, uint _id);
function name() external pure returns (string memory);
}
/// @title ConnectGelato
/// @notice Allows InstaDapp DSA to enter and exit Gelato Network
/// @author gitpusha
contract ConnectGelato is ConnectorInterface {
using Address for address payable;
using SafeMath for uint256;
string constant public override name = "ConnectGelato-v1";
address public immutable connectGelatoAddress;
uint256 public immutable id;
address public immutable gelatoCore;
constructor(uint256 _id, address _gelatoCore) public payable {
connectGelatoAddress = address(this);
id = _id;
gelatoCore = _gelatoCore;
}
/// @dev needed for unproviding funds from GelatoCore
receive() external payable {
require(msg.sender == gelatoCore, "ConnectGelato.receive");
}
/// @dev _id must be InstaConnectors.connectorLength+1
function connectorID() external view override returns(uint _type, uint _id) {
(_type, _id) = (1, id);
}
modifier delegatecallOnly(string memory _tracingInfo) {
require(
connectGelatoAddress != address(this),
string(abi.encodePacked(_tracingInfo, ":delegatecallOnly"))
);
_;
}
// ===== Gelato ENTRY APIs ======
function multiProvide(
address _executor,
TaskSpec[] calldata _taskSpecs,
IGelatoProviderModule[] calldata _modules
)
external
payable
delegatecallOnly("ConnectGelato.multiProvide")
{
try IGelatoProviders(gelatoCore).multiProvide{value: msg.value}(
_executor,
_taskSpecs,
_modules
) {
} catch Error(string memory error) {
revert(string(abi.encodePacked("ConnectGelato.multiProvide:", error)));
} catch {
revert("ConnectGelato.multiProvide: unknown error");
}
}
function submitTask(
Provider calldata _provider,
Task calldata _task,
uint256 _expiryDate
)
external
delegatecallOnly("ConnectGelato.submitTask")
{
try IGelatoCore(gelatoCore).submitTask(_provider, _task, _expiryDate) {
} catch Error(string memory error) {
revert(string(abi.encodePacked("ConnectGelato.submitTask:", error)));
} catch {
revert("ConnectGelato.submitTask: unknown error");
}
}
// ===== Gelato EXIT APIs ======
function multiUnprovide(
uint256 _withdrawAmount,
TaskSpec[] memory _taskSpecs,
IGelatoProviderModule[] memory _modules
)
external
delegatecallOnly("ConnectGelato.multiUnprovide")
{
uint256 balanceBefore = address(this).balance;
try IGelatoProviders(gelatoCore).multiUnprovide(
_withdrawAmount,
_taskSpecs,
_modules
) {
msg.sender.sendValue(address(this).balance.sub(balanceBefore));
} catch Error(string memory error) {
revert(string(abi.encodePacked("ConnectGelato.multiUnprovide:", error)));
} catch {
revert("ConnectGelato.multiUnprovide: unknown error");
}
}
function multiCancelTasks(TaskReceipt[] calldata _taskReceipts)
external
delegatecallOnly("ConnectGelato.multiCancelTasks")
{
try IGelatoCore(gelatoCore).multiCancelTasks(_taskReceipts) {
} catch Error(string memory error) {
revert(string(abi.encodePacked("ConnectGelato.multiCancelTasks:", error)));
} catch {
revert("ConnectGelato.multiCancelTasks: unknown error");
}
}
}

67
contracts/GelatoBytes.sol Normal file
View File

@ -0,0 +1,67 @@
// "SPDX-License-Identifier: UNLICENSED"
pragma solidity 0.6.12;
library GelatoBytes {
function calldataSliceSelector(bytes calldata _bytes)
internal
pure
returns (bytes4 selector)
{
selector =
_bytes[0] |
(bytes4(_bytes[1]) >> 8) |
(bytes4(_bytes[2]) >> 16) |
(bytes4(_bytes[3]) >> 24);
}
function memorySliceSelector(bytes memory _bytes)
internal
pure
returns (bytes4 selector)
{
selector =
_bytes[0] |
(bytes4(_bytes[1]) >> 8) |
(bytes4(_bytes[2]) >> 16) |
(bytes4(_bytes[3]) >> 24);
}
function revertWithErrorString(bytes memory _bytes, string memory _tracingInfo)
internal
pure
{
// 68: 32-location, 32-length, 4-ErrorSelector, UTF-8 err
if (_bytes.length % 32 == 4) {
bytes4 selector;
assembly { selector := mload(add(0x20, _bytes)) }
if (selector == 0x08c379a0) { // Function selector for Error(string)
assembly { _bytes := add(_bytes, 68) }
revert(string(abi.encodePacked(_tracingInfo, string(_bytes))));
} else {
revert(string(abi.encodePacked(_tracingInfo, "NoErrorSelector")));
}
} else {
revert(string(abi.encodePacked(_tracingInfo, "UnexpectedReturndata")));
}
}
function generateErrorString(bytes memory _bytes, string memory _tracingInfo)
internal
pure
returns (string memory)
{
// 68: 32-location, 32-length, 4-ErrorSelector, UTF-8 err
if (_bytes.length % 32 == 4) {
bytes4 selector;
assembly { selector := mload(add(0x20, _bytes)) }
if (selector == 0x08c379a0) { // Function selector for Error(string)
assembly { _bytes := add(_bytes, 68) }
return string(abi.encodePacked(_tracingInfo, string(_bytes)));
} else {
return string(abi.encodePacked(_tracingInfo, "NoErrorSelector"));
}
} else {
return string(abi.encodePacked(_tracingInfo, "UnexpectedReturndata"));
}
}
}

17
contracts/MockCDAI.sol Normal file
View File

@ -0,0 +1,17 @@
// "SPDX-License-Identifier: UNLICENSED"
pragma solidity 0.6.12;
contract MockCDAI {
// DSR
// https://compound.finance/docs#protocol-math
// CDAI uses supplyRatePerBlock with 10**18 precision
// Because MakerDAO dsr is rate per second with 10**27 precision,
// we also adopt this for CDAI.
uint256 public supplyRatePerSecond = 1000000000627937192491029810; // per second==2% annually
/// @dev Use this during tests to simulate changing CDAI.supplyRatePerBlock conditions
/// @param _rate CDAI.supplyRatePerBlock but in seconds and 10**27 precision
function setSupplyRatePerSecond(uint256 _rate) external virtual {
supplyRatePerSecond = _rate;
}
}

19
contracts/MockDSR.sol Normal file
View File

@ -0,0 +1,19 @@
// "SPDX-License-Identifier: UNLICENSED"
pragma solidity 0.6.12;
contract MockDSR {
// DSR
// https://github.com/makerdao/dss/blob/master/src/pot.sol
// https://docs.makerdao.com/smart-contract-modules/rates-module#a-note-on-setting-rates
// - is the rate per second
// - can be set by Maker governance on the Pot contract in the RatesModule.
// - returns annual percentage value as 10**27 [ray]
// - e.g. dsr=1000000000627937192491029810 == 2 % annually
uint256 public dsr = 1000000000627937192491029810; // per second==2% annually
/// @dev Use this during tests to simulate changing DSR conditions
/// @param _dsr The dsr to set.
function setDSR(uint256 _dsr) external virtual {
dsr = _dsr;
}
}

View File

@ -0,0 +1,104 @@
// "SPDX-License-Identifier: UNLICENSED"
pragma solidity 0.6.12;
pragma experimental ABIEncoderV2;
import {
GelatoProviderModuleStandard
} from "@gelatonetwork/core/contracts/provider_modules/GelatoProviderModuleStandard.sol";
import {Task} from "@gelatonetwork/core/contracts/gelato_core/interfaces/IGelatoCore.sol";
/// @dev InstaDapp Index
interface IndexInterface {
function connectors(uint version) external view returns (address);
function list() external view returns (address);
}
/// @dev InstaDapp List
interface ListInterface {
function accountID(address _account) external view returns (uint64);
}
/// @dev InstaDapp Connectors
interface ConnectorsInterface {
function isConnector(address[] calldata logicAddr) external view returns (bool);
function isStaticConnector(address[] calldata logicAddr) external view returns (bool);
}
/// @dev InstaDapp Defi Smart Account wallet
interface AccountInterface {
function version() external view returns (uint);
function isAuth(address user) external view returns (bool);
function shield() external view returns (bool);
function cast(address[] calldata _targets, bytes[] calldata _datas, address _origin)
external
payable
returns (bytes32[] memory responses);
}
contract ProviderModuleDSA is GelatoProviderModuleStandard {
IndexInterface public immutable index;
address public immutable gelatoCore;
constructor(IndexInterface _index, address _gelatoCore) public {
index = _index;
gelatoCore = _gelatoCore;
}
// ================= GELATO PROVIDER MODULE STANDARD ================
function isProvided(address _userProxy, address, Task calldata _task)
external
view
override
returns(string memory)
{
// Verify InstaDapp account identity
if (ListInterface(index.list()).accountID(_userProxy) == 0)
return "ProviderModuleDSA.isProvided:InvalidUserProxy";
// Is GelatoCore authorized
if (!AccountInterface(_userProxy).isAuth(gelatoCore))
return "ProviderModuleDSA.isProvided:GelatoCoreNotAuth";
// Is connector valid
ConnectorsInterface connectors = ConnectorsInterface(index.connectors(
AccountInterface(_userProxy).version()
));
address[] memory targets = new address[](_task.actions.length);
for (uint i = 0; i < _task.actions.length; i++)
targets[i] = _task.actions[i].addr;
bool isShield = AccountInterface(_userProxy).shield();
if (isShield)
if (!connectors.isStaticConnector(targets))
return "ProviderModuleDSA.isProvided:not-static-connector";
else
if (!connectors.isConnector(targets))
return "ProviderModuleDSA.isProvided:not-connector";
return OK;
}
/// @dev DS PROXY ONLY ALLOWS DELEGATE CALL for single actions, that's why we also use multisend
function execPayload(uint256, address, address, Task calldata _task, uint256)
external
view
override
returns(bytes memory payload, bool)
{
address[] memory targets = new address[](_task.actions.length);
for (uint i = 0; i < _task.actions.length; i++)
targets[i] = _task.actions[i].addr;
bytes[] memory datas = new bytes[](_task.actions.length);
for (uint i = 0; i < _task.actions.length; i++)
datas[i] = _task.actions[i].data;
payload = abi.encodeWithSelector(
AccountInterface.cast.selector,
targets,
datas,
tx.origin
);
}
}

22
package.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "gelato-instadapp",
"version": "1.0.0",
"description": "The smart contract and tests for automatic CHI token buying",
"repository": "https://github.com/gelatodigital/gelato-instadapp",
"author": "gitpusha",
"private": false,
"scripts": {},
"devDependencies": {
"@gelatonetwork/core": "0.5.3",
"@nomiclabs/buidler": "1.4.3",
"@nomiclabs/buidler-ethers": "2.0.0",
"@nomiclabs/buidler-ganache": "^1.3.3",
"@nomiclabs/buidler-waffle": "2.0.0",
"chai": "^4.2.0",
"dotenv": "8.2.0",
"ethereum-waffle": "3.0.2",
"ethers": "5.0.8",
"prettier": "2.0.5"
},
"dependencies": {}
}

View File

@ -0,0 +1,104 @@
{
"contractName": "ConnectAuth",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "_msgSender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "_authority",
"type": "address"
}
],
"name": "LogAddAuth",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "_msgSender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "_authority",
"type": "address"
}
],
"name": "LogRemoveAuth",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "authority",
"type": "address"
}
],
"name": "add",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "connectorID",
"outputs": [
{
"internalType": "uint256",
"name": "_type",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_id",
"type": "uint256"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "name",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "authority",
"type": "address"
}
],
"name": "remove",
"outputs": [],
"stateMutability": "payable",
"type": "function"
}
],
"bytecode": "0x608060405234801561001057600080fd5b5061047f806100206000396000f3fe60806040526004361061003f5760003560e01c806306fdde03146100445780630a3b0a4f146100ce57806329092d0e146100f6578063eb15f7811461011c575b600080fd5b34801561005057600080fd5b5061005961014a565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561009357818101518382015260200161007b565b50505050905090810190601f1680156100c05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100f4600480360360208110156100e457600080fd5b50356001600160a01b031661016d565b005b6100f46004803603602081101561010c57600080fd5b50356001600160a01b0316610342565b34801561012857600080fd5b50610131610429565b6040805192835260208301919091528051918290030190f35b60405180604001604052806007815260200166417574682d763160c81b81525081565b60408051630b7f436d60e31b81526001600160a01b038316600482015290513091635bfa1b6891602480830192600092919082900301818387803b1580156101b457600080fd5b505af11580156101c8573d6000803e3d6000fd5b50506040516001600160a01b03841692503391507f0a0883e359d023e38c8befc2b894f838c1942537ae51cba71e2bc651af2b3a5d90600090a3604080513360208201526001600160a01b0383168183015281518082038301815260609091019091527f0a0883e359d023e38c8befc2b894f838c1942537ae51cba71e2bc651af2b3a5d90600080610258610429565b91509150610264610431565b6001600160a01b031663e14d4fb1838387876040518563ffffffff1660e01b81526004018085815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156102d45781810151838201526020016102bc565b50505050905090810190601f1680156103015780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b15801561032357600080fd5b505af1158015610337573d6000803e3d6000fd5b505050505050505050565b6040805163e6c09edf60e01b81526001600160a01b03831660048201529051309163e6c09edf91602480830192600092919082900301818387803b15801561038957600080fd5b505af115801561039d573d6000803e3d6000fd5b50506040516001600160a01b03841692503391507f7289d07acd866f85ba9176bdbac8304ca6072c00bde3d94c43afd8fbc8114db890600090a3604080513360208201526001600160a01b0383168183015281518082038301815260609091019091527f7289d07acd866f85ba9176bdbac8304ca6072c00bde3d94c43afd8fbc8114db8906000806102585b600190600a90565b732af7ea6cb911035f3eb1ed895cb6692c39ecba979056fea2646970667358221220aaff9fce38c2adc6ce77e28826e95d8fa2b77e60224b45f4f0215bb6478057c164736f6c634300060c0033",
"deployedBytecode": "0x60806040526004361061003f5760003560e01c806306fdde03146100445780630a3b0a4f146100ce57806329092d0e146100f6578063eb15f7811461011c575b600080fd5b34801561005057600080fd5b5061005961014a565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561009357818101518382015260200161007b565b50505050905090810190601f1680156100c05780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100f4600480360360208110156100e457600080fd5b50356001600160a01b031661016d565b005b6100f46004803603602081101561010c57600080fd5b50356001600160a01b0316610342565b34801561012857600080fd5b50610131610429565b6040805192835260208301919091528051918290030190f35b60405180604001604052806007815260200166417574682d763160c81b81525081565b60408051630b7f436d60e31b81526001600160a01b038316600482015290513091635bfa1b6891602480830192600092919082900301818387803b1580156101b457600080fd5b505af11580156101c8573d6000803e3d6000fd5b50506040516001600160a01b03841692503391507f0a0883e359d023e38c8befc2b894f838c1942537ae51cba71e2bc651af2b3a5d90600090a3604080513360208201526001600160a01b0383168183015281518082038301815260609091019091527f0a0883e359d023e38c8befc2b894f838c1942537ae51cba71e2bc651af2b3a5d90600080610258610429565b91509150610264610431565b6001600160a01b031663e14d4fb1838387876040518563ffffffff1660e01b81526004018085815260200184815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b838110156102d45781810151838201526020016102bc565b50505050905090810190601f1680156103015780820380516001836020036101000a031916815260200191505b5095505050505050600060405180830381600087803b15801561032357600080fd5b505af1158015610337573d6000803e3d6000fd5b505050505050505050565b6040805163e6c09edf60e01b81526001600160a01b03831660048201529051309163e6c09edf91602480830192600092919082900301818387803b15801561038957600080fd5b505af115801561039d573d6000803e3d6000fd5b50506040516001600160a01b03841692503391507f7289d07acd866f85ba9176bdbac8304ca6072c00bde3d94c43afd8fbc8114db890600090a3604080513360208201526001600160a01b0383168183015281518082038301815260609091019091527f7289d07acd866f85ba9176bdbac8304ca6072c00bde3d94c43afd8fbc8114db8906000806102585b600190600a90565b732af7ea6cb911035f3eb1ed895cb6692c39ecba979056fea2646970667358221220aaff9fce38c2adc6ce77e28826e95d8fa2b77e60224b45f4f0215bb6478057c164736f6c634300060c0033",
"linkReferences": {},
"deployedLinkReferences": {}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

192
pre-compiles/IERC20.json Normal file
View File

@ -0,0 +1,192 @@
{
"contractName": "IERC20",
"abi": [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Approval",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "from",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "to",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "Transfer",
"type": "event"
},
{
"inputs": [
{
"internalType": "address",
"name": "owner",
"type": "address"
},
{
"internalType": "address",
"name": "spender",
"type": "address"
}
],
"name": "allowance",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "spender",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "approve",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "who",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transfer",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "from",
"type": "address"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
}
],
"name": "transferFrom",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}

View File

@ -0,0 +1,318 @@
{
"contractName": "IUniswapExchange",
"abi": [
{
"inputs": [
{
"internalType": "uint256",
"name": "MintTokens",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "ethToTokenSwapInput",
"outputs": [
{
"internalType": "uint256",
"name": "tokensBought",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokens_bought",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "ethToTokenSwapOutput",
"outputs": [
{
"internalType": "uint256",
"name": "tokensSold",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "MintTokens",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "ethToTokenTransferInput",
"outputs": [
{
"internalType": "uint256",
"name": "tokensBought",
"type": "uint256"
}
],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "ethSold",
"type": "uint256"
}
],
"name": "getEthToTokenInputPrice",
"outputs": [
{
"internalType": "uint256",
"name": "tokensBought",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokensSold",
"type": "uint256"
}
],
"name": "getTokenToEthInputPrice",
"outputs": [
{
"internalType": "uint256",
"name": "ethBought",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "ethbought",
"type": "uint256"
}
],
"name": "getTokenToEthOutputPrice",
"outputs": [
{
"internalType": "uint256",
"name": "tokensToBeSold",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokens_sold",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "min_eth",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "tokenToEthSwapInput",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "eth_bought",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "max_tokens",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
}
],
"name": "tokenToEthSwapOutput",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokens_sold",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "min_eth",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
}
],
"name": "tokenToEthTransferInput",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokensSold",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "MintTokensBought",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "minEthBought",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "address",
"name": "tokenAddr",
"type": "address"
}
],
"name": "tokenToTokenSwapInput",
"outputs": [
{
"internalType": "uint256",
"name": "tokensBought",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokens_sold",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "min_tokens_bought",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "min_eth_bought",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "deadline",
"type": "uint256"
},
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "address",
"name": "token_addr",
"type": "address"
}
],
"name": "tokenToTokenTransferInput",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

228
pre-compiles/InstaList.json Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,389 @@
// We require the Buidler Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
// When running the script with `buidler run <script>` you'll find the Buidler
// Runtime Environment's members available in the global scope.
const bre = require("@nomiclabs/buidler");
const ethers = bre.ethers;
const { constants, utils } = require("ethers");
// CPK Library
const CPK = require("contract-proxy-kit-custom");
// running `npx buidler test` automatically makes use of buidler-waffle plugin
// => only dependency we need is "chaFi"
const { expect } = require("chai");
const GelatoCoreLib = require("@gelatonetwork/core");
const GELATO = bre.network.config.deployments.GelatoCore;
const EXECUTOR = bre.network.config.addressBook.gelatoExecutor.default;
const PROVIDER_MODULE_GNOSIS =
bre.network.config.deployments.ProviderModuleGnosisSafeProxy;
// The gas limit for our automated CHI.mint TX
// ActionChiMint caps chiAmount to 140 CHI => 6 mio gas should always suffice
const CHI_TOKENS_TO_MINT = 10; // should be kept below 140 MAX!
const PER_CHI_GAS_EST = 50000;
const GELATO_OVERHEAD = 200000;
const SELF_PROVIDER_GAS_LIMIT = utils.bigNumberify(
PER_CHI_GAS_EST * CHI_TOKENS_TO_MINT + GELATO_OVERHEAD
);
// Current Gelato Gas Price
let currentGelatoGasPrice;
// TRIGGER GAS PRICE
let triggerGasPrice;
// FUNDS TO DEPOSIT
let fundsToDeposit = 0;
describe("1-click anything for auto-minting CHI", function () {
// No timeout for Mocha due to Rinkeby mining latency
this.timeout(0);
// We use our User Wallet. Per our config this wallet is at the accounts index 0
// and hence will be used by default for all transactions we send.
let myUserWallet;
let myUserAddress;
// 2) We will deploy a GnosisSafeProxy using the Factory, or if we already deployed
// one, we will use that one.
let cpk;
let gnosisSafe;
let proxyIsDeployed;
let gelatoCore;
before(async function () {
// We get our User Wallet from the Buidler Runtime Env
[myUserWallet] = await bre.ethers.getSigners();
myUserAddress = await myUserWallet.getAddress();
// Create CPK instance connected to new mastercopy
cpk = await CPK.create({ ethers, signer: myUserWallet });
expect(await cpk.getOwnerAccount()).to.be.equal(myUserAddress);
const codeAtProxy = await bre.ethers.provider.getCode(cpk.address);
proxyIsDeployed = codeAtProxy === "0x" ? false : true;
if (proxyIsDeployed) {
gnosisSafe = await bre.ethers.getContractAt(
bre.GnosisSafe.abi,
cpk.address
);
expect(await gnosisSafe.isOwner(myUserAddress)).to.be.true;
}
console.log(`
\n Network: ${bre.network.name}\
\n CPK Proxy address: ${cpk.address}\
\n Proxy deployed?: ${proxyIsDeployed}\n
`);
gelatoCore = await ethers.getContractAt(
GelatoCoreLib.GelatoCore.abi,
network.config.deployments.GelatoCore // the Rinkeby Address of the deployed GelatoCore
);
currentGelatoGasPrice = await bre.run("fetchGelatoGasPrice");
// FOR TESTING WE SET IT EQUAL TO CURRENT SO WE CAN CHECK FOR EXECUTION
triggerGasPrice = currentGelatoGasPrice;
// FUNDS TO DEPOSIT
fundsToDeposit = await gelatoCore.minExecProviderFunds(
SELF_PROVIDER_GAS_LIMIT,
triggerGasPrice
);
});
it("In a single tx: [deployProxy], whitelist GnosisModule, setup Gelato, submitTask", async function () {
// Check if Gelato is already whitelisted as Safe Module
let gelatoIsWhitelisted = false;
if (proxyIsDeployed)
gelatoIsWhitelisted = await gnosisSafe.isModuleEnabled(GELATO);
if (gelatoIsWhitelisted === true)
console.log(`✅ Gelato Safe Module ALREADY whitelisted.`);
// Check current funding on Gelato
const currentProviderFunds = await gelatoCore.providerFunds(cpk.address);
const fundsAlreadyProvided = currentProviderFunds.gte(fundsToDeposit);
if (fundsAlreadyProvided) {
console.log(
`\n ✅ Already provided ${utils.formatEther(
currentProviderFunds
)} ETH on Gelato`
);
}
// Check if SelfProvider UserProxy already has Default Executor assigned
const assignedExecutor = await gelatoCore.executorByProvider(
cpk.address // As the User is being his own provider, we will use the userProxy's address as the provider address
);
const isDefaultExecutorAssigned =
utils.getAddress(assignedExecutor) === utils.getAddress(EXECUTOR)
? true
: false;
if (isDefaultExecutorAssigned)
console.log("\n ✅Default Executor ALREADY assigned");
const isExecutorValid = await gelatoCore.isExecutorMinStaked(EXECUTOR);
if (!isExecutorValid) {
console.error("❌ Executor is not minStaked");
process.exit(1);
}
// If the user wants to use Gelato through their GnosisSafe,
// he needs to register the ProviderModuleGnosisSafeProxy
// to make his GnosisSafe compatible with Gelato. Here we check,
// if the User already enabled the ProviderModuleGnosisSafeProxy.
// If not, we will enable it in the upcoming Tx.
const isUserProxyModuleWhitelisted = await gelatoCore.isModuleProvided(
cpk.address,
PROVIDER_MODULE_GNOSIS
);
if (isUserProxyModuleWhitelisted)
console.log("\n ✅ UserProxyModule ALREADY whitelisted");
// To submit Tasks to Gelato we need to instantiate a GelatoProvider object
const myGelatoProvider = new GelatoCoreLib.GelatoProvider({
addr: cpk.address, // This time, the provider is paying for the Task, hence we input the Providers address
module: PROVIDER_MODULE_GNOSIS,
});
let actionChiMint = await deployments.get("ActionChiMint");
actionChiMint = await bre.ethers.getContractAt(
actionChiMint.abi,
actionChiMint.address
);
// Specify and Instantiate the Gelato Task
const taskAutoMintCHIWhenTriggerGasPrice = new GelatoCoreLib.Task({
actions: [
new GelatoCoreLib.Action({
addr: actionChiMint.address,
data: await actionChiMint.getActionData(
myUserAddress, // recipient of CHI Tokens
CHI_TOKENS_TO_MINT // CHI Tokens to be minted
),
operation: GelatoCoreLib.Operation.Delegatecall,
termsOkCheck: false,
}),
],
selfProviderGasLimit: SELF_PROVIDER_GAS_LIMIT,
// This makes sure we only mint CHI when the gelatoGasPrice is at or below
// our desired trigger gas price
selfProviderGasPriceCeil: triggerGasPrice,
});
// Specify ExpiryDate: 0 for infinite validity
const EXPIRY_DATE = 0;
// The single Transaction that:
// 1) deploys a GnosisSafeProxy if not deployed
// 2) enableModule(GELATO on GnosisSafe
// 3) multiProvide(funds, executor, providerModuleGnosisSafeProxy) on Gelato
// 4) submitTask to GELATO
try {
let tx;
if (!gelatoIsWhitelisted) {
// If we have not enabled Gelato Module we enable it and then setup Gelato
// and submitTask
console.log(
"\n Sending TX to whitelist Gelato Gnosis Module, setup UserProxy and submitTask"
);
tx = await cpk.execTransactions(
[
{
to: cpk.address,
operation: CPK.CALL,
value: 0,
data: await bre.run("abi-encode-withselector", {
abi: bre.GnosisSafe.abi,
functionname: "enableModule",
inputs: [GELATO],
}),
},
{
to: GELATO,
operation: CPK.CALL,
value: fundsAlreadyProvided ? 0 : fundsToDeposit,
data: await bre.run("abi-encode-withselector", {
abi: GelatoCoreLib.GelatoCore.abi,
functionname: "multiProvide",
inputs: [
isDefaultExecutorAssigned ? constants.AddressZero : EXECUTOR,
[], // this can be left empty, as it is only relevant for external providers
isUserProxyModuleWhitelisted ? [] : [PROVIDER_MODULE_GNOSIS],
],
}),
},
{
operation: CPK.CALL,
to: GELATO,
value: 0,
data: await bre.run("abi-encode-withselector", {
abi: GelatoCoreLib.GelatoCore.abi,
functionname: "submitTask",
inputs: [
myGelatoProvider,
taskAutoMintCHIWhenTriggerGasPrice,
EXPIRY_DATE,
],
}),
},
],
{
value: fundsAlreadyProvided ? 0 : fundsToDeposit,
gasLimit: 5000000,
}
);
} else if (
!fundsAlreadyProvided ||
!isDefaultExecutorAssigned ||
!isUserProxyModuleWhitelisted
) {
// If we already enabled Gelato Module we only setup Gelato and submitTask
console.log("\n Sending TX to setup UserProxy and submitTask");
tx = await cpk.execTransactions(
[
{
to: GELATO,
operation: CPK.CALL,
value: fundsAlreadyProvided ? 0 : fundsToDeposit,
data: await bre.run("abi-encode-withselector", {
abi: GelatoCoreLib.GelatoCore.abi,
functionname: "multiProvide",
inputs: [
isDefaultExecutorAssigned ? constants.AddressZero : EXECUTOR,
[], // this can be left empty, as it is only relevant for external providers
isUserProxyModuleWhitelisted ? [] : [PROVIDER_MODULE_GNOSIS],
],
}),
},
{
operation: CPK.CALL,
to: GELATO,
value: 0,
data: await bre.run("abi-encode-withselector", {
abi: GelatoCoreLib.GelatoCore.abi,
functionname: "submitTask",
inputs: [
myGelatoProvider,
taskAutoMintCHIWhenTriggerGasPrice,
EXPIRY_DATE,
],
}),
},
],
{
value: fundsAlreadyProvided ? 0 : fundsToDeposit,
gasLimit: 5000000,
}
);
} else {
// If we already enabled Gelato Module and already setup Gelato
console.log("\n Sending TX to submitTask");
tx = await cpk.execTransactions(
[
{
operation: CPK.CALL,
to: GELATO,
value: 0,
data: await bre.run("abi-encode-withselector", {
abi: GelatoCoreLib.GelatoCore.abi,
functionname: "submitTask",
inputs: [
myGelatoProvider,
taskAutoMintCHIWhenTriggerGasPrice,
EXPIRY_DATE,
],
}),
},
],
{
value: fundsAlreadyProvided ? 0 : fundsToDeposit,
gasLimit: 5000000,
}
);
}
// Wait for mining
console.log("📓 all-in-one TX:", tx.hash);
await tx.transactionResponse.wait();
// Mined !
// Make sure User is owner of deployed GnosisSafe
gnosisSafe = await bre.ethers.getContractAt(
bre.GnosisSafe.abi,
cpk.address
);
expect(await gnosisSafe.isOwner(myUserAddress)).to.be.true;
// GelatoModule whitelisted on GnosisSafe
if (!gelatoIsWhitelisted) {
expect(await gnosisSafe.isModuleEnabled(GELATO)).to.be.true;
console.log(`✅ Tx: Gelato GnosisModule whitelisted.`);
}
// Provided Funds on Gelato
if (!fundsAlreadyProvided) {
expect(await gelatoCore.providerFunds(gnosisSafe.address)).to.be.gte(
fundsToDeposit
);
console.log(
`✅ Tx: Deposited ${utils.formatEther(fundsToDeposit)} ETH on gelato`
);
console.log(
`Funds on Gelato: ${utils.formatEther(
await gelatoCore.providerFunds(gnosisSafe.address)
)} ETH`
);
}
// Gelato Default Executor assigned
if (!isDefaultExecutorAssigned) {
expect(
await gelatoCore.executorByProvider(gnosisSafe.address)
).to.be.equal(EXECUTOR);
console.log(`✅ Tx: Selected default execution network: ${EXECUTOR}`);
}
// ProviderModuleGnosisSafeProxy whitelisted on Gelato
if (!isUserProxyModuleWhitelisted) {
expect(
await gelatoCore.isModuleProvided(
gnosisSafe.address,
PROVIDER_MODULE_GNOSIS
)
).to.be.true;
console.log(
`✅ Tx: Whitelisted ProviderModuleGnosisSafeProxy: ${PROVIDER_MODULE_GNOSIS}`
);
}
// For our Task to be executable, our Provider must have sufficient funds on Gelato
const providerIsLiquid = await gelatoCore.isProviderLiquid(
cpk.address,
SELF_PROVIDER_GAS_LIMIT, // we need roughtly estimatedGasPerExecution * 3 executions as balance on gelato
triggerGasPrice
);
if (!providerIsLiquid) {
console.log(
"\n ❌ Ooops! Your GnosisSafe needs to provide more funds to Gelato \n"
);
process.exit(1);
}
// SUCCESS !
console.log("\nUser Proxy succesfully set up and Task Submitted ✅ \n");
} catch (error) {
console.error("\n Gelato UserProxy Setup Error ❌ \n", error);
process.exit(1);
}
});
});

View File

@ -0,0 +1,264 @@
// We require the Buidler Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
// When running the script with `buidler run <script>` you'll find the Buidler
// Runtime Environment's members available in the global scope.
const bre = require("@nomiclabs/buidler");
const ethers = bre.ethers;
const { constants, utils } = require("ethers");
// CPK Library
const CPK = require("contract-proxy-kit-custom");
// running `npx buidler test` automatically makes use of buidler-waffle plugin
// => only dependency we need is "chaFi"
const { expect } = require("chai");
const GelatoCoreLib = require("@gelatonetwork/core");
const GELATO = bre.network.config.deployments.GelatoCore;
const EXECUTOR = bre.network.config.addressBook.gelatoExecutor.default;
const PROVIDER_MODULE_GNOSIS =
bre.network.config.deployments.ProviderModuleGnosisSafeProxy;
const FUNDS_TO_DEPOSIT = utils.parseEther("1");
describe("Create a GnosisSafe via CPK and setup with Gelato", function () {
// No timeout for Mocha due to Rinkeby mining latency
this.timeout(0);
// We use our User Wallet. Per our config this wallet is at the accounts index 0
// and hence will be used by default for all transactions we send.
let myUserWallet;
let myUserAddress;
// 2) We will deploy a GnosisSafeProxy using the Factory, or if we already deployed
// one, we will use that one.
let cpk;
let gnosisSafe;
let proxyIsDeployed;
before(async function () {
// We get our User Wallet from the Buidler Runtime Env
[myUserWallet] = await bre.ethers.getSigners();
myUserAddress = await myUserWallet.getAddress();
// Create CPK instance connected to new mastercopy
cpk = await CPK.create({ ethers, signer: myUserWallet });
expect(await cpk.getOwnerAccount()).to.be.equal(myUserAddress);
const codeAtProxy = await bre.ethers.provider.getCode(cpk.address);
proxyIsDeployed = codeAtProxy === "0x" ? false : true;
if (proxyIsDeployed) {
gnosisSafe = await bre.ethers.getContractAt(
bre.GnosisSafe.abi,
cpk.address
);
expect(await gnosisSafe.isOwner(myUserAddress)).to.be.true;
}
console.log(`
\n Network: ${bre.network.name}\
\n CPK Proxy address: ${cpk.address}\
\n Proxy deployed?: ${proxyIsDeployed}\n
`);
});
it("Gelato: Whitelist GnosisModule and setup (funds, executor, ProviderModule)", async function () {
// Check if Gelato is already whitelisted as Safe Module
let gelatoIsWhitelisted = false;
if (proxyIsDeployed)
gelatoIsWhitelisted = await gnosisSafe.isModuleEnabled(GELATO);
if (gelatoIsWhitelisted === true)
console.log(`✅ Gelato Safe Module ALREADY whitelisted.`);
// Instantiate GelatoCore contract instance for sanity checks
const gelatoCore = await ethers.getContractAt(
GelatoCoreLib.GelatoCore.abi,
network.config.deployments.GelatoCore // the Rinkeby Address of the deployed GelatoCore
);
// Check current funding on Gelato
const currentProviderFunds = await gelatoCore.providerFunds(cpk.address);
const fundsAlreadyProvided = currentProviderFunds.gte(FUNDS_TO_DEPOSIT);
if (fundsAlreadyProvided) {
console.log(
`\n ✅ Already provided ${utils.formatEther(
currentProviderFunds
)} ETH on Gelato`
);
}
// Check if SelfProvider UserProxy already has Default Executor assigned
const assignedExecutor = await gelatoCore.executorByProvider(
cpk.address // As the User is being his own provider, we will use the userProxy's address as the provider address
);
const isDefaultExecutorAssigned =
utils.getAddress(assignedExecutor) === utils.getAddress(EXECUTOR)
? true
: false;
if (isDefaultExecutorAssigned)
console.log("\n ✅Default Executor ALREADY assigned");
const isExecutorValid = await gelatoCore.isExecutorMinStaked(EXECUTOR);
if (!isExecutorValid) {
console.error("❌ Executor is not minStaked");
process.exit(1);
}
// If the user wants to use Gelato through their GnosisSafe,
// he needs to register the ProviderModuleGnosisSafeProxy
// to make his GnosisSafe compatible with Gelato. Here we check,
// if the User already enabled the ProviderModuleGnosisSafeProxy.
// If not, we will enable it in the upcoming Tx.
const isUserProxyModuleWhitelisted = await gelatoCore.isModuleProvided(
cpk.address,
PROVIDER_MODULE_GNOSIS
);
if (isUserProxyModuleWhitelisted)
console.log("\n ✅ UserProxyModule ALREADY whitelisted");
// The single Transaction that:
// 1) deploys a GnosisSafeProxy if not deployed
// 2) enableModule(GELATO on GnosisSafe
// 3) multiProvide(funds, executor, providerModuleGnosisSafeProxy) on Gelato
if (
!gelatoIsWhitelisted ||
!fundsAlreadyProvided ||
!isDefaultExecutorAssigned ||
!isUserProxyModuleWhitelisted
) {
try {
console.log("\n Sending Transaction to setup UserProxy");
let tx;
if (!gelatoIsWhitelisted) {
// If we have not enabled Gelato Module we enable it and then setup Gelato
tx = await cpk.execTransactions(
[
{
to: cpk.address,
operation: CPK.CALL,
value: 0,
data: await bre.run("abi-encode-withselector", {
abi: bre.GnosisSafe.abi,
functionname: "enableModule",
inputs: [GELATO],
}),
},
{
to: GELATO,
operation: CPK.CALL,
value: fundsAlreadyProvided ? 0 : FUNDS_TO_DEPOSIT,
data: await bre.run("abi-encode-withselector", {
abi: GelatoCoreLib.GelatoCore.abi,
functionname: "multiProvide",
inputs: [
isDefaultExecutorAssigned
? constants.AddressZero
: EXECUTOR,
[], // this can be left empty, as it is only relevant for external providers
isUserProxyModuleWhitelisted
? []
: [PROVIDER_MODULE_GNOSIS],
],
}),
},
],
{
value: fundsAlreadyProvided ? 0 : FUNDS_TO_DEPOSIT,
gasLimit: 5000000,
}
);
} else {
// If we already enabled Gelato Module we only setup Gelato
tx = await cpk.execTransactions(
[
{
to: GELATO,
operation: CPK.CALL,
value: fundsAlreadyProvided ? 0 : FUNDS_TO_DEPOSIT,
data: await bre.run("abi-encode-withselector", {
abi: GelatoCoreLib.GelatoCore.abi,
functionname: "multiProvide",
inputs: [
isDefaultExecutorAssigned
? constants.AddressZero
: EXECUTOR,
[], // this can be left empty, as it is only relevant for external providers
isUserProxyModuleWhitelisted
? []
: [PROVIDER_MODULE_GNOSIS],
],
}),
},
],
{
value: fundsAlreadyProvided ? 0 : FUNDS_TO_DEPOSIT,
gasLimit: 5000000,
}
);
}
// Wait for mining
console.log("TX:", tx.hash);
await tx.transactionResponse.wait();
// Mined !
// Make sure User is owner of deployed GnosisSafe
gnosisSafe = await bre.ethers.getContractAt(
bre.GnosisSafe.abi,
cpk.address
);
expect(await gnosisSafe.isOwner(myUserAddress)).to.be.true;
// GelatoModule whitelisted on GnosisSafe
expect(await gnosisSafe.isModuleEnabled(GELATO)).to.be.true;
console.log(`✅ Gelato GnosisModule whitelisted.`);
// Provided Funds on Gelato
expect(await gelatoCore.providerFunds(gnosisSafe.address)).to.be.gte(
FUNDS_TO_DEPOSIT
);
console.log(
`✅ Deposited ${utils.formatEther(FUNDS_TO_DEPOSIT)} ETH on gelato`
);
console.log(
`Funds on Gelato: ${utils.formatEther(
await gelatoCore.providerFunds(gnosisSafe.address)
)} ETH`
);
// Gelato Default Executor assigned
if (!isDefaultExecutorAssigned) {
expect(
await gelatoCore.executorByProvider(gnosisSafe.address)
).to.be.equal(EXECUTOR);
console.log(`✅ Selected default execution network: ${EXECUTOR}`);
}
// ProviderModuleGnosisSafeProxy whitelisted on Gelato
if (!isUserProxyModuleWhitelisted) {
expect(
await gelatoCore.isModuleProvided(
gnosisSafe.address,
PROVIDER_MODULE_GNOSIS
)
).to.be.true;
console.log(
`✅ Whitelisted ProviderModuleGnosisSafeProxy: ${PROVIDER_MODULE_GNOSIS}`
);
}
// SUCCESS !
console.log("\nUser Proxy succesfully set up ✅ \n");
} catch (error) {
console.error("\n Gelato UserProxy Setup Error ❌ \n", error);
process.exit(1);
}
} else {
console.log("\n✅ UserProxy ALREADY fully set up on Gelato \n");
}
});
});

View File

@ -0,0 +1,197 @@
// We require the Buidler Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
// When running the script with `buidler run <script>` you'll find the Buidler
// Runtime Environment's members available in the global scope.
const bre = require("@nomiclabs/buidler");
const ethers = bre.ethers;
const { utils } = require("ethers");
// CPK Library
const CPK = require("contract-proxy-kit-custom");
// running `npx buidler test` automatically makes use of buidler-waffle plugin
// => only dependency we need is "chaFi"
const { expect } = require("chai");
const GelatoCoreLib = require("@gelatonetwork/core");
const GELATO = bre.network.config.deployments.GelatoCore;
const EXECUTOR = bre.network.config.addressBook.gelatoExecutor.default;
const PROVIDER_MODULE_GNOSIS =
bre.network.config.deployments.ProviderModuleGnosisSafeProxy;
// The gas limit for our automated CHI.mint TX
// ActionChiMint caps chiAmount to 140 CHI => 6 mio gas should always suffice
const CHI_TOKENS_TO_MINT = 10; // should be kept below 140 MAX!
const PER_CHI_GAS_EST = 50000;
const GELATO_OVERHEAD = 200000;
const SELF_PROVIDER_GAS_LIMIT = utils.bigNumberify(
PER_CHI_GAS_EST * CHI_TOKENS_TO_MINT + GELATO_OVERHEAD
);
// Current Gelato Gas Price
let currentGelatoGasPrice;
// TRIGGER GAS PRICE
let triggerGasPrice;
describe("Submitting ActionCHIMint Task to Gelato via GnosisSafe", function () {
// No timeout for Mocha due to Rinkeby mining latency
this.timeout(0);
// We use our User Wallet. Per our config this wallet is at the accounts index 0
// and hence will be used by default for all transactions we send.
let myUserWallet;
let myUserAddress;
// 2) We will deploy a GnosisSafeProxy using the Factory, or if we already deployed
// one, we will use that one.
let cpk;
let gelatoCore;
before(async function () {
// We get our User Wallet from the Buidler Runtime Env
[myUserWallet] = await bre.ethers.getSigners();
myUserAddress = await myUserWallet.getAddress();
// Create CPK instance connected to new mastercopy
cpk = await CPK.create({ ethers, signer: myUserWallet });
expect(await cpk.getOwnerAccount()).to.be.equal(myUserAddress);
const codeAtProxy = await bre.ethers.provider.getCode(cpk.address);
const proxyDeployed = codeAtProxy === "0x" ? false : true;
console.log(`
\n Network: ${bre.network.name}\
\n CPK Proxy address: ${cpk.address}\
\n Proxy deployed?: ${proxyDeployed}\n
`);
if (proxyDeployed === false) {
console.error("Need `yarn setup-proxy` first");
process.exit(1);
}
gelatoCore = await ethers.getContractAt(
GelatoCoreLib.GelatoCore.abi,
network.config.deployments.GelatoCore // the Rinkeby Address of the deployed GelatoCore
);
currentGelatoGasPrice = await bre.run("fetchGelatoGasPrice");
console.log(`Current Price: ${currentGelatoGasPrice.toString()}`);
triggerGasPrice = currentGelatoGasPrice.sub(utils.parseUnits("4", "Gwei"));
console.log(`Trigger Price: ${triggerGasPrice.toString()}`);
});
// Submit your Task to Gelato via your GelatoUserProxy
it("User submits Task as SelfProvider", async function () {
// First we want to make sure that the Task we want to submit actually has
// a valid Provider, so we need to ask GelatoCore some questions about the Provider.
// For our Task to be executable, our Provider must have sufficient funds on Gelato
const providerIsLiquid = await gelatoCore.isProviderLiquid(
cpk.address,
SELF_PROVIDER_GAS_LIMIT, // we need roughtly estimatedGasPerExecution * 3 executions as balance on gelato
triggerGasPrice
);
if (!providerIsLiquid) {
console.log(
"\n ❌ Ooops! Your GnosisSafe needs to provide more funds to Gelato \n"
);
console.log("DEMO: run this command: `yarn setup-proxy` first");
process.exit(1);
}
// For the Demo, make sure the Provider has the Gelato default Executor assigned
const assignedExecutor = await gelatoCore.executorByProvider(cpk.address);
if (assignedExecutor !== EXECUTOR) {
console.log(
"\n ❌ Ooops! Your GnosisSafe needs to assign the gelato default Executor \n"
);
console.log("DEMO: run this command: `yarn setup-proxy` first");
process.exit(1);
}
// For the Demo, our Provider must use the deployed ProviderModuleGelatoUserProxy
const userProxyModuleIsProvided = await gelatoCore.isModuleProvided(
cpk.address,
PROVIDER_MODULE_GNOSIS
);
if (!userProxyModuleIsProvided) {
console.log(
"\n ❌ Ooops! Your GnosisSafe still needs to add ProviderModuleGelatoUserProxy \n"
);
console.log("DEMO: run this command: `yarn setup-proxy` first");
process.exit(1);
}
// The transaction to submit a Task to Gelato
if (
providerIsLiquid &&
assignedExecutor === EXECUTOR &&
userProxyModuleIsProvided
) {
// To submit Tasks to Gelato we need to instantiate a GelatoProvider object
const myGelatoProvider = new GelatoCoreLib.GelatoProvider({
addr: cpk.address, // This time, the provider is paying for the Task, hence we input the Providers address
module: PROVIDER_MODULE_GNOSIS,
});
let actionChiMint = await deployments.get("ActionChiMint");
actionChiMint = await bre.ethers.getContractAt(
actionChiMint.abi,
actionChiMint.address
);
// Specify and Instantiate the Gelato Task
const taskAutoMintCHIWhenTriggerGasPrice = new GelatoCoreLib.Task({
actions: [
new GelatoCoreLib.Action({
addr: actionChiMint.address,
data: await actionChiMint.getActionData(
myUserAddress, // recipient of CHI Tokens
CHI_TOKENS_TO_MINT // CHI Tokens to be minted
),
operation: GelatoCoreLib.Operation.Delegatecall,
termsOkCheck: false,
}),
],
selfProviderGasLimit: SELF_PROVIDER_GAS_LIMIT,
// This makes sure we only mint CHI when the gelatoGasPrice is at or below
// our desired trigger gas price
selfProviderGasPriceCeil: triggerGasPrice,
});
// Specify ExpiryDate: 0 for infinite validity
const EXPIRY_DATE = 0;
// Submit Task to gelato
try {
const tx = await cpk.execTransactions([
{
operation: CPK.CALL,
to: GELATO,
value: 0,
data: await bre.run("abi-encode-withselector", {
abi: GelatoCoreLib.GelatoCore.abi,
functionname: "submitTask",
inputs: [
myGelatoProvider,
taskAutoMintCHIWhenTriggerGasPrice,
EXPIRY_DATE,
],
}),
},
]);
// Wait for mining
console.log(`SubmitTask Tx Hash: ${tx.hash}`);
await tx.transactionResponse.wait();
} catch (error) {
console.error("\n PRE taskSubmissionTx error ❌ \n", error);
process.exit(1);
}
}
});
});

View File

@ -0,0 +1,129 @@
// We require the Buidler Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
// When running the script with `buidler run <script>` you'll find the Buidler
// Runtime Environment's members available in the global scope.
const bre = require("@nomiclabs/buidler");
const ethers = bre.ethers;
const { utils } = require("ethers");
// CPK Library
const CPK = require("contract-proxy-kit-custom");
// running `npx buidler test` automatically makes use of buidler-waffle plugin
// => only dependency we need is "chaFi"
const { expect } = require("chai");
const GelatoCoreLib = require("@gelatonetwork/core");
const GELATO = bre.network.config.deployments.GelatoCore;
describe("Unproviding ETH deposited on Gelato via GnosisSafe", function () {
// No timeout for Mocha due to Rinkeby mining latency
this.timeout(0);
// We use our User Wallet. Per our config this wallet is at the accounts index 0
// and hence will be used by default for all transactions we send.
let myUserWallet;
let myUserAddress;
// 2) We will deploy a GnosisSafeProxy using the Factory, or if we already deployed
// one, we will use that one.
let cpk;
let gelatoCore;
before(async function () {
// We get our User Wallet from the Buidler Runtime Env
[myUserWallet] = await bre.ethers.getSigners();
myUserAddress = await myUserWallet.getAddress();
// Create CPK instance connected to new mastercopy
cpk = await CPK.create({ ethers, signer: myUserWallet });
expect(await cpk.getOwnerAccount()).to.be.equal(myUserAddress);
const codeAtProxy = await bre.ethers.provider.getCode(cpk.address);
const proxyDeployed = codeAtProxy === "0x" ? false : true;
console.log(`
\n Network: ${bre.network.name}\
\n CPK Proxy address: ${cpk.address}\
\n Proxy deployed?: ${proxyDeployed}\n
`);
if (proxyDeployed === false) {
console.error("Need `yarn setup-proxy` first");
process.exit(1);
}
gelatoCore = await ethers.getContractAt(
GelatoCoreLib.GelatoCore.abi,
network.config.deployments.GelatoCore // the Rinkeby Address of the deployed GelatoCore
);
});
it("Withdraws funds from Gelato and transfer to User", async function () {
const fundsOnGelato = await gelatoCore.providerFunds(cpk.address);
console.log(
`Current funds on Gelato: ${utils.formatEther(fundsOnGelato)} ETH`
);
const prevUserWalletBalance = await myUserWallet.getBalance();
console.log(
`Current funds in User Wallet: ${utils.formatEther(
prevUserWalletBalance
)} ETH`
);
if (fundsOnGelato.eq("0")) {
console.log(
`❌ GnosisSafe ${cpk.address} has no funds on Gelato on ${bre.network.name}`
);
process.exit(1);
}
console.log(
`\n Withdrawing ${utils.formatEther(fundsOnGelato)} ETH to userWallet`
);
try {
const tx = await cpk.execTransactions(
[
{
operation: CPK.CALL,
to: GELATO,
value: 0,
data: await bre.run("abi-encode-withselector", {
abi: GelatoCoreLib.GelatoCore.abi,
functionname: "unprovideFunds",
inputs: [fundsOnGelato],
}),
},
{
operation: CPK.CALL,
to: myUserAddress,
value: fundsOnGelato,
data: "0x",
},
],
{ gasLimit: 2000000 }
);
// Wait for mining
await tx.transactionResponse.wait();
console.log(`Tx Hash: ${tx.hash}`);
const newFundsOnGelato = await gelatoCore.providerFunds(cpk.address);
expect(newFundsOnGelato).to.be.equal(0);
console.log(
`New funds in Gelato: ${utils.formatEther(newFundsOnGelato)} ETH`
);
const userWalletBalance = await myUserWallet.getBalance();
expect(userWalletBalance).to.be.gt(prevUserWalletBalance);
console.log(
`Funds in UserWallet: ${utils.formatEther(userWalletBalance)} ETH`
);
} catch (error) {
console.error("\n Gelato unprovideFunds error ❌ \n", error);
process.exit(1);
}
});
});

View File

@ -0,0 +1,95 @@
// We require the Buidler Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
// When running the script with `buidler run <script>` you'll find the Buidler
// Runtime Environment's members available in the global scope.
const bre = require("@nomiclabs/buidler");
const ethers = bre.ethers;
const { utils } = require("ethers");
// CPK Library
const CPK = require("contract-proxy-kit-custom");
// running `npx buidler test` automatically makes use of buidler-waffle plugin
// => only dependency we need is "chaFi"
const { expect } = require("chai");
describe("Transfering ETH out of GnosisSafe", function () {
// No timeout for Mocha due to Rinkeby mining latency
this.timeout(0);
// We use our User Wallet. Per our config this wallet is at the accounts index 0
// and hence will be used by default for all transactions we send.
let myUserWallet;
let myUserAddress;
// 2) We will deploy a GnosisSafeProxy using the Factory, or if we already deployed
// one, we will use that one.
let cpk;
before(async function () {
// We get our User Wallet from the Buidler Runtime Env
[myUserWallet] = await bre.ethers.getSigners();
myUserAddress = await myUserWallet.getAddress();
// Create CPK instance connected to new mastercopy
cpk = await CPK.create({ ethers, signer: myUserWallet });
expect(await cpk.getOwnerAccount()).to.be.equal(myUserAddress);
const codeAtProxy = await bre.ethers.provider.getCode(cpk.address);
const proxyDeployed = codeAtProxy === "0x" ? false : true;
console.log(`
\n Network: ${bre.network.name}\
\n CPK Proxy address: ${cpk.address}\
\n Proxy deployed?: ${proxyDeployed}\n
`);
if (proxyDeployed === false) {
console.error("Need `yarn setup-proxy` first");
process.exit(1);
}
});
it("Transfer funds from GnosisSafe", async function () {
const prevFundsInUserProxy = await ethers.provider.getBalance(cpk.address);
console.log(
`Current funds in GnosisSafe: ${utils.formatEther(
prevFundsInUserProxy
)} ETH`
);
if (prevFundsInUserProxy.eq("0")) {
console.log(
`❌ GnosisSafe ${cpk.address} has no funds on ${bre.network.name}`
);
process.exit(1);
}
console.log(
`\n Transferring ${utils.formatEther(
prevFundsInUserProxy
)} ETH to ${myUserAddress} on ${bre.network.name}`
);
try {
const tx = await cpk.execTransactions([
{
operation: CPK.CALL,
to: myUserAddress,
value: prevFundsInUserProxy,
data: "0x",
},
]);
// Wait for mining
console.log(`Tx Hash: ${tx.hash}`);
await tx.transactionResponse.wait();
const fundsInUserProxy = await ethers.provider.getBalance(cpk.address);
expect(fundsInUserProxy).to.be.equal(0);
console.log(`New funds in GnosisSafe at ${cpk.address}`);
console.log(`${utils.formatEther(fundsInUserProxy)} ETH`);
} catch (error) {
console.error("\n GnosisSafe transfer funds error ❌ \n", error);
process.exit(1);
}
});
});

View File

@ -0,0 +1,200 @@
// We require the Buidler Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
// When running the script with `buidler run <script>` you'll find the Buidler
// Runtime Environment's members available in the global scope.
const bre = require("@nomiclabs/buidler");
const ethers = bre.ethers;
const { constants, utils } = require("ethers");
// CPK Library
const CPK = require("contract-proxy-kit-custom");
// running `npx buidler test` automatically makes use of buidler-waffle plugin
// => only dependency we need is "chaFi"
const { expect } = require("chai");
const GelatoCoreLib = require("@gelatonetwork/core");
const GELATO = bre.network.config.deployments.GelatoCore;
const PROVIDER_MODULE_GNOSIS =
bre.network.config.deployments.ProviderModuleGnosisSafeProxy;
describe("MultiUnprovide on GELATO", function () {
// No timeout for Mocha due to Rinkeby mining latency
this.timeout(0);
// We use our User Wallet. Per our config this wallet is at the accounts index 0
// and hence will be used by default for all transactions we send.
let myUserWallet;
let myUserAddress;
// 2) We will deploy a GnosisSafeProxy using the Factory, or if we already deployed
// one, we will use that one.
let cpk;
let gelatoCore;
before(async function () {
// We get our User Wallet from the Buidler Runtime Env
[myUserWallet] = await bre.ethers.getSigners();
myUserAddress = await myUserWallet.getAddress();
gelatoCore = await ethers.getContractAt(
GelatoCoreLib.GelatoCore.abi,
network.config.deployments.GelatoCore // the Rinkeby Address of the deployed GelatoCore
);
// Get address of CPKFactoryCustom deployment
cpkFactoryCustomAddress = (await deployments.get("CPKFactoryCustom"))
.address;
// Create CPK instance connected to new mastercopy
cpk = await CPK.create({ ethers, signer: myUserWallet });
expect(await cpk.getOwnerAccount()).to.be.equal(myUserAddress);
const codeAtProxy = await bre.ethers.provider.getCode(cpk.address);
const proxyDeployed = codeAtProxy === "0x" ? false : true;
console.log(`
\n Network: ${bre.network.name}\
\n CPK Proxy address: ${cpk.address}\
\n Proxy deployed?: ${proxyDeployed}\n
`);
if (proxyDeployed === false) {
console.error("Need `yarn setup-proxy` first");
process.exit(1);
}
});
it("Unprovide everything: Executor, Funds, ProviderModule", async function () {
const executorIsAssignedToMe =
(await gelatoCore.executorByProvider(cpk.address)) ==
constants.AddressZero
? false
: true;
if (executorIsAssignedToMe) {
console.log("\n Sending tx to unassign Executor");
// Cleanup TX-1:
try {
const tx = await cpk.execTransactions([
{
operation: CPK.CALL,
to: GELATO,
value: 0,
data: await bre.run("abi-encode-withselector", {
abi: GelatoCoreLib.GelatoCore.abi,
functionname: "providerAssignsExecutor",
inputs: [constants.AddressZero],
}),
},
]);
// Wait for mining
console.log(`Tx Hash: ${tx.hash}`);
await tx.transactionResponse.wait();
expect(await gelatoCore.executorByProvider(cpk.address)).to.be.equal(
constants.AddressZero
);
console.log(`✅ Executor unassigned`);
} catch (error) {
console.error("\n Cleanup: PRE Executor cleanup TX ❌ \n", error);
process.exit(1);
}
} else {
console.log("\n Executor already not assigned ✅ ");
}
// Cleanup TX-3
const providedFunds = await gelatoCore.providerFunds(cpk.address);
const fundsAreProvided = providedFunds.toString() === "0" ? false : true;
if (fundsAreProvided) {
console.log(
`\n Withdrawing ${utils.formatEther(providedFunds)} ETH to userWallet`
);
const prevUserWalletBalance = await myUserWallet.getBalance();
console.log(
`Current funds in User Wallet: ${utils.formatEther(
prevUserWalletBalance
)} ETH`
);
try {
const tx = await cpk.execTransactions(
[
{
operation: CPK.CALL,
to: GELATO,
value: 0,
data: await bre.run("abi-encode-withselector", {
abi: GelatoCoreLib.GelatoCore.abi,
functionname: "unprovideFunds",
inputs: [providedFunds],
}),
},
{
operation: CPK.CALL,
to: myUserAddress,
value: providedFunds,
data: "0x",
},
],
{ gasLimit: 4000000 }
);
// Wait for mining
console.log(`Tx Hash: ${tx.hash}`);
await tx.transactionResponse.wait();
const newFundsOnGelato = await gelatoCore.providerFunds(cpk.address);
expect(newFundsOnGelato).to.be.equal(0);
console.log(
`✅ New funds in Gelato: ${utils.formatEther(newFundsOnGelato)} ETH`
);
const userWalletBalance = await myUserWallet.getBalance();
expect(userWalletBalance).to.be.gt(prevUserWalletBalance);
console.log(
`✅ Funds in UserWallet: ${utils.formatEther(userWalletBalance)} ETH`
);
} catch (error) {
console.error("\n Gelato unprovideFunds error ❌ \n", error);
process.exit(1);
}
} else {
console.log("\n Already no Funds on Gelato ✅ ");
}
// Cleanup TX-3
const moduleIsProvided = await gelatoCore.isModuleProvided(
cpk.address,
PROVIDER_MODULE_GNOSIS
);
if (moduleIsProvided) {
console.log(`\n Removing ProviderModuleGnosisSafeProxy`);
try {
const tx = await cpk.execTransactions([
{
operation: CPK.CALL,
to: GELATO,
value: 0,
data: await bre.run("abi-encode-withselector", {
abi: GelatoCoreLib.GelatoCore.abi,
functionname: "removeProviderModules",
inputs: [[PROVIDER_MODULE_GNOSIS]],
}),
},
]);
// Wait for mining
await tx.transactionResponse.wait();
console.log(`Tx Hash: ${tx.hash}`);
expect(
await gelatoCore.isModuleProvided(cpk.address, PROVIDER_MODULE_GNOSIS)
).to.be.false;
console.log(`✅ ProviderModuleGnosisSafeProxy removed`);
} catch (error) {
console.error("\n Gelato removeProviderModules error ❌ \n", error);
process.exit(1);
}
} else {
console.log("\n Already removed ProviderModule from Gelato ✅ ");
}
});
});

View File

@ -0,0 +1,186 @@
// We require the Buidler Runtime Environment explicitly here. This is optional
// but useful for running the script in a standalone fashion through `node <script>`.
// When running the script with `buidler run <script>` you'll find the Buidler
// Runtime Environment's members available in the global scope.
const bre = require("@nomiclabs/buidler");
const ethers = bre.ethers;
const { utils } = require("ethers");
const fetch = require("node-fetch");
// CPK Library
const CPK = require("contract-proxy-kit");
// running `npx buidler test` automatically makes use of buidler-waffle plugin
// => only dependency we need is "chaFi"
const { expect } = require("chai");
const GelatoCoreLib = require("@gelatonetwork/core");
const GELATO = bre.network.config.deployments.GelatoCore;
// Current Gelato Gas Price
let currentGelatoGasPrice;
// TRIGGER GAS PRICE
let triggerGasPrice;
// The Graph query
const taskWrapperQuery = (proxyAddress) => {
return `
{
taskReceiptWrappers(where: {user: "${proxyAddress}"}) {
taskReceipt {
id
userProxy
provider {
addr
module
}
index
tasks {
conditions {
inst
data
}
actions {
addr
data
operation
dataFlow
value
termsOkCheck
}
selfProviderGasLimit
selfProviderGasPriceCeil
}
expiryDate
cycleId
submissionsLeft
}
submissionHash
status
submissionDate
executionDate
executionHash
selfProvided
}
}`;
};
describe("Canceling ActionCHIMint Task on Gelato via GnosisSafe", function () {
// No timeout for Mocha due to Rinkeby mining latency
this.timeout(0);
// We use our User Wallet. Per our config this wallet is at the accounts index 0
// and hence will be used by default for all transactions we send.
let myUserWallet;
let myUserAddress;
// 2) We will deploy a GnosisSafeProxy using the Factory, or if we already deployed
// one, we will use that one.
let cpk;
let gelatoCore;
before(async function () {
// We get our User Wallet from the Buidler Runtime Env
[myUserWallet] = await bre.ethers.getSigners();
myUserAddress = await myUserWallet.getAddress();
// Create CPK instance connected to new mastercopy
cpk = await CPK.create({ ethers, signer: myUserWallet });
expect(await cpk.getOwnerAccount()).to.be.equal(myUserAddress);
const codeAtProxy = bre.ethers.provider.getCode(cpk.address);
const proxyDeployed = codeAtProxy === "0x" ? false : true;
console.log(`
\n Network: ${bre.network.name}\
\n CPK Proxy address: ${cpk.address}\
\n Proxy deployed?: ${proxyDeployed}\n
`);
gelatoCore = await ethers.getContractAt(
GelatoCoreLib.GelatoCore.abi,
network.config.deployments.GelatoCore // the Rinkeby Address of the deployed GelatoCore
);
currentGelatoGasPrice = await bre.run("fetchGelatoGasPrice");
// FOR TESTING WE SET IT EQUAL TO CURRENT SO WE CAN CHECK FOR EXECUTION
triggerGasPrice = currentGelatoGasPrice;
});
// Submit your Task to Gelato via your GelatoUserProxy
it("User cancels Task as SelfProvider", async function () {
// 1. Fetch all taskReceipts that the UserProxy has submitted
const response = await fetch(
`https://api.thegraph.com/subgraphs/name/gelatodigital/gelato-network-rinkeby`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: taskWrapperQuery(cpk.address.toLowerCase()),
}),
}
);
// 2. Convert Response to Json and get taskReceiptWrappers
const json = await response.json();
const taskReceiptWrappers = json.data.taskReceiptWrappers;
console.log(taskReceiptWrappers);
// 3. Select only the CHi Tasks
let actionChiMint = await deployments.get("ActionChiMint");
const chiActionWrappers = taskReceiptWrappers.filter(
(wrapper) =>
utils.getAddress(wrapper.taskReceipt.tasks[0].actions[0].addr) ===
utils.getAddress(actionChiMint.address)
);
// console.log(chiActionWrappers);
// 4. Get first Chi Task where status == 'awaitingExec'
const taskReceiptWrapper = chiActionWrappers.find(
(wrapper) => wrapper.status === "awaitingExec"
);
console.log(taskReceiptWrapper);
if (taskReceiptWrapper) {
try {
// 5. Decode Task Receipt
const chiActionInputs = ethers.utils.defaultAbiCoder.decode(
["address", "uint256"],
ethers.utils.hexDataSlice(
taskReceiptWrapper.taskReceipt.tasks[0].actions[0].data,
4
)
);
console.log(`Recipient: ${chiActionInputs[0]}`);
console.log(`Chi Amount: ${chiActionInputs[1]}`);
// 6. Cancel Task on gelato if there is a pending task to cancel
const tx = await cpk.execTransactions([
{
operation: CPK.CALL,
to: GELATO,
value: 0,
data: await bre.run("abi-encode-withselector", {
abi: GelatoCoreLib.GelatoCore.abi,
functionname: "cancelTask",
inputs: [taskReceiptWrapper.taskReceipt],
}),
},
]);
// Wait for mining
console.log(`Cancel Task Receipt Tx Hash: ${tx.hash}`);
await tx.transactionResponse.wait();
console.log(`Cancel Task Receipt Success!`);
} catch (error) {
console.error("\n PRE Cancel Task Receipt error ❌ \n", error);
process.exit(1);
}
}
});
});

View File

@ -0,0 +1,185 @@
// running `npx buidler test` automatically makes use of buidler-waffle plugin
// => only dependency we need is "chai"
const { expect } = require("chai");
const bre = require("@nomiclabs/buidler");
const { ethers } = bre;
const GelatoCoreLib = require("@gelatonetwork/core");
// Constants
const INSTA_MASTER = "0xfCD22438AD6eD564a1C26151Df73F6B33B817B56";
const ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
const DAI_100 = ethers.utils.parseUnits("100", 18);
// Contracts
const InstaIndex = require("../pre-compiles/InstaIndex.json");
const InstaConnectors = require("../pre-compiles/InstaConnectors.json");
const InstaList = require("../pre-compiles/InstaList.json");
const InstaAccount = require("../pre-compiles/InstaAccount.json");
const ConnectAuth = require("../pre-compiles/ConnectAuth.json");
const ConnectMaker = require("../pre-compiles/ConnectMaker.json");
const ConnectCompound = require("../pre-compiles/ConnectCompound.json");
const IERC20 = require("../pre-compiles/IERC20.json");
const IUniswapExchange = require("../pre-compiles/IUniswapExchange.json");
describe("Move DAI lending from DSR to Compound", function () {
this.timeout(0);
if (bre.network.name !== "ganache") {
console.error("Test Suite is meant to be run on ganache only");
process.exit(1);
}
// Wallet to use for local testing
let userWallet;
let userAddress;
let dsaAddress;
// Deployed instances
let connectMaker;
let connectCompound;
let gelatoCore;
let dai;
// Contracts to deploy and use for local testing
let dsa;
before(async function () {
// Get Test Wallet for local testnet
[userWallet] = await ethers.getSigners();
userAddress = await userWallet.getAddress();
const instaMaster = await ethers.provider.getSigner(INSTA_MASTER);
// Ganache default accounts prefilled with 100 ETH
expect(await userWallet.getBalance()).to.be.equal(
ethers.utils.parseEther("100")
);
// ===== DSA SETUP ==================
const instaIndex = await ethers.getContractAt(
InstaIndex.abi,
bre.network.config.InstaIndex
);
const instaList = await ethers.getContractAt(
InstaList.abi,
bre.network.config.InstaList
);
const instaConnectors = await ethers.getContractAt(
InstaConnectors.abi,
bre.network.config.InstaConnectors
);
connectMaker = await ethers.getContractAt(
ConnectMaker.abi,
bre.network.config.ConnectMaker
);
connectCompound = await ethers.getContractAt(
ConnectCompound.abi,
bre.network.config.ConnectCompound
);
// Deploy DSA and get and verify ID of newly deployed DSA
const dsaIDPrevious = await instaList.accounts();
await expect(instaIndex.build(userAddress, 1, userAddress)).to.emit(
instaIndex,
"LogAccountCreated"
);
const dsaID = dsaIDPrevious.add(1);
await expect(await instaList.accounts()).to.be.equal(dsaID);
// Instantiate the DSA
dsaAddress = await instaList.accountAddr(dsaID);
dsa = await ethers.getContractAt(InstaAccount.abi, dsaAddress);
// ===== GELATO SETUP ==================
gelatoCore = await ethers.getContractAt(
GelatoCoreLib.GelatoCore.abi,
bre.network.config.GelatoCore
);
// Add GelatoCore as auth on DSA
const addAuthData = await bre.run("abi-encode-withselector", {
abi: ConnectAuth.abi,
functionname: "add",
inputs: [gelatoCore.address],
});
await dsa.cast(
[bre.network.config.ConnectAuth],
[addAuthData],
userAddress
);
expect(await dsa.isAuth(gelatoCore.address)).to.be.true;
// Deploy ConnectGelato to local testnet
// first query the correct connectorID
const connectorLength = await instaConnectors.connectorLength();
const connectorId = connectorLength.add(1);
const ConnectGelato = await ethers.getContractFactory("ConnectGelato");
const connectGelato = await ConnectGelato.deploy(
connectorId,
gelatoCore.address
);
await connectGelato.deployed();
// Enable ConnectGelato on InstaConnectors via InstaMaster multisig
// Send some ETH to the InstaMaster multi_sig
await userWallet.sendTransaction({
to: INSTA_MASTER,
value: ethers.utils.parseEther("0.1"),
});
await instaConnectors.connect(instaMaster).enable(connectGelato.address);
expect(
await instaConnectors.isConnector([connectGelato.address])
).to.be.true;
// Deploy ProviderModuleDSA to local testnet
const ProviderModuleDSA = await ethers.getContractFactory(
"ProviderModuleDSA"
);
providerModuleDSA = await ProviderModuleDSA.deploy(
instaIndex.address,
gelatoCore.address
);
await providerModuleDSA.deployed();
// ===== Dapp Dependencies SETUP ==================
// This test assumes our user has 100 DAI deposited in Maker DSR
dai = await ethers.getContractAt(IERC20.abi, bre.network.config.DAI);
expect(await dai.balanceOf(userAddress)).to.be.equal(0);
// Let's get the test user 100 DAI++ from Kyber
const daiUniswapExchange = await ethers.getContractAt(
IUniswapExchange.abi,
bre.network.config.DAI_UNISWAP
);
await daiUniswapExchange.ethToTokenTransferInput(
1,
2525644800, // random timestamp in the future (year 2050)
userAddress,
{
value: ethers.utils.parseEther("2"),
}
);
expect(await dai.balanceOf(userAddress)).to.be.gte(DAI_100);
// Next we transfer the 100 DAI into our DSA
await dai.transfer(dsa.address, DAI_100);
expect(await dai.balanceOf(dsa.address)).to.be.eq(DAI_100);
// Next we deposit the 100 DAI into the DSR
const depositDai = await bre.run("abi-encode-withselector", {
abi: ConnectMaker.abi,
functionname: "depositDai",
inputs: [DAI_100, 0, 0],
});
await expect(
dsa.cast([bre.network.config.ConnectMaker], [depositDai], userAddress)
)
.to.emit(dsa, "LogCast")
.withArgs(userAddress, userAddress, 0);
expect(await dai.balanceOf(dsa.address)).to.be.eq(0);
});
it("#1: Deploys a DSA with user as authority", async function () {
expect(await dsa.isAuth(userAddress)).to.be.true;
});
});

View File

@ -0,0 +1,281 @@
// running `npx buidler test` automatically makes use of buidler-waffle plugin
// => only dependency we need is "chai"
const { expect } = require("chai");
const bre = require("@nomiclabs/buidler");
const { ethers } = bre;
const GelatoCoreLib = require("@gelatonetwork/core");
const { sleep } = GelatoCoreLib;
// Constants
const INSTA_MASTER = "0xfCD22438AD6eD564a1C26151Df73F6B33B817B56";
const ETH = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
// Contracts
const InstaIndex = require("../pre-compiles/InstaIndex.json");
const InstaList = require("../pre-compiles/InstaList.json");
const InstaConnectors = require("../pre-compiles/InstaConnectors.json");
const InstaAccount = require("../pre-compiles/InstaAccount.json");
const ConnectAuth = require("../pre-compiles/ConnectAuth.json");
const ConnectBasic = require("../pre-compiles/ConnectBasic.json");
describe("DSA setup with Gelato Tests", function () {
this.timeout(50000);
if (bre.network.name !== "ganache") {
console.error("Test Suite is meant to be run on ganache only");
process.exit(1);
}
// Wallet to use for local testing
let userWallet;
let userAddress;
let dsaAddress;
let instaMaster;
// Deployed instances
let instaIndex;
let instaList;
let instaConnectors;
let instaAccount;
let gelatoCore;
// Contracts to deploy and use for local testing
let dsa;
let providerModuleDSA;
let connectGelato;
// Other variables
let dsaVersion;
let dsaID;
before(async function () {
// Get Test Wallet for local testnet
[userWallet] = await ethers.getSigners();
userAddress = await userWallet.getAddress();
instaMaster = await ethers.provider.getSigner(INSTA_MASTER);
// ===== DSA LOCAL SETUP ==================
instaIndex = await ethers.getContractAt(
InstaIndex.abi,
bre.network.config.InstaIndex
);
instaList = await ethers.getContractAt(
InstaList.abi,
bre.network.config.InstaList
);
instaConnectors = await ethers.getContractAt(
InstaConnectors.abi,
bre.network.config.InstaConnectors
);
instaAccount = await ethers.getContractAt(
InstaAccount.abi,
bre.network.config.InstaAccount
);
dsaVersion = await instaAccount.version();
dsaID = await instaList.accounts();
// Deploy DSA and get and verify ID of newly deployed DSA
await expect(instaIndex.build(userAddress, 1, userAddress)).to.emit(
instaIndex,
"LogAccountCreated"
);
await expect(await instaList.accounts()).to.be.equal(dsaID.add(1));
dsaID = dsaID.add(1);
// Instantiate the DSA
dsaAddress = await instaList.accountAddr(dsaID);
dsa = await ethers.getContractAt(InstaAccount.abi, dsaAddress);
// ===== GELATO LOCAL SETUP ==================
gelatoCore = await ethers.getContractAt(
GelatoCoreLib.GelatoCore.abi,
bre.network.config.GelatoCore
);
// Deploy ConnectGelato to local testnet
// first query the correct connectorID
const connectorLength = await instaConnectors.connectorLength();
const connectorId = connectorLength.add(1);
const ConnectGelato = await ethers.getContractFactory("ConnectGelato");
connectGelato = await ConnectGelato.deploy(connectorId, gelatoCore.address);
await connectGelato.deployed();
// Deploy ProviderModuleDSA to local testnet
const ProviderModuleDSA = await ethers.getContractFactory(
"ProviderModuleDSA"
);
providerModuleDSA = await ProviderModuleDSA.deploy(
instaIndex.address,
gelatoCore.address
);
await providerModuleDSA.deployed();
});
it("#1: Forks InstaDapp Mainnet config", async function () {
expect(await instaIndex.list()).to.be.equal(instaList.address);
expect(dsaVersion).to.be.equal(1);
expect(await instaIndex.connectors(dsaVersion)).to.be.equal(
instaConnectors.address
);
expect(await instaConnectors.connectors(bre.network.config.ConnectAuth)).to
.be.true;
expect(await instaConnectors.connectors(bre.network.config.ConnectBasic)).to
.be.true;
expect(await instaConnectors.connectors(bre.network.config.ConnectMaker)).to
.be.true;
expect(await instaConnectors.connectors(bre.network.config.ConnectCompound))
.to.be.true;
});
it("#2: Deploys a DSA with user as authority", async function () {
expect(await dsa.isAuth(userAddress)).to.be.true;
});
it("#3: Let's User deposit and withdraw funds from DSA", async function () {
// Send withdraw TX via DSA.cast delegatecall
const gasLimit = ethers.BigNumber.from(1000000);
const gasPrice = ethers.utils.parseUnits("20", "gwei");
const gasCostMax = gasLimit.mul(gasPrice);
// Deposit funds into DSA
const initialWalletBalance = await userWallet.getBalance();
expect(await ethers.provider.getBalance(dsaAddress)).to.be.equal(0);
await userWallet.sendTransaction({
to: dsaAddress,
value: ethers.utils.parseEther("1"),
gasLimit,
gasPrice,
});
expect(await userWallet.getBalance()).to.be.lt(
initialWalletBalance.sub(ethers.utils.parseEther("1"))
);
expect(await ethers.provider.getBalance(dsaAddress)).to.be.equal(
ethers.utils.parseEther("1")
);
// Encode Payloads for ConnectBasic.withdraw
const withdrawData = await bre.run("abi-encode-withselector", {
abi: ConnectBasic.abi,
functionname: "withdraw",
inputs: [ETH, ethers.utils.parseEther("1"), userAddress, 0, 0],
});
await expect(
dsa.cast([bre.network.config.ConnectBasic], [withdrawData], userAddress, {
gasLimit,
gasPrice,
})
)
.to.emit(dsa, "LogCast")
.withArgs(userAddress, userAddress, 0);
expect(await ethers.provider.getBalance(dsaAddress)).to.be.equal(0);
expect(await userWallet.getBalance()).to.be.gte(
initialWalletBalance.sub(gasCostMax.mul(2))
);
});
it("#4: Enables GelatoCore as a User of the DSA", async function () {
expect(await dsa.isAuth(gelatoCore.address)).to.be.false;
// Encode Payloads for ConnectAuth.addModule
const addAuthData = await bre.run("abi-encode-withselector", {
abi: ConnectAuth.abi,
functionname: "add",
inputs: [gelatoCore.address],
});
await expect(
dsa.cast([bre.network.config.ConnectAuth], [addAuthData], userAddress)
)
.to.emit(dsa, "LogCast")
.withArgs(userAddress, userAddress, 0);
expect(await dsa.isAuth(gelatoCore.address)).to.be.true;
});
it("#5: Allows unlocked InstaDapp master to enable Gelato connector", async function () {
expect(await instaConnectors.isConnector([connectGelato.address])).to.be
.false;
// Send some ETH to the InstaMaster multi_sig
await userWallet.sendTransaction({
to: INSTA_MASTER,
value: ethers.utils.parseEther("0.1"),
});
// Enable ConnectGelato on InstaConnectors via InstaMaster multisig
await expect(
instaConnectors.connect(instaMaster).enable(connectGelato.address)
)
.to.emit(instaConnectors, "LogEnable")
.withArgs(connectGelato.address);
expect(await instaConnectors.isConnector([connectGelato.address])).to.be
.true;
});
it("#6: Gelato ProviderModuleDSA returns correct execPayload", async function () {
// Deposit 1 ETH into DSA
await userWallet.sendTransaction({
to: dsaAddress,
value: ethers.utils.parseEther("1"),
});
expect(await ethers.provider.getBalance(dsaAddress)).to.be.equal(
ethers.utils.parseEther("1")
);
// We withdraw to otherWallet to ignore gasUsed during test
const { 1: otherWallet } = await ethers.getSigners();
// Instantiate Gelato ConnectBasic.withdraw Task
const withdrawFromDSATask = new GelatoCoreLib.Task({
actions: [
new GelatoCoreLib.Action({
addr: bre.network.config.ConnectBasic,
data: await bre.run("abi-encode-withselector", {
abi: ConnectBasic.abi,
functionname: "withdraw",
inputs: [
ETH,
ethers.utils.parseEther("1"),
await otherWallet.getAddress(),
0,
0,
],
}),
operation: GelatoCoreLib.Operation.Delegatecall, // placeholder
}),
],
});
// otherWallet needs to be an authority to qualify as withdraw to address.
const addAuthData = await bre.run("abi-encode-withselector", {
abi: ConnectAuth.abi,
functionname: "add",
inputs: [await otherWallet.getAddress()],
});
await dsa.cast(
[bre.network.config.ConnectAuth],
[addAuthData],
userAddress
);
const [execPayload] = await providerModuleDSA.execPayload(
0, // placeholder
ethers.constants.AddressZero, // placeholder
ethers.constants.AddressZero, // placeholder
withdrawFromDSATask,
0 // placeholder
);
await expect(() =>
userWallet.sendTransaction({
to: dsaAddress,
data: execPayload,
})
).to.changeBalance(otherWallet, ethers.utils.parseEther("1"));
expect(await ethers.provider.getBalance(dsaAddress)).to.be.equal(0);
});
});

8512
yarn.lock Normal file

File diff suppressed because it is too large Load Diff