Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 36 additions & 13 deletions l1-contracts/.solhint.json
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
{
"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",
{
"ignoreConstructors": true
}
],
"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",
Expand All @@ -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"
}
}
}
44 changes: 44 additions & 0 deletions l1-contracts/src/governance/Nomismatokopio.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
9 changes: 9 additions & 0 deletions l1-contracts/src/governance/interfaces/IMintableERC20.sol
Original file line number Diff line number Diff line change
@@ -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;
}
8 changes: 8 additions & 0 deletions l1-contracts/src/governance/interfaces/INomismatokopio.sol
Original file line number Diff line number Diff line change
@@ -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);
}
4 changes: 3 additions & 1 deletion l1-contracts/src/governance/libraries/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
20 changes: 20 additions & 0 deletions l1-contracts/test/governance/nomismatokopio/Base.t.sol
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 {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));
}
}
62 changes: 62 additions & 0 deletions l1-contracts/test/governance/nomismatokopio/mint.t.sol
Original file line number Diff line number Diff line change
@@ -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() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, comment would be nice that this should be empty because the base contract test deployed it.

_;
}

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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this assert needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No

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);
}
}
11 changes: 11 additions & 0 deletions l1-contracts/test/governance/nomismatokopio/mint.tree
Original file line number Diff line number Diff line change
@@ -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
38 changes: 38 additions & 0 deletions l1-contracts/test/governance/nomismatokopio/mintAvailable.t.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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