├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug.md │ ├── epic.md │ ├── feature-request.md │ └── new-issue.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .prettierrc ├── .solcover.js ├── .solhint.json ├── README.MD ├── allocations ├── kovan │ └── BTC++.json ├── localhost │ └── UTXO.json ├── mainnet │ ├── BTC++.json │ ├── USD++test.json │ └── UTXO.json └── rinkeby │ ├── BTC++.json │ └── UTXO.json ├── buidler.compile.config.ts ├── buidler.config.ts ├── contracts ├── Ownable.sol ├── PCToken.sol ├── ReentryProtection.sol ├── factory │ └── PProxiedFactory.sol ├── interfaces │ ├── IBFactory.sol │ ├── IBPool.sol │ ├── IERC20.sol │ └── IPV2SmartPool.sol ├── libraries │ ├── LibAddRemoveToken.sol │ ├── LibConst.sol │ ├── LibFees.sol │ ├── LibPoolEntryExit.sol │ ├── LibPoolMath.sol │ ├── LibPoolToken.sol │ ├── LibSafeApprove.sol │ ├── LibUnderlying.sol │ ├── LibWeights.sol │ └── Math.sol ├── smart-pools │ └── PV2SmartPool.sol ├── storage │ ├── OwnableStorage.sol │ ├── PBasicSmartPoolStorage.sol │ ├── PCTokenStorage.sol │ ├── PCappedSmartPoolStorage.sol │ ├── PV2SmartPoolStorage.sol │ └── ReentryProtectionStorage.sol └── test │ ├── TestLibSafeApprove.sol │ ├── TestPCToken.sol │ └── TestReentryProtection.sol ├── example.env ├── mainnet-test └── test.ts ├── package.json ├── test ├── advancedPoolFunctionality.ts ├── basicPoolFunctionality.ts ├── capPoolFunctionality.ts ├── pProxiedFactory.ts ├── poolToken.ts ├── reentryProtection.ts └── safeApproval.ts ├── tsconfig.json ├── tslint.json ├── utils ├── TimeTraveler.ts ├── balancerFactoryBytecode.ts ├── balancerPoolBytecode.ts └── index.ts └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 3 | about: File a bug report 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | #### Describe the bug 11 | 14 | 15 | #### Steps to Reproduce 16 | 23 | 24 | #### What do you believe the expected behavior is 25 | 28 | 29 | #### Relevant screenshots 30 | 33 | 34 | #### Platform details 35 | 39 | 40 | ## Additional context 41 | 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/epic.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Epic 3 | about: Issue format specific to Epics 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Description 11 | 16 | 17 | ## Initiative / goal 18 | 21 | 22 | ## Acceptance criteria and must have scope 23 | 26 | 27 | ## Stakeholders 28 | 32 | 33 | ## Timeline 34 | 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest a feature for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | #### Describe the ideal solution or feature request 11 | 14 | 15 | #### Who's asking? 16 | 20 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/new-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: New Issue 3 | about: Enhancement, maintenance, or refactor for a private repo 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 14 | 15 | #### User Story 16 | 24 | - As an : 25 | - I want to : 26 | - so that : 27 | 28 | #### Type 29 | 32 | - [ ] Enhancement 33 | - [ ] Maintenance 34 | - [ ] Refactor 35 | 36 | #### Description 37 | 40 | 41 | #### Definition of Done 42 | 46 | 47 | - [ ] Thing One... 48 | - [ ] Thing Two... 49 | 50 | #### Attach files or screenshots if it's helpful for this issue. 51 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ##### Description 2 | 3 | 4 | ##### Checklist 5 | 6 | 7 | - [ ] Linter status: 100% pass 8 | - [ ] Changes don't break existing behavior 9 | - [ ] Test coverage hasn't decreased 10 | 11 | ##### Testing 12 | 15 | 16 | ##### Refers/Fixes 17 | 22 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | pull_request: 5 | branches: [ '*' ] 6 | 7 | jobs: 8 | lint: 9 | name: with tslint and solhint 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | node-version: [10.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: borales/actions-yarn@master 19 | with: 20 | cmd: install # will run `yarn install` command 21 | - uses: borales/actions-yarn@master 22 | with: 23 | cmd: lint # will run `yarn lint` command -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: [ '*' ] 6 | 7 | jobs: 8 | lint: 9 | name: run tests 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | node-version: [10.x] 15 | 16 | steps: 17 | - uses: actions/checkout@v2 18 | - uses: borales/actions-yarn@master 19 | with: 20 | cmd: install # will run `yarn install` command 21 | - uses: borales/actions-yarn@master 22 | with: 23 | cmd: build # will run `yarn build` command 24 | - uses: borales/actions-yarn@master 25 | with: 26 | cmd: test # will run `yarn test` command 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | #Buidler files 107 | cache 108 | artifacts 109 | .secret 110 | 111 | .DS_Store 112 | coverage.json 113 | flats.sol 114 | .coverage_cache/ 115 | .coverage_contracts/ 116 | typechain/ 117 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false, 4 | "printWidth": 100 5 | } 6 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | mocha: { 3 | grep: "@skip-on-coverage", // Find everything with this tag 4 | invert: true // Run the grep's inverse set. 5 | }, 6 | providerOptions: { 7 | gasLimit: 1000000000, 8 | allowUnlimitedContractSize: true 9 | } 10 | } -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:default", 3 | "rules": { 4 | "max-line-length": ["error", 100] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Pie Smart Pools 2 | 3 | **Pie Smart Pools** are asset management agnostic(currently Balancer only) **D**ecentralised **T**raded **F**unds. They share a common interface to make them easy to integrate in other products. 4 | 5 | All Smart Pools are fully upgradeable to make it easy to add features and optimise gas usage at later stages. 6 | 7 | ## Development 8 | 9 | ### Setup the dev enviroment 10 | 11 | Clone this repo. And copy the contents of ``env.example`` to a new file called ``.env`` and edit the the relevant values inside. **DO NOT** share this file with anyone as it will contain sensitive data. 12 | 13 | Install all dependencies: 14 | ``` 15 | yarn 16 | ``` 17 | Build the project: 18 | ``` 19 | yarn build 20 | ``` 21 | Run the tests: 22 | ``` 23 | yarn test 24 | ``` 25 | Create coverage report: 26 | ``` 27 | yarn coverage 28 | ``` 29 | 30 | ### Running mainnet/testnet test 31 | 32 | To test a new implementation in testnet conditions. Set the implementation of a test pool to the new version and run the following script. 33 | 34 | ``` 35 | POOL=[POOL_ADDRESS] npx buidler test ./mainnet-test/test.ts --network [rinkeby|kovan|rinkeby] 36 | ``` 37 | 38 | ## Integration 39 | 40 | ### Adding and removing liquidity 41 | 42 | To add liquidity approve the smart pool to pull the underlying tokens. And call: 43 | 44 | ```solidity 45 | function joinPool(uint256 _amount) external; 46 | ``` 47 | 48 | To remove liquidity: 49 | 50 | ```solidity 51 | function exitPool(uint256 _amount) external; 52 | ``` 53 | 54 | ### Getting pool details 55 | 56 | To get the underlying tokens call: 57 | 58 | ```solidity 59 | function getTokens() external view returns(address[] memory); 60 | ``` 61 | 62 | To get the underlying tokens and amounts needed to mint a certain amount of pool shares call: 63 | 64 | ```solidity 65 | function calcTokensForAmount(uint256 _amount) external view returns(address[] memory tokens, uint256[] memory amounts); 66 | ``` 67 | 68 | #### Balancer smart pool specific 69 | Get the address of the underlying balancer pool: 70 | 71 | ```solidity 72 | function getBPool() external view returns(address); 73 | ``` 74 | 75 | Get the swap fee: 76 | 77 | ```solidity 78 | function getSwapFee() external view returns (uint256); 79 | ``` 80 | 81 | Get if trading is enabled on the underlying balancer pool: 82 | 83 | ```solidity 84 | function isPublicSwap() external view returns (bool); 85 | ``` 86 | 87 | 88 | #### Capped pool specific 89 | Some pools have a cap which limits the totalSupply of the pool shares token. To get the cap you call: 90 | 91 | ```solidity 92 | function getCap() external view returns(uint256); 93 | ``` 94 | 95 | ## Managing pie smart pools 96 | 97 | The pie smart pools have 4 roles which can manage the pie up to some extent: ``controller``, ``tokenBinder``, ``publicSwapSetter`` and the ``circuitBreaker`` 98 | 99 | 100 | ### Setting public swap 101 | 102 | Pie smart pools use an underlying balancer pool. If under some circumstances the swapping needs to be disabled/enabled this can be done by the ``publicSwapSetter`` by calling: 103 | 104 | ```solidity 105 | function setPublicSwap(bool _public) external 106 | ``` 107 | 108 | ### Setting the cap 109 | 110 | Under some conditions it might be a good idea to limit the amount that can be minted to limit potential losses of new pools. You can set the cap by calling from the ``controller``: 111 | 112 | ```solidity 113 | function setCap(uint256 _cap) external 114 | ``` 115 | 116 | ### Setting the swap fee 117 | 118 | Every time a trade happens or the pool is joined with a single asset a swap fee is charged. To change the swap fee call from the ``controller``: 119 | 120 | ```solidity 121 | function setSwapFee(uint256 _swapFee) external 122 | ``` 123 | 124 | ### Enabling and disabling join and exit 125 | 126 | During rebalances it is advised to disable joining and exiting the pool. This can be done by calling from the ``controller``: 127 | 128 | ```solidity 129 | function setJoinExitEnabled(bool _newValue) external 130 | ``` 131 | 132 | ### Setting the circuitBreaker address 133 | 134 | The ``circuitBreaker`` is able to trip the circuit breaker. To set this address, call from the controller: 135 | 136 | ```solidity 137 | function setCircuitBreaker(address _newCircuitBreaker) external 138 | ``` 139 | 140 | ### Setting the annual fee 141 | 142 | On every join and exit the annual fee is charged. 10**17 == 10%. A 10% fee is the maximum. To set the fee call from the ``controller``: 143 | 144 | ```solidity 145 | function setAnnualFee(uint256 _newFee) external 146 | ``` 147 | 148 | ### Setting fee recipient 149 | 150 | To set the address which receives the annual fee call from the ``controller``: 151 | 152 | ```solidity 153 | function setFeeRecipient(address _newRecipient) external 154 | ``` 155 | 156 | ### Adding, removal, and weight adjustment of tokens through binding. 157 | 158 | Binding and unbinding tokens removes them directly from the smart pool without changing the amount of pieTokens. These functions can only be called by the ``tokenBinder``. When using these functions the pool should be locked and ideally the per pool share value should remain the same. NOTE: adjusting weights should be done carefully and quickly to prevent value from the pool to be leaked out. 159 | 160 | To unbind (remove) a token call from the ``tokenBinder``: 161 | 162 | ```solidity 163 | function unbind(address _token) external 164 | ``` 165 | 166 | To bind (add) a token call from the ``tokenBinder``: 167 | 168 | ```solidity 169 | function bind(address _token, uint256 _balance, uint256 _denorm) external 170 | ``` 171 | 172 | To rebind(change a tokens weight) call from the ``tokenBinder``: 173 | 174 | ```solidity 175 | function rebind(address _token, uint256 _balance, uint256 _denorm) external 176 | ``` 177 | 178 | ### Circuit breaker 179 | 180 | Due to the nature of unrestricted AMMs a single token in the pool experiencing catostrophic will result in all value of a pool being drained. To prevent this from happening a circuit breaker can be tripped by the ``circuitBreaker`` to halt swaps, joins and exits this can only be reverted by the ``controller``. This can be done by calling the following function from the ``circuitBreaker``: 181 | 182 | ```solidity 183 | function tripCircuitBreaker() external 184 | ``` 185 | 186 | ### Updating a token's weight 187 | 188 | A token's weight can be updated while still retaining the per pool share value of a pie smart pool. When a token's weight goes down the underlying difference will be send to the controller and some of it's pool shares burned. When a tokens weight goes up the underlying difference will be send to the pool from the controller and pool shares will be minted. NOTE: Be aware of possible sandwhich attacks which could drain value from the pool during adjustment. To update a token's weight call from the controller: 189 | 190 | ```solidity 191 | function updateWeight(address _token, uint256 _newWeight) 192 | ``` 193 | 194 | ### Updating token weights gradually 195 | 196 | By slowly shifting weights over the course of many blocks to the new tarket weights IL from the rebalancing can be minimised. Additionally this mechanic allows for deployment of so called "Liquidity Bootstrapping Pools" pools. Any pending weight adjust will be cancelled if new tokens are added or weights are adjusted. ``pokeWeights`` should be called periodically to trigger weight changes. 197 | 198 | ```solidity 199 | function updateWeightsGradually(uint256[] calldata _newWeights, uint256 _startBlock, uint256 _endBlock) external 200 | ``` 201 | 202 | ### Adding a token 203 | 204 | For the sake of balancer smart pool compatibility adding a token is a two step process. During this process the price per pool share should remain the same because new pool shares are minted based on the weight of the new token added. To limit potential losses the weight should correctly reflect the market price of the token and the pool should be locked during adding. NOTE: always be wary about potential sandwhich attacks which could drain value from the pool 205 | 206 | To add the token call the following two functions from the controller: 207 | 208 | ```solidity 209 | function commitAddToken(address _token, uint256 _balance, uint256 _denormalizedWeight) external 210 | ``` 211 | 212 | ```solidity 213 | function applyAddToken() external 214 | ``` 215 | 216 | ### Removing a token 217 | 218 | When removing a token the tokens are send to the ``controller`` and in return pool tokens are burned from the ``controller``. This ensures the per pool share price should remain the same. Ideally you lock the pool to prevent value to be partially drained from the pool. 219 | 220 | To remove a token call from the ``controller``: 221 | 222 | ```solidity 223 | function removeToken(address _token) external 224 | ``` -------------------------------------------------------------------------------- /allocations/kovan/BTC++.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PieDAO BTC++", 3 | "symbol": "BTC++", 4 | "initialValue": "1", 5 | "initialSupply": "0.04", 6 | "cap": "10", 7 | "tokens": [ 8 | { 9 | "name": "wBTC", 10 | "decimals": "8", 11 | "address": "0x69FfB2c1Bfe8dE999a81bcf7fd58328540db3D6e", 12 | "weight": "25", 13 | "value": "1" 14 | }, 15 | { 16 | "name": "pBTC", 17 | "decimals": "18", 18 | "address": "0xAA2612E023ff753044baAfbA6abe98e37A341308", 19 | "weight": "25", 20 | "value": "1" 21 | }, 22 | { 23 | "name": "imBTC", 24 | "decimals": "8", 25 | "address": "0x8bB95f2E07149C6de146928e00bf99c0fb0F7D29", 26 | "weight": "25", 27 | "value": "1" 28 | }, 29 | { 30 | "name": "sBTC", 31 | "decimals": "18", 32 | "address": "0x958ad940f398D76B5Bc6923FcA12bD7f1F80f8B1", 33 | "weight": "25", 34 | "value": "1" 35 | } 36 | ] 37 | } 38 | 39 | -------------------------------------------------------------------------------- /allocations/localhost/UTXO.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PieDAO UTXO", 3 | "symbol": "UTXO", 4 | "initialValue": "1", 5 | "initialSupply": "1", 6 | "cap": "10", 7 | "tokens": [ 8 | { 9 | "name": "pBTC", 10 | "decimals": "18", 11 | "address": "0x0078371BDeDE8aAc7DeBfFf451B74c5EDB385Af7", 12 | "weight": "70", 13 | "value": "10917.27" 14 | }, 15 | { 16 | "name": "pLTC", 17 | "decimals": "18", 18 | "address": "0x7c2C195CD6D34B8F845992d380aADB2730bB9C6F", 19 | "weight": "30", 20 | "value": "50.05" 21 | } 22 | ] 23 | } 24 | 25 | -------------------------------------------------------------------------------- /allocations/mainnet/BTC++.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PieDAO BTC++", 3 | "symbol": "BTC++", 4 | "initialValue": "1", 5 | "initialSupply": "0.04", 6 | "cap": "10", 7 | "tokens": [ 8 | { 9 | "name": "wBTC", 10 | "decimals": "8", 11 | "address": "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", 12 | "weight": "25", 13 | "value": "1" 14 | }, 15 | { 16 | "name": "pBTC", 17 | "decimals": "18", 18 | "address": "0x5228a22e72ccc52d415ecfd199f99d0665e7733b", 19 | "weight": "25", 20 | "value": "1" 21 | }, 22 | { 23 | "name": "imBTC", 24 | "decimals": "8", 25 | "address": "0x3212b29E33587A00FB1C83346f5dBFA69A458923", 26 | "weight": "25", 27 | "value": "1" 28 | }, 29 | { 30 | "name": "sBTC", 31 | "decimals": "18", 32 | "address": "0xfE18be6b3Bd88A2D2A7f928d00292E7a9963CfC6", 33 | "weight": "25", 34 | "value": "1" 35 | } 36 | ] 37 | } 38 | 39 | -------------------------------------------------------------------------------- /allocations/mainnet/USD++test.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PieDAO Test USD Pool", 3 | "symbol": "TU", 4 | "initialValue": "1", 5 | "initialSupply": "100", 6 | "cap": "10", 7 | "tokens": [ 8 | { 9 | "name": "USDC", 10 | "decimals": "6", 11 | "address": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", 12 | "weight": "47.22", 13 | "value": "1" 14 | }, 15 | { 16 | "name": "DAI", 17 | "decimals": "18", 18 | "address": "0x6B175474E89094C44Da98b954EedeAC495271d0F", 19 | "weight": "20.42", 20 | "value": "1" 21 | }, 22 | { 23 | "name": "TUSD", 24 | "decimals": "18", 25 | "address": "0x0000000000085d4780B73119b644AE5ecd22b376", 26 | "weight": "28.58", 27 | "value": "1" 28 | }, 29 | { 30 | "name": "sUSD", 31 | "decimals": "18", 32 | "address": "0x57ab1e02fee23774580c119740129eac7081e9d3", 33 | "weight": "3.78", 34 | "value": "1" 35 | } 36 | ] 37 | } 38 | 39 | -------------------------------------------------------------------------------- /allocations/mainnet/UTXO.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PieDAO UTXO", 3 | "symbol": "UTXO", 4 | "initialValue": "1", 5 | "initialSupply": "1", 6 | "cap": "10", 7 | "tokens": [ 8 | { 9 | "name": "pBTC", 10 | "decimals": "18", 11 | "address": "0x5228a22e72ccc52d415ecfd199f99d0665e7733b", 12 | "weight": "70", 13 | "value": "10466.54" 14 | }, 15 | { 16 | "name": "pLTC", 17 | "decimals": "18", 18 | "address": "0x5979F50f1D4c08f9A53863C2f39A7B0492C38d0f", 19 | "weight": "30", 20 | "value": "48.22" 21 | } 22 | ] 23 | } 24 | 25 | -------------------------------------------------------------------------------- /allocations/rinkeby/BTC++.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PieDAO BTC++", 3 | "symbol": "BTC++", 4 | "initialValue": "1", 5 | "initialSupply": "0.04", 6 | "cap": "10", 7 | "tokens": [ 8 | { 9 | "name": "wBTC", 10 | "decimals": "8", 11 | "address": "0x2018b7214d959E5e3180eb87B9dfFf58ab721704", 12 | "weight": "25", 13 | "value": "1" 14 | }, 15 | { 16 | "name": "pBTC", 17 | "decimals": "18", 18 | "address": "0x68A0e427AFcF4A4544220d67d02Ccbff93BbE404", 19 | "weight": "25", 20 | "value": "1" 21 | }, 22 | { 23 | "name": "imBTC", 24 | "decimals": "8", 25 | "address": "0x4AF6b3698D959b17899B3AC2E49fa5e19556EE75", 26 | "weight": "25", 27 | "value": "1" 28 | }, 29 | { 30 | "name": "sBTC", 31 | "decimals": "18", 32 | "address": "0xC875BEc084e50Dbbdfe223b6ED4e8d9c1392E303", 33 | "weight": "25", 34 | "value": "1" 35 | } 36 | ] 37 | } 38 | 39 | -------------------------------------------------------------------------------- /allocations/rinkeby/UTXO.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PieDAO UTXO", 3 | "symbol": "UTXO", 4 | "initialValue": "1", 5 | "initialSupply": "1", 6 | "cap": "10", 7 | "tokens": [ 8 | { 9 | "name": "pBTC", 10 | "decimals": "18", 11 | "address": "0x013882e5481ec8a93BcDC45bEb0314006718ffD8", 12 | "weight": "70", 13 | "value": "10466.54" 14 | }, 15 | { 16 | "name": "pLTC", 17 | "decimals": "18", 18 | "address": "0xB9335F7985Bb9B7F8293cab50b95938895BCD78c", 19 | "weight": "30", 20 | "value": "48.22" 21 | } 22 | ] 23 | } 24 | 25 | -------------------------------------------------------------------------------- /buidler.compile.config.ts: -------------------------------------------------------------------------------- 1 | import { BuidlerConfig } from "@nomiclabs/buidler/config"; 2 | 3 | interface ExtendedBuidlerConfig extends BuidlerConfig { 4 | [x:string]: any 5 | } 6 | 7 | const config: ExtendedBuidlerConfig = { 8 | solc: { 9 | version: "0.6.4", 10 | optimizer: { 11 | runs: 200, 12 | enabled: true, 13 | } 14 | }, 15 | } 16 | 17 | export default config; -------------------------------------------------------------------------------- /buidler.config.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:no-var-requires 2 | require("dotenv").config(); 3 | import { BuidlerConfig, usePlugin, task, internalTask } from "@nomiclabs/buidler/config"; 4 | import { utils, constants, ContractTransaction, Wallet } from "ethers"; 5 | import {deployContract, solidity} from "ethereum-waffle"; 6 | import { parseUnits, parseEther, BigNumberish, BigNumber } from "ethers/utils"; 7 | import { MockTokenFactory } from "@pie-dao/mock-contracts/dist/typechain/MockTokenFactory"; 8 | 9 | import { IbFactoryFactory } from "./typechain/IbFactoryFactory"; 10 | import { deployBalancerFactory, deployAndGetLibObject, linkArtifact } from "./utils"; 11 | import { IbPoolFactory } from "./typechain/IbPoolFactory"; 12 | import { Ierc20Factory } from "./typechain/Ierc20Factory"; 13 | import { PProxiedFactoryFactory } from "./typechain/PProxiedFactoryFactory"; 14 | 15 | import { Pv2SmartPool } from "./typechain/Pv2SmartPool"; 16 | import { Pv2SmartPoolFactory } from "./typechain/Pv2SmartPoolFactory"; 17 | import Pv2SmartPoolArtifact from "./artifacts/PV2SmartPool.json"; 18 | 19 | import LibPoolEntryExitArtifact from "./artifacts/LibPoolEntryExit.json"; 20 | import LibAddRemoveTokenArtifact from "./artifacts/LibAddRemoveToken.json"; 21 | import LibWeightsArtifact from "./artifacts/LibWeights.json"; 22 | import LibPoolMathArtifact from "./artifacts/LibPoolMath.json"; 23 | 24 | // Uncomment line below when doing gas optimisations on a local network 25 | usePlugin("buidler-gas-reporter"); 26 | usePlugin("@nomiclabs/buidler-waffle"); 27 | usePlugin("@nomiclabs/buidler-etherscan"); 28 | usePlugin("solidity-coverage"); 29 | usePlugin("buidler-deploy"); 30 | 31 | const INFURA_API_KEY = process.env.INFURA_API_KEY || ""; 32 | const KOVAN_PRIVATE_KEY = process.env.KOVAN_PRIVATE_KEY || ""; 33 | const KOVAN_PRIVATE_KEY_SECONDARY = process.env.KOVAN_PRIVATE_KEY_SECONDARY || ""; 34 | const RINKEBY_PRIVATE_KEY = process.env.RINKEBY_PRIVATE_KEY || ""; 35 | const RINKEBY_PRIVATE_KEY_SECONDARY = process.env.RINKEBY_PRIVATE_KEY_SECONDARY || ""; 36 | const MAINNET_PRIVATE_KEY = process.env.MAINNET_PRIVATE_KEY || ""; 37 | const MAINNET_PRIVATE_KEY_SECONDARY = process.env.MAINNET_PRIVATE_KEY_SECONDARY || ""; 38 | const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY || ""; 39 | 40 | const PLACE_HOLDER_ADDRESS = "0x1000000000000000000000000000000000000001"; 41 | 42 | interface ExtendedBuidlerConfig extends BuidlerConfig { 43 | [x:string]: any 44 | } 45 | 46 | const config: ExtendedBuidlerConfig = { 47 | defaultNetwork: "buidlerevm", 48 | solc: { 49 | version: "0.6.4", 50 | optimizer: { 51 | runs: 200, 52 | enabled: true, 53 | } 54 | }, 55 | networks: { 56 | local: { 57 | url: "http://127.0.0.1:8545/" 58 | }, 59 | buidlerevm: { 60 | gasPrice: 0, 61 | blockGasLimit: 100000000, 62 | }, 63 | mainnet: { 64 | url: `https://mainnet.infura.io/v3/${INFURA_API_KEY}`, 65 | accounts: [ 66 | MAINNET_PRIVATE_KEY, 67 | MAINNET_PRIVATE_KEY_SECONDARY 68 | ].filter((item) => item !== "") 69 | }, 70 | kovan: { 71 | url: `https://kovan.infura.io/v3/${INFURA_API_KEY}`, 72 | accounts: [ 73 | KOVAN_PRIVATE_KEY, 74 | KOVAN_PRIVATE_KEY_SECONDARY 75 | ].filter((item) => item !== "") 76 | }, 77 | rinkeby: { 78 | url: `https://rinkeby.infura.io/v3/${INFURA_API_KEY}`, 79 | blockGasLimit: 12000000, 80 | gas: 12000000, 81 | gasPrice: 20000000000, 82 | accounts: [ 83 | RINKEBY_PRIVATE_KEY, 84 | RINKEBY_PRIVATE_KEY_SECONDARY 85 | ].filter((item) => item !== "") 86 | }, 87 | coverage: { 88 | url: 'http://127.0.0.1:8555', // Coverage launches its own ganache-cli client 89 | gasPrice: 0, 90 | blockGasLimit: 100000000, 91 | }, 92 | frame: { 93 | url: "http://localhost:1248" 94 | } 95 | }, 96 | etherscan: { 97 | apiKey: ETHERSCAN_API_KEY 98 | }, 99 | }; 100 | 101 | // Coverage fix 102 | const {TASK_COMPILE_GET_COMPILER_INPUT} = require("@nomiclabs/buidler/builtin-tasks/task-names"); 103 | task(TASK_COMPILE_GET_COMPILER_INPUT).setAction(async (_, __, runSuper) => { 104 | const input = await runSuper(); 105 | input.settings.metadata.useLiteralContent = false; 106 | return input; 107 | }); 108 | 109 | task("deploy-pie-smart-pool-factory", "deploys a pie smart pool factory") 110 | .addParam("balancerFactory", "Address of the balancer factory") 111 | .setAction(async(taskArgs, { ethers, run }) => { 112 | const signers = await ethers.getSigners(); 113 | const factory = await (new PProxiedFactoryFactory(signers[0])).deploy(); 114 | console.log(`Factory deployed at: ${factory.address}`); 115 | 116 | const implementation = await run("deploy-libraries-and-smartpool") as Pv2SmartPool; 117 | await implementation.init(PLACE_HOLDER_ADDRESS, "IMPL", "IMPL", "1337"); 118 | 119 | await factory.init(taskArgs.balancerFactory, implementation.address); 120 | return factory.address; 121 | }); 122 | 123 | task("deploy-pool-from-factory", "deploys a pie smart pool from the factory") 124 | .addParam("factory") 125 | .addParam("allocation", "path to allocation configuration") 126 | .setAction(async(taskArgs, { ethers }) => { 127 | const signers = await ethers.getSigners(); 128 | const factory = PProxiedFactoryFactory.connect(taskArgs.factory, signers[0]); 129 | 130 | const allocation = require(taskArgs.allocation); 131 | 132 | const name = allocation.name; 133 | const symbol = allocation.symbol 134 | const initialSupply = parseEther(allocation.initialSupply); 135 | const cap = parseEther(allocation.cap); 136 | const tokens = allocation.tokens; 137 | 138 | 139 | const tokenAddresses: string[] = []; 140 | const tokenAmounts: BigNumberish[] = []; 141 | const tokenWeights: BigNumberish[] = []; 142 | 143 | for (const token of tokens) { 144 | tokenAddresses.push(token.address); 145 | tokenWeights.push(parseEther(token.weight).div(2)); 146 | 147 | // Calc amount 148 | const amount = new BigNumber(Math.floor((allocation.initialValue / token.value * token.weight / 100 * allocation.initialSupply * 10 ** token.decimals)).toString()); 149 | tokenAmounts.push(amount); 150 | // Approve factory to spend token 151 | const tokenContract = Ierc20Factory.connect(token.address, signers[0]); 152 | 153 | const allowance = await tokenContract.allowance(await signers[0].getAddress(), factory.address); 154 | if(allowance.lt(amount)) { 155 | const approveTx = await tokenContract.approve(factory.address, constants.WeiPerEther); 156 | console.log(`Approved: ${token.address} tx: ${approveTx.hash}`); 157 | await approveTx.wait(1); 158 | } 159 | } 160 | 161 | const tx = await factory.newProxiedSmartPool(name, symbol, initialSupply, tokenAddresses, tokenAmounts, tokenWeights, cap, { gasLimit: 10000000 }); 162 | const receipt = await tx.wait(); // wait for 2 confirmations 163 | const event = receipt.events.pop(); 164 | console.log(`Deployed smart pool at : ${event.address}`); 165 | return event.address; 166 | }); 167 | 168 | task("deploy-pie-smart-pool", "deploys a pie smart pool") 169 | .setAction(async(taskArgs, { ethers, run }) => { 170 | const signers = await ethers.getSigners(); 171 | 172 | console.log("deploying libraries"); 173 | const libraries = await run("deploy-libraries"); 174 | console.log("libraries deployed"); 175 | console.table(libraries); 176 | const linkedArtifact = linkArtifact(Pv2SmartPoolArtifact, libraries); 177 | 178 | const smartpool = (await deployContract(signers[0] as Wallet, linkedArtifact, [], { 179 | gasLimit: 10000000, 180 | })) as Pv2SmartPool; 181 | 182 | console.log(`Pv2SmartPool deployed at: ${smartpool.address}`); 183 | 184 | return smartpool; 185 | }); 186 | 187 | task("init-smart-pool", "initialises a smart pool") 188 | .addParam("smartPool", "Smart pool address") 189 | .addParam("pool", "Balancer pool address (should have tokens binded)") 190 | .addParam("name", "Name of the token") 191 | .addParam("symbol", "Symbol of the token") 192 | .addParam("initialSupply", "Initial supply of the token") 193 | .setAction(async(taskArgs, { ethers }) => { 194 | const signers = await ethers.getSigners(); 195 | const smartpool = Pv2SmartPoolFactory.connect(taskArgs.smartPool, signers[0]); 196 | const tx = await smartpool.init(taskArgs.pool, taskArgs.name, taskArgs.symbol, utils.parseEther(taskArgs.initialSupply)); 197 | const receipt = await tx.wait(1); 198 | 199 | console.log(`Smart pool initialised: ${receipt.transactionHash}`); 200 | }); 201 | 202 | task("deploy-smart-pool-implementation-complete") 203 | .addParam("implName") 204 | .setAction(async(taskArgs, { ethers, run }) => { 205 | const signers = await ethers.getSigners(); 206 | 207 | // Deploy capped pool 208 | const implementation = await run("deploy-pie-smart-pool"); 209 | 210 | console.log(`Implementation deployed at: ${implementation.address}`); 211 | // Init capped smart pool 212 | await run("init-smart-pool", { 213 | smartPool: implementation.address, 214 | pool: PLACE_HOLDER_ADDRESS, 215 | name: taskArgs.implName, 216 | symbol: taskArgs.implName, 217 | initialSupply: "1337" 218 | }); 219 | 220 | return implementation; 221 | }); 222 | 223 | task("deploy-smart-pool-complete") 224 | .addParam("balancerFactory", "Address of the balancer factory. defaults to mainnet balancer factory", "0x9424B1412450D0f8Fc2255FAf6046b98213B76Bd") 225 | .addParam("allocation", "path to allocation") 226 | .setAction(async(taskArgs, { ethers, run }) => { 227 | // run deploy factory task 228 | const smartPoolFactoryAddress = await run("deploy-pie-smart-pool-factory", {balancerFactory: taskArgs.balancerFactory}); 229 | 230 | // run deploy pool from factory task 231 | await run("deploy-pool-from-factory", { factory: smartPoolFactoryAddress, allocation: taskArgs.allocation }); 232 | }); 233 | 234 | task("set-cap", "Sets the cap on a capped pool") 235 | .addParam("pool") 236 | .addParam("cap") 237 | .setAction(async(taskArgs, { ethers }) => { 238 | const signers = await ethers.getSigners(); 239 | const smartpool = Pv2SmartPoolFactory.connect(taskArgs.pool, signers[0]); 240 | const tx = await smartpool.setCap(parseEther(taskArgs.cap), {gasLimit: 2000000}); 241 | 242 | console.log(`Cap set tx: ${tx.hash}`); 243 | }); 244 | 245 | 246 | task("join-smart-pool") 247 | .addParam("pool") 248 | .addParam("amount") 249 | .setAction(async(taskArgs, { ethers }) => { 250 | const signers = await ethers.getSigners(); 251 | const smartpool = Pv2SmartPoolFactory.connect(taskArgs.pool, signers[0]); 252 | 253 | // TODO fix this confusing line 254 | const tokens = await IbPoolFactory.connect(await smartpool.getBPool(), signers[0]).getCurrentTokens(); 255 | 256 | for(const tokenAddress of tokens) { 257 | const token = Ierc20Factory.connect(tokenAddress, signers[0]); 258 | // TODO make below more readable 259 | console.log("approving tokens"); 260 | await (await token.approve(smartpool.address, constants.MaxUint256)).wait(1); 261 | } 262 | const tx = await smartpool.joinPool(parseEther(taskArgs.amount), {gasLimit: 2000000}); 263 | const receipt = await tx.wait(1); 264 | 265 | console.log(`Pool joined tx: ${receipt.transactionHash}`) 266 | }); 267 | 268 | task("approve-smart-pool") 269 | .addParam("pool") 270 | .setAction(async(taskArgs, { ethers }) => { 271 | const signers = await ethers.getSigners(); 272 | const smartpool = Pv2SmartPoolFactory.connect(taskArgs.pool, signers[0]); 273 | 274 | // TODO fix this confusing line 275 | const tokens = await IbPoolFactory.connect(await smartpool.bPool(), signers[0]).getCurrentTokens(); 276 | 277 | for(const tokenAddress of tokens) { 278 | const token = Ierc20Factory.connect(tokenAddress, signers[0]); 279 | // TODO make below more readable 280 | const receipt = await (await token.approve(smartpool.address, constants.MaxUint256)).wait(1); 281 | console.log(`${tokenAddress} approved tx: ${receipt.transactionHash}`); 282 | } 283 | }); 284 | 285 | task("deploy-mock-token", "deploys a mock token") 286 | .addParam("name", "Name of the token") 287 | .addParam("symbol", "Symbol of the token") 288 | .addParam("decimals", "Amount of decimals", "18") 289 | .setAction(async(taskArgs, { ethers }) => { 290 | const signers = await ethers.getSigners(); 291 | const factory = await new MockTokenFactory(signers[0]); 292 | const token = await factory.deploy(taskArgs.name, taskArgs.symbol, taskArgs.decimals); 293 | await token.mint(await signers[0].getAddress(), constants.WeiPerEther.mul(10000000000000)); 294 | console.log(`Deployed token at: ${token.address}`); 295 | }); 296 | 297 | task("deploy-balancer-factory", "deploys a balancer factory") 298 | .setAction(async(taskArgs, { ethers }) => { 299 | const signers = await ethers.getSigners(); 300 | const factoryAddress = await deployBalancerFactory(signers[0]); 301 | 302 | console.log(`Deployed balancer factory at: ${factoryAddress}`); 303 | }); 304 | 305 | task("deploy-balancer-pool", "deploys a balancer pool from a factory") 306 | .addParam("factory", "Address of the balancer pool address") 307 | .setAction(async(taskArgs, { ethers }) => { 308 | const signers = await ethers.getSigners(); 309 | const factory = await IbFactoryFactory.connect(taskArgs.factory, signers[0]); 310 | const tx = await factory.newBPool(); 311 | const receipt = await tx.wait(2); // wait for 2 confirmations 312 | const event = receipt.events.pop(); 313 | console.log(`Deployed balancer pool at : ${event.address}`); 314 | }); 315 | 316 | task("balancer-bind-token", "binds a token to a balancer pool") 317 | .addParam("pool", "the address of the Balancer pool") 318 | .addParam("token", "address of the token to bind") 319 | .addParam("balance", "amount of token to bind") 320 | .addParam("weight", "denormalised weight (max total weight = 50, min_weight = 1 == 2%") 321 | .addParam("decimals", "amount of decimals the token has", "18") 322 | .setAction(async(taskArgs, { ethers }) => { 323 | // Approve token 324 | const signers = await ethers.getSigners(); 325 | const account = await signers[0].getAddress(); 326 | const pool = IbPoolFactory.connect(taskArgs.pool, signers[0]); 327 | 328 | const weight = parseUnits(taskArgs.weight, 18); 329 | // tslint:disable-next-line:radix 330 | const balance = utils.parseUnits(taskArgs.balance, parseInt(taskArgs.decimals)); 331 | const token = await Ierc20Factory.connect(taskArgs.token, signers[0]); 332 | 333 | const allowance = await token.allowance(account, pool.address); 334 | 335 | if(allowance.lt(balance)) { 336 | await token.approve(pool.address, constants.MaxUint256); 337 | } 338 | 339 | const tx = await pool.bind(taskArgs.token, balance, weight, {gasLimit: 1000000}); 340 | const receipt = await tx.wait(1); 341 | 342 | console.log(`Token bound tx: ${receipt.transactionHash}`); 343 | }); 344 | 345 | task("balancer-unbind-token", "removed a balancer token from a pool") 346 | .addParam("pool", "the address of the balancer pool") 347 | .addParam("token", "the address of the token to unbind") 348 | .setAction(async(taskArgs, { ethers }) => { 349 | const signers = await ethers.getSigners(); 350 | const account = await signers[0].getAddress(); 351 | const pool = IbPoolFactory.connect(taskArgs.pool, signers[0]); 352 | 353 | const tx = await pool.unbind(taskArgs.token); 354 | const receipt = await tx.wait(1); 355 | 356 | console.log(`Token unbound tx: ${receipt.transactionHash}`); 357 | }); 358 | 359 | task("balancer-set-controller") 360 | .addParam("pool") 361 | .addParam("controller") 362 | .setAction(async(taskArgs, { ethers }) => { 363 | const signers = await ethers.getSigners(); 364 | const pool = IbPoolFactory.connect(taskArgs.pool, signers[0]); 365 | 366 | const tx = await pool.setController(taskArgs.controller); 367 | const receipt = await tx.wait(1); 368 | 369 | console.log(`Controller set tx: ${receipt.transactionHash}`); 370 | }); 371 | 372 | 373 | task("deploy-libraries", "deploys all external libraries") 374 | .setAction(async(taskArgs, { ethers, deployments }) => { 375 | const signers = await ethers.getSigners(); 376 | const {deploy} = deployments; 377 | const libraries: any[] = []; 378 | 379 | libraries.push(await deployAndGetLibObject(LibAddRemoveTokenArtifact, signers[0])); 380 | libraries.push(await deployAndGetLibObject(LibPoolEntryExitArtifact, signers[0])); 381 | libraries.push(await deployAndGetLibObject(LibWeightsArtifact, signers[0])); 382 | libraries.push(await deployAndGetLibObject(LibPoolMathArtifact, signers[0])); 383 | 384 | return libraries; 385 | }); 386 | 387 | task("deploy-libraries-and-get-object") 388 | .setAction(async(taskArgs, { ethers, run }) => { 389 | const libraries = await run("deploy-libraries"); 390 | 391 | const libObject: any = {}; 392 | 393 | for (const lib of libraries) { 394 | libObject[lib.name] = lib.address; 395 | } 396 | 397 | return libObject; 398 | 399 | }); 400 | 401 | // Use only in testing! 402 | internalTask("deploy-libraries-and-smartpool") 403 | .setAction(async(taskArgs, { ethers, run, deployments}) => { 404 | const {deploy} = deployments; 405 | const signers = await ethers.getSigners(); 406 | const libraries = await run("deploy-libraries-and-get-object"); 407 | 408 | console.log("libraries"); 409 | console.log(libraries); 410 | 411 | const contract = (await deploy("PV2SmartPool", {contractName: "PV2SmartPool", from: await signers[0].getAddress(), libraries})); 412 | 413 | return Pv2SmartPoolFactory.connect(contract.address, signers[0]); 414 | }); 415 | 416 | 417 | export default config; 418 | -------------------------------------------------------------------------------- /contracts/Ownable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.4; 2 | 3 | 4 | import {OwnableStorage as OStorage} from "./storage/OwnableStorage.sol"; 5 | 6 | contract Ownable { 7 | event OwnerChanged(address indexed previousOwner, address indexed newOwner); 8 | 9 | modifier onlyOwner() { 10 | require(msg.sender == OStorage.load().owner, "Ownable.onlyOwner: msg.sender not owner"); 11 | _; 12 | } 13 | 14 | /** 15 | @notice Transfer ownership to a new address 16 | @param _newOwner Address of the new owner 17 | */ 18 | function transferOwnership(address _newOwner) external onlyOwner { 19 | _setOwner(_newOwner); 20 | } 21 | 22 | /** 23 | @notice Internal method to set the owner 24 | @param _newOwner Address of the new owner 25 | */ 26 | function _setOwner(address _newOwner) internal { 27 | OStorage.StorageStruct storage s = OStorage.load(); 28 | emit OwnerChanged(s.owner, _newOwner); 29 | s.owner = _newOwner; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /contracts/PCToken.sol: -------------------------------------------------------------------------------- 1 | // This program is free software: you can redistribute it and/or modify 2 | // it under the terms of the GNU General Public License as published by 3 | // the Free Software Foundation, either version 3 of the License, or 4 | // (at your option) any later version. 5 | 6 | // This program is distributed in the hope that it will be useful, 7 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | // GNU General Public License for more details. 10 | 11 | // You should have received a copy of the GNU General Public License 12 | // along with this program. If not, see . 13 | 14 | pragma solidity 0.6.4; 15 | 16 | import {PCTokenStorage as PCStorage} from "./storage/PCTokenStorage.sol"; 17 | import "./libraries/LibPoolToken.sol"; 18 | import "./libraries/Math.sol"; 19 | import "./interfaces/IERC20.sol"; 20 | 21 | 22 | // Highly opinionated token implementation 23 | // Based on the balancer Implementation 24 | 25 | contract PCToken is IERC20 { 26 | using Math for uint256; 27 | 28 | event Approval(address indexed _src, address indexed _dst, uint256 _amount); 29 | event Transfer(address indexed _src, address indexed _dst, uint256 _amount); 30 | 31 | uint8 public constant decimals = 18; 32 | 33 | function _mint(uint256 _amount) internal { 34 | LibPoolToken._mint(address(this), _amount); 35 | } 36 | 37 | function _burn(uint256 _amount) internal { 38 | LibPoolToken._burn(address(this), _amount); 39 | } 40 | 41 | function _move( 42 | address _src, 43 | address _dst, 44 | uint256 _amount 45 | ) internal { 46 | PCStorage.StorageStruct storage s = PCStorage.load(); 47 | require(s.balance[_src] >= _amount, "ERR_INSUFFICIENT_BAL"); 48 | s.balance[_src] = s.balance[_src].bsub(_amount); 49 | s.balance[_dst] = s.balance[_dst].badd(_amount); 50 | emit Transfer(_src, _dst, _amount); 51 | } 52 | 53 | function _push(address _to, uint256 _amount) internal { 54 | _move(address(this), _to, _amount); 55 | } 56 | 57 | function _pull(address _from, uint256 _amount) internal { 58 | _move(_from, address(this), _amount); 59 | } 60 | 61 | function allowance(address _src, address _dst) external override view returns (uint256) { 62 | return PCStorage.load().allowance[_src][_dst]; 63 | } 64 | 65 | function balanceOf(address _whom) external override view returns (uint256) { 66 | return PCStorage.load().balance[_whom]; 67 | } 68 | 69 | function totalSupply() public override view returns (uint256) { 70 | return PCStorage.load().totalSupply; 71 | } 72 | 73 | function name() external view returns (string memory) { 74 | return PCStorage.load().name; 75 | } 76 | 77 | function symbol() external view returns (string memory) { 78 | return PCStorage.load().symbol; 79 | } 80 | 81 | function approve(address _dst, uint256 _amount) external override returns (bool) { 82 | PCStorage.load().allowance[msg.sender][_dst] = _amount; 83 | emit Approval(msg.sender, _dst, _amount); 84 | return true; 85 | } 86 | 87 | function increaseApproval(address _dst, uint256 _amount) external returns (bool) { 88 | PCStorage.StorageStruct storage s = PCStorage.load(); 89 | s.allowance[msg.sender][_dst] = s.allowance[msg.sender][_dst].badd(_amount); 90 | emit Approval(msg.sender, _dst, s.allowance[msg.sender][_dst]); 91 | return true; 92 | } 93 | 94 | function decreaseApproval(address _dst, uint256 _amount) external returns (bool) { 95 | PCStorage.StorageStruct storage s = PCStorage.load(); 96 | uint256 oldValue = s.allowance[msg.sender][_dst]; 97 | if (_amount > oldValue) { 98 | s.allowance[msg.sender][_dst] = 0; 99 | } else { 100 | s.allowance[msg.sender][_dst] = oldValue.bsub(_amount); 101 | } 102 | emit Approval(msg.sender, _dst, s.allowance[msg.sender][_dst]); 103 | return true; 104 | } 105 | 106 | function transfer(address _dst, uint256 _amount) external override returns (bool) { 107 | _move(msg.sender, _dst, _amount); 108 | return true; 109 | } 110 | 111 | function transferFrom( 112 | address _src, 113 | address _dst, 114 | uint256 _amount 115 | ) external override returns (bool) { 116 | PCStorage.StorageStruct storage s = PCStorage.load(); 117 | require( 118 | msg.sender == _src || _amount <= s.allowance[_src][msg.sender], 119 | "ERR_PCTOKEN_BAD_CALLER" 120 | ); 121 | _move(_src, _dst, _amount); 122 | if (msg.sender != _src && s.allowance[_src][msg.sender] != uint256(-1)) { 123 | s.allowance[_src][msg.sender] = s.allowance[_src][msg.sender].bsub(_amount); 124 | emit Approval(msg.sender, _dst, s.allowance[_src][msg.sender]); 125 | } 126 | return true; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /contracts/ReentryProtection.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.4; 2 | 3 | import {ReentryProtectionStorage as RPStorage} from "./storage/ReentryProtectionStorage.sol"; 4 | 5 | contract ReentryProtection { 6 | 7 | modifier noReentry { 8 | // Use counter to only write to storage once 9 | RPStorage.StorageStruct storage s = RPStorage.load(); 10 | s.lockCounter++; 11 | uint256 lockValue = s.lockCounter; 12 | _; 13 | require(lockValue == s.lockCounter, "ReentryProtection.noReentry: reentry detected"); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /contracts/factory/PProxiedFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.4; 2 | 3 | import "@pie-dao/proxy/contracts/PProxyPausable.sol"; 4 | 5 | import "../interfaces/IBFactory.sol"; 6 | import "../interfaces/IBPool.sol"; 7 | import "../interfaces/IERC20.sol"; 8 | import "../Ownable.sol"; 9 | import "../interfaces/IPV2SmartPool.sol"; 10 | import "../libraries/LibSafeApprove.sol"; 11 | 12 | contract PProxiedFactory is Ownable { 13 | using LibSafeApprove for IERC20; 14 | 15 | IBFactory public balancerFactory; 16 | address public smartPoolImplementation; 17 | mapping(address => bool) public isPool; 18 | address[] public pools; 19 | 20 | event SmartPoolCreated(address indexed poolAddress, string name, string symbol); 21 | 22 | function init(address _balancerFactory, address _implementation) public { 23 | require(smartPoolImplementation == address(0), "Already initialised"); 24 | _setOwner(msg.sender); 25 | balancerFactory = IBFactory(_balancerFactory); 26 | 27 | smartPoolImplementation = _implementation; 28 | } 29 | 30 | function setImplementation(address _implementation) external onlyOwner { 31 | smartPoolImplementation = _implementation; 32 | } 33 | 34 | function newProxiedSmartPool( 35 | string memory _name, 36 | string memory _symbol, 37 | uint256 _initialSupply, 38 | address[] memory _tokens, 39 | uint256[] memory _amounts, 40 | uint256[] memory _weights, 41 | uint256 _cap 42 | ) public onlyOwner returns (address) { 43 | // Deploy proxy contract 44 | PProxyPausable proxy = new PProxyPausable(); 45 | 46 | // Setup proxy 47 | proxy.setImplementation(smartPoolImplementation); 48 | proxy.setPauzer(msg.sender); 49 | proxy.setProxyOwner(msg.sender); 50 | 51 | // Setup balancer pool 52 | address balancerPoolAddress = balancerFactory.newBPool(); 53 | IBPool bPool = IBPool(balancerPoolAddress); 54 | 55 | for (uint256 i = 0; i < _tokens.length; i++) { 56 | IERC20 token = IERC20(_tokens[i]); 57 | // Transfer tokens to this contract 58 | token.transferFrom(msg.sender, address(this), _amounts[i]); 59 | // Approve the balancer pool 60 | token.safeApprove(balancerPoolAddress, uint256(-1)); 61 | // Bind tokens 62 | bPool.bind(_tokens[i], _amounts[i], _weights[i]); 63 | } 64 | bPool.setController(address(proxy)); 65 | 66 | // Setup smart pool 67 | IPV2SmartPool smartPool = IPV2SmartPool(address(proxy)); 68 | 69 | smartPool.init(balancerPoolAddress, _name, _symbol, _initialSupply); 70 | smartPool.setCap(_cap); 71 | smartPool.setPublicSwapSetter(msg.sender); 72 | smartPool.setTokenBinder(msg.sender); 73 | smartPool.setController(msg.sender); 74 | smartPool.approveTokens(); 75 | 76 | isPool[address(smartPool)] = true; 77 | pools.push(address(smartPool)); 78 | 79 | emit SmartPoolCreated(address(smartPool), _name, _symbol); 80 | 81 | smartPool.transfer(msg.sender, _initialSupply); 82 | 83 | return address(smartPool); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /contracts/interfaces/IBFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.4; 2 | 3 | interface IBFactory { 4 | function newBPool() external returns (address); 5 | } 6 | -------------------------------------------------------------------------------- /contracts/interfaces/IBPool.sol: -------------------------------------------------------------------------------- 1 | // This program is free software: you can redistribute it and/or modify 2 | // it under the terms of the GNU General Public License as published by 3 | // the Free Software Foundation, either version 3 of the License, or 4 | // (at your option) any later version. 5 | 6 | // This program is disstributed in the hope that it will be useful, 7 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 8 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 | // GNU General Public License for more details. 10 | 11 | // You should have received a copy of the GNU General Public License 12 | // along with this program. If not, see . 13 | 14 | pragma solidity 0.6.4; 15 | 16 | interface IBPool { 17 | function isBound(address token) external view returns (bool); 18 | 19 | function getBalance(address token) external view returns (uint256); 20 | 21 | function rebind( 22 | address token, 23 | uint256 balance, 24 | uint256 denorm 25 | ) external; 26 | 27 | function setSwapFee(uint256 swapFee) external; 28 | 29 | function setPublicSwap(bool _public) external; 30 | 31 | function bind( 32 | address token, 33 | uint256 balance, 34 | uint256 denorm 35 | ) external; 36 | 37 | function unbind(address token) external; 38 | 39 | function getDenormalizedWeight(address token) external view returns (uint256); 40 | 41 | function getTotalDenormalizedWeight() external view returns (uint256); 42 | 43 | function getCurrentTokens() external view returns (address[] memory); 44 | 45 | function setController(address manager) external; 46 | 47 | function isPublicSwap() external view returns (bool); 48 | 49 | function getSwapFee() external view returns (uint256); 50 | 51 | function gulp(address token) external; 52 | 53 | function calcPoolOutGivenSingleIn( 54 | uint256 tokenBalanceIn, 55 | uint256 tokenWeightIn, 56 | uint256 poolSupply, 57 | uint256 totalWeight, 58 | uint256 tokenAmountIn, 59 | uint256 swapFee 60 | ) external pure returns (uint256 poolAmountOut); 61 | 62 | function calcSingleInGivenPoolOut( 63 | uint256 tokenBalanceIn, 64 | uint256 tokenWeightIn, 65 | uint256 poolSupply, 66 | uint256 totalWeight, 67 | uint256 poolAmountOut, 68 | uint256 swapFee 69 | ) external pure returns (uint256 tokenAmountIn); 70 | 71 | function calcSingleOutGivenPoolIn( 72 | uint256 tokenBalanceOut, 73 | uint256 tokenWeightOut, 74 | uint256 poolSupply, 75 | uint256 totalWeight, 76 | uint256 poolAmountIn, 77 | uint256 swapFee 78 | ) external pure returns (uint256 tokenAmountOut); 79 | 80 | function calcPoolInGivenSingleOut( 81 | uint256 tokenBalanceOut, 82 | uint256 tokenWeightOut, 83 | uint256 poolSupply, 84 | uint256 totalWeight, 85 | uint256 tokenAmountOut, 86 | uint256 swapFee 87 | ) external pure returns (uint256 poolAmountIn); 88 | } 89 | -------------------------------------------------------------------------------- /contracts/interfaces/IERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.4; 2 | 3 | interface IERC20 { 4 | event Approval(address indexed _src, address indexed _dst, uint256 _amount); 5 | event Transfer(address indexed _src, address indexed _dst, uint256 _amount); 6 | 7 | function totalSupply() external view returns (uint256); 8 | 9 | function balanceOf(address _whom) external view returns (uint256); 10 | 11 | function allowance(address _src, address _dst) external view returns (uint256); 12 | 13 | function approve(address _dst, uint256 _amount) external returns (bool); 14 | 15 | function transfer(address _dst, uint256 _amount) external returns (bool); 16 | 17 | function transferFrom( 18 | address _src, 19 | address _dst, 20 | uint256 _amount 21 | ) external returns (bool); 22 | } 23 | -------------------------------------------------------------------------------- /contracts/interfaces/IPV2SmartPool.sol: -------------------------------------------------------------------------------- 1 | pragma experimental ABIEncoderV2; 2 | pragma solidity ^0.6.4; 3 | 4 | import "../interfaces/IERC20.sol"; 5 | import {PV2SmartPoolStorage as P2Storage} from "../storage/PV2SmartPoolStorage.sol"; 6 | 7 | interface IPV2SmartPool is IERC20 { 8 | /** 9 | @notice Initialise smart pool. Can only be called once 10 | @param _bPool Address of the underlying bPool 11 | @param _name Token name 12 | @param _symbol Token symbol (ticker) 13 | @param _initialSupply Initial token supply 14 | */ 15 | function init( 16 | address _bPool, 17 | string calldata _name, 18 | string calldata _symbol, 19 | uint256 _initialSupply 20 | ) external; 21 | 22 | /** 23 | @notice Set the address that can set public swap enabled or disabled. 24 | Can only be called by the controller 25 | @param _swapSetter Address of the new swapSetter 26 | */ 27 | function setPublicSwapSetter(address _swapSetter) external; 28 | 29 | /** 30 | @notice Set the address that can bind, unbind and rebind tokens. 31 | Can only be called by the controller 32 | @param _tokenBinder Address of the new token binder 33 | */ 34 | function setTokenBinder(address _tokenBinder) external; 35 | 36 | /** 37 | @notice Enable or disable trading on the underlying balancer pool. 38 | Can only be called by the public swap setter 39 | @param _public Wether public swap is enabled or not 40 | */ 41 | function setPublicSwap(bool _public) external; 42 | 43 | /** 44 | @notice Set the swap fee. Can only be called by the controller 45 | @param _swapFee The new swap fee. 10**18 == 100%. Max 10% 46 | */ 47 | function setSwapFee(uint256 _swapFee) external; 48 | 49 | /** 50 | @notice Set the totalSuppy cap. Can only be called by the controller 51 | @param _cap New cap 52 | */ 53 | function setCap(uint256 _cap) external; 54 | 55 | /** 56 | @notice Set the annual fee. Can only be called by the controller 57 | @param _newFee new fee 10**18 == 100% per 365 days. Max 10% 58 | */ 59 | function setAnnualFee(uint256 _newFee) external; 60 | 61 | /** 62 | @notice Charge the outstanding annual fee 63 | */ 64 | function chargeOutstandingAnnualFee() external; 65 | 66 | /** 67 | @notice Set the address that receives the annual fee. Can only be called by the controller 68 | */ 69 | function setFeeRecipient(address _newRecipient) external; 70 | 71 | /** 72 | @notice Set the controller address. Can only be called by the current address 73 | @param _controller Address of the new controller 74 | */ 75 | function setController(address _controller) external; 76 | 77 | /** 78 | @notice Set the circuit breaker address. Can only be called by the controller 79 | @param _newCircuitBreaker Address of the new circuit breaker 80 | */ 81 | function setCircuitBreaker(address _newCircuitBreaker) external; 82 | 83 | /** 84 | @notice Enable or disable joining and exiting 85 | @param _newValue enabled or not 86 | */ 87 | function setJoinExitEnabled(bool _newValue) external; 88 | 89 | /** 90 | @notice Trip the circuit breaker which disabled exit, join and swaps 91 | */ 92 | function tripCircuitBreaker() external; 93 | 94 | /** 95 | @notice Update the weight of a token. Can only be called by the controller 96 | @param _token Token to adjust the weight of 97 | @param _newWeight New denormalized weight 98 | */ 99 | function updateWeight(address _token, uint256 _newWeight) external; 100 | 101 | /** 102 | @notice Gradually adjust the weights of a token. Can only be called by the controller 103 | @param _newWeights Target weights 104 | @param _startBlock Block to start weight adjustment 105 | @param _endBlock Block to finish weight adjustment 106 | */ 107 | function updateWeightsGradually( 108 | uint256[] calldata _newWeights, 109 | uint256 _startBlock, 110 | uint256 _endBlock 111 | ) external; 112 | 113 | /** 114 | @notice Poke the weight adjustment 115 | */ 116 | function pokeWeights() external; 117 | 118 | /** 119 | @notice Apply the adding of a token. Can only be called by the controller 120 | */ 121 | function applyAddToken() external; 122 | 123 | /** 124 | @notice Commit a token to be added. Can only be called by the controller 125 | @param _token Address of the token to add 126 | @param _balance Amount of token to add 127 | @param _denormalizedWeight Denormalized weight 128 | */ 129 | function commitAddToken( 130 | address _token, 131 | uint256 _balance, 132 | uint256 _denormalizedWeight 133 | ) external; 134 | 135 | /** 136 | @notice Remove a token from the smart pool. Can only be called by the controller 137 | @param _token Address of the token to remove 138 | */ 139 | function removeToken(address _token) external; 140 | 141 | /** 142 | @notice Approve bPool to pull tokens from smart pool 143 | */ 144 | function approveTokens() external; 145 | 146 | /** 147 | @notice Mint pool tokens, locking underlying assets 148 | @param _amount Amount of pool tokens 149 | */ 150 | function joinPool(uint256 _amount) external; 151 | 152 | /** 153 | @notice Mint pool tokens, locking underlying assets. With front running protection 154 | @param _amount Amount of pool tokens 155 | @param _maxAmountsIn Maximum amounts of underlying assets 156 | */ 157 | function joinPool(uint256 _amount, uint256[] calldata _maxAmountsIn) external; 158 | 159 | /** 160 | @notice Burn pool tokens and redeem underlying assets 161 | @param _amount Amount of pool tokens to burn 162 | */ 163 | function exitPool(uint256 _amount) external; 164 | 165 | /** 166 | @notice Burn pool tokens and redeem underlying assets. With front running protection 167 | @param _amount Amount of pool tokens to burn 168 | @param _minAmountsOut Minimum amounts of underlying assets 169 | */ 170 | function exitPool(uint256 _amount, uint256[] calldata _minAmountsOut) external; 171 | 172 | /** 173 | @notice Join with a single asset, given amount of token in 174 | @param _token Address of the underlying token to deposit 175 | @param _amountIn Amount of underlying asset to deposit 176 | @param _minPoolAmountOut Minimum amount of pool tokens to receive 177 | */ 178 | function joinswapExternAmountIn( 179 | address _token, 180 | uint256 _amountIn, 181 | uint256 _minPoolAmountOut 182 | ) external returns (uint256); 183 | 184 | /** 185 | @notice Join with a single asset, given amount pool out 186 | @param _token Address of the underlying token to deposit 187 | @param _amountOut Amount of pool token to mint 188 | @param _maxAmountIn Maximum amount of underlying asset 189 | */ 190 | function joinswapPoolAmountOut( 191 | address _token, 192 | uint256 _amountOut, 193 | uint256 _maxAmountIn 194 | ) external returns (uint256 tokenAmountIn); 195 | 196 | /** 197 | @notice Exit with a single asset, given pool amount in 198 | @param _token Address of the underlying token to withdraw 199 | @param _poolAmountIn Amount of pool token to burn 200 | @param _minAmountOut Minimum amount of underlying asset to withdraw 201 | */ 202 | function exitswapPoolAmountIn( 203 | address _token, 204 | uint256 _poolAmountIn, 205 | uint256 _minAmountOut 206 | ) external returns (uint256 tokenAmountOut); 207 | 208 | /** 209 | @notice Exit with a single asset, given token amount out 210 | @param _token Address of the underlying token to withdraw 211 | @param _tokenAmountOut Amount of underlying asset to withdraw 212 | @param _maxPoolAmountIn Maximimum pool amount to burn 213 | */ 214 | function exitswapExternAmountOut( 215 | address _token, 216 | uint256 _tokenAmountOut, 217 | uint256 _maxPoolAmountIn 218 | ) external returns (uint256 poolAmountIn); 219 | 220 | /** 221 | @notice Exit pool, ignoring some tokens 222 | @param _amount Amount of pool tokens to burn 223 | @param _lossTokens Addresses of tokens to ignore 224 | */ 225 | function exitPoolTakingloss(uint256 _amount, address[] calldata _lossTokens) external; 226 | 227 | /** 228 | @notice Bind(add) a token to the pool 229 | @param _token Address of the token to bind 230 | @param _balance Amount of token to bind 231 | @param _denorm Denormalised weight 232 | */ 233 | function bind( 234 | address _token, 235 | uint256 _balance, 236 | uint256 _denorm 237 | ) external; 238 | 239 | /** 240 | @notice Rebind(adjust) a token's weight or amount 241 | @param _token Address of the token to rebind 242 | @param _balance New token amount 243 | @param _denorm New denormalised weight 244 | */ 245 | function rebind( 246 | address _token, 247 | uint256 _balance, 248 | uint256 _denorm 249 | ) external; 250 | 251 | /** 252 | @notice Unbind(remove) a token from the smart pool 253 | @param _token Address of the token to unbind 254 | */ 255 | function unbind(address _token) external; 256 | 257 | /** 258 | @notice Get the controller address 259 | @return Address of the controller 260 | */ 261 | function getController() external view returns (address); 262 | 263 | /** 264 | @notice Get the public swap setter address 265 | @return Address of the public swap setter 266 | */ 267 | function getPublicSwapSetter() external view returns (address); 268 | 269 | /** 270 | @notice Get the address of the token binder 271 | @return Token binder address 272 | */ 273 | function getTokenBinder() external view returns (address); 274 | 275 | /** 276 | @notice Get the circuit breaker address 277 | @return Circuit breaker address 278 | */ 279 | function getCircuitBreaker() external view returns (address); 280 | 281 | /** 282 | @notice Get if public trading is enabled or not 283 | @return Enabled or not 284 | */ 285 | function isPublicSwap() external view returns (bool); 286 | 287 | /** 288 | @notice Get the current tokens in the smart pool 289 | @return Addresses of the tokens in the smart pool 290 | */ 291 | function getTokens() external view returns (address[] memory); 292 | 293 | /** 294 | @notice Get the totalSupply cap 295 | @return The totalSupply cap 296 | */ 297 | function getCap() external view returns (uint256); 298 | 299 | /** 300 | @notice Get the annual fee 301 | @return the annual fee 302 | */ 303 | function getAnnualFee() external view returns (uint256); 304 | 305 | /** 306 | @notice Get the address receiving the fees 307 | @return Fee recipient address 308 | */ 309 | function getFeeRecipient() external view returns (address); 310 | 311 | /** 312 | @notice Get the denormalized weight of a token 313 | @param _token Address of the token 314 | @return The denormalised weight of the token 315 | */ 316 | function getDenormalizedWeight(address _token) external view returns (uint256); 317 | 318 | /** 319 | @notice Get all denormalized weights 320 | @return weights Denormalized weights 321 | */ 322 | function getDenormalizedWeights() external view returns (uint256[] memory weights); 323 | 324 | /** 325 | @notice Get the target weights 326 | @return weights Target weights 327 | */ 328 | function getNewWeights() external view returns (uint256[] memory weights); 329 | 330 | /** 331 | @notice Get weights at start of weight adjustment 332 | @return weights Start weights 333 | */ 334 | function getStartWeights() external view returns (uint256[] memory weights); 335 | 336 | /** 337 | @notice Get start block of weight adjustment 338 | @return Start block 339 | */ 340 | function getStartBlock() external view returns (uint256); 341 | 342 | /** 343 | @notice Get end block of weight adjustment 344 | @return End block 345 | */ 346 | function getEndBlock() external view returns (uint256); 347 | 348 | /** 349 | @notice Get new token being added 350 | @return New token 351 | */ 352 | function getNewToken() external view returns (P2Storage.NewToken memory); 353 | 354 | /** 355 | @notice Get if joining and exiting is enabled 356 | @return Enabled or not 357 | */ 358 | function getJoinExitEnabled() external view returns (bool); 359 | 360 | /** 361 | @notice Get the underlying Balancer pool address 362 | @return Address of the underlying Balancer pool 363 | */ 364 | function getBPool() external view returns (address); 365 | 366 | /** 367 | @notice Get the swap fee 368 | @return Swap fee 369 | */ 370 | function getSwapFee() external view returns (uint256); 371 | 372 | /** 373 | @notice Not supported 374 | */ 375 | function finalizeSmartPool() external view; 376 | 377 | /** 378 | @notice Not supported 379 | */ 380 | function createPool(uint256 initialSupply) external view; 381 | 382 | /** 383 | @notice Calculate the amount of underlying needed to mint a certain amount 384 | @return tokens Addresses of the underlying tokens 385 | @return amounts Amounts of the underlying tokens 386 | */ 387 | function calcTokensForAmount(uint256 _amount) 388 | external 389 | view 390 | returns (address[] memory tokens, uint256[] memory amounts); 391 | 392 | /** 393 | @notice Calculate the amount of pool tokens out given underlying in 394 | @param _token Underlying asset to deposit 395 | @param _amount Amount of underlying asset to deposit 396 | @return Pool amount out 397 | */ 398 | function calcPoolOutGivenSingleIn(address _token, uint256 _amount) 399 | external 400 | view 401 | returns (uint256); 402 | 403 | /** 404 | @notice Calculate underlying deposit amount given pool amount out 405 | @param _token Underlying token to deposit 406 | @param _amount Amount of pool out 407 | @return Underlying asset deposit amount 408 | */ 409 | function calcSingleInGivenPoolOut(address _token, uint256 _amount) 410 | external 411 | view 412 | returns (uint256); 413 | 414 | /** 415 | @notice Calculate underlying amount out given pool amount in 416 | @param _token Address of the underlying token to withdraw 417 | @param _amount Pool amount to burn 418 | @return Amount of underlying to withdraw 419 | */ 420 | function calcSingleOutGivenPoolIn(address _token, uint256 _amount) 421 | external 422 | view 423 | returns (uint256); 424 | 425 | /** 426 | @notice Calculate pool amount in given underlying input 427 | @param _token Address of the underlying token to withdraw 428 | @param _amount Underlying output amount 429 | @return Pool burn amount 430 | */ 431 | function calcPoolInGivenSingleOut(address _token, uint256 _amount) 432 | external 433 | view 434 | returns (uint256); 435 | } 436 | -------------------------------------------------------------------------------- /contracts/libraries/LibAddRemoveToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.4; 2 | 3 | import {PBasicSmartPoolStorage as PBStorage} from "../storage/PBasicSmartPoolStorage.sol"; 4 | import {PV2SmartPoolStorage as P2Storage} from "../storage/PV2SmartPoolStorage.sol"; 5 | import {PCTokenStorage as PCStorage} from "../storage/PCTokenStorage.sol"; 6 | import {LibConst as constants} from "./LibConst.sol"; 7 | import "./LibSafeApprove.sol"; 8 | import "./LibPoolToken.sol"; 9 | import "./Math.sol"; 10 | 11 | library LibAddRemoveToken { 12 | using Math for uint256; 13 | using LibSafeApprove for IERC20; 14 | 15 | function applyAddToken() external { 16 | P2Storage.StorageStruct storage ws = P2Storage.load(); 17 | PBStorage.StorageStruct storage s = PBStorage.load(); 18 | 19 | require(ws.newToken.isCommitted, "ERR_NO_TOKEN_COMMIT"); 20 | 21 | uint256 totalSupply = PCStorage.load().totalSupply; 22 | 23 | uint256 poolShares = totalSupply.bmul(ws.newToken.denorm).bdiv( 24 | s.bPool.getTotalDenormalizedWeight() 25 | ); 26 | 27 | ws.newToken.isCommitted = false; 28 | 29 | require( 30 | IERC20(ws.newToken.addr).transferFrom(msg.sender, address(this), ws.newToken.balance), 31 | "ERR_ERC20_FALSE" 32 | ); 33 | 34 | // Cancel potential weight adjustment process. 35 | ws.startBlock = 0; 36 | 37 | // Approves bPool to pull from this controller 38 | IERC20(ws.newToken.addr).safeApprove(address(s.bPool), uint256(-1)); 39 | s.bPool.bind(ws.newToken.addr, ws.newToken.balance, ws.newToken.denorm); 40 | LibPoolToken._mint(msg.sender, poolShares); 41 | } 42 | 43 | function commitAddToken( 44 | address _token, 45 | uint256 _balance, 46 | uint256 _denormalizedWeight 47 | ) external { 48 | P2Storage.StorageStruct storage ws = P2Storage.load(); 49 | PBStorage.StorageStruct storage s = PBStorage.load(); 50 | 51 | require(!s.bPool.isBound(_token), "ERR_IS_BOUND"); 52 | require(_denormalizedWeight <= constants.MAX_WEIGHT, "ERR_WEIGHT_ABOVE_MAX"); 53 | require(_denormalizedWeight >= constants.MIN_WEIGHT, "ERR_WEIGHT_BELOW_MIN"); 54 | require( 55 | s.bPool.getTotalDenormalizedWeight().badd(_denormalizedWeight) <= constants.MAX_TOTAL_WEIGHT, 56 | "ERR_MAX_TOTAL_WEIGHT" 57 | ); 58 | 59 | ws.newToken.addr = _token; 60 | ws.newToken.balance = _balance; 61 | ws.newToken.denorm = _denormalizedWeight; 62 | ws.newToken.commitBlock = block.number; 63 | ws.newToken.isCommitted = true; 64 | } 65 | 66 | function removeToken(address _token) external { 67 | P2Storage.StorageStruct storage ws = P2Storage.load(); 68 | PBStorage.StorageStruct storage s = PBStorage.load(); 69 | 70 | uint256 totalSupply = PCStorage.load().totalSupply; 71 | 72 | // poolShares = totalSupply * tokenWeight / totalWeight 73 | uint256 poolShares = totalSupply.bmul(s.bPool.getDenormalizedWeight(_token)).bdiv( 74 | s.bPool.getTotalDenormalizedWeight() 75 | ); 76 | 77 | // this is what will be unbound from the pool 78 | // Have to get it before unbinding 79 | uint256 balance = s.bPool.getBalance(_token); 80 | 81 | // Cancel potential weight adjustment process. 82 | ws.startBlock = 0; 83 | 84 | // Unbind and get the tokens out of balancer pool 85 | s.bPool.unbind(_token); 86 | 87 | require(IERC20(_token).transfer(msg.sender, balance), "ERR_ERC20_FALSE"); 88 | 89 | LibPoolToken._burn(msg.sender, poolShares); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /contracts/libraries/LibConst.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.4; 2 | 3 | library LibConst { 4 | uint256 internal constant MIN_WEIGHT = 10**18; 5 | uint256 internal constant MAX_WEIGHT = 10**18 * 50; 6 | uint256 internal constant MAX_TOTAL_WEIGHT = 10**18 * 50; 7 | uint256 internal constant MIN_BALANCE = (10**18) / (10**12); 8 | } 9 | -------------------------------------------------------------------------------- /contracts/libraries/LibFees.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.4; 2 | 3 | import "./Math.sol"; 4 | import "./LibPoolToken.sol"; 5 | import {PV2SmartPoolStorage as P2Storage} from "../storage/PV2SmartPoolStorage.sol"; 6 | import {PCTokenStorage as PCStorage} from "../storage/PCTokenStorage.sol"; 7 | 8 | library LibFees { 9 | using Math for uint256; 10 | 11 | uint256 public constant MAX_ANNUAL_FEE = 1 ether / 10; // Max annual fee 12 | 13 | event AnnualFeeClaimed(uint256 amount); 14 | event AnnualFeeChanged(uint256 oldFee, uint256 newFee); 15 | event FeeRecipientChanged(address indexed oldRecipient, address indexed newRecipient); 16 | 17 | function calcOutstandingAnnualFee() internal view returns (uint256) { 18 | P2Storage.StorageStruct storage v2s = P2Storage.load(); 19 | uint256 totalSupply = PCStorage.load().totalSupply; 20 | 21 | uint256 lastClaimed = v2s.lastAnnualFeeClaimed; 22 | 23 | if (lastClaimed == 0) { 24 | return 0; 25 | } 26 | 27 | uint256 timePassed = block.timestamp.bsub(lastClaimed); 28 | // TODO check this calc; 29 | return totalSupply.mul(v2s.annualFee).div(10**18).mul(timePassed).div(365 days); 30 | } 31 | 32 | function chargeOutstandingAnnualFee() internal { 33 | P2Storage.StorageStruct storage v2s = P2Storage.load(); 34 | uint256 outstandingFee = calcOutstandingAnnualFee(); 35 | 36 | if (outstandingFee == 0) { 37 | v2s.lastAnnualFeeClaimed = block.timestamp; 38 | return; 39 | } 40 | 41 | LibPoolToken._mint(v2s.feeRecipient, outstandingFee); 42 | 43 | v2s.lastAnnualFeeClaimed = block.timestamp; 44 | 45 | emit AnnualFeeClaimed(outstandingFee); 46 | } 47 | 48 | function setFeeRecipient(address _newRecipient) internal { 49 | emit FeeRecipientChanged(P2Storage.load().feeRecipient, _newRecipient); 50 | P2Storage.load().feeRecipient = _newRecipient; 51 | } 52 | 53 | function setAnnualFee(uint256 _newFee) internal { 54 | require(_newFee <= MAX_ANNUAL_FEE, "LibFees.setAnnualFee: Annual fee too high"); 55 | // Charge fee when the fee changes 56 | chargeOutstandingAnnualFee(); 57 | emit AnnualFeeChanged(P2Storage.load().annualFee, _newFee); 58 | P2Storage.load().annualFee = _newFee; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /contracts/libraries/LibPoolEntryExit.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.4; 2 | 3 | import {PBasicSmartPoolStorage as PBStorage} from "../storage/PBasicSmartPoolStorage.sol"; 4 | import {PCTokenStorage as PCStorage} from "../storage/PCTokenStorage.sol"; 5 | import "./LibFees.sol"; 6 | 7 | import "./LibPoolToken.sol"; 8 | import "./LibUnderlying.sol"; 9 | import "./Math.sol"; 10 | 11 | library LibPoolEntryExit { 12 | using Math for uint256; 13 | 14 | event LOG_EXIT(address indexed caller, address indexed tokenOut, uint256 tokenAmountOut); 15 | event LOG_JOIN(address indexed caller, address indexed tokenIn, uint256 tokenAmountIn); 16 | event PoolExited(address indexed from, uint256 amount); 17 | event PoolExitedWithLoss(address indexed from, uint256 amount, address[] lossTokens); 18 | event PoolJoined(address indexed from, uint256 amount); 19 | 20 | modifier lockBPoolSwap() { 21 | IBPool bPool = PBStorage.load().bPool; 22 | if(bPool.isPublicSwap()) { 23 | // If public swap is enabled turn it of, execute function and turn it off again 24 | bPool.setPublicSwap(false); 25 | _; 26 | bPool.setPublicSwap(true); 27 | } else { 28 | // If public swap is not enabled just execute 29 | _; 30 | } 31 | } 32 | 33 | function exitPool(uint256 _amount) internal { 34 | IBPool bPool = PBStorage.load().bPool; 35 | uint256[] memory minAmountsOut = new uint256[](bPool.getCurrentTokens().length); 36 | _exitPool(_amount, minAmountsOut); 37 | } 38 | 39 | function exitPool(uint256 _amount, uint256[] calldata _minAmountsOut) external { 40 | _exitPool(_amount, _minAmountsOut); 41 | } 42 | 43 | function _exitPool(uint256 _amount, uint256[] memory _minAmountsOut) internal lockBPoolSwap { 44 | IBPool bPool = PBStorage.load().bPool; 45 | LibFees.chargeOutstandingAnnualFee(); 46 | uint256 poolTotal = PCStorage.load().totalSupply; 47 | uint256 ratio = _amount.bdiv(poolTotal); 48 | require(ratio != 0); 49 | 50 | LibPoolToken._burn(msg.sender, _amount); 51 | 52 | address[] memory tokens = bPool.getCurrentTokens(); 53 | 54 | for (uint256 i = 0; i < tokens.length; i++) { 55 | address token = tokens[i]; 56 | uint256 balance = bPool.getBalance(token); 57 | uint256 tokenAmountOut = ratio.bmul(balance); 58 | 59 | require( 60 | tokenAmountOut >= _minAmountsOut[i], 61 | "LibPoolEntryExit.exitPool: Token amount out too small" 62 | ); 63 | 64 | emit LOG_EXIT(msg.sender, token, tokenAmountOut); 65 | LibUnderlying._pushUnderlying(token, msg.sender, tokenAmountOut, balance); 66 | } 67 | emit PoolExited(msg.sender, _amount); 68 | } 69 | 70 | function exitswapPoolAmountIn( 71 | address _token, 72 | uint256 _poolAmountIn, 73 | uint256 _minAmountOut 74 | ) external lockBPoolSwap returns (uint256 tokenAmountOut) { 75 | IBPool bPool = PBStorage.load().bPool; 76 | LibFees.chargeOutstandingAnnualFee(); 77 | require(bPool.isBound(_token), "LibPoolEntryExit.exitswapPoolAmountIn: Token Not Bound"); 78 | 79 | tokenAmountOut = bPool.calcSingleOutGivenPoolIn( 80 | bPool.getBalance(_token), 81 | bPool.getDenormalizedWeight(_token), 82 | PCStorage.load().totalSupply, 83 | bPool.getTotalDenormalizedWeight(), 84 | _poolAmountIn, 85 | bPool.getSwapFee() 86 | ); 87 | 88 | require( 89 | tokenAmountOut >= _minAmountOut, 90 | "LibPoolEntryExit.exitswapPoolAmountIn: Token Not Bound" 91 | ); 92 | 93 | emit LOG_EXIT(msg.sender, _token, tokenAmountOut); 94 | 95 | LibPoolToken._burn(msg.sender, _poolAmountIn); 96 | 97 | emit PoolExited(msg.sender, tokenAmountOut); 98 | 99 | uint256 bal = bPool.getBalance(_token); 100 | LibUnderlying._pushUnderlying(_token, msg.sender, tokenAmountOut, bal); 101 | 102 | return tokenAmountOut; 103 | } 104 | 105 | function exitswapExternAmountOut( 106 | address _token, 107 | uint256 _tokenAmountOut, 108 | uint256 _maxPoolAmountIn 109 | ) external lockBPoolSwap returns (uint256 poolAmountIn) { 110 | IBPool bPool = PBStorage.load().bPool; 111 | LibFees.chargeOutstandingAnnualFee(); 112 | require(bPool.isBound(_token), "LibPoolEntryExit.exitswapExternAmountOut: Token Not Bound"); 113 | 114 | poolAmountIn = bPool.calcPoolInGivenSingleOut( 115 | bPool.getBalance(_token), 116 | bPool.getDenormalizedWeight(_token), 117 | PCStorage.load().totalSupply, 118 | bPool.getTotalDenormalizedWeight(), 119 | _tokenAmountOut, 120 | bPool.getSwapFee() 121 | ); 122 | 123 | require( 124 | poolAmountIn <= _maxPoolAmountIn, 125 | "LibPoolEntryExit.exitswapExternAmountOut: pool amount in too large" 126 | ); 127 | 128 | emit LOG_EXIT(msg.sender, _token, _tokenAmountOut); 129 | 130 | LibPoolToken._burn(msg.sender, poolAmountIn); 131 | 132 | emit PoolExited(msg.sender, _tokenAmountOut); 133 | 134 | uint256 bal = bPool.getBalance(_token); 135 | LibUnderlying._pushUnderlying(_token, msg.sender, _tokenAmountOut, bal); 136 | 137 | return poolAmountIn; 138 | } 139 | 140 | function exitPoolTakingloss(uint256 _amount, address[] calldata _lossTokens) 141 | external 142 | lockBPoolSwap 143 | { 144 | IBPool bPool = PBStorage.load().bPool; 145 | LibFees.chargeOutstandingAnnualFee(); 146 | uint256 poolTotal = PCStorage.load().totalSupply; 147 | uint256 ratio = _amount.bdiv(poolTotal); 148 | require(ratio != 0); 149 | 150 | LibPoolToken._burn(msg.sender, _amount); 151 | 152 | address[] memory tokens = bPool.getCurrentTokens(); 153 | 154 | for (uint256 i = 0; i < tokens.length; i++) { 155 | // If taking loss on token skip one iteration of the loop 156 | if (_contains(tokens[i], _lossTokens)) { 157 | continue; 158 | } 159 | address t = tokens[i]; 160 | uint256 bal = bPool.getBalance(t); 161 | uint256 tAo = ratio.bmul(bal); 162 | emit LOG_EXIT(msg.sender, t, tAo); 163 | LibUnderlying._pushUnderlying(t, msg.sender, tAo, bal); 164 | } 165 | emit PoolExitedWithLoss(msg.sender, _amount, _lossTokens); 166 | } 167 | 168 | /** 169 | @notice Searches for an address in an array of addresses and returns if found 170 | @param _needle Address to look for 171 | @param _haystack Array to search 172 | @return If value is found 173 | */ 174 | function _contains(address _needle, address[] memory _haystack) internal pure returns (bool) { 175 | for (uint256 i = 0; i < _haystack.length; i++) { 176 | if (_haystack[i] == _needle) { 177 | return true; 178 | } 179 | } 180 | return false; 181 | } 182 | 183 | function joinPool(uint256 _amount) external { 184 | IBPool bPool = PBStorage.load().bPool; 185 | uint256[] memory maxAmountsIn = new uint256[](bPool.getCurrentTokens().length); 186 | for (uint256 i = 0; i < maxAmountsIn.length; i++) { 187 | maxAmountsIn[i] = uint256(-1); 188 | } 189 | _joinPool(_amount, maxAmountsIn); 190 | } 191 | 192 | function joinPool(uint256 _amount, uint256[] calldata _maxAmountsIn) external { 193 | _joinPool(_amount, _maxAmountsIn); 194 | } 195 | 196 | function _joinPool(uint256 _amount, uint256[] memory _maxAmountsIn) internal lockBPoolSwap { 197 | IBPool bPool = PBStorage.load().bPool; 198 | LibFees.chargeOutstandingAnnualFee(); 199 | uint256 poolTotal = PCStorage.load().totalSupply; 200 | uint256 ratio = _amount.bdiv(poolTotal); 201 | require(ratio != 0); 202 | 203 | address[] memory tokens = bPool.getCurrentTokens(); 204 | 205 | for (uint256 i = 0; i < tokens.length; i++) { 206 | address t = tokens[i]; 207 | uint256 bal = bPool.getBalance(t); 208 | uint256 tokenAmountIn = ratio.bmul(bal); 209 | require( 210 | tokenAmountIn <= _maxAmountsIn[i], 211 | "LibPoolEntryExit.joinPool: Token in amount too big" 212 | ); 213 | emit LOG_JOIN(msg.sender, t, tokenAmountIn); 214 | LibUnderlying._pullUnderlying(t, msg.sender, tokenAmountIn, bal); 215 | } 216 | LibPoolToken._mint(msg.sender, _amount); 217 | emit PoolJoined(msg.sender, _amount); 218 | } 219 | 220 | function joinswapExternAmountIn( 221 | address _token, 222 | uint256 _amountIn, 223 | uint256 _minPoolAmountOut 224 | ) external lockBPoolSwap returns (uint256 poolAmountOut) { 225 | IBPool bPool = PBStorage.load().bPool; 226 | LibFees.chargeOutstandingAnnualFee(); 227 | require(bPool.isBound(_token), "LibPoolEntryExit.joinswapExternAmountIn: Token Not Bound"); 228 | 229 | poolAmountOut = bPool.calcPoolOutGivenSingleIn( 230 | bPool.getBalance(_token), 231 | bPool.getDenormalizedWeight(_token), 232 | PCStorage.load().totalSupply, 233 | bPool.getTotalDenormalizedWeight(), 234 | _amountIn, 235 | bPool.getSwapFee() 236 | ); 237 | 238 | require( 239 | poolAmountOut >= _minPoolAmountOut, 240 | "LibPoolEntryExit.joinswapExternAmountIn: Insufficient pool amount out" 241 | ); 242 | 243 | emit LOG_JOIN(msg.sender, _token, _amountIn); 244 | 245 | LibPoolToken._mint(msg.sender, poolAmountOut); 246 | 247 | emit PoolJoined(msg.sender, poolAmountOut); 248 | 249 | uint256 bal = bPool.getBalance(_token); 250 | LibUnderlying._pullUnderlying(_token, msg.sender, _amountIn, bal); 251 | 252 | return poolAmountOut; 253 | } 254 | 255 | function joinswapPoolAmountOut( 256 | address _token, 257 | uint256 _amountOut, 258 | uint256 _maxAmountIn 259 | ) external lockBPoolSwap returns (uint256 tokenAmountIn) { 260 | IBPool bPool = PBStorage.load().bPool; 261 | LibFees.chargeOutstandingAnnualFee(); 262 | require(bPool.isBound(_token), "LibPoolEntryExit.joinswapPoolAmountOut: Token Not Bound"); 263 | 264 | tokenAmountIn = bPool.calcSingleInGivenPoolOut( 265 | bPool.getBalance(_token), 266 | bPool.getDenormalizedWeight(_token), 267 | PCStorage.load().totalSupply, 268 | bPool.getTotalDenormalizedWeight(), 269 | _amountOut, 270 | bPool.getSwapFee() 271 | ); 272 | 273 | require( 274 | tokenAmountIn <= _maxAmountIn, 275 | "LibPoolEntryExit.joinswapPoolAmountOut: Token amount in too big" 276 | ); 277 | 278 | emit LOG_JOIN(msg.sender, _token, tokenAmountIn); 279 | 280 | LibPoolToken._mint(msg.sender, _amountOut); 281 | 282 | emit PoolJoined(msg.sender, _amountOut); 283 | 284 | uint256 bal = bPool.getBalance(_token); 285 | LibUnderlying._pullUnderlying(_token, msg.sender, tokenAmountIn, bal); 286 | 287 | return tokenAmountIn; 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /contracts/libraries/LibPoolMath.sol: -------------------------------------------------------------------------------- 1 | // modified version of 2 | // https://github.com/balancer-labs/balancer-core/blob/master/contracts/BMath.sol 3 | 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | pragma solidity 0.6.4; 18 | 19 | import "./Math.sol"; 20 | import "./LibFees.sol"; 21 | import {PBasicSmartPoolStorage as PBStorage} from "../storage/PBasicSmartPoolStorage.sol"; 22 | import {PCTokenStorage as PCStorage} from "../storage/PCTokenStorage.sol"; 23 | 24 | library LibPoolMath { 25 | using Math for uint256; 26 | 27 | uint256 constant BONE = 1 * 10**18; 28 | uint256 constant EXIT_FEE = 0; 29 | 30 | /********************************************************************************************** 31 | // calcSpotPrice // 32 | // sP = spotPrice // 33 | // bI = tokenBalanceIn ( bI / wI ) 1 // 34 | // bO = tokenBalanceOut sP = ----------- * ---------- // 35 | // wI = tokenWeightIn ( bO / wO ) ( 1 - sF ) // 36 | // wO = tokenWeightOut // 37 | // sF = swapFee // 38 | **********************************************************************************************/ 39 | function calcSpotPrice( 40 | uint256 tokenBalanceIn, 41 | uint256 tokenWeightIn, 42 | uint256 tokenBalanceOut, 43 | uint256 tokenWeightOut, 44 | uint256 swapFee 45 | ) internal pure returns (uint256 spotPrice) { 46 | uint256 numer = tokenBalanceIn.bdiv(tokenWeightIn); 47 | uint256 denom = tokenBalanceOut.bdiv(tokenWeightOut); 48 | uint256 ratio = numer.bdiv(denom); 49 | uint256 scale = BONE.bdiv(BONE.bsub(swapFee)); 50 | return (spotPrice = ratio.bmul(scale)); 51 | } 52 | 53 | /********************************************************************************************** 54 | // calcOutGivenIn // 55 | // aO = tokenAmountOut // 56 | // bO = tokenBalanceOut // 57 | // bI = tokenBalanceIn / / bI \ (wI / wO) \ // 58 | // aI = tokenAmountIn aO = bO * | 1 - | -------------------------- | ^ | // 59 | // wI = tokenWeightIn \ \ ( bI + ( aI * ( 1 - sF )) / / // 60 | // wO = tokenWeightOut // 61 | // sF = swapFee // 62 | **********************************************************************************************/ 63 | function calcOutGivenIn( 64 | uint256 tokenBalanceIn, 65 | uint256 tokenWeightIn, 66 | uint256 tokenBalanceOut, 67 | uint256 tokenWeightOut, 68 | uint256 tokenAmountIn, 69 | uint256 swapFee 70 | ) internal pure returns (uint256 tokenAmountOut) { 71 | uint256 weightRatio = tokenWeightIn.bdiv(tokenWeightOut); 72 | uint256 adjustedIn = BONE.bsub(swapFee); 73 | adjustedIn = tokenAmountIn.bmul(adjustedIn); 74 | uint256 y = tokenBalanceIn.bdiv(tokenBalanceIn.badd(adjustedIn)); 75 | uint256 foo = y.bpow(weightRatio); 76 | uint256 bar = BONE.bsub(foo); 77 | tokenAmountOut = tokenBalanceOut.bmul(bar); 78 | return tokenAmountOut; 79 | } 80 | 81 | /********************************************************************************************** 82 | // calcInGivenOut // 83 | // aI = tokenAmountIn // 84 | // bO = tokenBalanceOut / / bO \ (wO / wI) \ // 85 | // bI = tokenBalanceIn bI * | | ------------ | ^ - 1 | // 86 | // aO = tokenAmountOut aI = \ \ ( bO - aO ) / / // 87 | // wI = tokenWeightIn -------------------------------------------- // 88 | // wO = tokenWeightOut ( 1 - sF ) // 89 | // sF = swapFee // 90 | **********************************************************************************************/ 91 | function calcInGivenOut( 92 | uint256 tokenBalanceIn, 93 | uint256 tokenWeightIn, 94 | uint256 tokenBalanceOut, 95 | uint256 tokenWeightOut, 96 | uint256 tokenAmountOut, 97 | uint256 swapFee 98 | ) internal pure returns (uint256 tokenAmountIn) { 99 | uint256 weightRatio = tokenWeightOut.bdiv(tokenWeightIn); 100 | uint256 diff = tokenBalanceOut.bsub(tokenAmountOut); 101 | uint256 y = tokenBalanceOut.bdiv(diff); 102 | uint256 foo = y.bpow(weightRatio); 103 | foo = foo.bsub(BONE); 104 | tokenAmountIn = BONE.bsub(swapFee); 105 | tokenAmountIn = tokenBalanceIn.bmul(foo).bdiv(tokenAmountIn); 106 | return tokenAmountIn; 107 | } 108 | 109 | /********************************************************************************************** 110 | // calcPoolOutGivenSingleIn // 111 | // pAo = poolAmountOut / \ // 112 | // tAi = tokenAmountIn /// / // wI \ \\ \ wI \ // 113 | // wI = tokenWeightIn //| tAi *| 1 - || 1 - -- | * sF || + tBi \ -- \ // 114 | // tW = totalWeight pAo=|| \ \ \\ tW / // | ^ tW | * pS - pS // 115 | // tBi = tokenBalanceIn \\ ------------------------------------- / / // 116 | // pS = poolSupply \\ tBi / / // 117 | // sF = swapFee \ / // 118 | **********************************************************************************************/ 119 | function calcPoolOutGivenSingleIn( 120 | uint256 tokenBalanceIn, 121 | uint256 tokenWeightIn, 122 | uint256 poolSupply, 123 | uint256 totalWeight, 124 | uint256 tokenAmountIn, 125 | uint256 swapFee 126 | ) internal pure returns (uint256 poolAmountOut) { 127 | // Charge the trading fee for the proportion of tokenAi 128 | /// which is implicitly traded to the other pool tokens. 129 | // That proportion is (1- weightTokenIn) 130 | // tokenAiAfterFee = tAi * (1 - (1-weightTi) * poolFee); 131 | uint256 normalizedWeight = tokenWeightIn.bdiv(totalWeight); 132 | uint256 zaz = BONE.bsub(normalizedWeight).bmul(swapFee); 133 | uint256 tokenAmountInAfterFee = tokenAmountIn.bmul(BONE.bsub(zaz)); 134 | 135 | uint256 newTokenBalanceIn = tokenBalanceIn.badd(tokenAmountInAfterFee); 136 | uint256 tokenInRatio = newTokenBalanceIn.bdiv(tokenBalanceIn); 137 | 138 | uint256 poolRatio = tokenInRatio.bpow(normalizedWeight); 139 | uint256 newPoolSupply = poolRatio.bmul(poolSupply); 140 | poolAmountOut = newPoolSupply.bsub(poolSupply); 141 | return poolAmountOut; 142 | } 143 | 144 | /********************************************************************************************** 145 | // calcSingleInGivenPoolOut // 146 | // tAi = tokenAmountIn //(pS + pAo)\ / 1 \\ // 147 | // pS = poolSupply || --------- | ^ | --------- || * bI - bI // 148 | // pAo = poolAmountOut \\ pS / \(wI / tW)// // 149 | // bI = balanceIn tAi = -------------------------------------------- // 150 | // wI = weightIn / wI \ // 151 | // tW = totalWeight 1 - | 1 - ---- | * sF // 152 | // sF = swapFee \ tW / // 153 | **********************************************************************************************/ 154 | function calcSingleInGivenPoolOut( 155 | uint256 tokenBalanceIn, 156 | uint256 tokenWeightIn, 157 | uint256 poolSupply, 158 | uint256 totalWeight, 159 | uint256 poolAmountOut, 160 | uint256 swapFee 161 | ) internal pure returns (uint256 tokenAmountIn) { 162 | uint256 normalizedWeight = tokenWeightIn.bdiv(totalWeight); 163 | uint256 newPoolSupply = poolSupply.badd(poolAmountOut); 164 | uint256 poolRatio = newPoolSupply.bdiv(poolSupply); 165 | 166 | //uint256 newBalTi = poolRatio^(1/weightTi) * balTi; 167 | uint256 boo = BONE.bdiv(normalizedWeight); 168 | uint256 tokenInRatio = poolRatio.bpow(boo); 169 | uint256 newTokenBalanceIn = tokenInRatio.bmul(tokenBalanceIn); 170 | uint256 tokenAmountInAfterFee = newTokenBalanceIn.bsub(tokenBalanceIn); 171 | // Do reverse order of fees charged in joinswap_ExternAmountIn, this way 172 | // ``` pAo == joinswap_ExternAmountIn(Ti, joinswap_PoolAmountOut(pAo, Ti)) ``` 173 | //uint256 tAi = tAiAfterFee / (1 - (1-weightTi) * swapFee) ; 174 | uint256 zar = BONE.bsub(normalizedWeight).bmul(swapFee); 175 | tokenAmountIn = tokenAmountInAfterFee.bdiv(BONE.bsub(zar)); 176 | return tokenAmountIn; 177 | } 178 | 179 | /********************************************************************************************** 180 | // calcSingleOutGivenPoolIn // 181 | // tAo = tokenAmountOut / / \\ // 182 | // bO = tokenBalanceOut / // pS - (pAi * (1 - eF)) \ / 1 \ \\ // 183 | // pAi = poolAmountIn | bO - || ----------------------- | ^ | --------- | * b0 || // 184 | // ps = poolSupply \ \\ pS / \(wO / tW)/ // // 185 | // wI = tokenWeightIn tAo = \ \ // // 186 | // tW = totalWeight / / wO \ \ // 187 | // sF = swapFee * | 1 - | 1 - ---- | * sF | // 188 | // eF = exitFee \ \ tW / / // 189 | **********************************************************************************************/ 190 | function calcSingleOutGivenPoolIn( 191 | uint256 tokenBalanceOut, 192 | uint256 tokenWeightOut, 193 | uint256 poolSupply, 194 | uint256 totalWeight, 195 | uint256 poolAmountIn, 196 | uint256 swapFee 197 | ) internal pure returns (uint256 tokenAmountOut) { 198 | uint256 normalizedWeight = tokenWeightOut.bdiv(totalWeight); 199 | // charge exit fee on the pool token side 200 | // pAiAfterExitFee = pAi*(1-exitFee) 201 | uint256 poolAmountInAfterExitFee = poolAmountIn.bmul(BONE.bsub(EXIT_FEE)); 202 | uint256 newPoolSupply = poolSupply.bsub(poolAmountInAfterExitFee); 203 | uint256 poolRatio = newPoolSupply.bdiv(poolSupply); 204 | 205 | // newBalTo = poolRatio^(1/weightTo) * balTo; 206 | uint256 tokenOutRatio = poolRatio.bpow(BONE.bdiv(normalizedWeight)); 207 | uint256 newTokenBalanceOut = tokenOutRatio.bmul(tokenBalanceOut); 208 | 209 | uint256 tokenAmountOutBeforeSwapFee = tokenBalanceOut.bsub(newTokenBalanceOut); 210 | 211 | // charge swap fee on the output token side 212 | //uint256 tAo = tAoBeforeSwapFee * (1 - (1-weightTo) * swapFee) 213 | uint256 zaz = BONE.bsub(normalizedWeight).bmul(swapFee); 214 | tokenAmountOut = tokenAmountOutBeforeSwapFee.bmul(BONE.bsub(zaz)); 215 | return tokenAmountOut; 216 | } 217 | 218 | /********************************************************************************************** 219 | // calcPoolInGivenSingleOut // 220 | // pAi = poolAmountIn // / tAo \\ / wO \ \ // 221 | // bO = tokenBalanceOut // | bO - -------------------------- |\ | ---- | \ // 222 | // tAo = tokenAmountOut pS - || \ 1 - ((1 - (tO / tW)) * sF)/ | ^ \ tW / * pS | // 223 | // ps = poolSupply \\ -----------------------------------/ / // 224 | // wO = tokenWeightOut pAi = \\ bO / / // 225 | // tW = totalWeight ------------------------------------------------------------- // 226 | // sF = swapFee ( 1 - eF ) // 227 | // eF = exitFee // 228 | **********************************************************************************************/ 229 | function calcPoolInGivenSingleOut( 230 | uint256 tokenBalanceOut, 231 | uint256 tokenWeightOut, 232 | uint256 poolSupply, 233 | uint256 totalWeight, 234 | uint256 tokenAmountOut, 235 | uint256 swapFee 236 | ) internal pure returns (uint256 poolAmountIn) { 237 | // charge swap fee on the output token side 238 | uint256 normalizedWeight = tokenWeightOut.bdiv(totalWeight); 239 | //uint256 tAoBeforeSwapFee = tAo / (1 - (1-weightTo) * swapFee) ; 240 | uint256 zoo = BONE.bsub(normalizedWeight); 241 | uint256 zar = zoo.bmul(swapFee); 242 | uint256 tokenAmountOutBeforeSwapFee = tokenAmountOut.bdiv(BONE.bsub(zar)); 243 | 244 | uint256 newTokenBalanceOut = tokenBalanceOut.bsub(tokenAmountOutBeforeSwapFee); 245 | uint256 tokenOutRatio = newTokenBalanceOut.bdiv(tokenBalanceOut); 246 | 247 | //uint256 newPoolSupply = (ratioTo ^ weightTo) * poolSupply; 248 | uint256 poolRatio = tokenOutRatio.bpow(normalizedWeight); 249 | uint256 newPoolSupply = poolRatio.bmul(poolSupply); 250 | uint256 poolAmountInAfterExitFee = poolSupply.bsub(newPoolSupply); 251 | 252 | // charge exit fee on the pool token side 253 | // pAi = pAiAfterExitFee/(1-exitFee) 254 | poolAmountIn = poolAmountInAfterExitFee.bdiv(BONE.bsub(EXIT_FEE)); 255 | return poolAmountIn; 256 | } 257 | 258 | // Wrapped public functions -------------------------------------------------------------------- 259 | 260 | /** 261 | @notice Gets the underlying assets and amounts to mint specific pool shares. 262 | @param _amount Amount of pool shares to calculate the values for 263 | @return tokens The addresses of the tokens 264 | @return amounts The amounts of tokens needed to mint that amount of pool shares 265 | */ 266 | function calcTokensForAmount(uint256 _amount) 267 | external 268 | view 269 | returns (address[] memory tokens, uint256[] memory amounts) 270 | { 271 | tokens = PBStorage.load().bPool.getCurrentTokens(); 272 | amounts = new uint256[](tokens.length); 273 | uint256 ratio = _amount.bdiv( 274 | PCStorage.load().totalSupply.badd(LibFees.calcOutstandingAnnualFee()) 275 | ); 276 | 277 | for (uint256 i = 0; i < tokens.length; i++) { 278 | address t = tokens[i]; 279 | uint256 bal = PBStorage.load().bPool.getBalance(t); 280 | uint256 amount = ratio.bmul(bal); 281 | amounts[i] = amount; 282 | } 283 | } 284 | 285 | /** 286 | @notice Calculate the amount of pool tokens out for a given amount in 287 | @param _token Address of the input token 288 | @param _amount Amount of input token 289 | @return Amount of pool token 290 | */ 291 | function calcPoolOutGivenSingleIn(address _token, uint256 _amount) 292 | external 293 | view 294 | returns (uint256) 295 | { 296 | PBStorage.StorageStruct storage s = PBStorage.load(); 297 | uint256 tokenBalanceIn = s.bPool.getBalance(_token); 298 | uint256 tokenWeightIn = s.bPool.getDenormalizedWeight(_token); 299 | uint256 poolSupply = PCStorage.load().totalSupply.badd(LibFees.calcOutstandingAnnualFee()); 300 | uint256 totalWeight = s.bPool.getTotalDenormalizedWeight(); 301 | uint256 swapFee = s.bPool.getSwapFee(); 302 | 303 | return ( 304 | LibPoolMath.calcPoolOutGivenSingleIn( 305 | tokenBalanceIn, 306 | tokenWeightIn, 307 | poolSupply, 308 | totalWeight, 309 | _amount, 310 | swapFee 311 | ) 312 | ); 313 | } 314 | 315 | /** 316 | @notice Calculate single in given pool out 317 | @param _token Address of the input token 318 | @param _amount Amount of pool out token 319 | @return Amount of token in 320 | */ 321 | function calcSingleInGivenPoolOut(address _token, uint256 _amount) 322 | external 323 | view 324 | returns (uint256) 325 | { 326 | PBStorage.StorageStruct storage s = PBStorage.load(); 327 | uint256 tokenBalanceIn = s.bPool.getBalance(_token); 328 | uint256 tokenWeightIn = s.bPool.getDenormalizedWeight(_token); 329 | uint256 poolSupply = PCStorage.load().totalSupply.badd(LibFees.calcOutstandingAnnualFee()); 330 | uint256 totalWeight = s.bPool.getTotalDenormalizedWeight(); 331 | uint256 swapFee = s.bPool.getSwapFee(); 332 | 333 | return ( 334 | LibPoolMath.calcSingleInGivenPoolOut( 335 | tokenBalanceIn, 336 | tokenWeightIn, 337 | poolSupply, 338 | totalWeight, 339 | _amount, 340 | swapFee 341 | ) 342 | ); 343 | } 344 | 345 | /** 346 | @notice Calculate single out given pool in 347 | @param _token Address of output token 348 | @param _amount Amount of pool in 349 | @return Amount of token in 350 | */ 351 | function calcSingleOutGivenPoolIn(address _token, uint256 _amount) 352 | external 353 | view 354 | returns (uint256) 355 | { 356 | PBStorage.StorageStruct storage s = PBStorage.load(); 357 | uint256 tokenBalanceOut = s.bPool.getBalance(_token); 358 | uint256 tokenWeightOut = s.bPool.getDenormalizedWeight(_token); 359 | uint256 poolSupply = PCStorage.load().totalSupply.badd(LibFees.calcOutstandingAnnualFee()); 360 | uint256 totalWeight = s.bPool.getTotalDenormalizedWeight(); 361 | uint256 swapFee = s.bPool.getSwapFee(); 362 | 363 | return ( 364 | LibPoolMath.calcSingleOutGivenPoolIn( 365 | tokenBalanceOut, 366 | tokenWeightOut, 367 | poolSupply, 368 | totalWeight, 369 | _amount, 370 | swapFee 371 | ) 372 | ); 373 | } 374 | 375 | /** 376 | @notice Calculate pool in given single token out 377 | @param _token Address of output token 378 | @param _amount Amount of output token 379 | @return Amount of pool in 380 | */ 381 | function calcPoolInGivenSingleOut(address _token, uint256 _amount) 382 | external 383 | view 384 | returns (uint256) 385 | { 386 | PBStorage.StorageStruct storage s = PBStorage.load(); 387 | uint256 tokenBalanceOut = s.bPool.getBalance(_token); 388 | uint256 tokenWeightOut = s.bPool.getDenormalizedWeight(_token); 389 | uint256 poolSupply = PCStorage.load().totalSupply.badd(LibFees.calcOutstandingAnnualFee()); 390 | uint256 totalWeight = s.bPool.getTotalDenormalizedWeight(); 391 | uint256 swapFee = s.bPool.getSwapFee(); 392 | 393 | return ( 394 | LibPoolMath.calcPoolInGivenSingleOut( 395 | tokenBalanceOut, 396 | tokenWeightOut, 397 | poolSupply, 398 | totalWeight, 399 | _amount, 400 | swapFee 401 | ) 402 | ); 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /contracts/libraries/LibPoolToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.4; 2 | 3 | import {PCTokenStorage as PCStorage} from "../storage/PCTokenStorage.sol"; 4 | import "../libraries/Math.sol"; 5 | import "../interfaces/IERC20.sol"; 6 | 7 | library LibPoolToken { 8 | using Math for uint256; 9 | 10 | event Transfer(address indexed _src, address indexed _dst, uint256 _amount); 11 | 12 | function _mint(address _to, uint256 _amount) internal { 13 | PCStorage.StorageStruct storage s = PCStorage.load(); 14 | s.balance[_to] = s.balance[_to].badd(_amount); 15 | s.totalSupply = s.totalSupply.badd(_amount); 16 | emit Transfer(address(0), _to, _amount); 17 | } 18 | 19 | function _burn(address _from, uint256 _amount) internal { 20 | PCStorage.StorageStruct storage s = PCStorage.load(); 21 | require(s.balance[_from] >= _amount, "ERR_INSUFFICIENT_BAL"); 22 | s.balance[_from] = s.balance[_from].bsub(_amount); 23 | s.totalSupply = s.totalSupply.bsub(_amount); 24 | emit Transfer(_from, address(0), _amount); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /contracts/libraries/LibSafeApprove.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.4; 2 | 3 | import "../interfaces/IERC20.sol"; 4 | 5 | library LibSafeApprove { 6 | function safeApprove(IERC20 _token, address _spender, uint256 _amount) internal { 7 | uint256 currentAllowance = _token.allowance(address(this), _spender); 8 | 9 | // Do nothing if allowance is already set to this value 10 | if(currentAllowance == _amount) { 11 | return; 12 | } 13 | 14 | // If approval is not zero reset it to zero first 15 | if(currentAllowance != 0) { 16 | _token.approve(_spender, 0); 17 | } 18 | 19 | // do the actual approval 20 | _token.approve(_spender, _amount); 21 | } 22 | } -------------------------------------------------------------------------------- /contracts/libraries/LibUnderlying.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.4; 2 | 3 | import "../interfaces/IERC20.sol"; 4 | import "../interfaces/IBPool.sol"; 5 | 6 | import {PBasicSmartPoolStorage as PBStorage} from "../storage/PBasicSmartPoolStorage.sol"; 7 | 8 | import "./Math.sol"; 9 | 10 | library LibUnderlying { 11 | using Math for uint256; 12 | 13 | function _pullUnderlying( 14 | address _token, 15 | address _from, 16 | uint256 _amount, 17 | uint256 _tokenBalance 18 | ) internal { 19 | IBPool bPool = PBStorage.load().bPool; 20 | // Gets current Balance of token i, Bi, and weight of token i, Wi, from BPool. 21 | uint256 tokenWeight = bPool.getDenormalizedWeight(_token); 22 | 23 | require( 24 | IERC20(_token).transferFrom(_from, address(this), _amount), 25 | "LibUnderlying._pullUnderlying: transferFrom failed" 26 | ); 27 | bPool.rebind(_token, _tokenBalance.badd(_amount), tokenWeight); 28 | } 29 | 30 | function _pushUnderlying( 31 | address _token, 32 | address _to, 33 | uint256 _amount, 34 | uint256 _tokenBalance 35 | ) internal { 36 | IBPool bPool = PBStorage.load().bPool; 37 | // Gets current Balance of token i, Bi, and weight of token i, Wi, from BPool. 38 | uint256 tokenWeight = bPool.getDenormalizedWeight(_token); 39 | bPool.rebind(_token, _tokenBalance.bsub(_amount), tokenWeight); 40 | 41 | require( 42 | IERC20(_token).transfer(_to, _amount), 43 | "LibUnderlying._pushUnderlying: transfer failed" 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /contracts/libraries/LibWeights.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.4; 2 | 3 | import {PBasicSmartPoolStorage as PBStorage} from "../storage/PBasicSmartPoolStorage.sol"; 4 | import {PV2SmartPoolStorage as P2Storage} from "../storage/PV2SmartPoolStorage.sol"; 5 | import {PCTokenStorage as PCStorage} from "../storage/PCTokenStorage.sol"; 6 | import {LibConst as constants} from "./LibConst.sol"; 7 | import "./LibPoolToken.sol"; 8 | import "./Math.sol"; 9 | 10 | library LibWeights { 11 | using Math for uint256; 12 | 13 | function updateWeight(address _token, uint256 _newWeight) external { 14 | PBStorage.StorageStruct storage s = PBStorage.load(); 15 | P2Storage.StorageStruct storage ws = P2Storage.load(); 16 | 17 | require(_newWeight >= constants.MIN_WEIGHT, "ERR_MIN_WEIGHT"); 18 | require(_newWeight <= constants.MAX_WEIGHT, "ERR_MAX_WEIGHT"); 19 | 20 | uint256 currentWeight = s.bPool.getDenormalizedWeight(_token); 21 | uint256 currentBalance = s.bPool.getBalance(_token); 22 | uint256 poolShares; 23 | uint256 deltaBalance; 24 | uint256 deltaWeight; 25 | uint256 totalSupply = PCStorage.load().totalSupply; 26 | uint256 totalWeight = s.bPool.getTotalDenormalizedWeight(); 27 | 28 | if (_newWeight < currentWeight) { 29 | // If weight goes down we need to pull tokens and burn pool shares 30 | require( 31 | totalWeight.badd(currentWeight.bsub(_newWeight)) <= constants.MAX_TOTAL_WEIGHT, 32 | "ERR_MAX_TOTAL_WEIGHT" 33 | ); 34 | 35 | deltaWeight = currentWeight.bsub(_newWeight); 36 | 37 | poolShares = totalSupply.bmul(deltaWeight.bdiv(totalWeight)); 38 | 39 | deltaBalance = currentBalance.bmul(deltaWeight.bdiv(currentWeight)); 40 | 41 | // New balance cannot be lower than MIN_BALANCE 42 | require(currentBalance.bsub(deltaBalance) >= constants.MIN_BALANCE, "ERR_MIN_BALANCE"); 43 | // First gets the tokens from this contract (Pool Controller) to msg.sender 44 | s.bPool.rebind(_token, currentBalance.bsub(deltaBalance), _newWeight); 45 | 46 | // Now with the tokens this contract can send them to msg.sender 47 | require(IERC20(_token).transfer(msg.sender, deltaBalance), "ERR_ERC20_FALSE"); 48 | 49 | // Cancel potential weight adjustment process. 50 | ws.startBlock = 0; 51 | 52 | LibPoolToken._burn(msg.sender, poolShares); 53 | } else { 54 | // This means the controller will deposit tokens to keep the price. 55 | // They will be minted and given PCTokens 56 | require( 57 | totalWeight.badd(_newWeight.bsub(currentWeight)) <= constants.MAX_TOTAL_WEIGHT, 58 | "ERR_MAX_TOTAL_WEIGHT" 59 | ); 60 | 61 | deltaWeight = _newWeight.bsub(currentWeight); 62 | poolShares = totalSupply.bmul(deltaWeight.bdiv(totalWeight)); 63 | deltaBalance = currentBalance.bmul(deltaWeight.bdiv(currentWeight)); 64 | 65 | // First gets the tokens from msg.sender to this contract (Pool Controller) 66 | require( 67 | IERC20(_token).transferFrom(msg.sender, address(this), deltaBalance), 68 | "TRANSFER_FAILED" 69 | ); 70 | // Now with the tokens this contract can bind them to the pool it controls 71 | s.bPool.rebind(_token, currentBalance.badd(deltaBalance), _newWeight); 72 | 73 | // Cancel potential weight adjustment process. 74 | ws.startBlock = 0; 75 | 76 | LibPoolToken._mint(msg.sender, poolShares); 77 | } 78 | } 79 | 80 | function updateWeightsGradually( 81 | uint256[] calldata _newWeights, 82 | uint256 _startBlock, 83 | uint256 _endBlock 84 | ) external { 85 | PBStorage.StorageStruct storage s = PBStorage.load(); 86 | P2Storage.StorageStruct storage ws = P2Storage.load(); 87 | 88 | uint256 weightsSum = 0; 89 | address[] memory tokens = s.bPool.getCurrentTokens(); 90 | // Check that endWeights are valid now to avoid reverting in a future pokeWeights call 91 | for (uint256 i = 0; i < tokens.length; i++) { 92 | require(_newWeights[i] <= constants.MAX_WEIGHT, "ERR_WEIGHT_ABOVE_MAX"); 93 | require(_newWeights[i] >= constants.MIN_WEIGHT, "ERR_WEIGHT_BELOW_MIN"); 94 | weightsSum = weightsSum.badd(_newWeights[i]); 95 | } 96 | require(weightsSum <= constants.MAX_TOTAL_WEIGHT, "ERR_MAX_TOTAL_WEIGHT"); 97 | 98 | if (block.number > _startBlock) { 99 | // This means the weight update should start ASAP 100 | ws.startBlock = block.number; 101 | } else { 102 | ws.startBlock = _startBlock; 103 | } 104 | ws.endBlock = _endBlock; 105 | ws.newWeights = _newWeights; 106 | 107 | require( 108 | _endBlock > _startBlock, 109 | "PWeightControlledSmartPool.updateWeightsGradually: End block must be after start block" 110 | ); 111 | 112 | delete ws.startWeights; 113 | 114 | for (uint256 i = 0; i < tokens.length; i++) { 115 | // startWeights are current weights 116 | ws.startWeights.push(s.bPool.getDenormalizedWeight(tokens[i])); 117 | } 118 | } 119 | 120 | function pokeWeights() external { 121 | PBStorage.StorageStruct storage s = PBStorage.load(); 122 | P2Storage.StorageStruct storage ws = P2Storage.load(); 123 | 124 | require(ws.startBlock != 0, "ERR_WEIGHT_ADJUSTMENT_FINISHED"); 125 | require(block.number >= ws.startBlock, "ERR_CANT_POKE_YET"); 126 | 127 | // This allows for pokes after endBlock that get weights to endWeights 128 | uint256 minBetweenEndBlockAndThisBlock; 129 | if (block.number > ws.endBlock) { 130 | minBetweenEndBlockAndThisBlock = ws.endBlock; 131 | } else { 132 | minBetweenEndBlockAndThisBlock = block.number; 133 | } 134 | 135 | uint256 blockPeriod = ws.endBlock.bsub(ws.startBlock); 136 | uint256 weightDelta; 137 | uint256 newWeight; 138 | address[] memory tokens = s.bPool.getCurrentTokens(); 139 | for (uint256 i = 0; i < tokens.length; i++) { 140 | if (ws.startWeights[i] >= ws.newWeights[i]) { 141 | weightDelta = ws.startWeights[i].bsub(ws.newWeights[i]); 142 | newWeight = ws.startWeights[i].bsub( 143 | (minBetweenEndBlockAndThisBlock.bsub(ws.startBlock)).bmul(weightDelta.bdiv(blockPeriod)) 144 | ); 145 | } else { 146 | weightDelta = ws.newWeights[i].bsub(ws.startWeights[i]); 147 | newWeight = ws.startWeights[i].badd( 148 | (minBetweenEndBlockAndThisBlock.bsub(ws.startBlock)).bmul(weightDelta.bdiv(blockPeriod)) 149 | ); 150 | } 151 | s.bPool.rebind(tokens[i], s.bPool.getBalance(tokens[i]), newWeight); 152 | } 153 | 154 | if(minBetweenEndBlockAndThisBlock == ws.endBlock) { 155 | // All the weights are adjusted, adjustment finished. 156 | 157 | // save gas option: set this to max number instead of 0 158 | // And be able to remove ERR_WEIGHT_ADJUSTMENT_FINISHED check 159 | ws.startBlock = 0; 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /contracts/libraries/Math.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.4; 2 | 3 | library Math { 4 | uint256 internal constant BONE = 10**18; 5 | uint256 internal constant MIN_BPOW_BASE = 1 wei; 6 | uint256 internal constant MAX_BPOW_BASE = (2 * BONE) - 1 wei; 7 | uint256 internal constant BPOW_PRECISION = BONE / 10**10; 8 | 9 | function btoi(uint256 a) internal pure returns (uint256) { 10 | return a / BONE; 11 | } 12 | 13 | // Add two numbers together checking for overflows 14 | function badd(uint256 a, uint256 b) internal pure returns (uint256) { 15 | uint256 c = a + b; 16 | require(c >= a, "ERR_ADD_OVERFLOW"); 17 | return c; 18 | } 19 | 20 | // subtract two numbers and return diffecerence when it underflows 21 | function bsubSign(uint256 a, uint256 b) internal pure returns (uint256, bool) { 22 | if (a >= b) { 23 | return (a - b, false); 24 | } else { 25 | return (b - a, true); 26 | } 27 | } 28 | 29 | // Subtract two numbers checking for underflows 30 | function bsub(uint256 a, uint256 b) internal pure returns (uint256) { 31 | (uint256 c, bool flag) = bsubSign(a, b); 32 | require(!flag, "ERR_SUB_UNDERFLOW"); 33 | return c; 34 | } 35 | 36 | // Multiply two 18 decimals numbers 37 | function bmul(uint256 a, uint256 b) internal pure returns (uint256) { 38 | uint256 c0 = a * b; 39 | require(a == 0 || c0 / a == b, "ERR_MUL_OVERFLOW"); 40 | uint256 c1 = c0 + (BONE / 2); 41 | require(c1 >= c0, "ERR_MUL_OVERFLOW"); 42 | uint256 c2 = c1 / BONE; 43 | return c2; 44 | } 45 | 46 | // Overflow protected multiplication 47 | function mul(uint256 a, uint256 b) internal pure returns (uint256) { 48 | // Gas optimization: this is cheaper than requiring 'a' not being zero, but the 49 | // benefit is lost if 'b' is also tested. 50 | // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522 51 | if (a == 0) { 52 | return 0; 53 | } 54 | 55 | uint256 c = a * b; 56 | require(c / a == b, "Math: multiplication overflow"); 57 | 58 | return c; 59 | } 60 | 61 | // Divide two 18 decimals numbers 62 | function bdiv(uint256 a, uint256 b) internal pure returns (uint256) { 63 | require(b != 0, "ERR_DIV_ZERO"); 64 | uint256 c0 = a * BONE; 65 | require(a == 0 || c0 / a == BONE, "ERR_DIV_INTERNAL"); // bmul overflow 66 | uint256 c1 = c0 + (b / 2); 67 | require(c1 >= c0, "ERR_DIV_INTERNAL"); // badd require 68 | uint256 c2 = c1 / b; 69 | return c2; 70 | } 71 | 72 | // Overflow protected division 73 | function div(uint256 a, uint256 b) internal pure returns (uint256) { 74 | require(b > 0, "Division by zero"); 75 | uint256 c = a / b; 76 | // assert(a == b * c + a % b); // There is no case in which this doesn't hold 77 | 78 | return c; 79 | } 80 | 81 | // DSMath.wpow 82 | function bpowi(uint256 a, uint256 n) internal pure returns (uint256) { 83 | uint256 z = n % 2 != 0 ? a : BONE; 84 | 85 | for (n /= 2; n != 0; n /= 2) { 86 | a = bmul(a, a); 87 | 88 | if (n % 2 != 0) { 89 | z = bmul(z, a); 90 | } 91 | } 92 | return z; 93 | } 94 | 95 | // Compute b^(e.w) by splitting it into (b^e)*(b^0.w). 96 | // Use `bpowi` for `b^e` and `bpowK` for k iterations 97 | // of approximation of b^0.w 98 | function bpow(uint256 base, uint256 exp) internal pure returns (uint256) { 99 | require(base >= MIN_BPOW_BASE, "ERR_BPOW_BASE_TOO_LOW"); 100 | require(base <= MAX_BPOW_BASE, "ERR_BPOW_BASE_TOO_HIGH"); 101 | 102 | uint256 whole = bfloor(exp); 103 | uint256 remain = bsub(exp, whole); 104 | 105 | uint256 wholePow = bpowi(base, btoi(whole)); 106 | 107 | if (remain == 0) { 108 | return wholePow; 109 | } 110 | 111 | uint256 partialResult = bpowApprox(base, remain, BPOW_PRECISION); 112 | return bmul(wholePow, partialResult); 113 | } 114 | 115 | function bpowApprox( 116 | uint256 base, 117 | uint256 exp, 118 | uint256 precision 119 | ) internal pure returns (uint256) { 120 | // term 0: 121 | uint256 a = exp; 122 | (uint256 x, bool xneg) = bsubSign(base, BONE); 123 | uint256 term = BONE; 124 | uint256 sum = term; 125 | bool negative = false; 126 | 127 | // term(k) = numer / denom 128 | // = (product(a - i - 1, i=1-->k) * x^k) / (k!) 129 | // each iteration, multiply previous term by (a-(k-1)) * x / k 130 | // continue until term is less than precision 131 | for (uint256 i = 1; term >= precision; i++) { 132 | uint256 bigK = i * BONE; 133 | (uint256 c, bool cneg) = bsubSign(a, bsub(bigK, BONE)); 134 | term = bmul(term, bmul(c, x)); 135 | term = bdiv(term, bigK); 136 | if (term == 0) break; 137 | 138 | if (xneg) negative = !negative; 139 | if (cneg) negative = !negative; 140 | if (negative) { 141 | sum = bsub(sum, term); 142 | } else { 143 | sum = badd(sum, term); 144 | } 145 | } 146 | 147 | return sum; 148 | } 149 | 150 | function bfloor(uint256 a) internal pure returns (uint256) { 151 | return btoi(a) * BONE; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /contracts/smart-pools/PV2SmartPool.sol: -------------------------------------------------------------------------------- 1 | pragma experimental ABIEncoderV2; 2 | pragma solidity 0.6.4; 3 | 4 | import "../interfaces/IPV2SmartPool.sol"; 5 | import "../interfaces/IBPool.sol"; 6 | import "../PCToken.sol"; 7 | import "../ReentryProtection.sol"; 8 | 9 | import "../libraries/LibPoolToken.sol"; 10 | import "../libraries/LibAddRemoveToken.sol"; 11 | import "../libraries/LibPoolEntryExit.sol"; 12 | import "../libraries/LibPoolMath.sol"; 13 | import "../libraries/LibWeights.sol"; 14 | import "../libraries/LibSafeApprove.sol"; 15 | 16 | import {PBasicSmartPoolStorage as PBStorage} from "../storage/PBasicSmartPoolStorage.sol"; 17 | import {PCTokenStorage as PCStorage} from "../storage/PCTokenStorage.sol"; 18 | import {PCappedSmartPoolStorage as PCSStorage} from "../storage/PCappedSmartPoolStorage.sol"; 19 | import {PV2SmartPoolStorage as P2Storage} from "../storage/PV2SmartPoolStorage.sol"; 20 | 21 | contract PV2SmartPool is IPV2SmartPool, PCToken, ReentryProtection { 22 | using LibSafeApprove for IERC20; 23 | 24 | event TokensApproved(); 25 | event ControllerChanged(address indexed previousController, address indexed newController); 26 | event PublicSwapSetterChanged(address indexed previousSetter, address indexed newSetter); 27 | event TokenBinderChanged(address indexed previousTokenBinder, address indexed newTokenBinder); 28 | event PublicSwapSet(address indexed setter, bool indexed value); 29 | event SwapFeeSet(address indexed setter, uint256 newFee); 30 | event CapChanged(address indexed setter, uint256 oldCap, uint256 newCap); 31 | event CircuitBreakerTripped(); 32 | event JoinExitEnabledChanged(address indexed setter, bool oldValue, bool newValue); 33 | event CircuitBreakerChanged( 34 | address indexed _oldCircuitBreaker, 35 | address indexed _newCircuitBreaker 36 | ); 37 | 38 | modifier ready() { 39 | require(address(PBStorage.load().bPool) != address(0), "PV2SmartPool.ready: not ready"); 40 | _; 41 | } 42 | 43 | modifier onlyController() { 44 | require( 45 | msg.sender == PBStorage.load().controller, 46 | "PV2SmartPool.onlyController: not controller" 47 | ); 48 | _; 49 | } 50 | 51 | modifier onlyPublicSwapSetter() { 52 | require( 53 | msg.sender == PBStorage.load().publicSwapSetter, 54 | "PV2SmartPool.onlyPublicSwapSetter: not public swap setter" 55 | ); 56 | _; 57 | } 58 | 59 | modifier onlyTokenBinder() { 60 | require( 61 | msg.sender == PBStorage.load().tokenBinder, 62 | "PV2SmartPool.onlyTokenBinder: not token binder" 63 | ); 64 | _; 65 | } 66 | 67 | modifier onlyPublicSwap() { 68 | require( 69 | PBStorage.load().bPool.isPublicSwap(), 70 | "PV2SmartPool.onlyPublicSwap: swapping not enabled" 71 | ); 72 | _; 73 | } 74 | 75 | modifier onlyCircuitBreaker() { 76 | require( 77 | msg.sender == P2Storage.load().circuitBreaker, 78 | "PV2SmartPool.onlyCircuitBreaker: not circuit breaker" 79 | ); 80 | _; 81 | } 82 | 83 | modifier onlyJoinExitEnabled() { 84 | require( 85 | P2Storage.load().joinExitEnabled, 86 | "PV2SmartPool.onlyJoinExitEnabled: join and exit not enabled" 87 | ); 88 | _; 89 | } 90 | 91 | modifier withinCap() { 92 | _; 93 | require(totalSupply() < PCSStorage.load().cap, "PV2SmartPool.withinCap: Cap limit reached"); 94 | } 95 | 96 | /** 97 | @notice Initialises the contract 98 | @param _bPool Address of the underlying balancer pool 99 | @param _name Name for the smart pool token 100 | @param _symbol Symbol for the smart pool token 101 | @param _initialSupply Initial token supply to mint 102 | */ 103 | function init( 104 | address _bPool, 105 | string calldata _name, 106 | string calldata _symbol, 107 | uint256 _initialSupply 108 | ) external override { 109 | PBStorage.StorageStruct storage s = PBStorage.load(); 110 | require(address(s.bPool) == address(0), "PV2SmartPool.init: already initialised"); 111 | require(_bPool != address(0), "PV2SmartPool.init: _bPool cannot be 0x00....000"); 112 | require(_initialSupply != 0, "PV2SmartPool.init: _initialSupply can not zero"); 113 | s.bPool = IBPool(_bPool); 114 | s.controller = msg.sender; 115 | s.publicSwapSetter = msg.sender; 116 | s.tokenBinder = msg.sender; 117 | PCStorage.load().name = _name; 118 | PCStorage.load().symbol = _symbol; 119 | 120 | LibPoolToken._mint(msg.sender, _initialSupply); 121 | } 122 | 123 | /** 124 | @notice Sets approval to all tokens to the underlying balancer pool 125 | @dev It uses this function to save on gas in joinPool 126 | */ 127 | function approveTokens() public override noReentry { 128 | IBPool bPool = PBStorage.load().bPool; 129 | address[] memory tokens = bPool.getCurrentTokens(); 130 | for (uint256 i = 0; i < tokens.length; i++) { 131 | IERC20(tokens[i]).safeApprove(address(bPool), uint256(-1)); 132 | } 133 | emit TokensApproved(); 134 | } 135 | 136 | // POOL EXIT ------------------------------------------------ 137 | 138 | /** 139 | @notice Burns pool shares and sends back the underlying assets leaving some in the pool 140 | @param _amount Amount of pool tokens to burn 141 | @param _lossTokens Tokens skipped on redemption 142 | */ 143 | function exitPoolTakingloss(uint256 _amount, address[] calldata _lossTokens) 144 | external 145 | override 146 | ready 147 | noReentry 148 | onlyJoinExitEnabled 149 | { 150 | LibPoolEntryExit.exitPoolTakingloss(_amount, _lossTokens); 151 | } 152 | 153 | /** 154 | @notice Burns pool shares and sends back the underlying assets 155 | @param _amount Amount of pool tokens to burn 156 | */ 157 | function exitPool(uint256 _amount) external override ready noReentry onlyJoinExitEnabled { 158 | LibPoolEntryExit.exitPool(_amount); 159 | } 160 | 161 | /** 162 | @notice Burn pool tokens and redeem underlying assets. With front running protection 163 | @param _amount Amount of pool tokens to burn 164 | @param _minAmountsOut Minimum amounts of underlying assets 165 | */ 166 | function exitPool(uint256 _amount, uint256[] calldata _minAmountsOut) 167 | external 168 | override 169 | ready 170 | noReentry 171 | onlyJoinExitEnabled 172 | { 173 | LibPoolEntryExit.exitPool(_amount, _minAmountsOut); 174 | } 175 | 176 | /** 177 | @notice Exitswap single asset pool exit given pool amount in 178 | @param _token Address of exit token 179 | @param _poolAmountIn Amount of pool tokens sending to the pool 180 | @return tokenAmountOut amount of exit tokens being withdrawn 181 | */ 182 | function exitswapPoolAmountIn( 183 | address _token, 184 | uint256 _poolAmountIn, 185 | uint256 _minAmountOut 186 | ) 187 | external 188 | override 189 | ready 190 | noReentry 191 | onlyPublicSwap 192 | onlyJoinExitEnabled 193 | returns (uint256 tokenAmountOut) 194 | { 195 | return LibPoolEntryExit.exitswapPoolAmountIn(_token, _poolAmountIn, _minAmountOut); 196 | } 197 | 198 | /** 199 | @notice Exitswap single asset pool entry given token amount out 200 | @param _token Address of exit token 201 | @param _tokenAmountOut Amount of exit tokens 202 | @return poolAmountIn amount of pool tokens being deposited 203 | */ 204 | function exitswapExternAmountOut( 205 | address _token, 206 | uint256 _tokenAmountOut, 207 | uint256 _maxPoolAmountIn 208 | ) 209 | external 210 | override 211 | ready 212 | noReentry 213 | onlyPublicSwap 214 | onlyJoinExitEnabled 215 | returns (uint256 poolAmountIn) 216 | { 217 | return LibPoolEntryExit.exitswapExternAmountOut(_token, _tokenAmountOut, _maxPoolAmountIn); 218 | } 219 | 220 | // POOL ENTRY ----------------------------------------------- 221 | /** 222 | @notice Takes underlying assets and mints smart pool tokens. Enforces the cap 223 | @param _amount Amount of pool tokens to mint 224 | */ 225 | function joinPool(uint256 _amount) 226 | external 227 | override 228 | withinCap 229 | ready 230 | noReentry 231 | onlyJoinExitEnabled 232 | { 233 | LibPoolEntryExit.joinPool(_amount); 234 | } 235 | 236 | /** 237 | @notice Takes underlying assets and mints smart pool tokens. 238 | Enforces the cap. Allows you to specify the maximum amounts of underlying assets 239 | @param _amount Amount of pool tokens to mint 240 | */ 241 | function joinPool(uint256 _amount, uint256[] calldata _maxAmountsIn) 242 | external 243 | override 244 | withinCap 245 | ready 246 | noReentry 247 | onlyJoinExitEnabled 248 | { 249 | LibPoolEntryExit.joinPool(_amount, _maxAmountsIn); 250 | } 251 | 252 | /** 253 | @notice Joinswap single asset pool entry given token amount in 254 | @param _token Address of entry token 255 | @param _amountIn Amount of entry tokens 256 | @return poolAmountOut 257 | */ 258 | function joinswapExternAmountIn( 259 | address _token, 260 | uint256 _amountIn, 261 | uint256 _minPoolAmountOut 262 | ) 263 | external 264 | override 265 | ready 266 | withinCap 267 | onlyPublicSwap 268 | noReentry 269 | onlyJoinExitEnabled 270 | returns (uint256 poolAmountOut) 271 | { 272 | return LibPoolEntryExit.joinswapExternAmountIn(_token, _amountIn, _minPoolAmountOut); 273 | } 274 | 275 | /** 276 | @notice Joinswap single asset pool entry given pool amount out 277 | @param _token Address of entry token 278 | @param _amountOut Amount of entry tokens to deposit into the pool 279 | @return tokenAmountIn 280 | */ 281 | function joinswapPoolAmountOut( 282 | address _token, 283 | uint256 _amountOut, 284 | uint256 _maxAmountIn 285 | ) 286 | external 287 | override 288 | ready 289 | withinCap 290 | onlyPublicSwap 291 | noReentry 292 | onlyJoinExitEnabled 293 | returns (uint256 tokenAmountIn) 294 | { 295 | return LibPoolEntryExit.joinswapPoolAmountOut(_token, _amountOut, _maxAmountIn); 296 | } 297 | 298 | // ADMIN FUNCTIONS ------------------------------------------ 299 | 300 | /** 301 | @notice Bind a token to the underlying balancer pool. Can only be called by the token binder 302 | @param _token Token to bind 303 | @param _balance Amount to bind 304 | @param _denorm Denormalised weight 305 | */ 306 | function bind( 307 | address _token, 308 | uint256 _balance, 309 | uint256 _denorm 310 | ) external override onlyTokenBinder noReentry { 311 | P2Storage.StorageStruct storage ws = P2Storage.load(); 312 | IBPool bPool = PBStorage.load().bPool; 313 | IERC20 token = IERC20(_token); 314 | require( 315 | token.transferFrom(msg.sender, address(this), _balance), 316 | "PV2SmartPool.bind: transferFrom failed" 317 | ); 318 | // Cancel potential weight adjustment process. 319 | ws.startBlock = 0; 320 | token.safeApprove(address(bPool), uint256(-1)); 321 | bPool.bind(_token, _balance, _denorm); 322 | } 323 | 324 | /** 325 | @notice Rebind a token to the pool 326 | @param _token Token to bind 327 | @param _balance Amount to bind 328 | @param _denorm Denormalised weight 329 | */ 330 | function rebind( 331 | address _token, 332 | uint256 _balance, 333 | uint256 _denorm 334 | ) external override onlyTokenBinder noReentry { 335 | P2Storage.StorageStruct storage ws = P2Storage.load(); 336 | IBPool bPool = PBStorage.load().bPool; 337 | IERC20 token = IERC20(_token); 338 | 339 | // gulp old non acounted for token balance in the contract 340 | bPool.gulp(_token); 341 | 342 | uint256 oldBalance = token.balanceOf(address(bPool)); 343 | // If tokens need to be pulled from msg.sender 344 | if (_balance > oldBalance) { 345 | require( 346 | token.transferFrom(msg.sender, address(this), _balance.bsub(oldBalance)), 347 | "PV2SmartPool.rebind: transferFrom failed" 348 | ); 349 | token.safeApprove(address(bPool), uint256(-1)); 350 | } 351 | 352 | bPool.rebind(_token, _balance, _denorm); 353 | // Cancel potential weight adjustment process. 354 | ws.startBlock = 0; 355 | // If any tokens are in this contract send them to msg.sender 356 | uint256 tokenBalance = token.balanceOf(address(this)); 357 | if (tokenBalance > 0) { 358 | require(token.transfer(msg.sender, tokenBalance), "PV2SmartPool.rebind: transfer failed"); 359 | } 360 | } 361 | 362 | /** 363 | @notice Unbind a token 364 | @param _token Token to unbind 365 | */ 366 | function unbind(address _token) external override onlyTokenBinder noReentry { 367 | P2Storage.StorageStruct storage ws = P2Storage.load(); 368 | IBPool bPool = PBStorage.load().bPool; 369 | IERC20 token = IERC20(_token); 370 | // unbind the token in the bPool 371 | bPool.unbind(_token); 372 | 373 | // Cancel potential weight adjustment process. 374 | ws.startBlock = 0; 375 | 376 | // If any tokens are in this contract send them to msg.sender 377 | uint256 tokenBalance = token.balanceOf(address(this)); 378 | if (tokenBalance > 0) { 379 | require(token.transfer(msg.sender, tokenBalance), "PV2SmartPool.unbind: transfer failed"); 380 | } 381 | } 382 | 383 | /** 384 | @notice Sets the controller address. Can only be set by the current controller 385 | @param _controller Address of the new controller 386 | */ 387 | function setController(address _controller) external override onlyController noReentry { 388 | emit ControllerChanged(PBStorage.load().controller, _controller); 389 | PBStorage.load().controller = _controller; 390 | } 391 | 392 | /** 393 | @notice Sets public swap setter address. Can only be set by the controller 394 | @param _newPublicSwapSetter Address of the new public swap setter 395 | */ 396 | function setPublicSwapSetter(address _newPublicSwapSetter) 397 | external 398 | override 399 | onlyController 400 | noReentry 401 | { 402 | emit PublicSwapSetterChanged(PBStorage.load().publicSwapSetter, _newPublicSwapSetter); 403 | PBStorage.load().publicSwapSetter = _newPublicSwapSetter; 404 | } 405 | 406 | /** 407 | @notice Sets the token binder address. Can only be set by the controller 408 | @param _newTokenBinder Address of the new token binder 409 | */ 410 | function setTokenBinder(address _newTokenBinder) external override onlyController noReentry { 411 | emit TokenBinderChanged(PBStorage.load().tokenBinder, _newTokenBinder); 412 | PBStorage.load().tokenBinder = _newTokenBinder; 413 | } 414 | 415 | /** 416 | @notice Enables or disables public swapping on the underlying balancer pool. 417 | Can only be set by the controller. 418 | @param _public Public or not 419 | */ 420 | function setPublicSwap(bool _public) external override onlyPublicSwapSetter noReentry { 421 | emit PublicSwapSet(msg.sender, _public); 422 | PBStorage.load().bPool.setPublicSwap(_public); 423 | } 424 | 425 | /** 426 | @notice Set the swap fee on the underlying balancer pool. 427 | Can only be called by the controller. 428 | @param _swapFee The new swap fee 429 | */ 430 | function setSwapFee(uint256 _swapFee) external override onlyController noReentry { 431 | emit SwapFeeSet(msg.sender, _swapFee); 432 | PBStorage.load().bPool.setSwapFee(_swapFee); 433 | } 434 | 435 | /** 436 | @notice Set the maximum cap of the contract 437 | @param _cap New cap in wei 438 | */ 439 | function setCap(uint256 _cap) external override onlyController noReentry { 440 | emit CapChanged(msg.sender, PCSStorage.load().cap, _cap); 441 | PCSStorage.load().cap = _cap; 442 | } 443 | 444 | /** 445 | @notice Enable or disable joining and exiting 446 | @param _newValue enabled or not 447 | */ 448 | function setJoinExitEnabled(bool _newValue) external override onlyController noReentry { 449 | emit JoinExitEnabledChanged(msg.sender, P2Storage.load().joinExitEnabled, _newValue); 450 | P2Storage.load().joinExitEnabled = _newValue; 451 | } 452 | 453 | /** 454 | @notice Set the circuit breaker address. Can only be called by the controller 455 | @param _newCircuitBreaker Address of the new circuit breaker 456 | */ 457 | function setCircuitBreaker( 458 | address _newCircuitBreaker 459 | ) external override onlyController noReentry { 460 | emit CircuitBreakerChanged(P2Storage.load().circuitBreaker, _newCircuitBreaker); 461 | P2Storage.load().circuitBreaker = _newCircuitBreaker; 462 | } 463 | 464 | /** 465 | @notice Set the annual fee. Can only be called by the controller 466 | @param _newFee new fee 10**18 == 100% per 365 days. Max 10% 467 | */ 468 | function setAnnualFee(uint256 _newFee) external override onlyController noReentry { 469 | LibFees.setAnnualFee(_newFee); 470 | } 471 | 472 | /** 473 | @notice Charge the outstanding annual fee 474 | */ 475 | function chargeOutstandingAnnualFee() external override noReentry { 476 | LibFees.chargeOutstandingAnnualFee(); 477 | } 478 | 479 | /** 480 | @notice Set the address that receives the annual fee. Can only be called by the controller 481 | */ 482 | function setFeeRecipient(address _newRecipient) external override onlyController noReentry { 483 | LibFees.setFeeRecipient(_newRecipient); 484 | } 485 | 486 | /** 487 | @notice Trip the circuit breaker which disabled exit, join and swaps 488 | */ 489 | function tripCircuitBreaker() external override onlyCircuitBreaker { 490 | P2Storage.load().joinExitEnabled = false; 491 | PBStorage.load().bPool.setPublicSwap(false); 492 | emit CircuitBreakerTripped(); 493 | } 494 | 495 | // TOKEN AND WEIGHT FUNCTIONS ------------------------------- 496 | 497 | /** 498 | @notice Update the weight of a token. Can only be called by the controller 499 | @param _token Token to adjust the weight of 500 | @param _newWeight New denormalized weight 501 | */ 502 | function updateWeight(address _token, uint256 _newWeight) 503 | external 504 | override 505 | noReentry 506 | onlyController 507 | { 508 | LibWeights.updateWeight(_token, _newWeight); 509 | } 510 | 511 | /** 512 | @notice Gradually adjust the weights of a token. Can only be called by the controller 513 | @param _newWeights Target weights 514 | @param _startBlock Block to start weight adjustment 515 | @param _endBlock Block to finish weight adjustment 516 | */ 517 | function updateWeightsGradually( 518 | uint256[] calldata _newWeights, 519 | uint256 _startBlock, 520 | uint256 _endBlock 521 | ) external override noReentry onlyController { 522 | LibWeights.updateWeightsGradually(_newWeights, _startBlock, _endBlock); 523 | } 524 | 525 | /** 526 | @notice Poke the weight adjustment 527 | */ 528 | function pokeWeights() external override noReentry { 529 | LibWeights.pokeWeights(); 530 | } 531 | 532 | /** 533 | @notice Apply the adding of a token. Can only be called by the controller 534 | */ 535 | function applyAddToken() external override noReentry onlyController { 536 | LibAddRemoveToken.applyAddToken(); 537 | } 538 | 539 | /** 540 | @notice Commit a token to be added. Can only be called by the controller 541 | @param _token Address of the token to add 542 | @param _balance Amount of token to add 543 | @param _denormalizedWeight Denormalized weight 544 | */ 545 | function commitAddToken( 546 | address _token, 547 | uint256 _balance, 548 | uint256 _denormalizedWeight 549 | ) external override noReentry onlyController { 550 | LibAddRemoveToken.commitAddToken(_token, _balance, _denormalizedWeight); 551 | } 552 | 553 | /** 554 | @notice Remove a token from the smart pool. Can only be called by the controller 555 | @param _token Address of the token to remove 556 | */ 557 | function removeToken(address _token) external override noReentry onlyController { 558 | LibAddRemoveToken.removeToken(_token); 559 | } 560 | 561 | // VIEW FUNCTIONS ------------------------------------------- 562 | 563 | /** 564 | @notice Gets the underlying assets and amounts to mint specific pool shares. 565 | @param _amount Amount of pool shares to calculate the values for 566 | @return tokens The addresses of the tokens 567 | @return amounts The amounts of tokens needed to mint that amount of pool shares 568 | */ 569 | function calcTokensForAmount(uint256 _amount) 570 | external 571 | override 572 | view 573 | returns (address[] memory tokens, uint256[] memory amounts) 574 | { 575 | return LibPoolMath.calcTokensForAmount(_amount); 576 | } 577 | 578 | /** 579 | @notice Calculate the amount of pool tokens out for a given amount in 580 | @param _token Address of the input token 581 | @param _amount Amount of input token 582 | @return Amount of pool token 583 | */ 584 | function calcPoolOutGivenSingleIn(address _token, uint256 _amount) 585 | external 586 | override 587 | view 588 | returns (uint256) 589 | { 590 | return LibPoolMath.calcPoolOutGivenSingleIn(_token, _amount); 591 | } 592 | 593 | /** 594 | @notice Calculate single in given pool out 595 | @param _token Address of the input token 596 | @param _amount Amount of pool out token 597 | @return Amount of token in 598 | */ 599 | function calcSingleInGivenPoolOut(address _token, uint256 _amount) 600 | external 601 | override 602 | view 603 | returns (uint256) 604 | { 605 | return LibPoolMath.calcSingleInGivenPoolOut(_token, _amount); 606 | } 607 | 608 | /** 609 | @notice Calculate single out given pool in 610 | @param _token Address of output token 611 | @param _amount Amount of pool in 612 | @return Amount of token in 613 | */ 614 | function calcSingleOutGivenPoolIn(address _token, uint256 _amount) 615 | external 616 | override 617 | view 618 | returns (uint256) 619 | { 620 | return LibPoolMath.calcSingleOutGivenPoolIn(_token, _amount); 621 | } 622 | 623 | /** 624 | @notice Calculate pool in given single token out 625 | @param _token Address of output token 626 | @param _amount Amount of output token 627 | @return Amount of pool in 628 | */ 629 | function calcPoolInGivenSingleOut(address _token, uint256 _amount) 630 | external 631 | override 632 | view 633 | returns (uint256) 634 | { 635 | return LibPoolMath.calcPoolInGivenSingleOut(_token, _amount); 636 | } 637 | 638 | /** 639 | @notice Get the current tokens in the smart pool 640 | @return Addresses of the tokens in the smart pool 641 | */ 642 | function getTokens() external override view returns (address[] memory) { 643 | return PBStorage.load().bPool.getCurrentTokens(); 644 | } 645 | 646 | /** 647 | @notice Get the address of the controller 648 | @return The address of the pool 649 | */ 650 | function getController() external override view returns (address) { 651 | return PBStorage.load().controller; 652 | } 653 | 654 | /** 655 | @notice Get the address of the public swap setter 656 | @return The public swap setter address 657 | */ 658 | function getPublicSwapSetter() external override view returns (address) { 659 | return PBStorage.load().publicSwapSetter; 660 | } 661 | 662 | /** 663 | @notice Get the address of the token binder 664 | @return The token binder address 665 | */ 666 | function getTokenBinder() external override view returns (address) { 667 | return PBStorage.load().tokenBinder; 668 | } 669 | 670 | /** 671 | @notice Get the address of the circuitBreaker 672 | @return The address of the circuitBreaker 673 | */ 674 | function getCircuitBreaker() external override view returns (address) { 675 | return P2Storage.load().circuitBreaker; 676 | } 677 | 678 | /** 679 | @notice Get if public swapping is enabled 680 | @return If public swapping is enabled 681 | */ 682 | function isPublicSwap() external override view returns (bool) { 683 | return PBStorage.load().bPool.isPublicSwap(); 684 | } 685 | 686 | /** 687 | @notice Get the current cap 688 | @return The current cap in wei 689 | */ 690 | function getCap() external override view returns (uint256) { 691 | return PCSStorage.load().cap; 692 | } 693 | 694 | function getAnnualFee() external override view returns (uint256) { 695 | return P2Storage.load().annualFee; 696 | } 697 | 698 | function getFeeRecipient() external override view returns (address) { 699 | return P2Storage.load().feeRecipient; 700 | } 701 | 702 | /** 703 | @notice Get the denormalized weight of a specific token in the underlying balancer pool 704 | @return the normalized weight of the token in uint 705 | */ 706 | function getDenormalizedWeight(address _token) external override view returns (uint256) { 707 | return PBStorage.load().bPool.getDenormalizedWeight(_token); 708 | } 709 | 710 | /** 711 | @notice Get all denormalized weights 712 | @return weights Denormalized weights 713 | */ 714 | function getDenormalizedWeights() external override view returns (uint256[] memory weights) { 715 | PBStorage.StorageStruct storage s = PBStorage.load(); 716 | address[] memory tokens = s.bPool.getCurrentTokens(); 717 | weights = new uint256[](tokens.length); 718 | for (uint256 i = 0; i < tokens.length; i++) { 719 | weights[i] = s.bPool.getDenormalizedWeight(tokens[i]); 720 | } 721 | } 722 | 723 | /** 724 | @notice Get the address of the underlying Balancer pool 725 | @return The address of the underlying balancer pool 726 | */ 727 | function getBPool() external override view returns (address) { 728 | return address(PBStorage.load().bPool); 729 | } 730 | 731 | /** 732 | @notice Get the current swap fee 733 | @return The current swap fee 734 | */ 735 | function getSwapFee() external override view returns (uint256) { 736 | return PBStorage.load().bPool.getSwapFee(); 737 | } 738 | 739 | /** 740 | @notice Get the target weights 741 | @return weights Target weights 742 | */ 743 | function getNewWeights() external override view returns (uint256[] memory weights) { 744 | return P2Storage.load().newWeights; 745 | } 746 | 747 | /** 748 | @notice Get weights at start of weight adjustment 749 | @return weights Start weights 750 | */ 751 | function getStartWeights() external override view returns (uint256[] memory weights) { 752 | return P2Storage.load().startWeights; 753 | } 754 | 755 | /** 756 | @notice Get start block of weight adjustment 757 | @return Start block 758 | */ 759 | function getStartBlock() external override view returns (uint256) { 760 | return P2Storage.load().startBlock; 761 | } 762 | 763 | /** 764 | @notice Get end block of weight adjustment 765 | @return End block 766 | */ 767 | function getEndBlock() external override view returns (uint256) { 768 | return P2Storage.load().endBlock; 769 | } 770 | 771 | /** 772 | @notice Get new token being added 773 | @return New token 774 | */ 775 | function getNewToken() external override view returns (P2Storage.NewToken memory) { 776 | return P2Storage.load().newToken; 777 | } 778 | 779 | /** 780 | @notice Get if joining and exiting is enabled 781 | @return Enabled or not 782 | */ 783 | function getJoinExitEnabled() external override view returns (bool) { 784 | return P2Storage.load().joinExitEnabled; 785 | } 786 | 787 | // UNSUPORTED METHODS --------------------------------------- 788 | 789 | /** 790 | @notice Not Supported in PieDAO implementation of Balancer Smart Pools 791 | */ 792 | function finalizeSmartPool() external override view { 793 | revert("PV2SmartPool.finalizeSmartPool: unsupported function"); 794 | } 795 | 796 | /** 797 | @notice Not Supported in PieDAO implementation of Balancer Smart Pools 798 | */ 799 | function createPool(uint256 initialSupply) external override view { 800 | revert("PV2SmartPool.createPool: unsupported function"); 801 | } 802 | } 803 | -------------------------------------------------------------------------------- /contracts/storage/OwnableStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.4; 2 | 3 | library OwnableStorage { 4 | bytes32 public constant oSlot = keccak256("Ownable.storage.location"); 5 | struct StorageStruct { 6 | address owner; 7 | } 8 | 9 | /** 10 | @notice Load pool token storage 11 | @return s Storage pointer to the pool token struct 12 | */ 13 | function load() internal pure returns (StorageStruct storage s) { 14 | bytes32 loc = oSlot; 15 | assembly { 16 | s_slot := loc 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/storage/PBasicSmartPoolStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.4; 2 | 3 | import "../interfaces/IBPool.sol"; 4 | 5 | library PBasicSmartPoolStorage { 6 | bytes32 public constant pbsSlot = keccak256("PBasicSmartPool.storage.location"); 7 | 8 | struct StorageStruct { 9 | IBPool bPool; 10 | address controller; 11 | address publicSwapSetter; 12 | address tokenBinder; 13 | } 14 | 15 | /** 16 | @notice Load PBasicPool storage 17 | @return s Pointer to the storage struct 18 | */ 19 | function load() internal pure returns (StorageStruct storage s) { 20 | bytes32 loc = pbsSlot; 21 | assembly { 22 | s_slot := loc 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /contracts/storage/PCTokenStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.4; 2 | 3 | library PCTokenStorage { 4 | bytes32 public constant ptSlot = keccak256("PCToken.storage.location"); 5 | struct StorageStruct { 6 | string name; 7 | string symbol; 8 | uint256 totalSupply; 9 | mapping(address => uint256) balance; 10 | mapping(address => mapping(address => uint256)) allowance; 11 | } 12 | 13 | /** 14 | @notice Load pool token storage 15 | @return s Storage pointer to the pool token struct 16 | */ 17 | function load() internal pure returns (StorageStruct storage s) { 18 | bytes32 loc = ptSlot; 19 | assembly { 20 | s_slot := loc 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /contracts/storage/PCappedSmartPoolStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.4; 2 | 3 | library PCappedSmartPoolStorage { 4 | bytes32 public constant pcsSlot = keccak256("PCappedSmartPool.storage.location"); 5 | 6 | struct StorageStruct { 7 | uint256 cap; 8 | } 9 | 10 | /** 11 | @notice Load PBasicPool storage 12 | @return s Pointer to the storage struct 13 | */ 14 | function load() internal pure returns (StorageStruct storage s) { 15 | bytes32 loc = pcsSlot; 16 | assembly { 17 | s_slot := loc 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/storage/PV2SmartPoolStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.6.4; 2 | 3 | library PV2SmartPoolStorage { 4 | bytes32 public constant pasSlot = keccak256("PV2SmartPoolStorage.storage.location"); 5 | 6 | struct StorageStruct { 7 | uint256 startBlock; 8 | uint256 endBlock; 9 | uint256[] startWeights; 10 | uint256[] newWeights; 11 | NewToken newToken; 12 | bool joinExitEnabled; 13 | uint256 annualFee; 14 | uint256 lastAnnualFeeClaimed; 15 | address feeRecipient; 16 | address circuitBreaker; 17 | } 18 | 19 | struct NewToken { 20 | address addr; 21 | bool isCommitted; 22 | uint256 balance; 23 | uint256 denorm; 24 | uint256 commitBlock; 25 | } 26 | 27 | function load() internal pure returns (StorageStruct storage s) { 28 | bytes32 loc = pasSlot; 29 | assembly { 30 | s_slot := loc 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/storage/ReentryProtectionStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.4; 2 | 3 | library ReentryProtectionStorage { 4 | bytes32 public constant rpSlot = keccak256("ReentryProtection.storage.location"); 5 | struct StorageStruct { 6 | uint256 lockCounter; 7 | } 8 | 9 | /** 10 | @notice Load pool token storage 11 | @return s Storage pointer to the pool token struct 12 | */ 13 | function load() internal pure returns (StorageStruct storage s) { 14 | bytes32 loc = rpSlot; 15 | assembly { 16 | s_slot := loc 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/test/TestLibSafeApprove.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.4; 2 | 3 | import "../libraries/LibSafeApprove.sol"; 4 | import "../interfaces/IERC20.sol"; 5 | 6 | contract TestLibSafeApprove { 7 | using LibSafeApprove for IERC20; 8 | 9 | function doubleApprovalUnsafe(address _token) external { 10 | IERC20 token = IERC20(_token); 11 | 12 | token.approve(msg.sender, 1337); 13 | token.approve(msg.sender, 42); 14 | } 15 | 16 | function doubleApprovalSafe(address _token) external { 17 | IERC20 token = IERC20(_token); 18 | 19 | token.safeApprove(msg.sender, 1337); 20 | token.safeApprove(msg.sender, 42); 21 | } 22 | } -------------------------------------------------------------------------------- /contracts/test/TestPCToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.4; 2 | 3 | import "../PCToken.sol"; 4 | 5 | contract TestPCToken is PCToken { 6 | constructor(string memory _name, string memory _symbol) public { 7 | PCStorage.load().name = _name; 8 | PCStorage.load().symbol = _symbol; 9 | } 10 | 11 | function mint(address _to, uint256 _amount) external { 12 | _mint(_amount); 13 | _push(_to, _amount); 14 | } 15 | 16 | function burn(address _from, uint256 _amount) external { 17 | _pull(_from, _amount); 18 | _burn(_amount); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /contracts/test/TestReentryProtection.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.6.4; 2 | 3 | import "../ReentryProtection.sol"; 4 | 5 | contract TestReentryProtection is ReentryProtection { 6 | // This should fail 7 | function test() external noReentry { 8 | reenter(); 9 | } 10 | 11 | function reenter() public noReentry { 12 | // Do nothing 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | INFURA_API_KEY= 2 | KOVAN_PRIVATE_KEY=0x..... 3 | KOVAN_PRIVATE_KEY_SECONDARY=0x..... 4 | RINKEBY_PRIVATE_KEY=0x.... 5 | RINKEBY_PRIVATE_KEY_SECONDARY=0x..... 6 | MAINNET_PRIVATE_KEY=0x.... 7 | MAINNET_PRIVATE_KEY_SECONDARY=0x.... 8 | ETHERSCAN_API_KEY= -------------------------------------------------------------------------------- /mainnet-test/test.ts: -------------------------------------------------------------------------------- 1 | import {ethers} from "@nomiclabs/buidler"; 2 | import {Signer, Wallet, utils, constants, ContractTransaction} from "ethers"; 3 | import chai from "chai"; 4 | import {deployContract, solidity} from "ethereum-waffle"; 5 | import {PCappedSmartPool} from "../typechain/PCappedSmartPool"; 6 | import {PCappedSmartPoolFactory} from "../typechain/PCappedSmartPoolFactory"; 7 | import {IBPoolFactory} from "../typechain/IBPoolFactory"; 8 | import {IERC20Factory} from "../typechain/IERC20Factory"; 9 | import {IERC20} from "../typechain/IERC20"; 10 | import {IBPool} from "../typechain/IBPool"; 11 | import {parseEther, BigNumber, bigNumberify} from "ethers/utils"; 12 | import {MockTokenFactory} from "@pie-dao/mock-contracts/dist/typechain/MockTokenFactory"; 13 | import {MockToken} from "@pie-dao/mock-contracts/typechain/MockToken"; 14 | 15 | chai.use(solidity); 16 | const {expect} = chai; 17 | 18 | describe("MAINNET TEST", function () { 19 | this.timeout(10000000); 20 | let signers: Signer[]; 21 | let account: string; 22 | let account2: string; 23 | let bPool: IBPool; 24 | let pool: PCappedSmartPool; 25 | let mockToken: MockToken; 26 | // Pool Alt Signer 27 | let poolAS: PCappedSmartPool; 28 | const tokens: IERC20[] = []; 29 | const mintAmount = parseEther("0.001"); 30 | const poolAddress = process.env.POOL; 31 | 32 | before(async () => { 33 | signers = await ethers.signers(); 34 | account = await signers[0].getAddress(); 35 | account2 = await signers[1].getAddress(); 36 | pool = PCappedSmartPoolFactory.connect(poolAddress, signers[0]); 37 | poolAS = PCappedSmartPoolFactory.connect(poolAddress, signers[1]); 38 | bPool = IBPoolFactory.connect(await pool.getBPool(), signers[0]); 39 | 40 | const tokenAddresses = await bPool.getCurrentTokens(); 41 | 42 | mockToken = await new MockTokenFactory(signers[0]).deploy("TEST", "TEST", 18); 43 | await (await mockToken.mint(account, parseEther("1000000"))).wait(1); 44 | await (await mockToken.approve(pool.address, constants.MaxUint256)).wait(1); 45 | 46 | // Approve tokens 47 | console.log("Approving tokens"); 48 | for (const tokenAddress of tokenAddresses) { 49 | const token = IERC20Factory.connect(tokenAddress, signers[0]); 50 | tokens.push(token); 51 | 52 | if ((await token.allowance(account, pool.address)).gt(constants.MaxUint256.div(2))) { 53 | console.log(`Approving ${tokenAddress}`); 54 | await (await token.approve(pool.address, constants.MaxUint256)).wait(1); 55 | } 56 | } 57 | }); 58 | 59 | it(`Controller should be correct`, async () => { 60 | const controller = await pool.getController(); 61 | expect(controller).to.eq(account); 62 | }); 63 | 64 | it("Cap should be zero", async () => { 65 | const cap = await pool.getCap(); 66 | expect(cap).to.eq(0); 67 | }); 68 | 69 | it("Exiting the pool should work", async () => { 70 | const balanceBefore = await pool.balanceOf(account); 71 | await await (await pool.exitPool(mintAmount, {gasLimit: 2000000})).wait(1); 72 | const balanceAfter = await pool.balanceOf(account); 73 | expect(balanceAfter).to.eq(balanceBefore.sub(mintAmount)); 74 | }); 75 | 76 | it("Cap should be enforced", async () => { 77 | const balanceBefore = await pool.balanceOf(account); 78 | expect(await transactionFails(pool.joinPool(parseEther("0.001"), {gasLimit: 2000000}))).to.be 79 | .true; 80 | const balanceAfter = await pool.balanceOf(account); 81 | expect(balanceBefore).to.eq(balanceAfter); 82 | }); 83 | 84 | it("Setting the cap should work", async () => { 85 | const newCap = parseEther("1000000000"); 86 | await (await pool.setCap(newCap, {gasLimit: 2000000})).wait(1); 87 | const cap = await pool.getCap(); 88 | expect(newCap).to.eq(cap); 89 | }); 90 | 91 | it("Joining the pool should work", async () => { 92 | const balanceBefore = await pool.balanceOf(account); 93 | await (await pool.joinPool(mintAmount, {gasLimit: 2000000})).wait(1); 94 | const balanceAfter = await pool.balanceOf(account); 95 | expect(balanceAfter).to.eq(balanceBefore.add(mintAmount)); 96 | }); 97 | 98 | it("Exit pool taking loss should work", async () => { 99 | const exitAmount = parseEther("0.001"); 100 | 101 | const expectedAmounts = await pool.calcTokensForAmount(exitAmount); 102 | 103 | const tokenBalancesBefore = await getBalances(account); 104 | const balanceBefore = await pool.balanceOf(account); 105 | await (await pool.exitPoolTakingloss(exitAmount, [tokens[0].address])).wait(1); 106 | const balanceAfter = await pool.balanceOf(account); 107 | const tokenBalancesAfter = await getBalances(account); 108 | 109 | for (let i = 1; i < tokenBalancesAfter.length; i++) { 110 | expect(tokenBalancesAfter[i]).to.eq(tokenBalancesBefore[i].add(expectedAmounts.amounts[i])); 111 | } 112 | 113 | expect(balanceAfter).to.eq(balanceBefore.sub(exitAmount)); 114 | expect(tokenBalancesBefore[0]).to.eq(tokenBalancesAfter[0]); 115 | }); 116 | 117 | it("Setting the cap back to zero should work", async () => { 118 | await (await pool.setCap(0, {gasLimit: 2000000})).wait(1); 119 | const cap = await pool.getCap(); 120 | expect(cap).to.eq(0); 121 | }); 122 | 123 | it("Setting public swap setter from non controller should fail", async () => { 124 | const publicSwapSetterBefore = await poolAS.getPublicSwapSetter(); 125 | expect(await transactionFails(poolAS.setPublicSwapSetter(account2, {gasLimit: 2000000}))).to.be 126 | .true; 127 | const publicSwapSetterAfter = await poolAS.getPublicSwapSetter(); 128 | expect(publicSwapSetterBefore).to.eq(publicSwapSetterAfter); 129 | }); 130 | 131 | it("Setting public swap setter from the controller should work", async () => { 132 | await (await pool.setPublicSwapSetter(account, {gasLimit: 2000000})).wait(1); 133 | const publicSwapSetter = await pool.getPublicSwapSetter(); 134 | expect(publicSwapSetter).to.eq(account); 135 | }); 136 | 137 | it("Setting public swap to true from the correct address should work", async () => { 138 | await (await pool.setPublicSwap(true, {gasLimit: 2000000})).wait(1); 139 | const publicSwap = await bPool.isPublicSwap(); 140 | expect(publicSwap).to.be.true; 141 | }); 142 | 143 | it("Setting public swap to false from the correct address should work", async () => { 144 | await (await pool.setPublicSwap(false, {gasLimit: 2000000})).wait(1); 145 | const publicSwap = await bPool.isPublicSwap(); 146 | expect(publicSwap).to.be.false; 147 | }); 148 | 149 | it("Setting public swap setter to zero again should work", async () => { 150 | await (await pool.setPublicSwapSetter(constants.AddressZero, {gasLimit: 2000000})).wait(1); 151 | const publicSwapSetter = await pool.getPublicSwapSetter(); 152 | expect(publicSwapSetter).to.eq(constants.AddressZero); 153 | }); 154 | 155 | it("Setting public swap to true should fail", async () => { 156 | expect(await transactionFails(pool.setPublicSwap(true, {gasLimit: 2000000}))).to.eq(true); 157 | }); 158 | 159 | it("Changing tokenBinder should work", async () => { 160 | await (await pool.setTokenBinder(account, {gasLimit: 2000000})).wait(1); 161 | const tokenBinder = await pool.getTokenBinder(); 162 | expect(tokenBinder).to.eq(account); 163 | }); 164 | 165 | it("Unbinding a token from a non tokenBinder should fail", async () => { 166 | expect(await transactionFails(poolAS.unbind(tokens[0].address, {gasLimit: 2000000}))).to.be 167 | .true; 168 | const poolTokens = await poolAS.getTokens(); 169 | expect(poolTokens[0]).to.eq(tokens[0].address); 170 | }); 171 | 172 | it("Binding a token from a non tokenBinder should fail", async () => { 173 | const tokensBefore = await bPool.getCurrentTokens(); 174 | expect( 175 | await transactionFails( 176 | poolAS.bind(mockToken.address, constants.WeiPerEther, constants.WeiPerEther, { 177 | gasLimit: 2000000, 178 | }) 179 | ) 180 | ).to.be.true; 181 | const tokensAfter = await bPool.getCurrentTokens(); 182 | expect(tokensAfter).to.eql(tokensBefore); 183 | }); 184 | 185 | it("Rebinding a token from a non tokenBinder should fail", async () => { 186 | const balanceBefore = await bPool.getBalance(tokens[0].address); 187 | expect( 188 | await transactionFails( 189 | poolAS.rebind(tokens[0].address, balanceBefore.div(2), constants.WeiPerEther, { 190 | gasLimit: 2000000, 191 | }) 192 | ) 193 | ).to.be.true; 194 | const balanceAfter = await bPool.getBalance(tokens[0].address); 195 | expect(balanceBefore).to.eq(balanceAfter); 196 | }); 197 | 198 | it("Rebinding a token should work", async () => { 199 | // Reducing the weight of a token to make room to bind another one 200 | const balanceBefore = await bPool.getBalance(tokens[0].address); 201 | await ( 202 | await pool.rebind(tokens[0].address, balanceBefore, constants.WeiPerEther, { 203 | gasLimit: 2000000, 204 | }) 205 | ).wait(1); 206 | const weight = await bPool.getDenormalizedWeight(tokens[0].address); 207 | expect(weight).to.eq(constants.WeiPerEther); 208 | }); 209 | 210 | it("Binding a token should work", async () => { 211 | await ( 212 | await pool.bind(mockToken.address, constants.WeiPerEther, constants.WeiPerEther, { 213 | gasLimit: 2000000, 214 | }) 215 | ).wait(1); 216 | const poolTokens = await bPool.getCurrentTokens(); 217 | expect(poolTokens[poolTokens.length - 1]).to.eq(mockToken.address); 218 | }); 219 | 220 | it("Gets denormalized weight of underlying token in balancer pool", async () => { 221 | const tokenWeight = await pool.getDenormalizedWeight(mockToken.address); 222 | 223 | expect(tokenWeight).to.eq(constants.WeiPerEther); 224 | }); 225 | 226 | it("poolAmountOut = joinswapExternAmountIn(joinswapPoolAmountOut(poolAmountOut))", async () => { 227 | const userPreBalance = await tokens[1].balanceOf(account); 228 | const userPrePoolBalance = await bPool.balanceOf(account); 229 | 230 | const poolAmountOut = constants.One; 231 | const tokenAmountIn = await bPool.joinswapPoolAmountOut(tokens[1].address, poolAmountOut); 232 | const tAIResponse = await tokenAmountIn.wait(1); 233 | const tAI = new BigNumber(tAIResponse.events[0].data); 234 | 235 | const calculatedPoolAmountOut = await bPool.joinswapExternAmountIn(tokens[1].address, tAI); 236 | const cPAOResponse = await calculatedPoolAmountOut.wait(1); 237 | const cPAO = new BigNumber(cPAOResponse.events[3].data); 238 | 239 | const userCurrentBalance = await tokens[1].balanceOf(account); 240 | const userCurrentPoolBalance = await bPool.balanceOf(account); 241 | 242 | expect(userCurrentPoolBalance).to.equal(userPrePoolBalance.add(poolAmountOut.mul(2))); 243 | expect(userCurrentBalance).to.equal(userPreBalance.sub(tAI.mul(2))); 244 | expect(cPAO).to.equal(poolAmountOut); 245 | }); 246 | 247 | it("tokenAmountOut = exitswapPoolAmountIn(exitswapExternAmountOut(tokenAmountOut))", async () => { 248 | const tokenAmountOut = constants.One; 249 | const poolAmountIn = await bPool.joinswapPoolAmountOut(tokens[1].address, tokenAmountOut); 250 | 251 | const pAIResponse = await poolAmountIn.wait(1); 252 | const pAI = new BigNumber(pAIResponse.events[0].data); 253 | 254 | const calculatedTokenAmountOut = await bPool.joinswapExternAmountIn(tokens[1].address, pAI); 255 | const cTAOResponse = await calculatedTokenAmountOut.wait(1); 256 | const cTAO = new BigNumber(cTAOResponse.events[3].data); 257 | 258 | expect(cTAO).to.equal(tokenAmountOut); 259 | }); 260 | 261 | it("Unbinding a token should work", async () => { 262 | await (await pool.unbind(mockToken.address, {gasLimit: 2000000})).wait(1); 263 | const poolTokens = await bPool.getCurrentTokens(); 264 | expect(tokens.length).to.eq(poolTokens.length); 265 | }); 266 | 267 | it("Setting the token binder to zero should work", async () => { 268 | await (await pool.setTokenBinder(constants.AddressZero, {gasLimit: 2000000})).wait(1); 269 | const tokenBinder = await pool.getTokenBinder(); 270 | expect(tokenBinder).to.eq(constants.AddressZero); 271 | }); 272 | 273 | it("Binding a token should fail", async () => { 274 | expect( 275 | await transactionFails( 276 | pool.bind(mockToken.address, constants.WeiPerEther, constants.WeiPerEther, { 277 | gasLimit: 2000000, 278 | }) 279 | ) 280 | ).to.be.true; 281 | }); 282 | 283 | async function getBalances(address: string) { 284 | const balances: BigNumber[] = []; 285 | 286 | for (const token of tokens) { 287 | balances.push(await token.balanceOf(address)); 288 | } 289 | 290 | return balances; 291 | } 292 | }); 293 | 294 | async function transactionFails(transaction: Promise) { 295 | let e: any = {reason: ""}; 296 | try { 297 | const receipt = await (await transaction).wait(1); 298 | } catch (error) { 299 | e = error; 300 | } 301 | 302 | return e.reason === "transaction failed"; 303 | } 304 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@pie-dao/smart-pools", 3 | "version": "0.1.0", 4 | "description": "Implementations of smart pools for Balancer", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "yarn compile && yarn typechain", 8 | "compile": "npx buidler --config ./buidler.compile.config.ts compile", 9 | "coverage": "npm run build && npx buidler coverage --temp artifacts --network coverage", 10 | "lint:solidity": "npx solhint ./contracts/**/*.sol", 11 | "lint:typescript": "tslint -c tslint.json {test,typechain,utils}/**/*.ts", 12 | "lint": "yarn lint:solidity && yarn lint:typescript", 13 | "prettier:solidity": "npx prettier --write ./contracts/**/*.sol --print-width 100", 14 | "prettier:typescript": "npx prettier --write {test,typechain,utils}/**/*.ts --print-width 100", 15 | "prettier": "yarn prettier:solidity && yarn prettier:typescript", 16 | "test": "npx buidler test", 17 | "typechain": "typechain --target ethers-v4 --outDir typechain ./artifacts/**/*.json", 18 | "clean": "rm -rf ./artifacts && rm -rf ./cache && rm -rf ./coverage && rm -rf dist && rm -rf ./typechain", 19 | "remix": "remixd -s . --remix-ide https://remix.ethereum.org" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/pie-dao/proxied-balancer-factory.git" 24 | }, 25 | "author": "Pie DAO", 26 | "license": "GPL-3.0-only", 27 | "bugs": { 28 | "url": "https://github.com/pie-dao/proxied-balancer-factory/issues" 29 | }, 30 | "homepage": "https://github.com/pie-dao/proxied-balancer-factory/readme", 31 | "devDependencies": { 32 | "@nomiclabs/buidler-ethers": "^1.3.4", 33 | "@nomiclabs/buidler-waffle": "^1.3.5", 34 | "@typechain/ethers-v4": "^1.0.0", 35 | "@types/chai": "^4.2.8", 36 | "@types/mocha": "^7.0.1", 37 | "@types/node": "^13.7.0", 38 | "buidler-deploy": "^0.4.13", 39 | "buidler-gas-reporter": "^0.1.3", 40 | "chai": "^4.2.0", 41 | "ethereum-waffle": "^2.3.2", 42 | "ethers": "^4.0.47", 43 | "prettier": "^2.0.5", 44 | "prettier-plugin-solidity": "^1.0.0-alpha.52", 45 | "solhint": "^3.0.0", 46 | "solidity-coverage": "^0.7.9", 47 | "ts-generator": "0.0.8", 48 | "ts-node": "^8.6.2", 49 | "tslint": "^6.1.2", 50 | "typechain": "^2.0.0", 51 | "typescript": "^3.7.5" 52 | }, 53 | "directories": { 54 | "test": "test" 55 | }, 56 | "dependencies": { 57 | "@nomiclabs/buidler": "^1.4.7", 58 | "@nomiclabs/buidler-etherscan": "^2.1.0", 59 | "@pie-dao/mock-contracts": "0.0.9", 60 | "@pie-dao/proxy": "0.0.6", 61 | "dotenv": "^8.2.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/capPoolFunctionality.ts: -------------------------------------------------------------------------------- 1 | // This way of importing is a bit funky. We should fix this in the Mock Contracts package 2 | import {MockTokenFactory} from "@pie-dao/mock-contracts/dist/typechain/MockTokenFactory"; 3 | import {MockToken} from "@pie-dao/mock-contracts/typechain/MockToken"; 4 | import {ethers, run} from "@nomiclabs/buidler"; 5 | import {Signer, Wallet, utils, constants} from "ethers"; 6 | import {BigNumber} from "ethers/utils"; 7 | import chai from "chai"; 8 | import {solidity} from "ethereum-waffle"; 9 | 10 | import {deployBalancerPool, linkArtifact} from "../utils"; 11 | import {IbPool} from "../typechain/IbPool"; 12 | import {IbPoolFactory} from "../typechain/IbPoolFactory"; 13 | import {Pv2SmartPool} from "../typechain/Pv2SmartPool"; 14 | import PV2SmartPoolArtifact from "../artifacts/PV2SmartPool.json"; 15 | 16 | chai.use(solidity); 17 | const {expect} = chai; 18 | 19 | const PLACE_HOLDER_ADDRESS = "0x0000000000000000000000000000000000000001"; 20 | const NAME = "TEST POOL"; 21 | const SYMBOL = "TPL"; 22 | const INITIAL_SUPPLY = constants.WeiPerEther; 23 | 24 | describe("Cap", function () { 25 | this.timeout(300000); 26 | let signers: Signer[]; 27 | let account: string; 28 | let tokens: MockToken[]; 29 | let pool: IbPool; 30 | let smartpool: Pv2SmartPool; 31 | 32 | beforeEach(async () => { 33 | signers = await ethers.signers(); 34 | account = await signers[0].getAddress(); 35 | 36 | pool = IbPoolFactory.connect(await deployBalancerPool(signers[0]), signers[0]); 37 | 38 | const tokenFactory = new MockTokenFactory(signers[0]); 39 | tokens = []; 40 | 41 | for (let i = 0; i < 8; i++) { 42 | const token: MockToken = await tokenFactory.deploy(`Mock ${i}`, `M${i}`, 18); 43 | await token.mint(account, constants.WeiPerEther.mul(1000000)); 44 | await token.mint(await signers[1].getAddress(), constants.WeiPerEther.mul(1000000)); 45 | await token.approve(pool.address, constants.MaxUint256); 46 | pool.bind(token.address, constants.WeiPerEther, constants.WeiPerEther.mul(1)); 47 | tokens.push(token); 48 | } 49 | 50 | smartpool = (await run("deploy-libraries-and-smartpool")) as Pv2SmartPool; 51 | 52 | await smartpool.init(pool.address, NAME, SYMBOL, INITIAL_SUPPLY); 53 | await smartpool.approveTokens(); 54 | await pool.setController(smartpool.address); 55 | 56 | for (const token of tokens) { 57 | await token.approve(smartpool.address, constants.MaxUint256); 58 | // Attach alt signer to token and approve pool 59 | await MockTokenFactory.connect(token.address, signers[1]).approve( 60 | smartpool.address, 61 | constants.MaxUint256 62 | ); 63 | } 64 | 65 | await smartpool.setJoinExitEnabled(true); 66 | }); 67 | 68 | it("Cap should initially zero", async () => { 69 | const cap = await smartpool.getCap(); 70 | expect(cap).to.eq(constants.Zero); 71 | }); 72 | 73 | it("Setting the cap should work", async () => { 74 | const capValue = new BigNumber(100); 75 | await smartpool.setCap(capValue); 76 | const cap = await smartpool.getCap(); 77 | expect(cap).to.eq(capValue); 78 | }); 79 | 80 | it("Setting the cap from a non controller address should fail", async () => { 81 | await smartpool.setController(await signers[1].getAddress()); 82 | await expect(smartpool.setCap(100)).to.be.revertedWith( 83 | "PV2SmartPool.onlyController: not controller" 84 | ); 85 | }); 86 | 87 | it("JoinPool with less than the cap should work", async () => { 88 | await smartpool.setCap(constants.MaxUint256); 89 | 90 | const mintAmount = constants.WeiPerEther; 91 | await smartpool.joinPool(mintAmount); 92 | 93 | const balance = await smartpool.balanceOf(account); 94 | expect(balance).to.eq(mintAmount.add(INITIAL_SUPPLY)); 95 | }); 96 | 97 | it("JoinPool with more than the cap should fail", async () => { 98 | const cap = constants.WeiPerEther.mul(100); 99 | await smartpool.setCap(cap); 100 | await expect(smartpool.joinPool(cap.add(1))).to.be.revertedWith( 101 | "PV2SmartPool.withinCap: Cap limit reached" 102 | ); 103 | }); 104 | 105 | it("joinswapExternAmountIn with less than the cap should work", async () => { 106 | const cap = constants.WeiPerEther.mul(100); 107 | await smartpool.setCap(cap); 108 | await smartpool.setPublicSwap(true); 109 | const tokenBalance = constants.WeiPerEther.div(100); 110 | 111 | await smartpool.joinswapExternAmountIn(tokens[0].address, tokenBalance, constants.Zero); 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /test/pProxiedFactory.ts: -------------------------------------------------------------------------------- 1 | // This way of importing is a bit funky. We should fix this in the Mock Contracts package 2 | import {MockTokenFactory} from "@pie-dao/mock-contracts/dist/typechain/MockTokenFactory"; 3 | import {MockToken} from "@pie-dao/mock-contracts/typechain/MockToken"; 4 | import {ethers, run} from "@nomiclabs/buidler"; 5 | import {Signer, Wallet, utils, constants} from "ethers"; 6 | import {BigNumber, BigNumberish} from "ethers/utils"; 7 | import chai from "chai"; 8 | import {deployContract, solidity} from "ethereum-waffle"; 9 | 10 | import {deployBalancerPool, deployBalancerFactory, linkArtifact} from "../utils"; 11 | import {PProxiedFactory} from "../typechain/PProxiedFactory"; 12 | import {Pv2SmartPool} from "../typechain/Pv2SmartPool"; 13 | import PV2SmartPoolArtifact from "../artifacts/PV2SmartPool.json"; 14 | import PProxiedFactoryArtifact from "../artifacts/PProxiedFactory.json"; 15 | 16 | chai.use(solidity); 17 | const {expect} = chai; 18 | 19 | const INITIAL_SUPPLY = constants.WeiPerEther; 20 | const PLACE_HOLDER_ADDRESS = "0x0000000000000000000000000000000000000001"; 21 | 22 | describe("PProxiedFactory", () => { 23 | let signers: Signer[]; 24 | let account: string; 25 | let factory: PProxiedFactory; 26 | const tokenAddresses: string[] = []; 27 | const amounts: BigNumberish[] = []; 28 | const weights: BigNumberish[] = []; 29 | 30 | beforeEach(async () => { 31 | signers = await ethers.signers(); 32 | account = await signers[0].getAddress(); 33 | 34 | const balancerFactoryAddress = await deployBalancerFactory(signers[0]); 35 | 36 | factory = (await deployContract(signers[0] as Wallet, PProxiedFactoryArtifact, [], { 37 | gasLimit: 100000000, 38 | })) as PProxiedFactory; 39 | 40 | const libraries = await run("deploy-libraries"); 41 | const linkedArtifact = linkArtifact(PV2SmartPoolArtifact, libraries); 42 | 43 | // Deploy this way to get the coverage provider to pick it up 44 | const implementation = (await deployContract(signers[0] as Wallet, linkedArtifact, [], { 45 | gasLimit: 100000000, 46 | })) as Pv2SmartPool; 47 | 48 | await implementation.init(PLACE_HOLDER_ADDRESS, "IMP", "IMP", 1337); 49 | await factory.init(balancerFactoryAddress, implementation.address); 50 | 51 | const tokenFactory = new MockTokenFactory(signers[0]); 52 | for (let i = 0; i < 3; i++) { 53 | const token: MockToken = await tokenFactory.deploy(`Mock ${i}`, `M${i}`, 18); 54 | await token.mint(account, constants.WeiPerEther.mul(1000000)); 55 | await token.approve(factory.address, constants.MaxUint256); 56 | tokenAddresses.push(token.address); 57 | weights.push(constants.WeiPerEther.mul(3)); 58 | amounts.push(constants.WeiPerEther.mul(10)); 59 | } 60 | }); 61 | 62 | it("Creating a new proxied pool should work", async () => { 63 | await factory.newProxiedSmartPool( 64 | "TEST", 65 | "TST", 66 | constants.WeiPerEther, 67 | tokenAddresses, 68 | amounts, 69 | weights, 70 | INITIAL_SUPPLY 71 | ); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /test/poolToken.ts: -------------------------------------------------------------------------------- 1 | // This way of importing is a bit funky. We should fix this in the Mock Contracts package 2 | import {MockTokenFactory} from "@pie-dao/mock-contracts/dist/typechain/MockTokenFactory"; 3 | import {MockToken} from "@pie-dao/mock-contracts/typechain/MockToken"; 4 | import {ethers} from "@nomiclabs/buidler"; 5 | import {Signer, Wallet, utils, constants} from "ethers"; 6 | import {BigNumber} from "ethers/utils"; 7 | import chai from "chai"; 8 | import {deployContract, solidity} from "ethereum-waffle"; 9 | 10 | import TestPCTokenArtifact from "../artifacts/TestPCToken.json"; 11 | import {TestPcToken} from "../typechain/TestPcToken"; 12 | 13 | chai.use(solidity); 14 | const {expect} = chai; 15 | 16 | const NAME = "TEST POOL"; 17 | const SYMBOL = "TPL"; 18 | 19 | describe("poolToken", function () { 20 | this.timeout(300000); 21 | let signers: Signer[]; 22 | let account: string; 23 | let account2: string; 24 | let pcToken: TestPcToken; 25 | 26 | beforeEach(async () => { 27 | signers = await ethers.signers(); 28 | account = await signers[0].getAddress(); 29 | account2 = await signers[1].getAddress(); 30 | 31 | pcToken = (await deployContract(signers[0] as Wallet, TestPCTokenArtifact, [NAME, SYMBOL], { 32 | gasLimit: 100000000, 33 | })) as TestPcToken; 34 | }); 35 | 36 | describe("token metadata", async () => { 37 | it("Should have 18 decimals", async () => { 38 | const decimals = await pcToken.decimals(); 39 | expect(decimals).to.equal(18); 40 | }); 41 | it("Token name should be correct", async () => { 42 | const name = await pcToken.name(); 43 | expect(name).to.eq(NAME); 44 | }); 45 | it("Symbol should be correct", async () => { 46 | const symbol = await pcToken.symbol(); 47 | expect(symbol).to.eq(SYMBOL); 48 | }); 49 | it("Initial supply should be zero", async () => { 50 | const totalSupply = await pcToken.totalSupply(); 51 | expect(totalSupply).to.eq(0); 52 | }); 53 | it("After minting total supply should go up by minted amount", async () => { 54 | const mintAmount = constants.WeiPerEther.mul(2); 55 | // Mint in two tx to check if that works 56 | await pcToken.mint(account, mintAmount.div(2)); 57 | await pcToken.mint(account, mintAmount.div(2)); 58 | 59 | const totalSupply = await pcToken.totalSupply(); 60 | expect(totalSupply).to.eq(mintAmount); 61 | }); 62 | it("Burning tokens should lower the total supply", async () => { 63 | const mintAmount = constants.WeiPerEther.mul(2); 64 | await pcToken.mint(account, mintAmount); 65 | await pcToken.burn(account, mintAmount.div(2)); 66 | const totalSupply = await pcToken.totalSupply(); 67 | expect(totalSupply).to.eq(mintAmount.div(2)); 68 | }); 69 | it("Burning more than an address's balance should fail", async () => { 70 | const mintAmount = constants.WeiPerEther; 71 | await pcToken.mint(account, mintAmount); 72 | await expect(pcToken.burn(account, constants.WeiPerEther.add(1))).to.be.revertedWith( 73 | "ERR_INSUFFICIENT_BAL" 74 | ); 75 | }); 76 | }); 77 | describe("balanceOf", async () => { 78 | it("Should return zero if no balance", async () => { 79 | const balance = await pcToken.balanceOf(account); 80 | expect(balance).to.eq(0); 81 | }); 82 | it("Should return correct amount if account has some tokens", async () => { 83 | const mintAmount = constants.WeiPerEther.mul(2); 84 | await pcToken.mint(account, mintAmount); 85 | const balance = await pcToken.balanceOf(account); 86 | expect(balance).to.eq(mintAmount); 87 | }); 88 | }); 89 | describe("transfer", async () => { 90 | it("Should fail when the sender does not have enought balance", async () => { 91 | await pcToken.mint(account, constants.WeiPerEther); 92 | await expect(pcToken.transfer(account2, constants.WeiPerEther.add(1))).to.be.revertedWith( 93 | "ERR_INSUFFICIENT_BAL" 94 | ); 95 | }); 96 | it("Sending the entire balance should work", async () => { 97 | await pcToken.mint(account, constants.WeiPerEther); 98 | await pcToken.transfer(account2, constants.WeiPerEther); 99 | 100 | const accountBalance = await pcToken.balanceOf(account); 101 | const account2Balance = await pcToken.balanceOf(account2); 102 | 103 | expect(accountBalance).to.eq(0); 104 | expect(account2Balance).to.eq(constants.WeiPerEther); 105 | }); 106 | it("Should emit transfer event", async () => { 107 | await pcToken.mint(account, constants.WeiPerEther); 108 | await expect(pcToken.transfer(account2, constants.WeiPerEther)) 109 | .to.emit(pcToken, "Transfer") 110 | .withArgs(account, account2, constants.WeiPerEther); 111 | }); 112 | it("Sending 0 tokens should work", async () => { 113 | await pcToken.mint(account, constants.WeiPerEther); 114 | await pcToken.transfer(account2, constants.Zero); 115 | 116 | const accountBalance = await pcToken.balanceOf(account); 117 | const account2Balance = await pcToken.balanceOf(account2); 118 | 119 | expect(accountBalance).to.eq(constants.WeiPerEther); 120 | expect(account2Balance).to.eq(0); 121 | }); 122 | }); 123 | describe("approve", async () => { 124 | it("Should emit event", async () => { 125 | await expect(pcToken.approve(account2, constants.WeiPerEther)) 126 | .to.emit(pcToken, "Approval") 127 | .withArgs(account, account2, constants.WeiPerEther); 128 | }); 129 | it("Should work when there was no approved amount before", async () => { 130 | await pcToken.approve(account2, constants.WeiPerEther); 131 | const approvalAmount = await pcToken.allowance(account, account2); 132 | expect(approvalAmount).to.eq(constants.WeiPerEther); 133 | }); 134 | it("Should work when there was a approved amount before", async () => { 135 | await pcToken.approve(account2, constants.WeiPerEther); 136 | await pcToken.approve(account2, constants.WeiPerEther.mul(2)); 137 | const approvalAmount = await pcToken.allowance(account, account2); 138 | expect(approvalAmount).to.eq(constants.WeiPerEther.mul(2)); 139 | }); 140 | it("Setting approval back to zero should work", async () => { 141 | await pcToken.approve(account2, constants.WeiPerEther); 142 | await pcToken.approve(account2, 0); 143 | }); 144 | }); 145 | describe("increaseApproval", async () => { 146 | it("Should emit event", async () => { 147 | await expect(pcToken.increaseApproval(account2, constants.WeiPerEther)) 148 | .to.emit(pcToken, "Approval") 149 | .withArgs(account, account2, constants.WeiPerEther); 150 | }); 151 | it("Should work when there was no approved amount before", async () => { 152 | await pcToken.increaseApproval(account2, constants.WeiPerEther); 153 | const approvalAmount = await pcToken.allowance(account, account2); 154 | expect(approvalAmount).to.eq(constants.WeiPerEther); 155 | }); 156 | it("Should work when there was an approved amount before", async () => { 157 | await pcToken.increaseApproval(account2, constants.WeiPerEther); 158 | await pcToken.increaseApproval(account2, constants.WeiPerEther); 159 | const approvalAmount = await pcToken.allowance(account, account2); 160 | expect(approvalAmount).to.eq(constants.WeiPerEther.mul(2)); 161 | }); 162 | it("Increasing approval beyond max uint256 should fail", async () => { 163 | await pcToken.increaseApproval(account2, constants.MaxUint256); 164 | await expect(pcToken.increaseApproval(account2, constants.WeiPerEther)).to.be.revertedWith( 165 | "ERR_ADD_OVERFLOW" 166 | ); 167 | }); 168 | }); 169 | describe("decreaseApproval", async () => { 170 | beforeEach(async () => { 171 | await pcToken.approve(account2, constants.WeiPerEther); 172 | }); 173 | it("Should emit event", async () => { 174 | await expect(pcToken.decreaseApproval(account2, constants.WeiPerEther)) 175 | .to.emit(pcToken, "Approval") 176 | .withArgs(account, account2, constants.Zero); 177 | }); 178 | it("Decreasing part of the approval should work", async () => { 179 | await pcToken.decreaseApproval(account2, constants.WeiPerEther.div(2)); 180 | const approvalAmount = await pcToken.allowance(account, account2); 181 | expect(approvalAmount).to.eq(constants.WeiPerEther.div(2)); 182 | }); 183 | it("Decreasing the entire approval should work", async () => { 184 | await pcToken.decreaseApproval(account2, constants.WeiPerEther); 185 | const approvalAmount = await pcToken.allowance(account, account2); 186 | expect(approvalAmount).to.eq(constants.Zero); 187 | }); 188 | it("Decreasing more than the approval amount should set approval to zero", async () => { 189 | await pcToken.decreaseApproval(account2, constants.WeiPerEther.mul(2)); 190 | const approvalAmount = await pcToken.allowance(account, account2); 191 | expect(approvalAmount).to.eq(constants.Zero); 192 | }); 193 | }); 194 | describe("transferFrom", async () => { 195 | beforeEach(async () => { 196 | await pcToken.mint(account, constants.WeiPerEther); 197 | }); 198 | it("Should emit event", async () => { 199 | await pcToken.approve(account2, constants.WeiPerEther); 200 | pcToken = pcToken.connect(signers[1]); 201 | await expect(pcToken.transferFrom(account, account2, constants.WeiPerEther)) 202 | .to.emit(pcToken, "Transfer") 203 | .withArgs(account, account2, constants.WeiPerEther); 204 | }); 205 | it("Should work when sender has enough balance and approved spender", async () => { 206 | await pcToken.approve(account2, constants.WeiPerEther); 207 | pcToken = pcToken.connect(signers[1]); 208 | await pcToken.transferFrom(account, account2, constants.WeiPerEther); 209 | 210 | const accountBalance = await pcToken.balanceOf(account); 211 | const account2Balance = await pcToken.balanceOf(account2); 212 | const approvalAmount = await pcToken.allowance(account, account2); 213 | 214 | expect(accountBalance).to.eq(constants.Zero); 215 | expect(account2Balance).to.eq(constants.WeiPerEther); 216 | expect(approvalAmount).to.eq(constants.Zero); 217 | }); 218 | it("Should fail when not enough allowance is set", async () => { 219 | await pcToken.approve(account2, constants.WeiPerEther.sub(1)); 220 | pcToken = pcToken.connect(signers[1]); 221 | await expect( 222 | pcToken.transferFrom(account, account2, constants.WeiPerEther) 223 | ).to.be.revertedWith("ERR_PCTOKEN_BAD_CALLER"); 224 | }); 225 | it("Should fail when sender does not have enough balance", async () => { 226 | await pcToken.approve(account2, constants.WeiPerEther.add(1)); 227 | pcToken = pcToken.connect(signers[1]); 228 | await expect( 229 | pcToken.transferFrom(account, account2, constants.WeiPerEther.add(1)) 230 | ).to.be.revertedWith("ERR_INSUFFICIENT_BAL"); 231 | }); 232 | it("Should not change approval amount when it was set to max uint256", async () => { 233 | await pcToken.approve(account2, constants.MaxUint256); 234 | pcToken = pcToken.connect(signers[1]); 235 | await pcToken.transferFrom(account, account2, constants.WeiPerEther); 236 | const approvalAmount = await pcToken.allowance(account, account2); 237 | expect(approvalAmount).to.eq(constants.MaxUint256); 238 | }); 239 | }); 240 | }); 241 | -------------------------------------------------------------------------------- /test/reentryProtection.ts: -------------------------------------------------------------------------------- 1 | // This way of importing is a bit funky. We should fix this in the Mock Contracts package 2 | import {ethers} from "@nomiclabs/buidler"; 3 | import {Signer, Wallet, utils, constants} from "ethers"; 4 | import chai from "chai"; 5 | import {deployContract, solidity} from "ethereum-waffle"; 6 | import {TestReentryProtection} from "../typechain/TestReentryProtection"; 7 | import TestReentryArtifact from "../artifacts/TestReentryProtection.json"; 8 | 9 | chai.use(solidity); 10 | const {expect} = chai; 11 | 12 | describe("ReentryProtection", function () { 13 | this.timeout(300000); 14 | let signers: Signer[]; 15 | let account: string; 16 | let testContract: TestReentryProtection; 17 | 18 | beforeEach(async () => { 19 | signers = await ethers.signers(); 20 | account = await signers[0].getAddress(); 21 | 22 | // smartpool = await deployContract(signers[0] as Wallet, PBasicSmartPoolArtifact, [], {gasLimit: 8000000}) as PBasicSmartPool 23 | testContract = (await deployContract( 24 | signers[0] as Wallet, 25 | TestReentryArtifact, 26 | [] 27 | )) as TestReentryProtection; 28 | }); 29 | 30 | it("Should prevent reentry", async () => { 31 | await expect(testContract.test()).to.be.revertedWith( 32 | "ReentryProtection.noReentry: reentry detected" 33 | ); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/safeApproval.ts: -------------------------------------------------------------------------------- 1 | // This way of importing is a bit funky. We should fix this in the Mock Contracts package 2 | import {ethers} from "@nomiclabs/buidler"; 3 | import {Signer, Wallet, utils, constants} from "ethers"; 4 | import chai from "chai"; 5 | import {deployContract, solidity} from "ethereum-waffle"; 6 | import {MockTokenFactory} from "@pie-dao/mock-contracts/dist/typechain/MockTokenFactory"; 7 | import {MockToken} from "@pie-dao/mock-contracts/typechain/MockToken"; 8 | import {TestLibSafeApprove} from "../typechain/TestLibSafeApprove"; 9 | import TestLibSafeApproveArtifact from "../artifacts/TestLibSafeApprove.json"; 10 | 11 | chai.use(solidity); 12 | const {expect} = chai; 13 | 14 | describe("LibSafeApproval", function () { 15 | this.timeout(300000); 16 | let signers: Signer[]; 17 | let account: string; 18 | let testContract: TestLibSafeApprove; 19 | let mockToken: MockToken; 20 | 21 | beforeEach(async () => { 22 | signers = await ethers.signers(); 23 | account = await signers[0].getAddress(); 24 | 25 | mockToken = await (new MockTokenFactory(signers[0])).deploy("MOCK", "MOCK", 18); 26 | 27 | await mockToken.setDoKyberLikeApproval(true); 28 | 29 | testContract = (await deployContract( 30 | signers[0] as Wallet, 31 | TestLibSafeApproveArtifact, 32 | [] 33 | )) as TestLibSafeApprove; 34 | }); 35 | 36 | it("Doing double approvals which are not \"safe\" should fail", async () => { 37 | // no reason 38 | await expect(testContract.doubleApprovalUnsafe(mockToken.address)).to.be.reverted; 39 | }); 40 | 41 | it("Doing double approvals which are \"safe\" should work", async() => { 42 | await testContract.doubleApprovalSafe(mockToken.address); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "commonjs", 5 | "strict": false, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "resolveJsonModule": true, 9 | "strictNullChecks": false, 10 | }, 11 | "include": ["./scripts", "./test", "./mainnet-test"], 12 | "files": [ 13 | "./buidler.config.ts", 14 | "node_modules/@nomiclabs/buidler-ethers/src/type-extensions.d.ts", 15 | "node_modules/@nomiclabs/buidler-etherscan/src/type-extensions.d.ts", 16 | "node_modules/@nomiclabs/buidler-waffle/src/type-extensions.d.ts", 17 | "node_modules/buidler-typechain/src/type-extensions.d.ts", 18 | "node_modules/buidler-deploy/src/type-extensions.d.ts" 19 | ] 20 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "no-console": false 9 | }, 10 | "rulesDirectory": [] 11 | } -------------------------------------------------------------------------------- /utils/TimeTraveler.ts: -------------------------------------------------------------------------------- 1 | import {EthereumProvider} from "@nomiclabs/buidler/types"; 2 | 3 | class TimeTraveler { 4 | private snapshotID: any; 5 | private ethereum: EthereumProvider; 6 | 7 | constructor(ethereum: EthereumProvider) { 8 | this.ethereum = ethereum; 9 | } 10 | 11 | public async snapshot() { 12 | const snapshot = await this.ethereum.send("evm_snapshot", []); 13 | await this.mine_blocks(1); 14 | this.snapshotID = snapshot; 15 | return; 16 | } 17 | 18 | public async revertSnapshot() { 19 | await this.ethereum.send("evm_revert", [this.snapshotID]); 20 | await this.mine_blocks(1); 21 | return; 22 | } 23 | 24 | public async mine_blocks(amount: number) { 25 | for (let i = 0; i < amount; i++) { 26 | await this.ethereum.send("evm_mine", []); 27 | } 28 | } 29 | 30 | public async increaseTime(amount: number) { 31 | await this.ethereum.send("evm_increaseTime", [amount]); 32 | } 33 | 34 | public async setNextBlockTimestamp(timestamp: number) { 35 | await this.ethereum.send("evm_setNextBlockTimestamp", [timestamp]); 36 | } 37 | } 38 | 39 | export default TimeTraveler; 40 | -------------------------------------------------------------------------------- /utils/index.ts: -------------------------------------------------------------------------------- 1 | import ethers from "ethers"; 2 | import balancerFactoryBytecode from "./balancerFactoryBytecode"; 3 | import balancerPoolBytecode from "./balancerPoolBytecode"; 4 | import TimeTraveler from "./TimeTraveler"; 5 | import {DeployOptions, DeployResult} from "@nomiclabs/buidler/types"; 6 | 7 | export const deployBalancerFactory = async (signer: ethers.Signer) => { 8 | const tx = (await signer.sendTransaction({data: balancerFactoryBytecode})) as any; 9 | return tx.creates; 10 | }; 11 | 12 | export const deployBalancerPool = async (signer: ethers.Signer) => { 13 | const tx = (await signer.sendTransaction({data: balancerPoolBytecode, gasLimit: 8000000})) as any; 14 | return tx.creates; 15 | }; 16 | 17 | export const simpleDeploy = async (artifact: any, signer: ethers.Signer) => { 18 | const tx = (await signer.sendTransaction({data: artifact.bytecode})) as any; 19 | await tx.wait(1); 20 | 21 | const contractAddress = tx.creates; 22 | 23 | return contractAddress; 24 | }; 25 | 26 | export const deployAndGetLibObject = async (artifact: any, signer: ethers.Signer) => { 27 | const contractAddress = await simpleDeploy(artifact, signer); 28 | return {name: artifact.contractName, address: contractAddress}; 29 | }; 30 | 31 | export const linkArtifact = (artifact: any, libraries: any[]) => { 32 | for (const library of Object.keys(artifact.linkReferences)) { 33 | // Messy 34 | let libPositions = artifact.linkReferences[library]; 35 | const libName = Object.keys(libPositions)[0]; 36 | libPositions = libPositions[libName]; 37 | 38 | const libContract = libraries.find((lib) => lib.name === libName); 39 | 40 | if (libContract === undefined) { 41 | throw new Error(`${libName} not deployed`); 42 | } 43 | 44 | const libAddress = libContract.address.replace("0x", ""); 45 | 46 | for (const position of libPositions) { 47 | artifact.bytecode = 48 | artifact.bytecode.substr(0, 2 + position.start * 2) + 49 | libAddress + 50 | artifact.bytecode.substr(2 + (position.start + position.length) * 2); 51 | } 52 | } 53 | 54 | return artifact; 55 | }; 56 | 57 | export {TimeTraveler}; 58 | --------------------------------------------------------------------------------