ERC4626.test.js 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109
  1. const { constants, expectEvent, expectRevert } = require('@openzeppelin/test-helpers');
  2. const { expect } = require('chai');
  3. const { Enum } = require('../../../helpers/enums');
  4. const { expectRevertCustomError } = require('../../../helpers/customError');
  5. const ERC20Decimals = artifacts.require('$ERC20DecimalsMock');
  6. const ERC4626 = artifacts.require('$ERC4626');
  7. const ERC4626LimitsMock = artifacts.require('$ERC4626LimitsMock');
  8. const ERC4626OffsetMock = artifacts.require('$ERC4626OffsetMock');
  9. const ERC4626FeesMock = artifacts.require('$ERC4626FeesMock');
  10. const ERC20ExcessDecimalsMock = artifacts.require('ERC20ExcessDecimalsMock');
  11. const ERC20Reentrant = artifacts.require('$ERC20Reentrant');
  12. contract('ERC4626', function (accounts) {
  13. const [holder, recipient, spender, other, user1, user2] = accounts;
  14. const name = 'My Token';
  15. const symbol = 'MTKN';
  16. const decimals = web3.utils.toBN(18);
  17. it('inherit decimals if from asset', async function () {
  18. for (const decimals of [0, 9, 12, 18, 36].map(web3.utils.toBN)) {
  19. const token = await ERC20Decimals.new('', '', decimals);
  20. const vault = await ERC4626.new('', '', token.address);
  21. expect(await vault.decimals()).to.be.bignumber.equal(decimals);
  22. }
  23. });
  24. it('asset has not yet been created', async function () {
  25. const vault = await ERC4626.new('', '', other);
  26. expect(await vault.decimals()).to.be.bignumber.equal(decimals);
  27. });
  28. it('underlying excess decimals', async function () {
  29. const token = await ERC20ExcessDecimalsMock.new();
  30. const vault = await ERC4626.new('', '', token.address);
  31. expect(await vault.decimals()).to.be.bignumber.equal(decimals);
  32. });
  33. it('decimals overflow', async function () {
  34. for (const offset of [243, 250, 255].map(web3.utils.toBN)) {
  35. const token = await ERC20Decimals.new('', '', decimals);
  36. const vault = await ERC4626OffsetMock.new(name + ' Vault', symbol + 'V', token.address, offset);
  37. await expectRevert(
  38. vault.decimals(),
  39. 'reverted with panic code 0x11 (Arithmetic operation underflowed or overflowed outside of an unchecked block)',
  40. );
  41. }
  42. });
  43. describe('reentrancy', async function () {
  44. const reenterType = Enum('No', 'Before', 'After');
  45. const value = web3.utils.toBN(1000000000000000000);
  46. const reenterValue = web3.utils.toBN(1000000000);
  47. let token;
  48. let vault;
  49. beforeEach(async function () {
  50. token = await ERC20Reentrant.new();
  51. // Use offset 1 so the rate is not 1:1 and we can't possibly confuse assets and shares
  52. vault = await ERC4626OffsetMock.new('', '', token.address, 1);
  53. // Funds and approval for tests
  54. await token.$_mint(holder, value);
  55. await token.$_mint(other, value);
  56. await token.$_approve(holder, vault.address, constants.MAX_UINT256);
  57. await token.$_approve(other, vault.address, constants.MAX_UINT256);
  58. await token.$_approve(token.address, vault.address, constants.MAX_UINT256);
  59. });
  60. // During a `_deposit`, the vault does `transferFrom(depositor, vault, assets)` -> `_mint(receiver, shares)`
  61. // such that a reentrancy BEFORE the transfer guarantees the price is kept the same.
  62. // If the order of transfer -> mint is changed to mint -> transfer, the reentrancy could be triggered on an
  63. // intermediate state in which the ratio of assets/shares has been decreased (more shares than assets).
  64. it('correct share price is observed during reentrancy before deposit', async function () {
  65. // mint token for deposit
  66. await token.$_mint(token.address, reenterValue);
  67. // Schedules a reentrancy from the token contract
  68. await token.scheduleReenter(
  69. reenterType.Before,
  70. vault.address,
  71. vault.contract.methods.deposit(reenterValue, holder).encodeABI(),
  72. );
  73. // Initial share price
  74. const sharesForDeposit = await vault.previewDeposit(value, { from: holder });
  75. const sharesForReenter = await vault.previewDeposit(reenterValue, { from: holder });
  76. // Do deposit normally, triggering the _beforeTokenTransfer hook
  77. const receipt = await vault.deposit(value, holder, { from: holder });
  78. // Main deposit event
  79. await expectEvent(receipt, 'Deposit', {
  80. sender: holder,
  81. owner: holder,
  82. assets: value,
  83. shares: sharesForDeposit,
  84. });
  85. // Reentrant deposit event → uses the same price
  86. await expectEvent(receipt, 'Deposit', {
  87. sender: token.address,
  88. owner: holder,
  89. assets: reenterValue,
  90. shares: sharesForReenter,
  91. });
  92. // Assert prices is kept
  93. const sharesAfter = await vault.previewDeposit(value, { from: holder });
  94. expect(sharesForDeposit).to.be.bignumber.eq(sharesAfter);
  95. });
  96. // During a `_withdraw`, the vault does `_burn(owner, shares)` -> `transfer(receiver, assets)`
  97. // such that a reentrancy AFTER the transfer guarantees the price is kept the same.
  98. // If the order of burn -> transfer is changed to transfer -> burn, the reentrancy could be triggered on an
  99. // intermediate state in which the ratio of shares/assets has been decreased (more assets than shares).
  100. it('correct share price is observed during reentrancy after withdraw', async function () {
  101. // Deposit into the vault: holder gets `value` share, token.address gets `reenterValue` shares
  102. await vault.deposit(value, holder, { from: holder });
  103. await vault.deposit(reenterValue, token.address, { from: other });
  104. // Schedules a reentrancy from the token contract
  105. await token.scheduleReenter(
  106. reenterType.After,
  107. vault.address,
  108. vault.contract.methods.withdraw(reenterValue, holder, token.address).encodeABI(),
  109. );
  110. // Initial share price
  111. const sharesForWithdraw = await vault.previewWithdraw(value, { from: holder });
  112. const sharesForReenter = await vault.previewWithdraw(reenterValue, { from: holder });
  113. // Do withdraw normally, triggering the _afterTokenTransfer hook
  114. const receipt = await vault.withdraw(value, holder, holder, { from: holder });
  115. // Main withdraw event
  116. await expectEvent(receipt, 'Withdraw', {
  117. sender: holder,
  118. receiver: holder,
  119. owner: holder,
  120. assets: value,
  121. shares: sharesForWithdraw,
  122. });
  123. // Reentrant withdraw event → uses the same price
  124. await expectEvent(receipt, 'Withdraw', {
  125. sender: token.address,
  126. receiver: holder,
  127. owner: token.address,
  128. assets: reenterValue,
  129. shares: sharesForReenter,
  130. });
  131. // Assert price is kept
  132. const sharesAfter = await vault.previewWithdraw(value, { from: holder });
  133. expect(sharesForWithdraw).to.be.bignumber.eq(sharesAfter);
  134. });
  135. // Donate newly minted tokens to the vault during the reentracy causes the share price to increase.
  136. // Still, the deposit that trigger the reentracy is not affected and get the previewed price.
  137. // Further deposits will get a different price (getting fewer shares for the same value of assets)
  138. it('share price change during reentracy does not affect deposit', async function () {
  139. // Schedules a reentrancy from the token contract that mess up the share price
  140. await token.scheduleReenter(
  141. reenterType.Before,
  142. token.address,
  143. token.contract.methods.$_mint(vault.address, reenterValue).encodeABI(),
  144. );
  145. // Price before
  146. const sharesBefore = await vault.previewDeposit(value);
  147. // Deposit, triggering the _beforeTokenTransfer hook
  148. const receipt = await vault.deposit(value, holder, { from: holder });
  149. // Price is as previewed
  150. await expectEvent(receipt, 'Deposit', {
  151. sender: holder,
  152. owner: holder,
  153. assets: value,
  154. shares: sharesBefore,
  155. });
  156. // Price was modified during reentrancy
  157. const sharesAfter = await vault.previewDeposit(value);
  158. expect(sharesAfter).to.be.bignumber.lt(sharesBefore);
  159. });
  160. // Burn some tokens from the vault during the reentracy causes the share price to drop.
  161. // Still, the withdraw that trigger the reentracy is not affected and get the previewed price.
  162. // Further withdraw will get a different price (needing more shares for the same value of assets)
  163. it('share price change during reentracy does not affect withdraw', async function () {
  164. await vault.deposit(value, other, { from: other });
  165. await vault.deposit(value, holder, { from: holder });
  166. // Schedules a reentrancy from the token contract that mess up the share price
  167. await token.scheduleReenter(
  168. reenterType.After,
  169. token.address,
  170. token.contract.methods.$_burn(vault.address, reenterValue).encodeABI(),
  171. );
  172. // Price before
  173. const sharesBefore = await vault.previewWithdraw(value);
  174. // Withdraw, triggering the _afterTokenTransfer hook
  175. const receipt = await vault.withdraw(value, holder, holder, { from: holder });
  176. // Price is as previewed
  177. await expectEvent(receipt, 'Withdraw', {
  178. sender: holder,
  179. receiver: holder,
  180. owner: holder,
  181. assets: value,
  182. shares: sharesBefore,
  183. });
  184. // Price was modified during reentrancy
  185. const sharesAfter = await vault.previewWithdraw(value);
  186. expect(sharesAfter).to.be.bignumber.gt(sharesBefore);
  187. });
  188. });
  189. describe('limits', async function () {
  190. beforeEach(async function () {
  191. this.token = await ERC20Decimals.new(name, symbol, decimals);
  192. this.vault = await ERC4626LimitsMock.new(name + ' Vault', symbol + 'V', this.token.address);
  193. });
  194. it('reverts on deposit() above max deposit', async function () {
  195. const maxDeposit = await this.vault.maxDeposit(holder);
  196. await expectRevertCustomError(this.vault.deposit(maxDeposit.addn(1), recipient), 'ERC4626ExceededMaxDeposit', [
  197. recipient,
  198. maxDeposit.addn(1),
  199. maxDeposit,
  200. ]);
  201. });
  202. it('reverts on mint() above max mint', async function () {
  203. const maxMint = await this.vault.maxMint(holder);
  204. await expectRevertCustomError(this.vault.mint(maxMint.addn(1), recipient), 'ERC4626ExceededMaxMint', [
  205. recipient,
  206. maxMint.addn(1),
  207. maxMint,
  208. ]);
  209. });
  210. it('reverts on withdraw() above max withdraw', async function () {
  211. const maxWithdraw = await this.vault.maxWithdraw(holder);
  212. await expectRevertCustomError(
  213. this.vault.withdraw(maxWithdraw.addn(1), recipient, holder),
  214. 'ERC4626ExceededMaxWithdraw',
  215. [holder, maxWithdraw.addn(1), maxWithdraw],
  216. );
  217. });
  218. it('reverts on redeem() above max redeem', async function () {
  219. const maxRedeem = await this.vault.maxRedeem(holder);
  220. await expectRevertCustomError(
  221. this.vault.redeem(maxRedeem.addn(1), recipient, holder),
  222. 'ERC4626ExceededMaxRedeem',
  223. [holder, maxRedeem.addn(1), maxRedeem],
  224. );
  225. });
  226. });
  227. for (const offset of [0, 6, 18].map(web3.utils.toBN)) {
  228. const parseToken = token => web3.utils.toBN(10).pow(decimals).muln(token);
  229. const parseShare = share => web3.utils.toBN(10).pow(decimals.add(offset)).muln(share);
  230. const virtualAssets = web3.utils.toBN(1);
  231. const virtualShares = web3.utils.toBN(10).pow(offset);
  232. describe(`offset: ${offset}`, function () {
  233. beforeEach(async function () {
  234. this.token = await ERC20Decimals.new(name, symbol, decimals);
  235. this.vault = await ERC4626OffsetMock.new(name + ' Vault', symbol + 'V', this.token.address, offset);
  236. await this.token.$_mint(holder, constants.MAX_INT256); // 50% of maximum
  237. await this.token.approve(this.vault.address, constants.MAX_UINT256, { from: holder });
  238. await this.vault.approve(spender, constants.MAX_UINT256, { from: holder });
  239. });
  240. it('metadata', async function () {
  241. expect(await this.vault.name()).to.be.equal(name + ' Vault');
  242. expect(await this.vault.symbol()).to.be.equal(symbol + 'V');
  243. expect(await this.vault.decimals()).to.be.bignumber.equal(decimals.add(offset));
  244. expect(await this.vault.asset()).to.be.equal(this.token.address);
  245. });
  246. describe('empty vault: no assets & no shares', function () {
  247. it('status', async function () {
  248. expect(await this.vault.totalAssets()).to.be.bignumber.equal('0');
  249. });
  250. it('deposit', async function () {
  251. expect(await this.vault.maxDeposit(holder)).to.be.bignumber.equal(constants.MAX_UINT256);
  252. expect(await this.vault.previewDeposit(parseToken(1))).to.be.bignumber.equal(parseShare(1));
  253. const { tx } = await this.vault.deposit(parseToken(1), recipient, { from: holder });
  254. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  255. from: holder,
  256. to: this.vault.address,
  257. value: parseToken(1),
  258. });
  259. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  260. from: constants.ZERO_ADDRESS,
  261. to: recipient,
  262. value: parseShare(1),
  263. });
  264. await expectEvent.inTransaction(tx, this.vault, 'Deposit', {
  265. sender: holder,
  266. owner: recipient,
  267. assets: parseToken(1),
  268. shares: parseShare(1),
  269. });
  270. });
  271. it('mint', async function () {
  272. expect(await this.vault.maxMint(holder)).to.be.bignumber.equal(constants.MAX_UINT256);
  273. expect(await this.vault.previewMint(parseShare(1))).to.be.bignumber.equal(parseToken(1));
  274. const { tx } = await this.vault.mint(parseShare(1), recipient, { from: holder });
  275. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  276. from: holder,
  277. to: this.vault.address,
  278. value: parseToken(1),
  279. });
  280. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  281. from: constants.ZERO_ADDRESS,
  282. to: recipient,
  283. value: parseShare(1),
  284. });
  285. await expectEvent.inTransaction(tx, this.vault, 'Deposit', {
  286. sender: holder,
  287. owner: recipient,
  288. assets: parseToken(1),
  289. shares: parseShare(1),
  290. });
  291. });
  292. it('withdraw', async function () {
  293. expect(await this.vault.maxWithdraw(holder)).to.be.bignumber.equal('0');
  294. expect(await this.vault.previewWithdraw('0')).to.be.bignumber.equal('0');
  295. const { tx } = await this.vault.withdraw('0', recipient, holder, { from: holder });
  296. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  297. from: this.vault.address,
  298. to: recipient,
  299. value: '0',
  300. });
  301. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  302. from: holder,
  303. to: constants.ZERO_ADDRESS,
  304. value: '0',
  305. });
  306. await expectEvent.inTransaction(tx, this.vault, 'Withdraw', {
  307. sender: holder,
  308. receiver: recipient,
  309. owner: holder,
  310. assets: '0',
  311. shares: '0',
  312. });
  313. });
  314. it('redeem', async function () {
  315. expect(await this.vault.maxRedeem(holder)).to.be.bignumber.equal('0');
  316. expect(await this.vault.previewRedeem('0')).to.be.bignumber.equal('0');
  317. const { tx } = await this.vault.redeem('0', recipient, holder, { from: holder });
  318. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  319. from: this.vault.address,
  320. to: recipient,
  321. value: '0',
  322. });
  323. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  324. from: holder,
  325. to: constants.ZERO_ADDRESS,
  326. value: '0',
  327. });
  328. await expectEvent.inTransaction(tx, this.vault, 'Withdraw', {
  329. sender: holder,
  330. receiver: recipient,
  331. owner: holder,
  332. assets: '0',
  333. shares: '0',
  334. });
  335. });
  336. });
  337. describe('inflation attack: offset price by direct deposit of assets', function () {
  338. beforeEach(async function () {
  339. // Donate 1 token to the vault to offset the price
  340. await this.token.$_mint(this.vault.address, parseToken(1));
  341. });
  342. it('status', async function () {
  343. expect(await this.vault.totalSupply()).to.be.bignumber.equal('0');
  344. expect(await this.vault.totalAssets()).to.be.bignumber.equal(parseToken(1));
  345. });
  346. /**
  347. * | offset | deposited assets | redeemable assets |
  348. * |--------|----------------------|----------------------|
  349. * | 0 | 1.000000000000000000 | 0. |
  350. * | 6 | 1.000000000000000000 | 0.999999000000000000 |
  351. * | 18 | 1.000000000000000000 | 0.999999999999999999 |
  352. *
  353. * Attack is possible, but made difficult by the offset. For the attack to be successful
  354. * the attacker needs to frontrun a deposit 10**offset times bigger than what the victim
  355. * was trying to deposit
  356. */
  357. it('deposit', async function () {
  358. const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets));
  359. const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares));
  360. const depositAssets = parseToken(1);
  361. const expectedShares = depositAssets.mul(effectiveShares).div(effectiveAssets);
  362. expect(await this.vault.maxDeposit(holder)).to.be.bignumber.equal(constants.MAX_UINT256);
  363. expect(await this.vault.previewDeposit(depositAssets)).to.be.bignumber.equal(expectedShares);
  364. const { tx } = await this.vault.deposit(depositAssets, recipient, { from: holder });
  365. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  366. from: holder,
  367. to: this.vault.address,
  368. value: depositAssets,
  369. });
  370. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  371. from: constants.ZERO_ADDRESS,
  372. to: recipient,
  373. value: expectedShares,
  374. });
  375. await expectEvent.inTransaction(tx, this.vault, 'Deposit', {
  376. sender: holder,
  377. owner: recipient,
  378. assets: depositAssets,
  379. shares: expectedShares,
  380. });
  381. });
  382. /**
  383. * | offset | deposited assets | redeemable assets |
  384. * |--------|----------------------|----------------------|
  385. * | 0 | 1000000000000000001. | 1000000000000000001. |
  386. * | 6 | 1000000000000000001. | 1000000000000000001. |
  387. * | 18 | 1000000000000000001. | 1000000000000000001. |
  388. *
  389. * Using mint protects against inflation attack, but makes minting shares very expensive.
  390. * The ER20 allowance for the underlying asset is needed to protect the user from (too)
  391. * large deposits.
  392. */
  393. it('mint', async function () {
  394. const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets));
  395. const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares));
  396. const mintShares = parseShare(1);
  397. const expectedAssets = mintShares.mul(effectiveAssets).div(effectiveShares);
  398. expect(await this.vault.maxMint(holder)).to.be.bignumber.equal(constants.MAX_UINT256);
  399. expect(await this.vault.previewMint(mintShares)).to.be.bignumber.equal(expectedAssets);
  400. const { tx } = await this.vault.mint(mintShares, recipient, { from: holder });
  401. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  402. from: holder,
  403. to: this.vault.address,
  404. value: expectedAssets,
  405. });
  406. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  407. from: constants.ZERO_ADDRESS,
  408. to: recipient,
  409. value: mintShares,
  410. });
  411. await expectEvent.inTransaction(tx, this.vault, 'Deposit', {
  412. sender: holder,
  413. owner: recipient,
  414. assets: expectedAssets,
  415. shares: mintShares,
  416. });
  417. });
  418. it('withdraw', async function () {
  419. expect(await this.vault.maxWithdraw(holder)).to.be.bignumber.equal('0');
  420. expect(await this.vault.previewWithdraw('0')).to.be.bignumber.equal('0');
  421. const { tx } = await this.vault.withdraw('0', recipient, holder, { from: holder });
  422. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  423. from: this.vault.address,
  424. to: recipient,
  425. value: '0',
  426. });
  427. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  428. from: holder,
  429. to: constants.ZERO_ADDRESS,
  430. value: '0',
  431. });
  432. await expectEvent.inTransaction(tx, this.vault, 'Withdraw', {
  433. sender: holder,
  434. receiver: recipient,
  435. owner: holder,
  436. assets: '0',
  437. shares: '0',
  438. });
  439. });
  440. it('redeem', async function () {
  441. expect(await this.vault.maxRedeem(holder)).to.be.bignumber.equal('0');
  442. expect(await this.vault.previewRedeem('0')).to.be.bignumber.equal('0');
  443. const { tx } = await this.vault.redeem('0', recipient, holder, { from: holder });
  444. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  445. from: this.vault.address,
  446. to: recipient,
  447. value: '0',
  448. });
  449. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  450. from: holder,
  451. to: constants.ZERO_ADDRESS,
  452. value: '0',
  453. });
  454. await expectEvent.inTransaction(tx, this.vault, 'Withdraw', {
  455. sender: holder,
  456. receiver: recipient,
  457. owner: holder,
  458. assets: '0',
  459. shares: '0',
  460. });
  461. });
  462. });
  463. describe('full vault: assets & shares', function () {
  464. beforeEach(async function () {
  465. // Add 1 token of underlying asset and 100 shares to the vault
  466. await this.token.$_mint(this.vault.address, parseToken(1));
  467. await this.vault.$_mint(holder, parseShare(100));
  468. });
  469. it('status', async function () {
  470. expect(await this.vault.totalSupply()).to.be.bignumber.equal(parseShare(100));
  471. expect(await this.vault.totalAssets()).to.be.bignumber.equal(parseToken(1));
  472. });
  473. /**
  474. * | offset | deposited assets | redeemable assets |
  475. * |--------|--------------------- |----------------------|
  476. * | 0 | 1.000000000000000000 | 0.999999999999999999 |
  477. * | 6 | 1.000000000000000000 | 0.999999999999999999 |
  478. * | 18 | 1.000000000000000000 | 0.999999999999999999 |
  479. *
  480. * Virtual shares & assets captures part of the value
  481. */
  482. it('deposit', async function () {
  483. const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets));
  484. const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares));
  485. const depositAssets = parseToken(1);
  486. const expectedShares = depositAssets.mul(effectiveShares).div(effectiveAssets);
  487. expect(await this.vault.maxDeposit(holder)).to.be.bignumber.equal(constants.MAX_UINT256);
  488. expect(await this.vault.previewDeposit(depositAssets)).to.be.bignumber.equal(expectedShares);
  489. const { tx } = await this.vault.deposit(depositAssets, recipient, { from: holder });
  490. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  491. from: holder,
  492. to: this.vault.address,
  493. value: depositAssets,
  494. });
  495. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  496. from: constants.ZERO_ADDRESS,
  497. to: recipient,
  498. value: expectedShares,
  499. });
  500. await expectEvent.inTransaction(tx, this.vault, 'Deposit', {
  501. sender: holder,
  502. owner: recipient,
  503. assets: depositAssets,
  504. shares: expectedShares,
  505. });
  506. });
  507. /**
  508. * | offset | deposited assets | redeemable assets |
  509. * |--------|--------------------- |----------------------|
  510. * | 0 | 0.010000000000000001 | 0.010000000000000000 |
  511. * | 6 | 0.010000000000000001 | 0.010000000000000000 |
  512. * | 18 | 0.010000000000000001 | 0.010000000000000000 |
  513. *
  514. * Virtual shares & assets captures part of the value
  515. */
  516. it('mint', async function () {
  517. const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets));
  518. const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares));
  519. const mintShares = parseShare(1);
  520. const expectedAssets = mintShares.mul(effectiveAssets).div(effectiveShares).addn(1); // add for the rounding
  521. expect(await this.vault.maxMint(holder)).to.be.bignumber.equal(constants.MAX_UINT256);
  522. expect(await this.vault.previewMint(mintShares)).to.be.bignumber.equal(expectedAssets);
  523. const { tx } = await this.vault.mint(mintShares, recipient, { from: holder });
  524. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  525. from: holder,
  526. to: this.vault.address,
  527. value: expectedAssets,
  528. });
  529. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  530. from: constants.ZERO_ADDRESS,
  531. to: recipient,
  532. value: mintShares,
  533. });
  534. await expectEvent.inTransaction(tx, this.vault, 'Deposit', {
  535. sender: holder,
  536. owner: recipient,
  537. assets: expectedAssets,
  538. shares: mintShares,
  539. });
  540. });
  541. it('withdraw', async function () {
  542. const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets));
  543. const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares));
  544. const withdrawAssets = parseToken(1);
  545. const expectedShares = withdrawAssets.mul(effectiveShares).div(effectiveAssets).addn(1); // add for the rounding
  546. expect(await this.vault.maxWithdraw(holder)).to.be.bignumber.equal(withdrawAssets);
  547. expect(await this.vault.previewWithdraw(withdrawAssets)).to.be.bignumber.equal(expectedShares);
  548. const { tx } = await this.vault.withdraw(withdrawAssets, recipient, holder, { from: holder });
  549. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  550. from: this.vault.address,
  551. to: recipient,
  552. value: withdrawAssets,
  553. });
  554. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  555. from: holder,
  556. to: constants.ZERO_ADDRESS,
  557. value: expectedShares,
  558. });
  559. await expectEvent.inTransaction(tx, this.vault, 'Withdraw', {
  560. sender: holder,
  561. receiver: recipient,
  562. owner: holder,
  563. assets: withdrawAssets,
  564. shares: expectedShares,
  565. });
  566. });
  567. it('withdraw with approval', async function () {
  568. const assets = await this.vault.previewWithdraw(parseToken(1));
  569. await expectRevertCustomError(
  570. this.vault.withdraw(parseToken(1), recipient, holder, { from: other }),
  571. 'ERC20InsufficientAllowance',
  572. [other, 0, assets],
  573. );
  574. await this.vault.withdraw(parseToken(1), recipient, holder, { from: spender });
  575. });
  576. it('redeem', async function () {
  577. const effectiveAssets = await this.vault.totalAssets().then(x => x.add(virtualAssets));
  578. const effectiveShares = await this.vault.totalSupply().then(x => x.add(virtualShares));
  579. const redeemShares = parseShare(100);
  580. const expectedAssets = redeemShares.mul(effectiveAssets).div(effectiveShares);
  581. expect(await this.vault.maxRedeem(holder)).to.be.bignumber.equal(redeemShares);
  582. expect(await this.vault.previewRedeem(redeemShares)).to.be.bignumber.equal(expectedAssets);
  583. const { tx } = await this.vault.redeem(redeemShares, recipient, holder, { from: holder });
  584. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  585. from: this.vault.address,
  586. to: recipient,
  587. value: expectedAssets,
  588. });
  589. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  590. from: holder,
  591. to: constants.ZERO_ADDRESS,
  592. value: redeemShares,
  593. });
  594. await expectEvent.inTransaction(tx, this.vault, 'Withdraw', {
  595. sender: holder,
  596. receiver: recipient,
  597. owner: holder,
  598. assets: expectedAssets,
  599. shares: redeemShares,
  600. });
  601. });
  602. it('redeem with approval', async function () {
  603. await expectRevertCustomError(
  604. this.vault.redeem(parseShare(100), recipient, holder, { from: other }),
  605. 'ERC20InsufficientAllowance',
  606. [other, 0, parseShare(100)],
  607. );
  608. await this.vault.redeem(parseShare(100), recipient, holder, { from: spender });
  609. });
  610. });
  611. });
  612. }
  613. describe('ERC4626Fees', function () {
  614. const feeBasisPoints = web3.utils.toBN(5e3);
  615. const valueWithoutFees = web3.utils.toBN(10000);
  616. const fees = valueWithoutFees.mul(feeBasisPoints).divn(1e4);
  617. const valueWithFees = valueWithoutFees.add(fees);
  618. describe('input fees', function () {
  619. beforeEach(async function () {
  620. this.token = await ERC20Decimals.new(name, symbol, 18);
  621. this.vault = await ERC4626FeesMock.new(
  622. name + ' Vault',
  623. symbol + 'V',
  624. this.token.address,
  625. feeBasisPoints,
  626. other,
  627. 0,
  628. constants.ZERO_ADDRESS,
  629. );
  630. await this.token.$_mint(holder, constants.MAX_INT256);
  631. await this.token.approve(this.vault.address, constants.MAX_INT256, { from: holder });
  632. });
  633. it('deposit', async function () {
  634. expect(await this.vault.previewDeposit(valueWithFees)).to.be.bignumber.equal(valueWithoutFees);
  635. ({ tx: this.tx } = await this.vault.deposit(valueWithFees, recipient, { from: holder }));
  636. });
  637. it('mint', async function () {
  638. expect(await this.vault.previewMint(valueWithoutFees)).to.be.bignumber.equal(valueWithFees);
  639. ({ tx: this.tx } = await this.vault.mint(valueWithoutFees, recipient, { from: holder }));
  640. });
  641. afterEach(async function () {
  642. // get total
  643. await expectEvent.inTransaction(this.tx, this.token, 'Transfer', {
  644. from: holder,
  645. to: this.vault.address,
  646. value: valueWithFees,
  647. });
  648. // redirect fees
  649. await expectEvent.inTransaction(this.tx, this.token, 'Transfer', {
  650. from: this.vault.address,
  651. to: other,
  652. value: fees,
  653. });
  654. // mint shares
  655. await expectEvent.inTransaction(this.tx, this.vault, 'Transfer', {
  656. from: constants.ZERO_ADDRESS,
  657. to: recipient,
  658. value: valueWithoutFees,
  659. });
  660. // deposit event
  661. await expectEvent.inTransaction(this.tx, this.vault, 'Deposit', {
  662. sender: holder,
  663. owner: recipient,
  664. assets: valueWithFees,
  665. shares: valueWithoutFees,
  666. });
  667. });
  668. });
  669. describe('output fees', function () {
  670. beforeEach(async function () {
  671. this.token = await ERC20Decimals.new(name, symbol, 18);
  672. this.vault = await ERC4626FeesMock.new(
  673. name + ' Vault',
  674. symbol + 'V',
  675. this.token.address,
  676. 0,
  677. constants.ZERO_ADDRESS,
  678. 5e3, // 5%
  679. other,
  680. );
  681. await this.token.$_mint(this.vault.address, constants.MAX_INT256);
  682. await this.vault.$_mint(holder, constants.MAX_INT256);
  683. });
  684. it('redeem', async function () {
  685. expect(await this.vault.previewRedeem(valueWithFees)).to.be.bignumber.equal(valueWithoutFees);
  686. ({ tx: this.tx } = await this.vault.redeem(valueWithFees, recipient, holder, { from: holder }));
  687. });
  688. it('withdraw', async function () {
  689. expect(await this.vault.previewWithdraw(valueWithoutFees)).to.be.bignumber.equal(valueWithFees);
  690. ({ tx: this.tx } = await this.vault.withdraw(valueWithoutFees, recipient, holder, { from: holder }));
  691. });
  692. afterEach(async function () {
  693. // withdraw principal
  694. await expectEvent.inTransaction(this.tx, this.token, 'Transfer', {
  695. from: this.vault.address,
  696. to: recipient,
  697. value: valueWithoutFees,
  698. });
  699. // redirect fees
  700. await expectEvent.inTransaction(this.tx, this.token, 'Transfer', {
  701. from: this.vault.address,
  702. to: other,
  703. value: fees,
  704. });
  705. // mint shares
  706. await expectEvent.inTransaction(this.tx, this.vault, 'Transfer', {
  707. from: holder,
  708. to: constants.ZERO_ADDRESS,
  709. value: valueWithFees,
  710. });
  711. // withdraw event
  712. await expectEvent.inTransaction(this.tx, this.vault, 'Withdraw', {
  713. sender: holder,
  714. receiver: recipient,
  715. owner: holder,
  716. assets: valueWithoutFees,
  717. shares: valueWithFees,
  718. });
  719. });
  720. });
  721. });
  722. /// Scenario inspired by solmate ERC4626 tests:
  723. /// https://github.com/transmissions11/solmate/blob/main/src/test/ERC4626.t.sol
  724. it('multiple mint, deposit, redeem & withdrawal', async function () {
  725. // test designed with both asset using similar decimals
  726. this.token = await ERC20Decimals.new(name, symbol, 18);
  727. this.vault = await ERC4626.new(name + ' Vault', symbol + 'V', this.token.address);
  728. await this.token.$_mint(user1, 4000);
  729. await this.token.$_mint(user2, 7001);
  730. await this.token.approve(this.vault.address, 4000, { from: user1 });
  731. await this.token.approve(this.vault.address, 7001, { from: user2 });
  732. // 1. Alice mints 2000 shares (costs 2000 tokens)
  733. {
  734. const { tx } = await this.vault.mint(2000, user1, { from: user1 });
  735. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  736. from: user1,
  737. to: this.vault.address,
  738. value: '2000',
  739. });
  740. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  741. from: constants.ZERO_ADDRESS,
  742. to: user1,
  743. value: '2000',
  744. });
  745. expect(await this.vault.previewDeposit(2000)).to.be.bignumber.equal('2000');
  746. expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000');
  747. expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('0');
  748. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('2000');
  749. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('0');
  750. expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal(
  751. '2000',
  752. );
  753. expect(await this.vault.totalSupply()).to.be.bignumber.equal('2000');
  754. expect(await this.vault.totalAssets()).to.be.bignumber.equal('2000');
  755. }
  756. // 2. Bob deposits 4000 tokens (mints 4000 shares)
  757. {
  758. const { tx } = await this.vault.mint(4000, user2, { from: user2 });
  759. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  760. from: user2,
  761. to: this.vault.address,
  762. value: '4000',
  763. });
  764. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  765. from: constants.ZERO_ADDRESS,
  766. to: user2,
  767. value: '4000',
  768. });
  769. expect(await this.vault.previewDeposit(4000)).to.be.bignumber.equal('4000');
  770. expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000');
  771. expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4000');
  772. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('2000');
  773. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('4000');
  774. expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal(
  775. '6000',
  776. );
  777. expect(await this.vault.totalSupply()).to.be.bignumber.equal('6000');
  778. expect(await this.vault.totalAssets()).to.be.bignumber.equal('6000');
  779. }
  780. // 3. Vault mutates by +3000 tokens (simulated yield returned from strategy)
  781. await this.token.$_mint(this.vault.address, 3000);
  782. expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000');
  783. expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4000');
  784. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('2999'); // used to be 3000, but virtual assets/shares captures part of the yield
  785. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('5999'); // used to be 6000, but virtual assets/shares captures part of the yield
  786. expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal(
  787. '6000',
  788. );
  789. expect(await this.vault.totalSupply()).to.be.bignumber.equal('6000');
  790. expect(await this.vault.totalAssets()).to.be.bignumber.equal('9000');
  791. // 4. Alice deposits 2000 tokens (mints 1333 shares)
  792. {
  793. const { tx } = await this.vault.deposit(2000, user1, { from: user1 });
  794. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  795. from: user1,
  796. to: this.vault.address,
  797. value: '2000',
  798. });
  799. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  800. from: constants.ZERO_ADDRESS,
  801. to: user1,
  802. value: '1333',
  803. });
  804. expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('3333');
  805. expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4000');
  806. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('4999');
  807. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('6000');
  808. expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal(
  809. '7333',
  810. );
  811. expect(await this.vault.totalSupply()).to.be.bignumber.equal('7333');
  812. expect(await this.vault.totalAssets()).to.be.bignumber.equal('11000');
  813. }
  814. // 5. Bob mints 2000 shares (costs 3001 assets)
  815. // NOTE: Bob's assets spent got rounded towards infinity
  816. // NOTE: Alices's vault assets got rounded towards infinity
  817. {
  818. const { tx } = await this.vault.mint(2000, user2, { from: user2 });
  819. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  820. from: user2,
  821. to: this.vault.address,
  822. value: '3000', // used to be 3001
  823. });
  824. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  825. from: constants.ZERO_ADDRESS,
  826. to: user2,
  827. value: '2000',
  828. });
  829. expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('3333');
  830. expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('6000');
  831. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('4999'); // used to be 5000
  832. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('9000');
  833. expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal(
  834. '9333',
  835. );
  836. expect(await this.vault.totalSupply()).to.be.bignumber.equal('9333');
  837. expect(await this.vault.totalAssets()).to.be.bignumber.equal('14000'); // used to be 14001
  838. }
  839. // 6. Vault mutates by +3000 tokens
  840. // NOTE: Vault holds 17001 tokens, but sum of assetsOf() is 17000.
  841. await this.token.$_mint(this.vault.address, 3000);
  842. expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('3333');
  843. expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('6000');
  844. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('6070'); // used to be 6071
  845. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('10928'); // used to be 10929
  846. expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal(
  847. '9333',
  848. );
  849. expect(await this.vault.totalSupply()).to.be.bignumber.equal('9333');
  850. expect(await this.vault.totalAssets()).to.be.bignumber.equal('17000'); // used to be 17001
  851. // 7. Alice redeem 1333 shares (2428 assets)
  852. {
  853. const { tx } = await this.vault.redeem(1333, user1, user1, { from: user1 });
  854. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  855. from: user1,
  856. to: constants.ZERO_ADDRESS,
  857. value: '1333',
  858. });
  859. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  860. from: this.vault.address,
  861. to: user1,
  862. value: '2427', // used to be 2428
  863. });
  864. expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000');
  865. expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('6000');
  866. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('3643');
  867. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('10929');
  868. expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal(
  869. '8000',
  870. );
  871. expect(await this.vault.totalSupply()).to.be.bignumber.equal('8000');
  872. expect(await this.vault.totalAssets()).to.be.bignumber.equal('14573');
  873. }
  874. // 8. Bob withdraws 2929 assets (1608 shares)
  875. {
  876. const { tx } = await this.vault.withdraw(2929, user2, user2, { from: user2 });
  877. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  878. from: user2,
  879. to: constants.ZERO_ADDRESS,
  880. value: '1608',
  881. });
  882. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  883. from: this.vault.address,
  884. to: user2,
  885. value: '2929',
  886. });
  887. expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('2000');
  888. expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4392');
  889. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('3643');
  890. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('8000');
  891. expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal(
  892. '6392',
  893. );
  894. expect(await this.vault.totalSupply()).to.be.bignumber.equal('6392');
  895. expect(await this.vault.totalAssets()).to.be.bignumber.equal('11644');
  896. }
  897. // 9. Alice withdraws 3643 assets (2000 shares)
  898. // NOTE: Bob's assets have been rounded back towards infinity
  899. {
  900. const { tx } = await this.vault.withdraw(3643, user1, user1, { from: user1 });
  901. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  902. from: user1,
  903. to: constants.ZERO_ADDRESS,
  904. value: '2000',
  905. });
  906. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  907. from: this.vault.address,
  908. to: user1,
  909. value: '3643',
  910. });
  911. expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('0');
  912. expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('4392');
  913. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('0');
  914. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('8000'); // used to be 8001
  915. expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal(
  916. '4392',
  917. );
  918. expect(await this.vault.totalSupply()).to.be.bignumber.equal('4392');
  919. expect(await this.vault.totalAssets()).to.be.bignumber.equal('8001');
  920. }
  921. // 10. Bob redeem 4392 shares (8001 tokens)
  922. {
  923. const { tx } = await this.vault.redeem(4392, user2, user2, { from: user2 });
  924. await expectEvent.inTransaction(tx, this.vault, 'Transfer', {
  925. from: user2,
  926. to: constants.ZERO_ADDRESS,
  927. value: '4392',
  928. });
  929. await expectEvent.inTransaction(tx, this.token, 'Transfer', {
  930. from: this.vault.address,
  931. to: user2,
  932. value: '8000', // used to be 8001
  933. });
  934. expect(await this.vault.balanceOf(user1)).to.be.bignumber.equal('0');
  935. expect(await this.vault.balanceOf(user2)).to.be.bignumber.equal('0');
  936. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user1))).to.be.bignumber.equal('0');
  937. expect(await this.vault.convertToAssets(await this.vault.balanceOf(user2))).to.be.bignumber.equal('0');
  938. expect(await this.vault.convertToShares(await this.token.balanceOf(this.vault.address))).to.be.bignumber.equal(
  939. '0',
  940. );
  941. expect(await this.vault.totalSupply()).to.be.bignumber.equal('0');
  942. expect(await this.vault.totalAssets()).to.be.bignumber.equal('1'); // used to be 0
  943. }
  944. });
  945. });