From 525fefe592637af977b70000a19af5e17ab2d412 Mon Sep 17 00:00:00 2001 From: LHerskind Date: Mon, 30 Sep 2024 14:19:55 +0000 Subject: [PATCH] feat: nomismatokopio --- l1-contracts/.solhint.json | 49 +++++++++++---- .../src/governance/Nomismatokopio.sol | 44 +++++++++++++ .../governance/interfaces/IMintableERC20.sol | 9 +++ .../governance/interfaces/INomismatokopio.sol | 8 +++ .../src/governance/libraries/Errors.sol | 4 +- .../test/governance/nomismatokopio/Base.t.sol | 20 ++++++ .../test/governance/nomismatokopio/mint.t.sol | 62 +++++++++++++++++++ .../test/governance/nomismatokopio/mint.tree | 11 ++++ .../nomismatokopio/mintAvailable.t.sol | 38 ++++++++++++ .../nomismatokopio/mintAvailable.tree | 8 +++ 10 files changed, 239 insertions(+), 14 deletions(-) create mode 100644 l1-contracts/src/governance/Nomismatokopio.sol create mode 100644 l1-contracts/src/governance/interfaces/IMintableERC20.sol create mode 100644 l1-contracts/src/governance/interfaces/INomismatokopio.sol create mode 100644 l1-contracts/test/governance/nomismatokopio/Base.t.sol create mode 100644 l1-contracts/test/governance/nomismatokopio/mint.t.sol create mode 100644 l1-contracts/test/governance/nomismatokopio/mint.tree create mode 100644 l1-contracts/test/governance/nomismatokopio/mintAvailable.t.sol create mode 100644 l1-contracts/test/governance/nomismatokopio/mintAvailable.tree diff --git a/l1-contracts/.solhint.json b/l1-contracts/.solhint.json index 2acdeeb2a606..970f17dbcc50 100644 --- a/l1-contracts/.solhint.json +++ b/l1-contracts/.solhint.json @@ -1,9 +1,11 @@ { "extends": "solhint:recommended", "rules": { - "compiler-version": ["error", ">=0.8.27"], + "compiler-version": [ + "error", + ">=0.8.27" + ], "no-inline-assembly": "off", - "gas-custom-errors": "off", "func-visibility": [ "error", { @@ -11,8 +13,12 @@ } ], "no-empty-blocks": "off", - "no-unused-vars": ["error"], - "state-visibility": ["error"], + "no-unused-vars": [ + "error" + ], + "state-visibility": [ + "error" + ], "not-rely-on-time": "off", "const-name-snakecase": [ "error", @@ -32,13 +38,30 @@ "allowPrefix": true } ], - "private-func-leading-underscore": ["error"], - "private-vars-no-leading-underscore": ["error"], - "func-param-name-leading-underscore": ["error"], - "func-param-name-mixedcase": ["error"], - "strict-override": ["error"], - "strict-import": ["error"], - "ordering": ["error"], - "comprehensive-interface": ["error"] + "private-func-leading-underscore": [ + "error" + ], + "private-vars-no-leading-underscore": [ + "error" + ], + "func-param-name-leading-underscore": [ + "error" + ], + "func-param-name-mixedcase": [ + "error" + ], + "strict-override": [ + "error" + ], + "strict-import": [ + "error" + ], + "ordering": [ + "error" + ], + "comprehensive-interface": [ + "error" + ], + "custom-error-over-require": "off" } -} +} \ No newline at end of file diff --git a/l1-contracts/src/governance/Nomismatokopio.sol b/l1-contracts/src/governance/Nomismatokopio.sol new file mode 100644 index 000000000000..7a5e97be0132 --- /dev/null +++ b/l1-contracts/src/governance/Nomismatokopio.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.27; + +import {Ownable} from "@oz/access/Ownable.sol"; +import {Errors} from "@aztec/governance/libraries/Errors.sol"; +import {IMintableERC20} from "@aztec/governance/interfaces/IMintableERC20.sol"; +import {INomismatokopio} from "@aztec/governance/interfaces/INomismatokopio.sol"; + +contract Nomismatokopio is INomismatokopio, Ownable { + IMintableERC20 public immutable ASSET; + uint256 public immutable RATE; + uint256 public timeOfLastMint; + + constructor(IMintableERC20 _asset, uint256 _rate, address _owner) Ownable(_owner) { + ASSET = _asset; + RATE = _rate; + timeOfLastMint = block.timestamp; + } + + /** + * @notice Mint tokens up to the `mintAvailable` limit + * Beware that the mintAvailable will be reset to 0, and not just + * reduced by the amount minted. + * + * @param _to - The address to receive the funds + * @param _amount - The amount to mint + */ + function mint(address _to, uint256 _amount) external override(INomismatokopio) onlyOwner { + uint256 maxMint = mintAvailable(); + require(_amount <= maxMint, Errors.Nomismatokopio__InssuficientMintAvailable(maxMint, _amount)); + timeOfLastMint = block.timestamp; + ASSET.mint(_to, _amount); + } + + /** + * @notice The amount of funds that is available for "minting" + * + * @return The amount mintable + */ + function mintAvailable() public view override(INomismatokopio) returns (uint256) { + return RATE * (block.timestamp - timeOfLastMint); + } +} diff --git a/l1-contracts/src/governance/interfaces/IMintableERC20.sol b/l1-contracts/src/governance/interfaces/IMintableERC20.sol new file mode 100644 index 000000000000..20b5b69b47b4 --- /dev/null +++ b/l1-contracts/src/governance/interfaces/IMintableERC20.sol @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.27; + +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; + +interface IMintableERC20 is IERC20 { + function mint(address _to, uint256 _amount) external; +} diff --git a/l1-contracts/src/governance/interfaces/INomismatokopio.sol b/l1-contracts/src/governance/interfaces/INomismatokopio.sol new file mode 100644 index 000000000000..27f65cbafbdf --- /dev/null +++ b/l1-contracts/src/governance/interfaces/INomismatokopio.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright 2023 Aztec Labs. +pragma solidity >=0.8.27; + +interface INomismatokopio { + function mint(address _to, uint256 _amount) external; + function mintAvailable() external view returns (uint256); +} diff --git a/l1-contracts/src/governance/libraries/Errors.sol b/l1-contracts/src/governance/libraries/Errors.sol index 96a6736ec237..be84e159db3c 100644 --- a/l1-contracts/src/governance/libraries/Errors.sol +++ b/l1-contracts/src/governance/libraries/Errors.sol @@ -11,6 +11,8 @@ pragma solidity >=0.8.27; */ library Errors { // Registry - error Registry__RollupNotRegistered(address rollup); // 0xa1fee4cf + error Nomismatokopio__InssuficientMintAvailable(uint256 available, uint256 needed); // 0xf268b931 + error Registry__RollupAlreadyRegistered(address rollup); // 0x3c34eabf + error Registry__RollupNotRegistered(address rollup); // 0xa1fee4cf } diff --git a/l1-contracts/test/governance/nomismatokopio/Base.t.sol b/l1-contracts/test/governance/nomismatokopio/Base.t.sol new file mode 100644 index 000000000000..a96a9db3a031 --- /dev/null +++ b/l1-contracts/test/governance/nomismatokopio/Base.t.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.27; + +import {Test} from "forge-std/Test.sol"; + +import {IMintableERC20} from "@aztec/governance/interfaces/IMintableERC20.sol"; + +import {TestERC20} from "@aztec/mock/TestERC20.sol"; +import {Nomismatokopio} from "@aztec/governance/Nomismatokopio.sol"; + +contract NomismatokopioBase is Test { + IMintableERC20 internal token; + + Nomismatokopio internal nom; + + function _deploy(uint256 _rate) internal { + token = IMintableERC20(address(new TestERC20())); + nom = new Nomismatokopio(token, _rate, address(this)); + } +} diff --git a/l1-contracts/test/governance/nomismatokopio/mint.t.sol b/l1-contracts/test/governance/nomismatokopio/mint.t.sol new file mode 100644 index 000000000000..052acba10d01 --- /dev/null +++ b/l1-contracts/test/governance/nomismatokopio/mint.t.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.27; + +import {Ownable} from "@oz/access/Ownable.sol"; +import {IERC20} from "@oz/token/ERC20/IERC20.sol"; +import {Errors} from "@aztec/governance/libraries/Errors.sol"; +import {NomismatokopioBase} from "./Base.t.sol"; + +contract MintTest is NomismatokopioBase { + uint256 internal constant RATE = 1e18; + uint256 internal maxMint; + + function setUp() public { + _deploy(RATE); + vm.warp(block.timestamp + 1000); + + maxMint = nom.mintAvailable(); + + assertGt(maxMint, 0); + } + + function test_GivenCallerIsNotOwner(address _caller) external { + // it reverts + vm.assume(_caller != address(this)); + vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, _caller)); + vm.prank(_caller); + nom.mint(address(0xdead), 1); + } + + modifier givenCallerIsOwner() { + _; + } + + function test_GivenAmountLargerThanMaxMint(uint256 _amount) external givenCallerIsOwner { + // it reverts + uint256 amount = bound(_amount, maxMint + 1, type(uint256).max); + vm.expectRevert( + abi.encodeWithSelector( + Errors.Nomismatokopio__InssuficientMintAvailable.selector, maxMint, amount + ) + ); + nom.mint(address(0xdead), amount); + } + + function test_GivenAmountLessThanOrEqualMaxMint(uint256 _amount) external givenCallerIsOwner { + // it updates timeOfLastMint + // it mints amount + // it emits a {Transfer} event + // it will return 0 for mintAvailable in same block + uint256 amount = bound(_amount, 1, maxMint); + assertGt(amount, 0); + uint256 balanceBefore = token.balanceOf(address(0xdead)); + + vm.expectEmit(true, true, true, false, address(token)); + emit IERC20.Transfer(address(0), address(0xdead), amount); + nom.mint(address(0xdead), amount); + + assertEq(token.balanceOf(address(0xdead)), balanceBefore + amount); + assertEq(nom.mintAvailable(), 0); + assertEq(nom.timeOfLastMint(), block.timestamp); + } +} diff --git a/l1-contracts/test/governance/nomismatokopio/mint.tree b/l1-contracts/test/governance/nomismatokopio/mint.tree new file mode 100644 index 000000000000..19134b170cdf --- /dev/null +++ b/l1-contracts/test/governance/nomismatokopio/mint.tree @@ -0,0 +1,11 @@ +MintTest +├── given caller is not owner +│ └── it reverts +└── given caller is owner + ├── given amount larger than maxMint + │ └── it reverts + └── given amount less than or equal maxMint + ├── it updates timeOfLastMint + ├── it mints amount + ├── it emits a {Transfer} event + └── it will return 0 for mintAvailable in same block \ No newline at end of file diff --git a/l1-contracts/test/governance/nomismatokopio/mintAvailable.t.sol b/l1-contracts/test/governance/nomismatokopio/mintAvailable.t.sol new file mode 100644 index 000000000000..1addf16f5a01 --- /dev/null +++ b/l1-contracts/test/governance/nomismatokopio/mintAvailable.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.27; + +import {NomismatokopioBase} from "./Base.t.sol"; + +contract MintAvailableTest is NomismatokopioBase { + function test_GivenRateIs0(uint256 _time) external { + // it returns 0 + _deploy(0); + uint256 timeJump = bound(_time, 0, type(uint128).max); + vm.warp(block.timestamp + timeJump); + + assertEq(nom.mintAvailable(), 0); + } + + modifier givenRateIsNot0(uint256 _rate) { + uint256 rate = bound(_rate, 1, type(uint128).max); + _deploy(rate); + + assertEq(rate, nom.RATE()); + _; + } + + function test_GivenSameTimeAsDeployment(uint256 _rate) external givenRateIsNot0(_rate) { + // it returns 0 + assertEq(nom.mintAvailable(), 0); + } + + function test_GivenAfterDeployment(uint256 _rate, uint256 _time) external givenRateIsNot0(_rate) { + // it returns >0 + + uint256 timeJump = bound(_time, 1, type(uint128).max); + vm.warp(block.timestamp + timeJump); + + assertGt(nom.mintAvailable(), 0); + assertEq(nom.mintAvailable(), nom.RATE() * timeJump); + } +} diff --git a/l1-contracts/test/governance/nomismatokopio/mintAvailable.tree b/l1-contracts/test/governance/nomismatokopio/mintAvailable.tree new file mode 100644 index 000000000000..4bede564c2b2 --- /dev/null +++ b/l1-contracts/test/governance/nomismatokopio/mintAvailable.tree @@ -0,0 +1,8 @@ +MintAvailableTest +├── given rate is 0 +│ └── it returns 0 +└── given rate is not 0 + ├── given same time as deployment + │ └── it returns 0 + └── given after deployment + └── it returns >0 \ No newline at end of file