Skip to content

Commit

Permalink
Fixing #58 and #59 (#60)
Browse files Browse the repository at this point in the history
* Disable balancer trading during pool entry/exit

* Add test for modifier

* Only able to poke weights once after finished

* Cancel weight adjustment when removing token

* Cancel weight adjustment when adding token

* Refactor current tests

* Cancel weight adjustment for bind/unbind/rebind

* Testing if weight adjustment still works after revert

* Cancel weight adjustment when calling updateWeight
  • Loading branch information
Evert0x authored Sep 8, 2020
1 parent 2410c82 commit 60ddc57
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 13 deletions.
7 changes: 7 additions & 0 deletions contracts/libraries/LibAddRemoveToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ library LibAddRemoveToken {
"ERR_ERC20_FALSE"
);

// Cancel potential weight adjustment process.
ws.startBlock = 0;

// Approves bPool to pull from this controller
IERC20(ws.newToken.addr).safeApprove(address(s.bPool), uint256(-1));
s.bPool.bind(ws.newToken.addr, ws.newToken.balance, ws.newToken.denorm);
Expand Down Expand Up @@ -61,6 +64,7 @@ library LibAddRemoveToken {
}

function removeToken(address _token) external {
P2Storage.StorageStruct storage ws = P2Storage.load();
PBStorage.StorageStruct storage s = PBStorage.load();

uint256 totalSupply = PCStorage.load().totalSupply;
Expand All @@ -74,6 +78,9 @@ library LibAddRemoveToken {
// Have to get it before unbinding
uint256 balance = s.bPool.getBalance(_token);

// Cancel potential weight adjustment process.
ws.startBlock = 0;

// Unbind and get the tokens out of balancer pool
s.bPool.unbind(_token);

Expand Down
30 changes: 23 additions & 7 deletions contracts/libraries/LibPoolEntryExit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ library LibPoolEntryExit {
event PoolExitedWithLoss(address indexed from, uint256 amount, address[] lossTokens);
event PoolJoined(address indexed from, uint256 amount);

modifier lockBPoolSwap() {
IBPool bPool = PBStorage.load().bPool;
if(bPool.isPublicSwap()) {
// If public swap is enabled turn it of, execute function and turn it off again
bPool.setPublicSwap(false);
_;
bPool.setPublicSwap(true);
} else {
// If public swap is not enabled just execute
_;
}
}

function exitPool(uint256 _amount) internal {
IBPool bPool = PBStorage.load().bPool;
uint256[] memory minAmountsOut = new uint256[](bPool.getCurrentTokens().length);
Expand All @@ -27,7 +40,7 @@ library LibPoolEntryExit {
_exitPool(_amount, _minAmountsOut);
}

function _exitPool(uint256 _amount, uint256[] memory _minAmountsOut) internal {
function _exitPool(uint256 _amount, uint256[] memory _minAmountsOut) internal lockBPoolSwap {
IBPool bPool = PBStorage.load().bPool;
LibFees.chargeOutstandingAnnualFee();
uint256 poolTotal = PCStorage.load().totalSupply;
Expand Down Expand Up @@ -58,7 +71,7 @@ library LibPoolEntryExit {
address _token,
uint256 _poolAmountIn,
uint256 _minAmountOut
) external returns (uint256 tokenAmountOut) {
) external lockBPoolSwap returns (uint256 tokenAmountOut) {
IBPool bPool = PBStorage.load().bPool;
LibFees.chargeOutstandingAnnualFee();
require(bPool.isBound(_token), "LibPoolEntryExit.exitswapPoolAmountIn: Token Not Bound");
Expand Down Expand Up @@ -93,7 +106,7 @@ library LibPoolEntryExit {
address _token,
uint256 _tokenAmountOut,
uint256 _maxPoolAmountIn
) external returns (uint256 poolAmountIn) {
) external lockBPoolSwap returns (uint256 poolAmountIn) {
IBPool bPool = PBStorage.load().bPool;
LibFees.chargeOutstandingAnnualFee();
require(bPool.isBound(_token), "LibPoolEntryExit.exitswapExternAmountOut: Token Not Bound");
Expand Down Expand Up @@ -124,7 +137,10 @@ library LibPoolEntryExit {
return poolAmountIn;
}

function exitPoolTakingloss(uint256 _amount, address[] calldata _lossTokens) external {
function exitPoolTakingloss(uint256 _amount, address[] calldata _lossTokens)
external
lockBPoolSwap
{
IBPool bPool = PBStorage.load().bPool;
LibFees.chargeOutstandingAnnualFee();
uint256 poolTotal = PCStorage.load().totalSupply;
Expand Down Expand Up @@ -177,7 +193,7 @@ library LibPoolEntryExit {
_joinPool(_amount, _maxAmountsIn);
}

function _joinPool(uint256 _amount, uint256[] memory _maxAmountsIn) internal {
function _joinPool(uint256 _amount, uint256[] memory _maxAmountsIn) internal lockBPoolSwap {
IBPool bPool = PBStorage.load().bPool;
LibFees.chargeOutstandingAnnualFee();
uint256 poolTotal = PCStorage.load().totalSupply;
Expand Down Expand Up @@ -205,7 +221,7 @@ library LibPoolEntryExit {
address _token,
uint256 _amountIn,
uint256 _minPoolAmountOut
) external returns (uint256 poolAmountOut) {
) external lockBPoolSwap returns (uint256 poolAmountOut) {
IBPool bPool = PBStorage.load().bPool;
LibFees.chargeOutstandingAnnualFee();
require(bPool.isBound(_token), "LibPoolEntryExit.joinswapExternAmountIn: Token Not Bound");
Expand Down Expand Up @@ -240,7 +256,7 @@ library LibPoolEntryExit {
address _token,
uint256 _amountOut,
uint256 _maxAmountIn
) external returns (uint256 tokenAmountIn) {
) external lockBPoolSwap returns (uint256 tokenAmountIn) {
IBPool bPool = PBStorage.load().bPool;
LibFees.chargeOutstandingAnnualFee();
require(bPool.isBound(_token), "LibPoolEntryExit.joinswapPoolAmountOut: Token Not Bound");
Expand Down
16 changes: 16 additions & 0 deletions contracts/libraries/LibWeights.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ library LibWeights {

function updateWeight(address _token, uint256 _newWeight) external {
PBStorage.StorageStruct storage s = PBStorage.load();
P2Storage.StorageStruct storage ws = P2Storage.load();

require(_newWeight >= constants.MIN_WEIGHT, "ERR_MIN_WEIGHT");
require(_newWeight <= constants.MAX_WEIGHT, "ERR_MAX_WEIGHT");
Expand Down Expand Up @@ -45,6 +46,9 @@ library LibWeights {
// Now with the tokens this contract can send them to msg.sender
require(IERC20(_token).transfer(msg.sender, deltaBalance), "ERR_ERC20_FALSE");

// Cancel potential weight adjustment process.
ws.startBlock = 0;

LibPoolToken._burn(msg.sender, poolShares);
} else {
// This means the controller will deposit tokens to keep the price.
Expand All @@ -66,6 +70,9 @@ library LibWeights {
// Now with the tokens this contract can bind them to the pool it controls
s.bPool.rebind(_token, currentBalance.badd(deltaBalance), _newWeight);

// Cancel potential weight adjustment process.
ws.startBlock = 0;

LibPoolToken._mint(msg.sender, poolShares);
}
}
Expand Down Expand Up @@ -114,6 +121,7 @@ library LibWeights {
PBStorage.StorageStruct storage s = PBStorage.load();
P2Storage.StorageStruct storage ws = P2Storage.load();

require(ws.startBlock != 0, "ERR_WEIGHT_ADJUSTMENT_FINISHED");
require(block.number >= ws.startBlock, "ERR_CANT_POKE_YET");

// This allows for pokes after endBlock that get weights to endWeights
Expand Down Expand Up @@ -142,5 +150,13 @@ library LibWeights {
}
s.bPool.rebind(tokens[i], s.bPool.getBalance(tokens[i]), newWeight);
}

if(minBetweenEndBlockAndThisBlock == ws.endBlock) {
// All the weights are adjusted, adjustment finished.

// save gas option: set this to max number instead of 0
// And be able to remove ERR_WEIGHT_ADJUSTMENT_FINISHED check
ws.startBlock = 0;
}
}
}
19 changes: 14 additions & 5 deletions contracts/smart-pools/PV2SmartPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ contract PV2SmartPool is IPV2SmartPool, PCToken, ReentryProtection {
}

/**
@notice Takes underlying assets and mints smart pool tokens.
@notice Takes underlying assets and mints smart pool tokens.
Enforces the cap. Allows you to specify the maximum amounts of underlying assets
@param _amount Amount of pool tokens to mint
*/
Expand Down Expand Up @@ -308,12 +308,15 @@ contract PV2SmartPool is IPV2SmartPool, PCToken, ReentryProtection {
uint256 _balance,
uint256 _denorm
) external override onlyTokenBinder noReentry {
P2Storage.StorageStruct storage ws = P2Storage.load();
IBPool bPool = PBStorage.load().bPool;
IERC20 token = IERC20(_token);
require(
token.transferFrom(msg.sender, address(this), _balance),
"PV2SmartPool.bind: transferFrom failed"
);
// Cancel potential weight adjustment process.
ws.startBlock = 0;
token.safeApprove(address(bPool), uint256(-1));
bPool.bind(_token, _balance, _denorm);
}
Expand All @@ -329,6 +332,7 @@ contract PV2SmartPool is IPV2SmartPool, PCToken, ReentryProtection {
uint256 _balance,
uint256 _denorm
) external override onlyTokenBinder noReentry {
P2Storage.StorageStruct storage ws = P2Storage.load();
IBPool bPool = PBStorage.load().bPool;
IERC20 token = IERC20(_token);

Expand All @@ -346,7 +350,8 @@ contract PV2SmartPool is IPV2SmartPool, PCToken, ReentryProtection {
}

bPool.rebind(_token, _balance, _denorm);

// Cancel potential weight adjustment process.
ws.startBlock = 0;
// If any tokens are in this contract send them to msg.sender
uint256 tokenBalance = token.balanceOf(address(this));
if (tokenBalance > 0) {
Expand All @@ -359,11 +364,15 @@ contract PV2SmartPool is IPV2SmartPool, PCToken, ReentryProtection {
@param _token Token to unbind
*/
function unbind(address _token) external override onlyTokenBinder noReentry {
P2Storage.StorageStruct storage ws = P2Storage.load();
IBPool bPool = PBStorage.load().bPool;
IERC20 token = IERC20(_token);
// unbind the token in the bPool
bPool.unbind(_token);

// Cancel potential weight adjustment process.
ws.startBlock = 0;

// If any tokens are in this contract send them to msg.sender
uint256 tokenBalance = token.balanceOf(address(this));
if (tokenBalance > 0) {
Expand Down Expand Up @@ -499,7 +508,7 @@ contract PV2SmartPool is IPV2SmartPool, PCToken, ReentryProtection {
LibWeights.updateWeight(_token, _newWeight);
}

/**
/**
@notice Gradually adjust the weights of a token. Can only be called by the controller
@param _newWeights Target weights
@param _startBlock Block to start weight adjustment
Expand Down Expand Up @@ -527,7 +536,7 @@ contract PV2SmartPool is IPV2SmartPool, PCToken, ReentryProtection {
LibAddRemoveToken.applyAddToken();
}

/**
/**
@notice Commit a token to be added. Can only be called by the controller
@param _token Address of the token to add
@param _balance Amount of token to add
Expand Down Expand Up @@ -626,7 +635,7 @@ contract PV2SmartPool is IPV2SmartPool, PCToken, ReentryProtection {
return LibPoolMath.calcPoolInGivenSingleOut(_token, _amount);
}

/**
/**
@notice Get the current tokens in the smart pool
@return Addresses of the tokens in the smart pool
*/
Expand Down
116 changes: 115 additions & 1 deletion test/advancedPoolFunctionality.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ describe("Advanced Pool Functionality", function () {
let smartpool: Pv2SmartPool;
let startBlock: number;
let endBlock: number;
let tokenFactory : MockTokenFactory

beforeEach(async () => {
signers = await ethers.signers();
Expand All @@ -40,7 +41,7 @@ describe("Advanced Pool Functionality", function () {

pool = IbPoolFactory.connect(await deployBalancerPool(signers[0]), signers[0]);

const tokenFactory = new MockTokenFactory(signers[0]);
tokenFactory = new MockTokenFactory(signers[0]);
tokens = [];

for (let i = 0; i < 8; i++) {
Expand Down Expand Up @@ -368,6 +369,16 @@ describe("Advanced Pool Functionality", function () {
expect(weightsAfter).to.eql(weigthsFixturePokeWeightsUp, "Weight increase incorrect");
});

it("Poking the weight twice after the end block should fail", async () => {
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
await mine_blocks(5);
await smartpool.pokeWeights();
await mine_blocks(200);
await smartpool.pokeWeights();
await expect(smartpool.pokeWeights()).to.be.revertedWith("ERR_WEIGHT_ADJUSTMENT_FINISHED");
});

describe("Adding tokens", async () => {
let newToken: MockToken;

Expand All @@ -377,6 +388,109 @@ describe("Advanced Pool Functionality", function () {
newToken = tokens[tokens.length - 1];
});

it("Weight update should cancel when removing token", async () => {
// verify there is no current adjustment going on
await expect(smartpool.pokeWeights()).to.be.revertedWith("ERR_WEIGHT_ADJUSTMENT_FINISHED");
// start adjustment
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
// remove a token
await smartpool.removeToken(tokens[tokens.length - 2].address);
await expect(smartpool.pokeWeights()).to.be.revertedWith("ERR_WEIGHT_ADJUSTMENT_FINISHED");
// weight adjustment should still work
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
});

it("Weight update should cancel when adding token", async () => {
// start adjustment
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
// add new token
const balance = constants.WeiPerEther.mul(100);
const weight = constants.WeiPerEther.mul(2);
await smartpool.commitAddToken(newToken.address, balance, weight);
await smartpool.pokeWeights();
await smartpool.applyAddToken();

// throws 'VM Exception while processing transaction: invalid opcode' @ f4aab193
await expect(smartpool.pokeWeights()).to.be.revertedWith("ERR_WEIGHT_ADJUSTMENT_FINISHED");
// weight adjustment should still work
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
});

it("Weight update should cancel when calling bind", async () => {
// start adjustment
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
// binding a token
const mintAmount = constants.WeiPerEther.mul(1000000);
const token: MockToken = await tokenFactory.deploy("Mock", "M", 18);
await token.mint(account, mintAmount);
await token.approve(smartpool.address, constants.MaxUint256);
await smartpool.bind(token.address, constants.WeiPerEther, constants.WeiPerEther);

await expect(smartpool.pokeWeights()).to.be.revertedWith("ERR_WEIGHT_ADJUSTMENT_FINISHED");
// weight adjustment should still work
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
});

it("Weight update should cancel when calling unbind", async () => {
// start adjustment
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
// unbinding a token
smartpool.unbind(tokens[0].address);
await expect(smartpool.pokeWeights()).to.be.revertedWith("ERR_WEIGHT_ADJUSTMENT_FINISHED");
// weight adjustment should still work
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
});

it("Weight update should cancel when calling rebind", async () => {
// start adjustment
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
// rebinding a token
await smartpool.rebind(
tokens[0].address,
constants.WeiPerEther.mul(2),
constants.WeiPerEther.mul(2)
);
await expect(smartpool.pokeWeights()).to.be.revertedWith("ERR_WEIGHT_ADJUSTMENT_FINISHED");
// weight adjustment should still work
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
});

it("Weight update should cancel when calling updateWeight (down)", async () => {
// start adjustment
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
// updating weight down
const weightsBefore = await smartpool.getDenormalizedWeights();
await smartpool.updateWeight(tokens[0].address, weightsBefore[0].div(2))
await expect(smartpool.pokeWeights()).to.be.revertedWith("ERR_WEIGHT_ADJUSTMENT_FINISHED");
// weight adjustment should still work
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
});

it("Weight update should cancel when calling updateWeight (up)", async () => {
// start adjustment
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
// updating weight up
const weightsBefore = await smartpool.getDenormalizedWeights();
await smartpool.updateWeight(tokens[0].address, weightsBefore[0].mul(2))
await expect(smartpool.pokeWeights()).to.be.revertedWith("ERR_WEIGHT_ADJUSTMENT_FINISHED");
// weight adjustment should still work
await smartpool.updateWeightsGradually(weigthsFixturePokeWeightsUp, startBlock, endBlock);
await smartpool.pokeWeights();
});

it("commitAddToken should work", async () => {
const balance = constants.WeiPerEther.mul(100);
const weight = constants.WeiPerEther.mul(2);
Expand Down
Loading

0 comments on commit 60ddc57

Please sign in to comment.