From d92cf6d5b229e3a8bc844617d72a43a92cfa3155 Mon Sep 17 00:00:00 2001 From: "tspoff@gmail.com" Date: Tue, 4 Feb 2020 18:13:39 -0600 Subject: [PATCH 1/2] Rewards wallet core --- .../interface/IRewardsDistributor.sol | 31 ++++ .../interface/IRewardsWalletEther.sol | 10 ++ .../interface/IUniswapExchange.sol | 146 ++++++++++++++++++ .../wallet/RewardsWalletERC20.sol | 65 ++++++++ .../wallet/RewardsWalletEther.sol | 60 +++++++ 5 files changed, 312 insertions(+) create mode 100644 contracts/BondingCurve/interface/IRewardsDistributor.sol create mode 100644 contracts/BondingCurve/interface/IRewardsWalletEther.sol create mode 100644 contracts/BondingCurve/interface/IUniswapExchange.sol create mode 100644 contracts/BondingCurve/wallet/RewardsWalletERC20.sol create mode 100644 contracts/BondingCurve/wallet/RewardsWalletEther.sol diff --git a/contracts/BondingCurve/interface/IRewardsDistributor.sol b/contracts/BondingCurve/interface/IRewardsDistributor.sol new file mode 100644 index 0000000..5d5ef13 --- /dev/null +++ b/contracts/BondingCurve/interface/IRewardsDistributor.sol @@ -0,0 +1,31 @@ +pragma solidity ^0.5.6; + +contract IRewardsDistributor { + /// @notice Deposit funds into contract. + function deposit(address staker, uint256 tokens) public returns (bool success); + + /// @notice Distribute tokens pro rata to all stakers. + function distribute(address from, uint256 tokens) public returns (bool success); + + /// @notice Withdraw accumulated reward for the staker address. + function withdrawReward(address staker) public returns (uint256 tokens); + + /// @notice Withdraw stake for the staker address + function withdrawStake(address staker, uint256 tokens) public returns (bool); + + /// @notice Withdraw stake for the staker address + function withdrawAllStake(address staker) public returns (bool); + + /// + /// READ ONLY + /// + + /// @notice Read total stake. + function getStakeTotal() public view returns (uint256); + /// @notice Read current stake for address. + function getStake(address staker) public view returns (uint256 tokens); + + /// @notice Read current accumulated reward for address. + function getReward(address staker) public view returns (uint256 tokens); + +} diff --git a/contracts/BondingCurve/interface/IRewardsWalletEther.sol b/contracts/BondingCurve/interface/IRewardsWalletEther.sol new file mode 100644 index 0000000..9ccc26b --- /dev/null +++ b/contracts/BondingCurve/interface/IRewardsWalletEther.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.5.6; + +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; + +contract IRewardsWalletEther { + /// @notice Deposit funds into contract. + function exchangeAndDistribute(IERC20 token, uint256 amount, uint256 minOut, uint256 deadline) + external + returns (uint256 amountOut); +} diff --git a/contracts/BondingCurve/interface/IUniswapExchange.sol b/contracts/BondingCurve/interface/IUniswapExchange.sol new file mode 100644 index 0000000..115405f --- /dev/null +++ b/contracts/BondingCurve/interface/IUniswapExchange.sol @@ -0,0 +1,146 @@ +pragma solidity ^0.5.7; + +// Solidity Interface +// https://docs.uniswap.io/smart-contract-integration/interface + +contract IUniswapExchange { + // Address of ERC20 token sold on this exchange + function tokenAddress() external view returns (address token); + // Address of Uniswap Factory + function factoryAddress() external view returns (address factory); + // Provide Liquidity + function addLiquidity(uint256 min_liquidity, uint256 max_tokens, uint256 deadline) + external + payable + returns (uint256); + function removeLiquidity(uint256 amount, uint256 min_eth, uint256 min_tokens, uint256 deadline) + external + returns (uint256, uint256); + // Get Prices + function getEthToTokenInputPrice(uint256 eth_sold) + external + view + returns (uint256 tokens_bought); + function getEthToTokenOutputPrice(uint256 tokens_bought) + external + view + returns (uint256 eth_sold); + function getTokenToEthInputPrice(uint256 tokens_sold) + external + view + returns (uint256 eth_bought); + function getTokenToEthOutputPrice(uint256 eth_bought) + external + view + returns (uint256 tokens_sold); + // Trade ETH to ERC20 + function ethToTokenSwapInput(uint256 min_tokens, uint256 deadline) + external + payable + returns (uint256 tokens_bought); + function ethToTokenTransferInput(uint256 min_tokens, uint256 deadline, address recipient) + external + payable + returns (uint256 tokens_bought); + function ethToTokenSwapOutput(uint256 tokens_bought, uint256 deadline) + external + payable + returns (uint256 eth_sold); + function ethToTokenTransferOutput(uint256 tokens_bought, uint256 deadline, address recipient) + external + payable + returns (uint256 eth_sold); + // Trade ERC20 to ETH + function tokenToEthSwapInput(uint256 tokens_sold, uint256 min_eth, uint256 deadline) + external + returns (uint256 eth_bought); + function tokenToEthTransferInput( + uint256 tokens_sold, + uint256 min_eth, + uint256 deadline, + address recipient + ) external returns (uint256 eth_bought); + function tokenToEthSwapOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline) + external + returns (uint256 tokens_sold); + function tokenToEthTransferOutput( + uint256 eth_bought, + uint256 max_tokens, + uint256 deadline, + address recipient + ) external returns (uint256 tokens_sold); + // Trade ERC20 to ERC20 + function tokenToTokenSwapInput( + uint256 tokens_sold, + uint256 min_tokens_bought, + uint256 min_eth_bought, + uint256 deadline, + address token_addr + ) external returns (uint256 tokens_bought); + function tokenToTokenTransferInput( + uint256 tokens_sold, + uint256 min_tokens_bought, + uint256 min_eth_bought, + uint256 deadline, + address recipient, + address token_addr + ) external returns (uint256 tokens_bought); + function tokenToTokenSwapOutput( + uint256 tokens_bought, + uint256 max_tokens_sold, + uint256 max_eth_sold, + uint256 deadline, + address token_addr + ) external returns (uint256 tokens_sold); + function tokenToTokenTransferOutput( + uint256 tokens_bought, + uint256 max_tokens_sold, + uint256 max_eth_sold, + uint256 deadline, + address recipient, + address token_addr + ) external returns (uint256 tokens_sold); + // Trade ERC20 to Custom Pool + function tokenToExchangeSwapInput( + uint256 tokens_sold, + uint256 min_tokens_bought, + uint256 min_eth_bought, + uint256 deadline, + address exchange_addr + ) external returns (uint256 tokens_bought); + function tokenToExchangeTransferInput( + uint256 tokens_sold, + uint256 min_tokens_bought, + uint256 min_eth_bought, + uint256 deadline, + address recipient, + address exchange_addr + ) external returns (uint256 tokens_bought); + function tokenToExchangeSwapOutput( + uint256 tokens_bought, + uint256 max_tokens_sold, + uint256 max_eth_sold, + uint256 deadline, + address exchange_addr + ) external returns (uint256 tokens_sold); + function tokenToExchangeTransferOutput( + uint256 tokens_bought, + uint256 max_tokens_sold, + uint256 max_eth_sold, + uint256 deadline, + address recipient, + address exchange_addr + ) external returns (uint256 tokens_sold); + // ERC20 comaptibility for liquidity tokens + bytes32 public name; + bytes32 public symbol; + uint256 public decimals; + function transfer(address _to, uint256 _value) external returns (bool); + function transferFrom(address _from, address _to, uint256 value) external returns (bool); + function approve(address _spender, uint256 _value) external returns (bool); + function allowance(address _owner, address _spender) external view returns (uint256); + function balanceOf(address _owner) external view returns (uint256); + function totalSupply() external view returns (uint256); + // Never use + function setup(address token_addr) external; +} diff --git a/contracts/BondingCurve/wallet/RewardsWalletERC20.sol b/contracts/BondingCurve/wallet/RewardsWalletERC20.sol new file mode 100644 index 0000000..64d7349 --- /dev/null +++ b/contracts/BondingCurve/wallet/RewardsWalletERC20.sol @@ -0,0 +1,65 @@ +pragma solidity ^0.5.6; + +import "@openzeppelin/upgrades/contracts/Initializable.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; +import "contracts/BondingCurve/interface/IRewardsDistributor.sol"; +import "contracts/BondingCurve/interface/IUniswapExchange.sol"; + +/** + * @title RewardsWalletERC20 + */ +contract RewardsWalletERC20 is Initializable { + using SafeMath for uint256; + + IERC20 _rewardsToken; + IRewardsDistributor _rewardsDistributor; + IUniswapExchange _exchange; + + string internal constant ONLY_REWARDS_TOKEN = "Only Rewards Token"; + string internal constant ONLY_FOR_ETHER_REWARDS_TOKEN = "Only functional for Ether as rewards currency"; + + event Recieve(address token, uint256 amount, address sender); + event Convert(address fromToken, address toToken, uint256 amount); + event Distribute(address token, uint256 amount); + + /// Initialize the contract. + function initialize( + IERC20 rewardsToken, + IRewardsDistributor rewardsDistributor, + IUniswapExchange exchange + ) public initializer { + _rewardsToken = rewardsToken; + _rewardsDistributor = rewardsDistributor; + _exchange = exchange; + } + + /// @dev Recieve payment and immediately distribute. Requires previous approval + function pay(IERC20 token, uint256 amount) public { + token.transferFrom(msg.sender, address(this), amount); + _distribute(token, amount); + } + + /// @notice Distribute wallet balance of specified token to dividend holders + /// @dev This has open access, can be used to distribte tokens recieved on native ERC20 transfers + /// @dev Tokens other than the reserve currency will be transferred via the exchange. + function distribute(IERC20 token) public { + require(_isRewardsToken(token), ONLY_REWARDS_TOKEN); + uint256 walletBalance = token.balanceOf(address(this)); + _distribute(token, walletBalance); + } + + function _distribute(IERC20 token, uint256 amount) internal { + require(_isRewardsToken(token), ONLY_REWARDS_TOKEN); + token.approve(address(_rewardsToken), amount); + _rewardsDistributor.distribute(address(this), amount); + } + + function _isRewardsToken(IERC20 token) internal returns (bool) { + return address(token) == address(_rewardsToken); + } + + function _isEtherAddress(address tokenAddress) internal returns (bool) { + return tokenAddress == address(0); + } +} diff --git a/contracts/BondingCurve/wallet/RewardsWalletEther.sol b/contracts/BondingCurve/wallet/RewardsWalletEther.sol new file mode 100644 index 0000000..b14333e --- /dev/null +++ b/contracts/BondingCurve/wallet/RewardsWalletEther.sol @@ -0,0 +1,60 @@ +pragma solidity ^0.5.6; + +import "@openzeppelin/upgrades/contracts/Initializable.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol"; +import "contracts/BondingCurve/interface/IRewardsWalletEther.sol"; +import "contracts/BondingCurve/interface/IRewardsDistributor.sol"; +import "contracts/BondingCurve/interface/IUniswapExchange.sol"; + +/** + * @title RewardsWalletEther + */ +contract RewardsWalletEther is Initializable, IRewardsWalletEther { + using SafeMath for uint256; + + IRewardsDistributor _rewardsDistributor; + IUniswapExchange _exchangeContract; + + string internal constant ONLY_REWARDS_TOKEN = "Only Rewards Token"; + string internal constant ONLY_FOR_ETHER_REWARDS_TOKEN = "Only functional for Ether as rewards currency"; + + event Receive(address token, uint256 amount, address sender); + event Convert(address fromToken, address toToken, uint256 amount); + event Distribute(address token, uint256 amount); + + /// Initialize the contract. + function initialize(IRewardsDistributor rewardsDistributor, IUniswapExchange exchangeContract) + public + initializer + { + _rewardsDistributor = rewardsDistributor; + _exchangeContract = exchangeContract; + } + + /// @notice Distribute wallet balance of specified token to dividend holders + /// @dev This has open access, can be used to distribte tokens recieved on native ERC20 transfers + /// @dev Tokens other than the reserve currency will be transferred via the exchange. + function exchangeAndDistribute(IERC20 token, uint256 amount, uint256 minOut, uint256 deadline) + external + returns (uint256 amountOut) + { + token.approve(address(_exchangeContract), amount); + amountOut = _exchangeContract.tokenToEthSwapInput(amount, minOut, deadline); + + emit Receive(address(0), amountOut, msg.sender); + _distribute(amountOut); + } + + function _distribute(uint256 amount) internal { + address(uint160(address(_rewardsDistributor))).transfer(amount); + _rewardsDistributor.distribute(address(this), amount); + emit Distribute(address(0), amount); + } + + /// @notice Automatically distribute ETH on payments + function() external payable { + emit Receive(address(0), msg.value, msg.sender); + _distribute(msg.value); + } +} From 132976fc05b85c5e16e1a9c8920af1fb137f75dc Mon Sep 17 00:00:00 2001 From: "tspoff@gmail.com" Date: Tue, 4 Feb 2020 19:09:34 -0600 Subject: [PATCH 2/2] Test formatting --- test/behaviors/BondingCurve.behavior.js | 62 +- test/behaviors/ERC20Burnable.behavior.js | 87 +- test/behaviors/bondingCurveAdmin.js | 341 +++--- test/behaviors/bondingCurveBuySell.js | 1118 ++++++++++---------- test/behaviors/bondingCurveBuySellEther.js | 976 ++++++++--------- test/behaviors/bondingCurveDeploy.js | 109 +- test/behaviors/bondingCurvePayment.js | 353 +++--- test/constants/bancorValues.js | 2 +- test/helpers/CurveEcosystem.js | 419 ++++---- test/helpers/utils.js | 25 +- test/unit/bancorCurveLogic.spec.js | 10 +- test/unit/rewardDistributor.spec.js | 40 +- 12 files changed, 1816 insertions(+), 1726 deletions(-) diff --git a/test/behaviors/BondingCurve.behavior.js b/test/behaviors/BondingCurve.behavior.js index 1a4ee13..704c76e 100644 --- a/test/behaviors/BondingCurve.behavior.js +++ b/test/behaviors/BondingCurve.behavior.js @@ -9,7 +9,7 @@ const should = require('chai').should(); const deploy = require('../../index.js'); const contractConstants = require('../constants/contractConstants.js'); const {defaultTestConfig} = require('../helpers/ecosystemConfigs'); -const {str, bn} = require("../helpers/utils"); +const {str, bn} = require('../helpers/utils'); async function shouldBehaveLikeBondingCurve(context, parameters) { const {adminAccount, curveOwner, tokenMinter, userAccounts, miscUser} = context; @@ -27,7 +27,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { // // Deploy bondingCurve proxy w/o initialization (check service for example) // // Call initialize and expect revert - beforeEach(async function() { + beforeEach(async () => { project = await deploy.deployProject(); // TODO: Use an ERC20Mintable instead of a BondedToken here! @@ -80,8 +80,6 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { }); describe('Initialization', async () => { - - it('should fail on invalid dividendPercentage', async () => { const invalidDividendPercentage = new BN(101); @@ -116,13 +114,13 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { const maxBuyPrice = new BN(0); //We don't want a max price unless we're specifically testing that const minSellPrice = new BN(0); //We don't want a min price unless we're specifically testing that - it('should show buy price correctly', async function() { + it('should show buy price correctly', async () => { result = await bondingCurve.methods.priceToBuy(numTokens.toString()).call({from: miscUser}); expect(new BN(result)).to.be.bignumber.equal(expectedBuyPrice); }); - it('should show sell reward correctly', async function() { + it('should show sell reward correctly', async () => { result = await bondingCurve.methods .rewardForSell(numTokens.toString()) .call({from: miscUser}); @@ -130,26 +128,26 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { expect(new BN(result)).to.be.bignumber.equal(expectedSellReward); }); - it('should not allow bondingCurve owner to mint bondedTokens', async function() { + it('should not allow bondingCurve owner to mint bondedTokens', async () => { await expectRevert.unspecified( bondedToken.methods.mint(curveOwner, 100).send({from: curveOwner}) ); }); - it('should not allow other addresses to mint bondedTokens', async function() { + it('should not allow other addresses to mint bondedTokens', async () => { await expectRevert.unspecified( bondedToken.methods.mint(userAccounts[3], 100).send({from: userAccounts[3]}) ); }); describe('Buy Failure Cases', async () => { - it('should not allow to buy with 0 tokens specified', async function() { + it('should not allow to buy with 0 tokens specified', async () => { await expectRevert.unspecified( bondingCurve.methods.buy(0, maxBuyPrice.toString(), buyer).send({from: buyer}) ); }); - it('should not allow user without collateralTokens approved to buy bondedTokens', async function() { + it('should not allow user without collateralTokens approved to buy bondedTokens', async () => { await expectRevert.unspecified( bondingCurve.methods .buy(numTokens.toString(), maxBuyPrice.toString(), buyer) @@ -157,7 +155,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { ); }); - it('should not allow buy if current price exceeds specified max price', async function() { + it('should not allow buy if current price exceeds specified max price', async () => { await expectRevert.unspecified( bondingCurve.methods.buy(numTokens.toString(), '1', buyer).send({ from: buyer @@ -180,7 +178,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { }); }); - it('should not allow owner to buy when paused', async function() { + it('should not allow owner to buy when paused', async () => { await bondingCurve.methods.pause().send({from: curveOwner}); await expectRevert.unspecified( bondingCurve.methods.buy(numTokens.toString(), maxBuyPrice.toString(), buyer).send({ @@ -189,7 +187,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { ); }); - it('should not allow user to buy when paused', async function() { + it('should not allow user to buy when paused', async () => { await bondingCurve.methods.pause().send({from: curveOwner}); await expectRevert.unspecified( bondingCurve.methods.buy(numTokens.toString(), maxBuyPrice.toString(), buyer).send({ @@ -198,7 +196,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { ); }); - it('should mint bondedTokens correctly on buy', async function() { + it('should mint bondedTokens correctly on buy', async () => { const beforeBalance = new BN( await bondedToken.methods.balanceOf(buyer).call({from: miscUser}) ); @@ -216,7 +214,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(numTokens)); }); - it('should transfer collateral tokens from buyer correctly on buy', async function() { + it('should transfer collateral tokens from buyer correctly on buy', async () => { const beforeBalance = new BN( await paymentToken.methods.balanceOf(buyer).call({from: miscUser}) ); @@ -234,7 +232,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { expect(afterBalance).to.be.bignumber.equal(beforeBalance.sub(expectedBuyPrice)); }); - it('should transfer collateral tokens to reserve correctly on buy', async function() { + it('should transfer collateral tokens to reserve correctly on buy', async () => { const beforeBalance = new BN( await paymentToken.methods.balanceOf(bondingCurve.address).call({from: miscUser}) ); @@ -256,7 +254,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(reserveAmount)); }); - it('should record reserve balance correctly on buy', async function() { + it('should record reserve balance correctly on buy', async () => { const beforeBalance = new BN( await paymentToken.methods.balanceOf(bondingCurve.address).call({from: miscUser}) ); @@ -278,7 +276,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { expect(reserveBalance).to.be.bignumber.equal(beforeBalance.add(reserveAmount)); }); - it('should transfer collateral tokens to beneficiary correctly on buy', async function() { + it('should transfer collateral tokens to beneficiary correctly on buy', async () => { const beforeBalance = new BN( await paymentToken.methods.balanceOf(deployParams.beneficiary).call({from: miscUser}) ); @@ -300,7 +298,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(beneficiaryAmount)); }); - it('should register buy event on buy', async function() { + it('should register buy event on buy', async () => { tx = await bondingCurve.methods .buy(numTokens.toString(), maxBuyPrice.toString(), buyer) .send({ @@ -314,7 +312,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { }); }); - it('should allow buy if current price is below max price specified', async function() { + it('should allow buy if current price is below max price specified', async () => { tx = await bondingCurve.methods .buy(numTokens.toString(), '1000000000000000000000000', buyer) .send({ @@ -328,7 +326,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { }); }); - it('should allow user to buy for a different recipient', async function() { + it('should allow user to buy for a different recipient', async () => { tx = await bondingCurve.methods .buy(numTokens.toString(), maxBuyPrice.toString(), userAccounts[1]) .send({ @@ -344,13 +342,13 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { }); describe('Sell Failure Cases', async () => { - it('should not allow to sell with 0 tokens specified', async function() { + it('should not allow to sell with 0 tokens specified', async () => { await expectRevert.unspecified( bondingCurve.methods.sell(0, maxBuyPrice.toString(), buyer).send({from: buyer}) ); }); - it('should not allow user without bondedTokens to sell', async function() { + it('should not allow user without bondedTokens to sell', async () => { //TODO: Test, REMOVE await expectRevert.unspecified( bondingCurve.methods @@ -387,7 +385,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { }); }); - it('should not allow owner to sell when paused', async function() { + it('should not allow owner to sell when paused', async () => { await bondingCurve.methods.pause().send({from: curveOwner}); await expectRevert.unspecified( bondingCurve.methods.sell(numTokens.toString(), minSellPrice.toString(), buyer).send({ @@ -396,7 +394,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { ); }); - it('should not allow user to sell when paused', async function() { + it('should not allow user to sell when paused', async () => { await bondingCurve.methods.pause().send({from: curveOwner}); await expectRevert.unspecified( bondingCurve.methods.sell(numTokens.toString(), minSellPrice.toString(), buyer).send({ @@ -405,7 +403,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { ); }); - it('should allow user with bondedTokens to sell all bondedTokens', async function() { + it('should allow user with bondedTokens to sell all bondedTokens', async () => { tx = await bondingCurve.methods .sell(numTokens.toString(), minSellPrice.toString(), buyer) .send({ @@ -419,7 +417,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { }); }); - it('should allow user with bondedTokens to sell some bondedTokens', async function() { + it('should allow user with bondedTokens to sell some bondedTokens', async () => { const tokensToSell = numTokens.div(new BN(2)); tx = await bondingCurve.methods @@ -435,7 +433,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { }); }); - it('should burn tokens from seller on sell', async function() { + it('should burn tokens from seller on sell', async () => { const beforeBalance = new BN( await bondedToken.methods.balanceOf(buyer).call({from: miscUser}) ); @@ -452,7 +450,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { expect(afterBalance).to.be.bignumber.equal(beforeBalance.sub(numTokens)); }); - it('should transfer collateral tokens from reserve on sell', async function() { + it('should transfer collateral tokens from reserve on sell', async () => { const beforeBalance = new BN( await paymentToken.methods.balanceOf(bondingCurve.address).call({from: miscUser}) ); @@ -469,7 +467,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { expect(afterBalance).to.be.bignumber.equal(beforeBalance.sub(expectedSellReward)); }); - it('should transfer collateral tokens to seller on sell', async function() { + it('should transfer collateral tokens to seller on sell', async () => { const beforeBalance = new BN( await paymentToken.methods.balanceOf(buyer).call({from: miscUser}) ); @@ -486,7 +484,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(expectedSellReward)); }); - it('should allow user to sell and send reward to different recipient', async function() { + it('should allow user to sell and send reward to different recipient', async () => { const recipient = userAccounts[2]; const recipientBeforeBalance = new BN( @@ -512,7 +510,7 @@ async function shouldBehaveLikeBondingCurve(context, parameters) { expect(recipientAfterBalance).to.be.bignumber.above(recipientBeforeBalance); }); - it('should not allow sell if current reward is lower than specified min reward', async function() { + it('should not allow sell if current reward is lower than specified min reward', async () => { const result = await bondingCurve.methods .rewardForSell(numTokens.toString()) .call({from: miscUser}); diff --git a/test/behaviors/ERC20Burnable.behavior.js b/test/behaviors/ERC20Burnable.behavior.js index 45a4cbe..20bc53b 100644 --- a/test/behaviors/ERC20Burnable.behavior.js +++ b/test/behaviors/ERC20Burnable.behavior.js @@ -1,42 +1,32 @@ -const { - BN, - constants, - expectEvent, - shouldFail -} = require("openzeppelin-test-helpers"); -const { ZERO_ADDRESS } = constants; - -function shouldBehaveLikeERC20Burnable( - tokenContract, - owner, - initialBalance, - [burner] -) { - describe("burn", function() { - describe("when the given amount is not greater than balance of the sender", function() { - context("for a zero amount", function() { +const {BN, constants, expectEvent, shouldFail} = require('openzeppelin-test-helpers'); +const {ZERO_ADDRESS} = constants; + +function shouldBehaveLikeERC20Burnable(tokenContract, owner, initialBalance, [burner]) { + describe('burn', () => { + describe('when the given amount is not greater than balance of the sender', () => { + context('for a zero amount', () => { shouldBurn(new BN(0)); }); - context("for a non-zero amount", function() { + context('for a non-zero amount', () => { shouldBurn(new BN(100)); }); function shouldBurn(amount) { - beforeEach(async function() { - ({ logs: this.logs } = await tokenContract.burn(amount, { + beforeEach(async () => { + ({logs: this.logs} = await tokenContract.burn(amount, { from: owner })); }); - it("burns the requested amount", async function() { + it('burns the requested amount', async () => { (await tokenContract.balanceOf(owner)).should.be.bignumber.equal( initialBalance.sub(amount) ); }); - it("emits a transfer event", async function() { - expectEvent.inLogs(this.logs, "Transfer", { + it('emits a transfer event', async () => { + expectEvent.inLogs(this.logs, 'Transfer', { from: owner, to: ZERO_ADDRESS, value: amount @@ -45,53 +35,52 @@ function shouldBehaveLikeERC20Burnable( } }); - describe("when the given amount is greater than the balance of the sender", function() { + describe('when the given amount is greater than the balance of the sender', () => { const amount = initialBalance.addn(1); - it("reverts", async function() { - await shouldFail.reverting(tokenContract.burn(amount, { from: owner })); + it('reverts', async () => { + await shouldFail.reverting(tokenContract.burn(amount, {from: owner})); }); }); }); - describe("burnFrom", function() { - describe("on success", function() { - context("for a zero amount", function() { + describe('burnFrom', () => { + describe('on success', () => { + context('for a zero amount', () => { shouldBurnFrom(new BN(0)); }); - context("for a non-zero amount", function() { + context('for a non-zero amount', () => { shouldBurnFrom(new BN(100)); }); function shouldBurnFrom(amount) { const originalAllowance = amount.muln(3); - beforeEach(async function() { + beforeEach(async () => { await tokenContract.approve(burner, originalAllowance, { from: owner }); - const { logs } = await tokenContract.burnFrom(owner, amount, { + const {logs} = await tokenContract.burnFrom(owner, amount, { from: burner }); this.logs = logs; }); - it("burns the requested amount", async function() { + it('burns the requested amount', async () => { (await tokenContract.balanceOf(owner)).should.be.bignumber.equal( initialBalance.sub(amount) ); }); - it("decrements allowance", async function() { - (await tokenContract.allowance( - owner, - burner - )).should.be.bignumber.equal(originalAllowance.sub(amount)); + it('decrements allowance', async () => { + (await tokenContract.allowance(owner, burner)).should.be.bignumber.equal( + originalAllowance.sub(amount) + ); }); - it("emits a transfer event", async function() { - expectEvent.inLogs(this.logs, "Transfer", { + it('emits a transfer event', async () => { + expectEvent.inLogs(this.logs, 'Transfer', { from: owner, to: ZERO_ADDRESS, value: amount @@ -100,24 +89,22 @@ function shouldBehaveLikeERC20Burnable( } }); - describe("when the given amount is greater than the balance of the sender", function() { + describe('when the given amount is greater than the balance of the sender', () => { const amount = initialBalance.addn(1); - it("reverts", async function() { - await tokenContract.approve(burner, amount, { from: owner }); - await shouldFail.reverting( - tokenContract.burnFrom(owner, amount, { from: burner }) - ); + it('reverts', async () => { + await tokenContract.approve(burner, amount, {from: owner}); + await shouldFail.reverting(tokenContract.burnFrom(owner, amount, {from: burner})); }); }); - describe("when the given amount is greater than the allowance", function() { + describe('when the given amount is greater than the allowance', () => { const allowance = new BN(100); - it("reverts", async function() { - await tokenContract.approve(burner, allowance, { from: owner }); + it('reverts', async () => { + await tokenContract.approve(burner, allowance, {from: owner}); await shouldFail.reverting( - tokenContract.burnFrom(owner, allowance.addn(1), { from: burner }) + tokenContract.burnFrom(owner, allowance.addn(1), {from: burner}) ); }); }); diff --git a/test/behaviors/bondingCurveAdmin.js b/test/behaviors/bondingCurveAdmin.js index 9978104..6abe73e 100644 --- a/test/behaviors/bondingCurveAdmin.js +++ b/test/behaviors/bondingCurveAdmin.js @@ -1,188 +1,183 @@ const {BN, constants, shouldFail, expectRevert} = require('openzeppelin-test-helpers'); const {ZERO_ADDRESS} = constants; -const BondedToken = artifacts.require("BondedToken.sol"); +const BondedToken = artifacts.require('BondedToken.sol'); const expectEvent = require('../expectEvent'); -const {CurveEcosystem} = require("../helpers/CurveEcosystem"); -const {str, bn} = require("../helpers/utils"); +const {CurveEcosystem} = require('../helpers/CurveEcosystem'); +const {str, bn} = require('../helpers/utils'); // Import preferred chai flavor: both expect and should are supported const {expect} = require('chai'); const {defaultTestConfig} = require('../helpers/ecosystemConfigs'); const bondingCurveAdminTests = async (suiteName, config) => { - contract('Bonding Curve Admin', async accounts => { - const adminAccount = accounts[0]; - const curveOwner = accounts[1]; - const tokenMinter = accounts[2]; - const userAccounts = accounts.slice(3, accounts.length); - const miscUser = userAccounts[0]; - - const accountsConfig = { - adminAccount, - curveOwner, - minter: tokenMinter, - userAccounts, - miscUser - } - - describe('Curve Admin', async () => { - it('should allow owner to set new beneficiary', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve} = await eco.init(web3); - - let tx = await bondingCurve.setBeneficiary(userAccounts[0], { - from: curveOwner - }); - expect(await bondingCurve.beneficiary({from: miscUser})).to.be.equal( - userAccounts[0] - ); - }); - - it('should not allow non-owner to set new beneficiary', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await expectRevert.unspecified( - bondingCurve.setBeneficiary(constants.ZERO_ADDRESS, { - from: miscUser - }) - ); - }); - - it('should allow owner to set new owner', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - const oldOwner = curveOwner; - const newOwner = userAccounts[0]; - - let tx = await bondingCurve.transferOwnership(newOwner, {from: oldOwner}); - - expect(await bondingCurve.owner({from: newOwner})).to.be.equal(newOwner); - }); - - it('should not allow non-owner to set new owner', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - const nonOwner = userAccounts[0]; - const newOwner = userAccounts[1]; - - await expectRevert.unspecified( - bondingCurve.transferOwnership(newOwner, { - from: nonOwner - }) - ); - }); - - it('should not allow old owner to set new beneficiary after ownership transfer', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - const oldOwner = curveOwner; - const oldBeneficiary = curveOwner; - const newOwner = userAccounts[0]; - const newBeneficiary = userAccounts[1]; - - let tx = await bondingCurve.transferOwnership(newOwner, { - from: oldOwner - }); - - let result = await bondingCurve.beneficiary({from: miscUser}); - expect(result).to.be.equal(oldBeneficiary); - - await bondingCurve.setBeneficiary(newBeneficiary, { - from: newOwner - }); - - result = await bondingCurve.beneficiary({from: miscUser}); - expect(result).to.be.equal(newBeneficiary); - }); - - it('should allow owner to set new buy curve', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - let tx = await bondingCurve.setBuyCurve(constants.ZERO_ADDRESS, { - from: curveOwner - }); - expect(await bondingCurve.buyCurve({from: miscUser})).to.be.equal( - constants.ZERO_ADDRESS - ); - }); - - it('should not allow non-owner to set new buy curve', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await expectRevert.unspecified( - bondingCurve.setBuyCurve(constants.ZERO_ADDRESS, { - from: miscUser - }) - ); - }); - - it('should allow owner to set new reserve percentage', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - const newReservePercentage = '20'; - - let tx = await bondingCurve.setReservePercentage(newReservePercentage, { - from: curveOwner - }); - expect(await bondingCurve.reservePercentage({from: miscUser})).to.be.bignumber.equal( - newReservePercentage - ); - }); - - it('should not allow non-owner to set new reserve percentage', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - const newReservePercentage = '20'; - - await expectRevert.unspecified( - bondingCurve.setReservePercentage(newReservePercentage, { - from: miscUser - }) - ); - }); - - it('should allow owner to set new dividend percentage', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - const newDividendPercentage = '20'; - - let tx = await bondingCurve.setDividendPercentage(newDividendPercentage, { - from: curveOwner - }); - expect(await bondingCurve.dividendPercentage({from: miscUser})).to.be.bignumber.equal( - newDividendPercentage - ); - }); - - it('should not allow non-owner to set new dividend percentage', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - const newDividendPercentage = '20'; - - await expectRevert.unspecified( - bondingCurve.setDividendPercentage(newDividendPercentage, { - from: miscUser - }) - ); - }); + contract('Bonding Curve Admin', async accounts => { + const adminAccount = accounts[0]; + const curveOwner = accounts[1]; + const tokenMinter = accounts[2]; + const userAccounts = accounts.slice(3, accounts.length); + const miscUser = userAccounts[0]; + + const accountsConfig = { + adminAccount, + curveOwner, + minter: tokenMinter, + userAccounts, + miscUser + }; + + describe('Curve Admin', async () => { + it('should allow owner to set new beneficiary', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve} = await eco.init(web3); + + const tx = await bondingCurve.setBeneficiary(userAccounts[0], { + from: curveOwner }); - }) -} + expect(await bondingCurve.beneficiary({from: miscUser})).to.be.equal(userAccounts[0]); + }); -module.exports = { - bondingCurveAdminTests -} + it('should not allow non-owner to set new beneficiary', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified( + bondingCurve.setBeneficiary(constants.ZERO_ADDRESS, { + from: miscUser + }) + ); + }); + + it('should allow owner to set new owner', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + const oldOwner = curveOwner; + const newOwner = userAccounts[0]; + + const tx = await bondingCurve.transferOwnership(newOwner, {from: oldOwner}); + + expect(await bondingCurve.owner({from: newOwner})).to.be.equal(newOwner); + }); + + it('should not allow non-owner to set new owner', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + const nonOwner = userAccounts[0]; + const newOwner = userAccounts[1]; + + await expectRevert.unspecified( + bondingCurve.transferOwnership(newOwner, { + from: nonOwner + }) + ); + }); + + it('should not allow old owner to set new beneficiary after ownership transfer', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + const oldOwner = curveOwner; + const oldBeneficiary = curveOwner; + const newOwner = userAccounts[0]; + const newBeneficiary = userAccounts[1]; + + const tx = await bondingCurve.transferOwnership(newOwner, { + from: oldOwner + }); + + let result = await bondingCurve.beneficiary({from: miscUser}); + expect(result).to.be.equal(oldBeneficiary); + + await bondingCurve.setBeneficiary(newBeneficiary, { + from: newOwner + }); + + result = await bondingCurve.beneficiary({from: miscUser}); + expect(result).to.be.equal(newBeneficiary); + }); + it('should allow owner to set new buy curve', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + const tx = await bondingCurve.setBuyCurve(constants.ZERO_ADDRESS, { + from: curveOwner + }); + expect(await bondingCurve.buyCurve({from: miscUser})).to.be.equal(constants.ZERO_ADDRESS); + }); + + it('should not allow non-owner to set new buy curve', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified( + bondingCurve.setBuyCurve(constants.ZERO_ADDRESS, { + from: miscUser + }) + ); + }); + + it('should allow owner to set new reserve percentage', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + const newReservePercentage = '20'; + + const tx = await bondingCurve.setReservePercentage(newReservePercentage, { + from: curveOwner + }); + expect(await bondingCurve.reservePercentage({from: miscUser})).to.be.bignumber.equal( + newReservePercentage + ); + }); + + it('should not allow non-owner to set new reserve percentage', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + const newReservePercentage = '20'; + + await expectRevert.unspecified( + bondingCurve.setReservePercentage(newReservePercentage, { + from: miscUser + }) + ); + }); + + it('should allow owner to set new dividend percentage', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + const newDividendPercentage = '20'; + + const tx = await bondingCurve.setDividendPercentage(newDividendPercentage, { + from: curveOwner + }); + expect(await bondingCurve.dividendPercentage({from: miscUser})).to.be.bignumber.equal( + newDividendPercentage + ); + }); + + it('should not allow non-owner to set new dividend percentage', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + const newDividendPercentage = '20'; + + await expectRevert.unspecified( + bondingCurve.setDividendPercentage(newDividendPercentage, { + from: miscUser + }) + ); + }); + }); + }); +}; + +module.exports = { + bondingCurveAdminTests +}; diff --git a/test/behaviors/bondingCurveBuySell.js b/test/behaviors/bondingCurveBuySell.js index 258bc63..3bd5a2f 100644 --- a/test/behaviors/bondingCurveBuySell.js +++ b/test/behaviors/bondingCurveBuySell.js @@ -1,12 +1,12 @@ const {BN, constants, shouldFail, expectRevert} = require('openzeppelin-test-helpers'); const {ZERO_ADDRESS} = constants; -const BondedToken = artifacts.require("BondedToken.sol"); +const BondedToken = artifacts.require('BondedToken.sol'); const expectEvent = require('../expectEvent'); -const {CurveEcosystem} = require("../helpers/CurveEcosystem"); -const {str, bn} = require("../helpers/utils"); +const {CurveEcosystem} = require('../helpers/CurveEcosystem'); +const {str, bn} = require('../helpers/utils'); // Import preferred chai flavor: both expect and should are supported const {expect} = require('chai'); @@ -14,547 +14,593 @@ const {defaultTestConfig} = require('../helpers/ecosystemConfigs'); const contractConstants = require('../constants/contractConstants'); const bondingCurveBuySellTests = async (suiteName, config) => { - contract('Bonding Curve Admin', async accounts => { - const adminAccount = accounts[0]; - const curveOwner = accounts[1]; - const tokenMinter = accounts[2]; - const userAccounts = accounts.slice(3, accounts.length); - const buyer = userAccounts[0]; - - const accountsConfig = { - adminAccount, - curveOwner, - minter: tokenMinter, - userAccounts, - buyer - } - - const userBalances = bn(100000000); - const approvalAmount = bn(100000000); - - const numTokens = bn(100000); - const expectedBuyPrice = numTokens - .mul(config.deployParams.curveLogicParams.tokenRatio) - .div(contractConstants.bondingCurve.tokenRatioPrecision); - const expectedSellReward = expectedBuyPrice - .mul(config.deployParams.curveParams.reservePercentage) - .div(bn(100)); - const maxBuyPrice = bn(0); //We don't want a max price unless we're specifically testing that - const minSellPrice = bn(0); //We don't want a min price unless we're specifically testing that - describe('Helper', async () => { - it('should show buy price correctly', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - let result = await bondingCurve.priceToBuy(numTokens, {from: buyer}); - - expect(new BN(result)).to.be.bignumber.equal(expectedBuyPrice); - }); - - it('should show sell reward correctly', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - let result = await bondingCurve - .rewardForSell(numTokens, {from: buyer}); - - expect(new BN(result)).to.be.bignumber.equal(expectedSellReward); - }); - - it('should not allow bondingCurve owner to mint bondedTokens', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await expectRevert.unspecified( - bondedToken.mint(curveOwner, 100, {from: curveOwner}) - ); - }); - - it('should not allow other addresses to mint bondedTokens', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await expectRevert.unspecified( - bondedToken.mint(userAccounts[3], 100, {from: userAccounts[3]}) - ); - }); + contract('Bonding Curve Admin', async accounts => { + const adminAccount = accounts[0]; + const curveOwner = accounts[1]; + const tokenMinter = accounts[2]; + const userAccounts = accounts.slice(3, accounts.length); + const buyer = userAccounts[0]; + + const accountsConfig = { + adminAccount, + curveOwner, + minter: tokenMinter, + userAccounts, + buyer + }; + + const userBalances = bn(100000000); + const approvalAmount = bn(100000000); + + const numTokens = bn(100000); + const expectedBuyPrice = numTokens + .mul(config.deployParams.curveLogicParams.tokenRatio) + .div(contractConstants.bondingCurve.tokenRatioPrecision); + const expectedSellReward = expectedBuyPrice + .mul(config.deployParams.curveParams.reservePercentage) + .div(bn(100)); + const maxBuyPrice = bn(0); //We don't want a max price unless we're specifically testing that + const minSellPrice = bn(0); //We don't want a min price unless we're specifically testing that + describe('Helper', async () => { + it('should show buy price correctly', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + let result = await bondingCurve.priceToBuy(numTokens, {from: buyer}); + + expect(new BN(result)).to.be.bignumber.equal(expectedBuyPrice); + }); + + it('should show sell reward correctly', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + let result = await bondingCurve.rewardForSell(numTokens, {from: buyer}); + + expect(new BN(result)).to.be.bignumber.equal(expectedSellReward); + }); + + it('should not allow bondingCurve owner to mint bondedTokens', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified(bondedToken.mint(curveOwner, 100, {from: curveOwner})); + }); + + it('should not allow other addresses to mint bondedTokens', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified( + bondedToken.mint(userAccounts[3], 100, {from: userAccounts[3]}) + ); + }); + }); + + describe('Buy Failure Cases', async () => { + it('should not allow to buy with 0 tokens specified', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified(bondingCurve.buy(0, maxBuyPrice, buyer, {from: buyer})); + }); + + it('should not allow user without collateralTokens approved to buy bondedTokens', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified( + bondingCurve.buy(numTokens, maxBuyPrice, buyer, {from: buyer}) + ); + }); + + it('should not allow buy if current price exceeds specified max price', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified( + bondingCurve.buy(numTokens, '1', buyer, { + from: buyer + }) + ); + }); + }); + + describe('Buy', async () => { + it('should not allow owner to buy when paused', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + + await bondingCurve.pause({from: curveOwner}); + await expectRevert.unspecified( + bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: curveOwner + }) + ); + }); + + it('should not allow user to buy when paused', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + + await bondingCurve.pause({from: curveOwner}); + await expectRevert.unspecified( + bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }) + ); + }); + + it('should mint bondedTokens correctly on buy', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + + const beforeBalance = new BN(await bondedToken.balanceOf(buyer, {from: buyer})); + + let tx = await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer }); - describe('Buy Failure Cases', async () => { - it('should not allow to buy with 0 tokens specified', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await expectRevert.unspecified( - bondingCurve.buy(0, maxBuyPrice, buyer, {from: buyer}) - ); - }); - - it('should not allow user without collateralTokens approved to buy bondedTokens', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await expectRevert.unspecified( - bondingCurve - .buy(numTokens, maxBuyPrice, buyer, {from: buyer}) - ); - }); - - it('should not allow buy if current price exceeds specified max price', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await expectRevert.unspecified( - bondingCurve.buy(numTokens, '1', buyer, { - from: buyer - }) - ); - }); + const afterBalance = new BN(await bondedToken.balanceOf(buyer, {from: buyer})); + + expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(numTokens)); + }); + + it('should transfer collateral tokens from buyer correctly on buy', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + + const beforeBalance = new BN(await paymentToken.balanceOf(buyer, {from: buyer})); + + let tx = await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer }); - describe('Buy', async () => { - it('should not allow owner to buy when paused', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - - await bondingCurve.pause({from: curveOwner}); - await expectRevert.unspecified( - bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: curveOwner - }) - ); - }); - - it('should not allow user to buy when paused', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - - await bondingCurve.pause({from: curveOwner}); - await expectRevert.unspecified( - bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }) - ); - }); - - it('should mint bondedTokens correctly on buy', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - - const beforeBalance = new BN( - await bondedToken.balanceOf(buyer, {from: buyer}) - ); - - let tx = await bondingCurve - .buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const afterBalance = new BN( - await bondedToken.balanceOf(buyer, {from: buyer}) - ); - - expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(numTokens)); - }); - - it('should transfer collateral tokens from buyer correctly on buy', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - - const beforeBalance = new BN( - await paymentToken.balanceOf(buyer, {from: buyer}) - ); - - let tx = await bondingCurve - .buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const afterBalance = new BN( - await paymentToken.balanceOf(buyer, {from: buyer}) - ); - - expect(afterBalance).to.be.bignumber.equal(beforeBalance.sub(expectedBuyPrice)); - }); - - it('should transfer collateral tokens to reserve correctly on buy', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - - const beforeBalance = new BN( - await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) - ); - - let tx = await bondingCurve - .buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - - const afterBalance = new BN( - await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) - ); - - const reserveAmount = tx.logs[0].args.reserveAmount; - - expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(reserveAmount)); - }); - - it('should record reserve balance correctly on buy', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - - const beforeBalance = new BN( - await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) - ); - - let tx = await bondingCurve - .buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const reserveBalance = new BN( - await bondingCurve.reserveBalance({from: buyer}) - ); - - const reserveAmount = tx.logs[0].args.reserveAmount; - - expect(reserveBalance).to.be.bignumber.equal(beforeBalance.add(reserveAmount)); - }); - - it('should transfer collateral tokens to beneficiary correctly on buy', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - const beneficiary = await bondingCurve.beneficiary(); - const beforeBalance = new BN( - await paymentToken.balanceOf(beneficiary, {from: buyer}) - ); - - let tx = await bondingCurve - .buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const event = expectEvent.inLogs(tx.events, 'Buy'); - - const afterBalance = new BN( - await paymentToken.balanceOf(beneficiary, {from: buyer}) - ); - - const beneficiaryAmount = tx.logs[0].args.beneficiaryAmount; - - expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(beneficiaryAmount)); - }); - - it('should register buy event on buy', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - - let tx = await bondingCurve - .buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - //Verify events - expectEvent.inLogs(tx.events, 'Buy', { - buyer: buyer, - recipient: buyer, - amount: numTokens - }); - }); - - it('should allow buy if current price is below max price specified', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - - let tx = await bondingCurve - .buy(numTokens, '1000000000000000000000000', buyer, { - from: buyer - }); - //Verify events - expectEvent.inLogs(tx.events, 'Buy', { - buyer: buyer, - recipient: buyer, - amount: numTokens - }); - }); - - it('should allow user to buy for a different recipient', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - - let tx = await bondingCurve - .buy(numTokens, maxBuyPrice, userAccounts[1], { - from: buyer - }); - //Verify events - expectEvent.inLogs(tx.events, 'Buy', { - buyer: buyer, - recipient: userAccounts[1], - amount: numTokens - }); - }); + const afterBalance = new BN(await paymentToken.balanceOf(buyer, {from: buyer})); + + expect(afterBalance).to.be.bignumber.equal(beforeBalance.sub(expectedBuyPrice)); + }); + + it('should transfer collateral tokens to reserve correctly on buy', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + + const beforeBalance = new BN( + await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) + ); + + let tx = await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer }); - describe('Sell Failure Cases', async () => { - it('should not allow to sell with 0 tokens specified', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await expectRevert.unspecified( - bondingCurve.sell(0, maxBuyPrice, buyer, {from: buyer}) - ); - }); - - it('should not allow user without bondedTokens to sell', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await expectRevert.unspecified( - bondingCurve - .sell(numTokens, minSellPrice, curveOwner, { - from: curveOwner - }) - ); - - await expectRevert.unspecified( - bondingCurve.sell(numTokens, minSellPrice, buyer, { - from: buyer - }) - ); - }); + const afterBalance = new BN( + await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) + ); + + const reserveAmount = tx.logs[0].args.reserveAmount; + + expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(reserveAmount)); + }); + + it('should record reserve balance correctly on buy', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + + const beforeBalance = new BN( + await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) + ); + + let tx = await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer }); - describe('Sell', async () => { - it('should not allow owner to sell when paused', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - await bondingCurve.pause({from: curveOwner}); - await expectRevert.unspecified( - bondingCurve.sell(numTokens, minSellPrice, buyer, { - from: curveOwner - }) - ); - }); - - it('should not allow user to sell when paused', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - await bondingCurve.pause({from: curveOwner}); - await expectRevert.unspecified( - bondingCurve.sell(numTokens, minSellPrice, buyer, { - from: buyer - }) - ); - }); - - it('should allow user with bondedTokens to sell all bondedTokens', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - let tx = await bondingCurve - .sell(numTokens, minSellPrice, buyer, { - from: buyer - }); - - expectEvent.inLogs(tx.events, 'Sell', { - seller: buyer, - recipient: buyer, - amount: numTokens - }); - }); - - it('should allow user with bondedTokens to sell some bondedTokens', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const tokensToSell = numTokens.div(new BN(2)); - - let tx = await bondingCurve - .sell(tokensToSell, minSellPrice, buyer, { - from: buyer - }); - - expectEvent.inLogs(tx.events, 'Sell', { - seller: buyer, - recipient: buyer, - amount: tokensToSell - }); - }); - - it('should burn tokens from seller on sell', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const beforeBalance = new BN( - await bondedToken.balanceOf(buyer, {from: buyer}) - ); - - let tx = await bondingCurve - .sell(numTokens, minSellPrice, buyer, { - from: buyer - }); - - const afterBalance = new BN( - await bondedToken.balanceOf(buyer, {from: buyer}) - ); - expect(afterBalance).to.be.bignumber.equal(beforeBalance.sub(numTokens)); - }); - - it('should transfer collateral tokens from reserve on sell', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const beforeBalance = new BN( - await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) - ); - - let tx = await bondingCurve - .sell(numTokens, minSellPrice, buyer, { - from: buyer - }); - - const afterBalance = new BN( - await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) - ); - expect(afterBalance).to.be.bignumber.equal(beforeBalance.sub(expectedSellReward)); - }); - - it('should transfer collateral tokens to seller on sell', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const beforeBalance = new BN( - await paymentToken.balanceOf(buyer, {from: buyer}) - ); - - let tx = await bondingCurve - .sell(numTokens, minSellPrice, buyer, { - from: buyer - }); - - const afterBalance = new BN( - await paymentToken.balanceOf(buyer, {from: buyer}) - ); - expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(expectedSellReward)); - }); - - it('should allow user to sell and send reward to different recipient', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const recipient = userAccounts[2]; - - const recipientBeforeBalance = new BN( - await paymentToken.balanceOf(recipient, {from: buyer}) - ); - - let tx = await bondingCurve - .sell(numTokens, minSellPrice, recipient, { - from: buyer - }); - - expectEvent.inLogs(tx.events, 'Sell', { - seller: buyer, - recipient: recipient, - amount: numTokens - }); - - const recipientAfterBalance = new BN( - await paymentToken.balanceOf(recipient, {from: buyer}) - ); - - expect(recipientAfterBalance).to.be.bignumber.above(recipientBeforeBalance); - }); - - it('should not allow sell if current reward is lower than specified min reward', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const result = await bondingCurve - .rewardForSell(numTokens, {from: buyer}); - - const rewardForSell = new BN(result); - const mulFactor = new BN(2); - - await expectRevert.unspecified( - bondingCurve - .sell(numTokens, rewardForSell.mul(mulFactor), buyer, { - from: buyer - }) - ); - }); + const reserveBalance = new BN(await bondingCurve.reserveBalance({from: buyer})); + + const reserveAmount = tx.logs[0].args.reserveAmount; + + expect(reserveBalance).to.be.bignumber.equal(beforeBalance.add(reserveAmount)); + }); + + it('should transfer collateral tokens to beneficiary correctly on buy', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + const beneficiary = await bondingCurve.beneficiary(); + const beforeBalance = new BN(await paymentToken.balanceOf(beneficiary, {from: buyer})); + + let tx = await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer }); - }) -} -module.exports = { - bondingCurveBuySellTests -} + const event = expectEvent.inLogs(tx.events, 'Buy'); + + const afterBalance = new BN(await paymentToken.balanceOf(beneficiary, {from: buyer})); + + const beneficiaryAmount = tx.logs[0].args.beneficiaryAmount; + + expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(beneficiaryAmount)); + }); + + it('should register buy event on buy', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + + let tx = await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + //Verify events + expectEvent.inLogs(tx.events, 'Buy', { + buyer: buyer, + recipient: buyer, + amount: numTokens + }); + }); + + it('should allow buy if current price is below max price specified', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + + let tx = await bondingCurve.buy(numTokens, '1000000000000000000000000', buyer, { + from: buyer + }); + //Verify events + expectEvent.inLogs(tx.events, 'Buy', { + buyer: buyer, + recipient: buyer, + amount: numTokens + }); + }); + + it('should allow user to buy for a different recipient', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + + let tx = await bondingCurve.buy(numTokens, maxBuyPrice, userAccounts[1], { + from: buyer + }); + //Verify events + expectEvent.inLogs(tx.events, 'Buy', { + buyer: buyer, + recipient: userAccounts[1], + amount: numTokens + }); + }); + }); + + describe('Sell Failure Cases', async () => { + it('should not allow to sell with 0 tokens specified', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified(bondingCurve.sell(0, maxBuyPrice, buyer, {from: buyer})); + }); + + it('should not allow user without bondedTokens to sell', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified( + bondingCurve.sell(numTokens, minSellPrice, curveOwner, { + from: curveOwner + }) + ); + + await expectRevert.unspecified( + bondingCurve.sell(numTokens, minSellPrice, buyer, { + from: buyer + }) + ); + }); + }); + + describe('Sell', async () => { + it('should not allow owner to sell when paused', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + await bondingCurve.pause({from: curveOwner}); + await expectRevert.unspecified( + bondingCurve.sell(numTokens, minSellPrice, buyer, { + from: curveOwner + }) + ); + }); + + it('should not allow user to sell when paused', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + await bondingCurve.pause({from: curveOwner}); + await expectRevert.unspecified( + bondingCurve.sell(numTokens, minSellPrice, buyer, { + from: buyer + }) + ); + }); + + it('should allow user with bondedTokens to sell all bondedTokens', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + let tx = await bondingCurve.sell(numTokens, minSellPrice, buyer, { + from: buyer + }); + expectEvent.inLogs(tx.events, 'Sell', { + seller: buyer, + recipient: buyer, + amount: numTokens + }); + }); + + it('should allow user with bondedTokens to sell some bondedTokens', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + const tokensToSell = numTokens.div(new BN(2)); + + let tx = await bondingCurve.sell(tokensToSell, minSellPrice, buyer, { + from: buyer + }); + + expectEvent.inLogs(tx.events, 'Sell', { + seller: buyer, + recipient: buyer, + amount: tokensToSell + }); + }); + + it('should burn tokens from seller on sell', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + const beforeBalance = new BN(await bondedToken.balanceOf(buyer, {from: buyer})); + + let tx = await bondingCurve.sell(numTokens, minSellPrice, buyer, { + from: buyer + }); + + const afterBalance = new BN(await bondedToken.balanceOf(buyer, {from: buyer})); + expect(afterBalance).to.be.bignumber.equal(beforeBalance.sub(numTokens)); + }); + + it('should transfer collateral tokens from reserve on sell', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + const beforeBalance = new BN( + await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) + ); + + let tx = await bondingCurve.sell(numTokens, minSellPrice, buyer, { + from: buyer + }); + + const afterBalance = new BN( + await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) + ); + expect(afterBalance).to.be.bignumber.equal(beforeBalance.sub(expectedSellReward)); + }); + + it('should transfer collateral tokens to seller on sell', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + const beforeBalance = new BN(await paymentToken.balanceOf(buyer, {from: buyer})); + + let tx = await bondingCurve.sell(numTokens, minSellPrice, buyer, { + from: buyer + }); + + const afterBalance = new BN(await paymentToken.balanceOf(buyer, {from: buyer})); + expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(expectedSellReward)); + }); + + it('should allow user to sell and send reward to different recipient', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + const recipient = userAccounts[2]; + + const recipientBeforeBalance = new BN( + await paymentToken.balanceOf(recipient, {from: buyer}) + ); + + let tx = await bondingCurve.sell(numTokens, minSellPrice, recipient, { + from: buyer + }); + + expectEvent.inLogs(tx.events, 'Sell', { + seller: buyer, + recipient: recipient, + amount: numTokens + }); + + const recipientAfterBalance = new BN( + await paymentToken.balanceOf(recipient, {from: buyer}) + ); + + expect(recipientAfterBalance).to.be.bignumber.above(recipientBeforeBalance); + }); + + it('should not allow sell if current reward is lower than specified min reward', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + const result = await bondingCurve.rewardForSell(numTokens, {from: buyer}); + + const rewardForSell = new BN(result); + const mulFactor = new BN(2); + + await expectRevert.unspecified( + bondingCurve.sell(numTokens, rewardForSell.mul(mulFactor), buyer, { + from: buyer + }) + ); + }); + }); + }); +}; + +module.exports = { + bondingCurveBuySellTests +}; diff --git a/test/behaviors/bondingCurveBuySellEther.js b/test/behaviors/bondingCurveBuySellEther.js index 365d8dd..c39545e 100644 --- a/test/behaviors/bondingCurveBuySellEther.js +++ b/test/behaviors/bondingCurveBuySellEther.js @@ -1,12 +1,12 @@ const {BN, constants, shouldFail, expectRevert} = require('openzeppelin-test-helpers'); const {ZERO_ADDRESS} = constants; -const BondedToken = artifacts.require("BondedToken.sol"); +const BondedToken = artifacts.require('BondedToken.sol'); const expectEvent = require('../expectEvent'); -const {CurveEcosystem} = require("../helpers/CurveEcosystem"); -const {str, bn, wad, MAX_UINT, WAD} = require("../helpers/utils"); +const {CurveEcosystem} = require('../helpers/CurveEcosystem'); +const {str, bn, wad, MAX_UINT, WAD} = require('../helpers/utils'); // Import preferred chai flavor: both expect and should are supported const {expect} = require('chai'); @@ -14,493 +14,517 @@ const {defaultTestConfig} = require('../helpers/ecosystemConfigs'); const contractConstants = require('../constants/contractConstants'); const bondingCurveBuySellEtherTests = async (suiteName, config) => { - contract('Bonding Curve Admin', async accounts => { - const adminAccount = accounts[0]; - const curveOwner = accounts[1]; - const tokenMinter = accounts[2]; - const userAccounts = accounts.slice(3, accounts.length); - const buyer = userAccounts[0]; - - const accountsConfig = { - adminAccount, - curveOwner, - minter: tokenMinter, - userAccounts, - buyer - } - - const userBalances = bn(100000000); - const approvalAmount = bn(100000000); - - const numTokens = bn(100000); - const expectedBuyPrice = numTokens - .mul(config.deployParams.curveLogicParams.tokenRatio) - .div(contractConstants.bondingCurve.tokenRatioPrecision); - const expectedSellReward = expectedBuyPrice - .mul(config.deployParams.curveParams.reservePercentage) - .div(bn(100)); - const maxBuyPrice = WAD; //We don't want a max price unless we're specifically testing that - const minSellPrice = bn(0); //We don't want a min price unless we're specifically testing that - describe('Helper', async () => { - it('should show buy price correctly', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - let result = await bondingCurve.priceToBuy(numTokens, {from: buyer}); - expect(new BN(result)).to.be.bignumber.equal(expectedBuyPrice); - }); - - it('should show sell reward correctly', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - let result = await bondingCurve.rewardForSell(numTokens, {from: buyer}); - expect(new BN(result)).to.be.bignumber.equal(expectedSellReward); - }); - - it('should not allow bondingCurve owner to mint bondedTokens', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await expectRevert.unspecified( - bondedToken.mint(curveOwner, 100, {from: curveOwner}) - ); - }); - - it('should not allow other addresses to mint bondedTokens', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await expectRevert.unspecified( - bondedToken.mint(userAccounts[3], 100, {from: userAccounts[3]}) - ); - }); + contract('Bonding Curve Admin', async accounts => { + const adminAccount = accounts[0]; + const curveOwner = accounts[1]; + const tokenMinter = accounts[2]; + const userAccounts = accounts.slice(3, accounts.length); + const buyer = userAccounts[0]; + + const accountsConfig = { + adminAccount, + curveOwner, + minter: tokenMinter, + userAccounts, + buyer + }; + + const userBalances = bn(100000000); + const approvalAmount = bn(100000000); + + const numTokens = bn(100000); + const expectedBuyPrice = numTokens + .mul(config.deployParams.curveLogicParams.tokenRatio) + .div(contractConstants.bondingCurve.tokenRatioPrecision); + const expectedSellReward = expectedBuyPrice + .mul(config.deployParams.curveParams.reservePercentage) + .div(bn(100)); + const maxBuyPrice = WAD; //We don't want a max price unless we're specifically testing that + const minSellPrice = bn(0); //We don't want a min price unless we're specifically testing that + describe('Helper', async () => { + it('should show buy price correctly', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + const result = await bondingCurve.priceToBuy(numTokens, {from: buyer}); + expect(new BN(result)).to.be.bignumber.equal(expectedBuyPrice); + }); + + it('should show sell reward correctly', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + const result = await bondingCurve.rewardForSell(numTokens, {from: buyer}); + expect(new BN(result)).to.be.bignumber.equal(expectedSellReward); + }); + + it('should not allow bondingCurve owner to mint bondedTokens', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified(bondedToken.mint(curveOwner, 100, {from: curveOwner})); + }); + + it('should not allow other addresses to mint bondedTokens', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified( + bondedToken.mint(userAccounts[3], 100, {from: userAccounts[3]}) + ); + }); + }); + + describe('Buy Failure Cases', async () => { + it('should not allow to buy with 0 tokens specified', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified( + bondingCurve.buy(0, maxBuyPrice, buyer, {from: buyer, value: 0}) + ); + }); + + it('should not allow user without ether sent to buy bondedTokens', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve} = await eco.init(web3); + + await expectRevert.unspecified( + bondingCurve.buy(numTokens, maxBuyPrice, buyer, {from: buyer, value: 0}) + ); + }); + + it('should not allow buy if current price exceeds specified max price', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified( + bondingCurve.buy(numTokens, '1', buyer, {from: buyer, value: WAD}) + ); + }); + + it('should not allow buy if incorrect ether sent given specified max price', async () => {}); + }); + + describe('Buy', async () => { + it('should not allow owner to buy when paused', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + + await bondingCurve.pause({from: curveOwner}); + await expectRevert.unspecified( + bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: curveOwner, + value: maxBuyPrice + }) + ); + }); + + it('should not allow user to buy when paused', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await bondingCurve.pause({from: curveOwner}); + await expectRevert.unspecified( + bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer, + value: maxBuyPrice + }) + ); + }); + + it('should have all correct side effects on successful buy', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + const beforeBalances = eco.getBalances([buyer]); + + const tx = await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer, + value: maxBuyPrice }); - describe('Buy Failure Cases', async () => { - it('should not allow to buy with 0 tokens specified', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + const afterBalances = eco.getBalances([buyer]); + const reserveAmount = tx.logs[0].args.reserveAmount; - await expectRevert.unspecified( - bondingCurve.buy(0, maxBuyPrice, buyer, {from: buyer, value: 0}) - ); - }); + expect(afterBalances[buyer].bondedToken).to.be.bignumber.equal( + beforeBalances[buyer].bondedToken.add(numTokens), + 'should mint bondedTokens correctly on buy' + ); - it('should not allow user without ether sent to buy bondedTokens', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve} = await eco.init(web3); + expect(afterBalances.bondingCurve.ether).to.be.bignumber.equal( + beforeBalances.bondingCurve.ether.add(reserveAmount), + 'should transfer ether to reserve correctly on buy' + ); - await expectRevert.unspecified( - bondingCurve.buy(numTokens, maxBuyPrice, buyer, {from: buyer, value: 0}) - ); - }); + expect(afterBalances[buyer].ether).to.be.bignumber.equal( + beforeBalances[buyer].ether.sub(expectedBuyPrice), + 'should transfer proper ether amount from buyer on buy' + ); + }); - it('should not allow buy if current price exceeds specified max price', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + it('should record reserve balance correctly on buy', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await expectRevert.unspecified( - bondingCurve.buy(numTokens, '1', buyer, {from: buyer, value: WAD}) - ); - }); + const beforeBalance = new BN( + await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) + ); - it('should not allow buy if incorrect ether sent given specified max price', async function () { - }); + const tx = await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer, + value: maxBuyPrice }); - describe('Buy', async () => { - it('should not allow owner to buy when paused', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - - await bondingCurve.pause({from: curveOwner}); - await expectRevert.unspecified( - bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: curveOwner, - value: maxBuyPrice - }) - ); - }); - - it('should not allow user to buy when paused', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await bondingCurve.pause({from: curveOwner}); - await expectRevert.unspecified( - bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer, - value: maxBuyPrice - }) - ); - }); - - it('should have all correct side effects on successful buy', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - const beforeBalances = eco.getBalances([buyer]); - - let tx = await bondingCurve - .buy(numTokens, maxBuyPrice, buyer, { - from: buyer, - value: maxBuyPrice - }); - - const afterBalances = eco.getBalances([buyer]); - const reserveAmount = tx.logs[0].args.reserveAmount; - - expect(afterBalances[buyer].bondedToken).to.be.bignumber.equal(beforeBalances[buyer].bondedToken.add(numTokens), 'should mint bondedTokens correctly on buy'); - - expect(afterBalances.bondingCurve.ether).to.be.bignumber.equal(beforeBalances.bondingCurve.ether.add(reserveAmount), 'should transfer ether to reserve correctly on buy'); - - expect(afterBalances[buyer].ether).to.be.bignumber.equal(beforeBalances[buyer].ether.sub(expectedBuyPrice), 'should transfer proper ether amount from buyer on buy'); - }); - - it('should record reserve balance correctly on buy', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - const beforeBalance = new BN( - await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) - ); - - let tx = await bondingCurve - .buy(numTokens, maxBuyPrice, buyer, { - from: buyer, - value: maxBuyPrice - }); - - const reserveBalance = new BN( - await bondingCurve.reserveBalance({from: buyer}) - ); - - const reserveAmount = tx.logs[0].args.reserveAmount; - - expect(reserveBalance).to.be.bignumber.equal(beforeBalance.add(reserveAmount)); - }); - - it('should transfer collateral tokens to beneficiary correctly on buy', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - const beneficiary = await bondingCurve.beneficiary(); - const beforeBalance = new BN( - await paymentToken.balanceOf(beneficiary, {from: buyer}) - ); - - let tx = await bondingCurve - .buy(numTokens, maxBuyPrice, buyer, { - from: buyer, - value: maxBuyPrice - }); - - const event = expectEvent.inLogs(tx.events, 'Buy'); - - const afterBalance = new BN( - await paymentToken.balanceOf(beneficiary, {from: buyer}) - ); - - const beneficiaryAmount = tx.logs[0].args.beneficiaryAmount; - - expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(beneficiaryAmount)); - }); - - it('should register buy event on buy', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - let tx = await bondingCurve - .buy(numTokens, maxBuyPrice, buyer, { - from: buyer, - value: maxBuyPrice - }); - //Verify events - expectEvent.inLogs(tx.events, 'Buy', { - buyer: buyer, - recipient: buyer, - amount: numTokens - }); - }); - - it('should allow buy if current price is below max price specified', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - let tx = await bondingCurve - .buy(numTokens, '1000000000000000000000000', buyer, { - from: buyer, - value: maxBuyPrice - }); - //Verify events - expectEvent.inLogs(tx.events, 'Buy', { - buyer: buyer, - recipient: buyer, - amount: numTokens - }); - }); - - it('should allow user to buy for a different recipient', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - let tx = await bondingCurve - .buy(numTokens, maxBuyPrice, userAccounts[1], { - from: buyer, - value: maxBuyPrice - }); - //Verify events - expectEvent.inLogs(tx.events, 'Buy', { - buyer: buyer, - recipient: userAccounts[1], - amount: numTokens - }); - }); + const reserveBalance = new BN(await bondingCurve.reserveBalance({from: buyer})); + + const reserveAmount = tx.logs[0].args.reserveAmount; + + expect(reserveBalance).to.be.bignumber.equal(beforeBalance.add(reserveAmount)); + }); + + it('should transfer collateral tokens to beneficiary correctly on buy', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + const beneficiary = await bondingCurve.beneficiary(); + const beforeBalance = new BN(await paymentToken.balanceOf(beneficiary, {from: buyer})); + + const tx = await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer, + value: maxBuyPrice }); - describe('Sell Failure Cases', async () => { - it('should not allow to sell with 0 tokens specified', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await expectRevert.unspecified( - bondingCurve.sell(0, maxBuyPrice, buyer, {from: buyer}) - ); - }); - - it('should not allow user without bondedTokens to sell', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await expectRevert.unspecified( - bondingCurve - .sell(numTokens, minSellPrice, curveOwner, { - from: curveOwner - }) - ); - - await expectRevert.unspecified( - bondingCurve.sell(numTokens, minSellPrice, buyer, { - from: buyer - }) - ); - }); + const event = expectEvent.inLogs(tx.events, 'Buy'); + + const afterBalance = new BN(await paymentToken.balanceOf(beneficiary, {from: buyer})); + + const beneficiaryAmount = tx.logs[0].args.beneficiaryAmount; + + expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(beneficiaryAmount)); + }); + + it('should register buy event on buy', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + const tx = await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer, + value: maxBuyPrice }); + //Verify events + expectEvent.inLogs(tx.events, 'Buy', { + buyer: buyer, + recipient: buyer, + amount: numTokens + }); + }); + + it('should allow buy if current price is below max price specified', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - describe('Sell', async () => { - it('should not allow owner to sell when paused', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - await bondingCurve.pause({from: curveOwner}); - await expectRevert.unspecified( - bondingCurve.sell(numTokens, minSellPrice, buyer, { - from: curveOwner - }) - ); - }); - - it('should not allow user to sell when paused', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - await bondingCurve.pause({from: curveOwner}); - await expectRevert.unspecified( - bondingCurve.sell(numTokens, minSellPrice, buyer, { - from: buyer - }) - ); - }); - - it('should allow user with bondedTokens to sell all bondedTokens', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - let tx = await bondingCurve - .sell(numTokens, minSellPrice, buyer, { - from: buyer - }); - - expectEvent.inLogs(tx.events, 'Sell', { - seller: buyer, - recipient: buyer, - amount: numTokens - }); - }); - - it('should allow user with bondedTokens to sell some bondedTokens', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const tokensToSell = numTokens.div(new BN(2)); - - let tx = await bondingCurve - .sell(tokensToSell, minSellPrice, buyer, { - from: buyer - }); - - expectEvent.inLogs(tx.events, 'Sell', { - seller: buyer, - recipient: buyer, - amount: tokensToSell - }); - }); - - it('should burn tokens from seller on sell', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const beforeBalance = new BN( - await bondedToken.balanceOf(buyer, {from: buyer}) - ); - - let tx = await bondingCurve - .sell(numTokens, minSellPrice, buyer, { - from: buyer - }); - - const afterBalance = new BN( - await bondedToken.balanceOf(buyer, {from: buyer}) - ); - expect(afterBalance).to.be.bignumber.equal(beforeBalance.sub(numTokens)); - }); - - it('should transfer collateral tokens from reserve on sell', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const beforeBalance = new BN( - await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) - ); - - let tx = await bondingCurve - .sell(numTokens, minSellPrice, buyer, { - from: buyer - }); - - const afterBalance = new BN( - await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) - ); - expect(afterBalance).to.be.bignumber.equal(beforeBalance.sub(expectedSellReward)); - }); - - it('should transfer collateral tokens to seller on sell', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const beforeBalance = new BN( - await paymentToken.balanceOf(buyer, {from: buyer}) - ); - - let tx = await bondingCurve - .sell(numTokens, minSellPrice, buyer, { - from: buyer - }); - - const afterBalance = new BN( - await paymentToken.balanceOf(buyer, {from: buyer}) - ); - expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(expectedSellReward)); - }); - - it('should allow user to sell and send reward to different recipient', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const recipient = userAccounts[2]; - - const recipientBeforeBalance = new BN( - await paymentToken.balanceOf(recipient, {from: buyer}) - ); - - let tx = await bondingCurve - .sell(numTokens, minSellPrice, recipient, { - from: buyer - }); - - expectEvent.inLogs(tx.events, 'Sell', { - seller: buyer, - recipient: recipient, - amount: numTokens - }); - - const recipientAfterBalance = new BN( - await paymentToken.balanceOf(recipient, {from: buyer}) - ); - - expect(recipientAfterBalance).to.be.bignumber.above(recipientBeforeBalance); - }); - - it('should not allow sell if current reward is lower than specified min reward', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, buyer], approvalAmount); - await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { - from: buyer - }); - - const result = await bondingCurve - .rewardForSell(numTokens, {from: buyer}); - - const rewardForSell = new BN(result); - const mulFactor = new BN(2); - - await expectRevert.unspecified( - bondingCurve - .sell(numTokens, rewardForSell.mul(mulFactor), buyer, { - from: buyer - }) - ); - }); + const tx = await bondingCurve.buy(numTokens, '1000000000000000000000000', buyer, { + from: buyer, + value: maxBuyPrice }); - }) -} + //Verify events + expectEvent.inLogs(tx.events, 'Buy', { + buyer: buyer, + recipient: buyer, + amount: numTokens + }); + }); -module.exports = { - bondingCurveBuySellEtherTests -} + it('should allow user to buy for a different recipient', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + const tx = await bondingCurve.buy(numTokens, maxBuyPrice, userAccounts[1], { + from: buyer, + value: maxBuyPrice + }); + //Verify events + expectEvent.inLogs(tx.events, 'Buy', { + buyer: buyer, + recipient: userAccounts[1], + amount: numTokens + }); + }); + }); + + describe('Sell Failure Cases', async () => { + it('should not allow to sell with 0 tokens specified', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified(bondingCurve.sell(0, maxBuyPrice, buyer, {from: buyer})); + }); + + it('should not allow user without bondedTokens to sell', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified( + bondingCurve.sell(numTokens, minSellPrice, curveOwner, { + from: curveOwner + }) + ); + + await expectRevert.unspecified( + bondingCurve.sell(numTokens, minSellPrice, buyer, { + from: buyer + }) + ); + }); + }); + + describe('Sell', async () => { + it('should not allow owner to sell when paused', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + await bondingCurve.pause({from: curveOwner}); + await expectRevert.unspecified( + bondingCurve.sell(numTokens, minSellPrice, buyer, { + from: curveOwner + }) + ); + }); + + it('should not allow user to sell when paused', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + await bondingCurve.pause({from: curveOwner}); + await expectRevert.unspecified( + bondingCurve.sell(numTokens, minSellPrice, buyer, { + from: buyer + }) + ); + }); + + it('should allow user with bondedTokens to sell all bondedTokens', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + const tx = await bondingCurve.sell(numTokens, minSellPrice, buyer, { + from: buyer + }); + + expectEvent.inLogs(tx.events, 'Sell', { + seller: buyer, + recipient: buyer, + amount: numTokens + }); + }); + + it('should allow user with bondedTokens to sell some bondedTokens', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + const tokensToSell = numTokens.div(new BN(2)); + + const tx = await bondingCurve.sell(tokensToSell, minSellPrice, buyer, { + from: buyer + }); + expectEvent.inLogs(tx.events, 'Sell', { + seller: buyer, + recipient: buyer, + amount: tokensToSell + }); + }); + + it('should burn tokens from seller on sell', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + const beforeBalance = new BN(await bondedToken.balanceOf(buyer, {from: buyer})); + + const tx = await bondingCurve.sell(numTokens, minSellPrice, buyer, { + from: buyer + }); + + const afterBalance = new BN(await bondedToken.balanceOf(buyer, {from: buyer})); + expect(afterBalance).to.be.bignumber.equal(beforeBalance.sub(numTokens)); + }); + + it('should transfer collateral tokens from reserve on sell', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + const beforeBalance = new BN( + await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) + ); + + const tx = await bondingCurve.sell(numTokens, minSellPrice, buyer, { + from: buyer + }); + + const afterBalance = new BN( + await paymentToken.balanceOf(bondingCurve.address, {from: buyer}) + ); + expect(afterBalance).to.be.bignumber.equal(beforeBalance.sub(expectedSellReward)); + }); + + it('should transfer collateral tokens to seller on sell', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + const beforeBalance = new BN(await paymentToken.balanceOf(buyer, {from: buyer})); + + const tx = await bondingCurve.sell(numTokens, minSellPrice, buyer, { + from: buyer + }); + + const afterBalance = new BN(await paymentToken.balanceOf(buyer, {from: buyer})); + expect(afterBalance).to.be.bignumber.equal(beforeBalance.add(expectedSellReward)); + }); + + it('should allow user to sell and send reward to different recipient', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + const recipient = userAccounts[2]; + + const recipientBeforeBalance = new BN( + await paymentToken.balanceOf(recipient, {from: buyer}) + ); + + const tx = await bondingCurve.sell(numTokens, minSellPrice, recipient, { + from: buyer + }); + + expectEvent.inLogs(tx.events, 'Sell', { + seller: buyer, + recipient: recipient, + amount: numTokens + }); + + const recipientAfterBalance = new BN( + await paymentToken.balanceOf(recipient, {from: buyer}) + ); + + expect(recipientAfterBalance).to.be.bignumber.above(recipientBeforeBalance); + }); + + it('should not allow sell if current reward is lower than specified min reward', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint(paymentToken, tokenMinter, [curveOwner, buyer], userBalances); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, buyer], + approvalAmount + ); + await bondingCurve.buy(numTokens, maxBuyPrice, buyer, { + from: buyer + }); + + const result = await bondingCurve.rewardForSell(numTokens, {from: buyer}); + + const rewardForSell = new BN(result); + const mulFactor = new BN(2); + + await expectRevert.unspecified( + bondingCurve.sell(numTokens, rewardForSell.mul(mulFactor), buyer, { + from: buyer + }) + ); + }); + }); + }); +}; + +module.exports = { + bondingCurveBuySellEtherTests +}; diff --git a/test/behaviors/bondingCurveDeploy.js b/test/behaviors/bondingCurveDeploy.js index 740ef92..47a9786 100644 --- a/test/behaviors/bondingCurveDeploy.js +++ b/test/behaviors/bondingCurveDeploy.js @@ -1,67 +1,56 @@ const {BN, constants, shouldFail, expectRevert} = require('openzeppelin-test-helpers'); -const {ZERO_ADDRESS} = constants; - -const BondedToken = artifacts.require("BondedToken.sol"); - -const expectEvent = require('../expectEvent'); - -const {CurveEcosystem} = require("../helpers/CurveEcosystem"); -const {str, bn} = require("../helpers/utils"); - -// Import preferred chai flavor: both expect and should are supported const {expect} = require('chai'); +const BondedToken = artifacts.require('BondedToken.sol'); +const expectEvent = require('../expectEvent'); +const {CurveEcosystem} = require('../helpers/CurveEcosystem'); +const {str, bn} = require('../helpers/utils'); const {defaultTestConfig} = require('../helpers/ecosystemConfigs'); -const bondingCurveDeployTests = async (suiteName, config) => { - contract('Bonding Curve Admin', async accounts => { - const adminAccount = accounts[0]; - const curveOwner = accounts[1]; - const tokenMinter = accounts[2]; - const userAccounts = accounts.slice(3, accounts.length); - const miscUser = userAccounts[0]; - - const accountsConfig = { - adminAccount, - curveOwner, - minter: tokenMinter, - userAccounts, - miscUser - } - - describe("", async () => { - it('should have properly initialized parameters', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); +const {ZERO_ADDRESS} = constants; - expect(await bondingCurve.owner({from: miscUser})).to.be.equal(curveOwner); - expect(await bondingCurve.beneficiary({from: miscUser})).to.be.equal( - curveOwner - ); - expect(await bondingCurve.collateralToken({from: miscUser})).to.be.equal( - paymentToken.address - ); - expect(await bondingCurve.bondedToken({from: miscUser})).to.be.equal( - bondedToken.address - ); - expect(await bondingCurve.buyCurve({from: miscUser})).to.be.equal( - buyCurve.address - ); - expect( - new BN(await bondingCurve.reservePercentage({from: miscUser})) - ).to.be.bignumber.equal(config.deployParams.curveParams.reservePercentage); - expect( - new BN(await bondingCurve.dividendPercentage({from: miscUser})) - ).to.be.bignumber.equal(config.deployParams.curveParams.dividendPercentage); - expect(await bondingCurve.reserveBalance({from: miscUser})).to.be.bignumber.equal('0'); - expect(await bondingCurve.getPaymentThreshold({from: miscUser})).to.be.bignumber.equal( - '100' - ); - }); - }) - }) -} +const bondingCurveDeployTests = async (suiteName, config) => { + contract('Bonding Curve Admin', async accounts => { + const adminAccount = accounts[0]; + const curveOwner = accounts[1]; + const tokenMinter = accounts[2]; + const userAccounts = accounts.slice(3, accounts.length); + const miscUser = userAccounts[0]; + + const accountsConfig = { + adminAccount, + curveOwner, + minter: tokenMinter, + userAccounts, + miscUser + }; + + describe('', async () => { + it('should have properly initialized parameters', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + expect(await bondingCurve.owner({from: miscUser})).to.be.equal(curveOwner); + expect(await bondingCurve.beneficiary({from: miscUser})).to.be.equal(curveOwner); + expect(await bondingCurve.collateralToken({from: miscUser})).to.be.equal( + paymentToken.address + ); + expect(await bondingCurve.bondedToken({from: miscUser})).to.be.equal(bondedToken.address); + expect(await bondingCurve.buyCurve({from: miscUser})).to.be.equal(buyCurve.address); + expect( + new BN(await bondingCurve.reservePercentage({from: miscUser})) + ).to.be.bignumber.equal(config.deployParams.curveParams.reservePercentage); + expect( + new BN(await bondingCurve.dividendPercentage({from: miscUser})) + ).to.be.bignumber.equal(config.deployParams.curveParams.dividendPercentage); + expect(await bondingCurve.reserveBalance({from: miscUser})).to.be.bignumber.equal('0'); + expect(await bondingCurve.getPaymentThreshold({from: miscUser})).to.be.bignumber.equal( + '100' + ); + }); + }); + }); +}; module.exports = { - bondingCurveDeployTests -} - + bondingCurveDeployTests +}; diff --git a/test/behaviors/bondingCurvePayment.js b/test/behaviors/bondingCurvePayment.js index 8d6e8ce..cf5339e 100644 --- a/test/behaviors/bondingCurvePayment.js +++ b/test/behaviors/bondingCurvePayment.js @@ -1,172 +1,211 @@ const {BN, constants, shouldFail, expectRevert} = require('openzeppelin-test-helpers'); const {ZERO_ADDRESS} = constants; -const BondedToken = artifacts.require("BondedToken.sol"); +const BondedToken = artifacts.require('BondedToken.sol'); const expectEvent = require('../expectEvent'); -const {CurveEcosystem} = require("../helpers/CurveEcosystem"); -const {str, bn} = require("../helpers/utils"); +const {CurveEcosystem} = require('../helpers/CurveEcosystem'); +const {str, bn} = require('../helpers/utils'); // Import preferred chai flavor: both expect and should are supported const {expect} = require('chai'); const {defaultTestConfig} = require('../helpers/ecosystemConfigs'); const bondingCurvePaymentTests = async (suiteName, config) => { - contract('Bonding Curve Admin', async accounts => { - const adminAccount = accounts[0]; - const curveOwner = accounts[1]; - const tokenMinter = accounts[2]; - const userAccounts = accounts.slice(3, accounts.length); - const miscUser = userAccounts[0]; - - const accountsConfig = { - adminAccount, - curveOwner, - minter: tokenMinter, - userAccounts, - miscUser - } - - describe('Payments', async () => { - const miscUser = userAccounts[0]; - - const userBalances = bn(100000); - const paymentAmount = bn(10000); - - it('should not allow payments of amount 0', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - - await expectRevert.unspecified(bondingCurve.pay(0, {from: curveOwner})); - }); - - it('should register payments', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, accountsConfig.minter, [curveOwner, miscUser], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, miscUser], paymentAmount); - - let tx = await bondingCurve.pay(paymentAmount, {from: miscUser}); - - expectEvent.inLogs(tx.events, 'Pay', { - from: miscUser, - token: paymentToken.address, - amount: paymentAmount - }); - - tx = await bondingCurve.pay(paymentAmount, {from: curveOwner}); - - expectEvent.inLogs(tx.events, 'Pay', { - from: curveOwner, - token: paymentToken.address, - amount: paymentAmount - }); - }); - - it('should not allow pay with greater amount than senders balance', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, accountsConfig.minter, [curveOwner, miscUser], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, miscUser], paymentAmount); - - const exceededBalance = userBalances.add(userBalances); - - await expectRevert.unspecified( - bondingCurve.pay(exceededBalance, { - from: miscUser - }) - ); - await expectRevert.unspecified( - bondingCurve.pay(exceededBalance, { - from: curveOwner - }) - ); - }); - - describe('Beneficiary / Dividend Split', async () => { - const maxPercentage = new BN(100); - const dividendSplit = maxPercentage.sub(config.deployParams.curveParams.dividendPercentage); - const expectedBeneficiaryAmount = paymentAmount - .mul(config.deployParams.curveParams.dividendPercentage) - .div(maxPercentage); - const expectedDividendAmount = paymentAmount.mul(dividendSplit).div(maxPercentage); - - it('should register correct split between beneficiary and dividend pool from non-curve owner', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, accountsConfig.minter, [curveOwner, miscUser], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, miscUser], paymentAmount); - - let tx = await bondingCurve.pay(paymentAmount, {from: miscUser}); - - expectEvent.inLogs(tx.events, 'Pay', { - from: miscUser, - token: paymentToken.address, - amount: paymentAmount, - beneficiaryAmount: expectedBeneficiaryAmount, - dividendAmount: expectedDividendAmount - }); - }); - - it('should register correct split between beneficiary and dividend pool from curve owner', async function () { - const eco = new CurveEcosystem(accountsConfig, config); - const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); - await eco.bulkMint(paymentToken, accountsConfig.minter, [curveOwner, miscUser], userBalances); - await eco.bulkApprove(paymentToken, bondingCurve.address, [curveOwner, miscUser], paymentAmount); - - let tx = await bondingCurve.pay(paymentAmount, { - from: curveOwner - }); - - expectEvent.inLogs(tx.events, 'Pay', { - from: curveOwner, - token: paymentToken.address, - amount: paymentAmount, - beneficiaryAmount: expectedBeneficiaryAmount, - dividendAmount: expectedDividendAmount - }); - }); - - // it('should transfer correct token amounts between beneficiary and dividend pool', async function() { - // const beneficiaryBeforeBalance = new BN( - // await paymentToken.balanceOf(curveOwner).call({from: miscUser}) - // ); - - // const dividendBeforeBalance = new BN( - // await paymentToken.balanceOf(dividendPool.address).call({from: miscUser}) - // ); - - // let tx = await bondingCurve.pay(paymentAmount, { - // from: miscUser - // }); - // const event = expectEvent.inLogs(tx.events, 'Pay'); - - // const beneficiaryAfterBalance = new BN( - // await paymentToken.balanceOf(curveOwner).call({from: miscUser}) - // ); - - // const dividendAfterBalance = new BN( - // await paymentToken.balanceOf(dividendPool.address).call({from: miscUser}) - // ); - - // const beneficiaryAmount = new BN(expectEvent.getParameter(event, 'beneficiaryAmount')); - // const dividendAmount = new BN(expectEvent.getParameter(event, 'dividendAmount')); - - // expect(beneficiaryAmount).to.be.bignumber.equal( - // beneficiaryAfterBalance.sub(beneficiaryBeforeBalance) - // ); - - // expect(dividendAmount).to.be.bignumber.equal( - // dividendAfterBalance.sub(dividendBeforeBalance) - // ); - // }); - }); + contract('Bonding Curve Admin', async accounts => { + const adminAccount = accounts[0]; + const curveOwner = accounts[1]; + const tokenMinter = accounts[2]; + const userAccounts = accounts.slice(3, accounts.length); + const miscUser = userAccounts[0]; + + const accountsConfig = { + adminAccount, + curveOwner, + minter: tokenMinter, + userAccounts, + miscUser + }; + + describe('Payments', async () => { + const miscUser = userAccounts[0]; + + const userBalances = bn(100000); + const paymentAmount = bn(10000); + + it('should not allow payments of amount 0', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + + await expectRevert.unspecified(bondingCurve.pay(0, {from: curveOwner})); + }); + + it('should register payments', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint( + paymentToken, + accountsConfig.minter, + [curveOwner, miscUser], + userBalances + ); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, miscUser], + paymentAmount + ); + + let tx = await bondingCurve.pay(paymentAmount, {from: miscUser}); + + expectEvent.inLogs(tx.events, 'Pay', { + from: miscUser, + token: paymentToken.address, + amount: paymentAmount }); - }) -} -module.exports = { - bondingCurvePaymentTests -} + tx = await bondingCurve.pay(paymentAmount, {from: curveOwner}); + + expectEvent.inLogs(tx.events, 'Pay', { + from: curveOwner, + token: paymentToken.address, + amount: paymentAmount + }); + }); + + it('should not allow pay with greater amount than senders balance', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint( + paymentToken, + accountsConfig.minter, + [curveOwner, miscUser], + userBalances + ); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, miscUser], + paymentAmount + ); + + const exceededBalance = userBalances.add(userBalances); + + await expectRevert.unspecified( + bondingCurve.pay(exceededBalance, { + from: miscUser + }) + ); + await expectRevert.unspecified( + bondingCurve.pay(exceededBalance, { + from: curveOwner + }) + ); + }); + + describe('Beneficiary / Dividend Split', async () => { + const maxPercentage = new BN(100); + const dividendSplit = maxPercentage.sub(config.deployParams.curveParams.dividendPercentage); + const expectedBeneficiaryAmount = paymentAmount + .mul(config.deployParams.curveParams.dividendPercentage) + .div(maxPercentage); + const expectedDividendAmount = paymentAmount.mul(dividendSplit).div(maxPercentage); + + it('should register correct split between beneficiary and dividend pool from non-curve owner', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint( + paymentToken, + accountsConfig.minter, + [curveOwner, miscUser], + userBalances + ); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, miscUser], + paymentAmount + ); + + let tx = await bondingCurve.pay(paymentAmount, {from: miscUser}); + + expectEvent.inLogs(tx.events, 'Pay', { + from: miscUser, + token: paymentToken.address, + amount: paymentAmount, + beneficiaryAmount: expectedBeneficiaryAmount, + dividendAmount: expectedDividendAmount + }); + }); + + it('should register correct split between beneficiary and dividend pool from curve owner', async () => { + const eco = new CurveEcosystem(accountsConfig, config); + const {bondingCurve, paymentToken, bondedToken, buyCurve} = await eco.init(web3); + await eco.bulkMint( + paymentToken, + accountsConfig.minter, + [curveOwner, miscUser], + userBalances + ); + await eco.bulkApprove( + paymentToken, + bondingCurve.address, + [curveOwner, miscUser], + paymentAmount + ); + + let tx = await bondingCurve.pay(paymentAmount, { + from: curveOwner + }); + + expectEvent.inLogs(tx.events, 'Pay', { + from: curveOwner, + token: paymentToken.address, + amount: paymentAmount, + beneficiaryAmount: expectedBeneficiaryAmount, + dividendAmount: expectedDividendAmount + }); + }); + + // it('should transfer correct token amounts between beneficiary and dividend pool', async () => { + // const beneficiaryBeforeBalance = new BN( + // await paymentToken.balanceOf(curveOwner).call({from: miscUser}) + // ); + + // const dividendBeforeBalance = new BN( + // await paymentToken.balanceOf(dividendPool.address).call({from: miscUser}) + // ); + // let tx = await bondingCurve.pay(paymentAmount, { + // from: miscUser + // }); + // const event = expectEvent.inLogs(tx.events, 'Pay'); + + // const beneficiaryAfterBalance = new BN( + // await paymentToken.balanceOf(curveOwner).call({from: miscUser}) + // ); + + // const dividendAfterBalance = new BN( + // await paymentToken.balanceOf(dividendPool.address).call({from: miscUser}) + // ); + + // const beneficiaryAmount = new BN(expectEvent.getParameter(event, 'beneficiaryAmount')); + // const dividendAmount = new BN(expectEvent.getParameter(event, 'dividendAmount')); + + // expect(beneficiaryAmount).to.be.bignumber.equal( + // beneficiaryAfterBalance.sub(beneficiaryBeforeBalance) + // ); + + // expect(dividendAmount).to.be.bignumber.equal( + // dividendAfterBalance.sub(dividendBeforeBalance) + // ); + // }); + }); + }); + }); +}; + +module.exports = { + bondingCurvePaymentTests +}; diff --git a/test/constants/bancorValues.js b/test/constants/bancorValues.js index fb563d8..bdb1991 100644 --- a/test/constants/bancorValues.js +++ b/test/constants/bancorValues.js @@ -1,4 +1,4 @@ -const { BN } = require("openzeppelin-test-helpers"); +const {BN} = require('openzeppelin-test-helpers'); module.exports.values = [ { diff --git a/test/helpers/CurveEcosystem.js b/test/helpers/CurveEcosystem.js index 8a82b5c..19d047e 100644 --- a/test/helpers/CurveEcosystem.js +++ b/test/helpers/CurveEcosystem.js @@ -1,5 +1,5 @@ -const {CurveLogicType, TokenType} = require("../helpers/CurveEcosystemConfig"); -const {bn} = require("./utils"); +const {CurveLogicType, TokenType} = require('../helpers/CurveEcosystemConfig'); +const {bn} = require('./utils'); const {constants} = require('openzeppelin-test-helpers'); const {ZERO_ADDRESS} = constants; const deploy = require('../../index.js'); @@ -13,227 +13,234 @@ const BancorCurveLogic = artifacts.require('BancorCurveLogic.sol'); const StaticCurveLogic = artifacts.require('StaticCurveLogic.sol'); class CurveEcosystem { - constructor(accounts, config) { - this.config = config; - this.accounts = accounts; - this.contracts = {}; + constructor(accounts, config) { + this.config = config; + this.accounts = accounts; + this.contracts = {}; + } + + async initStaticEther() {} + + async initStaticERC20() {} + + async initBancorEther() {} + + async initBancorERC20() {} + + async deployStaticCurveLogic() { + const {curveLogicParams} = this.config.deployParams; + const buyCurve = await StaticCurveLogic.new(); + await buyCurve.initialize(curveLogicParams.tokenRatio); + return buyCurve; + } + + async deployBancorCurveLogic() { + const {curveLogicParams} = this.config.deployParams; + + const bancorService = await BancorCurveService.new(); + await bancorService.initialize(); + const buyCurve = await BancorCurveLogic.new(); + await BancorCurveLogic.initialize(bancorService.address, curveLogicParams.reserveRatio); + + return { + bancorService, + buyCurve + }; + } + + async deployPaymentToken() { + const {accounts} = this; + const {collateralTokenParams} = this.config.deployParams; + + const paymentToken = await BondedToken.new(); + await paymentToken.initialize( + collateralTokenParams.name, + collateralTokenParams.symbol, + collateralTokenParams.decimals, + accounts.minter, + ZERO_ADDRESS, + ZERO_ADDRESS + ); + + const paymentTokenInitialBalance = bn(web3.utils.toWei('60000', 'ether')); + + await paymentToken.contract.methods + .mint(accounts.minter, paymentTokenInitialBalance.toString()) + .send({from: accounts.minter}); + + return paymentToken; + } + + async init(web3) { + const {collateralType, curveLogicType} = this.config.deployParams; + if (collateralType === TokenType.ETHER) { + return this.initEther(); } - async initStaticEther() { - - } - - async initStaticERC20() { - - } - - async initBancorEther() { - - } - - async initBancorERC20() { - - } - - async deployStaticCurveLogic() { - const {curveLogicParams} = this.config.deployParams; - const buyCurve = await StaticCurveLogic.new(); - await buyCurve.initialize(curveLogicParams.tokenRatio); - return buyCurve; - } - - async deployBancorCurveLogic() { - const {curveLogicParams} = this.config.deployParams; - - const bancorService = await BancorCurveService.new(); - await bancorService.initialize(); - const buyCurve = await BancorCurveLogic.new() - await BancorCurveLogic.initialize(bancorService.address, - curveLogicParams.reserveRatio); - - return { - bancorService, - buyCurve - } - } - - async deployPaymentToken() { - const accounts = this.accounts; - const {collateralTokenParams} = this.config.deployParams; - - const paymentToken = await BondedToken.new(); - await paymentToken.initialize( - collateralTokenParams.name, - collateralTokenParams.symbol, - collateralTokenParams.decimals, - accounts.minter, - ZERO_ADDRESS, - ZERO_ADDRESS); - - const paymentTokenInitialBalance = bn(web3.utils.toWei('60000', 'ether')); - - await paymentToken.contract.methods - .mint(accounts.minter, paymentTokenInitialBalance.toString()) - .send({from: accounts.minter}); - - return paymentToken; + if (collateralType === TokenType.ERC20) { + return this.initERC20(web3); } - - async init(web3) { - const {collateralType, curveLogicType} = this.config.deployParams; - if (collateralType === TokenType.ETHER) { - return await this.initEther(); - } - - if (collateralType === TokenType.ERC20) { - return await this.initERC20(web3); - } + } + + async initEther() { + const {accounts} = this; + const {curveParams, curveLogicType, bondedTokenParams} = this.config.deployParams; + + const rewardsDistributor = await RewardsDistributor.new(); + await rewardsDistributor.initialize(accounts.curveOwner); + + const bondedToken = await BondedToken.new(); + await bondedToken.initialize( + bondedTokenParams.name, + bondedTokenParams.symbol, + bondedTokenParams.decimals, + accounts.minter, + rewardsDistributor.address, + ZERO_ADDRESS + ); + + await rewardsDistributor.contract.methods + .transferOwnership(bondedToken.address) + .send({from: accounts.curveOwner}); + + let buyCurve; + + if (curveLogicType === CurveLogicType.CONSTANT) { + buyCurve = await this.deployStaticCurveLogic(); + } else if (curveLogicType === CurveLogicType.BANCOR) { + buyCurve = await (await this.deployBancorCurveLogic()).buyCurve; } - async initEther() { - const accounts = this.accounts; - const {curveParams, curveLogicType, bondedTokenParams} = this.config.deployParams; - - const rewardsDistributor = await RewardsDistributor.new(); - await rewardsDistributor.initialize(accounts.curveOwner); - - const bondedToken = await BondedToken.new(); - await bondedToken.initialize( - bondedTokenParams.name, - bondedTokenParams.symbol, - bondedTokenParams.decimals, - accounts.minter, - rewardsDistributor.address, - ZERO_ADDRESS); - - await rewardsDistributor.contract.methods - .transferOwnership(bondedToken.address) - .send({from: accounts.curveOwner}); - - let buyCurve; - - if (curveLogicType === CurveLogicType.CONSTANT) { - buyCurve = await this.deployStaticCurveLogic(); - } else if (curveLogicType === CurveLogicType.BANCOR) { - buyCurve = (await this.deployBancorCurveLogic()).buyCurve; - } - - const bondingCurve = await BondingCurveEther.new(); - await bondingCurve.initialize( - accounts.curveOwner, - accounts.curveOwner, - bondedToken.address, - buyCurve.address, - curveParams.reservePercentage, - curveParams.dividendPercentage - ); - - await bondedToken.contract.methods.addMinter(bondingCurve.address).send({from: accounts.minter}); - await bondedToken.contract.methods.renounceMinter().send({from: accounts.minter}); - - this.contracts = { - bondingCurve, - bondedToken, - rewardsDistributor, - buyCurve - } - - return this.contracts; + const bondingCurve = await BondingCurveEther.new(); + await bondingCurve.initialize( + accounts.curveOwner, + accounts.curveOwner, + bondedToken.address, + buyCurve.address, + curveParams.reservePercentage, + curveParams.dividendPercentage + ); + + await bondedToken.contract.methods + .addMinter(bondingCurve.address) + .send({from: accounts.minter}); + await bondedToken.contract.methods.renounceMinter().send({from: accounts.minter}); + + this.contracts = { + bondingCurve, + bondedToken, + rewardsDistributor, + buyCurve + }; + + return this.contracts; + } + + async initERC20(web3) { + const {accounts} = this; + const { + curveParams, + curveLogicType, + collateralType, + bondedTokenParams, + collateralTokenParams, + curveLogicParams + } = this.config.deployParams; + // TODO: Use an ERC20Mintable instead of a BondedToken here! + const paymentToken = await this.deployPaymentToken(); + + const rewardsDistributor = await RewardsDistributor.new(); + await rewardsDistributor.initialize(accounts.curveOwner); + + const bondedToken = await BondedToken.new(); + await bondedToken.initialize( + bondedTokenParams.name, + bondedTokenParams.symbol, + bondedTokenParams.decimals, + accounts.minter, + rewardsDistributor.address, + paymentToken.address + ); + + await rewardsDistributor.contract.methods + .transferOwnership(bondedToken.address) + .send({from: accounts.curveOwner}); + + let buyCurve; + + if (curveLogicType === CurveLogicType.CONSTANT) { + buyCurve = await this.deployStaticCurveLogic(); + } else if (curveLogicType === CurveLogicType.BANCOR) { + buyCurve = (await this.deployBancorCurveLogic()).buyCurve; } - async initERC20(web3) { - const accounts = this.accounts; - const {curveParams, curveLogicType, collateralType, bondedTokenParams, collateralTokenParams, curveLogicParams} = this.config.deployParams; - // TODO: Use an ERC20Mintable instead of a BondedToken here! - const paymentToken = await this.deployPaymentToken(); - - const rewardsDistributor = await RewardsDistributor.new(); - await rewardsDistributor.initialize(accounts.curveOwner); - - const bondedToken = await BondedToken.new(); - await bondedToken.initialize( - bondedTokenParams.name, - bondedTokenParams.symbol, - bondedTokenParams.decimals, - accounts.minter, - rewardsDistributor.address, - paymentToken.address); - - await rewardsDistributor.contract.methods - .transferOwnership(bondedToken.address) - .send({from: accounts.curveOwner}); - - let buyCurve; - - if (curveLogicType === CurveLogicType.CONSTANT) { - buyCurve = await this.deployStaticCurveLogic(); - } else if (curveLogicType === CurveLogicType.BANCOR) { - buyCurve = (await this.deployBancorCurveLogic()).buyCurve; - } - - const bondingCurve = await BondingCurve.new(); - await bondingCurve.initialize(accounts.curveOwner, - accounts.curveOwner, - paymentToken.address, - bondedToken.address, - buyCurve.address, - curveParams.reservePercentage, - curveParams.dividendPercentage); - - await bondedToken.contract.methods.addMinter(bondingCurve.address).send({from: accounts.minter}); - await bondedToken.contract.methods.renounceMinter().send({from: accounts.minter}); - - this.contracts = { - bondingCurve, - bondedToken, - paymentToken, - rewardsDistributor, - buyCurve - } - - return this.contracts; + const bondingCurve = await BondingCurve.new(); + await bondingCurve.initialize( + accounts.curveOwner, + accounts.curveOwner, + paymentToken.address, + bondedToken.address, + buyCurve.address, + curveParams.reservePercentage, + curveParams.dividendPercentage + ); + + await bondedToken.contract.methods + .addMinter(bondingCurve.address) + .send({from: accounts.minter}); + await bondedToken.contract.methods.renounceMinter().send({from: accounts.minter}); + + this.contracts = { + bondingCurve, + bondedToken, + paymentToken, + rewardsDistributor, + buyCurve + }; + + return this.contracts; + } + + async getBals(address) { + const {bondedToken} = this.contracts; + const bals = {}; + + bals.ether = bn(await web3.eth.getBalance(address)); + bals.bondedToken = bn(await bondedToken.balanceOf(address)); + + if (this.config.collateralType === TokenType.ERC20) { + const {paymentToken} = this.contracts; + bals.paymentToken = bn(await paymentToken.balanceOf(address)); } - async getBals(address) { - const {bondedToken} = this.contracts; - const bals = {} - - bals.ether = bn(await web3.eth.getBalance(address)); - bals.bondedToken = bn(await bondedToken.balanceOf(address)); + return bals; + } - if (this.config.collateralType === TokenType.ERC20) { - const {paymentToken} = this.contracts; - bals.paymentToken = bn(await paymentToken.balanceOf(address)); - } - - return bals; + async getBalances(accounts) { + const {bondingCurve} = this.contracts; + const bals = {}; + for (const account of accounts) { + bals[account] = await this.getBals(account); } - async getBalances(accounts) { - const {bondingCurve} = this.contracts - const bals = {} - for (const account of accounts) { - bals[account] = await this.getBals(account); - } - - bals.bondingCurve = await this.getBals(bondingCurve); + bals.bondingCurve = await this.getBals(bondingCurve); - return bals; - } + return bals; + } - async bulkMint(token, minter, accounts, amount) { - for (const account of accounts) { - await token.mint(account, amount, {from: minter}); - } + async bulkMint(token, minter, accounts, amount) { + for (const account of accounts) { + await token.mint(account, amount, {from: minter}); } + } - async bulkApprove(token, recipient, accounts, amount) { - for (const account of accounts) { - await token.approve(recipient, amount, {from: account}); - } + async bulkApprove(token, recipient, accounts, amount) { + for (const account of accounts) { + await token.approve(recipient, amount, {from: account}); } + } } module.exports = { - CurveEcosystem -} \ No newline at end of file + CurveEcosystem +}; diff --git a/test/helpers/utils.js b/test/helpers/utils.js index 80cf1b8..300ed08 100644 --- a/test/helpers/utils.js +++ b/test/helpers/utils.js @@ -1,20 +1,25 @@ const {BN} = require('openzeppelin-test-helpers'); + const MAX_GAS = 0xffffffff; -const MAX_UINT = web3.utils.toTwosComplement("-1"); +const MAX_UINT = web3.utils.toTwosComplement('-1'); const WAD = new BN(10).pow(new BN(18)); -const str = (val) => { - return val.toString(); -} +const str = val => { + return val.toString(); +}; const bn = val => { - return new BN(val.toString()); -} + return new BN(val.toString()); +}; const wad = val => { - return new BN(val.toString()).mul(WAD) -} + return new BN(val.toString()).mul(WAD); +}; module.exports = { - str, bn, MAX_GAS, MAX_UINT, WAD -} \ No newline at end of file + str, + bn, + MAX_GAS, + MAX_UINT, + WAD +}; diff --git a/test/unit/bancorCurveLogic.spec.js b/test/unit/bancorCurveLogic.spec.js index 5e2c0e9..e5fc5b6 100644 --- a/test/unit/bancorCurveLogic.spec.js +++ b/test/unit/bancorCurveLogic.spec.js @@ -21,7 +21,7 @@ contract('BancorCurveLogic', accounts => { let bancorCurveService; const reserveRatio = new BN(1000); - beforeEach(async function() { + beforeEach(async () => { project = await deployProject(); bancorCurveService = await deployBancorCurveService(project); curve = await deployBancorCurveLogic(project, [ @@ -30,19 +30,19 @@ contract('BancorCurveLogic', accounts => { ]); }); - it('initializes reserve ratio parameter correctly', async function() { + it('initializes reserve ratio parameter correctly', async () => { const result = await curve.methods.reserveRatio().call({from: initializer}); expect(new BN(result)).to.be.bignumber.equal(reserveRatio); }); - it('initializes curve service parameter correctly', async function() { + it('initializes curve service parameter correctly', async () => { const result = await curve.methods.bancorService().call({from: initializer}); expect(result).to.be.equal(bancorCurveService.address); }); - it('calculates correct buy results for all value sets', async function() { + it('calculates correct buy results for all value sets', async () => { for (let i = 0; i < values.length; i++) { const valueSet = values[i]; const result = await curve.methods @@ -53,7 +53,7 @@ contract('BancorCurveLogic', accounts => { } }); - it('calculates correct sell results for all value sets', async function() { + it('calculates correct sell results for all value sets', async () => { let valueSet = values[0]; const result = await curve.methods .calcBurnReward(valueSet.supply, valueSet.connectorBalance, valueSet.depositAmount) diff --git a/test/unit/rewardDistributor.spec.js b/test/unit/rewardDistributor.spec.js index 70b26db..703a9e8 100644 --- a/test/unit/rewardDistributor.spec.js +++ b/test/unit/rewardDistributor.spec.js @@ -27,17 +27,17 @@ contract('RewardsDistributor', accounts => { let acct_c = accounts[4]; let acct_d = accounts[5]; - beforeEach(async function() { + beforeEach(async () => { project = await deployProject(); rd = await deployRewardsDistributor(project, [creator]); ELIGIBLE_UNIT = rd.ELIGIBLE_UNIT; }); - it('deploys and initializes', async function() { + it('deploys and initializes', async () => { expect(await rd.methods.getStakeTotal().call({from: creator})).to.be.equal('0'); }); - it('withdraws ZERO reward', async function() { + it('withdraws ZERO reward', async () => { tx = await rd.methods.withdrawReward(acct_a).send({from: creator}); expectEvent.inLogs(tx.events, 'RewardWithdrawalMade', { to: acct_a, @@ -45,15 +45,15 @@ contract('RewardsDistributor', accounts => { }); }); - it('reads ZERO stake', async function() { + it('reads ZERO stake', async () => { expect(await rd.methods.getStake(acct_a).call({from: creator})).to.be.equal('0'); }); - it('reverts if trying to withdraw amount > stake', async function() { + it('reverts if trying to withdraw amount > stake', async () => { await expectRevert.unspecified(rd.methods.withdrawStake(acct_a, '1').send({from: creator})); }); - it('updates total stake after deposit > ELIGIBLE_UNIT', async function() { + it('updates total stake after deposit > ELIGIBLE_UNIT', async () => { var amount = new BN('100').mul(TEN18); tx = await rd.methods.deposit(acct_a, amount.toString()).send({from: creator}); @@ -66,7 +66,7 @@ contract('RewardsDistributor', accounts => { expect(await rd.methods.getStakeTotal().call({from: creator})).to.be.equal(amount.toString()); }); - it("doesn't update total stake after deposit < ELIGIBLE_UNIT", async function() { + it("doesn't update total stake after deposit < ELIGIBLE_UNIT", async () => { var amount = new BN('100'); tx = await rd.methods.deposit(acct_a, amount.toString()).send({from: creator}); @@ -74,7 +74,7 @@ contract('RewardsDistributor', accounts => { expect(await rd.methods.getStakeTotal().call({from: creator})).to.be.equal('0'); }); - it('deposits A, gets stake, withdraws all stake, gets stake again', async function() { + it('deposits A, gets stake, withdraws all stake, gets stake again', async () => { var amount = new BN('100').mul(TEN18); tx = await rd.methods.deposit(acct_a, amount.toString()).send({from: creator}); @@ -86,7 +86,7 @@ contract('RewardsDistributor', accounts => { expect(await rd.methods.getStake(acct_a).call({from: creator})).to.be.equal('0'); }); - it('does no distribution if no stake >= ELIGIBLE_UNIT', async function() { + it('does no distribution if no stake >= ELIGIBLE_UNIT', async () => { await rd.methods.deposit(acct_a, '1234').send({from: creator}); await rd.methods.distribute(creator, '1234000').send({from: creator}); @@ -94,7 +94,7 @@ contract('RewardsDistributor', accounts => { expect(await rd.methods.getReward(acct_a).call({from: creator})).to.be.equal('0'); }); - it('does no distribution if stake becomes ineligible after withdrawl', async function() { + it('does no distribution if stake becomes ineligible after withdrawl', async () => { var amountDeposit = new BN('100').mul(TEN18); var amountWithdraw = new BN('100').mul(TEN18).sub(new BN('10000')); @@ -117,7 +117,7 @@ contract('RewardsDistributor', accounts => { expect(await rd.methods.getStakeTotal().call({from: creator})).to.be.equal('0'); }); - it('allocates all reward to a single staker and allow its withdrawl', async function() { + it('allocates all reward to a single staker and allow its withdrawl', async () => { var amountDeposit = new BN('100').mul(TEN18); var amountDistribute = new BN('200').mul(TEN18); @@ -142,7 +142,7 @@ contract('RewardsDistributor', accounts => { expect(await rd.methods.getReward(acct_a).call({from: creator})).to.be.equal('0'); }); - it('allocates no reward to stake <= ELIGIBLE_UNIT', async function() { + it('allocates no reward to stake <= ELIGIBLE_UNIT', async () => { await rd.methods.deposit(acct_a, String(10 ** 9)).send({from: creator}); await rd.methods.deposit(acct_b, String(100)).send({from: creator}); @@ -157,7 +157,7 @@ contract('RewardsDistributor', accounts => { expect(await rd.methods.getReward(acct_b).call({from: creator})).to.be.equal('0'); }); - it('allocates 1st reward proportionally to 2 stakers and 2nd reward to remaining staker after the other withdrew', async function() { + it('allocates 1st reward proportionally to 2 stakers and 2nd reward to remaining staker after the other withdrew', async () => { var depositA = new BN(String(10 * 10 ** 9)); // 10 ELIGIBLE_UNITS var depositB = new BN(String(30 * 10 ** 9)); var distribute1 = new BN('400'); // notice this is in wei @@ -176,7 +176,7 @@ contract('RewardsDistributor', accounts => { expect(await rd.methods.getReward(acct_a).call({from: creator})).to.be.equal('100'); }); - it('withdraws reward after proportional reward distribution', async function() { + it('withdraws reward after proportional reward distribution', async () => { var depositA = new BN('100').mul(TEN18); var depositB = new BN('300').mul(TEN18); var distribute1 = new BN('40').mul(TEN18); @@ -192,7 +192,7 @@ contract('RewardsDistributor', accounts => { }); }); - it('withdraws reward after two consecutive reward distributions', async function() { + it('withdraws reward after two consecutive reward distributions', async () => { var depositA = new BN('100').mul(TEN18); var depositB = new BN('300').mul(TEN18); var distribute1 = new BN('400').mul(TEN18); @@ -223,7 +223,7 @@ contract('RewardsDistributor', accounts => { }); }); - it('distributes after partial stake withdrawal and reads reward', async function() { + it('distributes after partial stake withdrawal and reads reward', async () => { var depositA = new BN('100').mul(TEN18); var depositB = new BN('300').mul(TEN18); var distribute1 = new BN('100').mul(TEN18); @@ -255,7 +255,7 @@ contract('RewardsDistributor', accounts => { ); }); - it('withdraws reward after stake has been withdrawn', async function() { + it('withdraws reward after stake has been withdrawn', async () => { var depositA = new BN('100').mul(TEN18); var distribute1 = new BN('10').mul(TEN18); @@ -277,7 +277,7 @@ contract('RewardsDistributor', accounts => { expect(await rd.methods.getStakeTotal().call({from: creator})).to.be.equal('0'); }); - it('handles magnitude: A deposits 9999, B deposits 1, distribute, withdraw stake, distribute, withdraw reward', async function() { + it('handles magnitude: A deposits 9999, B deposits 1, distribute, withdraw stake, distribute, withdraw reward', async () => { var depositA = new BN('9999').mul(TEN18); var depositB = new BN('1').mul(TEN18); var distribute1 = new BN('1').mul(TEN18); @@ -295,7 +295,7 @@ contract('RewardsDistributor', accounts => { ); }); - it('handles magnitude: deposit 10**6 10**9 10**12 10**15, distribute, withdraw reward', async function() { + it('handles magnitude: deposit 10**6 10**9 10**12 10**15, distribute, withdraw reward', async () => { await rd.methods.deposit(acct_a, String(10 ** 6)).send({from: creator}); await rd.methods.deposit(acct_b, String(10 ** 9)).send({from: creator}); await rd.methods.deposit(acct_c, String(10 ** 12)).send({from: creator}); @@ -316,7 +316,7 @@ contract('RewardsDistributor', accounts => { ); }); - it('carries remainder to second distribution and withdraws reward', async function() { + it('carries remainder to second distribution and withdraws reward', async () => { await rd.methods.deposit(acct_a, String(10 ** 9)).send({from: creator}); await rd.methods.deposit(acct_b, String(9 * 10 ** 9)).send({from: creator});