Skip to content
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

Implement Lisk L1 token smart contract #13

Merged
merged 44 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
6b35c96
L1LiskToken is ERC20 contract
has5aan Dec 9, 2023
7904a6c
Unauthorized error
has5aan Dec 9, 2023
4f505ab
Ownable contract
has5aan Dec 9, 2023
e2b8c11
L1LiskToken is Ownable
has5aan Dec 9, 2023
b1151d2
forge install: openzeppelin-solidity
has5aan Dec 9, 2023
6eb0c72
BurnerRole contract
has5aan Dec 10, 2023
5c30171
L1LiskToken maintains burners through BurnerRole contract
has5aan Dec 10, 2023
b6d575c
L1LiskToken allows burners to burn their tokens
has5aan Dec 10, 2023
60621e0
L1LiskToken is ERC20Permit contract
has5aan Dec 10, 2023
dd5c930
L1LiskToken is Ownable from openzeppelin-contracts
has5aan Dec 11, 2023
d3a962c
:fire: Removes Ownable contract
has5aan Dec 11, 2023
0095668
:recycle: :white_check_mark: BurnerRole declares and throws Unauthori…
has5aan Dec 11, 2023
eccc339
:fire: Removes Errors.sol
has5aan Dec 11, 2023
f8673bc
L1LiskToken maintains burners
has5aan Dec 11, 2023
97f6a12
:fire: Removes BurnerRole contract
has5aan Dec 11, 2023
8da55e3
L1LiskToken is ERC20Burnable
has5aan Dec 11, 2023
eadfc27
:white_check_mark: L1LiskToken.permit
has5aan Dec 12, 2023
047a27f
:white_check_mark: L1LiskToken is AccessControl
has5aan Dec 13, 2023
24b88bd
:recycle: :white_check_mark:
has5aan Dec 14, 2023
11bd1cc
L1LiskToken maintains owner role
has5aan Dec 14, 2023
1b55613
Replaces OWNER_ROLE with DEFAULT_ADMIN_ROLE and removes owner()
has5aan Dec 14, 2023
bdc73b7
Increases total supply to 300 million
has5aan Dec 14, 2023
804a7f6
Adds transferOwnership
has5aan Dec 14, 2023
ba9c005
Verifies if deployer is owner and not a burner and total supply is 30…
has5aan Dec 14, 2023
c9fce59
:recycle: L1LiskToken declarations
has5aan Dec 14, 2023
67eef49
:recycle: Replaces _msgSender() with msg.sender
has5aan Dec 14, 2023
49105cd
:recycle: Sets DEFAULT_ADMIN_ROLE as the roleAdmin for BURNER_ROLE
has5aan Dec 14, 2023
662aee8
:recycle: Removes getBurnerRole() for BURNER_ROLE() :white_check_mark…
has5aan Dec 14, 2023
f7204c3
:white_check_mark: Adds assertions for test_Initialize
has5aan Dec 14, 2023
9c01f79
:white_check_mark: Owner is not a burner
has5aan Dec 14, 2023
2885e7e
:recycle: :white_check_mark: Rearranges imports
has5aan Dec 20, 2023
7c74f1b
:white_check_mark: Adds assertions
has5aan Dec 20, 2023
1ba3adf
:white_check_mark: Only burner can burn from an account
has5aan Dec 22, 2023
59fbf75
Transfers ownership of L1LiskToken contract to configured address aft…
has5aan Dec 22, 2023
b0a4ad4
:recycle: :white_check_mark: :fire: Removes duplicate SigUtils
has5aan Dec 22, 2023
16f1960
Updates script/L1LiskToken.s.sol
has5aan Dec 22, 2023
604bffe
:recycle: L1LiskToken script
has5aan Dec 22, 2023
0ccbbb9
:recycle: :white_check_mark: Renames test methods for L1LiskToken
has5aan Dec 22, 2023
0abcdac
:memo: L1LiskToken
has5aan Jan 4, 2024
96100b5
:memo: Updates renounceBurner NatSpec.
has5aan Jan 4, 2024
3b3c34a
:memo: L1LiskTokenScript
has5aan Jan 5, 2024
8d9340b
:memo: L1LiskTokenScript
has5aan Jan 5, 2024
0c34706
:memo: L1LiskTokenScript
has5aan Jan 5, 2024
2c02f71
:recycle: Removes call to _setRoleAdmin in ERC20 constructor
has5aan Jan 8, 2024
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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
# Salt for deterministic Lisk L2 token address generation
L2_TOKEN_SALT="test_l2_token_salt"

# Owner address for L1LiskToken contract
L1_TOKEN_OWNER_ADDRESS=0x5FbDB2315678afecb367f032d93F642f64180aa3

# L1 RPC URL, e.g. Infura, Alchemy, or your own node
L1_RPC_URL=https://sepolia.infura.io/v3/YOUR_INFURA_KEY

Expand Down
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,3 @@
[submodule "lib/openzeppelin-contracts"]
path = lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
4 changes: 1 addition & 3 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ solc_version = "0.8.21"
optimizer = true
optimizer_runs = 999999
remappings = [
'@openzeppelin/=lib/openzeppelin-contracts/',
'@openzeppelin-upgradeable/=lib/openzeppelin-contracts-upgradeable/',
'ds-test/=lib/forge-std/lib/ds-test/src/',
'erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/',
'forge-std/=lib/forge-std/src/',
'@openzeppelin/=lib/openzeppelin-contracts/',
'@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/',
'openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/',
'openzeppelin-contracts/=lib/openzeppelin-contracts/',
]

Expand Down
1 change: 0 additions & 1 deletion lib/openzeppelin-contracts-upgradeable
Submodule openzeppelin-contracts-upgradeable deleted from 625fb3
51 changes: 22 additions & 29 deletions script/L1LiskToken.s.sol
matjazv marked this conversation as resolved.
Show resolved Hide resolved
matjazv marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
pragma solidity 0.8.21;

import { Script, console2 } from "forge-std/Script.sol";
import { L1LiskToken, UUPSProxy } from "src/L1/L1LiskToken.sol";
import { L1LiskToken } from "src/L1/L1LiskToken.sol";
import "script/Utils.sol";

/// @title L1LiskTokenScript - L1 Lisk token deployment script
/// @notice This contract is used to deploy L1 Lisk token contract and write its address to JSON file.
/// @notice This contract is used to deploy L1 Lisk token contract, transfers its ownership and writes its address to
/// JSON file.
contract L1LiskTokenScript is Script {
/// @notice Utils contract which provides functions to read and write JSON files containing L1 and L2 addresses.
Utils utils;
Expand All @@ -15,49 +16,41 @@ contract L1LiskTokenScript is Script {
utils = new Utils();
}

/// @notice This function deploys L1 Lisk token contract and writes its address to JSON file.
/// @notice This function deploys L1 Lisk token contract, transfers its ownership and writes its address to JSON
/// file.
function run() public {
// Deployer's private key. Owner of the L1 Lisk token. PRIVATE_KEY is set in .env file.
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");

// Address, the ownership of L1 Lisk token contract is transferred to after deployment.
address ownerAddress = vm.envAddress("L1_TOKEN_OWNER_ADDRESS");

console2.log("Simulation: Deploying L1 Lisk token...");
matjazv marked this conversation as resolved.
Show resolved Hide resolved

// deploy L1LiskToken contract
// deploy L1LiskToken contract and transfer its ownership
vm.startBroadcast(deployerPrivateKey);
L1LiskToken l1LiskToken = new L1LiskToken();
l1LiskToken.transferOwnership(ownerAddress);
vm.stopBroadcast();

assert(address(l1LiskToken) != address(0));
assert(l1LiskToken.proxiableUUID() == 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc);

// deploy proxy contract and point it to the L1LiskToken contract
vm.startBroadcast(deployerPrivateKey);
UUPSProxy proxy = new UUPSProxy(address(l1LiskToken), "");
vm.stopBroadcast();

// wrap in ABI to support easier calls
vm.startBroadcast(deployerPrivateKey);
L1LiskToken wrappedProxy = L1LiskToken(address(proxy));
vm.stopBroadcast();

// initialize the proxy contract (calls the initialize function in L1LiskToken)
vm.startBroadcast(deployerPrivateKey);
wrappedProxy.initialize();
vm.stopBroadcast();

assert(keccak256(bytes(wrappedProxy.name())) == keccak256(bytes("Lisk")));
assert(keccak256(bytes(wrappedProxy.symbol())) == keccak256(bytes("LSK")));
assert(wrappedProxy.decimals() == 18);
assert(wrappedProxy.totalSupply() == 200000000 * 10 ** 18);
assert(wrappedProxy.balanceOf(vm.addr(deployerPrivateKey)) == 200000000 * 10 ** 18);
assert(wrappedProxy.owner() == vm.addr(deployerPrivateKey));
matjazv marked this conversation as resolved.
Show resolved Hide resolved
assert(keccak256(bytes(l1LiskToken.name())) == keccak256(bytes("Lisk")));
assert(keccak256(bytes(l1LiskToken.symbol())) == keccak256(bytes("LSK")));
assert(l1LiskToken.decimals() == 18);
assert(l1LiskToken.totalSupply() == 300000000 * 10 ** 18);
assert(l1LiskToken.balanceOf(vm.addr(deployerPrivateKey)) == 300000000 * 10 ** 18);
assert(l1LiskToken.hasRole(l1LiskToken.DEFAULT_ADMIN_ROLE(), vm.addr(deployerPrivateKey)) == false);
assert(l1LiskToken.hasRole(l1LiskToken.BURNER_ROLE(), vm.addr(deployerPrivateKey)) == false);
assert(l1LiskToken.hasRole(l1LiskToken.DEFAULT_ADMIN_ROLE(), ownerAddress) == true);
assert(l1LiskToken.hasRole(l1LiskToken.BURNER_ROLE(), ownerAddress) == false);
assert(l1LiskToken.balanceOf(ownerAddress) == 0);

console2.log("Simulation: L1 Lisk token successfully deployed!");
console2.log("Simulation: L1 Lisk token address: %s", address(wrappedProxy));
console2.log("Simulation: L1 Lisk token address: %s", address(l1LiskToken));

// write L1LiskToken address to l1addresses.json
Utils.L1AddressesConfig memory l1AddressesConfig;
l1AddressesConfig.L1LiskToken = address(wrappedProxy);
l1AddressesConfig.L1LiskToken = address(l1LiskToken);
utils.writeL1AddressesFile(l1AddressesConfig);
}
}
77 changes: 59 additions & 18 deletions src/L1/L1LiskToken.sol
has5aan marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,30 +1,71 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity 0.8.21;

import { ERC20Upgradeable } from "@openzeppelin-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol";
import { Initializable } from "@openzeppelin-upgradeable/contracts/proxy/utils/Initializable.sol";
import { OwnableUpgradeable } from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

contract UUPSProxy is ERC1967Proxy {
constructor(address _implementation, bytes memory _data) ERC1967Proxy(_implementation, _data) { }
}
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";

contract L1LiskToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, UUPSUpgradeable {
/// @title L1LiskToken
/// @notice L1LiskToken is an implementation of ERC20 token and is an extension of AccessControl, ERC20Permit and
/// ERC20Burnable token contracts.
/// It maintains the ownership of the deployed contract and only allows the owners to transfer the ownership.
/// L1LiskToken's only allows burners to burn the total supply and only the owner manages burner accounts.
contract L1LiskToken is ERC20Burnable, AccessControl, ERC20Permit {
/// @notice Name of the token.
string private constant NAME = "Lisk";

/// @notice Symbol of the token.
string private constant SYMBOL = "LSK";
uint256 private constant TOTAL_SUPPLY = 200_000_000 * 10 ** 18; //200 million LSK tokens

constructor() {
_disableInitializers();
}
/// @notice Total supply of the token.
uint256 private constant TOTAL_SUPPLY = 300_000_000 * 10 ** 18; //300 million LSK tokens

/// @notice Burner role.
bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");

function initialize() public initializer {
__ERC20_init(NAME, SYMBOL);
/// @notice Constructs the L1LiskToken contract.
constructor() ERC20(NAME, SYMBOL) ERC20Permit(NAME) {
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
matjazv marked this conversation as resolved.
Show resolved Hide resolved
_mint(msg.sender, TOTAL_SUPPLY);
__Ownable_init(msg.sender);
}

function _authorizeUpgrade(address newImplementation) internal override onlyOwner { }
/// @notice Allows the owner to transfer the ownership of the contract.
/// @param account The new owner of the contract.
function transferOwnership(address account) public onlyRole(DEFAULT_ADMIN_ROLE) {
matjazv marked this conversation as resolved.
Show resolved Hide resolved
_grantRole(DEFAULT_ADMIN_ROLE, account);
_revokeRole(DEFAULT_ADMIN_ROLE, msg.sender);
}

/// @notice Verifies if an account is a burner.
/// @param account Account to be verified.
/// @return Whether or not the provided account is a burner.
function isBurner(address account) public view returns (bool) {
return hasRole(BURNER_ROLE, account);
}

/// @notice Allows the owner to grant burner role to an account.
/// @param account Account to be added as a burner.
function addBurner(address account) public onlyRole(DEFAULT_ADMIN_ROLE) {
matjazv marked this conversation as resolved.
Show resolved Hide resolved
_grantRole(BURNER_ROLE, account);
}

/// @notice Allows the owner to revoke burner role from an account.
/// @param account Account to be removed as a burner.
function renounceBurner(address account) public onlyRole(DEFAULT_ADMIN_ROLE) {
matjazv marked this conversation as resolved.
Show resolved Hide resolved
_revokeRole(BURNER_ROLE, account);
}

/// @notice Allows a burner to burn token.
/// @param value Amount to be burned.
function burn(uint256 value) public override onlyRole(BURNER_ROLE) {
super.burn(value);
}

/// @notice Allows a burner to burn its allowance from an account.
/// @param account Account to burn tokens from.
/// @param value Amount to burned.
function burnFrom(address account, uint256 value) public override onlyRole(BURNER_ROLE) {
super.burnFrom(account, value);
}
}
Loading