From 2ccc8c473a43e8dd6485c5516ce017125670a015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Mon, 11 Oct 2021 14:42:01 +0100 Subject: [PATCH 1/5] feat: Auth Ether Faucet to manage refunds --- contracts/utils/AuthEtherFaucet.sol | 26 +++++++++++++++ test/007_auth_ether_faucet.ts | 50 +++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 contracts/utils/AuthEtherFaucet.sol create mode 100644 test/007_auth_ether_faucet.ts diff --git a/contracts/utils/AuthEtherFaucet.sol b/contracts/utils/AuthEtherFaucet.sol new file mode 100644 index 0000000..cbf6c73 --- /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]); + } + + 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/test/007_auth_ether_faucet.ts b/test/007_auth_ether_faucet.ts new file mode 100644 index 0000000..81bb62a --- /dev/null +++ b/test/007_auth_ether_faucet.ts @@ -0,0 +1,50 @@ +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"); + }); +}); From 962b77a437be37ab68608a700aac012b5e7d6fa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Mon, 11 Oct 2021 16:03:16 +0100 Subject: [PATCH 2/5] feat: deployed AuthEtherFaucet to kovan --- .../utils => deploy}/AuthEtherFaucet.sol | 2 +- deploy/auth_ether_faucet.ts | 17 +++++ deploy/faucet-args-42.js | 5 ++ hardhat.config.ts | 62 ++++++++++++++++--- test/005_timelock.ts | 3 +- test/006_emergency_brake.ts | 30 +++++---- test/007_auth_ether_faucet.ts | 20 +++--- 7 files changed, 108 insertions(+), 31 deletions(-) rename {contracts/utils => deploy}/AuthEtherFaucet.sol (97%) create mode 100644 deploy/auth_ether_faucet.ts create mode 100644 deploy/faucet-args-42.js diff --git a/contracts/utils/AuthEtherFaucet.sol b/deploy/AuthEtherFaucet.sol similarity index 97% rename from contracts/utils/AuthEtherFaucet.sol rename to deploy/AuthEtherFaucet.sol index cbf6c73..a3c099a 100644 --- a/contracts/utils/AuthEtherFaucet.sol +++ b/deploy/AuthEtherFaucet.sol @@ -10,7 +10,7 @@ contract AuthEtherFaucet is AccessControl { constructor(address[] memory operators) AccessControl() { for (uint256 i = 0; i < operators.length; i++) - _grantRole(AuthEtherFaucet.drip.selector, operators[i]); + _grantRole(AuthEtherFaucet.drip.selector, operators[i]); // 0x9e353a1e } receive() external payable {} 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/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 index 81bb62a..16bebb0 100644 --- a/test/007_auth_ether_faucet.ts +++ b/test/007_auth_ether_faucet.ts @@ -19,7 +19,6 @@ describe("AuthEtherFaucet", async function () { let faucet: AuthEtherFaucet; - beforeEach(async () => { const signers = await ethers.getSigners(); ownerAcc = signers[0]; @@ -30,21 +29,24 @@ describe("AuthEtherFaucet", async function () { user = userAcc.address; faucet = (await deployContract(ownerAcc, AuthEtherFaucetArtifact, [ - [operator] + [operator], ])) as AuthEtherFaucet; - await ownerAcc.sendTransaction({ to: faucet.address, value: '0x1000000000000000000' }); + 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)) + 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"); + await expect(faucet.connect(userAcc).drip(user, 1)).to.be.revertedWith( + "Access denied" + ); }); }); From 0493904e72d5eaa447ef19ca5aa5bd4a5e397f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Tue, 12 Oct 2021 06:47:38 +0100 Subject: [PATCH 3/5] npm: 2.4.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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": [ From c7dcb96d1dc2a8ed1b539bdae9b8930d1c770921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Tue, 12 Oct 2021 07:01:28 +0100 Subject: [PATCH 4/5] fix: move contract to right folder --- {deploy => contracts/utils}/AuthEtherFaucet.sol | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {deploy => contracts/utils}/AuthEtherFaucet.sol (100%) diff --git a/deploy/AuthEtherFaucet.sol b/contracts/utils/AuthEtherFaucet.sol similarity index 100% rename from deploy/AuthEtherFaucet.sol rename to contracts/utils/AuthEtherFaucet.sol From e73961eba449dfd8d2654e44ae63fa8daa7adfc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alberto=20Cuesta=20Ca=C3=B1ada?= Date: Tue, 12 Oct 2021 07:25:51 +0100 Subject: [PATCH 5/5] fix: removed migrations CI --- .github/workflows/ci.yml | 24 ------------------------ 1 file changed, 24 deletions(-) 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