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): Strike price to token grants #15506

Closed
wants to merge 8 commits into from
Closed
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
115 changes: 84 additions & 31 deletions packages/protocol/contracts/team/TimelockTokenPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,41 +57,53 @@ contract TimelockTokenPool is EssentialContract {
// If non-zero, specifies the total seconds required for the recipient
// to fully unlock all owned tokens.
uint32 unlockPeriod;
}

struct Recipient {
// Strike price per TKO (1e18) in stables (e.g. USDC) in wei.
uint64 strikePrice;
// Withdrawn already
uint128 amountWithdrawn;
Grant[] grants;
}

uint256 public constant MAX_GRANTS_PER_ADDRESS = 8;
uint128 public constant ONE_TKO_UNIT = 1 * 10 ** 18;

address public taikoToken;
address public strikePriceToken;
address public sharedVault;
uint128 public totalAmountGranted;
uint128 public totalAmountVoided;
uint128 public totalAmountWithdrawn;
mapping(address recipient => Recipient) public recipients;
uint128[44] private __gap;
mapping(address recipient => Grant[]) public recipients;
uint128[43] private __gap;

event Granted(address indexed recipient, Grant grant);
event Voided(address indexed recipient, uint128 amount);
event Withdrawn(address indexed recipient, address to, uint128 amount);

error INVALID_GRANT();
error INVALID_PARAM();
error INVALID_STRIKE_PRICE_TOKEN();
error NOTHING_TO_VOID();
error NOTHING_TO_WITHDRAW();
error TOO_MANY();

function init(address _taikoToken, address _sharedVault) external initializer {
function init(
address _taikoToken,
address _strikePriceToken,
address _sharedVault
)
external
initializer
{
__Essential_init();

if (_taikoToken == address(0)) revert INVALID_PARAM();
taikoToken = _taikoToken;

if (_sharedVault == address(0)) revert INVALID_PARAM();
sharedVault = _sharedVault;

if (_strikePriceToken == address(0)) revert INVALID_PARAM();
strikePriceToken = _strikePriceToken;
}

/// @notice Gives a new grant to a address with its own unlock schedule.
Expand All @@ -101,26 +113,26 @@ contract TimelockTokenPool is EssentialContract {
/// same recipient.
function grant(address recipient, Grant memory g) external onlyOwner {
if (recipient == address(0)) revert INVALID_PARAM();
if (recipients[recipient].grants.length >= MAX_GRANTS_PER_ADDRESS) {
if (recipients[recipient].length >= MAX_GRANTS_PER_ADDRESS) {
revert TOO_MANY();
}

_validateGrant(g);

totalAmountGranted += g.amount;
recipients[recipient].grants.push(g);
recipients[recipient].push(g);
emit Granted(recipient, g);
}

/// @notice Puts a stop to all grants for a given recipient.Tokens already
/// granted to the recipient will NOT be voided but are subject to the
/// original unlock schedule.
function void(address recipient) external onlyOwner {
Recipient storage r = recipients[recipient];
Grant[] storage grants = recipients[recipient];
uint128 amountVoided;
uint256 rGrantsLength = r.grants.length;
uint256 rGrantsLength = grants.length;
for (uint128 i; i < rGrantsLength; ++i) {
amountVoided += _voidGrant(r.grants[i]);
amountVoided += _voidGrant(grants[i]);
}
if (amountVoided == 0) revert NOTHING_TO_VOID();

Expand Down Expand Up @@ -148,41 +160,64 @@ contract TimelockTokenPool is EssentialContract {
uint128 amountOwned,
uint128 amountUnlocked,
uint128 amountWithdrawn,
uint128 amountWithdrawable
uint128 amountWithdrawable,
uint128 withdrawableCost
)
{
Recipient storage r = recipients[recipient];
uint256 rGrantsLength = r.grants.length;
Grant[] memory grants = recipients[recipient];
uint256 rGrantsLength = grants.length;
for (uint128 i; i < rGrantsLength; ++i) {
amountOwned += _getAmountOwned(r.grants[i]);
amountUnlocked += _getAmountUnlocked(r.grants[i]);
(
uint128 grantOwned,
uint128 grantUnlocked,
uint128 grantWithdrawableCost,
uint128 grantWithdrawn
) = _processGrantInfo(grants[i]);
// Accumulate towards the overall values
amountOwned += grantOwned;
amountUnlocked += grantUnlocked;
withdrawableCost += grantWithdrawableCost;
amountWithdrawn += grantWithdrawn;
}

amountWithdrawn = r.amountWithdrawn;
amountWithdrawable = amountUnlocked - amountWithdrawn;
}

function getMyGrants(address recipient) public view returns (Grant[] memory) {
return recipients[recipient].grants;
return recipients[recipient];
}

function _withdraw(address recipient, address to) private {
Recipient storage r = recipients[recipient];
uint128 amount;
Grant[] storage grants = recipients[recipient];

uint256 withdrawableCost;
uint128 amountUnlocked;
uint128 amountWithdrawn;

uint256 rGrantsLength = r.grants.length;
uint256 rGrantsLength = grants.length;
for (uint128 i; i < rGrantsLength; ++i) {
amount += _getAmountUnlocked(r.grants[i]);
(, uint128 grantUnlocked, uint128 grantWithdrawableCost, uint128 grantWithdrawn) =
_processGrantInfo(grants[i]);
// Accumulate towards the overall values
amountUnlocked += grantUnlocked;
withdrawableCost += grantWithdrawableCost;
amountWithdrawn += grantWithdrawn;

// Save grant's withdrawal
grants[i].amountWithdrawn += grantUnlocked - grantWithdrawn;
}

amount -= r.amountWithdrawn;
if (amount == 0) revert NOTHING_TO_WITHDRAW();
uint128 withdrawableAmount = amountUnlocked - amountWithdrawn;
if (withdrawableAmount == 0) revert NOTHING_TO_WITHDRAW();

totalAmountWithdrawn += withdrawableAmount;

r.amountWithdrawn += amount;
totalAmountWithdrawn += amount;
IERC20(taikoToken).transferFrom(sharedVault, to, amount);
// Pay the strike price - recipient pays always.
IERC20(strikePriceToken).safeTransferFrom(recipient, sharedVault, withdrawableCost);
// Receive the token
IERC20(taikoToken).transferFrom(sharedVault, to, withdrawableAmount);

emit Withdrawn(recipient, to, amount);
emit Withdrawn(recipient, to, totalAmountWithdrawn);
}

function _voidGrant(Grant storage g) private returns (uint128 amountVoided) {
Expand All @@ -199,7 +234,7 @@ contract TimelockTokenPool is EssentialContract {
return _calcAmount(g.amount, g.grantStart, g.grantCliff, g.grantPeriod);
}

function _getAmountUnlocked(Grant memory g) private view returns (uint128) {
function _getAmountUnlocked(Grant memory g) private view returns (uint128 amountUnlocked) {
return _calcAmount(_getAmountOwned(g), g.unlockStart, g.unlockCliff, g.unlockPeriod);
}

Expand All @@ -225,8 +260,26 @@ contract TimelockTokenPool is EssentialContract {
return amount * uint64(block.timestamp - start) / period;
}

function _processGrantInfo(Grant memory g)
private
view
returns (uint128, uint128, uint128, uint128)
{
uint128 amountOwned = _getAmountOwned(g);
uint128 amountUnlocked = _getAmountUnlocked(g);

// Calculate the USDC price, deducting the amount which is already withdrawn
uint128 withdrawableCost =
(amountUnlocked - g.amountWithdrawn) / ONE_TKO_UNIT * g.strikePrice;

// Accumulate towards the overall unlocked
uint128 amountWithdrawn = g.amountWithdrawn;

return (amountOwned, amountUnlocked, withdrawableCost, amountWithdrawn);
}

function _validateGrant(Grant memory g) private pure {
if (g.amount == 0) revert INVALID_GRANT();
if (g.amount < ONE_TKO_UNIT) revert INVALID_GRANT();
_validateCliff(g.grantStart, g.grantCliff, g.grantPeriod);
_validateCliff(g.unlockStart, g.unlockCliff, g.unlockPeriod);
}
Expand Down
Loading
Loading