diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c99b1f..50ae72a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,27 +56,3 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - run: yarn build - run: yarn test - - migrations: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-node@v2 - with: - node-version: 12.x - registry-url: 'https://registry.npmjs.org' - - - id: yarn-cache - run: echo "::set-output name=dir::$(yarn cache dir)" - - - uses: actions/cache@v1 - with: - path: ${{ steps.yarn-cache.outputs.dir }} - key: yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - yarn- - - run: yarn - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - run: yarn test:deploy diff --git a/contracts/utils/AuthEtherFaucet.sol b/contracts/utils/AuthEtherFaucet.sol new file mode 100644 index 0000000..a3c099a --- /dev/null +++ b/contracts/utils/AuthEtherFaucet.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; +import "../access/AccessControl.sol"; + + +/// @dev AuthEtherFaucet allowes privileged users to distribute Ether stored in this contract. +contract AuthEtherFaucet is AccessControl { + event Sent(address indexed to, uint256 amount); + + constructor(address[] memory operators) AccessControl() { + for (uint256 i = 0; i < operators.length; i++) + _grantRole(AuthEtherFaucet.drip.selector, operators[i]); // 0x9e353a1e + } + + receive() external payable {} + + function drip(address payable to, uint256 amount) + external + auth + { + (bool sent,) = to.call{value: amount}(""); + require(sent, "Failed to send Ether"); + emit Sent(to, amount); + } +} \ No newline at end of file diff --git a/deploy/auth_ether_faucet.ts b/deploy/auth_ether_faucet.ts new file mode 100644 index 0000000..5eed59c --- /dev/null +++ b/deploy/auth_ether_faucet.ts @@ -0,0 +1,17 @@ +import { ethers } from 'hardhat' +import * as fs from 'fs' + +import { AuthEtherFaucet } from '../typechain/AuthEtherFaucet' + +/** + * @dev This script deploys the SafeERC20Namer and YieldMath libraries + */ + +(async () => { + let faucet: AuthEtherFaucet + const [ ownerAcc ] = await ethers.getSigners(); + const faucetFactory = await ethers.getContractFactory('AuthEtherFaucet') + faucet = ((await faucetFactory.deploy([ownerAcc.address])) as unknown) as AuthEtherFaucet + await faucet.deployed() + console.log(`Faucet deployed at ${faucet.address}`) +})() diff --git a/deploy/faucet-args-42.js b/deploy/faucet-args-42.js new file mode 100644 index 0000000..2f3231a --- /dev/null +++ b/deploy/faucet-args-42.js @@ -0,0 +1,5 @@ +const operators = ['0x5AD7799f02D5a829B2d6FA085e6bd69A872619D5'] + +module.exports = [ + operators, +]; diff --git a/hardhat.config.ts b/hardhat.config.ts index 70d80c8..7cafe61 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -9,6 +9,9 @@ import 'hardhat-gas-reporter' import 'hardhat-typechain' import 'solidity-coverage' import 'hardhat-deploy' +import { task } from 'hardhat/config' + +// import { addAsset, makeBase, makeIlk, addSeries } from './scripts/add' // REQUIRED TO ENSURE METADATA IS SAVED IN DEPLOYMENTS (because solidity-coverage disable it otherwise) /* import { @@ -20,6 +23,42 @@ task(TASK_COMPILE_GET_COMPILER_INPUT).setAction(async (_, bre, runSuper) => { return input }) */ +/* task("asset", "Adds assets and makes them into ilks and/or bases") + .addFlag("add", "Add asset") + .addFlag("base", "Make asset into base") + .addFlag("ilk", "Make asset into ilk") + .addVariadicPositionalParam("asset", "The details of the asset") + .setAction(async (taskArgs, hre) => { + const argv: any = {} + if (taskArgs.add) { + argv.asset = taskArgs.asset[0] // address + await addAsset(argv, hre) + } else if (taskArgs.base) { + argv.asset = taskArgs.asset[0] // address + argv.rateSource = [1] // address + argv.chiSource = [2] // address + await makeBase(argv, hre) + } else if (taskArgs.ilk) { + argv.asset = taskArgs.asset[0] // address, p.e. MKR, which will be used as collateral + argv.base = taskArgs.asset[1] // address, p.e. DAI, which will be the underlying + argv.spotSource = taskArgs.asset[2] // address, p.e. DAI/MKR, which will be the source for the spot oracle + await makeIlk(argv, hre) + } else { + console.error("Must add an asset, make an asset into a base or make an asset into an ilk") + } +}); + +task("series", "Adds a series") + .addVariadicPositionalParam("series", "The details of the series") + .setAction(async (taskArgs, hre) => { + const argv: any = {} + argv.seriesId = taskArgs.series[0] // address, p.e. MKR, which will be used as collateral + argv.base = taskArgs.series[1] // address, p.e. DAI, which will be the underlying + argv.maturity = taskArgs.series[2] // address, p.e. DAI, which will be the underlying + argv.ilkIds = [] + argv.ilkIds = taskArgs.series.slice(3).forEach((ilkId: any) => { argv.ilkIds.push(ilkId) }) + await addSeries(argv, hre) +}); */ function nodeUrl(network: any) { let infuraKey @@ -54,21 +93,21 @@ module.exports = { settings: { optimizer: { enabled: true, - runs: 20000, + runs: 1000, } } }, - typechain: { - outDir: 'typechain', - target: 'ethers-v5', - }, abiExporter: { - path: './abi', + path: './abis', clear: true, flat: true, // only: [':ERC20$'], spacing: 2 }, + typechain: { + outDir: 'typechain', + target: 'ethers-v5', + }, contractSizer: { alphaSort: true, runOnCompile: false, @@ -84,8 +123,16 @@ module.exports = { other: 2, }, networks: { + hardhat: { + chainId: 31337 + }, + localhost: { + chainId: 31337 + }, kovan: { accounts, + gasPrice: 10000000000, + timeout: 600000, url: nodeUrl('kovan') }, goerli: { @@ -102,6 +149,7 @@ module.exports = { }, mainnet: { accounts, + timeout: 600000, url: nodeUrl('mainnet') }, coverage: { @@ -111,4 +159,4 @@ module.exports = { etherscan: { apiKey: etherscanKey }, -} \ No newline at end of file +} diff --git a/package.json b/package.json index bc02fc5..241d081 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@yield-protocol/utils-v2", - "version": "2.4.5", + "version": "2.4.6", "description": "Yield v2 utility contracts", "author": "Yield Inc.", "files": [ diff --git a/test/005_timelock.ts b/test/005_timelock.ts index fadf8bc..95c4cab 100644 --- a/test/005_timelock.ts +++ b/test/005_timelock.ts @@ -60,7 +60,8 @@ describe("Timelock", async function () { "TG2", ])) as ERC20; timelock = (await deployContract(governorAcc, TimelockArtifact, [ - governor, executor + governor, + executor, ])) as Timelock; ({ timestamp } = await ethers.provider.getBlock("latest")); now = BigNumber.from(timestamp); diff --git a/test/006_emergency_brake.ts b/test/006_emergency_brake.ts index d7cb33a..ebe3d2e 100644 --- a/test/006_emergency_brake.ts +++ b/test/006_emergency_brake.ts @@ -86,12 +86,12 @@ describe("EmergencyBrake", async function () { await expect(brake.connect(plannerAcc).cancel(txHash)).to.be.revertedWith( "Emergency not planned for." ); - await expect( - brake.connect(executorAcc).execute(txHash) - ).to.be.revertedWith("Emergency not planned for."); - await expect( - brake.connect(plannerAcc).restore(txHash) - ).to.be.revertedWith("Emergency plan not executed."); + await expect(brake.connect(executorAcc).execute(txHash)).to.be.revertedWith( + "Emergency not planned for." + ); + await expect(brake.connect(plannerAcc).restore(txHash)).to.be.revertedWith( + "Emergency plan not executed." + ); await expect( brake.connect(plannerAcc).terminate(txHash) ).to.be.revertedWith("Emergency plan not executed."); @@ -178,7 +178,9 @@ describe("EmergencyBrake", async function () { { contact: contact1.address, signatures: [MINT, BURN] }, { contact: contact2.address, signatures: [MINT, BURN] }, ]; - const txHash = await brake.connect(plannerAcc).callStatic.plan(target, permissions); // GEt the txHash + const txHash = await brake + .connect(plannerAcc) + .callStatic.plan(target, permissions); // GEt the txHash await brake.connect(plannerAcc).plan(target, permissions); // It can be planned, because permissions could be different at execution time await expect( brake.connect(executorAcc).execute(txHash) @@ -186,9 +188,10 @@ describe("EmergencyBrake", async function () { }); it("plans can be executed", async () => { - expect( - await brake.connect(executorAcc).execute(txHash) - ).to.emit(brake, "Executed"); + expect(await brake.connect(executorAcc).execute(txHash)).to.emit( + brake, + "Executed" + ); expect(await contact1.hasRole(MINT, target)).to.be.false; expect(await contact1.hasRole(BURN, target)).to.be.false; @@ -219,9 +222,10 @@ describe("EmergencyBrake", async function () { }); it("state can be restored", async () => { - expect( - await brake.connect(plannerAcc).restore(txHash) - ).to.emit(brake, "Restored"); + expect(await brake.connect(plannerAcc).restore(txHash)).to.emit( + brake, + "Restored" + ); expect(await contact1.hasRole(MINT, target)).to.be.true; expect(await contact1.hasRole(BURN, target)).to.be.true; diff --git a/test/007_auth_ether_faucet.ts b/test/007_auth_ether_faucet.ts new file mode 100644 index 0000000..16bebb0 --- /dev/null +++ b/test/007_auth_ether_faucet.ts @@ -0,0 +1,52 @@ +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address"; + +import { id } from "../src/index"; + +import AuthEtherFaucetArtifact from "../artifacts/contracts/utils/AuthEtherFaucet.sol/AuthEtherFaucet.json"; +import { AuthEtherFaucet } from "../typechain/AuthEtherFaucet"; + +import { ethers, waffle } from "hardhat"; +import { expect } from "chai"; +const { deployContract } = waffle; + +describe("AuthEtherFaucet", async function () { + let ownerAcc: SignerWithAddress; + let owner: string; + let operatorAcc: SignerWithAddress; + let operator: string; + let userAcc: SignerWithAddress; + let user: string; + + let faucet: AuthEtherFaucet; + + beforeEach(async () => { + const signers = await ethers.getSigners(); + ownerAcc = signers[0]; + owner = ownerAcc.address; + operatorAcc = signers[1]; + operator = operatorAcc.address; + userAcc = signers[2]; + user = userAcc.address; + + faucet = (await deployContract(ownerAcc, AuthEtherFaucetArtifact, [ + [operator], + ])) as AuthEtherFaucet; + + await ownerAcc.sendTransaction({ + to: faucet.address, + value: "0x1000000000000000000", + }); + }); + + it("allows to drip", async () => { + const userBalance = await ethers.provider.getBalance(user); + await faucet.connect(operatorAcc).drip(user, 1); + expect(await ethers.provider.getBalance(user)).to.equal(userBalance.add(1)); + }); + + it("allows to drip only to operators", async () => { + await expect(faucet.connect(userAcc).drip(user, 1)).to.be.revertedWith( + "Access denied" + ); + }); +});