Skip to content
This repository was archived by the owner on Oct 21, 2025. It is now read-only.

Commit b52f6c4

Browse files
feat: Add a mint amount limit and ownership transfer function to Faucet (#162)
* add packages * feat: add tx limit on mint * testing * chore: add to permissionless mode * feat: handle decimals * chore: update const to public and tests * fix: Simply max limit amount require * feat: Add function to transfer ownership of child contracts * fix: Allows minting max amount limit * fix: Remove unneeded new dep in package --------- Co-authored-by: miguelmtzinf <[email protected]>
1 parent 222c1c6 commit b52f6c4

File tree

3 files changed

+108
-11
lines changed

3 files changed

+108
-11
lines changed

contracts/mocks/testnet-helpers/Faucet.sol

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
// SPDX-License-Identifier: BUSL-1.1
22
pragma solidity ^0.8.0;
33

4-
import {IFaucet} from './IFaucet.sol';
5-
import {TestnetERC20} from './TestnetERC20.sol';
64
import {Ownable} from '@aave/core-v3/contracts/dependencies/openzeppelin/contracts/Ownable.sol';
5+
import {TestnetERC20} from './TestnetERC20.sol';
6+
import {IFaucet} from './IFaucet.sol';
77

88
/**
99
* @title Faucet
@@ -14,10 +14,12 @@ contract Faucet is IFaucet, Ownable {
1414
// If disabled, anyone can call mint at the faucet, for PoC environments
1515
bool internal _permissioned;
1616

17+
// Maximum amount of tokens per mint allowed
18+
uint256 public constant MAX_MINT_AMOUNT = 10000;
19+
1720
constructor(address owner, bool permissioned) {
1821
require(owner != address(0));
1922
transferOwnership(owner);
20-
2123
_permissioned = permissioned;
2224
}
2325

@@ -37,6 +39,11 @@ contract Faucet is IFaucet, Ownable {
3739
address to,
3840
uint256 amount
3941
) external override onlyOwnerIfPermissioned returns (uint256) {
42+
require(
43+
amount <= MAX_MINT_AMOUNT * (10 ** TestnetERC20(token).decimals()),
44+
'Error: Mint limit transaction exceeded'
45+
);
46+
4047
TestnetERC20(token).mint(to, amount);
4148
return amount;
4249
}
@@ -50,4 +57,14 @@ contract Faucet is IFaucet, Ownable {
5057
function isPermissioned() external view override returns (bool) {
5158
return _permissioned;
5259
}
60+
61+
/// @inheritdoc IFaucet
62+
function transferOwnershipOfChild(
63+
address[] calldata childContracts,
64+
address newOwner
65+
) external override onlyOwner {
66+
for (uint256 i = 0; i < childContracts.length; i++) {
67+
Ownable(childContracts[i]).transferOwnership(newOwner);
68+
}
69+
}
5370
}

contracts/mocks/testnet-helpers/IFaucet.sol

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,7 @@ interface IFaucet {
99
* @param amount The amount of tokens to mint
1010
* @return The amount minted
1111
**/
12-
function mint(
13-
address token,
14-
address to,
15-
uint256 amount
16-
) external returns (uint256);
12+
function mint(address token, address to, uint256 amount) external returns (uint256);
1713

1814
/**
1915
* @notice Enable or disable the need of authentication to call `mint` function
@@ -26,4 +22,11 @@ interface IFaucet {
2622
* @return Returns a boolean, if true the mode is enabled, if false is disabled
2723
*/
2824
function isPermissioned() external view returns (bool);
25+
26+
/**
27+
* @notice Transfer the ownership of child contracts
28+
* @param childContracts A list of child contract addresses
29+
* @param newOwner The address of the new owner
30+
*/
31+
function transferOwnershipOfChild(address[] calldata childContracts, address newOwner) external;
2932
}

test/faucet.spec.ts

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
import { getFirstSigner, waitForTx } from '@aave/deploy-v3';
1+
import { HardhatRuntimeEnvironment } from 'hardhat/types';
22
import { expect } from 'chai';
33
import { parseEther } from 'ethers/lib/utils';
4-
import { HardhatRuntimeEnvironment } from 'hardhat/types';
4+
import { ONE_ADDRESS, evmRevert, evmSnapshot, waitForTx } from '@aave/deploy-v3';
55
import { makeSuite, TestEnv } from './helpers/make-suite';
6+
import { Ownable__factory, TestnetERC20__factory } from '../types';
67

78
declare let hre: HardhatRuntimeEnvironment;
89

910
makeSuite('Faucet', (testEnv: TestEnv) => {
1011
const mintAmount = parseEther('100');
12+
1113
let faucetOwnable;
14+
1215
before(async () => {
1316
// Enforce permissioned mode as disabled for deterministic test suite
1417

@@ -19,6 +22,7 @@ makeSuite('Faucet', (testEnv: TestEnv) => {
1922

2023
await waitForTx(await faucetOwnable.setPermissioned(false));
2124
});
25+
2226
describe('Permissioned mode: disabled', () => {
2327
before(async () => {
2428
// Enforce permissioned mode as disabled for deterministic test suite
@@ -40,6 +44,39 @@ makeSuite('Faucet', (testEnv: TestEnv) => {
4044
it('Getter isPermissioned should return false', async () => {
4145
await expect(await faucetOwnable.isPermissioned()).is.equal(false);
4246
});
47+
48+
it('Mint function should mint tokens within limit', async () => {
49+
const {
50+
users: [, , , user],
51+
dai,
52+
deployer,
53+
} = testEnv;
54+
55+
const threshold = await faucetOwnable.connect(deployer.signer).MAX_MINT_AMOUNT();
56+
const thresholdValue = threshold.toNumber();
57+
const withinLimitThreshold = parseEther((thresholdValue).toString());
58+
59+
await faucetOwnable
60+
.connect(deployer.signer)
61+
.mint(dai.address, user.address, withinLimitThreshold);
62+
await expect(await dai.balanceOf(user.address)).eq(withinLimitThreshold);
63+
});
64+
65+
it('Mint function should revert with values over the limit', async () => {
66+
const {
67+
users: [, , , user],
68+
dai,
69+
deployer,
70+
} = testEnv;
71+
72+
const threshold = await faucetOwnable.connect(deployer.signer).MAX_MINT_AMOUNT();
73+
const thresholdValue = threshold.toNumber();
74+
const maxLimitThreshold = parseEther((thresholdValue + 1).toString());
75+
76+
await expect(
77+
faucetOwnable.connect(deployer.signer).mint(dai.address, user.address, maxLimitThreshold)
78+
).to.be.revertedWith('Error: Mint limit transaction exceeded');
79+
});
4380
});
4481

4582
describe('Permissioned mode: enabled', () => {
@@ -59,17 +96,20 @@ makeSuite('Faucet', (testEnv: TestEnv) => {
5996
});
6097

6198
it('Mint function can only be called by owner', async () => {
99+
const mintAmount = parseEther('100');
62100
const {
63101
users: [, , , user],
64102
dai,
65103
deployer,
66104
} = testEnv;
67105

106+
const initialBalance = await dai.balanceOf(user.address);
107+
68108
await waitForTx(
69109
await faucetOwnable.connect(deployer.signer).mint(dai.address, user.address, mintAmount)
70110
);
71111

72-
await expect(await dai.balanceOf(user.address)).eq(mintAmount);
112+
await expect(await dai.balanceOf(user.address)).eq(initialBalance.add(mintAmount));
73113
});
74114

75115
it('Getter isPermissioned should return true', async () => {
@@ -102,4 +142,41 @@ makeSuite('Faucet', (testEnv: TestEnv) => {
102142

103143
expect(await faucetOwnable.isPermissioned()).equal(true);
104144
});
145+
146+
it('Transfer ownership should revert if not owner', async () => {
147+
const {
148+
deployer,
149+
users: [user1],
150+
} = testEnv;
151+
152+
const childContract = await new TestnetERC20__factory(deployer.signer).deploy(
153+
'CHILD',
154+
'CHILD',
155+
18,
156+
faucetOwnable.address
157+
);
158+
expect(await childContract.owner()).to.be.eq(faucetOwnable.address);
159+
await expect(
160+
faucetOwnable
161+
.connect(user1.signer)
162+
.transferOwnershipOfChild([childContract.address], ONE_ADDRESS)
163+
).to.be.revertedWith('Ownable: caller is not the owner');
164+
expect(await childContract.owner()).to.be.eq(faucetOwnable.address);
165+
});
166+
167+
it('Transfer ownership of child to another address', async () => {
168+
const { deployer } = testEnv;
169+
170+
const childContract = await new TestnetERC20__factory(deployer.signer).deploy(
171+
'CHILD',
172+
'CHILD',
173+
18,
174+
faucetOwnable.address
175+
);
176+
expect(await childContract.owner()).to.be.eq(faucetOwnable.address);
177+
await faucetOwnable
178+
.connect(deployer.signer)
179+
.transferOwnershipOfChild([childContract.address], ONE_ADDRESS);
180+
expect(await childContract.owner()).to.be.eq(ONE_ADDRESS);
181+
});
105182
});

0 commit comments

Comments
 (0)