Skip to content

Commit ac88e49

Browse files
aleph-vdaejunpark
andauthored
V1.1 Collected Branch (#231)
* Removes the gas reserve which is unused in our deployed versions (#218) * remove gas reserve * remove warning * pause and admin upgrade (#222) * Changed CC Pool LP withdraw to use an input of Shares (#216) * changed the withdraw function to use LP token inputs instead * comment updates * Changes the governance fee collection (#217) * fee collection update * explictly ban the governance address * test merge fixes * Update contracts/ConvergentCurvePool.sol Co-authored-by: Daejun Park <[email protected]> * audit fixes * order fixes to nexus tests Co-authored-by: Daejun Park <[email protected]>
1 parent 4bae413 commit ac88e49

13 files changed

+591
-927
lines changed

contracts/ConvergentCurvePool.sol

+147-165
Large diffs are not rendered by default.

contracts/YVaultAssetProxy.sol

+101-217
Large diffs are not rendered by default.

contracts/YVaultV4AssetProxy.sol

-61
This file was deleted.

contracts/factories/ConvergentPoolFactory.sol

+5-2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ contract ConvergentPoolFactory is BasePoolFactory, Authorizable {
4141
/// @param _percentFee The fee percent of each trades implied yield paid to gov.
4242
/// @param _name The name of the balancer v2 lp token for this pool
4343
/// @param _symbol The symbol of the balancer v2 lp token for this pool
44+
/// @param _pauser An address with the power to stop trading and deposits
4445
/// @return The new pool address
4546
function create(
4647
address _underlying,
@@ -49,7 +50,8 @@ contract ConvergentPoolFactory is BasePoolFactory, Authorizable {
4950
uint256 _unitSeconds,
5051
uint256 _percentFee,
5152
string memory _name,
52-
string memory _symbol
53+
string memory _symbol,
54+
address _pauser
5355
) external returns (address) {
5456
address pool = address(
5557
new ConvergentCurvePool(
@@ -62,7 +64,8 @@ contract ConvergentPoolFactory is BasePoolFactory, Authorizable {
6264
percentFeeGov,
6365
governance,
6466
_name,
65-
_symbol
67+
_symbol,
68+
_pauser
6669
)
6770
);
6871
// Register the pool with the vault

contracts/test/TestConvergentCurvePool.sol

+5-15
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,20 @@ contract TestConvergentCurvePool is ConvergentCurvePool {
2929
_percentFee,
3030
_governance,
3131
name,
32-
symbol
32+
symbol,
33+
_governance
3334
)
3435
{} // solhint-disable-line no-empty-blocks
3536

3637
event UIntReturn(uint256 data);
3738

3839
// Allows tests to burn LP tokens directly
3940
function burnLP(
40-
uint256 outputUnderlying,
41-
uint256 outputBond,
41+
uint256 lpBurn,
4242
uint256[] memory currentBalances,
4343
address source
4444
) public {
45-
uint256[] memory outputs = _burnLP(
46-
outputUnderlying,
47-
outputBond,
48-
currentBalances,
49-
source
50-
);
45+
uint256[] memory outputs = _burnLP(lpBurn, currentBalances, source);
5146
// We use this to return because returndata from state changing tx isn't easily accessible.
5247
emit UIntReturn(outputs[baseIndex]);
5348
emit UIntReturn(outputs[bondIndex]);
@@ -71,11 +66,6 @@ contract TestConvergentCurvePool is ConvergentCurvePool {
7166
emit UIntReturn(amountsIn[bondIndex]);
7267
}
7368

74-
// Allows tests to access mint gov LP
75-
function mintGovLP(uint256[] memory currentReserves) public {
76-
_mintGovernanceLP(currentReserves);
77-
}
78-
7969
// Allows tests to access the trade fee calculator
8070
function assignTradeFee(
8171
uint256 amountIn,
@@ -103,7 +93,7 @@ contract TestConvergentCurvePool is ConvergentCurvePool {
10393
}
10494

10595
// Allows tests to specify fees without making trades
106-
function setFees(uint128 amountUnderlying, uint128 amountBond) public {
96+
function setFees(uint120 amountUnderlying, uint120 amountBond) public {
10797
feesUnderlying = amountUnderlying;
10898
feesBond = amountBond;
10999
}

contracts/test/TestYVault.sol

+4
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ contract TestYVault is ERC20PermitWithSupply {
4343
address destination,
4444
uint256
4545
) external returns (uint256) {
46+
// Yearn supports this
47+
if (_shares == type(uint256).max) {
48+
_shares = balanceOf[msg.sender];
49+
}
4650
uint256 _amount = (_shares * pricePerShare()) / (10**decimals);
4751
_burn(msg.sender, _shares);
4852
IERC20(token).transfer(destination, _amount);

test/convergentCurvePoolTests.ts

+122-65
Original file line numberDiff line numberDiff line change
@@ -299,72 +299,17 @@ describe("ConvergentCurvePool", function () {
299299
expect(totalSupply).to.be.eq(oneThousand.add(sixteenHundred));
300300
});
301301

302-
it("Internally Mints LP correctly for Governance", async function () {
303-
await resetPool();
304-
let govBalanceStart = await poolContract.balanceOf(elementAddress);
305-
const ten = ethers.utils.parseUnits("10", 18);
306-
const five = ethers.utils.parseUnits("5", 18);
307-
// We set the accumulated fees
308-
await mineTx(poolContract.setFees(ten, five));
309-
// Set the current total supply to 100 lp tokens
310-
await mineTx(poolContract.setLPBalance(elementAddress, ten.mul(ten)));
311-
govBalanceStart = await poolContract.balanceOf(elementAddress);
312-
// Mint governance lp
313-
await mineTx(poolContract.mintGovLP([ten.mul(ten), five.mul(ten)]));
314-
// We now check that all of the fees were consume
315-
const feesUnderlying = await poolContract.feesUnderlying();
316-
const feesBond = await poolContract.feesBond();
317-
expect(newBigNumber(0)).to.be.eq(feesUnderlying);
318-
expect(newBigNumber(0)).to.be.eq(feesBond);
319-
// We check that the governance address got ten lp tokens
320-
const govBalanceNew = await poolContract.balanceOf(elementAddress);
321-
expect(ethers.utils.parseUnits("0.5", 18).add(govBalanceStart)).to.be.eq(
322-
govBalanceNew
323-
);
324-
});
325-
326-
// We test the mint functionality where the bond should be fully consumed
327-
it("Internally Mints LP correctly for the bond max", async function () {
302+
// We test the burn functionality where the bond should be fully consumed
303+
it("Internally Burns LP correctly for the underlying max", async function () {
328304
await resetPool();
329305
const oneThousand = ethers.utils.parseUnits("1000", 18);
330306
// Set the current total supply to 1000 lp tokens
331307
await mineTx(poolContract.setLPBalance(accounts[0].address, oneThousand));
332-
// We want a min of 500 underlying and 100 bond
333-
const fiveHundred = ethers.utils.parseUnits("500", 18);
334-
const result = await mineTx(
335-
poolContract.burnLP(
336-
fiveHundred,
337-
fiveHundred.div(5),
338-
[oneThousand, fiveHundred],
339-
accounts[0].address
340-
)
341-
);
342-
// The call should have released 500 underlying and 250 bond
343-
const returned = result.events.filter(
344-
(event) => event.event == "UIntReturn"
345-
);
346-
expect(returned[0].data).to.be.eq(fiveHundred);
347-
expect(returned[1].data).to.be.eq(fiveHundred.div(2));
348-
// The call should have burned 50% of the LP tokens to produce this
349-
const balance = await poolContract.balanceOf(accounts[0].address);
350-
expect(balance).to.be.eq(fiveHundred);
351-
const totalSupply = await poolContract.totalSupply();
352-
expect(totalSupply).to.be.eq(fiveHundred);
353-
});
354-
355-
// We test the mint functionality where the bond should be fully consumed
356-
it("Internally Mints LP correctly for the underlying max", async function () {
357-
await resetPool();
358-
const oneThousand = ethers.utils.parseUnits("1000", 18);
359-
// Set the current total supply to 1000 lp tokens
360-
await mineTx(poolContract.setLPBalance(accounts[0].address, oneThousand));
361-
// We want a min of 250 underlying and 250 bond
362308
const fiveHundred = ethers.utils.parseUnits("500", 18);
363309
const twoFifty = fiveHundred.div(2);
364310
const result = await mineTx(
365311
poolContract.burnLP(
366-
twoFifty,
367-
twoFifty,
312+
fiveHundred,
368313
[oneThousand, fiveHundred],
369314
accounts[0].address
370315
)
@@ -603,11 +548,18 @@ describe("ConvergentCurvePool", function () {
603548
SECONDS_IN_YEAR,
604549
testVault.address,
605550
ethers.utils.parseEther("0.05"),
606-
elementAddress,
551+
balancerSigner.address,
607552
`Element ${baseAssetSymbol} - fy${baseAssetSymbol}`,
608553
`${baseAssetSymbol}-fy${baseAssetSymbol}`
609554
);
610555

556+
beforeEach(async () => {
557+
await createSnapshot(provider);
558+
});
559+
afterEach(async () => {
560+
await restoreSnapshot(provider);
561+
});
562+
611563
aliasedVault = TestConvergentCurvePool__factory.connect(
612564
testVault.address,
613565
tokenSigner
@@ -650,9 +602,7 @@ describe("ConvergentCurvePool", function () {
650602
);
651603
// Check the returned fees
652604
expect(data[1][0]).to.be.eq(ethers.utils.parseUnits("1", BASE_DECIMALS));
653-
expect(data[1][1]).to.be.eq(
654-
ethers.utils.parseUnits("0.5", BOND_DECIMALS)
655-
);
605+
expect(data[1][1]).to.be.eq(ethers.utils.parseUnits("1", BOND_DECIMALS));
656606
// We run the call but state changing
657607
await aliasedVault.onJoinPool(
658608
poolId,
@@ -666,9 +616,12 @@ describe("ConvergentCurvePool", function () {
666616
);
667617
// We check the state
668618
expect(await poolContract.feesUnderlying()).to.be.eq(0);
669-
expect(await poolContract.feesBond()).to.be.eq(
670-
ethers.utils.parseEther("5")
619+
expect(await poolContract.feesBond()).to.be.eq(0);
620+
// Note swap fee = 0.05 implies 1/20 as ratio
621+
expect(await poolContract.governanceFeesUnderlying()).to.be.eq(
622+
ten.div(20)
671623
);
624+
expect(await poolContract.governanceFeesBond()).to.be.eq(ten.div(20));
672625
// We run another trade to ensure fees are not charged when no lp
673626
// is minted
674627
data = await aliasedVault.callStatic.onJoinPool(
@@ -685,6 +638,55 @@ describe("ConvergentCurvePool", function () {
685638
expect(data[1][0]).to.be.eq(0);
686639
expect(data[1][1]).to.be.eq(0);
687640
});
641+
it("Allows the governance to collect realized fees", async () => {
642+
const poolId = await poolContract.getPoolId();
643+
// First create some pretend fees
644+
const ten = ethers.utils.parseUnits("10", 18);
645+
// Mint some lp to avoid init case
646+
await poolContract.setLPBalance(tokenSigner.address, 1);
647+
// We set the accumulated fees
648+
await poolContract.setFees(ten, ten);
649+
650+
const bondFirst = BigNumber.from(bondAssetContract.address).lt(
651+
BigNumber.from(baseAssetContract.address)
652+
);
653+
const bondIndex = bondFirst ? 0 : 1;
654+
const baseIndex = bondFirst ? 1 : 0;
655+
const reserves: BigNumberish[] = [0, 0];
656+
reserves[bondIndex] = ethers.utils.parseUnits("50", BOND_DECIMALS);
657+
reserves[baseIndex] = ethers.utils.parseUnits("100", BASE_DECIMALS);
658+
const lp_deposit: BigNumberish[] = [0, 0];
659+
lp_deposit[bondIndex] = ethers.utils.parseUnits("5", BOND_DECIMALS);
660+
lp_deposit[baseIndex] = ethers.utils.parseUnits("10", BASE_DECIMALS);
661+
// This call changes the state
662+
await aliasedVault.onJoinPool(
663+
poolId,
664+
fakeAddress,
665+
tokenSigner.address,
666+
// Pool reserves are [100, 50]
667+
reserves,
668+
0,
669+
ethers.utils.parseEther("0.1"),
670+
ethers.utils.defaultAbiCoder.encode(["uint256[]"], [lp_deposit])
671+
);
672+
// now we simulate a withdraw to see what the return values are
673+
const data = await aliasedVault.callStatic.onExitPool(
674+
poolId,
675+
await poolContract.governance(),
676+
fakeAddress,
677+
reserves,
678+
0,
679+
ethers.utils.parseEther("0.1"),
680+
ethers.utils.defaultAbiCoder.encode(["uint256"], [0])
681+
);
682+
// we check that the amounts out are the whole fees
683+
expect(data[0][bondIndex]).to.be.eq(
684+
ethers.utils.parseUnits("0.5", BOND_DECIMALS)
685+
);
686+
expect(data[0][baseIndex]).to.be.eq(
687+
ethers.utils.parseUnits("0.5", BASE_DECIMALS)
688+
);
689+
});
688690
it("Blocks invalid vault calls", async () => {
689691
const poolId = await poolContract.getPoolId();
690692
// First create some pretend fees
@@ -819,7 +821,8 @@ describe("ConvergentCurvePool", function () {
819821
SECONDS_IN_YEAR,
820822
1,
821823
"fake pool",
822-
"FP"
824+
"FP",
825+
elementSigner.address
823826
);
824827
});
825828
it("Allows changing fees", async () => {
@@ -840,4 +843,58 @@ describe("ConvergentCurvePool", function () {
840843
await expect(tx).to.be.revertedWith("Sender not owner");
841844
});
842845
});
846+
847+
describe("Pause function", async () => {
848+
beforeEach(async () => {
849+
createSnapshot(provider);
850+
});
851+
afterEach(async () => {
852+
restoreSnapshot(provider);
853+
});
854+
it("Only lets gov set pause status", async () => {
855+
await poolContract.setPauser(balancerSigner.address, true);
856+
const tx = poolContract
857+
.connect(balancerSigner)
858+
.setPauser(balancerSigner.address, true);
859+
await expect(tx).to.be.revertedWith("Sender not Owner");
860+
});
861+
it("Only let's pausers pause", async () => {
862+
await poolContract.pause(true);
863+
const tx = poolContract.connect(balancerSigner).pause(false);
864+
await expect(tx).to.be.revertedWith("Sender not Authorized");
865+
});
866+
it("Blocks trades and deposits on a paused pool", async () => {
867+
await poolContract.pause(true);
868+
869+
let tx = poolContract.onJoinPool(
870+
"0xb6749d30a0b09b310151e2cd2db8f72dd34aab4bbc60cf3e8dbca13b4d9369ad",
871+
fakeAddress,
872+
tokenSigner.address,
873+
// Pool reserves are [100, 50]
874+
[0, 0],
875+
0,
876+
ethers.utils.parseEther("0.1"),
877+
"0x"
878+
);
879+
await expect(tx).to.be.revertedWith("Paused");
880+
tx = poolContract.onSwap(
881+
{
882+
tokenIn: baseAssetContract.address,
883+
tokenOut: bondAssetContract.address,
884+
amount: ethers.utils.parseUnits("100", BASE_DECIMALS),
885+
kind: inForOutType,
886+
// Misc data
887+
poolId:
888+
"0xf4cc12715b126dabd383d98cfad15b0b6c3814ad57c5b9e22d941b5fcd3e4e43",
889+
lastChangeBlock: BigNumber.from(0),
890+
from: fakeAddress,
891+
to: fakeAddress,
892+
userData: "0x",
893+
},
894+
reserveUnderlying,
895+
reserveBond
896+
);
897+
await expect(tx).to.be.revertedWith("Paused");
898+
});
899+
});
843900
});

0 commit comments

Comments
 (0)