Skip to content

Commit

Permalink
add token factory
Browse files Browse the repository at this point in the history
  • Loading branch information
RensR committed Nov 29, 2024
1 parent bca2fe0 commit 40ed95b
Show file tree
Hide file tree
Showing 24 changed files with 1,874 additions and 4 deletions.
5 changes: 1 addition & 4 deletions contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@
},
"files": [
"src/v0.8/ccip/**/*.sol",
"src/v0.8/shared/access/ConfirmedOwner.sol",
"src/v0.8/shared/access/ConfirmedOwnerWithProposal.sol",
"src/v0.8/shared/access/OwnerIsCreator.sol",
"src/v0.8/shared/access/AuthorizedCallers.sol",
"src/v0.8/shared/access/*.sol",
"src/v0.8/shared/call/CallWithExactGas.sol",
"src/v0.8/shared/enumerable/EnumerableMapBytes32.sol",
"src/v0.8/shared/enumerable/EnumerableMapAddresses.sol",
Expand Down
20 changes: 20 additions & 0 deletions contracts/src/v0.8/ccip/interfaces/ITokenAdminRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,24 @@ interface ITokenAdminRegistry {
/// @param localToken The token to register the administrator for.
/// @param administrator The administrator to register.
function proposeAdministrator(address localToken, address administrator) external;

/// @notice Accepts the administrator role for a token.
/// @param localToken The token to accept the administrator role for.
/// @dev This function can only be called by the pending administrator.
function acceptAdminRole(
address localToken
) external;

/// @notice Sets the pool for a token. Setting the pool to address(0) effectively delists the token
/// from CCIP. Setting the pool to any other address enables the token on CCIP.
/// @param localToken The token to set the pool for.
/// @param pool The pool to set for the token.
function setPool(address localToken, address pool) external;

/// @notice Transfers the administrator role for a token to a new address with a 2-step process.
/// @param localToken The token to transfer the administrator role for.
/// @param newAdmin The address to transfer the administrator role to. Can be address(0) to cancel
/// a pending transfer.
/// @dev The new admin must call `acceptAdminRole` to accept the role.
function transferAdminRole(address localToken, address newAdmin) external;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol";
import {BaseTest} from "../../BaseTest.t.sol";

contract BurnMintERC20Setup is BaseTest {
FactoryBurnMintERC20 internal s_burnMintERC20;

address internal s_mockPool = makeAddr("s_mockPool");
uint256 internal s_amount = 1e18;

address internal s_alice;

function setUp() public virtual override {
BaseTest.setUp();

s_alice = makeAddr("alice");

s_burnMintERC20 = new FactoryBurnMintERC20("Chainlink Token", "LINK", 18, 1e27, 0, s_alice);

// Set s_mockPool to be a burner and minter
s_burnMintERC20.grantMintAndBurnRoles(s_mockPool);
deal(address(s_burnMintERC20), OWNER, s_amount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol";

contract FactoryBurnMintERC20_approve is BurnMintERC20Setup {
function test_Approve_Success() public {
uint256 balancePre = s_burnMintERC20.balanceOf(STRANGER);
uint256 sendingAmount = s_amount / 2;

s_burnMintERC20.approve(STRANGER, sendingAmount);

changePrank(STRANGER);

s_burnMintERC20.transferFrom(OWNER, STRANGER, sendingAmount);

assertEq(sendingAmount + balancePre, s_burnMintERC20.balanceOf(STRANGER));
}

// Reverts

function test_InvalidAddress_Reverts() public {
vm.expectRevert();

s_burnMintERC20.approve(address(s_burnMintERC20), s_amount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {IERC20} from "../../../../vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";
import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol";
import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol";

contract FactoryBurnMintERC20_burn is BurnMintERC20Setup {
function test_BasicBurn_Success() public {
s_burnMintERC20.grantBurnRole(OWNER);
deal(address(s_burnMintERC20), OWNER, s_amount);

vm.expectEmit();
emit IERC20.Transfer(OWNER, address(0), s_amount);

s_burnMintERC20.burn(s_amount);

assertEq(0, s_burnMintERC20.balanceOf(OWNER));
}

// Revert

function test_SenderNotBurner_Reverts() public {
vm.expectRevert(abi.encodeWithSelector(FactoryBurnMintERC20.SenderNotBurner.selector, OWNER));

s_burnMintERC20.burnFrom(STRANGER, s_amount);
}

function test_ExceedsBalance_Reverts() public {
changePrank(s_mockPool);

vm.expectRevert("ERC20: burn amount exceeds balance");

s_burnMintERC20.burn(s_amount * 2);
}

function test_BurnFromZeroAddress_Reverts() public {
s_burnMintERC20.grantBurnRole(address(0));
changePrank(address(0));

vm.expectRevert("ERC20: burn from the zero address");

s_burnMintERC20.burn(0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol";
import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol";

contract FactoryBurnMintERC20_burnFrom is BurnMintERC20Setup {
function setUp() public virtual override {
BurnMintERC20Setup.setUp();
}

function test_BurnFrom_Success() public {
s_burnMintERC20.approve(s_mockPool, s_amount);

changePrank(s_mockPool);

s_burnMintERC20.burnFrom(OWNER, s_amount);

assertEq(0, s_burnMintERC20.balanceOf(OWNER));
}

// Reverts

function test_SenderNotBurner_Reverts() public {
vm.expectRevert(abi.encodeWithSelector(FactoryBurnMintERC20.SenderNotBurner.selector, OWNER));

s_burnMintERC20.burnFrom(OWNER, s_amount);
}

function test_InsufficientAllowance_Reverts() public {
changePrank(s_mockPool);

vm.expectRevert("ERC20: insufficient allowance");

s_burnMintERC20.burnFrom(OWNER, s_amount);
}

function test_ExceedsBalance_Reverts() public {
s_burnMintERC20.approve(s_mockPool, s_amount * 2);

changePrank(s_mockPool);

vm.expectRevert("ERC20: burn amount exceeds balance");

s_burnMintERC20.burnFrom(OWNER, s_amount * 2);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol";
import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol";

contract FactoryBurnMintERC20_burnFromAlias is BurnMintERC20Setup {
function setUp() public virtual override {
BurnMintERC20Setup.setUp();
}

function test_BurnFrom_Success() public {
s_burnMintERC20.approve(s_mockPool, s_amount);

changePrank(s_mockPool);

s_burnMintERC20.burn(OWNER, s_amount);

assertEq(0, s_burnMintERC20.balanceOf(OWNER));
}

// Reverts

function test_SenderNotBurner_Reverts() public {
vm.expectRevert(abi.encodeWithSelector(FactoryBurnMintERC20.SenderNotBurner.selector, OWNER));

s_burnMintERC20.burn(OWNER, s_amount);
}

function test_InsufficientAllowance_Reverts() public {
changePrank(s_mockPool);

vm.expectRevert("ERC20: insufficient allowance");

s_burnMintERC20.burn(OWNER, s_amount);
}

function test_ExceedsBalance_Reverts() public {
s_burnMintERC20.approve(s_mockPool, s_amount * 2);

changePrank(s_mockPool);

vm.expectRevert("ERC20: burn amount exceeds balance");

s_burnMintERC20.burn(OWNER, s_amount * 2);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol";
import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol";

contract FactoryBurnMintERC20_constructor is BurnMintERC20Setup {
function test_Constructor_Success() public {
string memory name = "Chainlink token v2";
string memory symbol = "LINK2";
uint8 decimals = 19;
uint256 maxSupply = 1e33;

s_burnMintERC20 = new FactoryBurnMintERC20(name, symbol, decimals, maxSupply, 1e18, s_alice);

assertEq(name, s_burnMintERC20.name());
assertEq(symbol, s_burnMintERC20.symbol());
assertEq(decimals, s_burnMintERC20.decimals());
assertEq(maxSupply, s_burnMintERC20.maxSupply());

assertTrue(s_burnMintERC20.isMinter(s_alice));
assertTrue(s_burnMintERC20.isBurner(s_alice));
assertEq(s_burnMintERC20.balanceOf(s_alice), 1e18);
assertEq(s_burnMintERC20.totalSupply(), 1e18);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol";

contract FactoryBurnMintERC20_decreaseApproval is BurnMintERC20Setup {
function test_DecreaseApproval_Success() public {
s_burnMintERC20.approve(s_mockPool, s_amount);
uint256 allowance = s_burnMintERC20.allowance(OWNER, s_mockPool);
assertEq(allowance, s_amount);
s_burnMintERC20.decreaseApproval(s_mockPool, s_amount);
assertEq(s_burnMintERC20.allowance(OWNER, s_mockPool), allowance - s_amount);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol";
import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol";

contract FactoryBurnMintERC20_getCCIPAdmin is BurnMintERC20Setup {
function test_getCCIPAdmin_Success() public view {
assertEq(s_alice, s_burnMintERC20.getCCIPAdmin());
}

function test_setCCIPAdmin_Success() public {
address newAdmin = makeAddr("newAdmin");

vm.expectEmit();
emit FactoryBurnMintERC20.CCIPAdminTransferred(s_alice, newAdmin);

s_burnMintERC20.setCCIPAdmin(newAdmin);

assertEq(newAdmin, s_burnMintERC20.getCCIPAdmin());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol";
import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol";

contract FactoryBurnMintERC20_grantMintAndBurnRoles is BurnMintERC20Setup {
function test_GrantMintAndBurnRoles_Success() public {
assertFalse(s_burnMintERC20.isMinter(STRANGER));
assertFalse(s_burnMintERC20.isBurner(STRANGER));

vm.expectEmit();
emit FactoryBurnMintERC20.MintAccessGranted(STRANGER);
vm.expectEmit();
emit FactoryBurnMintERC20.BurnAccessGranted(STRANGER);

s_burnMintERC20.grantMintAndBurnRoles(STRANGER);

assertTrue(s_burnMintERC20.isMinter(STRANGER));
assertTrue(s_burnMintERC20.isBurner(STRANGER));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {FactoryBurnMintERC20} from "../../../tokenAdminRegistry/TokenPoolFactory/FactoryBurnMintERC20.sol";
import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol";

contract FactoryBurnMintERC20_grantRole is BurnMintERC20Setup {
function test_GrantMintAccess_Success() public {
assertFalse(s_burnMintERC20.isMinter(STRANGER));

vm.expectEmit();
emit FactoryBurnMintERC20.MintAccessGranted(STRANGER);

s_burnMintERC20.grantMintAndBurnRoles(STRANGER);

assertTrue(s_burnMintERC20.isMinter(STRANGER));

vm.expectEmit();
emit FactoryBurnMintERC20.MintAccessRevoked(STRANGER);

s_burnMintERC20.revokeMintRole(STRANGER);

assertFalse(s_burnMintERC20.isMinter(STRANGER));
}

function test_GrantBurnAccess_Success() public {
assertFalse(s_burnMintERC20.isBurner(STRANGER));

vm.expectEmit();
emit FactoryBurnMintERC20.BurnAccessGranted(STRANGER);

s_burnMintERC20.grantBurnRole(STRANGER);

assertTrue(s_burnMintERC20.isBurner(STRANGER));

vm.expectEmit();
emit FactoryBurnMintERC20.BurnAccessRevoked(STRANGER);

s_burnMintERC20.revokeBurnRole(STRANGER);

assertFalse(s_burnMintERC20.isBurner(STRANGER));
}

function test_GrantMany_Success() public {
// Since alice was already granted mint and burn roles in the setup, we will revoke them
// and then grant them again for the purposes of the test
s_burnMintERC20.revokeMintRole(s_alice);
s_burnMintERC20.revokeBurnRole(s_alice);

uint256 numberOfPools = 10;
address[] memory permissionedAddresses = new address[](numberOfPools + 1);
permissionedAddresses[0] = s_mockPool;

for (uint160 i = 0; i < numberOfPools; ++i) {
permissionedAddresses[i + 1] = address(i);
s_burnMintERC20.grantMintAndBurnRoles(address(i));
}

assertEq(permissionedAddresses, s_burnMintERC20.getBurners());
assertEq(permissionedAddresses, s_burnMintERC20.getMinters());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {BurnMintERC20Setup} from "./BurnMintERC20Setup.t.sol";

contract FactoryBurnMintERC20_increaseApproval is BurnMintERC20Setup {
function test_IncreaseApproval_Success() public {
s_burnMintERC20.approve(s_mockPool, s_amount);
uint256 allowance = s_burnMintERC20.allowance(OWNER, s_mockPool);
assertEq(allowance, s_amount);
s_burnMintERC20.increaseApproval(s_mockPool, s_amount);
assertEq(s_burnMintERC20.allowance(OWNER, s_mockPool), allowance + s_amount);
}
}
Loading

0 comments on commit 40ed95b

Please sign in to comment.