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

feat(protocol,supplementary-contracts): relocate & allow TokenUnlock to deploy and own ProverSets #17251

Merged
merged 20 commits into from
May 20, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
1 change: 1 addition & 0 deletions packages/protocol/contracts/common/LibStrings.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ library LibStrings {
bytes32 internal constant B_PROPOSER = bytes32("proposer");
bytes32 internal constant B_PROPOSER_ONE = bytes32("proposer_one");
bytes32 internal constant B_PROVER_ASSIGNMENT = bytes32("PROVER_ASSIGNMENT");
bytes32 internal constant B_PROVER_SET = bytes32("prover_set");
bytes32 internal constant B_QUOTA_MANAGER = bytes32("quota_manager");
bytes32 internal constant B_SGX_WATCHDOG = bytes32("sgx_watchdog");
bytes32 internal constant B_SIGNAL_SERVICE = bytes32("signal_service");
Expand Down
61 changes: 45 additions & 16 deletions packages/protocol/contracts/team/proving/ProverSet.sol
Original file line number Diff line number Diff line change
@@ -1,63 +1,92 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol";
import "@openzeppelin/contracts/interfaces/IERC1271.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../../common/EssentialContract.sol";
import "../../common/LibStrings.sol";
import "../../L1/ITaikoL1.sol";

interface IHasRecipient {
function recipient() external view returns (address);
}

/// @title ProverSet
/// @notice A contract that holds TKO token and acts as a Taiko prover. This contract will simply
/// relay `proveBlock` calls to TaikoL1 so msg.sender doesn't need to hold any TKO.
/// @custom:security-contact [email protected]
contract ProverSet is EssentialContract, IERC1271 {
bytes4 private constant _EIP1271_MAGICVALUE = 0x1626ba7e;

mapping(address prover => bool isProver) public isProver;
uint256[49] private __gap;
mapping(address prover => bool isProver) public isProver; // slot 1
address public admin; // slot 2

uint256[48] private __gap;

event ProverEnabled(address indexed prover, bool indexed enabled);
event BlockProvenBy(address indexed prover, uint64 indexed blockId);

error INVALID_STATUS();
error PERMISSION_DENIED();

modifier onlyAuthorized() {
if (msg.sender != admin && msg.sender != IHasRecipient(admin).recipient()) {
revert PERMISSION_DENIED();
}
_;
}

modifier onlyProver() {
if (!isProver[msg.sender]) revert PERMISSION_DENIED();
_;
}

/// @notice Initializes the contract.
function init(address _owner, address _addressManager) external initializer {
function init(
address _owner,
address _admin,
address _addressManager
)
external
nonZeroAddr(_admin)
initializer
{
__Essential_init(_owner, _addressManager);

IERC20 tko = IERC20(resolve(LibStrings.B_TAIKO_TOKEN, false));
admin = _admin;

address taikoL1 = resolve(LibStrings.B_TAIKO, false);
tko.approve(taikoL1, type(uint256).max);

address assignmentHook = resolve(LibStrings.B_ASSIGNMENT_HOOK, false);
tko.approve(assignmentHook, type(uint256).max);
IERC20 tko = IERC20(resolve(LibStrings.B_TAIKO_TOKEN, false));
tko.approve(resolve(LibStrings.B_TAIKO, false), type(uint256).max);
tko.approve(resolve(LibStrings.B_ASSIGNMENT_HOOK, false), type(uint256).max);
}

/// @notice Enables or disables a prover.
function enableProver(address _prover, bool _isProver) external onlyOwner {
function enableProver(address _prover, bool _isProver) external onlyAuthorized {
adaki2004 marked this conversation as resolved.
Show resolved Hide resolved
if (isProver[_prover] == _isProver) revert INVALID_STATUS();
isProver[_prover] = _isProver;

emit ProverEnabled(_prover, _isProver);
}

/// @notice Withdraws Taiko tokens back to the owner address.
function withdraw(uint256 _amount) external onlyOwner {
IERC20(resolve(LibStrings.B_TAIKO_TOKEN, false)).transfer(owner(), _amount);
/// @notice Withdraws Taiko tokens back to the admin address.
function withdrawToAdmin(uint256 _amount) external onlyAuthorized {
IERC20(resolve(LibStrings.B_TAIKO_TOKEN, false)).transfer(admin, _amount);
adaki2004 marked this conversation as resolved.
Show resolved Hide resolved
}

/// @notice Proves or contests a Taiko block.
function proveBlock(uint64 _blockId, bytes calldata _input) external whenNotPaused {
if (!isProver[msg.sender]) revert PERMISSION_DENIED();

function proveBlock(uint64 _blockId, bytes calldata _input) external onlyProver nonReentrant {
emit BlockProvenBy(msg.sender, _blockId);
ITaikoL1(resolve(LibStrings.B_TAIKO, false)).proveBlock(_blockId, _input);
}

/// @notice Delegates token voting right to a delegatee.
/// @param _delegatee The delegatee to receive the voting right.
function delegate(address _delegatee) external onlyAuthorized nonReentrant {
ERC20VotesUpgradeable(resolve(LibStrings.B_TAIKO_TOKEN, false)).delegate(_delegatee);
}
adaki2004 marked this conversation as resolved.
Show resolved Hide resolved

// This function is necessary for this contract to become an assigned prover.
function isValidSignature(
bytes32 _hash,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,37 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20VotesUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import "../../common/EssentialContract.sol";
import "../../common/LibStrings.sol";
import "../../libs/LibMath.sol";
import "../proving/ProverSet.sol";

/// @title TokenUnlocking
/// @title TokenUnlock
/// @notice Manages the linear unlocking of Taiko tokens over a four-year period.
/// Tokens purchased off-chain are deposited into this contract directly from the `msg.sender`
/// address. Token withdrawals are permitted linearly over four years starting from the Token
/// Generation Event (TGE), with no withdrawals allowed during the first year.
/// A separate instance of this contract is deployed for each recipient.
/// @custom:security-contact [email protected]
contract TokenUnlocking is OwnableUpgradeable, ReentrancyGuardUpgradeable {
contract TokenUnlock is EssentialContract {
using SafeERC20 for IERC20;
using LibMath for uint256;

uint256 public constant ONE_YEAR = 365 days;
uint256 public constant FOUR_YEARS = 4 * ONE_YEAR;

uint256 public amountVested;
uint256 public amountWithdrawn;
address public recipient;
address public taikoToken;
uint64 public tgeTimestamp;

uint256[46] private __gap;
mapping(address proverSet => bool valid) public isProverSet;

uint256[47] private __gap;

/// @notice Emitted when token is vested.
/// @param amount The newly vested amount.
Expand All @@ -43,8 +47,18 @@ contract TokenUnlocking is OwnableUpgradeable, ReentrancyGuardUpgradeable {
/// @param newRecipient The new recipient address.
event RecipientChanged(address indexed oldRecipient, address indexed newRecipient);

/// @notice Emitted when a new prover set is created.
/// @param proverSet The new prover set.
event ProverSetCreated(address indexed proverSet);

/// @notice Emitted when TKO are deposited to a prover set.
/// @param proverSet The prover set.
/// @param amount The amount of TKO deposited.
event DepositToProverSet(address indexed proverSet, uint256 amount);

error INVALID_PARAM();
error NOT_WITHDRAWABLE();
error NOT_PROVER_SET();
error PERMISSION_DENIED();

modifier onlyRecipient() {
Expand All @@ -59,29 +73,25 @@ contract TokenUnlocking is OwnableUpgradeable, ReentrancyGuardUpgradeable {

/// @notice Initializes the contract.
/// @param _owner The contract owner address.
/// @param _taikoToken The Taiko token address.
/// @param _addressManager The rollup address manager.
/// @param _recipient Who will be the grantee for this contract.
/// @param _tgeTimestamp The token generation event timestamp.
function init(
address _owner,
address _taikoToken,
address _addressManager,
address _recipient,
uint64 _tgeTimestamp
)
external
initializer
{
if (
_owner == _recipient || _owner == address(0) || _recipient == address(0)
|| _taikoToken == address(0) || _tgeTimestamp == 0
) {
if (_owner == _recipient || _recipient == address(0) || _tgeTimestamp == 0) {
revert INVALID_PARAM();
}

_transferOwnership(_owner);
__Essential_init(_owner, _addressManager);

recipient = _recipient;
taikoToken = _taikoToken;
tgeTimestamp = _tgeTimestamp;
}

Expand All @@ -93,27 +103,55 @@ contract TokenUnlocking is OwnableUpgradeable, ReentrancyGuardUpgradeable {
amountVested += _amount;
emit TokenVested(_amount);

IERC20(taikoToken).safeTransferFrom(msg.sender, address(this), _amount);
IERC20(resolve(LibStrings.B_TAIKO_TOKEN, false)).safeTransferFrom(
msg.sender, address(this), _amount
);
}

/// @notice Create a new prover set.
function createProverSet() external onlyRecipient returns (address proverSet_) {
bytes memory data = abi.encodeCall(ProverSet.init, (owner(), address(this), addressManager));
proverSet_ = address(new ERC1967Proxy(resolve(LibStrings.B_PROVER_SET, false), data));

isProverSet[proverSet_] = true;
emit ProverSetCreated(proverSet_);
}

function depositToProverSet(
address _proverSet,
uint256 _amount
)
external
nonZeroValue(bytes32(_amount))
onlyRecipient
{
if (!isProverSet[_proverSet]) revert NOT_PROVER_SET();

emit DepositToProverSet(_proverSet, _amount);
IERC20(resolve(LibStrings.B_TAIKO_TOKEN, false)).safeTransfer(_proverSet, _amount);
}

/// @notice Withdraws all withdrawable tokens.
/// @param _to The address the token will be sent to.
function withdraw(address _to) external nonReentrant {
uint256 amount = amountWithdrawable();
if (amount == 0) revert NOT_WITHDRAWABLE();

address to = _to == address(0) ? recipient : _to;
if (to != recipient && msg.sender != recipient) {
revert PERMISSION_DENIED();
}
/// @param _amount The amount of tokens to withdraw.
function withdraw(
address _to,
uint256 _amount
)
external
nonZeroAddr(_to)
nonZeroValue(bytes32(_amount))
onlyRecipient
nonReentrant
{
if (_amount > amountWithdrawable()) revert NOT_WITHDRAWABLE();

amountWithdrawn += amount;
emit TokenWithdrawn(to, amount);
emit TokenWithdrawn(_to, _amount);

IERC20(taikoToken).safeTransfer(to, amount);
IERC20(resolve(LibStrings.B_TAIKO_TOKEN, false)).safeTransfer(_to, _amount);
}

function changeRecipient(address _newRecipient) external onlyRecipient nonReentrant {
function changeRecipient(address _newRecipient) external onlyRecipient {
if (_newRecipient == address(0) || _newRecipient == recipient) {
revert INVALID_PARAM();
}
Expand All @@ -125,23 +163,27 @@ contract TokenUnlocking is OwnableUpgradeable, ReentrancyGuardUpgradeable {
/// @notice Delegates token voting right to a delegatee.
/// @param _delegatee The delegatee to receive the voting right.
function delegate(address _delegatee) external onlyRecipient nonReentrant {
ERC20VotesUpgradeable(taikoToken).delegate(_delegatee);
ERC20VotesUpgradeable(resolve(LibStrings.B_TAIKO_TOKEN, false)).delegate(_delegatee);
}

/// @notice Returns the amount of token withdrawable.
/// @return The amount of token withdrawable.
function amountWithdrawable() public view returns (uint256) {
return _getAmountUnlocked() - amountWithdrawn;
IERC20 tko = IERC20(resolve(LibStrings.B_TAIKO_TOKEN, false));
uint256 balance = tko.balanceOf(address(this));
uint256 locked = _getAmountLocked();

return balance.max(locked) - locked;
}

function _getAmountUnlocked() private view returns (uint256) {
function _getAmountLocked() private view returns (uint256) {
uint256 _amountVested = amountVested;
if (_amountVested == 0) return 0;

uint256 _tgeTimestamp = tgeTimestamp;

if (block.timestamp < _tgeTimestamp + ONE_YEAR) return 0;
if (block.timestamp >= _tgeTimestamp + FOUR_YEARS) return _amountVested;
return _amountVested * (block.timestamp - _tgeTimestamp) / FOUR_YEARS;
if (block.timestamp < _tgeTimestamp + ONE_YEAR) return _amountVested;
if (block.timestamp >= _tgeTimestamp + FOUR_YEARS) return 0;
return _amountVested * (_tgeTimestamp + FOUR_YEARS - block.timestamp) / FOUR_YEARS;
}
}
1 change: 1 addition & 0 deletions packages/protocol/deployments/gen-layouts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ contracts=(
"RiscZeroVerifier"
"QuotaManager"
"ProverSet"
"TokenUnlock"
)

# Empty the output file initially
Expand Down
10 changes: 5 additions & 5 deletions packages/protocol/deployments/mainnet-contract-logs.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
- Upgraded from `0xd912aB787624c9eb96a37e658e9596e114360440` to `0xF1cA1F1A068468E1dcF90dA6add185467de80943` @commit `b90b932` @tx`0x416560cd96dc75ccffebe889e8d1ab3e08b33f814dc4a2bf7c6f9555071d1f6f`
- `Init2()` called @tx `0x7311fee56f87294e336393b55939489bc1e810c402f304013475d04c90ca32a9`
- todo:
- register `prover_set`
- register `assignment_hook` for `0x537a2f0D3a5879b41BCb5A2afE2EA5c4961796F6`

#### taikoL1
Expand Down Expand Up @@ -265,12 +266,11 @@
- Upgraded from `0xde1b1FBe7D721af4A56651272ef91A59B7303323` to `0x5f73f0AdC7dAA6134Fe751C4a78d524f9384e0B5` @commit `3740dc0` @tx`0x46a6d47c15505a1259c64d1e09353680e525b2706dd9e095e15019dda7c1b295`
- Called `configureTcbInfoJson` @commit `3740dc0` @tx`0x46a6d47c15505a1259c64d1e09353680e525b2706dd9e095e15019dda7c1b295`

### token_unlocking
### token_unlock

- impl: `0xaccd34dffa1a112880422fccd9df8f08d79dd555`
- logs:
- deployed on May 13, 2024 at commit `165e28279`
- deployed again at commit `e0f6938` @tx `0x53dfd6a880826d12f5d697e85551d2ea56bcd071fecad93d3169c63c05b62a92`
- impl: ``
- todo:
- deploy

## L2 Contracts

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,20 @@ import "forge-std/src/Script.sol";
import "forge-std/src/console2.sol";

import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import "../../contracts/team/tokenunlock/TokenUnlock.sol";

import "../../contracts/tokenUnlocking/TokenUnlocking.sol";

contract DeployTokenUnlocking is Script {
contract DeployTokenUnlock is Script {
using stdJson for string;

address public OWNER = 0x9CBeE534B5D8a6280e01a14844Ee8aF350399C7F; // admin.taiko.eth
address public TAIKO_TOKEN = 0x10dea67478c5F8C5E2D90e5E9B26dBe60c54d800; // token.taiko.eth
address public ROLLUP_ADDRESS_MANAGER = 0x579f40D0BE111b823962043702cabe6Aaa290780;
uint64 public TGE = 1_716_767_999; // Date and time (GMT): Sunday, May 26, 2024 11:59:59 PM
address public IMPL = 0x244108e321FE03b0E33FE63Ef62285F05d191a62;

function setUp() public { }

function run() external {
string memory path = "/script/tokenUnlocking/Deploy.data.json";
string memory path = "/script/tokenunlock/Deploy.data.json";
address[] memory recipients = abi.decode(
vm.parseJson(vm.readFile(string.concat(vm.projectRoot(), path))), (address[])
);
Expand All @@ -30,7 +29,9 @@ contract DeployTokenUnlocking is Script {
vm.startBroadcast();
deployProxy({
impl: IMPL,
data: abi.encodeCall(TokenUnlocking.init, (OWNER, TAIKO_TOKEN, recipients[i], TGE))
data: abi.encodeCall(
TokenUnlock.init, (OWNER, ROLLUP_ADDRESS_MANAGER, recipients[i], TGE)
)
});
vm.stopBroadcast();
console2.log("Deployed!\n");
Expand Down
Loading