From 1b904932b63edc3118a931704619f2d00600747c Mon Sep 17 00:00:00 2001 From: Nicola Miotto Date: Mon, 19 Feb 2024 11:56:10 +0100 Subject: [PATCH 1/2] feat: support multiple pools --- contracts/Faucet.sol | 74 ++++----- deploy/00_deploy_faucet.ts | 2 +- package.json | 4 + test/{index.ts => Integration.ts} | 7 +- test/unit/Faucet.ts | 249 ++++++++++++++++++++++++++++++ yarn.lock | 231 +++++++++++++++++++++++++++ 6 files changed, 522 insertions(+), 45 deletions(-) rename test/{index.ts => Integration.ts} (97%) create mode 100644 test/unit/Faucet.ts diff --git a/contracts/Faucet.sol b/contracts/Faucet.sol index 5903f10..f783986 100644 --- a/contracts/Faucet.sol +++ b/contracts/Faucet.sol @@ -9,21 +9,21 @@ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; contract Faucet is Ownable { using SafeERC20 for IERC20; + uint256 public constant TIMEOUT_LIMIT = 30; + uint256 public constant MAX_WITHDRAWAL_AMOUNT = 5 ether; address public contractRegistry; - address public bctAddress; - address public nctAddress; + mapping(address => bool) public isPoolEligible; mapping(address => uint256) private lastWithdrawalTimes; event Deposited(address erc20Addr, uint256 amount); event Withdrawn(address account, address erc20Addr, uint256 amount); - constructor( - address _contractRegistry, - address _bctAddress, - address _nctAddress - ) { + constructor(address _contractRegistry, address[] memory poolAddresses) { contractRegistry = _contractRegistry; - bctAddress = _bctAddress; - nctAddress = _nctAddress; + + uint256 poolAddressesCount = poolAddresses.length; + for (uint256 i = 0; i < poolAddressesCount; i++) { + setPoolEligible(poolAddresses[i], true); + } } /// @notice change the TCO2 contracts registry @@ -34,6 +34,16 @@ contract Faucet is Ownable { contractRegistry = _address; } + /// @notice allows the owner to set the eligibility of a pool + /// @param _poolAddress the address of the pool + /// @param _isPoolEligible eligibility flag + function setPoolEligible( + address _poolAddress, + bool _isPoolEligible + ) public virtual onlyOwner { + isPoolEligible[_poolAddress] = _isPoolEligible; + } + /// @notice A function to get the Faucet's balances of multiple tokens at once /// @param _erc20Addresses An array of ERC20 contract addresses /// @return An array of balances @@ -47,28 +57,11 @@ contract Faucet is Ownable { return balances; } - /// @notice checks if token to be deposited is eligible for the Faucet - /// @param _erc20Address address to be checked - function checkTokenEligibility( - address _erc20Address - ) private view returns (bool) { - bool isToucanContract = IToucanContractRegistry(contractRegistry) - .checkERC20(_erc20Address); - if (isToucanContract) return true; - - if (_erc20Address == bctAddress) return true; - - if (_erc20Address == nctAddress) return true; - - return false; - } - /// @notice deposit tokens from caller to Faucet /// @param _erc20Address ERC20 contract address to be deposited /// @param _amount amount to be deposited function deposit(address _erc20Address, uint256 _amount) public { - bool eligibility = checkTokenEligibility(_erc20Address); - require(eligibility, "Token rejected"); + require(_checkEligible(_erc20Address), "Token rejected"); IERC20(_erc20Address).safeTransferFrom( msg.sender, @@ -81,28 +74,20 @@ contract Faucet is Ownable { /// @notice checks if the Faucet is in a withdrawal timeout for the caller /// @return true if in timeout, false if not - function checkIfWithdrawalTimeout() public returns (bool) { - uint256 timeoutLimit = 30; // amount of seconds in between withdrawals - if (lastWithdrawalTimes[msg.sender] == 0) { - lastWithdrawalTimes[msg.sender] = block.timestamp - timeoutLimit; - } - if (lastWithdrawalTimes[msg.sender] <= block.timestamp - timeoutLimit) { - return false; - } - return true; + function checkIfWithdrawalTimeout() public view returns (bool) { + return + lastWithdrawalTimes[msg.sender] > block.timestamp - TIMEOUT_LIMIT; } /// @notice withdraw tokens from Faucet to caller /// @param _erc20Address ERC20 contract address to be withdrawn /// @param _amount amount to be withdrawn function withdraw(address _erc20Address, uint256 _amount) public { - bool eligibility = checkTokenEligibility(_erc20Address); - require(eligibility, "Token rejected"); - + require(_checkEligible(_erc20Address), "Token rejected"); require(!checkIfWithdrawalTimeout(), "Cannot withdraw that often"); lastWithdrawalTimes[msg.sender] = block.timestamp; - require(_amount <= 5 ether, "Cannot withdraw more than 5 tokens"); + require(_amount <= MAX_WITHDRAWAL_AMOUNT, "Amount too high"); IERC20(_erc20Address).safeTransfer(msg.sender, _amount); @@ -118,4 +103,13 @@ contract Faucet is Ownable { ) public onlyOwner { IERC20(_erc20Address).safeTransfer(msg.sender, _amount); } + + function _checkEligible( + address _erc20Address + ) internal view returns (bool) { + return + IToucanContractRegistry(contractRegistry).checkERC20( + _erc20Address + ) || isPoolEligible[_erc20Address]; + } } diff --git a/deploy/00_deploy_faucet.ts b/deploy/00_deploy_faucet.ts index 7a2a1a0..704b5f4 100644 --- a/deploy/00_deploy_faucet.ts +++ b/deploy/00_deploy_faucet.ts @@ -20,7 +20,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { await deploy("Faucet", { from: deployer, - args: [CONTRACT_REGISTRY_ADDRESS, BCT_ADDRESS, NCT_ADDRESS], + args: [CONTRACT_REGISTRY_ADDRESS, [BCT_ADDRESS, NCT_ADDRESS]], log: true, autoMine: true, // speed up deployment on local network (ganache, hardhat), no effect on live networks }); diff --git a/package.json b/package.json index 5c2fee3..deca7e2 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,13 @@ "typescript": "^4.5.5" }, "dependencies": { + "@defi-wonderland/smock": "^2.3.5", "@nomicfoundation/hardhat-network-helpers": "^1.0.7", "@openzeppelin/contracts": "^4.8.0", "@openzeppelin/contracts-upgradeable": "^4.4.2", + "@types/chai-as-promised": "^7.1.8", + "chai-as-promised": "^7.1.1", + "mocha": "^10.3.0", "solc": "^0.8.11" } } diff --git a/test/index.ts b/test/Integration.ts similarity index 97% rename from test/index.ts rename to test/Integration.ts index 3725097..8246948 100644 --- a/test/index.ts +++ b/test/Integration.ts @@ -33,11 +33,10 @@ describe("TCO2Faucet", function () { )) as Faucet__factory; const { CONTRACT_REGISTRY_ADDRESS, BCT_ADDRESS, NCT_ADDRESS } = deploymentAddresses.mumbai; - const faucet = await FaucetFactory.deploy( - CONTRACT_REGISTRY_ADDRESS, + const faucet = await FaucetFactory.deploy(CONTRACT_REGISTRY_ADDRESS, [ BCT_ADDRESS, - NCT_ADDRESS - ); + NCT_ADDRESS, + ]); let TCO2Contracts: Record = {}; diff --git a/test/unit/Faucet.ts b/test/unit/Faucet.ts new file mode 100644 index 0000000..0fc4347 --- /dev/null +++ b/test/unit/Faucet.ts @@ -0,0 +1,249 @@ +// SPDX-FileCopyrightText: 2023 Toucan Labs +// +// SPDX-License-Identifier: LicenseRef-Proprietary + +// Explicit import of hardhat plugins are required to obtain type extensions +// when compiling without hardhat.config.ts (e.g. via lint-staged). Extensions +// are things like 'getNamedAccounts' and 'upgrades' on HardhatRuntimeEnvironment. +import { FakeContract, smock } from "@defi-wonderland/smock"; +import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; +import { use, expect } from "chai"; +import chaiAsPromised from "chai-as-promised"; + +import { solidity } from "ethereum-waffle"; +import { Faucet, IERC20, IToucanContractRegistry } from "../../typechain"; +import { ethers, network } from "hardhat"; +import { BigNumber } from "ethers"; + +use(solidity); +use(chaiAsPromised); +use(smock.matchers); + +describe("Faucet", async () => { + let owner: SignerWithAddress; + let alice: SignerWithAddress; + let fakePool1: FakeContract; + let fakePool2: FakeContract; + let fakePool3: FakeContract; + let fakeToucanContractRegistry: FakeContract; + let faucet: Faucet; + let maxWithdrawalAmount: BigNumber; + let timeoutLimit: BigNumber; + + beforeEach(async () => { + [owner, alice] = await ethers.getSigners(); + + fakeToucanContractRegistry = await smock.fake( + "IToucanContractRegistry" + ); + fakeToucanContractRegistry.checkERC20.returns(true); + + fakePool1 = await smock.fake("IERC20"); + fakePool1.transfer.returns(true); + fakePool1.transferFrom.returns(true); + + fakePool2 = await smock.fake("IERC20"); + fakePool3 = await smock.fake("IERC20"); + + const faucetFactory = await ethers.getContractFactory("Faucet"); + faucet = (await faucetFactory.deploy(fakeToucanContractRegistry.address, [ + fakePool1.address, + fakePool2.address, + ])) as Faucet; + + timeoutLimit = await faucet.TIMEOUT_LIMIT(); + maxWithdrawalAmount = await faucet.MAX_WITHDRAWAL_AMOUNT(); + }); + + describe("setPoolEligible", async () => { + it("reverts when called by a non-owner", async () => { + await expect( + faucet.connect(alice).setPoolEligible(fakePool1.address, false) + ).revertedWith("Ownable: caller is not the owner"); + }); + + it("sets the pool as eligible", async () => { + await faucet.setPoolEligible(fakePool3.address, true); + + const result = await faucet.isPoolEligible(fakePool3.address); + + expect(result).equal(true); + }); + + it("sets the pool as uneligible", async () => { + await faucet.setPoolEligible(fakePool2.address, false); + + const result = await faucet.isPoolEligible(fakePool2.address); + + expect(result).equal(false); + }); + }); + + describe("getTokenBalance", async () => { + const balancePool1 = BigNumber.from(42); + const balancePool2 = BigNumber.from(3); + + beforeEach(async () => { + fakePool1.balanceOf.returns(balancePool1); + fakePool2.balanceOf.returns(balancePool2); + }); + + it("returns the balance of a single token", async () => { + const result = await faucet.getTokenBalances([fakePool1.address]); + + expect(result).eql([balancePool1]); + }); + + it("returns the aggregated balance of a multiple tokens", async () => { + const result = await faucet.getTokenBalances([ + fakePool1.address, + fakePool2.address, + ]); + + expect(result).eql([balancePool1, balancePool2]); + }); + }); + + describe("deposit", async () => { + it("reverts when the address is neither an eligible pool nor a valid toucan contract", async () => { + fakeToucanContractRegistry.checkERC20.reset(); + fakeToucanContractRegistry.checkERC20.returns(false); + await faucet.setPoolEligible(fakePool1.address, false); + + await expect(faucet.deposit(fakePool3.address, 42)).revertedWith( + "Token rejected" + ); + }); + + it("transfers the given amount of tokens from the sender to the faucet", async () => { + const transferredAmount = BigNumber.from(42); + await faucet.connect(alice).deposit(fakePool1.address, transferredAmount); + + expect(fakePool1.transferFrom).calledOnceWith( + alice.address, + faucet.address, + transferredAmount + ); + }); + + it("emits a Deposited event", async () => { + const transferredAmount = BigNumber.from(42); + await expect( + faucet.connect(alice).deposit(fakePool1.address, transferredAmount) + ) + .to.emit(faucet, "Deposited") + .withArgs(fakePool1.address, transferredAmount); + }); + }); + + describe("checkIfWithdrawalTimeout", async () => { + it("returns false if the caller hasn't withdrawn", async () => { + const result = await faucet.checkIfWithdrawalTimeout(); + + expect(result).equal(false); + }); + + it("returns false if the caller withrew more than 30 seconds ago", async () => { + await faucet.withdraw(fakePool1.address, 42); + const currentTimestamp = (await ethers.provider.getBlock("latest")) + .timestamp; + network.provider.send("evm_setNextBlockTimestamp", [ + timeoutLimit.toNumber() + currentTimestamp + 1, + ]); + await network.provider.send("evm_mine"); + + const result = await faucet.checkIfWithdrawalTimeout(); + + expect(result).equal(false); + }); + + it("returns true if caller withrew less than 30 seconds ago", async () => { + await faucet.withdraw(fakePool1.address, 42); + + const result = await faucet.checkIfWithdrawalTimeout(); + + expect(result).equal(true); + }); + }); + + describe("withdraw", async () => { + it("reverts when the address is neither an eligible pool nor a valid toucan contract", async () => { + fakeToucanContractRegistry.checkERC20.reset(); + fakeToucanContractRegistry.checkERC20.returns(false); + await faucet.setPoolEligible(fakePool1.address, false); + + await expect(faucet.deposit(fakePool3.address, 42)).revertedWith( + "Token rejected" + ); + }); + + it("reverts when on withdrawal timeout", async () => { + await faucet.withdraw(fakePool1.address, 42); + + await expect(faucet.withdraw(fakePool1.address, 42)).revertedWith( + "Cannot withdraw that often" + ); + }); + + it("reverts when withdrawing more than the maximum allowed amount", async () => { + await expect( + faucet.withdraw(fakePool1.address, maxWithdrawalAmount.add(1)) + ).revertedWith("Amount too high"); + }); + + it("transfers the given amount of tokens from the faucet to the sender", async () => { + const transferredAmount = BigNumber.from(42); + await faucet + .connect(alice) + .withdraw(fakePool1.address, transferredAmount); + + expect(fakePool1.transfer).calledOnceWith( + alice.address, + transferredAmount + ); + }); + + it("emits a Withdrawn event", async () => { + const transferredAmount = BigNumber.from(42); + + await expect( + faucet.connect(alice).withdraw(fakePool1.address, transferredAmount) + ) + .to.emit(faucet, "Withdrawn") + .withArgs(alice.address, fakePool1.address, transferredAmount); + }); + + it("succeeds when called a second time after the timeout", async () => { + const transferredAmount = BigNumber.from(42); + await faucet + .connect(alice) + .withdraw(fakePool1.address, transferredAmount); + const currentTimestamp = (await ethers.provider.getBlock("latest")) + .timestamp; + network.provider.send("evm_setNextBlockTimestamp", [ + timeoutLimit.toNumber() + currentTimestamp + 1, + ]); + await network.provider.send("evm_mine"); + + await expect( + faucet.connect(alice).withdraw(fakePool1.address, transferredAmount) + ).to.emit(faucet, "Withdrawn"); + }); + }); + + describe("ownerWithdraw", async () => { + it("reverts when called by a non owner", async () => { + await expect( + faucet.connect(alice).ownerWithdraw(fakePool1.address, 42) + ).revertedWith("not the owner"); + }); + + it("transfers from given address to sender the given amount", async () => { + const transferredAmount = BigNumber.from(42); + + await faucet.ownerWithdraw(fakePool1.address, transferredAmount); + + expect(fakePool1.transfer).calledWith(owner.address, transferredAmount); + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 1bd270f..cfffe62 100644 --- a/yarn.lock +++ b/yarn.lock @@ -37,6 +37,20 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@defi-wonderland/smock@^2.3.5": + version "2.3.5" + resolved "https://registry.yarnpkg.com/@defi-wonderland/smock/-/smock-2.3.5.tgz#2f4daa10b4f4c69627200128778f61c51f4b32cc" + integrity sha512-klANj1hUpc3cd2ShXdVH/bEGwxJd+LxOngkF5gLcIbg6b37RCgMPMmR/94/hgL62F8bfWtuNKsQD7K+c6M5fWQ== + dependencies: + "@nomicfoundation/ethereumjs-evm" "^1.0.0-rc.3" + "@nomicfoundation/ethereumjs-util" "^8.0.0-rc.3" + "@nomicfoundation/ethereumjs-vm" "^6.0.0-rc.3" + 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" @@ -573,6 +587,18 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@nomicfoundation/ethereumjs-block@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.2.2.tgz#f317078c810a54381c682d0c12e1e81acfc11599" + integrity sha512-atjpt4gc6ZGZUPHBAQaUJsm1l/VCo7FmyQ780tMGO8QStjLdhz09dXynmhwVTy5YbRr0FOh/uX3QaEM0yIB2Zg== + dependencies: + "@nomicfoundation/ethereumjs-common" "3.1.2" + "@nomicfoundation/ethereumjs-rlp" "4.0.3" + "@nomicfoundation/ethereumjs-trie" "5.0.5" + "@nomicfoundation/ethereumjs-tx" "4.1.2" + "@nomicfoundation/ethereumjs-util" "8.0.6" + ethereum-cryptography "0.1.3" + "@nomicfoundation/ethereumjs-block@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-block/-/ethereumjs-block-4.0.0.tgz#fdd5c045e7baa5169abeed0e1202bf94e4481c49" @@ -585,6 +611,24 @@ "@nomicfoundation/ethereumjs-util" "^8.0.0" ethereum-cryptography "0.1.3" +"@nomicfoundation/ethereumjs-blockchain@6.2.2": + version "6.2.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.2.2.tgz#9f79dd2b3dc73f5d5a220f7d8a734330c4c26320" + integrity sha512-6AIB2MoTEPZJLl6IRKcbd8mUmaBAQ/NMe3O7OsAOIiDjMNPPH5KaUQiLfbVlegT4wKIg/GOsFH7XlH2KDVoJNg== + dependencies: + "@nomicfoundation/ethereumjs-block" "4.2.2" + "@nomicfoundation/ethereumjs-common" "3.1.2" + "@nomicfoundation/ethereumjs-ethash" "2.0.5" + "@nomicfoundation/ethereumjs-rlp" "4.0.3" + "@nomicfoundation/ethereumjs-trie" "5.0.5" + "@nomicfoundation/ethereumjs-util" "8.0.6" + abstract-level "^1.0.3" + debug "^4.3.3" + ethereum-cryptography "0.1.3" + level "^8.0.0" + lru-cache "^5.1.1" + memory-level "^1.0.0" + "@nomicfoundation/ethereumjs-blockchain@^6.0.0": version "6.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-blockchain/-/ethereumjs-blockchain-6.0.0.tgz#1a8c243a46d4d3691631f139bfb3a4a157187b0c" @@ -603,6 +647,14 @@ lru-cache "^5.1.1" memory-level "^1.0.0" +"@nomicfoundation/ethereumjs-common@3.1.2": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.1.2.tgz#041086da66ed40f2bf2a2116a1f2f0fcf33fb80d" + integrity sha512-JAEBpIua62dyObHM9KI2b4wHZcRQYYge9gxiygTWa3lNCr2zo+K0TbypDpgiNij5MCGNWP1eboNfNfx1a3vkvA== + dependencies: + "@nomicfoundation/ethereumjs-util" "8.0.6" + crc-32 "^1.2.0" + "@nomicfoundation/ethereumjs-common@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-common/-/ethereumjs-common-3.0.0.tgz#f6bcc7753994555e49ab3aa517fc8bcf89c280b9" @@ -611,6 +663,18 @@ "@nomicfoundation/ethereumjs-util" "^8.0.0" crc-32 "^1.2.0" +"@nomicfoundation/ethereumjs-ethash@2.0.5": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.5.tgz#0c605812f6f4589a9f6d597db537bbf3b86469db" + integrity sha512-xlLdcICGgAYyYmnI3r1t0R5fKGBJNDQSOQxXNjVO99JmxJIdXR5MgPo5CSJO1RpyzKOgzi3uIFn8agv564dZEQ== + dependencies: + "@nomicfoundation/ethereumjs-block" "4.2.2" + "@nomicfoundation/ethereumjs-rlp" "4.0.3" + "@nomicfoundation/ethereumjs-util" "8.0.6" + abstract-level "^1.0.3" + bigint-crypto-utils "^3.0.23" + ethereum-cryptography "0.1.3" + "@nomicfoundation/ethereumjs-ethash@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-ethash/-/ethereumjs-ethash-2.0.0.tgz#11539c32fe0990e1122ff987d1b84cfa34774e81" @@ -623,6 +687,20 @@ bigint-crypto-utils "^3.0.23" ethereum-cryptography "0.1.3" +"@nomicfoundation/ethereumjs-evm@1.3.2", "@nomicfoundation/ethereumjs-evm@^1.0.0-rc.3": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.3.2.tgz#f9d6bafd5c23d07ab75b8649d589af1a43b60bfc" + integrity sha512-I00d4MwXuobyoqdPe/12dxUQxTYzX8OckSaWsMcWAfQhgVDvBx6ffPyP/w1aL0NW7MjyerySPcSVfDJAMHjilw== + dependencies: + "@nomicfoundation/ethereumjs-common" "3.1.2" + "@nomicfoundation/ethereumjs-util" "8.0.6" + "@types/async-eventemitter" "^0.2.1" + async-eventemitter "^0.2.4" + debug "^4.3.3" + ethereum-cryptography "0.1.3" + mcl-wasm "^0.7.1" + rustbn.js "~0.2.0" + "@nomicfoundation/ethereumjs-evm@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-evm/-/ethereumjs-evm-1.0.0.tgz#99cd173c03b59107c156a69c5e215409098a370b" @@ -637,11 +715,29 @@ mcl-wasm "^0.7.1" rustbn.js "~0.2.0" +"@nomicfoundation/ethereumjs-rlp@4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.3.tgz#8d9147fbd0d49e8f4c5ce729d226694a8fe03eb8" + integrity sha512-DZMzB/lqPK78T6MluyXqtlRmOMcsZbTTbbEyAjo0ncaff2mqu/k8a79PBcyvpgAhWD/R59Fjq/x3ro5Lof0AtA== + "@nomicfoundation/ethereumjs-rlp@^4.0.0", "@nomicfoundation/ethereumjs-rlp@^4.0.0-beta.2": version "4.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-rlp/-/ethereumjs-rlp-4.0.0.tgz#d9a9c5f0f10310c8849b6525101de455a53e771d" integrity sha512-GaSOGk5QbUk4eBP5qFbpXoZoZUj/NrW7MRa0tKY4Ew4c2HAS0GXArEMAamtFrkazp0BO4K5p2ZCG3b2FmbShmw== +"@nomicfoundation/ethereumjs-statemanager@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.5.tgz#951cc9ff2c421d40233d2e9d0fe033db2391ee44" + integrity sha512-CAhzpzTR5toh/qOJIZUUOnWekUXuRqkkzaGAQrVcF457VhtCmr+ddZjjK50KNZ524c1XP8cISguEVNqJ6ij1sA== + dependencies: + "@nomicfoundation/ethereumjs-common" "3.1.2" + "@nomicfoundation/ethereumjs-rlp" "4.0.3" + "@nomicfoundation/ethereumjs-trie" "5.0.5" + "@nomicfoundation/ethereumjs-util" "8.0.6" + debug "^4.3.3" + ethereum-cryptography "0.1.3" + functional-red-black-tree "^1.0.1" + "@nomicfoundation/ethereumjs-statemanager@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-statemanager/-/ethereumjs-statemanager-1.0.0.tgz#14a9d4e1c828230368f7ab520c144c34d8721e4b" @@ -655,6 +751,16 @@ ethereum-cryptography "0.1.3" functional-red-black-tree "^1.0.1" +"@nomicfoundation/ethereumjs-trie@5.0.5": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.5.tgz#bf31c9306dcbba2007fad668e96109ddb147040c" + integrity sha512-+8sNZrXkzvA1NH5F4kz5RSYl1I6iaRz7mAZRsyxOm0IVY4UaP43Ofvfp/TwOalFunurQrYB5pRO40+8FBcxFMA== + dependencies: + "@nomicfoundation/ethereumjs-rlp" "4.0.3" + "@nomicfoundation/ethereumjs-util" "8.0.6" + ethereum-cryptography "0.1.3" + readable-stream "^3.6.0" + "@nomicfoundation/ethereumjs-trie@^5.0.0": version "5.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-trie/-/ethereumjs-trie-5.0.0.tgz#dcfbe3be53a94bc061c9767a396c16702bc2f5b7" @@ -665,6 +771,16 @@ ethereum-cryptography "0.1.3" readable-stream "^3.6.0" +"@nomicfoundation/ethereumjs-tx@4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.1.2.tgz#8659fad7f9094b7eb82aa6cc3c8097cb1c42ff31" + integrity sha512-emJBJZpmTdUa09cqxQqHaysbBI9Od353ZazeH7WgPb35miMgNY6mb7/3vBA98N5lUW/rgkiItjX0KZfIzihSoQ== + dependencies: + "@nomicfoundation/ethereumjs-common" "3.1.2" + "@nomicfoundation/ethereumjs-rlp" "4.0.3" + "@nomicfoundation/ethereumjs-util" "8.0.6" + ethereum-cryptography "0.1.3" + "@nomicfoundation/ethereumjs-tx@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-tx/-/ethereumjs-tx-4.0.0.tgz#59dc7452b0862b30342966f7052ab9a1f7802f52" @@ -675,6 +791,14 @@ "@nomicfoundation/ethereumjs-util" "^8.0.0" ethereum-cryptography "0.1.3" +"@nomicfoundation/ethereumjs-util@8.0.6", "@nomicfoundation/ethereumjs-util@^8.0.0-rc.3": + version "8.0.6" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.6.tgz#dbce5d258b017b37aa58b3a7c330ad59d10ccf0b" + integrity sha512-jOQfF44laa7xRfbfLXojdlcpkvxeHrE2Xu7tSeITsWFgoII163MzjOwFEzSNozHYieFysyoEMhCdP+NY5ikstw== + dependencies: + "@nomicfoundation/ethereumjs-rlp" "4.0.3" + ethereum-cryptography "0.1.3" + "@nomicfoundation/ethereumjs-util@^8.0.0": version "8.0.0" resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-util/-/ethereumjs-util-8.0.0.tgz#deb2b15d2c308a731e82977aefc4e61ca0ece6c5" @@ -705,6 +829,28 @@ mcl-wasm "^0.7.1" rustbn.js "~0.2.0" +"@nomicfoundation/ethereumjs-vm@^6.0.0-rc.3": + version "6.4.2" + resolved "https://registry.yarnpkg.com/@nomicfoundation/ethereumjs-vm/-/ethereumjs-vm-6.4.2.tgz#af1cf62e6c0054bc2b7febc8556d032433d1b18c" + integrity sha512-PRTyxZMP6kx+OdAzBhuH1LD2Yw+hrSpaytftvaK//thDy2OI07S0nrTdbrdk7b8ZVPAc9H9oTwFBl3/wJ3w15g== + dependencies: + "@nomicfoundation/ethereumjs-block" "4.2.2" + "@nomicfoundation/ethereumjs-blockchain" "6.2.2" + "@nomicfoundation/ethereumjs-common" "3.1.2" + "@nomicfoundation/ethereumjs-evm" "1.3.2" + "@nomicfoundation/ethereumjs-rlp" "4.0.3" + "@nomicfoundation/ethereumjs-statemanager" "1.0.5" + "@nomicfoundation/ethereumjs-trie" "5.0.5" + "@nomicfoundation/ethereumjs-tx" "4.1.2" + "@nomicfoundation/ethereumjs-util" "8.0.6" + "@types/async-eventemitter" "^0.2.1" + async-eventemitter "^0.2.4" + debug "^4.3.3" + ethereum-cryptography "0.1.3" + functional-red-black-tree "^1.0.1" + mcl-wasm "^0.7.1" + rustbn.js "~0.2.0" + "@nomicfoundation/hardhat-network-helpers@^1.0.7": version "1.0.7" resolved "https://registry.yarnpkg.com/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.7.tgz#9103be2b359899a8b7996f54df12a1b7977367e3" @@ -1070,6 +1216,13 @@ "@types/node" "*" "@types/responselike" "^1.0.0" +"@types/chai-as-promised@^7.1.8": + version "7.1.8" + resolved "https://registry.yarnpkg.com/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz#f2b3d82d53c59626b5d6bbc087667ccb4b677fe9" + integrity sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw== + dependencies: + "@types/chai" "*" + "@types/chai@*", "@types/chai@^4.3.0": version "4.3.4" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.4.tgz#e913e8175db8307d78b4e8fa690408ba6b65dee4" @@ -2743,6 +2896,13 @@ cbor@^8.1.0: dependencies: nofilter "^3.1.0" +chai-as-promised@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.1.tgz#08645d825deb8696ee61725dbf590c012eb00ca0" + integrity sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA== + dependencies: + check-error "^1.0.2" + chai@^4.3.4: version "4.3.7" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.7.tgz#ec63f6df01829088e8bf55fca839bcd464a8ec51" @@ -3449,6 +3609,11 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +diff@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -5147,6 +5312,17 @@ glob@7.2.0: once "^1.3.0" path-is-absolute "^1.0.0" +glob@8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + glob@^5.0.15: version "5.0.15" resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" @@ -6581,6 +6757,16 @@ lodash.assign@^4.0.3, lodash.assign@^4.0.6: resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" integrity sha512-hFuH8TY+Yji7Eja3mGiuAxBqLagejScbG8GbG0j6o9vzn0YL14My+ktnqtZgFTosKymC9/44wP6s7xyuLfnClw== +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + +lodash.isequalwith@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.isequalwith/-/lodash.isequalwith-4.4.0.tgz#266726ddd528f854f21f4ea98a065606e0fbc6b0" + integrity sha512-dcZON0IalGBpRmJBmMkaoV7d3I80R2O+FrzsZyHdNSFrANq/cgDqKQNmAHE8UEj4+QYWwwhkQOVdLHiAopzlsQ== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -6917,6 +7103,13 @@ minimatch@5.0.1: dependencies: brace-expansion "^2.0.1" +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6, minimist@~1.2.6: version "1.2.7" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" @@ -7005,6 +7198,32 @@ mocha@^10.0.0: yargs-parser "20.2.4" yargs-unparser "2.0.0" +mocha@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.3.0.tgz#0e185c49e6dccf582035c05fa91084a4ff6e3fe9" + integrity sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg== + dependencies: + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.3" + debug "4.3.4" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "8.1.0" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "5.0.1" + ms "2.1.3" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + workerpool "6.2.1" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + mocha@^7.1.1: version "7.2.0" resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.2.0.tgz#01cc227b00d875ab1eed03a75106689cfed5a604" @@ -8389,6 +8608,13 @@ rxjs@^6.4.0: dependencies: tslib "^1.9.0" +rxjs@^7.2.0: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -9450,6 +9676,11 @@ tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.1.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" + integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== + tsort@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/tsort/-/tsort-0.0.1.tgz#e2280f5e817f8bf4275657fd0f9aebd44f5a2786" From 046d1f0e487a33ba25a9c3751fa6b30dc56582b4 Mon Sep 17 00:00:00 2001 From: Nicola Miotto Date: Mon, 19 Feb 2024 16:28:02 +0100 Subject: [PATCH 2/2] refactor: stricter naming convetions prefix _ only for internal/private variables use suffix _ for param names in conflict with other variables --- contracts/Faucet.sol | 82 +++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/contracts/Faucet.sol b/contracts/Faucet.sol index f783986..79c59de 100644 --- a/contracts/Faucet.sol +++ b/contracts/Faucet.sol @@ -13,12 +13,12 @@ contract Faucet is Ownable { uint256 public constant MAX_WITHDRAWAL_AMOUNT = 5 ether; address public contractRegistry; mapping(address => bool) public isPoolEligible; - mapping(address => uint256) private lastWithdrawalTimes; + mapping(address => uint256) private _lastWithdrawalTimes; event Deposited(address erc20Addr, uint256 amount); event Withdrawn(address account, address erc20Addr, uint256 amount); - constructor(address _contractRegistry, address[] memory poolAddresses) { - contractRegistry = _contractRegistry; + constructor(address contractRegistry_, address[] memory poolAddresses) { + contractRegistry = contractRegistry_; uint256 poolAddressesCount = poolAddresses.length; for (uint256 i = 0; i < poolAddressesCount; i++) { @@ -27,89 +27,87 @@ contract Faucet is Ownable { } /// @notice change the TCO2 contracts registry - /// @param _address the new contract registry address + /// @param contractRegistry_ the new contract registry address function setToucanContractRegistry( - address _address + address contractRegistry_ ) public virtual onlyOwner { - contractRegistry = _address; + contractRegistry = contractRegistry_; } /// @notice allows the owner to set the eligibility of a pool - /// @param _poolAddress the address of the pool - /// @param _isPoolEligible eligibility flag + /// @param poolAddress the address of the pool + /// @param isPoolEligible_ eligibility flag function setPoolEligible( - address _poolAddress, - bool _isPoolEligible + address poolAddress, + bool isPoolEligible_ ) public virtual onlyOwner { - isPoolEligible[_poolAddress] = _isPoolEligible; + isPoolEligible[poolAddress] = isPoolEligible_; } /// @notice A function to get the Faucet's balances of multiple tokens at once - /// @param _erc20Addresses An array of ERC20 contract addresses + /// @param erc20Addresses An array of ERC20 contract addresses /// @return An array of balances function getTokenBalances( - address[] memory _erc20Addresses + address[] memory erc20Addresses ) public view returns (uint256[] memory) { - uint256[] memory balances = new uint256[](_erc20Addresses.length); - for (uint256 i = 0; i < _erc20Addresses.length; i++) { - balances[i] = IERC20(_erc20Addresses[i]).balanceOf(address(this)); + uint256[] memory balances = new uint256[](erc20Addresses.length); + for (uint256 i = 0; i < erc20Addresses.length; i++) { + balances[i] = IERC20(erc20Addresses[i]).balanceOf(address(this)); } return balances; } /// @notice deposit tokens from caller to Faucet - /// @param _erc20Address ERC20 contract address to be deposited - /// @param _amount amount to be deposited - function deposit(address _erc20Address, uint256 _amount) public { - require(_checkEligible(_erc20Address), "Token rejected"); + /// @param erc20Address ERC20 contract address to be deposited + /// @param amount amount to be deposited + function deposit(address erc20Address, uint256 amount) public { + require(_checkEligible(erc20Address), "Token rejected"); - IERC20(_erc20Address).safeTransferFrom( + IERC20(erc20Address).safeTransferFrom( msg.sender, address(this), - _amount + amount ); - emit Deposited(_erc20Address, _amount); + emit Deposited(erc20Address, amount); } /// @notice checks if the Faucet is in a withdrawal timeout for the caller /// @return true if in timeout, false if not function checkIfWithdrawalTimeout() public view returns (bool) { return - lastWithdrawalTimes[msg.sender] > block.timestamp - TIMEOUT_LIMIT; + _lastWithdrawalTimes[msg.sender] > block.timestamp - TIMEOUT_LIMIT; } /// @notice withdraw tokens from Faucet to caller - /// @param _erc20Address ERC20 contract address to be withdrawn - /// @param _amount amount to be withdrawn - function withdraw(address _erc20Address, uint256 _amount) public { - require(_checkEligible(_erc20Address), "Token rejected"); + /// @param erc20Address ERC20 contract address to be withdrawn + /// @param amount amount to be withdrawn + function withdraw(address erc20Address, uint256 amount) public { + require(_checkEligible(erc20Address), "Token rejected"); require(!checkIfWithdrawalTimeout(), "Cannot withdraw that often"); - lastWithdrawalTimes[msg.sender] = block.timestamp; + _lastWithdrawalTimes[msg.sender] = block.timestamp; - require(_amount <= MAX_WITHDRAWAL_AMOUNT, "Amount too high"); + require(amount <= MAX_WITHDRAWAL_AMOUNT, "Amount too high"); - IERC20(_erc20Address).safeTransfer(msg.sender, _amount); + IERC20(erc20Address).safeTransfer(msg.sender, amount); - emit Withdrawn(msg.sender, _erc20Address, _amount); + emit Withdrawn(msg.sender, erc20Address, amount); } /// @notice function that is only callable by owner and can withdraw as many tokens from this contract as they want - /// @param _erc20Address address of the token to be withdrawn - /// @param _amount amount of tokens to be withdrawn + /// @param erc20Address address of the token to be withdrawn + /// @param amount amount of tokens to be withdrawn function ownerWithdraw( - address _erc20Address, - uint256 _amount + address erc20Address, + uint256 amount ) public onlyOwner { - IERC20(_erc20Address).safeTransfer(msg.sender, _amount); + IERC20(erc20Address).safeTransfer(msg.sender, amount); } - function _checkEligible( - address _erc20Address - ) internal view returns (bool) { + function _checkEligible(address erc20Address) internal view returns (bool) { return IToucanContractRegistry(contractRegistry).checkERC20( - _erc20Address - ) || isPoolEligible[_erc20Address]; + erc20Address + ) || isPoolEligible[erc20Address]; } }