-
Notifications
You must be signed in to change notification settings - Fork 598
feat: add minter role to TestERC20 #12889
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
46e86d8
c3b399d
91a29c6
ff8a3ca
4df5eba
e888ff6
4ff8ca1
18bd140
f819f0d
d48d3e2
bf4dc55
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,5 +5,12 @@ pragma solidity >=0.8.27; | |
| import {IERC20} from "@oz/token/ERC20/IERC20.sol"; | ||
|
|
||
| interface IMintableERC20 is IERC20 { | ||
| event MinterAdded(address indexed minter); | ||
| event MinterRemoved(address indexed minter); | ||
|
|
||
| error NotMinter(address caller); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🫡 |
||
|
|
||
| function mint(address _to, uint256 _amount) external; | ||
| function addMinter(address _minter) external; | ||
| function removeMinter(address _minter) external; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| pragma solidity >=0.8.27; | ||
|
|
||
| import {Ownable} from "@oz/access/Ownable.sol"; | ||
| import {IMintableERC20} from "./../governance/interfaces/IMintableERC20.sol"; | ||
|
|
||
| interface IFeeAssetHandler { | ||
| event MintAmountSet(uint256 amount); | ||
|
|
||
| function mint(address _recipient) external; | ||
| function setMintAmount(uint256 _amount) external; | ||
| } | ||
|
|
||
| contract FeeAssetHandler is IFeeAssetHandler, Ownable { | ||
| IMintableERC20 public immutable FEE_ASSET; | ||
| uint256 public mintAmount; | ||
|
|
||
| constructor(address _owner, address _feeAsset, uint256 _mintAmount) Ownable(_owner) { | ||
| FEE_ASSET = IMintableERC20(_feeAsset); | ||
| mintAmount = _mintAmount; | ||
| } | ||
|
|
||
| function mint(address _recipient) external override { | ||
| FEE_ASSET.mint(_recipient, mintAmount); | ||
| } | ||
|
|
||
| function setMintAmount(uint256 _amount) external override onlyOwner { | ||
| mintAmount = _amount; | ||
| emit MintAmountSet(_amount); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,27 +7,42 @@ import {ERC20} from "@oz/token/ERC20/ERC20.sol"; | |
| import {IMintableERC20} from "./../governance/interfaces/IMintableERC20.sol"; | ||
|
|
||
| contract TestERC20 is ERC20, IMintableERC20, Ownable { | ||
| bool public freeForAll = false; | ||
| mapping(address => bool) public minters; | ||
|
|
||
| modifier ownerOrFreeForAll() { | ||
| if (msg.sender != owner() && !freeForAll) { | ||
| revert("Not owner or free for all"); | ||
| } | ||
| modifier onlyMinter() { | ||
| require(minters[msg.sender], NotMinter(msg.sender)); | ||
| _; | ||
| } | ||
|
|
||
| constructor(string memory _name, string memory _symbol, address _owner) | ||
| ERC20(_name, _symbol) | ||
| Ownable(_owner) | ||
| {} | ||
|
|
||
| // solhint-disable-next-line comprehensive-interface | ||
| function setFreeForAll(bool _freeForAll) external onlyOwner { | ||
| freeForAll = _freeForAll; | ||
| { | ||
| minters[_owner] = true; | ||
LHerskind marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| emit MinterAdded(_owner); | ||
| } | ||
|
|
||
| function mint(address _to, uint256 _amount) external override(IMintableERC20) ownerOrFreeForAll { | ||
| function mint(address _to, uint256 _amount) external override(IMintableERC20) onlyMinter { | ||
| _mint(_to, _amount); | ||
| } | ||
|
|
||
| function addMinter(address _minter) public override(IMintableERC20) onlyOwner { | ||
| minters[_minter] = true; | ||
| emit MinterAdded(_minter); | ||
| } | ||
|
|
||
| function removeMinter(address _minter) public override(IMintableERC20) onlyOwner { | ||
| minters[_minter] = false; | ||
| emit MinterRemoved(_minter); | ||
| } | ||
|
|
||
| function transferOwnership(address newOwner) public override(Ownable) onlyOwner { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Micronit, but arg should be |
||
| if (newOwner == address(0)) { | ||
| revert OwnableInvalidOwner(address(0)); | ||
| } | ||
| removeMinter(owner()); | ||
| addMinter(newOwner); | ||
| _transferOwnership(newOwner); | ||
| } | ||
| } | ||
| // docs:end:contract | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| pragma solidity >=0.8.27; | ||
|
|
||
| import {Test} from "forge-std/Test.sol"; | ||
| import {TestERC20} from "@aztec/mock/TestERC20.sol"; | ||
| import {FeeAssetHandler} from "@aztec/mock/FeeAssetHandler.sol"; | ||
|
|
||
| // solhint-disable comprehensive-interface | ||
| // solhint-disable func-name-mixedcase | ||
|
|
||
| contract FeeAssetHandlerTestBase is Test { | ||
| TestERC20 internal testERC20; | ||
| FeeAssetHandler internal feeAssetHandler; | ||
|
|
||
| function setUp() external { | ||
| testERC20 = new TestERC20("test", "TEST", address(this)); | ||
| feeAssetHandler = new FeeAssetHandler(address(this), address(testERC20), 100); | ||
| testERC20.addMinter(address(feeAssetHandler)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| pragma solidity >=0.8.27; | ||
|
|
||
| import {IERC20} from "@oz/token/ERC20/ERC20.sol"; | ||
| import {FeeAssetHandlerTestBase} from "./base.t.sol"; | ||
|
|
||
| // solhint-disable comprehensive-interface | ||
| // solhint-disable func-name-mixedcase | ||
|
|
||
| contract MintTest is FeeAssetHandlerTestBase { | ||
| function test_WhenAnyoneCallsMint(address _caller, address _recipient) external { | ||
| // it mints the mint amount to the recipient | ||
| // it emits {Minted} event | ||
| vm.assume(_recipient != address(0)); | ||
| vm.expectEmit(true, true, true, true, address(testERC20)); | ||
| emit IERC20.Transfer(address(0), _recipient, feeAssetHandler.mintAmount()); | ||
| vm.prank(_caller); | ||
| feeAssetHandler.mint(_recipient); | ||
| assertEq(testERC20.balanceOf(_recipient), feeAssetHandler.mintAmount()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| MintTest | ||
| └── when anyone calls mint | ||
| ├── it mints the mint amount to the recipient | ||
| └── it emits {Minted} event |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| pragma solidity >=0.8.27; | ||
|
|
||
| import {IFeeAssetHandler} from "@aztec/mock/FeeAssetHandler.sol"; | ||
| import {Ownable} from "@oz/access/Ownable.sol"; | ||
| import {FeeAssetHandlerTestBase} from "./base.t.sol"; | ||
|
|
||
| // solhint-disable comprehensive-interface | ||
| // solhint-disable func-name-mixedcase | ||
|
|
||
| contract SetMintAmountTest is FeeAssetHandlerTestBase { | ||
| function test_WhenCallerIsNotOwner(address _caller, uint256 _mintAmount) external { | ||
| // it reverts | ||
| vm.assume(_caller != feeAssetHandler.owner()); | ||
| vm.prank(_caller); | ||
| vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, _caller)); | ||
| feeAssetHandler.setMintAmount(_mintAmount); | ||
| } | ||
|
|
||
| function test_WhenCallerIsOwner(uint256 _mintAmount) external { | ||
| // it updates the mint amount | ||
| // it emits {MintAmountUpdated} event | ||
| vm.expectEmit(true, true, true, true, address(feeAssetHandler)); | ||
| emit IFeeAssetHandler.MintAmountSet(_mintAmount); | ||
| feeAssetHandler.setMintAmount(_mintAmount); | ||
| assertEq(feeAssetHandler.mintAmount(), _mintAmount); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| SetMintAmountTest | ||
| ├── when caller is not owner | ||
| │ └── it reverts | ||
| └── when caller is owner | ||
| ├── it updates the mint amount | ||
| └── it emits {MintAmountUpdated} event |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| pragma solidity >=0.8.27; | ||
|
|
||
| import {IMintableERC20} from "@aztec/governance/interfaces/IMintableERC20.sol"; | ||
| import {Ownable} from "@oz/access/Ownable.sol"; | ||
| import {TestERC20TestBase} from "./base.t.sol"; | ||
|
|
||
| // solhint-disable comprehensive-interface | ||
| // solhint-disable func-name-mixedcase | ||
|
|
||
| contract AddMinterTest is TestERC20TestBase { | ||
| modifier whenTheCallerIsTheOwner() { | ||
| vm.startPrank(testERC20.owner()); | ||
| _; | ||
| vm.stopPrank(); | ||
| } | ||
|
|
||
| modifier whenTheCallerIsNotTheOwner(address _caller) { | ||
| vm.assume(_caller != testERC20.owner()); | ||
| vm.startPrank(_caller); | ||
| _; | ||
| vm.stopPrank(); | ||
| } | ||
|
|
||
| function test_WhenTheCallerIsNotTheOwner(address _caller, address _minter) | ||
| external | ||
| whenTheCallerIsNotTheOwner(_caller) | ||
| { | ||
| // it reverts | ||
| vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, _caller)); | ||
| testERC20.addMinter(_minter); | ||
| } | ||
|
|
||
| function test_WhenTheCallerIsTheOwner(address _minter) external whenTheCallerIsTheOwner { | ||
| // it adds the minter | ||
| // it emits a MinterAdded event | ||
| vm.expectEmit(true, true, true, true, address(testERC20)); | ||
| emit IMintableERC20.MinterAdded(_minter); | ||
| testERC20.addMinter(_minter); | ||
| assertEq(testERC20.minters(_minter), true); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| AddMinterTest | ||
| ├── when the caller is not the owner | ||
| │ └── it reverts | ||
| └── when the caller is the owner | ||
| ├── it adds the minter | ||
| └── it emits a MinterAdded event |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| pragma solidity >=0.8.27; | ||
|
|
||
| import {Test} from "forge-std/Test.sol"; | ||
| import {TestERC20} from "@aztec/mock/TestERC20.sol"; | ||
|
|
||
| // solhint-disable comprehensive-interface | ||
| // solhint-disable func-name-mixedcase | ||
|
|
||
| contract TestERC20TestBase is Test { | ||
| // solhint-disable private-vars-leading-underscore | ||
| TestERC20 internal testERC20; | ||
|
|
||
| function setUp() external { | ||
| testERC20 = new TestERC20("test", "TEST", address(this)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| pragma solidity >=0.8.27; | ||
|
|
||
| import {IMintableERC20} from "@aztec/governance/interfaces/IMintableERC20.sol"; | ||
| import {IERC20} from "@oz/token/ERC20/IERC20.sol"; | ||
| import {TestERC20TestBase} from "./base.t.sol"; | ||
|
|
||
| // solhint-disable comprehensive-interface | ||
| // solhint-disable func-name-mixedcase | ||
|
|
||
| contract MintTest is TestERC20TestBase { | ||
| function test_WhenTheCallerIsNotAMinter(address _caller, address _to, uint256 _amount) external { | ||
| vm.assume(_caller != testERC20.owner()); | ||
| vm.startPrank(_caller); | ||
| // it reverts | ||
| vm.expectRevert(abi.encodeWithSelector(IMintableERC20.NotMinter.selector, _caller)); | ||
| testERC20.mint(_to, _amount); | ||
| vm.stopPrank(); | ||
| } | ||
|
|
||
| function test_WhenTheCallerIsAMinter(address _caller, address _to, uint256 _amount) external { | ||
| // it mints the amount to _to | ||
| // it emits a Transfer event | ||
| vm.prank(testERC20.owner()); | ||
| testERC20.addMinter(_caller); | ||
|
|
||
| vm.assume(_to != address(0)); | ||
|
|
||
| vm.prank(_caller); | ||
| vm.expectEmit(true, true, true, true, address(testERC20)); | ||
| emit IERC20.Transfer(address(0), _to, _amount); | ||
| testERC20.mint(_to, _amount); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| MintTest | ||
| ├── when the caller is not a minter | ||
| │ └── it reverts | ||
| └── when the caller is a minter | ||
| ├── it mints the amount to _to | ||
| └── it emits a Transfer event |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have an event here. We have been pretty bad generally at adding events when state change, but it is usually good practice since it makes analysis and such much simpler.