Commit 416d2e60 authored by smartcontracts's avatar smartcontracts Committed by GitHub

feat(ctp): introduce Drippie and rework deploy (#2569)

Drippie is a helper contract for managing automatic contract
interactions. Drippie is meant to be deployed as a middle layer between
our contracts and other automation services like Gelato. This commit
also revamps the deployment process to use hardhat-deploy deterministic
deployments, ledger support, and better authentication.
parent c8428772
---
'@eth-optimism/contracts-periphery': patch
---
Introduce the Drippie peripheral contract for managing ETH drips
......@@ -116,6 +116,19 @@ jobs:
command: yarn test:slither
working_directory: packages/contracts
contracts-periphery-slither:
docker:
- image: ethereumoptimism/js-builder:latest
steps:
- restore_cache:
keys:
- v2-cache-yarn-build-{{ .Revision }}
- checkout
- run:
name: Run Slither
command: yarn test:slither
working_directory: packages/contracts-periphery
contracts-tests:
docker:
- image: ethereumoptimism/js-builder:latest
......@@ -155,6 +168,10 @@ jobs:
name: Lint
command: yarn lint:check
working_directory: packages/contracts-periphery
- run:
name: Slither
command: yarn test:slither
working_directory: packages/contracts
- run:
name: Test
command: yarn test:coverage
......
# Etherscan API key for Ethereum and Ethereum testnets
ETHERSCAN_API_KEY=
ETHEREUM_ETHERSCAN_API_KEY=
# Etherscan API key for Optimism and Optimism testnets
OPTIMISTIC_ETHERSCAN_API_KEY=
# Ledger required to deploy stuff, insert address here
LEDGER_ADDRESS=
# Required to deploy to Ethereum or Ethereum testnets
INFURA_PROJECT_ID=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract CallRecorder {
struct CallInfo {
address sender;
bytes data;
uint256 gas;
uint256 value;
}
CallInfo public lastCall;
function record() public payable {
lastCall.sender = msg.sender;
lastCall.data = msg.data;
lastCall.gas = gasleft();
lastCall.value = msg.value;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract Reverter {
function doRevert() public pure {
revert("Reverter reverted");
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
contract SimpleStorage {
mapping(bytes32 => bytes32) public db;
function set(bytes32 _key, bytes32 _value) public payable {
db[_key] = _value;
}
function get(bytes32 _key) public view returns (bytes32) {
return db[_key];
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { Owned } from "@rari-capital/solmate/src/auth/Owned.sol";
import { ERC20 } from "@rari-capital/solmate/src/tokens/ERC20.sol";
import { ERC721 } from "@rari-capital/solmate/src/tokens/ERC721.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { Transactor } from "./Transactor.sol";
/**
* @title RetroReceiver
* @notice RetroReceiver is a minimal contract for receiving funds, meant to be deployed at the
* same address on every chain that supports EIP-2470.
* @title AssetReceiver
* @notice AssetReceiver is a minimal contract for receiving funds assets in the form of either
* ETH, ERC20 tokens, or ERC721 tokens. Only the contract owner may withdraw the assets.
*/
contract RetroReceiver is Owned {
contract AssetReceiver is Transactor {
/**
* Emitted when ETH is received by this address.
*/
......@@ -42,9 +42,9 @@ contract RetroReceiver is Owned {
);
/**
* @param _owner Address to initially own the contract.
* @param _owner Initial contract owner.
*/
constructor(address _owner) Owned(_owner) {}
constructor(address _owner) Transactor(_owner) {}
/**
* Make sure we can receive ETH.
......@@ -58,7 +58,7 @@ contract RetroReceiver is Owned {
*
* @param _to Address to receive the ETH balance.
*/
function withdrawETH(address payable _to) public onlyOwner {
function withdrawETH(address payable _to) external onlyOwner {
withdrawETH(_to, address(this).balance);
}
......@@ -69,6 +69,7 @@ contract RetroReceiver is Owned {
* @param _amount Amount of ETH to withdraw.
*/
function withdrawETH(address payable _to, uint256 _amount) public onlyOwner {
// slither-disable-next-line reentrancy-unlimited-gas
_to.transfer(_amount);
emit WithdrewETH(msg.sender, _to, _amount);
}
......@@ -79,7 +80,7 @@ contract RetroReceiver is Owned {
* @param _asset ERC20 token to withdraw.
* @param _to Address to receive the ERC20 balance.
*/
function withdrawERC20(ERC20 _asset, address _to) public onlyOwner {
function withdrawERC20(ERC20 _asset, address _to) external onlyOwner {
withdrawERC20(_asset, _to, _asset.balanceOf(address(this)));
}
......@@ -95,7 +96,9 @@ contract RetroReceiver is Owned {
address _to,
uint256 _amount
) public onlyOwner {
// slither-disable-next-line unchecked-transfer
_asset.transfer(_to, _amount);
// slither-disable-next-line reentrancy-events
emit WithdrewERC20(msg.sender, _to, address(_asset), _amount);
}
......@@ -110,8 +113,9 @@ contract RetroReceiver is Owned {
ERC721 _asset,
address _to,
uint256 _id
) public onlyOwner {
) external onlyOwner {
_asset.transferFrom(address(this), _to, _id);
// slither-disable-next-line reentrancy-events
emit WithdrewERC721(msg.sender, _to, address(_asset), _id);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title Transactor
* @notice Transactor is a minimal contract that can send transactions.
*/
contract Transactor is Ownable {
/**
* @param _owner Initial contract owner.
*/
constructor(address _owner) Ownable() {
transferOwnership(_owner);
}
/**
* Sends a CALL to a target address.
*
* @param _target Address to call.
* @param _data Data to send with the call.
* @param _gas Amount of gas to send with the call.
* @param _value ETH value to send with the call.
* @return Boolean success value.
* @return Bytes data returned by the call.
*/
function CALL(
address _target,
bytes memory _data,
uint256 _gas,
uint256 _value
) external payable onlyOwner returns (bool, bytes memory) {
return _target.call{ gas: _gas, value: _value }(_data);
}
/**
* Sends a DELEGATECALL to a target address.
*
* @param _target Address to call.
* @param _data Data to send with the call.
* @param _gas Amount of gas to send with the call.
* @return Boolean success value.
* @return Bytes data returned by the call.
*/
function DELEGATECALL(
address _target,
bytes memory _data,
uint256 _gas
) external payable onlyOwner returns (bool, bytes memory) {
// slither-disable-next-line controlled-delegatecall
return _target.delegatecall{ gas: _gas }(_data);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { AssetReceiver } from "../AssetReceiver.sol";
import { IDripCheck } from "./IDripCheck.sol";
/**
* @title Drippie
* @notice Drippie is a system for managing automated contract interactions. A specific interaction
* is called a "drip" and can be executed according to some condition (called a dripcheck) and an
* execution interval. Drips cannot be executed faster than the execution interval. Drips can
* trigger arbitrary contract calls where the calling contract is this contract address. Drips can
* also send ETH value, which makes them ideal for keeping addresses sufficiently funded with ETH.
* Drippie is designed to be connected with smart contract automation services so that drips can be
* executed automatically. However, Drippie is specifically designed to be separated from these
* services so that trust assumptions are better compartmentalized.
*/
contract Drippie is AssetReceiver {
/**
* Enum representing different status options for a given drip.
*/
enum DripStatus {
NONE,
ACTIVE,
PAUSED,
ARCHIVED
}
/**
* Represents a drip action.
*/
struct DripAction {
address payable target;
bytes data;
uint256 value;
}
/**
* Represents the configuration for a given drip.
*/
struct DripConfig {
uint256 interval;
IDripCheck dripcheck;
bytes checkparams;
DripAction[] actions;
}
/**
* Represents the state of an active drip.
*/
struct DripState {
DripStatus status;
DripConfig config;
uint256 last;
}
/**
* Emitted when a new drip is created.
*/
event DripCreated(string indexed name, DripConfig config);
/**
* Emitted when a drip status is updated.
*/
event DripStatusUpdated(string indexed name, DripStatus status);
/**
* Emitted when a drip is executed.
*/
event DripExecuted(string indexed name, address indexed executor, uint256 timestamp);
/**
* Maps from drip names to drip states.
*/
mapping(string => DripState) public drips;
/**
* @param _owner Initial contract owner.
*/
constructor(address _owner) AssetReceiver(_owner) {}
/**
* Creates a new drip with the given name and configuration. Once created, drips cannot be
* modified in any way (this is a security measure). If you want to update a drip, simply pause
* (and potentially archive) the existing drip and create a new one.
*
* @param _name Name of the drip.
* @param _config Configuration for the drip.
*/
function create(string memory _name, DripConfig memory _config) external onlyOwner {
// Make sure this drip doesn't already exist. We *must* guarantee that no other function
// will ever set the status of a drip back to NONE after it's been created. This is why
// archival is a separate status.
require(
drips[_name].status == DripStatus.NONE,
"Drippie: drip with that name already exists"
);
// We initialize this way because Solidity won't let us copy arrays into storage yet.
DripState storage state = drips[_name];
state.status = DripStatus.PAUSED;
state.config.interval = _config.interval;
state.config.dripcheck = _config.dripcheck;
state.config.checkparams = _config.checkparams;
// Solidity doesn't let us copy arrays into storage, so we push each array one by one.
for (uint256 i = 0; i < _config.actions.length; i++) {
state.config.actions.push(_config.actions[i]);
}
// Tell the world!
emit DripCreated(_name, _config);
}
/**
* Sets the status for a given drip. The behavior of this function depends on the status that
* the user is trying to set. A drip can always move between ACTIVE and PAUSED, but it can
* never move back to NONE and once ARCHIVED, it can never move back to ACTIVE or PAUSED.
*
* @param _name Name of the drip to update.
* @param _status New drip status.
*/
function status(string memory _name, DripStatus _status) external onlyOwner {
// Make sure we can never set drip status back to NONE. A simple security measure to
// prevent accidental overwrites if this code is ever updated down the line.
require(
_status != DripStatus.NONE,
"Drippie: drip status can never be set back to NONE after creation"
);
// Make sure the drip in question actually exists. Not strictly necessary but there doesn't
// seem to be any clear reason why you would want to do this, and it may save some gas in
// the case of a front-end bug.
require(
drips[_name].status != DripStatus.NONE,
"Drippie: drip with that name does not exist"
);
// Once a drip has been archived, it cannot be un-archived. This is, after all, the entire
// point of archiving a drip.
require(
drips[_name].status != DripStatus.ARCHIVED,
"Drippie: drip with that name has been archived"
);
// Although not strictly necessary, we make sure that the status here is actually changing.
// This may save the client some gas if there's a front-end bug and the user accidentally
// tries to "change" the status to the same value as before.
require(
drips[_name].status != _status,
"Drippie: cannot set drip status to same status as before"
);
// If the user is trying to archive this drip, make sure the drip has been paused. We do
// not allow users to archive active drips so that the effects of this action are more
// abundantly clear.
if (_status == DripStatus.ARCHIVED) {
require(
drips[_name].status == DripStatus.PAUSED,
"Drippie: drip must be paused to be archived"
);
}
// If we made it here then we can safely update the status.
drips[_name].status = _status;
emit DripStatusUpdated(_name, drips[_name].status);
}
/**
* Triggers a drip. This function is deliberately left as a public function because the
* assumption being made here is that setting the drip to ACTIVE is an affirmative signal that
* the drip should be executable according to the drip parameters, drip check, and drip
* interval. Note that drip parameters are read entirely from the state and are not supplied as
* user input, so there should not be any way for a non-authorized user to influence the
* behavior of the drip.
*
* @param _name Name of the drip to trigger.
*/
function drip(string memory _name) external {
DripState storage state = drips[_name];
// Only allow active drips to be executed, an obvious security measure.
require(
state.status == DripStatus.ACTIVE,
"Drippie: selected drip does not exist or is not currently active"
);
// Don't drip if the drip interval has not yet elapsed since the last time we dripped. This
// is a safety measure that prevents a malicious recipient from, e.g., spending all of
// their funds and repeatedly requesting new drips. Limits the potential impact of a
// compromised recipient to just a single drip interval, after which the drip can be paused
// by the owner address.
require(
state.last + state.config.interval <= block.timestamp,
"Drippie: drip interval has not elapsed since last drip"
);
// Make sure we're allowed to execute this drip.
require(
state.config.dripcheck.check(state.config.checkparams),
"Drippie: dripcheck failed so drip is not yet ready to be triggered"
);
// Update the last execution time for this drip before the call. Note that it's entirely
// possible for a drip to be executed multiple times per block or even multiple times
// within the same transaction (via re-entrancy) if the drip interval is set to zero. Users
// should set a drip interval of 1 if they'd like the drip to be executed only once per
// block (since this will then prevent re-entrancy).
state.last = block.timestamp;
// Execute each action in the drip. We allow drips to have multiple actions because there
// are scenarios in which a contract must do multiple things atomically. For example, the
// contract may need to withdraw ETH from one account and then deposit that ETH into
// another account within the same transaction.
uint256 len = state.config.actions.length;
for (uint256 i = 0; i < len; i++) {
// Must be marked as "storage" because copying structs into memory is not yet supported
// by Solidity. Won't significantly reduce gas costs but at least makes it easier to
// read what the rest of this section is doing.
DripAction storage action = state.config.actions[i];
// Actually execute the action. We could use ExcessivelySafeCall here but not strictly
// necessary (worst case, a drip gets bricked IFF the target is malicious, doubt this
// will ever happen in practice). Could save a marginal amount of gas to ignore the
// returndata.
// slither-disable-next-line calls-loop
(bool success, ) = action.target.call{ value: action.value }(action.data);
// Generally should not happen, but could if there's a misconfiguration (e.g., passing
// the wrong data to the target contract), the recipient is not payable, or
// insufficient gas was supplied to this transaction. We revert so the drip can be
// fixed and triggered again later. Means we cannot emit an event to alert of the
// failure, but can reasonably be detected by off-chain services even without an event.
// Note that this forces the drip executor to supply sufficient gas to the call
// (assuming there is some sufficient gas limit that exists, otherwise the drip will
// not execute).
require(
success,
"Drippie: drip was unsuccessful, please check your configuration for mistakes"
);
}
emit DripExecuted(_name, msg.sender, block.timestamp);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
interface IDripCheck {
// DripCheck contracts that want to take parameters as inputs MUST expose a struct called
// Params and an event _EventForExposingParamsStructInABI(Params params). This makes it
// possible to easily encode parameters on the client side. Solidity does not support generics
// so it's not possible to do this with explicit typing.
function check(bytes memory _params) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { IDripCheck } from "../IDripCheck.sol";
/**
* @title CheckBalanceHigh
* @notice DripCheck for checking if an account's balance is above a given threshold.
*/
contract CheckBalanceHigh is IDripCheck {
event _EventToExposeStructInABI__Params(Params params);
struct Params {
address target;
uint256 threshold;
}
function check(bytes memory _params) external view returns (bool) {
Params memory params = abi.decode(_params, (Params));
// Check target balance is above threshold.
return params.target.balance > params.threshold;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { IDripCheck } from "../IDripCheck.sol";
/**
* @title CheckBalanceLow
* @notice DripCheck for checking if an account's balance is below a given threshold.
*/
contract CheckBalanceLow is IDripCheck {
event _EventToExposeStructInABI__Params(Params params);
struct Params {
address target;
uint256 threshold;
}
function check(bytes memory _params) external view returns (bool) {
Params memory params = abi.decode(_params, (Params));
// Check target ETH balance is below threshold.
return params.target.balance < params.threshold;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { IDripCheck } from "../IDripCheck.sol";
interface IGelatoTreasury {
function userTokenBalance(address _user, address _token) external view returns (uint256);
}
/**
* @title CheckGelatoLow
* @notice DripCheck for checking if an account's Gelato ETH balance is below some threshold.
*/
contract CheckGelatoLow is IDripCheck {
event _EventToExposeStructInABI__Params(Params params);
struct Params {
address treasury;
uint256 threshold;
address recipient;
}
function check(bytes memory _params) external view returns (bool) {
Params memory params = abi.decode(_params, (Params));
// Check GelatoTreasury ETH balance is below threshold.
return
IGelatoTreasury(params.treasury).userTokenBalance(
params.recipient,
// Gelato represents ETH as 0xeeeee....eeeee
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE
) < params.threshold;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import { IDripCheck } from "../IDripCheck.sol";
/**
* @title CheckTrue
* @notice DripCheck that always returns true.
*/
contract CheckTrue is IDripCheck {
function check(bytes memory) external pure returns (bool) {
return true;
}
}
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { getDeployConfig } from '../src'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const config = getDeployConfig(hre.network.name)
const { deploy } = await hre.deployments.deterministic('AssetReceiver', {
salt: hre.ethers.utils.solidityKeccak256(['string'], ['RetroReceiver']),
from: deployer,
args: [config.retroReceiverOwner],
log: true,
})
await deploy()
}
deployFn.tags = ['RetroReceiver']
deployFn.dependencies = ['OptimismAuthority']
export default deployFn
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
import { getDeployConfig } from '../../src'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const config = getDeployConfig(hre.network.name)
const { deploy } = await hre.deployments.deterministic('Drippie', {
salt: hre.ethers.utils.solidityKeccak256(['string'], ['Drippie']),
from: deployer,
args: [config.drippieOwner],
log: true,
})
await deploy()
}
deployFn.tags = ['Drippie']
deployFn.dependencies = ['OptimismAuthority']
export default deployFn
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const { deploy } = await hre.deployments.deterministic('CheckBalanceHigh', {
salt: hre.ethers.utils.solidityKeccak256(['string'], ['CheckBalanceHigh']),
from: deployer,
log: true,
})
await deploy()
}
deployFn.tags = ['CheckBalanceHigh']
export default deployFn
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const { deploy } = await hre.deployments.deterministic('CheckBalanceLow', {
salt: hre.ethers.utils.solidityKeccak256(['string'], ['CheckBalanceLow']),
from: deployer,
log: true,
})
await deploy()
}
deployFn.tags = ['CheckBalanceLow']
export default deployFn
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const { deploy } = await hre.deployments.deterministic('CheckGelatoLow', {
salt: hre.ethers.utils.solidityKeccak256(['string'], ['CheckGelatoLow']),
from: deployer,
log: true,
})
await deploy()
}
deployFn.tags = ['CheckGelatoLow']
export default deployFn
/* Imports: External */
import { DeployFunction } from 'hardhat-deploy/dist/types'
const deployFn: DeployFunction = async (hre) => {
const { deployer } = await hre.getNamedAccounts()
const { deploy } = await hre.deployments.deterministic('CheckTrue', {
salt: hre.ethers.utils.solidityKeccak256(['string'], ['CheckTrue']),
from: deployer,
log: true,
})
await deploy()
}
deployFn.tags = ['CheckTrue']
export default deployFn
......@@ -7,6 +7,8 @@ import '@nomiclabs/hardhat-ethers'
import '@nomiclabs/hardhat-waffle'
import '@nomiclabs/hardhat-etherscan'
import 'solidity-coverage'
import 'hardhat-gas-reporter'
import 'hardhat-deploy'
// Hardhat tasks
import './tasks'
......@@ -18,15 +20,57 @@ const config: HardhatUserConfig = {
networks: {
optimism: {
chainId: 10,
url: 'https://mainnet.optimsim.io',
url: 'https://mainnet.optimism.io',
verify: {
etherscan: {
apiKey: getenv('OPTIMISTIC_ETHERSCAN_API_KEY'),
},
},
},
opkovan: {
chainId: 69,
url: 'https://kovan.optimism.io',
verify: {
etherscan: {
apiKey: getenv('OPTIMISTIC_ETHERSCAN_API_KEY'),
},
},
},
mainnet: {
ethereum: {
chainId: 1,
url: 'https://rpc.ankr.com/eth',
url: `https://mainnet.infura.io/v3/${getenv('INFURA_PROJECT_ID')}`,
verify: {
etherscan: {
apiKey: getenv('ETHEREUM_ETHERSCAN_API_KEY'),
},
},
},
goerli: {
chainId: 5,
url: `https://goerli.infura.io/v3/${getenv('INFURA_PROJECT_ID')}`,
verify: {
etherscan: {
apiKey: getenv('ETHEREUM_ETHERSCAN_API_KEY'),
},
},
},
ropsten: {
chainId: 3,
url: `https://ropsten.infura.io/v3/${getenv('INFURA_PROJECT_ID')}`,
verify: {
etherscan: {
apiKey: getenv('ETHEREUM_ETHERSCAN_API_KEY'),
},
},
},
kovan: {
chainId: 42,
url: `https://kovan.infura.io/v3/${getenv('INFURA_PROJECT_ID')}`,
verify: {
etherscan: {
apiKey: getenv('ETHEREUM_ETHERSCAN_API_KEY'),
},
},
},
},
mocha: {
......@@ -52,12 +96,8 @@ const config: HardhatUserConfig = {
},
},
},
etherscan: {
apiKey: {
mainnet: getenv('ETHERSCAN_API_KEY'),
optimisticEthereum: getenv('OPTIMISTIC_ETHERSCAN_API_KEY'),
optimisticKovan: getenv('OPTIMISTIC_ETHERSCAN_API_KEY'),
},
namedAccounts: {
deployer: `ledger://${getenv('LEDGER_ADDRESS')}`,
},
}
......
......@@ -21,6 +21,9 @@
"test": "yarn test:contracts",
"test:contracts": "hardhat test --show-stack-traces",
"test:coverage": "NODE_OPTIONS=--max_old_space_size=8192 hardhat coverage && istanbul check-coverage --statements 90 --branches 84 --functions 88 --lines 90",
"test:slither": "slither .",
"pretest:slither": "rm -f @openzeppelin && rm -f hardhat && ln -s node_modules/@openzeppelin @openzeppelin && ln -s ../../node_modules/hardhat hardhat",
"posttest:slither": "rm -f @openzeppelin && rm -f hardhat",
"lint:ts:check": "eslint . --max-warnings=0",
"lint:contracts:check": "yarn solhint -f table 'contracts/**/*.sol'",
"lint:check": "yarn lint:contracts:check && yarn lint:ts:check",
......@@ -49,16 +52,14 @@
"type": "git",
"url": "https://github.com/ethereum-optimism/optimism.git"
},
"dependencies": {
"@eth-optimism/core-utils": "^0.8.5",
"@openzeppelin/contracts": "4.3.2"
},
"devDependencies": {
"@defi-wonderland/smock": "^2.0.7",
"@eth-optimism/core-utils": "^0.8.5",
"@ethersproject/hardware-wallets": "^5.6.1",
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-etherscan": "^3.0.3",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@rari-capital/solmate": "^6.3.0",
"@openzeppelin/contracts": "4.6.0",
"@types/chai": "^4.2.18",
"@types/mocha": "^8.2.2",
"@types/node": "^17.0.21",
......@@ -80,9 +81,11 @@
"ethers": "^5.6.8",
"hardhat": "^2.9.6",
"hardhat-deploy": "^0.11.10",
"hardhat-gas-reporter": "^1.0.8",
"istanbul": "^0.4.5",
"lint-staged": "11.0.0",
"mocha": "^8.4.0",
"node-fetch": "^2.6.7",
"prettier": "^2.3.1",
"prettier-plugin-solidity": "^1.0.0-beta.18",
"solhint": "^3.3.6",
......
......@@ -8,5 +8,5 @@
"hardhat_ignore_compile": false,
"disable_color": false,
"exclude_dependencies": false,
"filter_paths": "@openzeppelin|hardhat|contracts/test-helpers|contracts/test-libraries|contracts/L2/predeploys/WETH9.sol"
"filter_paths": "@openzeppelin|hardhat|contracts/testing"
}
import { ethers } from 'ethers'
/**
* Defines the configuration for a deployment.
*/
export interface DeployConfig {
/**
* Initial RetroReceiver owner.
*/
retroReceiverOwner: string
/**
* Initial Drippie owner.
*/
drippieOwner: string
}
/**
* Specification for each of the configuration options.
*/
const configSpec: {
[K in keyof DeployConfig]: {
type: string
default?: any
}
} = {
retroReceiverOwner: {
type: 'address',
},
drippieOwner: {
type: 'address',
},
}
/**
* Gets the deploy config for the given network.
*
* @param network Network name.
* @returns Deploy config for the given network.
*/
export const getDeployConfig = (network: string): Required<DeployConfig> => {
let config: DeployConfig
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
config = require(`../../config/deploy/${network}.ts`).default
} catch (err) {
throw new Error(
`error while loading deploy config for network: ${network}, ${err}`
)
}
return parseDeployConfig(config)
}
/**
* Parses and validates the given deploy config, replacing any missing values with defaults.
*
* @param config Deploy config to parse.
* @returns Parsed deploy config.
*/
export const parseDeployConfig = (
config: DeployConfig
): Required<DeployConfig> => {
// Create a clone of the config object. Shallow clone is fine because none of the input options
// are expected to be objects or functions etc.
const parsed = { ...config }
for (const [key, spec] of Object.entries(configSpec)) {
// Make sure the value is defined, or use a default.
if (parsed[key] === undefined) {
if ('default' in spec) {
parsed[key] = spec.default
} else {
throw new Error(
`deploy config is missing required field: ${key} (${spec.type})`
)
}
} else {
// Make sure the default has the correct type.
if (spec.type === 'address') {
if (!ethers.utils.isAddress(parsed[key])) {
throw new Error(
`deploy config field: ${key} is not of type ${spec.type}: ${parsed[key]}`
)
}
} else if (typeof parsed[key] !== spec.type) {
throw new Error(
`deploy config field: ${key} is not of type ${spec.type}: ${parsed[key]}`
)
}
}
}
return parsed as Required<DeployConfig>
}
import assert from 'assert'
import { BigNumberish, ethers } from 'ethers'
import { Interface } from 'ethers/lib/utils'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
import { Etherscan } from '../etherscan'
export interface DripConfig {
interval: BigNumberish
dripcheck: string
checkparams?: any
actions: Array<{
target: string
value?: BigNumberish
data?:
| string
| {
fn: string
args: any[]
}
}>
}
export interface DrippieConfig {
[name: string]: DripConfig
}
export const getDrippieConfig = async (
hre: HardhatRuntimeEnvironment
): Promise<Required<DrippieConfig>> => {
let config: DrippieConfig
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
config = require(`../../config/drippie/${hre.network.name}.ts`).default
} catch (err) {
throw new Error(
`error while loading drippie config for network: ${hre.network.name}, ${err}`
)
}
return parseDrippieConfig(hre, config)
}
export const encodeDripCheckParams = (
iface: Interface,
params: any
): string => {
return ethers.utils.defaultAbiCoder.encode(
[iface.getEvent('_EventToExposeStructInABI__Params').inputs[0]],
[params]
)
}
export const parseDrippieConfig = async (
hre: HardhatRuntimeEnvironment,
config: DrippieConfig
): Promise<Required<DrippieConfig>> => {
// Create a clone of the config object. Shallow clone is fine because none of the input options
// are expected to be objects or functions etc.
const parsed = { ...config }
const etherscan = new Etherscan(
hre.network.config.verify.etherscan.apiKey,
hre.network.config.chainId
)
for (const dripConfig of Object.values(parsed)) {
for (const action of dripConfig.actions) {
assert(ethers.utils.isAddress(action.target), 'target is not an address')
if (action.data === undefined) {
action.data = '0x'
} else if (typeof action.data === 'string') {
assert(
ethers.utils.isHexString(action.data),
'action is not a hex string'
)
} else {
const abi = await etherscan.getContractABI(action.target)
const iface = new ethers.utils.Interface(abi)
action.data = iface.encodeFunctionData(action.data.fn, action.data.args)
}
if (action.value === undefined) {
action.value = ethers.BigNumber.from(0)
} else {
action.value = ethers.BigNumber.from(action.value)
}
}
const dripcheck = await hre.deployments.get(dripConfig.dripcheck)
dripConfig.dripcheck = dripcheck.address
if (dripConfig.checkparams === undefined) {
dripConfig.checkparams = '0x'
} else {
dripConfig.checkparams = encodeDripCheckParams(
new ethers.utils.Interface(dripcheck.abi),
dripConfig.checkparams
)
}
dripConfig.interval = ethers.BigNumber.from(dripConfig.interval)
}
return parsed as Required<DrippieConfig>
}
export * from './deploy'
export * from './drippie'
import fetch from 'node-fetch'
interface NetworkData {
chainId: number
names: string[]
etherscanApiUrl: string
}
const networks: {
[id: number]: NetworkData
} = {
1: {
chainId: 1,
names: ['mainnet', 'main', 'eth', 'ethereum'],
etherscanApiUrl: 'https://api.etherscan.io',
},
3: {
chainId: 3,
names: ['ropsten'],
etherscanApiUrl: 'https://api-ropsten.etherscan.io',
},
4: {
chainId: 4,
names: ['rinkeby'],
etherscanApiUrl: 'https://api-rinkeby.etherscan.io',
},
5: {
chainId: 5,
names: ['goerli'],
etherscanApiUrl: 'https://api-goerli.etherscan.io',
},
10: {
chainId: 10,
names: ['optimism'],
etherscanApiUrl: 'https://api-optimistic.etherscan.io',
},
42: {
chainId: 42,
names: ['kovan'],
etherscanApiUrl: 'https://api-kovan.etherscan.io',
},
69: {
chainId: 69,
names: ['opkovan', 'kovan-optimism', 'optimistic-kovan'],
etherscanApiUrl: 'https://api-kovan-optimistic.etherscan.io',
},
}
export class Etherscan {
net: NetworkData
constructor(
private readonly apiKey: string,
private readonly network: string | number
) {
if (typeof network === 'string') {
this.net = Object.values(networks).find((net) => {
return net.names.includes(network)
})
} else {
this.net = networks[this.network]
}
}
public async getContractSource(address: string): Promise<any> {
const url = new URL(`${this.net.etherscanApiUrl}/api`)
url.searchParams.append('module', 'contract')
url.searchParams.append('action', 'getsourcecode')
url.searchParams.append('address', address)
url.searchParams.append('apikey', this.apiKey)
const response = await fetch(url)
const result = await response.json()
return result.result[0]
}
public async getContractABI(address: string): Promise<any> {
const source = await this.getContractSource(address)
if (source.Proxy === '1') {
const impl = await this.getContractSource(source.Implementation)
return impl.ABI
} else {
return source.ABI
}
}
}
export * from './config'
export * from './etherscan'
import { task } from 'hardhat/config'
import * as types from 'hardhat/internal/core/params/argumentTypes'
import { LedgerSigner } from '@ethersproject/hardware-wallets'
task('deploy-receiver')
.addParam('creator', 'Creator address', undefined, types.string)
.addParam('owner', 'Owner address', undefined, types.string)
.setAction(async (args, hre) => {
console.log(`connecting to ledger...`)
const signer = new LedgerSigner(
hre.ethers.provider,
'default',
hre.ethers.utils.defaultPath
)
const addr = await signer.getAddress()
if (args.creator !== addr) {
throw new Error(`Incorrect key. Creator ${args.creator}, Signer ${addr}`)
}
const singleton = new hre.ethers.Contract(
'0xce0042B868300000d44A59004Da54A005ffdcf9f',
[
{
constant: false,
inputs: [
{
internalType: 'bytes',
name: '_initCode',
type: 'bytes',
},
{
internalType: 'bytes32',
name: '_salt',
type: 'bytes32',
},
],
name: 'deploy',
outputs: [
{
internalType: 'address payable',
name: 'createdContract',
type: 'address',
},
],
payable: false,
stateMutability: 'nonpayable',
type: 'function',
},
],
signer
)
const salt =
'0x0000000000000000000000000000000000000000000000000000000000000001'
const code = hre.ethers.utils.hexConcat([
hre.artifacts.readArtifactSync('RetroReceiver').bytecode,
hre.ethers.utils.defaultAbiCoder.encode(['address'], [addr]),
])
// Predict and connect to the contract address
const receiver = await hre.ethers.getContractAt(
'RetroReceiver',
await singleton.callStatic.deploy(code, salt, {
gasLimit: 2_000_000,
}),
signer
)
console.log(`creating contract: ${receiver.address}...`)
const tx1 = await singleton.deploy(code, salt, {
gasLimit: 2_000_000,
})
console.log(`waiting for tx: ${tx1.hash}...`)
await tx1.wait()
console.log(`transferring ownership to: ${args.owner}...`)
const tx2 = await receiver.setOwner(args.owner)
console.log(`waiting for tx: ${tx2.hash}...`)
await tx2.wait()
console.log(`verifying contract: ${receiver.address}...`)
await hre.run('verify:verify', {
address: receiver.address,
constructorArguments: [addr],
})
console.log(`all done`)
})
export * from './deploy-receiver'
export * from './install-drippie-config'
import { task } from 'hardhat/config'
import { LedgerSigner } from '@ethersproject/hardware-wallets'
import { PopulatedTransaction } from 'ethers'
import { DripConfig, getDrippieConfig } from '../src'
task('install-drippie-config').setAction(async (args, hre) => {
console.log(`connecting to ledger...`)
const signer = new LedgerSigner(
hre.ethers.provider,
'default',
hre.ethers.utils.defaultPath
)
console.log(`connecting to Drippie...`)
const Drippie = await hre.ethers.getContractAt(
'Drippie',
(
await hre.deployments.get('Drippie')
).address,
signer
)
console.log(`loading local version of Drippie config for network...`)
const config = await getDrippieConfig(hre)
// Need this to deal with annoying Ethers/Ledger 1559 issue.
const sendtx = async (tx: PopulatedTransaction): Promise<void> => {
const gas = await signer.estimateGas(tx)
tx.type = 1
tx.gasLimit = gas
const ret = await signer.sendTransaction(tx)
console.log(`sent tx: ${ret.hash}`)
console.log(`waiting for tx to be confirmed...`)
await ret.wait()
console.log(`tx confirmed`)
}
const isSameConfig = (a: DripConfig, b: DripConfig): boolean => {
return (
a.dripcheck.toLowerCase() === b.dripcheck.toLowerCase() &&
a.checkparams === b.checkparams &&
hre.ethers.BigNumber.from(a.interval).eq(b.interval) &&
a.actions.length === b.actions.length &&
a.actions.every((ax, i) => {
return (
ax.target === b.actions[i].target &&
ax.data === b.actions[i].data &&
hre.ethers.BigNumber.from(ax.value).eq(b.actions[i].value)
)
})
)
}
console.log(`installing Drippie config file...`)
for (const [dripName, dripConfig] of Object.entries(config)) {
console.log(`checking config for drip: ${dripName}`)
const drip = await Drippie.drips(dripName)
if (drip.status === 0) {
console.log(`drip does not exist yet: ${dripName}`)
console.log(`creating drip...`)
const tx = await Drippie.populateTransaction.create(dripName, dripConfig)
await sendtx(tx)
} else if (!isSameConfig(dripConfig, drip.config)) {
console.log(`drip exists but local config is different: ${dripName}`)
console.log(`drips cannot be modified for security reasons`)
console.log(`please do not modify the local config for existing drips`)
console.log(`you can archive the old drip and create another`)
} else {
console.log(`drip is already installed`)
}
}
console.log(`config is fully installed`)
})
......@@ -5,7 +5,7 @@ import { Contract } from 'ethers'
import { expect } from '../../setup'
import { deploy } from '../../helpers'
describe('RetroReceiver', () => {
describe('AssetReceiver', () => {
const DEFAULT_TOKEN_ID = 0
const DEFAULT_AMOUNT = hre.ethers.constants.WeiPerEther
const DEFAULT_RECIPIENT = '0x' + '11'.repeat(20)
......@@ -18,11 +18,11 @@ describe('RetroReceiver', () => {
let TestERC20: Contract
let TestERC721: Contract
let RetroReceiver: Contract
let AssetReceiver: Contract
beforeEach('deploy contracts', async () => {
TestERC20 = await deploy('TestERC20', { signer: signer1 })
TestERC721 = await deploy('TestERC721', { signer: signer1 })
RetroReceiver = await deploy('RetroReceiver', {
AssetReceiver = await deploy('AssetReceiver', {
signer: signer1,
args: [signer1.address],
})
......@@ -37,41 +37,35 @@ describe('RetroReceiver', () => {
])
})
describe('constructor', () => {
it('should set the owner', async () => {
expect(await RetroReceiver.owner()).to.equal(signer1.address)
})
})
describe('receive', () => {
it('should be able to receive ETH', async () => {
await expect(
signer1.sendTransaction({
to: RetroReceiver.address,
to: AssetReceiver.address,
value: DEFAULT_AMOUNT,
})
).to.not.be.reverted
expect(
await hre.ethers.provider.getBalance(RetroReceiver.address)
await hre.ethers.provider.getBalance(AssetReceiver.address)
).to.equal(DEFAULT_AMOUNT)
})
})
describe('withdrawETH(address)', () => {
describe('when called by the owner', () => {
describe('when called by authorized address', () => {
it('should withdraw all ETH in the contract', async () => {
await signer1.sendTransaction({
to: RetroReceiver.address,
to: AssetReceiver.address,
value: DEFAULT_AMOUNT,
})
await expect(RetroReceiver['withdrawETH(address)'](DEFAULT_RECIPIENT))
.to.emit(RetroReceiver, 'WithdrewETH')
await expect(AssetReceiver['withdrawETH(address)'](DEFAULT_RECIPIENT))
.to.emit(AssetReceiver, 'WithdrewETH')
.withArgs(signer1.address, DEFAULT_RECIPIENT, DEFAULT_AMOUNT)
expect(
await hre.ethers.provider.getBalance(RetroReceiver.address)
await hre.ethers.provider.getBalance(AssetReceiver.address)
).to.equal(0)
expect(
......@@ -80,36 +74,36 @@ describe('RetroReceiver', () => {
})
})
describe('when called by not the owner', () => {
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
RetroReceiver.connect(signer2)['withdrawETH(address)'](
AssetReceiver.connect(signer2)['withdrawETH(address)'](
signer2.address
)
).to.be.revertedWith('UNAUTHORIZED')
).to.be.revertedWith('Ownable: caller is not the owner')
})
})
})
describe('withdrawETH(address,uint256)', () => {
describe('when called by the owner', () => {
describe('when called by authorized address', () => {
it('should withdraw the given amount of ETH', async () => {
await signer1.sendTransaction({
to: RetroReceiver.address,
to: AssetReceiver.address,
value: DEFAULT_AMOUNT.mul(2),
})
await expect(
RetroReceiver['withdrawETH(address,uint256)'](
AssetReceiver['withdrawETH(address,uint256)'](
DEFAULT_RECIPIENT,
DEFAULT_AMOUNT
)
)
.to.emit(RetroReceiver, 'WithdrewETH')
.to.emit(AssetReceiver, 'WithdrewETH')
.withArgs(signer1.address, DEFAULT_RECIPIENT, DEFAULT_AMOUNT)
expect(
await hre.ethers.provider.getBalance(RetroReceiver.address)
await hre.ethers.provider.getBalance(AssetReceiver.address)
).to.equal(DEFAULT_AMOUNT)
expect(
......@@ -118,30 +112,30 @@ describe('RetroReceiver', () => {
})
})
describe('when called by not the owner', () => {
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
RetroReceiver.connect(signer2)['withdrawETH(address,uint256)'](
AssetReceiver.connect(signer2)['withdrawETH(address,uint256)'](
DEFAULT_RECIPIENT,
DEFAULT_AMOUNT
)
).to.be.revertedWith('UNAUTHORIZED')
).to.be.revertedWith('Ownable: caller is not the owner')
})
})
})
describe('withdrawERC20(address,address)', () => {
describe('when called by the owner', () => {
describe('when called by authorized address', () => {
it('should withdraw all ERC20 balance held by the contract', async () => {
await TestERC20.transfer(RetroReceiver.address, DEFAULT_AMOUNT)
await TestERC20.transfer(AssetReceiver.address, DEFAULT_AMOUNT)
await expect(
RetroReceiver['withdrawERC20(address,address)'](
AssetReceiver['withdrawERC20(address,address)'](
TestERC20.address,
DEFAULT_RECIPIENT
)
)
.to.emit(RetroReceiver, 'WithdrewERC20')
.to.emit(AssetReceiver, 'WithdrewERC20')
.withArgs(
signer1.address,
DEFAULT_RECIPIENT,
......@@ -155,31 +149,31 @@ describe('RetroReceiver', () => {
})
})
describe('when called by not the owner', () => {
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
RetroReceiver.connect(signer2)['withdrawERC20(address,address)'](
AssetReceiver.connect(signer2)['withdrawERC20(address,address)'](
TestERC20.address,
DEFAULT_RECIPIENT
)
).to.be.revertedWith('UNAUTHORIZED')
).to.be.revertedWith('Ownable: caller is not the owner')
})
})
})
describe('withdrawERC20(address,address,uint256)', () => {
describe('when called by the owner', () => {
describe('when called by authorized address', () => {
it('should withdraw the given ERC20 amount', async () => {
await TestERC20.transfer(RetroReceiver.address, DEFAULT_AMOUNT.mul(2))
await TestERC20.transfer(AssetReceiver.address, DEFAULT_AMOUNT.mul(2))
await expect(
RetroReceiver['withdrawERC20(address,address,uint256)'](
AssetReceiver['withdrawERC20(address,address,uint256)'](
TestERC20.address,
DEFAULT_RECIPIENT,
DEFAULT_AMOUNT
)
)
.to.emit(RetroReceiver, 'WithdrewERC20')
.to.emit(AssetReceiver, 'WithdrewERC20')
.withArgs(
signer1.address,
DEFAULT_RECIPIENT,
......@@ -193,34 +187,34 @@ describe('RetroReceiver', () => {
})
})
describe('when called by not the owner', () => {
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
RetroReceiver.connect(signer2)[
AssetReceiver.connect(signer2)[
'withdrawERC20(address,address,uint256)'
](TestERC20.address, DEFAULT_RECIPIENT, DEFAULT_AMOUNT)
).to.be.revertedWith('UNAUTHORIZED')
).to.be.revertedWith('Ownable: caller is not the owner')
})
})
})
describe('withdrawERC721', () => {
describe('when called by the owner', () => {
describe('when called by authorized address', () => {
it('should withdraw the token', async () => {
await TestERC721.transferFrom(
signer1.address,
RetroReceiver.address,
AssetReceiver.address,
DEFAULT_TOKEN_ID
)
await expect(
RetroReceiver.withdrawERC721(
AssetReceiver.withdrawERC721(
TestERC721.address,
DEFAULT_RECIPIENT,
DEFAULT_TOKEN_ID
)
)
.to.emit(RetroReceiver, 'WithdrewERC721')
.to.emit(AssetReceiver, 'WithdrewERC721')
.withArgs(
signer1.address,
DEFAULT_RECIPIENT,
......@@ -234,15 +228,15 @@ describe('RetroReceiver', () => {
})
})
describe('when called by not the owner', () => {
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
RetroReceiver.connect(signer2).withdrawERC721(
AssetReceiver.connect(signer2).withdrawERC721(
TestERC721.address,
DEFAULT_RECIPIENT,
DEFAULT_TOKEN_ID
)
).to.be.revertedWith('UNAUTHORIZED')
).to.be.revertedWith('Ownable: caller is not the owner')
})
})
})
......
import hre from 'hardhat'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { Contract } from 'ethers'
import { expect } from '../../setup'
import { decodeSolidityRevert, deploy } from '../../helpers'
describe('AssetReceiver', () => {
let signer1: SignerWithAddress
let signer2: SignerWithAddress
before('signer setup', async () => {
;[signer1, signer2] = await hre.ethers.getSigners()
})
let CallRecorder: Contract
let Reverter: Contract
let Transactor: Contract
beforeEach('deploy contracts', async () => {
CallRecorder = await deploy('CallRecorder')
Reverter = await deploy('Reverter')
Transactor = await deploy('Transactor', {
signer: signer1,
args: [signer1.address],
})
})
describe('CALL', () => {
describe('when called by authorized address', () => {
it('should do a call to the target contract', async () => {
const data = CallRecorder.interface.encodeFunctionData('record')
await Transactor.CALL(CallRecorder.address, data, 1_000_000, 0, {
gasLimit: 2_000_000,
})
const call = await CallRecorder.lastCall()
expect(call.data).to.equal(data)
expect(call.sender).to.equal(Transactor.address)
})
it('should be able to call with value', async () => {
const data = CallRecorder.interface.encodeFunctionData('record')
const value = 69
await Transactor.CALL(CallRecorder.address, data, 1_000_000, value, {
gasLimit: 2_000_000,
value,
})
const call = await CallRecorder.lastCall()
expect(call.value).to.equal(value)
})
it('should be able to set gas limit', async () => {
const data = CallRecorder.interface.encodeFunctionData('record')
const gas = 100_000
await Transactor.CALL(CallRecorder.address, data, gas, 0, {
gasLimit: 2_000_000,
})
const call = await CallRecorder.lastCall()
expect(call.gas.toNumber()).to.be.lessThan(gas)
})
})
describe('when called by not authorized address', () => {
it('should be reverted', async () => {
const data = CallRecorder.interface.encodeFunctionData('record')
await expect(
Transactor.connect(signer2).CALL(
CallRecorder.address,
data,
1_000_000,
0,
{
gasLimit: 2_000_000,
}
)
).to.be.revertedWith('Ownable: caller is not the owner')
})
})
})
describe('DELEGATECALL', () => {
describe('when called by authorized address', () => {
it('should do a delegatecall to the target contract', async () => {
const data = Reverter.interface.encodeFunctionData('doRevert')
const ret = await Transactor.callStatic.DELEGATECALL(
Reverter.address,
data,
1_000_000,
{
gasLimit: 2_000_000,
}
)
expect(ret[0]).to.equal(false)
expect(decodeSolidityRevert(ret[1])).to.deep.equal('Reverter reverted')
})
})
describe('when called by not authorized address', () => {
it('should be reverted', async () => {
const data = Reverter.interface.encodeFunctionData('doRevert')
await expect(
Transactor.connect(signer2).DELEGATECALL(
Reverter.address,
data,
1_000_000,
{
gasLimit: 2_000_000,
}
)
).to.be.revertedWith('Ownable: caller is not the owner')
})
})
})
})
import hre from 'hardhat'
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'
import { Contract } from 'ethers'
import { toRpcHexString } from '@eth-optimism/core-utils'
import { expect } from '../../../setup'
import { deploy } from '../../../helpers'
describe('Drippie', () => {
const DEFAULT_DRIP_NAME = 'drippity drip drip'
const DEFAULT_DRIP_CONFIG = {
interval: hre.ethers.BigNumber.from(100),
dripcheck: '', // Gets added in setup
checkparams: '0x',
actions: [
{
target: '0x' + '11'.repeat(20),
data: '0x',
value: hre.ethers.BigNumber.from(1),
},
],
}
let signer1: SignerWithAddress
let signer2: SignerWithAddress
before('signer setup', async () => {
;[signer1, signer2] = await hre.ethers.getSigners()
})
before('deploy default dripcheck', async () => {
DEFAULT_DRIP_CONFIG.dripcheck = (await deploy('CheckTrue')).address
})
let SimpleStorage: Contract
let Drippie: Contract
beforeEach('deploy contracts', async () => {
SimpleStorage = await deploy('SimpleStorage')
Drippie = await deploy('Drippie', {
signer: signer1,
args: [signer1.address],
})
})
beforeEach('balance setup', async () => {
await hre.ethers.provider.send('hardhat_setBalance', [
Drippie.address,
toRpcHexString(DEFAULT_DRIP_CONFIG.actions[0].value.mul(100000)),
])
await hre.ethers.provider.send('hardhat_setBalance', [
DEFAULT_DRIP_CONFIG.actions[0].target,
'0x0',
])
})
describe('create', () => {
describe('when called by authorized address', () => {
it('should create a drip with the given name', async () => {
await expect(
Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
).to.emit(Drippie, 'DripCreated')
const drip = await Drippie.drips(DEFAULT_DRIP_NAME)
expect(drip.status).to.equal(2) // PAUSED
expect(drip.last).to.deep.equal(hre.ethers.BigNumber.from(0))
expect(drip.config.interval).to.deep.equal(DEFAULT_DRIP_CONFIG.interval)
expect(drip.config.dripcheck).to.deep.equal(
DEFAULT_DRIP_CONFIG.dripcheck
)
expect(drip.config.checkparams).to.deep.equal(
DEFAULT_DRIP_CONFIG.checkparams
)
expect(drip.config.actions[0][0]).to.deep.equal(
DEFAULT_DRIP_CONFIG.actions[0].target
)
expect(drip.config.actions[0][1]).to.deep.equal(
DEFAULT_DRIP_CONFIG.actions[0].data
)
expect(drip.config.actions[0][2]).to.deep.equal(
DEFAULT_DRIP_CONFIG.actions[0].value
)
})
it('should not be able to create the same drip twice', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await expect(
Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
).to.be.revertedWith('Drippie: drip with that name already exists')
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
Drippie.connect(signer2).create(
DEFAULT_DRIP_NAME,
DEFAULT_DRIP_CONFIG
)
).to.be.revertedWith('Ownable: caller is not the owner')
})
})
})
describe('status', () => {
describe('when called by authorized address', () => {
it('should allow setting between PAUSED and ACTIVE', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
expect((await Drippie.drips(DEFAULT_DRIP_NAME)).status).to.equal(2) // PAUSED
await Drippie.status(DEFAULT_DRIP_NAME, 1) // ACTIVE
expect((await Drippie.drips(DEFAULT_DRIP_NAME)).status).to.equal(1) // ACTIVE
await Drippie.status(DEFAULT_DRIP_NAME, 2) // PAUSED
expect((await Drippie.drips(DEFAULT_DRIP_NAME)).status).to.equal(2) // PAUSED
})
it('should not allow setting status to NONE', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await expect(Drippie.status(DEFAULT_DRIP_NAME, 0)).to.be.revertedWith(
'Drippie: drip status can never be set back to NONE after creation'
)
})
it('should not allow setting status to same status as before', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await expect(Drippie.status(DEFAULT_DRIP_NAME, 2)).to.be.revertedWith(
'Drippie: cannot set drip status to same status as before'
)
})
it('should allow setting status to ARCHIVED if PAUSED', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await Drippie.status(DEFAULT_DRIP_NAME, 3) // ARCHIVED
expect((await Drippie.drips(DEFAULT_DRIP_NAME)).status).to.equal(3) // ARCHIVED
})
it('should not allow setting status to ARCHIVED if ACTIVE', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await Drippie.status(DEFAULT_DRIP_NAME, 1) // ACTIVE
await expect(Drippie.status(DEFAULT_DRIP_NAME, 3)).to.be.revertedWith(
'Drippie: drip must be paused to be archived'
)
})
it('should not allow setting status to PAUSED if ARCHIVED', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await Drippie.status(DEFAULT_DRIP_NAME, 3) // ARCHIVED
await expect(Drippie.status(DEFAULT_DRIP_NAME, 2)).to.be.revertedWith(
'Drippie: drip with that name has been archived'
)
})
it('should not allow setting status to ACTIVE if ARCHIVED', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await Drippie.status(DEFAULT_DRIP_NAME, 3) // ARCHIVED
await expect(Drippie.status(DEFAULT_DRIP_NAME, 1)).to.be.revertedWith(
'Drippie: drip with that name has been archived'
)
})
it('should revert if the drip does not exist yet', async () => {
await expect(Drippie.status(DEFAULT_DRIP_NAME, 1)).to.be.revertedWith(
'Drippie: drip with that name does not exist'
)
})
})
describe('when called by not authorized address', () => {
it('should revert', async () => {
await expect(
Drippie.connect(signer2).status(DEFAULT_DRIP_NAME, 1)
).to.be.revertedWith('Ownable: caller is not the owner')
})
})
})
describe('drip', () => {
it('should drip the amount', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await Drippie.status(DEFAULT_DRIP_NAME, 1) // ACTIVE
await expect(Drippie.drip(DEFAULT_DRIP_NAME)).to.emit(
Drippie,
'DripExecuted'
)
expect(
await signer1.provider.getBalance(DEFAULT_DRIP_CONFIG.actions[0].target)
).to.equal(DEFAULT_DRIP_CONFIG.actions[0].value)
})
it('should be able to trigger one function', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, {
...DEFAULT_DRIP_CONFIG,
actions: [
{
target: SimpleStorage.address,
data: SimpleStorage.interface.encodeFunctionData('set', [
'0x' + '33'.repeat(32),
'0x' + '44'.repeat(32),
]),
value: hre.ethers.BigNumber.from(0),
},
],
})
await Drippie.status(DEFAULT_DRIP_NAME, 1) // ACTIVE
await Drippie.drip(DEFAULT_DRIP_NAME)
expect(await SimpleStorage.get('0x' + '33'.repeat(32))).to.equal(
'0x' + '44'.repeat(32)
)
})
it('should be able to trigger two functions', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, {
...DEFAULT_DRIP_CONFIG,
actions: [
{
target: SimpleStorage.address,
data: SimpleStorage.interface.encodeFunctionData('set', [
'0x' + '33'.repeat(32),
'0x' + '44'.repeat(32),
]),
value: hre.ethers.BigNumber.from(0),
},
{
target: SimpleStorage.address,
data: SimpleStorage.interface.encodeFunctionData('set', [
'0x' + '44'.repeat(32),
'0x' + '55'.repeat(32),
]),
value: hre.ethers.BigNumber.from(0),
},
],
})
await Drippie.status(DEFAULT_DRIP_NAME, 1) // ACTIVE
await Drippie.drip(DEFAULT_DRIP_NAME)
expect(await SimpleStorage.get('0x' + '33'.repeat(32))).to.equal(
'0x' + '44'.repeat(32)
)
expect(await SimpleStorage.get('0x' + '44'.repeat(32))).to.equal(
'0x' + '55'.repeat(32)
)
})
it('should revert if dripping twice in one interval', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await Drippie.status(DEFAULT_DRIP_NAME, 1) // ACTIVE
await Drippie.drip(DEFAULT_DRIP_NAME)
await expect(Drippie.drip(DEFAULT_DRIP_NAME)).to.be.revertedWith(
'Drippie: drip interval has not elapsed'
)
await hre.ethers.provider.send('evm_increaseTime', [
DEFAULT_DRIP_CONFIG.interval.add(1).toHexString(),
])
await expect(Drippie.drip(DEFAULT_DRIP_NAME)).to.not.be.reverted
})
it('should revert when the drip does not exist', async () => {
await expect(Drippie.drip(DEFAULT_DRIP_NAME)).to.be.revertedWith(
'Drippie: selected drip does not exist or is not currently active'
)
})
it('should revert when the drip is not active', async () => {
await Drippie.create(DEFAULT_DRIP_NAME, DEFAULT_DRIP_CONFIG)
await expect(Drippie.drip(DEFAULT_DRIP_NAME)).to.be.revertedWith(
'Drippie: selected drip does not exist or is not currently active'
)
})
})
})
import hre from 'hardhat'
import { Contract } from 'ethers'
import { toRpcHexString } from '@eth-optimism/core-utils'
import { expect } from '../../../../setup'
import { deploy } from '../../../../helpers'
import { encodeDripCheckParams } from '../../../../../src'
describe('CheckBalanceHigh', () => {
const RECIPIENT = '0x' + '11'.repeat(20)
const THRESHOLD = 100
let CheckBalanceHigh: Contract
before(async () => {
CheckBalanceHigh = await deploy('CheckBalanceHigh')
})
describe('check', () => {
it('should return true when balance is above threshold', async () => {
await hre.ethers.provider.send('hardhat_setBalance', [
RECIPIENT,
toRpcHexString(THRESHOLD + 1),
])
expect(
await CheckBalanceHigh.check(
encodeDripCheckParams(CheckBalanceHigh.interface, {
target: RECIPIENT,
threshold: THRESHOLD,
})
)
).to.equal(true)
})
it('should return false when balance is below threshold', async () => {
await hre.ethers.provider.send('hardhat_setBalance', [
RECIPIENT,
toRpcHexString(THRESHOLD - 1),
])
expect(
await CheckBalanceHigh.check(
encodeDripCheckParams(CheckBalanceHigh.interface, {
target: RECIPIENT,
threshold: THRESHOLD,
})
)
).to.equal(false)
})
})
})
import hre from 'hardhat'
import { Contract } from 'ethers'
import { toRpcHexString } from '@eth-optimism/core-utils'
import { expect } from '../../../../setup'
import { deploy } from '../../../../helpers'
import { encodeDripCheckParams } from '../../../../../src'
describe('CheckBalanceLow', () => {
const RECIPIENT = '0x' + '11'.repeat(20)
const THRESHOLD = 100
let CheckBalanceLow: Contract
before(async () => {
CheckBalanceLow = await deploy('CheckBalanceLow')
})
describe('check', () => {
it('should return true when balance is below threshold', async () => {
await hre.ethers.provider.send('hardhat_setBalance', [
RECIPIENT,
toRpcHexString(THRESHOLD - 1),
])
expect(
await CheckBalanceLow.check(
encodeDripCheckParams(CheckBalanceLow.interface, {
target: RECIPIENT,
threshold: THRESHOLD,
})
)
).to.equal(true)
})
it('should return false when balance is above threshold', async () => {
await hre.ethers.provider.send('hardhat_setBalance', [
RECIPIENT,
toRpcHexString(THRESHOLD + 1),
])
expect(
await CheckBalanceLow.check(
encodeDripCheckParams(CheckBalanceLow.interface, {
target: RECIPIENT,
threshold: THRESHOLD,
})
)
).to.equal(false)
})
})
})
import { Contract } from 'ethers'
import { smock, FakeContract } from '@defi-wonderland/smock'
import { expect } from '../../../../setup'
import { deploy } from '../../../../helpers'
import { encodeDripCheckParams } from '../../../../../src'
describe('CheckGelatoLow', () => {
const RECIPIENT = '0x' + '11'.repeat(20)
const THRESHOLD = 100
let CheckGelatoLow: Contract
let FakeGelatoTresury: FakeContract<Contract>
before(async () => {
CheckGelatoLow = await deploy('CheckGelatoLow')
FakeGelatoTresury = await smock.fake('IGelatoTreasury')
})
describe('check', () => {
it('should return true when balance is below threshold', async () => {
FakeGelatoTresury.userTokenBalance.returns(THRESHOLD - 1)
expect(
await CheckGelatoLow.check(
encodeDripCheckParams(CheckGelatoLow.interface, {
treasury: FakeGelatoTresury.address,
threshold: THRESHOLD,
recipient: RECIPIENT,
})
)
).to.equal(true)
})
it('should return false when balance is above threshold', async () => {
FakeGelatoTresury.userTokenBalance.returns(THRESHOLD + 1)
expect(
await CheckGelatoLow.check(
encodeDripCheckParams(CheckGelatoLow.interface, {
treasury: FakeGelatoTresury.address,
threshold: THRESHOLD,
recipient: RECIPIENT,
})
)
).to.equal(false)
})
})
})
import { Contract } from 'ethers'
import { expect } from '../../../../setup'
import { deploy } from '../../../../helpers'
describe('CheckTrue', () => {
let CheckTrue: Contract
before(async () => {
CheckTrue = await deploy('CheckTrue')
})
describe('check', () => {
it('should return true', async () => {
expect(await CheckTrue.check('0x')).to.equal(true)
})
})
})
export * from './deploy'
export * from './solidity'
import { ethers } from 'ethers'
export const decodeSolidityRevert = (revert: string) => {
const iface = new ethers.utils.Interface([
{
inputs: [
{
internalType: 'string',
name: 'message',
type: 'string',
},
],
name: 'Error',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
])
return iface.decodeFunctionData('Error', revert)[0]
}
......@@ -478,6 +478,18 @@
rxjs "^7.2.0"
semver "^7.3.5"
"@defi-wonderland/smock@^2.0.7":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@defi-wonderland/smock/-/smock-2.0.7.tgz#59d5fc93e175ad120c5dcdd8294e07525606c855"
integrity sha512-RVpODLKZ/Cr0C1bCbhJ2aXbAr2Ll/K2WO7hDL96tqhMzCsA7ToWdDIgiNpV5Vtqqvpftu5ddO7v3TAurQNSU0w==
dependencies:
"@nomiclabs/ethereumjs-vm" "^4.2.2"
diff "^5.0.0"
lodash.isequal "^4.5.0"
lodash.isequalwith "^4.4.0"
rxjs "^7.2.0"
semver "^7.3.5"
"@ensdomains/ens@^0.4.4":
version "0.4.5"
resolved "https://registry.yarnpkg.com/@ensdomains/ens/-/ens-0.4.5.tgz#e0aebc005afdc066447c6e22feb4eda89a5edbfc"
......@@ -518,41 +530,6 @@
minimatch "^3.0.4"
strip-json-comments "^3.1.1"
"@eth-optimism/contracts@0.5.20":
version "0.5.20"
resolved "https://registry.yarnpkg.com/@eth-optimism/contracts/-/contracts-0.5.20.tgz#3cc9d97dbc924d629bb4446237b8a54b32676533"
integrity sha512-HliboXKsiCFJdtiOlqJH2ZS+u9lkvXqCufpSnNCOrvxD4J3O8VkGUZib2Zelaz6srIX+23z9zM5JvqLYX/bubA==
dependencies:
"@eth-optimism/core-utils" "0.8.3"
"@ethersproject/abstract-provider" "^5.5.1"
"@ethersproject/abstract-signer" "^5.5.0"
"@ethersproject/hardware-wallets" "^5.5.0"
"@eth-optimism/core-utils@0.8.3":
version "0.8.3"
resolved "https://registry.yarnpkg.com/@eth-optimism/core-utils/-/core-utils-0.8.3.tgz#226d6ac4cb80bd165d64f5ffa4f3e0b300568678"
integrity sha512-jM3dPAg9TinskLyH4w9KofEqgLWJsf5VrlC8a2/0zKNlZUJuVzE/ZYfcq243qqlJn1vb4QkBKjYVHKMu1Wjzjg==
dependencies:
"@ethersproject/abstract-provider" "^5.5.1"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/providers" "^5.5.3"
"@ethersproject/transactions" "^5.5.0"
"@ethersproject/web" "^5.5.1"
bufio "^1.0.7"
chai "^4.3.4"
ethers "^5.5.4"
"@eth-optimism/sdk@1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@eth-optimism/sdk/-/sdk-1.0.4.tgz#47b91ae99a5ad28b126dc2b04320fa6cbf796b12"
integrity sha512-bvnn4vHPADiq7I7JjXIGQq/OqTAf3k2u9PCQwc3xYZnfHeklanl/lL62JZtSI55rEugbXX/sRCIAp0TPdFTUTA==
dependencies:
"@eth-optimism/contracts" "0.5.20"
"@eth-optimism/core-utils" "0.8.3"
lodash "^4.17.21"
merkletreejs "^0.2.27"
rlp "^2.2.7"
"@ethereum-waffle/chai@^3.4.0":
version "3.4.0"
resolved "https://registry.yarnpkg.com/@ethereum-waffle/chai/-/chai-3.4.0.tgz#2477877410a96bf370edd64df905b04fb9aba9d5"
......@@ -762,21 +739,6 @@
"@ethersproject/properties" "^5.4.0"
"@ethersproject/strings" "^5.4.0"
"@ethersproject/abi@5.5.0", "@ethersproject/abi@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.5.0.tgz#fb52820e22e50b854ff15ce1647cc508d6660613"
integrity sha512-loW7I4AohP5KycATvc0MgujU6JyCHPqHdeoo9z3Nr9xEiNioxa65ccdm1+fsoJhkuhdRtfcL8cfyGamz2AxZ5w==
dependencies:
"@ethersproject/address" "^5.5.0"
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/constants" "^5.5.0"
"@ethersproject/hash" "^5.5.0"
"@ethersproject/keccak256" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/strings" "^5.5.0"
"@ethersproject/abi@5.6.3", "@ethersproject/abi@^5.6.3":
version "5.6.3"
resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.6.3.tgz#2d643544abadf6e6b63150508af43475985c23db"
......@@ -820,19 +782,6 @@
"@ethersproject/transactions" "^5.4.0"
"@ethersproject/web" "^5.4.0"
"@ethersproject/abstract-provider@5.5.1", "@ethersproject/abstract-provider@^5.5.0", "@ethersproject/abstract-provider@^5.5.1":
version "5.5.1"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.5.1.tgz#2f1f6e8a3ab7d378d8ad0b5718460f85649710c5"
integrity sha512-m+MA/ful6eKbxpr99xUYeRvLkfnlqzrF8SZ46d/xFB1A7ZVknYc/sXJG0RcufF52Qn2jeFj1hhcoQ7IXjNKUqg==
dependencies:
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/networks" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/transactions" "^5.5.0"
"@ethersproject/web" "^5.5.0"
"@ethersproject/abstract-provider@5.6.1", "@ethersproject/abstract-provider@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.6.1.tgz#02ddce150785caf0c77fe036a0ebfcee61878c59"
......@@ -857,17 +806,6 @@
"@ethersproject/logger" "^5.4.0"
"@ethersproject/properties" "^5.4.0"
"@ethersproject/abstract-signer@5.5.0", "@ethersproject/abstract-signer@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.5.0.tgz#590ff6693370c60ae376bf1c7ada59eb2a8dd08d"
integrity sha512-lj//7r250MXVLKI7sVarXAbZXbv9P50lgmJQGr2/is82EwEb8r7HrxsmMqAjTsztMYy7ohrIhGMIml+Gx4D3mA==
dependencies:
"@ethersproject/abstract-provider" "^5.5.0"
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/abstract-signer@5.6.2", "@ethersproject/abstract-signer@^5.6.2":
version "5.6.2"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.6.2.tgz#491f07fc2cbd5da258f46ec539664713950b0b33"
......@@ -890,17 +828,6 @@
"@ethersproject/logger" "^5.4.0"
"@ethersproject/rlp" "^5.4.0"
"@ethersproject/address@5.5.0", "@ethersproject/address@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.5.0.tgz#bcc6f576a553f21f3dd7ba17248f81b473c9c78f"
integrity sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw==
dependencies:
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/keccak256" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/rlp" "^5.5.0"
"@ethersproject/address@5.6.1", "@ethersproject/address@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.6.1.tgz#ab57818d9aefee919c5721d28cd31fd95eff413d"
......@@ -919,13 +846,6 @@
dependencies:
"@ethersproject/bytes" "^5.4.0"
"@ethersproject/base64@5.5.0", "@ethersproject/base64@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.5.0.tgz#881e8544e47ed976930836986e5eb8fab259c090"
integrity sha512-tdayUKhU1ljrlHzEWbStXazDpsx4eg1dBXUSI6+mHlYklOXoXF6lZvw8tnD6oVaWfnMxAgRSKROg3cVKtCcppA==
dependencies:
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/base64@5.6.1", "@ethersproject/base64@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.6.1.tgz#2c40d8a0310c9d1606c2c37ae3092634b41d87cb"
......@@ -941,14 +861,6 @@
"@ethersproject/bytes" "^5.4.0"
"@ethersproject/properties" "^5.4.0"
"@ethersproject/basex@5.5.0", "@ethersproject/basex@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.5.0.tgz#e40a53ae6d6b09ab4d977bd037010d4bed21b4d3"
integrity sha512-ZIodwhHpVJ0Y3hUCfUucmxKsWQA5TMnavp5j/UOuDdzZWzJlRmuOjcTMIGgHCYuZmHt36BfiSyQPSRskPxbfaQ==
dependencies:
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/basex@5.6.1", "@ethersproject/basex@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.6.1.tgz#badbb2f1d4a6f52ce41c9064f01eab19cc4c5305"
......@@ -966,15 +878,6 @@
"@ethersproject/logger" "^5.4.0"
bn.js "^4.11.9"
"@ethersproject/bignumber@5.5.0", "@ethersproject/bignumber@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.5.0.tgz#875b143f04a216f4f8b96245bde942d42d279527"
integrity sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg==
dependencies:
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
bn.js "^4.11.9"
"@ethersproject/bignumber@5.6.2", "@ethersproject/bignumber@^5.6.2":
version "5.6.2"
resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.6.2.tgz#72a0717d6163fab44c47bcc82e0c550ac0315d66"
......@@ -991,13 +894,6 @@
dependencies:
"@ethersproject/logger" "^5.4.0"
"@ethersproject/bytes@5.5.0", "@ethersproject/bytes@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.5.0.tgz#cb11c526de657e7b45d2e0f0246fb3b9d29a601c"
integrity sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog==
dependencies:
"@ethersproject/logger" "^5.5.0"
"@ethersproject/bytes@5.6.1", "@ethersproject/bytes@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.6.1.tgz#24f916e411f82a8a60412344bf4a813b917eefe7"
......@@ -1012,13 +908,6 @@
dependencies:
"@ethersproject/bignumber" "^5.4.0"
"@ethersproject/constants@5.5.0", "@ethersproject/constants@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.5.0.tgz#d2a2cd7d94bd1d58377d1d66c4f53c9be4d0a45e"
integrity sha512-2MsRRVChkvMWR+GyMGY4N1sAX9Mt3J9KykCsgUFd/1mwS0UH1qw+Bv9k1UJb3X3YJYFco9H20pjSlOIfCG5HYQ==
dependencies:
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/constants@5.6.1", "@ethersproject/constants@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.6.1.tgz#e2e974cac160dd101cf79fdf879d7d18e8cb1370"
......@@ -1042,22 +931,6 @@
"@ethersproject/properties" "^5.4.0"
"@ethersproject/transactions" "^5.4.0"
"@ethersproject/contracts@5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.5.0.tgz#b735260d4bd61283a670a82d5275e2a38892c197"
integrity sha512-2viY7NzyvJkh+Ug17v7g3/IJC8HqZBDcOjYARZLdzRxrfGlRgmYgl6xPRKVbEzy1dWKw/iv7chDcS83pg6cLxg==
dependencies:
"@ethersproject/abi" "^5.5.0"
"@ethersproject/abstract-provider" "^5.5.0"
"@ethersproject/abstract-signer" "^5.5.0"
"@ethersproject/address" "^5.5.0"
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/constants" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/transactions" "^5.5.0"
"@ethersproject/contracts@5.6.2":
version "5.6.2"
resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.6.2.tgz#20b52e69ebc1b74274ff8e3d4e508de971c287bc"
......@@ -1074,18 +947,6 @@
"@ethersproject/properties" "^5.6.0"
"@ethersproject/transactions" "^5.6.2"
"@ethersproject/hardware-wallets@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/hardware-wallets/-/hardware-wallets-5.5.0.tgz#b4a3bc99a843c3b78b133cdf94485a567ba17b8d"
integrity sha512-oZh/Ps/ohxFQdKVeMw8wpw0xZpL+ndsRlQwNE3Eki2vLeH2to14de6fNrgETZtAbAhzglH6ES9Nlx1+UuqvvYg==
dependencies:
"@ledgerhq/hw-app-eth" "5.27.2"
"@ledgerhq/hw-transport" "5.26.0"
"@ledgerhq/hw-transport-u2f" "5.26.0"
ethers "^5.5.0"
optionalDependencies:
"@ledgerhq/hw-transport-node-hid" "5.26.0"
"@ethersproject/hardware-wallets@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/hardware-wallets/-/hardware-wallets-5.6.1.tgz#d99ee1c6169bd9f3423c386e3b11e0ab09f34964"
......@@ -1112,20 +973,6 @@
"@ethersproject/properties" "^5.4.0"
"@ethersproject/strings" "^5.4.0"
"@ethersproject/hash@5.5.0", "@ethersproject/hash@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.5.0.tgz#7cee76d08f88d1873574c849e0207dcb32380cc9"
integrity sha512-dnGVpK1WtBjmnp3mUT0PlU2MpapnwWI0PibldQEq1408tQBAbZpPidkWoVVuNMOl/lISO3+4hXZWCL3YV7qzfg==
dependencies:
"@ethersproject/abstract-signer" "^5.5.0"
"@ethersproject/address" "^5.5.0"
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/keccak256" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/strings" "^5.5.0"
"@ethersproject/hash@5.6.1", "@ethersproject/hash@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.6.1.tgz#224572ea4de257f05b4abf8ae58b03a67e99b0f4"
......@@ -1158,24 +1005,6 @@
"@ethersproject/transactions" "^5.4.0"
"@ethersproject/wordlists" "^5.4.0"
"@ethersproject/hdnode@5.5.0", "@ethersproject/hdnode@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.5.0.tgz#4a04e28f41c546f7c978528ea1575206a200ddf6"
integrity sha512-mcSOo9zeUg1L0CoJH7zmxwUG5ggQHU1UrRf8jyTYy6HxdZV+r0PBoL1bxr+JHIPXRzS6u/UW4mEn43y0tmyF8Q==
dependencies:
"@ethersproject/abstract-signer" "^5.5.0"
"@ethersproject/basex" "^5.5.0"
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/pbkdf2" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/sha2" "^5.5.0"
"@ethersproject/signing-key" "^5.5.0"
"@ethersproject/strings" "^5.5.0"
"@ethersproject/transactions" "^5.5.0"
"@ethersproject/wordlists" "^5.5.0"
"@ethersproject/hdnode@5.6.2", "@ethersproject/hdnode@^5.6.2":
version "5.6.2"
resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.6.2.tgz#26f3c83a3e8f1b7985c15d1db50dc2903418b2d2"
......@@ -1213,25 +1042,6 @@
aes-js "3.0.0"
scrypt-js "3.0.1"
"@ethersproject/json-wallets@5.5.0", "@ethersproject/json-wallets@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.5.0.tgz#dd522d4297e15bccc8e1427d247ec8376b60e325"
integrity sha512-9lA21XQnCdcS72xlBn1jfQdj2A1VUxZzOzi9UkNdnokNKke/9Ya2xA9aIK1SC3PQyBDLt4C+dfps7ULpkvKikQ==
dependencies:
"@ethersproject/abstract-signer" "^5.5.0"
"@ethersproject/address" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/hdnode" "^5.5.0"
"@ethersproject/keccak256" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/pbkdf2" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/random" "^5.5.0"
"@ethersproject/strings" "^5.5.0"
"@ethersproject/transactions" "^5.5.0"
aes-js "3.0.0"
scrypt-js "3.0.1"
"@ethersproject/json-wallets@5.6.1", "@ethersproject/json-wallets@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.6.1.tgz#3f06ba555c9c0d7da46756a12ac53483fe18dd91"
......@@ -1259,14 +1069,6 @@
"@ethersproject/bytes" "^5.4.0"
js-sha3 "0.5.7"
"@ethersproject/keccak256@5.5.0", "@ethersproject/keccak256@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.5.0.tgz#e4b1f9d7701da87c564ffe336f86dcee82983492"
integrity sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg==
dependencies:
"@ethersproject/bytes" "^5.5.0"
js-sha3 "0.8.0"
"@ethersproject/keccak256@5.6.1", "@ethersproject/keccak256@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.6.1.tgz#b867167c9b50ba1b1a92bccdd4f2d6bd168a91cc"
......@@ -1280,11 +1082,6 @@
resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.4.0.tgz#f39adadf62ad610c420bcd156fd41270e91b3ca9"
integrity sha512-xYdWGGQ9P2cxBayt64d8LC8aPFJk6yWCawQi/4eJ4+oJdMMjEBMrIcIMZ9AxhwpPVmnBPrsB10PcXGmGAqgUEQ==
"@ethersproject/logger@5.5.0", "@ethersproject/logger@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.5.0.tgz#0c2caebeff98e10aefa5aef27d7441c7fd18cf5d"
integrity sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg==
"@ethersproject/logger@5.6.0", "@ethersproject/logger@^5.6.0":
version "5.6.0"
resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.6.0.tgz#d7db1bfcc22fd2e4ab574cba0bb6ad779a9a3e7a"
......@@ -1297,13 +1094,6 @@
dependencies:
"@ethersproject/logger" "^5.4.0"
"@ethersproject/networks@5.5.2", "@ethersproject/networks@^5.5.0":
version "5.5.2"
resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.5.2.tgz#784c8b1283cd2a931114ab428dae1bd00c07630b"
integrity sha512-NEqPxbGBfy6O3x4ZTISb90SjEDkWYDUbEeIFhJly0F7sZjoQMnj5KYzMSkMkLKZ+1fGpx00EDpHQCy6PrDupkQ==
dependencies:
"@ethersproject/logger" "^5.5.0"
"@ethersproject/networks@5.6.3", "@ethersproject/networks@^5.6.3":
version "5.6.3"
resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.6.3.tgz#3ee3ab08f315b433b50c99702eb32e0cf31f899f"
......@@ -1319,14 +1109,6 @@
"@ethersproject/bytes" "^5.4.0"
"@ethersproject/sha2" "^5.4.0"
"@ethersproject/pbkdf2@5.5.0", "@ethersproject/pbkdf2@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.5.0.tgz#e25032cdf02f31505d47afbf9c3e000d95c4a050"
integrity sha512-SaDvQFvXPnz1QGpzr6/HToLifftSXGoXrbpZ6BvoZhmx4bNLHrxDe8MZisuecyOziP1aVEwzC2Hasj+86TgWVg==
dependencies:
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/sha2" "^5.5.0"
"@ethersproject/pbkdf2@5.6.1", "@ethersproject/pbkdf2@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.6.1.tgz#f462fe320b22c0d6b1d72a9920a3963b09eb82d1"
......@@ -1342,13 +1124,6 @@
dependencies:
"@ethersproject/logger" "^5.4.0"
"@ethersproject/properties@5.5.0", "@ethersproject/properties@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.5.0.tgz#61f00f2bb83376d2071baab02245f92070c59995"
integrity sha512-l3zRQg3JkD8EL3CPjNK5g7kMx4qSwiR60/uk5IVjd3oq1MZR5qUg40CNOoEJoX5wc3DyY5bt9EbMk86C7x0DNA==
dependencies:
"@ethersproject/logger" "^5.5.0"
"@ethersproject/properties@5.6.0", "@ethersproject/properties@^5.6.0":
version "5.6.0"
resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.6.0.tgz#38904651713bc6bdd5bdd1b0a4287ecda920fa04"
......@@ -1381,31 +1156,6 @@
bech32 "1.1.4"
ws "7.4.6"
"@ethersproject/providers@5.5.3", "@ethersproject/providers@^5.5.3":
version "5.5.3"
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.5.3.tgz#56c2b070542ac44eb5de2ed3cf6784acd60a3130"
integrity sha512-ZHXxXXXWHuwCQKrgdpIkbzMNJMvs+9YWemanwp1fA7XZEv7QlilseysPvQe0D7Q7DlkJX/w/bGA1MdgK2TbGvA==
dependencies:
"@ethersproject/abstract-provider" "^5.5.0"
"@ethersproject/abstract-signer" "^5.5.0"
"@ethersproject/address" "^5.5.0"
"@ethersproject/basex" "^5.5.0"
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/constants" "^5.5.0"
"@ethersproject/hash" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/networks" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/random" "^5.5.0"
"@ethersproject/rlp" "^5.5.0"
"@ethersproject/sha2" "^5.5.0"
"@ethersproject/strings" "^5.5.0"
"@ethersproject/transactions" "^5.5.0"
"@ethersproject/web" "^5.5.0"
bech32 "1.1.4"
ws "7.4.6"
"@ethersproject/providers@5.6.8", "@ethersproject/providers@^5.6.8":
version "5.6.8"
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.6.8.tgz#22e6c57be215ba5545d3a46cf759d265bb4e879d"
......@@ -1440,14 +1190,6 @@
"@ethersproject/bytes" "^5.4.0"
"@ethersproject/logger" "^5.4.0"
"@ethersproject/random@5.5.1":
version "5.5.1"
resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.5.1.tgz#7cdf38ea93dc0b1ed1d8e480ccdaf3535c555415"
integrity sha512-YaU2dQ7DuhL5Au7KbcQLHxcRHfgyNgvFV4sQOo0HrtW3Zkrc9ctWNz8wXQ4uCSfSDsqX2vcjhroxU5RQRV0nqA==
dependencies:
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/random@5.6.1", "@ethersproject/random@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.6.1.tgz#66915943981bcd3e11bbd43733f5c3ba5a790255"
......@@ -1456,14 +1198,6 @@
"@ethersproject/bytes" "^5.6.1"
"@ethersproject/logger" "^5.6.0"
"@ethersproject/random@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.5.0.tgz#305ed9e033ca537735365ac12eed88580b0f81f9"
integrity sha512-egGYZwZ/YIFKMHcoBUo8t3a8Hb/TKYX8BCBoLjudVCZh892welR3jOxgOmb48xznc9bTcMm7Tpwc1gHC1PFNFQ==
dependencies:
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/rlp@5.4.0", "@ethersproject/rlp@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.4.0.tgz#de61afda5ff979454e76d3b3310a6c32ad060931"
......@@ -1472,14 +1206,6 @@
"@ethersproject/bytes" "^5.4.0"
"@ethersproject/logger" "^5.4.0"
"@ethersproject/rlp@5.5.0", "@ethersproject/rlp@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.5.0.tgz#530f4f608f9ca9d4f89c24ab95db58ab56ab99a0"
integrity sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA==
dependencies:
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/rlp@5.6.1", "@ethersproject/rlp@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.6.1.tgz#df8311e6f9f24dcb03d59a2bac457a28a4fe2bd8"
......@@ -1497,15 +1223,6 @@
"@ethersproject/logger" "^5.4.0"
hash.js "1.1.7"
"@ethersproject/sha2@5.5.0", "@ethersproject/sha2@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.5.0.tgz#a40a054c61f98fd9eee99af2c3cc6ff57ec24db7"
integrity sha512-B5UBoglbCiHamRVPLA110J+2uqsifpZaTmid2/7W5rbtYVz6gus6/hSDieIU/6gaKIDcOj12WnOdiymEUHIAOA==
dependencies:
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
hash.js "1.1.7"
"@ethersproject/sha2@5.6.1", "@ethersproject/sha2@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.6.1.tgz#211f14d3f5da5301c8972a8827770b6fd3e51656"
......@@ -1527,18 +1244,6 @@
elliptic "6.5.4"
hash.js "1.1.7"
"@ethersproject/signing-key@5.5.0", "@ethersproject/signing-key@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.5.0.tgz#2aa37169ce7e01e3e80f2c14325f624c29cedbe0"
integrity sha512-5VmseH7qjtNmDdZBswavhotYbWB0bOwKIlOTSlX14rKn5c11QmJwGt4GHeo7NrL/Ycl7uo9AHvEqs5xZgFBTng==
dependencies:
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
bn.js "^4.11.9"
elliptic "6.5.4"
hash.js "1.1.7"
"@ethersproject/signing-key@5.6.2", "@ethersproject/signing-key@^5.6.2":
version "5.6.2"
resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.6.2.tgz#8a51b111e4d62e5a62aee1da1e088d12de0614a3"
......@@ -1562,18 +1267,6 @@
"@ethersproject/sha2" "^5.4.0"
"@ethersproject/strings" "^5.4.0"
"@ethersproject/solidity@5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.5.0.tgz#2662eb3e5da471b85a20531e420054278362f93f"
integrity sha512-9NgZs9LhGMj6aCtHXhtmFQ4AN4sth5HuFXVvAQtzmm0jpSCNOTGtrHZJAeYTh7MBjRR8brylWZxBZR9zDStXbw==
dependencies:
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/keccak256" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/sha2" "^5.5.0"
"@ethersproject/strings" "^5.5.0"
"@ethersproject/solidity@5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.6.1.tgz#5845e71182c66d32e6ec5eefd041fca091a473e2"
......@@ -1595,15 +1288,6 @@
"@ethersproject/constants" "^5.4.0"
"@ethersproject/logger" "^5.4.0"
"@ethersproject/strings@5.5.0", "@ethersproject/strings@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.5.0.tgz#e6784d00ec6c57710755699003bc747e98c5d549"
integrity sha512-9fy3TtF5LrX/wTrBaT8FGE6TDJyVjOvXynXJz5MT5azq+E6D92zuKNx7i29sWW2FjVOaWjAsiZ1ZWznuduTIIQ==
dependencies:
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/constants" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/strings@5.6.1", "@ethersproject/strings@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.6.1.tgz#dbc1b7f901db822b5cafd4ebf01ca93c373f8952"
......@@ -1628,21 +1312,6 @@
"@ethersproject/rlp" "^5.4.0"
"@ethersproject/signing-key" "^5.4.0"
"@ethersproject/transactions@5.5.0", "@ethersproject/transactions@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.5.0.tgz#7e9bf72e97bcdf69db34fe0d59e2f4203c7a2908"
integrity sha512-9RZYSKX26KfzEd/1eqvv8pLauCKzDTub0Ko4LfIgaERvRuwyaNV78mJs7cpIgZaDl6RJui4o49lHwwCM0526zA==
dependencies:
"@ethersproject/address" "^5.5.0"
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/constants" "^5.5.0"
"@ethersproject/keccak256" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/rlp" "^5.5.0"
"@ethersproject/signing-key" "^5.5.0"
"@ethersproject/transactions@5.6.2", "@ethersproject/transactions@^5.6.2":
version "5.6.2"
resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.6.2.tgz#793a774c01ced9fe7073985bb95a4b4e57a6370b"
......@@ -1667,15 +1336,6 @@
"@ethersproject/constants" "^5.4.0"
"@ethersproject/logger" "^5.4.0"
"@ethersproject/units@5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.5.0.tgz#104d02db5b5dc42cc672cc4587bafb87a95ee45e"
integrity sha512-7+DpjiZk4v6wrikj+TCyWWa9dXLNU73tSTa7n0TSJDxkYbV3Yf1eRh9ToMLlZtuctNYu9RDNNy2USq3AdqSbag==
dependencies:
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/constants" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/units@5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.6.1.tgz#ecc590d16d37c8f9ef4e89e2005bda7ddc6a4e6f"
......@@ -1706,27 +1366,6 @@
"@ethersproject/transactions" "^5.4.0"
"@ethersproject/wordlists" "^5.4.0"
"@ethersproject/wallet@5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.5.0.tgz#322a10527a440ece593980dca6182f17d54eae75"
integrity sha512-Mlu13hIctSYaZmUOo7r2PhNSd8eaMPVXe1wxrz4w4FCE4tDYBywDH+bAR1Xz2ADyXGwqYMwstzTrtUVIsKDO0Q==
dependencies:
"@ethersproject/abstract-provider" "^5.5.0"
"@ethersproject/abstract-signer" "^5.5.0"
"@ethersproject/address" "^5.5.0"
"@ethersproject/bignumber" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/hash" "^5.5.0"
"@ethersproject/hdnode" "^5.5.0"
"@ethersproject/json-wallets" "^5.5.0"
"@ethersproject/keccak256" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/random" "^5.5.0"
"@ethersproject/signing-key" "^5.5.0"
"@ethersproject/transactions" "^5.5.0"
"@ethersproject/wordlists" "^5.5.0"
"@ethersproject/wallet@5.6.2":
version "5.6.2"
resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.6.2.tgz#cd61429d1e934681e413f4bc847a5f2f87e3a03c"
......@@ -1759,17 +1398,6 @@
"@ethersproject/properties" "^5.4.0"
"@ethersproject/strings" "^5.4.0"
"@ethersproject/web@5.5.1", "@ethersproject/web@^5.5.0", "@ethersproject/web@^5.5.1":
version "5.5.1"
resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.5.1.tgz#cfcc4a074a6936c657878ac58917a61341681316"
integrity sha512-olvLvc1CB12sREc1ROPSHTdFCdvMh0J5GSJYiQg2D0hdD4QmJDy8QYDb1CvoqD/bF1c++aeKv2sR5uduuG9dQg==
dependencies:
"@ethersproject/base64" "^5.5.0"
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/strings" "^5.5.0"
"@ethersproject/web@5.6.1", "@ethersproject/web@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.6.1.tgz#6e2bd3ebadd033e6fe57d072db2b69ad2c9bdf5d"
......@@ -1792,17 +1420,6 @@
"@ethersproject/properties" "^5.4.0"
"@ethersproject/strings" "^5.4.0"
"@ethersproject/wordlists@5.5.0", "@ethersproject/wordlists@^5.5.0":
version "5.5.0"
resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.5.0.tgz#aac74963aa43e643638e5172353d931b347d584f"
integrity sha512-bL0UTReWDiaQJJYOC9sh/XcRu/9i2jMrzf8VLRmPKx58ckSlOJiohODkECCO50dtLZHcGU6MLXQ4OOrgBwP77Q==
dependencies:
"@ethersproject/bytes" "^5.5.0"
"@ethersproject/hash" "^5.5.0"
"@ethersproject/logger" "^5.5.0"
"@ethersproject/properties" "^5.5.0"
"@ethersproject/strings" "^5.5.0"
"@ethersproject/wordlists@5.6.1", "@ethersproject/wordlists@^5.6.1":
version "5.6.1"
resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.6.1.tgz#1e78e2740a8a21e9e99947e47979d72e130aeda1"
......@@ -2648,6 +2265,16 @@
tweetnacl "^1.0.3"
tweetnacl-util "^0.15.1"
"@noble/hashes@1.0.0", "@noble/hashes@~1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.0.0.tgz#d5e38bfbdaba174805a4e649f13be9a9ed3351ae"
integrity sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg==
"@noble/secp256k1@1.5.5", "@noble/secp256k1@~1.5.2":
version "1.5.5"
resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.5.5.tgz#315ab5745509d1a8c8e90d0bdf59823ccf9bcfc3"
integrity sha512-sZ1W6gQzYnu45wPrWx8D3kwI2/U29VYTx9OjbDAd7jwRItJ0cSTMPRL/C8AWZFn9kWFLQGqEXVEE86w4Z8LpIQ==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
......@@ -2899,6 +2526,11 @@
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.3.2.tgz#ff80affd6d352dbe1bbc5b4e1833c41afd6283b6"
integrity sha512-AybF1cesONZStg5kWf6ao9OlqTZuPqddvprc0ky7lrUVOjXeKpmQ2Y9FK+6ygxasb+4aic4O5pneFBfwVsRRRg==
"@openzeppelin/contracts@4.6.0":
version "4.6.0"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.6.0.tgz#c91cf64bc27f573836dba4122758b4743418c1b3"
integrity sha512-8vi4d50NNya/bQqCmaVzvHNmwHvS0OBKb7HNtuNwEE3scXWrP31fKQoGxNMT+KbzmrNZzatE3QK5p2gFONI/hg==
"@openzeppelin/contracts@^4.3.2":
version "4.3.3"
resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.3.3.tgz#ff6ee919fc2a1abaf72b22814bfb72ed129ec137"
......@@ -2916,11 +2548,6 @@
dependencies:
squirrelly "^8.0.8"
"@rari-capital/solmate@^6.3.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@rari-capital/solmate/-/solmate-6.3.0.tgz#01050e276e71dc4cd4169cf8002b447eb07d90c3"
integrity sha512-SWPbnfZUCe4ahHNqcb0qsPrzzAzMZMoA3x6SxZn04g0dLm0xupVeHonM3LK13uhPGIULF8HzXg8CgXE/fEnMlQ==
"@resolver-engine/core@^0.3.3":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@resolver-engine/core/-/core-0.3.3.tgz#590f77d85d45bc7ecc4e06c654f41345db6ca967"
......@@ -2958,6 +2585,28 @@
path-browserify "^1.0.0"
url "^0.11.0"
"@scure/base@~1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.0.0.tgz#109fb595021de285f05a7db6806f2f48296fcee7"
integrity sha512-gIVaYhUsy+9s58m/ETjSJVKHhKTBMmcRb9cEV5/5dwvfDlfORjKrFsDeDHWRrm6RjcPvCLZFwGJjAjLj1gg4HA==
"@scure/bip32@1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.0.1.tgz#1409bdf9f07f0aec99006bb0d5827693418d3aa5"
integrity sha512-AU88KKTpQ+YpTLoicZ/qhFhRRIo96/tlb+8YmDDHR9yiKVjSsFZiefJO4wjS2PMTkz5/oIcw84uAq/8pleQURA==
dependencies:
"@noble/hashes" "~1.0.0"
"@noble/secp256k1" "~1.5.2"
"@scure/base" "~1.0.0"
"@scure/bip39@1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.0.0.tgz#47504e58de9a56a4bbed95159d2d6829fa491bb0"
integrity sha512-HrtcikLbd58PWOkl02k9V6nXWQyoa7A0+Ek9VF7z17DDk9XZAFUcIdqfh0jJXLypmizc5/8P6OxoUeKliiWv4w==
dependencies:
"@noble/hashes" "~1.0.0"
"@scure/base" "~1.0.0"
"@sentry/core@5.30.0":
version "5.30.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3"
......@@ -3125,7 +2774,7 @@
dependencies:
antlr4ts "^0.5.0-alpha.4"
"@solidity-parser/parser@^0.14.1":
"@solidity-parser/parser@^0.14.0", "@solidity-parser/parser@^0.14.1":
version "0.14.1"
resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.1.tgz#179afb29f4e295a77cc141151f26b3848abc3c46"
integrity sha512-eLjj2L6AuQjBB6s/ibwCAc0DwrR5Ge+ys+wgWo+bviU7fV2nTMQhU63CGaDKXg9iTmMxwhkyoggdIR7ZGRfMgw==
......@@ -4113,6 +3762,11 @@ array-union@^2.1.0:
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
array-uniq@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==
array-unique@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
......@@ -5849,7 +5503,7 @@ colors@1.0.3:
resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b"
integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
colors@^1.1.2:
colors@1.4.0, colors@^1.1.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
......@@ -7414,6 +7068,27 @@ eth-gas-reporter@^0.2.20:
sha1 "^1.1.1"
sync-request "^6.0.0"
eth-gas-reporter@^0.2.24:
version "0.2.25"
resolved "https://registry.yarnpkg.com/eth-gas-reporter/-/eth-gas-reporter-0.2.25.tgz#546dfa946c1acee93cb1a94c2a1162292d6ff566"
integrity sha512-1fRgyE4xUB8SoqLgN3eDfpDfwEfRxh2Sz1b7wzFbyQA+9TekMmvSjjoRu9SKcSVyK+vLkLIsVbJDsTWjw195OQ==
dependencies:
"@ethersproject/abi" "^5.0.0-beta.146"
"@solidity-parser/parser" "^0.14.0"
cli-table3 "^0.5.0"
colors "1.4.0"
ethereum-cryptography "^1.0.3"
ethers "^4.0.40"
fs-readdir-recursive "^1.1.0"
lodash "^4.17.14"
markdown-table "^1.1.3"
mocha "^7.1.1"
req-cwd "^2.0.0"
request "^2.88.0"
request-promise-native "^1.0.5"
sha1 "^1.1.1"
sync-request "^6.0.0"
eth-json-rpc-infura@^3.1.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/eth-json-rpc-infura/-/eth-json-rpc-infura-3.2.1.tgz#26702a821067862b72d979c016fd611502c6057f"
......@@ -7556,6 +7231,16 @@ ethereum-cryptography@^0.1.2, ethereum-cryptography@^0.1.3:
secp256k1 "^4.0.1"
setimmediate "^1.0.5"
ethereum-cryptography@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/ethereum-cryptography/-/ethereum-cryptography-1.0.3.tgz#b1f8f4e702434b2016248dbb2f9fdd60c54772d8"
integrity sha512-NQLTW0x0CosoVb/n79x/TRHtfvS3hgNUPTUSCu0vM+9k6IIhHFFrAOJReneexjZsoZxMjJHnJn4lrE8EbnSyqQ==
dependencies:
"@noble/hashes" "1.0.0"
"@noble/secp256k1" "1.5.5"
"@scure/bip32" "1.0.1"
"@scure/bip39" "1.0.0"
ethereum-waffle@^3.3.0, ethereum-waffle@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/ethereum-waffle/-/ethereum-waffle-3.4.0.tgz#990b3c6c26db9c2dd943bf26750a496f60c04720"
......@@ -7849,42 +7534,6 @@ ethers@^5.0.0, ethers@^5.0.1, ethers@^5.0.2:
"@ethersproject/web" "5.4.0"
"@ethersproject/wordlists" "5.4.0"
ethers@^5.5.0, ethers@^5.5.4:
version "5.5.4"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.5.4.tgz#e1155b73376a2f5da448e4a33351b57a885f4352"
integrity sha512-N9IAXsF8iKhgHIC6pquzRgPBJEzc9auw3JoRkaKe+y4Wl/LFBtDDunNe7YmdomontECAcC5APaAgWZBiu1kirw==
dependencies:
"@ethersproject/abi" "5.5.0"
"@ethersproject/abstract-provider" "5.5.1"
"@ethersproject/abstract-signer" "5.5.0"
"@ethersproject/address" "5.5.0"
"@ethersproject/base64" "5.5.0"
"@ethersproject/basex" "5.5.0"
"@ethersproject/bignumber" "5.5.0"
"@ethersproject/bytes" "5.5.0"
"@ethersproject/constants" "5.5.0"
"@ethersproject/contracts" "5.5.0"
"@ethersproject/hash" "5.5.0"
"@ethersproject/hdnode" "5.5.0"
"@ethersproject/json-wallets" "5.5.0"
"@ethersproject/keccak256" "5.5.0"
"@ethersproject/logger" "5.5.0"
"@ethersproject/networks" "5.5.2"
"@ethersproject/pbkdf2" "5.5.0"
"@ethersproject/properties" "5.5.0"
"@ethersproject/providers" "5.5.3"
"@ethersproject/random" "5.5.1"
"@ethersproject/rlp" "5.5.0"
"@ethersproject/sha2" "5.5.0"
"@ethersproject/signing-key" "5.5.0"
"@ethersproject/solidity" "5.5.0"
"@ethersproject/strings" "5.5.0"
"@ethersproject/transactions" "5.5.0"
"@ethersproject/units" "5.5.0"
"@ethersproject/wallet" "5.5.0"
"@ethersproject/web" "5.5.1"
"@ethersproject/wordlists" "5.5.0"
ethers@^5.5.3, ethers@^5.6.8:
version "5.6.8"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.6.8.tgz#d36b816b4896341a80a8bbd2a44e8cb6e9b98dd4"
......@@ -9109,6 +8758,15 @@ hardhat-gas-reporter@^1.0.4:
eth-gas-reporter "^0.2.20"
sha1 "^1.1.1"
hardhat-gas-reporter@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.8.tgz#93ce271358cd748d9c4185dbb9d1d5525ec145e0"
integrity sha512-1G5thPnnhcwLHsFnl759f2tgElvuwdkzxlI65fC9PwxYMEe9cmjkVAAWTf3/3y8uP6ZSPiUiOW8PgZnykmZe0g==
dependencies:
array-uniq "1.0.3"
eth-gas-reporter "^0.2.24"
sha1 "^1.1.1"
hardhat-output-validator@^0.1.18:
version "0.1.18"
resolved "https://registry.yarnpkg.com/hardhat-output-validator/-/hardhat-output-validator-0.1.18.tgz#4ca065ee203da323a6360524c9cdc2577f850865"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment