Skip to content

Commit

Permalink
Claim strewards and tests (#481)
Browse files Browse the repository at this point in the history
  • Loading branch information
addiaddiaddi authored Oct 9, 2023
1 parent d47d25f commit 029262d
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 1 deletion.
4 changes: 4 additions & 0 deletions contracts/mocks/MockStProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,8 @@ contract stFLIP is ERC20 {
emit Burn(msg.sender, value, refundee);
_burn(msg.sender, value);
}

function mockSlash(address account, uint256 amount) public {
_burn(account, amount);
}
}
54 changes: 53 additions & 1 deletion contracts/utils/TokenVestingStaking.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,15 @@ contract TokenVestingStaking is ITokenVestingStaking, Shared {

// The contract that holds the reference addresses for staking purposes.
IAddressHolder public immutable addressHolder;

Check failure on line 46 in contracts/utils/TokenVestingStaking.sol

View workflow job for this annotation

GitHub Actions / lint / lint

Delete ····
bool public revoked;

// Cumulative counter for amount staked to the st provider
uint256 public stTokenStaked;

// Cumulative counter for amount unstaked from the st provider
uint256 public stTokenUnstaked;

/**
* @param beneficiary_ address of the beneficiary to whom vested tokens are transferred
* @param revoker_ the person with the power to revoke the vesting. Address(0) means it is not revocable.
Expand Down Expand Up @@ -101,6 +107,8 @@ contract TokenVestingStaking is ITokenVestingStaking, Shared {

FLIP.approve(stMinter, amount);
require(IMinter(stMinter).mint(address(this), amount));

stTokenStaked += amount;
}

/**
Expand All @@ -111,7 +119,51 @@ contract TokenVestingStaking is ITokenVestingStaking, Shared {
(address stBurner, address stFlip) = addressHolder.getUnstakingAddresses();

IERC20(stFlip).approve(stBurner, amount);

stTokenUnstaked += amount;

return IBurner(stBurner).burn(address(this), amount);

Check failure on line 126 in contracts/utils/TokenVestingStaking.sol

View workflow job for this annotation

GitHub Actions / lint / lint

Delete ⏎
}

/**
* @notice Claims the liquid staking provider rewards.
* @param recipient_ the address to send the rewards to. If 0x0, then the beneficiary is used.
* @param amount_ the amount of rewards to claim. If greater than `totalRewards`, then all rewards are claimed.
* @dev `stTokenCounter` updates after staking/unstaking operation to keep track of the st token principle. Any amount above the
* principle is considered rewards and thus can be claimed by the beneficiary.
*

Check failure on line 135 in contracts/utils/TokenVestingStaking.sol

View workflow job for this annotation

GitHub Actions / lint / lint

Delete ·
* Claim rewards flow possibilities
* 1. increment stake (staked 100, unstaked 0, balance 100)
* 2. earn rewards (staked 100, unstaked 0, balance 103)
* 3. claim rewards (staked 100, unstaked 0, balance 100) 103 + 0 - 100 = 3
* 4. receive 3 stflip
*

Check failure on line 141 in contracts/utils/TokenVestingStaking.sol

View workflow job for this annotation

GitHub Actions / lint / lint

Delete ·
* 1. stake (staked 100, unstaked 0, balance 100)
* 2. earn rewards (staked 100, unstaked 0, balance 103)
* 3. unstake all (staked 100, unstaked 103, balance 0)
* 4. claim underflows (staked 100, unstaked 103, balance 0) 0 + 103 - 100 = 3
* 5. Need to have stflip to claim
* 1. stake (staked 100, unstaked 0, balance 100)
* 2. get slashed (staked 100, unstaked 0, balance 95)
* 3. unstake all (staked 100, unstaked 0, balance 95)
* 4. claim underflows (staked 100, unstaked 0, balance 95) 95 + 0 - 100 = -5
* 5. must earn 5 stflip first before earning claimable rewards
*

Check failure on line 152 in contracts/utils/TokenVestingStaking.sol

View workflow job for this annotation

GitHub Actions / lint / lint

Delete ·
* 1. stake (staked 100, unstaked 0, balance 100)
* 2. earn rewards (staked 100, unstaked 0, balance 103)
* 3. unstake half (staked 50, unstaked 53, balance 50)
* 4. claim rewards (staked 50, unstaked 53, balance 50) 50 + 53 - 50 = 3
* 5. Receive 3 stflip
*/
function claimStProviderRewards(address recipient_, uint256 amount_) external onlyBeneficiary notRevoked {
(, address stFlip) = addressHolder.getUnstakingAddresses();
uint256 totalRewards = stFLIP(stFlip).balanceOf(address(this)) + stTokenUnstaked - stTokenStaked;

uint256 amount = amount_ > totalRewards ? totalRewards : amount_;
address recipient = recipient_ == address(0) ? beneficiary : recipient_;

stFLIP(stFlip).transfer(recipient, amount);
}

/**
Expand Down
117 changes: 117 additions & 0 deletions tests/token_vesting/stakeStProvider/test_stProvider.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,120 @@ def test_unstakeFromStProvider(addrs, tokenVestingStaking, cf, mockStProvider):
assert stFLIP.balanceOf(tv) == 0
assert cf.flip.balanceOf(staking_address) == 0
assert stFLIP.balanceOf(staking_address) == 0


def test_stProviderClaimRewards(addrs, tokenVestingStaking, cf, mockStProvider):
tv, _, total = tokenVestingStaking
stFLIP, minter, _, staking_address = mockStProvider
reward_amount = 100 * 10**18

cf.flip.approve(minter, 2**256 - 1, {"from": addrs.DEPLOYER})
minter.mint(addrs.DEPLOYER, reward_amount, {"from": addrs.DEPLOYER})

assert cf.flip.balanceOf(tv) == total
assert stFLIP.balanceOf(tv) == 0
assert tv.stTokenStaked() == 0
assert tv.stTokenUnstaked() == 0

tv.stakeToStProvider(total, {"from": addrs.BENEFICIARY})

assert cf.flip.balanceOf(tv) == 0
assert stFLIP.balanceOf(tv) == total
assert tv.stTokenStaked() == total
assert tv.stTokenUnstaked() == 0

stFLIP.transfer(tv, reward_amount, {"from": addrs.DEPLOYER}) # earn rewards

assert cf.flip.balanceOf(tv) == 0
assert stFLIP.balanceOf(tv) == total + reward_amount
assert tv.stTokenStaked() == total
assert tv.stTokenUnstaked() == 0

tv.claimStProviderRewards(
addrs.BENEFICIARY, reward_amount, {"from": addrs.BENEFICIARY}
)

assert cf.flip.balanceOf(tv) == 0
assert stFLIP.balanceOf(tv) == total
assert tv.stTokenStaked() == total
assert tv.stTokenUnstaked() == 0
assert stFLIP.balanceOf(addrs.BENEFICIARY) == reward_amount

def test_stProviderClaimRewardsInsufficientStflip(addrs, tokenVestingStaking, cf, mockStProvider):
tv, _, total = tokenVestingStaking
stFLIP, minter, _, staking_address = mockStProvider
reward_amount = 100 * 10**18

cf.flip.approve(minter, 2**256 - 1, {"from": addrs.DEPLOYER})
minter.mint(addrs.DEPLOYER, reward_amount, {"from": addrs.DEPLOYER})

assert cf.flip.balanceOf(tv) == total
assert stFLIP.balanceOf(tv) == 0
assert tv.stTokenStaked() == 0
assert tv.stTokenUnstaked() == 0

tv.stakeToStProvider(total, {"from": addrs.BENEFICIARY})

assert cf.flip.balanceOf(tv) == 0
assert stFLIP.balanceOf(tv) == total
assert tv.stTokenStaked() == total
assert tv.stTokenUnstaked() == 0

stFLIP.transfer(tv, reward_amount, {"from": addrs.DEPLOYER}) # earn rewards

assert cf.flip.balanceOf(tv) == 0
assert stFLIP.balanceOf(tv) == total + reward_amount
assert tv.stTokenStaked() == total
assert tv.stTokenUnstaked() == 0


tv.unstakeFromStProvider(total + reward_amount, {"from": addrs.BENEFICIARY})

assert cf.flip.balanceOf(tv) == 0
assert stFLIP.balanceOf(tv) == 0
assert tv.stTokenStaked() == total
assert tv.stTokenUnstaked() == total + reward_amount


with reverts(REV_MSG_ERC20_EXCEED_BAL):
tv.claimStProviderRewards(
addrs.BENEFICIARY, reward_amount, {"from": addrs.BENEFICIARY}
)


def test_stProviderClaimRewardsSlash(addrs, tokenVestingStaking, cf, mockStProvider):
tv, _, total = tokenVestingStaking
stFLIP, minter, _, staking_address = mockStProvider
slash_amount = 100 * 10**18

assert cf.flip.balanceOf(tv) == total
assert stFLIP.balanceOf(tv) == 0
assert tv.stTokenStaked() == 0
assert tv.stTokenUnstaked() == 0

tv.stakeToStProvider(total, {"from": addrs.BENEFICIARY})

assert cf.flip.balanceOf(tv) == 0
assert stFLIP.balanceOf(tv) == total
assert tv.stTokenStaked() == total
assert tv.stTokenUnstaked() == 0

stFLIP.mockSlash(tv, slash_amount, {"from": addrs.DEPLOYER})

assert cf.flip.balanceOf(tv) == 0
assert stFLIP.balanceOf(tv) == total - slash_amount
assert tv.stTokenStaked() == total
assert tv.stTokenUnstaked() == 0

tv.unstakeFromStProvider(total - slash_amount, {"from": addrs.BENEFICIARY})

assert cf.flip.balanceOf(tv) == 0
assert stFLIP.balanceOf(tv) == 0
assert tv.stTokenStaked() == total
assert tv.stTokenUnstaked() == total - slash_amount


with reverts(REV_MSG_INTEGER_OVERFLOW):
tv.claimStProviderRewards(
addrs.BENEFICIARY, 2**256 - 1, {"from": addrs.BENEFICIARY}
)

0 comments on commit 029262d

Please sign in to comment.