Skip to content

Commit 225db12

Browse files
authored
Updated exclusivity logic (#266)
* updated exclusivity logic * handling edge case when totalStakedBal > vaultBal
1 parent e03be54 commit 225db12

File tree

3 files changed

+68
-27
lines changed

3 files changed

+68
-27
lines changed

contracts/ExclusiveGeyser.sol

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
pragma solidity 0.7.6;
33
pragma abicoder v2;
44

5+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
56
import {IUniversalVault} from "./UniversalVault.sol";
67
import {Geyser} from "./Geyser.sol";
78

89
/// @title ExclusiveGeyser
9-
/// @notice A special extension of GeyserV2 which allows staking in,
10-
/// at most one distribution program in any given time, for a given staking token.
10+
/// @notice A special extension of GeyserV2 which enforces that,
11+
/// no staking token balance may be staked in more than one geyser at a time.
1112
/// @dev Security contact: dev-support@ampleforth.org
1213
contract ExclusiveGeyser is Geyser {
1314
/// @inheritdoc Geyser
@@ -16,21 +17,31 @@ contract ExclusiveGeyser is Geyser {
1617
uint256 amount,
1718
bytes calldata permission
1819
) public override {
19-
// verify that vault has NOT locked staking token in other programs
20-
_enforceExclusiveStake(IUniversalVault(vault));
20+
// verify that vault isn't staking the same tokens in multiple programs
21+
_enforceExclusiveStake(IUniversalVault(vault), amount);
2122

2223
// continue with regular stake
2324
super.stake(vault, amount, permission);
2425
}
2526

26-
function _enforceExclusiveStake(IUniversalVault vault) private view {
27+
/// @dev Enforces that the vault can't use tokens which have already been staked.
28+
function _enforceExclusiveStake(IUniversalVault vault, uint256 amount) private view {
29+
require(amount <= computeAvailableStakingBalance(vault), "ExclusiveGeyser: expected exclusive stake");
30+
}
31+
32+
/// @notice Computes the amount of staking tokens in the vault available to be staked exclusively.
33+
function computeAvailableStakingBalance(IUniversalVault vault) public view returns (uint256) {
34+
// Iterates through the vault's locks to compute the total "stakingToken" balance staked across all geysers.
2735
address stakingToken = super.getGeyserData().stakingToken;
36+
uint256 vaultBal = IERC20(stakingToken).balanceOf(address(vault));
37+
uint256 totalStakedBal = 0;
2838
uint256 lockCount = vault.getLockSetCount();
2939
for (uint256 i = 0; i < lockCount; i++) {
3040
IUniversalVault.LockData memory lock = vault.getLockAt(i);
3141
if (lock.token == stakingToken) {
32-
require(lock.delegate == address(this), "ExclusiveGeyser: expected exclusive stake");
42+
totalStakedBal += lock.balance;
3343
}
3444
}
45+
return (vaultBal > totalStakedBal) ? vaultBal - totalStakedBal : 0;
3546
}
3647
}

frontend/src/utils/stakingToken.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,10 @@ const uniswapV2Pair = async (
141141
const totalSupply: BigNumber = await contract.totalSupply()
142142
const totalSupplyNumber = parseFloat(formatUnits(totalSupply, decimals))
143143

144-
const tokenCompositions = await getTokenCompositions(
145-
[token0Address, token1Address],
146-
address,
147-
signerOrProvider,
148-
[0.5, 0.5],
149-
)
144+
const tokenCompositions = await getTokenCompositions([token0Address, token1Address], address, signerOrProvider, [
145+
0.5,
146+
0.5,
147+
])
150148
const [token0Symbol, token1Symbol] = tokenCompositions.map((c) => c.symbol)
151149
const marketCap = getMarketCap(tokenCompositions)
152150

@@ -183,12 +181,10 @@ const getMooniswap = async (tokenAddress: string, signerOrProvider: SignerOrProv
183181

184182
const totalSupplyNumber = parseFloat(formatUnits(totalSupply, decimals))
185183

186-
const tokenCompositions = await getTokenCompositions(
187-
[token0Address, token1Address],
188-
address,
189-
signerOrProvider,
190-
[0.5, 0.5],
191-
)
184+
const tokenCompositions = await getTokenCompositions([token0Address, token1Address], address, signerOrProvider, [
185+
0.5,
186+
0.5,
187+
])
192188
const marketCap = getMarketCap(tokenCompositions)
193189

194190
return {

test/ExclusiveGeyser.ts

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,8 +1031,9 @@ describe('ExclusiveGeyser', function () {
10311031
})
10321032
describe('with insufficient balance', function () {
10331033
it('should fail', async function () {
1034+
// Exclusive stake condition fails first
10341035
await expect(stake(user, geyser, vault, stakingToken, stakeAmount.mul(2))).to.be.revertedWith(
1035-
'UniversalVault: insufficient balance',
1036+
'ExclusiveGeyser: expected exclusive stake',
10361037
)
10371038
})
10381039
})
@@ -1123,7 +1124,7 @@ describe('ExclusiveGeyser', function () {
11231124
}
11241125
})
11251126
it('should fail', async function () {
1126-
await expect(stake(user, geyser, vault, stakingToken, stakeAmount.div(quantity))).to.be.revertedWith(
1127+
await expect(stake(user, geyser, vault, stakingToken, 1)).to.be.revertedWith(
11271128
'Geyser: MAX_STAKES_PER_VAULT reached',
11281129
)
11291130
})
@@ -1166,28 +1167,61 @@ describe('ExclusiveGeyser', function () {
11661167
describe('non-exclusive stake', function () {
11671168
let otherGeyser: Contract
11681169
beforeEach(async function () {
1169-
const args = [
1170+
otherGeyser = await deployGeyser([
11701171
admin.address,
11711172
rewardPoolFactory.address,
11721173
powerSwitchFactory.address,
11731174
stakingToken.address,
11741175
rewardToken.address,
11751176
[rewardScaling.floor, rewardScaling.ceiling, rewardScaling.time],
1176-
]
1177-
otherGeyser = await deployGeyser(args, 'Geyser')
1177+
], 'Geyser')
11781178
await otherGeyser.connect(admin).registerVaultFactory(vaultFactory.address)
1179-
await stake(user, otherGeyser, vault, stakingToken, 1)
1179+
await stake(user, otherGeyser, vault, stakingToken, stakeAmount)
11801180
})
1181+
1182+
11811183
it('should fail', async function () {
11821184
await expect(stake(user, geyser, vault, stakingToken, stakeAmount)).to.be.revertedWith(
11831185
'ExclusiveGeyser: expected exclusive stake',
11841186
)
1187+
expect(await geyser.computeAvailableStakingBalance(vault.address)).to.eq(0)
1188+
expect(await vault.checkBalances()).to.eq(true)
11851189
})
1186-
it('should not fail when there are no locks', async function () {
1187-
await unstakeAndClaim(user, otherGeyser, vault, stakingToken, 1)
1188-
await expect(stake(user, geyser, vault, stakingToken, stakeAmount)).not.to.be.revertedWith(
1190+
1191+
it('should fail', async function () {
1192+
// Note: stakeAmount is staked in both otherGeyser and yetAnotherGeyser
1193+
const yetAnotherGeyser = await deployGeyser([
1194+
admin.address,
1195+
rewardPoolFactory.address,
1196+
powerSwitchFactory.address,
1197+
stakingToken.address,
1198+
rewardToken.address,
1199+
[rewardScaling.floor, rewardScaling.ceiling, rewardScaling.time],
1200+
], 'Geyser')
1201+
await yetAnotherGeyser.connect(admin).registerVaultFactory(vaultFactory.address)
1202+
await stake(user, yetAnotherGeyser, vault, stakingToken, stakeAmount)
1203+
await expect(stake(user, geyser, vault, stakingToken, stakeAmount)).to.be.revertedWith(
11891204
'ExclusiveGeyser: expected exclusive stake',
11901205
)
1206+
expect(await geyser.computeAvailableStakingBalance(vault.address)).to.eq(0)
1207+
expect(await vault.checkBalances()).to.eq(true)
1208+
})
1209+
1210+
it('should NOT fail when there is some unlocked amount', async function () {
1211+
await unstakeAndClaim(user, otherGeyser, vault, stakingToken, 15)
1212+
await expect(stake(user, geyser, vault, stakingToken, 14)).not.to.be.reverted
1213+
expect(await geyser.computeAvailableStakingBalance(vault.address)).to.eq(1)
1214+
expect(await vault.checkBalances()).to.eq(true)
1215+
await expect(stake(user, geyser, vault, stakingToken, 1)).not.to.be.reverted
1216+
expect(await geyser.computeAvailableStakingBalance(vault.address)).to.eq(0)
1217+
expect(await vault.checkBalances()).to.eq(true)
1218+
})
1219+
1220+
it('should NOT fail when there are no locks', async function () {
1221+
await unstakeAndClaim(user, otherGeyser, vault, stakingToken, stakeAmount)
1222+
await expect(stake(user, geyser, vault, stakingToken, stakeAmount)).not.to.be.reverted
1223+
expect(await geyser.computeAvailableStakingBalance(vault.address)).to.eq(0)
1224+
expect(await vault.checkBalances()).to.eq(true)
11911225
})
11921226
})
11931227
})

0 commit comments

Comments
 (0)