mirror of
https://github.com/Instadapp/Gelato-automations.git
synced 2024-07-29 22:28:07 +00:00
Gelato DSA setup tests complete
This commit is contained in:
parent
7c7112fec3
commit
095c036ea3
.gitattributes.gitignore
.vscode
buidler.config.jscontracts
ConditionCompareUintsFromTwoSources.solConditionHasBalanceAndAllowance.solConnectGelato.solGelatoBytes.solMockCDAI.solMockDSR.solProviderModuleDSA.sol
package.jsonpre-compiles
ConnectAuth.jsonConnectBasic.jsonConnectCompound.jsonConnectMaker.jsonIERC20.jsonIUniswapExchange.jsonInstaAccount.jsonInstaConnectors.jsonInstaIndex.jsonInstaList.json
rinkeby-test
0-1-tx-onboarding.js1-GnosisSafe-Gelato-setup.js2-submitTask.js3-unprovide-ETH.js4-transfer-funds-from-GnosisSafe.js5-multiUnprovide.js6-cancelTask.js
test
yarn.lock
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.sol linguist-language=Solidity
|
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal 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
29
.vscode/settings.json
vendored
Normal 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
154
buidler.config.js
Normal 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);
|
||||
}
|
||||
});
|
102
contracts/ConditionCompareUintsFromTwoSources.sol
Normal file
102
contracts/ConditionCompareUintsFromTwoSources.sol
Normal 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";
|
||||
}
|
||||
}
|
75
contracts/ConditionHasBalanceAndAllowance.sol
Normal file
75
contracts/ConditionHasBalanceAndAllowance.sol
Normal 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
136
contracts/ConnectGelato.sol
Normal 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
67
contracts/GelatoBytes.sol
Normal 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
17
contracts/MockCDAI.sol
Normal 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
19
contracts/MockDSR.sol
Normal 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;
|
||||
}
|
||||
}
|
104
contracts/ProviderModuleDSA.sol
Normal file
104
contracts/ProviderModuleDSA.sol
Normal 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
22
package.json
Normal 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": {}
|
||||
}
|
104
pre-compiles/ConnectAuth.json
Normal file
104
pre-compiles/ConnectAuth.json
Normal 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": {}
|
||||
}
|
169
pre-compiles/ConnectBasic.json
Normal file
169
pre-compiles/ConnectBasic.json
Normal file
File diff suppressed because one or more lines are too long
549
pre-compiles/ConnectCompound.json
Normal file
549
pre-compiles/ConnectCompound.json
Normal file
File diff suppressed because one or more lines are too long
639
pre-compiles/ConnectMaker.json
Normal file
639
pre-compiles/ConnectMaker.json
Normal file
File diff suppressed because one or more lines are too long
192
pre-compiles/IERC20.json
Normal file
192
pre-compiles/IERC20.json
Normal 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": {}
|
||||
}
|
318
pre-compiles/IUniswapExchange.json
Normal file
318
pre-compiles/IUniswapExchange.json
Normal 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": {}
|
||||
}
|
197
pre-compiles/InstaAccount.json
Normal file
197
pre-compiles/InstaAccount.json
Normal file
File diff suppressed because one or more lines are too long
324
pre-compiles/InstaConnectors.json
Normal file
324
pre-compiles/InstaConnectors.json
Normal file
File diff suppressed because one or more lines are too long
387
pre-compiles/InstaIndex.json
Normal file
387
pre-compiles/InstaIndex.json
Normal file
File diff suppressed because one or more lines are too long
228
pre-compiles/InstaList.json
Normal file
228
pre-compiles/InstaList.json
Normal file
File diff suppressed because one or more lines are too long
389
rinkeby-test/0-1-tx-onboarding.js
Normal file
389
rinkeby-test/0-1-tx-onboarding.js
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
264
rinkeby-test/1-GnosisSafe-Gelato-setup.js
Normal file
264
rinkeby-test/1-GnosisSafe-Gelato-setup.js
Normal 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");
|
||||
}
|
||||
});
|
||||
});
|
197
rinkeby-test/2-submitTask.js
Normal file
197
rinkeby-test/2-submitTask.js
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
129
rinkeby-test/3-unprovide-ETH.js
Normal file
129
rinkeby-test/3-unprovide-ETH.js
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
95
rinkeby-test/4-transfer-funds-from-GnosisSafe.js
Normal file
95
rinkeby-test/4-transfer-funds-from-GnosisSafe.js
Normal 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);
|
||||
}
|
||||
});
|
||||
});
|
200
rinkeby-test/5-multiUnprovide.js
Normal file
200
rinkeby-test/5-multiUnprovide.js
Normal 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 ✅ ");
|
||||
}
|
||||
});
|
||||
});
|
186
rinkeby-test/6-cancelTask.js
Normal file
186
rinkeby-test/6-cancelTask.js
Normal 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
185
test/mv-DAI-DSR-Compound.test.js
Normal file
185
test/mv-DAI-DSR-Compound.test.js
Normal 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;
|
||||
});
|
||||
});
|
281
test/setup-DSA-Gelato.test.js
Normal file
281
test/setup-DSA-Gelato.test.js
Normal 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);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user